tools
Health Uyari
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 9 GitHub stars
Code Gecti
- Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
Claude Code enforcer and adopter, context engineering, data oriented tools, compile time safe protobuf Java code generation
tools
Library of tooling for various purposes.
Table of Contents
- Claude Code files Maven enforcer
- Code generation
- gRPC example
- Context engineering
- Data
- Claude Code adoption
- Architecture tests (ArchUnit)
- Building
- Releasing
- License
Claude Code files Maven enforcer
The claude-code-enforcer module is a set of custommaven-enforcer-plugin
rules that fail the build when the repository's Claude Code files are
missing or malformed, keeping CLAUDE.md, AGENTS.md, .claude/settings.json,
the sub-agents under .claude/agents, and the skills under .claude/skills
consistent and in their expected shape:
claudeMdFormat(ClaudeMdFormatRule) — checks thatCLAUDE.mdexists
and is non-empty, starts with the# CLAUDE.mdtitle (a leading UTF-8 BOM is
tolerated), referencesAGENTS.md, and contains every required section
heading.agentsMdFormat(AgentsMdFormatRule) — applies the same structural
checks toAGENTS.md: it must start with the# AGENTS.mdtitle and contain
every required section heading.skillFilesExist(SkillFilesExistRule) — checks that every skill
directory under.claude/skillscontains a non-emptySKILL.mdthat opens
with a YAML front matter block declaring every required key (name,descriptionby default). Thenamemust follow the Claude Code naming
convention (lower-case kebab-case, bounded length) and match the skill's
directory name; thedescriptionmust be non-empty and withinmaxDescriptionLength. An optionalallowedFrontMatterKeyswhitelist catches
typos such asdescripton.subAgentFormat(SubAgentFormatRule) — treats every*.mdfile in the
configured agents directory as a sub-agent: it must be non-empty, open with a
YAML front matter block declaring every required key, and carry anamethat
follows the naming convention and matches its file name. An optionalallowedModelswhitelist rejects a mistypedmodelsuch asclaud-opus.commandFormat(CommandFormatRule) — treats every*.mdfile in the
configured commands directory (e.g..claude/commands) as a custom slash
command: it must be non-empty and carry a file name that follows the Claude
Code naming convention, because the command's name comes from its file name.
Front matter is optional, but when present adescriptionmust be non-empty, amodelmust be one ofallowedModelswhen that whitelist is configured, and
an optionalallowedFrontMatterKeyswhitelist catches typos such asargument-hnt.settingsJsonValid(SettingsJsonValidRule) — checks that.claude/settings.jsonexists, is non-empty, and parses as JSON. It can also
assert policy onpermissions.allow:requiredPermissionsmust all be
present andforbiddenPermissionsmust all be absent, so a project can mandate
a permission it relies on or ban an over-broad wildcard such asBash(*).hookCommandsValid(HookCommandsValidRule) — validates thehooks
section of.claude/settings.json: every event must map to an array of groups,
each group must carry ahooksarray, and every hook must declare a non-blanktype(acommandhook also a non-blankcommand). A command that points at
a project-local script through$CLAUDE_PROJECT_DIRis resolved againstprojectDirand must exist on disk, so a renamed or missing hook script is
caught. An optionalallowedEventswhitelist rejects a mistyped event such asSessionSart, andvalidateScriptReferencescan switch the script check off.mcpServersValid(McpServersValidRule) — validates the project's.mcp.json. A project-level MCP file is optional, so an absent file passes;
when present it must be non-empty and parse as JSON, and every entry undermcpServersmust be a JSON object with a well-formed transport. Astdio
server (the default when notypeis declared) needs a non-blankcommand;
ansseorhttpserver needs a non-blankurl. An explicittypeoutside
theallowedTypeswhitelist (stdio,sse,httpby default) is reported,
catching a mistypedhtttp.requiredServersmust all be present andforbiddenServersmust all be absent, so a project can mandate an MCP server
it relies on or ban one it does not want committed.mcpConfigFormat(McpConfigFormatRule) — validates the details of each.mcp.jsonserver entry thatmcpServersValidleaves unchecked:argsmust
be an array of strings,envandheadersmust be objects whose values are
all strings, aurlmust be a syntactically validhttp/httpsURL (andhttpsonly whenrequireHttpsis set), and a server must not mix transports
by declaring both acommandand aurl. LikemcpServersValidit treats an
absent file as a pass.hooksFormat(HooksFormatRule) — validates the hook scripts under a
configuredhooksDir(e.g..claude/hooks): every regular file must be
non-empty, start with a#!shebang (requireShebang), and carry the
executable bit (requireExecutable), and an optionalallowedExtensions
whitelist rejects a stray file. WherehookCommandsValidvalidates the JSON
shape of thehookssection, this rule validates the scripts themselves; when
asettingsFileis configured it also cross-checks the wiring, so a command
hook whose$CLAUDE_PROJECT_DIRpath lands in the hooks directory must point
at a script that exists there, andreportUnreferencedScriptsflags a script
no hook references. An absenthooksDiris a pass because hooks are optional.uniqueDescriptions(UniqueDescriptionsRule) — reads thedescription
from the front matter of every sub-agent (*.md), command (*.md), and skill
(SKILL.md) in the configuredcommandsDir,agentsDir, andskillsDir, and
fails when one description is used by more than one definition, naming every
file that uses it. Because Claude routes by matching intent against these
descriptions, two identical descriptions are ambiguous and one shadows the
other. Comparison ignores case and runs of whitespace; missing or blank
descriptions are left to the format rules. As withuniqueNames, at least one
directory must be configured and uniqueness is checked across all of them.uniqueNames(UniqueNamesRule) — gathers the names of every command,
sub-agent, and skill from the configuredcommandsDir,agentsDir, andskillsDir(a command's and a sub-agent's name is its*.mdfile name, a
skill's name is its directory name) and fails when one name is used more than
once, naming every file or directory that uses it. At least one directory must
be configured, and any directory that is configured must exist. Uniqueness is
checked across all configured directories at once, so a command that clashes
with a skill is caught just like two commands that clash.crossDocConsistency(CrossDocConsistencyRule) — keepsCLAUDE.mdandAGENTS.mdfrom contradicting each other. Each configuredconsistentPattern
is a regular expression with one capturing group; the captured value must
agree between the two files (or be absent from both). For exampleJava (\d+)fails the build if one file saysJava 25and the otherJava 24.readmeConsistency(ReadmeConsistencyRule) — keeps thisREADME.mdfrom
drifting away from the agent docs (AGENTS.md, the single source of truth).
Each configuredconsistentPattern(one capturing group) must capture the same
value in both files, so a documented capability or version cannot silently
disagree with the agent docs. UnlikecrossDocConsistency, a fact the README
simply does not repeat is ignored — the README is a curated, example-heavy view
and may document a subset — so only a value present in both files that disagrees
fails the build.
The claudeMdFormat and agentsMdFormat rules share a MarkdownFormatRule
base class that performs the file-existence, BOM, title, and section checks. It
also exposes optional checks, each disabled by default: forbiddenTokens that
must not appear outside code fences, enforceSectionOrder to require the
sections in the configured order, a maxLineLength cap, andvalidateFileReferences to confirm that Markdown links to local files resolve
to something on disk.
Every rule extends a common ClaudeCodeEnforcerRule base that reports all
violations together and honours a severity option: the default error fails
the build, while <severity>warn</severity> downgrades the same violations to a
logged warning so a team can adopt a rule gradually.
An optional <reportFile> writes the same outcome as a self-contained HTML
report — a single table pairing what failed and why (the header plus one entry
per violation) with the per-rule "How to fix" steps — so a build can surface the
violations in a browser or as a CI artifact. The page is inlined (styles
included, no external assets) so it opens anywhere, and it is written on pass and
fail alike, so a configured report file always reflects the latest run rather
than leaving a stale failure behind:
<reportFile>${project.build.directory}/claude-code-enforcer.html</reportFile>
The front matter rules (skillFilesExist, subAgentFormat, commandFormat)
also accept an autoFix option. When it is enabled and a definition's front
matter is malformed in a way that is safe to repair — a delimiter written with
too many dashes such as ----, or an opening --- whose closing delimiter is
missing — the rule rewrites the file in place and continues against the
corrected content instead of failing the build. The repair is conservative: it
only acts when the document opens with a dashes line enclosing realkey: value entries, so a lone --- thematic break is never mistaken for front
matter. autoFix is off by default.
The rules are wired into the root pom.xml and run at the repository root only.
The check is opt-in via the enforceClaudeMd property, so ordinary builds
are unaffected:
mvn -pl claude-code-enforcer -am install # install the rule jar once
mvn package -DenforceClaudeMd # build with the checks enabled
Code generation
Problem:
Generated builder java code for protobuffers detects missing required fields in runtime.
Solution:
Move detection to compile time (shift-left). For a visual walkthrough of how
the generated interface chain guarantees every required field is set beforebuild() can be called, see
docs/compile-time-safe-builders.md.
Example of the problem:
syntax = "proto2";
package example;
option java_multiple_files = true;
option java_package = "io.github.adamw7.tools.code.protos";
message Person {
optional string name = 1;
required int32 id = 2;
optional string email = 3;
required string department = 4;
}
and the builder that allows building the object without setting the required field "Id":
Person.Builder personBuilder = Person.newBuilder();
personBuilder.setEmail("[email protected]");
personBuilder.setName("Adam");
UninitializedMessageException thrown = assertThrows(UninitializedMessageException.class, personBuilder::build, "Expected build method to throw, but it didn't");
assertEquals("Message missing required fields: id, department", thrown.getMessage());
Solution:
<plugin>
<groupId>io.github.adamw7</groupId>
<artifactId>protogen-maven-plugin</artifactId>
<!-- Use the latest release: https://github.com/adamw7/tools/releases/latest -->
<version>2.4.0</version>
<configuration>
<generatedSourcesDir>${project.basedir}/target/generated-sources/</generatedSourcesDir>
<pkgs>
<param>io.github.adamw7.tools.code.protos</param>
</pkgs>
<outputpackage>io.github.adamw7.tools.code.builders</outputpackage>
</configuration>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>code-generator</goal>
</goals>
</execution>
</executions>
</plugin>
that generates builders detecting missing required fields in compile time (some methods are excluded for simplicity of the example):
interface OptionalIfc {
OptionalIfc setEmail(String email);
OptionalIfc setName(String name);
Person build();
}
interface DepartmentIfc {
OptionalIfc setDepartment(String department);
}
interface IdIfc {
DepartmentIfc setId(int id);
}
class OptionalImpl implements OptionalIfc {
private final Builder builder;
public OptionalImpl(Builder builder) {
this.builder = builder;
}
@Override
public OptionalIfc setEmail(String email) {
builder.setEmail(email);
return this;
}
@Override
public OptionalIfc setName(String name) {
builder.setName(name);
return this;
}
@Override
public Person build() {
return builder.build();
}
}
class DepartmentImpl implements DepartmentIfc {
private final Builder personOrBuilder;
public DepartmentImpl(Builder personOrBuilder) {
this.personOrBuilder = personOrBuilder;
}
@Override
public OptionalIfc setDepartment(String department) {
personOrBuilder.setDepartment(department);
return new OptionalImpl(personOrBuilder);
}
}
public class ExampleTest {
private static class PersonBuilderExample implements IdIfc {
private final Builder personBuilder = Person.newBuilder();
@Override
public DepartmentIfc setId(int id) {
personBuilder.setId(id);
return new DepartmentImpl(personBuilder);
}
}
@Test
public void happyPath() {
PersonBuilderExample builder = new PersonBuilderExample();
Person person = builder.setId(1).setDepartment("dep").setEmail("[email protected]").setName("Adam").build();
assertEquals(1, person.getId());
assertEquals("dep", person.getDepartment());
assertEquals("[email protected]", person.getEmail());
assertEquals("Adam", person.getName());
}
}
Both proto2 and proto3 are supported. In proto2 the generated builder enforces
that every required field is set before build() can be called. proto3 has norequired fields, so there is nothing to enforce there; the builder simply
exposes all fields as optional. Presence-tracking is handled correctly for each
syntax: a hasXxx() accessor is generated only for fields that actually track
presence — every singular field in proto2, but in proto3 only message fields and
those declared with the explicit optional keyword (implicit-presence proto3
scalars, which have no hasXxx(), are left alone).
A oneof group additionally gets a getXxxCase() accessor returning protobuf's
generated XxxCase enum, so you can tell which member is set, plus aclearXxx() that resets the whole group — both reachable through the fluent
builder chain. The synthetic oneofs that back proto3 optional fields are not
treated as groups, so no spurious case accessor is generated for them.
gRPC example
An end-to-end gRPC example combining standard protobuf/gRPC code generation with the compile-time-safe builder generation from this project.
Given greeter.proto:
syntax = "proto2";
message HelloRequest {
required string name = 1;
optional string title = 2;
}
message HelloReply {
required string message = 1;
}
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
Two generators run during the build:
protobuf-maven-plugincompiles the proto definitions into protobuf message classes and gRPC service stubs (GreeterGrpc).protogen-maven-plugin(this repo) generates compile-time-safe builders (HelloRequestBuilder,HelloReplyBuilder) that refuse to callbuild()until everyrequiredfield is set.
The service implementation uses the generated builder:
HelloReply reply = new HelloReplyBuilder().setMessage(greetingFor(request)).build();
Note: All example code (
GreeterServiceImpl,GreeterServer,GreeterClient) lives undersrc/test/javabecause the protogen-generated builders are written totarget/generated-test-sources. Run the example withmvn -pl grpc-example -am test.
See the grpc-example module for full details and how to run the example.
Context engineering
Java code context build up
For gen ai agents that work with Java code the context usually starts with one class but may get wider and be extended to the classes used by it etc so on.
In order to build this tree there is a very simple and fast regex based interface:
public interface Context {
Set<ClassContainer> find(ClassContainer root, int depth);
}
where ClassContainer contains the path of the class and its sources.
The depth param tells the finder how deep we want to go in the tree of usages of the class.
Of course when the depth is growing the tree grows very fast.
The regex-based Finder is the default implementation. Pick the language with
the Language enum (Language.JAVA is the default):
Context context = new Finder(allContainers, Language.JAVA);
Set<ClassContainer> used = context.find(root, depth);
Language.JAVA resolves .java files when mapping a referenced class name back
to its source file, so the usage-tree building works out of the box for Java
sources.
Kotlin code context build up
Kotlin is supported with exactly the same features as Java. The regex-basedFinder and the Context interface are language agnostic; the only difference
is the source-file extension used to resolve a referenced class back to a file.
Pick the language with the Language enum:
Context context = new Finder(allContainers, Language.KOTLIN);
Set<ClassContainer> used = context.find(root, depth);
Language.JAVA (the default) resolves .java files and Language.KOTLIN
resolves .kt files, so the same usage-tree building works for Kotlin sources.
Scala code context build up
Scala is supported with exactly the same features as Java and Kotlin. The
regex-based Finder and the Context interface are language agnostic; the only
difference is the source-file extension used to resolve a referenced class back
to a file. Pick the language with the Language enum:
Context context = new Finder(allContainers, Language.SCALA);
Set<ClassContainer> used = context.find(root, depth);
Language.SCALA resolves .scala files, so the same usage-tree building works
for Scala sources.
Project tree
A single class is rarely enough context — an agent usually needs to see how a
whole project is laid out and how its files relate. ProjectTreeBuilder walks a
Java (or Kotlin or Scala) project directory and produces a tree of its folders, files
and dependencies in one structure:
ProjectTreeNode root = new ProjectTreeBuilder(/* depth */ 1).build(Path.of("my-project"));
System.out.println(new ProjectTreePrinter().print(root));
For a Kotlin project just pass the language; everything else stays the same:
ProjectTreeNode root = new ProjectTreeBuilder(Language.KOTLIN, /* depth */ 1).build(Path.of("my-project"));
System.out.println(new ProjectTreePrinter().print(root));
The building blocks are:
ProjectTreeNode— a node in the tree. Each node is either a directory
(mirroring a project folder and holding child nodes) or a file. File nodes
additionally carry the set of project classes they depend on, so folders,
files and dependencies are all described by the same tree.ProjectTreeBuilder— scans the project directory recursively. Every
folder becomes a directory node and every file a file node (directories are
listed before files, then alphabetically). For each source file (.java,.ktor.scala, depending on the configuredLanguage) it resolves the
project classes it uses with the same regex-basedContext, skipping the
file's own class.depthcontrols how deep the usage search goes, exactly as in theContext
interface above.ContextFactory— decouples the builder from the concrete dependency
finder (defaulting toFinder), so a different resolution strategy can be
plugged in without changing the builder.ProjectTreePrinter— renders the tree as indented text, with each file's
dependencies listed beneath it:
[dir] pkg
[file] A.java
[file] B.java
-> A.java
The result is a compact, human- and LLM-readable view of the project, ready to
be handed to a gen-AI agent as context.
Output formats
The tree can be rendered in several formats behind a singleProjectTreeSerializer interface, so a consumer depends on the abstraction
rather than a concrete format and new formats can be added without touching the
tree:
ProjectTreeSerializer serializer = new ProjectTreeMarkdownSerializer(); // or JSON / printer
String rendered = serializer.serialize(root);
ProjectTreePrinter— indented plain text (shown above).ProjectTreeMarkdownSerializer— a nested Markdown bullet list, with each
file's dependencies as indented child bullets. Well suited to documents and
chat-based agents.ProjectTreeJsonSerializer— structured JSON (name,type,dependencies,children) for programmatic consumers;serializePretty
produces indented JSON.ProjectTreeDotSerializer— a Graphviz DOT
digraph of the dependency edges (file → depended-on class); directories add
structure but are not drawn. Render it with Graphviz tooling.ProjectTreeMermaidSerializer— the same dependency edges as a
Mermaidflowchart, which
renders inline on GitHub, in Markdown viewers and in many gen-AI agent surfaces
without any external tooling.
Token-budget-aware context
A model's context window is finite, so BudgetedContext wraps any Context and
trims its result to fit a token budget. Because Finder returns dependencies in
breadth-first order (closest first), the decorator keeps that priority order and
accepts containers until the next one would exceed the budget:
TokenEstimator estimator = new HeuristicTokenEstimator(); // ~chars/4, no tokenizer dependency
Context budgeted = new BudgetedContext(new Finder(allContainers), estimator, /* token budget */ 8000);
Set<ClassContainer> used = budgeted.find(root, depth);
TokenEstimator— abstracts how a piece of text is costed in tokens.HeuristicTokenEstimator— a fast, dependency-free estimate from character
count (configurable characters-per-token, default4), rounded up so any
non-empty text costs at least one token.BudgetedContext— aContextdecorator that returns the highest-priority
prefix of the dependency graph that fits the budget.
Data
It contains:
- data sources
- support relational data loading
- in memory and iterative loading
- CSV, JDBC support
- Parquet (
InMemoryParquetDataSource,IterableParquetDataSource) — read through an in-process DuckDB engine, exposing the file's columns and rows like any other JDBC-backed source - JSON (
InMemoryJSONDataSource,IterableJSONDataSource) — nested objects are flattened with dotted-path keys (e.g.people[0].address.city) - YAML (
InMemoryYAMLDataSource,IterableYAMLDataSource) — same flattening convention; no document-size limit - TOON (
InMemoryTOONDataSource,IterableTOONDataSource) — a compact, LLM-friendly format that minimises tokens; supports key-value pairs, primitive arrays, tabular arrays, and nested objects - All file-based sources accept either a file path or an
InputStream - GZIP decompression — any file-based source transparently decompresses
.gzfiles; no extra configuration needed
- uniqueness checks tool
- for a given set of data and subset of columns you can ask if these columns are unique (can be used as a key)
- the tool also tries to find a better (smaller) answer
- supports in memory and iterative processing
- data structures
- open addressing hashmap: a simpler alternative to HashMap based only on one array and double hashing, it implements java.util.Map<K, V>
- MCP server
- Model Context Protocol server exposing uniqueness checking as a tool for AI assistants
- Compatible with Claude Desktop, Cline, and other MCP clients
- Transports (select with
--transport.mode):stdio(default) — JSON-RPC over stdin/stdout (Spring Boot, no HTTP server started)streamable-http— the modern HTTP transport served at/mcpstateless-http— the same HTTP transport served at/mcp, but session-less: each JSON-RPC request is answered in isolation, which suits load-balanced or serverless deploymentssse— the legacy HTTP+SSE transport for older clients: the event stream is served at/sseand JSON-RPC messages are POSTed to/mcp/message
- Build:
mvn clean installproducesdata/target/tools.data-<version>.jar - Run:
java -jar data/target/tools.data-<version>.jar --transport.mode=stdio - See MCP Usage Documentation for client configuration (Claude Desktop, Cline) and usage examples
Examples:
in memory check:
AbstractUniqueness check = new InMemoryUniquenessCheck();
check.setDataSource(new InMemorySQLDataSource(connection, query));
Result result = check.exec("COLUMN1", "COLUMN2", "COLUMN3");
log.info(result.isUnique());
Set<Result> betterOptions = result.getBetterOptions();
for (Result betterOption : betterOptions) {
log.info(betterOption);
}
In order to add a new data source for example for XML, JSON, etc you just need to implement this interface:
public interface IterableDataSource extends AutoCloseable, Closeable {
public String[] getColumnNames();
public void open();
public String[] nextRow();
public boolean hasMoreData();
public void reset();
// default method, loads up to batchSize rows in one operation
public List<String[]> nextRows(int batchSize);
}
nextRows(int batchSize) lets callers decide how much data is pulled from the source at once instead of reading row by row. It is a default method built on hasMoreData()/nextRow(), so every source gets it for free; an empty list signals the source is exhausted. The SQL source additionally applies batchSize as the JDBC fetch size so the rows are fetched in a single round-trip.
If you need an in memory source you need to implement one more method:
public interface InMemoryDataSource extends IterableDataSource {
public List<String[]> readAll();
}
Notes:
in memory checks are using in memory sources that load all the data once and run multiple recursive checks to find better options.
Iterative (no memory) checks are keeping only one row at the time so they require very tiny heap size but for the recursive checks need to read the source many times.
Open-addressing map
OpenAddressingMap<K, V> is a java.util.Map implementation that is simpler
than java.util.HashMap because it uses only one array: entries are stored
directly in a single array via open addressing, instead of HashMap's array of
buckets with linked (or tree-ified) nodes. That makes it an allocation-light
alternative when you want a plain map without the per-entry node objects of
separate chaining.
Map<String, Integer> map = new OpenAddressingMap<>(); // default capacity 64
map.put("a", 1);
map.put("b", 2);
map.get("a"); // 1
map.remove("b"); // 2
map.containsKey("b"); // false
How it works:
- Double hashing resolves collisions: a key's probe sequence is
h1 + i * h2(modulo the array length), which spreads probes better than
linear probing and avoids primary clustering.h1/h2are derived from the
key'shashCode()and a prime chosen as the largest prime smaller than the
array length. - Tombstones for removal:
removemarks a slot as removed rather than
clearing it, so probe sequences that ran through that slot still find the
entries placed after it.putreuses the first free slot and a never-used
(null) slot terminates a lookup. - Automatic resizing: when the array is about to fill up, it grows by a
1.2factor and all live entries are re-hashed into the new array (tombstones
are dropped in the process). The initial capacity can be set vianew OpenAddressingMap<>(size)(minimum effective size is 3); a
non-positive size is rejected withIllegalArgumentException.
Caveats:
- Null keys are not supported —
put/getwith anullkey throwIllegalArgumentException. - Null values are not distinguishable from absence:
getreturnsnull
for a missing key andcontainsKeyis defined asget(key) != null, so a key
mapped to anullvalue is reported as absent. Avoid storingnullvalues. - It is not thread-safe; guard external synchronization if shared across
threads.
Open-addressing set
OpenAddressingSet<E> is a java.util.Set backed by an OpenAddressingMap, in
the same way that java.util.HashSet is backed by a java.util.HashMap.
Elements are stored as keys of the underlying map against a shared sentinel
value, so all of the open-addressing behaviour (double hashing, tombstone
removal and automatic resizing) is reused rather than re-implemented.
Set<String> set = new OpenAddressingSet<>(); // default capacity 64
set.add("a"); // true (newly added)
set.add("a"); // false (already present)
set.contains("a"); // true
set.remove("a"); // true
It inherits the map's caveats: null elements are not supported (they are
rejected with IllegalArgumentException) and it is not thread-safe. The
initial capacity can be set via new OpenAddressingSet<>(size).
Primitive int-keyed map
IntKeyOpenAddressingMap<V> is a primitive int-keyed sibling ofOpenAddressingMap. It uses the same double-hashing open-addressing strategy,
but stores keys in an int[] so that lookups and inserts never box the key.
That makes it an allocation-light choice for large, integer-keyed maps where the
autoboxing of a Map<Integer, V> would otherwise dominate.
IntKeyOpenAddressingMap<String> map = new IntKeyOpenAddressingMap<>();
map.put(1, "a");
map.get(1); // "a"
map.getOrDefault(2, ""); // "" (absent)
map.remove(1); // "a"
int[] keys = map.keys(); // live keys, unboxed
It deliberately does not implement java.util.Map, because that interface is
defined in terms of Object keys and would reintroduce the very boxing this
class exists to avoid; instead it mirrors the relevant map operations with
primitive int keys. Unlike OpenAddressingMap, null values are stored
faithfully and reported by containsKey(int) — only get(int) cannot tell a
stored null from an absent key. It is not thread-safe.
Network kill-switch
Switch turns all JVM outbound network access off for the lifetime of the
process. It is useful when you want to guarantee that a data-processing run stays
offline — e.g. no accidental calls out while loading and checking local data.
boolean changed = Switch.off(); // true the first time, false if already off
Switch.off() installs a default ProxySelector that refuses every proxy
selection by throwing UnsupportedOperationException("The network is off"), so
any subsequent attempt to open an outbound connection fails fast. The method is:
- One-way — there is no
on(); once off, the JVM stays offline. Apply it
early, only when you really mean to seal the process. - Idempotent and thread-safe — it is
synchronizedand guarded by avolatileflag; calling it again is a no-op that logs a warning and returnsfalse.
The switch is wired into the data module's unit-test run for exactly this
reason: a NetworkOffExtension (a JUnit BeforeAllCallback discovered throughMETA-INF/services and JUnit's extension auto-detection) calls Switch.off()
before any unit test runs, so a unit test can never open an outbound connection.
Auto-detection and the tools.test.network.off guard property are set only on
surefire, so the failsafe integration tests (*IT), which need real network, keep
it. See Testing in AGENTS.md for the details.
A single test can also opt in explicitly with the @NetworkOff annotation, which
registers the same extension via @ExtendWith:
@NetworkOff
class MyDataSourceTest {
// every test here runs with the network off
}
Unlike the module-wide auto-detection, an explicit @NetworkOff engages the
kill-switch unconditionally — regardless of the tools.test.network.off
property — so the network is off even when the test is run on its own from an IDE.
Claude Code adoption
The adopt module (tools.adopt) is an ordered pipeline that adopts Claude
Code into a GitHub repository. Given a repository URL it clones the repo,
creates a feature branch, runs the Claude Code CLI (claude -p /init) to
generate a CLAUDE.md and commits it, then wires a CLAUDE.md guard into the
project's build and commits that too — so the freshly generated CLAUDE.md keeps
being validated on every build. The guard is build-tool aware: a Maven project
gets the full claude-code-enforcer rule in
its pom.xml, while a Gradle project (Groovy build.gradle or Kotlinbuild.gradle.kts) gets a presence-and-non-empty guard task appended to the
build script — Gradle has no enforcer-rule equivalent. A repository with no
recognised build file falls back to a build-tool-agnostic GitHub Actions
workflow that runs a portable presence-and-non-empty check script on every push
and pull request, so even a build-less repository keeps the guard. Finally it
pushes the
branch and opens a pull request with the GitHub CLI, so the change is reviewed
rather than landing straight on the default branch, which is never written to.
Run it from the command line with a GitHub repository URL, an optional workspace
directory to clone into (a temporary directory is created when it is omitted),
and an optional feature-branch name (defaults to claude/adopt-claude-code).
Because it opens the pull request through the GitHub CLI, an authenticated gh
must be on the PATH alongside git and claude. Launch it through exec:java
so Maven puts the full runtime classpath (log4j2 and the rest) on the command — a
bare java -cp adopt/target/classes omits the dependency jars and fails at
start-up with a NoClassDefFoundError for the log4j LogManager:
mvn -pl adopt exec:java \
-Dexec.args="https://github.com/owner/repo.git [workspace-directory] [branch-name]"
The pipeline is a list of ordered, independent AdoptionSteps, each acting on a
shared immutable AdoptionContext (the repository URL, the workspace, the
derived checkout directory, and the feature-branch name):
CommandRunner runner = new ProcessCommandRunner();
GitHubRepoAdopter.withDefaultPipeline(runner)
.adopt(new AdoptionContext("https://github.com/owner/repo.git", workspace));
The default pipeline runs these steps in order:
ToolchainStep— probes the external tools the pipeline shells out to
(git,claude,gh) with a--versioncheck before any real work, so a
missing tool aborts the adoption immediately with a message naming every
absent one instead of failing minutes later after a clone, aclaude init,
and a Maven build have already run.CloneStep— clones the target repository into the workspace withgit clone, giving the remaining steps a working checkout.BranchStep— creates and checks out the adoption feature branch withgit checkout -B, so every later commit lands on that branch instead of the
default branch.TrustStep— marks the checkout trusted in~/.claude.jsonso the
headlessclauderun is not blocked by the interactive folder-trust prompt.ClaudeInitStep— runs the Claude Code CLI in headless mode
(claude -p /initby default; the invocation is configurable because the
flags differ between environments) so it generates aCLAUDE.md, aborting if
the file did not appear.CommitStep— commits the generatedCLAUDE.md(Adopt Claude Code: add CLAUDE.md).EnforcerStep— detects the checkout's build system and wires theCLAUDE.mdguard into it. A Maven project has theclaude-code-enforceradded
to its rootpom.xmlviaPomEnforcerInstaller(the edit is done on the JDK's
DOM — no third-party XML library — is namespace-aware, and is idempotent); a
Gradle project has aenforceClaudeMdguard task appended to itsbuild.gradle/build.gradle.ktsviaGradleGuardInstaller, wired intocheck. A repository with no recognised build file falls back to aFallbackBuildSystemthat installs a GitHub Actions workflow and the portable.github/claude-md-guard.shcheck it runs viaWorkflowGuardInstaller. All
installs are idempotent. Supporting a new build tool is a matter of adding aBuildSystemimplementation rather than branching inside the step.CommitStep— commits the build change (Add claude-code-enforcer to the build).VerifyStep— runs the detected build system's verification (a
non-recursivemvn -N validatefor Maven, theenforceClaudeMdtask for
Gradle, the.github/claude-md-guard.shscript for the fallback) so the
freshly wired guard actually executes against the generatedCLAUDE.md,
failing the adoption locally if the file is missing or malformed rather than
after the pull request lands.PushStep— pushes the feature branch to origin and sets its upstream
(git push -u origin <branch>).PullRequestStep— opens a pull request from the branch with
gh pr create, targeting the repository's default branch as the base. The
pull request metadata is supplied throughPullRequestOptions— title, body,
and optional reviewers, labels, and assignees to request, plus whether to open
the pull request as a--draft— so the defaults can be overridden per
project. LikeCommitStepit stays idempotent: aghfailure that only
reports an already-open pull request for the branch, or no commits between base
and head, is treated as a no-op rather than aborting the adoption.
External git/claude/gh invocations go through a CommandRunner abstraction,
so the steps are unit-tested without spawning real processes. The defaultProcessCommandRunner merges standard error into standard output for a single
ordered transcript, and bounds every command with a configurable timeout
(10 minutes by default) so a stalled clone or a stuck claude run cannot hang
the adoption — on expiry the child process is destroyed and the failure is
reported with whatever output was captured so far. A step whose command exits
non-zero aborts the pipeline with an AdoptionException carrying the command
transcript.
Architecture tests (ArchUnit)
Every production module guards its own package structure with
ArchUnit rules that run as ordinary JUnit 5 tests,
so an accidental dependency or a broken naming convention fails the build
instead of quietly eroding the design. Each module keeps its rules in a single*ArchitectureTest under an architecture package, annotated with@AnalyzeClasses(..., importOptions = ImportOption.DoNotIncludeTests.class) so
that only production classes are analysed. The ArchUnit dependency
(com.tngtech.archunit:archunit-junit5) is managed centrally in the rootpom.xml, and the tests run as part of the normal mvn install.
A set of conventions is shared across modules:
- No cycles between packages —
slices().matching(...).should().beFreeOfCycles()
keeps the package graph acyclic in each module. - Loggers are constants — every
org.apache.logging.log4j.Loggerfield must
beprivate static final(the context module relaxes this toprivate final),
because a logger is a shared, immutable, class-scoped collaborator. *Exceptiontypes really are exceptions — any class whose simple name ends
withExceptionmust be assignable tojava.lang.Exception.Abstract-prefixed names — a top-level abstract class must have a simple
name starting withAbstract, so that a type meant to be extended is obvious
at a glance.- Logging goes through log4j2, not the console or the JDK — ArchUnit's
GeneralCodingRulesforbid access toSystem.out/System.err, throwing
generic exceptions, and usingjava.util.logging; libraries additionally must
never callSystem.exit. Thedatamodule tightens this further, also
rejecting the JDK's ownSystem.Loggerso all logging stays on log4j2. - No raw stack traces — no class may call any
Throwable.printStackTrace
overload, because a failure must be reported through log4j2 rather than dumped
to the console. - Public fields are immutable — every
publicfield must befinal, so a
field that is part of a type's API surface cannot be reassigned from outside. - No legacy date library —
GeneralCodingRulesforbid a dependency on
Joda-Time, keeping date/time handling on thejava.timeAPI.
On top of that baseline, each module pins the boundaries specific to its own
design:
data— data-source contracts (source.interfaces) must stay interfaces
and must not know their concretesource.db/source.fileimplementations;structure.internalis accessible only fromstructure; the reusablestructurecollections must not couple to data sources; every concrete*DataSourcemust implementIterableDataSource; the uniqueness core must not
depend on itsmcpadapter; and alayeredArchitecturepins the source layers
so file and DB sources depend only downwards on their contracts (files may also
use compression), never on each other. Two rules keep the open-addressing
structures honest about their documented lack of thread-safety: no method instructuremay besynchronized, andstructuremay not depend onjava.util.concurrent, so neither can silently suggest the collections are safe
to share. Two more pin thenetworkkill-switch to its documented shape:Switch.off()must besynchronizedand itsisOffflag must bevolatile.code/context— the finder/tree core must not depend on themcp
delivery package, and only thatmcppackage may build on the shared MCP
scaffolding; every concrete*Serializermust honour theProjectTreeSerializercontract.code/protogen-maven-plugin— the reusableformatpackage must not
depend on thegencode generator that builds on it, and every concrete*Mojomust implement the MavenMojocontract.mcp-common— theMcpToolSPI must stay an interface, every concrete*Toolmust implement it, and the shared scaffolding must never callSystem.exit.claude-code-enforcer— alayeredArchitecturepins the module's layers
(textis the foundation,rulebuilds on it, and the feature packagesdefinition/doc/mcp/settingsbuild onrulewithout reaching sideways
into one another), and every concrete*Rulemust extend the sharedClaudeCodeEnforcerRulebase.adopt— thecommandrunner layer must not depend on thesteppackage, so the reusable command abstraction stays unaware of the
adoption steps that build on it; and every concrete*Stepinstepmust
implement theAdoptionStepcontract. The pipeline also carries the shared
baseline in full, including that it reports failure by throwingAdoptionExceptionand never callsSystem.exit.
Alongside the production rules, each module carries a companionTestConventionsArchitectureTest that analyses only the test classes (viaImportOption.OnlyIncludeTests) and pins conventions on the tests themselves:
every @Testable method must live in a *Test or *IT class so surefire or
failsafe actually runs it, no test is @Disabled, tests use JUnit 5 only (no
JUnit 4 org.junit API), and no test calls Thread.sleep or TimeUnit.sleep
(sleeping is slow and flaky — wait on a condition instead). A few rules guard
against tests that silently never run: a @Testable method must not beprivate or static (JUnit 5 quietly ignores both), and a @BeforeAll/@AfterAll method must be static (JUnit 5 requires it unless the class opts
into the PER_CLASS lifecycle).
Run them for a single module with, for example:
mvn -pl data test
or across the whole repository as part of mvn install.
Building
mvn clean install
The clean part is needed since the build contain code generation so if you remove a source of generation and do not use clean then the result of previous build may remain in target. If you do not remove anything you could benefit from faster:
mvn install
Releasing
In order to release a new version - X you need to:
- Change the revision property to X in root pom.xml
- Commit and push
- Check if all builds pass
- Release and mark as latest in GitHub
License
This project is licensed under the MIT License.
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi