# AGENTS.md Maven single-module demo (target Java 17) teaching `ProcessBuilder`/`Process`. No lint, no `exec-maven-plugin`. Runtime dep: `logback-classic` (slf4j); test dep: `junit-jupiter` (JUnit 5, via surefire). Code comments and tutorial notes (`process.md`, `doc.md`) are in Chinese. ## Tests — `mvn test` (JUnit 5) `ProcessMechanicsTest` drives the same fixtures end-to-end (compiles and runs child processes) and deliberately covers the stream-drain + timeout/`destroy` traps. Same `user.dir` rule applies — run from the repo root or the fixture `.start()` throws `error=2`. Tests recompile fixtures into `src/main/resources/code/**` (gitignored `*.class`). Keep tests fast: never block a full 10s sleep — use a short timeout then `destroy()`. ## Running examples Classes are plain `public static void main` entry points in `src/main/java/space/anyi/process/`. There is no run target, so run manually after compiling: ```bash mvn -q compile java -cp "target/classes:$(mvn -q dependency:build-classpath -Dmdep.outputFile=/dev/stdout | tail -1)" space.anyi.process. ``` All examples declare `static Logger log`, so slf4j jars must be on the classpath or you get `NoClassDefFoundError: org/slf4j/LoggerFactory` (when manually invoking `java -cp target/classes`, not an env problem). ## Child-process fixtures - Per-topic fixture dirs under `src/main/resources/code/`: `hello/` (prints greeting), `io/` (echoes stdin, writes stderr), `executeStatus/` (sleeps 10s). Each demo's `workDir` points at exactly one fixture dir. - Demos invoke `javac`/`java` at runtime to compile and run fixture `.java` files; shared helpers live in `space.anyi.process.Jdk` (`javac()`/`java()` return absolute paths from `java.home`, `compileFixture()` runs javac). Spawned processes' PATH often lacks the JDK bin dir (`error=2`) — never go back to bare `javac`/`java`. - Demos hardcode `workDir` relative to `user.dir` → run them from the repo root, or `ProcessBuilder.start()` throws `IOException: error=2`. Keep `workDir` in sync with the fixture tree when moving files. - Also: `space.anyi.process.Main` in package root must NOT exist — `Jdk.compileFixture` uses `-encoding UTF-8` + `Main` and fixtures are in the default package. ## Working rules - Every example's spawned child process must run the Java program under `src/main/resources/code/`, never an ad-hoc script or system command. Keep the fixture tree in sync with each example's `workDir`. - Every code example must carry reasonable comments (Chinese, matching repo style) explaining what each step does and why. - Each case point (每个案例点) is demonstrated by exactly one dedicated `static` method, invoked from `main()` — do not fold several points into one linear `main`. - Commit to the local git repo after completing each example/point (`git add` + `git commit`); commit in small increments, one point per commit. ## Known API traps in these examples - `new BufferedReader(new InputStreamReader(p.getInputStream())).lines().forEach(...)` **blocks the calling thread until the child exits** (stdout EOF only closes on exit). Draining stdout before `waitFor(timeout)` pins the main thread for the full child runtime (e.g. 10s) and silently defeats the timeout semantics. Drain child output on a background thread when using a timed `waitFor`. - `Process.waitFor(timeout, unit)` returns a boolean, not an exit code. After a timeout it returns `false` with the child still alive; calling `process.exitValue()` there throws `IllegalThreadStateException` — `destroy()` + blocking `waitFor()` before reading `exitValue()`.