docks-kit 0.14.0 → 0.14.2

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/AGENTS.md CHANGED
@@ -12,11 +12,8 @@ Tool-specific instructions live alongside this file:
12
12
 
13
13
 
14
14
  docks-kit runtime and standalone binary support is exactly Linux x64/arm64 and
15
- macOS x64/arm64. Session Relay prebuilt support is Linux x64/arm64 and macOS
16
- arm64 only: x86_64-apple-darwin is no longer published as of Session Relay
17
- 0.16.0; macOS support is aarch64-apple-darwin. Unsupported hosts fail before
18
- the launcher can fall back to Bun source, and Intel macOS hosts fail closed at
19
- the Session Relay install boundary.
15
+ macOS x64/arm64. Unsupported hosts fail before the launcher can fall back to
16
+ Bun source.
20
17
 
21
18
  ## Repository layout (cross-cutting)
22
19
 
@@ -40,11 +37,10 @@ the Session Relay install boundary.
40
37
 
41
38
  Codex SoT notes:
42
39
  - `SoT/.codex/AGENTS.md` deploys to `~/.codex/AGENTS.md` as global Codex instructions.
43
- - `SoT/.codex/config.toml` pins Codex to `model = "gpt-5.6-sol"`, sets normal and plan reasoning to `high` with concise summaries, and sets `model_verbosity = "low"`, `personality`, live top-level `web_search`, workspace-write sandboxing with sandboxed command network access, cross-session `memories` (+ dedicated note tools), `[agents]` subagent limits (`max_threads = 12`, `max_depth = 2` — intentionally above Codex defaults for broad parallel kit work; deeper recursion increases cost and predictability risk), a 128 KiB `project_doc_max_bytes` budget for the repo-side AGENTS.md chain (the global `~/.codex/AGENTS.md` is uncapped and not counted), and enables the Docks plugins as `docks@docks`, `session-relay@docks`, and `effect-kit@docks`.
40
+ - `SoT/.codex/config.toml` pins Codex to `model = "gpt-5.6-sol"`, sets normal and plan reasoning to `high` with concise summaries, and sets `model_verbosity = "low"`, `personality`, live top-level `web_search`, workspace-write sandboxing with sandboxed command network access, cross-session `memories` (+ dedicated note tools), `[agents]` subagent limits (`max_threads = 12`, `max_depth = 2` — intentionally above Codex defaults for broad parallel kit work; deeper recursion increases cost and predictability risk), a 128 KiB `project_doc_max_bytes` budget for the repo-side AGENTS.md chain (the global `~/.codex/AGENTS.md` is uncapped and not counted), and enables the three Docks plugins `docks@docks`, `plan-lifecycle@docks` (the shared plan lifecycle and an `effect-kit@docks` dependency), and `effect-kit@docks`.
44
41
  - `SoT/.codex/rules/*.rules` deploys to `~/.codex/rules/` as kit-managed Codex command policy. This is Codex's equivalent of permission allow/prompt/block rules; user-learned approvals in `~/.codex/rules/default.rules` are preserved.
45
42
  - `SoT/.codex/plugins/marketplace.json` deploys to Codex's personal marketplace path at `~/.agents/plugins/marketplace.json`; when the `codex` CLI is available, sync reruns `codex plugin add <plugin@marketplace>` for enabled SoT plugins so stale cached installs are refreshed.
46
- - `docks-kit status` verifies Session Relay only through the supported `codex plugin list --json` inventory. `ready` means installed and enabled for a newly started Codex session; it is not evidence about an old process, lifecycle state, receive-path health, or worker quiescence. The global prompt SoTs carry the owner's standing authorization for Docks cross-company plan review, which never overrides host or platform denial.
47
- - Claude and Codex sync call `sessionRelayCli.ts ensureSessionRelayCli` immediately before their plugin passes. It installs the exact source-pinned precompiled command at `~/.local/bin/session-relay` for Linux x64/arm64 and macOS arm64 only, requires committed digest = same-release `SHA256SUMS` row = downloaded bytes, smoke-tests the staged version, and atomically replaces the stable path. `agents`-only sync never enters this boundary. The three pinned production digests correspond to the `session-relay--v0.16.0` assets and its `SHA256SUMS`; x86_64-apple-darwin is no longer published as of Session Relay 0.16.0.
43
+ - The global prompt SoTs carry the owner's standing authorization for Docks cross-company plan review, which never overrides host or platform denial.
48
44
  - The `codex` CLI binary is upstream-owned, not kit-owned. The official standalone installer keeps package metadata under `$CODEX_HOME/packages/standalone` and places the `codex` symlink in `~/.local/bin` by default; sync only warns with a download-then-run installer command when the CLI is missing. Existing installs can self-update with `codex update`; npm and Homebrew remain upstream alternatives.
49
45
  - Neither global prompt SoT imports `@RTK.md`; Claude uses the hook-backed RTK integration, while Codex has no kit-managed RTK integration.
50
46
  - Claude runtime settings are an authoring template with sentinels. `claudeRuntime.ts` materializes absolute Bun/script paths only after the shared `bun.ts` bootstrap is ready; `claudeSync.ts` writes all runtime assets before atomically committing settings, then prunes the legacy shell scripts and Stop hook. Native `rate_limits` is the sole quota source, so jq/curl/OAuth caches are not runtime dependencies. A missing Bun defers only this cutover and preserves legacy pointers/files.
@@ -57,9 +53,9 @@ For per-tool SoT layouts (`SoT/.claude/`, `SoT/.codex/`), see the matching SoT d
57
53
  - **Idempotent operations.** Every EngineNative sync step must be safe to re-run. Settings merges, plugin installs, and marketplace adds are all idempotent — re-running with no SoT changes is a no-op.
58
54
  - **Removed bash engine.** The bash engine was removed after the `bash-engine-final` tag. `DOCKS_KIT_ENGINE=bash` must fail with the removed-engine message; engine bugs are fixed forward in EngineNative.
59
55
  - **Targeted syncs.** `./docks-kit sync` accepts positional targets: `claude`, `codex`, and `agents`. Use the narrowest target that matches the SoT change (for example, `./docks-kit sync codex` for Codex-only config edits); targets can be combined with `--dry-run`, `--skip-rtk`, `--skip-plugin-refresh` (install missing plugins without refreshing existing caches; used by `docks-kit update`), `--reconcile`, `--prune`, `--yes` (auto-accept toolchain prompts), and the deploy-time modifiers `--claude-compact-window=<tokens>` / `--claude-permissive` / `--claude-model=<m>` / `--claude-effort=<level>` / `--claude-advisor=<on|off|default>` / `--codex-model=<m>` / `--codex-effort=<level>` (see `CLAUDE.md` § Deploy-time modifiers).
60
- - **Additive by default.** Keys present in deployed config but absent from SoT are preserved on default sync. This protects user-only additions, but means drift accumulates — neither flag-less reset can clean it up. The one exception is the Claude `removed` manifest (`claude::_removed_manifest`), a curated list of unambiguous kit-owned artifacts that `claude::sync_removals` force-prunes on every sync; see `CLAUDE.md` § Pruning stale artifacts.
56
+ - **Additive by default.** Keys present in deployed config but absent from SoT are preserved on default sync. This protects user-only additions, but means drift accumulates — neither flag-less reset can clean it up. The one exception is the Claude `removed` manifest (`claude::_removed_manifest`), a curated list of unambiguous kit-owned artifacts that `claude::sync_removals` force-prunes on every sync, including the home-relative `~/.local/bin/session-relay` artifact installed outside `~/.claude`; see `CLAUDE.md` § Pruning stale artifacts.
61
57
  - **`--reconcile` / `--prune` are the kit-owned reconcile flags.** Orthogonal — `--reconcile` reconciles the settings layer (SoT-declared keys/tables/arrays win; user-only keys and nested objects are preserved; permissions arrays are replaced wholesale by SoT). `--prune` uninstalls kit-managed installations not in the SoT (plugins, marketplaces, and `~/.agents/skills/*` entries tracked in `~/.agents/.kit-managed-skills`). Combine for a full reset to SoT's kit-managed scope. User-only additions outside the kit's scope (custom env vars, mcpServers, manually-installed skills, third-party plugins not declared in SoT) are always preserved. Each tool's per-tool file documents the specific paths and diff recipes.
62
- - **SOLID-aligned modules.** `cli/src/engine-native/parseArgs.ts` owns flag parsing/validation. `toolchain.ts` owns the verified-version gate over `SoT/toolchain.json`; `sessionRelayCli.ts` owns the closed release manifest, platform mapping, checksum/smoke transaction, and atomic Session Relay replacement; `bun.ts` owns the shared, memoized Bun bootstrap; `claudeRuntime.ts` owns Claude settings materialization. `claudeSync.ts`, `codexSync.ts`, and `skillsSync.ts` own tool-specific sync logic. `index.ts` is the thin orchestrator. The public CLI seam is `cli/src/engine.ts`.
58
+ - **SOLID-aligned modules.** `cli/src/engine-native/parseArgs.ts` owns flag parsing/validation. `toolchain.ts` owns the verified-version gate over `SoT/toolchain.json`; `bun.ts` owns the shared, memoized Bun bootstrap; `claudeRuntime.ts` owns Claude settings materialization. `claudeSync.ts`, `codexSync.ts`, and `skillsSync.ts` own tool-specific sync logic. `index.ts` is the thin orchestrator. The public CLI seam is `cli/src/engine.ts`.
63
59
  - **Small, reviewable changes.** Bundled multi-concern PRs are harder to review and revert. Split an engine/CLI change and a per-tool config change unless the change requires atomicity.
64
60
  - **Dry-run before destructive flags.** Always preview with `./docks-kit sync --dry-run` (or the relevant `diff <(jq -S …)` recipe in the per-tool file) before invoking `--reconcile` or `--prune`. User-added permissions / env vars / plugins absent from SoT will be discarded.
65
61
  - **SoT prompt files are rules, not explanation.** `SoT/.claude/CLAUDE.md` and `SoT/.codex/AGENTS.md` are loaded into every agent session's prompt context — every line costs prompt tokens on every turn for every user. Restrict their content to rules, heuristics, and `<constraint>` blocks the agent must *act on* during a turn. Do NOT add inline source citations (`Source: …`, attributed quotes), "why this rule exists" preface text, version-watermarking trivia (e.g. "Distilled from X v2.0, captured 2025-11-07"), per-bug workarounds, or installation instructions. Provenance, motivation, and historical context belong in `CLAUDE.md` / `AGENTS.md` at the repo root (humans read once) or in commit messages — never in the SoT. For every line, apply the official test: would removing it cause the agent to make mistakes? If not, cut it — over-instruction degrades adherence on current frontier models.
package/README.md CHANGED
@@ -87,18 +87,6 @@ and a later flag-less sync reverts them. Full reference: `docks-kit docs flags`
87
87
  versions above the kit-verified pin prompt before installing
88
88
  (`--yes` accepts; non-TTY falls back to the pinned verified version
89
89
  when possible). `docks-kit toolchain check` shows the full table.
90
- - **Session Relay CLI** — Claude/Codex syncs ensure the source-pinned
91
- precompiled `session-relay` command in `~/.local/bin` immediately before
92
- Session Relay plugin reconciliation. Linux x64/arm64 and macOS arm64 assets
93
- must match both the committed digest and their release `SHA256SUMS`, then
94
- pass an exact staged version smoke before atomic replacement; failures
95
- preserve any prior command. `x86_64-apple-darwin` is no longer published as
96
- of Session Relay 0.16.0; macOS support is `aarch64-apple-darwin`. The three
97
- production digests are pinned for the tagged `session-relay--v0.16.0` assets
98
- and match the same-release `SHA256SUMS`:
99
- `x86_64-unknown-linux-musl` — `b3ca082dc5ea51e8322be407cdb4bbcaaa05d80bd62c3553f82ab98c1a95498a`
100
- `aarch64-unknown-linux-musl` — `816b6b8bd2d2c2518ea359a5a21502213347b387a1cc576a0fb9cf541e5646ed`
101
- `aarch64-apple-darwin` — `da8b114216c3f2301ad582df8e59b49e91953abcc1112b510466b31637fda825`
102
90
  - **Model catalog** — `SoT/models.json` is the research-verified source for
103
91
  model validation, listings, and pickers.
104
92
  - **Claude runtime** — sync materializes three dependency-free Bun `.mjs`
@@ -126,12 +114,12 @@ and a later flag-less sync reverts them. Full reference: `docks-kit docs flags`
126
114
 
127
115
  ## Platform support
128
116
 
129
- | Platform | Architecture | docks-kit binary | Session Relay prebuilt | Sync engine |
130
- |----------|--------------|------------------|------------------------|-------------|
131
- | Linux | x64 | ✅ | ✅ | ✅ native |
132
- | Linux | arm64 | ✅ | ✅ | ✅ native |
133
- | macOS | x64 | ✅ | ✅ | ✅ native |
134
- | macOS | arm64 | ✅ | ✅ | ✅ native |
117
+ | Platform | Architecture | docks-kit binary | Sync engine |
118
+ |----------|--------------|------------------|-------------|
119
+ | Linux | x64 | ✅ | ✅ native |
120
+ | Linux | arm64 | ✅ | ✅ native |
121
+ | macOS | x64 | ✅ | ✅ native |
122
+ | macOS | arm64 | ✅ | ✅ native |
135
123
 
136
124
  Details: `docks-kit docs platforms`.
137
125
 
@@ -140,7 +128,7 @@ Details: `docks-kit docs platforms`.
140
128
  Tagging `cli-v*` builds four standalone binaries (Linux x64/arm64 and macOS
141
129
  x64/arm64) plus `SHA256SUMS` and attaches them to the GitHub release; npm
142
130
  publishes the exact package tarball through trusted publishing with OIDC provenance.
143
- Package `docks-kit` 0.13.0 bundles the CLI + generated payload, so npm releases
131
+ Package `docks-kit` 0.14.2 bundles the CLI + generated payload, so npm releases
144
132
  are versioned config snapshots without shipping the authoring `SoT/` tree.
145
133
 
146
134
  ## Deeper docs
@@ -2,20 +2,15 @@
2
2
 
3
3
  The supported host matrix is exact:
4
4
 
5
- | Platform | Architecture | docks-kit binary | Session Relay prebuilt | Sync engine |
6
- |----------|--------------|------------------|------------------------|-------------|
7
- | Linux | x64 | `docks-kit-linux-x64` | `x86_64-unknown-linux-musl` | EngineNative (TS) |
8
- | Linux | arm64 | `docks-kit-linux-arm64` | `aarch64-unknown-linux-musl` | EngineNative (TS) |
9
- | macOS | x64 | `docks-kit-darwin-x64` | — retired | EngineNative (TS) |
10
- | macOS | arm64 | `docks-kit-darwin-arm64` | `aarch64-apple-darwin` | EngineNative (TS) |
11
-
12
- x86_64-apple-darwin is no longer published as of Session Relay 0.16.0; macOS
13
- support is aarch64-apple-darwin. Intel macOS keeps its docks-kit binary but
14
- fails closed at the Session Relay install boundary.
5
+ | Platform | Architecture | docks-kit binary | Sync engine |
6
+ |----------|--------------|------------------|-------------|
7
+ | Linux | x64 | `docks-kit-linux-x64` | EngineNative (TS) |
8
+ | Linux | arm64 | `docks-kit-linux-arm64` | EngineNative (TS) |
9
+ | macOS | x64 | `docks-kit-darwin-x64` | EngineNative (TS) |
10
+ | macOS | arm64 | `docks-kit-darwin-arm64` | EngineNative (TS) |
15
11
 
16
12
  Windows and every other host are unsupported. The launcher fails before any
17
- Bun-from-source fallback, and Session Relay installation reports the unsupported
18
- host without downloading an asset.
13
+ Bun-from-source fallback.
19
14
 
20
15
  EngineNative is the only supported engine on supported hosts.
21
16
  `DOCKS_KIT_ENGINE=bash` exits with the removed-engine message and points at the
@@ -19,22 +19,19 @@ Order matters — runtime readiness and settings form one transaction:
19
19
  4. **settings.json merge semantics** — additive: SoT keys win, permissions arrays are
20
20
  unioned, user-only keys survive. `--reconcile` replaces permissions arrays
21
21
  wholesale instead.
22
- 5. **Removed-artifact pruning** — prune old shell assets, the Stop hook, and
23
- stale kit-owned settings. A flag-less sync removes `advisorModel`; an
24
- explicit advisor state excludes only that key so the modifier owns it.
22
+ 5. **Removed-artifact pruning** — prune old shell assets, the Stop hook, stale
23
+ kit-owned settings, retired kit-owned plugin enablement, and the stale
24
+ `~/.local/bin/session-relay` command. A flag-less sync removes
25
+ `advisorModel`; an explicit advisor state excludes only that key so the
26
+ modifier owns it.
25
27
  6. **Deploy-time modifiers** (`--claude-compact-window`, `--claude-permissive`,
26
28
  `--claude-model`, `--claude-effort`, `--claude-advisor`) — deployed file only.
27
29
  7. ~/.claude.json (showTurnDuration, user-scoped MCP servers) and connector env
28
30
  export.
29
- 8. **Session Relay CLI** — ensure the exact source-pinned precompiled command
30
- in `~/.local/bin` for the supported host. A staged asset must match both the
31
- source digest and same-release `SHA256SUMS`, then report exact version before
32
- atomic replacement. Failure stops before Session Relay plugin work and
33
- preserves any prior command.
34
- 9. **Plugins** — seven idempotent passes via the `claude plugin` CLI
31
+ 8. **Plugins** — seven idempotent passes via the `claude plugin` CLI
35
32
  (marketplaces → install → update → [--prune: uninstall/remove] → re-assert
36
33
  SoT enabled-state). Optional opt-ins via `--claude-plugin=<name>`.
37
- 10. LSP server binaries (npm globals).
34
+ 9. LSP server binaries (npm globals).
38
35
 
39
36
  The statusline reads Claude's native `rate_limits`. There is no OAuth request,
40
37
  usage cache, jq/curl runtime dependency, or Stop fetch hook.
@@ -42,18 +39,16 @@ usage cache, jq/curl runtime dependency, or Stop fetch hook.
42
39
  ## codex (→ ~/.codex, ~/.agents/plugins)
43
40
 
44
41
  bubblewrap check (Linux), config.toml merge (top-level keys replaced
45
- per-key, [table] blocks replaced wholesale, user-only keys/tables preserved),
46
- `--codex-model` then `--codex-effort` modifiers, rules, AGENTS.md, personal
47
- marketplace file, `codex plugin add` refresh.
48
- Immediately before `codex plugin add`, the same Session Relay CLI ensure runs;
49
- an ensure failure prevents the plugin pass.
42
+ per-key, [table] blocks replaced wholesale, user-only keys/tables preserved,
43
+ and retired kit-owned `[plugins."<id>"]` tables stripped), `--codex-model`
44
+ then `--codex-effort` modifiers, rules, AGENTS.md, personal marketplace file,
45
+ and `codex plugin add` refresh.
50
46
 
51
47
  ## agents (→ ~/.agents/skills, ~/.claude/skills symlinks)
52
48
 
53
49
  `npx skills add` per missing manifest slug, Claude symlink healing,
54
50
  CLI binaries (agent-browser, effect-solutions — toolchain-gated), and the
55
51
  kit-managed snapshot that `--prune` reconciles against.
56
- This target never ensures or downloads Session Relay.
57
52
 
58
53
  ## Reconcile flags
59
54
 
@@ -4,8 +4,8 @@
4
4
 
5
5
  | Field | Meaning |
6
6
  |-------|---------|
7
- | `kind` | `check` (doctor visibility) / `managed` (kit installs + upgrades) / `managed-release` (dedicated source-pinned release transaction) / `pin` (no binary — a version pin for npx-invoked tools, e.g. `skills-cli`) |
8
- | `policy` | `track` (upgrade toward latest, gated by `verified`) / `present` (install when missing, never upgrade) / `exact` (install the declared release only) |
7
+ | `kind` | `check` (doctor visibility) / `managed` (kit installs + upgrades) / `pin` (no binary — a version pin for npx-invoked tools, e.g. `skills-cli`) |
8
+ | `policy` | `track` (upgrade toward latest, gated by `verified`) / `present` (install when missing, never upgrade) |
9
9
  | `floor` | Minimum acceptable version (below → upgrade automatically) |
10
10
  | `verified` | Last kit-tested version — the gate line |
11
11
  | `pinnable` | Whether an exact version can be installed (rtk: `RTK_VERSION=vX.Y.Z`) |
@@ -36,22 +36,6 @@ now kit-approved" act.
36
36
  toolchain ensure on supported Linux/macOS hosts.
37
37
  - **effect-solutions**, **agent-browser** — policy `track`: self-upgrade
38
38
  toward npm latest, gated by their `verified` pins.
39
- - **session-relay** — policy `exact`: Claude/Codex sync and
40
- `docks-kit toolchain ensure session-relay` select exactly one of three targets
41
- (`x86_64-unknown-linux-musl`, `aarch64-unknown-linux-musl`,
42
- `aarch64-apple-darwin`). `x86_64-apple-darwin` is no longer published as of
43
- Session Relay 0.16.0; macOS support is `aarch64-apple-darwin`, and Intel
44
- macOS hosts fail closed before any download. The installer downloads the
45
- pinned `session-relay--v0.16.0` asset and same-release `SHA256SUMS`, requires
46
- source pin = selected row = downloaded bytes, chmods/smoke-tests only the
47
- sibling stage, then renames it over `~/.local/bin/session-relay`. Offline,
48
- unsupported-host, checksum, chmod, version, download, or rename failures
49
- preserve an existing command byte-for-byte. The three pinned production
50
- digests correspond to the tagged `session-relay--v0.16.0` assets and match
51
- its `SHA256SUMS`:
52
- `x86_64-unknown-linux-musl` — `b3ca082dc5ea51e8322be407cdb4bbcaaa05d80bd62c3553f82ab98c1a95498a`
53
- `aarch64-unknown-linux-musl` — `816b6b8bd2d2c2518ea359a5a21502213347b387a1cc576a0fb9cf541e5646ed`
54
- `aarch64-apple-darwin` — `da8b114216c3f2301ad582df8e59b49e91953abcc1112b510466b31637fda825`
55
39
 
56
40
  jq and curl are `check` rows, not global prerequisites. jq is not consumed by
57
41
  normal sync. curl is checked only at a requested Linux/macOS RTK/Bun installer
@@ -71,6 +55,5 @@ probe falls back to the pinned `verified`, never to an ungated latest.
71
55
  ```
72
56
  docks-kit toolchain check # doctor table (also inside docks-kit status)
73
57
  docks-kit toolchain ensure rtk # install/upgrade one tool per policy
74
- docks-kit toolchain ensure session-relay # install exact verified release
75
58
  docks-kit sync --yes # unattended: auto-accept gates
76
59
  ```
@@ -10,7 +10,6 @@ import {
10
10
  sotCodexModel
11
11
  } from "../manifests"
12
12
  import { kitHome } from "../kitHome"
13
- import { sessionRelayReadiness } from "../engine-native/sessionRelayReadiness"
14
13
 
15
14
  const json = Options.boolean("json").pipe(
16
15
  Options.withDescription("Machine-readable output")
@@ -48,12 +47,11 @@ export const statusCommand = Command.make("status", { json }, (config) =>
48
47
  const drift = gatherDrift()
49
48
  const plugins = pluginsView()
50
49
  const skills = skillsView()
51
- const sessionRelay = sessionRelayReadiness()
52
50
  const toolchainTable = yield* engineCapture(["toolchain", "check"])
53
51
 
54
52
  if (config.json) {
55
53
  return yield* Console.log(
56
- JSON.stringify({ kitHome: kitHome(), drift, plugins, skills, sessionRelayReadiness: sessionRelay, toolchainTable }, null, 2)
54
+ JSON.stringify({ kitHome: kitHome(), drift, plugins, skills, toolchainTable }, null, 2)
57
55
  )
58
56
  }
59
57
 
@@ -69,11 +67,6 @@ export const statusCommand = Command.make("status", { json }, (config) =>
69
67
  yield* Console.log(
70
68
  `\nPlugins: ${plugins.length} known (${enabled} SoT-enabled) — details: docks-kit plugins list`
71
69
  )
72
- yield* Console.log(
73
- sessionRelay.state === "ready"
74
- ? `Session Relay: ready for new Codex sessions (v${sessionRelay.version})`
75
- : `Session Relay: unavailable for new Codex sessions (${sessionRelay.reason})`
76
- )
77
70
  const installed = skills.filter((s) => s.installed).length
78
71
  yield* Console.log(
79
72
  `Skills: ${skills.length} known (${installed} installed) — details: docks-kit skills list`
@@ -81,6 +74,6 @@ export const statusCommand = Command.make("status", { json }, (config) =>
81
74
  })
82
75
  ).pipe(
83
76
  Command.withDescription(
84
- "Doctor view: deployed-vs-SoT drift, toolchain, plugin/skill counts, and Session Relay readiness for new Codex sessions."
77
+ "Doctor view: deployed-vs-SoT drift, toolchain, and plugin/skill counts."
85
78
  )
86
79
  )
@@ -2,7 +2,7 @@ import { Args, Command, Options } from "@effect/cli"
2
2
  import { Effect, Option } from "effect"
3
3
  import { bail, engine } from "../engine"
4
4
 
5
- const MANAGED = ["rtk", "bun", "effect-solutions", "agent-browser", "session-relay"]
5
+ const MANAGED = ["rtk", "bun", "effect-solutions", "agent-browser"]
6
6
 
7
7
  const op = Args.text({ name: "op" }).pipe(
8
8
  Args.withDescription("check (default) | ensure <tool>"),
@@ -31,7 +31,6 @@ import { ExitError } from "./parseArgs"
31
31
  import { mergeSettings, reconcileSettings } from "./settings"
32
32
  import { ensure, field } from "./toolchain"
33
33
  import { payloadBytes, payloadDisplayPath, payloadText } from "../payload"
34
- import { ensureSessionRelayCli } from "./sessionRelayCli"
35
34
 
36
35
  export type ClaudeRuntimeState =
37
36
  | { readonly kind: "ready"; readonly paths: ClaudeRuntimePaths }
@@ -81,7 +80,6 @@ export function claudeSync(ctx: Ctx): ClaudeRuntimeState {
81
80
  syncClaudeAdvisor(ctx, ctx.claudeAdvisor)
82
81
  syncClaudeJson(ctx)
83
82
  syncConnectorEnv(ctx)
84
- ensureSessionRelayCli(ctx)
85
83
  syncPlugins(ctx, claudeDir)
86
84
  syncOptionalPlugins(ctx, claudeDir)
87
85
  syncLspServers(ctx)
@@ -449,13 +447,16 @@ const REMOVED_MANIFEST = {
449
447
  "env.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE",
450
448
  "env.CLAUDE_CODE_DISABLE_1M_CONTEXT",
451
449
  "env.CLAUDE_CODE_FORK_SUBAGENT",
452
- "env.CLAUDE_CODE_EFFORT_LEVEL"
450
+ "env.CLAUDE_CODE_EFFORT_LEVEL",
451
+ "enabledPlugins.session-relay@docks"
453
452
  ],
454
453
  permissionRules: {
455
454
  allow: ["Write(./)"],
456
455
  deny: ["Write(**/.env)", "Write(**/.env.local)", "Write(**/secrets/**)"]
457
456
  },
458
457
  claudeJsonKeys: [] as Array<string>,
458
+ /** Home-relative artifacts the kit installed outside ~/.claude. */
459
+ homeFiles: [".local/bin/session-relay"],
459
460
  runtimeReady: {
460
461
  hooks: ["notify.sh"],
461
462
  files: ["statusline.sh", "fetch-usage.sh"],
@@ -566,6 +567,17 @@ function syncRemovals(ctx: Ctx, claudeDir: string, runtime: ClaudeRuntimeState):
566
567
  }
567
568
  }
568
569
 
570
+ for (const rel of REMOVED_MANIFEST.homeFiles) {
571
+ const path = p(ctx.home, rel)
572
+ if (!existsSync(path)) continue
573
+ if (ctx.dryRun) {
574
+ echo(`[dry-run] rm ${path}`)
575
+ } else {
576
+ rmSync(path, { force: true })
577
+ filesRemoved++
578
+ }
579
+ }
580
+
569
581
  const skeys = pruneJsonKeys(ctx, p(claudeDir, "settings.json"), settingsKeys)
570
582
  const permissionRules = prunePermissionRules(
571
583
  ctx,
@@ -10,9 +10,7 @@ import { syncCodexEffort, syncCodexModel, replaceTopLevelSettingInFile } from ".
10
10
  import { p } from "./exec"
11
11
  import type { Ctx } from "./index"
12
12
  import { compareCodepoints, isObject, jqStringify, parseJson, type Json } from "./jq"
13
- import { sessionRelayReadiness } from "./sessionRelayReadiness"
14
13
  import { payloadBytes, payloadDisplayPath, payloadPaths, payloadText, type PayloadPath } from "../payload"
15
- import { ensureSessionRelayCli } from "./sessionRelayCli"
16
14
 
17
15
  export function codexSync(ctx: Ctx): void {
18
16
  const codexDir = p(ctx.home, ".codex")
@@ -28,7 +26,6 @@ export function codexSync(ctx: Ctx): void {
28
26
  syncAgentsMd(ctx, payloadText("SoT/.codex/AGENTS.md"), p(codexDir, "AGENTS.md"))
29
27
  syncMarketplace(ctx, payloadText("SoT/.codex/plugins/marketplace.json"), p(ctx.agentsDir, "plugins", "marketplace.json"))
30
28
  removeLegacyDocksMarketplace(ctx, userConfig)
31
- ensureSessionRelayCli(ctx)
32
29
  syncPlugins(ctx, sotConfig)
33
30
  }
34
31
 
@@ -124,6 +121,7 @@ function syncConfig(ctx: Ctx, sotConfigText: string, userConfig: string): void {
124
121
  writeFileSync(staging, before)
125
122
 
126
123
  scrubDeprecatedFeatures(ctx, staging)
124
+ removeRetiredPluginTables(ctx, staging)
127
125
  mergeTopLevelSettings(sotConfigText, staging)
128
126
  mergeTableSettings(sotConfigText, staging)
129
127
 
@@ -184,6 +182,49 @@ function scrubDeprecatedFeatures(ctx: Ctx, userConfig: string): void {
184
182
  change("Codex: scrubbed deprecated [features].use_legacy_landlock")
185
183
  }
186
184
 
185
+ const PLUGIN_TABLE_HEADER = /^\[plugins\."([^"]+)"\][ \t]*$/
186
+ /** Plugin ids the kit retired; their deployed tables are stripped on every sync. */
187
+ const RETIRED_PLUGIN_IDS: Readonly<Record<string, true>> = { "session-relay@docks": true }
188
+
189
+ /** codex::remove_retired_plugin_tables — drop [plugins."<id>"] blocks for retired ids. */
190
+ export function removeRetiredPluginTablesText(content: string): string {
191
+ const lines = content.split("\n")
192
+ if (lines[lines.length - 1] === "") lines.pop()
193
+ let out = ""
194
+ let skipping = false
195
+ for (const line of lines) {
196
+ const header = PLUGIN_TABLE_HEADER.exec(line)
197
+ if (header !== null) {
198
+ skipping = RETIRED_PLUGIN_IDS[header[1]!] === true
199
+ if (skipping) continue
200
+ } else if (skipping) {
201
+ if (!line.startsWith("[")) continue
202
+ skipping = false
203
+ }
204
+ out += `${line}\n`
205
+ }
206
+ return out
207
+ }
208
+
209
+ function retiredPluginTables(content: string): Array<string> {
210
+ return content
211
+ .split("\n")
212
+ .map((line) => PLUGIN_TABLE_HEADER.exec(line)?.[1])
213
+ .filter((id): id is string => id !== undefined && RETIRED_PLUGIN_IDS[id] === true)
214
+ }
215
+
216
+ function removeRetiredPluginTables(ctx: Ctx, userConfig: string): void {
217
+ const { change } = ctx.services.logger
218
+ if (!existsSync(userConfig)) return
219
+ const content = readFileSync(userConfig, "utf8")
220
+ const present = retiredPluginTables(content)
221
+ if (present.length === 0) return
222
+
223
+ writeFileSync(`${userConfig}.tmp`, removeRetiredPluginTablesText(content))
224
+ renameSync(`${userConfig}.tmp`, userConfig)
225
+ for (const id of present) change(`Codex: removed retired plugin table [plugins."${id}"]`)
226
+ }
227
+
187
228
  function mergeTopLevelSettings(sotConfigText: string, userConfig: string): void {
188
229
  for (const line of sotConfigText.split("\n")) {
189
230
  if (line.startsWith("[")) break
@@ -407,7 +448,7 @@ export function enabledPluginIdsFromText(configText: string): Array<string> {
407
448
  if (plugin !== "" && enabled) ids.push(plugin)
408
449
  }
409
450
  for (const line of configText.split("\n")) {
410
- const m = /^\[plugins\."([^"]+)"\][ \t]*$/.exec(line)
451
+ const m = PLUGIN_TABLE_HEADER.exec(line)
411
452
  if (m !== null) {
412
453
  flush()
413
454
  plugin = m[1]!
@@ -510,10 +551,6 @@ function syncPlugins(ctx: Ctx, sotConfigText: string): void {
510
551
  if (refreshed > 0) {
511
552
  change(`Codex plugins synced (plugins: ~${refreshed})`)
512
553
  ctx.nextStepTriggers.codexRestart = true
513
- const readiness = sessionRelayReadiness()
514
- if (readiness.state !== "ready") {
515
- warn(`Session Relay readiness unavailable after refresh: ${readiness.reason}`)
516
- }
517
554
  }
518
555
  if (ctx.skipPluginRefresh && pluginIds.length === 0) verbose("Codex plugins already installed; refresh-only updates skipped")
519
556
  if (failed > 0) warn(`${failed} Codex plugin operation(s) failed — re-run sync or install manually`)
@@ -29,7 +29,6 @@ export type ToolId =
29
29
  | "bwrap"
30
30
  | "agent-browser"
31
31
  | "effect-solutions"
32
- | "session-relay"
33
32
  | "chrome-for-testing"
34
33
  | "ffplay"
35
34
  | "intelephense"
@@ -241,11 +240,6 @@ export const DEPENDENCIES: Record<ToolId, DependencySpec> = {
241
240
  version: versionProbe("rtk"),
242
241
  latest: latestRtk
243
242
  }),
244
- "session-relay": spec("session-relay", "optional", () => "docks-kit toolchain ensure session-relay", {
245
- resolve: (exec) => pathProbe(p(home(), ".local", "bin", "session-relay"))(exec),
246
- version: (exec) => exec.capture(p(home(), ".local", "bin", "session-relay"), ["--version"]),
247
- locate: () => ({ path: p(home(), ".local", "bin", "session-relay"), binDir: p(home(), ".local", "bin") })
248
- }),
249
243
  bun: spec(
250
244
  "bun",
251
245
  "optional",
@@ -12,7 +12,6 @@ import type { Ctx } from "./index"
12
12
  import { isObject, parseJson, type Json } from "./jq"
13
13
  import { printModels, validateClaudeModel, validateCodexModel } from "./models"
14
14
  import { ensureRtk } from "./claudeSync"
15
- import { ensureSessionRelayCli } from "./sessionRelayCli"
16
15
  import { bunBootstrap } from "./bun"
17
16
  import { agentBrowserInstall, effectSolutionsInstall } from "./skillsSync"
18
17
  import { ensure, report } from "./toolchain"
@@ -143,10 +142,8 @@ export function modeToolchain(ctx: Ctx, args: ReadonlyArray<string>): number {
143
142
  return ensure(ctx, "effect-solutions", effectSolutionsInstall(ctx))
144
143
  case "agent-browser":
145
144
  return ensure(ctx, "agent-browser", agentBrowserInstall)
146
- case "session-relay":
147
- return ensureSessionRelayCli(ctx)
148
145
  default:
149
- err("toolchain ensure supports managed tools only (rtk, bun, effect-solutions, agent-browser, session-relay)")
146
+ err("toolchain ensure supports managed tools only (rtk, bun, effect-solutions, agent-browser)")
150
147
  return 2
151
148
  }
152
149
  }
@@ -57,8 +57,6 @@ export function installedVersion(ctx: Ctx, tool: ToolId): string {
57
57
  case "codex":
58
58
  case "agent-browser":
59
59
  return firstLineField(version(), -1)
60
- case "session-relay":
61
- return firstLineField(version(), 1)
62
60
  case "git":
63
61
  return firstLineField(version(), 2)
64
62
  case "node":
@@ -1,21 +1,21 @@
1
1
  // Generated by cli/scripts/generate-sot-payload.ts. DO NOT EDIT.
2
2
  // Edit SoT/, notification.mp3, or package.json, then run: bun cli/scripts/generate-sot-payload.ts
3
3
 
4
- export const GENERATED_PACKAGE_VERSION = "0.14.0"
4
+ export const GENERATED_PACKAGE_VERSION = "0.14.2"
5
5
 
6
6
  export const GENERATED_PAYLOAD_TEXT = {
7
7
  "SoT/.agents/skills.txt": "# Universal AI-agent skill manifest intentionally empty.\n# Global skill discovery is opt-in: add one <owner>/<repo> slug per line.\n# EngineNative ignores comments and blank lines.\n",
8
8
  "SoT/models.json": "{\n \"$comment\": \"Kit-verified model catalog — single source for EngineNative validators, the docks-kit CLI (models/model commands, pickers, and bare-flag help), and docs. Entries are research-proofed: update an entry and its tool-level `verified` date when a model ships or retires. Deploy-time model flags remain permissive.\",\n \"claude\": {\n \"verified\": \"2026-07-27\",\n \"models\": [\n { \"id\": \"best\", \"kind\": \"alias\", \"note\": \"Fable 5 where the org has access, latest Opus otherwise (Claude Code >=2.1.170)\" },\n { \"id\": \"opus\", \"kind\": \"alias\", \"note\": \"latest Opus — the kit SoT default (Opus 5 on the Anthropic API from Claude Code >=2.1.219; Opus 4.6 on Microsoft Foundry)\" },\n { \"id\": \"fable\", \"kind\": \"alias\", \"note\": \"Fable 5 — advisor opt-in default; needs org access + Claude Code >=2.1.170\" },\n { \"id\": \"sonnet\", \"kind\": \"alias\", \"note\": \"latest Sonnet (currently Sonnet 5)\" },\n { \"id\": \"haiku\", \"kind\": \"alias\", \"note\": \"latest Haiku (currently Haiku 4.5)\" },\n { \"id\": \"default\", \"kind\": \"alias\", \"note\": \"engine pseudo-value: deletes the deployed model key so the account default applies\" },\n { \"id\": \"claude-fable-5\", \"kind\": \"id\", \"note\": \"Fable 5\" },\n { \"id\": \"claude-opus-5\", \"kind\": \"id\", \"note\": \"Opus 5 — needs Claude Code >=2.1.219\" },\n { \"id\": \"claude-opus-4-8\", \"kind\": \"id\", \"note\": \"Opus 4.8\" },\n { \"id\": \"claude-sonnet-5\", \"kind\": \"id\", \"note\": \"Sonnet 5\" },\n { \"id\": \"claude-haiku-4-5-20251001\", \"kind\": \"id\", \"note\": \"Haiku 4.5\" }\n ]\n },\n \"codex\": {\n \"verified\": \"2026-07-16\",\n \"models\": [\n { \"id\": \"gpt-5.6-sol\", \"kind\": \"id\", \"note\": \"GPT-5.6 Sol — frontier, recommended default; the kit SoT pin\" },\n { \"id\": \"gpt-5.6-terra\", \"kind\": \"id\", \"note\": \"GPT-5.6 Terra — balanced tier\" },\n { \"id\": \"gpt-5.6-luna\", \"kind\": \"id\", \"note\": \"GPT-5.6 Luna — fast/light tier\" },\n { \"id\": \"gpt-5.5\", \"kind\": \"id\", \"note\": \"previous generation\" },\n { \"id\": \"gpt-5.5-codex\", \"kind\": \"id\", \"note\": \"codex-tuned gpt-5.5\" },\n { \"id\": \"gpt-5.1\", \"kind\": \"id\", \"note\": \"previous generation\" },\n { \"id\": \"gpt-5\", \"kind\": \"id\", \"note\": \"previous generation\" },\n { \"id\": \"gpt-5-codex\", \"kind\": \"id\", \"note\": \"codex-tuned gpt-5\" }\n ]\n }\n}\n",
9
- "SoT/toolchain.json": "{\n \"$comment\": \"Kit toolchain manifest — DATA only (versions, floors, policy); check/install logic lives in cli/src/engine-native/toolchain.ts with per-surface sync callbacks in cli/src/engine-native/. kind: check (doctor visibility only) | managed (kit installs/upgrades it) | managed-release (dedicated source-pinned release installer) | pin (no binary probe — a version pin for a tool the kit invokes via npx). policy (managed only): track (upgrade toward latest, gated by `verified`) | present (install when missing, never upgrade); managed-release uses exact. `verified` = last kit-tested version — anything above it prompts before install (--yes auto-accepts; non-TTY declines and falls back to the pinned `verified` when pinnable). Supply-chain stance: every kit-driven install is pinned to `verified` or gated by it — never floating @latest (npm-worm/Shai-Hulud surface). Update `verified` after testing a new release.\",\n \"tools\": {\n \"jq\": { \"kind\": \"check\", \"note\": \"optional operator CLI; EngineNative JSON and Claude runtime do not invoke it\" },\n \"curl\": { \"kind\": \"check\", \"note\": \"contextual POSIX installer transport for RTK/Bun bootstrap\" },\n \"git\": { \"kind\": \"check\", \"note\": \"plugin marketplaces (claude/codex clone them) + kit checkout updates\" },\n \"node\": { \"kind\": \"check\", \"note\": \"hosts npm globals (agent-browser, LSP servers)\" },\n \"npm\": { \"kind\": \"check\", \"note\": \"npm-global installer\" },\n \"claude\": { \"kind\": \"check\", \"floor\": \"2.1.219\", \"note\": \"kit floor — lets the `opus` alias select the newest Opus its provider offers (Anthropic API: 5; Foundry: 4.6) and subsumes Fable 5's >=2.1.170 requirement (mirrors settings minimumVersion)\" },\n \"codex\": { \"kind\": \"check\", \"note\": \"upstream-owned; standalone installer prints when missing\" },\n \"ffplay\": { \"kind\": \"check\", \"note\": \"Notification hook sound (previously unchecked)\" },\n \"bwrap\": { \"kind\": \"check\", \"os\": \"linux\", \"note\": \"Codex Linux sandbox runtime\" },\n \"intelephense\": { \"kind\": \"check\", \"verified\": \"1.18.5\", \"note\": \"php-lsp server binary; `verified` pins claude::sync_lsp_servers' npm install\" },\n \"typescript-language-server\": { \"kind\": \"check\", \"verified\": \"5.3.0\", \"note\": \"typescript-lsp server binary; `verified` pins claude::sync_lsp_servers' npm install\" },\n \"tsc\": { \"kind\": \"check\", \"verified\": \"6.0.3\", \"note\": \"typescript-lsp dependency (npm package `typescript`); `verified` pins claude::sync_lsp_servers' npm install. Deliberately on the 6.x line: typescript-language-server embeds TypeScript's programmatic API, which TS7 (native) doesn't yet expose — the repo's own devDependency runs TS7 for tsc --noEmit\" },\n \"rtk\": { \"kind\": \"managed\", \"policy\": \"track\", \"floor\": \"0.43.0\", \"verified\": \"0.43.0\", \"pinnable\": true,\n \"note\": \"PreToolUse hook — supply-chain review before unverified upgrades; installer honors RTK_VERSION=vX.Y.Z pin\" },\n \"bun\": { \"kind\": \"managed\", \"policy\": \"present\", \"verified\": \"1.3.14\", \"pinnable\": true,\n \"note\": \"runtime for effect-solutions + the docks-kit CLI; bootstrap installs the verified release (installer takes bun-vX.Y.Z); self-updates via `bun upgrade` when wanted\" },\n \"effect-solutions\": { \"kind\": \"managed\", \"policy\": \"track\", \"verified\": \"0.5.3\", \"pinnable\": true,\n \"note\": \"Effect docs CLI (bun global) — track keeps it self-upgrading, gated by the verified pin\" },\n \"agent-browser\": { \"kind\": \"managed\", \"policy\": \"track\", \"verified\": \"0.32.0\", \"pinnable\": true,\n \"note\": \"browser-automation CLI (npm global), gated by the verified pin; first install also downloads Chrome for Testing\" },\n \"session-relay\": { \"kind\": \"managed-release\", \"policy\": \"exact\", \"verified\": \"0.16.0\", \"repository\": \"DocksDocks/docks\", \"tag\": \"session-relay--v0.16.0\", \"plugin_id\": \"session-relay@docks\", \"plugin_version\": \"0.16.0\", \"install_path\": \"~/.local/bin/session-relay\",\n \"assets\": {\n \"x86_64-unknown-linux-musl\": \"b3ca082dc5ea51e8322be407cdb4bbcaaa05d80bd62c3553f82ab98c1a95498a\",\n \"aarch64-unknown-linux-musl\": \"816b6b8bd2d2c2518ea359a5a21502213347b387a1cc576a0fb9cf541e5646ed\",\n \"aarch64-apple-darwin\": \"da8b114216c3f2301ad582df8e59b49e91953abcc1112b510466b31637fda825\"\n } },\n \"skills-cli\": { \"kind\": \"pin\", \"verified\": \"1.5.15\",\n \"note\": \"the `skills` npm package the kit runs via `npx skills@<verified>` on every agents sync (universal-skill install/remove) — pinned, never @latest\" }\n }\n}\n",
9
+ "SoT/toolchain.json": "{\n \"$comment\": \"Kit toolchain manifest — DATA only (versions, floors, policy); check/install logic lives in cli/src/engine-native/toolchain.ts with per-surface sync callbacks in cli/src/engine-native/. kind: check (doctor visibility only) | managed (kit installs/upgrades it) | pin (no binary probe — a version pin for a tool the kit invokes via npx). policy (managed only): track (upgrade toward latest, gated by `verified`) | present (install when missing, never upgrade). `verified` = last kit-tested version — anything above it prompts before install (--yes auto-accepts; non-TTY declines and falls back to the pinned `verified` when pinnable). Supply-chain stance: every kit-driven install is pinned to `verified` or gated by it — never floating @latest (npm-worm/Shai-Hulud surface). Update `verified` after testing a new release.\",\n \"tools\": {\n \"jq\": { \"kind\": \"check\", \"note\": \"optional operator CLI; EngineNative JSON and Claude runtime do not invoke it\" },\n \"curl\": { \"kind\": \"check\", \"note\": \"contextual POSIX installer transport for RTK/Bun bootstrap\" },\n \"git\": { \"kind\": \"check\", \"note\": \"plugin marketplaces (claude/codex clone them) + kit checkout updates\" },\n \"node\": { \"kind\": \"check\", \"note\": \"hosts npm globals (agent-browser, LSP servers)\" },\n \"npm\": { \"kind\": \"check\", \"note\": \"npm-global installer\" },\n \"claude\": { \"kind\": \"check\", \"floor\": \"2.1.219\", \"note\": \"kit floor — lets the `opus` alias select the newest Opus its provider offers (Anthropic API: 5; Foundry: 4.6) and subsumes Fable 5's >=2.1.170 requirement (mirrors settings minimumVersion)\" },\n \"codex\": { \"kind\": \"check\", \"note\": \"upstream-owned; standalone installer prints when missing\" },\n \"ffplay\": { \"kind\": \"check\", \"note\": \"Notification hook sound (previously unchecked)\" },\n \"bwrap\": { \"kind\": \"check\", \"os\": \"linux\", \"note\": \"Codex Linux sandbox runtime\" },\n \"intelephense\": { \"kind\": \"check\", \"verified\": \"1.18.5\", \"note\": \"php-lsp server binary; `verified` pins claude::sync_lsp_servers' npm install\" },\n \"typescript-language-server\": { \"kind\": \"check\", \"verified\": \"5.3.0\", \"note\": \"typescript-lsp server binary; `verified` pins claude::sync_lsp_servers' npm install\" },\n \"tsc\": { \"kind\": \"check\", \"verified\": \"6.0.3\", \"note\": \"typescript-lsp dependency (npm package `typescript`); `verified` pins claude::sync_lsp_servers' npm install. Deliberately on the 6.x line: typescript-language-server embeds TypeScript's programmatic API, which TS7 (native) doesn't yet expose — the repo's own devDependency runs TS7 for tsc --noEmit\" },\n \"rtk\": { \"kind\": \"managed\", \"policy\": \"track\", \"floor\": \"0.43.0\", \"verified\": \"0.43.0\", \"pinnable\": true,\n \"note\": \"PreToolUse hook — supply-chain review before unverified upgrades; installer honors RTK_VERSION=vX.Y.Z pin\" },\n \"bun\": { \"kind\": \"managed\", \"policy\": \"present\", \"verified\": \"1.3.14\", \"pinnable\": true,\n \"note\": \"runtime for effect-solutions + the docks-kit CLI; bootstrap installs the verified release (installer takes bun-vX.Y.Z); self-updates via `bun upgrade` when wanted\" },\n \"effect-solutions\": { \"kind\": \"managed\", \"policy\": \"track\", \"verified\": \"0.5.3\", \"pinnable\": true,\n \"note\": \"Effect docs CLI (bun global) — track keeps it self-upgrading, gated by the verified pin\" },\n \"agent-browser\": { \"kind\": \"managed\", \"policy\": \"track\", \"verified\": \"0.32.0\", \"pinnable\": true,\n \"note\": \"browser-automation CLI (npm global), gated by the verified pin; first install also downloads Chrome for Testing\" },\n \"skills-cli\": { \"kind\": \"pin\", \"verified\": \"1.5.15\",\n \"note\": \"the `skills` npm package the kit runs via `npx skills@<verified>` on every agents sync (universal-skill install/remove) — pinned, never @latest\" }\n }\n}\n",
10
10
  "SoT/.claude/CLAUDE.md": "## Research Before Implementation\n\nBefore writing or modifying code that uses an API, hook, method, or config surface you have not verified in this session, research current documentation first.\n\nResearch workflow:\n1. Prefer official documentation and primary sources for the specific library, framework, or API.\n2. If a local docs or MCP tool is available, use it before broad web search.\n3. Only then proceed to implementation.\n\nResearch when:\n- Installing or configuring a dependency.\n- Using an API, hook, method, or pattern not verified in this session.\n- Upgrading or migrating between versions.\n- Any task where relying on memory could cause stale syntax or behavior.\n\nDo not:\n- Assume API signatures, method names, or config options from memory.\n- Generate framework code without checking current docs first.\n- Skip research because the library seems familiar.\n\n<constraint>\nResearch the codebase before editing. Never change code you have not read.\n</constraint>\n\n## Agentic Harness Heuristics\n\n**1. Persistence.** Keep going until the user's query is completely resolved. Only yield when sure the problem is solved. Before ending a turn, check the last paragraph: if it is a plan, a question you can answer yourself, or a promise of work not done (\"I'll…\"), do that work now.\n\n**2. Default to parallel.** Whenever you have multiple independent operations (reads, greps, web fetches, independent edits), invoke them in a single response with multiple tool-use blocks. Sequential calls only when output of one operation is required as input to the next.\n\n**3. Multi-pass search.** First-pass search often misses — vary the wording (colleague-questions over keywords) before concluding something doesn't exist.\n\n**4. Trace symbols.** Before modifying a symbol, trace it to its definitions and all usages. Don't assume a function's behavior or a type's shape from the call site alone.\n\n**5. Linter-loop 3-strike rule.** Don't loop more than 3 times fixing linter errors on the same file. On the third attempt, stop and ask the user — repeated failure usually means the diagnosis is wrong, not the code.\n\n**6. Read-before-Edit TTL.** If you haven't read a file with the Read tool in the last ~5 messages, re-read it before editing. Cached file content goes stale silently when the user edits between turns.\n\n**7. Big-file rule.** For files >1000 lines, prefer Grep + scoped Read (`offset` + `limit`) over reading the entire file. Whole-file reads bloat context; targeted reads keep the working set small.\n\n**8. Todo hygiene.** Use TaskCreate for items with meaningful outcome (≥5 min, distinct deliverable). Never include operational sub-actions (linting, testing, searching, examining the codebase) as their own todos — those are sub-steps in service of higher-level tasks. Mark complete immediately when done, never in batches.\n\n**9. Literal-instruction rule.** Current frontier models follow instructions literally — they do not silently generalize from intent. Phrase requirements as explicit checklists with success criteria, not narrative.\n\n**10. Context hygiene.** Prefer `/clear` at task boundaries and `/rewind` for wrong-path detours over carrying rot forward (corrections accumulate noise; rewinds preserve the prefix and discard the bad branch). On a continuing task, run `/compact` with steering before context quality degrades. Never stop, summarize, or suggest a new session on account of context limits.\n\n**11. Autonomy calibration.** For minor choices (naming, formatting, default values, which of two equivalent approaches), pick a reasonable option and note it — don't ask. Ask first only for scope changes, destructive actions, or decisions that change the deliverable. When the user is describing a problem or asking a question rather than requesting a change, the deliverable is your assessment — report findings and stop; don't apply fixes until asked. Don't close a finished task with \"Want me to also…?\" — run the obvious verification, then stop cleanly.\n\n**12. Capability triggering.** When the answer depends on current or version-specific information, search or fetch before answering — never answer from memory. When work fans out across independent items (many files to read, many tests to run, many candidates to check), delegate to parallel subagents; never spawn one for work you can complete directly. For verification, prefer a fresh-context subagent over self-critique. On tasks longer than a few turns, keep a running notes file and re-read it before each phase.\n\n<constraint>\nTreat the 12 heuristics above as protocol, not preference. If a turn ends without honoring an applicable one (e.g., lint-loop guard not respected, edit without re-read), self-correct in the next turn before continuing.\n</constraint>\n\nReuse before invention: inventory existing code, components, conventions, and dependencies; extend them instead of creating a parallel pattern. Load only the narrow skills supported by the task and repository evidence. If a request establishes a new React/Tailwind system and no convention exists, prefer current shadcn/ui `base-*` components backed by Base UI; otherwise preserve the existing stack. Treat probe, production access, publish, push, release, and deploy as literal current-request effects—never infer external authority from a plan, schedule, review, or old receipt.\n\nFor Docks plan reviews, cross-company review is standing-authorized; do not ask for export consent. This never overrides a host or platform security denial.\n\nUse a narrow-to-broad verification ladder: direct acceptance while iterating, focused regressions next, and one full CI at the pre-commit or release boundary. Reuse still-matching evidence; rerun full CI only after a relevant edit invalidates it.\n\n<constraint>\nNo secrets in committed config. Treat plugin marketplaces, installers, and downloaded artifacts as untrusted until verified.\n</constraint>\n",
11
11
  "SoT/.claude/mcp-servers.json": "{\n \"mcpServers\": {}\n}\n",
12
- "SoT/.claude/settings.json": "{\n \"$schema\": \"https://json.schemastore.org/claude-code-settings.json\",\n \"minimumVersion\": \"2.1.219\",\n \"model\": \"opus\",\n \"effortLevel\": \"high\",\n \"autoMemoryEnabled\": true,\n \"skillListingMaxDescChars\": 2048,\n \"respectGitignore\": true,\n \"cleanupPeriodDays\": 14,\n \"skillListingBudgetFraction\": 0.05,\n \"env\": {\n \"CLAUDE_CODE_MAX_OUTPUT_TOKENS\": \"64000\",\n \"CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR\": \"1\",\n \"CLAUDE_CODE_AUTO_COMPACT_WINDOW\": \"468000\",\n \"CLAUDE_CODE_NO_FLICKER\": \"1\"\n },\n \"permissions\": {\n \"defaultMode\": \"auto\",\n \"allow\": [\n \"Read\",\n \"Glob\",\n \"Grep\",\n \"WebFetch\",\n \"WebSearch\",\n \"Edit(./)\",\n \"Bash(git *)\",\n \"Bash(git add *)\",\n \"Bash(git commit *)\",\n \"Bash(git status *)\",\n \"Bash(git diff *)\",\n \"Bash(git log *)\",\n \"Bash(git branch *)\",\n \"Bash(git checkout *)\",\n \"Bash(git switch *)\",\n \"Bash(git stash *)\",\n \"Bash(git fetch *)\",\n \"Bash(git pull *)\",\n \"Bash(git tag *)\",\n \"Bash(git show *)\",\n \"Bash(git blame *)\",\n \"Bash(git worktree *)\",\n \"Bash(gh *)\",\n \"Bash(pnpm *)\",\n \"Bash(npm *)\",\n \"Bash(npx *)\",\n \"Bash(node *)\",\n \"Bash(docker *)\",\n \"Bash(docker-compose *)\",\n \"Bash(rtk *)\",\n \"Bash(ls *)\",\n \"Bash(cat *)\",\n \"Bash(find *)\",\n \"Bash(grep *)\",\n \"Bash(head *)\",\n \"Bash(tail *)\",\n \"Bash(wc *)\",\n \"Bash(sort *)\",\n \"Bash(uniq *)\",\n \"Bash(diff *)\",\n \"Bash(which *)\",\n \"Bash(pwd *)\",\n \"Bash(date *)\",\n \"Bash(mkdir *)\",\n \"Bash(basename *)\",\n \"Bash(dirname *)\",\n \"Bash(realpath *)\",\n \"Bash(jq *)\",\n \"Bash(curl *)\",\n \"Bash(tree *)\",\n \"Bash(sed *)\",\n \"Bash(awk *)\",\n \"Bash(cut *)\",\n \"Bash(tr *)\",\n \"Bash(tee *)\",\n \"Bash(echo *)\",\n \"Bash(printf *)\",\n \"Bash(env *)\",\n \"Bash(printenv *)\",\n \"Bash(uname *)\",\n \"Bash(file *)\",\n \"Bash(stat *)\",\n \"Bash(du *)\",\n \"Bash(id *)\",\n \"Bash(whoami *)\",\n \"Bash(php *)\",\n \"Bash(composer *)\",\n \"Bash(python3 *)\",\n \"Bash(python *)\",\n \"Bash(pip *)\",\n \"Bash(pip3 *)\"\n ],\n \"deny\": [\n \"Read(**/.env)\",\n \"Read(**/.env.local)\",\n \"Read(**/secrets/**)\",\n \"Read(**/*.key)\",\n \"Read(**/*.pem)\",\n \"Read(**/*.p12)\",\n \"Read(**/.credentials*)\",\n \"Edit(**/.env)\",\n \"Edit(**/.env.local)\",\n \"Edit(**/secrets/**)\",\n \"Bash(sudo *)\",\n \"Bash(rm -rf /)\",\n \"Bash(rm -rf / *)\",\n \"Bash(rm -rf ~)\",\n \"Bash(rm -rf ~ *)\",\n \"Bash(rm -rf $HOME)\",\n \"Bash(rm -rf $HOME *)\",\n \"Bash(> /dev *)\",\n \"Bash(dd if= *)\",\n \"Bash(mkfs *)\",\n \"Bash(eval *)\",\n \"Bash(chmod 777 *)\",\n \"Bash(chmod -R 777 *)\",\n \"Bash(git push --force origin main *)\",\n \"Bash(git push --force origin master *)\",\n \"Bash(git push -f origin main *)\",\n \"Bash(git push -f origin master *)\"\n ],\n \"ask\": [\n \"Bash(git clean *)\",\n \"Bash(docker volume rm *)\",\n \"Bash(docker system prune *)\"\n ]\n },\n \"hooks\": {\n \"SessionStart\": [\n {\n \"hooks\": [\n {\n \"type\": \"command\",\n \"command\": \"__DOCKS_KIT_BUN__\",\n \"args\": [\"__DOCKS_KIT_SESSION_START__\"],\n \"timeout\": 5\n }\n ]\n }\n ],\n \"Notification\": [\n {\n \"hooks\": [\n {\n \"type\": \"command\",\n \"command\": \"__DOCKS_KIT_BUN__\",\n \"args\": [\"__DOCKS_KIT_NOTIFY__\"],\n \"timeout\": 10,\n \"async\": true\n }\n ]\n }\n ],\n \"PreToolUse\": [\n {\n \"matcher\": \"Bash\",\n \"hooks\": [\n {\n \"type\": \"command\",\n \"command\": \"rtk hook claude\"\n }\n ]\n }\n ],\n \"PostToolUseFailure\": [\n {\n \"matcher\": \"Bash\",\n \"hooks\": [\n {\n \"type\": \"command\",\n \"command\": \"echo '{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PostToolUseFailure\\\",\\\"additionalContext\\\":\\\"Last bash command failed. Repository / file state may have shifted \\u2014 re-read affected files before retrying. If the failure is a missing dependency or env mismatch, surface it to the user rather than retrying blindly.\\\"}}'\",\n \"timeout\": 5\n }\n ]\n }\n ],\n \"SubagentStop\": [\n {\n \"hooks\": [\n {\n \"type\": \"prompt\",\n \"prompt\": \"You are a quality gate for subagent outputs in a multi-agent code-analysis pipeline.\\n\\nEvaluate the subagent's `last_assistant_message` field (in the JSON below) against these requirements:\\n\\n1. ALLOW (return `{}`): Mode-selection or no-issues responses. Examples: \\\"Which mode do you prefer\\\", \\\"select an option\\\", \\\"no issues / problems / violations / blockers found\\\".\\n\\n2. ALLOW (return `{}`): Output contains at least one concrete file:line citation \\u2014 e.g. `src/auth.ts:42`, `lib/db.ts:100-115`, or path references that include line numbers.\\n\\n3. BLOCK (return `{\\\"decision\\\":\\\"block\\\",\\\"reason\\\":\\\"<one-line explanation>\\\"}`): Output claims about code or findings WITHOUT concrete file:line citations. Vague references like \\\"the auth handler\\\" or \\\"near the database code\\\" are not acceptable as the only evidence.\\n\\nSubagent invocation JSON:\\n$ARGUMENTS\\n\\nReturn ONLY the JSON decision (no commentary, no markdown fences).\",\n \"timeout\": 30\n }\n ]\n }\n ]\n },\n \"statusLine\": {\n \"type\": \"command\",\n \"command\": \"__DOCKS_KIT_STATUSLINE__\",\n \"refreshInterval\": 5\n },\n \"enabledPlugins\": {\n \"docks@docks\": true,\n \"session-relay@docks\": true,\n \"effect-kit@docks\": true,\n \"php-lsp@claude-plugins-official\": true,\n \"typescript-lsp@claude-plugins-official\": true\n },\n \"extraKnownMarketplaces\": {\n \"docks\": {\n \"source\": {\n \"source\": \"github\",\n \"repo\": \"DocksDocks/docks\"\n }\n }\n },\n \"alwaysThinkingEnabled\": true,\n \"showThinkingSummaries\": true,\n \"viewMode\": \"default\",\n \"theme\": \"dark-daltonized\",\n \"skipDangerousModePermissionPrompt\": true\n}\n",
12
+ "SoT/.claude/settings.json": "{\n \"$schema\": \"https://json.schemastore.org/claude-code-settings.json\",\n \"minimumVersion\": \"2.1.219\",\n \"model\": \"opus\",\n \"effortLevel\": \"high\",\n \"autoMemoryEnabled\": true,\n \"skillListingMaxDescChars\": 2048,\n \"respectGitignore\": true,\n \"cleanupPeriodDays\": 14,\n \"skillListingBudgetFraction\": 0.05,\n \"env\": {\n \"CLAUDE_CODE_MAX_OUTPUT_TOKENS\": \"64000\",\n \"CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR\": \"1\",\n \"CLAUDE_CODE_AUTO_COMPACT_WINDOW\": \"468000\",\n \"CLAUDE_CODE_NO_FLICKER\": \"1\"\n },\n \"permissions\": {\n \"defaultMode\": \"auto\",\n \"allow\": [\n \"Read\",\n \"Glob\",\n \"Grep\",\n \"WebFetch\",\n \"WebSearch\",\n \"Edit(./)\",\n \"Bash(git *)\",\n \"Bash(git add *)\",\n \"Bash(git commit *)\",\n \"Bash(git status *)\",\n \"Bash(git diff *)\",\n \"Bash(git log *)\",\n \"Bash(git branch *)\",\n \"Bash(git checkout *)\",\n \"Bash(git switch *)\",\n \"Bash(git stash *)\",\n \"Bash(git fetch *)\",\n \"Bash(git pull *)\",\n \"Bash(git tag *)\",\n \"Bash(git show *)\",\n \"Bash(git blame *)\",\n \"Bash(git worktree *)\",\n \"Bash(gh *)\",\n \"Bash(pnpm *)\",\n \"Bash(npm *)\",\n \"Bash(npx *)\",\n \"Bash(node *)\",\n \"Bash(docker *)\",\n \"Bash(docker-compose *)\",\n \"Bash(rtk *)\",\n \"Bash(ls *)\",\n \"Bash(cat *)\",\n \"Bash(find *)\",\n \"Bash(grep *)\",\n \"Bash(head *)\",\n \"Bash(tail *)\",\n \"Bash(wc *)\",\n \"Bash(sort *)\",\n \"Bash(uniq *)\",\n \"Bash(diff *)\",\n \"Bash(which *)\",\n \"Bash(pwd *)\",\n \"Bash(date *)\",\n \"Bash(mkdir *)\",\n \"Bash(basename *)\",\n \"Bash(dirname *)\",\n \"Bash(realpath *)\",\n \"Bash(jq *)\",\n \"Bash(curl *)\",\n \"Bash(tree *)\",\n \"Bash(sed *)\",\n \"Bash(awk *)\",\n \"Bash(cut *)\",\n \"Bash(tr *)\",\n \"Bash(tee *)\",\n \"Bash(echo *)\",\n \"Bash(printf *)\",\n \"Bash(env *)\",\n \"Bash(printenv *)\",\n \"Bash(uname *)\",\n \"Bash(file *)\",\n \"Bash(stat *)\",\n \"Bash(du *)\",\n \"Bash(id *)\",\n \"Bash(whoami *)\",\n \"Bash(php *)\",\n \"Bash(composer *)\",\n \"Bash(python3 *)\",\n \"Bash(python *)\",\n \"Bash(pip *)\",\n \"Bash(pip3 *)\"\n ],\n \"deny\": [\n \"Read(**/.env)\",\n \"Read(**/.env.local)\",\n \"Read(**/secrets/**)\",\n \"Read(**/*.key)\",\n \"Read(**/*.pem)\",\n \"Read(**/*.p12)\",\n \"Read(**/.credentials*)\",\n \"Edit(**/.env)\",\n \"Edit(**/.env.local)\",\n \"Edit(**/secrets/**)\",\n \"Bash(sudo *)\",\n \"Bash(rm -rf /)\",\n \"Bash(rm -rf / *)\",\n \"Bash(rm -rf ~)\",\n \"Bash(rm -rf ~ *)\",\n \"Bash(rm -rf $HOME)\",\n \"Bash(rm -rf $HOME *)\",\n \"Bash(> /dev *)\",\n \"Bash(dd if= *)\",\n \"Bash(mkfs *)\",\n \"Bash(eval *)\",\n \"Bash(chmod 777 *)\",\n \"Bash(chmod -R 777 *)\",\n \"Bash(git push --force origin main *)\",\n \"Bash(git push --force origin master *)\",\n \"Bash(git push -f origin main *)\",\n \"Bash(git push -f origin master *)\"\n ],\n \"ask\": [\n \"Bash(git clean *)\",\n \"Bash(docker volume rm *)\",\n \"Bash(docker system prune *)\"\n ]\n },\n \"hooks\": {\n \"SessionStart\": [\n {\n \"hooks\": [\n {\n \"type\": \"command\",\n \"command\": \"__DOCKS_KIT_BUN__\",\n \"args\": [\"__DOCKS_KIT_SESSION_START__\"],\n \"timeout\": 5\n }\n ]\n }\n ],\n \"Notification\": [\n {\n \"hooks\": [\n {\n \"type\": \"command\",\n \"command\": \"__DOCKS_KIT_BUN__\",\n \"args\": [\"__DOCKS_KIT_NOTIFY__\"],\n \"timeout\": 10,\n \"async\": true\n }\n ]\n }\n ],\n \"PreToolUse\": [\n {\n \"matcher\": \"Bash\",\n \"hooks\": [\n {\n \"type\": \"command\",\n \"command\": \"rtk hook claude\"\n }\n ]\n }\n ],\n \"PostToolUseFailure\": [\n {\n \"matcher\": \"Bash\",\n \"hooks\": [\n {\n \"type\": \"command\",\n \"command\": \"echo '{\\\"hookSpecificOutput\\\":{\\\"hookEventName\\\":\\\"PostToolUseFailure\\\",\\\"additionalContext\\\":\\\"Last bash command failed. Repository / file state may have shifted \\u2014 re-read affected files before retrying. If the failure is a missing dependency or env mismatch, surface it to the user rather than retrying blindly.\\\"}}'\",\n \"timeout\": 5\n }\n ]\n }\n ],\n \"SubagentStop\": [\n {\n \"hooks\": [\n {\n \"type\": \"prompt\",\n \"prompt\": \"You are a quality gate for subagent outputs in a multi-agent code-analysis pipeline.\\n\\nEvaluate the subagent's `last_assistant_message` field (in the JSON below) against these requirements:\\n\\n1. ALLOW (return `{}`): Mode-selection or no-issues responses. Examples: \\\"Which mode do you prefer\\\", \\\"select an option\\\", \\\"no issues / problems / violations / blockers found\\\".\\n\\n2. ALLOW (return `{}`): Output contains at least one concrete file:line citation \\u2014 e.g. `src/auth.ts:42`, `lib/db.ts:100-115`, or path references that include line numbers.\\n\\n3. BLOCK (return `{\\\"decision\\\":\\\"block\\\",\\\"reason\\\":\\\"<one-line explanation>\\\"}`): Output claims about code or findings WITHOUT concrete file:line citations. Vague references like \\\"the auth handler\\\" or \\\"near the database code\\\" are not acceptable as the only evidence.\\n\\nSubagent invocation JSON:\\n$ARGUMENTS\\n\\nReturn ONLY the JSON decision (no commentary, no markdown fences).\",\n \"timeout\": 30\n }\n ]\n }\n ]\n },\n \"statusLine\": {\n \"type\": \"command\",\n \"command\": \"__DOCKS_KIT_STATUSLINE__\",\n \"refreshInterval\": 5\n },\n \"enabledPlugins\": {\n \"docks@docks\": true,\n \"plan-lifecycle@docks\": true,\n \"effect-kit@docks\": true,\n \"php-lsp@claude-plugins-official\": true,\n \"typescript-lsp@claude-plugins-official\": true\n },\n \"extraKnownMarketplaces\": {\n \"docks\": {\n \"source\": {\n \"source\": \"github\",\n \"repo\": \"DocksDocks/docks\"\n }\n }\n },\n \"alwaysThinkingEnabled\": true,\n \"showThinkingSummaries\": true,\n \"viewMode\": \"default\",\n \"theme\": \"dark-daltonized\",\n \"skipDangerousModePermissionPrompt\": true\n}\n",
13
13
  "SoT/.claude/bin/statusline.mjs": "const ESC = \"\\x1b[\"\nconst PIPE = `${ESC}90m | ${ESC}0m`\nconst DOT = `${ESC}90m • ${ESC}0m`\nconst DIM = `${ESC}2m${ESC}38;2;156;162;175m`\n\nfunction isRecord(value) {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n\nfunction finitePercentage(value) {\n return typeof value === \"number\" && Number.isFinite(value) && value >= 0 && value <= 100\n ? value\n : undefined\n}\n\nfunction roundHalfEven(value) {\n const lower = Math.floor(value)\n const fraction = value - lower\n if (fraction < 0.5) return lower\n if (fraction > 0.5) return lower + 1\n return lower % 2 === 0 ? lower : lower + 1\n}\n\nfunction pathBasename(path) {\n const parts = path.split(/[\\\\/]+/).filter((part) => part !== \"\")\n return parts.at(-1) ?? \"\"\n}\n\nfunction modelName(input) {\n const model = isRecord(input.model) && typeof input.model.display_name === \"string\"\n ? input.model.display_name\n : \"\"\n const suffix = model.indexOf(\" (\")\n return suffix === -1 ? model : model.slice(0, suffix)\n}\n\nfunction workingDirectory(input, cwd) {\n if (isRecord(input.workspace) && typeof input.workspace.current_dir === \"string\" && input.workspace.current_dir !== \"\") {\n return input.workspace.current_dir\n }\n if (typeof input.cwd === \"string\" && input.cwd !== \"\") return input.cwd\n return cwd\n}\n\nfunction compactWindow(env, total) {\n const raw = env.CLAUDE_CODE_AUTO_COMPACT_WINDOW\n if (typeof raw !== \"string\" || !/^[0-9]+$/.test(raw)) return total\n const parsed = Number(raw)\n return Number.isSafeInteger(parsed) && parsed >= 1000 && parsed < total ? parsed : total\n}\n\nfunction formatTokensK(value) {\n if (value < 1000) return `${value}k`\n if (value % 1000 === 0) return `${value / 1000}M`\n return `${(roundHalfEven(value / 100) / 10).toFixed(1)}M`\n}\n\nfunction contextSegment(input, env) {\n if (!isRecord(input.context_window)) return \"\"\n const used = finitePercentage(input.context_window.used_percentage)\n if (used === undefined) return \"\"\n\n const total = input.context_window.context_window_size\n if (typeof total !== \"number\" || !Number.isFinite(total) || total <= 0) {\n return `${ESC}38;2;130;160;230mctx ${roundHalfEven(used)}%${ESC}0m`\n }\n\n const usedK = roundHalfEven((used / 100) * (total / 1000))\n const effectiveK = Math.trunc(compactWindow(env, total) / 1000)\n if (effectiveK <= 0) return `${ESC}38;2;130;160;230mctx ${roundHalfEven(used)}%${ESC}0m`\n const effectivePercentage = roundHalfEven((usedK / effectiveK) * 100)\n return `${ESC}38;2;130;160;230mctx ${effectivePercentage}%${ESC}0m ${DIM}(${formatTokensK(usedK)}/${formatTokensK(effectiveK)})${ESC}0m`\n}\n\nfunction resetDelta(value, nowMs) {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < 0) return \"\"\n const seconds = Math.trunc(value) - Math.floor(nowMs / 1000)\n if (seconds <= 0) return \"now\"\n const days = Math.trunc(seconds / 86_400)\n if (days > 0) return `${days}d`\n const hours = Math.trunc((seconds % 86_400) / 3_600)\n if (hours > 0) return `${hours}h`\n return `${Math.trunc((seconds % 3_600) / 60)}m`\n}\n\nfunction quotaWindow(value, label, color, nowMs) {\n if (!isRecord(value)) return \"\"\n const used = finitePercentage(value.used_percentage)\n if (used === undefined) return \"\"\n const delta = resetDelta(value.resets_at, nowMs)\n const reset = delta === \"\" ? \"\" : ` ${DIM}(${delta})${ESC}0m`\n return `${ESC}38;2;${color}m${label} ${roundHalfEven(used)}%${ESC}0m${reset}`\n}\n\nfunction decodeStdout(result) {\n const stdout = result?.stdout\n if (stdout === undefined || stdout === null) return \"\"\n return typeof stdout === \"string\" ? stdout : stdout.toString()\n}\n\nfunction resolveBranch(directory, which, spawnSync) {\n const git = which(\"git\")\n if (typeof git !== \"string\" || git === \"\") return \"\"\n const commands = [\n [git, \"-C\", directory, \"symbolic-ref\", \"--short\", \"HEAD\"],\n [git, \"-C\", directory, \"rev-parse\", \"--short\", \"HEAD\"]\n ]\n for (const command of commands) {\n try {\n const result = spawnSync(command, { stdin: \"ignore\", stdout: \"pipe\", stderr: \"ignore\" })\n if (result?.success === true) return decodeStdout(result).trim()\n } catch {\n return \"\"\n }\n }\n return \"\"\n}\n\nexport function formatStatusline(input, options = {}) {\n if (!isRecord(input)) return \"\"\n const env = isRecord(options.env) ? options.env : process.env\n const nowMs = typeof options.nowMs === \"number\" ? options.nowMs : Date.now()\n const cwd = typeof options.cwd === \"string\" ? options.cwd : process.cwd()\n const branch = typeof options.branch === \"string\" ? options.branch : \"\"\n const directory = workingDirectory(input, cwd)\n\n const model = `${ESC}38;5;208m${ESC}1m${modelName(input)}${ESC}22m${ESC}0m`\n const folder = `${ESC}1m${ESC}38;2;76;208;222m${pathBasename(directory)}${ESC}22m${ESC}0m`\n const branchSegment = branch === \"\" ? \"\" : `${DOT}${ESC}1m${ESC}38;2;192;103;222m${branch}${ESC}22m${ESC}0m`\n const context = contextSegment(input, env)\n\n const rateLimits = isRecord(input.rate_limits) ? input.rate_limits : {}\n const fiveHour = quotaWindow(rateLimits.five_hour, \"5h\", \"100;200;200\", nowMs)\n const sevenDay = quotaWindow(rateLimits.seven_day, \"7d\", \"230;180;90\", nowMs)\n const quota = fiveHour === \"\" && sevenDay === \"\"\n ? \"\"\n : `${PIPE}${fiveHour}${fiveHour !== \"\" && sevenDay !== \"\" ? DOT : \"\"}${sevenDay}`\n\n return `${model}${PIPE}${folder}${branchSegment}${context === \"\" ? \"\" : `${PIPE}${context}`}${quota}`\n}\n\nexport async function main(options = {}) {\n const readStdin = options.readStdin ?? (() => Bun.stdin.text())\n const writeStdout = options.writeStdout ?? ((value) => process.stdout.write(value))\n let raw\n try {\n raw = await readStdin()\n } catch {\n return 0\n }\n\n let input\n try {\n input = JSON.parse(raw)\n } catch {\n return 0\n }\n if (!isRecord(input)) return 0\n\n const cwd = typeof options.cwd === \"string\" ? options.cwd : process.cwd()\n const directory = workingDirectory(input, cwd)\n const branch = resolveBranch(\n directory,\n options.which ?? ((name) => Bun.which(name)),\n options.spawnSync ?? ((argv, spawnOptions) => Bun.spawnSync(argv, spawnOptions))\n )\n const output = formatStatusline(input, {\n env: options.env ?? process.env,\n nowMs: options.nowMs ?? Date.now(),\n cwd,\n branch\n })\n if (output !== \"\") writeStdout(`${output}\\n`)\n return 0\n}\n\nif (import.meta.main) process.exit(await main())\n",
14
14
  "SoT/.claude/bin/session-start.mjs": "import { readFileSync } from \"node:fs\"\nimport { homedir } from \"node:os\"\n\nfunction isRecord(value) {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n\nfunction nonEmpty(value, fallback) {\n return typeof value === \"string\" && value !== \"\" ? value : fallback\n}\n\nfunction pad(value) {\n return String(value).padStart(2, \"0\")\n}\n\nfunction configuredEffort(home, readText) {\n try {\n const parsed = JSON.parse(readText(`${home}/.claude/settings.json`))\n return isRecord(parsed) ? nonEmpty(parsed.effortLevel, \"default\") : \"default\"\n } catch {\n return \"default\"\n }\n}\n\nfunction localZone(now) {\n const part = new Intl.DateTimeFormat(\"en-US\", { timeZoneName: \"short\" })\n .formatToParts(now)\n .find((value) => value.type === \"timeZoneName\")\n return part?.value ?? \"\"\n}\n\nexport function sessionStartLines(options = {}) {\n const env = isRecord(options.env) ? options.env : process.env\n const now = options.now instanceof Date ? options.now : new Date()\n const home = typeof options.home === \"string\" ? options.home : homedir()\n const readText = options.readText ?? ((path) => readFileSync(path, \"utf8\"))\n const weekday = new Intl.DateTimeFormat(\"en-US\", { weekday: \"long\" }).format(now)\n const date = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`\n const time = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`\n const effort = nonEmpty(env.CLAUDE_CODE_EFFORT_LEVEL, configuredEffort(home, readText))\n const context = env.CLAUDE_CODE_DISABLE_1M_CONTEXT === \"1\" ? \"200K\" : \"1M\"\n const compactWindow = nonEmpty(env.CLAUDE_CODE_AUTO_COMPACT_WINDOW, \"full\")\n const subagent = nonEmpty(env.CLAUDE_CODE_SUBAGENT_MODEL, \"default\")\n return [\n `[CONTEXT] Current date: ${weekday}, ${date} ${time} ${localZone(now)}`,\n `[CONFIG] Context: ${context} | Compact-window: ${compactWindow} | Effort: ${effort} | Thinking: adaptive | Subagent: ${subagent}`\n ]\n}\n\nexport async function main(options = {}) {\n const writeStdout = options.writeStdout ?? ((value) => process.stdout.write(value))\n writeStdout(`${sessionStartLines(options).join(\"\\n\")}\\n`)\n return 0\n}\n\nif (import.meta.main) process.exit(await main())\n",
15
15
  "SoT/.claude/bin/notify.mjs": "const DEFAULT_SOUND = `${import.meta.dir}/../notification.mp3`\n\nexport function selectPlayer(options = {}) {\n const platform = options.platform ?? process.platform\n const sound = options.sound ?? DEFAULT_SOUND\n const which = options.which ?? ((name) => Bun.which(name))\n if (platform === \"darwin\") {\n const afplay = which(\"afplay\")\n if (typeof afplay === \"string\" && afplay !== \"\") return [afplay, sound]\n }\n const ffplay = which(\"ffplay\")\n if (typeof ffplay === \"string\" && ffplay !== \"\") {\n return [ffplay, \"-nodisp\", \"-autoexit\", \"-loglevel\", \"quiet\", sound]\n }\n const paplay = which(\"paplay\")\n if (typeof paplay === \"string\" && paplay !== \"\") return [paplay, sound]\n const aplay = which(\"aplay\")\n if (typeof aplay === \"string\" && aplay !== \"\") return [aplay, \"-q\", sound]\n return undefined\n}\n\nexport async function main(options = {}) {\n const sound = options.sound ?? DEFAULT_SOUND\n const fileExists = options.fileExists ?? ((path) => Bun.file(path).exists())\n if (!await fileExists(sound)) return 0\n const command = selectPlayer({ ...options, sound })\n if (command === undefined) return 0\n const spawnSync = options.spawnSync ?? ((argv, spawnOptions) => Bun.spawnSync(argv, spawnOptions))\n const result = spawnSync(command, { stdin: \"ignore\", stdout: \"ignore\", stderr: \"ignore\" })\n return typeof result.exitCode === \"number\" ? result.exitCode : 1\n}\n\nif (import.meta.main) process.exit(await main())\n",
16
16
  "SoT/.codex/AGENTS.md": "# AGENTS.md\n\n## Research Before Implementation\n\nBefore writing or modifying code that uses an API, hook, method, or config surface you have not verified in this session, research current documentation first.\n\nResearch workflow:\n1. Prefer official documentation and primary sources for the specific library, framework, or API.\n2. If a local docs or MCP tool is available, use it before broad web search.\n3. Only then proceed to implementation.\n\nResearch when:\n- Installing or configuring a dependency.\n- Using an API, hook, method, or pattern not verified in this session.\n- Upgrading or migrating between versions.\n- Any task where relying on memory could cause stale syntax or behavior.\n\nDo not:\n- Assume API signatures, method names, or config options from memory.\n- Generate framework code without checking current docs first.\n- Skip research because the library seems familiar.\n\n<constraint>\nResearch the codebase before editing. Never change code you have not read.\n</constraint>\n\n## Agentic Harness Heuristics\n\nModel-agnostic operating rules for coding-agent work.\n\n1. Persistence. Keep going until the user's request is actually handled. Only yield when the problem is solved or a concrete blocker is identified. Resolve in the fewest useful tool loops — once you can answer the core request with evidence, answer. Before ending a turn, check the last paragraph: if it is a plan, a question you can answer yourself, or a promise of work not done, do that work now.\n2. Default to parallel. When multiple reads, searches, inspections, or independent checks can run without depending on each other, run them together.\n3. Multi-pass search. First-pass search often misses — vary the wording before concluding something does not exist.\n4. Trace symbols. Before modifying a symbol, trace its definition and usages. Do not infer behavior from one call site.\n5. Linter-loop 3-strike rule. Do not loop more than 3 times fixing the same lint/test failure without reassessing the diagnosis.\n6. Read-before-edit TTL. If you have not read a file recently, re-read it before editing. User edits can make cached context stale.\n7. Big-file rule. For files over 1000 lines, prefer targeted search plus scoped reads over whole-file reads.\n8. Task hygiene. Track meaningful deliverables, not operational sub-steps. Mark work complete as soon as it is done.\n9. Literal-instruction rule. Treat explicit user requirements as checklists with success criteria. Do not silently broaden scope.\n10. Context hygiene. Prefer a fresh session at task boundaries over carrying stale context; preserve useful state before quality decays. Never stop, summarize, or suggest a new session on account of context limits.\n11. Autonomy calibration. For minor choices (naming, formatting, defaults, equivalent approaches), pick a reasonable option and note it — do not ask. Ask first only for scope changes, destructive actions, or decisions that change the deliverable. When the user is describing a problem or asking a question rather than requesting a change, the deliverable is your assessment — report findings and stop; do not apply fixes until asked.\n12. Capability triggering. Search or fetch current documentation when the answer depends on current or version-specific information. When work fans out across independent items, parallelize or delegate; never delegate work you can complete directly. For verification, prefer a fresh-context check over self-critique. On long tasks, keep running notes and re-read them between phases.\n\n<constraint>\nTreat these heuristics as protocol. If a turn violates an applicable rule, self-correct before continuing.\n</constraint>\n\nReuse before invention: inventory existing code, components, conventions, and dependencies; extend them instead of creating a parallel pattern. Load only the narrow skills supported by the task and repository evidence. If a request establishes a new React/Tailwind system and no convention exists, prefer current shadcn/ui `base-*` components backed by Base UI; otherwise preserve the existing stack. Treat probe, production access, publish, push, release, and deploy as literal current-request effects—never infer external authority from a plan, schedule, review, or old receipt.\n\nFor Docks plan reviews, cross-company review is standing-authorized; do not ask for export consent. This never overrides a host or platform security denial.\n\nUse a narrow-to-broad verification ladder: direct acceptance while iterating, focused regressions next, and one full CI at the pre-commit or release boundary. Reuse still-matching evidence; rerun full CI only after a relevant edit invalidates it.\n\n<constraint>\nNo secrets in committed config. Treat plugin marketplaces, installers, and downloaded artifacts as untrusted until verified.\n</constraint>\n",
17
- "SoT/.codex/config.toml": "model = \"gpt-5.6-sol\"\nmodel_reasoning_effort = \"high\"\nplan_mode_reasoning_effort = \"high\"\nmodel_reasoning_summary = \"concise\"\nmodel_verbosity = \"low\"\npersonality = \"pragmatic\"\nweb_search = \"live\"\nproject_doc_max_bytes = 131072\napproval_policy = \"on-request\"\nsandbox_mode = \"workspace-write\"\napprovals_reviewer = \"auto_review\"\n\n[sandbox_workspace_write]\nnetwork_access = true\n\n[features]\nmemories = true\n\n[memories]\ndedicated_tools = true\nmax_rollout_age_days = 30\n\n[agents]\nmax_threads = 12\nmax_depth = 2\n\n[tui]\nstatus_line_use_colors = true\nstatus_line = [\n \"model-with-reasoning\",\n \"current-dir\",\n \"git-branch\",\n \"context-used\",\n \"five-hour-limit\",\n \"weekly-limit\",\n]\n\n[plugins.\"docks@docks\"]\nenabled = true\n\n[plugins.\"session-relay@docks\"]\nenabled = true\n\n[plugins.\"effect-kit@docks\"]\nenabled = true\n",
18
- "SoT/.codex/plugins/marketplace.json": "{\n \"name\": \"docks\",\n \"interface\": {\n \"displayName\": \"DocksDocks\"\n },\n \"plugins\": [\n {\n \"name\": \"docks\",\n \"source\": {\n \"source\": \"git-subdir\",\n \"url\": \"https://github.com/DocksDocks/docks.git\",\n \"path\": \"./plugins/docks\",\n \"ref\": \"main\"\n },\n \"policy\": {\n \"installation\": \"AVAILABLE\",\n \"authentication\": \"ON_INSTALL\"\n },\n \"category\": \"Productivity\"\n },\n {\n \"name\": \"session-relay\",\n \"source\": {\n \"source\": \"git-subdir\",\n \"url\": \"https://github.com/DocksDocks/docks.git\",\n \"path\": \"./plugins/session-relay\",\n \"ref\": \"main\"\n },\n \"policy\": {\n \"installation\": \"AVAILABLE\",\n \"authentication\": \"ON_INSTALL\"\n },\n \"category\": \"Productivity\"\n },\n {\n \"name\": \"effect-kit\",\n \"source\": {\n \"source\": \"git-subdir\",\n \"url\": \"https://github.com/DocksDocks/docks.git\",\n \"path\": \"./plugins/effect-kit\",\n \"ref\": \"main\"\n },\n \"policy\": {\n \"installation\": \"AVAILABLE\",\n \"authentication\": \"ON_INSTALL\"\n },\n \"category\": \"Productivity\"\n }\n ]\n}\n",
17
+ "SoT/.codex/config.toml": "model = \"gpt-5.6-sol\"\nmodel_reasoning_effort = \"high\"\nplan_mode_reasoning_effort = \"high\"\nmodel_reasoning_summary = \"concise\"\nmodel_verbosity = \"low\"\npersonality = \"pragmatic\"\nweb_search = \"live\"\nproject_doc_max_bytes = 131072\napproval_policy = \"on-request\"\nsandbox_mode = \"workspace-write\"\napprovals_reviewer = \"auto_review\"\n\n[sandbox_workspace_write]\nnetwork_access = true\n\n[features]\nmemories = true\n\n[memories]\ndedicated_tools = true\nmax_rollout_age_days = 30\n\n[agents]\nmax_threads = 12\nmax_depth = 2\n\n[tui]\nstatus_line_use_colors = true\nstatus_line = [\n \"model-with-reasoning\",\n \"current-dir\",\n \"git-branch\",\n \"context-used\",\n \"five-hour-limit\",\n \"weekly-limit\",\n]\n\n[plugins.\"docks@docks\"]\nenabled = true\n\n[plugins.\"plan-lifecycle@docks\"]\nenabled = true\n\n[plugins.\"effect-kit@docks\"]\nenabled = true\n",
18
+ "SoT/.codex/plugins/marketplace.json": "{\n \"name\": \"docks\",\n \"interface\": {\n \"displayName\": \"DocksDocks\"\n },\n \"plugins\": [\n {\n \"name\": \"docks\",\n \"source\": {\n \"source\": \"git-subdir\",\n \"url\": \"https://github.com/DocksDocks/docks.git\",\n \"path\": \"./plugins/docks\",\n \"ref\": \"main\"\n },\n \"policy\": {\n \"installation\": \"AVAILABLE\",\n \"authentication\": \"ON_INSTALL\"\n },\n \"category\": \"Productivity\"\n },\n {\n \"name\": \"effect-kit\",\n \"source\": {\n \"source\": \"git-subdir\",\n \"url\": \"https://github.com/DocksDocks/docks.git\",\n \"path\": \"./plugins/effect-kit\",\n \"ref\": \"main\"\n },\n \"policy\": {\n \"installation\": \"AVAILABLE\",\n \"authentication\": \"ON_INSTALL\"\n },\n \"category\": \"Productivity\"\n },\n {\n \"name\": \"plan-lifecycle\",\n \"source\": {\n \"source\": \"git-subdir\",\n \"url\": \"https://github.com/DocksDocks/docks.git\",\n \"path\": \"./plugins/plan-lifecycle\",\n \"ref\": \"main\"\n },\n \"policy\": {\n \"installation\": \"AVAILABLE\",\n \"authentication\": \"ON_INSTALL\"\n },\n \"category\": \"Productivity\"\n }\n ]\n}\n",
19
19
  "SoT/.codex/rules/docks.rules": "prefix_rule(pattern=[\"pwd\"], decision=\"allow\")\nprefix_rule(pattern=[\"ls\"], decision=\"allow\")\nprefix_rule(pattern=[\"cat\"], decision=\"allow\")\nprefix_rule(pattern=[\"head\"], decision=\"allow\")\nprefix_rule(pattern=[\"tail\"], decision=\"allow\")\nprefix_rule(pattern=[\"wc\"], decision=\"allow\")\nprefix_rule(pattern=[\"nl\"], decision=\"allow\")\nprefix_rule(pattern=[\"grep\"], decision=\"allow\")\nprefix_rule(pattern=[\"sort\"], decision=\"allow\")\nprefix_rule(pattern=[\"uniq\"], decision=\"allow\")\nprefix_rule(pattern=[\"diff\"], decision=\"allow\")\nprefix_rule(pattern=[\"which\"], decision=\"allow\")\nprefix_rule(pattern=[\"date\"], decision=\"allow\")\nprefix_rule(pattern=[\"basename\"], decision=\"allow\")\nprefix_rule(pattern=[\"dirname\"], decision=\"allow\")\nprefix_rule(pattern=[\"realpath\"], decision=\"allow\")\nprefix_rule(pattern=[\"readlink\"], decision=\"allow\")\nprefix_rule(pattern=[\"jq\"], decision=\"allow\")\nprefix_rule(pattern=[\"tree\"], decision=\"allow\")\nprefix_rule(pattern=[\"cut\"], decision=\"allow\")\nprefix_rule(pattern=[\"tr\"], decision=\"allow\")\nprefix_rule(pattern=[\"echo\"], decision=\"allow\")\nprefix_rule(pattern=[\"printf\"], decision=\"allow\")\nprefix_rule(pattern=[\"printenv\"], decision=\"allow\")\nprefix_rule(pattern=[\"uname\"], decision=\"allow\")\nprefix_rule(pattern=[\"file\"], decision=\"allow\")\nprefix_rule(pattern=[\"stat\"], decision=\"allow\")\nprefix_rule(pattern=[\"du\"], decision=\"allow\")\nprefix_rule(pattern=[\"id\"], decision=\"allow\")\nprefix_rule(pattern=[\"whoami\"], decision=\"allow\")\n\nprefix_rule(pattern=[\"git\", \"status\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"diff\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"log\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"show\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"blame\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"rev-parse\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"ls-files\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"grep\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"ls-tree\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"branch\", \"--show-current\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"branch\", \"-vv\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"mv\"], decision=\"allow\")\n\nprefix_rule(pattern=[\"gh\", \"pr\", \"view\"], decision=\"allow\")\nprefix_rule(pattern=[\"gh\", \"pr\", \"list\"], decision=\"allow\")\nprefix_rule(pattern=[\"gh\", \"pr\", \"diff\"], decision=\"allow\")\nprefix_rule(pattern=[\"gh\", \"pr\", \"status\"], decision=\"allow\")\nprefix_rule(pattern=[\"gh\", \"pr\", \"checks\"], decision=\"allow\")\n\nprefix_rule(pattern=[\"docker\", \"ps\"], decision=\"allow\")\n\nprefix_rule(pattern=[\"git\", \"push\"], decision=\"prompt\")\nprefix_rule(pattern=[\"git\", \"reset\"], decision=\"prompt\")\nprefix_rule(pattern=[\"git\", \"clean\"], decision=\"prompt\")\nprefix_rule(pattern=[\"git\", \"merge\"], decision=\"prompt\")\nprefix_rule(pattern=[\"git\", \"rebase\"], decision=\"prompt\")\nprefix_rule(pattern=[\"git\", \"checkout\"], decision=\"prompt\")\n\nprefix_rule(pattern=[\"rm\"], decision=\"prompt\")\nprefix_rule(pattern=[\"mv\"], decision=\"prompt\")\nprefix_rule(pattern=[\"chmod\"], decision=\"prompt\")\nprefix_rule(pattern=[\"chown\"], decision=\"prompt\")\nprefix_rule(pattern=[\"kill\"], decision=\"prompt\")\nprefix_rule(pattern=[\"pkill\"], decision=\"prompt\")\n\nprefix_rule(pattern=[\"npm\", \"install\"], decision=\"prompt\")\nprefix_rule(pattern=[\"npm\", \"uninstall\"], decision=\"prompt\")\nprefix_rule(pattern=[\"pnpm\", \"add\"], decision=\"prompt\")\nprefix_rule(pattern=[\"pnpm\", \"remove\"], decision=\"prompt\")\nprefix_rule(pattern=[\"yarn\", \"add\"], decision=\"prompt\")\nprefix_rule(pattern=[\"yarn\", \"remove\"], decision=\"prompt\")\nprefix_rule(pattern=[\"pip\", \"install\"], decision=\"prompt\")\nprefix_rule(pattern=[\"pip\", \"uninstall\"], decision=\"prompt\")\nprefix_rule(pattern=[\"pip3\", \"install\"], decision=\"prompt\")\nprefix_rule(pattern=[\"pip3\", \"uninstall\"], decision=\"prompt\")\n\nprefix_rule(pattern=[\"docker\", \"run\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker\", \"rm\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker\", \"stop\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker\", \"volume\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker\", \"system\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker\", \"compose\", \"up\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker\", \"compose\", \"down\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker\", \"compose\", \"rm\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker\", \"compose\", \"stop\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker-compose\", \"up\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker-compose\", \"down\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker-compose\", \"rm\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker-compose\", \"stop\"], decision=\"prompt\")\n\n# `tail -f`/`--follow` never returns and hangs the agent (bare `tail` stays allowed above).\nprefix_rule(pattern=[\"tail\", \"-f\"], decision=\"prompt\")\nprefix_rule(pattern=[\"tail\", \"--follow\"], decision=\"prompt\")\n# rg and `sed -n` are prompt, NOT allow: argv-prefix matching cannot gate their\n# code-exec forms (rg --pre=CMD or a reordered --pre; sed -n 'e CMD' / -ni) while a\n# shorter allow prefix would auto-approve the whole command.\nprefix_rule(pattern=[\"rg\"], decision=\"prompt\")\nprefix_rule(pattern=[\"sed\", \"-n\"], decision=\"prompt\")\nprefix_rule(pattern=[\"find\"], decision=\"prompt\")\nprefix_rule(pattern=[\"sed\", \"-i\"], decision=\"prompt\")\nprefix_rule(pattern=[\"sed\", \"--in-place\"], decision=\"prompt\")\nprefix_rule(pattern=[\"awk\"], decision=\"prompt\")\nprefix_rule(pattern=[\"xargs\"], decision=\"prompt\")\nprefix_rule(pattern=[\"tee\"], decision=\"prompt\")\nprefix_rule(pattern=[\"curl\"], decision=\"prompt\")\nprefix_rule(pattern=[\"env\"], decision=\"prompt\")\n\nprefix_rule(pattern=[\"sudo\"], decision=\"forbidden\")\nprefix_rule(pattern=[\"eval\"], decision=\"forbidden\")\nprefix_rule(pattern=[\"mkfs\"], decision=\"forbidden\")\nprefix_rule(pattern=[\"dd\"], decision=\"forbidden\")\nprefix_rule(pattern=[\"git\", \"push\", \"--force\", \"origin\", \"main\"], decision=\"forbidden\")\nprefix_rule(pattern=[\"git\", \"push\", \"--force\", \"origin\", \"master\"], decision=\"forbidden\")\nprefix_rule(pattern=[\"git\", \"push\", \"-f\", \"origin\", \"main\"], decision=\"forbidden\")\nprefix_rule(pattern=[\"git\", \"push\", \"-f\", \"origin\", \"master\"], decision=\"forbidden\")\n"
20
20
  } as const
21
21
 
@@ -40,4 +40,4 @@ export const GENERATED_PAYLOAD_PATHS = [
40
40
  "notification.mp3"
41
41
  ] as const
42
42
 
43
- export const GENERATED_PAYLOAD_HASH = "2eaa66dbce6cd0b9fac9ad0ec5ee6829cf5cf905bd395c0e71f1dda72bfe8bd4"
43
+ export const GENERATED_PAYLOAD_HASH = "9bff028c848a2c0de74c1de7e4197dd146f55f095c88aedeec4a7665ff3564e2"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docks-kit",
3
- "version": "0.14.0",
3
+ "version": "0.14.2",
4
4
  "description": "Portable AI coding agent config kit — SoT sync engine + typed CLI for Claude Code, Codex, and universal agent skills",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,262 +0,0 @@
1
- import { spawnSync } from "node:child_process"
2
- import { createHash, randomBytes } from "node:crypto"
3
- import {
4
- chmodSync,
5
- existsSync,
6
- mkdirSync,
7
- readFileSync,
8
- renameSync,
9
- rmSync,
10
- statSync
11
- } from "node:fs"
12
- import { dirname, join } from "node:path"
13
-
14
- import { isObject, parseJson, type Json } from "./jq"
15
- import type { Ctx } from "./index"
16
- import { ExitError } from "./parseArgs"
17
- import { payloadText } from "../payload"
18
-
19
- const REPOSITORY = "DocksDocks/docks"
20
- const PLUGIN_ID = "session-relay@docks"
21
- const INSTALL_PATH = "~/.local/bin/session-relay"
22
- // Intel macOS was retired as of Session Relay 0.16.0: the parent release
23
- // publishes exactly three native binaries, so darwin/x64 hosts fail closed in
24
- // sessionRelayTarget before any manifest parse or download.
25
- const TARGETS = [
26
- "x86_64-unknown-linux-musl",
27
- "aarch64-unknown-linux-musl",
28
- "aarch64-apple-darwin"
29
- ] as const
30
-
31
- export type SessionRelayTarget = typeof TARGETS[number]
32
-
33
- export interface SessionRelayManifest {
34
- readonly kind: "managed-release"
35
- readonly policy: "exact"
36
- readonly verified: string
37
- readonly repository: typeof REPOSITORY
38
- readonly tag: string
39
- readonly plugin_id: typeof PLUGIN_ID
40
- readonly plugin_version: string
41
- readonly install_path: typeof INSTALL_PATH
42
- readonly assets: Readonly<Record<SessionRelayTarget, string>>
43
- }
44
-
45
- export interface SessionRelayInstallOps {
46
- readonly download: (url: string, destination: string) => boolean
47
- readonly chmod: (path: string, mode: number) => void
48
- readonly runVersion: (path: string) => { readonly ok: boolean; readonly stdout: string }
49
- readonly rename: (from: string, to: string) => void
50
- readonly uniqueSuffix: () => string
51
- }
52
-
53
- export interface SessionRelayInstallInput {
54
- readonly home: string
55
- readonly dryRun: boolean
56
- readonly platform: string
57
- readonly arch: string
58
- readonly manifestText: string
59
- readonly log: (line: string) => void
60
- readonly error?: (line: string) => void
61
- }
62
-
63
- function fail(input: SessionRelayInstallInput, message: string): never {
64
- input.error?.(message)
65
- const error = new ExitError(1)
66
- error.message = message
67
- throw error
68
- }
69
-
70
- function exactKeys(value: { [key: string]: Json }, expected: ReadonlyArray<string>, label: string): void {
71
- const actual = Object.keys(value).sort()
72
- const wanted = [...expected].sort()
73
- if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
74
- throw new Error(`${label} violates the closed Session Relay manifest schema`)
75
- }
76
- }
77
-
78
- export function parseSessionRelayManifest(text: string): SessionRelayManifest {
79
- const value = parseJson(text)
80
- if (value === undefined || !isObject(value)) throw new Error("Session Relay manifest is not a JSON object")
81
- exactKeys(
82
- value,
83
- ["kind", "policy", "verified", "repository", "tag", "plugin_id", "plugin_version", "install_path", "assets"],
84
- "Session Relay manifest"
85
- )
86
- const version = value["verified"]
87
- if (typeof version !== "string" || !/^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/.test(version)) {
88
- throw new Error("Session Relay manifest verified must be a canonical stable SemVer core")
89
- }
90
- const expected = {
91
- kind: "managed-release",
92
- policy: "exact",
93
- verified: version,
94
- repository: REPOSITORY,
95
- tag: `session-relay--v${version}`,
96
- plugin_id: PLUGIN_ID,
97
- plugin_version: version,
98
- install_path: INSTALL_PATH
99
- } as const
100
- for (const [key, wanted] of Object.entries(expected)) {
101
- if (value[key] !== wanted) throw new Error(`Session Relay manifest ${key.replaceAll("_", " ")} must be ${wanted}`)
102
- }
103
- const assets = value["assets"]
104
- if (!isObject(assets)) throw new Error("Session Relay manifest assets must be an object")
105
- exactKeys(assets, TARGETS, "Session Relay manifest assets target set")
106
- const parsedAssets = {} as Record<SessionRelayTarget, string>
107
- for (const target of TARGETS) {
108
- const digest = assets[target]
109
- if (typeof digest !== "string" || !/^[0-9a-f]{64}$/.test(digest)) {
110
- throw new Error(`Session Relay manifest digest for ${target} must be 64 lowercase hex characters`)
111
- }
112
- parsedAssets[target] = digest
113
- }
114
- return { ...expected, assets: parsedAssets }
115
- }
116
-
117
- function manifestEntry(): string {
118
- const document = parseJson(payloadText("SoT/toolchain.json"))
119
- const tools = document !== undefined && isObject(document) ? document["tools"] : undefined
120
- const entry = tools !== undefined && isObject(tools) ? tools["session-relay"] : undefined
121
- if (entry === undefined) throw new Error("Embedded toolchain manifest has no session-relay entry")
122
- return JSON.stringify(entry)
123
- }
124
-
125
- export function sessionRelayTarget(platform: string, arch: string): SessionRelayTarget {
126
- if (platform === "linux" && arch === "x64") return "x86_64-unknown-linux-musl"
127
- if (platform === "linux" && arch === "arm64") return "aarch64-unknown-linux-musl"
128
- if (platform === "darwin" && arch === "arm64") return "aarch64-apple-darwin"
129
- throw new Error(`Unsupported host for Session Relay CLI: ${platform}/${arch}; supported: linux x64|arm64, darwin arm64`)
130
- }
131
-
132
- function trimOneLineEnding(text: string): string {
133
- if (text.endsWith("\r\n")) return text.slice(0, -2)
134
- if (text.endsWith("\n")) return text.slice(0, -1)
135
- return text
136
- }
137
-
138
- function exactVersion(ops: SessionRelayInstallOps, path: string, version: string): boolean {
139
- const result = ops.runVersion(path)
140
- return result.ok && trimOneLineEnding(result.stdout) === `session-relay ${version}`
141
- }
142
-
143
- function selectedChecksum(text: string, assetName: string): string {
144
- const selected = text.split("\n").filter((line) => line.endsWith(` ${assetName}`))
145
- if (selected.length !== 1 || !new RegExp(`^[0-9a-f]{64} ${assetName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`).test(selected[0]!)) {
146
- throw new Error(`SHA256SUMS must contain exactly one canonical row for ${assetName}`)
147
- }
148
- return selected[0]!.slice(0, 64)
149
- }
150
-
151
- const defaultOps: SessionRelayInstallOps = {
152
- download: (url, destination) => {
153
- const result = spawnSync("curl", ["-fL", "--retry", "2", "--connect-timeout", "10", "--output", destination, url], {
154
- stdio: "inherit"
155
- })
156
- return result.error === undefined && result.status === 0
157
- },
158
- chmod: chmodSync,
159
- runVersion: (path) => {
160
- const result = spawnSync(path, ["--version"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] })
161
- return { ok: result.error === undefined && result.status === 0, stdout: result.stdout ?? "" }
162
- },
163
- rename: renameSync,
164
- uniqueSuffix: () => `${process.pid}-${randomBytes(8).toString("hex")}`
165
- }
166
-
167
- export function installSessionRelayCli(
168
- input: SessionRelayInstallInput,
169
- ops: SessionRelayInstallOps = defaultOps
170
- ): void {
171
- let manifest: SessionRelayManifest
172
- let target: SessionRelayTarget
173
- try {
174
- manifest = parseSessionRelayManifest(input.manifestText)
175
- target = sessionRelayTarget(input.platform, input.arch)
176
- } catch (error) {
177
- fail(input, error instanceof Error ? error.message : String(error))
178
- }
179
-
180
- if (input.dryRun) {
181
- input.log(
182
- `[dry-run] ensure Session Relay CLI ${manifest.verified} from ${manifest.repository}@${manifest.tag} (${target}) -> ${manifest.install_path}`
183
- )
184
- return
185
- }
186
-
187
- const stable = join(input.home, ".local", "bin", "session-relay")
188
- if (existsSync(stable) && exactVersion(ops, stable, manifest.verified)) return
189
-
190
- const parent = dirname(stable)
191
- try {
192
- mkdirSync(parent, { recursive: true, mode: 0o755 })
193
- if (!statSync(parent).isDirectory()) {
194
- fail(input, `Session Relay install parent is not a directory: ${parent}`)
195
- }
196
- } catch (error) {
197
- if (error instanceof ExitError) throw error
198
- fail(input, `Cannot prepare Session Relay install directory ${parent}: ${error instanceof Error ? error.message : String(error)}`)
199
- }
200
-
201
- const suffix = ops.uniqueSuffix()
202
- const stage = join(parent, `.session-relay.stage-${suffix}`)
203
- const checksumFile = join(parent, `.session-relay.checksums-${suffix}`)
204
- const assetName = `session-relay-${target}`
205
- const baseUrl = `https://github.com/${manifest.repository}/releases/download/${manifest.tag}`
206
-
207
- try {
208
- if (!ops.download(`${baseUrl}/${assetName}`, stage)) fail(input, `Failed to download pinned Session Relay asset ${assetName}`)
209
- if (!ops.download(`${baseUrl}/SHA256SUMS`, checksumFile)) fail(input, "Failed to download pinned Session Relay SHA256SUMS")
210
-
211
- let checksumDigest: string
212
- try {
213
- checksumDigest = selectedChecksum(readFileSync(checksumFile, "utf8"), assetName)
214
- } catch (error) {
215
- fail(input, error instanceof Error ? error.message : String(error))
216
- }
217
- const sourceDigest = manifest.assets[target]
218
- if (checksumDigest !== sourceDigest) fail(input, `Session Relay source pin does not match SHA256SUMS for ${assetName}`)
219
- const downloadedDigest = createHash("sha256").update(readFileSync(stage)).digest("hex")
220
- if (downloadedDigest !== sourceDigest) fail(input, `Downloaded Session Relay checksum mismatch for ${assetName}`)
221
-
222
- try {
223
- ops.chmod(stage, 0o755)
224
- } catch (error) {
225
- fail(input, `Failed to chmod staged Session Relay CLI: ${error instanceof Error ? error.message : String(error)}`)
226
- }
227
- if (!exactVersion(ops, stage, manifest.verified)) {
228
- fail(input, `Staged Session Relay CLI did not report exact version session-relay ${manifest.verified}`)
229
- }
230
- try {
231
- ops.rename(stage, stable)
232
- } catch (error) {
233
- fail(input, `Failed to atomically replace Session Relay CLI: ${error instanceof Error ? error.message : String(error)}`)
234
- }
235
- input.log(`Session Relay CLI ready (${manifest.verified})`)
236
- } finally {
237
- try {
238
- rmSync(stage, { force: true })
239
- } catch {
240
- // A cleanup failure must not turn a successful atomic replacement into
241
- // an install failure or alter a pre-existing stable executable.
242
- }
243
- try {
244
- rmSync(checksumFile, { force: true })
245
- } catch {
246
- // Same failure-preservation rule as the staged executable above.
247
- }
248
- }
249
- }
250
-
251
- export function ensureSessionRelayCli(ctx: Ctx): number {
252
- installSessionRelayCli({
253
- home: ctx.home,
254
- dryRun: ctx.dryRun,
255
- platform: ctx.services.platform.raw(),
256
- arch: process.arch,
257
- manifestText: manifestEntry(),
258
- log: ctx.dryRun ? ctx.services.logger.echo : ctx.services.logger.change,
259
- error: ctx.services.logger.err
260
- })
261
- return 0
262
- }
@@ -1,94 +0,0 @@
1
- import { spawnSync } from "node:child_process"
2
-
3
- import { isObject, parseJson } from "./jq"
4
-
5
- const SESSION_RELAY_PLUGIN_ID = "session-relay@docks"
6
-
7
- export type SessionRelayReadinessReason =
8
- | "codex_cli_unavailable"
9
- | "plugin_list_failed"
10
- | "invalid_plugin_list"
11
- | "plugin_missing"
12
- | "plugin_ambiguous"
13
- | "plugin_not_installed"
14
- | "plugin_disabled"
15
-
16
- export interface SessionRelayReadiness {
17
- readonly schema: 1
18
- readonly state: "ready" | "unavailable"
19
- readonly reason: SessionRelayReadinessReason | null
20
- readonly version: string | null
21
- readonly installed: boolean
22
- readonly enabled: boolean
23
- readonly scope: "new_sessions"
24
- }
25
-
26
- export interface CodexPluginListProbe {
27
- readonly status: number | null
28
- readonly stdout: string
29
- readonly errorCode: string | null
30
- }
31
-
32
- function unavailable(
33
- reason: SessionRelayReadinessReason,
34
- fields: { version?: string | null; installed?: boolean; enabled?: boolean } = {}
35
- ): SessionRelayReadiness {
36
- return {
37
- schema: 1,
38
- state: "unavailable",
39
- reason,
40
- version: fields.version ?? null,
41
- installed: fields.installed ?? false,
42
- enabled: fields.enabled ?? false,
43
- scope: "new_sessions"
44
- }
45
- }
46
-
47
- export function classifySessionRelayReadiness(probe: CodexPluginListProbe): SessionRelayReadiness {
48
- if (probe.errorCode === "ENOENT") return unavailable("codex_cli_unavailable")
49
- if (probe.errorCode !== null || probe.status !== 0) return unavailable("plugin_list_failed")
50
-
51
- const value = parseJson(probe.stdout)
52
- if (value === undefined || !isObject(value) || !Array.isArray(value["installed"])) {
53
- return unavailable("invalid_plugin_list")
54
- }
55
-
56
- const matches = value["installed"].filter(
57
- (entry) => isObject(entry) && entry["pluginId"] === SESSION_RELAY_PLUGIN_ID
58
- )
59
- if (matches.length === 0) return unavailable("plugin_missing")
60
- if (matches.length !== 1) return unavailable("plugin_ambiguous")
61
-
62
- const row = matches[0]
63
- if (row === undefined || !isObject(row)) return unavailable("invalid_plugin_list")
64
- const version = row["version"]
65
- const installed = row["installed"]
66
- const enabled = row["enabled"]
67
- if (typeof version !== "string" || version.length === 0 || typeof installed !== "boolean" || typeof enabled !== "boolean") {
68
- return unavailable("invalid_plugin_list")
69
- }
70
- if (!installed) return unavailable("plugin_not_installed", { version, enabled })
71
- if (!enabled) return unavailable("plugin_disabled", { version, installed })
72
-
73
- return {
74
- schema: 1,
75
- state: "ready",
76
- reason: null,
77
- version,
78
- installed,
79
- enabled,
80
- scope: "new_sessions"
81
- }
82
- }
83
-
84
- export function sessionRelayReadiness(): SessionRelayReadiness {
85
- const result = spawnSync("codex", ["plugin", "list", "--json"], {
86
- encoding: "utf8",
87
- stdio: ["ignore", "pipe", "ignore"]
88
- })
89
- return classifySessionRelayReadiness({
90
- status: result.status,
91
- stdout: result.stdout ?? "",
92
- errorCode: (result.error as NodeJS.ErrnoException | undefined)?.code ?? null
93
- })
94
- }