diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..58c28f9 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,35 @@ +# pmacs agent instructions + +**Start here: read `docs/agent-handoff.md`, then +`docs/active-work.md`, before taking on any work.** The handoff carries +durable project state, working method, substrate invariants, and the +standing backlog. The active-work ledger carries volatile branches, +checkpoints, verification, and exact cross-machine recovery commands. +Keep both updated according to their own update protocols. + +Always true, independent of the handoff: + +- Rust core + Lua runtime (`builtin/runtime/*.lua`), TUI + GPU + (`pmacs-gpu`) frontends over a versioned semantic protocol + (`pmacs-protocol`). `#![forbid(unsafe_code)]`. +- Workflow: framing doc in `docs/` -> user approval -> branch -> implement + -> full gate suite -> PR -> user review rounds -> user says when to + merge. Never merge unprompted. One feature, one branch, one PR. +- Gates before any PR: `cargo fmt --check`; `cargo clippy --workspace + --all-targets -- -D warnings` (as its own step); `cargo test --lib`; + `cargo test --lib --features crdt`; the touched acceptance suites; + `cargo test --test m4_acceptance -- --skip basedpyright`; + `PMACS_REQUIRE_GPU=1 cargo test -p pmacs-gpu`; `git diff --check`. +- The checkout may be shared with the user: check `git status` for + foreign uncommitted work before stash, checkout, or branch operations, + and never delete untracked files you did not create. +- The canonical development URL is + `https://github.com/levineuwirth/pmacs.git`; recovery docs normalize + it to the local alias `githubsucks`. Remote names such as `origin` are + machine-local and carry no authority by themselves. Bootstrap/verify + the alias via `docs/active-work.md` before basing new work. +- Work is portable only after it is committed and pushed. Uncommitted + worktree changes, untracked files, and `/tmp` dependencies do not + travel to another machine. +- Write commit messages with `git commit -F `. Never use + `git add .`. diff --git a/CLAUDE.md b/CLAUDE.md index 1ede25a..58c28f9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,18 +1,19 @@ -# pmacs — agent instructions +# pmacs agent instructions -**Start here: read `docs/agent-handoff.md` before taking on any work.** -It carries current project state, the working method, substrate -invariants, and the standing backlog — it is the continuity bridge -between development machines. Keep it updated as part of your work -(update protocol is in the file itself). +**Start here: read `docs/agent-handoff.md`, then +`docs/active-work.md`, before taking on any work.** The handoff carries +durable project state, working method, substrate invariants, and the +standing backlog. The active-work ledger carries volatile branches, +checkpoints, verification, and exact cross-machine recovery commands. +Keep both updated according to their own update protocols. Always true, independent of the handoff: - Rust core + Lua runtime (`builtin/runtime/*.lua`), TUI + GPU (`pmacs-gpu`) frontends over a versioned semantic protocol (`pmacs-protocol`). `#![forbid(unsafe_code)]`. -- Workflow: framing doc in `docs/` → user approval → branch → implement - → full gate suite → PR → user's review rounds → user says when to +- Workflow: framing doc in `docs/` -> user approval -> branch -> implement + -> full gate suite -> PR -> user review rounds -> user says when to merge. Never merge unprompted. One feature, one branch, one PR. - Gates before any PR: `cargo fmt --check`; `cargo clippy --workspace --all-targets -- -D warnings` (as its own step); `cargo test --lib`; @@ -20,7 +21,15 @@ Always true, independent of the handoff: `cargo test --test m4_acceptance -- --skip basedpyright`; `PMACS_REQUIRE_GPU=1 cargo test -p pmacs-gpu`; `git diff --check`. - The checkout may be shared with the user: check `git status` for - foreign uncommitted work before stash/checkout/branch operations, and - never delete untracked files you didn't create. -- Commit messages via `git commit -F `, ending with the Claude - co-author line; PR bodies end with the Claude Code attribution. + foreign uncommitted work before stash, checkout, or branch operations, + and never delete untracked files you did not create. +- The canonical development URL is + `https://github.com/levineuwirth/pmacs.git`; recovery docs normalize + it to the local alias `githubsucks`. Remote names such as `origin` are + machine-local and carry no authority by themselves. Bootstrap/verify + the alias via `docs/active-work.md` before basing new work. +- Work is portable only after it is committed and pushed. Uncommitted + worktree changes, untracked files, and `/tmp` dependencies do not + travel to another machine. +- Write commit messages with `git commit -F `. Never use + `git add .`. diff --git a/docs/active-work.md b/docs/active-work.md new file mode 100644 index 0000000..c30e04e --- /dev/null +++ b/docs/active-work.md @@ -0,0 +1,137 @@ +# Active work — cross-machine resume ledger + +**Snapshot: 2026-07-21.** This file records volatile work that has not +landed on `main`. Read it after `docs/agent-handoff.md`. Remove completed +entries when their PR merges; do not let this become a second permanent +backlog. + +## Repository authority + +- Canonical development URL: + `https://github.com/levineuwirth/pmacs.git`. This ledger uses the + normalized local alias `githubsucks` so its refs and recovery commands + are identical on every machine. Remote names are otherwise + machine-local: `origin` may name this canonical URL, a release mirror, + or something else, and therefore has no authority by name alone. +- Canonical base at this snapshot: + `githubsucks/main` @ `2e37c04` (#127 merged; protocol v18). +- On the transfer source, `origin/main` named a release mirror at + `d3fa632` and lagged badly. On the current destination, `origin` names + the canonical URL. This difference is why all recovery begins by + verifying URLs and normalizing `githubsucks` rather than trusting + `origin/main`. +- The shared desktop checkout contained unrelated uncommitted work. The + branches below were prepared in isolated worktrees; never clean or + overwrite the shared checkout to recover them. + +Start on another machine by inspecting its remotes: + +```sh +git remote -v +git remote get-url githubsucks +``` + +If the second command says the alias is absent, add it; if it prints a +different URL, stop and resolve that collision rather than overwriting an +unknown remote: + +```sh +git remote add githubsucks https://github.com/levineuwirth/pmacs.git +``` + +Then recover current refs: + +```sh +git fetch githubsucks --prune +git log -1 --oneline githubsucks/main +git worktree list +git status --short --branch +``` + +The first command must expose `2e37c04` or a newer intentional main. +If it does not, stop and repair the remote/fetch configuration. + + +## Vterm Stage 2 framing lane + +- Portable branch: `githubsucks/vterm-framing` +- Approved framing head: `fb4f8f0` +- Base: canonical `main` @ `643d1e1` (Vterm Stage 1 / PR #126 merged). + `main` has since advanced to `2e37c04` (config registry #127, no + runtime overlap with vterm); cut the Stage 2 lane from current `main`, + not from `643d1e1`. +- State: `docs/vterm-framing.md` Revision 7 is framing-only, reviewed, and + approved for implementation. It closes the final `at_bottom`, terminal + `C-c` binding-reachability, and context-implicit Lua failure-mode findings. + There is no Stage 2 runtime implementation or PR yet. +- Next lane: create `pmacs-vterm-tui` / `vterm-tui` from current canonical + `main`, carry the approved framing as its first commit, then implement and + gate Stage 2. Do not implement on `vterm-framing`. + +Recovery worktree on a machine that does not already own the branch: + +```sh +git worktree add --track \ + -b vterm-framing \ + ../pmacs-vterm-framing \ + githubsucks/vterm-framing +``` + +## Parked lane: kill-ring browser + persistence + +- Portable branch: `githubsucks/kill-ring-browser` +- Parked framing head: `503c489` +- State: framing only, revision 2; no implementation and no PR. +- Status: explicitly parked by the user on 2026-07-20. +- Its original scout was based on `0efb5cd`. The preserved framing marks + this ground truth stale and requires a complete re-scout against the + then-current `githubsucks/main` before implementation. +- Compile-mode has merged since the original scout, so old + “compile-mode in flight” keybinding/touch-set assumptions are not + authoritative. + +Recovery worktree, only when the user un-parks it: + +```sh +git worktree add --track \ + -b kill-ring-browser \ + ../pmacs-kill-ring-browser \ + githubsucks/kill-ring-browser +``` + +## Documentation lane + +- Portable branch: `githubsucks/handoff-2026-07-20` +- Carries synchronized `AGENTS.md` / `CLAUDE.md`, this ledger, the + durable handoff refresh, and the keybinding reference correction. +- It changes no runtime code. +- Review and merge this documentation branch separately; it must not be + folded into a feature framing branch. +- Now also absorbs both landed arcs: Vterm Stage 1 (#126) and the config + registry (#127). Canonical `main` is merged into it up to `2e37c04`, + so its diff against `main` is documentation only. + +## Closed since the last snapshot + +- **Config registry — MERGED as #127** (`main` @ `2e37c04`). Its lane + (`config-registry`, worktree `../pmacs-config-registry`) is done; the + branch is kept but carries nothing unmerged. Durable substrate facts + moved to `docs/agent-handoff.md` §1 per rule 3 below. +- Both this and Vterm Stage 1 ran as **concurrent lanes in sibling + worktrees off `main`**, with the shared files (`src/editor.rs`, + `src/lua_bindings/mod.rs`, `src/lib.rs`) assigned to one lane each in + advance. The rebase of the second lane onto the first had **zero + conflicts** — worth repeating for future parallel work, along with its + precondition: agree the file split before either lane starts, and keep + each lane's footprint in the other's files to a single line. + +## Update protocol + +Whenever a listed lane changes materially: + +1. update its public branch and head/state here; +2. record new verification and remove superseded caveats; +3. keep durable architecture in `docs/agent-handoff.md`, not here; +4. remove the lane after merge or abandonment; +5. verify every recovery command from a clean worktree before calling + the transfer complete. diff --git a/docs/agent-handoff.md b/docs/agent-handoff.md index d811f1d..bb2fb8d 100644 --- a/docs/agent-handoff.md +++ b/docs/agent-handoff.md @@ -1,18 +1,66 @@ # Agent handoff — cross-machine continuity -**Last updated: 2026-07-21, after Vterm Stage 1 review round 2 was addressed -and fully gated on `vterm-core` (awaiting merge authorization; not merged). -Vterm Stages 2 and 3 are not implemented.** This file is the bridge between development -machines. If you are an agent reading on a fresh clone: this document -plus the `docs/*-framing.md` files ARE your memory. Read this fully -before taking on work, seed persistent memory from it, and **update this -file (and commit it) whenever project state changes materially** — the -next machine reads it the way you just did. +**Last updated: 2026-07-21, after the config registry (#127) and Vterm +Stage 1 terminal core (#126) both landed on `main`, atop completed +Themes Arc 4 (#120/#124/#125). Vterm Stages 2 and 3 are not +implemented.** +This file is the +bridge between development machines. If you are an agent reading +this on a fresh clone: this document plus the `docs/*-framing.md` +files ARE your memory. Read this fully before taking on work, seed +your persistent memory from it, and **update this file (and commit +it) whenever project state changes materially** — the next machine +reads it the way you just did. + +For volatile branches, checkpoints, verification, and recovery +commands, read `docs/active-work.md` immediately after this file. ## 1. Where the project stands (2026-07-21) -- Canonical `main` @ `7bc0c61` (#125 merged), protocol - **v18** (`SUPPORTED=[6..18]`). +- `main` @ `2e37c04` (config registry #127), protocol **v18** + (`SUPPORTED=[6..18]`; v16 = `ThemeFacts`, v17 = `FontFacts`, v18 = + `StatuslineSegments`). +- **Config registry LANDED — #127** (`docs/config-registry-framing.md` + rev 3; merge `2e37c04`; two review rounds). `pmacs.config` is the + typed, introspectable options registry the backlog ranked first, and + it closes the "config-registry-blocked" deferrals below. It was built + as a PARALLEL LANE alongside vterm in a sibling worktree; the files + were assigned per-lane up front and the rebase had zero conflicts. + - **Third registry** beside `CommandRegistry`/`HookRegistry` + (`src/config_registry.rs`), same R42/R50/duplicate-rejection/ + `SourceLocation` vocabulary; Lua surface in + `src/lua_bindings/config.rs`. **No protocol change (still v18) and + ZERO changes to `src/editor.rs`.** + - **An override is ALWAYS stored**, even when equal to the value it + shadows; only `value_epoch` and listener dispatch key on effective + change. The "equal-value set is a no-op" reading silently voids a + buffer-local pin: nothing is stored, and a later global `set` flips + the very buffer the user pinned. + - **Two scopes: global and buffer-local.** `get(name, buf)` resolves + local → global → default; **`get(name)` resolves the GLOBAL CHAIN + ONLY** and never consults an ambient buffer. Per-language and + per-project are *patterns* (a hook calling `set_local`), not scopes + the registry knows about. Mode scope is impossible until the mode + system is wired — every editor `KeymapStack::resolve` passes `&[]`. + - Buffer-locals live in a registry side table purged at + `after_buffer_removed`, beside the keymap purge. + - Listeners: commit → snapshot → **drop the borrow** → re-enter Lua; + a raising listener is logged without blocking the rest or rolling + back; a depth bound turns a cycle into a pointed error. **Explicit + dispose only** — there is no `MetaMethod::Gc` anywhere in the + codebase, and GC timing differs between the two Lua backends. + - `StartupOnly` freezes off the existing `InitCompleteFlag` at write + time (which is why no `editor.rs` call was needed). In `--lib` + builds `set_init_complete` never runs, so a post-freeze test must + flip the flag explicitly or it passes vacuously. + - Adopters own their own `define`, so `SourceLocation` names the + owning module: `editing.auto-pair` (pair.lua, read against the + typed edit's SOURCE buffer), `editing.trim-on-save` (editops.lua, + read against the buffer being saved), `autosave.interval-ms` + (autosave.lua, re-read per tick). **The migration wrappers keep + their legacy coercion** — the registry is strict, the legacy setters + stay lenient (`trim_on_save("yes")`, `interval_ms(1500.7)`). + - `M-x describe-setting` renders into `*help*`. - **Syntax-highlight / language-detection side-quest (#114–#118) LANDED** — a one-shot arc built in sibling worktrees off main while the user's themes lane (`theme-faces`) ran concurrently in the shared @@ -69,6 +117,16 @@ next machine reads it the way you just did. `inline` node; matches tree-sitter-md's own splitter), and the wire flattener runs over the WHOLE buffer via the file-style summary, so it must be an event sweep, not O(spans²). +- **JSON + YAML grammars and language servers (#123) LANDED** — bundled + ABI-current `tree-sitter-json` / `tree-sitter-yaml` cover `.json`, + `.yaml`, and `.yml`; the existing injection engine now highlights YAML + frontmatter and JSON/YAML fences. Default external LSP configs are the + pinned `vscode-json-language-server` provider and + `yaml-language-server`; configured settings are pushed after + `initialized`, which also supports push-model servers. The fake-server + delivery proof and PATH-gated live JSON/YAML provider smokes cover the + configuration contract. `.jsonc` / `.json5` remain a deliberate + follow-up because the JSON grammar is strict. - **Compile-mode (Arc 5 stage 1, #113) LANDED** (2026-07-14, 7 rounds; framing `docs/compile-mode-framing.md` rev 13). `compile.run` streams `/bin/sh -c "exec 2>&1; "` into an intercept-read-only @@ -120,13 +178,12 @@ next machine reads it the way you just did. workspace sweep 2,718 passed across 78 suites (19 ignored, `basedpyright` filtered); `git diff --check` clean. Stage 3 landed as #125 and completed Arc 4 on `main`. -- **Vterm Stage 1 terminal core IMPLEMENTED ON `vterm-core`, FULLY GATED, - AWAITING MERGE AUTHORIZATION, NOT MERGED** (`docs/vterm-framing.md` rev 5). +- **Vterm Stage 1 terminal core LANDED ON `main` — #126** + (`docs/vterm-framing.md` rev 5; merge `643d1e1`). - Implementation commits: `bbc1f33` (Stage 1), `962944b` (Darwin signal normalization), first-review fixes `f0a235f`, `28f2e6c`, `bf972a7`, and - second-review hardening `9797ada`; pull request: #126, - (open, non-draft, - targeting `main`). + second-review hardening `9797ada`; reviewed feature head `fc4e0ce` merged + through PR #126, . - `AnsiParserProfile::{LineOriented, FullScreen}` preserves compile/REPL behavior while terminal PTYs emit the full cursor/mode/device operation set. `src/terminal/{screen,input,session}.rs` owns the state machine, @@ -174,6 +231,10 @@ next machine reads it the way you just did. authenticated source routing, protocol-owned wire types/limits, and a deliberate complete-frame limit decision: 16 MiB is insufficient; use a measured legal-worst cap or aggregate bound, never silent chunking. +- **PARKED: kill-ring browser + persistence.** Revision 2 framing is + preserved on branch `kill-ring-browser`, but its `0efb5cd` scout is stale + and must be repeated before implementation. No PR or implementation is + active. - Roadmap: `docs/roadmap-2026-07.md` (ranked arcs). Position: - **Arc 1 (LSP utility surface) COMPLETE** — completion popup (#92/#93), panels/references/outline/hover (#94–#96), plus @@ -184,6 +245,16 @@ next machine reads it the way you just did. - **Arc 3 (persistence) COMPLETE** — saveplace/recentf (#98), desktop-save (#99), autosave/crash-recovery (#100), save-clobber fix (#101). + - **Arc 4 (themes + extensibility) COMPLETE** — named UI faces (#120), + live GPU font preferences (#124), statusline providers (#125). + - **Arc 5 terminal stage ACTIVE** — compile mode (#113) and Vterm terminal + core (#126) landed; Vterm TUI is the next formal stage. + - **Config registry COMPLETE (#127)** — not a numbered arc; it was the + cross-cutting substrate ranked first on + `docs/side-quest-backlog.md`'s north star, and it unblocks the + editing/indent/comment items that were config-blocked. + - Remaining ranked arcs: 6 folding, 7 DAP, 8 GPU splits, plus the + `.ipynb` arc (its JSON-grammar prerequisite shipped in #123). ## 2. How we work (the part that must not drift) @@ -292,8 +363,10 @@ buffer owns a path's recovery slot; only recover/discard release unclaimed crash data; adopt clears the old owner's skip cache. **Protocol** — encoding-breaking bumps are deliberate and versioned -(`SUPPORTED=[6..15]`). v15 = `CompletionPopup` + `StatusFacts.message`. -New wire surface ⇒ bump + both-frontends support + acceptance. +(`SUPPORTED=[6..18]`). v15 = `CompletionPopup` + +`StatusFacts.message`; v16 = `ThemeFacts`; v17 = `FontFacts`; v18 = +`StatuslineSegments`. New wire surface ⇒ bump + both-frontends support + +acceptance. **Fake LSP** (`src/bin/pmacs_fake_lsp.rs`) modes: `fullonly`, `rangeonly`, `rangeonly16` (UTF-16 + fail-closed bounds validation), @@ -337,6 +410,28 @@ New wire surface ⇒ bump + both-frontends support + acceptance. in per-session baselines; and any daemon-side reset needs its frontend mirror audited in the same round (the GPU snapshot arm missed search/menu/status the first time). +- **Tab width is a rendering-parity bug, NOT a config gap** (scouted at + `7bc0c61` while framing #127; still true). There are FIVE tab-width + sites across TWO crates with TWO different values: `TAB_WIDTH = 8` in + `src/text_view.rs`, `src/highlight.rs`, `src/diag.rs` and + `src/completion.rs`, versus `advance_minimap_col` in + `pmacs-gpu/src/main.rs` expanding to **4** — and the GPU's main text + path expands tabs *not at all* (buffer bytes reach the frontend raw, + so a literal `\t` is shaped by the font). `editor.tab-width` is + therefore the obvious-looking first config adopter and is not one: + defining the setting cannot make the GPU honor it. Doing it properly + needs frontend tab expansion plus a wire-or-frontend-local decision. + Deferred from #127 on exactly these grounds; don't re-plan it as a + config task. +- **A test that never runs passes.** Two #127 review-round tests passed + vacuously at first: `pmacs.editor.save()` is the RAW save, while + `buffer.before-save` fires inside the `buffer.save` COMMAND + (`builtin/commands/default.lua`), and `save()` no-ops on an + unmodified buffer — so a fixture that opens a file and saves it + asserts on bytes nothing rewrote. Dirty the buffer with a real edit + and go through `pmacs.command.invoke("buffer.save")`. Caught only + because the *other* case failed and the cause was chased instead of + the assertion adjusted. ## 6. Named deferrals (the standing backlog, consolidated) @@ -347,8 +442,9 @@ mid-line comment spans, comment-dwim append-at-EOL, per-language comment padding. Pairing (framing "Deferred"): wrap-region on opener, pair-aware backspace, RET-inside-pair closer-on-own-line, in-string/in-comment inhibit (needs node-at-byte `pmacs.parse`), -undo amalgamation (pair = one step), balance-aware quotes, -per-buffer toggle (config-registry-blocked). Editops deferrals (full +undo amalgamation (pair = one step), balance-aware quotes +(the per-buffer toggle SHIPPED in #127 as `editing.auto-pair`). +Editops deferrals (full list in its framing): recenter (blocked on viewport facts — the GPU never consumes daemon `view_top`), Unicode case/word classes, region-spanning move/duplicate, locale collation for sort-lines, @@ -364,7 +460,20 @@ origin-pinned `buffer.after-edit` fan-out (a context-switching intercept changes what later callbacks — LSP, completion — observe). LSP/persistence: hidden-buffer LSP attach, daemon desktop-restore, the *warning* half of external-change detection (verify-visited-file- -modtime), config registry (no unified config surface yet). +modtime). +Config registry (SHIPPED #127; these are its own named deferrals): +persistence of settings and the `custom-file` split-brain question, +`M-x list-settings` as a listview panel, a settings completion source +for the minibuffer (`minibuffer.read`'s `source` is a fixed Rust-side +vocabulary), table-valued settings (so `pmacs.lsp.config`, +`pmacs.pair.sets`, `pmacs.comment.strings` and the `pmacs.parse.*` +write-through proxies stay raw Lua), migrating the remaining scalar +setters (`async_config` ×2, `killring.max`, the `enable` booleans) and +`pmacs.gpu.set_font`, pending-set staging for names defined after +`init.lua` runs, and a `scope = "global"` define flag — `set_local` is +currently accepted for `autosave.interval-ms`, where a per-buffer +value is meaningless. +**Tab width is NOT a config gap** — see §5. Highlight/detection (from the #114–#118 side-quest + injections #122): locals-query processing (run each grammar's LOCALS_QUERY so `#is?`/`#is-not? local` is honored instead of the current fail-closed @@ -377,18 +486,11 @@ runtime/Lua-registered languages (v1 resolves only against `BUILTIN_LANGUAGES`), and the next injection *consumers* gated on new grammars — HTML/CSS/GraphQL/SQL (`