docks-kit 0.16.7 → 0.16.8

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
@@ -63,10 +63,10 @@ Codex SoT notes:
63
63
  omp SoT notes:
64
64
  - `SoT/.omp/AGENTS.md`, `config.yml`, `models.yml`, and `mcp.json` deploy to `~/.omp/agent/`.
65
65
  - `SoT/.omp/intercom.json` deploys to `$PI_CODING_AGENT_DIR/intercom/config.json`. The default root is `~/.pi/agent`.
66
- - `ompSync.ts syncMergedYaml` deep-merges `config.yml` through `mergeOmpConfig`.
66
+ - `ompSync.ts syncMergedYaml` deep-merges `config.yml` through `ompYaml.ts mergeOmpConfig` and `models.yml` through `mergeOmpModels`. Both wrap one generic mapping merge; only the config wrapper prunes stale `retry.fallbackChains` wildcards.
67
67
  - `cycleOrder` ends with the `fable` role (`modelRoles.fable` = `anthropic/claude-fable-5-1:medium`, `modelTags.fable` visible, `retry.fallbackChains.fable` empty), so the model switcher reaches Fable 5.1 as its fourth stop and never falls back off it. The hidden `switch_fable` role points at the same model and level.
68
68
  - `modelRoles.task` is `openai-codex/gpt-6-astra:low`, chosen for 2.60 s TTFT and 4k output tokens per task at cost parity with the previous `gpt-5.6-sol:high`. `task.agentModelOverrides` points four reviewer names at `@task`; only the bundled `reviewer` and `security-reviewer` are discoverable OMP agents, so those two inherit `task`, and the `code-reviewer` and `plan-reviewer` entries are dormant. Artificial Analysis publishes no per-level Astra Coding Agent Index score, so reviewer output is the signal to watch.
69
- - `SoT/.omp/models.yml` deploys to `~/.omp/agent/models.yml` through the same `mergeOmpConfig` merge as `config.yml`. A user file may carry provider credentials, so whole-file replacement is wrong. It restricts the `gpt-6-astra` effort ladder to `low`, so every Astra subagent runs `low` while `task.maxEffort: high` lets `scout` and `sonic` reach Luna `high` with `effort: hi`. `ompSync.ts syncMergedYaml` owns both YAML deployments. `cli/docs/omp-models.md` records the verification.
69
+ - `SoT/.omp/models.yml` deploys to `~/.omp/agent/models.yml` through `mergeOmpModels`, which keeps every deployed-only key. A user file may carry provider credentials, so whole-file replacement is wrong. It restricts the `gpt-6-astra` effort ladder to `low`, so every Astra subagent runs `low` while `task.maxEffort: high` lets `scout` and `sonic` reach Luna `high` with `effort: hi`. `cli/docs/omp-models.md` records the verification.
70
70
  - `SoT/.omp/AGENTS.md` carries the rule `Please remove all mannered prose.` Anthropic's Fable 5.1 prompting guide documents mannered prose as a Fable 5.1 behavior and gives that sentence as its short-version fix: https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/prompting-claude-fable-5-1
71
71
  - `cli/docs/omp-models.md` (topic `omp-models`) records the role map rationale and the Artificial Analysis snapshot behind it. Model choices change with published benchmarks, so update that topic in the same commit as a role change.
72
72
  - Sync registers the `docks` marketplace. It installs or upgrades `docks@docks` and `plan-lifecycle@docks` at user scope.
@@ -14,7 +14,7 @@ import { p, spawnProcess, type AsyncProcessResult } from "./exec"
14
14
  import type { Ctx } from "./index"
15
15
  import { isObject, parseJson } from "./jq"
16
16
  import { ompPaths } from "./ompPaths"
17
- import { mergeOmpConfig } from "./ompYaml"
17
+ import { mergeOmpConfig, mergeOmpModels } from "./ompYaml"
18
18
  import { field } from "./toolchain"
19
19
 
20
20
  const MARKETPLACE_NAME = "docks"
@@ -35,8 +35,8 @@ export async function ompSync(ctx: Ctx): Promise<OmpState> {
35
35
 
36
36
  syncWholeFile(ctx, "SoT/.omp/AGENTS.md", p(agentDir, "AGENTS.md"), "omp AGENTS.md already in sync", "omp AGENTS.md synced")
37
37
  syncWholeFile(ctx, "SoT/.omp/mcp.json", p(agentDir, "mcp.json"), "omp mcp.json already in sync", "omp mcp.json synced")
38
- syncMergedYaml(ctx, "SoT/.omp/config.yml", p(agentDir, "config.yml"))
39
- syncMergedYaml(ctx, "SoT/.omp/models.yml", p(agentDir, "models.yml"))
38
+ syncMergedYaml(ctx, "SoT/.omp/config.yml", p(agentDir, "config.yml"), mergeOmpConfig)
39
+ syncMergedYaml(ctx, "SoT/.omp/models.yml", p(agentDir, "models.yml"), mergeOmpModels)
40
40
 
41
41
  const intercomRootSetting = process.env["PI_CODING_AGENT_DIR"]
42
42
  const intercomRoot = intercomRootSetting !== undefined && intercomRootSetting !== ""
@@ -112,7 +112,12 @@ function syncWholeFile(
112
112
  ctx.nextStepTriggers.ompRestart = true
113
113
  }
114
114
 
115
- function syncMergedYaml(ctx: Ctx, sourcePath: OmpTextPayloadPath, target: string): void {
115
+ function syncMergedYaml(
116
+ ctx: Ctx,
117
+ sourcePath: OmpTextPayloadPath,
118
+ target: string,
119
+ merge: (sotText: string, deployedText: string) => string
120
+ ): void {
116
121
  const { change, echo, verbose } = ctx.services.logger
117
122
  const source = payloadDisplayPath(sourcePath)
118
123
  const sotText = payloadText(sourcePath)
@@ -130,7 +135,7 @@ function syncMergedYaml(ctx: Ctx, sourcePath: OmpTextPayloadPath, target: string
130
135
  }
131
136
 
132
137
  const deployedText = readFileSync(target, "utf8")
133
- const merged = mergeOmpConfig(sotText, deployedText)
138
+ const merged = merge(sotText, deployedText)
134
139
  if (merged === deployedText) {
135
140
  verbose(`omp ${name} already in sync`)
136
141
  return
@@ -1,12 +1,18 @@
1
1
  /**
2
- * YAML merge for the omp config.yml and models.yml files.
3
- * The kit uses the yaml package rather than a line-based merge. Deployed-only keys are
4
- * retained except for slash-bearing keys directly under retry.fallbackChains:
5
- * omp treats those as model or provider wildcards ahead of role chains, so a
6
- * stale wildcard would silently override the kit-managed role chains.
2
+ * YAML merge for the omp config.yml and models.yml files. omp serialises both
3
+ * files itself, so the kit uses the yaml package rather than a line-based
4
+ * merge. Deployed-only keys are retained, except where the file-specific
5
+ * wrapper drops them: config.yml drops slash-bearing keys directly under
6
+ * retry.fallbackChains, because omp treats those as model or provider
7
+ * wildcards ahead of role chains, so a stale wildcard would silently override
8
+ * the kit-managed role chains. models.yml drops nothing; a user file may carry
9
+ * provider credentials and custom models the kit never declares.
7
10
  */
8
11
  import { isMap, isScalar, parseDocument, type YAMLMap } from "yaml"
9
12
 
13
+ /** Decides whether a deployed-only key at `path` is dropped instead of kept. */
14
+ type DropDeployedKey = (path: ReadonlyArray<string>, key: string) => boolean
15
+
10
16
  const fallbackChainsPath = ["retry", "fallbackChains"] as const
11
17
 
12
18
  function stringKey(key: unknown): string | undefined {
@@ -15,29 +21,31 @@ function stringKey(key: unknown): string | undefined {
15
21
  return undefined
16
22
  }
17
23
 
18
- function isFallbackChains(path: ReadonlyArray<string>): boolean {
19
- return (
20
- path.length === fallbackChainsPath.length &&
21
- path[0] === fallbackChainsPath[0] &&
22
- path[1] === fallbackChainsPath[1]
23
- )
24
+ function keyName(key: unknown): string {
25
+ return isScalar(key) ? String(key.value) : String(key)
24
26
  }
25
27
 
26
- function pruneFallbackWildcards(mapping: YAMLMap, path: ReadonlyArray<string>): void {
27
- const underFallbackChains = isFallbackChains(path)
28
+ const dropFallbackWildcard: DropDeployedKey = (path, key) =>
29
+ path.length === fallbackChainsPath.length &&
30
+ path[0] === fallbackChainsPath[0] &&
31
+ path[1] === fallbackChainsPath[1] &&
32
+ key.includes("/")
33
+
34
+ const keepEveryKey: DropDeployedKey = () => false
35
+
36
+ function pruneMapping(mapping: YAMLMap, path: ReadonlyArray<string>, drop: DropDeployedKey): void {
28
37
  for (let index = mapping.items.length - 1; index >= 0; index--) {
29
38
  const pair = mapping.items[index]
30
39
  if (pair === undefined) continue
31
40
 
32
- const keyName = isScalar(pair.key) ? String(pair.key.value) : String(pair.key)
33
- if (underFallbackChains && keyName.includes("/")) {
41
+ if (drop(path, keyName(pair.key))) {
34
42
  mapping.items.splice(index, 1)
35
43
  continue
36
44
  }
37
45
 
38
46
  const key = stringKey(pair.key)
39
47
  if (key !== undefined && isMap(pair.value)) {
40
- pruneFallbackWildcards(pair.value, [...path, key])
48
+ pruneMapping(pair.value, [...path, key], drop)
41
49
  }
42
50
  }
43
51
  }
@@ -45,7 +53,8 @@ function pruneFallbackWildcards(mapping: YAMLMap, path: ReadonlyArray<string>):
45
53
  function mergeMappings(
46
54
  sotMapping: YAMLMap,
47
55
  deployedMapping: YAMLMap,
48
- path: ReadonlyArray<string>
56
+ path: ReadonlyArray<string>,
57
+ drop: DropDeployedKey
49
58
  ): void {
50
59
  for (const sotPair of sotMapping.items) {
51
60
  const key = stringKey(sotPair.key)
@@ -53,7 +62,7 @@ function mergeMappings(
53
62
 
54
63
  const deployedPair = deployedMapping.items.find((candidate) => stringKey(candidate.key) === key)
55
64
  if (deployedPair !== undefined && isMap(sotPair.value) && isMap(deployedPair.value)) {
56
- mergeMappings(sotPair.value, deployedPair.value, [...path, key])
65
+ mergeMappings(sotPair.value, deployedPair.value, [...path, key], drop)
57
66
  }
58
67
  }
59
68
 
@@ -63,22 +72,24 @@ function mergeMappings(
63
72
  key !== undefined && sotMapping.items.some((candidate) => stringKey(candidate.key) === key)
64
73
  if (isManaged) continue
65
74
 
66
- const keyName = isScalar(deployedPair.key)
67
- ? String(deployedPair.key.value)
68
- : String(deployedPair.key)
69
- if (isFallbackChains(path) && keyName.includes("/")) continue
75
+ if (drop(path, keyName(deployedPair.key))) continue
70
76
  if (key !== undefined && isMap(deployedPair.value)) {
71
- pruneFallbackWildcards(deployedPair.value, [...path, key])
77
+ pruneMapping(deployedPair.value, [...path, key], drop)
72
78
  }
73
79
  sotMapping.items.push(deployedPair)
74
80
  }
75
81
  }
76
82
 
77
- export function mergeOmpConfig(sotText: string, deployedText: string): string {
83
+ function mergeYamlDocuments(
84
+ sotText: string,
85
+ deployedText: string,
86
+ name: string,
87
+ drop: DropDeployedKey
88
+ ): string {
78
89
  const deployedDoc = parseDocument(deployedText)
79
90
  const deployedError = deployedDoc.errors[0]
80
91
  if (deployedError !== undefined) {
81
- throw new Error(`Invalid deployed omp YAML: ${deployedError.message}`)
92
+ throw new Error(`Invalid deployed omp ${name} YAML: ${deployedError.message}`)
82
93
  }
83
94
 
84
95
  const deployedContents = deployedDoc.contents
@@ -90,18 +101,28 @@ export function mergeOmpConfig(sotText: string, deployedText: string): string {
90
101
  return sotText
91
102
  }
92
103
  if (!isMap(deployedContents)) {
93
- throw new Error("Deployed omp YAML root must be a mapping")
104
+ throw new Error(`Deployed omp ${name} YAML root must be a mapping`)
94
105
  }
95
106
 
96
107
  const sotDoc = parseDocument(sotText)
97
108
  const sotError = sotDoc.errors[0]
98
109
  if (sotError !== undefined) {
99
- throw new Error(`Invalid SoT omp YAML: ${sotError.message}`)
110
+ throw new Error(`Invalid SoT omp ${name} YAML: ${sotError.message}`)
100
111
  }
101
112
  if (!isMap(sotDoc.contents)) {
102
- throw new Error("SoT omp YAML root must be a mapping")
113
+ throw new Error(`SoT omp ${name} YAML root must be a mapping`)
103
114
  }
104
115
 
105
- mergeMappings(sotDoc.contents, deployedContents, [])
116
+ mergeMappings(sotDoc.contents, deployedContents, [], drop)
106
117
  return sotDoc.toString()
107
118
  }
119
+
120
+ /** config.yml merge: keeps user-only keys, drops stale fallback-chain wildcards. */
121
+ export function mergeOmpConfig(sotText: string, deployedText: string): string {
122
+ return mergeYamlDocuments(sotText, deployedText, "config.yml", dropFallbackWildcard)
123
+ }
124
+
125
+ /** models.yml merge: keeps every user-only key, including credentials and custom models. */
126
+ export function mergeOmpModels(sotText: string, deployedText: string): string {
127
+ return mergeYamlDocuments(sotText, deployedText, "models.yml", keepEveryKey)
128
+ }
@@ -1,7 +1,7 @@
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.16.7"
4
+ export const GENERATED_PACKAGE_VERSION = "0.16.8"
5
5
 
6
6
  export const GENERATED_PAYLOAD_TEXT = {
7
7
  "SoT/.agents/skills.txt": "# Universal AI-agent skill manifest intentionally empty.\n# Global skill discovery is opt-in: add one <owner>/<repo> slug per line.\n# EngineNative ignores comments and blank lines.\n",
@@ -21,7 +21,7 @@ export const GENERATED_PAYLOAD_TEXT = {
21
21
  "SoT/.omp/config.yml": "symbolPreset: unicode\n\ntheme:\n dark: titanium\n\nstatusLine:\n preset: default\n compactThinkingLevel: false\n showHookStatus: true\n sessionAccent: true\n transparent: false\n\nterminal:\n showProgress: false\n\ntui:\n textSizing: false\n tight: false\n\ndefaultThinkingLevel: high\n\ncycleOrder:\n - smol\n - default\n - slow\n - fable\n\ntier:\n openai: none\n anthropic: none\n\nadvisor:\n enabled: true\n syncBacklog: \"1\"\n\ngithub:\n enabled: true\n\ntask:\n eager: always\n showResolvedModelBadge: true\n softRequestBudget: 200\n softRequestBudgetNotice: true\n batch: true\n enableLsp: true\n enableEffort: true\n maxEffort: high\n maxConcurrency: 16\n maxRecursionDepth: 2\n maxRuntimeMs: 3600000\n agentModelOverrides:\n reviewer: \"@task\"\n security-reviewer: \"@task\"\n code-reviewer: \"@task\"\n plan-reviewer: \"@task\"\n\nmodelRoles:\n smol: openai-codex/gpt-5.6-luna:medium\n advisor: openai-codex/gpt-5.6-sol:medium\n designer: anthropic/claude-opus-5:high\n plan: anthropic/claude-opus-5:xhigh\n commit: openai-codex/gpt-5.6-luna:medium\n task: openai-codex/gpt-6-astra:low\n vision: anthropic/claude-opus-5:medium\n tiny: openai-codex/gpt-5.6-luna:low\n default: anthropic/claude-opus-5:high\n slow: anthropic/claude-opus-5:xhigh\n fable: anthropic/claude-fable-5-1:medium\n switch_fable: anthropic/claude-fable-5-1:medium\n\nmodelTags:\n fable:\n name: Fable 5.1\n switch_fable:\n name: Fable switch default\n hidden: true\n\ndisplay:\n shimmer: classic\n showTokenUsage: true\n\nproviders:\n anthropic:\n serverSideFallback: false\n fetch: auto\n webSearchTimeoutSeconds: 30\n webSearchOrder:\n - firecrawl\n - exa\n - perplexity\n - gemini\n - codex\n\nretry:\n usageAwareFallback: true\n fallbackChains:\n default:\n - openai-codex/gpt-5.6-sol:high\n advisor:\n - anthropic/claude-opus-5:medium\n task:\n - anthropic/claude-opus-5:high\n vision:\n - openai-codex/gpt-5.6-sol:medium\n smol:\n - anthropic/claude-opus-5:low\n tiny:\n - anthropic/claude-opus-5:low\n commit:\n - anthropic/claude-opus-5:medium\n switch_fable: []\n fable: []\n\nomitThinking: false\nincludeWorkspaceTree: false\nautocompleteMaxVisible: 10\nemojiAutocomplete: true\n\nbranchSummary:\n enabled: true\n\nreadLineNumbers: false\n\ncommands:\n enableOpencodeProject: false\n enableOpencodeUser: false\n\nskills:\n enableClaudeUser: false\n enableCodexUser: false\n enableAgentsUser: true\n\nsteeringMode: all\ninterruptMode: immediate\n\ndev:\n autoqa: false\n autoqaConsent: denied\n\ncompaction:\n thresholdTokens: 231200\n idleEnabled: true\n handoffSaveToDisk: true\n\ndoubleEscapeAction: tree\nhideThinkingBlock: false\nautoResume: false\ntextVerbosity: low\n\nfeatures:\n unexpectedStopDetection: smart\n\ncodexResets:\n autoRedeem: \"no\"\n\nstartup:\n quiet: true\n changelogMode: summary\n\ntools:\n approvalMode: yolo\n",
22
22
  "SoT/.omp/intercom.json": "{\n \"brokerCommand\": \"bun\",\n \"brokerArgs\": []\n}\n",
23
23
  "SoT/.omp/mcp.json": "{\n \"$schema\": \"https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/config/mcp-schema.json\",\n \"disabledServers\": [\n \"chrome-devtools\",\n \"context7:context7\",\n \"openaiDeveloperDocs\"\n ]\n}\n",
24
- "SoT/.omp/models.yml": "providers:\n openai-codex:\n modelOverrides:\n gpt-6-astra:\n thinking:\n mode: effort\n efforts: [low]\n defaultLevel: low\n"
24
+ "SoT/.omp/models.yml": "providers:\n openai-codex:\n modelOverrides:\n gpt-6-astra:\n thinking:\n mode: effort\n efforts:\n - low\n defaultLevel: low\n"
25
25
  } as const
26
26
 
27
27
  export const GENERATED_PAYLOAD_BASE64 = {
@@ -50,4 +50,4 @@ export const GENERATED_PAYLOAD_PATHS = [
50
50
  "notification.mp3"
51
51
  ] as const
52
52
 
53
- export const GENERATED_PAYLOAD_HASH = "a6afdeb3a37285dda06cb48bcf39b5a42702e8d06bf9c47f590047ba6c767bc9"
53
+ export const GENERATED_PAYLOAD_HASH = "5f9f8b3d00c15f6611af2bd8d92bbfcd12481442ff6e0e249846ac2e320b4e37"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docks-kit",
3
- "version": "0.16.7",
3
+ "version": "0.16.8",
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",