|
1 | | -# hosh project guidance |
| 1 | +# hosh — Claude guidance |
| 2 | + |
| 3 | +Human Oriented SHell: experimental Java shell, typed records, virtual-thread pipelines. Multi-module Maven project (JDK 25). |
| 4 | + |
| 5 | +Website: <https://hosh-shell.github.io> |
| 6 | + |
| 7 | +## Build |
| 8 | + |
| 9 | +```bash |
| 10 | +./mvnw clean verify # full build (unit + integration + fitness + acceptance) |
| 11 | +./mvnw -Pskip-slow-tests clean verify # ~2× faster, skip slow tests |
| 12 | +./mvnw test-compile org.pitest:pitest-maven:mutationCoverage # mutation testing |
| 13 | +./mvnw clean verify sonar:sonar -Psonar -Dsonar.token=MYTOKEN # sonar analysis |
| 14 | +``` |
| 15 | + |
| 16 | +Run: `java -jar main/target/hosh.jar` |
| 17 | + |
| 18 | +Debug: |
| 19 | + |
| 20 | +```bash |
| 21 | +java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=1044 -jar main/target/hosh.jar |
| 22 | +``` |
| 23 | + |
| 24 | +## Logging |
| 25 | + |
| 26 | +Hosh uses `java.util.logging`, disabled by default. |
| 27 | + |
| 28 | +```bash |
| 29 | +HOSH_LOG_LEVEL=FINE java -jar main/target/hosh.jar |
| 30 | +``` |
| 31 | + |
| 32 | +Log events written to `$HOME/.hosh.log`. |
| 33 | + |
| 34 | +## Module map |
| 35 | + |
| 36 | +| Module | Purpose | |
| 37 | +|---|---| |
| 38 | +| `spi/` | Core abstractions: `Command`, `Channel`, `Record`, `ExitStatus`, `Value` subtypes | |
| 39 | +| `spi-test-support/` | Test helpers for SPI | |
| 40 | +| `test-support/` | JUnit 5 utilities | |
| 41 | +| `runtime/` | Shell engine: `Interpreter`, `Supervisor`, ANTLR4 `Compiler`/`Parser` | |
| 42 | +| `modules/system/` | echo, sleep, withTimeout, benchmark, … | |
| 43 | +| `modules/filesystem/` | ls, cd, walk, cp, mv, rm, find, … | |
| 44 | +| `modules/text/` | grep, sort, count, split, join, trim, regex, … | |
| 45 | +| `modules/network/` | http, resolve, … | |
| 46 | +| `modules/terminal/` | clear, … | |
| 47 | +| `modules/history/` | history | |
| 48 | +| `main/` | Entry point `Hosh.java`, produces uberjar | |
| 49 | + |
| 50 | +Root package: `hosh`. Key sub-packages: `hosh.spi`, `hosh.runtime`, `hosh.runtime.prompt`, `hosh.runtime.completion`, `hosh.modules.*`. |
| 51 | + |
| 52 | +## Key classes |
| 53 | + |
| 54 | +| Concern | Class | Location | |
| 55 | +|---|---|---| |
| 56 | +| Entry point | `Hosh` | `main/` | |
| 57 | +| Parsing | `Compiler`, `Parser` | `runtime/` | |
| 58 | +| ANTLR4 grammar | `Hosh.g4` | `runtime/src/main/antlr4/` | |
| 59 | +| Pipeline execution | `Supervisor` | `runtime/` | |
| 60 | +| Inter-stage data | `PipelineChannel` | `runtime/` | |
| 61 | +| Core record type | `Record`, `Records` | `spi/` | |
| 62 | +| Typed values | `Value`, `Keys` | `spi/` | |
| 63 | +| Command interface | `Command` | `spi/` | |
| 64 | +| Command arguments | `CommandArguments`, `CommandArgument` | `spi/` | |
| 65 | +| Channels | `InputChannel`, `OutputChannel` | `spi/` | |
| 66 | +| Module registration | `Module` | `spi/` | |
| 67 | + |
| 68 | +## Architecture constraints |
| 69 | + |
| 70 | +- Commands communicate via typed `Record`s — never raw strings across subsystem boundaries. |
| 71 | +- Pipeline stages run concurrently on virtual threads (`Executors.newVirtualThreadPerTaskExecutor()`). |
| 72 | +- Inter-stage data flows through `PipelineChannel` backed by `LinkedTransferQueue`. |
| 73 | +- Strict error handling by default — equivalent to `set -euo pipefail` in bash. |
| 74 | +- Modules depend only on `spi/` — never on `runtime/` internals (enforced by `module-info.java`). |
| 75 | +- Uses Java Platform Module System (`module-info.java` in every module). |
| 76 | +- To add a command: create class implementing `Command`, register in the module's `Module` impl. |
| 77 | +- Dependency injection happens in `Interpreter` via awareness interfaces (`StateAware`, `StateMutatorAware`, `LineReaderAware`, `TerminalAware`, `HistoryAware`, `VersionAware`). |
| 78 | + |
| 79 | +## CommandArguments |
| 80 | + |
| 81 | +Always use `CommandArguments` — never raw `List<String>`. |
| 82 | + |
| 83 | +```java |
| 84 | +@Override |
| 85 | +public ExitStatus run(CommandArguments args, InputChannel in, OutputChannel out, OutputChannel err, State state) { |
| 86 | + if (args.size() != 1) { |
| 87 | + err.send(Records.singleton(Keys.ERROR, Values.ofText("usage: mycommand <arg>"))); |
| 88 | + return ExitStatus.error(); |
| 89 | + } |
| 90 | + String value = args.get(0).asString(); |
| 91 | + // ... |
| 92 | +} |
| 93 | +``` |
| 94 | + |
| 95 | +### CommandArguments methods |
| 96 | + |
| 97 | +| Method | Description | |
| 98 | +|---|---| |
| 99 | +| `CommandArguments.of(String... values)` | Factory for tests and internal use | |
| 100 | +| `isEmpty()` | True if no arguments | |
| 101 | +| `size()` | Number of arguments | |
| 102 | +| `get(int index)` | Argument at index; programming error if out of bounds | |
| 103 | +| `stream()` | Stream over arguments | |
| 104 | + |
| 105 | +### CommandArgument accessors |
| 106 | + |
| 107 | +| Method | Return type | Description | |
| 108 | +|---|---|---| |
| 109 | +| `asString()` | `String` | Raw string value | |
| 110 | +| `asKey()` | `Key` | Converts via `Keys.of(value)` | |
| 111 | +| `asLong()` | `OptionalLong` | Parses as `long`; empty if invalid | |
| 112 | +| `asInt()` | `OptionalInt` | Parses as `int`; empty if invalid | |
| 113 | +| `asDuration()` | `Optional<Duration>` | Parses ISO-8601 duration; empty if invalid | |
| 114 | +| `asPath(State state)` | `Path` | Resolves relative to `state.getCwd()`; absolute, normalized | |
| 115 | + |
| 116 | +## Code style |
| 117 | + |
| 118 | +- Tabs, not spaces (Java and XML). Checkstyle will fail otherwise. |
| 119 | +- Checkstyle enforced for all files under `src/main` and `src/test` (`checkstyle.xml` at root). |
| 120 | +- Java 25. No Kotlin, no Gradle. |
| 121 | +- Zero SonarQube bugs/smells policy. |
| 122 | +- No `sun.misc.Unsafe` or internal JDK APIs. |
| 123 | +- Prefer explicit over clever. Fail fast on unhandled cases. |
| 124 | +- Domain primitives: use `Key`, `Value`, `ExitStatus`, `VariableName`, `CommandName` — never pass raw `String`/primitives across subsystem boundaries. |
2 | 125 |
|
3 | 126 | ## Testing |
4 | 127 |
|
5 | | -### Property-Based Testing (PBT) |
| 128 | +- All features covered by unit tests. Always check the happy path at minimum. |
| 129 | +- JUnit 5 + Mockito (BDDMockito) + AssertJ. |
| 130 | +- Every test has `// Given` / `// When` / `// Then` sections. |
| 131 | +- Class under test is always named `sut`. |
| 132 | +- Use `BDDMockito` exclusively: `given(mock.method()).willReturn(value)`. Never the reverse form. Only static-import `given` and `then` — not `willReturn`/`willThrow`. |
| 133 | +- Prefer `@ParameterizedTest` over copy-pasting tests. Use `@ValueSource` when possible; `@ArgumentsSource` when more structure needed (test case must have a name). |
| 134 | +- Acceptance tests run the built jar end-to-end with hosh scripts. |
| 135 | +- Mutation testing via PIT (`pitest-maven`). |
6 | 136 |
|
7 | | -This project uses [jqwik](https://jqwik.net/) for property-based tests. |
| 137 | +## Property-Based Testing (jqwik) |
8 | 138 |
|
9 | | -**Known issue:** jqwik 1.9.3 targets JUnit Platform 1.x but the project uses JUnit 6 (Platform 6.x). `@Property` tests compile and are structurally correct but the jqwik engine does not execute them at runtime. Track https://github.com/jqwik-team/jqwik/issues for jqwik 2.x release targeting JUnit 6. |
| 139 | +**Known issue:** jqwik 1.9.3 targets JUnit Platform 1.x; project uses JUnit 6 (Platform 6.x). `@Property` tests compile and are structurally correct but the jqwik engine does not execute them at runtime. Track https://github.com/jqwik-team/jqwik/issues for jqwik 2.x. |
10 | 140 |
|
11 | | -**Writing property tests:** use `@Property` + `@ForAll` for parameters, `@Provide` for custom arbitraries, `Assume.that(...)` for preconditions. Follow the existing patterns in `ValuesTest.java` (spi module). |
| 141 | +Write property tests: `@Property` + `@ForAll` for parameters, `@Provide` for custom arbitraries, `Assume.that(...)` for preconditions. See `ValuesTest.java` (spi module) for patterns. |
12 | 142 |
|
13 | | -**Key properties to test:** |
| 143 | +Key properties: |
14 | 144 | - Comparator contract: reflexivity, antisymmetry, transitivity |
15 | 145 | - Merge commutativity: `a.merge(b) == b.merge(a)` |
16 | 146 | - Sort idempotence: `sort(sort(xs)) == sort(xs)` |
17 | 147 | - Partition: `take(n, xs) + drop(n, xs) == xs` |
18 | 148 |
|
19 | | -**Architecture rule:** `UnitTestsFitnessTest` allows `@Property` and `@Provide` annotations alongside the standard JUnit annotations (`@Test`, `@BeforeEach`, `@AfterEach`, `@ParameterizedTest`). |
| 149 | +`UnitTestsFitnessTest` allows `@Property` and `@Provide` alongside standard JUnit annotations. |
0 commit comments