@arnilo/prism 0.0.20 → 0.0.23

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 CHANGED
@@ -1,5 +1,51 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.0.23] - 2026-08-03
4
+
5
+ ### Added
6
+ - `@arnilo/prism-enterprise-postgres`: optional PostgreSQL composition for policy decisions, evaluation records, work-mutation idempotency, and model-router state.
7
+ - Checked enterprise PostgreSQL conformance/restart/contention, cleanup/index/storage performance evidence, and protected `PRISM_TEST_POSTGRES_URL` gate.
8
+
9
+ ### Changed
10
+ - `@arnilo/prism-work-tools` idempotency uses claim/CAS lifecycle states; ambiguous connector outcomes are `unknown` and require reconciliation.
11
+ - `@arnilo/prism-model-router` accepts durable async state; `recordUsage`/`recordOutcome` are awaited and `providerSource` cannot bypass a supplied state store.
12
+ - Publishable graph: **47** manifests (was 46); `@arnilo/prism-all` includes enterprise PostgreSQL state.
13
+
14
+ ### Breaking (minor, pre-1.0)
15
+ - Hosts implementing `IdempotencyStore` must migrate from `get`/`put` to `begin`/transition methods.
16
+ - Hosts using durable router state must await router methods with verified identity; synchronous `providerSource` is memory-state only.
17
+
18
+ See [docs/migration.md](docs/migration.md) for the 0.0.22 → 0.0.23 guide.
19
+
20
+
21
+ ## [0.0.22] - 2026-07-31
22
+
23
+ ### Added
24
+ - `@arnilo/prism-caveman` and `@arnilo/prism-ponytail`: optional third-party behavior integrations (Phase 5).
25
+ - Example `examples/caveman-ponytail.ts`.
26
+
27
+ ### Changed
28
+ - Publishable manifest count: **46** (was 44).
29
+
30
+ See [docs/migration.md](docs/migration.md) for the full 0.0.21 → 0.0.22 notes.
31
+
32
+ ## [0.0.21] - 2026-07-31
33
+
34
+ ### Added
35
+ - `@arnilo/prism-coding-agent`: `repo_search` `outputMode`, bounded `glob`, optional `requireReadBeforeWrite`/`ReadPathSet`, bounded `delete`/`move`.
36
+ - Example `examples/coding-tools-capability-gaps.ts`.
37
+
38
+ ### Changed
39
+ - Default coding aggregator: 9 tools (`createCodingTools`); read-only aggregator: 4 (includes `glob`).
40
+ - `@arnilo/prism-coding-security`: approval + sandbox wiring for `delete`/`move`.
41
+
42
+ ### Breaking (minor, pre-1.0)
43
+ - Hosts asserting exact `createCodingTools().length === 6` or readonly length `3` must update (now 9 / 4).
44
+ - Custom sandbox `RepositoryOperations` must implement `glob`; full sandbox custom ops must supply `delete`/`move`.
45
+
46
+ See [docs/migration.md](docs/migration.md) for the full 0.0.20 → 0.0.21 notes.
47
+
48
+
3
49
  ## [0.0.20] - 2026-07-31
4
50
 
5
51
  ### Added
package/dist/index.d.ts CHANGED
@@ -100,5 +100,5 @@ export { createToolParameterValidator, createToolRegistry, dispatchToolCall, fil
100
100
  export type { ResolvedUseCaseModel, ResolveUseCaseModelInput, UseCaseModelBinding, } from "./use-case-model.js";
101
101
  export { resolveUseCaseModel, resolveUseCaseModelBinding, useCaseCredentialProviderId, } from "./use-case-model.js";
102
102
  export declare const name = "prism";
103
- export declare const version = "0.0.20";
103
+ export declare const version = "0.0.23";
104
104
  export declare const description = "Agent harness for AI providers, agents, sessions, and tools.";
package/dist/index.js CHANGED
@@ -54,6 +54,6 @@ export { applyThinkingLevel, isThinkingLevel, normalizeThinkingLevel, THINKING_L
54
54
  export { createToolParameterValidator, createToolRegistry, dispatchToolCall, filterTools } from "./tools.js";
55
55
  export { resolveUseCaseModel, resolveUseCaseModelBinding, useCaseCredentialProviderId, } from "./use-case-model.js";
56
56
  export const name = "prism";
57
- export const version = "0.0.20";
57
+ export const version = "0.0.23";
58
58
  export const description = "Agent harness for AI providers, agents, sessions, and tools.";
59
59
  //# sourceMappingURL=index.js.map
@@ -1,12 +1,12 @@
1
1
  # 0.1.0 / 1.0 Readiness Gates
2
2
 
3
- Status: **0.0.20** is the current release line (Phase 3 skills progressive disclosure); **1.0** readiness remains operator-gated, not automatic.
3
+ Status: **0.0.23** is the current release line (Phase 6 production enterprise PostgreSQL state adapters); **1.0** readiness remains operator-gated, not automatic.
4
4
 
5
5
  This page distills runnable readiness gates into one command-per-gate table. The
6
6
  **Last evidence** column records the 2026-07-26 **0.0.16** baseline snapshot
7
7
  (Phase 11, Node v24.18.0, Linux x86_64). Treat it as historical floor evidence,
8
8
  not the current release tag. Re-run each gate on the target release tree before
9
- cutting 0.0.19 / 1.0. The decision to cut 1.0 stays with the operator after
9
+ cutting 0.0.23 / 1.0. The decision to cut 1.0 stays with the operator after
10
10
  operator-gated legs run in a protected environment and Phase 12 demand evidence
11
11
  exists.
12
12
 
@@ -14,14 +14,15 @@ Evidence trail: [`docs/review-coverage-2026-07-26-phase-11.md`](./review-coverag
14
14
  (addenda 0–9), [`docs/release-and-install.md`](./release-and-install.md),
15
15
  [`docs/migration.md`](./migration.md), [`docs/performance.md`](./performance.md).
16
16
 
17
- ## Current line (0.0.20)
17
+ ## Current line (0.0.23)
18
18
 
19
19
  | Item | Status |
20
20
  |---|---|
21
- | Published graph | **44** publishable manifests at **0.0.20** (`docs/release-and-install.md`) |
22
- | Phase 3 progressive disclosure | `skillsDisclosure`, `load_skill`, empty registry default + `activateAllSkills`, priority budget demotion, optional `toolResultFold` |
23
- | Docs tripwires | `node --test dist/__tests__/docs.test.js` — migration section `0.0.19 → 0.0.20 skills and context progressive disclosure` documents intentional breaks |
24
- | Readiness table below | **0.0.16 measured values** — refresh evidence columns when 1.0 RC gates are recorded |
21
+ | Published graph | **47** publishable manifests at **0.0.23** (`docs/release-and-install.md`) |
22
+ | Phase 6 enterprise state | `@arnilo/prism-enterprise-postgres`: policy/evaluation/work-idempotency/router state, checksummed migration, explicit cleanup |
23
+ | Docs tripwires | `node --test dist/__tests__/docs.test.js` — migration section `0.0.22 → 0.0.23 production enterprise state adapters` documents idempotency and async-router changes |
24
+ | Protected database evidence | `PRISM_TEST_POSTGRES_URL="$DATABASE_URL" npm run test:postgres`; benchmark evidence fixes 10 scenarios, 14 index plans, and 50/100 ms p95 ceilings |
25
+ | Readiness table below | **0.0.16 measured values** remain historical network-free baseline; 0.0.23 database evidence is recorded separately |
25
26
 
26
27
  ## Gate table
27
28
 
@@ -39,7 +40,7 @@ Evidence trail: [`docs/review-coverage-2026-07-26-phase-11.md`](./review-coverag
39
40
  | Whitespace hygiene | `git diff --check` | clean | CI |
40
41
  | Publish order + tarball validation | `node scripts/release.mjs publish --version <v> --dry-run --allow-dirty --allow-untagged` | 44/44 packages `dry-run`, deterministic dependency order, no failures | Operator (dry-run), CI |
41
42
  | Node 20 compatibility | CI `node20-compat` (build + public-import smoke) | all 21 root exports import cleanly on Node 20.20.2 | CI |
42
- | PostgreSQL suite | `npm run test:postgres` | **operator-gated** (requires live PostgreSQL) | Operator |
43
+ | PostgreSQL suite | `PRISM_TEST_POSTGRES_URL="$DATABASE_URL" npm run test:postgres` | 57 checks including enterprise migration/restart/contention/cleanup; operator-gated | Operator |
43
44
  | Keychain / live-provider suites | `npm run test:live` (protected) | **operator-gated** (requires credentials) | Operator |
44
45
  | SAST | GitHub CodeQL | **operator-gated** (runs in CI workflow) | CI |
45
46
  | Signed, provenance publication | `npm run release:publish` (clean tagged tree, OIDC) | **operator-gated** (see "Remaining for 1.0") | Operator |
@@ -79,7 +80,7 @@ Deterministic budgets (CI gate, `scripts/budget-gate.test.mjs`):
79
80
  | Root unpacked bytes | 2,043,402 | +5% | 2.1 MB (within) |
80
81
  | Root file count | 270 | +5% | 270 |
81
82
  | Cold-startup import | 38 ms | ceiling 250 ms | ~38 ms |
82
- | Aggregate packed (44 manifests, reference only) | 1,217,694 | +10% | not gated in fast test |
83
+ | Aggregate packed (47 manifests, reference only) | 1,217,694 | +10% | remeasure for the 0.0.23 graph before release |
83
84
 
84
85
  Benchmark medians (on-demand evidence, `scripts/benchmark-0.0.16.mjs`, ±25%):
85
86
 
@@ -0,0 +1,129 @@
1
+ # Caveman behavior integration
2
+
3
+ ## What it does
4
+
5
+ `@arnilo/prism-caveman` is an optional package that wires [juliusbrussee/caveman](https://github.com/juliusbrussee/caveman) into Prism contribution contracts.
6
+
7
+ It registers upstream skills and commands, injects active level prompt slices via `InstructionInjector`, and persists level as session custom `caveman-level` entries. Import and extension `setup` without a resolvable upstream path fail closed with a bounded redacted error and register zero contributions.
8
+
9
+ Upstream prompt fragments, skill bodies, and rules load from the host-supplied upstream checkout — Prism does not reimplement or vendor Caveman content.
10
+
11
+ ## When to use it
12
+
13
+ Use it when a host wants terse token-efficient communication modes (`lite`, `full`, `ultra`, wenyan variants, `micro`) with upstream Caveman skills (`caveman-commit`, `caveman-review`, `caveman-stats`, `caveman-compress`, `caveman-help`, `cavecrew`) in a Prism extension kernel.
14
+
15
+ Skip it when you do not have a local Caveman checkout (Caveman is not published on npm) or when you only need progressive skill catalog without mode injection.
16
+
17
+ Pair with Phase 3 progressive disclosure: register `createLoadSkillTool` and keep `skillsDisclosure: "progressive"` so full `SKILL.md` bodies stay catalog-only; mode slices come from the `caveman-mode` injector, not eager skill bodies.
18
+
19
+ ## Inputs / request
20
+
21
+ `createCavemanExtension(options)`:
22
+
23
+ | Field | Type | Required | Purpose |
24
+ | --- | --- | --- | --- |
25
+ | `upstreamPath` | `string` | yes | Absolute path to a Caveman checkout containing `skills/`. |
26
+ | `defaultLevel` | `CavemanLevel` | no | Initial level when no session entry exists (default upstream: `full`). |
27
+ | `showStatus` | `boolean` | no | Emit `caveman:status` extension events on level changes. |
28
+ | `appendEntry` | `(entry, opts?) => Promise<void>` | yes | Host session append (OM `attach` pattern). |
29
+ | `getEntries` | `() => readonly SessionEntry[] \| Promise<...>` | yes | Current branch entries for level restore. |
30
+ | `configPath` | `string` | no | Bounded local config file for `defaultLevel` / `showStatus`. |
31
+
32
+ `CavemanLevel`: `off` \| `lite` \| `full` \| `ultra` \| `wenyan-lite` \| `wenyan` \| `wenyan-ultra` \| `micro`.
33
+
34
+ Session custom entry shape:
35
+
36
+ ```json
37
+ { "kind": "custom", "data": { "type": "caveman-level", "level": "full" } }
38
+ ```
39
+
40
+ Registered skills: `caveman`, `caveman-commit`, `caveman-review`, `caveman-stats`, `caveman-compress`, `caveman-help`, `cavecrew`.
41
+
42
+ Registered commands: `caveman`, `caveman-init`, `caveman-commit`, `caveman-review`, `caveman-stats`, `caveman-compress`.
43
+
44
+ ## Outputs / response / events
45
+
46
+ | Export | Purpose |
47
+ | --- | --- |
48
+ | `createCavemanExtension(options)` | Returns an inert `Extension` until `kernel.load([...])`. |
49
+ | `caveman-mode` injector | `InstructionInjector` — upstream filtered `skills/caveman/SKILL.md` slice when level ≠ `off`. |
50
+ | `caveman` command | Set level (`/caveman lite\|full\|ultra\|wenyan\|micro\|off`) or toggle `off`↔`full`. |
51
+ | Alias commands | Dispatch `{ skill, dispatch: "load_skill" }` metadata for companion skills. |
52
+ | `caveman:status` event | Optional metadata when `showStatus: true`. |
53
+
54
+ Deactivation phrases `stop caveman` and `normal mode` clear active injection without erasing session history.
55
+
56
+ ## Request/response example
57
+
58
+ ```json
59
+ { "command": "caveman", "args": { "level": "ultra" }, "sessionId": "s1" }
60
+ ```
61
+
62
+ ```json
63
+ { "kind": "custom", "data": { "type": "caveman-level", "level": "ultra" } }
64
+ ```
65
+
66
+ ## Implementation example
67
+
68
+ ```ts
69
+ import { createCavemanExtension } from "@arnilo/prism-caveman";
70
+ import {
71
+ createExtensionKernel,
72
+ createLoadSkillTool,
73
+ createLoadedSkillSet,
74
+ createMemorySessionStore,
75
+ createSkillRegistry,
76
+ createSessionEntry,
77
+ } from "@arnilo/prism";
78
+
79
+ const store = createMemorySessionStore();
80
+ const callbacks = {
81
+ appendEntry: async (entry, options) => store.append(entry, options),
82
+ getEntries: async () => store.list("s1"),
83
+ };
84
+
85
+ const kernel = createExtensionKernel({ errorPolicy: "throw" });
86
+ await kernel.load([
87
+ createCavemanExtension({
88
+ upstreamPath: "/path/to/juliusbrussee-caveman",
89
+ defaultLevel: "full",
90
+ ...callbacks,
91
+ }),
92
+ ]);
93
+
94
+ const registry = createSkillRegistry(kernel.registries.skills.list());
95
+ const loaded = createLoadedSkillSet();
96
+ const loadSkill = createLoadSkillTool({ registry, loaded });
97
+
98
+ await kernel.registries.commands.get("caveman")!.execute({ level: "lite" }, { sessionId: "s1" });
99
+ // Select instructionInjectors: ["caveman-mode"] on runs that should receive level slices.
100
+ ```
101
+
102
+ See `examples/caveman-ponytail.ts` for progressive catalog + `load_skill` wiring with fixture upstream trees (network-free).
103
+
104
+ ## Extension and configuration notes
105
+
106
+ - Import alone registers nothing and starts no timers, watchers, or network I/O (`sideEffects: false`).
107
+ - `kernel.load` calls `setup`, which resolves upstream first; failure throws before any `register*`.
108
+ - Level restore scans `getEntries()` for the latest `data.type === "caveman-level"` — same OM attach pattern; core does not auto-emit `session_start`.
109
+ - Host must register `createLoadSkillTool` and pass `skillsDisclosure: "progressive"` for catalog-only skill bodies.
110
+ - `caveman-stats` dispatches skill metadata only; full stats need host session-log integration.
111
+ - `caveman-init` returns upstream guidance text; it does not write files in the host repo.
112
+ - No TUI status bar; optional `caveman:status` events for host UI.
113
+
114
+ ## Security and performance notes
115
+
116
+ - Upstream `SKILL.md` and injected text are untrusted host-supplied content; reads are size-bounded (`MAX_SKILL_FILE_BYTES` 256 KiB, `MAX_INJECTED_INSTRUCTION_BYTES` 32 KiB).
117
+ - Config read/write is bounded (`MAX_CONFIG_FILE_BYTES` 16 KiB) at host-owned `configPath` only.
118
+ - Errors redact home directories and absolute paths.
119
+ - Setup is O(skills) directory scan; mode read/write is O(1) per change; injection is O(1) upstream lookup per turn.
120
+ - Session custom entries respect host session ownership and redaction policies.
121
+
122
+ ## Related APIs
123
+
124
+ - [Ponytail behavior integration](ponytail.md): complementary lazy-minimalism mode package.
125
+ - [Extension kernel and event bus](extensions.md): `kernel.load` and contribution registration.
126
+ - [Context and skills](context-and-skills.md): progressive disclosure + `createLoadSkillTool`.
127
+ - [Instruction injection](instruction-injection.md): `caveman-mode` injector selection.
128
+ - [Observational memory compaction package](compaction-observational-memory.md): `appendEntry` / `getEntries` attach precedent.
129
+ - [Migration guide](migration.md): `0.0.21 → 0.0.22` install and opt-in notes.
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## What it does
4
4
 
5
- `@arnilo/prism-coding-agent` is an optional first-party package that provides host shell/filesystem/repository tools as Prism `ToolDefinition` objects. It ships six default coding tools — `shell`, `read`, `write`, `edit`, `repo_list`, `repo_search` — plus opt-in structured Git/check set (`createGitTools`), opt-in `createAskUserDecisionTool({ ask })`, and bounded coding-plan/checkpoint helpers. The tools are **inert** until a host imports them and registers them into a `ToolRegistry`. Hosts may register any subset, omit aggregators entirely, or mix first-party tools with host-owned `ToolDefinition`s. Behavior for shell/read/write/edit is a behavioral port of the pi coding agent's tools, adapted to Prism's `ToolDefinition` / `ToolResult` contracts (no `@earendil-works/*` or `typebox` dependencies; only `diff` plus the Node standard library). List/search/Git are native Prism tools with no glob/ripgrep/Git-library dependency.
5
+ `@arnilo/prism-coding-agent` is an optional first-party package that provides host shell/filesystem/repository tools as Prism `ToolDefinition` objects. It ships nine default coding tools — `shell`, `read`, `write`, `edit`, `repo_list`, `repo_search`, `glob`, `delete`, `move` — plus opt-in structured Git/check set (`createGitTools`), opt-in `createAskUserDecisionTool({ ask })`, and bounded coding-plan/checkpoint helpers. The tools are **inert** until a host imports them and registers them into a `ToolRegistry`. Hosts may register any subset, omit aggregators entirely, or mix first-party tools with host-owned `ToolDefinition`s. Behavior for shell/read/write/edit is a behavioral port of the pi coding agent's tools, adapted to Prism's `ToolDefinition` / `ToolResult` contracts (no `@earendil-works/*` or `typebox` dependencies; only `diff` plus the Node standard library). List/search/glob/Git are native Prism tools with no picomatch/ripgrep/Git-library dependency (hand-rolled `*`/`?`/`**` glob matcher).
6
6
 
7
7
  | Export | Purpose |
8
8
  | --- | --- |
@@ -11,14 +11,18 @@
11
11
  | `createWriteTool(cwd, options?)` | `write` tool: create or overwrite a file, creating parent directories. |
12
12
  | `createEditTool(cwd, options?)` | `edit` tool: precise exact-then-fuzzy text replacement in an existing file. |
13
13
  | `createRepoListTool(cwd, options?)` | `repo_list` tool: bounded deterministic repository listing. |
14
- | `createRepoSearchTool(cwd, options?)` | `repo_search` tool: bounded literal text search. |
15
- | `createCodingTools(cwd, options?)` | Default six tools (`shell`, `read`, `write`, `edit`, `repo_list`, `repo_search`). |
16
- | `createReadOnlyTools(cwd, options?)` | Read-only subset: `read`, `repo_list`, `repo_search`. |
14
+ | `createRepoSearchTool(cwd, options?)` | `repo_search` tool: bounded literal text search (`outputMode`: content / files_with_matches / count). |
15
+ | `createGlobTool(cwd, options?)` | `glob` tool: bounded filename-pattern match (`*` / `?` / `**`; no brace expansion). |
16
+ | `createDeleteTool(cwd, options?)` | `delete` tool: high-risk delete of a file or empty directory (no recursive delete, no trash). |
17
+ | `createMoveTool(cwd, options?)` | `move` tool: high-risk rename/move within the workspace (`overwrite` default false). |
18
+ | `createReadPathSet()` | Session-scoped path set for optional `requireReadBeforeWrite` soft guard. |
19
+ | `createCodingTools(cwd, options?)` | Default nine tools (`shell`, `read`, `write`, `edit`, `repo_list`, `repo_search`, `glob`, `delete`, `move`). |
20
+ | `createReadOnlyTools(cwd, options?)` | Read-only subset: `read`, `repo_list`, `repo_search`, `glob`. |
17
21
  | `createAllTools(cwd, options?)` | Identical to `createCodingTools` (Git tools remain opt-in via `createGitTools`). |
18
22
  | `createGitTools(cwd, options?)` | Opt-in Git tools (`git_status`/`git_diff`/`git_branch`/`git_worktree`/`git_apply`/`git_commit`/`git_pr_handoff`) plus optional `coding_check`. |
19
23
  | `createCodingCheckTool(cwd, options)` | Named host-declared checks; model selects only a name. |
20
24
  | `createAskUserDecisionTool(options)` | Opt-in user decision tool (`ask_user_decision`); host supplies `ask` callback. Not in default aggregators. |
21
- | `createLocalRepositoryOperations(limits?)` | Default streaming Node filesystem backend for list/search. |
25
+ | `createLocalRepositoryOperations(limits?)` | Default streaming Node filesystem backend for list/search/glob. |
22
26
  | `createGitOperations(options)` | Typed Git operations backend (argument arrays, safe config, finite output). |
23
27
  | `buildCodingCheckpointMetadata` / `validateCodingCheckpointMetadata` / `assertCodingResumeAllowed` | Bounded durable coding-task metadata for workflow `state.coding` (no second runtime). |
24
28
  | `writeCodingPlanFile` / `readCodingPlanFile` / `createCodingPlanMarkdown` / `parseCodingPlanTodos` | Workspace plan/todo Markdown helpers with finite byte/todo caps and hash verification. |
@@ -66,13 +70,39 @@ const tools = createCodingTools(workspaceRoot, {
66
70
  | `read` | `read` |
67
71
  | `write` | `write` |
68
72
  | `edit` | `edit` |
69
- | `repo_list` / `repo_search` | _(native; no pi equivalent)_ |
73
+ | `repo_list` / `repo_search` / `glob` | _(native; no pi equivalent)_ |
74
+ | `delete` / `move` | _(native; no pi equivalent)_ |
75
+
76
+ ### Tool selection guide
77
+
78
+ | Need | Prefer | Avoid |
79
+ | --- | --- | --- |
80
+ | Enumerate directories | `repo_list` | `shell` `find`/`ls` |
81
+ | Match filename patterns | `glob` | `shell` `find` |
82
+ | Find text in files | `repo_search` | `shell` `grep`/`rg` |
83
+ | Read one file (paged) | `read` | `shell` `cat` |
84
+ | Create / full overwrite | `write` | — |
85
+ | Targeted replace | `edit` | full `write` rewrite when a small edit works |
86
+ | Remove file / empty dir | `delete` | `shell` `rm` |
87
+ | Rename / relocate | `move` | `shell` `mv` |
88
+ | Arbitrary process | `shell` | dedicated tools above |
89
+
90
+ ### Phase 4 non-goals (0.0.21)
91
+
92
+ These are **out of scope** for this package release (see roadmap Phase 9 / later):
93
+
94
+ - **No PDF / document reader** — text and supported images only via `read`.
95
+ - **No trash / recycle daemon** — `delete` / `move` are permanent; host undo is not automatic.
96
+ - **No PTY / interactive process control** — `shell` is one-shot exec with bounded capture.
97
+ - **No LSP / language-server tools** — use host-owned tools if needed.
98
+ - **No recursive directory delete** — `delete` refuses non-empty directories.
99
+ - **No brace-expansion globs** — `glob` supports only `*`, `?`, and `**`.
70
100
 
71
101
  ## Inputs / request
72
102
 
73
103
  ### `shell`
74
104
 
75
- Run a shell command and return combined stdout+stderr.
105
+ Run a shell command and return combined stdout+stderr. Prefer dedicated coding tools (table above) when they fit.
76
106
 
77
107
  **Inputs:**
78
108
 
@@ -141,7 +171,7 @@ const read = createReadTool(cwd, {
141
171
 
142
172
  ### `write`
143
173
 
144
- Create or overwrite a file, creating parent directories as needed.
174
+ Create or **overwrite** a file (full replace), creating parent directories as needed. Prefer `edit` for targeted changes.
145
175
 
146
176
  **Inputs:**
147
177
 
@@ -149,6 +179,7 @@ Create or overwrite a file, creating parent directories as needed.
149
179
  | --- | --- | --- |
150
180
  | `path` | `string` | Path to the file to write (relative or absolute). Required. |
151
181
  | `content` | `string` | Content to write (empty string creates an empty file). Required. |
182
+ | `force` | `boolean` | Bypass optional read-before-write guard when the host enabled `requireReadBeforeWrite`. |
152
183
 
153
184
  **Outputs:** a `TextContent` confirmation naming the **absolute path** with UTF-8 byte and line counts (e.g. `Successfully wrote 42 bytes (3 lines) to /abs/path.txt`). `maxInputBytes` defaults to 8 MiB (64 MiB hard cap); oversized UTF-8 input fails before policy evaluation, directory creation, or write. Write failures and abort are error results. Empty `content` is valid.
154
185
 
@@ -156,6 +187,19 @@ Default local `writeFile` uses same-directory temp + `rename` so a crash mid-wri
156
187
 
157
188
  `write` result `metadata`: `{ bytes, lines, path }` (absolute path). Concurrent writes to the same path serialize through `withFileMutationQueue`; writes to different paths run in parallel.
158
189
 
190
+ ### Optional read-before-write guard
191
+
192
+ Hosts may opt in to a session-scoped soft guard: share one `createReadPathSet()` across `read` / `write` / `edit` and set `requireReadBeforeWrite: true` on write/edit options. Successful `read` marks the path; unread existing-file writes/edits fail with a clear error unless `force: true`. Default is **off** (no behavior change for hosts that ignore it).
193
+
194
+ ```ts
195
+ import { createReadPathSet, createReadTool, createWriteTool, createEditTool } from "@arnilo/prism-coding-agent";
196
+
197
+ const readPaths = createReadPathSet();
198
+ const read = createReadTool(cwd, { readPathSet: readPaths });
199
+ const write = createWriteTool(cwd, { requireReadBeforeWrite: true, readPathSet: readPaths });
200
+ const edit = createEditTool(cwd, { requireReadBeforeWrite: true, readPathSet: readPaths });
201
+ ```
202
+
159
203
  ### `edit`
160
204
 
161
205
  Precise text replacement in an existing file via exact-then-fuzzy matching.
@@ -166,8 +210,13 @@ Precise text replacement in an existing file via exact-then-fuzzy matching.
166
210
  | --- | --- | --- |
167
211
  | `path` | `string` | Path to the file to edit. Required. |
168
212
  | `edits` | `Array<{ oldText: string, newText: string }>` | Targeted replacements, each matched against the **original** file (not incrementally). No overlapping/nested edits. Required, non-empty. |
213
+ | `force` | `boolean` | Bypass optional read-before-write guard when enabled. |
214
+
215
+ Each `edits[].oldText` must match a unique, non-overlapping region of the original file. Matching is exact first, then fuzzy (unicode normalization / whitespace collapse).
169
216
 
170
- Each `edits[].oldText` must match a unique, non-overlapping region of the original file. Matching is exact first, then fuzzy (unicode normalization / whitespace collapse). **Fuzzy matching can apply a wrong region when `oldText` is slightly off** — tradeoff for imprecise models; prefer exact `oldText` when possible. A BOM is stripped before matching and re-prepended on write; original line endings are restored. Defaults reject targets over 8 MiB, aggregate old/new UTF-8 input over 2 MiB, or more than 100 edits (hard caps: 64 MiB, 16 MiB, and 1,000). Stat and bounded read checks run before matching or mutation. Default local `writeFile` uses same-directory temp + `rename` (crash-safe replace).
217
+ **Fuzzy silent-success tradeoff (loud):** when exact match fails, fuzzy may still apply a replacement **without warning the model**. That can edit the wrong region if `oldText` is slightly off (extra/missing whitespace, unicode lookalikes). Prefer exact `oldText` copied from a fresh `read`. Duplicate / non-unique matches already **fail closed** and leave the file unchanged — ambiguity is not silently resolved by picking the first hit.
218
+
219
+ A BOM is stripped before matching and re-prepended on write; original line endings are restored. Defaults reject targets over 8 MiB, aggregate old/new UTF-8 input over 2 MiB, or more than 100 edits (hard caps: 64 MiB, 16 MiB, and 1,000). Stat and bounded read checks run before matching or mutation. Default local `writeFile` uses same-directory temp + `rename` (crash-safe replace).
171
220
 
172
221
  **Outputs:** a `TextContent` confirmation (`Successfully replaced N block(s) in {path}.`) plus `metadata`. Any failure — missing/unreadable file, no match, duplicate (non-unique) match, overlap, empty `oldText`, no-op edit, or abort — is an error result, and the file is left **unchanged** (the match runs before the write).
173
222
 
@@ -175,7 +224,7 @@ Each `edits[].oldText` must match a unique, non-overlapping region of the origin
175
224
 
176
225
  ### `repo_list`
177
226
 
178
- List repository entries with deterministic relative paths. Uses Node `opendir`/`lstat` only — no glob dependency. Does not follow symlinks; rejects path escapes outside the workspace root. Hidden names and excluded basenames (default `.git`, `node_modules`, `dist`) are skipped unless `includeHidden` is set / host `exclude` is overridden.
227
+ List repository entries with deterministic relative paths. Uses Node `opendir`/`lstat` only — no glob dependency. Prefer `glob` when you already know a filename pattern. Prefer `repo_search` to find text inside files. Does not follow symlinks; rejects path escapes outside the workspace root. Hidden names and excluded basenames (default `.git`, `node_modules`, `dist`) are skipped unless `includeHidden` is set / host `exclude` is overridden.
179
228
 
180
229
  **Inputs:**
181
230
 
@@ -202,10 +251,59 @@ Search text files under the workspace using literal substring match. Binary file
202
251
  | `mode` | `"literal"` | Literal only (default). `regex` removed in 0.0.18. |
203
252
  | `caseSensitive` | `boolean` | Default false. |
204
253
  | `includeHidden` | `boolean` | Default false. |
205
- | `context` | `number` | Context lines before/after each match (default 5, hard 20). |
254
+ | `context` | `number` | Context lines before/after each match (default 5, hard 20). Ignored for non-content `outputMode`. |
206
255
  | `maxMatches` | `number` | Match cap (default 1,000, hard 10,000). |
256
+ | `outputMode` | `"content"` \| `"files_with_matches"` \| `"count"` | Result shape (default `content`). |
257
+
258
+ **Outputs:**
259
+ - `content` (default): ripgrep-like lines `path:line:column:text` with optional `path-` / `path+` context.
260
+ - `files_with_matches`: unique matching paths only.
261
+ - `count`: totals (`N matches in M files`) without line bodies.
262
+
263
+ Metadata includes `matches`, `truncated`, scan/skip counts; non-content modes also expose `fileCount`.
264
+
265
+ ### `glob`
266
+
267
+ Find workspace files by filename pattern without shell `find`. Hand-rolled matcher: `*` (one path segment), `?` (one char), `**` (directories). Brace expansion (`{a,b}`) is **rejected**. Patterns match workspace-relative full paths (e.g. `src/util/a.ts`). Returns **files only** (directories traversed but not listed). Same exclude/hidden/depth/page/time caps as `repo_list`.
207
268
 
208
- **Outputs:** ripgrep-like lines `path:line:column:text` with optional `path-` / `path+` context, plus metadata (`matches`, `truncated`, scan/skip counts).
269
+ **Inputs:**
270
+
271
+ | Field | Type | Purpose |
272
+ | --- | --- | --- |
273
+ | `pattern` | `string` | Glob pattern (required). |
274
+ | `path` | `string` | Workspace-relative start directory (default root). |
275
+ | `includeHidden` | `boolean` | Default false. |
276
+ | `maxDepth` | `number` | Depth cap (default 32, hard 128). |
277
+ | `maxResults` | `number` | Page size (default 1,000, hard 10,000). |
278
+ | `offset` | `number` | Matches to skip (default 0). |
279
+
280
+ **Outputs:** one relative path per line plus metadata (`truncated`, `truncatedBy`, `nextOffset`, scan counts). Continue with `offset=nextOffset` when truncated.
281
+
282
+ ### `delete`
283
+
284
+ High-risk: permanently delete a **single file or empty directory**. Non-empty directories fail closed (no recursive delete). Symlinks are unlinked as links (targets not followed for containment). **No trash daemon** — host undo is not automatic; gate with approval policy.
285
+
286
+ **Inputs:**
287
+
288
+ | Field | Type | Purpose |
289
+ | --- | --- | --- |
290
+ | `path` | `string` | File or empty directory to delete. Required. |
291
+
292
+ **Outputs:** confirmation with absolute path, or error (missing, non-empty dir, escape, abort).
293
+
294
+ ### `move`
295
+
296
+ High-risk: rename or move a file within the workspace. Dual-path mutation queue (lexicographic lock order). `overwrite` defaults **false**; when true, replaces an existing destination **file** only. Does not create parent directories. **No trash** — host undo is not automatic.
297
+
298
+ **Inputs:**
299
+
300
+ | Field | Type | Purpose |
301
+ | --- | --- | --- |
302
+ | `from` | `string` | Source path. Required. |
303
+ | `to` | `string` | Destination path. Required. |
304
+ | `overwrite` | `boolean` | Replace existing destination file (default false). |
305
+
306
+ **Outputs:** confirmation with absolute from/to, or error (missing source, dest exists without overwrite, escape, abort).
209
307
 
210
308
  ### Structured Git tools (`createGitTools`)
211
309
 
@@ -351,10 +449,10 @@ Minimal drop-in for any Prism app:
351
449
  import { createToolRegistry } from "@arnilo/prism";
352
450
  import { createCodingTools, createReadOnlyTools } from "@arnilo/prism-coding-agent";
353
451
 
354
- // Full coding set (shell + read + write + edit + repo_list + repo_search) against the project root:
452
+ // Full coding set (shell + read + write + edit + repo_list + repo_search + glob + delete + move):
355
453
  const tools = createToolRegistry(createCodingTools(process.cwd()));
356
454
 
357
- // Or a read-only set for inspection-only agents (read + repo_list + repo_search):
455
+ // Or a read-only set for inspection-only agents (read + repo_list + repo_search + glob):
358
456
  const ro = createToolRegistry(createReadOnlyTools(process.cwd()));
359
457
  ```
360
458
 
@@ -381,23 +479,27 @@ const remoteWrite = createWriteTool("/repo", {
381
479
  });
382
480
  ```
383
481
 
482
+ Packed capability demo: `examples/coding-tools-capability-gaps.ts` (search modes, glob, read-before-write, delete/move).
483
+
384
484
  ## Extension and configuration notes
385
485
 
386
486
  - **Long coding sessions.** Use `createCodingCompactionStrategy()` from optional `@arnilo/prism-compaction-llm` when history needs a bounded coding handoff. It is selected explicitly through normal `session.compact()` / agent compaction configuration, preserves raw session entries, and prioritizes file paths, patch intent, checks, plan/todo state, blockers, and verification steps. It does not read files, retain full diffs, or create a second coding runtime.
387
- - **Pluggable operation backends.** Every tool accepts an `operations` seam. Custom `ReadOperations` must implement bounded `readText` plus `statFile`; custom `EditOperations` must implement `statFile`; read/write methods receive caps/signals. `BashOperations` must stream through `onData` and honor `signal`/`timeout`. Custom `RepositoryOperations` must honor depth/entry/file/match/scan/time caps and abort. A hostile custom backend can still violate its host-owned contract, so isolate it separately.
388
- - **Per-tool options.** `ShellToolOptions` adds `timeout` and `maxTotalOutputBytes`; `ReadToolOptions` adds `maxScanBytes`; `WriteToolOptions` adds `maxInputBytes`; `EditToolOptions` adds `maxFileBytes`, `maxInputBytes`, and `maxEdits`; list/search accept `repository` limits and shared aggregator `ToolsOptions.repository`.
389
- - **Aggregator options.** `ToolsOptions` (`{ executionPolicy?, shell?, read?, write?, edit?, list?, search?, repository? }`) threads each sub-object to the matching tool. `createCodingTools()`, `createAllTools()`, and `createReadOnlyTools()` apply the shared policy unless that tool has an explicit per-tool override. Read-only membership is deliberately `read` + `repo_list` + `repo_search` (0.0.9 behavior change).
390
- - **Sandbox composition.** Prefer `@arnilo/prism-coding-security` `createSandboxCodingComposition(cwd, { workspaceMode, sandbox, ... })` (or tools-only wrappers). `workspaceMode` is required: `"sandbox"` keeps shell/read/write/edit/list/search on one disposable tree; `"host"` runs against host cwd and never claims containment. Mixed sandbox-shell + host-FS wiring throws unless `allowMixedWorkspaceWiring: true`. Same-tree Git: `createGitTools(composition.workspaceRoot, { execFile: sandbox.execFile, commitIdentity })`.
487
+ - **Pluggable operation backends.** Every tool accepts an `operations` seam. Custom `ReadOperations` must implement bounded `readText` plus `statFile`; custom `EditOperations` must implement `statFile`; read/write methods receive caps/signals. `BashOperations` must stream through `onData` and honor `signal`/`timeout`. Custom `RepositoryOperations` must honor depth/entry/file/match/scan/time caps and abort (including `glob`). Custom `DeleteOperations` / `MoveOperations` must honor containment and abort. A hostile custom backend can still violate its host-owned contract, so isolate it separately.
488
+ - **Per-tool options.** `ShellToolOptions` adds `timeout` and `maxTotalOutputBytes`; `ReadToolOptions` adds `maxScanBytes` and optional `readPathSet`; `WriteToolOptions` / `EditToolOptions` add input caps plus optional `requireReadBeforeWrite` / `readPathSet` / `force`; list/search/glob accept `repository` limits and shared aggregator `ToolsOptions.repository`.
489
+ - **Aggregator options.** `ToolsOptions` (`{ executionPolicy?, shell?, read?, write?, edit?, delete?, move?, list?, search?, glob?, repository? }`) threads each sub-object to the matching tool. `createCodingTools()`, `createAllTools()`, and `createReadOnlyTools()` apply the shared policy unless that tool has an explicit per-tool override. Full membership is nine tools; read-only is `read` + `repo_list` + `repo_search` + `glob`.
490
+ - **Sandbox composition.** Prefer `@arnilo/prism-coding-security` `createSandboxCodingComposition(cwd, { workspaceMode, sandbox, ... })` (or tools-only wrappers). `workspaceMode` is required: `"sandbox"` keeps shell/read/write/edit/list/search/glob/delete/move on one disposable tree; `"host"` runs against host cwd and never claims containment. Mixed sandbox-shell + host-FS wiring throws unless `allowMixedWorkspaceWiring: true`. Same-tree Git: `createGitTools(composition.workspaceRoot, { execFile: sandbox.execFile, commitIdentity })`.
391
491
  - **`ToolsOptions`** and the per-tool option types are exported from the package barrel for host configuration.
392
492
  - No auto-discovery or manifest registration: import and register explicitly. This package registers no extensions and owns no globals (the mutation queue is a process-wide per-path map — see `ponytail:` note in the source).
393
493
 
394
494
  ## Security and performance notes
395
495
 
396
- - **Host shell/filesystem access.** These tools run real commands and read/write/list/search real files. They provide **no sandbox**. Gate them with Prism `PermissionPolicy` / `ToolValidator` / trust policies before registering them for any provider turn. Shared `executionPolicy` applies to both full and read-only aggregators before filesystem/process side effects. See [Host security guide](host-security.md) and [Security/auth/trust](settings-auth-trust-security.md).
496
+ - **Host shell/filesystem access.** These tools run real commands and read/write/list/search/glob/delete/move real files. They provide **no sandbox**. Gate them with Prism `PermissionPolicy` / `ToolValidator` / trust policies before registering them for any provider turn. Shared `executionPolicy` applies to both full and read-only aggregators before filesystem/process side effects. See [Host security guide](host-security.md) and [Security/auth/trust](settings-auth-trust-security.md).
497
+ - **High-risk mutations.** `delete` and `move` are permanent (no trash). Prefer host confirmation via `ExecutionPolicy` before allowing them. Do not instruct models to bypass policy/sandbox.
397
498
  - **Non-zero exit is not an error.** A failing command is a normal `shell` result (exit code in metadata); only timeout/abort/spawn failures are error results. Do not assume `error == undefined` means the command succeeded.
398
- - **Bounded I/O.** `read` streams one page and bounds scan bytes; image/edit reads use stat plus a shared cap-enforcing reader; write/edit inputs are measured before mutation. `repo_list`/`repo_search` stream walks and charge depth/entry/file/match/scan/time before retention. Structured Git tools use argument arrays with finite output/path/ref/message/patch caps, disable hooks/credential prompts/external diff by default, and never push or open PRs. `shell` retains only a rolling display tail and synchronously spills accepted raw chunks so stream backpressure cannot grow heap; wall time and total raw output remain finite.
399
- - **Per-path serialization.** Concurrent mutations to the same file serialize; concurrent mutations to different files do not block each other. The queue is a process-wide map — across sessions in one process, same-path writes still serialize (upgrade path: scope per registry if throughput matters).
499
+ - **Bounded I/O.** `read` streams one page and bounds scan bytes; image/edit reads use stat plus a shared cap-enforcing reader; write/edit inputs are measured before mutation. `repo_list`/`repo_search`/`glob` stream walks and charge depth/entry/file/match/scan/time before retention. Structured Git tools use argument arrays with finite output/path/ref/message/patch caps, disable hooks/credential prompts/external diff by default, and never push or open PRs. `shell` retains only a rolling display tail and synchronously spills accepted raw chunks so stream backpressure cannot grow heap; wall time and total raw output remain finite.
500
+ - **Per-path serialization.** Concurrent mutations to the same file serialize; concurrent mutations to different files do not block each other. `move` locks both paths in lexicographic order. The queue is a process-wide map — across sessions in one process, same-path writes still serialize (upgrade path: scope per registry if throughput matters).
400
501
  - **Bounded image reads.** `read` rejects images over `maxImageBytes` (default 10 MB) by `stat` before read when possible; MIME is detected from magic bytes only. Optional `transformImage` is host-owned — the base package has no image-processing dependency.
502
+ - **Fuzzy edit risk.** Silent fuzzy success can mis-apply edits; duplicate matches fail closed. See the `edit` section above.
401
503
 
402
504
  ### Resource-limit defaults and hard caps
403
505
 
@@ -26,14 +26,14 @@ import type { ExecutionAction, ExecutionPolicy, ExecutionDecision } from "@arnil
26
26
 
27
27
  Use this package when coding tools need path scoping, human approval, command rules, or a pluggable sandbox backend. Wire the returned policy through `createCodingTools(cwd, { executionPolicy })` or per-tool `executionPolicy` options.
28
28
 
29
- Use `createDockerSandbox()` when the host wants a production-reference containment boundary. Prism does **not** claim OS-level isolation unless the host constructs this adapter (or supplies an equivalent custom `DisposableSandbox`). Default policy denies shell/write/edit without an `approve` callback and rejects paths outside configured roots. Coding shell definitions are marked `exclusive: true`, matching the approval policy's shell decision, so a single-shot turn containing shell work runs sequentially even when `toolConcurrency > 1`. Non-shell turns retain configured parallelism.
29
+ Use `createDockerSandbox()` when the host wants a production-reference containment boundary. Prism does **not** claim OS-level isolation unless the host constructs this adapter (or supplies an equivalent custom `DisposableSandbox`). Default policy denies shell/write/edit/delete/move without an `approve` callback and rejects paths outside configured roots. Coding shell definitions are marked `exclusive: true`, matching the approval policy's shell decision, so a single-shot turn containing shell work runs sequentially even when `toolConcurrency > 1`. Non-shell turns retain configured parallelism.
30
30
 
31
31
  ## Inputs / request
32
32
 
33
33
  | Option | Default | Purpose |
34
34
  | --- | --- | --- |
35
35
  | `roots` | required | Realpath-contained filesystem roots. |
36
- | `readOnly` | `false` | Deny shell/write/edit actions. |
36
+ | `readOnly` | `false` | Deny non-`read` actions (including shell/write/edit/delete/move). |
37
37
  | `commandRules` | `[]` | Ordered allow/deny/approval command classification. |
38
38
  | `approve` | none | Host callback for actions not statically allowed; omission fails closed. |
39
39
  | `approvalCacheScope` | `"none"` | Optional `run` or `session` decision cache scope. |
@@ -110,7 +110,7 @@ const sandbox = await createDockerSandbox({
110
110
  limits: { cpus: 2, memoryBytes: 2 * 1024 ** 3, maxPids: 256, workspaceBytes: 1024 ** 3 },
111
111
  });
112
112
 
113
- // Sandbox mode: shell/read/write/edit/list/search share one disposable tree.
113
+ // Sandbox mode: shell/read/write/edit/list/search/glob/delete/move share one disposable tree.
114
114
  const { tools, composition } = createSandboxCodingComposition("/srv/jobs/task-1/source", {
115
115
  workspaceMode: "sandbox",
116
116
  sandbox,
@@ -196,6 +196,17 @@ await agent.createSession().run("…", { activeSkills: ["ponytail"] });
196
196
  // Turn 1: catalog only. After load_skill({ name: "ponytail" }), later turns include instructions.
197
197
  ```
198
198
 
199
+ ### Third-party behavior packages (Caveman, Ponytail)
200
+
201
+ `@arnilo/prism-caveman` and `@arnilo/prism-ponytail` register upstream skills into the extension kernel skill registry. Hosts should:
202
+
203
+ 1. `kernel.load([createCavemanExtension(...), createPonytailExtension(...)])` with session `appendEntry` / `getEntries` callbacks.
204
+ 2. Build `createSkillRegistry(kernel.registries.skills.list())` and pass `activeSkills` / `resolveActiveSkills` names.
205
+ 3. Keep `skillsDisclosure: "progressive"` and register `createLoadSkillTool` — full `SKILL.md` bodies stay catalog-only until `load_skill`.
206
+ 4. Select `instructionInjectors: ["caveman-mode", "ponytail-mode"]` (or subset) for mode/level slices **without** forcing `skillsDisclosure: "eager"`.
207
+
208
+ Mode slices and skill bodies are independent: the injector can add `PONYTAIL MODE ACTIVE` while `ponytail-audit` remains catalog-only until loaded. See [Caveman](caveman.md), [Ponytail](ponytail.md), and `examples/caveman-ponytail.ts`.
209
+
199
210
  Pure validation without the tool: `resolveSkillLoad({ registry, name, tools, loaded, activeSkillNames })`.
200
211
 
201
212
  ### Context budget priority and skill demotion
@@ -8,6 +8,8 @@ Prism itself does not ship a production database adapter. The built-in `SessionS
8
8
 
9
9
  Plan 056 Task 1 adds dialect-neutral shared primitives under `@arnilo/prism/testing/persistence-schema`, `@arnilo/prism/testing/session-store-conformance`, and `@arnilo/prism/testing/run-ledger-conformance`. Task 2 ships `@arnilo/prism-session-store-sqlite` (see [SQLite persistence](sqlite-persistence.md)); Task 3 ships `@arnilo/prism-session-store-postgres` (see [PostgreSQL persistence](postgres-persistence.md)). Both implement dialect-local SQL against the shared model; Prism core still ships no ORM, driver, or migration runner.
10
10
 
11
+ Release 0.0.23 additionally ships [`@arnilo/prism-enterprise-postgres`](enterprise-postgres-state.md), a separate PostgreSQL composition for policy decisions, evaluations, work-mutation claims, and model-router state. It is not a `ProductionPersistenceStore` replacement and does not store sessions/runs. Its fixed `prism_enterprise_migrations` history is independent of `prism_migrations`; hosts may use both compositions against the same validated schema.
12
+
11
13
  ## When to use it
12
14
 
13
15
  Use these contracts when you write a database-backed `SessionStore` or a separate persistence adapter that needs:
@@ -461,6 +463,7 @@ const dbStore: ProductionPersistenceStore = {
461
463
  ## Related APIs
462
464
 
463
465
  - [Session store conformance](session-store-conformance.md): executable adapter baseline for append/idempotency/conflict/branch invariants.
466
+ - [Enterprise PostgreSQL state](enterprise-postgres-state.md): durable governance and connector/router state outside the session/run contract.
464
467
  - [Migration guide](migration.md): before/after shapes for moving from in-memory/JSONL to this contract.
465
468
  - [Performance limits](performance.md): production sizing, subscriber queues, branch-read limits, and database adapter guidance.
466
469
  - [Session stores and branching](session-stores-and-branching.md): `SessionStore`, `SessionEntry`, branch helpers, and runtime branch semantics.