@dungle-scrubs/harness-cli-normalizer 0.6.0 → 0.6.2

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 (219) hide show
  1. package/README.md +33 -6
  2. package/dist/cli/args.d.ts +9 -1
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +36 -26
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/cli/check.d.ts +14 -0
  7. package/dist/cli/check.d.ts.map +1 -1
  8. package/dist/cli/check.js +10 -3
  9. package/dist/cli/check.js.map +1 -1
  10. package/dist/cli/config.d.ts +4 -4
  11. package/dist/cli/config.d.ts.map +1 -1
  12. package/dist/cli/config.js +8 -6
  13. package/dist/cli/config.js.map +1 -1
  14. package/dist/cli/exit-codes.d.ts +0 -1
  15. package/dist/cli/exit-codes.d.ts.map +1 -1
  16. package/dist/cli/exit-codes.js +0 -1
  17. package/dist/cli/exit-codes.js.map +1 -1
  18. package/dist/cli/help.d.ts +2 -2
  19. package/dist/cli/help.d.ts.map +1 -1
  20. package/dist/cli/help.js +10 -2
  21. package/dist/cli/help.js.map +1 -1
  22. package/dist/cli/index.d.ts.map +1 -1
  23. package/dist/cli/index.js +3 -1
  24. package/dist/cli/index.js.map +1 -1
  25. package/dist/cli/inspect.d.ts.map +1 -1
  26. package/dist/cli/inspect.js +84 -265
  27. package/dist/cli/inspect.js.map +1 -1
  28. package/dist/cli/plan-turn.d.ts +68 -0
  29. package/dist/cli/plan-turn.d.ts.map +1 -0
  30. package/dist/cli/plan-turn.js +268 -0
  31. package/dist/cli/plan-turn.js.map +1 -0
  32. package/dist/cli/refuse.d.ts.map +1 -1
  33. package/dist/cli/refuse.js +2 -1
  34. package/dist/cli/refuse.js.map +1 -1
  35. package/dist/cli/resolve-harness.d.ts.map +1 -1
  36. package/dist/cli/resolve-harness.js +2 -3
  37. package/dist/cli/resolve-harness.js.map +1 -1
  38. package/dist/cli/run.d.ts.map +1 -1
  39. package/dist/cli/run.js +33 -367
  40. package/dist/cli/run.js.map +1 -1
  41. package/dist/cli/session-json.d.ts +1 -1
  42. package/dist/cli/session-json.d.ts.map +1 -1
  43. package/dist/cli/session-json.js +1 -3
  44. package/dist/cli/session-json.js.map +1 -1
  45. package/dist/cli/session.d.ts.map +1 -1
  46. package/dist/cli/session.js +35 -43
  47. package/dist/cli/session.js.map +1 -1
  48. package/dist/cli/skills-root.d.ts +1 -1
  49. package/dist/cli/skills-root.d.ts.map +1 -1
  50. package/dist/cli/skills-root.js +1 -1
  51. package/dist/cli/skills-root.js.map +1 -1
  52. package/dist/execution/decode.d.ts.map +1 -1
  53. package/dist/execution/decode.js +11 -36
  54. package/dist/execution/decode.js.map +1 -1
  55. package/dist/execution/events.d.ts +4 -1
  56. package/dist/execution/events.d.ts.map +1 -1
  57. package/dist/execution/events.js.map +1 -1
  58. package/dist/execution/failure.d.ts +3 -1
  59. package/dist/execution/failure.d.ts.map +1 -1
  60. package/dist/execution/failure.js +14 -11
  61. package/dist/execution/failure.js.map +1 -1
  62. package/dist/execution/open-session.d.ts +1 -1
  63. package/dist/execution/open-session.d.ts.map +1 -1
  64. package/dist/execution/open-session.js +150 -223
  65. package/dist/execution/open-session.js.map +1 -1
  66. package/dist/execution/stream-turn.d.ts +9 -14
  67. package/dist/execution/stream-turn.d.ts.map +1 -1
  68. package/dist/execution/stream-turn.js +85 -188
  69. package/dist/execution/stream-turn.js.map +1 -1
  70. package/dist/execution/supervisor.d.ts +71 -0
  71. package/dist/execution/supervisor.d.ts.map +1 -0
  72. package/dist/execution/supervisor.js +127 -0
  73. package/dist/execution/supervisor.js.map +1 -0
  74. package/dist/interpretation/argv.d.ts +39 -12
  75. package/dist/interpretation/argv.d.ts.map +1 -1
  76. package/dist/interpretation/argv.js +28 -20
  77. package/dist/interpretation/argv.js.map +1 -1
  78. package/dist/interpretation/content.d.ts +10 -1
  79. package/dist/interpretation/content.d.ts.map +1 -1
  80. package/dist/interpretation/content.js +20 -11
  81. package/dist/interpretation/content.js.map +1 -1
  82. package/dist/interpretation/hints.d.ts +1 -1
  83. package/dist/interpretation/hints.d.ts.map +1 -1
  84. package/dist/interpretation/hints.js +10 -11
  85. package/dist/interpretation/hints.js.map +1 -1
  86. package/dist/interpretation/index.d.ts +0 -1
  87. package/dist/interpretation/index.d.ts.map +1 -1
  88. package/dist/interpretation/index.js +0 -1
  89. package/dist/interpretation/index.js.map +1 -1
  90. package/dist/interpretation/isolation.d.ts +10 -0
  91. package/dist/interpretation/isolation.d.ts.map +1 -0
  92. package/dist/interpretation/isolation.js +30 -0
  93. package/dist/interpretation/isolation.js.map +1 -0
  94. package/dist/interpretation/limits.d.ts +2 -5
  95. package/dist/interpretation/limits.d.ts.map +1 -1
  96. package/dist/interpretation/limits.js +8 -62
  97. package/dist/interpretation/limits.js.map +1 -1
  98. package/dist/interpretation/question.d.ts +27 -0
  99. package/dist/interpretation/question.d.ts.map +1 -1
  100. package/dist/interpretation/question.js +28 -0
  101. package/dist/interpretation/question.js.map +1 -1
  102. package/dist/interpretation/refusal.d.ts +10 -5
  103. package/dist/interpretation/refusal.d.ts.map +1 -1
  104. package/dist/interpretation/refusal.js +24 -15
  105. package/dist/interpretation/refusal.js.map +1 -1
  106. package/dist/interpretation/resolve-options.d.ts +36 -2
  107. package/dist/interpretation/resolve-options.d.ts.map +1 -1
  108. package/dist/interpretation/resolve-options.js +53 -22
  109. package/dist/interpretation/resolve-options.js.map +1 -1
  110. package/dist/interpretation/session-input.d.ts +44 -1
  111. package/dist/interpretation/session-input.d.ts.map +1 -1
  112. package/dist/interpretation/session-input.js +59 -4
  113. package/dist/interpretation/session-input.js.map +1 -1
  114. package/dist/interpretation/shape.d.ts +1 -0
  115. package/dist/interpretation/shape.d.ts.map +1 -1
  116. package/dist/interpretation/shape.js +10 -0
  117. package/dist/interpretation/shape.js.map +1 -1
  118. package/dist/interpretation/skills-selection.d.ts +25 -33
  119. package/dist/interpretation/skills-selection.d.ts.map +1 -1
  120. package/dist/interpretation/skills-selection.js +41 -48
  121. package/dist/interpretation/skills-selection.js.map +1 -1
  122. package/dist/interpretation/support.d.ts.map +1 -1
  123. package/dist/interpretation/support.js +25 -8
  124. package/dist/interpretation/support.js.map +1 -1
  125. package/dist/interpretation/tool-selection.d.ts +7 -6
  126. package/dist/interpretation/tool-selection.d.ts.map +1 -1
  127. package/dist/interpretation/tool-selection.js +54 -116
  128. package/dist/interpretation/tool-selection.js.map +1 -1
  129. package/dist/interpretation/tool-vocabulary.d.ts +31 -12
  130. package/dist/interpretation/tool-vocabulary.d.ts.map +1 -1
  131. package/dist/interpretation/tool-vocabulary.js +19 -41
  132. package/dist/interpretation/tool-vocabulary.js.map +1 -1
  133. package/dist/interpretation/turn-options.d.ts +4 -0
  134. package/dist/interpretation/turn-options.d.ts.map +1 -1
  135. package/dist/interpretation/turn-options.js +107 -241
  136. package/dist/interpretation/turn-options.js.map +1 -1
  137. package/dist/interpretation/vocabulary.d.ts +8 -6
  138. package/dist/interpretation/vocabulary.d.ts.map +1 -1
  139. package/dist/interpretation/vocabulary.js +22 -9
  140. package/dist/interpretation/vocabulary.js.map +1 -1
  141. package/dist/knowledge/claude-code.d.ts +3 -1
  142. package/dist/knowledge/claude-code.d.ts.map +1 -1
  143. package/dist/knowledge/claude-code.js +17 -4
  144. package/dist/knowledge/claude-code.js.map +1 -1
  145. package/dist/knowledge/codex.d.ts.map +1 -1
  146. package/dist/knowledge/codex.js +31 -7
  147. package/dist/knowledge/codex.js.map +1 -1
  148. package/dist/knowledge/descriptor.d.ts +67 -36
  149. package/dist/knowledge/descriptor.d.ts.map +1 -1
  150. package/dist/knowledge/descriptor.js +50 -15
  151. package/dist/knowledge/descriptor.js.map +1 -1
  152. package/dist/knowledge/matchers.d.ts +12 -0
  153. package/dist/knowledge/matchers.d.ts.map +1 -1
  154. package/dist/knowledge/matchers.js +34 -0
  155. package/dist/knowledge/matchers.js.map +1 -1
  156. package/dist/knowledge/muse.d.ts.map +1 -1
  157. package/dist/knowledge/muse.js +13 -6
  158. package/dist/knowledge/muse.js.map +1 -1
  159. package/dist/knowledge/overrides.d.ts.map +1 -1
  160. package/dist/knowledge/overrides.js +9 -20
  161. package/dist/knowledge/overrides.js.map +1 -1
  162. package/dist/knowledge/pi.d.ts.map +1 -1
  163. package/dist/knowledge/pi.js +8 -6
  164. package/dist/knowledge/pi.js.map +1 -1
  165. package/dist/knowledge/profile.d.ts +1 -0
  166. package/dist/knowledge/profile.d.ts.map +1 -1
  167. package/dist/knowledge/profile.js +1 -0
  168. package/dist/knowledge/profile.js.map +1 -1
  169. package/package.json +1 -1
  170. package/src/cli/args.ts +39 -27
  171. package/src/cli/check.ts +10 -3
  172. package/src/cli/config.ts +14 -11
  173. package/src/cli/exit-codes.ts +0 -2
  174. package/src/cli/help.ts +10 -2
  175. package/src/cli/index.ts +3 -1
  176. package/src/cli/inspect.ts +88 -269
  177. package/src/cli/plan-turn.ts +350 -0
  178. package/src/cli/refuse.ts +2 -1
  179. package/src/cli/resolve-harness.ts +4 -5
  180. package/src/cli/run.ts +36 -399
  181. package/src/cli/session-json.ts +1 -6
  182. package/src/cli/session.ts +51 -44
  183. package/src/cli/skills-root.ts +1 -4
  184. package/src/execution/decode.ts +10 -36
  185. package/src/execution/events.ts +4 -1
  186. package/src/execution/failure.ts +18 -11
  187. package/src/execution/open-session.ts +154 -228
  188. package/src/execution/stream-turn.ts +90 -193
  189. package/src/execution/supervisor.ts +199 -0
  190. package/src/interpretation/argv.ts +65 -34
  191. package/src/interpretation/content.ts +30 -13
  192. package/src/interpretation/hints.ts +2 -12
  193. package/src/interpretation/index.ts +0 -1
  194. package/src/interpretation/isolation.ts +39 -0
  195. package/src/interpretation/limits.ts +14 -75
  196. package/src/interpretation/question.ts +46 -0
  197. package/src/interpretation/refusal.ts +28 -18
  198. package/src/interpretation/resolve-options.ts +98 -33
  199. package/src/interpretation/session-input.ts +88 -5
  200. package/src/interpretation/shape.ts +10 -0
  201. package/src/interpretation/skills-selection.ts +70 -74
  202. package/src/interpretation/support.ts +21 -6
  203. package/src/interpretation/tool-selection.ts +67 -122
  204. package/src/interpretation/tool-vocabulary.ts +48 -53
  205. package/src/interpretation/turn-options.ts +125 -220
  206. package/src/interpretation/vocabulary.ts +15 -10
  207. package/src/knowledge/claude-code.ts +17 -4
  208. package/src/knowledge/codex.ts +31 -7
  209. package/src/knowledge/descriptor.ts +106 -49
  210. package/src/knowledge/matchers.ts +35 -0
  211. package/src/knowledge/muse.ts +13 -6
  212. package/src/knowledge/overrides.ts +17 -32
  213. package/src/knowledge/pi.ts +8 -6
  214. package/src/knowledge/profile.ts +1 -0
  215. package/dist/interpretation/dimensions.d.ts +0 -11
  216. package/dist/interpretation/dimensions.d.ts.map +0 -1
  217. package/dist/interpretation/dimensions.js +0 -4
  218. package/dist/interpretation/dimensions.js.map +0 -1
  219. package/src/interpretation/dimensions.ts +0 -14
package/src/cli/args.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { parseArgs } from "node:util";
3
3
  import type { DiscoveryOptions, TurnOptions } from "../interpretation/argv.js";
4
+ import { isQuestionMode, QUESTION_MODES } from "../interpretation/question.js";
4
5
  import { ArgvRefusalError } from "../interpretation/refusal.js";
5
6
 
6
7
  export type ParsedPromptSource =
@@ -49,7 +50,6 @@ export const parseEnvEntries = (
49
50
  if (eq === -1) {
50
51
  throw new ArgvRefusalError({
51
52
  issue: "invalid-env",
52
- harness: "claude",
53
53
  supported: ["KEY=VAL"],
54
54
  detail: entry,
55
55
  });
@@ -59,7 +59,6 @@ export const parseEnvEntries = (
59
59
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || value.includes("\0") || key.includes("\0")) {
60
60
  throw new ArgvRefusalError({
61
61
  issue: "invalid-env",
62
- harness: "claude",
63
62
  supported: ["keys must match ^[A-Za-z_][A-Za-z0-9_]*$ and contain no NUL"],
64
63
  detail: entry,
65
64
  });
@@ -82,7 +81,6 @@ export const resolvePrompt = (args: {
82
81
  if (count === 0) {
83
82
  throw new ArgvRefusalError({
84
83
  issue: "invalid-option-value",
85
- harness: "claude",
86
84
  supported: ["provide prompt via positional, --prompt, or --prompt-file"],
87
85
  detail: "missing prompt",
88
86
  });
@@ -90,7 +88,6 @@ export const resolvePrompt = (args: {
90
88
  if (count > 1) {
91
89
  throw new ArgvRefusalError({
92
90
  issue: "invalid-option-value",
93
- harness: "claude",
94
91
  supported: ["use one of positional, --prompt, or --prompt-file"],
95
92
  detail: "mutual exclusion",
96
93
  });
@@ -122,7 +119,6 @@ export const resolvePromptAsync = async (args: {
122
119
  if (count === 0) {
123
120
  throw new ArgvRefusalError({
124
121
  issue: "invalid-option-value",
125
- harness: "claude",
126
122
  supported: ["provide prompt via positional, --prompt, or --prompt-file"],
127
123
  detail: "missing prompt",
128
124
  });
@@ -130,7 +126,6 @@ export const resolvePromptAsync = async (args: {
130
126
  if (count > 1) {
131
127
  throw new ArgvRefusalError({
132
128
  issue: "invalid-option-value",
133
- harness: "claude",
134
129
  supported: ["use one of positional, --prompt, or --prompt-file"],
135
130
  detail: "mutual exclusion",
136
131
  });
@@ -148,15 +143,21 @@ export const resolvePromptAsync = async (args: {
148
143
  return { prompt: content, source: "prompt-file" };
149
144
  };
150
145
 
146
+ /** Parsed turn options plus the skill names as typed, which the command
147
+ * resolves against the registry before they become a turn option. */
148
+ export type ParsedTurnOptions = TurnOptions & { readonly skillNames?: readonly string[] };
149
+
151
150
  /**
152
151
  * Build TurnOptions from parsed flag values. Shared between run and inspect --argv.
153
152
  */
154
- export const parseTurnOptions = (values: Record<string, unknown>): TurnOptions => {
153
+ export const parseTurnOptions = (values: Record<string, unknown>): ParsedTurnOptions => {
155
154
  const opts: Record<string, unknown> = {};
156
155
 
156
+ if (values.isolation !== undefined) opts.isolation = values.isolation;
157
157
  if (values.model !== undefined) opts.model = values.model;
158
158
  if (values.effort !== undefined) opts.effort = values.effort;
159
159
  if (values.sandbox !== undefined) opts.sandbox = values.sandbox;
160
+ if (values["context-window"] !== undefined) opts.contextWindow = Number(values["context-window"]);
160
161
  if (values.provider !== undefined) opts.provider = values.provider;
161
162
  if (values.tools !== undefined) {
162
163
  const raw = String(values.tools);
@@ -167,9 +168,10 @@ export const parseTurnOptions = (values: Record<string, unknown>): TurnOptions =
167
168
  opts.excludeTools = raw.length === 0 ? [] : raw.split(",").map((s) => s.trim());
168
169
  }
169
170
  if (values.skills !== undefined) {
171
+ // Names as typed; the command resolves them against the registry and
172
+ // builds the skills turn option (picks plus known names) from them.
170
173
  const raw = String(values.skills);
171
- (opts as Record<string, unknown>).skills =
172
- raw.length === 0 ? [] : raw.split(",").map((s) => s.trim());
174
+ opts.skillNames = raw.length === 0 ? [] : raw.split(",").map((s) => s.trim());
173
175
  }
174
176
  if (values.autonomy === true) opts.autonomy = true;
175
177
  else if (values["no-autonomy"] === true) opts.autonomy = false;
@@ -181,16 +183,15 @@ export const parseTurnOptions = (values: Record<string, unknown>): TurnOptions =
181
183
  else if (values["no-shell"] === true) opts.shell = false;
182
184
  if (values.questions !== undefined) {
183
185
  const v = String(values.questions);
184
- if (!["ask", "assume", "none"].includes(v)) {
186
+ if (!isQuestionMode(v)) {
185
187
  throw new ArgvRefusalError({
186
188
  issue: "invalid-option-value",
187
- harness: "claude",
188
189
  option: "questions",
189
- supported: ["ask", "assume", "none"],
190
+ supported: [...QUESTION_MODES],
190
191
  detail: v,
191
192
  });
192
193
  }
193
- (opts as Record<string, unknown>).questions = v;
194
+ opts.questions = v;
194
195
  }
195
196
  if (values["system-prompt"] !== undefined) opts.systemPrompt = String(values["system-prompt"]);
196
197
  if (values["append-system-prompt"] !== undefined)
@@ -201,7 +202,6 @@ export const parseTurnOptions = (values: Record<string, unknown>): TurnOptions =
201
202
  if (!Number.isFinite(n)) {
202
203
  throw new ArgvRefusalError({
203
204
  issue: "invalid-option-value",
204
- harness: "claude",
205
205
  option: "maxSteps",
206
206
  supported: ["integer 1-10000"],
207
207
  detail: String(values["max-steps"]),
@@ -234,7 +234,22 @@ export const parseTurnOptions = (values: Record<string, unknown>): TurnOptions =
234
234
  if (values.access !== undefined) opts.access = String(values.access);
235
235
 
236
236
  // prompt will be set by caller after resolvePrompt
237
- return opts as unknown as TurnOptions;
237
+ return opts as unknown as ParsedTurnOptions;
238
+ };
239
+
240
+ /** `--resume` and `--session-id` are aliases for one session id; both at
241
+ * once refuses. The one check every command calls (run, inspect, session). */
242
+ export const resumeIdOf = (values: Record<string, unknown>): string | undefined => {
243
+ if (values.resume !== undefined && values["session-id"] !== undefined) {
244
+ throw new ArgvRefusalError({
245
+ issue: "mutually-exclusive-options",
246
+ supported: ["--resume or --session-id, not both (--session-id is an alias for --resume)"],
247
+ detail: "both --resume and --session-id given",
248
+ });
249
+ }
250
+ if (values.resume !== undefined) return String(values.resume);
251
+ if (values["session-id"] !== undefined) return String(values["session-id"]);
252
+ return undefined;
238
253
  };
239
254
 
240
255
  export const parseRunExtra = (
@@ -256,8 +271,7 @@ export const parseRunExtra = (
256
271
  if (!Number.isFinite(n) || n < 0 || !Number.isInteger(n)) {
257
272
  throw new ArgvRefusalError({
258
273
  issue: "invalid-option-value",
259
- harness: "claude",
260
- option: "maxSteps",
274
+ option: "timeout",
261
275
  supported: ["whole seconds, >= 0 (0 disables)"],
262
276
  detail: String(values.timeout),
263
277
  });
@@ -265,16 +279,8 @@ export const parseRunExtra = (
265
279
  extra.timeoutSeconds = n;
266
280
  }
267
281
  if (values.cwd !== undefined) extra.cwd = String(values.cwd);
268
- if (values.resume !== undefined && values["session-id"] !== undefined) {
269
- throw new ArgvRefusalError({
270
- issue: "mutually-exclusive-options",
271
- harness: "claude",
272
- supported: ["--resume or --session-id, not both (--session-id is an alias for --resume)"],
273
- detail: "both --resume and --session-id given",
274
- });
275
- }
276
- if (values.resume !== undefined) extra.resume = String(values.resume);
277
- if (values["session-id"] !== undefined) extra.resume = String(values["session-id"]);
282
+ const resume = resumeIdOf(values);
283
+ if (resume !== undefined) extra.resume = resume;
278
284
  if (values.env !== undefined) {
279
285
  // parseArgs with multiple:true gives string[] ; else string
280
286
  const list = values.env as string | string[];
@@ -294,6 +300,7 @@ const KNOWN_FLAGS = new Set([
294
300
  "--model",
295
301
  "--effort",
296
302
  "--sandbox",
303
+ "--context-window",
297
304
  "--provider",
298
305
  "--tools",
299
306
  "--exclude-tools",
@@ -308,6 +315,7 @@ const KNOWN_FLAGS = new Set([
308
315
  "--system-prompt",
309
316
  "--append-system-prompt",
310
317
  "--max-steps",
318
+ "--isolation",
311
319
  "--no-tools",
312
320
  "--no-instruction-files",
313
321
  "--no-extensions",
@@ -328,11 +336,13 @@ const KNOWN_FLAGS = new Set([
328
336
  ]);
329
337
 
330
338
  const FLAGS_WITH_VALUE = new Set([
339
+ "--isolation",
331
340
  "--prompt",
332
341
  "--prompt-file",
333
342
  "--model",
334
343
  "--effort",
335
344
  "--sandbox",
345
+ "--context-window",
336
346
  "--provider",
337
347
  "--tools",
338
348
  "--exclude-tools",
@@ -448,6 +458,7 @@ export const parseCommonFlags = (
448
458
  model: { type: "string" as const },
449
459
  effort: { type: "string" as const },
450
460
  sandbox: { type: "string" as const },
461
+ "context-window": { type: "string" as const },
451
462
  provider: { type: "string" as const },
452
463
  tools: { type: "string" as const },
453
464
  "exclude-tools": { type: "string" as const },
@@ -462,6 +473,7 @@ export const parseCommonFlags = (
462
473
  "system-prompt": { type: "string" as const },
463
474
  "append-system-prompt": { type: "string" as const },
464
475
  "max-steps": { type: "string" as const },
476
+ isolation: { type: "string" as const },
465
477
  access: { type: "string" as const },
466
478
  "no-tools": { type: "boolean" as const },
467
479
  "no-instruction-files": { type: "boolean" as const },
package/src/cli/check.ts CHANGED
@@ -12,7 +12,10 @@ interface Row {
12
12
  status: VersionStatus;
13
13
  }
14
14
 
15
- const npmLatest = async (pkg: string): Promise<string | null> => {
15
+ /** The latest published version of an npm package, or null when the
16
+ * registry cannot be reached. One owner; the version-check script imports
17
+ * it (RFC-02 change 12). */
18
+ export const npmLatest = async (pkg: string): Promise<string | null> => {
16
19
  try {
17
20
  const res = await fetch(`https://registry.npmjs.org/${pkg}`, {
18
21
  headers: { accept: "application/vnd.npm.install-v1+json" },
@@ -27,7 +30,9 @@ const npmLatest = async (pkg: string): Promise<string | null> => {
27
30
  }
28
31
  };
29
32
 
30
- const installedVersion = (bin: string): string | null => {
33
+ /** The locally installed CLI's version from `<bin> --version`, or null
34
+ * when the binary is absent or reports none. */
35
+ export const installedVersion = (bin: string): string | null => {
31
36
  try {
32
37
  const out = execFileSync(bin, ["--version"], { encoding: "utf8", timeout: 10_000 });
33
38
  const match = out.match(/\d+\.\d+\.\d+(?:[.+-][0-9A-Za-z.-]+)?/);
@@ -37,7 +42,9 @@ const installedVersion = (bin: string): string | null => {
37
42
  }
38
43
  };
39
44
 
40
- const resolveLatest = async (
45
+ /** Where a descriptor's latest version is found, per its versionSource:
46
+ * the npm registry, or the installed binary. */
47
+ export const resolveLatest = async (
41
48
  h: HarnessDescriptor,
42
49
  ): Promise<{ latest: string | null; source: string }> => {
43
50
  if (h.versionSource.kind === "npm") {
package/src/cli/config.ts CHANGED
@@ -20,8 +20,9 @@ import { existsSync, readFileSync } from "node:fs";
20
20
  import { homedir } from "node:os";
21
21
  import { dirname, join, resolve } from "node:path";
22
22
  import type { TurnOptions } from "../interpretation/argv.js";
23
- import { TOOL_SELECTOR } from "../interpretation/tool-selection.js";
24
- import { validateAccess } from "../interpretation/vocabulary.js";
23
+ import { isQuestionMode, QUESTION_MODES } from "../interpretation/question.js";
24
+ import type { ConfigTier } from "../interpretation/resolve-options.js";
25
+ import { CLEAN_SELECTOR, validateAccess } from "../interpretation/vocabulary.js";
25
26
  import { HARNESS_NAMES } from "../knowledge/descriptor.js";
26
27
  import { defaultDescriptors } from "../knowledge/overrides.js";
27
28
 
@@ -63,11 +64,14 @@ type MutableTurnOptions = {
63
64
  [K in keyof TurnOptions]?: TurnOptions[K];
64
65
  };
65
66
 
67
+ // Isolation is invocation-only: a config must not silently change native auth
68
+ // or disable all tools for unrelated turns.
66
69
  const KNOWN_KEYS = new Set([
67
70
  "effort",
68
71
  "model",
69
72
  "provider",
70
73
  "sandbox",
74
+ "contextWindow",
71
75
  "tools",
72
76
  "excludeTools",
73
77
  "autonomy",
@@ -88,11 +92,10 @@ const KNOWN_KEYS = new Set([
88
92
 
89
93
  const LIST_KEYS = new Set(["tools", "excludeTools"]);
90
94
  const BOOL_KEYS = new Set(["autonomy", "write", "shell"]);
91
- const QUESTIONS_VALUES = new Set(["ask", "assume", "none"]);
92
95
 
93
96
  /** Parse + validate config text. Throws ConfigError with the offending key
94
97
  * named on any violation - never warns and continues. */
95
- export const parseUserConfig = (text: string): Partial<TurnOptions> => {
98
+ export const parseUserConfig = (text: string): ConfigTier => {
96
99
  let raw: unknown;
97
100
  try {
98
101
  raw = JSON.parse(text);
@@ -126,7 +129,7 @@ export const parseUserConfig = (text: string): Partial<TurnOptions> => {
126
129
  );
127
130
  }
128
131
  const toolMapObj = value as Record<string, unknown>;
129
- const SEL = TOOL_SELECTOR;
132
+ const SEL = CLEAN_SELECTOR;
130
133
  const descSet = defaultDescriptors();
131
134
  const harnessNames = HARNESS_NAMES as readonly string[];
132
135
  for (const [harness, inner] of Object.entries(toolMapObj)) {
@@ -228,9 +231,9 @@ export const parseUserConfig = (text: string): Partial<TurnOptions> => {
228
231
  continue;
229
232
  }
230
233
  if (key === "questions") {
231
- if (typeof value !== "string" || !QUESTIONS_VALUES.has(value)) {
234
+ if (typeof value !== "string" || !isQuestionMode(value)) {
232
235
  throw new ConfigError(
233
- `config key ${JSON.stringify(key)} must be one of ${[...QUESTIONS_VALUES].join(", ")}`,
236
+ `config key ${JSON.stringify(key)} must be one of ${QUESTION_MODES.join(", ")}`,
234
237
  );
235
238
  }
236
239
  (out as Record<string, unknown>).questions = value;
@@ -250,24 +253,24 @@ export const parseUserConfig = (text: string): Partial<TurnOptions> => {
250
253
  }
251
254
  (out as Record<string, unknown>)[key] = value;
252
255
  }
253
- return out as Partial<TurnOptions>;
256
+ return out as ConfigTier;
254
257
  };
255
258
 
256
259
  /** Load the user config if the file exists; absent file is an empty config
257
260
  * (no tiers engaged), unreadable or invalid file is a hard error. */
258
- export const loadUserConfig = (): { config: Partial<TurnOptions>; path: string } | null =>
261
+ export const loadUserConfig = (): { config: ConfigTier; path: string } | null =>
259
262
  loadConfigAt(userConfigPath());
260
263
 
261
264
  /** Load the project config (git-root auto-discovery, ratified A). */
262
265
  export const loadProjectConfig = (
263
266
  startDir?: string,
264
- ): { config: Partial<TurnOptions>; path: string } | null => {
267
+ ): { config: ConfigTier; path: string } | null => {
265
268
  const path = projectConfigPath(startDir);
266
269
  if (path === null) return null;
267
270
  return loadConfigAt(path);
268
271
  };
269
272
 
270
- const loadConfigAt = (path: string): { config: Partial<TurnOptions>; path: string } | null => {
273
+ const loadConfigAt = (path: string): { config: ConfigTier; path: string } | null => {
271
274
  let text: string;
272
275
  try {
273
276
  text = readFileSync(path, "utf8");
@@ -9,5 +9,3 @@ export const exitCodeForCause = (cause: ExitCause): number => {
9
9
  // All non-clean causes are failures that should be exit 1, except refusal is already handled separately
10
10
  return EXIT_FAILURE;
11
11
  };
12
-
13
- export const exitCodeForRefusal = (): number => EXIT_REFUSAL;
package/src/cli/help.ts CHANGED
@@ -31,6 +31,8 @@ Options:
31
31
  --model <id> Model id (validated per harness)
32
32
  --effort <value> Effort level (validated per harness/model)
33
33
  --sandbox <value> Sandbox mode (codex only)
34
+ --context-window <tokens> Context window (codex, integer 1-272000;
35
+ launch default 272000; config: contextWindow)
34
36
  --provider <value> Provider (pi only)
35
37
  --tools <a,b> Tool grant allowlist - canonical names (read, write,
36
38
  edit, shell, grep, glob, list, web-fetch,
@@ -70,11 +72,14 @@ Options:
70
72
  --max-steps <n> Max steps (muse, 1-10000)
71
73
  --timeout <seconds> Wall-clock budget for the run (all harnesses,
72
74
  hcn-enforced; 0 disables; no default)
75
+ --isolation <tool-free> Fresh tool-free turn (claude); no resume, tools,
76
+ skills, access, autonomy, discovery, or native passthrough.
77
+ Uses bare mode; native authentication must support it.
73
78
  --no-tools Disable tools discovery facet
74
79
  --no-instruction-files Disable instructionFiles discovery facet
75
80
  --no-extensions Disable extensions discovery facet
76
81
  --skills <a,b> Skill allowlist (names resolved against $HCN_SKILLS_ROOT
77
- or ~/.agents/skills; pi loads, claude narrows)
82
+ or ~/.agents/skills; pi loads, claude and codex narrow)
78
83
  --no-skills Disable skills discovery facet
79
84
  --cwd <path> Working directory for spawn
80
85
  --env KEY=VAL Environment (repeatable; KEY= deletes)
@@ -99,7 +104,8 @@ Defaults with no flags:
99
104
  git root) > user config (~/.config/hcn/config.json) > built-in profile
100
105
  > harness default. The profile pins: effort medium, sandbox
101
106
  workspace-write (codex only; other harnesses report divergence),
102
- discovery on, autonomy off, write/shell on. timeout, max-steps and
107
+ context window 272000 (codex only; divergence elsewhere), discovery on,
108
+ autonomy off, write/shell on. timeout, max-steps and
103
109
  access have no default; harness default applies (access write emits
104
110
  nothing on claude/pi/muse, --sandbox workspace-write on codex via
105
111
  profile). toolMap is config-only (no flag) - canonical -> native
@@ -171,6 +177,7 @@ Options:
171
177
  --model <id> Model
172
178
  --effort <value> Effort
173
179
  --sandbox <value> Sandbox
180
+ --context-window <tokens> Context window (codex, integer 1-272000)
174
181
  --provider <value> Provider
175
182
  --tools <a,b> Tools - canonical names (read, write, edit,
176
183
  shell, grep, glob, list, web-fetch,
@@ -182,6 +189,7 @@ Options:
182
189
  --questions <ask|assume|none>
183
190
  (accepted; renders nothing - rides the run prompt)
184
191
  --max-steps <n>
192
+ --isolation <tool-free> (fresh claude turn only)
185
193
  --no-tools, --no-instruction-files, --no-extensions, --no-skills
186
194
  --questions <ask|assume|none>
187
195
  Accepted; renders nothing in argv (the mode
package/src/cli/index.ts CHANGED
@@ -2,10 +2,12 @@
2
2
  import { realpathSync } from "node:fs";
3
3
  import { resolve } from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
+ import { HARNESS_NAMES } from "../knowledge/descriptor.js";
5
6
  import { TOP_LEVEL_HELP } from "./help.js";
6
7
  import { getVersion } from "./version.js";
7
8
 
8
- const SUPPORTED = ["claude", "codex", "pi", "muse"] as const;
9
+ /** The one harness list, read from the descriptor vocabulary. */
10
+ const SUPPORTED: readonly string[] = HARNESS_NAMES;
9
11
 
10
12
  // Prevent EPIPE crashes when piped to head/grep -q (e.g., hcn ls | head, hcn run --json | head)
11
13
  process.stdout.on("error", (err) => {