From 6ce4305263d35a33b946d88c3df43bbde7b17ec6 Mon Sep 17 00:00:00 2001 From: Daniel Vianna <1708810+pasunboneleve@users.noreply.github.com> Date: Wed, 2 Sep 2026 17:19:32 +1000 Subject: [PATCH 1/2] Expose transactional artifact guidance Context: Agents could reach the artifact guide only by discovering the docs subcommand, opening its help, and guessing the relevant topic. Bare `devloop docs` failed instead of helping, and artifact validation errors did not name the guide. Decision: Make the docs topic optional, render the existing documentation index when it is omitted, advertise the artifact use case in root help, and attach the exact guide command to artifact validation failures. Keep the smoke harness's readiness signal deterministic by selecting its required log level. Alternatives considered: A machine-readable capabilities command would add another public interface without fixing the ordinary help path that agents already inspect. Duplicating a topic index in Rust would let the repository and binary documentation drift. Tradeoffs: Root help gains one targeted hint, and bare `devloop docs` changes from an error to successful output. The index remains intentionally concise and delegates complete contracts to each topic. Architectural impact: The CLI now treats the documentation index as the default docs resource while preserving every existing topic command. Validation keeps its existing rules and adds only actionable context at the artifact boundary. SemVer impact is a backward-compatible patch. --- CHANGELOG.md | 7 +++++ README.md | 1 + docs/README.md | 29 ++++++++++++-------- scripts/ci-smoke.sh | 2 +- src/config.rs | 7 +++-- src/main.rs | 67 +++++++++++++++++++++++++++++++++------------ tests/cli_docs.rs | 28 +++++++++++++++++++ 7 files changed, 109 insertions(+), 32 deletions(-) create mode 100644 tests/cli_docs.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index d6a244d..caf5509 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to `devloop` will be recorded in this file. ## [Unreleased] +### Fixed + +- Made transactional artifact guidance discoverable from root help, the bare + `devloop docs` index, and artifact validation errors. +- Made the runtime smoke test select its required informational log level + instead of inheriting a caller setting that could hide its readiness signal. + ## [0.11.0] - 2026-09-02 ### Added diff --git a/README.md b/README.md index db95ae9..6d711bb 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ The tool will: Built-in reference docs are also available from the CLI: ```bash +devloop docs devloop docs config devloop docs behavior devloop docs artifacts diff --git a/docs/README.md b/docs/README.md index b1e9aaf..49cc076 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,13 +1,20 @@ -# Docs +# Devloop Documentation -- [Behavior Reference](behavior.md) -- [Configuration Reference](configuration.md) -- [Transactional Artifact Generations](artifacts.md) -- [Development Guide](development.md) -- [Security Notes](security.md) +Run `devloop docs` to print this index in a terminal. Choose a topic according +to the work in front of you: -This directory holds detailed reference material for `devloop`. -Keep the top-level `README.md` focused on purpose, installation, and the -main development loop; put configuration detail here. Session-log behaviour -is documented in the Behavior Reference, and its path follows the state-file -configuration in the Configuration Reference. +- [Configuration Reference](configuration.md) — run `devloop docs config` when + writing or validating `devloop.toml`. +- [Behavior Reference](behavior.md) — run `devloop docs behavior` when reasoning + about supervision, workflows, state, or failure handling. +- [Transactional Artifact Generations](artifacts.md) — run + `devloop docs artifacts` when a build replaces files that a managed process + is serving. +- [Development Guide](development.md) — run `devloop docs development` when + changing or validating devloop itself. +- [Security Notes](security.md) — run `devloop docs security` when configuring + control surfaces, tokens, hooks, or inherited environments. + +This directory holds devloop's detailed reference material. The top-level +README remains a synopsis of the tool, installation, and the main development +loop. diff --git a/scripts/ci-smoke.sh b/scripts/ci-smoke.sh index 506f5f1..716dac2 100755 --- a/scripts/ci-smoke.sh +++ b/scripts/ci-smoke.sh @@ -71,7 +71,7 @@ devloop_bin="${repo_root}/target/debug/devloop" (cd "${repo_root}" && cargo build --bins >/dev/null) -"${devloop_bin}" run --config "${tmp_dir}/devloop.toml" >"${log_path}" 2>&1 & +RUST_LOG=info "${devloop_bin}" run --config "${tmp_dir}/devloop.toml" >"${log_path}" 2>&1 & devloop_pid=$! start_watchdog diff --git a/src/config.rs b/src/config.rs index ef06d1b..a9169f2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -102,9 +102,9 @@ impl Config { } } for (name, artifact) in &self.artifact { - artifact - .validate(self, name) - .with_context(|| format!("invalid artifact '{name}'"))?; + artifact.validate(self, name).with_context(|| { + format!("invalid artifact '{name}'; for guidance, run `devloop docs artifacts`") + })?; } self.event_server.validate()?; self.browser_reload_server.validate()?; @@ -1094,6 +1094,7 @@ steps = [{ action = "notify_reload" }] .expect_err("artifact consumer must not start before recovery"); assert!(format!("{error:#}").contains("must set autostart = false")); + assert!(format!("{error:#}").contains("devloop docs artifacts")); } #[test] diff --git a/src/main.rs b/src/main.rs index 68b97ca..6026faf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,7 +41,8 @@ const SESSION_LOG_SHUTDOWN_FLUSH_TIMEOUT: Duration = Duration::from_secs(5); author, version, about = "Run config-driven local development workflows", - long_about = "devloop watches a client repository, supervises its processes, and executes ordered workflows defined in a TOML config file." + long_about = "devloop watches a client repository, supervises its processes, and executes ordered workflows defined in a TOML config file.", + after_help = "Serving generated files that are replaced during rebuilds? Use transactional artifacts. Run `devloop docs artifacts`." )] struct Cli { #[command(subcommand)] @@ -65,7 +66,7 @@ enum Command { /// Print built-in reference documentation. Docs { #[arg(value_enum)] - topic: DocsTopic, + topic: Option, }, } @@ -322,17 +323,18 @@ fn resolve_config_path(config: Option) -> Result { } } -fn docs_text(topic: DocsTopic) -> &'static str { +fn docs_text(topic: Option) -> &'static str { match topic { - DocsTopic::Config => include_str!("../docs/configuration.md"), - DocsTopic::Behavior => include_str!("../docs/behavior.md"), - DocsTopic::Artifacts => include_str!("../docs/artifacts.md"), - DocsTopic::Development => include_str!("../docs/development.md"), - DocsTopic::Security => include_str!("../docs/security.md"), + None => include_str!("../docs/README.md"), + Some(DocsTopic::Config) => include_str!("../docs/configuration.md"), + Some(DocsTopic::Behavior) => include_str!("../docs/behavior.md"), + Some(DocsTopic::Artifacts) => include_str!("../docs/artifacts.md"), + Some(DocsTopic::Development) => include_str!("../docs/development.md"), + Some(DocsTopic::Security) => include_str!("../docs/security.md"), } } -fn render_docs_text(topic: DocsTopic) -> String { +fn render_docs_text(topic: Option) -> String { render_markdown_for_terminal(docs_text(topic)) } @@ -524,7 +526,7 @@ mod tests { }; use crate::session_log::SessionLog; use crate::test_support::RustLogGuard; - use clap::Parser; + use clap::{CommandFactory, Parser}; use tempfile::tempdir; #[test] @@ -639,7 +641,7 @@ mod tests { #[test] fn docs_text_uses_embedded_configuration_reference() { - let rendered = docs_text(DocsTopic::Config); + let rendered = docs_text(Some(DocsTopic::Config)); assert!(rendered.starts_with("# Configuration Reference")); assert!(rendered.contains("startup_workflows")); @@ -649,7 +651,7 @@ mod tests { #[test] fn docs_text_uses_embedded_session_log_behavior_reference() { - let rendered = docs_text(DocsTopic::Behavior); + let rendered = docs_text(Some(DocsTopic::Behavior)); assert!(rendered.starts_with("# Behavior Reference")); assert!(rendered.contains("### Session logs")); @@ -658,7 +660,7 @@ mod tests { #[test] fn docs_text_uses_embedded_development_reference() { - let rendered = docs_text(DocsTopic::Development); + let rendered = docs_text(Some(DocsTopic::Development)); assert!(rendered.starts_with("# Development Guide")); assert!(rendered.contains("DEVLOOP_RUN_WATCH_FLAKE_SMOKE")); @@ -666,7 +668,7 @@ mod tests { #[test] fn docs_text_exposes_agent_safe_artifact_workflow() { - let rendered = docs_text(DocsTopic::Artifacts); + let rendered = docs_text(Some(DocsTopic::Artifacts)); assert!(rendered.starts_with("# Transactional Artifact Generations")); assert!(rendered.contains("## Agent rule")); @@ -676,7 +678,7 @@ mod tests { #[test] fn rendered_docs_drop_markdown_heading_markers() { - let rendered = render_docs_text(DocsTopic::Config); + let rendered = render_docs_text(Some(DocsTopic::Config)); assert!(rendered.starts_with("CONFIGURATION REFERENCE")); assert!(!rendered.contains("# Configuration Reference")); @@ -712,7 +714,9 @@ mod tests { let cli = Cli::try_parse_from(["devloop", "docs", "security"]).expect("parse cli"); match cli.command { - super::Command::Docs { topic } => assert!(matches!(topic, DocsTopic::Security)), + super::Command::Docs { topic } => { + assert!(matches!(topic, Some(DocsTopic::Security))) + } _ => panic!("expected docs subcommand"), } } @@ -722,8 +726,37 @@ mod tests { let cli = Cli::try_parse_from(["devloop", "docs", "development"]).expect("parse cli"); match cli.command { - super::Command::Docs { topic } => assert!(matches!(topic, DocsTopic::Development)), + super::Command::Docs { topic } => { + assert!(matches!(topic, Some(DocsTopic::Development))) + } _ => panic!("expected docs subcommand"), } } + + #[test] + fn cli_root_help_points_agents_to_artifact_guidance() { + let help = Cli::command().render_long_help().to_string(); + + assert!(help.contains("Serving generated files that are replaced during rebuilds?")); + assert!(help.contains("devloop docs artifacts")); + } + + #[test] + fn cli_accepts_docs_without_a_topic() { + let cli = Cli::try_parse_from(["devloop", "docs"]).expect("parse docs index command"); + + match cli.command { + super::Command::Docs { topic } => assert!(topic.is_none()), + _ => panic!("expected docs subcommand"), + } + } + + #[test] + fn docs_index_explains_when_to_use_artifacts() { + let rendered = render_docs_text(None); + + assert!(rendered.starts_with("DEVLOOP DOCUMENTATION")); + assert!(rendered.contains("devloop docs artifacts")); + assert!(rendered.contains("replaces files that a managed process is serving")); + } } diff --git a/tests/cli_docs.rs b/tests/cli_docs.rs new file mode 100644 index 0000000..c0274e8 --- /dev/null +++ b/tests/cli_docs.rs @@ -0,0 +1,28 @@ +use std::process::Command; + +#[test] +fn root_help_names_the_artifact_use_case_and_guide() { + let output = Command::new(env!("CARGO_BIN_EXE_devloop")) + .arg("--help") + .output() + .expect("run root help"); + + assert!(output.status.success()); + let stdout = String::from_utf8(output.stdout).expect("help is UTF-8"); + assert!(stdout.contains("Serving generated files that are replaced during rebuilds?")); + assert!(stdout.contains("devloop docs artifacts")); +} + +#[test] +fn bare_docs_prints_the_topic_index() { + let output = Command::new(env!("CARGO_BIN_EXE_devloop")) + .arg("docs") + .output() + .expect("run docs index"); + + assert!(output.status.success()); + let stdout = String::from_utf8(output.stdout).expect("docs index is UTF-8"); + assert!(stdout.starts_with("DEVLOOP DOCUMENTATION")); + assert!(stdout.contains("devloop docs artifacts")); + assert!(stdout.contains("replaces files that a managed process is serving")); +} From 3bee1f977037c460d517d0605c3a6d744716f37e Mon Sep 17 00:00:00 2001 From: Daniel Vianna <1708810+pasunboneleve@users.noreply.github.com> Date: Wed, 2 Sep 2026 17:22:21 +1000 Subject: [PATCH 2/2] Prepare devloop 0.11.1 Context: The artifact-guide discoverability fix is backward compatible and ready for a patch release. Tag automation requires the Cargo package version, changelog section, release notes input, and intended tag to agree. Decision: Promote the accumulated `[Unreleased]` fixes into the dated `0.11.1` section, recreate an empty unreleased section, and update Cargo's package and lockfile versions to `0.11.1`. Alternatives considered: Leaving the fixes unreleased would not update installed binaries. A minor version would overstate a discoverability correction that adds no separate feature or incompatible command contract. Tradeoffs: This creates an immediate patch after `0.11.0`, but it makes the artifact workflow usable by agents without relying on repository knowledge. Architectural impact: Runtime architecture is unchanged. Release metadata and the intended `v0.11.1` tag now share one version boundary for protected CI and artifact publication. --- CHANGELOG.md | 2 ++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index caf5509..79aca3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to `devloop` will be recorded in this file. ## [Unreleased] +## [0.11.1] - 2026-09-02 + ### Fixed - Made transactional artifact guidance discoverable from root help, the bare diff --git a/Cargo.lock b/Cargo.lock index 9e0cf32..11eca2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -235,7 +235,7 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "devloop" -version = "0.11.0" +version = "0.11.1" dependencies = [ "anyhow", "axum", diff --git a/Cargo.toml b/Cargo.toml index 0a2f810..3037dab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "devloop" -version = "0.11.0" +version = "0.11.1" edition = "2024" [dependencies]