Skip to content

feat: perry dev — V1 watch mode (auto-recompile + relaunch on save)#126

Merged
proggeramlug merged 2 commits intoPerryTS:mainfrom
TheHypnoo:feat/watch-mode
Apr 22, 2026
Merged

feat: perry dev — V1 watch mode (auto-recompile + relaunch on save)#126
proggeramlug merged 2 commits intoPerryTS:mainfrom
TheHypnoo:feat/watch-mode

Conversation

@TheHypnoo
Copy link
Copy Markdown
Contributor

Summary

New perry dev <entry.ts> subcommand: watches the project tree, recompiles on save, and relaunches the compiled binary. V1 — no caching, just "save → fast restart".

The goal is to close the gap with Node's tsx watch / Vite's dev UX for Perry users, without waiting for the deeper incremental-compilation work. This PR is the minimal shippable slice; two larger follow-ups are called out at the bottom.

What's in scope

One commit (v0.5.142), self-contained:

  • crates/perry/src/commands/dev.rs — new subcommand. Uses the notify crate (already in workspace deps) with a 300 ms debounce window so editor "save storms" collapse into one build.

  • Watches the nearest project root discovered by walking up from the entry file until a package.json or perry.toml is found; falls back to the entry's parent directory. Extra roots accepted via --watch.

  • Trigger extensions: .ts | .tsx | .mts | .cts | .json | .toml. Ignored directories (never watched, never retrigger): node_modules, target, .git, dist, build, .perry-dev, .perry-cache.

  • Rebuilds by calling super::compile::run() directly — no subprocess, no startup overhead. Kills the previous child before overwriting the binary (avoids the link step racing a running process on macOS/Linux and hard-failing on Windows).

  • Output defaults to .perry-dev/<stem> so dev artifacts don't pollute the CWD. Args after -- forward to the child: perry dev src/main.ts -- --port 3000.

  • 8 unit tests for the pure helpers:

    • is_trigger_path — extension allowlist + IGNORED_DIRS traversal (the filter most likely to silently break the feature).
    • is_relevantModify / Create / Remove pass, Access and errors rejected.
    • find_project_root — finds package.json / perry.toml, falls back to start when no marker exists.
    • tempfile added as a [dev-dependencies] (not shipped in release).
  • Wiring: Commands::Dev(DevArgs) variant, "dev" added to the legacy-invocation matcher so perry file.ts still works. No telemetry on dev (sessions trigger many recompiles — would be noisy).

Benchmark

Smoke-tested locally on /tmp/perry-dev-smoke/main.ts (trivial console.log):

Phase Time
Initial build (cold auto-optimize: runtime + stdlib .a rebuild) ~15 s
Post-edit rebuild (hot libs cached, recompile only the entry) ~330 ms
Debounce window (swallows follow-up save events) 300 ms

The 45× speedup on hot rebuilds comes entirely from Perry's existing auto-optimize library cache — nothing in this PR re-engineers the build pipeline. That's also the ceiling: a trivial one-file program rebuilds in ~330 ms because that's the cost of LLVM codegen + link for one .ts file with libs already on disk. Real projects with multiple modules will recompile all of them on every change, which is where the follow-up work pays off.

What's explicitly NOT in this PR

V1 is deliberately thin — ship something useful today, iterate.

  • In-memory AST cache — reuse SWC parses across rebuilds. Planned for V2.
  • Per-module .o cache on disk — only re-codegen the changed module, relink the rest. Planned for V2 (this is where the real win is for non-trivial projects).
  • State preservation across rebuilds — V3 idea (something like a saveState/loadState hook so long-running dev sessions don't lose in-memory counters etc. on rebuild). Needs design work; not in scope.
  • Hot module replacement — not planned. HMR requires dynamic code loading Perry doesn't have; "fast restart" is the honest target.

Happy to fold V2 into this PR instead of a follow-up if that's preferred — let me know. Splitting it that way because the caching work touches the compile pipeline and is materially more invasive than what's here.

Test plan

  • cargo test -p perry --bin perry commands::dev — 8/8 pass
  • cargo check -p perry — clean (only pre-existing warnings)
  • Smoke test: edit .ts, observe rebuild log + relaunched child stdout
  • perry dev nonexistent.ts → clear error, no panic
  • perry dev src/main.ts -- --port 3000 → child receives args

TheHypnoo and others added 2 commits April 22, 2026 03:18
…0.5.143)

New `perry dev <entry.ts>` subcommand: watches the nearest project root
(package.json / perry.toml, falls back to entry's parent) recursively via
the `notify` crate, debounces 300ms, recompiles on any
.ts|.tsx|.mts|.cts|.json|.toml change, kills the running child, and
relaunches the fresh binary. Ignores node_modules, target, .git, dist,
build, .perry-dev, .perry-cache. Output defaults to .perry-dev/<stem>.
Args after `--` forward to the child (perry dev src/main.ts -- --port 3000).

Smoke-tested: initial build ~15s (cold auto-optimize), post-edit rebuild
~330ms (hot libs cached).

8 unit tests cover the pure helpers (is_trigger_path, is_relevant,
find_project_root). tempfile added as dev-dep.

V2 follow-ups planned: in-memory AST cache + per-module .o reuse for
incremental compilation.

(Rebased onto main and bumped from v0.5.142 → v0.5.143; the 0.5.142
slot was already taken by two fixes on main — fs/roundtrip cross-platform
path and async-closure Promise return — between PR author and merge.)
PR PerryTS#126 shipped the `perry dev` watch-mode subcommand but not the docs
page. Adding it on the contributor's behalf as part of the merge so the
CLI reference stays in sync with what ships.

Covers: usage examples, flag table, mechanics (project-root discovery,
debounce, kill-then-rebuild order), trigger extension allowlist and
ignored-directory list, benchmark table, and the V2 incremental-cache
roadmap called out in the PR description.
proggeramlug added a commit that referenced this pull request Apr 22, 2026
Surfaced by PR #126 (first external contributor): main shipped two
v0.5.142 commits while the PR was open, and the PR's own v0.5.142 bump
turned into a merge conflict that had nothing to do with the feature.

Fix the rule rather than the symptom: external contributors leave
`[workspace.package] version`, the `**Current Version:**` line in
CLAUDE.md, and the "Recent Changes" entry alone. Maintainer folds that
metadata in at merge time via rebase + amend.

No code change, no version bump for this commit — it's pure policy
documentation.
@proggeramlug proggeramlug merged commit 814b4e4 into PerryTS:main Apr 22, 2026
8 checks passed
@proggeramlug
Copy link
Copy Markdown
Contributor

Merged. Thanks enormously, @TheHypnoo — this is Perry's first external PR and a genuinely solid one to start with. The debounced-watcher design, the pure-helper testing split, and the .perry-dev/ output convention (out of the way, self-excluded from the watcher) were all exactly right on the first try. The 8/8 tests passed clean on my local rebase and through CI, and cargo check -p perry stayed tidy.

Two small notes on the merge mechanics so the force-push history doesn't look mysterious:

  1. I rebased your branch onto current main and bumped the version from 0.5.142 → 0.5.143 because main had already shipped two 0.5.142 commits between your open-date and merge-day (a cross-platform fs/roundtrip fix and an async-closure Promise fix). The force-with-lease was scoped to exactly that.
  2. I added a docs/src/cli/commands.md entry for perry dev on top of your feature commit — same PR, separate maintainer commit so your authorship stayed clean. Your original commit is still attributed to you.

Off the back of your PR we also carved out a repo policy (coming shortly in a follow-up commit): external contributors should leave [workspace.package] version, the **Current Version:** line in CLAUDE.md, and the "Recent Changes" entry alone — the maintainer folds those in at merge time. That's exactly the friction you hit when the version number raced ahead of your PR, and we'd rather not have the next contributor hit it.

The V2 follow-ups you called out in the PR (in-memory AST cache, per-module .o reuse) would be massively welcome — those are where the real ceiling is for non-trivial projects. No pressure, no timeline, but if you're up for another round, we'd be glad to have you back. If you do pick something up, ping in a new PR or issue and we'll help scope.

Thanks again for the excellent bring-up.

proggeramlug added a commit that referenced this pull request Apr 22, 2026
First inbound external PR (#126) landed today, and a second substantial
one (#127) is in flight — time to make the project legible as a real
community project instead of a solo push.

Adds:
- CONTRIBUTING.md — build-from-source prereqs derived from
  .github/workflows/test.yml, PR guidelines (including the "don't bump
  version or touch CLAUDE.md" rule we carved out this week), conventional-
  commit guidance as loose convention, how to claim an issue, pointers to
  good-first-issue / help-wanted labels.
- CODE_OF_CONDUCT.md — Contributor Covenant 2.1 verbatim, contact email
  set to ralph@skelpo.com. Fetched directly from the EthicalSource canon.
- .github/ISSUE_TEMPLATE/bug_report.md — requires Perry version, target,
  host OS, and a minimal repro.
- .github/ISSUE_TEMPLATE/feature_request.md — problem / proposed solution
  / alternatives / scope estimate.
- .github/ISSUE_TEMPLATE/config.yml — disables blank issues, routes
  questions to Discussions and security reports to email.
- .github/pull_request_template.md — leads with "do NOT bump version or
  edit CLAUDE.md" so the next contributor doesn't retrace #126's
  version-collision friction.

Explicitly NOT doing (decided earlier in the design pass):
- MAINTAINERS.md — skipped until there's a real list beyond "Ralph,
  everything".
- All Contributors bot — defer until contributor volume justifies the
  badge wall + permanent bot webhook.
- Community docs page inside mdBook — would duplicate CONTRIBUTING.md
  and go stale.

No DCO / CLA required; commit style is guidance-only, not enforced.
No code or build-config changes.
@TheHypnoo TheHypnoo deleted the feat/watch-mode branch April 22, 2026 07:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants