@dungle-scrubs/harness-cli-normalizer 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -86
- package/dist/cli/args.d.ts +12 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +44 -5
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config.d.ts +25 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +147 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/help.d.ts +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +17 -1
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/inspect.d.ts.map +1 -1
- package/dist/cli/inspect.js +35 -1
- package/dist/cli/inspect.js.map +1 -1
- package/dist/cli/render.d.ts.map +1 -1
- package/dist/cli/render.js +10 -1
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +175 -5
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/skills-root.d.ts +4 -0
- package/dist/cli/skills-root.d.ts.map +1 -0
- package/dist/cli/skills-root.js +45 -0
- package/dist/cli/skills-root.js.map +1 -0
- package/dist/execution/failure.d.ts +24 -4
- package/dist/execution/failure.d.ts.map +1 -1
- package/dist/execution/failure.js +39 -3
- package/dist/execution/failure.js.map +1 -1
- package/dist/execution/stream-turn.d.ts +4 -0
- package/dist/execution/stream-turn.d.ts.map +1 -1
- package/dist/execution/stream-turn.js +43 -6
- package/dist/execution/stream-turn.js.map +1 -1
- package/dist/interpretation/argv.d.ts +6 -0
- package/dist/interpretation/argv.d.ts.map +1 -1
- package/dist/interpretation/argv.js +17 -19
- package/dist/interpretation/argv.js.map +1 -1
- package/dist/interpretation/hints.d.ts +22 -0
- package/dist/interpretation/hints.d.ts.map +1 -0
- package/dist/interpretation/hints.js +59 -0
- package/dist/interpretation/hints.js.map +1 -0
- package/dist/interpretation/refusal.d.ts +25 -4
- package/dist/interpretation/refusal.d.ts.map +1 -1
- package/dist/interpretation/refusal.js +16 -0
- package/dist/interpretation/refusal.js.map +1 -1
- package/dist/interpretation/resolve-options.d.ts +42 -0
- package/dist/interpretation/resolve-options.d.ts.map +1 -0
- package/dist/interpretation/resolve-options.js +162 -0
- package/dist/interpretation/resolve-options.js.map +1 -0
- package/dist/interpretation/skills-selection.d.ts +22 -0
- package/dist/interpretation/skills-selection.d.ts.map +1 -0
- package/dist/interpretation/skills-selection.js +49 -0
- package/dist/interpretation/skills-selection.js.map +1 -0
- package/dist/interpretation/support.d.ts +32 -0
- package/dist/interpretation/support.d.ts.map +1 -0
- package/dist/interpretation/support.js +107 -0
- package/dist/interpretation/support.js.map +1 -0
- package/dist/interpretation/tool-selection.d.ts +29 -0
- package/dist/interpretation/tool-selection.d.ts.map +1 -0
- package/dist/interpretation/tool-selection.js +122 -0
- package/dist/interpretation/tool-selection.js.map +1 -0
- package/dist/interpretation/turn-options.d.ts.map +1 -1
- package/dist/interpretation/turn-options.js +4 -0
- package/dist/interpretation/turn-options.js.map +1 -1
- package/dist/knowledge/claude-code.d.ts.map +1 -1
- package/dist/knowledge/claude-code.js +35 -1
- package/dist/knowledge/claude-code.js.map +1 -1
- package/dist/knowledge/codex.d.ts.map +1 -1
- package/dist/knowledge/codex.js +20 -0
- package/dist/knowledge/codex.js.map +1 -1
- package/dist/knowledge/descriptor.d.ts +39 -0
- package/dist/knowledge/descriptor.d.ts.map +1 -1
- package/dist/knowledge/muse.d.ts.map +1 -1
- package/dist/knowledge/muse.js +20 -0
- package/dist/knowledge/muse.js.map +1 -1
- package/dist/knowledge/pi.d.ts.map +1 -1
- package/dist/knowledge/pi.js +22 -0
- package/dist/knowledge/pi.js.map +1 -1
- package/dist/knowledge/profile.d.ts +16 -0
- package/dist/knowledge/profile.d.ts.map +1 -0
- package/dist/knowledge/profile.js +51 -0
- package/dist/knowledge/profile.js.map +1 -0
- package/package.json +2 -23
- package/src/cli/args.ts +64 -3
- package/src/cli/config.ts +176 -0
- package/src/cli/help.ts +17 -1
- package/src/cli/inspect.ts +34 -1
- package/src/cli/render.ts +10 -1
- package/src/cli/run.ts +203 -6
- package/src/cli/skills-root.ts +47 -0
- package/src/execution/failure.ts +58 -5
- package/src/execution/stream-turn.ts +50 -5
- package/src/interpretation/argv.ts +23 -19
- package/src/interpretation/hints.ts +99 -0
- package/src/interpretation/refusal.ts +33 -3
- package/src/interpretation/resolve-options.ts +214 -0
- package/src/interpretation/skills-selection.ts +73 -0
- package/src/interpretation/support.ts +119 -0
- package/src/interpretation/tool-selection.ts +161 -0
- package/src/interpretation/turn-options.ts +4 -0
- package/src/knowledge/claude-code.ts +35 -1
- package/src/knowledge/codex.ts +20 -0
- package/src/knowledge/descriptor.ts +39 -0
- package/src/knowledge/muse.ts +20 -0
- package/src/knowledge/pi.ts +22 -0
- package/src/knowledge/profile.ts +53 -0
- package/dist/index.d.ts +0 -8
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -8
- package/dist/index.js.map +0 -1
- package/src/index.ts +0 -7
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dungle-scrubs/harness-cli-normalizer",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "One stable
|
|
3
|
+
"version": "0.4.1",
|
|
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,15 @@ 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
|
+
}
|
|
169
|
+
if (values.skills !== undefined) {
|
|
170
|
+
const raw = String(values.skills);
|
|
171
|
+
(opts as Record<string, unknown>).skills =
|
|
172
|
+
raw.length === 0 ? [] : raw.split(",").map((s) => s.trim());
|
|
173
|
+
}
|
|
165
174
|
if (values.autonomy === true) opts.autonomy = true;
|
|
166
175
|
else if (values["no-autonomy"] === true) opts.autonomy = false;
|
|
167
176
|
else if (values.autonomy === false) opts.autonomy = false; // for completeness
|
|
@@ -212,8 +221,31 @@ export const parseTurnOptions = (values: Record<string, unknown>): TurnOptions =
|
|
|
212
221
|
|
|
213
222
|
export const parseRunExtra = (
|
|
214
223
|
values: Record<string, unknown>,
|
|
215
|
-
): {
|
|
216
|
-
|
|
224
|
+
): {
|
|
225
|
+
cwd?: string;
|
|
226
|
+
env?: Record<string, string>;
|
|
227
|
+
resume?: string;
|
|
228
|
+
timeoutSeconds?: number;
|
|
229
|
+
} => {
|
|
230
|
+
const extra: {
|
|
231
|
+
cwd?: string;
|
|
232
|
+
env?: Record<string, string>;
|
|
233
|
+
resume?: string;
|
|
234
|
+
timeoutSeconds?: number;
|
|
235
|
+
} = {};
|
|
236
|
+
if (values.timeout !== undefined) {
|
|
237
|
+
const n = Number(values.timeout);
|
|
238
|
+
if (!Number.isFinite(n) || n < 0 || !Number.isInteger(n)) {
|
|
239
|
+
throw new ArgvRefusalError({
|
|
240
|
+
issue: "invalid-option-value",
|
|
241
|
+
harness: "claude",
|
|
242
|
+
option: "maxSteps",
|
|
243
|
+
supported: ["whole seconds, >= 0 (0 disables)"],
|
|
244
|
+
detail: String(values.timeout),
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
extra.timeoutSeconds = n;
|
|
248
|
+
}
|
|
217
249
|
if (values.cwd !== undefined) extra.cwd = String(values.cwd);
|
|
218
250
|
if (values.resume !== undefined) extra.resume = String(values.resume);
|
|
219
251
|
if (values["session-id"] !== undefined) extra.resume = String(values["session-id"]);
|
|
@@ -238,6 +270,7 @@ const KNOWN_FLAGS = new Set([
|
|
|
238
270
|
"--sandbox",
|
|
239
271
|
"--provider",
|
|
240
272
|
"--tools",
|
|
273
|
+
"--exclude-tools",
|
|
241
274
|
"--autonomy",
|
|
242
275
|
"--no-autonomy",
|
|
243
276
|
"--write",
|
|
@@ -269,11 +302,13 @@ const FLAGS_WITH_VALUE = new Set([
|
|
|
269
302
|
"--sandbox",
|
|
270
303
|
"--provider",
|
|
271
304
|
"--tools",
|
|
305
|
+
"--exclude-tools",
|
|
272
306
|
"--max-steps",
|
|
273
307
|
"--cwd",
|
|
274
308
|
"--env",
|
|
275
309
|
"--resume",
|
|
276
310
|
"--session-id",
|
|
311
|
+
"--timeout",
|
|
277
312
|
]);
|
|
278
313
|
|
|
279
314
|
export const detectPositionalPromptInjection = (argv: string[]): string | null => {
|
|
@@ -339,11 +374,34 @@ const preprocessPromptArgs = (argv: string[]): string[] => {
|
|
|
339
374
|
* Wrapper around node:util parseArgs for the common flag table.
|
|
340
375
|
* Throws with exit code 2 on unknown flag.
|
|
341
376
|
*/
|
|
377
|
+
export interface SplitPassthrough {
|
|
378
|
+
/** Tokens before the separator - hcn's normalized surface. */
|
|
379
|
+
readonly normalized: string[];
|
|
380
|
+
/** Tokens after the separator - verbatim passthrough to the harness,
|
|
381
|
+
* empty when no separator was present. */
|
|
382
|
+
readonly passthrough: readonly string[];
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** D6: split argv at the first bare `--`. Everything after it belongs to
|
|
386
|
+
* the harness, not to hcn - wrong-harness flags there fail in the harness
|
|
387
|
+
* itself and surface as native errors, never as hcn refusals. */
|
|
388
|
+
export const splitPassthrough = (argv: readonly string[]): SplitPassthrough => {
|
|
389
|
+
const idx = argv.indexOf("--");
|
|
390
|
+
if (idx === -1) return { normalized: [...argv], passthrough: [] };
|
|
391
|
+
return {
|
|
392
|
+
normalized: argv.slice(0, idx),
|
|
393
|
+
passthrough: argv.slice(idx + 1),
|
|
394
|
+
};
|
|
395
|
+
};
|
|
396
|
+
|
|
342
397
|
export const parseCommonFlags = (
|
|
343
398
|
argv: string[],
|
|
344
399
|
opts: { strict?: boolean } = {},
|
|
345
400
|
): ReturnType<typeof parseArgs> => {
|
|
346
|
-
|
|
401
|
+
// The separator itself never reaches parseArgs: passthrough tokens may
|
|
402
|
+
// be unknown to hcn by design (that is their purpose).
|
|
403
|
+
const { normalized: withoutPassthrough } = splitPassthrough(argv);
|
|
404
|
+
const normalized = preprocessPromptArgs(withoutPassthrough);
|
|
347
405
|
const config = {
|
|
348
406
|
allowPositionals: true,
|
|
349
407
|
strict: opts.strict ?? true,
|
|
@@ -355,6 +413,8 @@ export const parseCommonFlags = (
|
|
|
355
413
|
sandbox: { type: "string" as const },
|
|
356
414
|
provider: { type: "string" as const },
|
|
357
415
|
tools: { type: "string" as const },
|
|
416
|
+
"exclude-tools": { type: "string" as const },
|
|
417
|
+
skills: { type: "string" as const },
|
|
358
418
|
autonomy: { type: "boolean" as const },
|
|
359
419
|
"no-autonomy": { type: "boolean" as const },
|
|
360
420
|
write: { type: "boolean" as const },
|
|
@@ -370,6 +430,7 @@ export const parseCommonFlags = (
|
|
|
370
430
|
env: { type: "string" as const, multiple: true },
|
|
371
431
|
resume: { type: "string" as const },
|
|
372
432
|
"session-id": { type: "string" as const },
|
|
433
|
+
timeout: { type: "string" as const },
|
|
373
434
|
json: { type: "boolean" as const },
|
|
374
435
|
argv: { type: "boolean" as const },
|
|
375
436
|
help: { type: "boolean" as const },
|
|
@@ -0,0 +1,176 @@
|
|
|
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
|
+
"timeout",
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
const LIST_KEYS = new Set(["tools", "excludeTools"]);
|
|
72
|
+
const BOOL_KEYS = new Set(["autonomy", "write", "shell"]);
|
|
73
|
+
|
|
74
|
+
/** Parse + validate config text. Throws ConfigError with the offending key
|
|
75
|
+
* named on any violation - never warns and continues. */
|
|
76
|
+
export const parseUserConfig = (text: string): Partial<TurnOptions> => {
|
|
77
|
+
let raw: unknown;
|
|
78
|
+
try {
|
|
79
|
+
raw = JSON.parse(text);
|
|
80
|
+
} catch (e) {
|
|
81
|
+
throw new ConfigError(`config is not valid JSON: ${(e as Error).message}`);
|
|
82
|
+
}
|
|
83
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
84
|
+
throw new ConfigError("config root must be a JSON object");
|
|
85
|
+
}
|
|
86
|
+
const obj = raw as Record<string, unknown>;
|
|
87
|
+
|
|
88
|
+
if (obj.version === undefined) {
|
|
89
|
+
throw new ConfigError("config missing required field: version");
|
|
90
|
+
}
|
|
91
|
+
if (obj.version !== SCHEMA_VERSION) {
|
|
92
|
+
throw new ConfigError(
|
|
93
|
+
`config version ${JSON.stringify(obj.version)} not supported (expected ${SCHEMA_VERSION})`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
delete obj.version;
|
|
97
|
+
|
|
98
|
+
const out: MutableTurnOptions = {};
|
|
99
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
100
|
+
if (!KNOWN_KEYS.has(key)) {
|
|
101
|
+
throw new ConfigError(`unknown config key: ${JSON.stringify(key)}`);
|
|
102
|
+
}
|
|
103
|
+
if (key === "toolsets") {
|
|
104
|
+
if (
|
|
105
|
+
typeof value !== "object" ||
|
|
106
|
+
value === null ||
|
|
107
|
+
Array.isArray(value) ||
|
|
108
|
+
Object.entries(value).some(
|
|
109
|
+
([, v]) => !Array.isArray(v) || v.some((t) => typeof t !== "string" || t.trim() === ""),
|
|
110
|
+
)
|
|
111
|
+
) {
|
|
112
|
+
throw new ConfigError(
|
|
113
|
+
'config key "toolsets" must be an object of name -> non-empty string array',
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
(out as Record<string, unknown>)[key] = value;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (key === "timeout") {
|
|
120
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
121
|
+
throw new ConfigError(
|
|
122
|
+
'config key "timeout" must be a whole number of seconds, >= 0 (0 disables)',
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
(out as Record<string, unknown>)[key] = value;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (LIST_KEYS.has(key)) {
|
|
129
|
+
if (!Array.isArray(value) || value.some((v) => typeof v !== "string")) {
|
|
130
|
+
throw new ConfigError(`config key ${JSON.stringify(key)} must be an array of strings`);
|
|
131
|
+
}
|
|
132
|
+
(out as Record<string, unknown>)[key] = value;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (BOOL_KEYS.has(key)) {
|
|
136
|
+
if (typeof value !== "boolean") {
|
|
137
|
+
throw new ConfigError(`config key ${JSON.stringify(key)} must be a boolean`);
|
|
138
|
+
}
|
|
139
|
+
(out as Record<string, unknown>)[key] = value;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (typeof value !== "string" && typeof value !== "number") {
|
|
143
|
+
throw new ConfigError(
|
|
144
|
+
`config key ${JSON.stringify(key)} must be a string or number, got ${typeof value}`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
(out as Record<string, unknown>)[key] = value;
|
|
148
|
+
}
|
|
149
|
+
return out as Partial<TurnOptions>;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/** Load the user config if the file exists; absent file is an empty config
|
|
153
|
+
* (no tiers engaged), unreadable or invalid file is a hard error. */
|
|
154
|
+
export const loadUserConfig = (): { config: Partial<TurnOptions>; path: string } | null =>
|
|
155
|
+
loadConfigAt(userConfigPath());
|
|
156
|
+
|
|
157
|
+
/** Load the project config (git-root auto-discovery, ratified A). */
|
|
158
|
+
export const loadProjectConfig = (
|
|
159
|
+
startDir?: string,
|
|
160
|
+
): { config: Partial<TurnOptions>; path: string } | null => {
|
|
161
|
+
const path = projectConfigPath(startDir);
|
|
162
|
+
if (path === null) return null;
|
|
163
|
+
return loadConfigAt(path);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const loadConfigAt = (path: string): { config: Partial<TurnOptions>; path: string } | null => {
|
|
167
|
+
let text: string;
|
|
168
|
+
try {
|
|
169
|
+
text = readFileSync(path, "utf8");
|
|
170
|
+
} catch (e) {
|
|
171
|
+
const code = (e as NodeJS.ErrnoException).code;
|
|
172
|
+
if (code === "ENOENT") return null;
|
|
173
|
+
throw new ConfigError(`cannot read config at ${path}: ${(e as Error).message}`);
|
|
174
|
+
}
|
|
175
|
+
return { config: parseUserConfig(text), path };
|
|
176
|
+
};
|
package/src/cli/help.ts
CHANGED
|
@@ -32,7 +32,10 @@ Options:
|
|
|
32
32
|
--effort <value> Effort level (validated per harness/model)
|
|
33
33
|
--sandbox <value> Sandbox mode (codex only)
|
|
34
34
|
--provider <value> Provider (pi only)
|
|
35
|
-
--tools <a,b> Tool
|
|
35
|
+
--tools <a,b> Tool grant allowlist (claude, pi; a bare name
|
|
36
|
+
matching a configured toolset expands to it)
|
|
37
|
+
--exclude-tools <a,b> Complement over known tools (claude, pi;
|
|
38
|
+
mutually exclusive with --tools)
|
|
36
39
|
--autonomy Enable autonomy flag (claude/codex/muse)
|
|
37
40
|
--no-autonomy Disable autonomy
|
|
38
41
|
--write Enable write (muse)
|
|
@@ -40,6 +43,8 @@ Options:
|
|
|
40
43
|
--shell Enable shell (muse)
|
|
41
44
|
--no-shell Disable shell
|
|
42
45
|
--max-steps <n> Max steps (muse, 1-10000)
|
|
46
|
+
--timeout <seconds> Wall-clock budget for the run (all harnesses,
|
|
47
|
+
hcn-enforced; 0 disables; no default)
|
|
43
48
|
--no-tools Disable tools discovery facet
|
|
44
49
|
--no-instruction-files Disable instructionFiles discovery facet
|
|
45
50
|
--no-extensions Disable extensions discovery facet
|
|
@@ -47,9 +52,20 @@ Options:
|
|
|
47
52
|
--cwd <path> Working directory for spawn
|
|
48
53
|
--env KEY=VAL Environment (repeatable; KEY= deletes)
|
|
49
54
|
--resume <uuid> Resume session id (UUID)
|
|
55
|
+
-- Passthrough: native harness args verbatim
|
|
56
|
+
(failures surface as labeled native errors)
|
|
50
57
|
--json NDJSON HarnessEvent to stdout
|
|
51
58
|
-h, --help Show help
|
|
52
59
|
-V, --version Show version
|
|
60
|
+
|
|
61
|
+
Defaults with no flags:
|
|
62
|
+
Every launch resolves args > project config (.hcn/config.json at the
|
|
63
|
+
git root) > user config (~/.config/hcn/config.json) > built-in profile
|
|
64
|
+
> harness default. The profile pins: effort medium, sandbox
|
|
65
|
+
workspace-write (codex only; other harnesses report divergence),
|
|
66
|
+
discovery on, autonomy off, write/shell on. timeout and max-steps have
|
|
67
|
+
no default. Provenance prints to stderr on every run; see
|
|
68
|
+
'hcn inspect <harness>' for the resolved argv of a bare run.
|
|
53
69
|
`;
|
|
54
70
|
|
|
55
71
|
export const SESSION_HELP = `hcn session - Interactive session (Claude-only)
|
package/src/cli/inspect.ts
CHANGED
|
@@ -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
|
-
...
|
|
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
|
-
|
|
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": {
|