Skip to content
Open
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
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ as a design choice rather than a default optimization.
It also tells the agent to check the project Java version first. The right stream code for Java 8
may be different from the right code for Java 17, Java 21, or Java 24.

For general lambda, method-reference, identity-function, no-op functional stage, supplier-laziness,
and callback readability guidance, install the companion package
`martinfrancois/java-functional-style` together with this stream package.

## Contents

- [Getting Started](#getting-started)
Expand Down Expand Up @@ -234,6 +238,21 @@ Good fit:
- choosing Java-version-compatible APIs such as `takeWhile`, `mapMulti`, `Stream.toList()`, and
gatherers.

## Ownership Boundaries

`java-streams` owns stream and collector semantics: terminal operation choice, collector choice,
duplicate key and null behavior, encounter order, primitive streams, stream Java-version
compatibility, parallel stream behavior, gatherers, and stream-specific behavior preservation.

`java-functional-style` owns general Java lambda and functional-interface style: identity
functions, no-op functional stages, method references, callback readability, supplier laziness, and
callback side-effect boundaries.

`java-optionals` owns Optional semantics.

Install `java-streams` and `java-functional-style` together for stream cleanup involving non-trivial
callbacks.

Poor fit:

- broad Java style enforcement unrelated to streams or collectors;
Expand Down
42 changes: 42 additions & 0 deletions docs/agents/ownership-boundaries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Ownership Boundaries

`java-streams` owns stream and collector semantics:

- terminal operation choice
- collector choice
- duplicate key and null behavior
- encounter order
- primitive streams
- stream Java-version compatibility
- `findFirst` versus `findAny`
- `parallelStream`
- `Gatherers.mapConcurrent`
- stream-specific behavior preservation

`java-functional-style` owns general Java lambda and functional-interface style:

- method references
- identity functions
- no-op functional stages
- callback readability and helper extraction
- supplier laziness
- callback side-effect boundaries

`java-optionals` owns Optional semantics.

The expected high-quality setup for stream cleanup involving non-trivial callbacks is both
`java-streams` and `java-functional-style`.

Do not copy all generic lambda guidance back into `java-streams` to fix composition regressions.
Add only the smallest stream-side bridge instruction if hosted evidence proves it is required.

## Composition Gate

Before opening a PR for this split, existing stream evals must prove:

```text
current java-streams behavior <= slimmed java-streams + java-functional-style behavior
```

The comparison must use existing evals unchanged and criterion-level results must be equal or
better. Local validation alone is not enough.
7 changes: 4 additions & 3 deletions docs/agents/skill-behavior.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ guidance, or auto-selection wording.
that name.
- The skill should not force streams over clear stateful loops. Stateful sequence output, checked IO,
prompts, mutation-heavy code, or complex early exits can remain imperative.
- Keep stream lambdas as short glue. Prefer method references or one-expression lambdas whose body
stays on the same line as `->`, and extract named helpers for branching, loops, temporary
variables, formatting, merge rules, or nested stream chains that would continue on later lines.
- Generic lambda, method-reference, identity-function, no-op functional stage, supplier-laziness,
and callback readability guidance belongs to `java-functional-style`. Keep only stream and
collector semantics canonical here.
- Runtime guidance should keep internal workflow language out of ordinary user-facing reviews. Avoid
terms such as "hard stop", "marker", "scan", "checklist", and skill names unless the user asked
for an explicit skill workflow, audit, or scan command.
Expand All @@ -52,3 +52,4 @@ guidance, or auto-selection wording.

- [README Guidance](readme.md)
- [Eval Guidance](evals.md)
- [Ownership Boundaries](ownership-boundaries.md)
116 changes: 58 additions & 58 deletions skills/java-streams/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
---
name: java-streams
license: MIT
description: Review Java stream performance advice, especially slow stream mappings, external collection mutation with forEach/add, and whether parallelStream is safe; clean up mutation and write or refactor Java Stream and Collector code. Avoid common stream antipatterns such as materializing just to inspect, sorting before min/max, counting for existence, nested stream collections, unsafe null sorting, multi-line lambdas, and careless findFirst/findAny changes. Use whenever writing, reviewing, or refactoring Java code that uses Java streams, collectors, stream pipelines, grouping, joining strings, first/any element lookup, sorting, limiting, distinct values, primitive totals, Optional values in streams, or parallel streams, including review prompts asking whether a lookup should use findFirst or findAny.
description: Review Java stream performance advice, especially slow stream mappings, external collection mutation with forEach/add, and whether parallelStream is safe; clean up mutation and write or refactor Java Stream and Collector code. Avoid common stream antipatterns such as materializing just to inspect, sorting before min/max, counting for existence, nested stream collections, unsafe null sorting, and careless findFirst/findAny changes. Use whenever writing, reviewing, or refactoring Java code that uses Java streams, collectors, stream pipelines, grouping, joining strings, first/any element lookup, sorting, limiting, distinct values, primitive totals, Optional values in streams, or parallel streams, including review prompts asking whether a lookup should use findFirst or findAny.
---

# Java Streams Skill

Preserve requested behavior, public API/artifact shape, encounter order, exceptions, null handling,
side effects, mutability, and Java-version compatibility. For implementation prompts, write the
requested Java source file before explaining. Keep provided helper/record/service types in that file,
and keep them nested when requested; do not create sibling source files, package-private top-level
replacements, hooks, test seams, delegate fields, overloads, caches, retries, or adapters unless
asked.
side effects, mutability, and Java-version compatibility. For implementation prompts, write only the
requested source; no extra public API, Javadoc, null guards, constructors, or utility ceremony unless
asked. Keep provided helper/record/service types in the requested file and nested when requested.

## Reference Bundle

Expand All @@ -27,62 +25,59 @@ When the prompt asks for a named artifact such as `review.md` or a Java source f
exact file. Do not answer only in chat when a file artifact is requested.

0. Check the Java baseline first. Use [java-stream-api.md](references/java-stream-api.md) for
minimum versions and fallbacks; do not emulate unavailable APIs with stateful or multi-line
lambdas.
minimum versions and fallbacks.
1. Identify the requested result and pick the matching terminal or collector:

| Goal | Preferred API |
|---|---|
| Arbitrary/equivalent match | `filter(...).findAny()` |
| First encounter-order match | `filter(...).findFirst()` |
| Existence check | `anyMatch` / `noneMatch` / `allMatch` |
| Transformed list/set | `map`/`filter` then collect |
| Concatenated text | `Collectors.joining` |
| Numeric primitive result | `mapToInt`/`mapToLong`/`mapToDouble` terminals |
| Two aggregates over same input (Java 12+) | `Collectors.teeing` |
| Grouping/indexing | `groupingBy`, `partitioningBy`, or `toMap` with merge/null handling |

Find-first rule: keep `findFirst()` when code takes element `0`, sorted order, or encounter order
chooses the winner. In these reviews, include the exception before performance claims: `findAny`
fits only if all matching values are equivalent and order does not choose the winner. Keep
`sorted(...).filter(...).findFirst()` when it defines the selected value; suggest `min`/`max` only
when it preserves that winner.
Use `findAny()` only when all matches are equivalent and order does not choose the winner; in
reviews, name that exception with the code's noun, e.g. "all matching primary addresses are
equivalent." Use `findFirst()` when element `0`, sorted order, encounter order, or priority
selects the value. Do not offer `min`/`max` unless the user asks for optimization.

2. Use intent-encoding terminals: `anyMatch`, `count`, `joining`, `min`/`max`, Java 12+
`teeing`, and primitive terminals. Do not mutate external containers, arrays, counters, or
builders from `forEach`; let the stream produce the result directly.

- Implementation prompts: for one-to-one transformations, write the direct stream result unless
mutable output or the Java baseline says otherwise; on Java 16+, prefer
- Direct transforms: write the direct stream result; on Java 16+, prefer
`names.stream().map(String::toUpperCase).toList()` over a manual `ArrayList` loop.
- External mutation/performance reviews: show a sequential result-producing snippet first. For
million-item CPU maps, mention benchmarking a pure parallel variant and include:
"`parallelStream()` can be slower for small lists or call paths that are usually small."
- Parallel reviews: apply [hard-stops.md](references/hard-stops.md); no custom pool snippets
unless asked.
- Java 24 bounded blocking calls: use `Gatherers.mapConcurrent(limit, item -> carrier(item,
stub(item)))`, then filter/map/sort; no test hooks, overloads, `CompletableFuture` fan-out, or
null sentinels.

```java
import java.util.List;

final class OrderChecks {
boolean hasOverdue(List<Order> orders) {
return orders.stream().anyMatch(Order::isOverdue);
}

record Order(boolean overdue) {
boolean isOverdue() {
return overdue;
}
}
}
```
- Performance/parallel reviews: for external mutation, show a sequential result-producing snippet
first. Explicitly separate the correctness fix from the performance decision: collecting
directly removes external mutation; it is not a guaranteed throughput win. Do not cite resize
overhead or list throughput as why `forEach(add)` is wrong. If discussing parallel work, mention
measurement, common-pool/split overhead, and slower small-list or mostly-small call paths.
- Java 24 bounded blocking calls: use `Gatherers.mapConcurrent` as documented in
[java-stream-api.md](references/java-stream-api.md); no test hooks, overloads,
`CompletableFuture` fan-out, or null sentinels. Call the provided production stub directly and
carry `element + boolean result` through a non-null holder:

```java
records.stream()
.gather(Gatherers.mapConcurrent(
limit,
record -> new Checked<>(record, RemoteService.accepts(record.id()))))
.filter(Checked::accepted)
.map(Checked::value)
.toList();

record Checked<T>(T value, boolean accepted) {}
```
3. Flatten nested sources deliberately. Use `flatMap`, `flatMap(Optional::stream)` on Java 9+,
and `mapMulti` on Java 16+ when clearer. Use helpers when nested lambdas would wrap. For subtype
primitives, filter/cast first, then call `mapToInt`/`mapToLong`/`mapToDouble` directly. For
nested collector callbacks, extract a named `Stream<T>` helper.
and `mapMulti` on Java 16+ when clearer. For subtype primitives, filter/cast first, then call
`mapToInt`/`mapToLong`/`mapToDouble` directly.
Extract nested `flatMap` callbacks recursively: use `.flatMap(Type::childEntries)`, keep helpers
stream-based rather than temp-list loops, and repeat inside helpers until callbacks no longer
continue stream chains across lines. Apply the same shape to nested `anyMatch` chains and
downstream `Collectors.flatMapping`: use named stream-returning helpers or method references, not
lambdas whose bodies continue a nested stream chain on later lines. See
[stream-examples.md](references/stream-examples.md) for final-shape helper patterns.

```java
// Java 9+: flatten Optional values instead of filter(Optional::isPresent).map(Optional::get)
Expand All @@ -91,22 +86,27 @@ exact file. Do not answer only in chat when a file artifact is requested.
4. Choose accumulation/collectors by result semantics: use `reduce(identity, op)` for immutable
non-primitives, `toMap` with merge behavior and deliberate null-key/value handling, non-null keys
for `groupingBy`, `partitioningBy` for boolean splits, and flattened nested indexes when clearer.
Extract duplicate-key merge rules into named helpers when ties, nulls, or ordering need more than
a same-line expression. Carry `element + result`, never null sentinels.
5. Preserve ordering, mutability, short-circuit behavior, and lambda readability. Keep stream lambdas
as short glue or method references; use named helpers for branching, merge logic, or nested stream
work.
Preserve duplicate-key merge rules, tie handling, null contracts, and map suppliers. Carry
`element + result`, never null sentinels. Extract collector merge helpers when duplicate-key
resolution needs branching, tie-breaking, or more than one comparison; do not hide those rules in
multi-line ternaries or block merge lambdas.
5. Preserve ordering, mutability, short-circuit behavior, and stream/collector semantics.
6. Keep imperative code when it is the clearer boundary for stateful output, checked IO,
mutation-heavy logic, or complex early exits.
7. Verify changed branches for empty inputs, one element, duplicates, nulls, ordering,
parallel-safety, and baseline compatibility. Run the marker scan from
[hard-stops.md](references/hard-stops.md), fix hits, and re-scan. In scan audits, keep
hard-stop severities: required hits stay required unless explicitly acceptable.

Review output:

- Give a direct behavior-preserving decision plus one safe snippet.
- Create `review.md` when requested, even for rejection-only reviews.
- Explain code behavior, not internal workflow.
- Avoid internal workflow labels such as "per the skill", "hard stop", "marker", "scan",
"checklist", "rubric", or "criteria" unless the user asks about the workflow itself.
hard-stop severities: required hits stay required unless explicitly acceptable; also name
acceptable non-primitive reductions such as `BigDecimal.reduce(...)` when present.

Generic lambda and callback-style guidance belongs to the companion package
`martinfrancois/java-functional-style`. Install both packages when stream cleanup depends on callback
style. When both are available, keep stream callbacks as glue: extract nested callback bodies and
filters with more than one domain condition to named helpers.

Review output: give a direct behavior-preserving decision plus one safe snippet. For `review.md`,
write short prose first; avoid tables, extra snippets, style sections, redundant-output sections, and
internal workflow labels unless asked. For ordered lookup rejections, include: "Keep the existing
`sorted(...).filter(...).findFirst()` chain." When rejecting `parallelStream().findAny()`, say
parallelism makes ordered selection less predictable or more expensive here and no CPU-bound work or
measurement justifies the overhead.
Loading