docks-kit 0.7.0 → 0.7.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 +2 -2
- package/README.md +5 -2
- package/cli/docs/flags.md +1 -0
- package/cli/docs/install.md +9 -6
- package/cli/docs/overview.md +1 -1
- package/cli/src/commands/sync.ts +5 -0
- package/cli/src/commands/update.ts +10 -4
- package/cli/src/engine-native/claudeSync.ts +51 -7
- package/cli/src/engine-native/codexSync.ts +35 -3
- package/cli/src/engine-native/index.ts +2 -0
- package/cli/src/engine-native/parseArgs.ts +4 -0
- package/cli/src/generated/sotPayload.ts +3 -3
- package/docks-kit +14 -2
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -14,7 +14,7 @@ Tool-specific instructions live alongside this file:
|
|
|
14
14
|
|
|
15
15
|
| Path | Purpose |
|
|
16
16
|
|------|---------|
|
|
17
|
-
| `docks-kit` | CLI launcher: runs the
|
|
17
|
+
| `docks-kit` | CLI launcher: runs the platform binary in `cli/dist/` only when its `--version` matches `package.json`, otherwise Bun-from-source (auto-installs Bun + `node_modules`). No-Bun recovery is the standalone platform release binary |
|
|
18
18
|
| `cli/src/engine-native/` | EngineNative implementation for `sync`, `model`, `workflow`, and `toolchain`; idempotent, flag-gated for destructive reconciliation |
|
|
19
19
|
| `cli/` | Effect-TS CLI + bundled docs topics |
|
|
20
20
|
| `SoT/models.json` | Kit-verified model catalog plus the strict Docks workflow-role registry |
|
|
@@ -48,7 +48,7 @@ For per-tool SoT layouts (`SoT/.claude/`, `SoT/.codex/`), see the matching SoT d
|
|
|
48
48
|
|
|
49
49
|
- **Idempotent operations.** Every EngineNative sync step must be safe to re-run. Settings merges, plugin installs, and marketplace adds are all idempotent — re-running with no SoT changes is a no-op.
|
|
50
50
|
- **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.
|
|
51
|
-
- **Targeted syncs.** `./docks-kit sync` accepts positional targets: `claude`, `codex`, and `agents`. Use the narrowest target that matches the SoT change (for example, `./docks-kit sync codex` for Codex-only config edits); targets can be combined with `--dry-run`, `--skip-rtk`, `--reconcile`, `--prune`, `--yes` (auto-accept toolchain prompts), and the deploy-time modifiers `--claude-compact-window=<tokens>` / `--claude-permissive` / `--claude-model=<m>` / `--claude-effort=<level>` / `--claude-advisor=<on|off|default>` / `--codex-model=<m>` / `--codex-effort=<level>` (see `CLAUDE.md` § Deploy-time modifiers).
|
|
51
|
+
- **Targeted syncs.** `./docks-kit sync` accepts positional targets: `claude`, `codex`, and `agents`. Use the narrowest target that matches the SoT change (for example, `./docks-kit sync codex` for Codex-only config edits); targets can be combined with `--dry-run`, `--skip-rtk`, `--skip-plugin-refresh` (install missing plugins without refreshing existing caches; used by `docks-kit update`), `--reconcile`, `--prune`, `--yes` (auto-accept toolchain prompts), and the deploy-time modifiers `--claude-compact-window=<tokens>` / `--claude-permissive` / `--claude-model=<m>` / `--claude-effort=<level>` / `--claude-advisor=<on|off|default>` / `--codex-model=<m>` / `--codex-effort=<level>` (see `CLAUDE.md` § Deploy-time modifiers).
|
|
52
52
|
- **Additive by default.** Keys present in deployed config but absent from SoT are preserved on default sync. This protects user-only additions, but means drift accumulates — neither flag-less reset can clean it up. The one exception is the Claude `removed` manifest (`claude::_removed_manifest`), a curated list of unambiguous kit-owned artifacts that `claude::sync_removals` force-prunes on every sync; see `CLAUDE.md` § Pruning stale artifacts.
|
|
53
53
|
- **`--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.
|
|
54
54
|
- **SOLID-aligned modules.** `cli/src/engine-native/parseArgs.ts` owns flag parsing/validation. `toolchain.ts` owns the verified-version gate over `SoT/toolchain.json`; `bun.ts` owns the shared, memoized Bun bootstrap; `claudeRuntime.ts` owns Claude settings materialization. `claudeSync.ts`, `codexSync.ts`, and `skillsSync.ts` own tool-specific sync logic. `index.ts` is the thin orchestrator. The public CLI seam is `cli/src/engine.ts`.
|
package/README.md
CHANGED
|
@@ -17,8 +17,10 @@ cd ~/projects/public
|
|
|
17
17
|
./docks-kit status # doctor view: drift, toolchain, plugins, skills
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
The `./docks-kit` launcher prefers a compiled binary (`cli/dist/`)
|
|
21
|
-
from source via Bun — auto-installing
|
|
20
|
+
The `./docks-kit` launcher prefers a compiled binary (`cli/dist/`) only when
|
|
21
|
+
its version matches the checkout, then runs from source via Bun — auto-installing
|
|
22
|
+
Bun and dependencies when missing. Stale ignored build artifacts cannot mask
|
|
23
|
+
newer checkout code.
|
|
22
24
|
|
|
23
25
|
Other install paths (global `bun add -g docks-kit`, curl installer) —
|
|
24
26
|
see `./docks-kit docs install`.
|
|
@@ -65,6 +67,7 @@ golden-regression coverage for dry-run output, mutation snapshots, and argv logs
|
|
|
65
67
|
| `--claude-plugin=<name>` | Sticky opt-in plugin (supabase, n8n) |
|
|
66
68
|
| `--codex-model=<m>` | Deploy-time modifier: deployed Codex model |
|
|
67
69
|
| `--skip-rtk` | Skip optional tool bootstrap |
|
|
70
|
+
| `--skip-plugin-refresh` | Install missing plugins but skip refresh-only updates (used automatically by `docks-kit update`) |
|
|
68
71
|
| `--yes` | Auto-accept toolchain prompts (CI/containers) |
|
|
69
72
|
|
|
70
73
|
**Deploy-time modifiers** change deployed config only — the SoT is untouched
|
package/cli/docs/flags.md
CHANGED
|
@@ -19,6 +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-rtk` | Skip optional tool bootstrap (RTK, bubblewrap) |
|
|
22
|
+
| `--skip-plugin-refresh` | Install missing Claude/Codex plugins but skip refresh-only updates; `docks-kit update` uses this automatically |
|
|
22
23
|
| `--yes` | Auto-accept toolchain above-verified prompts (containers/CI) |
|
|
23
24
|
| `--verbose` / `-v` | Also print no-op confirmations (already in sync, up to date, left as-is); accepted on `sync`, `model`, and `toolchain` |
|
|
24
25
|
|
package/cli/docs/install.md
CHANGED
|
@@ -8,9 +8,10 @@ cd ~/projects/public
|
|
|
8
8
|
./docks-kit sync
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
The `./docks-kit` launcher prefers a compiled binary in `cli/dist
|
|
12
|
-
falls back to Bun-from-source
|
|
13
|
-
`bun install --frozen-lockfile`
|
|
11
|
+
The `./docks-kit` launcher prefers a compiled binary in `cli/dist/` only when
|
|
12
|
+
its reported version matches `package.json`, then falls back to Bun-from-source
|
|
13
|
+
(auto-installing Bun via download-then-run and `bun install --frozen-lockfile`
|
|
14
|
+
when needed). An ignored stale build is reported and bypassed.
|
|
14
15
|
|
|
15
16
|
## 2. Global via Bun (effect-solutions-style)
|
|
16
17
|
|
|
@@ -78,7 +79,7 @@ Two supported paths (CI-verified on windows-2025, native PowerShell):
|
|
|
78
79
|
## Keeping the kit up to date
|
|
79
80
|
|
|
80
81
|
```
|
|
81
|
-
docks-kit update # autodetect + update +
|
|
82
|
+
docks-kit update # autodetect + update + install-missing-only sync
|
|
82
83
|
docks-kit update --no-sync # update only
|
|
83
84
|
```
|
|
84
85
|
|
|
@@ -86,8 +87,10 @@ Autodetection: a kit home with `.git` is a checkout (requires a clean
|
|
|
86
87
|
worktree and an upstream; `git pull --ff-only`, re-runs
|
|
87
88
|
`bun install --frozen-lockfile` when the lockfile changed); a kit home
|
|
88
89
|
under `node_modules` is a global package (`bun add -g` /
|
|
89
|
-
`npm install -g docks-kit@latest`).
|
|
90
|
-
|
|
90
|
+
`npm install -g docks-kit@latest`). The chained sync skips refresh-only work
|
|
91
|
+
for already-installed Claude/Codex plugins but still installs missing ones.
|
|
92
|
+
A compiled binary inside a checkout updates the checkout; on the next invocation
|
|
93
|
+
the launcher bypasses that now-stale binary and uses updated source until rebuilt.
|
|
91
94
|
Every `docks-kit sync` also does a best-effort behind-upstream check and
|
|
92
95
|
nudges when the checkout is stale (silent offline / detached / no git).
|
|
93
96
|
|
package/cli/docs/overview.md
CHANGED
|
@@ -17,7 +17,7 @@ AI-assisted dev environment on every machine.
|
|
|
17
17
|
| `cli/src/generated/sotPayload.ts` | Deterministic generated payload embedded in standalone/npm execution |
|
|
18
18
|
| `cli/src/engine-native/` | EngineNative mutation logic for sync/model/toolchain |
|
|
19
19
|
| `cli/` | This CLI (Effect-TS on Bun) plus bundled docs |
|
|
20
|
-
| `docks-kit` | Launcher: compiled binary → bun-from-source, with Bun auto-install |
|
|
20
|
+
| `docks-kit` | Launcher: version-matching compiled binary → bun-from-source, with Bun auto-install |
|
|
21
21
|
|
|
22
22
|
## Design rules
|
|
23
23
|
|
package/cli/src/commands/sync.ts
CHANGED
|
@@ -80,6 +80,9 @@ const prune = Options.boolean("prune").pipe(
|
|
|
80
80
|
const skipRtk = Options.boolean("skip-rtk").pipe(
|
|
81
81
|
Options.withDescription("Skip optional tool bootstrap (RTK, bubblewrap)")
|
|
82
82
|
)
|
|
83
|
+
const skipPluginRefresh = Options.boolean("skip-plugin-refresh").pipe(
|
|
84
|
+
Options.withDescription("Install missing plugins but skip refresh-only updates for existing plugins")
|
|
85
|
+
)
|
|
83
86
|
const yes = Options.boolean("yes").pipe(
|
|
84
87
|
Options.withDescription("Auto-accept toolchain prompts (containers/CI)")
|
|
85
88
|
)
|
|
@@ -129,6 +132,7 @@ export const syncCommand = Command.make(
|
|
|
129
132
|
reconcile,
|
|
130
133
|
prune,
|
|
131
134
|
skipRtk,
|
|
135
|
+
skipPluginRefresh,
|
|
132
136
|
yes,
|
|
133
137
|
verbose,
|
|
134
138
|
claudeModel,
|
|
@@ -172,6 +176,7 @@ export const syncCommand = Command.make(
|
|
|
172
176
|
if (config.reconcile) args.push("--reconcile")
|
|
173
177
|
if (config.prune) args.push("--prune")
|
|
174
178
|
if (config.skipRtk) args.push("--skip-rtk")
|
|
179
|
+
if (config.skipPluginRefresh) args.push("--skip-plugin-refresh")
|
|
175
180
|
if (config.yes) args.push("--yes")
|
|
176
181
|
if (config.verbose) args.push("--verbose")
|
|
177
182
|
if (config.claudePermissive) args.push("--claude-permissive")
|
|
@@ -26,6 +26,12 @@ const chainSync = (argv0: string, args: Array<string>): Effect.Effect<void> =>
|
|
|
26
26
|
if (res.error !== undefined || res.status !== 0) process.exit(res.status ?? 1)
|
|
27
27
|
})
|
|
28
28
|
|
|
29
|
+
export const updateSyncArgs = (home: string): Array<string> => [
|
|
30
|
+
join(home, "cli/src/main.ts"),
|
|
31
|
+
"sync",
|
|
32
|
+
"--skip-plugin-refresh"
|
|
33
|
+
]
|
|
34
|
+
|
|
29
35
|
const updateCheckout = (home: string, skipSync: boolean) =>
|
|
30
36
|
Effect.gen(function* () {
|
|
31
37
|
if (spawnSync("git", ["--version"], { stdio: "ignore" }).status !== 0) {
|
|
@@ -62,12 +68,12 @@ const updateCheckout = (home: string, skipSync: boolean) =>
|
|
|
62
68
|
|
|
63
69
|
if (compiled) {
|
|
64
70
|
return yield* Console.log(
|
|
65
|
-
"This compiled binary still runs the previous version - rebuild
|
|
71
|
+
"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)."
|
|
66
72
|
)
|
|
67
73
|
}
|
|
68
74
|
if (skipSync) return yield* Console.log("Kit updated. Run: docks-kit sync")
|
|
69
75
|
yield* Console.log("Kit updated - running sync with the new version...")
|
|
70
|
-
return yield* chainSync(process.execPath,
|
|
76
|
+
return yield* chainSync(process.execPath, updateSyncArgs(home))
|
|
71
77
|
})
|
|
72
78
|
|
|
73
79
|
const updatePackage = (home: string, skipSync: boolean) =>
|
|
@@ -93,7 +99,7 @@ const updatePackage = (home: string, skipSync: boolean) =>
|
|
|
93
99
|
yield* Console.log("Kit updated - running sync with the new version...")
|
|
94
100
|
// Chain through the package dir just updated (global installs update in
|
|
95
101
|
// place) — a bare `docks-kit` PATH lookup could hit a different shim.
|
|
96
|
-
return yield* chainSync(process.execPath,
|
|
102
|
+
return yield* chainSync(process.execPath, updateSyncArgs(home))
|
|
97
103
|
})
|
|
98
104
|
|
|
99
105
|
export const updateCommand = Command.make("update", { noSync }, (config) =>
|
|
@@ -111,6 +117,6 @@ export const updateCommand = Command.make("update", { noSync }, (config) =>
|
|
|
111
117
|
})
|
|
112
118
|
).pipe(
|
|
113
119
|
Command.withDescription(
|
|
114
|
-
"Self-update the kit: autodetects the install (git checkout -> ff-only pull; bun/npm global -> @latest) and chains
|
|
120
|
+
"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)."
|
|
115
121
|
)
|
|
116
122
|
)
|
|
@@ -504,6 +504,10 @@ const REMOVED_MANIFEST = {
|
|
|
504
504
|
"env.CLAUDE_CODE_FORK_SUBAGENT",
|
|
505
505
|
"env.CLAUDE_CODE_EFFORT_LEVEL"
|
|
506
506
|
],
|
|
507
|
+
permissionRules: {
|
|
508
|
+
allow: ["Write(./)"],
|
|
509
|
+
deny: ["Write(**/.env)", "Write(**/.env.local)", "Write(**/secrets/**)"]
|
|
510
|
+
},
|
|
507
511
|
claudeJsonKeys: [] as Array<string>,
|
|
508
512
|
runtimeReady: {
|
|
509
513
|
hooks: ["notify.sh"],
|
|
@@ -545,6 +549,30 @@ function pruneJsonKeys(ctx: Ctx, file: string, keys: Array<string>): number {
|
|
|
545
549
|
return presentKeys.length
|
|
546
550
|
}
|
|
547
551
|
|
|
552
|
+
function prunePermissionRules(
|
|
553
|
+
ctx: Ctx,
|
|
554
|
+
file: string,
|
|
555
|
+
rules: Readonly<Record<"allow" | "deny", ReadonlyArray<string>>>
|
|
556
|
+
): number {
|
|
557
|
+
if (!existsSync(file)) return 0
|
|
558
|
+
const doc = parseJson(readFileSync(file, "utf8"))
|
|
559
|
+
if (doc === undefined || !isObject(doc) || !isObject(doc["permissions"])) return 0
|
|
560
|
+
const permissions = doc["permissions"]
|
|
561
|
+
let present = 0
|
|
562
|
+
for (const key of ["allow", "deny"] as const) {
|
|
563
|
+
const values = permissions[key]
|
|
564
|
+
if (!Array.isArray(values)) continue
|
|
565
|
+
const removed = new Set(rules[key])
|
|
566
|
+
present += rules[key].filter((rule) => values.includes(rule)).length
|
|
567
|
+
if (!ctx.dryRun) permissions[key] = values.filter((value) => typeof value !== "string" || !removed.has(value))
|
|
568
|
+
}
|
|
569
|
+
if (present > 0 && !ctx.dryRun) {
|
|
570
|
+
writeFileSync(`${file}.tmp`, jqStringify(doc))
|
|
571
|
+
renameSync(`${file}.tmp`, file)
|
|
572
|
+
}
|
|
573
|
+
return present
|
|
574
|
+
}
|
|
575
|
+
|
|
548
576
|
function syncRemovals(ctx: Ctx, claudeDir: string, runtime: ClaudeRuntimeState): void {
|
|
549
577
|
const { change, echo } = ctx.services.logger
|
|
550
578
|
let hooksRemoved = 0
|
|
@@ -592,16 +620,25 @@ function syncRemovals(ctx: Ctx, claudeDir: string, runtime: ClaudeRuntimeState):
|
|
|
592
620
|
}
|
|
593
621
|
|
|
594
622
|
const skeys = pruneJsonKeys(ctx, p(claudeDir, "settings.json"), settingsKeys)
|
|
623
|
+
const permissionRules = prunePermissionRules(
|
|
624
|
+
ctx,
|
|
625
|
+
p(claudeDir, "settings.json"),
|
|
626
|
+
REMOVED_MANIFEST.permissionRules
|
|
627
|
+
)
|
|
595
628
|
const cjkeys = pruneJsonKeys(ctx, p(ctx.home, ".claude.json"), REMOVED_MANIFEST.claudeJsonKeys)
|
|
596
629
|
|
|
597
630
|
if (ctx.dryRun) {
|
|
598
631
|
if (skeys > 0) echo(`[dry-run] del ${skeys} stale key(s) from ${p(claudeDir, "settings.json")}`)
|
|
632
|
+
if (permissionRules > 0) {
|
|
633
|
+
echo(`[dry-run] del ${permissionRules} stale permission rule(s) from ${p(claudeDir, "settings.json")}`)
|
|
634
|
+
}
|
|
599
635
|
if (cjkeys > 0) echo(`[dry-run] del ${cjkeys} stale key(s) from ${p(ctx.home, ".claude.json")}`)
|
|
600
636
|
return
|
|
601
637
|
}
|
|
602
638
|
|
|
603
|
-
if (hooksRemoved + filesRemoved + skeys + cjkeys > 0) {
|
|
604
|
-
|
|
639
|
+
if (hooksRemoved + filesRemoved + skeys + permissionRules + cjkeys > 0) {
|
|
640
|
+
const permissionSummary = permissionRules > 0 ? `, permission rules: ${permissionRules}` : ""
|
|
641
|
+
change(`Pruned stale artifacts (hooks: ${hooksRemoved}, files: ${filesRemoved}, settings keys: ${skeys}, claude.json keys: ${cjkeys}${permissionSummary})`)
|
|
605
642
|
ctx.nextStepTriggers.claudeRestart = true
|
|
606
643
|
}
|
|
607
644
|
}
|
|
@@ -637,7 +674,11 @@ function syncPlugins(ctx: Ctx, claudeDir: string): void {
|
|
|
637
674
|
const installedPlugins = p(claudeDir, "plugins", "installed_plugins.json")
|
|
638
675
|
|
|
639
676
|
if (ctx.dryRun) {
|
|
640
|
-
echo(
|
|
677
|
+
echo(
|
|
678
|
+
ctx.skipPluginRefresh
|
|
679
|
+
? "[dry-run] bootstrap + install missing plugins from SoT; skip refresh-only plugin updates"
|
|
680
|
+
: "[dry-run] bootstrap + update plugin marketplaces + plugins from SoT"
|
|
681
|
+
)
|
|
641
682
|
if (ctx.prune) {
|
|
642
683
|
echo("[dry-run] (--prune) would also uninstall plugins not in SoT and remove extra marketplaces")
|
|
643
684
|
}
|
|
@@ -695,13 +736,16 @@ function syncPlugins(ctx: Ctx, claudeDir: string): void {
|
|
|
695
736
|
}
|
|
696
737
|
}
|
|
697
738
|
|
|
698
|
-
// Pass 3 — refresh every installed plugin.
|
|
699
|
-
cli(["plugin", "marketplace", "update"])
|
|
700
739
|
let updatedPl = 0
|
|
701
740
|
const installedDoc = readJsonFile(installedPlugins)
|
|
702
741
|
const installedKeys = installedDoc !== undefined && isObject(installedDoc) ? sortedKeys(installedDoc["plugins"]) : []
|
|
703
|
-
|
|
704
|
-
|
|
742
|
+
// Pass 3 — refresh every installed plugin unless the update command
|
|
743
|
+
// selected its install-missing-only fast path.
|
|
744
|
+
if (!ctx.skipPluginRefresh) {
|
|
745
|
+
cli(["plugin", "marketplace", "update"])
|
|
746
|
+
for (const pluginId of installedKeys) {
|
|
747
|
+
if (cli(["plugin", "update", pluginId]).out.includes("Successfully updated")) updatedPl++
|
|
748
|
+
}
|
|
705
749
|
}
|
|
706
750
|
|
|
707
751
|
// Passes 4 + 5 — prune-gated uninstall + marketplace removal.
|
|
@@ -432,10 +432,30 @@ function manualPluginRefreshCommand(sotConfigText: string): string {
|
|
|
432
432
|
return first !== undefined ? `codex plugin add ${first}` : "codex plugin add <plugin@marketplace>"
|
|
433
433
|
}
|
|
434
434
|
|
|
435
|
+
function installedPluginIdsFromCli(): Set<string> | undefined {
|
|
436
|
+
const result = spawnSync("codex", ["plugin", "list", "--json"], {
|
|
437
|
+
encoding: "utf8",
|
|
438
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
439
|
+
})
|
|
440
|
+
if (result.error !== undefined || result.status !== 0) return undefined
|
|
441
|
+
const value = parseJson(result.stdout ?? "")
|
|
442
|
+
if (value === undefined || !isObject(value) || !Array.isArray(value["installed"])) return undefined
|
|
443
|
+
const ids = new Set<string>()
|
|
444
|
+
for (const row of value["installed"]) {
|
|
445
|
+
if (!isObject(row) || row["installed"] !== true || typeof row["pluginId"] !== "string") continue
|
|
446
|
+
ids.add(row["pluginId"])
|
|
447
|
+
}
|
|
448
|
+
return ids
|
|
449
|
+
}
|
|
450
|
+
|
|
435
451
|
function syncPlugins(ctx: Ctx, sotConfigText: string): void {
|
|
436
|
-
const { change, echo, warn } = ctx.services.logger
|
|
452
|
+
const { change, echo, verbose, warn } = ctx.services.logger
|
|
437
453
|
if (ctx.dryRun) {
|
|
438
|
-
echo(
|
|
454
|
+
echo(
|
|
455
|
+
ctx.skipPluginRefresh
|
|
456
|
+
? "[dry-run] add missing enabled Codex plugins from SoT; skip refresh-only plugin updates"
|
|
457
|
+
: "[dry-run] add enabled Codex plugins from SoT"
|
|
458
|
+
)
|
|
439
459
|
return
|
|
440
460
|
}
|
|
441
461
|
|
|
@@ -454,9 +474,20 @@ function syncPlugins(ctx: Ctx, sotConfigText: string): void {
|
|
|
454
474
|
return
|
|
455
475
|
}
|
|
456
476
|
|
|
477
|
+
const desiredPluginIds = enabledPluginIdsFromText(sotConfigText)
|
|
478
|
+
let pluginIds = desiredPluginIds
|
|
479
|
+
if (ctx.skipPluginRefresh) {
|
|
480
|
+
const installedPluginIds = installedPluginIdsFromCli()
|
|
481
|
+
if (installedPluginIds === undefined) {
|
|
482
|
+
warn("Codex plugin inventory unavailable — falling back to the full refresh path")
|
|
483
|
+
} else {
|
|
484
|
+
pluginIds = desiredPluginIds.filter((pluginId) => !installedPluginIds.has(pluginId))
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
457
488
|
let refreshed = 0
|
|
458
489
|
let failed = 0
|
|
459
|
-
for (const pluginId of
|
|
490
|
+
for (const pluginId of pluginIds) {
|
|
460
491
|
const res = spawnSync("codex", ["plugin", "add", pluginId], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] })
|
|
461
492
|
const addOut = `${res.stdout ?? ""}${res.stderr ?? ""}`
|
|
462
493
|
if (res.error === undefined && res.status === 0) {
|
|
@@ -483,6 +514,7 @@ function syncPlugins(ctx: Ctx, sotConfigText: string): void {
|
|
|
483
514
|
warn(`Session Relay readiness unavailable after refresh: ${readiness.reason}`)
|
|
484
515
|
}
|
|
485
516
|
}
|
|
517
|
+
if (ctx.skipPluginRefresh && pluginIds.length === 0) verbose("Codex plugins already installed; refresh-only updates skipped")
|
|
486
518
|
if (failed > 0) warn(`${failed} Codex plugin operation(s) failed — re-run sync or install manually`)
|
|
487
519
|
}
|
|
488
520
|
|
|
@@ -32,6 +32,7 @@ export interface Ctx {
|
|
|
32
32
|
dryRun: boolean
|
|
33
33
|
verbose: boolean
|
|
34
34
|
skipRtk: boolean
|
|
35
|
+
skipPluginRefresh?: boolean
|
|
35
36
|
reconcile: boolean
|
|
36
37
|
prune: boolean
|
|
37
38
|
assumeYes: boolean
|
|
@@ -72,6 +73,7 @@ function makeCtx(services: EngineServices): Ctx {
|
|
|
72
73
|
dryRun: env["DRY_RUN"] === "1",
|
|
73
74
|
verbose: env["DOCKS_KIT_VERBOSE"] === "1",
|
|
74
75
|
skipRtk: env["SKIP_RTK"] === "1",
|
|
76
|
+
skipPluginRefresh: false,
|
|
75
77
|
reconcile: env["RECONCILE"] === "1",
|
|
76
78
|
prune: env["PRUNE"] === "1",
|
|
77
79
|
assumeYes: env["ASSUME_YES"] === "1",
|
|
@@ -56,6 +56,7 @@ function usage(ctx: Ctx): void {
|
|
|
56
56
|
" --prune uninstall kit-managed installs not in SoT (plugins, marketplaces, skills in SoT/.agents/skills.txt)"
|
|
57
57
|
)
|
|
58
58
|
echo(" --skip-rtk skip optional tool bootstrap (RTK, bubblewrap)")
|
|
59
|
+
echo(" --skip-plugin-refresh install missing plugins but skip refresh-only updates")
|
|
59
60
|
echo(" --yes auto-accept toolchain prompts (containers/CI)")
|
|
60
61
|
echo(" --verbose also print no-op confirmations (already in sync, up to date, left as-is)")
|
|
61
62
|
echo("")
|
|
@@ -154,6 +155,9 @@ export function parseArgs(ctx: Ctx, args: ReadonlyArray<string>): void {
|
|
|
154
155
|
case "--skip-rtk":
|
|
155
156
|
ctx.skipRtk = true
|
|
156
157
|
continue
|
|
158
|
+
case "--skip-plugin-refresh":
|
|
159
|
+
ctx.skipPluginRefresh = true
|
|
160
|
+
continue
|
|
157
161
|
case "--reconcile":
|
|
158
162
|
ctx.reconcile = true
|
|
159
163
|
continue
|
|
@@ -1,12 +1,12 @@
|
|
|
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.
|
|
4
|
+
export const GENERATED_PACKAGE_VERSION = "0.7.1"
|
|
5
5
|
|
|
6
6
|
export const GENERATED_PAYLOAD_TEXT = {
|
|
7
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
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",
|
|
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.
|
|
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
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
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
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",
|
|
@@ -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 = "
|
|
43
|
+
export const GENERATED_PAYLOAD_HASH = "1b7e61acf5863f8910246421ba219f30f89585fe826ee3022be3db4e3fd85187"
|
package/docks-kit
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# docks-kit — launcher. Resolution order:
|
|
3
|
-
# 1. compiled binary in cli/dist/ (bun build --compile output)
|
|
3
|
+
# 1. version-matching compiled binary in cli/dist/ (bun build --compile output)
|
|
4
4
|
# 2. Bun from source (auto-installs Bun + node_modules when missing)
|
|
5
5
|
# No-Bun recovery path: download a release binary for your platform.
|
|
6
6
|
set -euo pipefail
|
|
@@ -22,7 +22,19 @@ case "$(uname -s)-$(uname -m)" in
|
|
|
22
22
|
*) KIT_BIN="" ;;
|
|
23
23
|
esac
|
|
24
24
|
if [[ -n "$KIT_BIN" && -x "$REPO_DIR/cli/dist/$KIT_BIN" ]]; then
|
|
25
|
-
|
|
25
|
+
CHECKOUT_VERSION=""
|
|
26
|
+
while IFS= read -r line; do
|
|
27
|
+
if [[ "$line" =~ ^[[:space:]]*\"version\"[[:space:]]*:[[:space:]]*\"([^\"]+)\" ]]; then
|
|
28
|
+
CHECKOUT_VERSION="${BASH_REMATCH[1]}"
|
|
29
|
+
break
|
|
30
|
+
fi
|
|
31
|
+
done < "$REPO_DIR/package.json"
|
|
32
|
+
IFS= read -r BIN_VERSION < <("$REPO_DIR/cli/dist/$KIT_BIN" --version 2>/dev/null || true)
|
|
33
|
+
BIN_VERSION="${BIN_VERSION%$'\r'}"
|
|
34
|
+
if [[ -z "$CHECKOUT_VERSION" || "$BIN_VERSION" == "$CHECKOUT_VERSION" ]]; then
|
|
35
|
+
exec "$REPO_DIR/cli/dist/$KIT_BIN" "$@"
|
|
36
|
+
fi
|
|
37
|
+
echo "[docks-kit] ignoring stale cli/dist/$KIT_BIN ${BIN_VERSION:-<unknown>}; checkout is $CHECKOUT_VERSION — running from source" >&2
|
|
26
38
|
fi
|
|
27
39
|
|
|
28
40
|
# Locate bun: PATH, then the known install locations that sit off the
|
package/package.json
CHANGED