Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .beads/issues.jsonl
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
{"id":"devloop-0nx","title":"Define project docs, AGENTS, and roadmap","description":"Add README, PLAN, and repository-specific AGENTS guidance for the standalone tool.","status":"closed","priority":2,"issue_type":"task","owner":"1708810+dmvianna@users.noreply.github.com","created_at":"2026-03-24T01:15:00Z","created_by":"Daniel Vianna","updated_at":"2026-03-24T01:27:27Z","closed_at":"2026-03-24T01:27:27Z","close_reason":"Closed"}
{"id":"devloop-8km","title":"Implement config and workflow engine","description":"Load config, watch paths, classify events, and execute ordered workflows against named processes and hooks.","status":"closed","priority":2,"issue_type":"feature","owner":"1708810+dmvianna@users.noreply.github.com","created_at":"2026-03-24T01:15:00Z","created_by":"Daniel Vianna","updated_at":"2026-03-24T02:09:33Z","closed_at":"2026-03-24T02:09:33Z","close_reason":"Closed","dependencies":[{"issue_id":"devloop-8km","depends_on_id":"devloop-0nx","type":"blocks","created_at":"2026-03-24T12:15:07Z","created_by":"Daniel Vianna","metadata":"{}"}]}
{"id":"devloop-c6l","title":"Polish 0.8.0 release docs and test ergonomics","status":"closed","priority":2,"issue_type":"task","owner":"1708810+dmvianna@users.noreply.github.com","created_at":"2026-04-08T07:12:58Z","created_by":"Daniel Vianna","updated_at":"2026-04-08T07:20:27Z","closed_at":"2026-04-08T07:20:27Z","close_reason":"Closed"}
{"id":"devloop-d81","title":"Move real client config out of devloop examples","description":"Keep only generic examples in devloop and move the working blog config into the client repository root.","status":"closed","priority":2,"issue_type":"task","owner":"1708810+dmvianna@users.noreply.github.com","created_at":"2026-03-24T02:31:27Z","created_by":"Daniel Vianna","updated_at":"2026-03-24T02:39:07Z","closed_at":"2026-03-24T02:39:07Z","close_reason":"Kept only generic examples in devloop and moved the working blog config into the client repo root."}
{"id":"devloop-dzy","title":"Make client hook paths repo-relative","description":"Resolve devloop config command paths relative to the client repo or config, not the tool checkout.","status":"closed","priority":2,"issue_type":"task","owner":"1708810+dmvianna@users.noreply.github.com","created_at":"2026-03-24T02:16:34Z","created_by":"Daniel Vianna","updated_at":"2026-03-24T02:22:17Z","closed_at":"2026-03-24T02:22:17Z","close_reason":"Closed"}
{"id":"devloop-mml","title":"Address roborev findings on state ownership and client-specific URL composition","status":"closed","priority":1,"issue_type":"task","owner":"1708810+dmvianna@users.noreply.github.com","created_at":"2026-03-24T02:44:51Z","created_by":"Daniel Vianna","updated_at":"2026-03-24T02:53:31Z","closed_at":"2026-03-24T02:53:31Z","close_reason":"Shared session state is now owned in memory, generic state templating replaced blog-specific derivation, redundant writes are skipped, and the review job was addressed."}
{"id":"devloop-nmu","title":"Add blog client example and verification","description":"Create example config/hooks for the blog repo and verify the tool runs against it.","status":"closed","priority":2,"issue_type":"task","owner":"1708810+dmvianna@users.noreply.github.com","created_at":"2026-03-24T01:15:00Z","created_by":"Daniel Vianna","updated_at":"2026-03-24T02:09:33Z","closed_at":"2026-03-24T02:09:33Z","close_reason":"Closed","dependencies":[{"issue_id":"devloop-nmu","depends_on_id":"devloop-8km","type":"blocks","created_at":"2026-03-24T12:15:07Z","created_by":"Daniel Vianna","metadata":"{}"}]}
{"id":"devloop-s2h","title":"Bootstrap configurable dev-loop engine MVP","description":"Create a standalone Rust CLI in /tmp/devloop with config-driven file watching, process supervision, workflows, hooks, and documentation. Use the blog repo as the first client.","status":"open","priority":2,"issue_type":"epic","owner":"1708810+dmvianna@users.noreply.github.com","created_at":"2026-03-24T01:14:54Z","created_by":"Daniel Vianna","updated_at":"2026-03-24T01:14:54Z","dependencies":[{"issue_id":"devloop-s2h","depends_on_id":"devloop-nmu","type":"blocks","created_at":"2026-03-24T12:15:07Z","created_by":"Daniel Vianna","metadata":"{}"}]}
{"id":"devloop-ufx","title":"Add client adapter for dynamic tunnel url consumption","description":"The blog app still treats SITE_URL as startup-only state. Add a client-side integration pattern that reads devloop state dynamically so tunnel restarts affect rendered metadata.","status":"closed","priority":2,"issue_type":"task","owner":"1708810+dmvianna@users.noreply.github.com","created_at":"2026-03-24T01:27:27Z","created_by":"Daniel Vianna","updated_at":"2026-03-24T02:09:33Z","closed_at":"2026-03-24T02:09:33Z","close_reason":"Closed"}
{"id":"devloop-uog","title":"Add watcher regression smoke test and poll backend fallback","status":"closed","priority":2,"issue_type":"task","owner":"1708810+dmvianna@users.noreply.github.com","created_at":"2026-04-08T05:45:33Z","created_by":"Daniel Vianna","updated_at":"2026-04-08T06:46:22Z","closed_at":"2026-04-08T06:46:22Z","close_reason":"Closed"}
{"id":"devloop-vxg","title":"Support process-emitted state updates","description":"Long-running processes such as cloudflared need a first-class way to publish readiness and state into the engine instead of relying on wrapper scripts mutating the session file.","status":"closed","priority":2,"issue_type":"feature","owner":"1708810+dmvianna@users.noreply.github.com","created_at":"2026-03-24T01:27:27Z","created_by":"Daniel Vianna","updated_at":"2026-03-24T02:09:33Z","closed_at":"2026-03-24T02:09:33Z","close_reason":"Closed"}
{"id":"devloop-w34","title":"Support workflow composition to reduce repeated setup steps","description":"Add a generic way for one workflow to reuse another so client configs can avoid duplicating repeated step sequences such as wait-for-tunnel plus templated write_state composition.","status":"closed","priority":3,"issue_type":"task","owner":"1708810+dmvianna@users.noreply.github.com","created_at":"2026-03-24T02:53:31Z","created_by":"Daniel Vianna","updated_at":"2026-03-24T03:01:13Z","closed_at":"2026-03-24T03:01:13Z","close_reason":"Added reusable run_workflow steps, validated nested workflow recursion and missing references, and updated the generic example."}
{"id":"devloop-y2l","title":"Fix repeated literal file edit watch flakiness","description":"A Rust integration smoke test now reproduces missed workflow triggers during repeated edits to the same watched file. Fix the watch pipeline so the smoke test passes deterministically without sleeps.","status":"closed","priority":1,"issue_type":"task","owner":"1708810+dmvianna@users.noreply.github.com","created_at":"2026-04-08T06:23:10Z","created_by":"Daniel Vianna","updated_at":"2026-04-08T06:46:22Z","closed_at":"2026-04-08T06:46:22Z","close_reason":"Closed"}
7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ without hard-coding knowledge of any one repository.
avoid baking it into the core.
- Do not push unless explicitly asked. This repository may not even have a
remote during early development.
- Do not use `sleep` to resolve races. Races must be resolved with
deterministic logic, explicit readiness signals, or ordered state
transitions.
- When tests must mutate process-global state such as environment
variables, isolate the unsafe operation in a small helper, serialize
access with a lock, and document the safety rationale instead of
scattering raw unsafe calls through test bodies.
- Run quality gates for code changes: `cargo fmt`, `cargo test`,
`cargo clippy --all-targets --all-features -- -D warnings`.

Expand Down
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,39 @@ All notable changes to `devloop` will be recorded in this file.

## [Unreleased]

## [0.8.0] - 2026-04-08

### Added
- Added a configurable watcher backend with non-breaking `native`
default behavior plus a `poll` fallback mode for environments where
native filesystem notifications are unreliable.
- Added a Rust repeated-edit watch flake smoke test that can be run
locally with `DEVLOOP_RUN_WATCH_FLAKE_SMOKE=1 cargo test --test
watch_flake_smoke -- --nocapture`.
- Added explicit trailing-slash syntax for literal directory watch
targets, for example `content/`, so recursive directory intent is
preserved even when the directory does not yet exist at startup.
- Added a development guide under [`docs/development.md`](docs/development.md)
and exposed it in the CLI as `devloop docs development`.

### Changed
- `devloop` now derives concrete watch targets from configured watch
patterns and asks the backend to watch only those files or
directories instead of always watching the whole repository root.
- The watch flake smoke test is now opt-in instead of running during
every default `cargo test` or CI run. The existing runtime smoke test
remains in CI.

### Fixed
- Native watch registration now resolves file and directory targets at
runtime, so startup no longer depends on those paths already existing
when config is parsed.
- Fixed a real watch flake where the debounce batch could be dropped if
another `tokio::select!` branch won the race while filesystem events
were already buffered.
- Test-only environment mutation now lives behind locked helpers with
documented safety rationale instead of scattered raw unsafe blocks.

## [0.7.0] - 2026-03-27

### Added
Expand Down
41 changes: 40 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "devloop"
version = "0.7.0"
version = "0.8.0"
edition = "2024"

[dependencies]
Expand All @@ -21,3 +21,6 @@ toml = "0.8.22"
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.19", features = ["env-filter", "fmt"] }
unicode-width = "0.2"

[dev-dependencies]
tempfile = "3.20.0"
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ Built-in reference docs are also available from the CLI:
```bash
devloop docs config
devloop docs behavior
devloop docs development
devloop docs security
```

Expand Down Expand Up @@ -186,6 +187,9 @@ For the runtime behavior reference, see
For the full configuration reference, see
[`docs/configuration.md`](docs/configuration.md).

For local contributor workflow details, including the opt-in watch
flake smoke test, see [`docs/development.md`](docs/development.md).

For the external-event trust model and push-versus-polling tradeoffs,
see [`docs/security.md`](docs/security.md).

Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- [Behavior Reference](behavior.md)
- [Configuration Reference](configuration.md)
- [Development Guide](development.md)
- [Security Notes](security.md)

This directory holds detailed reference material for `devloop`.
Expand Down
9 changes: 8 additions & 1 deletion docs/behavior.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,20 @@ merged back into the live session.

## Watching and debounce

`devloop` watches the configured `root` recursively.
`devloop` derives concrete filesystem watch targets from the configured
watch-group patterns and watches only those files or directories.

- Only relevant file-system events are considered.
- Events are batched for `debounce_ms`.
- Matching changes are grouped by workflow name before execution.
- Each workflow receives the set of changed relative paths that matched
it during the debounce window.
- The default backend uses native filesystem notifications. A polling
backend can be selected in config as a fallback for environments
where native events are unreliable.
- Literal file targets are watched as narrowly as the backend allows.
Use a trailing `/` in the config when you mean an explicit directory
target that should be watched recursively.

If multiple watch groups map to the same workflow, their matched paths
are merged for that workflow run.
Expand Down
31 changes: 28 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,21 @@ startup_workflows = ["startup"]
- `startup_workflows`: workflows to run after autostart processes have
been started.

Optional watcher backend config:

```toml
[watcher]
kind = "native"
poll_interval_ms = 250
```

- `watcher.kind`: watcher backend to use. `native` is the default and
uses the platform-recommended `notify` backend. `poll` uses
`notify`'s polling watcher as a fallback for environments where
native filesystem events are unreliable.
- `watcher.poll_interval_ms`: polling interval used when
`watcher.kind = "poll"`. Default: `250`.

Optional browser reload server config:

```toml
Expand All @@ -41,16 +56,26 @@ bind = "127.0.0.1:0"
Watch groups map file patterns to workflows.

```toml
[watch.rust]
paths = ["src/**/*.rs", "Cargo.toml"]
workflow = "rust"
[watch.content]
paths = ["content/", "templates/**/*.html"]
workflow = "content"
```

- Table name: the watch-group name.
- `paths`: glob patterns evaluated relative to `root`.
Use a trailing `/` for a literal directory target that should be
watched recursively, including when the directory may not exist yet
at startup. Without the trailing slash, a literal path is treated as
a file target.
- `workflow`: workflow to run when a matching file changes. If omitted,
the watch-group name is used as the workflow name.

`devloop` derives concrete watch targets from these patterns and asks
the backend to watch only those literal files or directories instead of
always watching the whole repository root recursively. `native` remains
the default backend; `poll` exists as a fallback for environments where
filesystem notifications are unreliable.

## Processes

Processes are long-running commands supervised by `devloop`.
Expand Down
46 changes: 46 additions & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Development Guide

This guide covers local development workflows for `devloop` itself.

## Quality gates

Run the standard checks before committing:

```bash
cargo fmt
cargo test
cargo clippy --all-targets --all-features -- -D warnings
./scripts/ci-smoke.sh
```

`./scripts/ci-smoke.sh` is the fast runtime smoke test used in CI. It
checks that `devloop run` can start, begin watching, react to one file
change, and shut down cleanly.

## Opt-in watch flake smoke

The repeated-edit watch flake smoke test is intentionally opt-in. It is
useful when changing watch registration, debounce logic, or event
delivery, but it is slower and more environment-sensitive than the
standard test suite.

Run it locally with:

```bash
DEVLOOP_RUN_WATCH_FLAKE_SMOKE=1 cargo test --test watch_flake_smoke -- --nocapture
```

Without that environment variable, the test exits early so normal
`cargo test` and CI runs stay fast.

## Test policy

When a test must mutate process-global state such as environment
variables:

- serialize access with a test-local lock
- keep `unsafe` in a narrow helper
- document the safety rationale at the helper

Do not scatter raw `unsafe { std::env::set_var(...) }` calls across test
bodies.
3 changes: 3 additions & 0 deletions examples/blog/devloop.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ debounce_ms = 300
state_file = "./.devloop/state.json"
startup_workflows = ["startup"]

[watcher]
kind = "native"

[watch.rust]
paths = ["src/**/*.rs", "Cargo.toml", "content/layout.html", "content/banner.html", "content/site.toml"]
workflow = "rust"
Expand Down
1 change: 1 addition & 0 deletions src/browser_reload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ mod tests {
Config {
root: ".".into(),
debounce_ms: 100,
watcher: crate::config::WatcherConfig::default(),
state_file: Some("./state.json".into()),
startup_workflows: vec![],
watch: BTreeMap::new(),
Expand Down
Loading