@dungle-scrubs/harness-cli-normalizer 0.5.7 → 0.6.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.
Files changed (212) hide show
  1. package/README.md +25 -8
  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 +31 -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 +6 -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 +3 -3
  19. package/dist/cli/help.d.ts.map +1 -1
  20. package/dist/cli/help.js +9 -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 +266 -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 +27 -45
  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 +6 -1
  63. package/dist/execution/open-session.d.ts.map +1 -1
  64. package/dist/execution/open-session.js +151 -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 +44 -12
  75. package/dist/interpretation/argv.d.ts.map +1 -1
  76. package/dist/interpretation/argv.js +38 -24
  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/limits.d.ts +2 -5
  91. package/dist/interpretation/limits.d.ts.map +1 -1
  92. package/dist/interpretation/limits.js +8 -62
  93. package/dist/interpretation/limits.js.map +1 -1
  94. package/dist/interpretation/question.d.ts +27 -0
  95. package/dist/interpretation/question.d.ts.map +1 -1
  96. package/dist/interpretation/question.js +28 -0
  97. package/dist/interpretation/question.js.map +1 -1
  98. package/dist/interpretation/refusal.d.ts +10 -5
  99. package/dist/interpretation/refusal.d.ts.map +1 -1
  100. package/dist/interpretation/refusal.js +24 -15
  101. package/dist/interpretation/refusal.js.map +1 -1
  102. package/dist/interpretation/resolve-options.d.ts +36 -2
  103. package/dist/interpretation/resolve-options.d.ts.map +1 -1
  104. package/dist/interpretation/resolve-options.js +38 -21
  105. package/dist/interpretation/resolve-options.js.map +1 -1
  106. package/dist/interpretation/session-input.d.ts +44 -1
  107. package/dist/interpretation/session-input.d.ts.map +1 -1
  108. package/dist/interpretation/session-input.js +59 -4
  109. package/dist/interpretation/session-input.js.map +1 -1
  110. package/dist/interpretation/shape.d.ts +1 -0
  111. package/dist/interpretation/shape.d.ts.map +1 -1
  112. package/dist/interpretation/shape.js +10 -0
  113. package/dist/interpretation/shape.js.map +1 -1
  114. package/dist/interpretation/skills-selection.d.ts +25 -33
  115. package/dist/interpretation/skills-selection.d.ts.map +1 -1
  116. package/dist/interpretation/skills-selection.js +41 -48
  117. package/dist/interpretation/skills-selection.js.map +1 -1
  118. package/dist/interpretation/support.d.ts.map +1 -1
  119. package/dist/interpretation/support.js +23 -8
  120. package/dist/interpretation/support.js.map +1 -1
  121. package/dist/interpretation/tool-selection.d.ts +7 -6
  122. package/dist/interpretation/tool-selection.d.ts.map +1 -1
  123. package/dist/interpretation/tool-selection.js +54 -116
  124. package/dist/interpretation/tool-selection.js.map +1 -1
  125. package/dist/interpretation/tool-vocabulary.d.ts +31 -12
  126. package/dist/interpretation/tool-vocabulary.d.ts.map +1 -1
  127. package/dist/interpretation/tool-vocabulary.js +19 -41
  128. package/dist/interpretation/tool-vocabulary.js.map +1 -1
  129. package/dist/interpretation/turn-options.d.ts +4 -0
  130. package/dist/interpretation/turn-options.d.ts.map +1 -1
  131. package/dist/interpretation/turn-options.js +105 -241
  132. package/dist/interpretation/turn-options.js.map +1 -1
  133. package/dist/interpretation/vocabulary.d.ts +8 -6
  134. package/dist/interpretation/vocabulary.d.ts.map +1 -1
  135. package/dist/interpretation/vocabulary.js +22 -9
  136. package/dist/interpretation/vocabulary.js.map +1 -1
  137. package/dist/knowledge/claude-code.js +3 -3
  138. package/dist/knowledge/claude-code.js.map +1 -1
  139. package/dist/knowledge/codex.d.ts.map +1 -1
  140. package/dist/knowledge/codex.js +31 -7
  141. package/dist/knowledge/codex.js.map +1 -1
  142. package/dist/knowledge/descriptor.d.ts +67 -36
  143. package/dist/knowledge/descriptor.d.ts.map +1 -1
  144. package/dist/knowledge/descriptor.js +49 -15
  145. package/dist/knowledge/descriptor.js.map +1 -1
  146. package/dist/knowledge/matchers.d.ts +12 -0
  147. package/dist/knowledge/matchers.d.ts.map +1 -1
  148. package/dist/knowledge/matchers.js +34 -0
  149. package/dist/knowledge/matchers.js.map +1 -1
  150. package/dist/knowledge/muse.d.ts.map +1 -1
  151. package/dist/knowledge/muse.js +7 -5
  152. package/dist/knowledge/muse.js.map +1 -1
  153. package/dist/knowledge/overrides.d.ts.map +1 -1
  154. package/dist/knowledge/overrides.js +9 -20
  155. package/dist/knowledge/overrides.js.map +1 -1
  156. package/dist/knowledge/pi.d.ts.map +1 -1
  157. package/dist/knowledge/pi.js +8 -6
  158. package/dist/knowledge/pi.js.map +1 -1
  159. package/dist/knowledge/profile.d.ts +1 -0
  160. package/dist/knowledge/profile.d.ts.map +1 -1
  161. package/dist/knowledge/profile.js +1 -0
  162. package/dist/knowledge/profile.js.map +1 -1
  163. package/package.json +1 -1
  164. package/src/cli/args.ts +35 -27
  165. package/src/cli/check.ts +10 -3
  166. package/src/cli/config.ts +12 -11
  167. package/src/cli/exit-codes.ts +0 -2
  168. package/src/cli/help.ts +9 -2
  169. package/src/cli/index.ts +3 -1
  170. package/src/cli/inspect.ts +88 -269
  171. package/src/cli/plan-turn.ts +346 -0
  172. package/src/cli/refuse.ts +2 -1
  173. package/src/cli/resolve-harness.ts +4 -5
  174. package/src/cli/run.ts +36 -399
  175. package/src/cli/session-json.ts +1 -6
  176. package/src/cli/session.ts +36 -45
  177. package/src/cli/skills-root.ts +1 -4
  178. package/src/execution/decode.ts +10 -36
  179. package/src/execution/events.ts +4 -1
  180. package/src/execution/failure.ts +18 -11
  181. package/src/execution/open-session.ts +160 -228
  182. package/src/execution/stream-turn.ts +90 -193
  183. package/src/execution/supervisor.ts +199 -0
  184. package/src/interpretation/argv.ts +86 -38
  185. package/src/interpretation/content.ts +30 -13
  186. package/src/interpretation/hints.ts +2 -12
  187. package/src/interpretation/index.ts +0 -1
  188. package/src/interpretation/limits.ts +14 -75
  189. package/src/interpretation/question.ts +46 -0
  190. package/src/interpretation/refusal.ts +28 -18
  191. package/src/interpretation/resolve-options.ts +85 -32
  192. package/src/interpretation/session-input.ts +88 -5
  193. package/src/interpretation/shape.ts +10 -0
  194. package/src/interpretation/skills-selection.ts +70 -74
  195. package/src/interpretation/support.ts +19 -6
  196. package/src/interpretation/tool-selection.ts +67 -122
  197. package/src/interpretation/tool-vocabulary.ts +48 -53
  198. package/src/interpretation/turn-options.ts +123 -220
  199. package/src/interpretation/vocabulary.ts +15 -10
  200. package/src/knowledge/claude-code.ts +3 -3
  201. package/src/knowledge/codex.ts +31 -7
  202. package/src/knowledge/descriptor.ts +105 -49
  203. package/src/knowledge/matchers.ts +35 -0
  204. package/src/knowledge/muse.ts +7 -5
  205. package/src/knowledge/overrides.ts +17 -32
  206. package/src/knowledge/pi.ts +8 -6
  207. package/src/knowledge/profile.ts +1 -0
  208. package/dist/interpretation/dimensions.d.ts +0 -11
  209. package/dist/interpretation/dimensions.d.ts.map +0 -1
  210. package/dist/interpretation/dimensions.js +0 -4
  211. package/dist/interpretation/dimensions.js.map +0 -1
  212. package/src/interpretation/dimensions.ts +0 -14
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
 
@@ -68,6 +69,7 @@ const KNOWN_KEYS = new Set([
68
69
  "model",
69
70
  "provider",
70
71
  "sandbox",
72
+ "contextWindow",
71
73
  "tools",
72
74
  "excludeTools",
73
75
  "autonomy",
@@ -88,11 +90,10 @@ const KNOWN_KEYS = new Set([
88
90
 
89
91
  const LIST_KEYS = new Set(["tools", "excludeTools"]);
90
92
  const BOOL_KEYS = new Set(["autonomy", "write", "shell"]);
91
- const QUESTIONS_VALUES = new Set(["ask", "assume", "none"]);
92
93
 
93
94
  /** Parse + validate config text. Throws ConfigError with the offending key
94
95
  * named on any violation - never warns and continues. */
95
- export const parseUserConfig = (text: string): Partial<TurnOptions> => {
96
+ export const parseUserConfig = (text: string): ConfigTier => {
96
97
  let raw: unknown;
97
98
  try {
98
99
  raw = JSON.parse(text);
@@ -126,7 +127,7 @@ export const parseUserConfig = (text: string): Partial<TurnOptions> => {
126
127
  );
127
128
  }
128
129
  const toolMapObj = value as Record<string, unknown>;
129
- const SEL = TOOL_SELECTOR;
130
+ const SEL = CLEAN_SELECTOR;
130
131
  const descSet = defaultDescriptors();
131
132
  const harnessNames = HARNESS_NAMES as readonly string[];
132
133
  for (const [harness, inner] of Object.entries(toolMapObj)) {
@@ -228,9 +229,9 @@ export const parseUserConfig = (text: string): Partial<TurnOptions> => {
228
229
  continue;
229
230
  }
230
231
  if (key === "questions") {
231
- if (typeof value !== "string" || !QUESTIONS_VALUES.has(value)) {
232
+ if (typeof value !== "string" || !isQuestionMode(value)) {
232
233
  throw new ConfigError(
233
- `config key ${JSON.stringify(key)} must be one of ${[...QUESTIONS_VALUES].join(", ")}`,
234
+ `config key ${JSON.stringify(key)} must be one of ${QUESTION_MODES.join(", ")}`,
234
235
  );
235
236
  }
236
237
  (out as Record<string, unknown>).questions = value;
@@ -250,24 +251,24 @@ export const parseUserConfig = (text: string): Partial<TurnOptions> => {
250
251
  }
251
252
  (out as Record<string, unknown>)[key] = value;
252
253
  }
253
- return out as Partial<TurnOptions>;
254
+ return out as ConfigTier;
254
255
  };
255
256
 
256
257
  /** Load the user config if the file exists; absent file is an empty config
257
258
  * (no tiers engaged), unreadable or invalid file is a hard error. */
258
- export const loadUserConfig = (): { config: Partial<TurnOptions>; path: string } | null =>
259
+ export const loadUserConfig = (): { config: ConfigTier; path: string } | null =>
259
260
  loadConfigAt(userConfigPath());
260
261
 
261
262
  /** Load the project config (git-root auto-discovery, ratified A). */
262
263
  export const loadProjectConfig = (
263
264
  startDir?: string,
264
- ): { config: Partial<TurnOptions>; path: string } | null => {
265
+ ): { config: ConfigTier; path: string } | null => {
265
266
  const path = projectConfigPath(startDir);
266
267
  if (path === null) return null;
267
268
  return loadConfigAt(path);
268
269
  };
269
270
 
270
- const loadConfigAt = (path: string): { config: Partial<TurnOptions>; path: string } | null => {
271
+ const loadConfigAt = (path: string): { config: ConfigTier; path: string } | null => {
271
272
  let text: string;
272
273
  try {
273
274
  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,
@@ -74,7 +76,7 @@ Options:
74
76
  --no-instruction-files Disable instructionFiles discovery facet
75
77
  --no-extensions Disable extensions discovery facet
76
78
  --skills <a,b> Skill allowlist (names resolved against $HCN_SKILLS_ROOT
77
- or ~/.agents/skills; pi loads, claude narrows)
79
+ or ~/.agents/skills; pi loads, claude and codex narrow)
78
80
  --no-skills Disable skills discovery facet
79
81
  --cwd <path> Working directory for spawn
80
82
  --env KEY=VAL Environment (repeatable; KEY= deletes)
@@ -99,7 +101,8 @@ Defaults with no flags:
99
101
  git root) > user config (~/.config/hcn/config.json) > built-in profile
100
102
  > harness default. The profile pins: effort medium, sandbox
101
103
  workspace-write (codex only; other harnesses report divergence),
102
- discovery on, autonomy off, write/shell on. timeout, max-steps and
104
+ context window 272000 (codex only; divergence elsewhere), discovery on,
105
+ autonomy off, write/shell on. timeout, max-steps and
103
106
  access have no default; harness default applies (access write emits
104
107
  nothing on claude/pi/muse, --sandbox workspace-write on codex via
105
108
  profile). toolMap is config-only (no flag) - canonical -> native
@@ -132,6 +135,9 @@ Options:
132
135
  (default: no limit)
133
136
  --provider <value> Provider (pi only)
134
137
  --model <id> Model for the session
138
+ --effort <value> Effort level for the session spawn (validated
139
+ per harness/model; no default - the harness's
140
+ own effort applies without the flag)
135
141
  --resume <uuid> Resume session id (UUID). Continues the
136
142
  conversation where it left off. --session-id
137
143
  is an alias (mutually exclusive with --resume).
@@ -168,6 +174,7 @@ Options:
168
174
  --model <id> Model
169
175
  --effort <value> Effort
170
176
  --sandbox <value> Sandbox
177
+ --context-window <tokens> Context window (codex, integer 1-272000)
171
178
  --provider <value> Provider
172
179
  --tools <a,b> Tools - canonical names (read, write, edit,
173
180
  shell, grep, glob, list, web-fetch,
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) => {
@@ -1,69 +1,63 @@
1
- import { redactArgv } from "../execution/stream-turn.js";
2
- import { buildLaunchArgv } from "../interpretation/argv.js";
3
1
  import { capabilitiesOf } from "../interpretation/capabilities.js";
4
- import { ArgvRefusalError } from "../interpretation/refusal.js";
5
- import { FloorExceededError, resolveEffectiveOptions } from "../interpretation/resolve-options.js";
6
2
  import { canonicalTable, mergeToolMaps } from "../interpretation/tool-vocabulary.js";
7
- import type { HarnessMode } from "../knowledge/descriptor.js";
3
+ import { HARNESS_MODES, type HarnessMode } from "../knowledge/descriptor.js";
8
4
  import { defaultDescriptors } from "../knowledge/overrides.js";
9
- import { parseTurnOptions, resolvePromptAsync } from "./args.js";
5
+ import { splitPassthrough } from "./args.js";
10
6
  import { ConfigError, loadProjectConfig, loadUserConfig } from "./config.js";
11
7
  import { EXIT_REFUSAL } from "./exit-codes.js";
12
- import { refusalOf, refuse } from "./refuse.js";
8
+ import { planTurn, writePlanDiagnostics } from "./plan-turn.js";
9
+ import { refuse } from "./refuse.js";
13
10
  import { resolveHarness } from "./resolve-harness.js";
14
11
 
15
- const HARNESS_MODES = ["headless-turn", "headless-session", "interactive"] as const;
16
-
17
12
  export const inspect = async (harnessName: string, rawArgs: string[]): Promise<void> => {
18
13
  const h = resolveHarness(harnessName);
14
+ const { normalized } = splitPassthrough(rawArgs);
19
15
 
20
- // Check for --help
21
- if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
16
+ if (normalized.includes("--help") || normalized.includes("-h")) {
22
17
  const { INSPECT_HELP } = await import("./help.js");
23
18
  process.stdout.write(INSPECT_HELP);
24
19
  return;
25
20
  }
26
21
 
27
- // Check for positional prompt injection before parse (only when --argv and no explicit prompt)
28
- const { parseCommonFlags, detectPositionalPromptInjection } = await import("./args.js");
29
- if (rawArgs.includes("--argv")) {
30
- const injection = detectPositionalPromptInjection(rawArgs.filter((a) => a !== "--argv"));
31
- if (injection) {
32
- const err = new ArgvRefusalError({
33
- issue: "prompt-flag-injection",
34
- harness: h.name,
35
- supported: ["prompt must not start with '-'"],
36
- detail: `it would be parsed as a flag by ${h.bin}`,
37
- });
38
- process.stderr.write(`${err.message}\n`);
39
- process.stderr.write(`supported: prompt must not start with '-'\n`);
40
- process.exitCode = 2;
22
+ // --argv: the preview is the plan the run command would spawn from, so
23
+ // the two agree by construction (RFC-02 change 10). Refusals go through
24
+ // the shared refuse path like every other command's.
25
+ if (normalized.includes("--argv")) {
26
+ if (normalized.includes("--capabilities")) {
27
+ process.stderr.write(`--capabilities and --argv are mutually exclusive; pick one\n`);
28
+ process.exitCode = EXIT_REFUSAL;
29
+ return;
30
+ }
31
+ const outcome = await planTurn(h, rawArgs, { command: "inspect" });
32
+ if (outcome.kind === "refusal") {
33
+ refuse(outcome.refusal, false);
41
34
  return;
42
35
  }
36
+ writePlanDiagnostics(h, outcome.plan, "argv");
37
+ process.stdout.write(`${JSON.stringify(outcome.plan.redactedArgv)}\n`);
38
+ return;
43
39
  }
44
40
 
45
- // Parse flags for inspect
41
+ // --capabilities path: pure capability record, no spawn, no config, no prompt
42
+ const { parseCommonFlags } = await import("./args.js");
46
43
  let parsed: ReturnType<typeof parseCommonFlags>;
47
44
  try {
48
45
  parsed = parseCommonFlags(rawArgs);
49
46
  } catch (err) {
50
47
  const message = err instanceof Error ? err.message : String(err);
51
- process.stderr.write(`unknown flag: ${message}\n`);
52
- process.stderr.write(`Run 'hcn inspect --help' for usage.\n`);
53
- process.exitCode = 2;
48
+ refuse(
49
+ {
50
+ message: `unknown flag: ${message}`,
51
+ issue: "invalid-option-value",
52
+ trailer: ["Run 'hcn inspect --help' for usage."],
53
+ },
54
+ false,
55
+ );
54
56
  return;
55
57
  }
56
-
57
58
  const values = parsed.values as Record<string, unknown>;
58
- const wantArgv = values.argv === true;
59
59
 
60
- // --capabilities path: pure capability record, no spawn, no config, no prompt
61
60
  if (values.capabilities === true) {
62
- if (wantArgv) {
63
- process.stderr.write(`--capabilities and --argv are mutually exclusive; pick one\n`);
64
- process.exitCode = EXIT_REFUSAL;
65
- return;
66
- }
67
61
  const mode = values.mode === undefined ? "headless-turn" : String(values.mode);
68
62
  if (!(HARNESS_MODES as readonly string[]).includes(mode)) {
69
63
  process.stderr.write(
@@ -78,247 +72,72 @@ export const inspect = async (harnessName: string, rawArgs: string[]): Promise<v
78
72
  return;
79
73
  }
80
74
 
81
- // Load config once at top and reuse for both paths
82
- let rawUserMap: Record<string, Record<string, string>> | undefined;
83
- let rawProjectMap: Record<string, Record<string, string>> | undefined;
84
- let loadedTiers: {
85
- user?: import("../interpretation/argv.js").TurnOptions;
86
- project?: import("../interpretation/argv.js").TurnOptions;
87
- } = {};
75
+ // The descriptor dump, with the tool vocabulary slice the config tiers
76
+ // extend.
77
+ let userMap: ReturnType<typeof loadUserConfig> = null;
78
+ let projectMap: ReturnType<typeof loadProjectConfig> = null;
88
79
  try {
89
- const u = loadUserConfig();
90
- rawUserMap = (u?.config as { toolMap?: Record<string, Record<string, string>> } | undefined)
91
- ?.toolMap;
92
- if (u) loadedTiers = { ...loadedTiers, user: u.config as never };
93
- const p = loadProjectConfig();
94
- rawProjectMap = (p?.config as { toolMap?: Record<string, Record<string, string>> } | undefined)
95
- ?.toolMap;
96
- if (p) loadedTiers = { ...loadedTiers, project: p.config as never };
80
+ userMap = loadUserConfig();
81
+ projectMap = loadProjectConfig();
97
82
  } catch (e) {
98
83
  if (e instanceof ConfigError) {
99
- process.stderr.write(`config error: ${(e as Error).message}\n`);
100
- process.exitCode = 2;
84
+ refuse({ message: `config error: ${e.message}`, issue: "invalid-option-value" }, false);
101
85
  return;
102
86
  }
103
87
  throw e;
104
88
  }
105
- const merged = mergeToolMaps({ user: rawUserMap, project: rawProjectMap });
106
-
107
- if (!wantArgv) {
108
- const table = canonicalTable(defaultDescriptors());
109
- const slice: Record<string, unknown> = {};
110
- const allCanonicalForInspect = [
111
- ...new Set([...Object.keys(table), ...Object.keys(merged[h.name] ?? {})]),
112
- ].sort();
113
- for (const canonical of allCanonicalForInspect) {
114
- const perHarness = table[canonical] as Record<string, unknown> | undefined;
115
- const v = perHarness?.[h.name];
116
- const mapped = merged[h.name]?.[canonical];
117
- if (mapped !== undefined) {
118
- slice[canonical] = { native: mapped.native, source: mapped.tier };
119
- } else if (v !== undefined) {
120
- const vv = v as { kind: string; native?: string; key?: string };
121
- const val =
122
- vv.kind === "builtin"
123
- ? { native: vv.native, source: "descriptor" }
124
- : { ...vv, source: "descriptor" };
125
- slice[canonical] = val;
126
- } else {
127
- slice[canonical] = { native: null, source: "none" };
128
- }
89
+ const merged = mergeToolMaps({
90
+ user: userMap?.config.toolMap,
91
+ project: projectMap?.config.toolMap,
92
+ });
93
+ const table = canonicalTable(defaultDescriptors());
94
+ const slice: Record<string, unknown> = {};
95
+ const allCanonicalForInspect = [
96
+ ...new Set([...Object.keys(table), ...Object.keys(merged[h.name] ?? {})]),
97
+ ].sort();
98
+ for (const canonical of allCanonicalForInspect) {
99
+ const v = table[canonical]?.[h.name];
100
+ const mapped = merged[h.name]?.[canonical];
101
+ if (mapped !== undefined) {
102
+ slice[canonical] = { native: mapped.native, source: mapped.tier };
103
+ } else if (v !== undefined) {
104
+ slice[canonical] =
105
+ v.kind === "builtin"
106
+ ? { native: v.native, source: "descriptor" }
107
+ : { ...v, source: "descriptor" };
108
+ } else {
109
+ slice[canonical] = { native: null, source: "none" };
129
110
  }
130
- const out = {
131
- name: h.name,
132
- bin: h.bin,
133
- verifiedAgainst: h.verifiedAgainst,
134
- versionSource: h.versionSource,
135
- launch: {
136
- baseFlags: h.launch.baseFlags,
137
- subcommands: h.launch.subcommands,
138
- streamFlags: h.launch.streamFlags,
139
- promptStyle: h.launch.promptStyle,
140
- toolsFlag: h.launch.toolsFlag,
141
- idFlag: h.launch.idFlag,
142
- },
143
- resume: h.resume,
144
- sessionMode: h.sessionMode,
145
- vocabulary: {
146
- models: h.vocabulary.models,
147
- aliases: h.vocabulary.aliases,
148
- efforts: h.vocabulary.efforts,
149
- effortsByModel: (h.vocabulary as { effortsByModel?: unknown }).effortsByModel,
150
- extensible: h.vocabulary.extensible,
151
- modelFlag: h.vocabulary.modelFlag,
152
- },
153
- turnOptions: h.turnOptions,
154
- limitMatchers: h.limitMatchers,
155
- authMatchers: h.authMatchers,
156
- toolVocabulary: slice,
157
- };
158
- process.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
159
- return;
160
- }
161
-
162
- // --argv preview path: need prompt + turn options
163
- const positional = parsed.positionals as string[];
164
- // positional[0] would be harness already consumed, but we passed rawArgs without harness - check: caller passes remaining after harness
165
- // So positionals are potential prompt positional if not using --prompt
166
- const positionalPrompt = positional.length > 0 ? positional[0] : undefined;
167
- if (positional.length > 1) {
168
- process.stderr.write(`too many positionals for inspect --argv\n`);
169
- process.exitCode = 2;
170
- return;
171
- }
172
-
173
- let prompt: string;
174
- let promptSource: string;
175
- try {
176
- const resolved = await resolvePromptAsync({
177
- positionalPrompt,
178
- promptFlag: values.prompt as string | undefined,
179
- promptFile: values["prompt-file"] as string | undefined,
180
- });
181
- prompt = resolved.prompt;
182
- promptSource = resolved.source;
183
- } catch (err) {
184
- if (err instanceof ArgvRefusalError) {
185
- process.stderr.write(`${err.message}\n`);
186
- if (err.supported.length) process.stderr.write(`supported: ${err.supported.join(", ")}\n`);
187
- process.exitCode = 2;
188
- return;
189
- }
190
- throw err;
191
111
  }
192
-
193
- // Build TurnOptions from flags
194
- let turnOpts: ReturnType<typeof parseTurnOptions>;
195
- try {
196
- turnOpts = parseTurnOptions(values);
197
- } catch (err) {
198
- if (err instanceof ArgvRefusalError) {
199
- process.stderr.write(`${err.message}\n`);
200
- if (err.supported.length) process.stderr.write(`supported: ${err.supported.join(", ")}\n`);
201
- process.exitCode = 2;
202
- return;
203
- }
204
- throw err;
205
- }
206
-
207
- // Mirror run.ts skills resolution (F-16): resolve names and build claude tokens
208
- const rawSkills = (turnOpts as unknown as { skills?: string[] }).skills;
209
- if (rawSkills !== undefined && rawSkills.length > 0) {
210
- try {
211
- const { resolveSkillNames, listKnownSkills } = await import("./skills-root.js");
212
- const resolvedSkills = resolveSkillNames(rawSkills, h.name);
213
- const skillTokens: string[] = [];
214
- if (h.name === "claude") {
215
- const { claudeSkillOverridesArg } = await import("../interpretation/skills-selection.js");
216
- skillTokens.push(...claudeSkillOverridesArg(listKnownSkills(), resolvedSkills));
217
- } else if (h.name === "codex") {
218
- const { codexSkillConfigArg } = await import("../interpretation/skills-selection.js");
219
- skillTokens.push(...codexSkillConfigArg(listKnownSkills(), resolvedSkills));
220
- }
221
- (turnOpts as unknown as Record<string, unknown>).skills = resolvedSkills;
222
- (turnOpts as unknown as Record<string, unknown>).__skillTokens = skillTokens;
223
- } catch (err) {
224
- if (err instanceof ArgvRefusalError) {
225
- process.stderr.write(`${err.message}\n`);
226
- if (err.supported.length) process.stderr.write(`supported: ${err.supported.join(", ")}\n`);
227
- process.exitCode = 2;
228
- return;
229
- }
230
- throw err;
231
- }
232
- }
233
-
234
- // Resume previews from unresolved options, mirroring run.ts launch-only rule (F-15)
235
- if (values.resume !== undefined || values["session-id"] !== undefined) {
236
- if (values.resume !== undefined && values["session-id"] !== undefined) {
237
- const err = new ArgvRefusalError({
238
- issue: "mutually-exclusive-options",
239
- harness: h.name,
240
- supported: ["--resume or --session-id, not both (--session-id is an alias for --resume)"],
241
- detail: "both --resume and --session-id given",
242
- });
243
- process.stderr.write(`${err.message}\n`);
244
- if (err.supported.length) process.stderr.write(`supported: ${err.supported.join(", ")}\n`);
245
- process.exitCode = 2;
246
- return;
247
- }
248
- const { buildResumeArgv } = await import("../interpretation/argv.js");
249
- const resumeId = String(values.resume ?? values["session-id"]);
250
- try {
251
- const resumeOpts = {
252
- ...(turnOpts as object),
253
- prompt,
254
- sessionId: resumeId,
255
- ...(promptSource !== "positional" ? { __explicitPrompt: true as const } : {}),
256
- } as Parameters<typeof buildResumeArgv>[1];
257
- const resumeArgv = buildResumeArgv(h, resumeOpts);
258
- const redactedResume = redactArgv(resumeArgv, prompt);
259
- process.stdout.write(`${JSON.stringify(redactedResume)}\n`);
260
- process.stderr.write(`argv: ${redactedResume.join(" ")}\n`);
261
- return;
262
- } catch (err) {
263
- if (err instanceof ArgvRefusalError) {
264
- process.stderr.write(`${err.message}\n`);
265
- if (err.supported.length) process.stderr.write(`supported: ${err.supported.join(", ")}\n`);
266
- process.exitCode = 2;
267
- return;
268
- }
269
- throw err;
270
- }
271
- }
272
-
273
- // Reuse already-loaded config for argv preview
274
- const tiers: {
275
- user?: Partial<ReturnType<typeof parseTurnOptions>>;
276
- project?: Partial<ReturnType<typeof parseTurnOptions>>;
277
- } = {};
278
- if (loadedTiers.user) tiers.user = loadedTiers.user as never;
279
- if (loadedTiers.project) tiers.project = loadedTiers.project as never;
280
- let resolved: ReturnType<typeof resolveEffectiveOptions>;
281
- try {
282
- resolved = resolveEffectiveOptions(h, { ...turnOpts, prompt } as never, tiers);
283
- } catch (resErr) {
284
- if (resErr instanceof FloorExceededError) {
285
- process.stderr.write(`${(resErr as Error).message}\n`);
286
- process.exitCode = 2;
287
- return;
288
- }
289
- if (resErr instanceof ArgvRefusalError) {
290
- refuse(refusalOf(resErr as ArgvRefusalError), false);
291
- return;
292
- }
293
- throw resErr;
294
- }
295
- const { writeProvenance } = await import("./provenance.js");
296
- writeProvenance(h.name, resolved.provenance, resolved.unrenderable);
297
- const { prompt: _p, ...effectiveRest } = resolved.options as { prompt: string };
298
- const fullOpts = {
299
- ...(effectiveRest as object),
300
- prompt,
301
- ...(promptSource !== "positional" ? { __explicitPrompt: true as const } : {}),
302
- } as Parameters<typeof buildLaunchArgv>[1];
303
-
304
- let argv: string[];
305
- try {
306
- argv = buildLaunchArgv(h, fullOpts);
307
- const skillTokens = (effectiveRest as unknown as { __skillTokens?: string[] }).__skillTokens;
308
- if (skillTokens !== undefined && skillTokens.length > 0) argv.push(...skillTokens);
309
- } catch (err) {
310
- if (err instanceof ArgvRefusalError) {
311
- refuse(refusalOf(err as ArgvRefusalError), false);
312
- return;
313
- }
314
- throw err;
315
- }
316
-
317
- // Redact prompt for display, but keep structure
318
- const redacted = redactArgv(argv, prompt);
319
-
320
- process.stdout.write(`${JSON.stringify(redacted)}\n`);
321
- process.stderr.write(`argv: ${redacted.join(" ")}\n`);
112
+ const out = {
113
+ name: h.name,
114
+ bin: h.bin,
115
+ verifiedAgainst: h.verifiedAgainst,
116
+ versionSource: h.versionSource,
117
+ launch: {
118
+ baseFlags: h.launch.baseFlags,
119
+ subcommands: h.launch.subcommands,
120
+ streamFlags: h.launch.streamFlags,
121
+ promptStyle: h.launch.promptStyle,
122
+ toolsFlag: h.tools.includeFlag,
123
+ idFlag: h.launch.idFlag,
124
+ },
125
+ resume: h.resume,
126
+ sessionMode: h.sessionMode,
127
+ vocabulary: {
128
+ models: h.vocabulary.models,
129
+ aliases: h.vocabulary.aliases,
130
+ efforts: h.vocabulary.efforts,
131
+ effortsByModel: h.vocabulary.effortsByModel,
132
+ extensible: h.vocabulary.extensible,
133
+ modelFlag: h.vocabulary.modelFlag,
134
+ },
135
+ turnOptions: h.turnOptions,
136
+ limitMatchers: h.limitMatchers,
137
+ authMatchers: h.authMatchers,
138
+ toolVocabulary: slice,
139
+ };
140
+ process.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
322
141
  };
323
142
 
324
143
  export const inspectCommand = inspect;