Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
59 changes: 52 additions & 7 deletions docs/developers-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -3208,6 +3208,42 @@ context rather than resolving output or process configuration again; tests
should inject the program through `run_with_ninja_program` when they need a
deterministic child executable.

### Module: `runner::reporter`

`src/runner/reporter.rs` owns construction of the run's `StatusReporter` from
resolved output settings. `ReporterOptions` bundles the resolved output mode,
progress preference, verbose preference, output preferences, and whether
standard output is a TTY. `make_reporter(options)` selects the base reporter,
`AccessibleReporter` or `IndicatifReporter` when progress is enabled and
`SilentReporter` otherwise, then wraps it in `VerboseTimingReporter` when
verbose mode is active. `should_force_text_task_updates` decides whether the
indicatif reporter emits textual task updates, forcing them for accessible
mode or non-TTY standard output.

`run_with_ninja_program` (in `src/runner/mod.rs`) constructs the run's
`StatusReporter` through `reporter::make_reporter` after resolving output mode
and reporter settings, then shares it via the `ExecutionContext` it passes to
`dispatch::execute`.

#### Reuse boundary

- **Ownership:** `runner::reporter` is an internal, non-public submodule of
the runner; it owns all `StatusReporter` construction and the
concrete-reporter selection rules. Nothing outside it builds the run's
reporter, and it is not part of the crate's public API.
- **Permitted call-sites:** only the runner boundary in `src/runner/mod.rs`
may call `make_reporter` — today solely `run_with_ninja_program`.
`runner::process`, dispatch handlers, and external embedders never
construct reporters; handlers consume the already-built reporter through
the `ExecutionContext`/`&dyn StatusReporter` only.
- **Composition rules:** the caller must resolve all `ReporterOptions` inputs
(output mode, progress, verbose, output prefs, stdout TTY) from
CLI/environment state before calling `make_reporter`; the module performs
no such resolution itself. The reporter is composed once per run and
shared immutably. New reporter kinds or selection policies belong in this
module beside the mode-selection logic, colocated with the output-mode
policy.

### Module: `runner::process::ninja_program`

`src/runner/process/ninja_program.rs` owns the executable-resolution boundary.
Expand Down Expand Up @@ -3235,8 +3271,9 @@ All command events share these structured fields:

- `operation`: caller-provided operation label such as `"build"` or tool name.
- `ninja_program`: command program after UTF-8 normalization.
- `suppress_stderr`: derived from `cli.json`, true when JSON output suppresses
direct child-process streams.
- `suppress_stderr`: bool derived from the `StderrMode` policy via
`stderr_mode.is_suppress()`, true when the policy suppresses direct
child-process streams.

Phase-specific fields supplement that shared set. The informational execution
event includes `arg_count`. Spawn- and exit-failure events instead set
Expand All @@ -3259,10 +3296,15 @@ paths:
1. Create `Command` with `Command::new(request.program)`.
2. Pass it into a closure that applies operation-specific configuration.
3. Call `run_command_and_stream_with_context` with optional status observer,
`cli.json` as `suppress_stderr`, and the chosen `operation`.
the request's `stderr_mode` policy, and the chosen `operation`.
4. Let `run_command_and_stream_with_context` handle span creation, execution
logging, failure logging, and exit-status enforcement via context helpers.

The `StderrMode` policy type is independent of `Cli`; the runner derives the
policy at request-build time with `StderrMode::from_json_enabled(cli.json)`,
while the process layer consumes the request's `stderr_mode` field and never
reads `cli.json` itself.

### Module: `runner::process::redaction`

`src/runner/process/redaction.rs` owns the argument-redaction boundary that
Expand Down Expand Up @@ -3366,10 +3408,13 @@ through the child `PATH` is acceptable. What the injected `PATH` always
governs is the environment Ninja's own child commands see when it shells out.

The explicit request APIs compose on top of `CommandEnv`: `NinjaBuildRequest`/
`NinjaToolRequest` carry an `env: &CommandEnv` field alongside the program, CLI
settings, and build file, and are consumed by `run_ninja_with`/
`run_ninja_tool_with`. The convenience wrappers `run_ninja`/`run_ninja_tool`
call these with `CommandEnv::inherit()`, reproducing production behaviour;
`NinjaToolRequest` carry `env: &CommandEnv` and `stderr_mode: StderrMode`
fields alongside the program, CLI settings, and build file, and are consumed
by `run_ninja_with`/`run_ninja_tool_with`. The convenience wrappers
`run_ninja`/`run_ninja_tool` live at the runner boundary
(`src/runner/mod.rs`), call these with `CommandEnv::inherit()`, and derive the
`stderr_mode` policy from the CLI via
`StderrMode::from_json_enabled(cli.json)`, reproducing production behaviour;
tests reach for `run_ninja_with`/`run_ninja_tool_with` directly to supply a
`CommandEnv` built with `with_path` instead. Section 6.1 of the
[design document](netsuke-design.md) records the same architecture from the
Expand Down
3 changes: 2 additions & 1 deletion docs/execplans/3-10-1-guarantee-status-message-ordering.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,8 @@ The output architecture spans several modules:
- `report_pipeline_stage()` helper for stage transitions.

4. **Runner module** (`src/runner/mod.rs`):
- `make_reporter()`: Factory function for selecting reporter.
- `make_reporter()`: Factory function for selecting reporter, defined in
`src/runner/reporter.rs`.
- `handle_build()` and `handle_ninja_tool()`: Orchestrate build execution.
- `on_task_progress_callback()`: Bridges Ninja status parsing to reporter.

Expand Down
4 changes: 2 additions & 2 deletions docs/execplans/3-10-2-consistent-log-prefixes.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ without breaking any existing functionality.
`IndicatifReporter` accepts `OutputPrefs` and uses success prefix
- `src/status_timing.rs`: `VerboseTimingReporter` accepts `OutputPrefs` and
uses timing prefix with indented detail lines
- `src/runner/mod.rs`: Updated reporter factory
- `src/runner/reporter.rs`: Updated reporter factory
- `tests/features/progress_output.feature`: Updated and expanded scenarios
- `docs/roadmap.md`: Marked 3.10.2 as complete

Expand All @@ -138,7 +138,7 @@ without breaking any existing functionality.
5. `locales/en-US/messages.ftl` — English locale messages.
6. `locales/es-ES/messages.ftl` — Spanish locale messages.
7. `tests/features/progress_output.feature` — BDD progress scenarios.
8. `src/runner/mod.rs` — Reporter factory (`make_reporter()`).
8. `src/runner/reporter.rs` — Reporter factory (`make_reporter()`).

### New prefix design

Expand Down
12 changes: 7 additions & 5 deletions docs/execplans/3-10-3-json-diagnostics-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,9 @@ The current diagnostics path is split across a small number of files:
existing `Cli` struct and already derives `OrthoConfig`.
- [src/cli_l10n.rs](../../src/cli_l10n.rs) maps clap
argument identifiers to Fluent help keys.
- [src/runner/mod.rs](../../src/runner/mod.rs) constructs
status reporters and executes commands.
- [src/runner/reporter.rs](../../src/runner/reporter.rs)
constructs the status reporter, while
[src/runner/mod.rs](../../src/runner/mod.rs) executes commands.
- [src/runner/error.rs](../../src/runner/error.rs) contains
`RunnerError`, which already implements `miette::Diagnostic`.
- [src/manifest/diagnostics/mod.rs](../../src/manifest/diagnostics/mod.rs)
Expand Down Expand Up @@ -479,9 +480,10 @@ Changes:
through CLI or environment hints
- render runtime failures as JSON when the merged CLI enables it
2. Suppress `tracing_subscriber` stderr logging when JSON mode is active.
3. In [src/runner/mod.rs](../../src/runner/mod.rs), force
`SilentReporter` when JSON mode is active so no status lines or timing
summaries reach `stderr`.
3. In [src/runner/reporter.rs](../../src/runner/reporter.rs),
`make_reporter` selects `SilentReporter` when progress is disabled
in JSON mode, so no status lines or timing summaries reach
`stderr`.
4. Ensure success-path subcommand output remains unchanged on `stdout`.

Acceptance for Stage C:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ Observable success:
task-progress rendering in `IndicatifReporter` and
`AccessibleReporter`.
- [x] (2026-02-24) Stage C: Added centralized fallback behaviour in
`src/runner/mod.rs` (`should_force_text_task_updates`) to emit textual
task updates when stdout is non-TTY or accessible mode is enabled.
`src/runner/reporter.rs` (`should_force_text_task_updates`) to emit
textual task updates when stdout is non-TTY or accessible mode is enabled.
- [x] (2026-02-24) Stage D: Added/updated unit and behavioural coverage using
`rstest` and `rstest-bdd`:
`src/runner/process/ninja_status.rs`,
Expand Down Expand Up @@ -184,7 +184,8 @@ Validation summary:
Primary implementation surfaces:

- `src/runner/process/mod.rs`: child process spawning and output forwarding.
- `src/runner/mod.rs`: reporter construction and pipeline orchestration.
- `src/runner/reporter.rs`: reporter construction.
- `src/runner/mod.rs`: pipeline orchestration.
- `src/status.rs`: reporter trait and implementations.
- `src/status_pipeline.rs`: six-stage canonical ordering and labels.
- `src/cli/mod.rs`: OrthoConfig-derived CLI configuration.
Expand Down
9 changes: 5 additions & 4 deletions docs/execplans/3-9-3-per-stage-timing-metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@ What shipped:
- a deterministic stage-timing recorder,
- a duration formatter (`ns`/`us`/`ms`/`s`),
- `VerboseTimingReporter` wrapper with an injectable monotonic clock.
- Wired reporter selection in `src/runner/mod.rs` so verbose mode wraps the
resolved base reporter (including silent progress mode).
- Wired reporter selection in `src/runner/reporter.rs` so verbose mode
wraps the resolved base reporter (including silent progress mode).
- Added localized timing summary runtime strings and updated verbose help copy
in both locales.
- Added `rstest` unit coverage for timing happy/unhappy/edge behaviour.
Expand Down Expand Up @@ -213,7 +213,8 @@ Primary implementation surfaces:

- `src/status.rs`: reporter trait and concrete status reporters.
- `src/status_pipeline.rs`: canonical six-stage order and labels.
- `src/runner/mod.rs`: reporter construction and pipeline orchestration.
- `src/runner/reporter.rs`: reporter construction.
- `src/runner/mod.rs`: pipeline orchestration.
- `src/cli/mod.rs`: OrthoConfig-derived `verbose` configuration.
- `src/cli_l10n.rs`: localized clap help key mapping.
- `src/localization/keys.rs`: Fluent key constants.
Expand Down Expand Up @@ -268,7 +269,7 @@ silent by default.

Planned changes:

- Update `runner::make_reporter(...)` to accept a verbose flag and wrap the
- Update `reporter::make_reporter(...)` to accept a verbose flag and wrap the
selected base reporter with timing support when `cli.verbose` is true.
- Ensure behaviour by mode:
- non-verbose: unchanged output (no timing summary),
Expand Down
28 changes: 21 additions & 7 deletions docs/netsuke-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -2132,10 +2132,13 @@ child process's execution environment.
Every invocation is described by a borrowed request bundle rather than a long
parameter list: `NinjaBuildRequest` for a build and `NinjaToolRequest` for
`ninja -t <tool>`. Each names the resolved program, the parsed CLI settings,
the generated build file, the targets or tool, and a `&CommandEnv` describing
the child's environment. `run_ninja_with` and `run_ninja_tool_with` consume
these; the convenience wrappers `run_ninja` and `run_ninja_tool` call them with
`CommandEnv::inherit()`, which is production behaviour.
the generated build file, the targets or tool, a `&CommandEnv` describing the
child's environment, and the `stderr_mode: StderrMode` policy field.
`run_ninja_with` and `run_ninja_tool_with` consume these; the convenience
wrappers `run_ninja` and `run_ninja_tool` live at the runner boundary, call
them with `CommandEnv::inherit()`, and derive the `stderr_mode` policy from the
CLI via `StderrMode::from_json_enabled(cli.json)`, which is production
behaviour.

The command construction follows this pattern:

Expand Down Expand Up @@ -2171,9 +2174,20 @@ The command construction follows this pattern:
to the user's console, potentially with additional formatting or status
updates from Netsuke itself.

Standard output and error are piped and written back to Netsuke's own streams
so users see Ninja's messages in order. A non-zero exit status or failure to
spawn the process is reported as an `io::Error` for the CLI to surface.
Stream routing follows the `stderr_mode` policy carried by the request.
Outside JSON mode (`StderrMode::Forward`) the child's standard output and
error are piped and forwarded to Netsuke's own streams concurrently: stdout is
drained on the main thread while a separate thread forwards stderr, so users
see Ninja's messages as it produces them. No relative ordering is guaranteed
between the two streams. In JSON diagnostics mode the request
carries `StderrMode::Suppress`, which drains both streams to `io::sink()`:
stdout carries only the versioned result document and stderr only the
diagnostic document, keeping both machine-readable. The runner derives the
policy from the CLI's JSON setting via
`StderrMode::from_json_enabled(cli.json)`; the process layer consumes the
request's `stderr_mode` field and never re-derives it. A non-zero exit status
or failure to spawn the process is reported as an `io::Error` for the CLI to
surface.

The `ninja_subprocess` span and its spawn and exit events carry
`env_override_count` and `path_overridden`, derived from the prepared
Expand Down
37 changes: 23 additions & 14 deletions docs/users-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -591,14 +591,17 @@ its own process environment. `netsuke::runner::CommandEnv` carries child
environment overrides as data — `inherit()` changes nothing, `with_var` and
`with_path` set variables for the spawned command only — and the explicit
request forms `run_ninja_with` and `run_ninja_tool_with` accept a request
naming the program, build file, targets or tool, and that environment. The
convenience wrappers `run_ninja` and `run_ninja_tool` behave identically
with an inherited environment. Overrides are additive: variables not named
are inherited from the calling process, and the injected `PATH` governs
what commands Ninja launches will see. Relative program names remain valid
and resolve through that child `PATH`; supply an absolute or otherwise
resolved `program` only when executable selection must stay isolated from
the injected `PATH`.
naming the program, build file, targets or tool, that environment, and a
`stderr_mode: StderrMode` policy routing the child's standard streams:
`Suppress` drains both streams (keeping JSON diagnostics machine-readable),
while `Forward` relays them to the caller. The convenience wrappers
`run_ninja` and `run_ninja_tool` behave identically with an inherited
environment, deriving the policy from the CLI's JSON setting. Overrides are
additive: variables not named are inherited from the calling process, and
the injected `PATH` governs what commands Ninja launches will see. Relative
program names remain valid and resolve through that child `PATH`; supply an
absolute or otherwise resolved `program` only when executable selection must
stay isolated from the injected `PATH`.

The request itself is a named type: `netsuke::runner::NinjaBuildRequest` for a
build and `netsuke::runner::NinjaToolRequest` for `ninja -t <tool>`. Both
Expand All @@ -611,7 +614,7 @@ summarizes these additions and confirms the wrappers are unchanged.
```rust
use netsuke::cli::Cli;
use netsuke::runner::{
BuildTargets, CommandEnv, NinjaBuildRequest, NinjaToolRequest, run_ninja_tool_with,
BuildTargets, CommandEnv, NinjaBuildRequest, NinjaToolRequest, StderrMode, run_ninja_tool_with,
run_ninja_with,
};
use std::path::Path;
Expand All @@ -632,13 +635,17 @@ let build = NinjaBuildRequest {
build_file: Path::new("build.ninja"),
targets: &targets,
env: &env,
// `Suppress` in JSON diagnostics mode keeps the child's output out of
// the machine-readable streams; `Forward` relays it to the caller.
stderr_mode: StderrMode::from_json_enabled(cli.json),
};
let clean = NinjaToolRequest {
program: Path::new("/usr/bin/ninja"),
cli: &cli,
build_file: Path::new("build.ninja"),
tool: "clean",
env: &env,
stderr_mode: StderrMode::from_json_enabled(cli.json),
};

if std::env::var_os("NETSUKE_GUIDE_RUN").is_some() {
Expand All @@ -647,11 +654,13 @@ if std::env::var_os("NETSUKE_GUIDE_RUN").is_some() {
}
```

These types are additive: `run_ninja` and `run_ninja_tool` keep their existing
signatures and behaviour, so an existing caller needs no change. Each release
records such additions in [`CHANGELOG.md`](../CHANGELOG.md), which is where
Netsuke signposts Rust API changes — with no stability promise attached to
them ahead of 1.0.
The convenience wrappers `run_ninja` and `run_ninja_tool` keep their existing
signatures and behaviour, so a caller that uses them needs no change; the
request bundles gained the required `stderr_mode` field, so a caller that
constructs `NinjaBuildRequest`/`NinjaToolRequest` directly must supply it.
Each release records such additions in [`CHANGELOG.md`](../CHANGELOG.md),
which is where Netsuke signposts Rust API changes — with no stability promise
attached to them ahead of 1.0.

## Use the template standard library

Expand Down
7 changes: 5 additions & 2 deletions docs/v0-1-0-migration-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,18 @@ Table: v0.1.0 child-environment API additions and their impact
| --- | --- | --- |
| Convenience wrappers | Unchanged. `run_ninja` and `run_ninja_tool` behave exactly as before, inheriting the process environment. | [Users' guide](users-guide.md) |
| Child environment | New opt-in `netsuke::runner::CommandEnv` carries additive variable overrides and an injected `PATH` for Ninja child processes. | [Users' guide](users-guide.md) |
| Request types | New `netsuke::runner::NinjaBuildRequest` and `netsuke::runner::NinjaToolRequest` name the program, build file, and targets or tool for the `*_with` run functions. | [Users' guide](users-guide.md) |
| Request types | New `netsuke::runner::NinjaBuildRequest` and `netsuke::runner::NinjaToolRequest` name the program, build file, targets or tool, a child environment, and a required `stderr_mode: StderrMode` policy for the `*_with` run functions. | [Users' guide](users-guide.md) |
| Glob expansion | Parent-relative patterns such as `glob('../shared/*.h')` now expand. Metadata checks use a capability rooted at the pattern's longest literal directory prefix; missing or non-directory prefixes return no matches, and unresolvable symlink matches are skipped. | [Users' guide](users-guide.md) and [ADR-010](adr-010-scope-glob-capability-to-literal-prefix.md) |
| Command recipes | Existing scalar `command` recipes are unchanged. New YAML command lists are opt-in and run in declaration order with fail-fast semantics. | [Rules and recipes](users-guide.md#rules-and-recipes) |

## Nothing to change for existing callers

The convenience wrappers keep their signatures and their behaviour: the
child inherits the calling process's environment, and Ninja is resolved
exactly as before. No caller needs to change to adopt this release.
exactly as before. Callers of `run_ninja` or `run_ninja_tool` need no change;
a caller that constructs `NinjaBuildRequest`/`NinjaToolRequest` directly must
now supply the required `stderr_mode: StderrMode` field, derived from the CLI
with `StderrMode::from_json_enabled(cli.json)`.

## Opting into ordered command lists

Expand Down
Loading
Loading