mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2026-08-24 02:34:19 -05:00
chore(skills): embed repo skills in .agents/skills
.agents/skills is the vendor-neutral Agent Skills location that Copilot, Codex, Claude Code, and most other agents discover automatically, while .github/skills is read by Copilot alone. Move the embedded skills there and add project-knowledge, a committed per-topic memory of verified gotchas (codebase, shells, terminal, testing). The directory stays gitignored for apm-managed skills; only the three embedded skills are allowlisted, mirroring the former .github/skills pattern. AGENTS.md now tells agents to read the relevant topic before starting work and to commit new findings alongside the change they relate to. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Entire-Checkpoint: 70fe8f468dbe
This commit is contained in:
committed by
Jan De Dobbeleer
co-authored by
Claude Fable 5
parent
0a0774dff3
commit
60232851e0
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: project-knowledge
|
||||
description: >
|
||||
Accumulated project memory: verified gotchas and prior findings for oh-my-posh work. Consult
|
||||
BEFORE touching shell integration scripts (zsh, pwsh, fish, bash, cmd/Clink), terminal or pty
|
||||
behavior, WSL-based shell testing, or internals (cache, segments, streaming, serve daemon).
|
||||
Read only the topic files relevant to the task at hand.
|
||||
---
|
||||
|
||||
# Project Knowledge
|
||||
|
||||
A modular memory of hard-won, verified facts about this codebase and its runtime environments.
|
||||
Everything here was learned the expensive way - through debugging, benchmarking, or reproduction -
|
||||
and is not derivable from a quick read of the code.
|
||||
|
||||
## How to use
|
||||
|
||||
1. Identify which topics the task touches (a zsh script change touches `zsh` and probably
|
||||
`testing`; a segment bug touches `codebase`).
|
||||
2. Read those reference files before writing code or designing an experiment.
|
||||
3. Treat entries as point-in-time observations: they carry dates, and code moves. Verify a claim
|
||||
against the current code before building on it, and fix the entry when it drifted.
|
||||
|
||||
## Topics
|
||||
|
||||
| Topic | Read when |
|
||||
| ------------------------------------ | ---------------------------------------------------------------------- |
|
||||
| [codebase](references/codebase.md) | Touching Go code: segments, cache, templates, streaming, serve daemon |
|
||||
| [zsh](references/zsh.md) | Touching `omp.zsh`, zle widgets, coproc, or zsh plugin interop |
|
||||
| [pwsh](references/pwsh.md) | Touching `omp.ps1`, PSReadLine, runspaces, events, or pwsh perf |
|
||||
| [fish](references/fish.md) | Touching `omp.fish`, fish jobs, fifos, or fish event handlers |
|
||||
| [bash](references/bash.md) | Touching `omp.bash`, PROMPT_COMMAND, readline, or bash coprocs |
|
||||
| [cmd-clink](references/cmd-clink.md) | Touching `omp.lua`, Clink integration, or Windows pipe lifecycles |
|
||||
| [terminal](references/terminal.md) | Reasoning about ptys, ConPTY, Windows Terminal, or terminal encoding |
|
||||
| [testing](references/testing.md) | Building a harness to drive a shell end-to-end (WSL, zpty, script(1)) |
|
||||
|
||||
## How to maintain
|
||||
|
||||
This is a living memory - extend it whenever a session ends with knowledge worth keeping:
|
||||
|
||||
- Add durable, **verified** facts only: gotchas, platform quirks, measured numbers, failed
|
||||
approaches worth not retrying. No speculation, no session-specific state.
|
||||
- Date non-obvious claims (`verified 2026-07-14`) so future readers can judge staleness.
|
||||
- One topic per file. Append to the matching reference file; create a new file and index row when
|
||||
a fact fits no existing topic.
|
||||
- Prefer updating or deleting a stale entry over stacking corrections on top of it.
|
||||
- Keep entries self-contained: name the file, function, or command they apply to.
|
||||
- This skill is committed - include knowledge updates in the commit of the change they relate to.
|
||||
@@ -0,0 +1,34 @@
|
||||
# bash
|
||||
|
||||
Verified on bash 5.2 (2026-07).
|
||||
|
||||
## Readline and prompt
|
||||
|
||||
- Running the `exec` builtin from PROMPT_COMMAND - even a harmless `exec {fd}</dev/null` -
|
||||
**silently disables readline for the whole session**: the prompt never paints again, no error,
|
||||
commands still execute non-interactively. Use coproc fds directly; never exec-dup or exec-close
|
||||
from prompt context.
|
||||
- PS1 must stay `'$(_omp_get_primary)'` (single-quoted). With promptvars on, literal prompt
|
||||
content executes `$(...)` - a directory named `$(cmd)` becomes command injection.
|
||||
- "Expansion returns correct bytes" is NOT "prompt displays" - always capture a real typescript
|
||||
(`script -qe -c ... FILE`) when verifying prompt behavior. Note `script(1)`'s stdout relay does
|
||||
not carry bash prompt bytes (zsh's does); probe `${PS1@P}` in-session instead.
|
||||
|
||||
## coproc
|
||||
|
||||
- `coproc NAME { cmd; }` reports the pid of a wrapper subshell - `exec` in the body so the daemon
|
||||
replaces it.
|
||||
- The coproc's ORIGINAL fds must be closed after duplication
|
||||
(`eval "exec ${NAME[0]}<&- ${NAME[1]}>&-"`) or the child's stdin never EOFs when the dups close.
|
||||
- Subshells close coproc fds in non-interactive bash - a `(trap '' PIPE; ...)` subshell guard does
|
||||
not work there; save/ignore/restore `trap '' PIPE` in the parent instead (nothing forks while
|
||||
ignored).
|
||||
- bash prints failed `>&fd` redirection errors before later redirections apply - put
|
||||
`2>/dev/null` BEFORE `>&"$fd"`.
|
||||
|
||||
## History
|
||||
|
||||
- A bash serve daemon was implemented and **reverted** (2026-07-07): no measurable speedup -
|
||||
native-Linux spawns are 11-16ms and sync-only wait-mode plus the display-time subshell cannot
|
||||
beat that. Do not re-propose without new evidence. The gotchas above came out of that work and
|
||||
remain valid.
|
||||
@@ -0,0 +1,25 @@
|
||||
# cmd / Clink
|
||||
|
||||
## Lua and pipes
|
||||
|
||||
- Clink's `io.popenrw` reads are BLOCKING with no peek/timeout in Clink Lua (verified in
|
||||
`io_api.cpp`). Any protocol consumed from Lua must guarantee a fixed number of records per
|
||||
request - the serve wait-mode contract (exactly 2 records, upheld by Go's `renderComplete` even
|
||||
on segment panic) exists for this.
|
||||
- `io.popenrw` runs the command via `%COMSPEC% /c`, so `2>nul` works in the command string and is
|
||||
REQUIRED - the child's stderr otherwise inherits the console and corrupts the display.
|
||||
- Clink creates its pipes `_O_NOINHERIT` and only dups the child ends inheritable
|
||||
(`pipe_pair::init`), so cmd's death guarantees the daemon's stdin write handle closes.
|
||||
|
||||
## Windows lifecycle
|
||||
|
||||
- There is no SIGPIPE on Windows - stdin EOF is a daemon's ONLY exit signal. Design teardown
|
||||
around fd closure, not signals.
|
||||
- The cmd feature line for the daemon is `serve_enabled = true` (Streaming feature in
|
||||
`src/shell/cmd.go`).
|
||||
|
||||
## Testing
|
||||
|
||||
- `luac -p` for syntax; a Lua harness with a stubbed Clink API covers logic
|
||||
(lua.exe via `winget install DEVCOM.Lua`, Clink via `winget install chrisant996.Clink`).
|
||||
- Clink itself cannot run headless - live smoke tests stay manual.
|
||||
@@ -0,0 +1,62 @@
|
||||
# Codebase
|
||||
|
||||
## Dev environment
|
||||
|
||||
- The Go module root is `src/`, not the repo root - run all `go` commands from there.
|
||||
- On windows/arm64 dev machines `go test -race` is NOT supported. Concurrency-sensitive changes
|
||||
must rely on CI (amd64) for race detection.
|
||||
- Rendering hot-path benchmarks live in `src/template/bench_test.go`,
|
||||
`src/terminal/bench_test.go`, and `src/prompt/bench_test.go`; compare runs with `benchstat`.
|
||||
- `template.Init` resets the parsed-template cache - a macro benchmark that calls it per iteration
|
||||
measures the cold-parse path, not steady state.
|
||||
|
||||
## Shell integration scripts
|
||||
|
||||
- Everything under `src/shell/scripts/` is **embedded at build time** (`go:embed`). After editing a
|
||||
script, rebuild the binary before testing; `oh-my-posh init <shell> --print` shows the generated
|
||||
output and is the fastest way to inspect what a user actually sources.
|
||||
- Features (transient prompt, tooltips, vi mode, streaming) are emitted per shell from
|
||||
`src/shell/<shell>.go` - a script function is dead code unless the feature switch emits its
|
||||
activation line.
|
||||
|
||||
## Segments and panics
|
||||
|
||||
- Segment `Execute` runs in bare goroutines with **no recover** (`src/prompt/segments.go`), and
|
||||
template rendering re-panics runtime errors. Any panic there kills the whole process - the user
|
||||
sees a completely blank prompt. So when a user reports a blank prompt: find the panic.
|
||||
- If the panic trigger persists (e.g. a poisoned cache entry with a TTL), every prompt crashes
|
||||
until the entry expires.
|
||||
- Segment writers gob-encode only exported fields. `segments.Base.env/options` are unexported and
|
||||
MUST survive a cache restore: overlay the restored data onto the writer initialized by
|
||||
`MapSegmentWithWriter`, never replace the writer.
|
||||
|
||||
## Cache
|
||||
|
||||
- Cache persistence only happens with the hidden `--save-cache` flag (print/stream commands);
|
||||
without it, stores never write on close. Redirect the location with `OMP_CACHE_DIR`.
|
||||
- Debug logs are buffered and only printed by the `oh-my-posh debug` command (grep for
|
||||
`restored segment from cache` / `setting entry`). `POSH_TRACE=1` and stderr show nothing for
|
||||
print commands.
|
||||
- On Windows the cache file is a memory-mapped 50KB+5 "persistent shared string" with a 4-byte
|
||||
length header; a fresh file is all zeros and logs a harmless `store.go:init EOF` error on first
|
||||
read.
|
||||
|
||||
## Streaming and serve daemon
|
||||
|
||||
- Streaming is enabled by the top-level `"streaming": <ms>` config key. That value is ALSO each
|
||||
segment's pending-timeout and overwrites segment-level `timeout`.
|
||||
- `stream` always emits the transient prompt as a `\x1e`-prefixed NUL record (initial + refreshed
|
||||
once all segments resolve); serve records are `<id>\x1f<payload>\0`.
|
||||
- Serve pitfall class: process-lifetime initializers (`if X != nil return`) pin first-render state
|
||||
in a daemon. `template.Cache` did exactly that (pinned PWD/Folder/Code/Jobs) - fixed with
|
||||
`template.ResetCache()` per render in `startRenderCycle` (flag-based rebuild; never nil the
|
||||
global, abandoned segment goroutines may still read it). Audit for this pattern when extending
|
||||
serve.
|
||||
- Do NOT memoize the config in serve: the per-render gob decode is load-bearing - a fresh segment
|
||||
graph per cycle isolates the active render from abandoned-cycle goroutines holding pointers into
|
||||
their own graph.
|
||||
- Daemon tests must vary per-request context (cwd, status) across cycles; single-context tests
|
||||
cannot catch one-shot-assumption state.
|
||||
- `config.Get` prefers the session gob cache over `POSH_THEME`.
|
||||
- Go guarantees exactly 2 records per wait-mode serve request even on segment panic
|
||||
(`renderComplete`) - blocking clients (Clink) rely on this.
|
||||
@@ -0,0 +1,29 @@
|
||||
# fish
|
||||
|
||||
Verified on fish 4.1.2 in WSL (2026-07).
|
||||
|
||||
## Process and job model
|
||||
|
||||
- fish does NOT fork a backgrounded pipeline stage that is a *function* - it blocks the main
|
||||
shell. Run readers as an external process: `fish --no-config -c $script args &`.
|
||||
- fish 4.x `jobs --last --pid` prints a "Process" header plus one pid per pipeline stage - filter
|
||||
with `string match --regex '^\d+$'` and treat the result as a list.
|
||||
- `kill -0 0` always succeeds (signals the caller's own process group) - guard zero pids
|
||||
everywhere before liveness checks.
|
||||
|
||||
## Fifos and lifecycle
|
||||
|
||||
- A fifo write with no reader blocks forever - liveness-check the reader before every fifo write.
|
||||
- The serve daemon opens its request fifo O_RDWR so fish's open-write-close pattern never EOFs it;
|
||||
consequence: SIGKILL of fish orphans daemon and reader permanently (no EOF, no SIGPIPE ever).
|
||||
Normal teardown runs via the `fish_exit` event handler (quit + kill the pipeline).
|
||||
- `--on-signal SIGUSR1` handlers fire between commands in non-interactive scripts (verified with a
|
||||
200ms poll loop).
|
||||
|
||||
## Testing quirks
|
||||
|
||||
- fish under `script(1)` discards/paste-buffers piped typeahead, so in-session probes never
|
||||
execute - a harness limitation, not a bug (zsh under the same setup is fine). Assert via files
|
||||
written by event handlers instead.
|
||||
- The streaming transient prompt is cached in a tempfile (`$_omp_streaming_tempfile.transient`),
|
||||
not a variable, because `_omp_cleanup_stream` runs before the transient repaint.
|
||||
@@ -0,0 +1,57 @@
|
||||
# PowerShell (pwsh)
|
||||
|
||||
## Engine events and runspaces
|
||||
|
||||
- `Register-EngineEvent` (e.g. `PowerShell.OnIdle`) `-Action` handlers, verified pwsh 7 (2026-07):
|
||||
- `-MessageData` is accepted but arrives as `$null` in the action (unlike `Register-ObjectEvent`,
|
||||
where it works).
|
||||
- `.GetNewClosure()` bindings are lost when the scriptblock is created inside a module function -
|
||||
captured variables resolve to `$null` when the action fires.
|
||||
- The only reliable way to pass state into an engine-event action is a `$global:` variable
|
||||
(see `$global:_ompStreamingState` in `src/shell/scripts/omp.ps1`).
|
||||
- PSReadLine generates `PowerShell.OnIdle` itself every ~300-450ms while waiting for input (only
|
||||
when the input buffer is empty) and pumps ALL queued subscriber actions via a nested pipeline.
|
||||
`InvokePrompt()` is designed for OnIdle subscribers.
|
||||
- PSEvents raised from a NON-engine thread (e.g. `Register-ObjectEvent DataAdded` on a collection
|
||||
appended by a background runspace) can re-enter `RunspaceBase.Pulse()` and **crash the host**
|
||||
with InvalidPipelineStateException under rapid prompt cycles. Consume records only on the engine
|
||||
thread: sync waiter plus OnIdle drain.
|
||||
- Whether `Register-ObjectEvent` actions fire during a busy-wait is CONTEXT-DEPENDENT. Never share
|
||||
a consume-cursor between an event action and a synchronous waiter - give the waiter a private
|
||||
cursor (records stay in the PSDataCollection) and make the action idempotent.
|
||||
- OnIdle needs ~300ms of idle time. Anything the user can trigger sooner (transient prompt on a
|
||||
fast Enter) must ALSO drain synchronously at its call site or it pays a CLI-spawn fallback.
|
||||
|
||||
## Exit lifecycle
|
||||
|
||||
- pwsh cannot exit while a `[powershell]::Create()` pipeline thread runs - pipeline threads are
|
||||
foreground threads. A reader runspace blocked in `ReadByte()` on a child's stdout deadlocks
|
||||
`exit` when that child only terminates on stdin EOF (issue #7643).
|
||||
- Module `OnRemove` only runs on `Remove-Module`, never on normal `exit` - it cannot be a
|
||||
daemon's teardown path. Use a `Register-EngineEvent PowerShell.Exiting` handler that writes
|
||||
`quit`, **closes the child's stdin** (the guaranteed EOF signal), and kills after a short
|
||||
`WaitForExit`. State reaches the action via `$global:` (see above).
|
||||
|
||||
## Performance (measured 2026-07-06, ARM64 Windows 11)
|
||||
|
||||
- Process creation floor is ~70ms (`cmd /c exit`); any omp spawn from pwsh costs ~100-130ms wall
|
||||
regardless of exe speed. When touching `omp.ps1` perf, count process spawns per Enter first -
|
||||
the spawn-per-prompt architecture dominates, script/module cmdlet overhead is negligible
|
||||
(<0.1ms; module vs plain script is a non-issue).
|
||||
- `&` call operator vs the Process API is only ~10-17ms faster - CreateProcess dominates. Under a
|
||||
stock CP437/1252 console, `&` mangles UTF-8 output (U+E0B6 becomes U+03B5); dropping the Process
|
||||
API requires `[Console]::OutputEncoding = UTF8` once at init.
|
||||
- Serve daemon warm render ~10ms; warm prompt cost 18-23ms vs 160-220ms for the per-prompt
|
||||
streaming cycle it replaced (runspace creation ~35ms + event churn + 15.6ms-quantized sleeps).
|
||||
- Windows sleep granularity: each `Start-Sleep -Milliseconds 1` is a ~15.6ms tick - sleep-polling
|
||||
loops are ~16x slower than intended. Prefer a `ManualResetEventSlim` signaled by the reader.
|
||||
|
||||
## Testing
|
||||
|
||||
- `New-Event -SourceIdentifier PowerShell.OnIdle` runs the real OnIdle action deterministically
|
||||
(engines never idle mid-script). Beware the action's side effects short-circuiting later prompt
|
||||
calls in the harness.
|
||||
- Set module-scope flags from a test: `& (Get-Module oh-my-posh-core) { $script:X = $true }`.
|
||||
- pwsh 5.1 / ConstrainedLanguage keep the legacy stream path - serve is gated to pwsh 6+.
|
||||
- Exit-deadlock repro pattern: `Start-Process pwsh -File test.ps1` + `WaitForExit(timeout)`;
|
||||
`findstr x` with redirected stdio is a good stand-in daemon.
|
||||
@@ -0,0 +1,30 @@
|
||||
# Terminal
|
||||
|
||||
## Windows Terminal and ConPTY
|
||||
|
||||
- The "[process exited with code 0] / Ctrl+D to close" text flashing at shell exit is **Windows
|
||||
Terminal's own teardown message** (`ConptyConnection::_indicateExitWithStatus`) - visibility is
|
||||
a render/close race, never omp output.
|
||||
- Panes launched via `wt.exe <commandline>` never auto-close under `closeOnExit=automatic`;
|
||||
profile-launched tabs do.
|
||||
- On Windows 11 build 26300 a headless conhost reports cursor 0,0 / blank buffer and drops pty
|
||||
pipe input - visual row placement cannot be asserted headlessly, only state-machine behavior.
|
||||
- pywinpty's ConPTY teardown EOF lags process death by a constant ~5s (harness artifact - it shows
|
||||
up in a plain-pwsh control too).
|
||||
- SendKeys into Windows Terminal is unreliable while the user works - keystrokes get silently
|
||||
lost, so a "hung" test may simply never have received its `exit`.
|
||||
|
||||
## Encoding
|
||||
|
||||
- Under a stock CP437/ACP1252 console, pwsh's `&` call operator mangles UTF-8 program output
|
||||
(U+E0B6 becomes U+03B5). Reading a native program's UTF-8 output reliably requires
|
||||
`[Console]::OutputEncoding = UTF8` or the Process API.
|
||||
- PowerShell `| Set-Content -NoNewline` joins piped lines into ONE line and corrupts scripts -
|
||||
write files with Git Bash redirection when byte fidelity matters.
|
||||
|
||||
## Process facts
|
||||
|
||||
- Windows process creation floor is ~70ms; a timer tick is 15.6ms (quantizes every short sleep).
|
||||
- On Windows there is no SIGPIPE; child lifecycle management must rely on fd closure / stdin EOF.
|
||||
- On native Linux, process spawns cost 11-16ms - daemon architectures that pay off on Windows can
|
||||
be a wash there (see the bash serve revert in [bash](bash.md)).
|
||||
@@ -0,0 +1,55 @@
|
||||
# Testing shells end-to-end
|
||||
|
||||
Patterns for functionally driving omp's shell integrations, mostly in WSL (verified on aarch64,
|
||||
zsh 5.9, fish 4.1.2).
|
||||
|
||||
## WSL basics
|
||||
|
||||
- WSL `/tmp` is wiped between separate `wsl.exe` invocations (instance auto-shutdown). Either make
|
||||
a test fully self-contained in ONE `wsl -e` call, or stage everything under `$HOME` (e.g.
|
||||
`~/omp-test`).
|
||||
- From Git Bash, prefix `MSYS_NO_PATHCONV=1` so `/mnt/c/...` arguments reach wsl.exe unmangled.
|
||||
- Build for WSL either inside WSL (`/usr/local/go/bin/go`) or cross-compile from Windows
|
||||
(`GOOS=linux GOARCH=arm64`). Shell scripts are embedded at build time - rebuild after every
|
||||
script edit, then regenerate the sourced init with `oh-my-posh init <shell> --print`.
|
||||
- Differential testing: build the control binary from `git show HEAD:<file>` or via
|
||||
`git stash push -- <file>`; the fixed binary from the working tree.
|
||||
- `pkill -f <pattern>` matches its own caller's command line when inlined - pick patterns that
|
||||
cannot appear in the runner script.
|
||||
|
||||
## Getting a pty
|
||||
|
||||
- `zsh -i` under plain `wsl -e` hangs before the first prompt (no foreground pty). Options:
|
||||
- `wsl -e script -qec 'zsh -i' /dev/null` - real pty, MONITOR enabled, job-control behavior
|
||||
testable. Run backgrounded with output to a file and poll a done-marker; foreground runs can
|
||||
wedge under the wsl relay.
|
||||
- `zmodload zsh/zpty` in a driver zsh - best for keystroke-level scenarios: `zpty omp zsh -i`,
|
||||
write raw keys with `zpty -w -n omp $'\x03'` (Ctrl+C delivers a real SIGINT through the pty),
|
||||
assert via state files written by in-session commands, drain output with `zpty -r` after exit.
|
||||
Note `script(1)` does NOT work inside zpty (typescript never appears).
|
||||
- Per-shell `script(1)` quirks: bash prompt bytes are not relayed to stdout (probe `${PS1@P}`
|
||||
in-session); fish discards piped typeahead (in-session probes never run); zsh relays fine.
|
||||
- Do not test the zsh serve path from a non-interactive `zsh script.zsh` - it hangs in
|
||||
`read -u fd -d $'\0' -t N` (the `-t` is ignored with `-d`, see [zsh](zsh.md)).
|
||||
|
||||
## Driving vi-mode / keystroke scenarios (zpty pattern, verified 2026-07-14)
|
||||
|
||||
- Harness pattern (built for #5992): stage a binary, config, zdot dirs, and zpty driver scripts
|
||||
under `$HOME` in WSL; one driver runs mode-aware ESC/Enter/Ctrl+C scenarios into a state log,
|
||||
another asserts the transient prompt renders. Always compare a fixed run against a control zdot
|
||||
(same setup, feature under test disabled).
|
||||
- Keep probes mode-aware: with zsh-vi-mode, a line after a normal-mode accept starts in normal
|
||||
mode - prefix typed commands with `i` where needed, and remember a stray self-inserted `i`
|
||||
turns the probe into a failing command (useful as a broken-state detector: `last_status=127`).
|
||||
|
||||
## Keeping a render alive
|
||||
|
||||
- There is no shell-command segment type. To hold `omp stream`/a render open for a controllable
|
||||
window, point an `http` segment at a silent local TCP listener
|
||||
(`python3 -c 'socket...accept...sleep'`) with a large `http_timeout`; kill the listener to
|
||||
trigger the async update record.
|
||||
|
||||
## Reading protocol streams
|
||||
|
||||
- One `bufio.Scanner` per pipe, ever - a second scanner on the same pipe loses buffered data
|
||||
(use a shared reader helper in Go tests).
|
||||
@@ -0,0 +1,53 @@
|
||||
# zsh
|
||||
|
||||
## zle facts
|
||||
|
||||
- Every new editor invocation starts in keymap `main`; a `vicmd` selection does not survive into
|
||||
the next line.
|
||||
- `$?` after the transient prompt's `zle .send-break` is 1; native Ctrl+C yields 130. Known gap,
|
||||
documented as a TODO in `_omp_zle-line-init` - do not "fix" one without solving the other path.
|
||||
- Ubuntu's `/etc/zsh/zshrc` defines a `zle-line-init` (terminfo smkx), so omp's widget takes the
|
||||
decorate path even in minimal setups - never assume the widget slot is empty.
|
||||
|
||||
## zsh-vi-mode (ZVM) interaction (verified 2026-07-14, issue #5992)
|
||||
|
||||
- ZVM initializes lazily at first `precmd`, so it ALWAYS wraps omp's `zle-line-init` regardless of
|
||||
source order: its wrapper runs our widget first, `zvm_zle-line-init` second. Because the
|
||||
transient prompt's `zle .recursive-edit` consumes the whole editing session, ZVM's line-init
|
||||
effectively ran at line END - any line accepted or interrupted from normal mode desynced
|
||||
`ZVM_MODE` from the active keymap, and `zvm_select_vi_mode`'s same-mode early return made the
|
||||
break permanent.
|
||||
- Fix in `_omp_zle-line-init`: call `zvm_zle-line-init` up front (guarded on
|
||||
`$+functions[zvm_zle-line-init]` and `ZVM_INIT_DONE == true`). Keep this if the function is ever
|
||||
restructured.
|
||||
- Landmine: `zvm_reset_prompt` resolves `$rawfunc` via **dynamic scoping**. Any ZVM code running
|
||||
`zle reset-prompt` while inside our line-init picks up the line-init wrapper's `rawfunc`
|
||||
(`_omp_decorated_zle-line-init`) and re-enters the widget recursively. The `local rawfunc=` at
|
||||
the top of `_omp_zle-line-init` shadows it - load-bearing, keep it.
|
||||
|
||||
## coproc and signals
|
||||
|
||||
- An interactive zsh with MONITOR prints "[n] pid" at coproc spawn; `disown` is too late and a
|
||||
`{ coproc ... } 2>/dev/null` block does NOT suppress it. `setopt localoptions no_monitor` does -
|
||||
side effect: the child inherits SIGINT/SIGQUIT ignored (POSIX no-job-control), which Go
|
||||
preserves.
|
||||
- Duplicate coproc fds to session fds (`exec {out}<&p {in}>&p`) - duplicates survive a later
|
||||
`coproc` replacing the slot. `disown %+` keeps the daemon out of `jobs` and the job-count
|
||||
segment.
|
||||
- Writing to a dead coproc pipe raises SIGPIPE, which **kills a non-interactive zsh outright**
|
||||
(`2>/dev/null` cannot stop a signal). Guard daemon writes with a `kill -0 $pid` pre-check plus
|
||||
`setopt localoptions localtraps; trap '' PIPE` (function-local, user pipelines unaffected).
|
||||
- Never pass a possibly-zero pid to `kill -0` - `kill -0 0` signals the caller's own process group
|
||||
and always succeeds.
|
||||
|
||||
## Footguns
|
||||
|
||||
- A redirection-only `exec` applies EVERY listed redirection to the shell permanently:
|
||||
`exec {fd}<&p {fd}>&p 2>/dev/null` silences the session's stderr for good (caused issue #7653).
|
||||
Scope the stderr suppression with a brace block: `{ exec ... } 2>/dev/null`.
|
||||
- zsh 5.9: `read -r -u $fd -d $'\0' -t N` ignores `-t` entirely and blocks forever on a silent fd.
|
||||
Without `-d`, the timeout works.
|
||||
- Teardown belongs in `zshexit_functions`; the daemon lifecycle is fd-governed (closing the fds -
|
||||
or the shell dying, even by SIGKILL - EOFs the daemon's stdin).
|
||||
- To debug widget re-entry, log `${funcstack[*]}` inside the widget - `zle` calls from shell
|
||||
functions appear on the stack and expose who invoked what.
|
||||
+5
-4
@@ -1,11 +1,12 @@
|
||||
# APM
|
||||
apm_modules/
|
||||
.github/skills/*
|
||||
.github/instructions/*
|
||||
!.github/skills/segment-create/
|
||||
!.github/skills/segment-docs/
|
||||
.github/instructions/*
|
||||
.agents/*
|
||||
!.agents/skills
|
||||
.agents/skills/*
|
||||
!.agents/skills/segment-create/
|
||||
!.agents/skills/segment-docs/
|
||||
!.agents/skills/project-knowledge/
|
||||
|
||||
# Others
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# GitHub Copilot Instructions
|
||||
# Agent Instructions
|
||||
|
||||
For general coding guidelines, commit conventions, and agent workflows, see [AGENTS.md](../AGENTS.md).
|
||||
General coding guidelines, commit conventions, and agent workflows for this repository.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
@@ -66,6 +66,27 @@ Themes are plain JSON files in `themes/`. New themes must validate against
|
||||
`website/static/schema.json`. Do not introduce breaking schema changes without updating the
|
||||
schema file.
|
||||
|
||||
## Skills
|
||||
|
||||
Agent skills live in `.agents/skills/` - the vendor-neutral Agent Skills location that Copilot,
|
||||
Codex, Claude Code, and most other agents discover automatically. Most skills are installed via
|
||||
APM (see [CONTRIBUTING.md](CONTRIBUTING.md)) and gitignored; the repository embeds three of its
|
||||
own: `segment-create`, `segment-docs`, and `project-knowledge`.
|
||||
|
||||
## Project Knowledge
|
||||
|
||||
The `project-knowledge` skill (`.agents/skills/project-knowledge/`) is the project's durable
|
||||
memory: verified gotchas about the codebase, shells, terminals, and test harnesses. Before working
|
||||
in any of those areas, read the matching topic file - it exists to keep you out of known rabbit
|
||||
holes.
|
||||
|
||||
Reading it is half the contract; writing to it is the other half. When a session uncovers
|
||||
something a future session should know before going down the same rabbit hole - a platform quirk,
|
||||
a non-obvious root cause, a failed approach worth not retrying - append it (dated, verified) to
|
||||
the matching file in
|
||||
`.agents/skills/project-knowledge/references/`. Create a new topic file plus an index row in its
|
||||
`SKILL.md` when none fits. Commit the knowledge update together with the change it relates to.
|
||||
|
||||
## Pull Request Reviews
|
||||
|
||||
Whenever any agent performs or addresses a pull request review, follow this process at all
|
||||
|
||||
+2
-2
@@ -14,8 +14,8 @@ can be a good starting point.
|
||||
## Setting Up Agents and Skills
|
||||
|
||||
This project uses [APM (Agent Package Manager)][apm] to manage shared AI agent skills.
|
||||
Project-specific skills live in `.github/skills/`, while shared skills are declared
|
||||
in `apm.yml` and installed via APM.
|
||||
Project-specific skills are embedded in `.agents/skills/`, while shared skills are declared
|
||||
in `apm.yml` and installed via APM into the gitignored parts of that same directory.
|
||||
|
||||
### Install APM
|
||||
|
||||
|
||||
Reference in New Issue
Block a user