@dungle-scrubs/harness-cli-normalizer 0.3.1 → 0.4.0

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 (97) hide show
  1. package/README.md +64 -86
  2. package/dist/cli/args.d.ts +11 -0
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +23 -5
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/cli/config.d.ts +25 -0
  7. package/dist/cli/config.d.ts.map +1 -0
  8. package/dist/cli/config.js +139 -0
  9. package/dist/cli/config.js.map +1 -0
  10. package/dist/cli/inspect.d.ts.map +1 -1
  11. package/dist/cli/inspect.js +35 -1
  12. package/dist/cli/inspect.js.map +1 -1
  13. package/dist/cli/render.d.ts.map +1 -1
  14. package/dist/cli/render.js +10 -1
  15. package/dist/cli/render.js.map +1 -1
  16. package/dist/cli/run.d.ts.map +1 -1
  17. package/dist/cli/run.js +134 -4
  18. package/dist/cli/run.js.map +1 -1
  19. package/dist/execution/failure.d.ts +21 -4
  20. package/dist/execution/failure.d.ts.map +1 -1
  21. package/dist/execution/failure.js +26 -3
  22. package/dist/execution/failure.js.map +1 -1
  23. package/dist/execution/stream-turn.d.ts +4 -0
  24. package/dist/execution/stream-turn.d.ts.map +1 -1
  25. package/dist/execution/stream-turn.js +28 -4
  26. package/dist/execution/stream-turn.js.map +1 -1
  27. package/dist/interpretation/argv.d.ts +1 -0
  28. package/dist/interpretation/argv.d.ts.map +1 -1
  29. package/dist/interpretation/argv.js +13 -19
  30. package/dist/interpretation/argv.js.map +1 -1
  31. package/dist/interpretation/hints.d.ts +22 -0
  32. package/dist/interpretation/hints.d.ts.map +1 -0
  33. package/dist/interpretation/hints.js +59 -0
  34. package/dist/interpretation/hints.js.map +1 -0
  35. package/dist/interpretation/refusal.d.ts +25 -4
  36. package/dist/interpretation/refusal.d.ts.map +1 -1
  37. package/dist/interpretation/refusal.js +16 -0
  38. package/dist/interpretation/refusal.js.map +1 -1
  39. package/dist/interpretation/resolve-options.d.ts +42 -0
  40. package/dist/interpretation/resolve-options.d.ts.map +1 -0
  41. package/dist/interpretation/resolve-options.js +140 -0
  42. package/dist/interpretation/resolve-options.js.map +1 -0
  43. package/dist/interpretation/support.d.ts +32 -0
  44. package/dist/interpretation/support.d.ts.map +1 -0
  45. package/dist/interpretation/support.js +107 -0
  46. package/dist/interpretation/support.js.map +1 -0
  47. package/dist/interpretation/tool-selection.d.ts +29 -0
  48. package/dist/interpretation/tool-selection.d.ts.map +1 -0
  49. package/dist/interpretation/tool-selection.js +122 -0
  50. package/dist/interpretation/tool-selection.js.map +1 -0
  51. package/dist/interpretation/turn-options.d.ts.map +1 -1
  52. package/dist/interpretation/turn-options.js +4 -0
  53. package/dist/interpretation/turn-options.js.map +1 -1
  54. package/dist/knowledge/claude-code.d.ts.map +1 -1
  55. package/dist/knowledge/claude-code.js +34 -1
  56. package/dist/knowledge/claude-code.js.map +1 -1
  57. package/dist/knowledge/codex.d.ts.map +1 -1
  58. package/dist/knowledge/codex.js +19 -0
  59. package/dist/knowledge/codex.js.map +1 -1
  60. package/dist/knowledge/descriptor.d.ts +32 -0
  61. package/dist/knowledge/descriptor.d.ts.map +1 -1
  62. package/dist/knowledge/muse.d.ts.map +1 -1
  63. package/dist/knowledge/muse.js +19 -0
  64. package/dist/knowledge/muse.js.map +1 -1
  65. package/dist/knowledge/pi.d.ts.map +1 -1
  66. package/dist/knowledge/pi.js +21 -0
  67. package/dist/knowledge/pi.js.map +1 -1
  68. package/dist/knowledge/profile.d.ts +13 -0
  69. package/dist/knowledge/profile.d.ts.map +1 -0
  70. package/dist/knowledge/profile.js +31 -0
  71. package/dist/knowledge/profile.js.map +1 -0
  72. package/package.json +2 -23
  73. package/src/cli/args.ts +31 -1
  74. package/src/cli/config.ts +166 -0
  75. package/src/cli/inspect.ts +34 -1
  76. package/src/cli/render.ts +10 -1
  77. package/src/cli/run.ts +161 -5
  78. package/src/execution/failure.ts +44 -5
  79. package/src/execution/stream-turn.ts +33 -3
  80. package/src/interpretation/argv.ts +14 -19
  81. package/src/interpretation/hints.ts +99 -0
  82. package/src/interpretation/refusal.ts +32 -3
  83. package/src/interpretation/resolve-options.ts +190 -0
  84. package/src/interpretation/support.ts +119 -0
  85. package/src/interpretation/tool-selection.ts +161 -0
  86. package/src/interpretation/turn-options.ts +4 -0
  87. package/src/knowledge/claude-code.ts +34 -1
  88. package/src/knowledge/codex.ts +19 -0
  89. package/src/knowledge/descriptor.ts +32 -0
  90. package/src/knowledge/muse.ts +19 -0
  91. package/src/knowledge/pi.ts +21 -0
  92. package/src/knowledge/profile.ts +33 -0
  93. package/dist/index.d.ts +0 -8
  94. package/dist/index.d.ts.map +0 -1
  95. package/dist/index.js +0 -8
  96. package/dist/index.js.map +0 -1
  97. package/src/index.ts +0 -7
@@ -109,5 +109,24 @@ export const museCode = deepFreeze({
109
109
  render: { kind: "flag-value", flag: "--max-model-steps" },
110
110
  },
111
111
  },
112
+ // Phase 0 fixtures: muse-category-flags.md. No name lists; category
113
+ // switches are enforcement gates - tools stay listed in the catalog but
114
+ // calls are denied per session policy (unlike claude's set removal).
115
+ // Tool names follow muse.<name> (write_file, edit_file, bash, bash_input,
116
+ // add_memory, edit_memory, web_search). --disable-web-tools has no
117
+ // normalized turnOption yet - candidate for a web toggle or passthrough.
118
+ tools: {
119
+ includeFlag: null,
120
+ excludeFlag: null,
121
+ includeIsStrictAllowlist: false,
122
+ composable: false,
123
+ builtins: [],
124
+ categories: [
125
+ { key: "write", disableFlag: "--disable-write", configKey: null },
126
+ { key: "shell", disableFlag: "--disable-shell", configKey: null },
127
+ { key: "web", disableFlag: "--disable-web-tools", configKey: null },
128
+ ],
129
+ denySemantics: "policy-gate",
130
+ },
112
131
  });
113
132
  //# sourceMappingURL=muse.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"muse.js","sourceRoot":"","sources":["../../src/knowledge/muse.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"muse.js","sourceRoot":"","sources":["../../src/knowledge/muse.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"pi.d.ts","sourceRoot":"","sources":["../../src/knowledge/pi.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAc,KAAK,iBAAiB,EAAc,MAAM,iBAAiB,CAAC;AAGjF,eAAO,MAAM,KAAK,EAAE,iBA6GlB,CAAC"}
1
+ {"version":3,"file":"pi.d.ts","sourceRoot":"","sources":["../../src/knowledge/pi.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAc,KAAK,iBAAiB,EAAc,MAAM,iBAAiB,CAAC;AAGjF,eAAO,MAAM,KAAK,EAAE,iBAkIlB,CAAC"}
@@ -117,5 +117,26 @@ export const piCli = deepFreeze({
117
117
  },
118
118
  },
119
119
  },
120
+ // Phase 0 fixtures: pi-both-tool-flags.md. Both list flags legal at once;
121
+ // exclude subtracts from include. --tools is strict over BUILT-INS but
122
+ // does not strip MCP/extension registrations (additive over them);
123
+ // -nbt (built-ins only off) exists but has no normalized spelling yet.
124
+ tools: {
125
+ includeFlag: "--tools",
126
+ excludeFlag: "--exclude-tools",
127
+ includeIsStrictAllowlist: true,
128
+ composable: true,
129
+ builtins: [
130
+ { name: "read", defaultEnabled: true },
131
+ { name: "bash", defaultEnabled: true },
132
+ { name: "edit", defaultEnabled: true },
133
+ { name: "write", defaultEnabled: true },
134
+ { name: "grep", defaultEnabled: false },
135
+ { name: "find", defaultEnabled: false },
136
+ { name: "ls", defaultEnabled: false },
137
+ ],
138
+ categories: [],
139
+ denySemantics: "remove-from-set",
140
+ },
120
141
  });
121
142
  //# sourceMappingURL=pi.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pi.js","sourceRoot":"","sources":["../../src/knowledge/pi.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pi.js","sourceRoot":"","sources":["../../src/knowledge/pi.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export declare const DEFAULT_TURN_PROFILE: {
2
+ readonly effort: "medium";
3
+ readonly sandbox: "workspace-write";
4
+ readonly discovery: {
5
+ readonly tools: true;
6
+ readonly instructionFiles: true;
7
+ readonly extensions: true;
8
+ readonly skills: true;
9
+ };
10
+ readonly autonomy: false;
11
+ };
12
+ export type ProfileKey = keyof typeof DEFAULT_TURN_PROFILE;
13
+ //# sourceMappingURL=profile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../src/knowledge/profile.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,oBAAoB;qBACvB,QAAQ;sBAKP,iBAAiB;;iBAMb,KAAK;iBAAQ,gBAAgB;iBAAQ,UAAU;iBAAQ,MAAM;;;CAIjE,CAAC;AAEZ,MAAM,MAAM,UAAU,GAAG,MAAM,OAAO,oBAAoB,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The built-in default turn profile: hcn's curated defaults for a bare run,
3
+ * decided in review (2026-08-18) and applied LAUNCH-ONLY, before user config
4
+ * and args. Every entry here is a ratified dimension; unratified dimensions
5
+ * stay absent and defer to the harness.
6
+ *
7
+ * effort: "medium" - the only value present in all four effort ladders
8
+ * (claude/pi/muse/codex-per-model). Live probe on claude 2.1.233 showed the
9
+ * internal default is above medium and nondeterministic (599-1482 thinking
10
+ * tokens on identical tasks vs 436 at medium); pinning makes bare runs
11
+ * cheaper on claude, uniform everywhere, and knowable from the outside.
12
+ */
13
+ import { deepFreeze } from "./descriptor.js";
14
+ export const DEFAULT_TURN_PROFILE = deepFreeze({
15
+ effort: "medium",
16
+ // codex-only (the only harness with a sandbox dimension). Promotes the
17
+ // descriptor's implicit workspace-write default into the visible
18
+ // profile tier. On the other three the dimension is unrenderable and
19
+ // reports as divergence.
20
+ sandbox: "workspace-write",
21
+ // Ratified: discovery fully ON. The harnesses' bare runs already
22
+ // discover instruction files, skills, and extensions; the profile makes
23
+ // that a stated contract. The off-spellings exist per harness
24
+ // (-ns/-nc/-ne on pi, --setting-sources on claude) for callers who want
25
+ // less.
26
+ discovery: { tools: true, instructionFiles: true, extensions: true, skills: true },
27
+ // Ratified: autonomy OFF. No bare run is unattended; --autonomy or a
28
+ // config must say so deliberately.
29
+ autonomy: false,
30
+ });
31
+ //# sourceMappingURL=profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/knowledge/profile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,MAAM,CAAC,MAAM,oBAAoB,GAAG,UAAU,CAAC;IAC7C,MAAM,EAAE,QAAQ;IAChB,uEAAuE;IACvE,iEAAiE;IACjE,qEAAqE;IACrE,yBAAyB;IACzB,OAAO,EAAE,iBAAiB;IAC1B,iEAAiE;IACjE,wEAAwE;IACxE,8DAA8D;IAC9D,wEAAwE;IACxE,QAAQ;IACR,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;IAClF,qEAAqE;IACrE,mCAAmC;IACnC,QAAQ,EAAE,KAAK;CACP,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dungle-scrubs/harness-cli-normalizer",
3
- "version": "0.3.1",
4
- "description": "One stable interface to four coding-agent CLIs.",
3
+ "version": "0.4.0",
4
+ "description": "One stable CLI to four coding-agent harnesses: normalized flags, defaults, and events.",
5
5
  "license": "MIT",
6
6
  "author": "Kevin Frilot",
7
7
  "repository": {
@@ -13,27 +13,6 @@
13
13
  "bin": {
14
14
  "hcn": "./dist/cli.js"
15
15
  },
16
- "main": "./dist/index.js",
17
- "types": "./dist/index.d.ts",
18
- "exports": {
19
- ".": {
20
- "types": "./dist/index.d.ts",
21
- "import": "./dist/index.js"
22
- },
23
- "./knowledge": {
24
- "types": "./dist/knowledge/index.d.ts",
25
- "import": "./dist/knowledge/index.js"
26
- },
27
- "./interpretation": {
28
- "types": "./dist/interpretation/index.d.ts",
29
- "import": "./dist/interpretation/index.js"
30
- },
31
- "./execution": {
32
- "types": "./dist/execution/index.d.ts",
33
- "import": "./dist/execution/index.js"
34
- },
35
- "./package.json": "./package.json"
36
- },
37
16
  "files": [
38
17
  "dist",
39
18
  "src"
package/src/cli/args.ts CHANGED
@@ -162,6 +162,10 @@ export const parseTurnOptions = (values: Record<string, unknown>): TurnOptions =
162
162
  const raw = String(values.tools);
163
163
  opts.tools = raw.length === 0 ? [] : raw.split(",").map((s) => s.trim());
164
164
  }
165
+ if (values["exclude-tools"] !== undefined) {
166
+ const raw = String(values["exclude-tools"]);
167
+ opts.excludeTools = raw.length === 0 ? [] : raw.split(",").map((s) => s.trim());
168
+ }
165
169
  if (values.autonomy === true) opts.autonomy = true;
166
170
  else if (values["no-autonomy"] === true) opts.autonomy = false;
167
171
  else if (values.autonomy === false) opts.autonomy = false; // for completeness
@@ -238,6 +242,7 @@ const KNOWN_FLAGS = new Set([
238
242
  "--sandbox",
239
243
  "--provider",
240
244
  "--tools",
245
+ "--exclude-tools",
241
246
  "--autonomy",
242
247
  "--no-autonomy",
243
248
  "--write",
@@ -269,6 +274,7 @@ const FLAGS_WITH_VALUE = new Set([
269
274
  "--sandbox",
270
275
  "--provider",
271
276
  "--tools",
277
+ "--exclude-tools",
272
278
  "--max-steps",
273
279
  "--cwd",
274
280
  "--env",
@@ -339,11 +345,34 @@ const preprocessPromptArgs = (argv: string[]): string[] => {
339
345
  * Wrapper around node:util parseArgs for the common flag table.
340
346
  * Throws with exit code 2 on unknown flag.
341
347
  */
348
+ export interface SplitPassthrough {
349
+ /** Tokens before the separator - hcn's normalized surface. */
350
+ readonly normalized: string[];
351
+ /** Tokens after the separator - verbatim passthrough to the harness,
352
+ * empty when no separator was present. */
353
+ readonly passthrough: readonly string[];
354
+ }
355
+
356
+ /** D6: split argv at the first bare `--`. Everything after it belongs to
357
+ * the harness, not to hcn - wrong-harness flags there fail in the harness
358
+ * itself and surface as native errors, never as hcn refusals. */
359
+ export const splitPassthrough = (argv: readonly string[]): SplitPassthrough => {
360
+ const idx = argv.indexOf("--");
361
+ if (idx === -1) return { normalized: [...argv], passthrough: [] };
362
+ return {
363
+ normalized: argv.slice(0, idx),
364
+ passthrough: argv.slice(idx + 1),
365
+ };
366
+ };
367
+
342
368
  export const parseCommonFlags = (
343
369
  argv: string[],
344
370
  opts: { strict?: boolean } = {},
345
371
  ): ReturnType<typeof parseArgs> => {
346
- const normalized = preprocessPromptArgs(argv);
372
+ // The separator itself never reaches parseArgs: passthrough tokens may
373
+ // be unknown to hcn by design (that is their purpose).
374
+ const { normalized: withoutPassthrough } = splitPassthrough(argv);
375
+ const normalized = preprocessPromptArgs(withoutPassthrough);
347
376
  const config = {
348
377
  allowPositionals: true,
349
378
  strict: opts.strict ?? true,
@@ -355,6 +384,7 @@ export const parseCommonFlags = (
355
384
  sandbox: { type: "string" as const },
356
385
  provider: { type: "string" as const },
357
386
  tools: { type: "string" as const },
387
+ "exclude-tools": { type: "string" as const },
358
388
  autonomy: { type: "boolean" as const },
359
389
  "no-autonomy": { type: "boolean" as const },
360
390
  write: { type: "boolean" as const },
@@ -0,0 +1,166 @@
1
+ /**
2
+ * User-level config: ~/.config/hcn/config.json (or $HCN_CONFIG_DIR for
3
+ * tests). Hard-fail discipline: unknown keys, malformed JSON, and schema
4
+ * version mismatches exit 2 naming the offender - a config that silently
5
+ * no-ops is a safety hole in a tool whose config can tighten a sandbox.
6
+ *
7
+ * Schema is intentionally minimal and mirrors the normalized option
8
+ * surface; it grows as dimensions ratify. The parser validates SHAPE
9
+ * (key known, value type plausible); value legality (effort in ladder,
10
+ * model in vocabulary) is enforced by the same renderers as args, so
11
+ * config and CLI can never disagree about what is legal.
12
+ */
13
+ import { existsSync, readFileSync } from "node:fs";
14
+ import { homedir } from "node:os";
15
+ import { dirname, join, resolve } from "node:path";
16
+ import type { TurnOptions } from "../interpretation/argv.js";
17
+
18
+ const SCHEMA_VERSION = 1;
19
+
20
+ export class ConfigError extends Error {
21
+ constructor(message: string) {
22
+ super(message);
23
+ this.name = "ConfigError";
24
+ }
25
+ }
26
+
27
+ export const userConfigPath = (): string =>
28
+ join(
29
+ process.env.HCN_CONFIG_DIR ??
30
+ join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "hcn"),
31
+ "config.json",
32
+ );
33
+
34
+ /** Project config: `.hcn/config.json` at the git root (P4 ratified as A -
35
+ * auto-discover, no trust gate; the config is code-reviewed like AGENTS.md
36
+ * and every project-tier provenance line names its path). Null outside a
37
+ * repo or when the repo declares none. */
38
+ export const projectConfigPath = (startDir: string = process.cwd()): string | null => {
39
+ let dir = resolve(startDir);
40
+ for (;;) {
41
+ if (existsSync(join(dir, ".hcn", "config.json"))) return join(dir, ".hcn", "config.json");
42
+ const parent = dirname(dir);
43
+ if (parent === dir) return null;
44
+ // stop at the git root: above it is not this project
45
+ if (existsSync(join(dir, ".git")) && !existsSync(join(parent, ".git"))) {
46
+ return null;
47
+ }
48
+ dir = parent;
49
+ }
50
+ };
51
+
52
+ type MutableTurnOptions = {
53
+ [K in keyof TurnOptions]?: TurnOptions[K];
54
+ };
55
+
56
+ const KNOWN_KEYS = new Set([
57
+ "effort",
58
+ "model",
59
+ "provider",
60
+ "sandbox",
61
+ "tools",
62
+ "excludeTools",
63
+ "autonomy",
64
+ "write",
65
+ "shell",
66
+ "maxSteps",
67
+ "toolsets",
68
+ ]);
69
+
70
+ const LIST_KEYS = new Set(["tools", "excludeTools"]);
71
+ const BOOL_KEYS = new Set(["autonomy", "write", "shell"]);
72
+
73
+ /** Parse + validate config text. Throws ConfigError with the offending key
74
+ * named on any violation - never warns and continues. */
75
+ export const parseUserConfig = (text: string): Partial<TurnOptions> => {
76
+ let raw: unknown;
77
+ try {
78
+ raw = JSON.parse(text);
79
+ } catch (e) {
80
+ throw new ConfigError(`config is not valid JSON: ${(e as Error).message}`);
81
+ }
82
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
83
+ throw new ConfigError("config root must be a JSON object");
84
+ }
85
+ const obj = raw as Record<string, unknown>;
86
+
87
+ if (obj.version === undefined) {
88
+ throw new ConfigError("config missing required field: version");
89
+ }
90
+ if (obj.version !== SCHEMA_VERSION) {
91
+ throw new ConfigError(
92
+ `config version ${JSON.stringify(obj.version)} not supported (expected ${SCHEMA_VERSION})`,
93
+ );
94
+ }
95
+ delete obj.version;
96
+
97
+ const out: MutableTurnOptions = {};
98
+ for (const [key, value] of Object.entries(obj)) {
99
+ if (!KNOWN_KEYS.has(key)) {
100
+ throw new ConfigError(`unknown config key: ${JSON.stringify(key)}`);
101
+ }
102
+ if (key === "toolsets") {
103
+ if (
104
+ typeof value !== "object" ||
105
+ value === null ||
106
+ Array.isArray(value) ||
107
+ Object.entries(value).some(
108
+ ([, v]) => !Array.isArray(v) || v.some((t) => typeof t !== "string" || t.trim() === ""),
109
+ )
110
+ ) {
111
+ throw new ConfigError(
112
+ 'config key "toolsets" must be an object of name -> non-empty string array',
113
+ );
114
+ }
115
+ (out as Record<string, unknown>)[key] = value;
116
+ continue;
117
+ }
118
+ if (LIST_KEYS.has(key)) {
119
+ if (!Array.isArray(value) || value.some((v) => typeof v !== "string")) {
120
+ throw new ConfigError(`config key ${JSON.stringify(key)} must be an array of strings`);
121
+ }
122
+ (out as Record<string, unknown>)[key] = value;
123
+ continue;
124
+ }
125
+ if (BOOL_KEYS.has(key)) {
126
+ if (typeof value !== "boolean") {
127
+ throw new ConfigError(`config key ${JSON.stringify(key)} must be a boolean`);
128
+ }
129
+ (out as Record<string, unknown>)[key] = value;
130
+ continue;
131
+ }
132
+ if (typeof value !== "string" && typeof value !== "number") {
133
+ throw new ConfigError(
134
+ `config key ${JSON.stringify(key)} must be a string or number, got ${typeof value}`,
135
+ );
136
+ }
137
+ (out as Record<string, unknown>)[key] = value;
138
+ }
139
+ return out as Partial<TurnOptions>;
140
+ };
141
+
142
+ /** Load the user config if the file exists; absent file is an empty config
143
+ * (no tiers engaged), unreadable or invalid file is a hard error. */
144
+ export const loadUserConfig = (): { config: Partial<TurnOptions>; path: string } | null =>
145
+ loadConfigAt(userConfigPath());
146
+
147
+ /** Load the project config (git-root auto-discovery, ratified A). */
148
+ export const loadProjectConfig = (
149
+ startDir?: string,
150
+ ): { config: Partial<TurnOptions>; path: string } | null => {
151
+ const path = projectConfigPath(startDir);
152
+ if (path === null) return null;
153
+ return loadConfigAt(path);
154
+ };
155
+
156
+ const loadConfigAt = (path: string): { config: Partial<TurnOptions>; path: string } | null => {
157
+ let text: string;
158
+ try {
159
+ text = readFileSync(path, "utf8");
160
+ } catch (e) {
161
+ const code = (e as NodeJS.ErrnoException).code;
162
+ if (code === "ENOENT") return null;
163
+ throw new ConfigError(`cannot read config at ${path}: ${(e as Error).message}`);
164
+ }
165
+ return { config: parseUserConfig(text), path };
166
+ };
@@ -1,6 +1,7 @@
1
1
  import { redactArgv } from "../execution/stream-turn.js";
2
2
  import { buildLaunchArgv } from "../interpretation/argv.js";
3
3
  import { ArgvRefusalError } from "../interpretation/refusal.js";
4
+ import { FloorExceededError, resolveEffectiveOptions } from "../interpretation/resolve-options.js";
4
5
  import { parseTurnOptions, resolvePromptAsync } from "./args.js";
5
6
  import { resolveHarness } from "./resolve-harness.js";
6
7
 
@@ -123,8 +124,40 @@ export const inspect = async (harnessName: string, rawArgs: string[]): Promise<v
123
124
  throw err;
124
125
  }
125
126
 
127
+ // Inspect resolves exactly as a launch would: profile + user config,
128
+ // launch-only semantics, so --argv previews the truth of a bare run.
129
+ const { loadUserConfig, loadProjectConfig, ConfigError } = await import("./config.js");
130
+ const tiers: {
131
+ user?: Partial<ReturnType<typeof parseTurnOptions>>;
132
+ project?: Partial<ReturnType<typeof parseTurnOptions>>;
133
+ } = {};
134
+ try {
135
+ const loaded = loadUserConfig();
136
+ if (loaded !== null) tiers.user = loaded.config;
137
+ const proj = loadProjectConfig();
138
+ if (proj !== null) tiers.project = proj.config;
139
+ } catch (configErr) {
140
+ if (configErr instanceof ConfigError) {
141
+ process.stderr.write(`config error: ${(configErr as Error).message}\n`);
142
+ process.exitCode = 2;
143
+ return;
144
+ }
145
+ throw configErr;
146
+ }
147
+ let resolved: ReturnType<typeof resolveEffectiveOptions>;
148
+ try {
149
+ resolved = resolveEffectiveOptions(h, { ...turnOpts, prompt } as never, tiers);
150
+ } catch (resErr) {
151
+ if (resErr instanceof FloorExceededError) {
152
+ process.stderr.write(`${(resErr as Error).message}\n`);
153
+ process.exitCode = 2;
154
+ return;
155
+ }
156
+ throw resErr;
157
+ }
158
+ const { prompt: _p, ...effectiveRest } = resolved.options as { prompt: string };
126
159
  const fullOpts = {
127
- ...turnOpts,
160
+ ...(effectiveRest as object),
128
161
  prompt,
129
162
  ...(promptSource !== "positional" ? { __explicitPrompt: true as const } : {}),
130
163
  } as Parameters<typeof buildLaunchArgv>[1];
package/src/cli/render.ts CHANGED
@@ -41,7 +41,16 @@ export const renderEvent = (event: HarnessEvent, state: RenderState): void => {
41
41
  break;
42
42
  case "failure": {
43
43
  const detail = event.message ?? `${event.class}`;
44
- process.stdout.write(red(`\n ✗ failure ${event.class}: ${detail}`));
44
+ // D6: native failures get an unmistakable prefix and the native exit
45
+ // code shown as data, so a human never reads a harness error as an
46
+ // hcn error.
47
+ if (event.class === "native") {
48
+ const nat =
49
+ event.nativeExitCode !== undefined ? ` [native exit ${event.nativeExitCode}]` : "";
50
+ process.stdout.write(red(`\n ✗ NATIVE${nat} ${detail}`));
51
+ } else {
52
+ process.stdout.write(red(`\n ✗ failure ${event.class}: ${detail}`));
53
+ }
45
54
  break;
46
55
  }
47
56
  case "done": {