@gajae-code/coding-agent 0.18.6 → 0.18.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/dist/types/capability/types.d.ts +4 -0
- package/dist/types/config/settings.d.ts +4 -0
- package/dist/types/discovery/helpers.d.ts +2 -2
- package/dist/types/runtime-mcp/elicitation.d.ts +12 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +9 -0
- package/dist/types/sdk/broker/task-artifact-owner-validation.d.ts +55 -0
- package/dist/types/sdk/bus/telegram-daemon.d.ts +3 -2
- package/dist/types/sdk/host/host.d.ts +6 -0
- package/dist/types/sdk/router/session-router.d.ts +7 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +11 -1
- package/dist/types/session/internal/task-artifact-owner-access.d.ts +17 -0
- package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +3 -0
- package/dist/types/session/session-retirement.d.ts +47 -0
- package/dist/types/session/session-storage.d.ts +0 -46
- package/dist/types/session/session-transcript-header.d.ts +2 -0
- package/dist/types/session/task-artifact-owner-codec.d.ts +107 -0
- package/dist/types/session/task-artifact-owner-retirement.d.ts +7 -0
- package/dist/types/session/task-artifact-owner.d.ts +10 -0
- package/package.json +7 -7
- package/scripts/resident-memory-bench.ts +174 -20
- package/src/capability/index.ts +1 -0
- package/src/capability/types.ts +4 -0
- package/src/cli/config-cli.ts +8 -18
- package/src/cli-main.ts +7 -8
- package/src/commands/sdk.ts +112 -21
- package/src/config/settings.ts +44 -4
- package/src/discovery/helpers.ts +9 -3
- package/src/discovery/ssh.ts +8 -3
- package/src/gjc-runtime/gc-runtime.ts +2 -1
- package/src/hindsight/config.ts +9 -3
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/modes/acp/acp-agent.ts +75 -3
- package/src/runtime-mcp/client.ts +13 -4
- package/src/runtime-mcp/elicitation.ts +142 -0
- package/src/sdk/broker/broker.ts +8 -1
- package/src/sdk/broker/lifecycle.ts +109 -7
- package/src/sdk/broker/task-artifact-owner-validation.ts +259 -0
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +3 -0
- package/src/sdk/bus/index.ts +3 -3
- package/src/sdk/bus/telegram-daemon.ts +4 -2
- package/src/sdk/host/host.ts +6 -0
- package/src/sdk/host/session-runtime.test.ts +43 -2
- package/src/sdk/host/session-runtime.ts +2 -2
- package/src/sdk/mcp/server.ts +15 -3
- package/src/sdk/router/session-router.ts +19 -2
- package/src/sdk/session.ts +14 -0
- package/src/session/agent-session.ts +57 -6
- package/src/session/internal/managed-session-storage.ts +107 -1
- package/src/session/internal/task-artifact-owner-access.ts +229 -0
- package/src/session/internal/task-artifact-owner-transcript.ts +68 -0
- package/src/session/session-retirement.ts +155 -0
- package/src/session/session-storage.ts +1 -160
- package/src/session/session-transcript-header.ts +17 -0
- package/src/session/task-artifact-owner-codec.ts +578 -0
- package/src/session/task-artifact-owner-retirement.ts +475 -0
- package/src/session/task-artifact-owner.ts +289 -0
|
@@ -69,7 +69,7 @@ export const EMBEDDED_DOCS: Readonly<Record<string, string>> = {
|
|
|
69
69
|
"natives-rust-task-cancellation.md": "# Native Rust task execution and cancellation (`pi-natives`)\n\nThis document describes how `crates/pi-natives` schedules native work and how cancellation flows from JS options (`timeoutMs`, `AbortSignal`) into Rust execution.\n\n## Implementation files\n\n- `crates/pi-natives/src/task.rs`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/fd.rs`\n- `crates/pi-natives/src/ast.rs`\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/ps.rs`\n\n## Core primitives (`task.rs`)\n\n`task.rs` defines:\n\n1. `task::blocking(tag, cancel_token, work)`\n - Wraps `napi::AsyncTask` / `Task`.\n - `compute()` runs on libuv worker threads.\n - Returns a JS `Promise<T>` for exported functions.\n - Records a profiling sample through `profile_region(tag)`.\n\n2. `task::future(env, tag, work)`\n - Wraps `env.spawn_future(...)`.\n - Runs async work on Tokio's runtime.\n - Returns `PromiseRaw<'env, T>`.\n - Records a profiling sample through `profile_region(tag)`.\n\n3. `CancelToken` / `AbortToken` / `AbortReason`\n - `CancelToken::new(timeout_ms, signal)` combines an optional deadline and optional JS `AbortSignal` converted from `Unknown`.\n - `CancelToken::heartbeat()` is cooperative cancellation for blocking loops.\n - `CancelToken::wait()` asynchronously waits for signal, timeout, or Ctrl-C.\n - `CancelToken::emplace_abort_token()` creates an abortable flag when a later `Shell.abort()`/internal bridge needs one.\n - `AbortToken::abort(reason)` lets external code request abort.\n\n## `blocking` vs `future`: execution model and selection\n\n### Use `task::blocking`\n\nUse when work is CPU-heavy or fundamentally synchronous/blocking:\n\n- regex/file scanning (`grep`, `glob`, `fuzzyFind`)\n- ast-grep search/edit worker work\n- PTY loop internals through `tokio::task::spawn_blocking`\n- image decode/resize/encode\n- HTML conversion\n- clipboard image read\n\nBehavior:\n\n- Work closure receives a cloned `CancelToken`.\n- Cancellation is only observed where code checks `ct.heartbeat()?`.\n- Closure `Err(...)` rejects the JS promise.\n\n### Use `task::future`\n\nUse when work must `await` async operations:\n\n- shell session orchestration (`Shell.run`, `executeShell`)\n- PTY outer promise (`PtySession.start`) before it enters `spawn_blocking`\n- task racing (`tokio::select!`) between completion and cancellation\n\nBehavior:\n\n- Future code can race normal completion against `ct.wait()`.\n- On cancel path, async implementations typically cancel subordinate machinery and may force-abort after a grace timeout.\n\n## JS API ↔ Rust export mapping (task/cancel relevant)\n\n| JS-facing API | Rust export | Scheduler | Cancellation hookup |\n| --------------------------------------- | ------------------------------------ | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |\n| `grep(options, onMatch?)` | `grep` | `task::blocking(\"grep\", ct, ...)` | `CancelToken::new(options.timeoutMs, options.signal)` + heartbeat checks |\n| `glob(options, onMatch?)` | `glob` | `task::blocking(\"glob\", ct, ...)` | `CancelToken::new(...)` + heartbeat checks |\n| `fuzzyFind(options)` | `fuzzy_find` | `task::blocking(\"fuzzy_find\", ct, ...)` | `CancelToken::new(...)` + heartbeat checks |\n| `astGrep(options)` / `astEdit(options)` | ast exports | blocking worker path | timeout/signal fields are accepted by options and checked cooperatively in worker loops |\n| `Shell#run(options, onChunk?)` | `Shell::run` | `task::future(env, \"shell.run\", ...)` | `ct.wait()` raced against run task; bridges to Tokio cancellation token and `AbortToken` |\n| `executeShell(options, onChunk?)` | `execute_shell` | `task::future(env, \"shell.execute\", ...)` | same cancel race and 2s graceful window |\n| `PtySession#start(options, onChunk?)` | `PtySession::start` | `task::future(env, \"pty.start\", ...)` + inner `spawn_blocking` | `CancelToken` checked in sync PTY loop via `heartbeat()` |\n| `htmlToMarkdown(html, options?)` | `html_to_markdown` | `task::blocking(\"html_to_markdown\", (), ...)` | none (`()` token) |\n| `readImageFromClipboard()` | `read_image_from_clipboard` | `task::blocking(\"clipboard.read_image\", (), ...)` | none (`()` token) |\n\n`text.rs`, `keys.rs`, most `ps.rs` functions, and synchronous utility exports do not use `task::blocking`/`task::future` and therefore do not participate in this cancellation path.\n\n## Cancellation lifecycle and state transitions\n\n### `CancelToken` lifecycle\n\n```text\nCreated\n ├─ no signal + no timeout -> passive token\n ├─ signal registered -> AbortSignal callback can set AbortReason::Signal\n └─ deadline set -> timeout check becomes active\n\nRunning\n ├─ heartbeat()/wait() sees signal -> AbortReason::Signal\n ├─ heartbeat()/wait() sees deadline -> AbortReason::Timeout\n ├─ wait() sees Ctrl-C -> AbortReason::User\n └─ no abort -> continue\n\nAborted\n └─ flag stores first observed cause for waiters; heartbeat formats it as \"Aborted: <reason>\"\n```\n\n### Before-start vs mid-execution cancellation\n\n- **Before start / before first cancellation check**:\n - `task::future` users that race on `ct.wait()` can resolve cancellation once they enter `select!`.\n - `task::blocking` users only observe cancellation when closure code reaches `heartbeat()`.\n\n- **Mid-execution**:\n - `blocking`: next `heartbeat()` returns `Err(\"Aborted: ...\")`.\n - `future`: `ct.wait()` branch wins `select!`, then code cancels subordinate async machinery.\n - shell: cancellation triggers a Tokio cancellation token, waits up to 2 seconds, then aborts the task if needed.\n - PTY: heartbeat failure or `kill()` terminates PTY child/process tree and drains output briefly.\n\n## Heartbeat expectations for long-running loops\n\n`heartbeat()` must run at predictable cadence in loops with unbounded or large work sets.\n\nObserved patterns:\n\n- `glob` filtering checks entries during scan/filter work.\n- `fd` scoring checks scanned candidates.\n- `grep` checks before/during expensive search and passes tokens into shared scan/cache helpers.\n- `run_pty_sync` checks every loop tick with a maximum 16ms wait cadence.\n\nPractical rule: no loop over external-size input should exceed a short bounded interval without a heartbeat.\n\n## Failure behavior and error propagation to JS\n\n### Blocking tasks\n\nError path:\n\n1. Closure returns `Err(napi::Error)` (including `heartbeat()` abort).\n2. `Task::compute()` returns `Err`.\n3. `AsyncTask` rejects JS promise.\n\n\n`Blocking::compute()` catches panics from the work closure before they can unwind across napi-rs' `extern \"C\"` async-work callback and converts them into a rejected promise.\nTypical error strings:\n\n- `Aborted: Timeout`\n- `Aborted: Signal`\n- domain errors (`Failed to decode image: ...`, `Conversion error: ...`, etc.)\n\n### Future tasks\n\nError path:\n\n1. Async body returns `Err(napi::Error)` or join failure is mapped (`... task failed: {err}`).\n2. `task::future`-spawned promise rejects.\n3. Shell and PTY command APIs model cancellation as structured results instead of rejection when the cancellation path wins: `exitCode` omitted, `cancelled` or `timedOut` set.\n\n### Cancellation reporting split\n\n- **Abort as error**: blocking exports using `heartbeat()?`.\n- **Abort as typed result**: shell/PTY command APIs that model cancellation in result structs.\n\nChoose one model per API and document it explicitly.\n\n## Common pitfalls\n\n1. **Missing heartbeat in blocking loops**\n - Symptom: timeout/signal appears ignored until loop ends.\n - Fix: add `ct.heartbeat()?` at loop top and before expensive per-item steps.\n\n2. **Long uncancelable sections**\n - Symptom: cancellation latency spikes during single large call (decode, sort, compression, parser invocation, etc.).\n - Fix: split work into chunks with heartbeat boundaries; if impossible, document latency.\n\n3. **Blocking async executor**\n - Symptom: async API stalls when sync-heavy code runs directly in future.\n - Fix: move CPU/sync blocks to `task::blocking` or `tokio::task::spawn_blocking`.\n\n4. **Inconsistent cancel semantics**\n - Symptom: one API rejects on cancel, another resolves with flags, confusing callers.\n - Fix: standardize per domain and keep docs aligned.\n\n5. **Forgetting cancellation bridge in nested async tasks**\n - Symptom: outer token is cancelled but inner readers/subprocess tasks keep running.\n - Fix: bridge cancellation to inner token/signal and enforce grace timeout + forced abort fallback.\n\n## Checklist for new cancellable exports\n\n1. Classify work correctly:\n - CPU-bound or sync blocking -> `task::blocking`.\n - async I/O / `await` orchestration -> `task::future`.\n\n2. Expose cancel inputs when needed:\n - include `timeoutMs` and `signal` in `#[napi(object)]` options,\n - create `let ct = task::CancelToken::new(timeout_ms, signal);`.\n\n3. Wire cancellation through all layers:\n - blocking loops: `ct.heartbeat()?` at stable intervals,\n - async orchestration: race with `ct.wait()` and cancel sub-tasks/tokens.\n\n4. Decide cancellation contract:\n - reject promise with abort error, or\n - resolve typed `{ cancelled, timedOut, ... }`,\n - keep this contract consistent for the API family.\n\n5. Propagate failures with context:\n - map errors via `Error::from_reason(format!(\"...: {err}\"))`,\n - include stage-specific prefixes (`spawn`, `decode`, `wait`, etc.).\n\n6. Handle before-start and mid-flight cancellation:\n - cancellation check/await must happen before expensive body and during long execution.\n\n7. Validate no executor misuse:\n - no long sync work directly inside async futures without `spawn_blocking`/blocking task wrapper.\n",
|
|
70
70
|
"natives-shell-pty-process.md": "# Natives Shell, PTY, Process, and Key Internals\n\nThis document covers the execution/process/terminal primitives in `@gajae-code/natives`: `shell`, `pty`, `ps`, and `keys`, using the architecture terms from `docs/natives-architecture.md`.\n\n## Implementation files\n\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-shell/src/windows.rs` (Windows-only PATH enrichment)\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/ps.rs`\n- `crates/pi-natives/src/keys.rs`\n- `crates/pi-natives/src/task.rs`\n- `packages/natives/native/index.d.ts`\n\n## Layer ownership\n\n- **Package entrypoint** (`packages/natives/native/index.js`): loads the `.node` addon and exports generated N-API bindings.\n- **Rust N-API module layer** (`crates/pi-natives/src/*`): shell/PTY process execution, process-tree traversal/termination, and key-sequence parsing.\n- **Consumers** (`packages/coding-agent`, `packages/tui`): higher-level session policy, output artifact/minimizer handling, render policy, and UI key handling.\n\n## Shell subsystem (`shell`)\n\n### API model\n\nTwo execution modes are exposed:\n\n1. **One-shot** via `executeShell(options, onChunk?)`.\n2. **Persistent session** via `new Shell(options?)` then `shell.run(...)` repeatedly.\n\nBoth stream output through a threadsafe callback and return `{ exitCode?, cancelled, timedOut, minimized? }`.\n\n`ShellOptions` supports `sessionEnv`, `snapshotPath`, and optional output `minimizer`. `ShellExecuteOptions` supports command-scoped `env`, session-level `sessionEnv`, `snapshotPath`, timeout/signal, and optional minimizer. `ShellRunOptions` supports command, cwd, command-scoped env, timeout, and signal.\n\n### Session creation and environment model\n\nRust creates `brush_core::Shell` with:\n\n- non-interactive, non-login mode,\n- `no_profile` and `no_rc`,\n- `do_not_inherit_env: true`,\n- bash-mode builtins, with `exec` and `suspend` disabled,\n- explicit environment reconstruction from host env,\n- skip-list for shell-sensitive vars (`PS1`, `PWD`, `SHLVL`, bash function exports, etc.).\n\nSession env behavior:\n\n- `ShellOptions.sessionEnv` / one-shot `sessionEnv` is applied at session creation.\n- `ShellRunOptions.env` / one-shot `env` is command-scoped (`EnvironmentScope::Command`) and popped after the command.\n- `PATH` is merged specially on Windows with case-insensitive dedupe.\n- Windows-only path enrichment (`shell/windows.rs`) appends discovered Git-for-Windows paths when present and not already included.\n- `snapshotPath`, when present, is sourced during session creation with stdout/stderr/stdin wired to null files.\n\n### Runtime lifecycle and state transitions\n\nPersistent shell (`Shell.run`) uses this state machine:\n\n- **Idle/Uninitialized**: `session: None`.\n- **Running**: first `run()` lazily creates a session, stores an abort token, executes command.\n- **Completed + keepalive**: if execution control flow is normal, abort state is cleared and session is reused.\n- **Completed + teardown**: if control flow is loop/script/shell-exit related, session is dropped.\n- **Cancelled/Timed out**: run task is cancelled, grace wait is 2 seconds, task may be force-aborted, session is dropped if lock can be acquired.\n- **Error**: session is dropped.\n\nOne-shot shell (`executeShell`) always creates and drops a fresh session per call.\n\n### Streaming/output and minimizer behavior\n\n- Stdout/stderr are routed into a shared pipe and read concurrently.\n- Reader decodes UTF-8 incrementally; invalid byte sequences emit `U+FFFD` replacement chunks.\n- The command runs in a new process group policy.\n- Optional minimizer configuration can capture and rewrite output. When minimization occurs, the result includes `minimized` with filter name, replacement text, original text, and byte counts.\n- Consumers are responsible for persisting or displaying minimizer artifacts; the native result only carries the data.\n\n### Cancellation, timeout, and abort\n\n- `CancelToken` is constructed from `timeoutMs` and optional `AbortSignal`.\n- On cancellation/timeout, shell cancellation token is triggered, then task gets a 2-second graceful window before forced abort.\n- Structured result flags are used:\n - timeout -> `exitCode` omitted, `timedOut: true`.\n - abort signal / `Shell.abort()` -> `exitCode` omitted, `cancelled: true`.\n\n`Shell.abort()` behavior:\n\n- aborts the current running command for that `Shell` instance through the stored `AbortToken`,\n- resolves successfully even when nothing is running.\n\n### Failure behavior\n\nCommon surfaced errors include:\n\n- session init failures (`Failed to initialize shell`),\n- cwd errors (`Failed to set cwd`),\n- env set/pop failures,\n- snapshot source failures (`Failed to source snapshot`),\n- pipe creation/clone failures,\n- execution failure (`Shell execution failed: ...`),\n- task wrapper failures (`Shell execution task failed: ...`).\n\n## PTY subsystem (`pty`)\n\n### API model\n\n`new PtySession()` exposes:\n\n- `start(options, onChunk?) -> Promise<{ exitCode?, cancelled, timedOut }>`\n- `write(data)`\n- `resize(cols, rows)`\n- `kill()`\n\n`PtyStartOptions` supports `command`, optional `cwd`, optional `env`, `timeoutMs`, `signal`, `cols`, and `rows`.\n\n### Runtime lifecycle and state transitions\n\n`PtySession` state machine:\n\n- **Idle**: `core: None`.\n- **Reserved**: `start()` installs control channel synchronously (`core: Some`) before async work begins, so `write/resize/kill` become immediately valid.\n- **Running**: blocking PTY loop handles child state, reader events, cancellation heartbeat, and control messages.\n- **Terminal closed / drain**: child exit or cancellation starts a short reader drain window.\n- **Finalized**: `core` is always reset to `None` after start task completion (success or error).\n\nConcurrency guard:\n\n- starting while already running returns `PTY session already running`.\n\n### Spawn/attach/write/read/terminate patterns\n\n- PTY opened via `portable_pty::native_pty_system().openpty(...)`.\n- Command currently runs as `sh -lc <command>` with optional `cwd` and env overrides.\n- Default size is `120x40`; dimensions are clamped (`cols 20..400`, `rows 5..200`).\n- `write()` sends raw bytes to PTY stdin.\n- `resize()` sends a control message and clamps dimensions again.\n- `kill()` sends a control message that marks the run cancelled and terminates the child/process tree.\n\nOutput path:\n\n- dedicated reader thread reads master stream,\n- incremental UTF-8 decode emits `U+FFFD` for invalid bytes,\n- chunks forwarded through N-API threadsafe callback.\n\nTermination path:\n\n- Unix: terminate process group when known, terminate child tree, call child kill, then repeat with SIGKILL.\n- Non-Unix: terminate child tree, call child kill, then repeat with SIGKILL-equivalent process-tree helper.\n\n### Cancellation and timeout semantics\n\n- `timeoutMs` and `AbortSignal` feed a `CancelToken`.\n- Loop calls `ct.heartbeat()` periodically with a 16ms maximum wait cadence.\n- Timeout classification is based on the heartbeat error string containing `Timeout`.\n- Cancellation/kill starts a 300ms post-cancel drain window; normal child exit starts a 300ms post-exit drain window.\n\n### Failure behavior\n\nError surfaces include:\n\n- PTY allocation/open failure,\n- PTY spawn failure,\n- writer/reader acquisition failure,\n- child status/wait failures,\n- lock poisoning,\n- control-channel disconnection (`PTY session is no longer available`).\n\nControl call failures when not running:\n\n- `write/resize/kill` return `PTY session is not running`.\n\n## Process-tree subsystem (`ps`)\n\n### API model\n\n- `killTree(pid, signal) -> number`\n- `listDescendants(pid) -> number[]`\n\n### Platform-specific implementation\n\n- **Linux**: recursively reads `/proc/<pid>/task/<pid>/children`.\n- **macOS**: uses `libproc` `proc_listchildpids`.\n- **Windows**: snapshots process table with `CreateToolhelp32Snapshot`, builds parent->children map, terminates with `OpenProcess(PROCESS_TERMINATE)` + `TerminateProcess`.\n\n### Kill-tree behavior\n\n- Descendants are collected recursively.\n- Kill order is bottom-up (deepest descendants first).\n- Root pid is killed last.\n- Return value is count of successful terminations.\n\nSignal behavior:\n\n- POSIX: provided `signal` is passed to `kill`.\n- Windows: `signal` is ignored; termination is unconditional process terminate.\n\n### Failure behavior\n\nThis module is intentionally non-throwing at API surface for ordinary process misses:\n\n- missing/inaccessible process tree branches are skipped,\n- per-pid kill failures are counted as unsuccessful,\n- lookup miss typically yields `[]` from `listDescendants` and `0` from `killTree`.\n\n## Key parsing subsystem (`keys`)\n\n### API model\n\nExposed helpers:\n\n- `parseKey(data, kittyProtocolActive)`\n- `matchesKey(data, keyId, kittyProtocolActive)`\n- `parseKittySequence(data)`\n- `matchesKittySequence(data, expectedCodepoint, expectedModifier)`\n- `matchesLegacySequence(data, keyName)`\n\n### Parsing model\n\nThe parser combines:\n\n- direct single-byte mappings (`enter`, `tab`, `ctrl+<letter>`, printable ASCII),\n- O(1) legacy escape-sequence lookup (PHF map),\n- xterm `modifyOtherKeys` parsing,\n- Kitty protocol parsing (`CSI u`, `CSI ~`, `CSI 1;...<letter>`),\n- normalization to key IDs (`ctrl+c`, `shift+tab`, `pageUp`, `f5`, etc.).\n\nModifier handling:\n\n- only shift/alt/ctrl bits are compared for key matching,\n- lock bits are masked out before comparisons.\n\nLayout behavior:\n\n- base-layout fallback is intentionally constrained so remapped layouts do not create false matches for ASCII letters/symbols.\n\n### Failure behavior\n\n- Unrecognized or invalid sequences produce `null` from parse functions.\n- Match functions return `false` on parse failure or mismatch.\n- No thrown error surface for malformed key input.\n\n## JS API ↔ Rust export mapping\n\n### Shell + PTY + Process\n\n| JS API | Rust N-API export | Notes |\n| --------------------------------- | -------------------------------------- | ----------------------------------------- |\n| `executeShell(options, onChunk?)` | `executeShell` (`execute_shell`) | One-shot shell execution |\n| `new Shell(options?)` | `Shell` class | Persistent shell session |\n| `shell.run(options, onChunk?)` | `Shell::run` | Reuses session on keepalive control flow |\n| `shell.abort()` | `Shell::abort` | Aborts active run for that shell instance |\n| `new PtySession()` | `PtySession` class | Stateful PTY session |\n| `pty.start(options, onChunk?)` | `PtySession::start` | Interactive PTY run |\n| `pty.write(data)` | `PtySession::write` | Raw stdin passthrough |\n| `pty.resize(cols, rows)` | `PtySession::resize` | Clamped terminal dimensions |\n| `pty.kill()` | `PtySession::kill` | Force-kills active PTY child |\n| `killTree(pid, signal)` | `killTree` (`kill_tree`) | Children-first process tree termination |\n| `listDescendants(pid)` | `listDescendants` (`list_descendants`) | Recursive descendants listing |\n\n### Keys\n\n| JS API | Rust N-API export | Notes |\n| ---------------------------------------------- | --------------------------------------------------- | ------------------------------- |\n| `matchesKittySequence(data, cp, mod)` | `matchesKittySequence` (`matches_kitty_sequence`) | Kitty codepoint+modifier match |\n| `parseKey(data, kittyProtocolActive)` | `parseKey` (`parse_key`) | Normalized key-id parser |\n| `matchesLegacySequence(data, keyName)` | `matchesLegacySequence` (`matches_legacy_sequence`) | Exact legacy sequence map check |\n| `parseKittySequence(data)` | `parseKittySequence` (`parse_kitty_sequence`) | Structured Kitty parse result |\n| `matchesKey(data, keyId, kittyProtocolActive)` | `matchesKey` (`matches_key`) | High-level key matcher |\n\n## Abandoned session cleanup and finalization notes\n\n- **Shell persistent session**: if a run is cancelled/timed out/errors/non-keepalive control flow, Rust drops the internal session state. Successful normal runs keep the session for reuse.\n- **PTY session**: `core` is always cleared after `start()` finishes, including failure paths.\n- **No explicit JS finalizer-driven kill contract** is exposed by wrappers; cleanup is primarily tied to run completion/cancellation paths. Callers should use `timeoutMs`, `AbortSignal`, `shell.abort()`, or `pty.kill()` for deterministic teardown.\n",
|
|
71
71
|
"natives-text-search-pipeline.md": "# Natives Text/Search Pipeline\n\nThis document maps the `@gajae-code/natives` text/search/code surface from generated JS/TS exports to Rust N-API modules and back to JS result objects.\n\nTerminology follows `docs/natives-architecture.md`:\n\n- **Generated binding**: public API in `packages/natives/native/index.d.ts`.\n- **Rust module layer**: N-API exports in `crates/pi-natives/src/*`.\n- **Shared scan cache**: `fs_cache`-backed directory-entry cache used by discovery/search flows.\n\n## Implementation files\n\n- `packages/natives/native/index.d.ts`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/glob_util.rs`\n- `crates/pi-natives/src/fs_cache.rs`\n- `crates/pi-natives/src/fd.rs`\n- `crates/pi-natives/src/ast.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/highlight.rs`\n\n## JS API ↔ Rust export mapping\n\n| JS API | Rust export (`#[napi]`, snake_case -> camelCase) | Rust module |\n| ------------------------------------------------------------------------------- | ------------------------------------------------ | -------------- |\n| `grep(options, onMatch?)` | `grep` | `grep.rs` |\n| `search(content, options)` | `search` | `grep.rs` |\n| `hasMatch(content, pattern, ignoreCase?, multiline?)` | `hasMatch` | `grep.rs` |\n| `fuzzyFind(options)` | `fuzzyFind` | `fd.rs` |\n| `glob(options, onMatch?)` | `glob` | `glob.rs` |\n| `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `fs_cache.rs` |\n| `astGrep(options)` | `astGrep` | `ast.rs` |\n| `astEdit(options)` | `astEdit` | `ast.rs` |\n| `wrapTextWithAnsi(text, width, tabWidth)` | `wrapTextWithAnsi` | `text.rs` |\n| `truncateToWidth(text, maxWidth, ellipsis, pad, tabWidth)` | `truncateToWidth` | `text.rs` |\n| `sliceWithWidth(line, startCol, length, strict, tabWidth)` | `sliceWithWidth` | `text.rs` |\n| `extractSegments(line, beforeEnd, afterStart, afterLen, strictAfter, tabWidth)` | `extractSegments` | `text.rs` |\n| `visibleWidth(text, tabWidth)` | `visibleWidth` | `text.rs` |\n| `highlightCode(code, lang, colors)` | `highlightCode` | `highlight.rs` |\n| `supportsLanguage(lang)` | `supportsLanguage` | `highlight.rs` |\n| `getSupportedLanguages()` | `getSupportedLanguages` | `highlight.rs` |\n\n## Pipeline overview by subsystem\n\n## 1) Regex search (`grep`, `search`, `hasMatch`)\n\n### Input/options flow\n\n1. Callers invoke generated native exports directly; there is no package-local TS wrapper that renames `search` to `searchContent`.\n2. Rust option structs in `grep.rs` deserialize camelCase fields (`ignoreCase`, `maxCount`, `contextBefore`, `contextAfter`, `maxColumns`, `timeoutMs`).\n3. `grep` creates `CancelToken` from `timeoutMs` + `AbortSignal` and runs inside `task::blocking(\"grep\", ...)`.\n4. `search` and `hasMatch` operate on provided string/`Uint8Array` content and do not scan the filesystem.\n\n### Execution branches\n\n- **In-memory branch**\n - `search` -> `search_sync` / search helpers over provided content bytes.\n - `hasMatch` compiles/checks pattern against provided content and returns a boolean.\n - No filesystem scan, no `fs_cache`.\n- **Single-file branch**\n - `grep` resolves path, checks metadata is file, and searches that file.\n- **Directory branch**\n - Optional cache lookup via `fs_cache::get_or_scan` when `cache: true`.\n - Fresh scan via `fs_cache::force_rescan` when `cache: false`.\n - Optional empty-result recheck when cached results are older than the empty-result recheck threshold.\n - Entry filtering: file-only + optional glob filter (`glob_util`) + optional type filter mapping (`js`, `ts`, `rust`, etc.).\n\n### Search/collection semantics\n\n- Regex engine: `grep_regex::RegexMatcherBuilder` with `ignoreCase` and `multiline`.\n- Context resolution:\n - `contextBefore/contextAfter` override legacy `context`.\n - Non-content modes do not collect context.\n- Output modes:\n - `content` -> one `GrepMatch` per hit.\n - `count` and `filesWithMatches` map to count-style entries (`lineNumber=0`, `line=\"\"`, `matchCount` set).\n- Limits:\n - Global `offset` and `maxCount` apply across files.\n - Parallel path is used only when `maxCount` is unset and `offset == 0`; otherwise sequential path preserves deterministic global offset/limit semantics.\n\n### Result shaping back to JS\n\n- Rust `SearchResult`/`GrepResult` fields map to TS interfaces via N-API object conversion.\n- Counters are clamped before crossing N-API where needed.\n- `GrepResult.limitReached` is optional and emitted when true.\n- Streaming callback receives each shaped `GrepMatch` for content or count-style entries.\n\n### Failure behavior\n\n- `search` returns `SearchResult.error` for regex/search failures instead of throwing.\n- `grep` rejects on hard errors such as invalid path, invalid glob/regex, or cancellation timeout/abort.\n- `hasMatch` returns a boolean on success and throws on invalid pattern/UTF-8 conversion errors.\n- File open/search errors in multi-file scans are skipped per-file; scan continues.\n\n### Malformed regex handling\n\n`grep.rs` sanitizes braces before regex compile:\n\n- Invalid repetition-like braces are escaped (`{`/`}` -> `\\{`/`\\}`) when they cannot form `{N}`, `{N,}`, `{N,M}`.\n- This prevents common literal-template fragments (for example `${platform}`) from failing as malformed repetition.\n- Remaining invalid regex syntax still returns a regex error.\n\n## 2) File discovery (`glob`) and fuzzy path search (`fuzzyFind`)\n\n`glob` and `fuzzyFind` share `fs_cache` scans; matching logic differs.\n\n### `glob` flow\n\n1. Caller passes `GlobOptions` directly. `pattern` and `path` are required in the generated type.\n2. Rust resolves the search path and compiles pattern via `glob_util::compile_glob`.\n3. Entry source:\n - `cache=true` -> `get_or_scan` + optional stale-empty `force_rescan`.\n - `cache=false` -> `force_rescan(..., store=false)` (fresh only).\n4. Filtering:\n - skip `.git` always;\n - skip `node_modules` unless requested (`includeNodeModules`) or pattern mentions `node_modules`;\n - apply glob match;\n - apply file-type filter; symlink `file`/`dir` filters resolve target metadata.\n5. Optional sort by mtime descending (`sortByMtime`) before truncating to `maxResults`.\n\n### `fuzzyFind` flow\n\n1. Rust implementation lives in `fd.rs`; generated export is `fuzzyFind`.\n2. Shared scan source from `fs_cache` with the same cache/no-cache split and stale-empty recheck policy.\n3. Scoring:\n - exact / starts-with / contains / subsequence-based fuzzy score;\n - separator/punctuation-normalized scoring path;\n - directory bonus and deterministic tie-break (`score desc`, then `path asc`).\n4. Symlink entries are excluded from fuzzy results.\n\n### Failure behavior\n\n- Invalid glob pattern returns an error from `glob_util::compile_glob`.\n- Search root must resolve to an existing directory for directory discovery flows.\n- Cancellation/timeouts propagate as abort errors via `CancelToken::heartbeat()` checks in loops.\n\n### Malformed glob handling\n\n`glob_util::build_glob_pattern` is tolerant:\n\n- normalizes `\\` to `/`,\n- auto-prefixes simple recursive patterns with `**/` when `recursive=true`,\n- auto-closes unbalanced `{...` alternation groups before compile.\n\n## 3) AST search/edit (`astGrep`, `astEdit`)\n\n`ast.rs` exposes syntax-aware code search and rewrite operations.\n\n- `astGrep(options)` returns matches with byte/line/column coordinates and optional metavariable bindings.\n- `astEdit(options)` returns replacement changes, per-file counts, searched/touched file counts, parse errors, and whether edits were applied.\n- `dryRun` defaults to true for edit options in the generated documentation.\n- Options include language override, path/glob/selector, strictness, limits, parse-error policy, `signal`, and `timeoutMs`.\n\nThese exports are direct native APIs used by tooling; they are not mediated by a TS wrapper in `packages/natives`.\n\n## 4) Shared scan/cache lifecycle (`fs_cache`)\n\n`fs_cache` stores scan results as normalized relative entries (`path`, `fileType`, optional `mtime`) keyed by:\n\n- canonical search root,\n- `include_hidden`,\n- `use_gitignore`.\n\n### Cache state transitions\n\n1. **Miss / disabled**\n - TTL is `0` or key absent/expired -> fresh collection.\n2. **Hit**\n - Entry age is within TTL -> return cached entries + `cache_age_ms`.\n3. **Stale-empty recheck**\n - If query yields zero matches and cache age exceeds the empty-result threshold, force one rescan.\n4. **Invalidation**\n - `invalidateFsScanCache(path?)`:\n - no arg: clear all keys;\n - path arg: remove keys for roots affected by that path.\n\n### Stale-result tradeoff\n\n- Cache favors low-latency repeated scans over immediate consistency.\n- TTL window can return stale positives/negatives.\n- Empty-result recheck reduces stale negatives for older cached scans at the cost of one extra scan.\n- Explicit invalidation is the intended correctness hook after file mutations.\n\n## 5) ANSI text utilities (`text`)\n\nThese are pure, in-memory utilities.\n\n### Boundaries and responsibilities\n\n- `text.rs` owns terminal-cell semantics:\n - ANSI sequence parsing,\n - grapheme-aware width and slicing,\n - wrap/truncate/sanitize behavior,\n - explicit tab-width parameter on width-sensitive APIs.\n- `grep.rs` line truncation (`maxColumns`) is separate:\n - simple character-boundary truncation of matched lines with `...`,\n - not ANSI-state-preserving and not terminal-cell width aware.\n\n### Key behaviors\n\n- `wrapTextWithAnsi`: wraps by visible width, carries active SGR codes across wrapped lines.\n- `truncateToWidth`: visible-cell truncation with ellipsis policy (`Unicode`, `Ascii`, `Omit`), optional right padding.\n- `sliceWithWidth`: column slicing with optional strict width enforcement.\n- `extractSegments`: extracts before/after segments around an overlay while restoring ANSI state for the `after` segment.\n- `sanitizeText` (ANSI/control/surrogate stripping with line-ending normalization) no longer lives in `text.rs`; it moved to `@gajae-code/utils` as a pure-JS implementation in `packages/utils/src/sanitize-text.ts`. The native binding was removed in the same change because the JS version was competitive on the benchmarked workloads, and keeping a Rust copy forced every caller (including `pi-utils`) to pull in `@gajae-code/natives`.\n- `visibleWidth`: counts visible terminal cells using caller-supplied tab width.\n\n### Failure behavior\n\nText functions generally return deterministic transformed output; errors are limited to N-API argument/string conversion boundaries.\n\n## 6) Syntax highlighting (`highlight`)\n\n`highlight.rs` is pure transformation; it does not use the filesystem scan cache.\n\n### Flow\n\n1. Caller passes `code`, optional `lang`, and ANSI color palette.\n2. Rust resolves syntax by token/name lookup, extension lookup, alias table fallback, then plain-text fallback.\n3. Each line is parsed with syntect `ParseState` and scope stack.\n4. Scopes map to semantic color categories and ANSI color codes are injected/reset.\n\n### Failure behavior\n\n- Per-line parse failure does not fail the call: that line is appended unhighlighted and processing continues.\n- Unknown/unsupported language falls back to plain text syntax.\n\n## Pure utility vs filesystem-dependent flows\n\n| Flow | Filesystem access | Shared cache | Notes |\n| ---------------------------- | ----------------- | -------------------- | --------------------------------------------- |\n| `search` / `hasMatch` | No | No | regex on provided bytes/string only |\n| `text` module functions | No | No | ANSI/width/sanitization only |\n| `highlight` module functions | No | No | syntax + ANSI coloring only |\n| `astGrep` / `astEdit` | Yes | No | syntax-aware file search/edit |\n| `glob` | Yes | Optional | directory scans + glob filtering |\n| `fuzzyFind` | Yes | Optional | directory scans + fuzzy scoring |\n| `grep` (file/dir path) | Yes | Optional in dir mode | ripgrep over files, optional filters/callback |\n\n## End-to-end lifecycle summary\n\n1. Caller invokes generated native export with typed options.\n2. Rust validates/normalizes options and builds matcher/search config.\n3. For filesystem flows, entries are scanned (cache hit/miss/rescan where applicable) then filtered/scored/searched.\n4. Worker loops periodically call cancel heartbeat; timeout/abort can terminate execution.\n5. Rust shapes outputs into N-API objects (`lineNumber`, `matchCount`, `limitReached`, etc.).\n6. Generated bindings return typed JS objects and optional per-match callbacks for `grep`/`glob`.\n",
|
|
72
|
-
"non-compaction-retry-policy.md": "# Non-compaction auto-retry policy\n\nThis document describes the standard API-error retry path in `AgentSession`.\n\nIt explicitly excludes context-overflow recovery via auto-compaction. Overflow is handled by compaction logic and is documented separately in [`compaction.md`](../docs/compaction.md).\n\n## Implementation files\n\n- [`../src/session/agent-session.ts`](../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/config/settings-schema.ts`](../packages/coding-agent/src/config/settings-schema.ts)\n- [`../src/modes/controllers/event-controller.ts`](../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`sdk.md`](./sdk.md) for the external machine interface.\n\n## Scope boundary vs compaction\n\nRetry and compaction are checked from the same `agent_end` path, but they are intentionally separated:\n\n1. `agent_end` inspects the last assistant message.\n2. `#isRetryableError(...)` runs first.\n3. If retry is initiated, compaction checks are skipped for that turn.\n4. Context-overflow errors are hard-excluded from retry classification (`isContextOverflow(...)` short-circuits retry).\n5. Overflow therefore falls through to `#checkCompaction(...)` instead of standard retry.\n\nSo: overload/rate/server/network-style failures use this retry policy; context-window overflow uses compaction recovery.\n\n## Retry classification\n\n`#isRetryableError(...)` requires all of the following:\n\n- assistant `stopReason === \"error\"`\n- `errorMessage` exists\n- message is **not** context overflow\n- `errorMessage` matches transient transport/envelope patterns or `isUsageLimitError(...)`\n\nCurrent retryable inputs are regex/string-classified:\n\n- transient transport/envelope failures, including Anthropic stream-envelope failures before `message_start`\n- overloaded/provider-returned-error wording\n- rate limit / usage limit / too many requests\n- HTTP-like server classes: 429, 500, 502, 503, 504\n- service unavailable / server/internal error\n- provider-suggested retry wording, including OpenAI `retry your request` failures\n- network/connection/socket failures, refused/closed connections, upstream connect/reset-before-headers, socket hang up, timeout/timed out, fetch failed, terminated, retry delay wording, and unexpected socket close messages\n- canonical idle-stream watchdog stalls (`stream stalled while waiting for the next event`); in the legacy single-model path these remain retryable but use the bounded `retry.maxRetries` budget\n- canonical local snapshot failure classification (`errorKind: \"local_snapshot_failure\"`, or the stable `Managed fallback attempt could not produce a serializable event snapshot` message prefix for restored sessions) is recognized, but only so the failure can be routed to its immediate-surface policy below — it is never re-issued\n\nManaged fallback uses structured transport facts and typed provider error codes when available. A structured classification of `other` becomes the bounded `unknown` fallback class; error prose cannot promote it to quota or transient. Regex classification is retained only as a legacy fallback.\n\n### Bare-default admissions\n\nA session with no explicit `retry.*` settings and a single-model default role (no managed fallback) does not use the classification list above on its own. It admits only these content-free failures:\n\n- canonical first-event and idle-stream watchdog aborts, recognized from the typed timeout fact or an exact canonical sentinel message\n- the OpenAI Codex `server_is_overloaded` event, recognized from that provider's typed overload code\n- the generic OpenAI Responses `server_is_overloaded` terminal envelope, recognized from the exact statusless `openaiErrorCode` and matching `providerCode`\n- Anthropic's typed `overloaded_error` envelope, recognized by parsing the error envelope and requiring both the outer `type` and the nested `error.type` to match\n\nOverload admissions therefore require a provider-specific typed signature, while watchdog admissions accept only their canonical sentinel messages. Every admission additionally requires that the attempt carry no assistant text, thinking, or tool call and no conflicting transport facts; a status-bearing or otherwise typed failure surfaces instead. Untyped or noncanonical overload and timeout wording never authorizes a replay.\n\n### Local snapshot failures (surface immediately, no retry)\n\n`local_snapshot_failure` is a local machinery fault, not provider evidence. The retained producer shape is deterministic, so re-streaming the same request only reproduces the same local defect; it is surfaced immediately instead of being amplified across identical retries:\n\n- Surfaces immediately with the original producer-boundary diagnostic, regardless of `retry.*` settings.\n- Never charges the fallback controller (the started attempt's provisional charge is discarded), never advances models, never emits `model_fallback_switched`, and never mutates or rotates credentials.\n\n### Local buffer overflows (surface immediately, no retry)\n\n`local_buffer_overflow` (`errorKind`, or the stable `Managed fallback attempt exceeded the provisional event buffer limit` message prefix for restored sessions) is the sibling local staging fault: the provisional managed-attempt buffer exceeded its cap. Like snapshot failures, re-streaming the same request reproduces the same oversized response, so it is never retried:\n\n- Surfaces immediately with the original local diagnostic, regardless of `retry.*` settings.\n- Like snapshot failures, it never charges the fallback controller (the started attempt's provisional charge is discarded), never advances models, never emits `model_fallback_switched`, and never mutates or rotates credentials.\n\n## Retry lifecycle and state transitions\n\nSession state used by retry:\n\n- `#retryAttempt: number` (`0` means idle)\n- `#retryPromise: Promise<void> | undefined` (tracks in-progress retry lifecycle)\n- `#retryResolve: (() => void) | undefined` (resolves `#retryPromise`)\n- `#retryAbortController: AbortController | undefined` (cancels backoff sleep)\n\nFlow (`#handleRetryableError`):\n\n1. Read `retry` settings group.\n2. If `retry.enabled === false`, stop immediately (`false`, no retry started). Managed provider-fallback failures keep their own chain policy; local snapshot and buffer-overflow failures surface immediately regardless of this setting.\n3. Increment `#retryAttempt`.\n4. Create `#retryPromise` once (first attempt in a chain).\n5. In the legacy single-model path, ordinary transient errors retry without an attempt limit. Typed provider-overload replays, canonical idle-stream watchdog stalls, and unknown/no-code errors stop after `retry.maxRetries`. Managed fallback instead uses its controller's per-entry `fallback.maxAttempts` budget.\n6. Compute exponential full-jitter delay capped at `retry.maxDelayMs`. Managed fallback ignores Retry-After when calculating retry delays and advances when its attempt budget is consumed. Legacy parsed provider retry-after values still override computed backoff and are capped at `retry.maxDelayMs`.\n7. For usage-limit errors, call auth storage (`markUsageLimitReached(...)`); if credential switching succeeds, force delay to `0`, otherwise use the applicable backoff.\n8. Eligible ordered role-array fallback chains advance on entry-budget exhaustion. A selected fallback entry remains sticky until the head selector's rate-limit cooldown expires, when `retry.fallbackRevertPolicy: cooldown-expiry` probes it again on a new turn.\n9. Emit `auto_retry_start`.\n10. Remove the trailing assistant error message from agent runtime state (kept in persisted session history).\n11. Sleep with abort support.\n12. Schedule `agent.continue()` through the post-prompt task scheduler (`delayMs: 1`) for the same prompt generation.\n\n### What resets retry counters\n\n`#retryAttempt` resets to `0` in these cases:\n\n- first successful non-error, non-aborted assistant message after retries started (emits `auto_retry_end { success: true }`)\n- retry cancellation during backoff sleep\n- max retries exceeded path\n\n`#retryPromise` resolves/clears when retry chain ends (success, cancellation, or max-exceeded), via `#resolveRetry()`.\n\n## Preferred credential quota fallback\n\n`--prefer-credential <selector>` gives one active stored OAuth credential first priority without pinning it:\n\n```bash\ngjc --prefer-credential id:15\ngjc --prefer-credential email:name@example.com\ngjc --prefer-credential anthropic/id:15\ngjc --resume --prefer-credential id:15\n```\n\nThe selector works for any provider backed by a multi-account OAuth credential pool; it is not Anthropic-specific. API-key credentials and runtime `--api-key` overrides are intentionally outside this soft-selection path, and `--credential` (the hard pin) and `--prefer-credential` are mutually exclusive.\n\nA usable preferred credential is placed ahead of candidates ordered by the provider's existing balanced/earliest-reset ranking. A content-free quota or rate-limit failure marks that row blocked, switches immediately to another active candidate, and replays the request with zero delay — the same `markUsageLimitReached` credential-switch path documented above under \"What starts a retry\", step 7. The fallback row then remains sticky for the session like any other credential switch. Partial assistant output or tool execution still prevents replay, and exhaustion of every row surfaces the final error without a retry loop, including the earliest stored `blockedUntil` as a `retryable at <ISO-8601>` hint. `403 forbidden` remains an authorization failure and never mutates quota state.\n\nAn unqualified selector (no `provider/` prefix) must match exactly one active OAuth provider's credential pool; an ambiguous match across providers fails startup and asks for an explicit `provider/<selector>` prefix. Once resolved, the model that the session ends up using must belong to that same provider — a default model, restored session model, or explicit `--model` from a different provider fails closed with an error naming both providers, instead of silently stranding the preference.\n\n## Backoff and max-attempt semantics\n\nSettings:\n\n- `retry.enabled` (default `true`)\n- `retry.maxRetries` (default `3`)\n- `retry.baseDelayMs` (default `2000`)\n- `retry.maxDelayMs` (default `300000`)\n- `retry.requestMaxRetries` (default `5`) — provider request retries before a stream is established; counts retries, not the initial request\n- `retry.streamMaxRetries` (default `5`) — provider stream replay retries for replay-safe transient stream failures; counts retries, not the initial stream attempt\n\nAttempt numbering:\n\n- attempt counter is incremented before max-check\n- start events use current attempt (1-based)\n- max-exceeded end event reports `attempt: this.#retryAttempt - 1` (last attempted retry count)\n\nBackoff uses capped exponential full jitter. With default settings the maximum jitter windows are:\n\n- attempt 1: 2000 ms\n- attempt 2: 4000 ms\n- attempt 3: 8000 ms\n\nManaged fallback uses capped exponential full jitter and `fallback.maxAttempts` (including the initial request), not Retry-After, to schedule attempts. Rate-limit selector cooldown is recorded when the entry advances or exhausts, rather than interrupting its remaining attempts. Credential rotation retains its existing policy. `retry.maxDelayMs` caps legacy retry delays including provider hints; zero retains the existing uncapped backoff setting. In the legacy single-model path, transient errors have unbounded attempts except canonical idle-stream watchdog stalls, which are bounded by `retry.maxRetries`; unknown/no-code errors use the same bound.\n\n## Abort mechanics\n\n### Explicit retry abort\n\n`abortRetry()`:\n\n- aborts `#retryAbortController` (if present)\n- resolves retry promise (`#resolveRetry()`) so awaiters are unblocked\n\nIf abort hits while sleeping, catch path emits:\n\n- `auto_retry_end { success: false, finalError: \"Retry cancelled\" }`\n- resets attempt/controller\n\n### Global operation abort interaction\n\n`abort()` calls `abortRetry()` before aborting the active agent stream. This guarantees retry backoff is cancelled when user issues a general abort.\n\n### TUI interaction\n\nOn `auto_retry_start`, EventController:\n\n- swaps `Esc` handler to `session.abortRetry()`\n- renders loader text: `Retrying (attempt/maxAttempts) in Ns… (esc to cancel)`\n\nOn `auto_retry_end`, it restores prior `Esc` handler and clears loader state.\n\n## Streaming and prompt completion behavior\n\nA prompt's full settlement (`#settleEndedInFlight(..., \"full\")`) awaits `#waitForPostPromptRecovery()` after the agent run ends. That helper loops until `#retryPromise`, the TTSR resume promise, and outstanding post-prompt recovery tasks have all settled.\n\nEffect:\n\n- a prompt call does not fully resolve until any started retry chain finishes (success/failure/cancel)\n- retry lifecycle is part of one logical prompt execution boundary\n\nThis prevents callers from treating a retrying turn as complete too early.\n\n## Controls: settings and SDK actions\n\n### Configuration knobs\n\nThe standard retry controls are defined in the settings schema under `retry`:\n\n- `retry.enabled`\n- `retry.maxRetries`\n- `retry.baseDelayMs`\n- `retry.maxDelayMs`\n\nFallback candidates are configured as ordered selector arrays on preset `model_mapping` roles, top-level `modelRoles`, or `task.agentModelOverrides`; `fallback.maxAttempts` controls the total request-time attempts per concrete entry. Resolution-time unavailable, unauthenticated, and unknown entries advance immediately without consuming that budget.\n\nAn entry that fails out of a managed chain (advance or exhaustion) opens a circuit in the shared model registry, so later turns, chain restarts, and sibling sessions such as subagents skip it without spending its attempt budget again. The cooldown starts at `fallback.circuitCooldownMs` (default 60s) and doubles on each consecutive open up to `fallback.circuitMaxCooldownMs` (default 30m); an accepted response on the entry closes the circuit and resets the escalation. A typed Retry-After replaces the local cooldown, so sibling sessions also wait for the provider-specified time. After the cooldown the circuit is half-open. The first session to resolve the entry claims a probe lease for one cooldown window, and other sessions keep skipping the entry until the probe succeeds (closing the circuit) or fails (re-opening it with a doubled cooldown). With `retry.fallbackRevertPolicy: cooldown-expiry`, the next turn probes the head again. Every chain resolver, including profile activation, the model selector, and subagent resolution, consults the same circuit state. The last chain entry is never skipped, and a chain whose entries are all open still probes the first one. Set `fallback.circuitCooldownMs: 0` to disable the breaker.\n\nOn settings load, a source-aware one-shot migration still reads legacy `retry.fallbackChains` and combines the effective role chain with its ordered, deduplicated legacy tail into the corresponding role array. The legacy key is ignored after migration; it is not a retry configuration surface.\n\nProgrammatic toggles in session:\n\n- `setAutoRetryEnabled(enabled)` writes `retry.enabled`\n- `autoRetryEnabled` reads `retry.enabled`\n- `isRetrying` reports whether retry lifecycle promise is active\n\n### External control\n\nExternal clients observe retry lifecycle through the [SDK machine interface](./sdk.md). The removed RPC command surface and `RpcClient` helpers are not supported.\n\n## Event emission and failure surfacing\n\nSession-level retry events:\n\n- `auto_retry_start { attempt, maxAttempts, delayMs, errorMessage }`\n- `auto_retry_end { success, attempt, finalError? }`\n- `model_fallback_switched { eventId, from, to, reason, role, scope, activeIndex, chainLength, attemptsUsed }` — emitted once for each real fallback-model switch\n\nPropagation:\n\n- emitted through `AgentSession.subscribe(...)`\n- forwarded to extension runner as extension events\n- exposed to external clients through SDK event subscriptions\n- in the TUI, `model_fallback_switched` updates the fallback-model status/notice and `EventController` consumes retry lifecycle events for loader/error UI\n\nFinal failure surfacing:\n\n- On max-exceeded or cancellation, `auto_retry_end.success === false`\n- TUI shows: `Retry failed after N attempts: <finalError>`\n- Extensions/hooks receive `auto_retry_end` with same fields\n- SDK clients receive the same event stream\n\n## Permanent stop conditions\n\nRetry stops and will not auto-continue when any of these occur:\n\n- `retry.enabled` is false, or legacy retry settings have not been explicitly configured (`legacyRetryConfigured` fail-closed gate) — except for the bare-default admissions listed above\n- error is not retry-classified\n- error is context overflow (delegated to compaction path)\n- max retries exceeded\n- user cancels retry through the session/SDK action or `Esc` during retry loader\n- global abort (`abort`) cancels retry first\n\nA new retry chain can still start later on a future retryable error after counters reset.\n\n## Operational caveats\n\n- Managed fallback uses typed transport facts and provider error codes; regex text matching is limited to the legacy retry path.\n- Retry strips the failing assistant error from **runtime context** before re-continue, but session history still keeps that error entry.\n- SDK clients observe retry state through session events and state updates.\n- Fallback state is driven by the configured ordered role array and remains on a selected fallback entry across later user prompts. A real model change emits the canonical `model_fallback_switched` event rather than a legacy retry-fallback event.\n- Temporary provider-session scopes retain and restore their own fallback controller and provider state when unwound; an authoritative model selection commits those temporary scopes.\n\n## Provider request/stream retry budgets\n\nThe provider budgets are deliberately separate from session auto-retry:\n\n```yaml\nretry:\n requestMaxRetries: 4\n streamMaxRetries: 100\n```\n\n`requestMaxRetries` maps to provider SDK/fetch retry counts for request setup failures such as retryable 5xx/408/429/network errors. `streamMaxRetries` maps to provider-specific stream replay loops that are safe to repeat without duplicating visible assistant output. Providers that cannot safely replay a stream continue to surface the terminal error so the session-level auto-retry layer can decide whether to retry the turn.\n\nFail-fast cases stay fail-fast: invalid credentials (after any credential-refresh path is exhausted), unsupported model/provider configuration, malformed requests, context overflow, explicit user aborts, and permanent quota failures are not treated as transient provider budget candidates.\n",
|
|
72
|
+
"non-compaction-retry-policy.md": "# Non-compaction auto-retry policy\n\nThis document describes the standard API-error retry path in `AgentSession`.\n\nIt explicitly excludes context-overflow recovery via auto-compaction. Overflow is handled by compaction logic and is documented separately in [`compaction.md`](../docs/compaction.md).\n\n## Implementation files\n\n- [`../src/session/agent-session.ts`](../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/config/settings-schema.ts`](../packages/coding-agent/src/config/settings-schema.ts)\n- [`../src/modes/controllers/event-controller.ts`](../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`sdk.md`](./sdk.md) for the external machine interface.\n\n## Scope boundary vs compaction\n\nRetry and compaction are checked from the same `agent_end` path, but they are intentionally separated:\n\n1. `agent_end` inspects the last assistant message.\n2. `#isRetryableError(...)` runs first.\n3. If retry is initiated, compaction checks are skipped for that turn.\n4. Context-overflow errors are hard-excluded from retry classification (`isContextOverflow(...)` short-circuits retry).\n5. Overflow therefore falls through to `#checkCompaction(...)` instead of standard retry.\n\nSo: overload/rate/server/network-style failures use this retry policy; context-window overflow uses compaction recovery.\n\n## Retry classification\n\n`#isRetryableError(...)` requires all of the following:\n\n- assistant `stopReason === \"error\"`\n- `errorMessage` exists\n- message is **not** context overflow\n- `errorMessage` matches transient transport/envelope patterns or `isUsageLimitError(...)`\n\nCurrent retryable inputs are regex/string-classified:\n\n- transient transport/envelope failures, including Anthropic stream-envelope failures before `message_start`\n- overloaded/provider-returned-error wording\n- rate limit / usage limit / too many requests\n- HTTP-like server classes: 429, 500, 502, 503, 504\n- service unavailable / server/internal error\n- provider-suggested retry wording, including OpenAI `retry your request` failures\n- network/connection/socket failures, refused/closed connections, upstream connect/reset-before-headers, socket hang up, timeout/timed out, fetch failed, terminated, retry delay wording, and unexpected socket close messages\n- canonical idle-stream watchdog stalls (`stream stalled while waiting for the next event`); in the legacy single-model path these remain retryable but use the bounded `retry.maxRetries` budget\n- canonical local snapshot failure classification (`errorKind: \"local_snapshot_failure\"`, or the stable `Managed fallback attempt could not produce a serializable event snapshot` message prefix for restored sessions) is recognized, but only so the failure can be routed to its immediate-surface policy below — it is never re-issued\n\nManaged fallback uses structured transport facts and typed provider error codes when available. A structured classification of `other` becomes the bounded `unknown` fallback class; error prose cannot promote it to quota or transient. Regex classification is retained only as a legacy fallback.\n\n### Bare-default admissions\n\nA session with no explicit `retry.*` settings and a single-model default role (no managed fallback) does not use the classification list above on its own. It admits only these content-free failures:\n\n- canonical first-event and idle-stream watchdog aborts, recognized from the typed timeout fact or an exact canonical sentinel message\n- the OpenAI Codex `server_is_overloaded`, `server_error`, and `internal_error` events, recognized from that provider's typed code or the exact canonical `Codex error event … (code=<code>)` message with no conflicting transport facts. Explicit Codex terminal vetoes (for example `invalid_prompt` or `invalid_function_parameters`, including a `server_error` whose message carries `Request blocked (code=invalid_prompt)`) and terminal-error wording are never admitted, on this path or under configured `retry.*` settings\n- the generic OpenAI Responses `server_is_overloaded` terminal envelope, recognized from the exact statusless `openaiErrorCode` and matching `providerCode`\n- Anthropic's typed `overloaded_error` envelope, recognized by parsing the error envelope and requiring both the outer `type` and the nested `error.type` to match\n\nOverload and Codex server/internal-error admissions therefore require a provider-specific typed signature (or, for Codex, its exact canonical error-event message), while watchdog admissions accept only their canonical sentinel messages. Every admission additionally requires that the attempt carry no assistant text, thinking, or tool call and no conflicting transport facts; a status-bearing or otherwise typed failure surfaces instead. Untyped or noncanonical overload and timeout wording never authorizes a replay.\n\n### Local snapshot failures (surface immediately, no retry)\n\n`local_snapshot_failure` is a local machinery fault, not provider evidence. The retained producer shape is deterministic, so re-streaming the same request only reproduces the same local defect; it is surfaced immediately instead of being amplified across identical retries:\n\n- Surfaces immediately with the original producer-boundary diagnostic, regardless of `retry.*` settings.\n- Never charges the fallback controller (the started attempt's provisional charge is discarded), never advances models, never emits `model_fallback_switched`, and never mutates or rotates credentials.\n\n### Local buffer overflows (surface immediately, no retry)\n\n`local_buffer_overflow` (`errorKind`, or the stable `Managed fallback attempt exceeded the provisional event buffer limit` message prefix for restored sessions) is the sibling local staging fault: the provisional managed-attempt buffer exceeded its cap. Like snapshot failures, re-streaming the same request reproduces the same oversized response, so it is never retried:\n\n- Surfaces immediately with the original local diagnostic, regardless of `retry.*` settings.\n- Like snapshot failures, it never charges the fallback controller (the started attempt's provisional charge is discarded), never advances models, never emits `model_fallback_switched`, and never mutates or rotates credentials.\n\n## Retry lifecycle and state transitions\n\nSession state used by retry:\n\n- `#retryAttempt: number` (`0` means idle)\n- `#retryPromise: Promise<void> | undefined` (tracks in-progress retry lifecycle)\n- `#retryResolve: (() => void) | undefined` (resolves `#retryPromise`)\n- `#retryAbortController: AbortController | undefined` (cancels backoff sleep)\n\nFlow (`#handleRetryableError`):\n\n1. Read `retry` settings group.\n2. If `retry.enabled === false`, stop immediately (`false`, no retry started). Managed provider-fallback failures keep their own chain policy; local snapshot and buffer-overflow failures surface immediately regardless of this setting.\n3. Increment `#retryAttempt`.\n4. Create `#retryPromise` once (first attempt in a chain).\n5. In the legacy single-model path, ordinary transient errors retry without an attempt limit. Typed provider-overload replays, canonical idle-stream watchdog stalls, and unknown/no-code errors stop after `retry.maxRetries`. Managed fallback instead uses its controller's per-entry `fallback.maxAttempts` budget.\n6. Compute exponential full-jitter delay capped at `retry.maxDelayMs`. Managed fallback ignores Retry-After when calculating retry delays and advances when its attempt budget is consumed. Legacy parsed provider retry-after values still override computed backoff and are capped at `retry.maxDelayMs`.\n7. For usage-limit errors, call auth storage (`markUsageLimitReached(...)`); if credential switching succeeds, force delay to `0`, otherwise use the applicable backoff.\n8. Eligible ordered role-array fallback chains advance on entry-budget exhaustion. A selected fallback entry remains sticky until the head selector's rate-limit cooldown expires, when `retry.fallbackRevertPolicy: cooldown-expiry` probes it again on a new turn.\n9. Emit `auto_retry_start`.\n10. Remove the trailing assistant error message from agent runtime state (kept in persisted session history).\n11. Sleep with abort support.\n12. Schedule `agent.continue()` through the post-prompt task scheduler (`delayMs: 1`) for the same prompt generation.\n\n### What resets retry counters\n\n`#retryAttempt` resets to `0` in these cases:\n\n- first successful non-error, non-aborted assistant message after retries started (emits `auto_retry_end { success: true }`)\n- retry cancellation during backoff sleep\n- max retries exceeded path\n\n`#retryPromise` resolves/clears when retry chain ends (success, cancellation, or max-exceeded), via `#resolveRetry()`.\n\n## Preferred credential quota fallback\n\n`--prefer-credential <selector>` gives one active stored OAuth credential first priority without pinning it:\n\n```bash\ngjc --prefer-credential id:15\ngjc --prefer-credential email:name@example.com\ngjc --prefer-credential anthropic/id:15\ngjc --resume --prefer-credential id:15\n```\n\nThe selector works for any provider backed by a multi-account OAuth credential pool; it is not Anthropic-specific. API-key credentials and runtime `--api-key` overrides are intentionally outside this soft-selection path, and `--credential` (the hard pin) and `--prefer-credential` are mutually exclusive.\n\nA usable preferred credential is placed ahead of candidates ordered by the provider's existing balanced/earliest-reset ranking. A content-free quota or rate-limit failure marks that row blocked, switches immediately to another active candidate, and replays the request with zero delay — the same `markUsageLimitReached` credential-switch path documented above under \"What starts a retry\", step 7. The fallback row then remains sticky for the session like any other credential switch. Partial assistant output or tool execution still prevents replay, and exhaustion of every row surfaces the final error without a retry loop, including the earliest stored `blockedUntil` as a `retryable at <ISO-8601>` hint. `403 forbidden` remains an authorization failure and never mutates quota state.\n\nAn unqualified selector (no `provider/` prefix) must match exactly one active OAuth provider's credential pool; an ambiguous match across providers fails startup and asks for an explicit `provider/<selector>` prefix. Once resolved, the model that the session ends up using must belong to that same provider — a default model, restored session model, or explicit `--model` from a different provider fails closed with an error naming both providers, instead of silently stranding the preference.\n\n## Backoff and max-attempt semantics\n\nSettings:\n\n- `retry.enabled` (default `true`)\n- `retry.maxRetries` (default `3`)\n- `retry.baseDelayMs` (default `2000`)\n- `retry.maxDelayMs` (default `300000`)\n- `retry.requestMaxRetries` (default `5`) — provider request retries before a stream is established; counts retries, not the initial request\n- `retry.streamMaxRetries` (default `5`) — provider stream replay retries for replay-safe transient stream failures; counts retries, not the initial stream attempt\n\nAttempt numbering:\n\n- attempt counter is incremented before max-check\n- start events use current attempt (1-based)\n- max-exceeded end event reports `attempt: this.#retryAttempt - 1` (last attempted retry count)\n\nBackoff uses capped exponential full jitter. With default settings the maximum jitter windows are:\n\n- attempt 1: 2000 ms\n- attempt 2: 4000 ms\n- attempt 3: 8000 ms\n\nManaged fallback uses capped exponential full jitter and `fallback.maxAttempts` (including the initial request), not Retry-After, to schedule attempts. Rate-limit selector cooldown is recorded when the entry advances or exhausts, rather than interrupting its remaining attempts. Credential rotation retains its existing policy. `retry.maxDelayMs` caps legacy retry delays including provider hints; zero retains the existing uncapped backoff setting. In the legacy single-model path, transient errors have unbounded attempts except canonical idle-stream watchdog stalls, which are bounded by `retry.maxRetries`; unknown/no-code errors use the same bound.\n\n## Abort mechanics\n\n### Explicit retry abort\n\n`abortRetry()`:\n\n- aborts `#retryAbortController` (if present)\n- resolves retry promise (`#resolveRetry()`) so awaiters are unblocked\n\nIf abort hits while sleeping, catch path emits:\n\n- `auto_retry_end { success: false, finalError: \"Retry cancelled\" }`\n- resets attempt/controller\n\n### Global operation abort interaction\n\n`abort()` calls `abortRetry()` before aborting the active agent stream. This guarantees retry backoff is cancelled when user issues a general abort.\n\n### TUI interaction\n\nOn `auto_retry_start`, EventController:\n\n- swaps `Esc` handler to `session.abortRetry()`\n- renders loader text: `Retrying (attempt/maxAttempts) in Ns… (esc to cancel)`\n\nOn `auto_retry_end`, it restores prior `Esc` handler and clears loader state.\n\n## Streaming and prompt completion behavior\n\nA prompt's full settlement (`#settleEndedInFlight(..., \"full\")`) awaits `#waitForPostPromptRecovery()` after the agent run ends. That helper loops until `#retryPromise`, the TTSR resume promise, and outstanding post-prompt recovery tasks have all settled.\n\nEffect:\n\n- a prompt call does not fully resolve until any started retry chain finishes (success/failure/cancel)\n- retry lifecycle is part of one logical prompt execution boundary\n\nThis prevents callers from treating a retrying turn as complete too early.\n\n## Controls: settings and SDK actions\n\n### Configuration knobs\n\nThe standard retry controls are defined in the settings schema under `retry`:\n\n- `retry.enabled`\n- `retry.maxRetries`\n- `retry.baseDelayMs`\n- `retry.maxDelayMs`\n\nFallback candidates are configured as ordered selector arrays on preset `model_mapping` roles, top-level `modelRoles`, or `task.agentModelOverrides`; `fallback.maxAttempts` controls the total request-time attempts per concrete entry. Resolution-time unavailable, unauthenticated, and unknown entries advance immediately without consuming that budget.\n\nAn entry that fails out of a managed chain (advance or exhaustion) opens a circuit in the shared model registry, so later turns, chain restarts, and sibling sessions such as subagents skip it without spending its attempt budget again. The cooldown starts at `fallback.circuitCooldownMs` (default 60s) and doubles on each consecutive open up to `fallback.circuitMaxCooldownMs` (default 30m); an accepted response on the entry closes the circuit and resets the escalation. A typed Retry-After replaces the local cooldown, so sibling sessions also wait for the provider-specified time. After the cooldown the circuit is half-open. The first session to resolve the entry claims a probe lease for one cooldown window, and other sessions keep skipping the entry until the probe succeeds (closing the circuit) or fails (re-opening it with a doubled cooldown). With `retry.fallbackRevertPolicy: cooldown-expiry`, the next turn probes the head again. Every chain resolver, including profile activation, the model selector, and subagent resolution, consults the same circuit state. The last chain entry is never skipped, and a chain whose entries are all open still probes the first one. Set `fallback.circuitCooldownMs: 0` to disable the breaker.\n\nOn settings load, a source-aware one-shot migration still reads legacy `retry.fallbackChains` and combines the effective role chain with its ordered, deduplicated legacy tail into the corresponding role array. The legacy key is ignored after migration; it is not a retry configuration surface.\n\nProgrammatic toggles in session:\n\n- `setAutoRetryEnabled(enabled)` writes `retry.enabled`\n- `autoRetryEnabled` reads `retry.enabled`\n- `isRetrying` reports whether retry lifecycle promise is active\n\n### External control\n\nExternal clients observe retry lifecycle through the [SDK machine interface](./sdk.md). The removed RPC command surface and `RpcClient` helpers are not supported.\n\n## Event emission and failure surfacing\n\nSession-level retry events:\n\n- `auto_retry_start { attempt, maxAttempts, delayMs, errorMessage }`\n- `auto_retry_end { success, attempt, finalError? }`\n- `model_fallback_switched { eventId, from, to, reason, role, scope, activeIndex, chainLength, attemptsUsed }` — emitted once for each real fallback-model switch\n\nPropagation:\n\n- emitted through `AgentSession.subscribe(...)`\n- forwarded to extension runner as extension events\n- exposed to external clients through SDK event subscriptions\n- in the TUI, `model_fallback_switched` updates the fallback-model status/notice and `EventController` consumes retry lifecycle events for loader/error UI\n\nFinal failure surfacing:\n\n- On max-exceeded or cancellation, `auto_retry_end.success === false`\n- TUI shows: `Retry failed after N attempts: <finalError>`\n- Extensions/hooks receive `auto_retry_end` with same fields\n- SDK clients receive the same event stream\n\n## Permanent stop conditions\n\nRetry stops and will not auto-continue when any of these occur:\n\n- `retry.enabled` is false, or legacy retry settings have not been explicitly configured (`legacyRetryConfigured` fail-closed gate) — except for the bare-default admissions listed above\n- error is not retry-classified\n- error is context overflow (delegated to compaction path)\n- max retries exceeded\n- user cancels retry through the session/SDK action or `Esc` during retry loader\n- global abort (`abort`) cancels retry first\n\nA new retry chain can still start later on a future retryable error after counters reset.\n\n## Operational caveats\n\n- Managed fallback uses typed transport facts and provider error codes; regex text matching is limited to the legacy retry path.\n- Retry strips the failing assistant error from **runtime context** before re-continue, but session history still keeps that error entry.\n- SDK clients observe retry state through session events and state updates.\n- Fallback state is driven by the configured ordered role array and remains on a selected fallback entry across later user prompts. A real model change emits the canonical `model_fallback_switched` event rather than a legacy retry-fallback event.\n- Temporary provider-session scopes retain and restore their own fallback controller and provider state when unwound; an authoritative model selection commits those temporary scopes.\n\n## Provider request/stream retry budgets\n\nThe provider budgets are deliberately separate from session auto-retry:\n\n```yaml\nretry:\n requestMaxRetries: 4\n streamMaxRetries: 100\n```\n\n`requestMaxRetries` maps to provider SDK/fetch retry counts for request setup failures such as retryable 5xx/408/429/network errors. `streamMaxRetries` maps to provider-specific stream replay loops that are safe to repeat without duplicating visible assistant output. Providers that cannot safely replay a stream continue to surface the terminal error so the session-level auto-retry layer can decide whether to retry the turn.\n\nFail-fast cases stay fail-fast: invalid credentials (after any credential-refresh path is exhausted), unsupported model/provider configuration, malformed requests, context overflow, explicit user aborts, and permanent quota failures are not treated as transient provider budget candidates.\n",
|
|
73
73
|
"notebook-tool-runtime.md": "# Notebook runtime internals\n\nThis document describes how coding-agent reads and edits Jupyter notebooks (`.ipynb`) and how that relates to Python execution.\n\nThe critical distinction: **there is no dedicated `notebook` tool, and nothing automatically executes notebook cells**. The notebook-aware `read` route and the `replace`, `patch`, `hashline`, and `apply_patch` edit modes convert a notebook to an editable plain-text cell representation and serialize those edits back to notebook JSON; `vim` and `write` use their ordinary text paths described below. Running Python goes through the `eval` tool (`language: \"py\"`) or the `python` tool. Neither automatically loads notebook cells, though supplied Python code can use ordinary filesystem APIs to read or write files.\n\n## Implementation files\n\n- [`src/edit/notebook.ts`](../packages/coding-agent/src/edit/notebook.ts) — notebook JSON ↔ editable text conversion\n- [`src/edit/read-file.ts`](../packages/coding-agent/src/edit/read-file.ts) — edit-mode read/serialize hooks that route `.ipynb` through the converter\n- [`src/tools/read.ts`](../packages/coding-agent/src/tools/read.ts) — `read` tool notebook branch\n- [`src/tools/eval.ts`](../packages/coding-agent/src/tools/eval.ts), [`src/tools/python.ts`](../packages/coding-agent/src/tools/python.ts) — Python execution surfaces (see [`python-repl.md`](python-repl.md))\n\n## 1) Editable cell representation\n\n`notebookToEditableText()` renders each cell as a marker line followed by the cell source, and joins cells with `\\n`:\n\n```text\n# %% [markdown] cell:0\n# Title\n# %% [code] cell:1\nimport pandas as pd\ndf = pd.read_csv(\"data.csv\")\n# %% [raw] cell:2\n```\n\n- Marker format: `# %% [<cell_type>] cell:<index>`, where `<cell_type>` is `code`, `markdown`, or `raw` and `<index>` is the cell's position in the original notebook.\n- A cell with empty source renders as the marker line alone.\n- Only cell sources are shown. On round-trip, top-level notebook fields and metadata on matched original cells are preserved. Existing outputs and execution counts are preserved only for matched cells that remain code cells; changing a matched cell to a non-code type removes those fields. New and removed cells follow the rules in [Round-trip semantics](#3-round-trip-semantics).\n- A cell source that ends with a newline shows as a blank line before the next marker and round-trips with that newline intact.\n\n## 2) Tool integration\n\n### `read`\n\n- A `.ipynb` path is converted with `readEditableNotebookText()` unless the `:raw` selector is used. `:raw` returns the notebook JSON verbatim.\n- Line selectors and multi-range selectors apply to the converted text, not the JSON. Results are labelled as `notebook` content.\n- See [`tools/read.md`](tools/read.md#jupyter-notebooks) for truncation defaults.\n\n### `edit`\n\nFor `.ipynb` updates, the `replace`, `patch`, and `hashline` edit modes and `apply_patch` updates route existing-file content through `readEditFileText()` and serialize edits through `serializeEditFileText()`. An `apply_patch` create operation skips the read step but still uses the serializer when writing the new `.ipynb` file.\n\n- On the existing-file read path, `readEditFileText()` enforces the `MAX_EDIT_FILE_BYTES` (8 MiB) guard **before** notebook conversion, so an oversized notebook fails fast instead of being parsed on the main thread.\n- For `.ipynb`, the edit mode then operates on the editable text, and `serializeEditFileText()` maps the edited text back to notebook JSON via `serializeEditedNotebookText()`.\n- If the notebook does not exist at serialization time (an edit mode creating a new file), edits apply to a new empty notebook (`nbformat` 4, `nbformat_minor` 5).\n- Notebook JSON is written with `JSON.stringify(notebook, null, 1)`.\n\nThe `vim` edit mode reads the file as UTF-8 text and edits the notebook's raw JSON text directly; it does not use the notebook cell converter. Saving follows the ordinary write-through path.\n\n### `write`\n\n`write` does not use the notebook converter. It follows the ordinary write path and does not translate editable cell markers into notebook JSON, so supply valid notebook JSON when the result should remain a notebook.\n\n## 3) Round-trip semantics\n\n`applyNotebookEditableText()` parses the edited text into cells and rebuilds the `cells` array from a deep clone of the original notebook, so notebook-level `metadata`, `nbformat`, and any other top-level keys are preserved.\n\nFor each parsed cell, in order:\n\n- **Marker with `cell:N` that refers to an unused original cell**: that original cell is cloned, keeping its metadata, outputs, and any other keys. Its `cell_type` is set from the marker and its `source` is replaced.\n - If the result is a `code` cell, missing `execution_count` / `outputs` are initialized to `null` / `[]`; existing values are kept.\n - If the result is a non-code cell, `execution_count` and `outputs` are removed.\n- **Marker without `cell:N`, with an out-of-range index, or with an index already used earlier in the text**: a new cell is created with `metadata: {}`; new code cells also get `execution_count: null` and `outputs: []`.\n- Original cells whose markers are removed from the text are dropped.\n\nConsequences:\n\n- Deleting a cell = deleting its marker and source.\n- Inserting a cell = adding a marker without an index (for example `# %% [code]`).\n- Moving a cell = moving its marker block; keeping the original `cell:N` preserves its outputs and metadata.\n- Duplicating a marker's index gives the first occurrence the original cell and makes later occurrences new cells.\n\nCell source is stored as a line array with trailing newlines preserved (`splitNotebookSource()`): every line except the last keeps its `\\n`, and the last line has no forced trailing newline.\n\n## 4) Error surfaces\n\nConversion throws for:\n\n- missing file on read: `File not found: <path>` (serialization of a newly created notebook starts from an empty notebook instead)\n- invalid JSON: `Invalid JSON in notebook: <path>`\n- non-object root or missing `cells` array: `Invalid notebook structure (...)`\n- a cell that is not an object or has a `cell_type` other than `code` / `markdown` / `raw`: `Invalid notebook cell <index> in <path>`\n- edited text whose first line is not a cell marker: `Invalid notebook editable representation for <path>: ...`\n\n## 5) Relationship to Python execution\n\nNotebook conversion and Python execution share no code path:\n\n- Notebook-aware `read`/`edit` paths never start a kernel or execute cells. The edit converter preserves or clears output fields according to the round-trip rules above; it does not execute cells to produce new outputs.\n- `eval` (`language: \"py\"`) and `python` run code in a subprocess-backed Python runner (`src/eval/py/`). Kernel lifecycle, session reuse, cancellation, display capture, and output truncation are documented in [`python-repl.md`](python-repl.md).\n\nTo run code from a notebook, read the relevant cells and pass their source to `eval` or `python` explicitly. No built-in notebook-aware path both edits an `.ipynb` document and automatically executes its cells in a kernel.\n",
|
|
74
74
|
"onboarding-packet.md": "# Gajae-Code Onboarding Packet\n\nThis packet is a docs-only, public-safe context seed for the `gajae-code` repository as inspected on 2026-06-01. It is intentionally a no-new-skill artifact: not a new workflow skill, command, agent, configuration surface, issue template, or runtime behavior.\n\n## Purpose in one paragraph\n\nGajae-Code is the `gjc` coding-agent CLI and supporting monorepo. The product centers on a small public workflow loop: clarify with `deep-interview`, plan with `ralplan`, execute and verify through `ultragoal`, and run goal-directed research missions with `autoresearch`. The main product package is `packages/coding-agent/`; supporting packages provide LLM/provider access, agent runtime, TUI rendering, native helpers, stats, utilities, benchmarks, and SDK machine interfaces.\n\n## Fixed public surface\n\nKeep this invariant front-and-center when onboarding to the repo:\n\n- Default workflow skills: `autoresearch`, `deep-interview`, `ralplan`, `ultragoal`.\n- Public role agents: `executor`, `architect`, `planner`, `critic`.\n- Bundled default workflow skill sources live under `packages/coding-agent/src/defaults/gjc/skills/`.\n- Bundled role-agent prompt sources live under `packages/coding-agent/src/prompts/agents/`.\n- Runtime state, specs, plans, goals, research missions, and local overrides belong under `.gjc/` for the product and `.omx/` only for this agent-run orchestration.\n\nDo not add a fifth default skill, fifth public role agent, new command, new config surface, or feature-intake behavior unless that product decision has already been made and the default-surface gates are updated.\n\n## Primary entrypoints\n\n| Area | Repo-relative path | Why it matters |\n| ---------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------- |\n| CLI bootstrap | `packages/coding-agent/src/cli.ts` | Registers top-level CLI commands and routes default launch behavior. |\n| Session launch | `packages/coding-agent/src/main.ts` | Converts CLI/runtime settings into agent-session creation and mode dispatch. |\n| Agent assembly | `packages/coding-agent/src/sdk/session.ts` | Loads settings, default skills, rules, tools, auth/model state, system prompt, and agent runtime. |\n| Built-in tools | `packages/coding-agent/src/tools/index.ts` | Registers file, shell, edit, search, browser, task/subagent, and related public coding-harness tools. Memory backends are private integrations, not public tools. |\n| Default skills | `packages/coding-agent/src/defaults/gjc-defaults.ts` | Embeds and installs the four default workflow skills plus deep-interview fragments. |\n| Role agents | `packages/coding-agent/src/task/agents.ts` | Embeds bundled task-agent prompts; tests enforce public role-agent expectations. |\n| Product overview | `README.md` | Explains installation, product story, fixed workflow surface, and development entry commands. |\n| Architecture map | `docs/codebase-overview.md` | Public package map and runtime-flow reference. |\n\n## Package map\n\n- `packages/coding-agent/` — main `gjc` CLI, workflows, session runtime, tool registry, discovery, settings, prompts, and tests.\n- `packages/ai/` — provider/model boundary, streaming, auth, model registry, retries, and provider integrations.\n- `packages/agent/` — stateful agent loop and append-only context runtime.\n- `packages/tui/` — terminal UI framework and rendering primitives.\n- `packages/natives/` plus `crates/*` — native helpers, Rust/N-API bindings, shell/PTY, text search, AST, filesystem, and media utilities.\n- `packages/utils/` — shared TypeScript utilities, logging, formatting, process helpers, JSON/frontmatter, and sanitization.\n- `packages/stats/` — local observability dashboard and session/model usage aggregation.\n- `packages/typescript-edit-benchmark/` — TypeScript edit benchmark tooling.\n- External machine clients use the broker-bound SDK session CLI, Coordinator MCP, or managed adapters documented in `docs/sdk.md`; `--mode rpc`, `--mode rpc-ui`, `--mode bridge`, and `gjc sdk serve` were removed.\n\n## Build, test, and validation commands\n\nPrefer targeted checks first, then broader checks when code changes justify them. For this docs-only packet, lightweight validation is enough.\n\n| Command | Scope | When to use |\n| --------------------------------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------- |\n| `bun install` | Workspace dependencies | Initial local setup. |\n| `bun run install:defaults` | Local default install | Installs source-bundled default workflow definitions for local development. |\n| `bun packages/coding-agent/src/cli.ts --help` | CLI smoke/discovery | Fast source checkout CLI sanity check. |\n| `bun run check:ts` | Type/lint/default UI checks | Broad TypeScript validation; heavier than docs-only changes. |\n| `bun run test` | Full TS + Rust tests | Broad regression check; use for runtime/product changes. |\n| `bun run ci:test:smoke` | CLI version/help/stats worker smoke | Useful before release/install changes. |\n| `bun scripts/check-visible-definitions.ts` | Default surface gate | Required after workflow-definition changes. |\n| `bun scripts/verify-g002-gates.ts` | Rebrand/default-surface gate | Required after workflow-definition or public-surface changes. |\n| `bun scripts/rebrand-inventory.ts --strict` | Rebrand inventory gate | Required after workflow-definition or public-surface changes. |\n| `bun test packages/coding-agent/test/default-gjc-definitions.test.ts` | Four-skills/four-agents contract | Required after default workflow/agent surface changes. |\n\nRepository rule: do not run `tsc` or `npx tsc`; use the Bun scripts above.\n\n## Danger zones\n\n- **Default surface expansion:** `packages/coding-agent/src/defaults/gjc/skills/`, `packages/coding-agent/src/defaults/gjc-defaults.ts`, `packages/coding-agent/src/prompts/agents/`, and model-assignment tests are contract-heavy. Changes here can accidentally alter the fixed four-skills/four-agents shape.\n- **CLI commands:** `packages/coding-agent/src/cli.ts` and `packages/coding-agent/src/commands/` define visible behavior. Adding commands or aliases is a product-surface change.\n- **Runtime/session assembly:** `packages/coding-agent/src/main.ts`, `packages/coding-agent/src/sdk/session.ts`, discovery, settings, tools, and system-prompt paths can affect every session.\n- **TUI/logging:** Avoid `console.log`, `console.warn`, or `console.error` inside `packages/coding-agent/`; use the centralized logger to avoid corrupting TUI rendering.\n- **Secrets/auth/config:** Keep `docs/secrets.md`, auth broker/gateway code, settings, and environment-variable docs public-safe. Do not expose tokens or private infrastructure.\n- **Native/Rust build:** `packages/natives/` and `crates/*` can require platform-specific toolchains and CI artifact behavior.\n- **Generated model data:** Do not edit `packages/ai/src/models.json` directly; update generators/descriptors/resolvers and regenerate with `bun --cwd=packages/ai run generate-models`.\n\n## Unknowns worth preserving\n\n- Which onboarding packet shape will be most useful for future `gjc` context ingestion is still an experiment, not a product contract.\n- Public issue #158 / `gajae-deep-onboarding` context is summarized only from the user-provided prompt in this run; this packet does not add issue intake or feature workflow behavior.\n- Full CI may depend on runner/system dependencies and native artifacts; docs-only changes usually do not need the full matrix locally.\n- Some packages contain internal or hidden utility prompts/agents beyond the four public role agents. Public-facing docs should keep the four-role contract clear.\n\n## First safe tasks for a new contributor or agent\n\n1. Read `README.md`, `docs/codebase-overview.md`, and this packet.\n2. Run `bun packages/coding-agent/src/cli.ts --help` for a fast CLI surface check after dependencies are installed.\n3. For docs-only edits, run formatting/check commands that do not mutate runtime behavior.\n4. For default-surface edits, run the four required gates listed in the command table before claiming completion.\n5. For package code edits, start with the nearest package test, then escalate to `bun run check:ts` or `bun run test` as risk increases.\n6. Before changing `packages/coding-agent/src/defaults/gjc/skills/`, `packages/coding-agent/src/prompts/agents/`, `packages/coding-agent/src/commands/`, or config/settings paths, write down whether the change alters public surface area.\n\n## Context seed checklist\n\nA future agent can use this packet as context if it preserves these constraints:\n\n- Keep changes public-safe and repo-relative.\n- Prefer docs and tests over new runtime abstractions for onboarding experiments.\n- Treat the fixed four-skills/four-agents shape as a product constraint.\n- Verify claims with repo files before summarizing them.\n- Report validation evidence and caveats instead of implying hidden automation.\n",
|
|
75
75
|
"onboarding-receipt.md": "# Onboarding Packet Receipt\n\n- Date: 2026-06-01\n- Scope: docs-only no-new-skill onboarding packet experiment for this repository.\n- Output files:\n - `docs/onboarding-packet.md`\n - `docs/onboarding-receipt.md`\n- Public-safe boundary: no secrets, tokens, hidden prompts, private infrastructure, internal ops, or private paths beyond repo-relative paths.\n- Product boundary: no new skill, command, agent slot, issue, config, or runtime behavior.\n\n## Evidence inspected\n\n- `README.md`\n- `docs/codebase-overview.md`\n- `package.json`\n- `packages/coding-agent/package.json`\n- `packages/coding-agent/src/cli.ts`\n- `packages/coding-agent/src/main.ts`\n- `packages/coding-agent/src/sdk/session.ts`\n- `packages/coding-agent/src/defaults/gjc-defaults.ts`\n- `packages/coding-agent/src/task/agents.ts`\n- `packages/coding-agent/test/default-gjc-definitions.test.ts`\n- `.github/workflows/ci.yml`\n- `.github/workflows/dev-ci.yml`\n\n## Result\n\nThe packet records repo purpose, package layout, main entrypoints, build/test commands, danger zones, unknowns, and first safe tasks without changing the product surface. It is suitable as a public context seed for future onboarding experiments, not as a feature intake mechanism.\n\n## Caveats\n\n- The attempted `omx question --input '<json>' --json` interview round failed before user input because the runtime reported no attached tmux client; no human answer was inferred from that failed call.\n- Public issue context is limited to the user-provided prompt summary for this run.\n- Full CI was not required for the docs-only artifact unless later code/runtime files change.\n",
|
|
@@ -94,7 +94,7 @@ export const EMBEDDED_DOCS: Readonly<Record<string, string>> = {
|
|
|
94
94
|
"resolve-tool-runtime.md": "# Resolve tool runtime internals\n\nThis document explains how preview/apply workflows are modeled in coding-agent and how built-in or custom tools can participate via the tool-choice queue and `pushPendingAction`.\n\n## Scope and key files\n\n- [`src/tools/resolve.ts`](../packages/coding-agent/src/tools/resolve.ts)\n- [`src/tools/ast-edit.ts`](../packages/coding-agent/src/tools/ast-edit.ts)\n- [`src/extensibility/custom-tools/types.ts`](../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`src/extensibility/custom-tools/loader.ts`](../packages/coding-agent/src/extensibility/custom-tools/loader.ts)\n- [`src/sdk/session.ts`](../packages/coding-agent/src/sdk/session.ts)\n\n## What `resolve` does\n\n`resolve` is a hidden tool that finalizes a pending preview action.\n\n- `action: \"apply\"` executes the queued action's `apply(reason)` callback and returns that result with resolve metadata.\n- `action: \"discard\"` invokes `reject(reason)` if provided; otherwise returns `Discarded: <label>. Reason: <reason>`.\n\nIf no pending action exists, `resolve` fails with:\n\n- `No pending action to resolve. Nothing to apply or discard.`\n\n## Pending actions use the tool-choice queue\n\nPreview producers call `queueResolveHandler(...)`, which pushes a one-shot forced `resolve` directive onto the session tool-choice queue and adds a `resolve-reminder` steering message.\n\nRuntime behavior:\n\n- the queued handler owns the pending `apply`/`reject` callbacks,\n- `resolve` looks up the current queue invoker with `session.peekQueueInvoker()`,\n- if the model rejects the forced tool choice, the queue directive is requeued,\n- `resolve` does not maintain a separate pending-action stack.\n\nMultiple pending previews therefore follow the active tool-choice queue ordering, not an independent pending-action store.\n\n## Built-in producer example (`ast_edit`)\n\n`ast_edit` previews structural replacements first. When the preview has replacements and is not applied yet, it queues a resolve handler that contains:\n\n- label (human-readable summary)\n- `sourceToolName` (`ast_edit`)\n- `apply(reason: string)` callback that reruns AST edit with `dryRun: false`\n\n`resolve(action=\"apply\", reason=\"...\")` passes `reason` into this callback.\n\n## Custom tools: `pushPendingAction`\n\nCustom tools can register resolve-compatible pending actions through `CustomToolAPI.pushPendingAction(...)`. The custom tool loader forwards these actions to `queueResolveHandler(...)` when that hook is available.\n\n`CustomToolPendingAction`:\n\n- `label: string` (required)\n- `apply(reason: string): Promise<AgentToolResult<unknown>>` (required) — invoked on apply; `reason` is the string passed to `resolve`\n- `reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>` (optional) — invoked on discard; return value replaces the default \"Discarded\" message if provided\n- `details?: unknown` exists on the public custom-tool type but is not currently forwarded by the loader into resolve metadata\n- `sourceToolName?: string` (optional, defaults to `\"custom_tool\"`)\n\n### Minimal usage example\n\n```ts\nimport type { CustomToolFactory } from \"@gajae-code/coding-agent\";\n\nconst factory: CustomToolFactory = (pi) => ({\n name: \"batch_rename_preview\",\n label: \"Batch Rename Preview\",\n description: \"Previews renames and defers commit to resolve\",\n parameters: pi.zod.object({\n files: pi.zod.array(pi.zod.string()),\n }),\n\n async execute(_toolCallId, params) {\n const previewSummary = `Prepared rename plan for ${params.files.length} files`;\n\n pi.pushPendingAction({\n label: `Batch rename: ${params.files.length} files`,\n sourceToolName: \"batch_rename_preview\",\n apply: async (reason) => {\n // apply writes here\n return {\n content: [\n { type: \"text\", text: `Applied batch rename. Reason: ${reason}` },\n ],\n };\n },\n reject: async (reason) => {\n // optional: cleanup or notify on discard\n return {\n content: [\n { type: \"text\", text: `Discarded batch rename. Reason: ${reason}` },\n ],\n };\n },\n });\n\n return {\n content: [\n {\n type: \"text\",\n text: `${previewSummary}. Call resolve to apply or discard.`,\n },\n ],\n };\n },\n});\n\nexport default factory;\n```\n\n## Runtime availability and failures\n\n`pushPendingAction` is wired by the custom tool loader through the active session's resolve queue hook.\n\nIf the runtime did not provide the resolve queue hook, `pushPendingAction` throws:\n\n- `Pending action store unavailable for custom tools in this runtime.`\n\n## Tool-choice behavior\n\nWhen `queueResolveHandler(...)` registers a preview, the agent runtime forces a one-shot `resolve` tool choice so pending previews are explicitly finalized before normal tool flow continues.\n\n## Developer guidance\n\n- Use pending actions only for destructive or high-impact operations that should support explicit apply/discard.\n- Keep `label` concise and specific; it is shown in resolve renderer output.\n- Ensure `apply(reason)` is deterministic and idempotent enough for one-shot execution; `reason` is informational and should not change behavior.\n- Implement `reject(reason)` when the discard needs cleanup (temp state, locks, notifications); omit it for stateless previews where the default message suffices.\n- If your tool can stage multiple previews, remember they are mediated by the tool-choice queue rather than a separate pending-action stack.\n",
|
|
95
95
|
"rulebook-matching-pipeline.md": "# Rulebook Matching Pipeline\n\nThis document describes how coding-agent discovers rules from supported config formats, normalizes them into a single `Rule` shape, resolves precedence conflicts, and splits the result into:\n\n- **Rulebook rules** (available to the model via system prompt + `rule://` URLs)\n- **TTSR rules** (time-travel stream interruption rules)\n\nIt reflects the current implementation, including partial semantics and metadata that is parsed but not enforced.\n\n## Implementation files\n\n- [`../src/capability/rule.ts`](../packages/coding-agent/src/capability/rule.ts)\n- [`../src/capability/index.ts`](../packages/coding-agent/src/capability/index.ts)\n- [`../src/discovery/index.ts`](../packages/coding-agent/src/discovery/index.ts)\n- [`../src/discovery/helpers.ts`](../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/discovery/builtin.ts`](../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/cursor.ts`](../packages/coding-agent/src/discovery/cursor.ts)\n- [`../src/discovery/windsurf.ts`](../packages/coding-agent/src/discovery/windsurf.ts)\n- [`../src/discovery/cline.ts`](../packages/coding-agent/src/discovery/cline.ts)\n- [`../src/sdk/session.ts`](../packages/coding-agent/src/sdk/session.ts)\n- [`../src/system-prompt.ts`](../packages/coding-agent/src/system-prompt.ts)\n- [`../src/internal-urls/rule-protocol.ts`](../packages/coding-agent/src/internal-urls/rule-protocol.ts)\n- [`packages/utils/src/frontmatter.ts`](../packages/utils/src/frontmatter.ts)\n\n## 1. Canonical rule shape\n\nAll providers normalize source files into `Rule`:\n\n```ts\ninterface Rule {\n name: string;\n path: string;\n content: string;\n globs?: string[];\n alwaysApply?: boolean;\n description?: string;\n condition?: string[];\n scope?: string[];\n interruptMode?: \"never\" | \"prose-only\" | \"tool-only\" | \"always\";\n _source: SourceMeta;\n}\n```\n\nCapability identity is `rule.name` (`ruleCapability.key = rule => rule.name`).\n\nConsequence: precedence and deduplication are **name-based only**. Two different files with the same `name` are considered the same logical rule.\n\n## 2. Discovery sources and normalization\n\n`src/discovery/index.ts` auto-registers providers. For `rules`, current providers are:\n\n- `native` (priority `100`)\n- `cursor` (priority `50`)\n- `windsurf` (priority `50`)\n- `cline` (priority `40`)\n\n### Native provider (`builtin.ts`)\n\nLoads `.gjc` rules from:\n\n- project: `<cwd>/.gjc/rules/*.{md,mdc}`\n- user: `~/.gjc/agent/rules/*.{md,mdc}`\n\nNormalization:\n\n- `name` = filename without `.md`/`.mdc`\n- frontmatter parsed via `parseFrontmatter`\n- `content` = body (frontmatter stripped)\n- `globs`, `alwaysApply`, `description`, `condition`/legacy `ttsr_trigger`, `scope`, and `interruptMode` are parsed by `buildRuleFromMarkdown`\n\nImportant caveat: `condition` values that look like file globs are converted into `tool:edit(...)` / `tool:write(...)` scope shorthands with catch-all condition `.*`.\n\n### Cursor provider (`cursor.ts`)\n\nLoads from:\n\n- user: `~/.cursor/rules/*.{mdc,md}`\n- project: `<cwd>/.cursor/rules/*.{mdc,md}`\n\nNormalization (`transformMDCRule`):\n\n- `description`: kept only if string\n- `alwaysApply`: only `true` is preserved (`false` becomes `undefined`)\n- `globs`: accepts array (string elements only) or single string\n- `condition`/legacy `ttsr_trigger`, `scope`, and `interruptMode` are parsed by shared rule helpers\n- `name` from filename without extension\n\n### Windsurf provider (`windsurf.ts`)\n\nLoads from:\n\n- user: `~/.codeium/windsurf/memories/global_rules.md` (fixed rule name `global_rules`)\n- project: `<cwd>/.windsurf/rules/*.md`\n\nNormalization:\n\n- `globs`: array-of-string or single string\n- `alwaysApply`, `description`, `condition`/legacy `ttsr_trigger`, `scope`, and `interruptMode` parsed by shared rule helpers\n- `name` is fixed to `global_rules` for the user global file and derived from filename for project rules\n\n### Cline provider (`cline.ts`)\n\nSearches upward from `cwd` for nearest `.clinerules`:\n\n- if directory: loads `*.md` inside it\n- if file: loads single file as rule named `clinerules`\n\nNormalization:\n\n- `globs`: array-of-string or single string\n- `alwaysApply`, `description`, `condition`/legacy `ttsr_trigger`, `scope`, and `interruptMode` parsed by shared rule helpers\n- `name` is fixed to `clinerules` for a `.clinerules` file and derived from filename for `.clinerules/*.md`\n\n## 3. Frontmatter parsing behavior and ambiguity\n\nAll providers use `parseFrontmatter` (`utils/frontmatter.ts`) with these semantics:\n\n1. Frontmatter is parsed only when content starts with `---` and has a closing `\\n---`.\n2. Body is trimmed after frontmatter extraction.\n3. If YAML parse fails:\n - warning is logged,\n - parser falls back to simple `key: value` line parsing (`^(\\w+):\\s*(.*)$`).\n\nAmbiguity consequences:\n\n- Fallback parser does not support arrays, nested objects, quoting rules, or hyphenated keys.\n- Fallback values become strings (for example `alwaysApply: true` becomes string `\"true\"`), so providers requiring boolean/string types may drop metadata.\n- `ttsr_trigger` works in fallback (underscore key); keys like `thinking-level` would not.\n- Files without valid frontmatter still load as rules with empty metadata and full content body.\n\n## 4. Provider precedence and deduplication\n\n`loadCapability(\"rules\")` (`capability/index.ts`) merges provider outputs and then deduplicates by `rule.name`.\n\n### Precedence model\n\n- Providers are ordered by priority descending.\n- Equal priority keeps registration order (`cursor` before `windsurf` from `discovery/index.ts`).\n- Dedup is first-wins: first encountered rule name is kept; later same-name items are marked `_shadowed` in `all` and excluded from `items`.\n\nEffective rule provider order is currently:\n\n1. `native` (100)\n2. `cursor` (50)\n3. `windsurf` (50)\n4. `cline` (40)\n\n### Intra-provider ordering caveat\n\nWithin a provider, item order comes from `loadFilesFromDir` glob result ordering plus explicit push order. This is deterministic enough for normal use but not explicitly sorted in code.\n\nNotable source-order differences:\n\n- `native` appends project then user config dirs.\n- `cursor` appends user then project results.\n- `windsurf` appends user `global_rules` first, then project rules.\n- `cline` loads only nearest `.clinerules` source.\n\n## 5. Split into Rulebook, Always-Apply, and TTSR buckets\n\nAfter rule discovery in `createAgentSession` (`sdk.ts`):\n\n1. All discovered rules are scanned.\n2. Rules with `condition` entries are registered into `TtsrManager`; legacy `ttsr_trigger` / `ttsrTrigger` are accepted during rule parsing as condition fallbacks.\n3. A separate `rulebookRules` list is built with this predicate:\n\n```ts\n!isTtsrRule && rule.alwaysApply !== true && !!rule.description;\n```\n\n4. An `alwaysApplyRules` list is built:\n\n```ts\n!isTtsrRule && rule.alwaysApply === true;\n```\n\n### Bucket behavior\n\n- **TTSR bucket**: any rule with a non-empty parsed `condition` that `TtsrManager.addRule(...)` accepts. Takes priority over other buckets.\n- **Always-apply bucket**: `alwaysApply === true`, not TTSR. Full content injected into system prompt. Resolvable via `rule://`.\n- **Rulebook bucket**: must have description, must not be TTSR, must not be `alwaysApply`. Listed in system prompt by name+description; content read on demand via `rule://`.\n- A rule with both `condition` and `alwaysApply` goes to TTSR only (TTSR takes priority).\n- A rule with both `alwaysApply` and `description` goes to always-apply only (not rulebook).\n\n## 6. How metadata affects runtime surfaces\n\n### `description`\n\n- Required for inclusion in rulebook.\n- Rendered in system prompt `<rules>` block.\n- Missing description means rule is not available via `rule://` and not listed in system prompt rules.\n\n### `globs`\n\n- Carried through on `Rule`.\n- Rendered as `<glob>...</glob>` entries in the system prompt rules block.\n- Exposed in rules UI state (`extensions` mode list).\n- **Not enforced for automatic matching in this pipeline.** There is no runtime glob matcher selecting rules by current file/tool target.\n\n### `alwaysApply`\n\n- Parsed and preserved by providers.\n- Used in UI display (`\"always\"` trigger label in extensions state manager).\n- Used as an exclusion condition from `rulebookRules`.\n- **Full rule content is auto-injected into the system prompt** (before the rulebook rules section).\n- Rule is also addressable via `rule://<name>` for re-reading.\n\n### `condition`, `scope`, and `interruptMode`\n\n- `condition` is the current TTSR trigger field; legacy `ttsr_trigger` / `ttsrTrigger` are accepted as fallback inputs during parsing.\n- `scope` narrows TTSR matching scope. A condition token that looks like a file glob becomes `tool:edit(<glob>)` and `tool:write(<glob>)` scope entries plus catch-all condition `.*`.\n- `interruptMode` can override the global TTSR interrupt mode for the rule.\n\n## 7. System prompt inclusion path\n\n`buildSystemPromptInternal` receives both `rules` (rulebook) and `alwaysApplyRules`.\n\nAlways-apply rules are rendered first, injecting their raw content directly into the prompt.\n\nRulebook rules are rendered in a `# Rules` section with:\n\n- `Read rule://<name> when working in matching domain`\n- Each rule's `name`, `description`, and optional `<glob>` list\n\nThis is advisory/contextual: prompt text asks the model to read applicable rules, but code does not enforce glob applicability.\n\n## 8. `rule://` internal URL behavior\n\n`RuleProtocolHandler` is registered with:\n\n```ts\nnew RuleProtocolHandler({\n getRules: () => [...rulebookRules, ...alwaysApplyRules],\n});\n```\n\nImplications:\n\n- `rule://<name>` resolves against both **rulebookRules** and **alwaysApplyRules**.\n- TTSR-only rules and rules with no description and no `alwaysApply` are not addressable via `rule://`.\n- Resolution is exact name match.\n- Unknown names return error listing available rule names.\n- Returned content is raw `rule.content` (frontmatter stripped), content type `text/markdown`.\n\n## 9. Known partial / non-enforced semantics\n\n1. Provider descriptions mention legacy files (`.cursorrules`, `.windsurfrules`), but current loader code paths do not actually read those files.\n2. `globs` metadata is surfaced to prompt/UI but not enforced by rule selection logic.\n3. Rule selection for `rule://` includes rulebook and always-apply rules, but not TTSR-only rules.\n4. Discovery warnings (`loadCapability(\"rules\").warnings`) are produced but `createAgentSession` does not currently surface/log them in this path.\n",
|
|
96
96
|
"rust-porting-baseline.md": "# Rust porting profiling baseline\n\nBaseline CPU profile for the Rust porting inventory (Phase 1a). Produced by the session-replay harness (`bun run bench:profile -- --scenario all`, see `docs/perf-profiling-corpus.md`). Raw `.cpuprofile` and report files stay under the gitignored `.gjc/profiles/`; only aggregated function names and shares are recorded here. No transcript text is included.\n\n- Git SHA: `d84ec8697170486d822e17c064be15804857df3c`\n- Bun: `1.4.0`, host `darwin/arm64`, sampling interval 100 µs\n- Corpus manifest SHA-256: `4e6f50a2485350fffeca7c7addf3b946830e96da7b80cdcec0c1f24d9791a108` (sanitizer version 1; three `sanitized-real` sessions and one `synthetic` stress session)\n- Replay fidelity: every session replayed with equal assistant-turn counts, equal tool-call sequence hashes, and a non-empty final frame (real-compaction-01 48/48, real-long-01 51/51, real-tools-01 24/24, synthetic-stress-01 20/20).\n\n## Hot TypeScript functions (≥5% self time)\n\n| Scenario | Function | Share |\n|---|---|---:|\n| tools | `push` — `packages/coding-agent/src/session/streaming-output.ts:1052` | 13.6% |\n| compaction | `serializeCanonicalJson` — `packages/coding-agent/src/config/model-preset-registry.ts:668` | 7.3% |\n| startup | `recordResolution` — `scripts/trace-loader.ts:113` (harness trace loader, excluded) | 9.3% |\n\nsession-load, session-save, replay and keystroke have no single TypeScript function at ≥5%; their top self time is runtime/native (`spawnSync`, `structuredClone`, xterm parse).\n\n## Critical paths\n\n| Window | Top self time |\n|---|---|\n| startup | `scanImports` 21.6%, `readFileSync` 17.0% (module loading) |\n| per keystroke | xterm `parse` 10.6% (test terminal), `matchesKey` 8.4%, `TUI#doRender` 6.9%, `structuredClone` 3.9%, `TUI#renderPreparedFrame` 3.8%, `parseKey` 3.4% |\n| per token delta | `structuredClone` 11.6%, `managedChargeStringBytes` (`packages/agent/src/agent-loop.ts:1574`) 7.9%, `spawnSync` 4.8%, `stableStreamingEditArgsVersion` (`agent-session.ts:2297`) 4.0%, `validatePersistedUsageTotals` (`session-manager.ts:6653`) 3.4%, `walkPrepared` (`agent-loop.ts:1627`) 3.4% |\n\n## Hand-port candidates for Phase 4\n\nConfirmed by this baseline: `streaming-output.ts` `push` (tools), `serializeCanonicalJson` (compaction), and `managedChargeStringBytes` (per-token critical path). The remaining `docs/cpu-hotspot-map.json` entries are unconfirmed at this baseline. Phase 4 re-profiles on the post-vendoring build with the same corpus manifest hash before choosing hand-ports.\n\n## Stability\n\nAC1a.3 passes on the dedicated `macos-14` runner (`native-bench-ab.yml`, suite `profile-stability`, run https://github.com/Yeachan-Heo/gajae-code/actions/runs/36502046414 on dev `1011a4aede`). The job waits for the post-build load to settle below half the cores (1-minute load 1.36 before, 1.45 after), runs one warm-up profile, then two measured `--scenario all` runs compared with `--compare`:\n\n| Scenario | Top-10 overlap |\n|---|---:|\n| compaction | 8/10 |\n| keystroke | 9/10 |\n| replay | 9/10 |\n| session-load | 10/10 |\n| session-save | 9/10 |\n| startup | 10/10 |\n| tools | 8/10 |\n\nEarlier local runs fell short because of host load and three harness defects, fixed before this run:\n- `bench:profile` never exited after the tools scenario: a leaked shell session held the event loop open (#6073).\n- Self time credited each sample with the whole gap since the previous one, so idle waits (child processes, I/O) made the last sampled frame look hot. Credit is now capped at ten sampling intervals. On-CPU work, including long synchronous native calls, keeps being sampled at the configured interval (#6092).\n- Startup children were sampled at 1 ms with 5 repeats. They now use the harness interval with 60 repeats, and the I/O-bound tools scenario runs for 45 s (#6092, #6103).\n",
|
|
97
|
-
"rust-porting-inventory.md": "# Rust porting inventory\n\nTracks every candidate for moving gjc hot paths and TypeScript implementations into the native addon (`crates/pi-natives` via `@gajae-code/natives`), whether vendored from upstream or hand-ported after profiling.\n\nUpstream pin: `can1357/oh-my-pi@a85bd5228d9f0f619deade1db78fa49420a721e1`\n\n- Upstream sources are read only at this commit (`https://raw.githubusercontent.com/can1357/oh-my-pi/a85bd5228d9f0f619deade1db78fa49420a721e1/<path>`), never from `main`.\n- Every vendored file carries an attribution header naming this commit, unless its row below records a per-row bump made by the PR that needed newer code.\n- Upstream is MIT licensed; vendored crates keep their `LICENSE` and are listed in `NOTICE.md`.\n- `docs/rust-porting/upstream-workspace-deps@a85bd522.toml` is a verbatim snapshot of the upstream root `[workspace.dependencies]` at the pin. `bun scripts/verify-rust-porting-inventory.ts --deps` checks the local workspace against it.\n\n## Status vocabulary\n\n| Status | Meaning |\n|---|---|\n| `candidate` | Listed, not started. |\n| `blocked` | Implementation or dependency is present, but a documented decision or parity gap blocks adoption. |\n| `in-progress` | Port is integrated on the branch; stays in-progress until PR and rehearsal run URLs are recorded. |\n| `adopted` | Merged with parity, benchmark, and rehearsal evidence; replaced TS deleted with no fallback. |\n| `rejected` | Not adopted; `reason` is mandatory. |\n| `keep-local` | gjc-only Rust that a re-sync must not overwrite; guarded by named tests. |\n\n## Phase log\n\n| Phase | Status | PR | Rehearsal run | Notes |\n|---|---|---|---|---|\n| 0 — toolchain and shared deps at pin | in-progress | | | `nightly-2026-08-12`; shared workspace deps aligned to the pin. |\n| 1b — inventory and shared gates | in-progress | | | Schema and gate tooling are being established. |\n| 2.7 — pi-edit matcher foundation | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/6016 | | Matcher and fuzzy scoring landed via #5980/#6016; UTF-16 threshold semantics kept under keep-local D-PI-EDIT-UTF16-SCORING. |\n| 2.8–2.10 — remaining pi-edit consumers | rejected | | | Upstream pi-edit hashline/patch/streaming modes implement a different edit protocol (PUT / unified-hunk rejection) than the gjc grammar in `hashline/grammar.lark`; D9 makes gjc prompts and grammars authoritative, so adopting them would change model-visible bytes. `Diff.applyPatch` and the hashline/apply_patch/streaming TS paths stay local, guarded by D-TRANSITIONAL-TS. |\n\n## Inventory tables\n\n`—` means not applicable or not yet recorded. Every table uses the schema enforced by `scripts/verify-rust-porting-inventory.ts`.\n\n### A. Upstream-only crates\n\n| id | kind | upstream path@sha | local target | class | napi surface | TS callsite(s) | differential corpus | benchmark suite | rss scenarios | status | PR | rehearsal run | reason | keep-local guards | deleted paths | no-fallback paths |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| A-PI-VFS | crate | `crates/pi-vfs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-vfs` | infra | — | Filesystem substrate only; no TS callsite. | Filesystem fixture trees, symlinks, ignore files, large dirs, Windows paths. | `tools:glob`, `rss` | S1-S5,S7 | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Integrated async/blocking injectable filesystem required by pi-walker, pi-shell, pi-builtins and pi-natives iofs. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6039. | — | — | — |\n| A-PI-DIFF | crate | `crates/pi-diff@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-diff`; `crates/pi-natives/src/diff.rs` | (a) | diffLines, diffLineRuns, structuredPatchHunks, diffWords, DiffStream | `packages/coding-agent/src/edit/diff.ts`; `modes/components/diff.ts`; `tools/vim.ts`; `eval/js/shared/helpers.ts`; `hashline/recovery.ts` | jsdiff oracle, fixtures × seeded mutations | `edit-hotspots` H03, `word-diff`, `render-transcript` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Merged via #5980; word-diff A/B vs pre-port `01511ffd5f` PASS at 30 blocks (D01-D03 p50 upper <=0.92, p95 upper <=0.97): `docs/rust-porting/evidence/native-ab-word-diff.json`. `Diff.applyPatch` stays in TypeScript for `hashline/recovery.ts` (keep-local until item 2.9). | — | — | `packages/coding-agent/src/edit/diff.ts` |\n| A-PI-WALKER | crate | `crates/pi-walker@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-walker`; `crates/pi-natives/src/iofs.rs` | infra/overlap | glob, invalidateFsScanCache, walkerPoolStatus | Filesystem traversal substrate; `packages/coding-agent/src/tools/find.ts` | Recorded glob/fd/workspace outputs on fixture trees | `tools:glob`, `startup`, `tools` | S1-S5,S7 | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Integrated traversal substrate and pi-edit dependency; absorbs fs_cache policy and keeps the serial-on-pool-failure branch. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6027, https://github.com/Yeachan-Heo/gajae-code/pull/6039. | — | — | — |\n| A-PI-EDIT | crate | `crates/pi-edit@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-edit`; `crates/pi-natives/src/edit.rs` | (a) | `editFindMatch`, `editSeekSequence`, hashline, patch, EditSession, EditStore APIs | `packages/coding-agent/src/edit/{modes/replace,patch,apply-patch}.ts`; `edit/apply-patch/*`; `edit/{diff,normalize,streaming,notebook}.ts`; `hashline/*.ts` | Edit-benchmark TS matcher goldens including errors; per-mode goldens through 2.10 | `edit-hotspots` H01,H02,H06; `tools:edit`; `bench:edit`; `replay` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/6016 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Matcher merged via #5980 and fuzzy scoring fix #6016; edit-hotspots H01/H02 covered by the pi-edit fuzzy A/B in #6016. | — | — | `packages/coding-agent/src/edit/modes/replace.ts`; `packages/coding-agent/src/edit/streaming.ts`\n| A-PI-VCS | crate | `crates/pi-vcs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-vcs`; `crates/pi-natives/src/vcs.rs` | (a), size-gated (D5) | VCS read APIs | `packages/coding-agent/src/utils/git.ts`; `autoresearch/git.ts`; `status-line/git-utils.ts`; `commit/agentic/tools/git-*.ts` | Git CLI oracle over fixture repositories | `status-line`, `tools:git` | — | rejected | | | D5 perf gate: large-worktree read suite upper-CI p50 ratio was 0.826 (needs <0.80), and status was 1.20x slower. | — | — | — |\n| A-PI-BUILTINS-BASE | crate | `pi-builtins/src/<builtin>.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/brush-builtins-vendored` | overlap | — | Shell builtins; port changes one builtin at a time. | Existing fork tests plus per-builtin guard tests. | `builtins`, `shell` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36538316951 | Integrated portable base-builtin changes; per-builtin parity: eight builtins (echo, printf, test/[, read, cd/pwd, export, type/command -v, for/arithmetic) produce byte-identical stdout on 01511ffd5f and dev in the `builtins` suite. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6062, https://github.com/Yeachan-Heo/gajae-code/pull/6117. | `crates/brush-builtins-vendored/src/read.rs` | — | — |\n| A-PI-BUILTINS-UTILS | crate | `pi-builtins/src/utils@a85bd5228d9f0f619deade1db78fa49420a721e1` | — | (c) | — | Bash tool builtins | — | — | — | rejected | | | GNU/BSD behavior diverges; utility builtins pull C-backed onig_sys and pcre2-sys. uucore is already a pre-existing printf dependency and is not a rejection criterion. | — | — | — |\n| A-PI-VOICE | crate | `crates/pi-voice@a85bd5228d9f0f619deade1db78fa49420a721e1` | — | (c) | — | gjc STT uses `packages/coding-agent/src/stt/recorder.ts` | — | — | — | rejected | | | opus/webrtc and dynamically loaded PulseAudio/ALSA are out of scope; gjc uses sox/ffmpeg/arecord. | — | — | — |\n\n### B. Upstream-only pi-natives modules\n\n| id | kind | upstream path@sha | local target | class | napi surface | TS callsite(s) | differential corpus | benchmark suite | rss scenarios | status | PR | rehearsal run | reason | keep-local guards | deleted paths | no-fallback paths |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| B-DIFF | native module | `crates/pi-natives/src/diff.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/diff.rs` | (a) | diffLines, diffLineRuns, structuredPatchHunks, diffWords, DiffStream | `packages/coding-agent/src/edit/diff.ts`; `modes/components/diff.ts`; `tools/vim.ts`; `eval/js/shared/helpers.ts`; `hashline/recovery.ts` | jsdiff oracle and seeded mutations | `edit-hotspots` H03, `word-diff`, `render-transcript` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Native diff module merged via #5980; word-diff A/B PASS (`docs/rust-porting/evidence/native-ab-word-diff.json`). | — | — | `packages/coding-agent/src/edit/diff.ts` |\n| B-EDIT | native module | `crates/pi-natives/src/edit.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/edit.rs` | (a) | `editFindMatch`, `editSeekSequence` | `packages/coding-agent/src/edit/modes/replace.ts`; `packages/coding-agent/src/edit/modes/patch.ts`; `packages/coding-agent/src/edit/diff.ts` | Edit-benchmark TS matcher goldens including errors and every confidence | `edit-hotspots` H01,H02 | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/6016 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | editFindMatch/editSeekSequence consumers merged via #5980 and #6016, no TS fuzzy fallback. | — | — | `packages/coding-agent/src/edit/modes/replace.ts`\n| B-VCS | native module | `crates/pi-natives/src/vcs.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/vcs.rs` | (a), size-gated (D5) | VCS read APIs | `packages/coding-agent/src/utils/git.ts`; listed git callers | Git CLI over fixture repositories | `status-line`, `tools:git` | — | rejected | | | D5: addon +5.79 MB and aggregate read ratio 0.240 pass, but status regresses (1.14x-1.33x, CI lower >1.12) and native `show` output differs from the git CLI the TS parses. Evidence: `docs/rust-porting/evidence/2.17-pi-vcs-d5.json`. Partial adoption (diff/log native, status/show CLI) needs an owner D5 decision. | — | — | — |\n| B-MERMAID | native module | `crates/pi-natives/src/mermaid.rs`, `crates/pi-natives/fixtures/mermaid.json@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/mermaid/` | (a) | renderMermaidAscii | `packages/utils/src/mermaid-ascii.ts`; `packages/coding-agent/src/tools/render-mermaid.ts`; mermaid cache | beautiful-mermaid golden; divergence records per D6 | `mermaid-render` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Upstream output changes require an accepted divergence and changelog entry. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | — | — | `packages/utils/src/mermaid-ascii.ts` |\n| B-PDF | native module | `crates/pi-natives/src/pdf.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/pdf.rs`; pdf-inspector | (a) | pdfToMarkdown | Coding-agent PDF reading paths | License-clean PDF fixtures; MuPDF output goldens and explicit divergences | `read(pdf)` | S4 | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Full MuPDF removal; pdf-inspector default features only, no pdfium/ort/OCR. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | — | `packages/coding-agent/src/utils/mupdf.ts`; `packages/coding-agent/src/utils/mupdf-wasm.ts`; `packages/coding-agent/src/utils/mupdf-embedded.ts` | `packages/coding-agent/src/utils/pdf.ts`; `packages/coding-agent/src/utils/markit.ts` |\n| B-TOKENS | native module | `crates/pi-natives/src/tokens.rs`, `crates/pi-natives/src/utok/@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/tokens.rs`; `crates/pi-natives/src/utok/` | (a), RSS-gated (D2) | countTokens | `packages/agent/src/compaction/compaction.ts`; `packages/coding-agent/src/session/context-estimation.ts` | gpt-tokenizer oracle | `compaction-estimate` | all | rejected | | | D2, S3 idle RSS +9.5%. Evidence: `docs/rust-porting/evidence/2.15-tokens-rss.json`. Re-evaluable after #5934. | — | — | `packages/agent/src/compaction/compaction.ts` |\n| B-TTY-WRITER | native module | `crates/pi-natives/src/tty_writer.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/tty_writer.rs` | (a) | TtyWriter | `packages/tui/src/terminal.ts`; `terminal-capabilities.ts` | VirtualTerminal/PTY byte capture | `tui-render-frame`, `tui-input-write`, `keystroke` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/6194 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36846716134 | Unix implementation with win32 platform split per D4. Owner waived W01 non-inferiority failure (W01 FAIL p50 upper 1.176 / p95 upper 1.446; W02 PASS p50 0.52–0.60). Evidence: `docs/rust-porting/evidence/native-ab-tty-write.json`. | — | — | `packages/tui/src/terminal.ts` |\n| B-FILE-LOCK | native module | `crates/pi-natives/src/file_lock/`@a85bd5228d9f0f619deade1db78fa49420a721e1 | `crates/pi-natives/src/file_lock/` | (a), architect-gated (D1) | FileLock | `packages/coding-agent/src/config/file-lock.ts`; `atomic-yaml-patch.ts`; model-preset-registry | Multi-process contention fixtures | `contention` | — | rejected | | | Cross-version lock safety (D1): upstream uses an abstract socket (Linux), a named mutex (Windows) or `flock` on a regular `${path}.lock` file (other Unix); none interlock with the legacy `${path}.lock` directory. Evidence: `packages/coding-agent/test/file-lock-cross-version-interlock.test.ts` (flock vs directory: each side errors instead of waiting, and the persistent sidecar wedges gjc after release). | — | — | `packages/coding-agent/src/config/file-lock.ts` |\n| B-SVG | native module | `crates/pi-natives/src/svg.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/svg.rs`; resvg/png | (b) | rasterizeSvg | Read/fetch previews of `image/svg+xml` | SVG fixtures to PNG dimensions/hash | `svg-rasterize` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36538316951 | New capability: `rasterizeSvg` backs read/fetch previews of `image/svg+xml`. `01511ffd5f` has no SVG rasterization entrypoint (its image loader has no SVG path), so there is nothing to A/B for non-inferiority. The differential is the committed geometry.svg PNG size/hash golden in `packages/natives/test/native.test.ts`. | — | — | — |\n| B-BLOCK | native module | `crates/pi-natives/src/block.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/block.rs` | (b)/infra | blockRange only if consumed | pi-edit if consumed, otherwise none | Block-range parity corpus | `edit-hotspots` | — | rejected | | | No `blockRange` napi export or TS consumer was added: block ranges are consumed inside Rust by pi-edit (`diff_string.rs`, hashline `block.rs`) through `pi_ast::block`, which C-AST covers; `crates/pi-natives/src/block.rs` is not vendored. | — | — | — |\n| B-SIXEL-DECODER | native module | `crates/pi-natives/src/sixel.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | — | (c) | decodeSixelToPng | — | — | — | — | rejected | | | No gjc consumer. | — | — | — |\n| B-IOFS | native module | `crates/pi-natives/src/iofs.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/iofs.rs` | infra | Filesystem operations; walkerPoolStatus | No direct TS swap; supports pi-vfs/walker integration | Filesystem fixture trees | `tools:glob`, `rss` | S1-S5,S7 | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Integrated with pi-vfs and pi-walker in 2.3. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6039. | — | — | — |\n| B-JS-TESTING | native module | `crates/pi-natives/src/{js,testing}.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/{js,testing}.rs` | infra | NAPI JS arena/testing helpers | Native modules; no TS replacement | Existing `packages/natives/test/*` | `edit-hotspots`, `natives-grep` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | NAPI JS arena/testing helpers merged via #5980; infrastructure with no TS replacement. | — | — | — |\n| B-WINDOWS-RUNTIME | native module | `crates/pi-natives/src/lib.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | Local `lib.rs` Windows runtime glue | infra | `__gjcInstallTokioRuntime` | N/A; loader runtime install | `loader-runtime-install.test.ts`; `windows-runtime-install.windows.test.ts` | `natives-grep` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Add local runtime install symbol; never copy upstream lib.rs or its sentinel. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | `packages/natives/test/loader-help-worktree.test.ts` | — | — |\n| B-CRASH-HANDLER | native module | `crates/pi-natives/src/crash_handler.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | Local `crates/pi-natives/src/crash.rs` | overlap | Local opt-in crash diagnostics | N/A | Opt-in/disabled-by-default contract | `crash`, `startup` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36538316951 | Ported from upstream while preserving the D8 opt-in diagnostics contract. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6062. | `packages/natives/test/crash-diagnostics-optin.test.ts` | — | — |\n| B-REJECTED-HELPERS | native module | `crates/pi-natives/src/{snapcompact,fonts,vectors,spelling,devicecheck,oauth_callback,desktop,applefm,audio,live}.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | — | (c) | — | No accepted consumer / build.rs helper executables | — | — | — | rejected | | | Not Cargo-only or no intended consumer at the pinned source. | — | — | — |\n\n### C. Overlapping modules (drift notes; approximate upstream/local lines)\n\n| id | kind | upstream path@sha | local target | class | napi surface | TS callsite(s) | differential corpus | benchmark suite | rss scenarios | status | PR | rehearsal run | reason | keep-local guards | deleted paths | no-fallback paths |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| C-GREP | overlap | `crates/pi-natives/src/grep.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/grep.rs` | overlap | grep/search/hasMatch | Existing tool search/read callsites | Grep goldens on fixture repository | `natives-grep` | S1-S5,S7 | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | ≈3.29k upstream/1.95k local; PCRE2 static bundled only, JS arena and walker; retains cached serial fallback and omits unused injectable shell::vfs support. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6027, https://github.com/Yeachan-Heo/gajae-code/pull/6039. | — | — | — |\n| C-GLOB | overlap | `crates/pi-natives/src/glob.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/glob.rs`; walker + iofs | overlap | glob, invalidateFsScanCache | Coding-agent discovery/find | Recorded filesystem fixture outputs | `tools:glob` | S1-S5,S7 | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Integrated into pi-walker while retaining fs_cache policy. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6027, https://github.com/Yeachan-Heo/gajae-code/pull/6039. | `crates/pi-natives/src/fs_cache.rs` | — | — |\n| C-FD | overlap | `crates/pi-natives/src/fd.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/fd.rs`; walker | overlap | fuzzyFind | File discovery/find | Recorded fixture trees | `startup`, `tools` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | File discovery uses the integrated pi-walker traversal substrate. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6027. | — | — | — |\n| C-TEXT | overlap | `crates/pi-natives/src/text.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/text.rs` | overlap | Text APIs | TUI text layout and edit hot paths | Text layout goldens, grapheme/combining fixtures | `keystroke`, `tui-render-frame` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | ≈2.12k/2.22k; js arena and per-keystroke/per-token path. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | — | — | — |\n| C-KEYS | overlap | `crates/pi-natives/src/keys.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/keys.rs` | overlap | Key parsing | `packages/tui/src/keys.ts` | Windows key fixtures incl. 0x08 | `keystroke` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | ≈1.77k/1.88k; retain Windows 0x08 behavior. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | `packages/natives/test/native.test.ts` | — | — |\n| C-HIGHLIGHT | overlap | `crates/pi-natives/src/highlight.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/highlight.rs`; `syntaxes/` | overlap | Syntax highlighting | Coding-agent code rendering | Language fixture set | `render-transcript` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/6023 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Merged via #6023; render-transcript A/B vs `01511ffd5f` PASS (`docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`). | — | — | — |\n| C-HTML | overlap | `crates/pi-natives/src/html.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/html.rs` | overlap | HTML-to-markdown | Read/fetch HTML | HTML fixtures | `read(html)` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | ≈0.07k/0.05k; html-to-markdown-rs 3.x. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | — | — | — |\n| C-SIXEL | overlap | `crates/pi-natives/src/sixel.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/sixel.rs` | overlap | Sixel encoding | Image display | License-clean Sixel fixtures | `read(image)` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | ≈0.20k/0.06k; icy_sixel 0.7. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | — | — | — |\n| C-CLIPBOARD | overlap | `crates/pi-natives/src/clipboard.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/clipboard.rs` | overlap | Clipboard APIs | Clipboard/image paths | Image payload fixtures | `clipboard` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36538316951 | ≈0.37k/0.08k; clipboard-win and image paths. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6129. | — | — | — |\n| C-PTY | overlap | `crates/pi-natives/src/pty.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/pty.rs` | overlap | PTY APIs | Shell/TUI terminal paths | PTY byte capture and teardown | `pty`, `tui-input-write` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36538316951 | ≈1.13k/1.25k; retain local hardening. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6062. | `packages/natives/test/native.test.ts` | — | — |\n| C-SHELL | overlap | `crates/pi-natives/src/shell.rs`, `pi-shell@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/shell.rs`; `crates/pi-shell` | overlap | executeShell | `packages/coding-agent/src/tools/bash.ts` | Shell behavior corpus and cancellation cases | `tools:bash`, `startup` | S1,S3,S7 | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Re-synced the shell stream decoder and portable base-builtin improvements; retained local process, cancellation, Windows PATH, minimizer, fixup, guardian, output budget, read cancellation, and hidden-console guards. Brush-core Fs/async-dependent changes, process/job API expansions, and upstream utility builtins are intentionally not adopted. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | `crates/pi-natives/src/shell.rs`; `packages/coding-agent/test/bash-executor.test.ts`; `packages/coding-agent/test/tools/bash-command-fixup.test.ts`; `packages/coding-agent/test/tools/bash-master-owner-session-id.test.ts`; `packages/natives/test/shell-read-builtin-abort.test.ts` | — | — |\n| C-PS | overlap | `crates/pi-natives/src/ps.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/ps.rs`; `crates/pi-shell/src/process.rs` | overlap | Process observation/control | Shell process observation | Child process lifecycle tests | `tools:bash` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | ≈0.24k/0.28k; local pi_shell::process remains. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | `packages/coding-agent/test/bash-executor.test.ts` | — | — |\n| C-ISO | overlap | `crates/pi-natives/src/iso.rs`, `crates/pi-iso@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/iso.rs`; `crates/pi-iso` | overlap | iso* | Platform-specific path/process helpers | ISO diff goldens on fixture trees | `iso`, `rss` | S1-S5,S7 | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36538316951 | ≈0.25k/0.24k; preserve local windows-sys Wdk features. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6062, https://github.com/Yeachan-Heo/gajae-code/pull/6089. | `crates/pi-natives/src/iso.rs` | — | — |\n| C-WORKSPACE | overlap | `crates/pi-natives/src/workspace.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/workspace.rs`; walker | overlap | listWorkspace | Workspace discovery | Recorded fixture trees | `startup`, `tools` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Workspace discovery uses the integrated pi-walker traversal substrate. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | — | — | — |\n| C-SUMMARY | overlap | `crates/pi-natives/src/summary.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/summary.rs`; pi-ast parse_cache | overlap | summarizeCode | Coding-agent summaries | Language fixtures | `tools:ast_grep` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36538316951 | ≈0.09k/0.09k; pi-ast parse_cache. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6054. | — | — | — |\n| C-AST | overlap | `crates/pi-natives/src/ast.rs`, `crates/pi-ast@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/ast.rs`; `crates/pi-ast` | overlap | astGrep, astEdit | Coding-agent AST tools | Full language-set ast-grep goldens | `tools:ast_grep` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | ≈1.52k/1.02k; block + parse_cache; preserve full-langs, perl and languageSet. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | `crates/pi-natives/src/ast.rs` | — | — |\n| C-TASK | overlap | `crates/pi-natives/src/task.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/task.rs` | overlap | Async task helpers | Native async callsites | Abort and panic-to-rejection tests | `natives-grep` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | ≈0.56k/0.42k; preserve AbortReason/AbortToken and catch_unwind contract. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | `crates/pi-natives/src/task.rs` | — | — |\n| C-POWER | overlap | `crates/pi-natives/src/power.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/power.rs` | overlap | Power status | TUI power/status consumers | Recorded power status outputs | `power` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36538316951 | ≈0.53k/0.27k; drift to reconcile in its PR. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6062. | — | — | — |\n| C-PROF | overlap | `crates/pi-natives/src/prof.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/prof.rs` | overlap | Profiling helper | Runtime profiling | Profile output fixtures | `prof`, `startup` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36538316951 | ≈0.25k/0.26k; minor drift. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6062. | — | — | — |\n| C-APPEARANCE | overlap | `crates/pi-natives/src/appearance.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/appearance.rs` | overlap | Appearance status | TUI theme/appearance | Appearance fixtures | `appearance` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36538316951 | ≈0.46k/0.45k; minor drift. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6069, https://github.com/Yeachan-Heo/gajae-code/pull/6089. | — | — | — |\n\n### D. Keep-local ledger\n\nEach ledger row is `retain` or `reconcile`. Guard tests must pass on the pre-merge base and post-merge head. A row may be removed only by inventory status `retired` plus a reason and PR.\n\n| id | kind | upstream path@sha | local target | class | napi surface | TS callsite(s) | differential corpus | benchmark suite | rss scenarios | status | PR | rehearsal run | reason | keep-local guards | deleted paths | no-fallback paths |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| D-PI-NATIVES-LOCAL | keep-local | — | `crates/pi-natives/src/{build_info,computer,doctor_journal,doctor_link,memory,path_identity,recovery_fs,sdk}` | keep-local | Existing GJC-only exports | Existing public APIs | Existing native tests | — | — | keep-local | | | Preserve gjc-only native modules during upstream sync. | `packages/natives/test/computer.test.ts`; `packages/natives/test/doctor-journal.test.ts`; `packages/natives/test/doctor-retained-publish.test.ts`; `packages/natives/test/path-identity.test.ts`; `packages/natives/test/recovery-fs.test.ts`; `packages/natives/test/memory-guard-native.test.ts` | — | — |\n| D-CRASH-OPTIN | keep-local | `crates/pi-natives/src/crash_handler.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/crash.rs` | keep-local | Opt-in crash diagnostics | Startup diagnostics | Disabled-default and explicit-opt-in child-process tests | `startup` | — | keep-local | | | D8 retains opt-in behavior; current source defaults to a system-temp directory unless `GJC_CRASH_DIAGNOSTICS_DIR` overrides it, which differs from the ledger's planned `~/.gjc` destination and must be reconciled in 3.14. | `packages/natives/test/crash-diagnostics-optin.test.ts` | — | — |\n| D-FS-CACHE | keep-local | `crates/pi-natives/src/fs_cache.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/fs_cache.rs`; pi-walker cache | keep-local | Filesystem cache policy | Glob/find/workspace/grep | Filesystem cache unit tests | `tools:glob`, `rss` | S1-S5,S7 | keep-local | | | Reconcile budget, TTL and generation-safe publication into walker cache in 2.3. | `crates/pi-natives/src/fs_cache.rs` | — | — |\n| D-WALKER-POOL | keep-local | `crates/pi-walker/src/cache.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | pi-walker cache.rs and local `walkerPoolStatus` export | keep-local | `walkerPoolStatus` | Native filesystem consumers | Pool unavailable/serial traversal tests | `tools:glob`, `rss` | S1-S5,S7 | keep-local | | | Reapply and test pool-failure serial branch on every walker bump; never call par_iter outside pool.install. | `crates/pi-natives/src/lib.rs`; `packages/natives/test/native.test.ts` | — | — |\n| D-LOADER-SENTINELS | keep-local | `crates/pi-natives/src/lib.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | Local `lib.rs`; `build.rs` | keep-local | Local sentinels and currentExecutablePath | Native loader | Loader/sentinel tests | `startup` | — | keep-local | | | Keep `__piNativesV0_17_5`, `__piNativesPublishOutcomeV1`, currentExecutablePath and local build.rs. | `packages/natives/test/loader-help-worktree.test.ts`; `packages/natives/test/publish-diagnostic-declaration.test.ts`; `packages/natives/test/embedded-extraction-freshness.test.ts` | — | — |\n| D-PI-SHELL-PROCESS | keep-local | `crates/pi-shell/src/process.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-shell/src/process.rs` | keep-local | Process observation and signal target selection | Bash process lifecycle | Process unit/integration tests | `tools:bash` | S1,S3,S7 | keep-local | | | Preserve Present/Absent/Unknown, pgid/target selection, self-pgroup refusal and Darwin unique id. | `crates/pi-shell/src/process.rs`; `packages/coding-agent/test/bash-executor.test.ts` | — | — |\n| D-PI-SHELL-CANCEL | keep-local | `crates/pi-shell/src/cancel.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-shell/src/cancel.rs`; shell abort state | keep-local | AbortReason, AbortToken | Bash abort/timeout | Abort state Rust and TS tests | `tools:bash` | S1,S3,S7 | keep-local | | | Preserve Timeout/Signal/User, latched abort before token publication and overlapping aborts. | `crates/pi-natives/src/shell.rs`; `packages/coding-agent/test/bash-executor.test.ts` | — | — |\n| D-PI-SHELL-WINDOWS | keep-local | `crates/pi-shell/src/windows.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-shell/src/windows.rs` | keep-local | Windows PATH/spawn policy | Bash Windows shell | Windows PATH and hidden-shell tests | `tools:bash` | — | keep-local | | | Preserve MSYS path translation, Git-for-Windows roots and CREATE_NO_WINDOW flags. | `crates/pi-shell/src/windows.rs`; `packages/natives/test/windows-hidden-shell.windows.test.ts`; `packages/natives/test/windows-shell-path.windows.test.ts` | — | — |\n| D-PI-SHELL-MINIMIZER | keep-local | `crates/pi-shell/src/minimizer/`@a85bd5228d9f0f619deade1db78fa49420a721e1 | `crates/pi-shell/src/minimizer/`; defs and build.rs concatenation | keep-local | Shell output minimizer | Bash output | Minimizer engine/inline TOML tests | `tools:bash` | — | keep-local | | | Retain rtk-derived attribution and port upstream changes change-by-change. | `crates/pi-shell/src/minimizer.rs`; `packages/coding-agent/test/bash-executor.test.ts` | — | — |\n| D-PI-SHELL-FIXUP | keep-local | `crates/pi-shell/src/fixup.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-shell/src/fixup.rs` | keep-local | applyBashFixups | Bash command execution | Fixup unit tests | `tools:bash` | — | keep-local | | | Preserve local shell fixup behavior. | `crates/pi-shell/src/fixup.rs` | — | — |\n| D-PI-SHELL-GUARDIAN | keep-local | `crates/pi-shell/src/shell.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | Shell HMAC guardian ownership ledger | keep-local | Guardian process ownership | Bash process lifecycle | Ownership-ledger and guardian tests | `tools:bash` | — | keep-local | | | Preserve guardian ownership authentication and cleanup. | `packages/coding-agent/test/bash-executor.test.ts` | — | — |\n| D-PI-SHELL-CHILD-SESSION | keep-local | `crates/pi-shell/src/shell.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | Child-session action and output truncation | keep-local | Shell session action | Bash child process and output handling | Rust truth table and truncation tests | `tools:bash` | — | keep-local | | | Preserve detach semantics, output budgets and truncation. | `crates/pi-natives/src/shell.rs`; `packages/coding-agent/test/bash-executor.test.ts` | — | — |\n| D-BUILTINS-READ-CANCEL | keep-local | `crates/brush-builtins-vendored/src/read.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | Vendored brush read builtin | keep-local | Shell read builtin | Bash `read` cancellation/timeout | Read builtin unit tests | `tools:bash` | — | keep-local | | | Preserve cancellation, `-t` timeout, Interrupted, and `-t 0` readiness semantics. | `crates/brush-builtins-vendored/src/read.rs` | — | — |\n| D-BRUSH-CORE-WINDOWS | keep-local | `crates/brush-core-vendored/src/sys/windows/commands.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | brush-core-vendored Windows commands | keep-local | CommandWindowControlExt | Windows shell commands | Windows shell tests | `tools:bash` | — | keep-local | | | Preserve local Windows command/window-control patches. | `crates/brush-core-vendored/src/sys/windows/commands.rs`; `packages/natives/test/windows-hidden-shell.windows.test.ts` | — | — |\n| D-PI-AST-ISO | keep-local | `crates/pi-ast/Cargo.toml`, `crates/pi-iso/Cargo.toml@a85bd5228d9f0f619deade1db78fa49420a721e1` | pi-ast features and pi-iso local features | keep-local | `nativeBuildInfo().languageSet`; iso* | AST and platform consumers | LanguageSet test; pi-iso Rust tests | `tools:ast_grep`, `rss` | S1-S5,S7 | keep-local | | | Retain full-langs + perl and local windows-sys Wdk features. | `crates/pi-natives/src/ast.rs`; `crates/pi-iso/Cargo.toml` | — | — |\n| D-TASK-CATCH-UNWIND | keep-local | `crates/pi-natives/src/task.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/task.rs` | keep-local | Abort task APIs | Async NAPI operations | Task panic-to-rejection test | `natives-grep` | — | keep-local | | | Preserve catch_unwind to napi::Error contract. | `crates/pi-natives/src/task.rs` | — | — |\n| D-NATIVES-PACKAGING | keep-local | `packages/natives/native/loader-state.js@a85bd5228d9f0f619deade1db78fa49420a721e1` | loader-state.js, build-native.ts, gen-enums.ts, embed-native.ts, platform packages | keep-local | Native loader symbols and platform artifacts | Native package entrypoints | Loader/staging/variant tests | `startup` | — | keep-local | | | Preserve AVX2 variants, embedded addon, sentinels, napi-rs profiles and all five platform packages. | `packages/natives/test/native.test.ts`; `packages/natives/test/variant-override.test.ts` | — | — |\n| D-TRANSITIONAL-TS | keep-local | `crates/pi-natives/src/{hashline,linediff}.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | Transitional native modules | keep-local | Hashline and line diff | Edit TS implementations | Differential goldens per superseding item | `edit-hotspots` H03,H06 | — | keep-local | | | Retire each remaining transitional module only with its superseding item and parity evidence. The `edit_fuzzy.rs` module and H01/H02 exports were removed with 2.7 matcher adoption. | `packages/coding-agent/test/core/diff-oracle.test.ts`; `packages/coding-agent/test/core/hashline-native.test.ts` | `crates/pi-natives/src/edit_fuzzy.rs` | — |\n| D-PI-EDIT-UTF16-SCORING | keep-local | `crates/pi-edit/src/fuzzy.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-edit/src/fuzzy.rs` | keep-local | UTF-16 code-unit Levenshtein and similarity | `pi_edit::fuzzy::{find_match, seek_sequence, replace_text}` | Surrogate custom-threshold test; 80-case edit-benchmark differential confidences | `edit-hotspots` H01,H02 | — | keep-local | | | Preserve JavaScript UTF-16 code-unit scoring; upstream Unicode-scalar scoring changes confidence and flips the supported 0.88 surrogate threshold. Reapply on pi-edit re-sync. | `packages/coding-agent/test/core/edit-native-fuzzy-blocked-threshold.test.ts`; `packages/natives/test/differential/edit-fuzzy.test.ts` | — | — |\n| D-HIGHLIGHT-TS-JS-GRAMMAR | keep-local | `crates/pi-natives/src/syntaxes/TypeScript.sublime-syntax@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/highlight.rs`; `crates/pi-natives/src/syntaxes/Astro.sublime-syntax` | keep-local | `highlightCode` for ts/tsx/astro | Coding-agent code rendering | Highlight golden (`typescript`, `tsx`, `astro`) | `render-transcript` | — | keep-local | | | Do not bundle the pinned TypeScript/TypeScriptReact grammars: they grow first-use syntax-set construction from ~10ms to ~600ms and make each ts/tsx highlight ~3x slower (render-transcript R02 renderDiff 48ms -> 160ms). ts/tsx keep syntect's JavaScript grammar and Astro embeds `source.js`. Reapply on highlight re-sync. | `packages/natives/test/phase3-highlight-golden.test.ts`; `crates/pi-natives/src/highlight.rs` | — | — |\n\n### E. Hot-path candidates\n\nResolved by the Phase 4 re-profile in `docs/rust-porting-reprofile.md` (post-vendoring dev, same corpus manifest as `docs/rust-porting-baseline.md`). The Phase 1a candidates H04, H05, H07–H11, M01–M05, TUI markdown render and the render-frame pipeline do not reach 5% self time in any scenario. The E-P4 rows are the functions that did reach it.\n\n| id | kind | upstream path@sha | local target | class | napi surface | TS callsite(s) | differential corpus | benchmark suite | rss scenarios | status | PR | rehearsal run | reason | keep-local guards | deleted paths | no-fallback paths |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| E-H04 | hot-path | — | `packages/coding-agent/src/modes/components/diff.ts` | candidate | — | Diff rendering component | Render golden transcript | `render-transcript` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-H05 | hot-path | — | Coding-agent file/render path from Phase 1a profile | candidate | — | Coding-agent rendering callsite | Render transcript golden | `render-transcript` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-H07 | hot-path | — | Coding-agent callsite from Phase 1a profile | candidate | — | Coding-agent startup or command path | Recorded-session corpus | `startup` | S1 | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-H08 | hot-path | — | Coding-agent callsite from Phase 1a profile | candidate | — | Coding-agent tool execution path | Recorded-session corpus | `tools` | S3,S7 | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-H09 | hot-path | — | Coding-agent callsite from Phase 1a profile | candidate | — | Coding-agent callsite | Recorded-session corpus | `replay` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-H10 | hot-path | — | Coding-agent callsite from Phase 1a profile | candidate | — | Coding-agent callsite | Recorded-session corpus | `replay` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-H11 | hot-path | — | Coding-agent callsite from Phase 1a profile | candidate | — | Coding-agent callsite | Recorded-session corpus | `replay` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-M01 | hot-path | — | Agent callsite from Phase 1a profile | candidate | — | Agent per-token processing | Sanitized replay corpus | `replay` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): CI run 36697257230 (dev `929e0fb367`) attributes `managedChargeStringBytes` at 3.13%/3.65% of replay scenario self time — below the 5% hand-port threshold. Per-token-window attribution is higher at 14.0%/15.3% of sampled, but does not lead the critical path (path leader is `structuredClone` at 20.0%/17.4% per-token). Rejection rule: replay scenario self time < 5%. | — | — | — |\n| E-M02 | hot-path | — | Agent callsite from Phase 1a profile | candidate | — | Agent context estimation | Sanitized replay corpus | `replay` | S1,S3,S7 | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-M03 | hot-path | — | Agent callsite from Phase 1a profile | candidate | — | Agent callsite | Sanitized replay corpus | `replay` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-M04 | hot-path | — | Agent callsite from Phase 1a profile | candidate | — | Agent callsite | Sanitized replay corpus | `replay` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-M05 | hot-path | — | Agent callsite from Phase 1a profile | candidate | — | Agent callsite | Sanitized replay corpus | `replay` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-TUI-MARKDOWN | hot-path | — | `packages/tui/src/components/markdown.ts` | candidate | — | TUI markdown render | Recorded markdown/render fixtures | `tui-render-frame` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-TUI-RENDER-FRAME | hot-path | — | TUI render-frame pipeline | candidate | — | TUI frame construction and rendering | Replay frame snapshots | `tui-render-frame` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-P4-REGISTRY-SELECTOR | hot-path | — | `packages/coding-agent/src/config/model-registry.ts` (`registrySelectorResolvesToModel`) | candidate | — | Registry-profile selector filtering on every registry refresh | Model-registry and profile-activation suites | `bench:profile` compaction, replay | — | rejected | | | Re-profile: 41.6% of compaction and 24.4% of replay self time. Not hand-ported: the cost was a per-selector catalog rescan, removed in TypeScript by a prebuilt selector index (#6125); CI re-profile of that change measures 0%. | — | — | — |\n| E-P4-CANONICAL-JSON | hot-path | — | `packages/coding-agent/src/config/model-preset-registry.ts` (`serializeCanonicalJson`) | candidate | — | Cached registry generation re-validation | Canonical JSON parity tests | `bench:profile` compaction | — | rejected | | | Re-profile: 7.8% of compaction self time. Not hand-ported: the input is a JS object graph, so a native serializer pays a napi call per property; skipping per-string `JSON.stringify` in TypeScript cut it 32% with byte-identical output (#6127). | — | — | — |\n| E-P4-NAMESPACE-SUFFIXES | hot-path | — | `packages/coding-agent/src/config/model-equivalence.ts` (`getQualifiedNamespaceSuffixes`) | candidate | — | Canonical model-index candidate expansion | Model equivalence suites | `bench:profile` compaction | — | rejected | | | Re-profile: 5.2% of compaction self time, just over the threshold. Not hand-ported: it generates short string candidates from JS strings behind a memo, where napi call overhead per candidate exceeds the work; `buildCanonicalModelIndex` over all 4677 bundled models takes 1.6 ms. | — | — | — |\n| E-P4-MATERIALIZE-RESIDENT | hot-path | — | `packages/coding-agent/src/session/session-manager.ts` (`materializeResidentValueSync`) | candidate | — | Session resident value materialization on load | Session replay scenarios | `bench:profile` session-load | — | rejected | | | Re-profile: 10.8% of session-load critical-path self time. Not hand-ported: materializes JS objects from resident stores; a napi boundary would add overhead per property traversal. Refactoring to reduce materialization overhead or async materialization belongs in TypeScript optimization. | — | — | — |\n| E-P4-MEASURE-JSON-LIKE | hot-path | — | `packages/coding-agent/src/session/session-manager.ts` (`measureJsonLikeBytes`) | candidate | — | JSON-like value size estimation for context tracking | Session tracking and persistence | `bench:profile` session-load | — | rejected | | | Re-profile: 6.8% of session-load critical-path self time. Not hand-ported: measures byte size of JSON-serializable values; would require serialization in native code with minimal per-call amortization. TypeScript optimizations to reduce size or less-frequent measurements are preferable. | — | — | — |\n\n## Shared gate tooling\n\n### Differential harness\n\n`packages/natives/test/differential/` provides a shared differential runner. `defineDifferential` binds a module name, stable case IDs and inputs, an optional live reference, a native implementation, optional semantic-only normalization, and a comparator. Record with `bun run diffcheck <module> --record` (record mode requires a live reference); verify with `bun run diffcheck <module>`. Goldens are JSONL records keyed by case ID and a SHA-256 hash of the canonical input. Verification reports `total`, `identical`, `accepted`, `unlisted`, and `stale`. Normalization may remove only explicitly declared non-semantic fields. Divergences belong in `divergences/<module>.json` with `caseId`, `reason`, `upstreamRationale`, `changelog`, and `testsUpdated`.\n\n### A/B and RSS gates\n\n`bun scripts/native-bench-ab.ts --suite edit-hotspots --base <base-sha> --json` runs the suite on the base worktree and current head on the same host, interleaved in paired blocks. Adapters emit `{schema:\"gjc.native-bench-ab/1\",suite,cases:[{id,status:\"measured\"|\"skipped\"|\"error\",samples:number[]}]}` when run with `--json --strict`. Default latency collection uses 15 paired blocks × 200 measured iterations after warmup. A/A calibration is same-commit mode: `bun scripts/native-bench-ab.ts --suite edit-hotspots --base HEAD --calibrate --json`; the RSS suite uses `--suite rss` and measures S1–S5,S7. Bootstrap resamples paired blocks of per-block p50 and p95 timings. PASS requires the upper 95% paired-block bootstrap CI for head/base p50 ≤ 1.03 and p95 ≤ 1.10. FAIL applies if the lower bound exceeds either threshold; otherwise the runner doubles blocks 15→30→60 and a still-inconclusive outcome fails. Skips, errors, missing cases, or empty samples fail.\n\nFor RSS, build the compiled binary on each side with `bun --cwd=packages/coding-agent run build`. For each side and each capture, run from that worktree root exactly: `bun scripts/verify-rss-checkpoints.ts --all --json > $AB/rss-<side>-<i>.json`. Compare `scenarios[id].rssBytes.stableTree.median` for measured S1,S2,S3,S4,S5,S7 with a paired bootstrap 95% CI; PASS requires upper bound ≤1.05 for every scenario. INCONCLUSIVE triggers K=10 once, then fails. A non-measured selected scenario fails. The legacy envelope also applies: base runs `bun scripts/verify-rss-checkpoints.ts --all --write-baseline --json`, then copy `.gjc/rss-checkpoints/<base-sha>.json` to `$AB/rss-base.json`; head runs `bun scripts/verify-rss-checkpoints.ts --all --compare --baseline $AB/rss-base.json --allow-baseline-drift --json` and must show zero regressions. Without `--allow-baseline-drift`, `BaselineIdentityMismatch` is an ERROR. The W5b idle rule is separately checked with `bun scripts/verify-module-trace.ts --scenario help,version,idle --milestone W5b`.\n",
|
|
97
|
+
"rust-porting-inventory.md": "# Rust porting inventory\n\nTracks every candidate for moving gjc hot paths and TypeScript implementations into the native addon (`crates/pi-natives` via `@gajae-code/natives`), whether vendored from upstream or hand-ported after profiling.\n\nUpstream pin: `can1357/oh-my-pi@a85bd5228d9f0f619deade1db78fa49420a721e1`\n\n- Upstream sources are read only at this commit (`https://raw.githubusercontent.com/can1357/oh-my-pi/a85bd5228d9f0f619deade1db78fa49420a721e1/<path>`), never from `main`.\n- Every vendored file carries an attribution header naming this commit, unless its row below records a per-row bump made by the PR that needed newer code.\n- Upstream is MIT licensed; vendored crates keep their `LICENSE` and are listed in `NOTICE.md`.\n- `docs/rust-porting/upstream-workspace-deps@a85bd522.toml` is a verbatim snapshot of the upstream root `[workspace.dependencies]` at the pin. `bun scripts/verify-rust-porting-inventory.ts --deps` checks the local workspace against it.\n\n## Status vocabulary\n\n| Status | Meaning |\n|---|---|\n| `candidate` | Listed, not started. |\n| `blocked` | Implementation or dependency is present, but a documented decision or parity gap blocks adoption. |\n| `in-progress` | Port is integrated on the branch; stays in-progress until PR and rehearsal run URLs are recorded. |\n| `adopted` | Merged with parity, benchmark, and rehearsal evidence; replaced TS deleted with no fallback. |\n| `rejected` | Not adopted; `reason` is mandatory. |\n| `keep-local` | gjc-only Rust that a re-sync must not overwrite; guarded by named tests. |\n\n## Phase log\n\n| Phase | Status | PR | Rehearsal run | Notes |\n|---|---|---|---|---|\n| 0 — toolchain and shared deps at pin | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5923 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36105428757 | `nightly-2026-08-12`; shared workspace deps aligned to the pin. |\n| 1b — inventory and shared gates | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5924 | | Schema, `--deps` and `--final` gate tooling delivered. |\n| 2.7 — pi-edit matcher foundation | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/6016 | | Matcher and fuzzy scoring landed via #5980/#6016; UTF-16 threshold semantics kept under keep-local D-PI-EDIT-UTF16-SCORING. |\n| 2.8–2.10 — remaining pi-edit consumers | rejected | | | Upstream pi-edit hashline/patch/streaming modes implement a different edit protocol (PUT / unified-hunk rejection) than the gjc grammar in `hashline/grammar.lark`; D9 makes gjc prompts and grammars authoritative, so adopting them would change model-visible bytes. `Diff.applyPatch` and the hashline/apply_patch/streaming TS paths stay local, guarded by D-TRANSITIONAL-TS. |\n\n## Inventory tables\n\n`—` means not applicable or not yet recorded. Every table uses the schema enforced by `scripts/verify-rust-porting-inventory.ts`.\n\n### A. Upstream-only crates\n\n| id | kind | upstream path@sha | local target | class | napi surface | TS callsite(s) | differential corpus | benchmark suite | rss scenarios | status | PR | rehearsal run | reason | keep-local guards | deleted paths | no-fallback paths |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| A-PI-VFS | crate | `crates/pi-vfs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-vfs` | infra | — | Filesystem substrate only; no TS callsite. | Filesystem fixture trees, symlinks, ignore files, large dirs, Windows paths. | `tools:glob`, `rss` | S1-S5,S7 | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Integrated async/blocking injectable filesystem required by pi-walker, pi-shell, pi-builtins and pi-natives iofs. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6039. | — | — | — |\n| A-PI-DIFF | crate | `crates/pi-diff@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-diff`; `crates/pi-natives/src/diff.rs` | (a) | diffLines, diffLineRuns, structuredPatchHunks, diffWords, DiffStream | `packages/coding-agent/src/edit/diff.ts`; `modes/components/diff.ts`; `tools/vim.ts`; `eval/js/shared/helpers.ts`; `hashline/recovery.ts` | jsdiff oracle, fixtures × seeded mutations | `edit-hotspots` H03, `word-diff`, `render-transcript` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Merged via #5980; word-diff A/B vs pre-port `01511ffd5f` PASS at 30 blocks (D01-D03 p50 upper <=0.92, p95 upper <=0.97): `docs/rust-porting/evidence/native-ab-word-diff.json`. `Diff.applyPatch` stays in TypeScript for `hashline/recovery.ts` (keep-local under the rejected 2.8–2.10 consumers and D-TRANSITIONAL-TS). | — | — | `packages/coding-agent/src/edit/diff.ts` |\n| A-PI-WALKER | crate | `crates/pi-walker@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-walker`; `crates/pi-natives/src/iofs.rs` | infra/overlap | glob, invalidateFsScanCache, walkerPoolStatus | Filesystem traversal substrate; `packages/coding-agent/src/tools/find.ts` | Recorded glob/fd/workspace outputs on fixture trees | `tools:glob`, `startup`, `tools` | S1-S5,S7 | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Integrated traversal substrate and pi-edit dependency; absorbs fs_cache policy and keeps the serial-on-pool-failure branch. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6027, https://github.com/Yeachan-Heo/gajae-code/pull/6039. | — | — | — |\n| A-PI-EDIT | crate | `crates/pi-edit@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-edit`; `crates/pi-natives/src/edit.rs` | (a) | `editFindMatch`, `editSeekSequence`, hashline, patch, EditSession, EditStore APIs | `packages/coding-agent/src/edit/{modes/replace,patch,apply-patch}.ts`; `edit/apply-patch/*`; `edit/{diff,normalize,streaming,notebook}.ts`; `hashline/*.ts` | Edit-benchmark TS matcher goldens including errors; per-mode goldens through 2.10 | `edit-hotspots` H01,H02,H06; `tools:edit`; `bench:edit`; `replay` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/6016 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Matcher merged via #5980 and fuzzy scoring fix #6016; edit-hotspots H01/H02 covered by the pi-edit fuzzy A/B in #6016. | — | — | `packages/coding-agent/src/edit/modes/replace.ts`; `packages/coding-agent/src/edit/streaming.ts`\n| A-PI-VCS | crate | `crates/pi-vcs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-vcs`; `crates/pi-natives/src/vcs.rs` | (a), size-gated (D5) | VCS read APIs | `packages/coding-agent/src/utils/git.ts`; `autoresearch/git.ts`; `status-line/git-utils.ts`; `commit/agentic/tools/git-*.ts` | Git CLI oracle over fixture repositories | `status-line`, `tools:git` | — | rejected | | | D5 perf gate: large-worktree read suite upper-CI p50 ratio was 0.826 (needs <0.80), and status was 1.20x slower. | — | — | — |\n| A-PI-BUILTINS-BASE | crate | `pi-builtins/src/<builtin>.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/brush-builtins-vendored` | overlap | — | Shell builtins; port changes one builtin at a time. | Existing fork tests plus per-builtin guard tests. | `builtins`, `shell` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36538316951 | Integrated portable base-builtin changes; per-builtin parity: eight builtins (echo, printf, test/[, read, cd/pwd, export, type/command -v, for/arithmetic) produce byte-identical stdout on 01511ffd5f and dev in the `builtins` suite. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6062, https://github.com/Yeachan-Heo/gajae-code/pull/6117. | `crates/brush-builtins-vendored/src/read.rs` | — | — |\n| A-PI-BUILTINS-UTILS | crate | `pi-builtins/src/utils@a85bd5228d9f0f619deade1db78fa49420a721e1` | — | (c) | — | Bash tool builtins | — | — | — | rejected | | | GNU/BSD behavior diverges; utility builtins pull C-backed onig_sys and pcre2-sys. uucore is already a pre-existing printf dependency and is not a rejection criterion. | — | — | — |\n| A-PI-VOICE | crate | `crates/pi-voice@a85bd5228d9f0f619deade1db78fa49420a721e1` | — | (c) | — | gjc STT uses `packages/coding-agent/src/stt/recorder.ts` | — | — | — | rejected | | | opus/webrtc and dynamically loaded PulseAudio/ALSA are out of scope; gjc uses sox/ffmpeg/arecord. | — | — | — |\n\n### B. Upstream-only pi-natives modules\n\n| id | kind | upstream path@sha | local target | class | napi surface | TS callsite(s) | differential corpus | benchmark suite | rss scenarios | status | PR | rehearsal run | reason | keep-local guards | deleted paths | no-fallback paths |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| B-DIFF | native module | `crates/pi-natives/src/diff.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/diff.rs` | (a) | diffLines, diffLineRuns, structuredPatchHunks, diffWords, DiffStream | `packages/coding-agent/src/edit/diff.ts`; `modes/components/diff.ts`; `tools/vim.ts`; `eval/js/shared/helpers.ts`; `hashline/recovery.ts` | jsdiff oracle and seeded mutations | `edit-hotspots` H03, `word-diff`, `render-transcript` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Native diff module merged via #5980; word-diff A/B PASS (`docs/rust-porting/evidence/native-ab-word-diff.json`). | — | — | `packages/coding-agent/src/edit/diff.ts` |\n| B-EDIT | native module | `crates/pi-natives/src/edit.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/edit.rs` | (a) | `editFindMatch`, `editSeekSequence` | `packages/coding-agent/src/edit/modes/replace.ts`; `packages/coding-agent/src/edit/modes/patch.ts`; `packages/coding-agent/src/edit/diff.ts` | Edit-benchmark TS matcher goldens including errors and every confidence | `edit-hotspots` H01,H02 | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/6016 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | editFindMatch/editSeekSequence consumers merged via #5980 and #6016, no TS fuzzy fallback. | — | — | `packages/coding-agent/src/edit/modes/replace.ts`\n| B-VCS | native module | `crates/pi-natives/src/vcs.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/vcs.rs` | (a), size-gated (D5) | VCS read APIs | `packages/coding-agent/src/utils/git.ts`; listed git callers | Git CLI over fixture repositories | `status-line`, `tools:git` | — | rejected | | | D5: addon +5.79 MB and aggregate read ratio 0.240 pass, but status regresses (1.14x-1.33x, CI lower >1.12) and native `show` output differs from the git CLI the TS parses. Evidence: `docs/rust-porting/evidence/2.17-pi-vcs-d5.json`. Partial adoption (diff/log native, status/show CLI) needs an owner D5 decision. | — | — | — |\n| B-MERMAID | native module | `crates/pi-natives/src/mermaid.rs`, `crates/pi-natives/fixtures/mermaid.json@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/mermaid/` | (a) | renderMermaidAscii | `packages/utils/src/mermaid-ascii.ts`; `packages/coding-agent/src/tools/render-mermaid.ts`; mermaid cache | beautiful-mermaid golden; divergence records per D6 | `mermaid-render` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Upstream output changes require an accepted divergence and changelog entry. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | — | — | `packages/utils/src/mermaid-ascii.ts` |\n| B-PDF | native module | `crates/pi-natives/src/pdf.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/pdf.rs`; pdf-inspector | (a) | pdfToMarkdown | Coding-agent PDF reading paths | License-clean PDF fixtures; MuPDF output goldens and explicit divergences | `read(pdf)` | S4 | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Full MuPDF removal; pdf-inspector default features only, no pdfium/ort/OCR. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | — | `packages/coding-agent/src/utils/mupdf.ts`; `packages/coding-agent/src/utils/mupdf-wasm.ts`; `packages/coding-agent/src/utils/mupdf-embedded.ts` | `packages/coding-agent/src/utils/pdf.ts`; `packages/coding-agent/src/utils/markit.ts` |\n| B-TOKENS | native module | `crates/pi-natives/src/tokens.rs`, `crates/pi-natives/src/utok/@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/tokens.rs`; `crates/pi-natives/src/utok/` | (a), RSS-gated (D2) | countTokens | `packages/agent/src/compaction/compaction.ts`; `packages/coding-agent/src/session/context-estimation.ts` | gpt-tokenizer oracle | `compaction-estimate` | all | rejected | | | D2, S3 idle RSS +9.5%. Evidence: `docs/rust-porting/evidence/2.15-tokens-rss.json`. Re-evaluable after #5934. | — | — | `packages/agent/src/compaction/compaction.ts` |\n| B-TTY-WRITER | native module | `crates/pi-natives/src/tty_writer.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/tty_writer.rs` | (a) | TtyWriter | `packages/tui/src/terminal.ts`; `terminal-capabilities.ts` | VirtualTerminal/PTY byte capture | `tui-render-frame`, `tui-input-write`, `keystroke` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/6194 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36846716134 | Unix implementation with win32 platform split per D4. Owner waived W01 non-inferiority failure (W01 FAIL at 65a80cf326 after the direct-UTF-8 enqueue fix: p50 [1.070, 1.111], p95 upper 1.188 (gate p50 upper <=1.03, p95 upper <=1.10; before the fix p50 upper 1.176, p95 upper 1.446); W02 PASS p50 upper 0.596). Evidence: `docs/rust-porting/evidence/native-ab-tty-write.json`. | — | — | `packages/tui/src/terminal.ts` |\n| B-FILE-LOCK | native module | `crates/pi-natives/src/file_lock/`@a85bd5228d9f0f619deade1db78fa49420a721e1 | `crates/pi-natives/src/file_lock/` | (a), architect-gated (D1) | FileLock | `packages/coding-agent/src/config/file-lock.ts`; `atomic-yaml-patch.ts`; model-preset-registry | Multi-process contention fixtures | `contention` | — | rejected | | | Cross-version lock safety (D1): upstream uses an abstract socket (Linux), a named mutex (Windows) or `flock` on a regular `${path}.lock` file (other Unix); none interlock with the legacy `${path}.lock` directory. Evidence: `packages/coding-agent/test/file-lock-cross-version-interlock.test.ts` (flock vs directory: each side errors instead of waiting, and the persistent sidecar wedges gjc after release). | — | — | `packages/coding-agent/src/config/file-lock.ts` |\n| B-SVG | native module | `crates/pi-natives/src/svg.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/svg.rs`; resvg/png | (b) | rasterizeSvg | Read/fetch previews of `image/svg+xml` | SVG fixtures to PNG dimensions/hash | `svg-rasterize` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36538316951 | New capability: `rasterizeSvg` backs read/fetch previews of `image/svg+xml`. `01511ffd5f` has no SVG rasterization entrypoint (its image loader has no SVG path), so there is nothing to A/B for non-inferiority. The differential is the committed geometry.svg PNG size/hash golden in `packages/natives/test/native.test.ts`. | — | — | — |\n| B-BLOCK | native module | `crates/pi-natives/src/block.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/block.rs` | (b)/infra | blockRange only if consumed | pi-edit if consumed, otherwise none | Block-range parity corpus | `edit-hotspots` | — | rejected | | | No `blockRange` napi export or TS consumer was added: block ranges are consumed inside Rust by pi-edit (`diff_string.rs`, hashline `block.rs`) through `pi_ast::block`, which C-AST covers; `crates/pi-natives/src/block.rs` is not vendored. | — | — | — |\n| B-SIXEL-DECODER | native module | `crates/pi-natives/src/sixel.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | — | (c) | decodeSixelToPng | — | — | — | — | rejected | | | No gjc consumer. | — | — | — |\n| B-IOFS | native module | `crates/pi-natives/src/iofs.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/iofs.rs` | infra | Filesystem operations; walkerPoolStatus | No direct TS swap; supports pi-vfs/walker integration | Filesystem fixture trees | `tools:glob`, `rss` | S1-S5,S7 | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Integrated with pi-vfs and pi-walker in 2.3. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6039. | — | — | — |\n| B-JS-TESTING | native module | `crates/pi-natives/src/{js,testing}.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/{js,testing}.rs` | infra | NAPI JS arena/testing helpers | Native modules; no TS replacement | Existing `packages/natives/test/*` | `edit-hotspots`, `natives-grep` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | NAPI JS arena/testing helpers merged via #5980; infrastructure with no TS replacement. | — | — | — |\n| B-WINDOWS-RUNTIME | native module | `crates/pi-natives/src/lib.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | Local `lib.rs` Windows runtime glue | infra | `__gjcInstallTokioRuntime` | N/A; loader runtime install | `loader-runtime-install.test.ts`; `windows-runtime-install.windows.test.ts` | `natives-grep` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Add local runtime install symbol; never copy upstream lib.rs or its sentinel. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | `packages/natives/test/loader-help-worktree.test.ts` | — | — |\n| B-CRASH-HANDLER | native module | `crates/pi-natives/src/crash_handler.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | Local `crates/pi-natives/src/crash.rs` | overlap | Local opt-in crash diagnostics | N/A | Opt-in/disabled-by-default contract | `crash`, `startup` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36538316951 | Ported from upstream while preserving the D8 opt-in diagnostics contract. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6062. | `packages/natives/test/crash-diagnostics-optin.test.ts` | — | — |\n| B-REJECTED-HELPERS | native module | `crates/pi-natives/src/{snapcompact,fonts,vectors,spelling,devicecheck,oauth_callback,desktop,applefm,audio,live}.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | — | (c) | — | No accepted consumer / build.rs helper executables | — | — | — | rejected | | | Not Cargo-only or no intended consumer at the pinned source. | — | — | — |\n\n### C. Overlapping modules (drift notes; approximate upstream/local lines)\n\n| id | kind | upstream path@sha | local target | class | napi surface | TS callsite(s) | differential corpus | benchmark suite | rss scenarios | status | PR | rehearsal run | reason | keep-local guards | deleted paths | no-fallback paths |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| C-GREP | overlap | `crates/pi-natives/src/grep.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/grep.rs` | overlap | grep/search/hasMatch | Existing tool search/read callsites | Grep goldens on fixture repository | `natives-grep` | S1-S5,S7 | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | ≈3.29k upstream/1.95k local; PCRE2 static bundled only, JS arena and walker; retains cached serial fallback and omits unused injectable shell::vfs support. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6027, https://github.com/Yeachan-Heo/gajae-code/pull/6039. | — | — | — |\n| C-GLOB | overlap | `crates/pi-natives/src/glob.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/glob.rs`; walker + iofs | overlap | glob, invalidateFsScanCache | Coding-agent discovery/find | Recorded filesystem fixture outputs | `tools:glob` | S1-S5,S7 | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Integrated into pi-walker while retaining fs_cache policy. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6027, https://github.com/Yeachan-Heo/gajae-code/pull/6039. | `crates/pi-natives/src/fs_cache.rs` | — | — |\n| C-FD | overlap | `crates/pi-natives/src/fd.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/fd.rs`; walker | overlap | fuzzyFind | File discovery/find | Recorded fixture trees | `startup`, `tools` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | File discovery uses the integrated pi-walker traversal substrate. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6027. | — | — | — |\n| C-TEXT | overlap | `crates/pi-natives/src/text.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/text.rs` | overlap | Text APIs | TUI text layout and edit hot paths | Text layout goldens, grapheme/combining fixtures | `keystroke`, `tui-render-frame` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | ≈2.12k/2.22k; js arena and per-keystroke/per-token path. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | — | — | — |\n| C-KEYS | overlap | `crates/pi-natives/src/keys.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/keys.rs` | overlap | Key parsing | `packages/tui/src/keys.ts` | Windows key fixtures incl. 0x08 | `keystroke` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | ≈1.77k/1.88k; retain Windows 0x08 behavior. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | `packages/natives/test/native.test.ts` | — | — |\n| C-HIGHLIGHT | overlap | `crates/pi-natives/src/highlight.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/highlight.rs`; `syntaxes/` | overlap | Syntax highlighting | Coding-agent code rendering | Language fixture set | `render-transcript` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/6023 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Merged via #6023; render-transcript A/B vs `01511ffd5f` PASS (`docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`). | — | — | — |\n| C-HTML | overlap | `crates/pi-natives/src/html.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/html.rs` | overlap | HTML-to-markdown | Read/fetch HTML | HTML fixtures | `read(html)` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | ≈0.07k/0.05k; html-to-markdown-rs 3.x. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | — | — | — |\n| C-SIXEL | overlap | `crates/pi-natives/src/sixel.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/sixel.rs` | overlap | Sixel encoding | Image display | License-clean Sixel fixtures | `read(image)` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | ≈0.20k/0.06k; icy_sixel 0.7. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | — | — | — |\n| C-CLIPBOARD | overlap | `crates/pi-natives/src/clipboard.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/clipboard.rs` | overlap | Clipboard APIs | Clipboard/image paths | Image payload fixtures | `clipboard` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36538316951 | ≈0.37k/0.08k; clipboard-win and image paths. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6129. | — | — | — |\n| C-PTY | overlap | `crates/pi-natives/src/pty.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/pty.rs` | overlap | PTY APIs | Shell/TUI terminal paths | PTY byte capture and teardown | `pty`, `tui-input-write` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36538316951 | ≈1.13k/1.25k; retain local hardening. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6062. | `packages/natives/test/native.test.ts` | — | — |\n| C-SHELL | overlap | `crates/pi-natives/src/shell.rs`, `pi-shell@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/shell.rs`; `crates/pi-shell` | overlap | executeShell | `packages/coding-agent/src/tools/bash.ts` | Shell behavior corpus and cancellation cases | `tools:bash`, `startup` | S1,S3,S7 | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Re-synced the shell stream decoder and portable base-builtin improvements; retained local process, cancellation, Windows PATH, minimizer, fixup, guardian, output budget, read cancellation, and hidden-console guards. Brush-core Fs/async-dependent changes, process/job API expansions, and upstream utility builtins are intentionally not adopted. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | `crates/pi-natives/src/shell.rs`; `packages/coding-agent/test/bash-executor.test.ts`; `packages/coding-agent/test/tools/bash-command-fixup.test.ts`; `packages/coding-agent/test/tools/bash-master-owner-session-id.test.ts`; `packages/natives/test/shell-read-builtin-abort.test.ts` | — | — |\n| C-PS | overlap | `crates/pi-natives/src/ps.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/ps.rs`; `crates/pi-shell/src/process.rs` | overlap | Process observation/control | Shell process observation | Child process lifecycle tests | `tools:bash` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | ≈0.24k/0.28k; local pi_shell::process remains. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | `packages/coding-agent/test/bash-executor.test.ts` | — | — |\n| C-ISO | overlap | `crates/pi-natives/src/iso.rs`, `crates/pi-iso@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/iso.rs`; `crates/pi-iso` | overlap | iso* | Platform-specific path/process helpers | ISO diff goldens on fixture trees | `iso`, `rss` | S1-S5,S7 | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36538316951 | ≈0.25k/0.24k; preserve local windows-sys Wdk features. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6062, https://github.com/Yeachan-Heo/gajae-code/pull/6089. | `crates/pi-natives/src/iso.rs` | — | — |\n| C-WORKSPACE | overlap | `crates/pi-natives/src/workspace.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/workspace.rs`; walker | overlap | listWorkspace | Workspace discovery | Recorded fixture trees | `startup`, `tools` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | Workspace discovery uses the integrated pi-walker traversal substrate. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | — | — | — |\n| C-SUMMARY | overlap | `crates/pi-natives/src/summary.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/summary.rs`; pi-ast parse_cache | overlap | summarizeCode | Coding-agent summaries | Language fixtures | `tools:ast_grep` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36538316951 | ≈0.09k/0.09k; pi-ast parse_cache. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6054. | — | — | — |\n| C-AST | overlap | `crates/pi-natives/src/ast.rs`, `crates/pi-ast@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/ast.rs`; `crates/pi-ast` | overlap | astGrep, astEdit | Coding-agent AST tools | Full language-set ast-grep goldens | `tools:ast_grep` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | ≈1.52k/1.02k; block + parse_cache; preserve full-langs, perl and languageSet. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | `crates/pi-natives/src/ast.rs` | — | — |\n| C-TASK | overlap | `crates/pi-natives/src/task.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/task.rs` | overlap | Async task helpers | Native async callsites | Abort and panic-to-rejection tests | `natives-grep` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36352209021 | ≈0.56k/0.42k; preserve AbortReason/AbortToken and catch_unwind contract. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. | `crates/pi-natives/src/task.rs` | — | — |\n| C-POWER | overlap | `crates/pi-natives/src/power.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/power.rs` | overlap | Power status | TUI power/status consumers | Recorded power status outputs | `power` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36538316951 | ≈0.53k/0.27k; drift to reconcile in its PR. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6062. | — | — | — |\n| C-PROF | overlap | `crates/pi-natives/src/prof.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/prof.rs` | overlap | Profiling helper | Runtime profiling | Profile output fixtures | `prof`, `startup` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36538316951 | ≈0.25k/0.26k; minor drift. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6062. | — | — | — |\n| C-APPEARANCE | overlap | `crates/pi-natives/src/appearance.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/appearance.rs` | overlap | Appearance status | TUI theme/appearance | Appearance fixtures | `appearance` | — | adopted | https://github.com/Yeachan-Heo/gajae-code/pull/5980 | https://github.com/Yeachan-Heo/gajae-code/actions/runs/36538316951 | ≈0.46k/0.45k; minor drift. A/B vs pre-port dev `01511ffd5f`: `docs/rust-porting/evidence/native-ab-vs-01511ffd5f.json`. Follow-up fixes: https://github.com/Yeachan-Heo/gajae-code/pull/6069, https://github.com/Yeachan-Heo/gajae-code/pull/6089. | — | — | — |\n\n### D. Keep-local ledger\n\nEach ledger row is `retain` or `reconcile`. Guard tests must pass on the pre-merge base and post-merge head. A row may be removed only by inventory status `retired` plus a reason and PR.\n\n| id | kind | upstream path@sha | local target | class | napi surface | TS callsite(s) | differential corpus | benchmark suite | rss scenarios | status | PR | rehearsal run | reason | keep-local guards | deleted paths | no-fallback paths |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| D-PI-NATIVES-LOCAL | keep-local | — | `crates/pi-natives/src/{build_info,computer,doctor_journal,doctor_link,memory,path_identity,recovery_fs,sdk}` | keep-local | Existing GJC-only exports | Existing public APIs | Existing native tests | — | — | keep-local | | | Preserve gjc-only native modules during upstream sync. | `packages/natives/test/computer.test.ts`; `packages/natives/test/doctor-journal.test.ts`; `packages/natives/test/doctor-retained-publish.test.ts`; `packages/natives/test/path-identity.test.ts`; `packages/natives/test/recovery-fs.test.ts`; `packages/natives/test/memory-guard-native.test.ts` | — | — |\n| D-CRASH-OPTIN | keep-local | `crates/pi-natives/src/crash_handler.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/crash.rs` | keep-local | Opt-in crash diagnostics | Startup diagnostics | Disabled-default and explicit-opt-in child-process tests | `startup` | — | keep-local | | | D8 retains opt-in behavior; current source defaults to a system-temp directory unless `GJC_CRASH_DIAGNOSTICS_DIR` overrides it, which differs from the ledger's planned `~/.gjc` destination and must be reconciled in 3.14. | `packages/natives/test/crash-diagnostics-optin.test.ts` | — | — |\n| D-FS-CACHE | keep-local | `crates/pi-natives/src/fs_cache.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/fs_cache.rs`; pi-walker cache | keep-local | Filesystem cache policy | Glob/find/workspace/grep | Filesystem cache unit tests | `tools:glob`, `rss` | S1-S5,S7 | keep-local | | | Reconcile budget, TTL and generation-safe publication into walker cache in 2.3. | `crates/pi-natives/src/fs_cache.rs` | — | — |\n| D-WALKER-POOL | keep-local | `crates/pi-walker/src/cache.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | pi-walker cache.rs and local `walkerPoolStatus` export | keep-local | `walkerPoolStatus` | Native filesystem consumers | Pool unavailable/serial traversal tests | `tools:glob`, `rss` | S1-S5,S7 | keep-local | | | Reapply and test pool-failure serial branch on every walker bump; never call par_iter outside pool.install. | `crates/pi-natives/src/lib.rs`; `packages/natives/test/native.test.ts` | — | — |\n| D-LOADER-SENTINELS | keep-local | `crates/pi-natives/src/lib.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | Local `lib.rs`; `build.rs` | keep-local | Local sentinels and currentExecutablePath | Native loader | Loader/sentinel tests | `startup` | — | keep-local | | | Keep `__piNativesV0_17_5`, `__piNativesPublishOutcomeV1`, currentExecutablePath and local build.rs. | `packages/natives/test/loader-help-worktree.test.ts`; `packages/natives/test/publish-diagnostic-declaration.test.ts`; `packages/natives/test/embedded-extraction-freshness.test.ts` | — | — |\n| D-PI-SHELL-PROCESS | keep-local | `crates/pi-shell/src/process.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-shell/src/process.rs` | keep-local | Process observation and signal target selection | Bash process lifecycle | Process unit/integration tests | `tools:bash` | S1,S3,S7 | keep-local | | | Preserve Present/Absent/Unknown, pgid/target selection, self-pgroup refusal and Darwin unique id. | `crates/pi-shell/src/process.rs`; `packages/coding-agent/test/bash-executor.test.ts` | — | — |\n| D-PI-SHELL-CANCEL | keep-local | `crates/pi-shell/src/cancel.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-shell/src/cancel.rs`; shell abort state | keep-local | AbortReason, AbortToken | Bash abort/timeout | Abort state Rust and TS tests | `tools:bash` | S1,S3,S7 | keep-local | | | Preserve Timeout/Signal/User, latched abort before token publication and overlapping aborts. | `crates/pi-natives/src/shell.rs`; `packages/coding-agent/test/bash-executor.test.ts` | — | — |\n| D-PI-SHELL-WINDOWS | keep-local | `crates/pi-shell/src/windows.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-shell/src/windows.rs` | keep-local | Windows PATH/spawn policy | Bash Windows shell | Windows PATH and hidden-shell tests | `tools:bash` | — | keep-local | | | Preserve MSYS path translation, Git-for-Windows roots and CREATE_NO_WINDOW flags. | `crates/pi-shell/src/windows.rs`; `packages/natives/test/windows-hidden-shell.windows.test.ts`; `packages/natives/test/windows-shell-path.windows.test.ts` | — | — |\n| D-PI-SHELL-MINIMIZER | keep-local | `crates/pi-shell/src/minimizer/`@a85bd5228d9f0f619deade1db78fa49420a721e1 | `crates/pi-shell/src/minimizer/`; defs and build.rs concatenation | keep-local | Shell output minimizer | Bash output | Minimizer engine/inline TOML tests | `tools:bash` | — | keep-local | | | Retain rtk-derived attribution and port upstream changes change-by-change. | `crates/pi-shell/src/minimizer.rs`; `packages/coding-agent/test/bash-executor.test.ts` | — | — |\n| D-PI-SHELL-FIXUP | keep-local | `crates/pi-shell/src/fixup.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-shell/src/fixup.rs` | keep-local | applyBashFixups | Bash command execution | Fixup unit tests | `tools:bash` | — | keep-local | | | Preserve local shell fixup behavior. | `crates/pi-shell/src/fixup.rs` | — | — |\n| D-PI-SHELL-GUARDIAN | keep-local | `crates/pi-shell/src/shell.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | Shell HMAC guardian ownership ledger | keep-local | Guardian process ownership | Bash process lifecycle | Ownership-ledger and guardian tests | `tools:bash` | — | keep-local | | | Preserve guardian ownership authentication and cleanup. | `packages/coding-agent/test/bash-executor.test.ts` | — | — |\n| D-PI-SHELL-CHILD-SESSION | keep-local | `crates/pi-shell/src/shell.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | Child-session action and output truncation | keep-local | Shell session action | Bash child process and output handling | Rust truth table and truncation tests | `tools:bash` | — | keep-local | | | Preserve detach semantics, output budgets and truncation. | `crates/pi-natives/src/shell.rs`; `packages/coding-agent/test/bash-executor.test.ts` | — | — |\n| D-BUILTINS-READ-CANCEL | keep-local | `crates/brush-builtins-vendored/src/read.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | Vendored brush read builtin | keep-local | Shell read builtin | Bash `read` cancellation/timeout | Read builtin unit tests | `tools:bash` | — | keep-local | | | Preserve cancellation, `-t` timeout, Interrupted, and `-t 0` readiness semantics. | `crates/brush-builtins-vendored/src/read.rs` | — | — |\n| D-BRUSH-CORE-WINDOWS | keep-local | `crates/brush-core-vendored/src/sys/windows/commands.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | brush-core-vendored Windows commands | keep-local | CommandWindowControlExt | Windows shell commands | Windows shell tests | `tools:bash` | — | keep-local | | | Preserve local Windows command/window-control patches. | `crates/brush-core-vendored/src/sys/windows/commands.rs`; `packages/natives/test/windows-hidden-shell.windows.test.ts` | — | — |\n| D-PI-AST-ISO | keep-local | `crates/pi-ast/Cargo.toml`, `crates/pi-iso/Cargo.toml@a85bd5228d9f0f619deade1db78fa49420a721e1` | pi-ast features and pi-iso local features | keep-local | `nativeBuildInfo().languageSet`; iso* | AST and platform consumers | LanguageSet test; pi-iso Rust tests | `tools:ast_grep`, `rss` | S1-S5,S7 | keep-local | | | Retain full-langs + perl and local windows-sys Wdk features. | `crates/pi-natives/src/ast.rs`; `crates/pi-iso/Cargo.toml` | — | — |\n| D-TASK-CATCH-UNWIND | keep-local | `crates/pi-natives/src/task.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/task.rs` | keep-local | Abort task APIs | Async NAPI operations | Task panic-to-rejection test | `natives-grep` | — | keep-local | | | Preserve catch_unwind to napi::Error contract. | `crates/pi-natives/src/task.rs` | — | — |\n| D-NATIVES-PACKAGING | keep-local | `packages/natives/native/loader-state.js@a85bd5228d9f0f619deade1db78fa49420a721e1` | loader-state.js, build-native.ts, gen-enums.ts, embed-native.ts, platform packages | keep-local | Native loader symbols and platform artifacts | Native package entrypoints | Loader/staging/variant tests | `startup` | — | keep-local | | | Preserve AVX2 variants, embedded addon, sentinels, napi-rs profiles and all five platform packages. | `packages/natives/test/native.test.ts`; `packages/natives/test/variant-override.test.ts` | — | — |\n| D-TRANSITIONAL-TS | keep-local | `crates/pi-natives/src/{hashline,linediff}.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | Transitional native modules | keep-local | Hashline and line diff | Edit TS implementations | Differential goldens per superseding item | `edit-hotspots` H03,H06 | — | keep-local | | | Retire each remaining transitional module only with its superseding item and parity evidence. The `edit_fuzzy.rs` module and H01/H02 exports were removed with 2.7 matcher adoption. | `packages/coding-agent/test/core/diff-oracle.test.ts`; `packages/coding-agent/test/core/hashline-native.test.ts` | `crates/pi-natives/src/edit_fuzzy.rs` | — |\n| D-PI-EDIT-UTF16-SCORING | keep-local | `crates/pi-edit/src/fuzzy.rs@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-edit/src/fuzzy.rs` | keep-local | UTF-16 code-unit Levenshtein and similarity | `pi_edit::fuzzy::{find_match, seek_sequence, replace_text}` | Surrogate custom-threshold test; 80-case edit-benchmark differential confidences | `edit-hotspots` H01,H02 | — | keep-local | | | Preserve JavaScript UTF-16 code-unit scoring; upstream Unicode-scalar scoring changes confidence and flips the supported 0.88 surrogate threshold. Reapply on pi-edit re-sync. | `packages/coding-agent/test/core/edit-native-fuzzy-blocked-threshold.test.ts`; `packages/natives/test/differential/edit-fuzzy.test.ts` | — | — |\n| D-HIGHLIGHT-TS-JS-GRAMMAR | keep-local | `crates/pi-natives/src/syntaxes/TypeScript.sublime-syntax@a85bd5228d9f0f619deade1db78fa49420a721e1` | `crates/pi-natives/src/highlight.rs`; `crates/pi-natives/src/syntaxes/Astro.sublime-syntax` | keep-local | `highlightCode` for ts/tsx/astro | Coding-agent code rendering | Highlight golden (`typescript`, `tsx`, `astro`) | `render-transcript` | — | keep-local | | | Do not bundle the pinned TypeScript/TypeScriptReact grammars: they grow first-use syntax-set construction from ~10ms to ~600ms and make each ts/tsx highlight ~3x slower (render-transcript R02 renderDiff 48ms -> 160ms). ts/tsx keep syntect's JavaScript grammar and Astro embeds `source.js`. Reapply on highlight re-sync. | `packages/natives/test/phase3-highlight-golden.test.ts`; `crates/pi-natives/src/highlight.rs` | — | — |\n\n### E. Hot-path candidates\n\nResolved by the Phase 4 re-profile in `docs/rust-porting-reprofile.md` (post-vendoring dev, same corpus manifest as `docs/rust-porting-baseline.md`). The Phase 1a candidates H04, H05, H07–H11, M01–M05, TUI markdown render and the render-frame pipeline do not reach 5% self time in any scenario. The E-P4 rows are the functions that did reach it.\n\n| id | kind | upstream path@sha | local target | class | napi surface | TS callsite(s) | differential corpus | benchmark suite | rss scenarios | status | PR | rehearsal run | reason | keep-local guards | deleted paths | no-fallback paths |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| E-H04 | hot-path | — | `packages/coding-agent/src/modes/components/diff.ts` | candidate | — | Diff rendering component | Render golden transcript | `render-transcript` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-H05 | hot-path | — | Coding-agent file/render path from Phase 1a profile | candidate | — | Coding-agent rendering callsite | Render transcript golden | `render-transcript` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-H07 | hot-path | — | Coding-agent callsite from Phase 1a profile | candidate | — | Coding-agent startup or command path | Recorded-session corpus | `startup` | S1 | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-H08 | hot-path | — | Coding-agent callsite from Phase 1a profile | candidate | — | Coding-agent tool execution path | Recorded-session corpus | `tools` | S3,S7 | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-H09 | hot-path | — | Coding-agent callsite from Phase 1a profile | candidate | — | Coding-agent callsite | Recorded-session corpus | `replay` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-H10 | hot-path | — | Coding-agent callsite from Phase 1a profile | candidate | — | Coding-agent callsite | Recorded-session corpus | `replay` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-H11 | hot-path | — | Coding-agent callsite from Phase 1a profile | candidate | — | Coding-agent callsite | Recorded-session corpus | `replay` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-M01 | hot-path | — | Agent callsite from Phase 1a profile | candidate | — | Agent per-token processing | Sanitized replay corpus | `replay` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): CI run 36697257230 (dev `929e0fb367`) attributes `managedChargeStringBytes` at 3.13%/3.65% of replay scenario self time — below the 5% hand-port threshold. Per-token-window attribution is higher at 14.0%/15.3% of sampled, but does not lead the critical path (path leader is `structuredClone` at 20.0%/17.4% per-token). Rejection rule: replay scenario self time < 5%. | — | — | — |\n| E-M02 | hot-path | — | Agent callsite from Phase 1a profile | candidate | — | Agent context estimation | Sanitized replay corpus | `replay` | S1,S3,S7 | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-M03 | hot-path | — | Agent callsite from Phase 1a profile | candidate | — | Agent callsite | Sanitized replay corpus | `replay` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-M04 | hot-path | — | Agent callsite from Phase 1a profile | candidate | — | Agent callsite | Sanitized replay corpus | `replay` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-M05 | hot-path | — | Agent callsite from Phase 1a profile | candidate | — | Agent callsite | Sanitized replay corpus | `replay` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-TUI-MARKDOWN | hot-path | — | `packages/tui/src/components/markdown.ts` | candidate | — | TUI markdown render | Recorded markdown/render fixtures | `tui-render-frame` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-TUI-RENDER-FRAME | hot-path | — | TUI render-frame pipeline | candidate | — | TUI frame construction and rendering | Replay frame snapshots | `tui-render-frame` | — | rejected | | | Phase 4 re-profile (`docs/rust-porting-reprofile.md`): no function on this path reaches 5% self time in any scenario or leads a critical path, so there is no hand-port to measure against the win threshold. | — | — | — |\n| E-P4-REGISTRY-SELECTOR | hot-path | — | `packages/coding-agent/src/config/model-registry.ts` (`registrySelectorResolvesToModel`) | candidate | — | Registry-profile selector filtering on every registry refresh | Model-registry and profile-activation suites | `bench:profile` compaction, replay | — | rejected | | | Re-profile: 41.6% of compaction and 24.4% of replay self time. Not hand-ported: the cost was a per-selector catalog rescan, removed in TypeScript by a prebuilt selector index (#6125); CI re-profile of that change measures 0%. | — | — | — |\n| E-P4-CANONICAL-JSON | hot-path | — | `packages/coding-agent/src/config/model-preset-registry.ts` (`serializeCanonicalJson`) | candidate | — | Cached registry generation re-validation | Canonical JSON parity tests | `bench:profile` compaction | — | rejected | | | Re-profile: 7.8% of compaction self time. Not hand-ported: the input is a JS object graph, so a native serializer pays a napi call per property; skipping per-string `JSON.stringify` in TypeScript cut it 32% with byte-identical output (#6127). | — | — | — |\n| E-P4-NAMESPACE-SUFFIXES | hot-path | — | `packages/coding-agent/src/config/model-equivalence.ts` (`getQualifiedNamespaceSuffixes`) | candidate | — | Canonical model-index candidate expansion | Model equivalence suites | `bench:profile` compaction | — | rejected | | | Re-profile: 5.2% of compaction self time, just over the threshold. Not hand-ported: it generates short string candidates from JS strings behind a memo, where napi call overhead per candidate exceeds the work; `buildCanonicalModelIndex` over all 4677 bundled models takes 1.6 ms. | — | — | — |\n| E-P4-MATERIALIZE-RESIDENT | hot-path | — | `packages/coding-agent/src/session/session-manager.ts` (`materializeResidentValueSync`) | candidate | — | Session resident value materialization on load | Session replay scenarios | `bench:profile` session-load | — | rejected | | | Re-profile: 10.8% of session-load critical-path self time. Not hand-ported: materializes JS objects from resident stores; a napi boundary would add overhead per property traversal. Refactoring to reduce materialization overhead or async materialization belongs in TypeScript optimization. | — | — | — |\n| E-P4-MEASURE-JSON-LIKE | hot-path | — | `packages/coding-agent/src/session/session-manager.ts` (`measureJsonLikeBytes`) | candidate | — | JSON-like value size estimation for context tracking | Session tracking and persistence | `bench:profile` session-load | — | rejected | | | Re-profile: 6.8% of session-load critical-path self time. Not hand-ported: measures byte size of JSON-serializable values; would require serialization in native code with minimal per-call amortization. TypeScript optimizations to reduce size or less-frequent measurements are preferable. | — | — | — |\n\n## Shared gate tooling\n\n### Differential harness\n\n`packages/natives/test/differential/` provides a shared differential runner. `defineDifferential` binds a module name, stable case IDs and inputs, an optional live reference, a native implementation, optional semantic-only normalization, and a comparator. Record with `bun run diffcheck <module> --record` (record mode requires a live reference); verify with `bun run diffcheck <module>`. Goldens are JSONL records keyed by case ID and a SHA-256 hash of the canonical input. Verification reports `total`, `identical`, `accepted`, `unlisted`, and `stale`. Normalization may remove only explicitly declared non-semantic fields. Divergences belong in `divergences/<module>.json` with `caseId`, `reason`, `upstreamRationale`, `changelog`, and `testsUpdated`.\n\n### A/B and RSS gates\n\n`bun scripts/native-bench-ab.ts --suite edit-hotspots --base <base-sha> --json` runs the suite on the base worktree and current head on the same host, interleaved in paired blocks. Adapters emit `{schema:\"gjc.native-bench-ab/1\",suite,cases:[{id,status:\"measured\"|\"skipped\"|\"error\",samples:number[]}]}` when run with `--json --strict`. Default latency collection uses 15 paired blocks × 200 measured iterations after warmup. A/A calibration is same-commit mode: `bun scripts/native-bench-ab.ts --suite edit-hotspots --base HEAD --calibrate --json`; the RSS suite uses `--suite rss` and measures S1–S5,S7. Bootstrap resamples paired blocks of per-block p50 and p95 timings. PASS requires the upper 95% paired-block bootstrap CI for head/base p50 ≤ 1.03 and p95 ≤ 1.10. FAIL applies if the lower bound exceeds either threshold; otherwise the runner doubles blocks 15→30→60 and a still-inconclusive outcome fails. Skips, errors, missing cases, or empty samples fail.\n\nFor RSS, build the compiled binary on each side with `bun --cwd=packages/coding-agent run build`. For each side and each capture, run from that worktree root exactly: `bun scripts/verify-rss-checkpoints.ts --all --json > $AB/rss-<side>-<i>.json`. Compare `scenarios[id].rssBytes.stableTree.median` for measured S1,S2,S3,S4,S5,S7 with a paired bootstrap 95% CI; PASS requires upper bound ≤1.05 for every scenario. INCONCLUSIVE triggers K=10 once, then fails. A non-measured selected scenario fails. The legacy envelope also applies: base runs `bun scripts/verify-rss-checkpoints.ts --all --write-baseline --json`, then copy `.gjc/rss-checkpoints/<base-sha>.json` to `$AB/rss-base.json`; head runs `bun scripts/verify-rss-checkpoints.ts --all --compare --baseline $AB/rss-base.json --allow-baseline-drift --json` and must show zero regressions. Without `--allow-baseline-drift`, `BaselineIdentityMismatch` is an ERROR. The W5b idle rule is separately checked with `bun scripts/verify-module-trace.ts --scenario help,version,idle --milestone W5b`.\n",
|
|
98
98
|
"rust-porting-reprofile.md": "# Rust porting Phase 4 re-profile\n\nThis is the post-vendoring CPU profile that the Rust porting plan's Phase 4 requires. It was produced by the session-replay harness (`bun run bench:profile -- --scenario all`) on the dedicated `macos-14` runner (`native-bench-ab.yml`, suite `profile-stability`). The runner waited for the 1-minute load to settle below half the cores and ran one warm-up profile before the measured runs. Raw profiles stay in the workflow artifacts; only aggregated function names and shares are recorded here.\n\n- Run: https://github.com/Yeachan-Heo/gajae-code/actions/runs/36502046414\n- Git SHA: `1011a4aedecaab7aa0913b1d84fb00172b2e5a61` (post-vendoring dev), Bun `1.4.0`, host `darwin/arm64`, sampling interval 100 µs\n- Corpus manifest SHA-256: `4e6f50a2485350fffeca7c7addf3b946830e96da7b80cdcec0c1f24d9791a108`, the same corpus as the Phase 1a baseline (`docs/rust-porting-baseline.md`)\n- Stability: every scenario had ≥8/10 top-10 overlap between the two measured runs (AC1a.3)\n\n## TypeScript functions at ≥5% self time\n\n| Scenario | Function | Share | Decision |\n|---|---|---:|---|\n| compaction | anonymous closure in `registrySelectorResolvesToModel` — `packages/coding-agent/src/config/model-registry.ts:789` | 41.6% | Fixed in TypeScript (#6125); not a port candidate |\n| replay | same closure | 24.4% | same |\n\nAfter #6125, the passing profile-stability run (https://github.com/Yeachan-Heo/gajae-code/actions/runs/36697257230, dev `929e0fb367`, all seven scenarios >=8/10 overlap) shows 0% in both scenarios; the earlier run 36548743458 failed its stability comparison and is provisional only. It also shows two remaining compaction functions over the threshold:\n\n| Scenario | Function | Share | Decision |\n|---|---|---:|---|\n| compaction | `serializeCanonicalJson` — `packages/coding-agent/src/config/model-preset-registry.ts:668` | 7.8% | Pure-TS fix, 32% faster with byte-identical output (#6127); Rust port rejected |\n| compaction | `getQualifiedNamespaceSuffixes` — `packages/coding-agent/src/config/model-equivalence.ts:364` | 5.2% | Rust port rejected (napi overhead per short-string candidate) |\n\nstartup, session-save, tools and keystroke have no TypeScript function at ≥5%. Their top self time is runtime and native: module resolution, `spawnSync`, file I/O and the xterm test terminal. Session-load has two TypeScript functions over the threshold (see Critical paths below).\n\n## Critical paths\n\n| Window | Top self time |\n|---|---|\n| per keystroke | `match` (regex, runtime) 91–100%, `nativeKeys` 8.6% |\n| per token delta | `structuredClone` 6.5–7.0% (replay scenario self time), `managedChargeStringBytes` 3.1–3.7% (replay scenario self time), `stringify` 5.3–6.1% (replay scenario self time); per-token-window attribution: `structuredClone` 20.0–17.4%, `managedChargeStringBytes` 14.0–15.3% of sampled |\n| session-load | `openSync`, `entries`, `writeFileSync`, `materializeResidentValueSync` 10.8%, `measureJsonLikeBytes` 6.8% |\n\nNone of the Phase 1a hand-port candidates (E-H*, E-M*, E-TUI-*) reaches 5% self time in any scenario or leads a critical path (except E-M01, where replay scenario self time is measured below the threshold: `managedChargeStringBytes` 3.1–3.7%; the per-token-window attribution is higher at 14.0–15.3%, but does not lead the path; see E-M01 note below). Each is recorded as `rejected` or `unconfirmed` in `docs/rust-porting-inventory.md`. The five functions that crossed 5% or showed significant critical-path presence are the rows E-P4-REGISTRY-SELECTOR, E-P4-CANONICAL-JSON, E-P4-NAMESPACE-SUFFIXES, E-P4-MATERIALIZE-RESIDENT, and E-P4-MEASURE-JSON-LIKE. None was hand-ported: in each case the cost is TypeScript work over JS objects or strings, where a napi boundary adds per-call overhead or the function is a utility that enables higher-level TypeScript optimizations.\n",
|
|
99
99
|
"sdk-app-guide.md": "# Building Applications on the Gajae-Code SDK\n\nA beginner-friendly guide to using Gajae-Code as the **agent runtime for your own\napplication** — mobile apps, desktop apps, custom web frontends, chat bots, and\nvertical AI products.\n\n> Proof that this works in production: the bundled **Telegram, Discord, and Slack\n> integrations are SDK-core managed adapters**. They use opaque Router attachments\n> and provider-neutral presentation contracts rather than private endpoint credentials.\n\nRelated references:\n\n- [SDK wire protocol & machine interfaces](./sdk.md) — internal wire semantics and supported managed adapters\n- [Embedding SDK](./sdk-embedding.md) — the in-process TypeScript API\n- [External control readiness](./external-control-readiness.md) — supported surfaces\n\n## Why build on Gajae-Code?\n\nEvery vertical AI app ends up needing the same backend pieces: an agentic loop,\ntool execution, session persistence, model/auth management, streaming, retries,\nand compaction. Some also need a configured remote-notification integration.\nTeams keep rebuilding these from scratch.\n\nGajae-Code packages the runtime as a reusable component:\n\n- **Drop the agentic loop from your codebase.** `createAgentSession()` gives you\n a production agent loop (tools, retries, compaction, session files, model\n fallback chains) in one call.\n- **A managed machine interface is available.** Top-level sessions host an internal\n loopback endpoint, while SDK-core `SessionRouter` retains its discovery record,\n credentials, replay cursor, and exact attachment authority. Applications use\n Coordinator MCP, the SDK session CLI, or a configured managed adapter rather\n than opening that endpoint directly.\n- **Many subscribers, one session.** In-process subscribers and configured managed\n adapters can observe the same session without sharing endpoint credentials.\n- **Not just for coding.** Tools, skills, rules, and the system prompt are all\n injectable, so the same runtime powers legal assistants, research agents,\n data-analysis products — any vertical.\n\n\n## The two supported integration surfaces\n\n| | Embedding SDK (in-process) | Managed external control |\n| --- | --- | --- |\n| What it is | Import `@gajae-code/coding-agent` as a library | Use Coordinator MCP, SDK session CLI, or a configured Telegram/Discord/Slack adapter |\n| Language | TypeScript / Bun (Node-compatible) | Any client capable of the selected MCP/CLI/provider interface |\n| Telemetry | Full token deltas, tool events, and session events | Curated provider-neutral frames and queries |\n| Trust model | Your process hosts the runtime | SDK core retains endpoint credentials and issues exact opaque attachments |\n| Typical consumer | Your app UI and business logic | Bots, dashboards, and orchestrators |\n\nA common production shape combines an in-process UI with one or more configured\nmanaged adapters. Applications never discover endpoint records or open raw session\nWebSockets.\n\n\n## Quick start: embed the runtime\n\n```bash\nbun add @gajae-code/coding-agent\n```\n\n```ts\nimport { createAgentSession } from \"@gajae-code/coding-agent\";\n\nconst { session } = await createAgentSession();\n\nsession.subscribe((event) => {\n if (\n event.type === \"message_update\" &&\n event.assistantMessageEvent.type === \"text_delta\"\n ) {\n process.stdout.write(event.assistantMessageEvent.delta); // token-level stream\n }\n});\n\nawait session.prompt(\"Summarize this repository in 3 bullets.\");\nawait session.dispose();\n```\n\n`createAgentSession()` follows *provide to override, omit to discover*: with no\noptions it auto-discovers auth, models, settings, tools, context files, and a\nfile-backed session store. Everything is overridable.\n\n## Customizing the runtime for your vertical\n\nThis is the part that turns Gajae-Code from \"a coding agent\" into a general\nexecution runtime. All of the following are `createAgentSession()` options; see\nthe [Embedding SDK](./sdk-embedding.md) for the public API.\n\n### Restrict or drop tools\n\n```ts\nconst { session } = await createAgentSession({\n // Allowlist of built-ins — everything else is dropped.\n toolNames: [\"read\", \"grep\", \"find\"],\n // Optionally restrict bash to specific command prefixes.\n bashAllowedPrefixes: [\"git status\", \"git log\"],\n});\n```\n\nRuntime changes are also supported: `session.getActiveToolNames()`,\n`session.getAllToolNames()`, `session.setActiveToolsByName(names)` — the system\nprompt is rebuilt automatically.\n\n### Add custom tools\n\n```ts\nconst { session } = await createAgentSession({\n toolNames: [\"read\"],\n customTools: [myDomainTool], // CustomTool | ToolDefinition\n // Or bring tools from an MCP server you own:\n mcpConfigPath: \"/abs/path/to/mcp-config.json\",\n});\n```\n\n### Inject skills, rules, and identity\n\n```ts\nconst { session } = await createAgentSession({\n skills: myVerticalSkills, // replaces bundled skill discovery\n rules: myRules,\n contextFiles: [{ path: \"DOMAIN.md\", content: domainKnowledge }],\n systemPrompt: (defaults) => [...defaults, myVerticalPromptBlock],\n promptTemplates: myTemplates,\n});\n```\n\n### Isolate state for request-scoped agents\n\n```ts\nimport { SessionManager, Settings } from \"@gajae-code/coding-agent\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.inMemory(), // no filesystem persistence\n settings: Settings.isolated({ \"compaction.enabled\": true }),\n});\n```\n\n### Structured-output subagents\n\n`outputSchema`, `requireYieldTool`, `taskDepth`, and `parentTaskPrefix` support\norchestrator patterns where a session must return machine-readable results.\n\n### Observability\n\nPass `telemetry: {}` to enable OpenTelemetry GenAI-semantic-convention spans\n(no-op unless an OTEL SDK is registered in your host).\n\n## Attach from another process\n\nProcess-isolated integrations use an SDK-core adapter backed by `SessionRouter`. Endpoint discovery records and bearer tokens are internal implementation details; applications must not read them or open raw session WebSockets.\n\nManaged adapters receive opaque `SessionAttachment` capabilities for live controls and submit lifecycle mutations through `SessionLifecycleService`. See [sdk.md](./sdk.md) for the ownership and adapter contract.\n\nThe `models.list/current` (Q10) catalog also lists model profiles as synthetic\n`gajae-code/<profile>` entries (e.g. `gajae-code/codex-eco`). Treat them as\nlogical selections, not API providers: sending the id back through `model.set`\nactivates the profile for the live session only. Persisting remains an explicit\nTUI choice. Request Q27 (`models.profiles.list`) when you need the\nfull profile catalog including unavailable profiles and their `available`\nstatus. See [Model profiles as synthetic models](./sdk.md#model-profiles-as-synthetic-models-gajae-codeprofile).\n\n## Creating and supervising sessions\n\nEmbedding creates a session directly with `createAgentSession()`. For an\nexternal controller that needs lifecycle operations, use Coordinator MCP or the\nSDK session CLI. A lifecycle CLI request names the `global` action,\nprovides its operation and JSON input, and supplies a caller-chosen idempotency\nkey:\n\n```bash\ngjc sdk session raw global --op session.create \\\n --idempotency-key <unique-key> \\\n --json-input '{\"cwd\":\"/absolute/path/to/repo\"}'\n```\n\nThe CLI connects to the broker as needed; broker bootstrap is not an embedder\nAPI. See the [external controller integration guide](./bot-integration.md#integration-surfaces)\nfor the supported controller surfaces and lifecycle constraints.\n\nFor an in-process application queue, treat `session.prompt()` as a bounded full\nturn and keep request identity and transport completion in the application.\nUse `steer`/`followUp` for live conversational controls, not as a durable queue\nreceipt: admission can complete before a message is consumed, a follow-up can\nshare the current run, and `waitForIdle()` does not by itself promise that every\ninput queue is empty. See [Queued input lifecycle](./sdk-embedding.md#queued-input-lifecycle)\nfor the admission, consumption, promotion, and terminal boundaries.\n\n\n## Application recipes\n\n- **Vertical AI app.** Embed with `toolNames`, `customTools`, `skills`, and a domain\n `systemPrompt`. Subscribe in-process for full-fidelity streaming.\n- **Custom web app or dashboard.** Keep the agent runtime in your backend process;\n expose your own authenticated product API, or use Coordinator MCP for supported\n external orchestration. Do not relay the internal session endpoint.\n- **Mobile or desktop companion.** Pair through a configured managed notification\n adapter for actions and approvals, or call an application backend that embeds GJC.\n- **Fleet orchestrator.** Use Coordinator MCP or daemon-session lifecycle operations\n to create and supervise worktree-scoped sessions.\n\n## External interface constraints\n\n- Endpoint records, bearer tokens, and raw session WebSockets are private SDK-core\n implementation details.\n- `config.patch` rejects secret fields, and `session.get_endpoint` is prohibited on\n public adapters.\n- Full-fidelity token deltas remain an in-process embedding capability.\n- Action identity is fail-closed: stale IDs never regain authority.\n\nDestructive operations (`session.delete`, `context.clear`) require\n`confirm: true`.\n\n## FAQ\n\n**Is embedding a subprocess?** No — it is a library import; the agent loop runs\nin your process. Process-isolated control uses Coordinator MCP, SDK session CLI,\nor a configured managed adapter.\n\n**Can multiple consumers watch one session?** Yes. In-process subscribers and\nmanaged adapters are additive; replies to asks remain first-valid-wins.\n\n**Can the TUI and my code share a session?** Use in-process embedding for code that\nhosts the runtime, or Coordinator MCP/managed adapters for process-isolated control.\nSession files remain resumable through canonical lifecycle operations.\n\n**I need another language.** Use Coordinator MCP or your own authenticated backend\naround the TypeScript embedding SDK. Dedicated embedding-like Rust/Python SDKs are\nroadmap work, not raw endpoint contracts.\n",
|
|
100
100
|
"sdk-embedding.md": "# SDK\n\nFor managed control and notification attachment policy, see [the Gajae-Code SDK](./sdk.md).\n\nThe SDK is the in-process integration surface for `@gajae-code/coding-agent`.\nUse it when you want direct access to agent state, event streaming, tool wiring, and session control from your own Bun/Node process.\n\nFor process-isolated control, use a managed SDK-core adapter backed by `SessionRouter`; raw endpoint discovery and direct WebSocket clients are not public integration surfaces.\n\n## Installation\n\n```bash\nbun add @gajae-code/coding-agent\n```\n\n\n## Entry points\n\n`@gajae-code/coding-agent/sdk` is the canonical entry point for embedders. The package root exports the same SDK APIs for convenience.\n\nCore exports for embedders:\n\n- `createAgentSession`\n- `SessionManager`\n- `Settings`\n- `AuthStorage`\n- `ModelRegistry`\n- `discoverAuthStorage`\n- Discovery helpers for retained context/prompt surfaces (`discoverContextFiles`, `discoverPromptTemplates`)\n- Tool factory surface (`createTools`, `BUILTIN_TOOLS`, tool classes)\n\n## Quick start (auto-discovery defaults)\n\n```ts\nimport { createAgentSession } from \"@gajae-code/coding-agent\";\n\nconst { session, modelFallbackMessage } = await createAgentSession();\n\nif (modelFallbackMessage) {\n process.stderr.write(`${modelFallbackMessage}\\n`);\n}\n\nconst unsubscribe = session.subscribe((event) => {\n if (\n event.type === \"message_update\" &&\n event.assistantMessageEvent.type === \"text_delta\"\n ) {\n process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Summarize this repository in 3 bullets.\");\nunsubscribe();\nawait session.dispose();\n```\n\n## What `createAgentSession()` discovers by default\n\n`createAgentSession()` follows “provide to override, omit to discover”.\n\nIf omitted, it resolves:\n\n- `cwd`: `getProjectDir()`\n- `agentDir`: `~/.gjc/agent` (via `getAgentDir()`)\n- `authStorage`: `discoverAuthStorage(agentDir)`\n- `modelRegistry`: `new ModelRegistry(authStorage)` + background `refreshInBackground()` when the registry is not provided\n- `settings`: `await Settings.init({ cwd, agentDir })`\n- `sessionManager`: `SessionManager.create(cwd)` (file-backed)\n- context files and prompt templates\n- built-in tools via `createTools(...)`\n- LSP integration (enabled by default)\n- `eventBus`: new `EventBus()` unless supplied\n\n### Required vs optional inputs\n\nTypically you must provide only what you want to control:\n\n- **Must provide**: nothing for a minimal session\n- **Usually provide explicitly** in embedders:\n - `sessionManager` (if you need in-memory or custom location)\n - `authStorage` + `modelRegistry` (if you own credential/model lifecycle)\n - `model` or `modelPattern` (if deterministic model selection matters)\n - `settings` (if you need isolated/test config)\n\n## Session manager behavior (persistent vs in-memory)\n\n`AgentSession` always uses a `SessionManager`; behavior depends on which factory you use.\n\n### File-backed (default)\n\n```ts\nimport { createAgentSession, SessionManager } from \"@gajae-code/coding-agent\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.create(process.cwd()),\n});\n\nconsole.log(session.sessionFile); // absolute .jsonl path\n```\n\n- Persists conversation/messages/state deltas to session files.\n- Supports resume/open/list/fork workflows.\n- `session.sessionFile` is defined.\n\n### In-memory\n\n```ts\nimport { createAgentSession, SessionManager } from \"@gajae-code/coding-agent\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.inMemory(),\n});\n\nconsole.log(session.sessionFile); // undefined\n```\n\n- No filesystem persistence.\n- Useful for tests, ephemeral workers, request-scoped agents.\n- Session methods still work, but persistence-specific behaviors (file resume/fork paths) are naturally limited.\n\n### Resume/open/list helpers\n\n```ts\nimport { SessionManager } from \"@gajae-code/coding-agent\";\n\nconst recent = await SessionManager.continueRecent(process.cwd());\nconst listed = await SessionManager.list(process.cwd());\nconst opened = listed[0] ? await SessionManager.open(listed[0].path) : null;\n```\n\n## Model and auth wiring\n\n`createAgentSession()` uses `ModelRegistry` + `AuthStorage` for model selection and API key resolution.\n\n### Explicit wiring\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n} from \"@gajae-code/coding-agent\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst available = modelRegistry.getAvailable();\nif (available.length === 0)\n throw new Error(\"No authenticated models available\");\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n model: available[0],\n thinkingLevel: \"medium\",\n sessionManager: SessionManager.inMemory(),\n});\n```\n\n### Selection order when `model` is omitted\n\nWhen no explicit `model`/`modelPattern` is provided:\n\n1. restore model from existing session (if restorable + key available)\n2. settings default model role (`default`)\n3. first available model with valid auth\n\nIf restore fails, `modelFallbackMessage` explains fallback.\n\n### Auth priority\n\n`AuthStorage.getApiKey(...)` resolves in this order:\n\n1. runtime override (`setRuntimeApiKey`)\n2. stored credentials in `agent.db`\n3. provider environment variables\n4. custom-provider resolver fallback (if configured)\n\n## Event subscription model\n\nSubscribe with `session.subscribe(listener)`; it returns an unsubscribe function.\n\n```ts\nconst unsubscribe = session.subscribe((event) => {\n switch (event.type) {\n case \"agent_start\":\n case \"turn_start\":\n case \"tool_execution_start\":\n break;\n case \"message_update\":\n if (event.assistantMessageEvent.type === \"text_delta\") {\n process.stdout.write(event.assistantMessageEvent.delta);\n }\n break;\n }\n});\n```\n\n`AgentSessionEvent` includes core `AgentEvent` plus session-level events:\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `agent_failed` (diagnostic failure for the current attempt; the correlated\n terminal `agent_end` still follows and remains the lifecycle boundary)\n- `retry_fallback_applied` / `retry_fallback_succeeded`\n- `ttsr_triggered`\n- `todo_reminder` / `todo_auto_clear`\n- `irc_message`\n\n## Prompt lifecycle\n\n`session.prompt(text, options?)` is the primary entry point.\n\nFailure consumers should handle `agent_failed` as an additive diagnostic event,\nnot as terminal completion. It carries only the sanitized `{ code, message }`\ndiagnostic for the correlated attempt — never the raw provider/local error —\nso consumers must not depend on provider-specific detail or raw error objects;\ncontinue waiting for `agent_end` before releasing prompt ownership, deadlines,\nor transport resources. This ordering also applies when the run is canceled or\nenters a maintenance continuation.\n\nBehavior:\n\n1. optional command/template expansion (`/` commands, custom commands, file slash commands, prompt templates)\n2. if currently streaming:\n - requires `streamingBehavior: \"steer\" | \"followUp\"`\n - queues instead of throwing work away\n3. if idle:\n - validates model + API key\n - appends user message\n - starts agent turn\n\n### Queued input lifecycle\n\nKeep admission, consumption, and completion separate when embedding the SDK:\n\n| Session state | Admission | Consumption | Completion |\n| --- | --- | --- | --- |\n| Fresh or settled idle with an assistant tail | `prompt()` starts a turn; an explicit `followUp` is accepted into the executable queue | The next turn consumes the queued message and normally starts its own run | That run publishes its own terminal `agent_end` |\n| Live model/tool loop | `steer` may be consumed by the current run; `followUp` waits for the next turn | The live loop can consume the message before its terminal boundary | The message may share the current run and terminal; it is not necessarily a separate model call |\n| Prompt unwind after the Agent loop emitted `agent_end` | An explicit `followUp` is still accepted | The SDK schedules a queued-only successor after the unwind finishes; it is not left waiting for an unrelated prompt | The successor owns a new run and terminal boundary |\n| Paused, cancelled, or a non-resumable non-assistant tail | The message can remain queued | No automatic successor is promised until a supported resume/abort path makes the queue deliverable | The admission is not completion; clear/remove the queue or resume it explicitly |\n| Existing queued input ahead of a new submission | The new message is ordered behind the existing queue | Queue order is preserved; later plain prompts do not overtake an earlier follow-up | Each consumed message is correlated at its actual dequeue boundary |\n\n`session.waitForIdle()` waits for active Agent work, session settlement, and\ncontinuations that the SDK has scheduled. It is **not** a receipt that every\nsteering/follow-up queue is empty. Inspect `pendingMessageCounts` or use the\nqueue APIs when queue state matters. A queued follow-up can also share the\npredecessor's run and terminal when the live loop consumes it; do not equate\none accepted submission with one model call or one `agent_end` event.\n\nFor generic embedders, prefer an application-owned queue of bounded full turns\nsubmitted through `session.prompt()`, and use `steer`/`followUp` only for live\nconversational controls. When a queued control must remain pending until its\nexact ownership is known, use `submitUserMessage()`:\n\n```ts\nconst submission = await session.submitUserMessage(\"refresh the status\", {\n deliverAs: \"followUp\",\n queuePolicy: \"sequential\",\n trackSubmission: true,\n});\n\nawait submission.admitted;\nconst execution = await submission.execution;\n// execution.disposition is \"joined-current-run\", \"promoted-to-run\", or \"removed\".\n// For execution, attemptScope identifies the run that owns the terminal boundary.\nawait submission.terminal;\n```\n\n`submissionId` is generated per call, so identical message text remains\nindependently correlatable. `admitted` resolves only after the exact queue\nentry exists. `execution` resolves at same-run consumption, successor-run\npromotion, or exact removal. `terminal` resolves at the owning run's\n`agent_end` boundary, or immediately for removal. `submission.cancel()` removes\nthe entry when it is still queued; it returns `false` after execution or\nremoval. `queuePolicy: \"sequential\"` preserves FIFO delivery one queued input\nat a time, including when the session's configured follow-up/steering mode is\n`\"all\"`.\n\nThe ordinary `sendUserMessage` promise retains its delivery-mode-dependent\ncompletion semantics:\n\n- An ordinary idle submission with no `deliverAs` queues nothing and awaits the\n prompt turn, including its terminal completion.\n- An explicit queued `steer`/`followUp`, or a submission diverted into a queue\n because a live turn is active, resolves when the submission is admitted to\n that delivery path. Use `submitUserMessage` when later consumption,\n completion, and cancellation must be correlated to one exact submission.\n\nNeither promise is a generic queue-drained receipt. Do not build a generic\nembedder contract around internal dispatch or promotion-correlation hooks.\n\nRelated APIs:\n\n- `sendUserMessage(content, { deliverAs? })`\n- `steer(text, images?)`\n- `followUp(text, images?)`\n- `sendCustomMessage({ customType, content, ... }, { deliverAs?, triggerTurn? })`\n- `abort()`\n\n## Tools integration\n\n### Built-ins and filtering\n\n- Built-ins come from `createTools(...)` and `BUILTIN_TOOLS`.\n- `toolNames` acts as an allowlist for built-ins.\n- Hidden tools (for example `yield`) are opt-in unless required by options.\n\n```ts\nconst { session } = await createAgentSession({\n toolNames: [\"read\", \"search\", \"find\", \"write\"],\n requireYieldTool: true,\n});\n```\n\n### Runtime tool set changes\n\n`AgentSession` supports runtime activation updates:\n\n- `getActiveToolNames()`\n- `getAllToolNames()`\n- `setActiveToolsByName(names)`\n\nSystem prompt is rebuilt to reflect active tool changes.\n\n## Discovery helpers\n\nUse these when you want partial control without recreating internal discovery logic:\n\n- `discoverAuthStorage(agentDir?)`\n- `discoverContextFiles(cwd?, _agentDir?)`\n- `discoverPromptTemplates(cwd?, agentDir?)`\n- `buildSystemPrompt(options?)`\n\n## Subagent-oriented options\n\nFor SDK consumers building orchestrators (similar to task executor flow):\n\n- `outputSchema`: passes structured output expectation into tool context\n- `requireYieldTool`: forces `yield` tool inclusion\n- `taskDepth`: recursion-depth context for nested task sessions\n- `parentTaskPrefix`: artifact naming prefix for nested task outputs\n\nThese are optional for normal single-agent embedding.\n\n## `createAgentSession()` return value\n\n```ts\ntype CreateAgentSessionResult = {\n session: AgentSession;\n setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void;\n modelFallbackMessage?: string;\n lspServers?: Array<{\n name: string;\n status: \"ready\" | \"error\";\n fileTypes: string[];\n error?: string;\n }>;\n eventBus: EventBus;\n};\n```\n\nUse `setToolUIContext(...)` only if your embedder provides UI capabilities that tools should call into.\n\n## Startup performance\n\n`createAgentSession()` runs two background optimizations to overlap I/O with the rest of session setup:\n\n- **Model-host preconnect.** As soon as the model is resolved, the SDK fires a best-effort `fetch.preconnect(model.baseUrl)` so DNS + TCP + TLS + HTTP/2 to the provider's host happens in parallel with tool registry build, and system-prompt assembly. The first real `fetch(...)` then reuses the warm connection, saving 100–300 ms on transcontinental hops (e.g. residential IP → `api.anthropic.com`). `packages/coding-agent/src/sdk/session.ts` triggers it through the `startup.networkPrewarm` runtime service (`packages/coding-agent/src/runtime/network-prewarm-service.ts`); setting `startup.networkPrewarm: false` skips it. If `fetch.preconnect` is unavailable (non-Bun runtime) or the call throws, the optimization is skipped (failures are logged at debug level) — never a hard dependency. Applies to interactive, print, and ACP modes.\n- **Conditional LSP warmup.** Startup LSP servers (those returned by `discoverStartupLspServers(cwd)`) are only warmed when **all** of these hold:\n - `enableLsp !== false` on the session options, **and**\n - `options.hasUI === true` (interactive TUI), **and**\n - the `lsp.diagnosticsOnWrite` setting is enabled.\n\n Print, script, and ACP invocations (`hasUI=false`) skip the warmup entirely: they don't render the warmup status indicator and typically finish before the language servers would stabilize, so warming them just spends CPU parsing big `initialize` responses concurrently with the LLM stream consumer and jitters perceived latency. Tools that actually need an LSP server still spin one up on demand through `getOrCreateClient()` — only the *startup* warmup is skipped. The returned `lspServers` field in `CreateAgentSessionResult` is therefore `undefined` (not an empty array) whenever the warmup branch was bypassed.\n\n## Minimal controlled embed example\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n Settings,\n} from \"@gajae-code/coding-agent\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst settings = Settings.isolated({\n \"compaction.enabled\": true,\n \"retry.enabled\": true,\n});\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n settings,\n sessionManager: SessionManager.inMemory(),\n toolNames: [\"read\", \"search\", \"find\", \"edit\", \"write\"],\n enableLsp: true,\n});\n\nsession.subscribe((event) => {\n if (\n event.type === \"message_update\" &&\n event.assistantMessageEvent.type === \"text_delta\"\n ) {\n process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Find all TODO comments in this repo and propose fixes.\");\nawait session.dispose();\n```\n",
|