docks-kit 0.7.1 → 0.8.1

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
@@ -24,7 +24,7 @@ Tool-specific instructions live alongside this file:
24
24
  | `.github/workflows/release-cli.yml` | `cli-v*` release binaries + npm publish |
25
25
  | `README.md` | Front door |
26
26
  | `package.json` / `bun.lock` | npm package: `bin` = `cli/src/main.ts`; bundles `cli/` with the generated in-memory SoT payload |
27
- | `SoT/.agents/skills.txt` | Universal-skill manifest. One [agentskills.io](https://agentskills.io) slug per line; EngineNative runs `npx skills add` for each missing entry into `~/.agents/skills/`, where Codex et al. discover it natively and Claude Code follows a symlink at `~/.claude/skills/` |
27
+ | `SoT/.agents/skills.txt` | Universal-skill manifest, intentionally empty by default. Adding an [agentskills.io](https://agentskills.io/specification) slug opts it into EngineNative's shared `~/.agents/skills/` bootstrap and Claude symlink. |
28
28
  | `notification.mp3` | Audio asset for Notification hooks (consumed by Claude Code today; tool-agnostic file) |
29
29
  | `docs/plans/` | Multi-commit work-item plans (`active/` with status in frontmatter, plus `finished/` archive). Convention: `docs/plans/AGENTS.md` |
30
30
  | `CLAUDE.md` | Claude-specific instructions; imports this `AGENTS.md` |
@@ -32,13 +32,14 @@ Tool-specific instructions live alongside this file:
32
32
 
33
33
  Codex SoT notes:
34
34
  - `SoT/.codex/AGENTS.md` deploys to `~/.codex/AGENTS.md` as global Codex instructions.
35
- - `SoT/.codex/config.toml` pins Codex to `model = "gpt-5.6-sol"`, sets reasoning effort/summaries (`xhigh` + `concise`), `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`.
35
+ - `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`.
36
36
  - `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.
37
37
  - `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.
38
38
  - `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.
39
39
  - Both global prompt SoTs carry one byte-identical compact `Docks-workflow-models:` record. Root `--model-orchestrator` / `--model-reviewer` / `--model-implementer` / review-bound flags update only that deployed record; `docks-kit models workflow` lists the closed selectors, and a flag-less sync restores defaults.
40
+ - Codex workflow selectors use the closed `<tool>:<model>@<effort>[+fast]` routing grammar. Fast is intentionally absent from the global SoT: no suffix means Standard and keeps the backward-compatible schema-1 record; `+fast` emits a schema-2 candidate with `service_tier: "fast"`. Docks and Session Relay consumers must explicitly launch unsuffixed Codex roles with the default service tier so a user's global Fast preference cannot leak into workflows.
40
41
  - 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.
41
- - `SoT/.codex/AGENTS.md` deliberately does not import `@RTK.md`: RTK's published Codex integration is prompt-file based rather than hook based, so importing it leaks implementation detail into agent-visible context. Use Codex hooks for RTK only after the kit installs a hook-backed Codex integration.
42
+ - Neither global prompt SoT imports `@RTK.md`; Claude uses the hook-backed RTK integration, while Codex has no kit-managed RTK integration.
42
43
  - 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.
43
44
  - Claude's deployed SoT defaults are `model: fable` and `effortLevel: high`; `advisorModel` is deliberately absent/off. `--claude-advisor=on` is the per-machine opt-in and writes `advisorModel: fable` after the settings merge.
44
45
 
@@ -82,7 +83,7 @@ This project ships **kit-mechanic skills** under `.claude/skills/` — narrowly-
82
83
  When a kit-mechanic skill, its `references/`, or a wrapper agent (`.claude/agents/*.md` + its `.codex/agents/*.toml` twin) cites EngineNative internals, name the **module + exported/local function + semantic anchor** (e.g. `claudeSync.ts syncPlugins, pass 5 uninstall guard`) — never a raw `file:NNN` line number, which goes stale on every refactor. Keep exactly one coarse `metadata.source_files[].lines` range per skill file as the sole intentional line-number touchpoint.
83
84
  </constraint>
84
85
 
85
- **Universal-skill bootstrap.** `SoT/.agents/skills.txt` declares [agentskills.io](https://agentskills.io/specification) slugs the kit installs to `~/.agents/skills/` on every machine via `skillsSync.ts`. The bootstrap invokes `npx skills add <slug> -g -y -a claude-code codex` per missing skill — `<slug>` comes first because the CLI's `-a/--agent` flag is variadic and would otherwise swallow it. Naming **both** agents (`claude-code` + `codex`, the kit's support matrix) keeps the CLI in multi-agent mode: it writes the canonical `SKILL.md` to the universal `~/.agents/skills/<name>/` path — which Codex reads natively (per [OpenAI's Codex docs](https://developers.openai.com/codex/skills/), `$HOME/.agents/skills` is a user-level skill source) — and symlinks `~/.claude/skills/<name>` → it for Claude Code, which wants its own per-tool directory. A *single* `-a claude-code` would instead trigger a copy-direct shortcut (a real copy into `~/.claude/skills/`, no canonical path, Codex uncovered); `-a '*'` would over-reach into every AI tool the CLI can detect (~50). Add a new universal skill by appending one `<owner>/<repo>` line to `skills.txt` and re-running `./docks-kit sync` — idempotent: existing skills are skipped after checking `~/.agents/skills/<name>`. Skills that depend on a separate CLI binary get an explicit auto-install helper in `skillsSync.ts` (e.g. `syncAgentBrowserCli` runs `npm install -g agent-browser` + `agent-browser install --with-deps` on Linux; the `--with-deps` flag may prompt for sudo to install system libs). That helper also **self-upgrades** a present-but-stale binary: when `agent-browser`'s installed version is older than npm's `latest` it re-runs `npm install -g agent-browser` (the numeric-sort compare never downgrades a locally-newer pre-release, and skips silently when npm is absent/offline); the Chrome download is not repeated on upgrade. A second helper, `syncEffectSolutionsCli`, installs the optional `effect-solutions` Effect-docs CLI used by the `effect-kit` plugin. It calls the shared `bun.ts` `bunBootstrap` when needed, then symlinks **both** `bun` and the CLI into `~/.local/bin`. Linking Bun too is mandatory — the CLI's `#!/usr/bin/env bun` shebang needs it on PATH at run time — and `~/.local/bin` is the only dir reliably on the *non-interactive* agent PATH, since `~/.bashrc`'s "if not interactive, return" guard means rc PATH edits never reach agent shells (the same PATH reason the official Codex standalone installer targets `~/.local/bin`).
86
+ **Universal-skill bootstrap.** `SoT/.agents/skills.txt` is intentionally empty, so the default sync exposes no universal skills. The generic opt-in contract remains: each [agentskills.io](https://agentskills.io/specification) slug is installed by `skillsSync.ts` with `npx skills add <slug> -g -y -a claude-code codex`. The slug comes first because `-a/--agent` is variadic; naming both supported agents preserves the canonical `~/.agents/skills/<name>/SKILL.md` plus Claude's `~/.claude/skills/<name>` symlink. Existing canonical directories are reused idempotently, `--prune` removes only entries tracked in `~/.agents/.kit-managed-skills`, and skills requiring a separate CLI retain explicit helpers in `skillsSync.ts`.
86
87
 
87
88
  ## Plans
88
89
 
package/README.md CHANGED
@@ -84,10 +84,12 @@ and a later flag-less sync reverts them. Full reference: `docks-kit docs flags`
84
84
  | `--review-min-score=<0..100>` | Override the completion-review target |
85
85
  | `--review-max-rounds=<1..10>` | Bound completion-review batches |
86
86
 
87
- Selectors are strict `profile:<name>` or `<tool>:<model>@<effort>` values from
88
- `docks-kit models workflow`. These root flags update only the identical compact
89
- record in `~/.claude/CLAUDE.md` and `~/.codex/AGENTS.md`; omitted fields retain
90
- their current valid values, while a later flag-less sync restores all defaults.
87
+ Selectors are strict `profile:<name>` or `<tool>:<model>@<effort>[+fast]`
88
+ values from `docks-kit models workflow`; `+fast` is Codex-only. A selector
89
+ without `+fast` means Standard, while `+fast` writes a schema-2 candidate with
90
+ `service_tier: "fast"`. These root flags update only the identical compact record
91
+ in `~/.claude/CLAUDE.md` and `~/.codex/AGENTS.md`; omitted fields retain their
92
+ current valid values, while a later flag-less sync restores all defaults.
91
93
 
92
94
  ## How syncing works
93
95
 
package/cli/docs/flags.md CHANGED
@@ -34,7 +34,7 @@ docks-kit sync claude agents # two
34
34
  | `--claude-permissive` | Deploy-time modifier: empty permissions.ask/deny (sandboxes) |
35
35
  | `--claude-plugin=<name>` | Sticky opt-in plugin (known: supabase, n8n); comma-separate for several |
36
36
  | `--codex-model=<m>` | Deploy-time modifier: deployed Codex model |
37
- | `--codex-effort=<level>` | Deploy-time modifier: `model_reasoning_effort`; valid `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`, `ultra`, or `default` (Codex SoT: `xhigh`; model-dependent) |
37
+ | `--codex-effort=<level>` | Deploy-time modifier: `model_reasoning_effort`; valid `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`, `ultra`, or `default` (Codex SoT: `high`; model-dependent) |
38
38
 
39
39
  Bare model, effort, or advisor modifiers print the relevant valid-value catalog
40
40
  and exit 2. A modifier for a target not selected by the positional arguments is
@@ -47,7 +47,7 @@ These flags belong at the root (`docks-kit --model-reviewer=...`), not after
47
47
 
48
48
  | Flag | Accepted value |
49
49
  |------|----------------|
50
- | `--model-orchestrator=<selector>` | `profile:<name>` or `<tool>:<model>@<effort>` |
50
+ | `--model-orchestrator=<selector>` | `profile:<name>` or `<tool>:<model>@<effort>[+fast]` |
51
51
  | `--model-reviewer=<selector>` | same strict selector grammar |
52
52
  | `--model-implementer=<selector>` | same strict selector grammar |
53
53
  | `--review-min-score=<n>` | base-10 integer `0..100` |
@@ -55,7 +55,10 @@ These flags belong at the root (`docks-kit --model-reviewer=...`), not after
55
55
 
56
56
  Use `docks-kit models workflow [--json]` for the closed registry. Bare, empty,
57
57
  unknown, or malformed values print that helper and exit 2 before either prompt
58
- file changes.
58
+ file changes. `+fast` is accepted only on Codex exact targets and writes
59
+ `service_tier: "fast"` in workflow record schema 2. Without `+fast`, the role is
60
+ Standard and the record remains schema 1 unless another role is Fast. Shell
61
+ quotes are optional but recommended around selectors containing `+fast`.
59
62
 
60
63
  ## Renamed legacy flags (pre-CLI sync.sh)
61
64
 
@@ -39,12 +39,12 @@ docks-kit models workflow --json # closed machine-readable registry
39
39
 
40
40
  The `workflow` section is strict even though ordinary tool-model modifiers are
41
41
  forward-compatible. It accepts only known `profile:<name>` values or exact
42
- `<tool>:<model>@<effort>` targets whose model and effort both appear in the
43
- catalog. The defaults are:
42
+ `<tool>:<model>@<effort>[+fast]` targets whose model and effort both appear in
43
+ the catalog. `+fast` is accepted only for Codex exact targets. The defaults are:
44
44
 
45
45
  - orchestrator: `profile:claude-best`, ordered as `claude:fable@high` then
46
46
  `claude:opus@xhigh`;
47
- - reviewer and implementer: `codex:gpt-5.6-sol@xhigh`;
47
+ - reviewer and implementer: `codex:gpt-5.6-sol@high`;
48
48
  - review minimum score 90 and maximum 3 rounds.
49
49
 
50
50
  `claude:best@high` is Claude's native one-model alias. It is deliberately not
@@ -52,6 +52,14 @@ the same as the Docks-managed `profile:claude-best` candidate chain.
52
52
  Availability is `checked_when_used`: Docks classifies each launch attempt;
53
53
  docks-kit neither probes providers nor promises provider-wide fallback.
54
54
 
55
+ The default and every unsuffixed selector emit the backward-compatible schema-1
56
+ record with no service-tier field; absence means Standard. A `+fast` selector
57
+ promotes the complete record to schema 2 and adds `service_tier: "fast"` only to
58
+ the selected Codex candidate. Replacing the last Fast selector with an
59
+ unsuffixed selector demotes the record back to schema 1. Docks and Session Relay
60
+ must support schema 2 before `+fast` is deployed, and must explicitly select the
61
+ default tier for unsuffixed roles so global Codex configuration cannot leak in.
62
+
55
63
  ## Advisor pairing note (Claude)
56
64
 
57
65
  The SoT ships `model: fable` with advisor off (`advisorModel` unset).
@@ -10,7 +10,8 @@ the SoT is never touched. They all share one contract:
10
10
  > touches.
11
11
 
12
12
  Claude's embedded SoT is `model: fable`, `effortLevel: high`, with advisor
13
- off (`advisorModel` unset). Codex's embedded reasoning effort is `xhigh`.
13
+ off (`advisorModel` unset). Codex's embedded normal and plan reasoning effort is
14
+ `high`.
14
15
 
15
16
  | Modifier | Deployed change | Typical use |
16
17
  |----------|-----------------|-------------|
@@ -20,7 +21,7 @@ off (`advisorModel` unset). Codex's embedded reasoning effort is `xhigh`.
20
21
  | `--claude-compact-window=<n>` | `env.CLAUDE_CODE_AUTO_COMPACT_WINDOW` | Disposable containers running long autonomous work (e.g. `680k`) — not host machines |
21
22
  | `--claude-permissive` | `permissions.ask = []`, `permissions.deny = []` | Sandboxes/containers where prompts stall unattended work. Never on a host — the deny list is the safety floor |
22
23
  | `--codex-model=<m>` | top-level `model = "…"` in ~/.codex/config.toml | Same as claude-model, for Codex |
23
- | `--codex-effort=<level>` | top-level `model_reasoning_effort = "…"` (`default` writes `xhigh`) | Tune Codex effort per machine; valid `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`, `ultra` (model-dependent) |
24
+ | `--codex-effort=<level>` | top-level `model_reasoning_effort = "…"` (`default` writes `high`) | Tune Codex effort per machine; valid `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`, `ultra` (model-dependent) |
24
25
 
25
26
  Bare effort/advisor flags print their verified catalog and exit 2. Invalid
26
27
  values do the same with a clear error. Passing a tool-specific modifier without
@@ -32,6 +33,13 @@ owns that key for the run: `on` writes `fable`; `off` and `default` delete it.
32
33
  Codex has no advisor modifier because its documented config has no advisor
33
34
  setting; `review_model` applies only to `/review`.
34
35
 
36
+ Codex Fast mode is intentionally not a global SoT default or per-machine
37
+ modifier. It is role-scoped through the root workflow flags: append `+fast` to a
38
+ Codex exact selector to emit `service_tier: "fast"` in record schema 2. Without
39
+ the suffix, the role means Standard and emits no tier field. Docks and Session
40
+ Relay consumers must map that absence to an explicit default-tier process
41
+ override rather than inherit a user's global Fast preference.
42
+
35
43
  ## Standalone get/set (no full sync)
36
44
 
37
45
  ```
@@ -50,7 +58,8 @@ The root workflow flags are deployed modifiers for Docks rather than one tool:
50
58
 
51
59
  ```text
52
60
  docks-kit --model-orchestrator=profile:claude-best \
53
- --model-reviewer=codex:gpt-5.6-terra@high \
61
+ --model-reviewer="codex:gpt-5.6-sol@high+fast" \
62
+ --model-implementer=codex:gpt-5.6-sol@high \
54
63
  --review-min-score=80 --review-max-rounds=5
55
64
  ```
56
65
 
@@ -61,11 +70,13 @@ stops before mutation, a repeated request is a no-op, and failure on the second
61
70
  atomic write restores both snapshots. A later flag-less sync restores the
62
71
  embedded defaults. Start fresh sessions after changing the record.
63
72
 
64
- Selectors are strict `profile:<name>` or `<tool>:<model>@<effort>` entries from
65
- `docks-kit models workflow`. `profile:claude-best` is the ordered Docks
73
+ Selectors are strict `profile:<name>` or `<tool>:<model>@<effort>[+fast]`
74
+ entries from `docks-kit models workflow`. `+fast` is Codex-only and quotes are
75
+ optional; its absence means Standard. `profile:claude-best` is the ordered Docks
66
76
  Fable-high then Opus-xhigh chain; `claude:best@high` is instead Claude's native
67
77
  single-model alias. Docks checks each candidate when used—there is no docks-kit
68
- provider preflight.
78
+ provider preflight. Install schema-2 support in Docks and Session Relay before
79
+ using a Fast selector.
69
80
 
70
81
  ## Sticky opt-ins are NOT modifiers
71
82
 
@@ -45,9 +45,8 @@ download boundary; Windows Bun bootstrap uses PowerShell's native download.
45
45
 
46
46
  Every kit-driven install is pinned to a `verified` version or gated by one —
47
47
  never floating `@latest` (the npm-worm / Shai-Hulud attack surface). That
48
- includes the `npx skills@<verified>` invocations on every agents sync, the
49
- `chrome-devtools-mcp` MCP server pin in SoT/.claude/mcp-servers.json, and the
50
- release workflow (actions pinned to commit SHAs, exact bun/npm versions).
48
+ includes the `npx skills@<verified>` invocations on agents sync and the release
49
+ workflow (actions pinned to commit SHAs, exact bun/npm versions).
51
50
  Callbacks receive the exact gate-approved version; an offline "latest unknown"
52
51
  probe falls back to the pinned `verified`, never to an ungated latest.
53
52
 
@@ -23,7 +23,7 @@ import {
23
23
  syncClaudeModel
24
24
  } from "./claudeSettingsModifiers"
25
25
  import { claudeRuntimePaths, materializeClaudeSettings, type ClaudeRuntimePaths } from "./claudeRuntime"
26
- import { p, writeBytesIfChanged, writeFileIfChanged, writeTextIfChanged } from "./exec"
26
+ import { p, writeBytesIfChanged, writeTextIfChanged } from "./exec"
27
27
  import type { Ctx } from "./index"
28
28
  import { compareCodepoints, deepMerge, isObject, jqStringify, parseJson, type Json } from "./jq"
29
29
  import type { EngineServices } from "./services"
@@ -206,36 +206,13 @@ function syncClaudeRuntime(ctx: Ctx, runtime: ClaudeRuntimeState): void {
206
206
 
207
207
  function syncClaudeMd(ctx: Ctx, claudeDir: string): void {
208
208
  const { change, echo, verbose } = ctx.services.logger
209
- // The @RTK.md import only resolves once `rtk init` has generated
210
- // ~/.claude/RTK.md (the rtk phase runs before this). Deploying the import
211
- // without the file leaves a dangling reference in every Claude session
212
- // (seen on Windows, where rtk never auto-installs) — strip it while the
213
- // file is absent; a later sync after rtk init restores it.
214
- const rtkMdAbsent = !existsSync(p(claudeDir, "RTK.md"))
215
209
  if (ctx.dryRun) {
216
- if (ctx.skipRtk) {
217
- echo("[dry-run] cp SoT/.claude/CLAUDE.md -> ~/.claude/CLAUDE.md (stripping @RTK.md import: --skip-rtk)")
218
- } else if (rtkMdAbsent) {
219
- echo("[dry-run] cp SoT/.claude/CLAUDE.md -> ~/.claude/CLAUDE.md (would strip @RTK.md import while ~/.claude/RTK.md is absent)")
220
- } else {
221
- echo("[dry-run] cp SoT/.claude/CLAUDE.md -> ~/.claude/CLAUDE.md")
222
- }
210
+ echo("[dry-run] cp SoT/.claude/CLAUDE.md -> ~/.claude/CLAUDE.md")
223
211
  return
224
212
  }
225
213
 
226
214
  const source = renderDefaultWorkflowInstructions(payloadText("SoT/.claude/CLAUDE.md"))
227
- const stripReason = ctx.skipRtk ? "--skip-rtk" : rtkMdAbsent ? "~/.claude/RTK.md absent (rtk not initialized)" : ""
228
- if (stripReason !== "") {
229
- const stripped = source
230
- .split("\n")
231
- .filter((l) => l !== "@RTK.md")
232
- .join("\n")
233
- if (writeFileIfChanged(p(claudeDir, "CLAUDE.md"), stripped)) {
234
- change(`CLAUDE.md synced (@RTK.md import stripped: ${stripReason})`)
235
- } else {
236
- verbose("CLAUDE.md already in sync")
237
- }
238
- } else if (writeTextIfChanged(p(claudeDir, "CLAUDE.md"), source)) {
215
+ if (writeTextIfChanged(p(claudeDir, "CLAUDE.md"), source)) {
239
216
  change("CLAUDE.md synced")
240
217
  } else {
241
218
  verbose("CLAUDE.md already in sync")
@@ -668,6 +645,24 @@ function pluginUserScopeInstalled(installedPlugins: string, pluginId: string): b
668
645
  return records.some((r) => isObject(r) && r["scope"] === "user")
669
646
  }
670
647
 
648
+ function nonUserScopeMarketplaces(installedDoc: Json | undefined): Set<string> {
649
+ const marketplaces = new Set<string>()
650
+ if (installedDoc === undefined || !isObject(installedDoc) || !isObject(installedDoc["plugins"])) {
651
+ return marketplaces
652
+ }
653
+
654
+ for (const [pluginId, value] of Object.entries(installedDoc["plugins"])) {
655
+ const records = Array.isArray(value) ? value : [value]
656
+ const hasNonUserInstall = records.some(
657
+ (record) => isObject(record) && (record["scope"] === "project" || record["scope"] === "local")
658
+ )
659
+ if (!hasNonUserInstall) continue
660
+ const separator = pluginId.lastIndexOf("@")
661
+ if (separator > 0) marketplaces.add(pluginId.slice(separator + 1))
662
+ }
663
+ return marketplaces
664
+ }
665
+
671
666
  function syncPlugins(ctx: Ctx, claudeDir: string): void {
672
667
  const { change, echo, verbose, warn } = ctx.services.logger
673
668
  const knownMarketplaces = p(claudeDir, "plugins", "known_marketplaces.json")
@@ -739,6 +734,7 @@ function syncPlugins(ctx: Ctx, claudeDir: string): void {
739
734
  let updatedPl = 0
740
735
  const installedDoc = readJsonFile(installedPlugins)
741
736
  const installedKeys = installedDoc !== undefined && isObject(installedDoc) ? sortedKeys(installedDoc["plugins"]) : []
737
+ const nonUserMarketplaces = nonUserScopeMarketplaces(installedDoc)
742
738
  // Pass 3 — refresh every installed plugin unless the update command
743
739
  // selected its install-missing-only fast path.
744
740
  if (!ctx.skipPluginRefresh) {
@@ -767,6 +763,7 @@ function syncPlugins(ctx: Ctx, claudeDir: string): void {
767
763
  const known = readJsonFile(knownMarketplaces)
768
764
  for (const mpName of sortedKeys(known)) {
769
765
  if (mpName === "claude-plugins-official") continue
766
+ if (nonUserMarketplaces.has(mpName)) continue
770
767
  const declared = isObject(sotMarketplaces) ? sotMarketplaces[mpName] : undefined
771
768
  if (declared !== undefined && declared !== null && declared !== false) continue
772
769
  if (cli(["plugin", "marketplace", "remove", mpName]).ok) {
@@ -280,9 +280,9 @@ function workflowUsage(ctx: Ctx): void {
280
280
  printCatalog(ctx, workflowCatalog())
281
281
  ctx.services.logger.echo("")
282
282
  ctx.services.logger.echo("Workflow override flags:")
283
- ctx.services.logger.echo(" --model-orchestrator=<profile:name|tool:model@effort>")
284
- ctx.services.logger.echo(" --model-reviewer=<profile:name|tool:model@effort>")
285
- ctx.services.logger.echo(" --model-implementer=<profile:name|tool:model@effort>")
283
+ ctx.services.logger.echo(" --model-orchestrator=<profile:name|tool:model@effort[+fast]>")
284
+ ctx.services.logger.echo(" --model-reviewer=<profile:name|tool:model@effort[+fast]>")
285
+ ctx.services.logger.echo(" --model-implementer=<profile:name|tool:model@effort[+fast]>")
286
286
  ctx.services.logger.echo(" --review-min-score=<0..100>")
287
287
  ctx.services.logger.echo(" --review-max-rounds=<1..10>")
288
288
  }
@@ -350,7 +350,7 @@ function reconcileRemovals(ctx: Ctx, manifest: string, snapshot: string): void {
350
350
  echo(`[dry-run] kit-managed skill no longer in SoT — would remove: ${base}`)
351
351
  continue
352
352
  }
353
- const res = spawnSync("npx", ["--yes", skillsCli(ctx), "remove", "--global", "-y", "-a", "*", "-s", base], {
353
+ const res = spawnSync("npx", ["--yes", skillsCli(ctx), "remove", "--global", base, "-y"], {
354
354
  stdio: "ignore"
355
355
  })
356
356
  if (res.error === undefined && res.status === 0) {
@@ -365,7 +365,7 @@ function reconcileRemovals(ctx: Ctx, manifest: string, snapshot: string): void {
365
365
  change(`Kit-managed skills removed (-${removed})`)
366
366
  ctx.nextStepTriggers.skillsRestart = true
367
367
  }
368
- if (failed > 0) warn(`${failed} skill remove(s) failed — re-run with --prune or run: npx skills remove -g -y -a '*' -s <name>`)
368
+ if (failed > 0) warn(`${failed} skill remove(s) failed — re-run with --prune or run: npx skills remove --global <name> -y`)
369
369
  }
370
370
 
371
371
  function updateSnapshot(ctx: Ctx, manifest: string, snapshot: string): void {
@@ -9,7 +9,7 @@ import {
9
9
  parseWorkflowRecord,
10
10
  renderWorkflowRecordLine,
11
11
  type WorkflowOverrides,
12
- type WorkflowRecordV1
12
+ type WorkflowRecord
13
13
  } from "../workflowModels"
14
14
 
15
15
  export interface WorkflowFileSystem {
@@ -36,7 +36,7 @@ const nodeFileSystem: WorkflowFileSystem = {
36
36
  }
37
37
 
38
38
  interface DocumentRecord {
39
- readonly record: WorkflowRecordV1 | undefined
39
+ readonly record: WorkflowRecord | undefined
40
40
  readonly line: string | undefined
41
41
  }
42
42
 
@@ -45,9 +45,9 @@ function readDocumentRecord(document: string | undefined, label: string): Docume
45
45
  if (lines.length === 0) return { record: undefined, line: undefined }
46
46
 
47
47
  let canonicalLine: string | undefined
48
- let canonicalRecord: WorkflowRecordV1 | undefined
48
+ let canonicalRecord: WorkflowRecord | undefined
49
49
  for (const line of lines) {
50
- let record: WorkflowRecordV1
50
+ let record: WorkflowRecord
51
51
  try {
52
52
  record = parseWorkflowRecord(JSON.parse(line.slice(WORKFLOW_RECORD_PREFIX.length)) as unknown)
53
53
  } catch (error) {
@@ -64,7 +64,7 @@ function readDocumentRecord(document: string | undefined, label: string): Docume
64
64
  return { record: canonicalRecord, line: canonicalLine }
65
65
  }
66
66
 
67
- export function upsertWorkflowRecord(document: string, record: WorkflowRecordV1): string {
67
+ export function upsertWorkflowRecord(document: string, record: WorkflowRecord): string {
68
68
  const content = document
69
69
  .split("\n")
70
70
  .filter((line) => !line.startsWith(WORKFLOW_RECORD_PREFIX))
@@ -1,20 +1,20 @@
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.7.1"
4
+ export const GENERATED_PACKAGE_VERSION = "0.8.1"
5
5
 
6
6
  export const GENERATED_PAYLOAD_TEXT = {
7
- "SoT/.agents/skills.txt": "# Universal AI-agent skills (agentskills.io standard).\n# Bootstrapped to ~/.agents/skills/ by cli/src/engine-native/skillsSync.ts during ./docks-kit sync.\n# One slug per line: <owner>/<repo>. Lines starting with # are comments.\n# Each skill's canonical SKILL.md lands in ~/.agents/skills/<name>/ — Codex\n# reads that path natively; Claude Code gets a ~/.claude/skills/<name>\n# symlink to it. The skills sync names both agents the kit supports\n# (-a claude-code codex) so the CLI keeps the shared canonical copy.\n\n# Browser automation CLI — reaches JS-rendered, auth-walled, login-gated pages\n# (x.com, LinkedIn, Confluence) that built-in WebFetch can't. The skills sync\n# auto-installs the `agent-browser` npm package + downloads Chrome for Testing\n# (~175 MB) on first sync; Linux runs `agent-browser install --with-deps` which\n# may prompt for sudo to install system libs (libnss3, libatk, ...).\nvercel-labs/agent-browser\n",
8
- "SoT/models.json": "{\n \"$comment\": \"Kit-verified model catalog — single source for EngineNative validators, the docks-kit CLI (models/model commands, workflow selectors, pickers, 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; workflow selectors are strict.\",\n \"claude\": {\n \"verified\": \"2026-07-08\",\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 (currently Opus 4.8)\" },\n { \"id\": \"fable\", \"kind\": \"alias\", \"note\": \"Fable 5 — the kit SoT 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-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-09\",\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 \"workflow\": {\n \"schema\": 1,\n \"profiles\": {\n \"claude-best\": {\n \"candidates\": [\n { \"company\": \"anthropic\", \"tool\": \"claude\", \"model\": \"fable\", \"effort\": \"high\" },\n { \"company\": \"anthropic\", \"tool\": \"claude\", \"model\": \"opus\", \"effort\": \"xhigh\" }\n ]\n }\n },\n \"defaults\": {\n \"orchestrator\": \"profile:claude-best\",\n \"reviewer\": \"codex:gpt-5.6-sol@xhigh\",\n \"implementer\": \"codex:gpt-5.6-sol@xhigh\",\n \"review\": {\n \"minimum_score\": 90,\n \"max_rounds\": 3\n }\n },\n \"exact_target_grammar\": \"<tool>:<model>@<effort>\",\n \"availability\": \"checked_when_used\"\n }\n}\n",
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
+ "SoT/models.json": "{\n \"$comment\": \"Kit-verified model catalog — single source for EngineNative validators, the docks-kit CLI (models/model commands, workflow selectors, pickers, 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; workflow selectors are strict.\",\n \"claude\": {\n \"verified\": \"2026-07-08\",\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 (currently Opus 4.8)\" },\n { \"id\": \"fable\", \"kind\": \"alias\", \"note\": \"Fable 5 — the kit SoT 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-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 \"workflow\": {\n \"schema\": 2,\n \"profiles\": {\n \"claude-best\": {\n \"candidates\": [\n { \"company\": \"anthropic\", \"tool\": \"claude\", \"model\": \"fable\", \"effort\": \"high\" },\n { \"company\": \"anthropic\", \"tool\": \"claude\", \"model\": \"opus\", \"effort\": \"xhigh\" }\n ]\n }\n },\n \"defaults\": {\n \"orchestrator\": \"profile:claude-best\",\n \"reviewer\": \"codex:gpt-5.6-sol@high\",\n \"implementer\": \"codex:gpt-5.6-sol@high\",\n \"review\": {\n \"minimum_score\": 90,\n \"max_rounds\": 3\n }\n },\n \"exact_target_grammar\": \"<tool>:<model>@<effort>[+fast]\",\n \"availability\": \"checked_when_used\"\n }\n}\n",
9
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.170\", \"note\": \"kit floor — `best` alias + Fable 5 need >=2.1.170 (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
- "SoT/.claude/CLAUDE.md": "@RTK.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\n**Research workflow:**\n1. Use `resolve-library-id` → `query-docs` (context7) to fetch up-to-date docs for the specific library/framework\n2. If context7 doesn't cover it, read the official docs with `agent-browser` (it reaches JS-rendered, auth-walled, and login-gated pages); fall back to `WebFetch` for a simple static page\n3. Only then proceed to implementation\n\n**When to research:**\n- Installing or configuring a dependency\n- Using an API, hook, method, or pattern you haven't verified in this session\n- Upgrading or migrating between versions\n- Any task where you'd otherwise rely on training data for syntax/behavior\n\n**Do 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 haven't 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\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\nDocks-workflow-models: {\"implementer\":{\"candidates\":[{\"company\":\"openai\",\"effort\":\"xhigh\",\"model\":\"gpt-5.6-sol\",\"tool\":\"codex\"}],\"selector\":\"codex:gpt-5.6-sol@xhigh\"},\"orchestrator\":{\"candidates\":[{\"company\":\"anthropic\",\"effort\":\"high\",\"model\":\"fable\",\"tool\":\"claude\"},{\"company\":\"anthropic\",\"effort\":\"xhigh\",\"model\":\"opus\",\"tool\":\"claude\"}],\"selector\":\"profile:claude-best\"},\"review\":{\"max_rounds\":3,\"minimum_score\":90},\"reviewer\":{\"candidates\":[{\"company\":\"openai\",\"effort\":\"xhigh\",\"model\":\"gpt-5.6-sol\",\"tool\":\"codex\"}],\"selector\":\"codex:gpt-5.6-sol@xhigh\"},\"schema\":1}\n\n## Project Skills\n\nProjects may have a `.claude/skills/` directory with Tool Wrapper skills managed by `/docs`. Claude Code auto-discovers these at session start — only descriptions are loaded, full content loads on demand via the Skill tool.\n\nSkills follow the [agentskills.io](https://agentskills.io) open standard:\n- **SKILL.md**: frontmatter (`name`, `description`, `user-invocable: false`, `metadata`) + body (≤500 lines)\n- **references/**: on-demand detail files (30-150 lines each), loaded when the skill instructs Claude to read them\n- **Discovery**: Claude Code scans `.claude/skills/*/SKILL.md` at session start, loads only `name` + `description` (~100 tokens per skill)\n- **Triggering**: Claude semantically matches descriptions against user tasks, invokes via `Skill` tool — no `@import` or pointer tables needed\n- **CSO (Claude Search Optimization)**: descriptions MUST start with \"Use when...\" and describe trigger conditions, not capabilities\n- **Third-party / vendored skills**: add an `upstream:` frontmatter block (`source`, `license`, `vendored_at: \"YYYY-MM-DD\"`) when vendoring a skill from an external repo. The block marks the skill as vendored so kit-specific checks (CSO start-prefix, `user-invocable`, `metadata.updated`) are relaxed and the skill's body is preserved verbatim from upstream. Universal structural checks (fenced frontmatter, name matches directory, description length, 500-line body cap) still apply.\n\n<constraint>\nAfter any code change affecting documented patterns, update the relevant skill in `.claude/skills/` and its `metadata.updated` frontmatter field. When introducing something new, create a skill or add a `references/` file to an existing skill.\n</constraint>\n\n## Project Agents\n\nProjects and the global kit may have a `.claude/agents/` directory containing subagent definitions. Each agent file declares its `model` (`sonnet`/`opus`/`haiku`/`inherit`/full model ID), `tools`, and system prompt. Claude Code auto-discovers them at session start and delegates when a `subagent_type` matches or when a slash command explicitly invokes them.\n\nAgent files follow this structure:\n- **Frontmatter**: `name` (kebab-case, matches filename), `description` (CSO — starts \"Use when…\" with a \"Not for…\" exclusion clause), `tools`, `model`\n- **Body** (≤500 lines): `<constraint>` blocks for non-negotiable rules, `## Workflow` with context-acknowledgment as step 1, `## Output Format`, `## Anti-Hallucination Checks`, `## Success Criteria`\n- **Model-selection resolution** (per Claude Code docs): `CLAUDE_CODE_SUBAGENT_MODEL` env var → per-invocation `model` param → agent frontmatter `model:` → parent conversation. The env var is NOT set in this kit, so per-agent frontmatter controls selection.\n\n<constraint>\nWhen adding a new agent: use kebab-case name matching filename, CSO-compliant description (starts \"Use when…\", contains a \"Not\" exclusion clause), explicit `model` and `tools`.\n</constraint>\n\n## Picking the right models for workflows and subagents\n\nRankings, all scores 10 = best. Intelligence is how hard a problem the model can be handed unsupervised. Taste covers UI/UX, code quality, API design, and copy. Budget fit is marginal spend plus quota headroom — a tie-breaker only.\n\n| model | budget fit | intelligence | taste |\n|-------------|------------|--------------|-------|\n| gpt-5.6-sol | 9 | 10 | 6 |\n| fable-5 | 3 | 9 | 9 |\n| opus-4.8 | 5 | 7 | 8 |\n| sonnet-5 | 7 | 5 | 7 |\n\nCapacity: Sol has abundant subscription headroom; Fable is scarce; Opus is moderate; Sonnet is economical. gpt-5.6-sol holds the top intelligence slot — it is the default implementer for plan-sized work. Fable 5 (available again; the kit's Claude default) holds the top taste slot and is the orchestrator/interactive tier, not a bulk executor.\n\nHow to apply:\n- These are defaults, not limits. Standing permission to override: if a cheaper model's output misses the bar, rerun with a smarter one without asking. Judge the output, not the price tag — escalating costs less than shipping mediocre work.\n- Budget fit is a tie-breaker only; when axes conflict for anything that ships, intelligence > taste > budget fit.\n- Implementation of plans, and bulk/mechanical work (clear-spec implementation, data analysis, migrations): gpt-5.6-sol.\n- Anything user-facing (UI, copy, API design) needs taste ≥ 7 → fable-5 or opus-4.8 (sonnet-5 when both are saturated).\n- Reviews of plans/implementations: gpt-5.6-sol, optionally the best available Claude — fable-5 when access allows, opus-4.8 otherwise — as a second independent perspective.\n- Never use Haiku.\n- Claude models run via the Agent/Workflow `model` parameter (`opus`, `sonnet`, `fable` where org access allows).\n\nReaching gpt-5.6-sol — always through the `session-relay` skill (shared bus + `relay` CLI, Claude ⇄ Codex), even for one-shots, so every exchange stays resumable:\n- `relay spawn <dir> --tool codex --model gpt-5.6-sol --effort xhigh` (or `--tool claude --model opus` for a Claude worker in another project), then continue it with `send` / `wake`. Codex runs on its own CLI (`~/.codex/config.toml` defaults to gpt-5.6-sol) and is more efficient than Claude on well-specced execution and stronger at computer-use and UI/UX verification — offload those and report results back.\n- An independent fresh-context review = a NEW spawn (fresh spawn is fresh context). Two independent perspectives on a plan = the red-team pair spawn: a gpt-5.6-sol worker and an opus worker debate over the bus, orchestrator writes the verdict — the concrete form of the \"second independent perspective\" review above.\n- Pin `--model`/`--effort` on every spawn/wake; never leave an unattended relay child on a top interactive default (e.g. Fable). Each spawn/wake bills the target's subscription — spawn deliberately, never in loops.\n\n## Agentic Engineering Discipline\n\n1. **State assumptions; push back when warranted.** If a requirement is ambiguous in a way that changes the deliverable, surface the ambiguity and propose 1–2 concrete interpretations in your first message — do not silently pick one and run with it. Surface inconsistencies and confusion instead of guessing past them; present tradeoffs when approaches genuinely differ; push back when the request looks wrong. Agreeable-but-wrong is the failure mode, not disagreement.\n2. **Minimum code that solves the stated problem.** Each named pattern below is a defect — catch it during generation, not after:\n\n **Code slop**\n - **Defensive guards** around internally-trusted calls (`try`, `if x != null`). Validate at system boundaries only.\n - **Speculative abstraction.** No helper for one caller; no interface for one implementer. Three similar lines beats premature DRY.\n - **Backwards-compat shims** without a caller — re-exports, deprecation aliases, untoggled feature flags. Just change the code.\n - **Half-finished stubs.** `TODO handle later`, `throw new Error('not implemented')`. Implement or remove the path.\n - **Underscore-rename of unused vars.** Delete the var.\n - **Dead code left behind.** After a refactor, delete the paths, helpers, and imports the change made unreachable.\n\n **Comment slop**\n - **Restate-the-code.** `// increment i`. The identifier already says it.\n - **Provenance.** `// added for ticket X`, `// used by Y`. Belongs in the PR description; rots in code.\n - **Tombstones.** `// removed Z`, `// previously did W`. Git remembers.\n - **Docstring bloat** on self-evident functions. One line, only when the WHY isn't obvious from the name.\n\n **Output slop**\n - **End-of-turn diff-restatement.** One or two sentences: what changed, what's next. Don't recap what's in the diff.\n - **Narration tics.** \"Now I'll…\", \"Let me check…\", play-by-play between tool calls. Terse working shorthand between tool calls is fine; play-by-play is not — write a sentence when something load-bearing happens (a finding, a direction change, a blocker).\n - **Compressed final summaries.** The final message is for a reader who didn't watch the work: outcome first, complete sentences. Shorten by dropping detail, never by compressing into fragments or arrow chains.\n3. **Surgical changes only.** Do not modify code, comments, or formatting outside the explicit scope of the request. Surface unrelated issues as follow-ups — do not fix inline.\n4. **State how success will be verified before implementing.** Name the test, build, smoke check, or diff inspection that will prove the change works. Prefer executable criteria — a test that fails before and passes after, a command with expected output — over judgment calls, and keep each change small enough that its diff is reviewable in one sitting.\n5. **Review scope follows the pipeline.** In pipeline reviews with a downstream filter (multi-agent scans, verification phases), report every issue found with confidence and severity — filtering happens downstream. In ad-hoc reviews, flag only gaps that affect correctness or the stated requirements; treat the rest as optional.\n6. **Ground every progress claim in evidence.** Before reporting progress or completion, audit each claim against a tool result from this session — show the test output, the command and what it returned. If something is unverified, say so explicitly; if tests fail, say so with the output.\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>\nTreat the six rules above as preventive (during generation), not remedial (after the fact). Self-correct if a turn drifts.\n</constraint>\n",
11
- "SoT/.claude/mcp-servers.json": "{\n \"mcpServers\": {\n \"chrome-devtools\": {\n \"type\": \"stdio\",\n \"command\": \"npx\",\n \"args\": [\"-y\", \"chrome-devtools-mcp@1.5.0\"],\n \"env\": {}\n }\n }\n}\n",
12
- "SoT/.claude/settings.json": "{\n \"$schema\": \"https://json.schemastore.org/claude-code-settings.json\",\n \"minimumVersion\": \"2.1.170\",\n \"model\": \"fable\",\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 \"context7@claude-plugins-official\": true,\n \"frontend-design@claude-plugins-official\": true,\n \"php-lsp@claude-plugins-official\": true,\n \"typescript-lsp@claude-plugins-official\": true,\n \"docks@docks\": true,\n \"session-relay@docks\": true,\n \"effect-kit@docks\": 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",
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\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\nDocks-workflow-models: {\"implementer\":{\"candidates\":[{\"company\":\"openai\",\"effort\":\"high\",\"model\":\"gpt-5.6-sol\",\"tool\":\"codex\"}],\"selector\":\"codex:gpt-5.6-sol@high\"},\"orchestrator\":{\"candidates\":[{\"company\":\"anthropic\",\"effort\":\"high\",\"model\":\"fable\",\"tool\":\"claude\"},{\"company\":\"anthropic\",\"effort\":\"xhigh\",\"model\":\"opus\",\"tool\":\"claude\"}],\"selector\":\"profile:claude-best\"},\"review\":{\"max_rounds\":3,\"minimum_score\":90},\"reviewer\":{\"candidates\":[{\"company\":\"openai\",\"effort\":\"high\",\"model\":\"gpt-5.6-sol\",\"tool\":\"codex\"}],\"selector\":\"codex:gpt-5.6-sol@high\"},\"schema\":1}\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
+ "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.170\",\n \"model\": \"fable\",\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",
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
- "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\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\nDocks-workflow-models: {\"implementer\":{\"candidates\":[{\"company\":\"openai\",\"effort\":\"xhigh\",\"model\":\"gpt-5.6-sol\",\"tool\":\"codex\"}],\"selector\":\"codex:gpt-5.6-sol@xhigh\"},\"orchestrator\":{\"candidates\":[{\"company\":\"anthropic\",\"effort\":\"high\",\"model\":\"fable\",\"tool\":\"claude\"},{\"company\":\"anthropic\",\"effort\":\"xhigh\",\"model\":\"opus\",\"tool\":\"claude\"}],\"selector\":\"profile:claude-best\"},\"review\":{\"max_rounds\":3,\"minimum_score\":90},\"reviewer\":{\"candidates\":[{\"company\":\"openai\",\"effort\":\"xhigh\",\"model\":\"gpt-5.6-sol\",\"tool\":\"codex\"}],\"selector\":\"codex:gpt-5.6-sol@xhigh\"},\"schema\":1}\n\n## Engineering Discipline\n\n- Prefer the repository's existing patterns and helpers over new abstractions.\n- Keep edits scoped to the user's request and the surrounding ownership boundary.\n- Add abstractions only when they remove real complexity or match an established local pattern.\n- Preserve user changes. Never revert unrelated dirty work unless explicitly asked.\n- Verify with the narrowest useful command first, then broaden if risk warrants it.\n- Surface any test or verification you could not run.\n- Before the first tool call, state in one or two sentences what you are about to do; give a brief progress update every few execution steps.\n- Concise teammate tone: no status tics, no log-style updates; reference file paths instead of dumping contents; lead with what changed and why, not a \"Summary\" heading.\n- The final message is for a reader who did not watch the work: outcome first, complete sentences. Shorten by dropping detail, never by compressing into fragments or arrow chains.\n- Use 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\n## Agentic Engineering Discipline\n\n1. **State assumptions; push back when warranted.** If a requirement is ambiguous in a way that changes the deliverable, surface the ambiguity and propose 1–2 concrete interpretations in your first message — do not silently pick one. Surface inconsistencies instead of guessing past them; present tradeoffs when approaches genuinely differ; push back when the request looks wrong.\n2. **Minimum code that solves the stated problem.** No speculative features, no abstractions without a second caller, no broad exception handling around internally-trusted calls, no dead code left behind after a refactor, no comments that restate what the code says.\n3. **Surgical changes only.** Do not modify code, comments, or formatting outside the explicit scope of the request. Surface unrelated issues as follow-ups — do not fix inline.\n4. **State how success will be verified before implementing.** Name the test, build, smoke check, or diff inspection that will prove the change works. Prefer executable criteria — a test that fails before and passes after, a command with expected output — and keep each change small enough that its diff is reviewable in one sitting.\n5. **Review scope follows the pipeline.** In pipeline reviews with a downstream filter, report every issue found with confidence and severity — filtering happens downstream. In ad-hoc reviews, flag only gaps that affect correctness or the stated requirements; treat the rest as optional.\n6. **Ground every progress claim in evidence.** Before reporting progress or completion, audit each claim against a tool result from this session — show the test output, the command and what it returned. If something is unverified, say so explicitly; if tests fail, say so with the output.\n\n<constraint>\nTreat the six rules above as preventive (during generation), not remedial (after the fact). Self-correct if a turn drifts.\n</constraint>\n",
17
- "SoT/.codex/config.toml": "model = \"gpt-5.6-sol\"\nmodel_reasoning_effort = \"xhigh\"\nplan_mode_reasoning_effort = \"xhigh\"\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",
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\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\nDocks-workflow-models: {\"implementer\":{\"candidates\":[{\"company\":\"openai\",\"effort\":\"high\",\"model\":\"gpt-5.6-sol\",\"tool\":\"codex\"}],\"selector\":\"codex:gpt-5.6-sol@high\"},\"orchestrator\":{\"candidates\":[{\"company\":\"anthropic\",\"effort\":\"high\",\"model\":\"fable\",\"tool\":\"claude\"},{\"company\":\"anthropic\",\"effort\":\"xhigh\",\"model\":\"opus\",\"tool\":\"claude\"}],\"selector\":\"profile:claude-best\"},\"review\":{\"max_rounds\":3,\"minimum_score\":90},\"reviewer\":{\"candidates\":[{\"company\":\"openai\",\"effort\":\"high\",\"model\":\"gpt-5.6-sol\",\"tool\":\"codex\"}],\"selector\":\"codex:gpt-5.6-sol@high\"},\"schema\":1}\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
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",
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
@@ -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 = "1b7e61acf5863f8910246421ba219f30f89585fe826ee3022be3db4e3fd85187"
43
+ export const GENERATED_PAYLOAD_HASH = "4decac588a31cdaf6cbc88997bccb77add7bf0cbcfc3ae0fba30eca23525707d"
@@ -8,12 +8,14 @@ export type WorkflowTool = "claude" | "codex"
8
8
  export type WorkflowCompany = "anthropic" | "openai"
9
9
  export type WorkflowRoleName = "orchestrator" | "reviewer" | "implementer"
10
10
  export type WorkflowBoundName = "minimum_score" | "max_rounds"
11
+ export type WorkflowServiceTier = "fast"
11
12
 
12
13
  export interface WorkflowCandidate {
13
14
  readonly company: WorkflowCompany
14
15
  readonly tool: WorkflowTool
15
16
  readonly model: string
16
17
  readonly effort: string
18
+ readonly service_tier?: WorkflowServiceTier
17
19
  }
18
20
 
19
21
  export interface WorkflowRole {
@@ -32,6 +34,19 @@ export interface WorkflowRecordV1 {
32
34
  }
33
35
  }
34
36
 
37
+ export interface WorkflowRecordV2 {
38
+ readonly schema: 2
39
+ readonly orchestrator: WorkflowRole
40
+ readonly reviewer: WorkflowRole
41
+ readonly implementer: WorkflowRole
42
+ readonly review: {
43
+ readonly minimum_score: number
44
+ readonly max_rounds: number
45
+ }
46
+ }
47
+
48
+ export type WorkflowRecord = WorkflowRecordV1 | WorkflowRecordV2
49
+
35
50
  export interface WorkflowOverrides {
36
51
  readonly orchestrator?: string
37
52
  readonly reviewer?: string
@@ -45,7 +60,7 @@ interface WorkflowProfile {
45
60
  }
46
61
 
47
62
  interface WorkflowRegistryCore {
48
- readonly schema: 1
63
+ readonly schema: 2
49
64
  readonly profiles: Readonly<Record<string, WorkflowProfile>>
50
65
  readonly defaults: {
51
66
  readonly orchestrator: string
@@ -56,7 +71,7 @@ interface WorkflowRegistryCore {
56
71
  readonly max_rounds: number
57
72
  }
58
73
  }
59
- readonly exact_target_grammar: "<tool>:<model>@<effort>"
74
+ readonly exact_target_grammar: "<tool>:<model>@<effort>[+fast]"
60
75
  readonly availability: "checked_when_used"
61
76
  }
62
77
 
@@ -117,9 +132,14 @@ const toolEfforts = (tool: WorkflowTool): ReadonlyArray<string> =>
117
132
  function parseCandidate(
118
133
  value: unknown,
119
134
  label: string,
120
- tools: WorkflowRegistryView["tools"]
135
+ tools: WorkflowRegistryView["tools"],
136
+ allowServiceTier: boolean
121
137
  ): WorkflowCandidate {
122
- const candidate = expectRecord(value, label, ["company", "effort", "model", "tool"])
138
+ const serviceTier = isRecord(value) ? value["service_tier"] : undefined
139
+ const keys = serviceTier === undefined
140
+ ? ["company", "effort", "model", "tool"]
141
+ : ["company", "effort", "model", "service_tier", "tool"]
142
+ const candidate = expectRecord(value, label, keys)
123
143
  const tool = candidate["tool"]
124
144
  if (tool !== "claude" && tool !== "codex") throw new Error(`${label} has an invalid tool`)
125
145
  const company = candidate["company"]
@@ -133,18 +153,26 @@ function parseCandidate(
133
153
  if (typeof effort !== "string" || !tools[tool].efforts.includes(effort)) {
134
154
  throw new Error(`${label} has an unverified ${tool} effort`)
135
155
  }
156
+ if (serviceTier !== undefined) {
157
+ if (!allowServiceTier) throw new Error(`${label} requires workflow record schema 2 for a service tier`)
158
+ if (tool !== "codex" || serviceTier !== "fast") throw new Error(`${label} has an invalid service tier`)
159
+ return { company: expectedCompany, tool, model, effort, service_tier: "fast" }
160
+ }
136
161
  return { company: expectedCompany, tool, model, effort }
137
162
  }
138
163
 
139
164
  function parseCandidates(
140
165
  value: unknown,
141
166
  label: string,
142
- tools: WorkflowRegistryView["tools"]
167
+ tools: WorkflowRegistryView["tools"],
168
+ allowServiceTier = false
143
169
  ): ReadonlyArray<WorkflowCandidate> {
144
170
  if (!Array.isArray(value) || value.length < 1 || value.length > 3) {
145
171
  throw new Error(`${label} must contain one to three candidates`)
146
172
  }
147
- return value.map((candidate, index) => parseCandidate(candidate, `${label}[${index}]`, tools))
173
+ return value.map((candidate, index) =>
174
+ parseCandidate(candidate, `${label}[${index}]`, tools, allowServiceTier)
175
+ )
148
176
  }
149
177
 
150
178
  function parseNumericBound(name: WorkflowBoundName, value: unknown): number {
@@ -167,24 +195,29 @@ function resolveSelector(
167
195
  return { selector, candidates: profile.candidates }
168
196
  }
169
197
 
170
- const exactMatch = /^(claude|codex):([A-Za-z0-9._-]+)@([A-Za-z0-9._-]+)$/.exec(selector)
198
+ const exactMatch = /^(claude|codex):([A-Za-z0-9._-]+)@([A-Za-z0-9._-]+)(\+fast)?$/.exec(selector)
171
199
  if (exactMatch === null) {
172
200
  throw new Error(
173
- `Invalid workflow selector '${selector}' — expected profile:<name> or <tool>:<model>@<effort>`
201
+ `Invalid workflow selector '${selector}' — expected profile:<name> or <tool>:<model>@<effort>[+fast]`
174
202
  )
175
203
  }
176
204
  const tool = exactMatch[1] as WorkflowTool
177
205
  const model = exactMatch[2]!
178
206
  const effort = exactMatch[3]!
207
+ const fast = exactMatch[4] !== undefined
179
208
  if (!registry.tools[tool].models.includes(model)) {
180
209
  throw new Error(`Unknown ${tool} workflow model '${model}'`)
181
210
  }
182
211
  if (!registry.tools[tool].efforts.includes(effort)) {
183
212
  throw new Error(`Unknown ${tool} workflow effort '${effort}'`)
184
213
  }
214
+ if (fast && tool !== "codex") {
215
+ throw new Error(`Invalid workflow selector '${selector}' — Fast is available only for Codex selectors`)
216
+ }
217
+ const candidate: WorkflowCandidate = { company: TOOL_COMPANIES[tool], tool, model, effort }
185
218
  return {
186
219
  selector,
187
- candidates: [{ company: TOOL_COMPANIES[tool], tool, model, effort }]
220
+ candidates: [fast ? { ...candidate, service_tier: "fast" } : candidate]
188
221
  }
189
222
  }
190
223
 
@@ -199,8 +232,8 @@ function loadWorkflowRegistry(): WorkflowRegistryView {
199
232
  "Embedded workflow registry",
200
233
  ["availability", "defaults", "exact_target_grammar", "profiles", "schema"]
201
234
  )
202
- if (workflow["schema"] !== 1) throw new Error("Embedded workflow registry schema must be 1")
203
- if (workflow["exact_target_grammar"] !== "<tool>:<model>@<effort>") {
235
+ if (workflow["schema"] !== 2) throw new Error("Embedded workflow registry schema must be 2")
236
+ if (workflow["exact_target_grammar"] !== "<tool>:<model>@<effort>[+fast]") {
204
237
  throw new Error("Embedded workflow exact-target grammar is invalid")
205
238
  }
206
239
  if (workflow["availability"] !== "checked_when_used") {
@@ -234,7 +267,7 @@ function loadWorkflowRegistry(): WorkflowRegistryView {
234
267
  if (typeof rawDefaults[role] !== "string") throw new Error(`Embedded workflow ${role} default is invalid`)
235
268
  }
236
269
  const registry: WorkflowRegistryView = {
237
- schema: 1,
270
+ schema: 2,
238
271
  profiles,
239
272
  defaults: {
240
273
  orchestrator: rawDefaults["orchestrator"] as string,
@@ -246,7 +279,7 @@ function loadWorkflowRegistry(): WorkflowRegistryView {
246
279
  }
247
280
  },
248
281
  tools,
249
- exact_target_grammar: "<tool>:<model>@<effort>",
282
+ exact_target_grammar: "<tool>:<model>@<effort>[+fast]",
250
283
  availability: "checked_when_used"
251
284
  }
252
285
  resolveSelector(registry.defaults.orchestrator, registry)
@@ -285,55 +318,74 @@ export function defaultWorkflowRecord(): WorkflowRecordV1 {
285
318
  function parseRole(
286
319
  value: unknown,
287
320
  name: WorkflowRoleName,
288
- registry: WorkflowRegistryView
321
+ registry: WorkflowRegistryView,
322
+ schema: 1 | 2
289
323
  ): WorkflowRole {
290
324
  const role = expectRecord(value, `Workflow ${name} role`, ["candidates", "selector"])
291
325
  if (typeof role["selector"] !== "string") throw new Error(`Workflow ${name} selector is invalid`)
292
326
  const resolved = resolveSelector(role["selector"], registry)
293
- const candidates = parseCandidates(role["candidates"], `Workflow ${name} candidates`, registry.tools)
327
+ const candidates = parseCandidates(
328
+ role["candidates"],
329
+ `Workflow ${name} candidates`,
330
+ registry.tools,
331
+ schema === 2
332
+ )
294
333
  if (compactJcs(candidates) !== compactJcs(resolved.candidates)) {
295
334
  throw new Error(`Workflow ${name} candidates do not match its selector`)
296
335
  }
297
336
  return resolved
298
337
  }
299
338
 
300
- export function parseWorkflowRecord(value: unknown): WorkflowRecordV1 {
339
+ function recordUsesFast(roles: ReadonlyArray<WorkflowRole>): boolean {
340
+ return roles.some((role) => role.candidates.some((candidate) => candidate.service_tier === "fast"))
341
+ }
342
+
343
+ export function parseWorkflowRecord(value: unknown): WorkflowRecord {
301
344
  const record = expectRecord(
302
345
  value,
303
346
  "Workflow record",
304
347
  ["implementer", "orchestrator", "review", "reviewer", "schema"]
305
348
  )
306
- if (record["schema"] !== 1) throw new Error("Workflow record schema must be 1")
349
+ const schema = record["schema"]
350
+ if (schema !== 1 && schema !== 2) throw new Error("Workflow record schema must be 1 or 2")
307
351
  const registry = loadWorkflowRegistry()
308
352
  const review = expectRecord(record["review"], "Workflow record review", ["max_rounds", "minimum_score"])
309
- return {
310
- schema: 1,
311
- orchestrator: parseRole(record["orchestrator"], "orchestrator", registry),
312
- reviewer: parseRole(record["reviewer"], "reviewer", registry),
313
- implementer: parseRole(record["implementer"], "implementer", registry),
353
+ const orchestrator = parseRole(record["orchestrator"], "orchestrator", registry, schema)
354
+ const reviewer = parseRole(record["reviewer"], "reviewer", registry, schema)
355
+ const implementer = parseRole(record["implementer"], "implementer", registry, schema)
356
+ const parsed = {
357
+ orchestrator,
358
+ reviewer,
359
+ implementer,
314
360
  review: {
315
361
  minimum_score: parseNumericBound("minimum_score", review["minimum_score"]),
316
362
  max_rounds: parseNumericBound("max_rounds", review["max_rounds"])
317
363
  }
318
364
  }
365
+ if (schema === 2 && !recordUsesFast([orchestrator, reviewer, implementer])) {
366
+ throw new Error("Workflow record schema 2 requires a Fast service tier")
367
+ }
368
+ return schema === 2 ? { schema: 2, ...parsed } : { schema: 1, ...parsed }
319
369
  }
320
370
 
321
371
  export function buildWorkflowRecord(
322
372
  overrides: WorkflowOverrides,
323
- base: WorkflowRecordV1 = defaultWorkflowRecord()
324
- ): WorkflowRecordV1 {
373
+ base: WorkflowRecord = defaultWorkflowRecord()
374
+ ): WorkflowRecord {
325
375
  const current = parseWorkflowRecord(base)
326
- return {
327
- schema: 1,
328
- orchestrator: overrides.orchestrator === undefined
376
+ const orchestrator = overrides.orchestrator === undefined
329
377
  ? current.orchestrator
330
- : resolveWorkflowSelector(overrides.orchestrator),
331
- reviewer: overrides.reviewer === undefined
378
+ : resolveWorkflowSelector(overrides.orchestrator)
379
+ const reviewer = overrides.reviewer === undefined
332
380
  ? current.reviewer
333
- : resolveWorkflowSelector(overrides.reviewer),
334
- implementer: overrides.implementer === undefined
381
+ : resolveWorkflowSelector(overrides.reviewer)
382
+ const implementer = overrides.implementer === undefined
335
383
  ? current.implementer
336
- : resolveWorkflowSelector(overrides.implementer),
384
+ : resolveWorkflowSelector(overrides.implementer)
385
+ const next = {
386
+ orchestrator,
387
+ reviewer,
388
+ implementer,
337
389
  review: {
338
390
  minimum_score: overrides.minimumScore === undefined
339
391
  ? current.review.minimum_score
@@ -343,6 +395,9 @@ export function buildWorkflowRecord(
343
395
  : parseWorkflowBound("max_rounds", overrides.maxRounds)
344
396
  }
345
397
  }
398
+ return recordUsesFast([orchestrator, reviewer, implementer])
399
+ ? { schema: 2, ...next }
400
+ : { schema: 1, ...next }
346
401
  }
347
402
 
348
403
  function canonicalValue(value: unknown): unknown {
@@ -366,7 +421,7 @@ export function compactJcs(value: unknown): string {
366
421
  return JSON.stringify(canonicalValue(value))
367
422
  }
368
423
 
369
- export function renderWorkflowRecordLine(record: WorkflowRecordV1): string {
424
+ export function renderWorkflowRecordLine(record: WorkflowRecord): string {
370
425
  return `${WORKFLOW_RECORD_PREFIX}${compactJcs(parseWorkflowRecord(record))}`
371
426
  }
372
427
 
@@ -390,6 +445,8 @@ export function workflowCatalog(): string {
390
445
  ` implementer ${registry.defaults.implementer}`,
391
446
  ` review minimum score ${registry.defaults.review.minimum_score}; maximum rounds ${registry.defaults.review.max_rounds}`,
392
447
  `Exact targets: ${registry.exact_target_grammar}`,
448
+ "Fast: append +fast to a Codex exact target. Without +fast, Codex roles use Standard.",
449
+ "Compatibility: +fast requires a Docks consumer that supports workflow record schema 2.",
393
450
  "Availability: checked when used by Docks; docks-kit does not probe providers."
394
451
  ].join("\n")
395
452
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docks-kit",
3
- "version": "0.7.1",
3
+ "version": "0.8.1",
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",