@dungle-scrubs/harness-cli-normalizer 0.1.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 (178) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +103 -0
  3. package/dist/execution/channel.d.ts +33 -0
  4. package/dist/execution/channel.d.ts.map +1 -0
  5. package/dist/execution/channel.js +86 -0
  6. package/dist/execution/channel.js.map +1 -0
  7. package/dist/execution/decode.d.ts +16 -0
  8. package/dist/execution/decode.d.ts.map +1 -0
  9. package/dist/execution/decode.js +60 -0
  10. package/dist/execution/decode.js.map +1 -0
  11. package/dist/execution/deps.d.ts +53 -0
  12. package/dist/execution/deps.d.ts.map +1 -0
  13. package/dist/execution/deps.js +8 -0
  14. package/dist/execution/deps.js.map +1 -0
  15. package/dist/execution/events.d.ts +54 -0
  16. package/dist/execution/events.d.ts.map +1 -0
  17. package/dist/execution/events.js +2 -0
  18. package/dist/execution/events.js.map +1 -0
  19. package/dist/execution/index.d.ts +19 -0
  20. package/dist/execution/index.d.ts.map +1 -0
  21. package/dist/execution/index.js +19 -0
  22. package/dist/execution/index.js.map +1 -0
  23. package/dist/execution/lines.d.ts +21 -0
  24. package/dist/execution/lines.d.ts.map +1 -0
  25. package/dist/execution/lines.js +70 -0
  26. package/dist/execution/lines.js.map +1 -0
  27. package/dist/execution/node-deps.d.ts +13 -0
  28. package/dist/execution/node-deps.d.ts.map +1 -0
  29. package/dist/execution/node-deps.js +164 -0
  30. package/dist/execution/node-deps.js.map +1 -0
  31. package/dist/execution/open-session.d.ts +31 -0
  32. package/dist/execution/open-session.d.ts.map +1 -0
  33. package/dist/execution/open-session.js +360 -0
  34. package/dist/execution/open-session.js.map +1 -0
  35. package/dist/execution/stream-turn.d.ts +43 -0
  36. package/dist/execution/stream-turn.d.ts.map +1 -0
  37. package/dist/execution/stream-turn.js +283 -0
  38. package/dist/execution/stream-turn.js.map +1 -0
  39. package/dist/index.d.ts +8 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +8 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/interpretation/argv.d.ts +35 -0
  44. package/dist/interpretation/argv.d.ts.map +1 -0
  45. package/dist/interpretation/argv.js +147 -0
  46. package/dist/interpretation/argv.js.map +1 -0
  47. package/dist/interpretation/capabilities.d.ts +19 -0
  48. package/dist/interpretation/capabilities.d.ts.map +1 -0
  49. package/dist/interpretation/capabilities.js +25 -0
  50. package/dist/interpretation/capabilities.js.map +1 -0
  51. package/dist/interpretation/content.d.ts +33 -0
  52. package/dist/interpretation/content.d.ts.map +1 -0
  53. package/dist/interpretation/content.js +169 -0
  54. package/dist/interpretation/content.js.map +1 -0
  55. package/dist/interpretation/context.d.ts +17 -0
  56. package/dist/interpretation/context.d.ts.map +1 -0
  57. package/dist/interpretation/context.js +16 -0
  58. package/dist/interpretation/context.js.map +1 -0
  59. package/dist/interpretation/dimensions.d.ts +13 -0
  60. package/dist/interpretation/dimensions.d.ts.map +1 -0
  61. package/dist/interpretation/dimensions.js +6 -0
  62. package/dist/interpretation/dimensions.js.map +1 -0
  63. package/dist/interpretation/identity.d.ts +32 -0
  64. package/dist/interpretation/identity.d.ts.map +1 -0
  65. package/dist/interpretation/identity.js +44 -0
  66. package/dist/interpretation/identity.js.map +1 -0
  67. package/dist/interpretation/index.d.ts +23 -0
  68. package/dist/interpretation/index.d.ts.map +1 -0
  69. package/dist/interpretation/index.js +23 -0
  70. package/dist/interpretation/index.js.map +1 -0
  71. package/dist/interpretation/limits.d.ts +20 -0
  72. package/dist/interpretation/limits.d.ts.map +1 -0
  73. package/dist/interpretation/limits.js +35 -0
  74. package/dist/interpretation/limits.js.map +1 -0
  75. package/dist/interpretation/parse-resume.d.ts +28 -0
  76. package/dist/interpretation/parse-resume.d.ts.map +1 -0
  77. package/dist/interpretation/parse-resume.js +91 -0
  78. package/dist/interpretation/parse-resume.js.map +1 -0
  79. package/dist/interpretation/presence.d.ts +16 -0
  80. package/dist/interpretation/presence.d.ts.map +1 -0
  81. package/dist/interpretation/presence.js +32 -0
  82. package/dist/interpretation/presence.js.map +1 -0
  83. package/dist/interpretation/resume-last.d.ts +32 -0
  84. package/dist/interpretation/resume-last.d.ts.map +1 -0
  85. package/dist/interpretation/resume-last.js +70 -0
  86. package/dist/interpretation/resume-last.js.map +1 -0
  87. package/dist/interpretation/session-id.d.ts +22 -0
  88. package/dist/interpretation/session-id.d.ts.map +1 -0
  89. package/dist/interpretation/session-id.js +29 -0
  90. package/dist/interpretation/session-id.js.map +1 -0
  91. package/dist/interpretation/session-input.d.ts +13 -0
  92. package/dist/interpretation/session-input.d.ts.map +1 -0
  93. package/dist/interpretation/session-input.js +32 -0
  94. package/dist/interpretation/session-input.js.map +1 -0
  95. package/dist/interpretation/shape.d.ts +17 -0
  96. package/dist/interpretation/shape.d.ts.map +1 -0
  97. package/dist/interpretation/shape.js +54 -0
  98. package/dist/interpretation/shape.js.map +1 -0
  99. package/dist/interpretation/store.d.ts +16 -0
  100. package/dist/interpretation/store.d.ts.map +1 -0
  101. package/dist/interpretation/store.js +27 -0
  102. package/dist/interpretation/store.js.map +1 -0
  103. package/dist/interpretation/versions.d.ts +16 -0
  104. package/dist/interpretation/versions.d.ts.map +1 -0
  105. package/dist/interpretation/versions.js +44 -0
  106. package/dist/interpretation/versions.js.map +1 -0
  107. package/dist/interpretation/vocabulary.d.ts +29 -0
  108. package/dist/interpretation/vocabulary.d.ts.map +1 -0
  109. package/dist/interpretation/vocabulary.js +55 -0
  110. package/dist/interpretation/vocabulary.js.map +1 -0
  111. package/dist/knowledge/claude-code.d.ts +8 -0
  112. package/dist/knowledge/claude-code.d.ts.map +1 -0
  113. package/dist/knowledge/claude-code.js +132 -0
  114. package/dist/knowledge/claude-code.js.map +1 -0
  115. package/dist/knowledge/codex.d.ts +9 -0
  116. package/dist/knowledge/codex.d.ts.map +1 -0
  117. package/dist/knowledge/codex.js +106 -0
  118. package/dist/knowledge/codex.js.map +1 -0
  119. package/dist/knowledge/descriptor.d.ts +211 -0
  120. package/dist/knowledge/descriptor.d.ts.map +1 -0
  121. package/dist/knowledge/descriptor.js +20 -0
  122. package/dist/knowledge/descriptor.js.map +1 -0
  123. package/dist/knowledge/index.d.ts +19 -0
  124. package/dist/knowledge/index.d.ts.map +1 -0
  125. package/dist/knowledge/index.js +19 -0
  126. package/dist/knowledge/index.js.map +1 -0
  127. package/dist/knowledge/matchers.d.ts +10 -0
  128. package/dist/knowledge/matchers.d.ts.map +1 -0
  129. package/dist/knowledge/matchers.js +11 -0
  130. package/dist/knowledge/matchers.js.map +1 -0
  131. package/dist/knowledge/muse.d.ts +12 -0
  132. package/dist/knowledge/muse.d.ts.map +1 -0
  133. package/dist/knowledge/muse.js +94 -0
  134. package/dist/knowledge/muse.js.map +1 -0
  135. package/dist/knowledge/overrides.d.ts +11 -0
  136. package/dist/knowledge/overrides.d.ts.map +1 -0
  137. package/dist/knowledge/overrides.js +166 -0
  138. package/dist/knowledge/overrides.js.map +1 -0
  139. package/dist/knowledge/pi.d.ts +11 -0
  140. package/dist/knowledge/pi.d.ts.map +1 -0
  141. package/dist/knowledge/pi.js +97 -0
  142. package/dist/knowledge/pi.js.map +1 -0
  143. package/package.json +69 -0
  144. package/src/execution/channel.ts +86 -0
  145. package/src/execution/decode.ts +79 -0
  146. package/src/execution/deps.ts +59 -0
  147. package/src/execution/events.ts +37 -0
  148. package/src/execution/index.ts +18 -0
  149. package/src/execution/lines.ts +71 -0
  150. package/src/execution/node-deps.ts +179 -0
  151. package/src/execution/open-session.ts +392 -0
  152. package/src/execution/stream-turn.ts +310 -0
  153. package/src/index.ts +7 -0
  154. package/src/interpretation/argv.ts +188 -0
  155. package/src/interpretation/capabilities.ts +51 -0
  156. package/src/interpretation/content.ts +191 -0
  157. package/src/interpretation/context.ts +28 -0
  158. package/src/interpretation/dimensions.ts +20 -0
  159. package/src/interpretation/identity.ts +81 -0
  160. package/src/interpretation/index.ts +22 -0
  161. package/src/interpretation/limits.ts +63 -0
  162. package/src/interpretation/parse-resume.ts +117 -0
  163. package/src/interpretation/presence.ts +49 -0
  164. package/src/interpretation/resume-last.ts +93 -0
  165. package/src/interpretation/session-id.ts +35 -0
  166. package/src/interpretation/session-input.ts +45 -0
  167. package/src/interpretation/shape.ts +55 -0
  168. package/src/interpretation/store.ts +43 -0
  169. package/src/interpretation/versions.ts +43 -0
  170. package/src/interpretation/vocabulary.ts +74 -0
  171. package/src/knowledge/claude-code.ts +132 -0
  172. package/src/knowledge/codex.ts +106 -0
  173. package/src/knowledge/descriptor.ts +217 -0
  174. package/src/knowledge/index.ts +23 -0
  175. package/src/knowledge/matchers.ts +19 -0
  176. package/src/knowledge/muse.ts +94 -0
  177. package/src/knowledge/overrides.ts +182 -0
  178. package/src/knowledge/pi.ts +97 -0
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Override loading (D-006): code defaults plus a validated override file;
3
+ * the override wins, and a malformed file - structurally OR semantically -
4
+ * throws with the file path and offending harness in the message. A silent
5
+ * fallback to defaults, or a garbage value flowing onward, would ship an
6
+ * override bug invisibly. Pure over TEXT: the caller reads the file and
7
+ * passes its content; nothing here touches the filesystem.
8
+ *
9
+ * Merge semantics: recursive to full depth for plain-object sections (a
10
+ * partial nested override keeps its sibling keys); arrays replace
11
+ * wholesale; every leaf is type-checked against the code default's shape,
12
+ * closed string vocabularies are enforced, and any path whose default
13
+ * carries a RegExp cannot be overridden from JSON.
14
+ */
15
+ import { claudeCode } from "./claude-code.js";
16
+ import { codexCli } from "./codex.js";
17
+ import {
18
+ HARNESS_NAMES,
19
+ type HarnessDescriptor,
20
+ type HarnessName,
21
+ SESSION_INPUT_KINDS,
22
+ } from "./descriptor.js";
23
+ import { museCode } from "./muse.js";
24
+ import { piCli } from "./pi.js";
25
+
26
+ export type DescriptorSet = Partial<Record<HarnessName, HarnessDescriptor>>;
27
+
28
+ export const defaultDescriptors = (): DescriptorSet => ({
29
+ claude: claudeCode,
30
+ codex: codexCli,
31
+ pi: piCli,
32
+ muse: museCode,
33
+ });
34
+
35
+ export class OverrideRefusalError extends Error {
36
+ constructor(
37
+ readonly path: string,
38
+ detail: string,
39
+ readonly harness?: string,
40
+ ) {
41
+ super(
42
+ `invalid harness override file ${path}: ${harness === undefined ? "" : `harness ${JSON.stringify(harness)}: `}${detail}`,
43
+ );
44
+ this.name = "OverrideRefusalError";
45
+ }
46
+ }
47
+
48
+ const isPlain = (v: unknown): v is Record<string, unknown> =>
49
+ typeof v === "object" && v !== null && !Array.isArray(v) && !(v instanceof RegExp);
50
+
51
+ const FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype"]);
52
+
53
+ const hasRegExpDeep = (value: unknown): boolean => {
54
+ if (value instanceof RegExp) return true;
55
+ if (Array.isArray(value)) return value.some(hasRegExpDeep);
56
+ if (isPlain(value)) return Object.values(value).some(hasRegExpDeep);
57
+ return false;
58
+ };
59
+
60
+ /** Closed string vocabularies by descriptor path - a typo like "Verbatim"
61
+ * must refuse loudly, not collapse every store path into one directory. */
62
+ const enumAt = (keyPath: readonly string[]): readonly string[] | null => {
63
+ if (keyPath[0] === "capabilities" && keyPath[1] === "streamingByMode") {
64
+ return ["token", "message", "none"];
65
+ }
66
+ switch (keyPath.join(".")) {
67
+ case "stdin":
68
+ return ["inherit", "close-required"];
69
+ case "store.cwdSlug":
70
+ return ["dash-separators", "pi-dash-wrapped", "verbatim"];
71
+ case "resume.style":
72
+ return ["flag", "positional"];
73
+ case "identity.authority":
74
+ return ["caller-assigned", "harness-minted"];
75
+ case "output.floor":
76
+ return ["token", "message", "none"];
77
+ case "launch.promptStyle":
78
+ return ["positional"];
79
+ case "sessionMode.input.kind":
80
+ return SESSION_INPUT_KINDS;
81
+ default:
82
+ return null;
83
+ }
84
+ };
85
+
86
+ const mergeValue = (
87
+ base: unknown,
88
+ override: unknown,
89
+ keyPath: readonly string[],
90
+ refuse: (detail: string) => never,
91
+ ): unknown => {
92
+ const at = keyPath.length === 0 ? "descriptor" : `section ${JSON.stringify(keyPath.join("."))}`;
93
+ if (base instanceof RegExp) {
94
+ refuse(`${at} carries a regular expression and cannot be overridden from JSON`);
95
+ }
96
+ if (base === null) {
97
+ if (override === null) return null;
98
+ refuse(`${at} defaults to null - there is no shape to validate an override against`);
99
+ }
100
+ if (isPlain(base)) {
101
+ if (!isPlain(override)) refuse(`${at} must be an object`);
102
+ const merged: Record<string, unknown> = { ...base };
103
+ for (const [key, value] of Object.entries(override)) {
104
+ if (FORBIDDEN_KEYS.has(key)) refuse(`${at} key ${JSON.stringify(key)} is not allowed`);
105
+ if (keyPath.length === 0 && key === "name") {
106
+ refuse(`the "name" field is the registry key and cannot be overridden`);
107
+ }
108
+ if (!Object.hasOwn(base, key)) {
109
+ refuse(`${at} has no field ${JSON.stringify(key)} to override`);
110
+ }
111
+ merged[key] = mergeValue(base[key], value, [...keyPath, key], refuse);
112
+ }
113
+ return merged;
114
+ }
115
+ if (Array.isArray(base)) {
116
+ if (hasRegExpDeep(base)) {
117
+ refuse(`${at} carries regular expressions and cannot be overridden from JSON`);
118
+ }
119
+ if (!Array.isArray(override)) refuse(`${at} must be an array`);
120
+ const element = base[0];
121
+ if (element === undefined) {
122
+ // Every empty-default array in the descriptor is a string list.
123
+ if (override.some((v) => typeof v !== "string")) {
124
+ refuse(`${at} must be an array of strings`);
125
+ }
126
+ return override;
127
+ }
128
+ if (isPlain(element)) {
129
+ // Object elements (output.pins) validate recursively against the
130
+ // default element's shape - null or wrong-shaped entries must refuse
131
+ // here, not crash a consumer later.
132
+ return override.map((v, i) => mergeValue(element, v, [...keyPath, String(i)], refuse));
133
+ }
134
+ if (override.some((v) => typeof v !== typeof element || v === null)) {
135
+ refuse(`${at} must be an array of ${typeof element}s`);
136
+ }
137
+ return override;
138
+ }
139
+ if (typeof override !== typeof base) {
140
+ refuse(`${at} must be a ${typeof base}, got ${typeof override}`);
141
+ }
142
+ const allowed = enumAt(keyPath);
143
+ if (allowed !== null && !allowed.includes(override as string)) {
144
+ refuse(`${at} must be one of ${allowed.join(", ")}, got ${JSON.stringify(override)}`);
145
+ }
146
+ return override;
147
+ };
148
+
149
+ /** Parse an override document and merge it over the code defaults. */
150
+ export const parseOverrides = (jsonText: string, path: string): DescriptorSet => {
151
+ let doc: unknown;
152
+ try {
153
+ doc = JSON.parse(jsonText);
154
+ } catch (cause) {
155
+ throw new OverrideRefusalError(path, `not valid JSON (${(cause as Error).message})`);
156
+ }
157
+ if (!isPlain(doc)) {
158
+ throw new OverrideRefusalError(path, "top level must be an object keyed by harness name");
159
+ }
160
+
161
+ const defaults = defaultDescriptors();
162
+ const merged: DescriptorSet = { ...defaults };
163
+
164
+ for (const [name, sections] of Object.entries(doc)) {
165
+ const refuse = (detail: string): never => {
166
+ throw new OverrideRefusalError(path, detail, name);
167
+ };
168
+ if (!HARNESS_NAMES.includes(name as HarnessName)) {
169
+ refuse(`unknown harness; known: ${HARNESS_NAMES.join(", ")}`);
170
+ }
171
+ const base = defaults[name as HarnessName];
172
+ if (base === undefined) {
173
+ refuse("this harness has no code defaults to override yet");
174
+ }
175
+ const next = mergeValue(base, sections, [], refuse) as HarnessDescriptor;
176
+ if (next.store.template.includes("..")) {
177
+ refuse('store.template must not contain ".."');
178
+ }
179
+ merged[name as HarnessName] = next;
180
+ }
181
+ return merged;
182
+ };
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The pi descriptor: facts about the `pi` CLI as data, verified against
3
+ * pi 0.84.1. Descriptor groundwork only (D-003). The load-bearing scars:
4
+ * pi reads stdin even in -p mode (a backgrounded call without `< /dev/null`
5
+ * hangs forever), it auto-discovers instruction files/skills/extensions
6
+ * unless disabled, and its model registry is runtime-extensible (D-008) -
7
+ * the curated list here is a baseline, never a refusal authority.
8
+ */
9
+ import { deepFreeze, type HarnessDescriptor, UUID_SHAPE } from "./descriptor.js";
10
+ import { SHARED_AUTH_MATCHERS, SHARED_LIMIT_MATCHERS } from "./matchers.js";
11
+
12
+ export const piCli: HarnessDescriptor = deepFreeze({
13
+ name: "pi",
14
+ bin: "pi",
15
+ verifiedAgainst: "0.84.1",
16
+ versionSource: { kind: "npm", package: "@earendil-works/pi-coding-agent" },
17
+ launch: {
18
+ // -p --mode json: bare -p prints plain text; --mode json emits the
19
+ // structured v3 records the runner decodes (verified 0.84.1).
20
+ baseFlags: ["-p", "--mode", "json"],
21
+ subcommands: [],
22
+ promptStyle: "positional",
23
+ toolsFlag: null,
24
+ streamFlags: [],
25
+ idFlag: "--session-id",
26
+ },
27
+ resume: {
28
+ // Caller-assigned: the same --session-id re-enters the session. Resume
29
+ // carries the same structured-output flags launch does, or a resumed
30
+ // turn would stream plain text the runner cannot decode.
31
+ style: "flag",
32
+ flag: "--session-id",
33
+ aliases: [],
34
+ idShape: UUID_SHAPE,
35
+ onMissing: "create",
36
+ extraFlags: ["-p", "--mode", "json"],
37
+ },
38
+ // --mode rpc exists on 0.84.1 but its session semantics are unverified
39
+ // against a live run; per the truthfulness rule it stays null until a
40
+ // spike proves the contract (the claude slice is the proven vertical -
41
+ // D-003).
42
+ sessionMode: null,
43
+ output: {
44
+ // pi -p prints plain text; --mode json emits structured v3 records
45
+ // INCLUDING assistantMessageEvent text_delta tokens (verified 0.84.1),
46
+ // so this invocation is token-granular, not merely message.
47
+ pins: [{ flags: ["--mode", "json"], granularity: "token" }],
48
+ floor: "none",
49
+ flagAliases: {},
50
+ },
51
+ identity: {
52
+ authority: "caller-assigned",
53
+ // The v3 session record header: {"type":"session","version":3,"id":...}
54
+ // - the field is `id`, observed in real transcripts under ~/.pi/sessions.
55
+ announce: { match: { type: "session" }, idField: "id" },
56
+ },
57
+ limitMatchers: [...SHARED_LIMIT_MATCHERS],
58
+ authMatchers: [...SHARED_AUTH_MATCHERS],
59
+ autonomy: null,
60
+ vocabulary: {
61
+ modelFlag: "--model",
62
+ models: ["zai/glm-5.2"],
63
+ aliases: {},
64
+ efforts: ["off", "minimal", "low", "medium", "high", "xhigh", "max"],
65
+ effortFlag: "--thinking",
66
+ // D-008: providers register models at runtime (~/.pi/models.json), so
67
+ // the vocabulary is open - validation accepts clean unknown selectors
68
+ // and capability claims degrade to unknown instead.
69
+ extensible: true,
70
+ },
71
+ store: {
72
+ // Verified on pi 0.84.1: ~/.pi/sessions/<slug>/<ISO-stamp>_<uuid>.jsonl
73
+ // where the slug is the cwd dash-flattened and dash-wrapped, dots
74
+ // preserved (--Users-kevin-dev-x--). The stamp needs a store scan, so
75
+ // the template names the per-cwd directory.
76
+ template: "{home}/.pi/sessions/{cwdSlug}",
77
+ cwdSlug: "pi-dash-wrapped",
78
+ },
79
+ contextHook: null,
80
+ resumeLast: null,
81
+ provider: { flag: "--provider" },
82
+ stdin: "close-required",
83
+ discoveryDisableFlags: ["-nt", "-nc", "-ne", "-ns"],
84
+ presence: {
85
+ headlessMarkers: ["-p", "--print"],
86
+ },
87
+ capabilities: {
88
+ vision: false,
89
+ images: false,
90
+ streamingByMode: {
91
+ "headless-turn": "token",
92
+ "headless-session": "none",
93
+ interactive: "none",
94
+ },
95
+ session: false,
96
+ },
97
+ });