docks-kit 0.17.1 → 0.17.3

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.
Files changed (87) hide show
  1. package/AGENTS.md +41 -33
  2. package/cli/docs/omp-context.md +104 -0
  3. package/cli/docs/omp-models.md +106 -56
  4. package/cli/src/argv.ts +38 -469
  5. package/cli/src/argvSurface.ts +371 -0
  6. package/cli/src/argvValidate.ts +122 -0
  7. package/cli/src/commands/docs.ts +62 -42
  8. package/cli/src/commands/harnesses.ts +38 -45
  9. package/cli/src/commands/model.ts +48 -53
  10. package/cli/src/commands/models.ts +24 -26
  11. package/cli/src/commands/omp.ts +132 -112
  12. package/cli/src/commands/plugins.ts +20 -20
  13. package/cli/src/commands/skills.ts +22 -20
  14. package/cli/src/commands/status.ts +87 -80
  15. package/cli/src/commands/sync.ts +113 -87
  16. package/cli/src/commands/toolchain.ts +21 -21
  17. package/cli/src/commands/update.ts +139 -117
  18. package/cli/src/efforts.ts +35 -34
  19. package/cli/src/engine-native/DESIGN.md +30 -30
  20. package/cli/src/engine-native/bun.ts +77 -55
  21. package/cli/src/engine-native/claudeLsp.ts +154 -0
  22. package/cli/src/engine-native/claudeOptionalPlugins.ts +113 -0
  23. package/cli/src/engine-native/claudePluginPasses.ts +348 -0
  24. package/cli/src/engine-native/claudeRemovals.ts +195 -0
  25. package/cli/src/engine-native/claudeRetired.ts +4 -4
  26. package/cli/src/engine-native/claudeRuntime.ts +94 -68
  27. package/cli/src/engine-native/claudeSettings.ts +174 -0
  28. package/cli/src/engine-native/claudeSettingsModifiers.ts +50 -54
  29. package/cli/src/engine-native/claudeSync.ts +176 -456
  30. package/cli/src/engine-native/codexConfig.ts +158 -0
  31. package/cli/src/engine-native/codexHooks.ts +91 -0
  32. package/cli/src/engine-native/codexPlugins.ts +290 -0
  33. package/cli/src/engine-native/codexStatus.ts +24 -0
  34. package/cli/src/engine-native/codexSync.ts +110 -568
  35. package/cli/src/engine-native/codexToml.ts +140 -138
  36. package/cli/src/engine-native/deps.ts +144 -107
  37. package/cli/src/engine-native/engineCtx.ts +126 -0
  38. package/cli/src/engine-native/exec.ts +98 -87
  39. package/cli/src/engine-native/failures.ts +3 -3
  40. package/cli/src/engine-native/harnesses.ts +62 -72
  41. package/cli/src/engine-native/index.ts +34 -315
  42. package/cli/src/engine-native/jq.ts +25 -24
  43. package/cli/src/engine-native/logger.ts +98 -100
  44. package/cli/src/engine-native/models.ts +50 -45
  45. package/cli/src/engine-native/modes.ts +83 -80
  46. package/cli/src/engine-native/ompFileDeploy.ts +111 -0
  47. package/cli/src/engine-native/ompMarketplace.ts +136 -0
  48. package/cli/src/engine-native/ompOverlay.ts +67 -64
  49. package/cli/src/engine-native/ompPaths.ts +49 -38
  50. package/cli/src/engine-native/ompPlugins.ts +170 -0
  51. package/cli/src/engine-native/ompRemovals.ts +159 -0
  52. package/cli/src/engine-native/ompSync.ts +69 -409
  53. package/cli/src/engine-native/ompYaml.ts +43 -41
  54. package/cli/src/engine-native/os/darwin.ts +5 -5
  55. package/cli/src/engine-native/os/index.ts +15 -15
  56. package/cli/src/engine-native/os/linux.ts +5 -5
  57. package/cli/src/engine-native/os/posix.ts +28 -24
  58. package/cli/src/engine-native/os/targets.ts +17 -17
  59. package/cli/src/engine-native/os/types.ts +31 -31
  60. package/cli/src/engine-native/os/windows.ts +61 -61
  61. package/cli/src/engine-native/parseArgs.ts +122 -372
  62. package/cli/src/engine-native/parseHelp.ts +66 -0
  63. package/cli/src/engine-native/parseModifiers.ts +222 -0
  64. package/cli/src/engine-native/services.ts +34 -34
  65. package/cli/src/engine-native/settings.ts +15 -15
  66. package/cli/src/engine-native/sharedTypes.d.ts +46 -0
  67. package/cli/src/engine-native/skillsInstall.ts +105 -0
  68. package/cli/src/engine-native/skillsLinks.ts +203 -0
  69. package/cli/src/engine-native/skillsManifest.ts +16 -0
  70. package/cli/src/engine-native/skillsPrune.ts +108 -0
  71. package/cli/src/engine-native/skillsSync.ts +28 -362
  72. package/cli/src/engine-native/syncConcurrency.ts +55 -0
  73. package/cli/src/engine-native/syncDispatch.ts +141 -0
  74. package/cli/src/engine-native/toolchain.ts +65 -62
  75. package/cli/src/engine.ts +65 -57
  76. package/cli/src/generated/sotPayload.ts +7 -7
  77. package/cli/src/kitHome.ts +44 -40
  78. package/cli/src/main.ts +63 -56
  79. package/cli/src/manifests.ts +53 -54
  80. package/cli/src/md.d.ts +2 -2
  81. package/cli/src/payload.ts +9 -9
  82. package/cli/src/services.ts +21 -13
  83. package/cli/tsconfig.json +4 -1
  84. package/docks-kit +13 -2
  85. package/docks-kit.ps1 +21 -6
  86. package/package.json +10 -2
  87. package/cli/src/engine-native/claudePlugins.ts +0 -510
@@ -0,0 +1,371 @@
1
+ /**
2
+ * Flag grammar and parsing for the Effect CLI layer: flag surfaces derived
3
+ * from caller-supplied command values, argv scanning/normalization,
4
+ * subcommand detection, and the omp passthrough boundary. Pure: surfaces are
5
+ * built from structural command values, so this module never touches Effect
6
+ * CLI globals or process state.
7
+ */
8
+
9
+ export interface FlagSurface {
10
+ readonly longFlags: ReadonlyArray<string>;
11
+ readonly aliases: ReadonlyMap<string, string>;
12
+ readonly valueFlags: ReadonlyArray<string>;
13
+ readonly repeatableFlags: ReadonlyArray<string>;
14
+ }
15
+
16
+ export interface GlobalFlagSurface extends FlagSurface {
17
+ readonly actionFlags: ReadonlySet<string>;
18
+ }
19
+
20
+ export interface CommandValue {
21
+ readonly name: string;
22
+ }
23
+
24
+ export interface ArgvSurfaces {
25
+ readonly commandSurfaces: ReadonlyMap<string, FlagSurface>;
26
+ readonly globalSurface: GlobalFlagSurface;
27
+ }
28
+
29
+ const flagMetadata = (
30
+ param: unknown,
31
+ ): {
32
+ readonly name: string;
33
+ readonly aliases: ReadonlyArray<string>;
34
+ readonly takesValue: boolean;
35
+ readonly repeatable: boolean;
36
+ } => {
37
+ let current = param;
38
+ let repeatable = false;
39
+
40
+ while (typeof current === "object" && current !== null) {
41
+ if (!("_tag" in current)) break;
42
+ if (current._tag === "Variadic") repeatable = true;
43
+ if (current._tag !== "Single") {
44
+ if (!("param" in current)) break;
45
+ current = current.param;
46
+ continue;
47
+ }
48
+
49
+ if (!("name" in current) || typeof current.name !== "string") {
50
+ throw new Error("Effect CLI exposed a flag without a string name");
51
+ }
52
+ if (
53
+ !("aliases" in current) ||
54
+ !Array.isArray(current.aliases) ||
55
+ !current.aliases.every((alias) => typeof alias === "string")
56
+ ) {
57
+ throw new Error(`Effect CLI exposed invalid aliases for --${current.name}`);
58
+ }
59
+ if (
60
+ !("primitiveType" in current) ||
61
+ typeof current.primitiveType !== "object" ||
62
+ current.primitiveType === null ||
63
+ !("_tag" in current.primitiveType) ||
64
+ typeof current.primitiveType._tag !== "string"
65
+ ) {
66
+ throw new Error(`Effect CLI exposed no primitive type for --${current.name}`);
67
+ }
68
+ const aliases: ReadonlyArray<string> = current.aliases;
69
+ return {
70
+ name: current.name,
71
+ aliases,
72
+ takesValue: current.primitiveType._tag !== "Boolean",
73
+ repeatable,
74
+ };
75
+ }
76
+
77
+ throw new Error("Effect CLI exposed an unsupported flag parameter");
78
+ };
79
+
80
+ const flagSurface = (params: ReadonlyArray<unknown>): FlagSurface => {
81
+ const longFlags: Array<string> = [];
82
+ const aliases = new Map<string, string>();
83
+ const valueFlags: Array<string> = [];
84
+ const repeatableFlags: Array<string> = [];
85
+
86
+ for (const param of params) {
87
+ const metadata = flagMetadata(param);
88
+ const longName = `--${metadata.name}`;
89
+ longFlags.push(longName);
90
+ if (metadata.takesValue) valueFlags.push(longName);
91
+ if (metadata.repeatable) repeatableFlags.push(longName);
92
+ for (const alias of metadata.aliases) aliases.set(`-${alias}`, longName);
93
+ }
94
+
95
+ return { longFlags, aliases, valueFlags, repeatableFlags };
96
+ };
97
+
98
+ const buildCommandSurface = (command: CommandValue): FlagSurface => {
99
+ if (!("config" in command)) {
100
+ throw new Error(`Effect CLI did not expose flags for '${command.name}'`);
101
+ }
102
+ const config = command.config;
103
+ if (
104
+ typeof config !== "object" ||
105
+ config === null ||
106
+ !("flags" in config) ||
107
+ !Array.isArray(config.flags)
108
+ ) {
109
+ throw new Error(`Effect CLI did not expose flags for '${command.name}'`);
110
+ }
111
+
112
+ return flagSurface(config.flags);
113
+ };
114
+
115
+ const globalSurface = (builtIns: unknown): GlobalFlagSurface => {
116
+ if (!Array.isArray(builtIns)) {
117
+ throw new Error("Effect CLI did not expose its built-in global flags");
118
+ }
119
+
120
+ const actionFlags = new Set<string>();
121
+ const flags = builtIns.map((builtIn) => {
122
+ if (
123
+ (typeof builtIn !== "object" && typeof builtIn !== "function") ||
124
+ builtIn === null ||
125
+ !("flag" in builtIn) ||
126
+ !("_tag" in builtIn) ||
127
+ typeof builtIn._tag !== "string"
128
+ ) {
129
+ throw new Error("Effect CLI exposed an unsupported built-in global flag");
130
+ }
131
+ if (builtIn._tag === "Action") {
132
+ // Only a presence-based action is idempotent. `--completions` is an action
133
+ // that takes a value, and Effect 4 would silently keep the first one, which
134
+ // is exactly what the duplicate rule exists to refuse.
135
+ const metadata = flagMetadata(builtIn.flag);
136
+ if (!metadata.takesValue) actionFlags.add(`--${metadata.name}`);
137
+ }
138
+ return builtIn.flag;
139
+ });
140
+ return { ...flagSurface(flags), actionFlags };
141
+ };
142
+
143
+ export const buildSurfaces = (
144
+ commands: ReadonlyArray<CommandValue>,
145
+ builtIns: unknown,
146
+ ): ArgvSurfaces => {
147
+ // A Map, not a plain object: an object literal answers `toString` and friends from
148
+ // `Object.prototype`, so an unknown subcommand with such a name would slip past the
149
+ // unknown-command guard and dereference a surface that was never built.
150
+ const commandSurfaces: ReadonlyMap<string, FlagSurface> = new Map(
151
+ commands.map((command) => [command.name, buildCommandSurface(command)]),
152
+ );
153
+ return { commandSurfaces, globalSurface: globalSurface(builtIns) };
154
+ };
155
+
156
+ const flagNameOf = (token: string): string => {
157
+ const equals = token.indexOf("=");
158
+ return equals === -1 ? token : token.slice(0, equals);
159
+ };
160
+
161
+ const canonicalFlagName = (name: string, surface: FlagSurface | undefined): string | undefined => {
162
+ if (surface === undefined) return undefined;
163
+ const aliased = surface.aliases.get(name);
164
+ if (aliased !== undefined) return aliased;
165
+ return surface.longFlags.includes(name) ? name : undefined;
166
+ };
167
+
168
+ const declaredFlagName = (
169
+ name: string,
170
+ commandSurface: FlagSurface | undefined,
171
+ surfaces: ArgvSurfaces,
172
+ ): string | undefined =>
173
+ canonicalFlagName(name, commandSurface) ?? canonicalFlagName(name, surfaces.globalSurface);
174
+
175
+ const declaredInAnySurface = (name: string, surfaces: ArgvSurfaces): boolean => {
176
+ if (canonicalFlagName(name, surfaces.globalSurface) !== undefined) return true;
177
+ for (const surface of surfaces.commandSurfaces.values()) {
178
+ if (canonicalFlagName(name, surface) !== undefined) return true;
179
+ }
180
+ return false;
181
+ };
182
+
183
+ const takesValueInAnySurface = (name: string, surfaces: ArgvSurfaces): boolean => {
184
+ const globalName = canonicalFlagName(name, surfaces.globalSurface);
185
+ if (globalName !== undefined && surfaces.globalSurface.valueFlags.includes(globalName))
186
+ return true;
187
+ for (const surface of surfaces.commandSurfaces.values()) {
188
+ const canonicalName = canonicalFlagName(name, surface);
189
+ if (canonicalName !== undefined && surface.valueFlags.includes(canonicalName)) return true;
190
+ }
191
+ return false;
192
+ };
193
+
194
+ /** The resolved subcommand word, or undefined at the root. */
195
+ export const subcommandName = (
196
+ args: ReadonlyArray<string>,
197
+ surfaces: ArgvSurfaces,
198
+ ): string | undefined => {
199
+ for (let index = 0; index < args.length; index++) {
200
+ const token = args[index];
201
+ if (token === "--") return undefined;
202
+ const name = flagNameOf(token);
203
+ if (token === name && takesValueInAnySurface(name, surfaces)) {
204
+ const next = args[index + 1];
205
+ if (next === "--") return undefined;
206
+ if (
207
+ next !== undefined &&
208
+ !(next.startsWith("-") && declaredInAnySurface(flagNameOf(next), surfaces))
209
+ ) {
210
+ index++;
211
+ continue;
212
+ }
213
+ }
214
+ if (!token.startsWith("-")) return token;
215
+ }
216
+ return undefined;
217
+ };
218
+
219
+ export interface ScannedFlag {
220
+ readonly token: string;
221
+ readonly name: string;
222
+ readonly canonicalName: string | undefined;
223
+ readonly hasValue: boolean;
224
+ readonly takesValue: boolean;
225
+ }
226
+
227
+ export interface ArgvNormalization {
228
+ readonly flagIndex: number;
229
+ readonly valueIndex: number;
230
+ readonly token: string;
231
+ }
232
+
233
+ export interface ScannedArgv {
234
+ readonly flags: ReadonlyArray<ScannedFlag>;
235
+ readonly normalizations: ReadonlyArray<ArgvNormalization>;
236
+ }
237
+
238
+ export const scanArgv = (
239
+ args: ReadonlyArray<string>,
240
+ commandSurface: FlagSurface | undefined,
241
+ surfaces: ArgvSurfaces,
242
+ ): ScannedArgv => {
243
+ const flags: Array<ScannedFlag> = [];
244
+ const normalizations: Array<ArgvNormalization> = [];
245
+
246
+ for (let index = 0; index < args.length; index++) {
247
+ const token = args[index];
248
+ if (token === "--") break;
249
+ if (!token.startsWith("-")) continue;
250
+
251
+ const name = flagNameOf(token);
252
+ const canonicalName = declaredFlagName(name, commandSurface, surfaces);
253
+ let hasValue = token.includes("=");
254
+ const takesValue =
255
+ canonicalName !== undefined &&
256
+ (commandSurface?.valueFlags.includes(canonicalName) === true ||
257
+ surfaces.globalSurface.valueFlags.includes(canonicalName));
258
+
259
+ if (!hasValue && takesValue) {
260
+ const next = args[index + 1];
261
+ const nextIsRecognizedFlag =
262
+ next !== undefined &&
263
+ next.startsWith("-") &&
264
+ declaredFlagName(flagNameOf(next), commandSurface, surfaces) !== undefined;
265
+ // A legitimate value may begin with `-`; only a recognized flag proves it is missing.
266
+ if (next !== undefined && next !== "--" && !nextIsRecognizedFlag) {
267
+ hasValue = true;
268
+ if (next.startsWith("-")) {
269
+ // Effect 4's lexer treats any `-`-leading token as an option, so a legitimate
270
+ // dash-leading value only survives in the inline form.
271
+ normalizations.push({
272
+ flagIndex: index,
273
+ valueIndex: index + 1,
274
+ token: `${token}=${next}`,
275
+ });
276
+ }
277
+ index++;
278
+ }
279
+ }
280
+
281
+ flags.push({ token, name, canonicalName, hasValue, takesValue });
282
+ }
283
+
284
+ return { flags, normalizations };
285
+ };
286
+
287
+ export const normalizeArgv = (
288
+ args: ReadonlyArray<string>,
289
+ normalizations: ReadonlyArray<ArgvNormalization>,
290
+ ): ReadonlyArray<string> => {
291
+ if (normalizations.length === 0) return args;
292
+
293
+ const normalized: Array<string> = [];
294
+ let normalizationIndex = 0;
295
+ for (let index = 0; index < args.length; index++) {
296
+ const normalization = normalizations[normalizationIndex];
297
+ if (normalization !== undefined && normalization.flagIndex === index) {
298
+ normalized.push(normalization.token);
299
+ index = normalization.valueIndex;
300
+ normalizationIndex++;
301
+ continue;
302
+ }
303
+ normalized.push(args[index]);
304
+ }
305
+ return normalized;
306
+ };
307
+
308
+ // Without the injected `--`, Effect 4 rejects a forwarded flag such as `-p` or
309
+ // `--mode` as unrecognized. The omp launcher therefore owns a passthrough
310
+ // boundary: the first token after the `omp` word that is neither a flag
311
+ // declared on the omp or global surface nor a value consumed by such a flag
312
+ // starts the verbatim tail forwarded to omp. An undeclared long flag joins the
313
+ // tail too, because most omp flags are long (`--mode`, `--continue`,
314
+ // `--models`), and omp itself reports an unrecognized one accurately. Use
315
+ // `docks-kit omp -- --model x` to reach omp's own same-named flag.
316
+ export const spliceOmpBoundary = (
317
+ args: ReadonlyArray<string>,
318
+ surfaces: ArgvSurfaces,
319
+ ): ReadonlyArray<string> => {
320
+ const surface = surfaces.commandSurfaces.get("omp");
321
+ let ompIndex = -1;
322
+ for (let index = 0; index < args.length; index++) {
323
+ const token = args[index] as string;
324
+ if (token === "--") return args;
325
+ const name = flagNameOf(token);
326
+ if (token === name && takesValueInAnySurface(name, surfaces)) {
327
+ const next = args[index + 1];
328
+ if (next === "--") return args;
329
+ if (
330
+ next !== undefined &&
331
+ !(next.startsWith("-") && declaredInAnySurface(flagNameOf(next), surfaces))
332
+ ) {
333
+ index++;
334
+ continue;
335
+ }
336
+ }
337
+ if (!token.startsWith("-")) {
338
+ ompIndex = index;
339
+ break;
340
+ }
341
+ }
342
+ if (ompIndex === -1 || args[ompIndex] !== "omp") return args;
343
+ for (let index = ompIndex + 1; index < args.length; index++) {
344
+ const token = args[index] as string;
345
+ // An explicit delimiter already marks the tail; a second one would be
346
+ // forwarded to omp as a literal argument.
347
+ if (token === "--") return args;
348
+ if (!token.startsWith("-")) {
349
+ return [...args.slice(0, index), "--", ...args.slice(index)];
350
+ }
351
+ const name = flagNameOf(token);
352
+ const canonical = declaredFlagName(name, surface, surfaces);
353
+ if (canonical === undefined) return [...args.slice(0, index), "--", ...args.slice(index)];
354
+ const takesValue =
355
+ surface?.valueFlags.includes(canonical) === true ||
356
+ surfaces.globalSurface.valueFlags.includes(canonical);
357
+ if (takesValue && !token.includes("=")) {
358
+ const next = args[index + 1];
359
+ if (
360
+ next !== undefined &&
361
+ !(
362
+ next.startsWith("-") &&
363
+ declaredFlagName(flagNameOf(next), surface, surfaces) !== undefined
364
+ )
365
+ ) {
366
+ index++;
367
+ }
368
+ }
369
+ }
370
+ return args;
371
+ };
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Per-command validation for the Effect CLI layer: legacy-flag hints,
3
+ * unknown-command/flag detection, boolean-value and duplicate rejection, and
4
+ * the sync modifiers' missing-value diagnostics. Operates on scanned flags so
5
+ * the grammar machinery stays in argvSurface.ts.
6
+ */
7
+
8
+ import { KNOWN_CLAUDE_OPTIN_PLUGINS } from "./engine-native/parseModifiers";
9
+ import { modelCatalog } from "./engine-native/models";
10
+ import { advisorCatalog, advisorFlagGrammar, effortCatalog, effortFlagGrammar } from "./efforts";
11
+ import type { Tool } from "./manifests";
12
+ import type { ArgvSurfaces, FlagSurface, ScannedFlag } from "./argvSurface";
13
+
14
+ const LEGACY_HINTS: Readonly<Record<string, string>> = {
15
+ "--force": "--force was renamed to --reconcile",
16
+ "--remove-plugins":
17
+ "--remove-plugins was renamed to --prune (it also removes marketplaces + kit-managed skills)",
18
+ "--680k": "--680k was renamed to --claude-compact-window=680k",
19
+ "--permissive": "--permissive was renamed to --claude-permissive",
20
+ "--supabase": "--supabase was renamed to --claude-plugin=supabase",
21
+ "--n8n": "--n8n was renamed to --claude-plugin=n8n",
22
+ "--skip-rtk": "--skip-rtk was renamed to --skip-bubblewrap",
23
+ "--claude": "--claude was renamed: pass the target as a word, e.g. 'sync claude'",
24
+ "--codex": "--codex was renamed: pass the target as a word, e.g. 'sync codex'",
25
+ "--agents": "--agents was renamed: pass the target as a word, e.g. 'sync agents'",
26
+ };
27
+
28
+ const modelCatalogHint = (tool: Tool): string => {
29
+ const catalog = modelCatalog(tool);
30
+ const list = catalog.models
31
+ .map((model) => ` ${model.id}${model.note !== undefined ? ` — ${model.note}` : ""}`)
32
+ .join("\n");
33
+ return `Available ${tool} models (kit-verified ${catalog.verified} — SoT/models.json):\n${list}`;
34
+ };
35
+
36
+ const missingModifierValue = (flag: string): string | undefined => {
37
+ switch (flag) {
38
+ case "--claude-model":
39
+ case "--codex-model": {
40
+ const tool: Tool = flag === "--claude-model" ? "claude" : "codex";
41
+ return `${modelCatalogHint(tool)}\n${flag} requires a value: ${flag}=<model>`;
42
+ }
43
+ case "--claude-effort":
44
+ case "--codex-effort": {
45
+ const tool: Tool = flag === "--claude-effort" ? "claude" : "codex";
46
+ return `${effortCatalog(tool)}\n${flag} requires a value: ${effortFlagGrammar(tool)}`;
47
+ }
48
+ case "--claude-advisor":
49
+ return `${advisorCatalog()}\n${flag} requires a value: ${advisorFlagGrammar()}`;
50
+ case "--claude-compact-window":
51
+ return `${flag} requires a value: ${flag}=<tokens> (e.g. 680k)`;
52
+ case "--claude-plugin":
53
+ return `${flag} requires a value: ${flag}=<${KNOWN_CLAUDE_OPTIN_PLUGINS.join("|")}>`;
54
+ default:
55
+ return undefined;
56
+ }
57
+ };
58
+
59
+ /**
60
+ * Run the validation checks in diagnostic order. Returns the reject message
61
+ * for the first failure, or undefined when the scanned flags are valid.
62
+ */
63
+ export const validateScannedArgv = (
64
+ subcommand: string | undefined,
65
+ commandSurface: FlagSurface | undefined,
66
+ surfaces: ArgvSurfaces,
67
+ flags: ReadonlyArray<ScannedFlag>,
68
+ ): string | undefined => {
69
+ const unknownCommandWouldMisdiagnoseFlag =
70
+ subcommand !== undefined &&
71
+ commandSurface === undefined &&
72
+ flags.some((flag) => flag.canonicalName === undefined);
73
+ if (unknownCommandWouldMisdiagnoseFlag) {
74
+ return `unknown command '${subcommand}'`;
75
+ }
76
+
77
+ if (subcommand === "sync") {
78
+ for (const flag of flags) {
79
+ const hint = LEGACY_HINTS[flag.name];
80
+ if (hint !== undefined) return hint;
81
+ }
82
+ }
83
+
84
+ // Effect 4 expands clustered shorts, but the kit declares no clusterable alias
85
+ // pair, so refusing a cluster as one unknown flag is the conservative direction.
86
+ for (const flag of flags) {
87
+ if (flag.canonicalName !== undefined) continue;
88
+ const scope = subcommand === undefined ? "" : ` for '${subcommand}'`;
89
+ return `unknown flag ${flag.token}${scope}`;
90
+ }
91
+
92
+ for (const flag of flags) {
93
+ if (!flag.token.includes("=") || flag.takesValue) continue;
94
+ // These booleans are presence-based; Effect 4 would otherwise let
95
+ // `--dry-run=false` read as a dry run while performing a real mutating sync.
96
+ return `flag ${flag.name} does not take a value`;
97
+ }
98
+
99
+ const seen = new Set<string>();
100
+ for (const flag of flags) {
101
+ const name = flag.canonicalName;
102
+ if (name === undefined) continue;
103
+ const duplicateAllowed =
104
+ commandSurface?.repeatableFlags.includes(name) === true ||
105
+ surfaces.globalSurface.repeatableFlags.includes(name) ||
106
+ surfaces.globalSurface.actionFlags.has(name);
107
+ if (!duplicateAllowed && seen.has(name)) {
108
+ return `flag ${name} was given more than once`;
109
+ }
110
+ seen.add(name);
111
+ }
112
+
113
+ if (subcommand === "sync") {
114
+ for (const flag of flags) {
115
+ if (flag.hasValue) continue;
116
+ const message = missingModifierValue(flag.name);
117
+ if (message !== undefined) return message;
118
+ }
119
+ }
120
+
121
+ return undefined;
122
+ };
@@ -1,42 +1,62 @@
1
- import { Argument, Command, Flag } from "effect/unstable/cli"
2
- import { Console, Effect, Option } from "effect"
3
- import { bail } from "../engine"
4
- import overview from "../../docs/overview.md" with { type: "text" }
5
- import flags from "../../docs/flags.md" with { type: "text" }
6
- import modifiers from "../../docs/modifiers.md" with { type: "text" }
7
- import models from "../../docs/models.md" with { type: "text" }
8
- import toolchain from "../../docs/toolchain.md" with { type: "text" }
9
- import plugins from "../../docs/plugins.md" with { type: "text" }
10
- import syncLayers from "../../docs/sync-layers.md" with { type: "text" }
11
- import install from "../../docs/install.md" with { type: "text" }
12
- import platforms from "../../docs/platforms.md" with { type: "text" }
13
- import ompModels from "../../docs/omp-models.md" with { type: "text" }
1
+ import { Argument, Command, Flag } from "effect/unstable/cli";
2
+ import { Console, Effect, Option } from "effect";
3
+ import { bail } from "../engine";
4
+ import overview from "../../docs/overview.md" with { type: "text" };
5
+ import flags from "../../docs/flags.md" with { type: "text" };
6
+ import modifiers from "../../docs/modifiers.md" with { type: "text" };
7
+ import models from "../../docs/models.md" with { type: "text" };
8
+ import toolchain from "../../docs/toolchain.md" with { type: "text" };
9
+ import plugins from "../../docs/plugins.md" with { type: "text" };
10
+ import syncLayers from "../../docs/sync-layers.md" with { type: "text" };
11
+ import install from "../../docs/install.md" with { type: "text" };
12
+ import platforms from "../../docs/platforms.md" with { type: "text" };
13
+ import ompModels from "../../docs/omp-models.md" with { type: "text" };
14
+ import ompContext from "../../docs/omp-context.md" with { type: "text" };
14
15
 
15
16
  const TOPICS: Record<string, { summary: string; body: string }> = {
16
- "overview": { summary: "What docks-kit is and how the pieces fit", body: overview },
17
- "sync-layers": { summary: "The three sync layers and additive-by-default semantics", body: syncLayers },
18
- "flags": { summary: "Full flag reference incl. the old→new rename table", body: flags },
19
- "modifiers": { summary: "Deploy-time modifiers and the flag-less-sync-reverts contract", body: modifiers },
20
- "models": { summary: "Model catalog, validation rules, model get/set", body: models },
21
- "toolchain": { summary: "Verified-version floors and the doctor table", body: toolchain },
22
- "plugins": { summary: "enabledPlugins tri-state + optional plugin opt-ins", body: plugins },
23
- "install": { summary: "Install paths: repo checkout, bun add -g, POSIX/Windows installers", body: install },
24
- "platforms": { summary: "Platform support: Linux, macOS, and Windows on x64 and arm64", body: platforms },
25
- "omp-models": { summary: "omp role map and the Artificial Analysis snapshot behind it", body: ompModels }
26
- }
17
+ overview: { summary: "What docks-kit is and how the pieces fit", body: overview },
18
+ "sync-layers": {
19
+ summary: "The three sync layers and additive-by-default semantics",
20
+ body: syncLayers,
21
+ },
22
+ flags: { summary: "Full flag reference incl. the old→new rename table", body: flags },
23
+ modifiers: {
24
+ summary: "Deploy-time modifiers and the flag-less-sync-reverts contract",
25
+ body: modifiers,
26
+ },
27
+ models: { summary: "Model catalog, validation rules, model get/set", body: models },
28
+ toolchain: { summary: "Verified-version floors and the doctor table", body: toolchain },
29
+ plugins: { summary: "enabledPlugins tri-state + optional plugin opt-ins", body: plugins },
30
+ install: {
31
+ summary: "Install paths: repo checkout, bun add -g, POSIX/Windows installers",
32
+ body: install,
33
+ },
34
+ platforms: {
35
+ summary: "Platform support: Linux, macOS, and Windows on x64 and arm64",
36
+ body: platforms,
37
+ },
38
+ "omp-models": {
39
+ summary: "omp role map and the Artificial Analysis snapshot behind it",
40
+ body: ompModels,
41
+ },
42
+ "omp-context": {
43
+ summary: "omp compaction trigger, the reserve-based default, and context-window lanes",
44
+ body: ompContext,
45
+ },
46
+ };
27
47
 
28
48
  const topic = Argument.String("topic").pipe(
29
49
  Argument.withDescription(`One of: ${Object.keys(TOPICS).join(", ")}`),
30
- Argument.optional
31
- )
50
+ Argument.optional,
51
+ );
32
52
  const json = Flag.Boolean("json").pipe(
33
53
  Flag.withDescription("List topics as JSON"),
34
- Flag.withDefault(false)
35
- )
54
+ Flag.withDefault(false),
55
+ );
36
56
 
37
57
  export const docsCommand = Command.make("docs", { topic, json }, (config) =>
38
58
  Effect.gen(function* () {
39
- const requested = Option.getOrUndefined(config.topic)
59
+ const requested = Option.getOrUndefined(config.topic);
40
60
 
41
61
  if (requested === undefined) {
42
62
  if (config.json) {
@@ -44,25 +64,25 @@ export const docsCommand = Command.make("docs", { topic, json }, (config) =>
44
64
  JSON.stringify(
45
65
  Object.entries(TOPICS).map(([name, t]) => ({ name, summary: t.summary })),
46
66
  null,
47
- 2
48
- )
49
- )
67
+ 2,
68
+ ),
69
+ );
50
70
  }
51
- yield* Console.log("docks-kit documentation topics (docks-kit docs <topic>):\n")
71
+ yield* Console.log("docks-kit documentation topics (docks-kit docs <topic>):\n");
52
72
  for (const [name, t] of Object.entries(TOPICS)) {
53
- yield* Console.log(` ${name.padEnd(14)} ${t.summary}`)
73
+ yield* Console.log(` ${name.padEnd(14)} ${t.summary}`);
54
74
  }
55
- return
75
+ return;
56
76
  }
57
77
 
58
- const entry = TOPICS[requested]
78
+ const entry = TOPICS[requested];
59
79
  if (entry === undefined) {
60
- return yield* bail(`Unknown topic '${requested}'. Valid: ${Object.keys(TOPICS).join(", ")}`)
80
+ return yield* bail(`Unknown topic '${requested}'. Valid: ${Object.keys(TOPICS).join(", ")}`);
61
81
  }
62
- yield* Console.log(entry.body)
63
- })
82
+ yield* Console.log(entry.body);
83
+ }),
64
84
  ).pipe(
65
85
  Command.withDescription(
66
- "Self-documentation: the kit's concepts as readable topics, bundled with the CLI — a fresh agent can learn the kit from here alone."
67
- )
68
- )
86
+ "Self-documentation: the kit's concepts as readable topics, bundled with the CLI — a fresh agent can learn the kit from here alone.",
87
+ ),
88
+ );