@wrenai/genbi 0.0.4 → 0.0.5

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 (230) hide show
  1. package/README.md +64 -2
  2. package/dist/assets/{echarts-LTFC-ysd.js → echarts-DYM4hjvk.js} +2 -2
  3. package/dist/assets/index-A_Wrq7Ey.css +1 -0
  4. package/dist/assets/{index-Bo--QMUN.js → index-DsH129tV.js} +18 -17
  5. package/dist/index.html +2 -2
  6. package/dist-server/harness/bundle/schema.d.ts +38 -0
  7. package/dist-server/harness/bundle/schema.js +18 -10
  8. package/dist-server/harness/bundle/version.js +1 -1
  9. package/dist-server/harness/cli-args.d.ts +10 -2
  10. package/dist-server/harness/cli-args.js +24 -8
  11. package/dist-server/harness/cli.js +75 -9
  12. package/dist-server/harness/compile/cache.js +4 -4
  13. package/dist-server/harness/compile/compose-profile.d.ts +51 -14
  14. package/dist-server/harness/compile/compose-profile.js +85 -19
  15. package/dist-server/harness/compile/context-loader.d.ts +56 -0
  16. package/dist-server/harness/compile/context-loader.js +237 -0
  17. package/dist-server/harness/compile/errors.d.ts +19 -0
  18. package/dist-server/harness/compile/errors.js +35 -0
  19. package/dist-server/harness/compile/index.d.ts +4 -3
  20. package/dist-server/harness/compile/index.js +4 -3
  21. package/dist-server/harness/compile/pipeline.d.ts +15 -7
  22. package/dist-server/harness/compile/pipeline.js +62 -14
  23. package/dist-server/harness/compile/resolve-binary.d.ts +2 -2
  24. package/dist-server/harness/compile/resolve-binary.js +3 -3
  25. package/dist-server/harness/compile/types.d.ts +29 -0
  26. package/dist-server/harness/compile/warble-identity.d.ts +11 -0
  27. package/dist-server/harness/compile/warble-identity.js +13 -0
  28. package/dist-server/harness/components/ai-step.d.ts +4 -0
  29. package/dist-server/harness/components/ai-step.js +40 -0
  30. package/dist-server/harness/components/broker.d.ts +60 -0
  31. package/dist-server/harness/components/broker.js +117 -0
  32. package/dist-server/harness/components/capability-card.d.ts +98 -0
  33. package/dist-server/harness/components/capability-card.js +169 -0
  34. package/dist-server/harness/components/context.d.ts +2 -0
  35. package/dist-server/harness/components/context.js +30 -0
  36. package/dist-server/harness/components/display.d.ts +6 -0
  37. package/dist-server/harness/components/display.js +29 -0
  38. package/dist-server/harness/components/egress-judge.d.ts +12 -0
  39. package/dist-server/harness/components/egress-judge.js +27 -0
  40. package/dist-server/harness/components/egress.d.ts +142 -0
  41. package/dist-server/harness/components/egress.js +469 -0
  42. package/dist-server/harness/components/in-process.d.ts +7 -0
  43. package/dist-server/harness/components/in-process.js +245 -0
  44. package/dist-server/harness/components/normalize.d.ts +35 -0
  45. package/dist-server/harness/components/normalize.js +245 -0
  46. package/dist-server/harness/components/plan.d.ts +31 -0
  47. package/dist-server/harness/components/plan.js +184 -0
  48. package/dist-server/harness/components/report.d.ts +207 -0
  49. package/dist-server/harness/components/report.js +309 -0
  50. package/dist-server/harness/components/runner.d.ts +193 -0
  51. package/dist-server/harness/components/runner.js +492 -0
  52. package/dist-server/harness/components/semantic-guards.d.ts +20 -0
  53. package/dist-server/harness/components/semantic-guards.js +42 -0
  54. package/dist-server/harness/components/terminal-json.d.ts +18 -0
  55. package/dist-server/harness/components/terminal-json.js +82 -0
  56. package/dist-server/harness/components/usage.d.ts +14 -0
  57. package/dist-server/harness/components/usage.js +19 -0
  58. package/dist-server/harness/components/wren-access.d.ts +29 -0
  59. package/dist-server/harness/components/wren-access.js +233 -0
  60. package/dist-server/harness/components/zone-dry-run.d.ts +43 -0
  61. package/dist-server/harness/components/zone-dry-run.js +107 -0
  62. package/dist-server/harness/components/zone-gate.d.ts +76 -0
  63. package/dist-server/harness/components/zone-gate.js +144 -0
  64. package/dist-server/harness/events/index.d.ts +1 -1
  65. package/dist-server/harness/events/types.d.ts +52 -0
  66. package/dist-server/harness/index.d.ts +9 -2
  67. package/dist-server/harness/index.js +5 -1
  68. package/dist-server/harness/loop/executor.d.ts +1 -0
  69. package/dist-server/harness/loop/executor.js +37 -15
  70. package/dist-server/harness/npm-package-resolve.d.ts +2 -0
  71. package/dist-server/harness/npm-package-resolve.js +16 -4
  72. package/dist-server/harness/providers/adapters/openai-compatible.d.ts +13 -0
  73. package/dist-server/harness/providers/adapters/openai-compatible.js +17 -0
  74. package/dist-server/harness/providers/adapters/openai.d.ts +7 -0
  75. package/dist-server/harness/providers/adapters/openai.js +6 -0
  76. package/dist-server/harness/providers/adapters/pi-ai.d.ts +48 -0
  77. package/dist-server/harness/providers/adapters/pi-ai.js +518 -0
  78. package/dist-server/harness/providers/binding.d.ts +45 -4
  79. package/dist-server/harness/providers/binding.js +44 -8
  80. package/dist-server/harness/providers/index.d.ts +9 -3
  81. package/dist-server/harness/providers/index.js +10 -3
  82. package/dist-server/harness/providers/zone.d.ts +96 -0
  83. package/dist-server/harness/providers/zone.js +112 -0
  84. package/dist-server/harness/render/answering-query.d.ts +29 -0
  85. package/dist-server/harness/render/answering-query.js +62 -0
  86. package/dist-server/harness/render/envelope.d.ts +6 -0
  87. package/dist-server/harness/render/envelope.js +10 -3
  88. package/dist-server/harness/route/adapter-spec.d.ts +8 -9
  89. package/dist-server/harness/route/adapter-spec.js +13 -14
  90. package/dist-server/harness/route/codex-ask-events.js +2 -1
  91. package/dist-server/harness/route/codex-ask.d.ts +4 -0
  92. package/dist-server/harness/route/codex-ask.js +69 -30
  93. package/dist-server/harness/route/codex-host-query.d.ts +52 -0
  94. package/dist-server/harness/route/codex-host-query.js +191 -0
  95. package/dist-server/harness/route/codex-local-manifest.d.ts +9 -1
  96. package/dist-server/harness/route/codex-local-manifest.js +96 -15
  97. package/dist-server/harness/route/codex-wren-proxy.d.ts +33 -0
  98. package/dist-server/harness/route/codex-wren-proxy.js +227 -0
  99. package/dist-server/harness/route/describe.js +45 -6
  100. package/dist-server/harness/route/in-process.d.ts +22 -1
  101. package/dist-server/harness/route/in-process.js +54 -4
  102. package/dist-server/harness/route/route.js +5 -2
  103. package/dist-server/harness/route/types.d.ts +30 -1
  104. package/dist-server/harness/session/run.js +30 -10
  105. package/dist-server/harness/setup/runner.d.ts +2 -0
  106. package/dist-server/harness/setup/runner.js +38 -17
  107. package/dist-server/harness/setup/yaml-scalar.d.ts +23 -0
  108. package/dist-server/harness/setup/yaml-scalar.js +62 -0
  109. package/dist-server/server/adopt.js +1 -17
  110. package/dist-server/server/app.d.ts +1 -0
  111. package/dist-server/server/app.js +163 -14
  112. package/dist-server/server/auth-choice.d.ts +13 -1
  113. package/dist-server/server/auth-choice.js +66 -1
  114. package/dist-server/server/bin.js +177 -25
  115. package/dist-server/server/codex-local-grounding.d.ts +18 -0
  116. package/dist-server/server/codex-local-grounding.js +92 -0
  117. package/dist-server/server/codex-native-session.d.ts +25 -0
  118. package/dist-server/server/codex-native-session.js +1 -0
  119. package/dist-server/server/codex-runtime-composition.d.ts +34 -0
  120. package/dist-server/server/codex-runtime-composition.js +250 -0
  121. package/dist-server/server/codex-structured-ask.d.ts +23 -0
  122. package/dist-server/server/codex-structured-ask.js +102 -0
  123. package/dist-server/server/conn-config.js +4 -16
  124. package/dist-server/server/conversation-socket.d.ts +16 -0
  125. package/dist-server/server/conversation-socket.js +65 -0
  126. package/dist-server/server/db.d.ts +46 -0
  127. package/dist-server/server/db.js +88 -9
  128. package/dist-server/server/env-detect.d.ts +6 -0
  129. package/dist-server/server/env-detect.js +7 -0
  130. package/dist-server/server/fold.d.ts +22 -4
  131. package/dist-server/server/fold.js +37 -5
  132. package/dist-server/server/harness.d.ts +2 -2
  133. package/dist-server/server/harness.js +33 -7
  134. package/dist-server/server/interactive-terminal.d.ts +10 -6
  135. package/dist-server/server/interactive-terminal.js +33 -8
  136. package/dist-server/server/managed-wren-runtime.d.ts +116 -0
  137. package/dist-server/server/managed-wren-runtime.js +670 -0
  138. package/dist-server/server/native-artifacts.d.ts +23 -7
  139. package/dist-server/server/native-artifacts.js +43 -12
  140. package/dist-server/server/native-component-context.d.ts +19 -0
  141. package/dist-server/server/native-component-context.js +76 -0
  142. package/dist-server/server/native-component-preparation.d.ts +20 -0
  143. package/dist-server/server/native-component-preparation.js +129 -0
  144. package/dist-server/server/native-component-runtime.d.ts +36 -0
  145. package/dist-server/server/native-component-runtime.js +92 -0
  146. package/dist-server/server/native-components.d.ts +84 -0
  147. package/dist-server/server/native-components.js +160 -0
  148. package/dist-server/server/native-dispatch-process.d.ts +8 -0
  149. package/dist-server/server/native-dispatch-process.js +65 -0
  150. package/dist-server/server/native-runtime-spec.d.ts +89 -0
  151. package/dist-server/server/native-runtime-spec.js +169 -0
  152. package/dist-server/server/native-sessions.d.ts +172 -19
  153. package/dist-server/server/native-sessions.js +839 -244
  154. package/dist-server/server/native-shutdown.d.ts +6 -0
  155. package/dist-server/server/native-shutdown.js +28 -0
  156. package/dist-server/server/native-wren-home.d.ts +18 -0
  157. package/dist-server/server/native-wren-home.js +262 -0
  158. package/dist-server/server/node-pty-host.d.ts +2 -1
  159. package/dist-server/server/node-pty-host.js +3 -2
  160. package/dist-server/server/profile-registry.d.ts +139 -0
  161. package/dist-server/server/profile-registry.js +620 -0
  162. package/dist-server/server/runtime-binding.d.ts +4 -1
  163. package/dist-server/server/runtime-binding.js +50 -2
  164. package/dist-server/server/runtime-host/claude-component-step.d.ts +25 -0
  165. package/dist-server/server/runtime-host/claude-component-step.js +186 -0
  166. package/dist-server/server/runtime-host/codex-app-server.d.ts +75 -0
  167. package/dist-server/server/runtime-host/codex-app-server.js +239 -0
  168. package/dist-server/server/runtime-host/codex-compatibility.d.ts +110 -0
  169. package/dist-server/server/runtime-host/codex-compatibility.js +162 -0
  170. package/dist-server/server/runtime-host/codex-component-step.d.ts +19 -0
  171. package/dist-server/server/runtime-host/codex-component-step.js +308 -0
  172. package/dist-server/server/runtime-host/codex-conversation.d.ts +109 -0
  173. package/dist-server/server/runtime-host/codex-conversation.js +295 -0
  174. package/dist-server/server/runtime-host/codex-direct-tools.d.ts +23 -0
  175. package/dist-server/server/runtime-host/codex-direct-tools.js +59 -0
  176. package/dist-server/server/runtime-host/codex-events.d.ts +647 -0
  177. package/dist-server/server/runtime-host/codex-events.js +66 -0
  178. package/dist-server/server/runtime-host/codex-policy.d.ts +16 -0
  179. package/dist-server/server/runtime-host/codex-policy.js +151 -0
  180. package/dist-server/server/runtime-host/codex-process.d.ts +8 -0
  181. package/dist-server/server/runtime-host/codex-process.js +96 -0
  182. package/dist-server/server/runtime-host/codex-rpc.d.ts +47 -0
  183. package/dist-server/server/runtime-host/codex-rpc.js +182 -0
  184. package/dist-server/server/runtime-host/codex-session.d.ts +74 -0
  185. package/dist-server/server/runtime-host/codex-session.js +492 -0
  186. package/dist-server/server/runtime-host/codex-terminal-protocol.d.ts +35 -0
  187. package/dist-server/server/runtime-host/codex-terminal-protocol.js +243 -0
  188. package/dist-server/server/runtime-host/codex-terminal.d.ts +16 -0
  189. package/dist-server/server/runtime-host/codex-terminal.js +120 -0
  190. package/dist-server/server/runtime-host/index.d.ts +5 -0
  191. package/dist-server/server/runtime-host/index.js +3 -0
  192. package/dist-server/server/runtime-host/local.d.ts +29 -0
  193. package/dist-server/server/runtime-host/local.js +90 -0
  194. package/dist-server/server/runtime-host/policy.d.ts +8 -0
  195. package/dist-server/server/runtime-host/policy.js +78 -0
  196. package/dist-server/server/runtime-host/types.d.ts +52 -0
  197. package/dist-server/server/runtime-host/types.js +62 -0
  198. package/dist-server/server/structured-query-evidence.d.ts +7 -0
  199. package/dist-server/server/structured-query-evidence.js +47 -0
  200. package/dist-server/server/structured-runtime.d.ts +52 -0
  201. package/dist-server/server/structured-runtime.js +130 -0
  202. package/dist-server/server/structured-scope.d.ts +4 -0
  203. package/dist-server/server/structured-scope.js +12 -0
  204. package/dist-server/server/turn.d.ts +6 -1
  205. package/dist-server/server/turn.js +82 -25
  206. package/dist-server/server/wire-types.d.ts +67 -3
  207. package/dist-server/server/wren-profiles.js +1 -1
  208. package/managed-wren/README.md +60 -0
  209. package/managed-wren/dependency-wheels.json +282 -0
  210. package/managed-wren/manifest.json +373 -0
  211. package/managed-wren/release-inputs.json +45 -0
  212. package/managed-wren/runtime-tree.cjs +115 -0
  213. package/managed-wren/source-inputs.json +40 -0
  214. package/package.json +15 -6
  215. package/profiles/genbi-default/context/binding.yml +17 -7
  216. package/profiles/genbi-default/context/context.json +146 -0
  217. package/profiles/genbi-default/ir.golden.json +39 -43
  218. package/profiles/genbi-default/profile.yml +31 -0
  219. package/profiles/genbi-enrich-context/context/binding.yml +14 -3
  220. package/profiles/genbi-enrich-context/context/context.json +146 -0
  221. package/profiles/genbi-enrich-context/ir.golden.json +4 -5
  222. package/profiles/genbi-monitor/context/binding.yml +13 -5
  223. package/profiles/genbi-monitor/context/context.json +146 -0
  224. package/profiles/genbi-monitor/ir.golden.json +13 -7
  225. package/profiles/genbi-report/context/binding.yml +19 -0
  226. package/profiles/genbi-report/context/context.json +223 -0
  227. package/profiles/genbi-report/ir.golden.json +328 -0
  228. package/profiles/genbi-report/profile.yml +38 -0
  229. package/profiles/genbi-setup/ir.golden.json +3 -1
  230. package/dist/assets/index-Xs4q7pNo.css +0 -1
@@ -1,27 +1,44 @@
1
- import { cp, readFile, writeFile } from "node:fs/promises";
1
+ import { cp, mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { PathTraversalError } from "../exec/index.js";
4
+ import { generatePreparedContext } from "./context-loader.js";
4
5
  import { InvalidProfileShapeError } from "./errors.js";
5
6
  /**
6
- * Copies `profileSource` into `destDir`, then rewrites its context binding's `project:` field to
7
- * point at `userProject` (absolute path) instead of the profile's own fixture binding. Returns the
8
- * composed profile's directory (safe to pass straight to `warble compile`).
7
+ * Copies `profileSource` into `destDir` and rebinds its context to `userProject`, producing a
8
+ * composed profile directory that is safe to pass straight to `warble compile`.
9
+ *
10
+ * The profile's context binding is a **prepared** binding (`kind: prepared`): Warble does not read
11
+ * the user's semantic layer itself, it reads a JSON *document* that WrenAI's own generator
12
+ * (`wren-context-loader`) produces. So rebinding is two writes, not one:
13
+ *
14
+ * 1. Run the generator over `userProject` and write its document over the profile's committed
15
+ * fixture document, at the path the binding's `document:` field already names.
16
+ * 2. Rewrite the binding's `project:` line to `userProject` (absolute).
17
+ *
18
+ * `project:` stays the layer's **identity** and never becomes the document path. That exact
19
+ * substitution shipped once in Warble and put the filename into every compiled prompt, telling the
20
+ * agent it was working on a project called `context.json`; Warble now guards against it, and this
21
+ * side must not hand it a binding that tries.
22
+ *
23
+ * Because the generated document is written *to the path the binding already declares*, `document:`
24
+ * needs no rewriting at all — the composed binding differs from the committed one only in
25
+ * `project:`. Note that `document:` (like `project:`) resolves relative to the **Warble project
26
+ * root** (the composed directory), not relative to the binding file that declares it.
9
27
  *
10
28
  * The binding file to rewrite is *not* hardcoded to `context/binding.yml` — it's read from the
11
29
  * copied `profile.yml`'s own `context: project: <path>` pointer (the documented Warble mechanism:
12
30
  * "profile mounts context via `context: project: <path-to-binding.yml>`"), so this works for any
13
- * profile following that shape, not just `genbi-default`. The resolved binding path is confined to
14
- * the composed directory: a `../`-laden pointer that escapes it is a loud {@link PathTraversalError}
15
- * (reusing the exec module's shared traversal error rather than reading/overwriting an arbitrary
16
- * host file).
31
+ * profile following that shape, not just `genbi-default`. Both the binding path and the resolved
32
+ * document path are confined to the composed directory: a `../`-laden pointer that escapes it is a
33
+ * loud {@link PathTraversalError} (reusing the exec module's shared traversal error rather than
34
+ * reading/overwriting an arbitrary host file).
17
35
  *
18
- * Both `profile.yml` and the binding file are small, flat YAML where only a single scalar matters
19
- * here — regex extraction/rewrite is used deliberately instead of pulling in a full YAML parser
20
- * dependency for that. Both the pointer lookup and the `project:` rewrite assert *exactly one*
21
- * match and loud-fail on zero or multiple (a duplicate top-level `project:` would otherwise leave a
22
- * stale line that last-key-wins YAML parsing could silently bind to the wrong project). If a Warble
23
- * profile ever grows a more elaborate `context:` block (e.g. multiple context sources), this needs
24
- * revisiting.
36
+ * Both `profile.yml` and the binding file are small, flat YAML where only a few scalars matter here
37
+ * — regex extraction/rewrite is used deliberately instead of pulling in a full YAML parser
38
+ * dependency for that. Every lookup asserts *exactly one* match and loud-fails on zero or multiple
39
+ * (a duplicate top-level `project:` would otherwise leave a stale line that last-key-wins YAML
40
+ * parsing could silently bind to the wrong project). If a Warble profile ever grows a more
41
+ * elaborate `context:` block (e.g. multiple context sources), this needs revisiting.
25
42
  */
26
43
  export async function composeUserProfile(options) {
27
44
  const profileSourceAbs = path.resolve(options.profileSource);
@@ -33,21 +50,26 @@ export async function composeUserProfile(options) {
33
50
  const bindingRelPath = extractContextBindingPath(profileYamlText, profileYamlPath);
34
51
  const bindingPath = resolveWithinComposedDir(composedDir, bindingRelPath);
35
52
  const bindingText = await readFile(bindingPath, "utf-8");
53
+ assertPreparedBindingKind(bindingText, bindingPath);
54
+ const documentRelPath = extractBindingDocumentPath(bindingText, bindingPath);
55
+ const documentPath = resolveWithinComposedDir(composedDir, documentRelPath);
56
+ await mkdir(path.dirname(documentPath), { recursive: true });
57
+ await generatePreparedContext(options.contextLoaderBin, userProjectAbs, documentPath);
36
58
  const rewritten = rewriteContextBindingProject(bindingText, userProjectAbs, bindingPath);
37
59
  await writeFile(bindingPath, rewritten, "utf-8");
38
60
  return composedDir;
39
61
  }
40
62
  /**
41
- * Resolves `bindingRelPath` against `composedDir` and rejects anything that escapes it. Mirrors the
63
+ * Resolves `relPath` against `composedDir` and rejects anything that escapes it. Mirrors the
42
64
  * containment check `harness/exec/local.ts`'s (private) `resolveWithinScope` performs, reusing its
43
65
  * exported {@link PathTraversalError} — kept as a local check here so the fix stays within this
44
66
  * module rather than widening the exec module's public surface.
45
67
  */
46
- function resolveWithinComposedDir(composedDir, bindingRelPath) {
47
- const targetAbs = path.resolve(composedDir, bindingRelPath);
68
+ function resolveWithinComposedDir(composedDir, relPath) {
69
+ const targetAbs = path.resolve(composedDir, relPath);
48
70
  const relative = path.relative(composedDir, targetAbs);
49
71
  if (relative.startsWith("..") || path.isAbsolute(relative)) {
50
- throw new PathTraversalError(bindingRelPath, composedDir);
72
+ throw new PathTraversalError(relPath, composedDir);
51
73
  }
52
74
  return targetAbs;
53
75
  }
@@ -71,6 +93,50 @@ export function extractContextBindingPath(profileYamlText, sourcePath) {
71
93
  }
72
94
  return unquoted;
73
95
  }
96
+ /**
97
+ * Asserts the binding declares exactly one top-level `kind: prepared`.
98
+ *
99
+ * A **missing** `kind:` is rejected rather than tolerated. Omitting it selects Warble's serde
100
+ * default (`wren_project`), i.e. Warble's own built-in MDL adapter — so a binding without the line
101
+ * would compile perfectly well while quietly ignoring the document this function's caller just
102
+ * generated. Since Warble emits neither `kind` nor `document` into the IR, that divergence is
103
+ * invisible in the compiled artifact; the only place it can be caught is here.
104
+ */
105
+ export function assertPreparedBindingKind(bindingText, sourcePath) {
106
+ const matches = [...bindingText.matchAll(/^kind:[ \t]*(\S+)[ \t]*$/gm)];
107
+ if (matches.length === 0) {
108
+ throw new InvalidProfileShapeError(`"${sourcePath}" declares no top-level "kind:" — a context binding rebound to a user project must ` +
109
+ `declare "kind: prepared" (omitting it selects Warble's built-in "wren_project" adapter, which ` +
110
+ `would silently ignore the generated prepared-context document)`);
111
+ }
112
+ if (matches.length > 1) {
113
+ throw new InvalidProfileShapeError(`"${sourcePath}" has ${matches.length} top-level "kind:" fields — expected exactly one (ambiguous binding)`);
114
+ }
115
+ const kind = stripSurroundingQuotes(matches[0]?.[1] ?? "");
116
+ if (kind !== "prepared") {
117
+ throw new InvalidProfileShapeError(`"${sourcePath}" declares "kind: ${kind}" — a context binding rebound to a user project must declare ` +
118
+ `"kind: prepared"`);
119
+ }
120
+ }
121
+ /**
122
+ * Extracts the single top-level `document: <path>` field naming where the prepared-context document
123
+ * lives, relative to the Warble project root (NOT to the binding file's own directory).
124
+ */
125
+ export function extractBindingDocumentPath(bindingText, sourcePath) {
126
+ const matches = [...bindingText.matchAll(/^document:[ \t]*(\S+)[ \t]*$/gm)];
127
+ if (matches.length === 0) {
128
+ throw new InvalidProfileShapeError(`"${sourcePath}" has no top-level "document:" field — a "kind: prepared" binding must name the ` +
129
+ `prepared-context document it reads`);
130
+ }
131
+ if (matches.length > 1) {
132
+ throw new InvalidProfileShapeError(`"${sourcePath}" has ${matches.length} top-level "document:" fields — expected exactly one (ambiguous binding)`);
133
+ }
134
+ const unquoted = stripSurroundingQuotes(matches[0]?.[1] ?? "");
135
+ if (unquoted.length === 0) {
136
+ throw new InvalidProfileShapeError(`"${sourcePath}" has an empty "document:" field`);
137
+ }
138
+ return unquoted;
139
+ }
74
140
  /**
75
141
  * Replaces the single top-level `project:` line in a context-binding YAML's text, preserving
76
142
  * surrounding comments. Asserts exactly one top-level `project:` — a second one would survive the
@@ -0,0 +1,56 @@
1
+ export interface ResolvedContextLoader {
2
+ readonly bin: string;
3
+ readonly kind: "explicit" | "in-repo" | "package";
4
+ /** Package resolution is already digest-verified. Development modes use the binary hash below. */
5
+ readonly verifiedIdentity?: string;
6
+ }
7
+ /**
8
+ * Resolves the `wren-context-loader` binary — the generator that renders a wren project as the
9
+ * prepared-context document a `kind: prepared` binding reads.
10
+ *
11
+ * Three deterministic tiers, then a loud failure:
12
+ *
13
+ * 1. `explicit`, else the `WREN_HARNESS_CONTEXT_LOADER_BIN` environment variable. Either must
14
+ * exist on disk; neither falls through to a search.
15
+ * 2. This repo's own build of the generator crate, found by walking up from this package looking
16
+ * for `core/wren-context-loader/target/{release,debug}/wren-context-loader`. `release` wins
17
+ * when both exist, so a stale `debug` build never shadows a deliberate release build.
18
+ * 3. The verified package-local binary from the exact `@wrenai/context-loader` dependency. Its
19
+ * install record must match package version and target, name the canonical binary, and pass a
20
+ * fresh SHA-256 check. Resolution never downloads at runtime.
21
+ *
22
+ * There is deliberately **no** `PATH` tier and — more importantly — **no fallback to the old
23
+ * `wren_project` binding**. A fallback would make the extraction unverifiable: a compiled IR is
24
+ * byte-identical whichever path produced it (Warble emits neither `kind` nor `document` into the
25
+ * IR), so a silent degrade would be undetectable from the artifact alone. Failing loudly is the
26
+ * only way the caller can know which path ran.
27
+ *
28
+ * Throws {@link ContextLoaderNotFoundError} with the accumulated attempts when neither tier works.
29
+ */
30
+ export declare function resolveContextLoader(explicit?: string): ResolvedContextLoader;
31
+ /** Backward-compatible path-only API for callers that do not need cache provenance. */
32
+ export declare function resolveContextLoaderBinary(explicit?: string): string;
33
+ /** Stable cache identity: resolver kind plus either a verified package tuple or the dev binary's bytes. */
34
+ export declare function getContextLoaderIdentity(resolved: ResolvedContextLoader): string;
35
+ /** Validates a package-local installation record without resolving it from the host environment. */
36
+ export declare function readVerifiedContextLoaderPackage(packageRoot: string, target?: string): ResolvedContextLoader | undefined;
37
+ /**
38
+ * Runs the generator over `projectDir`, writing the prepared-context document to `outPath`.
39
+ *
40
+ * An *unreadable* project is not an error here: the generator writes `parseable: false` with the
41
+ * assembly error attached and exits 0, because "this layer could not be assembled" is a fact about
42
+ * the project that Warble's `mdl_parseable` precondition exists to report. Only a genuinely
43
+ * unusable invocation (a path that is not a directory, an unwritable output) exits non-zero, and
44
+ * that surfaces as {@link ContextLoaderFailedError}.
45
+ *
46
+ * Shells with `execFile` (argv array, no shell interpolation), matching the convention `runWarble`
47
+ * and `harness/exec/local.ts` already use.
48
+ */
49
+ export declare function generatePreparedContext(bin: string, projectDir: string, outPath: string, signal?: AbortSignal): Promise<void>;
50
+ /**
51
+ * Runs the generator over `projectDir` writing both the prepared-context document (`outPath`) and
52
+ * the **capability catalog** (`catalogPath`): the authored, descriptive, SQL-free slice of the
53
+ * project that `buildCapabilityCard` renders for a planner outside the data zone. One process,
54
+ * one read of the project, so the two documents can never describe different states of it.
55
+ */
56
+ export declare function generatePreparedContextAndCatalog(bin: string, projectDir: string, outPath: string, catalogPath: string, signal?: AbortSignal): Promise<void>;
@@ -0,0 +1,237 @@
1
+ import { execFile } from "node:child_process";
2
+ import { existsSync, lstatSync, readFileSync, realpathSync, statSync } from "node:fs";
3
+ import { createHash } from "node:crypto";
4
+ import { fileURLToPath } from "node:url";
5
+ import path from "node:path";
6
+ import { ContextLoaderFailedError, ContextLoaderNotFoundError } from "./errors.js";
7
+ import { resolveInstalledPackageRoot } from "../npm-package-resolve.js";
8
+ /** Environment override for {@link resolveContextLoaderBinary}, named like the other `WREN_HARNESS_*` binary overrides. */
9
+ const CONTEXT_LOADER_BIN_ENV = "WREN_HARNESS_CONTEXT_LOADER_BIN";
10
+ /** The generator's crate-relative build outputs, most-preferred first. */
11
+ const IN_REPO_BUILD_PATHS = [
12
+ ["core", "wren-context-loader", "target", "release", "wren-context-loader"],
13
+ ["core", "wren-context-loader", "target", "debug", "wren-context-loader"],
14
+ ];
15
+ /**
16
+ * A generous ceiling for a step that reads a semantic layer off local disk and writes one JSON
17
+ * document — guards against a hang, not meant to ever fire in normal operation.
18
+ */
19
+ const CONTEXT_LOADER_TIMEOUT_MS = 60 * 1000;
20
+ const CONTEXT_LOADER_PACKAGE = "@wrenai/context-loader";
21
+ const CANONICAL_PACKAGE_BINARY = path.join("bin", "wren-context-loader");
22
+ /**
23
+ * Resolves the `wren-context-loader` binary — the generator that renders a wren project as the
24
+ * prepared-context document a `kind: prepared` binding reads.
25
+ *
26
+ * Three deterministic tiers, then a loud failure:
27
+ *
28
+ * 1. `explicit`, else the `WREN_HARNESS_CONTEXT_LOADER_BIN` environment variable. Either must
29
+ * exist on disk; neither falls through to a search.
30
+ * 2. This repo's own build of the generator crate, found by walking up from this package looking
31
+ * for `core/wren-context-loader/target/{release,debug}/wren-context-loader`. `release` wins
32
+ * when both exist, so a stale `debug` build never shadows a deliberate release build.
33
+ * 3. The verified package-local binary from the exact `@wrenai/context-loader` dependency. Its
34
+ * install record must match package version and target, name the canonical binary, and pass a
35
+ * fresh SHA-256 check. Resolution never downloads at runtime.
36
+ *
37
+ * There is deliberately **no** `PATH` tier and — more importantly — **no fallback to the old
38
+ * `wren_project` binding**. A fallback would make the extraction unverifiable: a compiled IR is
39
+ * byte-identical whichever path produced it (Warble emits neither `kind` nor `document` into the
40
+ * IR), so a silent degrade would be undetectable from the artifact alone. Failing loudly is the
41
+ * only way the caller can know which path ran.
42
+ *
43
+ * Throws {@link ContextLoaderNotFoundError} with the accumulated attempts when neither tier works.
44
+ */
45
+ export function resolveContextLoader(explicit) {
46
+ const override = explicit ?? process.env[CONTEXT_LOADER_BIN_ENV];
47
+ if (override !== undefined && override.length > 0) {
48
+ if (!existsSync(override)) {
49
+ const source = explicit !== undefined ? `explicit contextLoaderBin` : CONTEXT_LOADER_BIN_ENV;
50
+ throw new ContextLoaderNotFoundError([`${source} "${override}" does not exist`]);
51
+ }
52
+ return { bin: override, kind: "explicit" };
53
+ }
54
+ const inRepo = findInRepoBuild();
55
+ if (inRepo !== undefined)
56
+ return { bin: inRepo, kind: "in-repo" };
57
+ const packaged = resolveVerifiedPackageBinary();
58
+ if (packaged !== undefined)
59
+ return packaged;
60
+ throw new ContextLoaderNotFoundError([
61
+ `${CONTEXT_LOADER_BIN_ENV} is not set`,
62
+ `no in-repo build found (searched ancestors of this package for ` +
63
+ IN_REPO_BUILD_PATHS.map((segments) => `"${path.join(...segments)}"`).join(" and ") +
64
+ `) — ${CONTEXT_LOADER_PACKAGE} is not installed with a verified ${targetForCurrentPlatform()} binary`,
65
+ ]);
66
+ }
67
+ /** Backward-compatible path-only API for callers that do not need cache provenance. */
68
+ export function resolveContextLoaderBinary(explicit) {
69
+ return resolveContextLoader(explicit).bin;
70
+ }
71
+ /** Stable cache identity: resolver kind plus either a verified package tuple or the dev binary's bytes. */
72
+ export function getContextLoaderIdentity(resolved) {
73
+ if (resolved.verifiedIdentity !== undefined)
74
+ return resolved.verifiedIdentity;
75
+ return `${resolved.kind}:${createHash("sha256").update(readFileSync(resolved.bin)).digest("hex")}`;
76
+ }
77
+ function resolveVerifiedPackageBinary() {
78
+ const packageRoot = resolveInstalledPackageRoot(CONTEXT_LOADER_PACKAGE);
79
+ if (packageRoot === undefined)
80
+ return undefined;
81
+ return readVerifiedContextLoaderPackage(packageRoot);
82
+ }
83
+ /** Validates a package-local installation record without resolving it from the host environment. */
84
+ export function readVerifiedContextLoaderPackage(packageRoot, target = targetForCurrentPlatform()) {
85
+ try {
86
+ const root = physicalPackageRoot(packageRoot);
87
+ if (root === undefined)
88
+ return undefined;
89
+ const packageJson = readVerifiedJson(root, "package.json");
90
+ const manifest = readVerifiedJson(root, "artifacts.json");
91
+ const state = readVerifiedJson(root, "install-state.json");
92
+ if (packageJson === undefined || manifest === undefined || state === undefined)
93
+ return undefined;
94
+ const artifacts = typeof manifest.artifacts === "object" && manifest.artifacts !== null ? manifest.artifacts : undefined;
95
+ const artifact = artifacts?.[target];
96
+ const bin = physicalRegularFile(root, CANONICAL_PACKAGE_BINARY);
97
+ if (packageJson.name !== CONTEXT_LOADER_PACKAGE ||
98
+ typeof packageJson.version !== "string" ||
99
+ manifest.schema !== 1 ||
100
+ manifest.package !== CONTEXT_LOADER_PACKAGE ||
101
+ manifest.version !== packageJson.version ||
102
+ !isArtifact(artifact) ||
103
+ state.package !== CONTEXT_LOADER_PACKAGE ||
104
+ state.version !== packageJson.version ||
105
+ state.target !== target ||
106
+ state.binaryPath !== CANONICAL_PACKAGE_BINARY ||
107
+ state.archiveSha256 !== artifact.archiveSha256 ||
108
+ state.binarySha256 !== artifact.binarySha256 ||
109
+ bin === undefined ||
110
+ createHash("sha256").update(readFileSync(bin)).digest("hex") !== artifact.binarySha256) {
111
+ return undefined;
112
+ }
113
+ return { bin, kind: "package", verifiedIdentity: `package:${CONTEXT_LOADER_PACKAGE}@${packageJson.version}:${target}:${artifact.binarySha256}` };
114
+ }
115
+ catch {
116
+ return undefined;
117
+ }
118
+ }
119
+ function readVerifiedJson(root, relativePath) {
120
+ const file = physicalRegularFile(root, relativePath);
121
+ if (file === undefined)
122
+ return undefined;
123
+ try {
124
+ const value = JSON.parse(readFileSync(file, "utf8"));
125
+ return typeof value === "object" && value !== null ? value : undefined;
126
+ }
127
+ catch {
128
+ return undefined;
129
+ }
130
+ }
131
+ function isSha256(value) {
132
+ return typeof value === "string" && /^[a-f0-9]{64}$/u.test(value);
133
+ }
134
+ function isArtifact(value) {
135
+ if (typeof value !== "object" || value === null)
136
+ return false;
137
+ const artifact = value;
138
+ return (typeof artifact.url === "string" &&
139
+ artifact.url.startsWith("https://") &&
140
+ isSha256(artifact.archiveSha256) &&
141
+ isSha256(artifact.binarySha256) &&
142
+ typeof artifact.binaryPath === "string" &&
143
+ !artifact.binaryPath.includes("..") &&
144
+ !path.isAbsolute(artifact.binaryPath));
145
+ }
146
+ /** Returns a package-owned physical root; package-root symlinks are never trusted. */
147
+ function physicalPackageRoot(packageRoot) {
148
+ try {
149
+ if (!lstatSync(packageRoot).isDirectory())
150
+ return undefined;
151
+ return realpathSync(packageRoot);
152
+ }
153
+ catch {
154
+ return undefined;
155
+ }
156
+ }
157
+ /** Refuses final-file links and any ancestor link that resolves outside the physical package root. */
158
+ function physicalRegularFile(root, relativePath) {
159
+ const lexical = path.resolve(root, relativePath);
160
+ if (!lexical.startsWith(`${root}${path.sep}`))
161
+ return undefined;
162
+ try {
163
+ if (!lstatSync(lexical).isFile())
164
+ return undefined;
165
+ const physical = realpathSync(lexical);
166
+ return physical.startsWith(`${root}${path.sep}`) && statSync(physical).isFile() ? physical : undefined;
167
+ }
168
+ catch {
169
+ return undefined;
170
+ }
171
+ }
172
+ function targetForCurrentPlatform() {
173
+ return `${process.platform}-${process.arch}`;
174
+ }
175
+ /**
176
+ * Walks up from this package's own directory (`harness/compile/` in source, `dist/compile/` once
177
+ * built — either way two levels below the package root), returning the first existing generator
178
+ * build. Mirrors the ancestor walk `resolveWarbleBinary`'s sibling-checkout tier uses, so this
179
+ * works from the main checkout or from a git worktree nested several levels under it.
180
+ */
181
+ function findInRepoBuild() {
182
+ let dir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
183
+ const maxDepth = 10;
184
+ for (let i = 0; i < maxDepth; i += 1) {
185
+ for (const segments of IN_REPO_BUILD_PATHS) {
186
+ const candidate = path.join(dir, ...segments);
187
+ if (existsSync(candidate))
188
+ return candidate;
189
+ }
190
+ const parent = path.dirname(dir);
191
+ if (parent === dir)
192
+ break; // reached filesystem root
193
+ dir = parent;
194
+ }
195
+ return undefined;
196
+ }
197
+ /**
198
+ * Runs the generator over `projectDir`, writing the prepared-context document to `outPath`.
199
+ *
200
+ * An *unreadable* project is not an error here: the generator writes `parseable: false` with the
201
+ * assembly error attached and exits 0, because "this layer could not be assembled" is a fact about
202
+ * the project that Warble's `mdl_parseable` precondition exists to report. Only a genuinely
203
+ * unusable invocation (a path that is not a directory, an unwritable output) exits non-zero, and
204
+ * that surfaces as {@link ContextLoaderFailedError}.
205
+ *
206
+ * Shells with `execFile` (argv array, no shell interpolation), matching the convention `runWarble`
207
+ * and `harness/exec/local.ts` already use.
208
+ */
209
+ export function generatePreparedContext(bin, projectDir, outPath, signal) {
210
+ return runContextLoader(bin, [projectDir, "-o", outPath], signal);
211
+ }
212
+ /**
213
+ * Runs the generator over `projectDir` writing both the prepared-context document (`outPath`) and
214
+ * the **capability catalog** (`catalogPath`): the authored, descriptive, SQL-free slice of the
215
+ * project that `buildCapabilityCard` renders for a planner outside the data zone. One process,
216
+ * one read of the project, so the two documents can never describe different states of it.
217
+ */
218
+ export function generatePreparedContextAndCatalog(bin, projectDir, outPath, catalogPath, signal) {
219
+ return runContextLoader(bin, [projectDir, "-o", outPath, "--catalog-out", catalogPath], signal);
220
+ }
221
+ function runContextLoader(bin, args, signal) {
222
+ return new Promise((resolve, reject) => {
223
+ execFile(bin, args, { maxBuffer: 32 * 1024 * 1024, timeout: CONTEXT_LOADER_TIMEOUT_MS, ...(signal ? { signal } : {}) }, (error, stdout, stderr) => {
224
+ if (error) {
225
+ const exitCode = typeof error.code === "number" ? error.code : 1;
226
+ const clarifiedStderr = stderr.length > 0
227
+ ? stderr
228
+ : error.killed === true
229
+ ? `wren-context-loader timed out after ${CONTEXT_LOADER_TIMEOUT_MS}ms`
230
+ : stderr;
231
+ reject(new ContextLoaderFailedError(bin, args, exitCode, clarifiedStderr, stdout));
232
+ return;
233
+ }
234
+ resolve();
235
+ });
236
+ });
237
+ }
@@ -16,6 +16,25 @@ export declare class WarbleCommandFailedError extends Error {
16
16
  readonly stdout: string;
17
17
  constructor(command: string, args: readonly string[], exitCode: number, stderr: string, stdout?: string);
18
18
  }
19
+ /**
20
+ * Thrown by {@link resolveContextLoaderBinary} (see `./context-loader.js`) when the
21
+ * `wren-context-loader` generator can be found neither via an explicit override nor as an in-repo
22
+ * build. Deliberately loud, and deliberately *not* recoverable by falling back to the old
23
+ * `wren_project` binding: Warble emits neither `kind` nor `document` into the IR, so a silent
24
+ * degrade to the built-in MDL adapter would be invisible in the compiled artifact.
25
+ */
26
+ export declare class ContextLoaderNotFoundError extends Error {
27
+ constructor(attempts: readonly string[]);
28
+ }
29
+ /** Thrown when the `wren-context-loader` subprocess exits non-zero. */
30
+ export declare class ContextLoaderFailedError extends Error {
31
+ readonly command: string;
32
+ readonly args: readonly string[];
33
+ readonly exitCode: number;
34
+ readonly stderr: string;
35
+ readonly stdout: string;
36
+ constructor(command: string, args: readonly string[], exitCode: number, stderr: string, stdout?: string);
37
+ }
19
38
  /** Thrown when a Warble profile directory doesn't have the expected `profile.yml` / context-binding shape. */
20
39
  export declare class InvalidProfileShapeError extends Error {
21
40
  constructor(message: string);
@@ -32,6 +32,41 @@ export class WarbleCommandFailedError extends Error {
32
32
  this.name = "WarbleCommandFailedError";
33
33
  }
34
34
  }
35
+ /**
36
+ * Thrown by {@link resolveContextLoaderBinary} (see `./context-loader.js`) when the
37
+ * `wren-context-loader` generator can be found neither via an explicit override nor as an in-repo
38
+ * build. Deliberately loud, and deliberately *not* recoverable by falling back to the old
39
+ * `wren_project` binding: Warble emits neither `kind` nor `document` into the IR, so a silent
40
+ * degrade to the built-in MDL adapter would be invisible in the compiled artifact.
41
+ */
42
+ export class ContextLoaderNotFoundError extends Error {
43
+ constructor(attempts) {
44
+ super(`could not resolve the "wren-context-loader" binary:\n` +
45
+ attempts.map((attempt) => ` - ${attempt}`).join("\n") +
46
+ `\nfix: install the exact @wrenai/context-loader package, build it in-repo from the repository root ` +
47
+ `("cargo build --release --manifest-path core/wren-context-loader/Cargo.toml") ` +
48
+ `or point WREN_HARNESS_CONTEXT_LOADER_BIN at an existing build.`);
49
+ this.name = "ContextLoaderNotFoundError";
50
+ }
51
+ }
52
+ /** Thrown when the `wren-context-loader` subprocess exits non-zero. */
53
+ export class ContextLoaderFailedError extends Error {
54
+ command;
55
+ args;
56
+ exitCode;
57
+ stderr;
58
+ stdout;
59
+ constructor(command, args, exitCode, stderr, stdout = "") {
60
+ const diagnostics = [stderr.trim(), stdout.trim()].filter((chunk) => chunk.length > 0).join("\n");
61
+ super(`"${command} ${args.join(" ")}" exited ${exitCode}:\n${diagnostics || "(no output)"}`);
62
+ this.command = command;
63
+ this.args = args;
64
+ this.exitCode = exitCode;
65
+ this.stderr = stderr;
66
+ this.stdout = stdout;
67
+ this.name = "ContextLoaderFailedError";
68
+ }
69
+ }
35
70
  /** Thrown when a Warble profile directory doesn't have the expected `profile.yml` / context-binding shape. */
36
71
  export class InvalidProfileShapeError extends Error {
37
72
  constructor(message) {
@@ -1,8 +1,9 @@
1
1
  export { createFileSystemCompileCache, createInMemoryCompileCache, resolveDefaultCacheDir } from "./cache.js";
2
- export { composeUserProfile, extractContextBindingPath, rewriteContextBindingProject } from "./compose-profile.js";
3
- export { InvalidProfileShapeError, WarbleBinaryNotFoundError, WarbleCommandFailedError, } from "./errors.js";
2
+ export { assertPreparedBindingKind, composeUserProfile, extractBindingDocumentPath, extractContextBindingPath, rewriteContextBindingProject, } from "./compose-profile.js";
3
+ export { generatePreparedContext, generatePreparedContextAndCatalog, resolveContextLoaderBinary } from "./context-loader.js";
4
+ export { ContextLoaderFailedError, ContextLoaderNotFoundError, InvalidProfileShapeError, WarbleBinaryNotFoundError, WarbleCommandFailedError, } from "./errors.js";
4
5
  export { hashDirectory, hashFiles } from "./fingerprint.js";
5
6
  export { compileProfile, compileRawProfile, runWarble } from "./pipeline.js";
6
7
  export { resolveHubDir, resolveWarbleBinary } from "./resolve-binary.js";
7
8
  export type { CompileCache, CompileCacheEntry, CompileCacheKey, CompileMode, CompileProfileOptions, CompileProfileResult, CompileRawProfileOptions, } from "./types.js";
8
- export { getWarbleIdentity } from "./warble-identity.js";
9
+ export { getBinaryIdentity, getWarbleIdentity } from "./warble-identity.js";
@@ -1,7 +1,8 @@
1
1
  export { createFileSystemCompileCache, createInMemoryCompileCache, resolveDefaultCacheDir } from "./cache.js";
2
- export { composeUserProfile, extractContextBindingPath, rewriteContextBindingProject } from "./compose-profile.js";
3
- export { InvalidProfileShapeError, WarbleBinaryNotFoundError, WarbleCommandFailedError, } from "./errors.js";
2
+ export { assertPreparedBindingKind, composeUserProfile, extractBindingDocumentPath, extractContextBindingPath, rewriteContextBindingProject, } from "./compose-profile.js";
3
+ export { generatePreparedContext, generatePreparedContextAndCatalog, resolveContextLoaderBinary } from "./context-loader.js";
4
+ export { ContextLoaderFailedError, ContextLoaderNotFoundError, InvalidProfileShapeError, WarbleBinaryNotFoundError, WarbleCommandFailedError, } from "./errors.js";
4
5
  export { hashDirectory, hashFiles } from "./fingerprint.js";
5
6
  export { compileProfile, compileRawProfile, runWarble } from "./pipeline.js";
6
7
  export { resolveHubDir, resolveWarbleBinary } from "./resolve-binary.js";
7
- export { getWarbleIdentity } from "./warble-identity.js";
8
+ export { getBinaryIdentity, getWarbleIdentity } from "./warble-identity.js";
@@ -12,6 +12,13 @@ export declare const DEFAULT_WREN_PROVIDER_PATH: string;
12
12
  * mode — additionally `warble dispatch --target vercel` for the vercel bundle this package's
13
13
  * `runAgent()` consumes.
14
14
  *
15
+ * The rebind runs WrenAI's own `wren-context-loader` generator over `userProject` and binds its
16
+ * output as a `kind: prepared` document, so Warble reads a projection WrenAI produced rather than
17
+ * introspecting the semantic layer itself. Resolving that generator loud-fails
18
+ * ({@link ContextLoaderNotFoundError}) — there is no fallback to Warble's built-in `wren_project`
19
+ * adapter, because the compiled IR is byte-identical either way and a silent degrade would be
20
+ * undetectable from the artifact.
21
+ *
15
22
  * A cache hit (see below) never *executes* `warble`, but by default DOES need to resolve + read
16
23
  * the binary — its content hash and the Hub root derived from its location are both cache-key
17
24
  * inputs (see `warbleIdentity` and `hubDir` below), so a hit can't be trusted without knowing which
@@ -26,13 +33,14 @@ export declare const DEFAULT_WREN_PROVIDER_PATH: string;
26
33
  * the same no-shell, structured-result `execFile` convention `harness/exec/local.ts` already uses.
27
34
  *
28
35
  * Results are cached on (profile content hash x user-context fingerprint x mode x resolved
29
- * provider-fragment content hash x warble binary identity x resolved Hub root) — see `./cache.js` —
30
- * so an unchanged profile/context/providers/compiler/Hub tuple returns the previous artifact
31
- * instead of recompiling. Folding in the provider-fragment hash, warble identity and Hub root
32
- * (rather than just profile+context+mode) matters: otherwise a custom `options.providers` and this
33
- * package's default fragment collide on the same key, and editing a provider fragment, rebuilding
34
- * `warble`, or compiling against a different component library would keep serving a stale artifact
35
- * forever, since there's no TTL/eviction.
36
+ * provider-fragment content hash x warble binary identity x `wren-context-loader` binary identity x
37
+ * resolved Hub root) — see `./cache.js` — so an unchanged profile/context/providers/compiler/
38
+ * generator/Hub tuple returns the previous artifact instead of recompiling. Folding in the
39
+ * provider-fragment hash, warble identity, generator identity and Hub root (rather than just
40
+ * profile+context+mode) matters: otherwise a custom `options.providers` and this package's default
41
+ * fragment collide on the same key, and editing a provider fragment, rebuilding `warble`, rebuilding
42
+ * the generator, or compiling against a different component library would keep serving a stale
43
+ * artifact forever, since there's no TTL/eviction.
36
44
  *
37
45
  * The `warble` invocations run inside a temporary scratch directory (the composed profile + raw
38
46
  * compiler output). When the cache relocates the artifacts to a durable home outside that scratch