diff --git a/CHANGELOG.md b/CHANGELOG.md index d6a244d..79aca3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ 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 + `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/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] 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")); +}