feat(cli): add mds watch subcommand with auto-recompile on save#93
Open
dean0x wants to merge 5 commits into
Open
feat(cli): add mds watch subcommand with auto-recompile on save#93dean0x wants to merge 5 commits into
dean0x wants to merge 5 commits into
Conversation
… Implements mds watch for milestone v0.3.0 — watches .mds files and auto-recompiles on save, enabling live-preview workflows. Single-file mode tracks transitive @import deps (ADR-016: dep set recomputed on every rebuild). Directory mode compiles each changed .mds independently; on source deletion, removes the matching output file. Includes a behavior-preserving refactor: build logic extracted from main.rs into build.rs with pub(crate) helpers. New compile_and_write helper routes both markdown and messages modes through read_build_input and compile_with_deps, preserving the 10 MiB file-size cap on every code path (avoids PF-004). New deps: notify 8 (MSRV 1.77, stable 2025-07-03) and ctrlc 3.5 (MSRV 1.69, stable 2026-02-10) — both satisfy the 30-day cooldown and MSRV 1.88 requirements. 6 new unit tests (T-U1..T-U6) and 19 integration tests. All 912 workspace tests pass, 0 regressions. Co-Authored-By: Claude <noreply@anthropic.com>
…lish Add 7 new integration tests to crates/mds-cli/tests/cli_watch.rs covering: - AC-F3: import-removal resync: verifies both add AND remove directions of dynamic dep tracking, T-I4 completion - AC-F7: dir mode vars-recompile-all: editing vars.json triggers rebuild of all .mds files in directory mode - AC-A5: quiet mode keeps compile errors visible: -q suppresses status messages but errors still appear on stderr; watcher stays alive and recovers - AC-F9: "Stopped watching." message on Ctrl+C: extends T-I16 to assert the message appears on stderr - AC-P1: debounce coalesces burst: counts "Recompiled" lines to assert <= 2 rebuilds from a 10-write burst in a 250ms window - AC-F10: watch no-arg auto-detect: single .mds in cwd is compiled; multiple .mds files without explicit arg yields non-zero exit with descriptive error Also includes Simplifier's canonicalize_vars_path extraction in watch.rs and Scrutinizer's T-I10 --clear test rewrite with piped stderr and rebuild trigger. Total mds-cli test count: 321 (was 314). All pass, 0 regressions. Co-Authored-By: Claude <noreply@anthropic.com>
…rite in watch (#57) macOS FSEvents delivers synthetic events for source files immediately after watcher registration, causing the watch loop to fire 2-3 extra compile cycles on startup even with no user edits. With -o - (stdout output) this writes the compiled content 2-3x to stdout, corrupting downstream pipe consumers. Fix: content-based output dedup in the watch loop. - Add compile_to_content in build.rs: returns (content, deps) without writing. compile_and_write (used by build subcommand) is now a thin wrapper that always calls compile_to_content then write_output - build behavior is unchanged. - After the initial compile, record a baseline by re-compiling (quiet, no write) and storing the content in last_written keyed by resolved output path (or "<stdout>" sentinel for -o -). - In the watch loop: compile to content, compare with last_written, skip the write and "Recompiled" summary line when content is byte-identical. Dep set and watched dirs are still resynced regardless (ADR-016). - Flush stdout after each write to stdout so pipe consumers receive data before a potential SIGKILL in tests or pipeline teardowns. Regression tests added (QA-R1, QA-R2) in cli_watch.rs without -q so stderr/stdout noise is directly observable: - QA-R1: no edits at startup yields exactly 1 "Compiled to" and 0 "Recompiled" - QA-R2: -o - with no edits writes compiled content EXACTLY ONCE to stdout Both tests confirm: before fix stdout had 3x writes; after fix exactly 1. All 921 workspace tests pass, 0 regressions. Co-Authored-By: Claude <noreply@anthropic.com>
…build status line (#57) Mirror the file-mode content-dedup approach in run_watch_dir: maintain a HashMap<PathBuf, String> of last-written content keyed by resolved output path. Populate the baseline after startup compile (but after watcher registration) so synthetic FSEvents from macOS are recognised as no-ops. In-loop rebuilds compile to content first, compare, and skip write + summary when content is unchanged. Deletion path removes the entry from the dedup map. Add an announce: bool parameter to write_output. In-loop rebuild writes pass announce=false so only the "Recompiled" summary line is emitted. Startup compiles and mds build continue to pass announce=true. Add QA-R3: dir-mode no-spurious-startup-recompile integration test. Add QA-R4: single-status-line-per-rebuild integration test. All 923 workspace tests pass. Co-Authored-By: Claude <noreply@anthropic.com>
… - features/mds-cli/KNOWLEDGE.md + index.json: knowledge base for the CLI front-end (build/check/watch subcommands, output-path/mds.json-config/ runtime-vars resolution, the notify+ctrlc watch architecture with debounce and content-dedup, and the stdout/stderr stream contract / exit-code mapping). - decisions.md / pitfalls.md: ADR-019 (.devflow ignore-by-default tracking), ADR-020 (don't reset the worktree while background maintenance writes .devflow), PF-005 (git reset --hard + checkout deletes worktree copies of files untracked on the destination) — recorded by background maintenance during this work.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements
mds watchfor milestone v0.3.0 (closes #57). Watches.mdsfiles (or a directory) and auto-recompiles on save, enabling live-preview workflows without a build step on every change.Changes
New command:
mds watchmds watch template.mds): watches the entry file and all transitive@importdependencies. Editing any imported file triggers a recompile of the entry. Dep set is recomputed on every rebuild — never trusted from a stale prior state (applies ADR-016).mds watch src/): recursive watch; compiles each changed.mdsindependently. On source deletion, removes the matching output file. Does not do reverse-dep tracking (documented limitation).mds build:-o,--out-dir,--vars,--set,--format(single-file only), global--quiet.--clear(clear terminal before rebuild, TTY-guarded),--debounce <ms>(default 100, coalesces rapid saves).-o -.Recompiled <out> (<n> deps) in <ms>ms.Behavior-preserving refactor (AC-A7)
Build logic extracted from
main.rs(722 lines) intobuild.rswithpub(crate)helpers. The refactor is verified green against the full existing test suite before any watch code was added.New shared helper
compile_and_writeroutes both markdown and messages modes throughread_build_inputandcompile_with_deps, preserving the 10 MiB file-size cap on every code path (avoids PF-004: alternate output path silently bypassing a resource limit).resolve_output_path_no_createadded: pure path computation with nocreate_dir_allside effect, used by watch to compute deletion targets.New dependencies
notify8ctrlc3.5libc0.2libc::killBoth deps satisfy the project 30-day cooldown and MSRV 1.88 requirements.
notify-debouncer-fullintentionally NOT added — debounce is hand-rolled per the minimal-deps policy.Breaking Changes
None. The refactor is behavior-preserving; all existing
mds buildandmds checkbehavior is unchanged.Reviewer Focus Areas
PF-004 compliance (
build.rs:compile_and_write,read_build_input): both markdown and messages modes go through the sameread_build_inputenforcing MAX_FILE_SIZE. The testT-U6exercises the dep-return path; theresolve_output_path_no_createtest verifies no mkdir side effect.ADR-016 compliance (
watch.rs:run_watch_fileloop): after each successful rebuild,files_of_interestanddirs_to_watchare recomputed from fresh dep output, not from the stale prior set.Loop bounds:
drain_debounceterminates at adeadline = Instant::now() + duration(notwhile true). Thecollect_mds_filesrecursion is bounded bymax_depth=64. Both outer watch loops usewhile let Ok(msg) = rx.recv()(terminates on sender drop or Interrupt).Path canonicalization (
watch.rs): entry, vars file, and event paths are all canonicalized to handle macOS/tmp→/private/tmpsymlink differences.event_is_relevanttries both raw and canonical forms.Documented limitations (in
Commands::Watchdoc comment and README): no reverse-dep tracking in dir mode; entry parent-dir deletion loses the watch; stem collision in flat--out-dir.Test plan
cargo test --workspacepasses (912 tests, 0 regressions) — verified locallydirs_to_watch,files_of_interest,event_is_relevant,collect_mds_files,output_path_for,compile_and_writedep returncargo fmt --all --checkpassescargo clippy --workspace --all-targets -- -D warningspasses