@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,94 @@
1
+ /**
2
+ * The muse descriptor: facts about the `muse` CLI as data, verified against
3
+ * Muse Code 0.1.0 and lucid v1's registry. Descriptor groundwork only
4
+ * (D-003). The v1 scars this encodes: headless re-entry is `muse exec
5
+ * --session-id <id>` (the positional `muse resume <id>` is the INTERACTIVE
6
+ * picker - recognized when pasted, never built), and `muse exec` exits 0
7
+ * even when the work inside failed (verification always reruns the
8
+ * project's own checks).
9
+ */
10
+ import { deepFreeze, UUID_SHAPE } from "./descriptor.js";
11
+ import { SHARED_AUTH_MATCHERS, SHARED_LIMIT_MATCHERS } from "./matchers.js";
12
+ export const museCode = deepFreeze({
13
+ name: "muse",
14
+ bin: "muse",
15
+ verifiedAgainst: "0.1.0",
16
+ versionSource: { kind: "installed" },
17
+ launch: {
18
+ // exec --json emits the payload_type/stream records the runner decodes
19
+ // (verified 0.1.0); bare exec streams human text.
20
+ baseFlags: ["exec", "--json"],
21
+ subcommands: ["exec"],
22
+ promptStyle: "positional",
23
+ toolsFlag: null,
24
+ streamFlags: [],
25
+ idFlag: "--session-id",
26
+ },
27
+ resume: {
28
+ // Headless re-entry: the same --session-id on exec (v1 registry). The
29
+ // interactive `muse resume <id>` spelling is parse-only.
30
+ style: "flag",
31
+ flag: "--session-id",
32
+ aliases: [],
33
+ idShape: UUID_SHAPE,
34
+ onMissing: "create",
35
+ // Structured output on resume too (see launch baseFlags).
36
+ extraFlags: ["--json"],
37
+ positionalParseWord: "resume",
38
+ },
39
+ sessionMode: null,
40
+ output: {
41
+ // muse exec --json emits payload.kind run_output_delta text chunks
42
+ // (verified 0.1.0), so this invocation is token-granular.
43
+ pins: [{ flags: ["--json"], granularity: "token" }],
44
+ floor: "none",
45
+ flagAliases: {},
46
+ },
47
+ identity: {
48
+ authority: "caller-assigned",
49
+ // Verified on 0.1.0 (--provider echo --json): records carry
50
+ // payload_type discriminators, no top-level type; the session id lives
51
+ // nested at stream.id - so match any record and read the path.
52
+ announce: { match: {}, idField: "stream.id" },
53
+ },
54
+ limitMatchers: [...SHARED_LIMIT_MATCHERS],
55
+ authMatchers: [...SHARED_AUTH_MATCHERS],
56
+ autonomy: { flag: "--yolo" },
57
+ vocabulary: {
58
+ modelFlag: "--model",
59
+ models: ["muse-spark-1.2-contributor", "muse-spark-1.2", "muse-spark-1.1"],
60
+ aliases: {},
61
+ efforts: ["none", "minimal", "low", "medium", "high", "xhigh", "ultra"],
62
+ effortFlag: "--reasoning-effort",
63
+ extensible: false,
64
+ },
65
+ store: {
66
+ // ~/.local/share/muse/sessions/YYYY/MM/DD/{id}/session.jsonl (index at
67
+ // session-index.db) - the template names the sessions root; the
68
+ // execution layer resolves the dated session file.
69
+ template: "{home}/.local/share/muse/sessions",
70
+ cwdSlug: "verbatim",
71
+ },
72
+ contextHook: null,
73
+ // `muse resume --last` exists (muse resume --help).
74
+ resumeLast: { flag: "--last" },
75
+ provider: null,
76
+ stdin: "inherit",
77
+ discoveryDisableFlags: [],
78
+ presence: {
79
+ headlessMarkers: ["exec"],
80
+ },
81
+ capabilities: {
82
+ // images: muse exec exposes --image <PATH>; kept false pending runtime
83
+ // verification (curated claims stay conservative).
84
+ vision: false,
85
+ images: false,
86
+ streamingByMode: {
87
+ "headless-turn": "token",
88
+ "headless-session": "none",
89
+ interactive: "none",
90
+ },
91
+ session: false,
92
+ },
93
+ });
94
+ //# sourceMappingURL=muse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"muse.js","sourceRoot":"","sources":["../../src/knowledge/muse.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,UAAU,EAA0B,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACjF,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAE5E,MAAM,CAAC,MAAM,QAAQ,GAAsB,UAAU,CAAC;IACpD,IAAI,EAAE,MAAM;IACZ,GAAG,EAAE,MAAM;IACX,eAAe,EAAE,OAAO;IACxB,aAAa,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE;IACpC,MAAM,EAAE;QACN,uEAAuE;QACvE,kDAAkD;QAClD,SAAS,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC;QAC7B,WAAW,EAAE,CAAC,MAAM,CAAC;QACrB,WAAW,EAAE,YAAY;QACzB,SAAS,EAAE,IAAI;QACf,WAAW,EAAE,EAAE;QACf,MAAM,EAAE,cAAc;KACvB;IACD,MAAM,EAAE;QACN,sEAAsE;QACtE,yDAAyD;QACzD,KAAK,EAAE,MAAM;QACb,IAAI,EAAE,cAAc;QACpB,OAAO,EAAE,EAAE;QACX,OAAO,EAAE,UAAU;QACnB,SAAS,EAAE,QAAQ;QACnB,0DAA0D;QAC1D,UAAU,EAAE,CAAC,QAAQ,CAAC;QACtB,mBAAmB,EAAE,QAAQ;KAC9B;IACD,WAAW,EAAE,IAAI;IACjB,MAAM,EAAE;QACN,mEAAmE;QACnE,0DAA0D;QAC1D,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;QACnD,KAAK,EAAE,MAAM;QACb,WAAW,EAAE,EAAE;KAChB;IACD,QAAQ,EAAE;QACR,SAAS,EAAE,iBAAiB;QAC5B,4DAA4D;QAC5D,uEAAuE;QACvE,+DAA+D;QAC/D,QAAQ,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE;KAC9C;IACD,aAAa,EAAE,CAAC,GAAG,qBAAqB,CAAC;IACzC,YAAY,EAAE,CAAC,GAAG,oBAAoB,CAAC;IACvC,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IAC5B,UAAU,EAAE;QACV,SAAS,EAAE,SAAS;QACpB,MAAM,EAAE,CAAC,4BAA4B,EAAE,gBAAgB,EAAE,gBAAgB,CAAC;QAC1E,OAAO,EAAE,EAAE;QACX,OAAO,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC;QACvE,UAAU,EAAE,oBAAoB;QAChC,UAAU,EAAE,KAAK;KAClB;IACD,KAAK,EAAE;QACL,uEAAuE;QACvE,gEAAgE;QAChE,mDAAmD;QACnD,QAAQ,EAAE,mCAAmC;QAC7C,OAAO,EAAE,UAAU;KACpB;IACD,WAAW,EAAE,IAAI;IACjB,oDAAoD;IACpD,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IAC9B,QAAQ,EAAE,IAAI;IACd,KAAK,EAAE,SAAS;IAChB,qBAAqB,EAAE,EAAE;IACzB,QAAQ,EAAE;QACR,eAAe,EAAE,CAAC,MAAM,CAAC;KAC1B;IACD,YAAY,EAAE;QACZ,uEAAuE;QACvE,mDAAmD;QACnD,MAAM,EAAE,KAAK;QACb,MAAM,EAAE,KAAK;QACb,eAAe,EAAE;YACf,eAAe,EAAE,OAAO;YACxB,kBAAkB,EAAE,MAAM;YAC1B,WAAW,EAAE,MAAM;SACpB;QACD,OAAO,EAAE,KAAK;KACf;CACF,CAAC,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { type HarnessDescriptor, type HarnessName } from "./descriptor.js";
2
+ export type DescriptorSet = Partial<Record<HarnessName, HarnessDescriptor>>;
3
+ export declare const defaultDescriptors: () => DescriptorSet;
4
+ export declare class OverrideRefusalError extends Error {
5
+ readonly path: string;
6
+ readonly harness?: string | undefined;
7
+ constructor(path: string, detail: string, harness?: string | undefined);
8
+ }
9
+ /** Parse an override document and merge it over the code defaults. */
10
+ export declare const parseOverrides: (jsonText: string, path: string) => DescriptorSet;
11
+ //# sourceMappingURL=overrides.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overrides.d.ts","sourceRoot":"","sources":["../../src/knowledge/overrides.ts"],"names":[],"mappings":"AAgBA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAEjB,MAAM,iBAAiB,CAAC;AAIzB,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC,CAAC;AAE5E,eAAO,MAAM,kBAAkB,QAAO,aAKpC,CAAC;AAEH,qBAAa,oBAAqB,SAAQ,KAAK;IAE3C,QAAQ,CAAC,IAAI,EAAE,MAAM;IAErB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM;IAH3B,YACW,IAAI,EAAE,MAAM,EACrB,MAAM,EAAE,MAAM,EACL,OAAO,CAAC,EAAE,MAAM,YAAA,EAM1B;CACF;AAuGD,sEAAsE;AACtE,eAAO,MAAM,cAAc,aAAc,MAAM,QAAQ,MAAM,KAAG,aAgC/D,CAAC"}
@@ -0,0 +1,166 @@
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 { HARNESS_NAMES, SESSION_INPUT_KINDS, } from "./descriptor.js";
18
+ import { museCode } from "./muse.js";
19
+ import { piCli } from "./pi.js";
20
+ export const defaultDescriptors = () => ({
21
+ claude: claudeCode,
22
+ codex: codexCli,
23
+ pi: piCli,
24
+ muse: museCode,
25
+ });
26
+ export class OverrideRefusalError extends Error {
27
+ path;
28
+ harness;
29
+ constructor(path, detail, harness) {
30
+ super(`invalid harness override file ${path}: ${harness === undefined ? "" : `harness ${JSON.stringify(harness)}: `}${detail}`);
31
+ this.path = path;
32
+ this.harness = harness;
33
+ this.name = "OverrideRefusalError";
34
+ }
35
+ }
36
+ const isPlain = (v) => typeof v === "object" && v !== null && !Array.isArray(v) && !(v instanceof RegExp);
37
+ const FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype"]);
38
+ const hasRegExpDeep = (value) => {
39
+ if (value instanceof RegExp)
40
+ return true;
41
+ if (Array.isArray(value))
42
+ return value.some(hasRegExpDeep);
43
+ if (isPlain(value))
44
+ return Object.values(value).some(hasRegExpDeep);
45
+ return false;
46
+ };
47
+ /** Closed string vocabularies by descriptor path - a typo like "Verbatim"
48
+ * must refuse loudly, not collapse every store path into one directory. */
49
+ const enumAt = (keyPath) => {
50
+ if (keyPath[0] === "capabilities" && keyPath[1] === "streamingByMode") {
51
+ return ["token", "message", "none"];
52
+ }
53
+ switch (keyPath.join(".")) {
54
+ case "stdin":
55
+ return ["inherit", "close-required"];
56
+ case "store.cwdSlug":
57
+ return ["dash-separators", "pi-dash-wrapped", "verbatim"];
58
+ case "resume.style":
59
+ return ["flag", "positional"];
60
+ case "identity.authority":
61
+ return ["caller-assigned", "harness-minted"];
62
+ case "output.floor":
63
+ return ["token", "message", "none"];
64
+ case "launch.promptStyle":
65
+ return ["positional"];
66
+ case "sessionMode.input.kind":
67
+ return SESSION_INPUT_KINDS;
68
+ default:
69
+ return null;
70
+ }
71
+ };
72
+ const mergeValue = (base, override, keyPath, refuse) => {
73
+ const at = keyPath.length === 0 ? "descriptor" : `section ${JSON.stringify(keyPath.join("."))}`;
74
+ if (base instanceof RegExp) {
75
+ refuse(`${at} carries a regular expression and cannot be overridden from JSON`);
76
+ }
77
+ if (base === null) {
78
+ if (override === null)
79
+ return null;
80
+ refuse(`${at} defaults to null - there is no shape to validate an override against`);
81
+ }
82
+ if (isPlain(base)) {
83
+ if (!isPlain(override))
84
+ refuse(`${at} must be an object`);
85
+ const merged = { ...base };
86
+ for (const [key, value] of Object.entries(override)) {
87
+ if (FORBIDDEN_KEYS.has(key))
88
+ refuse(`${at} key ${JSON.stringify(key)} is not allowed`);
89
+ if (keyPath.length === 0 && key === "name") {
90
+ refuse(`the "name" field is the registry key and cannot be overridden`);
91
+ }
92
+ if (!Object.hasOwn(base, key)) {
93
+ refuse(`${at} has no field ${JSON.stringify(key)} to override`);
94
+ }
95
+ merged[key] = mergeValue(base[key], value, [...keyPath, key], refuse);
96
+ }
97
+ return merged;
98
+ }
99
+ if (Array.isArray(base)) {
100
+ if (hasRegExpDeep(base)) {
101
+ refuse(`${at} carries regular expressions and cannot be overridden from JSON`);
102
+ }
103
+ if (!Array.isArray(override))
104
+ refuse(`${at} must be an array`);
105
+ const element = base[0];
106
+ if (element === undefined) {
107
+ // Every empty-default array in the descriptor is a string list.
108
+ if (override.some((v) => typeof v !== "string")) {
109
+ refuse(`${at} must be an array of strings`);
110
+ }
111
+ return override;
112
+ }
113
+ if (isPlain(element)) {
114
+ // Object elements (output.pins) validate recursively against the
115
+ // default element's shape - null or wrong-shaped entries must refuse
116
+ // here, not crash a consumer later.
117
+ return override.map((v, i) => mergeValue(element, v, [...keyPath, String(i)], refuse));
118
+ }
119
+ if (override.some((v) => typeof v !== typeof element || v === null)) {
120
+ refuse(`${at} must be an array of ${typeof element}s`);
121
+ }
122
+ return override;
123
+ }
124
+ if (typeof override !== typeof base) {
125
+ refuse(`${at} must be a ${typeof base}, got ${typeof override}`);
126
+ }
127
+ const allowed = enumAt(keyPath);
128
+ if (allowed !== null && !allowed.includes(override)) {
129
+ refuse(`${at} must be one of ${allowed.join(", ")}, got ${JSON.stringify(override)}`);
130
+ }
131
+ return override;
132
+ };
133
+ /** Parse an override document and merge it over the code defaults. */
134
+ export const parseOverrides = (jsonText, path) => {
135
+ let doc;
136
+ try {
137
+ doc = JSON.parse(jsonText);
138
+ }
139
+ catch (cause) {
140
+ throw new OverrideRefusalError(path, `not valid JSON (${cause.message})`);
141
+ }
142
+ if (!isPlain(doc)) {
143
+ throw new OverrideRefusalError(path, "top level must be an object keyed by harness name");
144
+ }
145
+ const defaults = defaultDescriptors();
146
+ const merged = { ...defaults };
147
+ for (const [name, sections] of Object.entries(doc)) {
148
+ const refuse = (detail) => {
149
+ throw new OverrideRefusalError(path, detail, name);
150
+ };
151
+ if (!HARNESS_NAMES.includes(name)) {
152
+ refuse(`unknown harness; known: ${HARNESS_NAMES.join(", ")}`);
153
+ }
154
+ const base = defaults[name];
155
+ if (base === undefined) {
156
+ refuse("this harness has no code defaults to override yet");
157
+ }
158
+ const next = mergeValue(base, sections, [], refuse);
159
+ if (next.store.template.includes("..")) {
160
+ refuse('store.template must not contain ".."');
161
+ }
162
+ merged[name] = next;
163
+ }
164
+ return merged;
165
+ };
166
+ //# sourceMappingURL=overrides.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overrides.js","sourceRoot":"","sources":["../../src/knowledge/overrides.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
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 { type HarnessDescriptor } from "./descriptor.js";
10
+ export declare const piCli: HarnessDescriptor;
11
+ //# sourceMappingURL=pi.d.ts.map
@@ -0,0 +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,iBAqFlB,CAAC"}
@@ -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, UUID_SHAPE } from "./descriptor.js";
10
+ import { SHARED_AUTH_MATCHERS, SHARED_LIMIT_MATCHERS } from "./matchers.js";
11
+ export const piCli = deepFreeze({
12
+ name: "pi",
13
+ bin: "pi",
14
+ verifiedAgainst: "0.84.1",
15
+ versionSource: { kind: "npm", package: "@earendil-works/pi-coding-agent" },
16
+ launch: {
17
+ // -p --mode json: bare -p prints plain text; --mode json emits the
18
+ // structured v3 records the runner decodes (verified 0.84.1).
19
+ baseFlags: ["-p", "--mode", "json"],
20
+ subcommands: [],
21
+ promptStyle: "positional",
22
+ toolsFlag: null,
23
+ streamFlags: [],
24
+ idFlag: "--session-id",
25
+ },
26
+ resume: {
27
+ // Caller-assigned: the same --session-id re-enters the session. Resume
28
+ // carries the same structured-output flags launch does, or a resumed
29
+ // turn would stream plain text the runner cannot decode.
30
+ style: "flag",
31
+ flag: "--session-id",
32
+ aliases: [],
33
+ idShape: UUID_SHAPE,
34
+ onMissing: "create",
35
+ extraFlags: ["-p", "--mode", "json"],
36
+ },
37
+ // --mode rpc exists on 0.84.1 but its session semantics are unverified
38
+ // against a live run; per the truthfulness rule it stays null until a
39
+ // spike proves the contract (the claude slice is the proven vertical -
40
+ // D-003).
41
+ sessionMode: null,
42
+ output: {
43
+ // pi -p prints plain text; --mode json emits structured v3 records
44
+ // INCLUDING assistantMessageEvent text_delta tokens (verified 0.84.1),
45
+ // so this invocation is token-granular, not merely message.
46
+ pins: [{ flags: ["--mode", "json"], granularity: "token" }],
47
+ floor: "none",
48
+ flagAliases: {},
49
+ },
50
+ identity: {
51
+ authority: "caller-assigned",
52
+ // The v3 session record header: {"type":"session","version":3,"id":...}
53
+ // - the field is `id`, observed in real transcripts under ~/.pi/sessions.
54
+ announce: { match: { type: "session" }, idField: "id" },
55
+ },
56
+ limitMatchers: [...SHARED_LIMIT_MATCHERS],
57
+ authMatchers: [...SHARED_AUTH_MATCHERS],
58
+ autonomy: null,
59
+ vocabulary: {
60
+ modelFlag: "--model",
61
+ models: ["zai/glm-5.2"],
62
+ aliases: {},
63
+ efforts: ["off", "minimal", "low", "medium", "high", "xhigh", "max"],
64
+ effortFlag: "--thinking",
65
+ // D-008: providers register models at runtime (~/.pi/models.json), so
66
+ // the vocabulary is open - validation accepts clean unknown selectors
67
+ // and capability claims degrade to unknown instead.
68
+ extensible: true,
69
+ },
70
+ store: {
71
+ // Verified on pi 0.84.1: ~/.pi/sessions/<slug>/<ISO-stamp>_<uuid>.jsonl
72
+ // where the slug is the cwd dash-flattened and dash-wrapped, dots
73
+ // preserved (--Users-kevin-dev-x--). The stamp needs a store scan, so
74
+ // the template names the per-cwd directory.
75
+ template: "{home}/.pi/sessions/{cwdSlug}",
76
+ cwdSlug: "pi-dash-wrapped",
77
+ },
78
+ contextHook: null,
79
+ resumeLast: null,
80
+ provider: { flag: "--provider" },
81
+ stdin: "close-required",
82
+ discoveryDisableFlags: ["-nt", "-nc", "-ne", "-ns"],
83
+ presence: {
84
+ headlessMarkers: ["-p", "--print"],
85
+ },
86
+ capabilities: {
87
+ vision: false,
88
+ images: false,
89
+ streamingByMode: {
90
+ "headless-turn": "token",
91
+ "headless-session": "none",
92
+ interactive: "none",
93
+ },
94
+ session: false,
95
+ },
96
+ });
97
+ //# sourceMappingURL=pi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pi.js","sourceRoot":"","sources":["../../src/knowledge/pi.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@dungle-scrubs/harness-cli-normalizer",
3
+ "version": "0.1.1",
4
+ "description": "One stable interface to four coding-agent CLIs that survives their updates.",
5
+ "license": "MIT",
6
+ "author": "Kevin Frilot",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dungle-scrubs/harness-cli-normalizer.git"
10
+ },
11
+ "homepage": "https://github.com/dungle-scrubs/harness-cli-normalizer#readme",
12
+ "type": "module",
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
19
+ },
20
+ "./knowledge": {
21
+ "types": "./dist/knowledge/index.d.ts",
22
+ "import": "./dist/knowledge/index.js"
23
+ },
24
+ "./interpretation": {
25
+ "types": "./dist/interpretation/index.d.ts",
26
+ "import": "./dist/interpretation/index.js"
27
+ },
28
+ "./execution": {
29
+ "types": "./dist/execution/index.d.ts",
30
+ "import": "./dist/execution/index.js"
31
+ },
32
+ "./package.json": "./package.json"
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "src"
37
+ ],
38
+ "sideEffects": false,
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "packageManager": "pnpm@11.5.2",
43
+ "engines": {
44
+ "node": ">=24"
45
+ },
46
+ "scripts": {
47
+ "build": "node scripts/build.ts",
48
+ "check": "pnpm lint && pnpm typecheck && pnpm test && pnpm test:bun && pnpm build && pnpm check:package",
49
+ "check:package": "node scripts/check-package.ts",
50
+ "lint": "biome check .",
51
+ "typecheck": "tsc --noEmit",
52
+ "test": "vitest run",
53
+ "test:bun": "bun test",
54
+ "prepare": "git config --local core.hooksPath .git/hooks && lefthook install --force",
55
+ "prepack": "pnpm build",
56
+ "smoke:claude": "bun scripts/smoke-claude.ts",
57
+ "smoke:all": "bun scripts/smoke-all.ts",
58
+ "demo": "bun scripts/demo.ts",
59
+ "smoke:seven": "bun scripts/smoke-seven.ts",
60
+ "check:versions": "bun scripts/check-versions.ts"
61
+ },
62
+ "devDependencies": {
63
+ "@biomejs/biome": "^2.5.7",
64
+ "@types/node": "^26.2.0",
65
+ "lefthook": "^2.1.10",
66
+ "typescript": "^7.0.2",
67
+ "vitest": "^4.1.10"
68
+ }
69
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * A single-consumer async channel with producer backpressure: push from
3
+ * producers (await the returned promise - past the high water mark it
4
+ * blocks until the consumer drains below low water, so OS pipe
5
+ * backpressure can reach a child process), iterate from EXACTLY ONE
6
+ * consumer, close to end iteration. A second concurrent iterator would
7
+ * overwrite the consumer wake and strand the first - it throws instead.
8
+ * push after close is a silent no-op by contract (the closer decided the
9
+ * stream is over; racing producers must not crash).
10
+ */
11
+ export class AsyncChannel<T> implements AsyncIterable<T> {
12
+ private items: T[] = [];
13
+ private head = 0;
14
+ private closed = false;
15
+ private wake: (() => void) | null = null;
16
+ private producerWaiters: Array<() => void> = [];
17
+ private consuming = false;
18
+ private backpressureEnabled = true;
19
+
20
+ constructor(
21
+ private readonly highWater = 1024,
22
+ private readonly lowWater = 256,
23
+ ) {}
24
+
25
+ private get size(): number {
26
+ return this.items.length - this.head;
27
+ }
28
+
29
+ push(item: T): Promise<void> {
30
+ if (this.closed) return Promise.resolve();
31
+ this.items.push(item);
32
+ this.wake?.();
33
+ if (this.backpressureEnabled && this.size > this.highWater) {
34
+ return new Promise((resolve) => this.producerWaiters.push(resolve));
35
+ }
36
+ return Promise.resolve();
37
+ }
38
+
39
+ close(): void {
40
+ this.closed = true;
41
+ this.wake?.();
42
+ this.releaseProducers();
43
+ }
44
+
45
+ /** Stop blocking producers without closing delivery. This is a terminal
46
+ * process state: the child has exited and output reads are being disposed,
47
+ * so only already-buffered pipe data can still be added. */
48
+ releaseBackpressure(): void {
49
+ this.backpressureEnabled = false;
50
+ this.releaseProducers();
51
+ }
52
+
53
+ get isClosed(): boolean {
54
+ return this.closed;
55
+ }
56
+
57
+ private releaseProducers(): void {
58
+ const waiters = this.producerWaiters;
59
+ this.producerWaiters = [];
60
+ for (const release of waiters) release();
61
+ }
62
+
63
+ async *[Symbol.asyncIterator](): AsyncIterator<T> {
64
+ if (this.consuming) {
65
+ throw new Error("AsyncChannel is single-consumer; a second iterator would strand the first");
66
+ }
67
+ this.consuming = true;
68
+ while (true) {
69
+ if (this.size > 0) {
70
+ const item = this.items[this.head++];
71
+ if (this.head === this.items.length) {
72
+ this.items = [];
73
+ this.head = 0;
74
+ }
75
+ if (this.size < this.lowWater) this.releaseProducers();
76
+ if (item !== undefined) yield item;
77
+ continue;
78
+ }
79
+ if (this.closed) return;
80
+ await new Promise<void>((resolve) => {
81
+ this.wake = resolve;
82
+ });
83
+ this.wake = null;
84
+ }
85
+ }
86
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Stream-line decoding: one claude stream-json line in, zero or more
3
+ * HarnessEvents out. Stateful only through the explicit DecodeState the
4
+ * caller threads (identity dedupe per D-022). Unparseable lines on a
5
+ * structured stream are tolerated - scanned for walls, never fatal.
6
+ */
7
+ import { capabilitiesOf } from "../interpretation/capabilities.js";
8
+ import { contentEventsOf } from "../interpretation/content.js";
9
+ import { decodeIdentity } from "../interpretation/identity.js";
10
+ import { detectLimitInLine } from "../interpretation/limits.js";
11
+ import type { HarnessDescriptor } from "../knowledge/descriptor.js";
12
+ import type { HarnessEvent } from "./events.js";
13
+
14
+ export interface DecodeState {
15
+ lastSeenId: string | null;
16
+ limitSeen: boolean;
17
+ /** The id this turn expects (resume paths); rotation is classified
18
+ * against it. Null for fresh launches. */
19
+ requestedId: string | null;
20
+ }
21
+
22
+ export const freshDecodeState = (requestedId: string | null = null): DecodeState => ({
23
+ lastSeenId: null,
24
+ limitSeen: false,
25
+ requestedId,
26
+ });
27
+
28
+ export const decodeLine = (
29
+ h: HarnessDescriptor,
30
+ line: string,
31
+ state: DecodeState,
32
+ model: string,
33
+ ): HarnessEvent[] => {
34
+ let raw: unknown;
35
+ try {
36
+ raw = JSON.parse(line);
37
+ } catch {
38
+ // Not structured output: the only signal a plain line can carry is a wall.
39
+ const code = detectLimitInLine(h, line);
40
+ if (code !== null) {
41
+ state.limitSeen = true;
42
+ return [{ kind: "limit", code, message: `limit wall detected (${code})` }];
43
+ }
44
+ return [];
45
+ }
46
+ return decodeParsed(h, raw, state, model);
47
+ };
48
+
49
+ /** The parsed-record half of decodeLine, for pumps that already parsed the
50
+ * line once (a session pump inspects `type` before routing) - the hottest
51
+ * path must not JSON.parse every token delta twice. */
52
+ export const decodeParsed = (
53
+ h: HarnessDescriptor,
54
+ raw: unknown,
55
+ state: DecodeState,
56
+ model: string,
57
+ ): HarnessEvent[] => {
58
+ const events: HarnessEvent[] = [];
59
+ const decoded = decodeIdentity(h, raw, state.lastSeenId, state.requestedId);
60
+ if (decoded.sessionId !== null) state.lastSeenId = decoded.sessionId;
61
+ if (decoded.identity !== null) {
62
+ events.push({
63
+ kind: "identity",
64
+ sessionId: decoded.identity,
65
+ authority: h.identity.authority,
66
+ capabilities: capabilitiesOf(h, model, "headless-turn"),
67
+ });
68
+ } else if (decoded.outcome === "malformed" || decoded.outcome === "rotated") {
69
+ // The interpretation layer classified an identity anomaly; swallowing
70
+ // it would leave the runner waiting for an identity that already
71
+ // failed to arrive (or bind a rotated one).
72
+ events.push({ kind: "error", message: `identity ${decoded.outcome}` });
73
+ }
74
+
75
+ // Content (message/token/tool/error) is per-harness; identity above is
76
+ // descriptor-driven. contentEventsOf dispatches by harness name.
77
+ for (const content of contentEventsOf(h.name, raw)) events.push(content);
78
+ return events;
79
+ };