docks-kit 0.16.9 → 0.16.11

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
@@ -82,7 +82,7 @@ For per-tool SoT layouts (`SoT/.claude/`, `SoT/.codex/`, `SoT/.omp/`), see the m
82
82
  - **Removed bash engine.** The bash engine was removed after the `bash-engine-final` tag. `DOCKS_KIT_ENGINE=bash` must fail with the removed-engine message; engine bugs are fixed forward in EngineNative.
83
83
  - **Effect 4 CLI stack.** The CLI pins `effect@4.0.0-rc.109` (including `effect/unstable/cli`), `@effect/platform-bun@4.0.0-rc.109` (`BunServices.layer`, `BunRuntime.runMain`), `@effect/vitest@4.0.0-rc.109`, and `vitest@4.1.11` (inside the `@effect/vitest` peer range `>=4.1.0 <5.0.0`; 4.1.11 fixes GHSA-82fw-gwwq-j7x9). `@effect/cli` and `@effect/platform` are removed and must not be reintroduced.
84
84
  - **Effect skill routing.** Effect work in this checkout must verify migration and API call shapes against the installed declarations under `node_modules/effect/dist/unstable/cli/`, never from memory or a mutable dist-tag. The `effect-ts-setup`, `effect-ts-port`, and `effect-ts-specialist` skills target Effect 3.x and do not apply.
85
- - **Targeted syncs.** `./docks-kit sync` accepts positional targets: `claude`, `codex`, `agents`, and `omp`. Use the narrowest target that matches the SoT change (for example, `./docks-kit sync omp` for omp-only config edits); targets can be combined with `--dry-run`, `--skip-bubblewrap` (skip optional bubblewrap bootstrap for the Codex Linux sandbox), `--skip-plugin-refresh` (install missing plugins without refreshing existing caches; used by `docks-kit update`), `--reconcile`, `--prune`, and the deploy-time modifiers `--claude-compact-window=<tokens>` / `--claude-permissive` / `--claude-model=<m>` / `--claude-effort=<level>` / `--claude-advisor=<on|off|default>` / `--codex-model=<m>` / `--codex-effort=<level>` (see `CLAUDE.md` § Deploy-time modifiers).
85
+ - **Targeted syncs.** `./docks-kit sync` accepts positional targets: `claude`, `codex`, `agents`, and `omp`. Use the narrowest target that matches the SoT change (for example, `./docks-kit sync omp` for omp-only config edits); targets can be combined with `--dry-run`, `--skip-bubblewrap` (skip optional bubblewrap bootstrap for the Codex Linux sandbox), `--skip-plugin-refresh` (install missing plugins without refreshing existing caches), `--reconcile`, `--prune`, and the deploy-time modifiers `--claude-compact-window=<tokens>` / `--claude-permissive` / `--claude-model=<m>` / `--claude-effort=<level>` / `--claude-advisor=<on|off|default>` / `--codex-model=<m>` / `--codex-effort=<level>` (see `CLAUDE.md` § Deploy-time modifiers).
86
86
  - **Per-machine harness selection.** `~/.docks-kit/state.json` drives a flag-less sync. A missing file selects `claude`, `codex`, and `agents`; it never selects `omp` implicitly. `sync` never prompts and never writes the selection file. `docks-kit harnesses` is the only command that writes the selection.
87
87
  - **Additive by default.** Keys present in deployed config but absent from SoT are preserved on default sync. This protects user-only additions, but means drift accumulates — neither flag-less reset can clean it up. The one exception is the Claude `removed` manifest (`claude::_removed_manifest`), a curated list of unambiguous kit-owned artifacts that `claude::sync_removals` force-prunes on every sync, including the home-relative `~/.local/bin/session-relay` artifact installed outside `~/.claude`; see `CLAUDE.md` § Pruning stale artifacts.
88
88
  - **`--reconcile` / `--prune` are the kit-owned reconcile flags.** Orthogonal — `--reconcile` reconciles the settings layer (SoT-declared keys/tables/arrays win; user-only keys and nested objects are preserved; permissions arrays are replaced wholesale by SoT). `--prune` uninstalls kit-managed installations not in the SoT (plugins, marketplaces, and `~/.agents/skills/*` entries tracked in `~/.agents/.kit-managed-skills`). Combine for a full reset to SoT's kit-managed scope. User-only additions outside the kit's scope (custom env vars, mcpServers, manually-installed skills, third-party plugins not declared in SoT) are always preserved. Each tool's per-tool file documents the specific paths and diff recipes.
package/cli/docs/flags.md CHANGED
@@ -19,7 +19,7 @@ docks-kit sync claude agents # two
19
19
  | `--reconcile` | Settings layer reconciled toward SoT (SoT keys win; user-only keys preserved; permissions arrays replaced) |
20
20
  | `--prune` | Uninstall kit-managed installs not in SoT: plugins, marketplaces, universal skills |
21
21
  | `--skip-bubblewrap` | Skip optional bubblewrap bootstrap (Codex Linux sandbox) |
22
- | `--skip-plugin-refresh` | Install missing Claude/Codex plugins but skip refresh-only updates; `docks-kit update` uses this automatically |
22
+ | `--skip-plugin-refresh` | Install missing Claude/Codex plugins but skip refresh-only updates |
23
23
  | `--verbose` / `-v` | Also print no-op confirmations (already in sync, up to date, left as-is); accepted on `sync`, `model`, and `toolchain` |
24
24
 
25
25
  ## Environment overrides
@@ -80,7 +80,7 @@ installer bootstraps Bun when absent. It then runs
80
80
  ## Keeping the kit up to date
81
81
 
82
82
  ```
83
- docks-kit update # autodetect + update + install-missing-only sync
83
+ docks-kit update # autodetect + update + full sync (refreshes plugins)
84
84
  docks-kit update --no-sync # update only
85
85
  ```
86
86
 
@@ -68,11 +68,7 @@ const chainSync = (argv0: string, args: Array<string>): Effect.Effect<void> =>
68
68
  if (res.error !== undefined || res.status !== 0) process.exit(res.status ?? 1)
69
69
  })
70
70
 
71
- export const updateSyncArgs = (home: string): Array<string> => [
72
- p(home, "cli/src/main.ts"),
73
- "sync",
74
- "--skip-plugin-refresh"
75
- ]
71
+ export const updateSyncArgs = (home: string): Array<string> => [p(home, "cli/src/main.ts"), "sync"]
76
72
 
77
73
  const readPackageVersion = (home: string): string => {
78
74
  try {
@@ -211,28 +207,33 @@ const updateCheckout = (home: string, skipSync: boolean) =>
211
207
  if (!pull.ok) return yield* bail(`git pull --ff-only failed (diverged history?):\n${pull.out}`)
212
208
  const after = git(home, ["rev-parse", "HEAD"]).out
213
209
 
214
- if (before === after) {
215
- return yield* Console.log(`Already at the latest version (${after.slice(0, 7)}, upstream ${upstream.out}).`)
216
- }
217
-
218
- const count = git(home, ["rev-list", "--count", `${before}..${after}`]).out
219
- yield* Console.log(`Updated ${before.slice(0, 7)}..${after.slice(0, 7)} (${count} commit(s) from ${upstream.out}).`)
210
+ // A current kit still syncs: the plugin passes deliver marketplace and
211
+ // plugin updates that move independently of the kit's own version.
212
+ const changed = before !== after
213
+ if (changed) {
214
+ const count = git(home, ["rev-list", "--count", `${before}..${after}`]).out
215
+ yield* Console.log(`Updated ${before.slice(0, 7)}..${after.slice(0, 7)} (${count} commit(s) from ${upstream.out}).`)
220
216
 
221
- const touched = git(home, ["diff", "--name-only", before, after]).out.split("\n")
222
- if (touched.includes("bun.lock") || touched.includes("package.json")) {
223
- const res = spawnUpdate("bun", ["install", "--frozen-lockfile"], { cwd: home, stdio: "inherit" })
224
- if (res.error !== undefined || res.status !== 0) {
225
- return yield* bail("dependencies changed but 'bun install --frozen-lockfile' failed - fix that, then run docks-kit sync", 1)
217
+ const touched = git(home, ["diff", "--name-only", before, after]).out.split("\n")
218
+ if (touched.includes("bun.lock") || touched.includes("package.json")) {
219
+ const res = spawnUpdate("bun", ["install", "--frozen-lockfile"], { cwd: home, stdio: "inherit" })
220
+ if (res.error !== undefined || res.status !== 0) {
221
+ return yield* bail("dependencies changed but 'bun install --frozen-lockfile' failed - fix that, then run docks-kit sync", 1)
222
+ }
226
223
  }
224
+ } else {
225
+ yield* Console.log(`Already at the latest version (${after.slice(0, 7)}, upstream ${upstream.out}).`)
227
226
  }
228
227
 
229
228
  if (compiled) {
230
229
  return yield* Console.log(
231
- "This compiled binary still runs the previous version - the checkout launcher will use updated source next time. Run: ./docks-kit sync (rebuild with bash cli/build-binaries.sh to restore the binary fast path)."
230
+ changed
231
+ ? "This compiled binary still runs the previous version - the checkout launcher will use updated source next time. Run: ./docks-kit sync (rebuild with bash cli/build-binaries.sh to restore the binary fast path)."
232
+ : "This compiled binary cannot chain the sync. Run: ./docks-kit sync"
232
233
  )
233
234
  }
234
- if (skipSync) return yield* Console.log("Kit updated. Run: docks-kit sync")
235
- yield* Console.log("Kit updated - running sync with the new version...")
235
+ if (skipSync) return yield* Console.log(changed ? "Kit updated. Run: docks-kit sync" : "Run: docks-kit sync")
236
+ yield* Console.log(changed ? "Kit updated - running sync with the new version..." : "Syncing to deliver plugin and config updates...")
236
237
  return yield* chainSync(process.execPath, updateSyncArgs(home))
237
238
  })
238
239
 
@@ -267,9 +268,12 @@ const updatePackage = (home: string, skipSync: boolean) =>
267
268
  }
268
269
  const result = packageUpdateResult(beforeVersion, afterVersion, home === updated.home)
269
270
  if (result.message !== "") yield* Console.log(result.message)
270
- if (result.alreadyCurrent) return
271
- if (skipSync) return yield* Console.log("Kit updated. Run: docks-kit sync")
272
- yield* Console.log("Kit updated - running sync with the new version...")
271
+ if (skipSync) return yield* Console.log(result.alreadyCurrent ? "Run: docks-kit sync" : "Kit updated. Run: docks-kit sync")
272
+ yield* Console.log(
273
+ result.alreadyCurrent
274
+ ? "Syncing to deliver plugin and config updates..."
275
+ : "Kit updated - running sync with the new version..."
276
+ )
273
277
  return yield* chainSync(process.execPath, updateSyncArgs(updated.home))
274
278
  })
275
279
 
@@ -288,6 +292,6 @@ export const updateCommand = Command.make("update", { noSync }, (config) =>
288
292
  })
289
293
  ).pipe(
290
294
  Command.withDescription(
291
- "Self-update the kit: autodetects the install (git checkout -> ff-only pull; bun/npm global -> @latest) and chains an install-missing-only sync with the new version (--no-sync to skip)."
295
+ "Self-update the kit: autodetects the install (git checkout -> ff-only pull; bun/npm global -> @latest), then chains a flag-less sync that also refreshes plugin marketplaces and plugins, even when the kit was already current (--no-sync to skip)."
292
296
  )
293
297
  )
@@ -11,9 +11,19 @@ import { compareCodepoints, deepMerge, isObject, jqStringify, parseJson, readJso
11
11
  import { field } from "./toolchain"
12
12
  import { payloadText } from "../payload"
13
13
 
14
- async function cli(args: Array<string>): Promise<{ ok: boolean; out: string }> {
14
+ async function cli(args: Array<string>): Promise<{ ok: boolean; out: string; detail: string }> {
15
15
  const res = await spawnProcess("claude", args, { stdio: ["ignore", "pipe", "pipe"] })
16
- return { ok: res.error === undefined && res.exitCode === 0, out: `${res.stdout}${res.stderr}` }
16
+ // A spawn error carries its cause in `error`, not in either stream, and that
17
+ // is the case a failure message cannot afford to drop: it names an
18
+ // unresolvable launcher instead of a rejected command.
19
+ const out = res.error !== undefined ? res.error.message : `${res.stdout}${res.stderr}`
20
+ const detail = out.split("\n").map((line) => line.trim()).find((line) => line !== "") ?? "unknown error"
21
+ return { ok: res.error === undefined && res.exitCode === 0, out, detail }
22
+ }
23
+
24
+ /** Failure message tail: the cause, then the command to re-run by hand. */
25
+ function manually(detail: string, args: Array<string>): string {
26
+ return `${detail}; run manually: claude ${args.join(" ")}`
17
27
  }
18
28
 
19
29
  function sortedKeys(obj: Json | undefined): Array<string> {
@@ -96,7 +106,10 @@ export async function syncPlugins(ctx: Ctx, claudeDir: string): Promise<void> {
96
106
  if (marketplaceResult.ok) {
97
107
  addedMp++
98
108
  } else {
99
- recordFailure(ctx, `Failed to add marketplace: ${mpName} (${repo})`)
109
+ recordFailure(
110
+ ctx,
111
+ `Failed to add marketplace: ${mpName} (${repo}): ${manually(marketplaceResult.detail, ["plugin", "marketplace", "add", repo])}`
112
+ )
100
113
  f1++
101
114
  }
102
115
  }
@@ -117,7 +130,10 @@ export async function syncPlugins(ctx: Ctx, claudeDir: string): Promise<void> {
117
130
  const refreshResult = await cli(["plugin", "marketplace", "update", mpName])
118
131
  clearProgress()
119
132
  if (!refreshResult.ok) {
120
- recordFailure(ctx, `Failed to refresh marketplace: ${mpName}`)
133
+ recordFailure(
134
+ ctx,
135
+ `Failed to refresh marketplace: ${mpName}: ${manually(refreshResult.detail, ["plugin", "marketplace", "update", mpName])}`
136
+ )
121
137
  f3++
122
138
  }
123
139
  refreshedMarketplaces.add(mpName)
@@ -128,7 +144,10 @@ export async function syncPlugins(ctx: Ctx, claudeDir: string): Promise<void> {
128
144
  if (installResult.ok) {
129
145
  addedPl++
130
146
  } else {
131
- recordFailure(ctx, `Failed to install plugin: ${pluginId}`)
147
+ recordFailure(
148
+ ctx,
149
+ `Failed to install plugin: ${pluginId}: ${manually(installResult.detail, ["plugin", "install", pluginId])}`
150
+ )
132
151
  f2++
133
152
  }
134
153
  }
@@ -162,7 +181,10 @@ export async function syncPlugins(ctx: Ctx, claudeDir: string): Promise<void> {
162
181
  clearProgress()
163
182
  refreshedMarketplaces.add(mpName)
164
183
  if (!refreshResult.ok) {
165
- recordFailure(ctx, `Failed to refresh marketplace: ${mpName}`)
184
+ recordFailure(
185
+ ctx,
186
+ `Failed to refresh marketplace: ${mpName}: ${manually(refreshResult.detail, ["plugin", "marketplace", "update", mpName])}`
187
+ )
166
188
  f3++
167
189
  }
168
190
  }
@@ -173,7 +195,10 @@ export async function syncPlugins(ctx: Ctx, claudeDir: string): Promise<void> {
173
195
  const updateResult = await cli(["plugin", "update", pluginId, "--scope", "user"])
174
196
  clearProgress()
175
197
  if (!updateResult.ok) {
176
- recordFailure(ctx, `Failed to update plugin: ${pluginId}`)
198
+ recordFailure(
199
+ ctx,
200
+ `Failed to update plugin: ${pluginId}: ${manually(updateResult.detail, ["plugin", "update", pluginId, "--scope", "user"])}`
201
+ )
177
202
  f4++
178
203
  } else if (updateResult.out.includes("Successfully updated")) {
179
204
  updatedPl++
@@ -196,7 +221,10 @@ export async function syncPlugins(ctx: Ctx, claudeDir: string): Promise<void> {
196
221
  if (uninstallResult.ok) {
197
222
  removedPl++
198
223
  } else {
199
- recordFailure(ctx, `Failed to uninstall plugin: ${pluginId}`)
224
+ recordFailure(
225
+ ctx,
226
+ `Failed to uninstall plugin: ${pluginId}: ${manually(uninstallResult.detail, ["plugin", "uninstall", "-y", "--scope", "user", pluginId])}`
227
+ )
200
228
  f5++
201
229
  }
202
230
  }
@@ -212,7 +240,10 @@ export async function syncPlugins(ctx: Ctx, claudeDir: string): Promise<void> {
212
240
  if (removeResult.ok) {
213
241
  removedMp++
214
242
  } else {
215
- recordFailure(ctx, `Failed to remove marketplace: ${mpName}`)
243
+ recordFailure(
244
+ ctx,
245
+ `Failed to remove marketplace: ${mpName}: ${manually(removeResult.detail, ["plugin", "marketplace", "remove", mpName])}`
246
+ )
216
247
  f6++
217
248
  }
218
249
  }
@@ -247,10 +278,14 @@ async function reassertEnabledState(ctx: Ctx, repoObj: { [k: string]: Json }, us
247
278
  const user = readJsonFile(userSettingsFile)
248
279
  const enabled = user !== undefined && isObject(user) && isObject(user["enabledPlugins"]) ? (user["enabledPlugins"] as { [k: string]: Json })[pluginId] : undefined
249
280
  if (enabled !== true) continue
250
- if ((await cli(["plugin", "disable", pluginId])).ok) {
281
+ const disableResult = await cli(["plugin", "disable", pluginId])
282
+ if (disableResult.ok) {
251
283
  cliDisabled = true
252
284
  } else {
253
- recordFailure(ctx, `Failed to disable SoT-false plugin: ${pluginId} (will retry next sync)`)
285
+ recordFailure(
286
+ ctx,
287
+ `Failed to disable SoT-false plugin: ${pluginId} (will retry next sync): ${manually(disableResult.detail, ["plugin", "disable", pluginId])}`
288
+ )
254
289
  }
255
290
  }
256
291
 
@@ -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.9"
4
+ export const GENERATED_PACKAGE_VERSION = "0.16.11"
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docks-kit",
3
- "version": "0.16.9",
3
+ "version": "0.16.11",
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",