modelrig 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +115 -8
  3. package/dist/adapters/anthropic.d.ts +46 -0
  4. package/dist/adapters/anthropic.d.ts.map +1 -0
  5. package/dist/adapters/anthropic.js +202 -0
  6. package/dist/adapters/deepseek.d.ts +41 -0
  7. package/dist/adapters/deepseek.d.ts.map +1 -0
  8. package/dist/adapters/deepseek.js +163 -0
  9. package/dist/adapters/gemini.d.ts +44 -0
  10. package/dist/adapters/gemini.d.ts.map +1 -0
  11. package/dist/adapters/gemini.js +222 -0
  12. package/dist/adapters/grok.d.ts +44 -0
  13. package/dist/adapters/grok.d.ts.map +1 -0
  14. package/dist/adapters/grok.js +168 -0
  15. package/dist/adapters/openai-compat.d.ts +74 -0
  16. package/dist/adapters/openai-compat.d.ts.map +1 -0
  17. package/dist/adapters/openai-compat.js +187 -0
  18. package/dist/adapters/openai.d.ts +49 -0
  19. package/dist/adapters/openai.d.ts.map +1 -0
  20. package/dist/adapters/openai.js +222 -0
  21. package/dist/bakeoff/engine.d.ts +52 -0
  22. package/dist/bakeoff/engine.d.ts.map +1 -0
  23. package/dist/bakeoff/engine.js +163 -0
  24. package/dist/bakeoff/replay.d.ts +52 -0
  25. package/dist/bakeoff/replay.d.ts.map +1 -0
  26. package/dist/bakeoff/replay.js +94 -0
  27. package/dist/bakeoff/samples.d.ts +62 -0
  28. package/dist/bakeoff/samples.d.ts.map +1 -0
  29. package/dist/bakeoff/samples.js +128 -0
  30. package/dist/bakeoff/stats.d.ts +13 -0
  31. package/dist/bakeoff/stats.d.ts.map +1 -0
  32. package/dist/bakeoff/stats.js +48 -0
  33. package/dist/bakeoff/types.d.ts +37 -0
  34. package/dist/bakeoff/types.d.ts.map +1 -0
  35. package/dist/bakeoff/types.js +10 -0
  36. package/dist/cli.d.ts +27 -0
  37. package/dist/cli.d.ts.map +1 -0
  38. package/dist/cli.js +572 -0
  39. package/dist/config.d.ts +104 -0
  40. package/dist/config.d.ts.map +1 -0
  41. package/dist/config.js +148 -0
  42. package/dist/feedback/coverage.d.ts +32 -0
  43. package/dist/feedback/coverage.d.ts.map +1 -0
  44. package/dist/feedback/coverage.js +95 -0
  45. package/dist/feedback/feedback.d.ts +35 -0
  46. package/dist/feedback/feedback.d.ts.map +1 -0
  47. package/dist/feedback/feedback.js +39 -0
  48. package/dist/index.d.ts +155 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +448 -0
  51. package/dist/init/finder.d.ts +79 -0
  52. package/dist/init/finder.d.ts.map +1 -0
  53. package/dist/init/finder.js +297 -0
  54. package/dist/init/run.d.ts +99 -0
  55. package/dist/init/run.d.ts.map +1 -0
  56. package/dist/init/run.js +281 -0
  57. package/dist/init/signatures.d.ts +39 -0
  58. package/dist/init/signatures.d.ts.map +1 -0
  59. package/dist/init/signatures.js +116 -0
  60. package/dist/matrix/aggregate.d.ts +89 -0
  61. package/dist/matrix/aggregate.d.ts.map +1 -0
  62. package/dist/matrix/aggregate.js +186 -0
  63. package/dist/query/grammar.d.ts +35 -0
  64. package/dist/query/grammar.d.ts.map +1 -0
  65. package/dist/query/grammar.js +279 -0
  66. package/dist/query/run.d.ts +8 -0
  67. package/dist/query/run.d.ts.map +1 -0
  68. package/dist/query/run.js +21 -0
  69. package/dist/registry/build.d.ts +126 -0
  70. package/dist/registry/build.d.ts.map +1 -0
  71. package/dist/registry/build.js +192 -0
  72. package/dist/registry/call-notes.d.ts +11 -0
  73. package/dist/registry/call-notes.d.ts.map +1 -0
  74. package/dist/registry/call-notes.js +72 -0
  75. package/dist/registry/claims.d.ts +63 -0
  76. package/dist/registry/claims.d.ts.map +1 -0
  77. package/dist/registry/claims.js +107 -0
  78. package/dist/registry/facts.d.ts +32 -0
  79. package/dist/registry/facts.d.ts.map +1 -0
  80. package/dist/registry/facts.js +120 -0
  81. package/dist/registry/fingerprint.d.ts +20 -0
  82. package/dist/registry/fingerprint.d.ts.map +1 -0
  83. package/dist/registry/fingerprint.js +150 -0
  84. package/dist/registry/parity.d.ts +26 -0
  85. package/dist/registry/parity.d.ts.map +1 -0
  86. package/dist/registry/parity.js +41 -0
  87. package/dist/registry/pricing.d.ts +19 -0
  88. package/dist/registry/pricing.d.ts.map +1 -0
  89. package/dist/registry/pricing.js +69 -0
  90. package/dist/registry/rows.d.ts +16 -0
  91. package/dist/registry/rows.d.ts.map +1 -0
  92. package/dist/registry/rows.js +57 -0
  93. package/dist/registry/seed.d.ts +43 -0
  94. package/dist/registry/seed.d.ts.map +1 -0
  95. package/dist/registry/seed.js +97 -0
  96. package/dist/registry/select.d.ts +68 -0
  97. package/dist/registry/select.d.ts.map +1 -0
  98. package/dist/registry/select.js +141 -0
  99. package/dist/registry/staleness.d.ts +18 -0
  100. package/dist/registry/staleness.d.ts.map +1 -0
  101. package/dist/registry/staleness.js +39 -0
  102. package/dist/routes/draft.d.ts +83 -0
  103. package/dist/routes/draft.d.ts.map +1 -0
  104. package/dist/routes/draft.js +135 -0
  105. package/dist/routes/loader.d.ts +26 -0
  106. package/dist/routes/loader.d.ts.map +1 -0
  107. package/dist/routes/loader.js +436 -0
  108. package/dist/routes/manifest.d.ts +24 -0
  109. package/dist/routes/manifest.d.ts.map +1 -0
  110. package/dist/routes/manifest.js +118 -0
  111. package/dist/routes/serveability.d.ts +52 -0
  112. package/dist/routes/serveability.d.ts.map +1 -0
  113. package/dist/routes/serveability.js +73 -0
  114. package/dist/routes/template.d.ts +18 -0
  115. package/dist/routes/template.d.ts.map +1 -0
  116. package/dist/routes/template.js +94 -0
  117. package/dist/run/cost.d.ts +26 -0
  118. package/dist/run/cost.d.ts.map +1 -0
  119. package/dist/run/cost.js +41 -0
  120. package/dist/run/envelope.d.ts +21 -0
  121. package/dist/run/envelope.d.ts.map +1 -0
  122. package/dist/run/envelope.js +45 -0
  123. package/dist/run/grounding.d.ts +56 -0
  124. package/dist/run/grounding.d.ts.map +1 -0
  125. package/dist/run/grounding.js +89 -0
  126. package/dist/run/repair.d.ts +36 -0
  127. package/dist/run/repair.d.ts.map +1 -0
  128. package/dist/run/repair.js +61 -0
  129. package/dist/run/resolve.d.ts +26 -0
  130. package/dist/run/resolve.d.ts.map +1 -0
  131. package/dist/run/resolve.js +81 -0
  132. package/dist/run/retry.d.ts +19 -0
  133. package/dist/run/retry.d.ts.map +1 -0
  134. package/dist/run/retry.js +50 -0
  135. package/dist/run/run.d.ts +41 -0
  136. package/dist/run/run.d.ts.map +1 -0
  137. package/dist/run/run.js +0 -0
  138. package/dist/run/validate.d.ts +15 -0
  139. package/dist/run/validate.d.ts.map +1 -0
  140. package/dist/run/validate.js +56 -0
  141. package/dist/savings/emit.d.ts +25 -0
  142. package/dist/savings/emit.d.ts.map +1 -0
  143. package/dist/savings/emit.js +75 -0
  144. package/dist/savings/odometer.d.ts +32 -0
  145. package/dist/savings/odometer.d.ts.map +1 -0
  146. package/dist/savings/odometer.js +70 -0
  147. package/dist/savings/swap-execute.d.ts +44 -0
  148. package/dist/savings/swap-execute.d.ts.map +1 -0
  149. package/dist/savings/swap-execute.js +87 -0
  150. package/dist/savings/swaps-client.d.ts +41 -0
  151. package/dist/savings/swaps-client.d.ts.map +1 -0
  152. package/dist/savings/swaps-client.js +81 -0
  153. package/dist/telemetry/capture.d.ts +28 -0
  154. package/dist/telemetry/capture.d.ts.map +1 -0
  155. package/dist/telemetry/capture.js +74 -0
  156. package/dist/telemetry/exporter.d.ts +62 -0
  157. package/dist/telemetry/exporter.d.ts.map +1 -0
  158. package/dist/telemetry/exporter.js +233 -0
  159. package/dist/telemetry/ingest-protocol.d.ts +161 -0
  160. package/dist/telemetry/ingest-protocol.d.ts.map +1 -0
  161. package/dist/telemetry/ingest-protocol.js +163 -0
  162. package/dist/telemetry/sinks.d.ts +54 -0
  163. package/dist/telemetry/sinks.d.ts.map +1 -0
  164. package/dist/telemetry/sinks.js +89 -0
  165. package/dist/telemetry/store.d.ts +38 -0
  166. package/dist/telemetry/store.d.ts.map +1 -0
  167. package/dist/telemetry/store.js +332 -0
  168. package/dist/telemetry/supabase-exporter.d.ts +64 -0
  169. package/dist/telemetry/supabase-exporter.d.ts.map +1 -0
  170. package/dist/telemetry/supabase-exporter.js +211 -0
  171. package/dist/tenancy/api-key-format.d.ts +67 -0
  172. package/dist/tenancy/api-key-format.d.ts.map +1 -0
  173. package/dist/tenancy/api-key-format.js +91 -0
  174. package/dist/tenancy/constants.d.ts +23 -0
  175. package/dist/tenancy/constants.d.ts.map +1 -0
  176. package/dist/tenancy/constants.js +25 -0
  177. package/dist/tenancy/scrub.d.ts +47 -0
  178. package/dist/tenancy/scrub.d.ts.map +1 -0
  179. package/dist/tenancy/scrub.js +85 -0
  180. package/dist/types/failures-teaching.d.ts +56 -0
  181. package/dist/types/failures-teaching.d.ts.map +1 -0
  182. package/dist/types/failures-teaching.js +177 -0
  183. package/dist/types/failures.d.ts +27 -0
  184. package/dist/types/failures.d.ts.map +1 -0
  185. package/dist/types/failures.js +41 -0
  186. package/dist/types/provider.d.ts +79 -0
  187. package/dist/types/provider.d.ts.map +1 -0
  188. package/dist/types/provider.js +20 -0
  189. package/dist/types/route.d.ts +127 -0
  190. package/dist/types/route.d.ts.map +1 -0
  191. package/dist/types/route.js +6 -0
  192. package/dist/types/telemetry.d.ts +89 -0
  193. package/dist/types/telemetry.d.ts.map +1 -0
  194. package/dist/types/telemetry.js +6 -0
  195. package/dist/watch/budget.d.ts +19 -0
  196. package/dist/watch/budget.d.ts.map +1 -0
  197. package/dist/watch/budget.js +35 -0
  198. package/dist/watch/evaluate.d.ts +66 -0
  199. package/dist/watch/evaluate.d.ts.map +1 -0
  200. package/dist/watch/evaluate.js +94 -0
  201. package/dist/watch/proposals.d.ts +73 -0
  202. package/dist/watch/proposals.d.ts.map +1 -0
  203. package/dist/watch/proposals.js +169 -0
  204. package/dist/watch/run-record.d.ts +64 -0
  205. package/dist/watch/run-record.d.ts.map +1 -0
  206. package/dist/watch/run-record.js +86 -0
  207. package/dist/watch/run-reported.d.ts +20 -0
  208. package/dist/watch/run-reported.d.ts.map +1 -0
  209. package/dist/watch/run-reported.js +53 -0
  210. package/dist/watch/run.d.ts +40 -0
  211. package/dist/watch/run.d.ts.map +1 -0
  212. package/dist/watch/run.js +44 -0
  213. package/dist/watch/state-store.d.ts +48 -0
  214. package/dist/watch/state-store.d.ts.map +1 -0
  215. package/dist/watch/state-store.js +86 -0
  216. package/dist/watch/state.d.ts +21 -0
  217. package/dist/watch/state.d.ts.map +1 -0
  218. package/dist/watch/state.js +93 -0
  219. package/package.json +50 -14
  220. package/index.js +0 -3
@@ -0,0 +1,297 @@
1
+ "use strict";
2
+ /**
3
+ * Provider call-site finder — `modelrig init` v0 (spec WS2).
4
+ *
5
+ * AST-lite on purpose: a balanced-brace reader over the call's options
6
+ * object, not a TypeScript parser. The job is to find call sites and read
7
+ * the handful of literals that matter (model, system prompt, schema hint,
8
+ * generation params) well enough to DRAFT a route a human will review. It is
9
+ * allowed to be unsure — every uncertainty is recorded as a `note` on the
10
+ * site rather than guessed into the output, because a confident wrong draft
11
+ * is worse than an honest incomplete one.
12
+ *
13
+ * Generic by construction: everything provider-specific lives in
14
+ * signatures.ts. No repo, framework, or company is special-cased.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.DEFAULT_SKIP_DIRS = void 0;
18
+ exports.readObjectLiteral = readObjectLiteral;
19
+ exports.findCallSitesInText = findCallSitesInText;
20
+ exports.scanRepoForCallSites = scanRepoForCallSites;
21
+ const signatures_1 = require("./signatures");
22
+ exports.DEFAULT_SKIP_DIRS = [
23
+ "node_modules",
24
+ "dist",
25
+ "build",
26
+ ".next",
27
+ ".git",
28
+ "coverage",
29
+ "vendor",
30
+ ".turbo",
31
+ ];
32
+ const DEFAULT_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
33
+ const DEFAULT_MAX_FILE_BYTES = 1000000;
34
+ /** Read the balanced `{...}` object starting at or after `from`. Returns null
35
+ * when the call has no object literal (e.g. a spread or a variable). */
36
+ function readObjectLiteral(source, from) {
37
+ const open = source.indexOf("{", from);
38
+ if (open === -1)
39
+ return null;
40
+ // Anything but whitespace between the paren and the brace means the first
41
+ // argument is not an inline object — don't misread a later one.
42
+ const between = source.slice(from, open);
43
+ if (/[^\s(]/.test(between))
44
+ return null;
45
+ let depth = 0;
46
+ let inString = null;
47
+ let escaped = false;
48
+ for (let i = open; i < source.length; i += 1) {
49
+ const char = source[i];
50
+ if (inString !== null) {
51
+ if (escaped)
52
+ escaped = false;
53
+ else if (char === "\\")
54
+ escaped = true;
55
+ else if (char === inString)
56
+ inString = null;
57
+ continue;
58
+ }
59
+ if (char === '"' || char === "'" || char === "`") {
60
+ inString = char;
61
+ continue;
62
+ }
63
+ if (char === "{")
64
+ depth += 1;
65
+ else if (char === "}") {
66
+ depth -= 1;
67
+ if (depth === 0)
68
+ return source.slice(open, i + 1);
69
+ }
70
+ }
71
+ return null; // unbalanced — treat as unreadable rather than guessing
72
+ }
73
+ /** Read `key: "literal"` at any nesting depth of the options object. */
74
+ function readStringOption(objectText, key) {
75
+ const pattern = new RegExp(`\\b${key}\\s*:\\s*(['"\`])((?:\\\\.|(?!\\1).)*)\\1`, "s");
76
+ const match = objectText.match(pattern);
77
+ return match === null ? null : match[2].replace(/\\n/g, "\n").replace(/\\(['"`])/g, "$1");
78
+ }
79
+ function readNumberOption(objectText, key) {
80
+ const match = objectText.match(new RegExp(`\\b${key}\\s*:\\s*(-?\\d+(?:\\.\\d+)?)`));
81
+ return match === null ? null : Number(match[1]);
82
+ }
83
+ /** The identifier a non-literal option refers to — reported, never resolved. */
84
+ function readIdentifierOption(objectText, key) {
85
+ const match = objectText.match(new RegExp(`\\b${key}\\s*:\\s*([A-Za-z_$][\\w$.]*)`));
86
+ return match === null ? null : match[1];
87
+ }
88
+ function hasKey(objectText, key) {
89
+ return new RegExp(`\\b${key}\\s*:`).test(objectText);
90
+ }
91
+ function lineOf(source, index) {
92
+ let line = 1;
93
+ for (let i = 0; i < index && i < source.length; i += 1) {
94
+ if (source[i] === "\n")
95
+ line += 1;
96
+ }
97
+ return line;
98
+ }
99
+ function detectSchemaHint(objectText, fileText, signature) {
100
+ for (const key of signature.schemaKeys) {
101
+ if (!hasKey(objectText, key))
102
+ continue;
103
+ const identifier = readIdentifierOption(objectText, key);
104
+ if (identifier !== null && /zod|Schema$/i.test(identifier))
105
+ return "zod";
106
+ if (key === "responseMimeType") {
107
+ return readStringOption(objectText, key) === "application/json" ? "json-mode" : null;
108
+ }
109
+ if (/json_schema|responseSchema|responseJsonSchema|output_schema/.test(key))
110
+ return "json-schema";
111
+ if (readStringOption(objectText, key) === "json_object")
112
+ return "json-mode";
113
+ if (/json_object/.test(objectText))
114
+ return "json-mode";
115
+ return "json-schema";
116
+ }
117
+ // A zod import plus a parse near the call is the common untyped-SDK shape.
118
+ if (/from\s+["']zod["']/.test(fileText) && /\bz\.object\s*\(/.test(fileText))
119
+ return "zod";
120
+ return null;
121
+ }
122
+ function extractParams(objectText) {
123
+ const params = {};
124
+ for (const [key, normalized] of Object.entries(signatures_1.PARAM_KEYS)) {
125
+ const value = readNumberOption(objectText, key);
126
+ if (value !== null && params[normalized] === undefined)
127
+ params[normalized] = value;
128
+ }
129
+ return params;
130
+ }
131
+ function buildSite(args) {
132
+ const { file, fileText, index, signature, matchedBy } = args;
133
+ const objectText = readObjectLiteral(fileText, index) ?? "";
134
+ const notes = [];
135
+ let model = null;
136
+ let modelExpression = null;
137
+ const modelKeys = signature.modelKeys.length > 0 ? signature.modelKeys : signatures_1.ALL_MODEL_KEYS;
138
+ for (const key of modelKeys) {
139
+ model = readStringOption(objectText, key);
140
+ if (model !== null)
141
+ break;
142
+ modelExpression = modelExpression ?? readIdentifierOption(objectText, key);
143
+ }
144
+ if (objectText === "") {
145
+ notes.push("The call's options were not an inline object, so nothing could be read from it — " +
146
+ "fill in the model and prompt by hand.");
147
+ }
148
+ else if (model === null) {
149
+ notes.push(modelExpression === null
150
+ ? "No model id found at this call site — name the model in the draft before running it."
151
+ : `The model comes from \`${modelExpression}\`, not a literal — set the candidate by hand ` +
152
+ "(and check whether it varies at runtime, which is exactly what a route replaces).");
153
+ }
154
+ let systemPrompt = null;
155
+ for (const key of signature.systemKeys) {
156
+ systemPrompt = readStringOption(objectText, key);
157
+ if (systemPrompt !== null)
158
+ break;
159
+ }
160
+ // OpenAI/Anthropic-style: the system turn lives inside `messages`.
161
+ if (systemPrompt === null && /role\s*:\s*['"`]system['"`]/.test(objectText)) {
162
+ const match = objectText.match(/role\s*:\s*['"`]system['"`]\s*,\s*content\s*:\s*(['"`])((?:\\.|(?!\1).)*)\1/s);
163
+ if (match !== null)
164
+ systemPrompt = match[2];
165
+ }
166
+ if (systemPrompt === null && objectText !== "") {
167
+ notes.push("No inline system prompt found — it is probably built in a variable. Paste it into the " +
168
+ "drafted prompt file.");
169
+ }
170
+ const schemaHint = detectSchemaHint(objectText, fileText, signature);
171
+ const wantsStructuredOutput = schemaHint !== null || signature.schemaKeys.some((k) => hasKey(objectText, k));
172
+ return {
173
+ file,
174
+ line: lineOf(fileText, index),
175
+ provider: signature.provider,
176
+ model,
177
+ modelExpression: model === null ? modelExpression : null,
178
+ systemPrompt,
179
+ wantsStructuredOutput,
180
+ schemaHint,
181
+ params: extractParams(objectText),
182
+ matchedBy,
183
+ notes,
184
+ };
185
+ }
186
+ /** Find provider call sites in one file's text. Exported for fixtures. */
187
+ function findCallSitesInText(file, fileText) {
188
+ const sites = [];
189
+ const seen = new Set();
190
+ for (const signature of signatures_1.PROVIDER_SIGNATURES) {
191
+ const sdkPresent = signature.packages.length === 0 ||
192
+ signature.packages.some((pkg) =>
193
+ // Static `from "pkg"`, CommonJS `require("pkg")`, AND dynamic
194
+ // `await import("pkg")` — lazy SDK loading is common in server code
195
+ // and missing it silently hides real call sites.
196
+ new RegExp(`(from|require\\(|import\\()\\s*["']${pkg.replace(/[/\\^$*+?.()|[\]{}]/g, "\\$&")}["']`).test(fileText));
197
+ if (sdkPresent) {
198
+ for (const pattern of signature.callPatterns) {
199
+ const regex = (0, signatures_1.freshPattern)(pattern);
200
+ let match = regex.exec(fileText);
201
+ while (match !== null) {
202
+ const index = match.index + match[0].length - 1;
203
+ if (!seen.has(index)) {
204
+ seen.add(index);
205
+ sites.push(buildSite({ file, fileText, index, signature, matchedBy: "sdk" }));
206
+ }
207
+ match = regex.exec(fileText);
208
+ }
209
+ }
210
+ }
211
+ // Host fallback: a raw fetch() to a provider API is still a call site.
212
+ // But a bare `const BASE_URL = "https://api.deepseek.com"` is NOT one —
213
+ // SDK-configured providers name their host in a constant, and reporting
214
+ // that as a call site produces a route nobody asked for. Require the
215
+ // mention to sit inside something request-shaped.
216
+ for (const host of signature.hosts) {
217
+ const hostRegex = new RegExp(host.replace(/\./g, "\\."), "g");
218
+ let match = hostRegex.exec(fileText);
219
+ while (match !== null) {
220
+ const before = fileText.slice(Math.max(0, match.index - 200), match.index);
221
+ const after = fileText.slice(match.index, match.index + 600);
222
+ const isRequest = /\b(fetch|request|axios|got|post)\s*\(/.test(before) && /\bbody\s*:/.test(after);
223
+ if (!isRequest) {
224
+ match = hostRegex.exec(fileText);
225
+ continue;
226
+ }
227
+ // A raw fetch puts the request in `body:`, usually wrapped in
228
+ // JSON.stringify(...). Aim the object reader at the payload itself
229
+ // rather than at the `body` key, or the wrapper hides it.
230
+ const bodyIndex = fileText.indexOf("body", match.index);
231
+ let index = bodyIndex === -1 ? match.index : bodyIndex + 4;
232
+ if (bodyIndex !== -1) {
233
+ const window = fileText.slice(bodyIndex, bodyIndex + 60);
234
+ const wrapper = window.indexOf("JSON.stringify(");
235
+ if (wrapper !== -1)
236
+ index = bodyIndex + wrapper + "JSON.stringify(".length;
237
+ else
238
+ index = fileText.indexOf(":", bodyIndex) + 1;
239
+ }
240
+ const near = [...seen].some((s) => Math.abs(s - index) < 400);
241
+ if (!near) {
242
+ seen.add(index);
243
+ sites.push(buildSite({ file, fileText, index, signature, matchedBy: "host" }));
244
+ }
245
+ match = hostRegex.exec(fileText);
246
+ }
247
+ }
248
+ }
249
+ return sites.sort((a, b) => a.line - b.line);
250
+ }
251
+ function scanRepoForCallSites(root, fs, path, options = {}) {
252
+ const skipDirs = new Set(options.skipDirs ?? exports.DEFAULT_SKIP_DIRS);
253
+ const extensions = options.extensions ?? DEFAULT_EXTENSIONS;
254
+ const maxBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
255
+ const sites = [];
256
+ const skipped = [];
257
+ let filesScanned = 0;
258
+ const walk = (dir) => {
259
+ let entries;
260
+ try {
261
+ entries = fs.readdirSync(dir, { withFileTypes: true });
262
+ }
263
+ catch {
264
+ return; // unreadable directory is not a reason to abort the scan
265
+ }
266
+ for (const entry of entries) {
267
+ const full = path.join(dir, entry.name);
268
+ if (entry.isDirectory()) {
269
+ if (skipDirs.has(entry.name))
270
+ continue;
271
+ walk(full);
272
+ continue;
273
+ }
274
+ if (!entry.isFile())
275
+ continue;
276
+ if (!extensions.some((ext) => entry.name.endsWith(ext)))
277
+ continue;
278
+ const isTest = /\.(test|spec)\.[jt]sx?$/.test(entry.name) || full.includes("__tests__") || full.includes("__mocks__");
279
+ if (isTest && options.includeTests !== true)
280
+ continue;
281
+ try {
282
+ if (fs.statSync(full).size > maxBytes) {
283
+ skipped.push(path.relative(root, full));
284
+ continue;
285
+ }
286
+ const text = fs.readFileSync(full, "utf8");
287
+ filesScanned += 1;
288
+ sites.push(...findCallSitesInText(path.relative(root, full), text));
289
+ }
290
+ catch {
291
+ skipped.push(path.relative(root, full));
292
+ }
293
+ }
294
+ };
295
+ walk(root);
296
+ return { sites, filesScanned, filesSkipped: skipped };
297
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * `modelrig init` v0 — spec WS2.
3
+ *
4
+ * Finds provider call sites in a repo, drafts a route bundle per site, and
5
+ * writes a MIGRATION.md mapping call site → route → the `rig.run` line that
6
+ * would replace it.
7
+ *
8
+ * THE HARD RULE, restated because it is the whole design: init NEVER edits
9
+ * the target's source. It writes under `modelrig/routes/drafts/` and nothing
10
+ * else. The diff is the human's to apply — spec §4 puts writing-to-source
11
+ * permanently out of scope for this phase, and the drafts-only property is
12
+ * tested, not just documented.
13
+ */
14
+ import type { CallSite, FileSystemLike, FinderOptions } from "./finder";
15
+ import type { ModelSelectRow, CapabilityPredicate } from "../registry/select";
16
+ import type { RouteDraft } from "../routes/draft";
17
+ /**
18
+ * The cost opportunity at a call site — SHOWN, never taken.
19
+ *
20
+ * A migration draft routes to the incumbent so the first run reproduces
21
+ * today's behavior (see draft.ts on why `prefer` is empty). That is a
22
+ * sequencing decision, not a decision to ignore cost: the saving is the
23
+ * whole point of the product, so the migration map names it and hands over
24
+ * the one command that would prove it.
25
+ *
26
+ * The number is INDICATIVE. It comes from probe runs on a generic schema
27
+ * task, not from the caller's traffic — which is exactly why the honest move
28
+ * is to show it next to a bake-off command rather than act on it.
29
+ */
30
+ export interface CostOutlook {
31
+ readonly incumbentEffectiveUsdPer1k: number | null;
32
+ readonly bestChallenger: {
33
+ readonly model: string;
34
+ readonly effectiveUsdPer1k: number;
35
+ } | null;
36
+ /** Indicative only; null when the incumbent is unprobed. */
37
+ readonly indicativeSavingsPct: number | null;
38
+ /** Why the number is, or is not, available. */
39
+ readonly note: string;
40
+ }
41
+ export interface InitDraft {
42
+ readonly route: string;
43
+ readonly site: CallSite;
44
+ readonly draft: RouteDraft;
45
+ readonly candidateReasoning: readonly string[];
46
+ /** The rig.run(...) call that would replace this site. */
47
+ readonly replacement: string;
48
+ readonly costOutlook: CostOutlook;
49
+ }
50
+ export interface InitResult {
51
+ readonly drafts: readonly InitDraft[];
52
+ readonly sitesFound: number;
53
+ readonly filesScanned: number;
54
+ readonly filesSkipped: readonly string[];
55
+ readonly written: readonly string[];
56
+ readonly skipped: readonly string[];
57
+ readonly migrationMarkdown: string;
58
+ }
59
+ export interface InitOptions extends FinderOptions {
60
+ /** Registry rows for candidate seeding; empty = incumbent-only drafts. */
61
+ readonly registryRows?: readonly ModelSelectRow[];
62
+ readonly challengerCount?: number;
63
+ /** Plan without writing (`--dry-run`). */
64
+ readonly dryRun?: boolean;
65
+ }
66
+ export interface InitDeps {
67
+ readonly root: string;
68
+ readonly draftsDir: string;
69
+ readonly fs: FileSystemLike & {
70
+ existsSync: (p: string) => boolean;
71
+ mkdirSync: (p: string, o: {
72
+ recursive: boolean;
73
+ }) => void;
74
+ writeFileSync: (p: string, c: string, e: "utf8") => void;
75
+ };
76
+ readonly path: {
77
+ join: (...parts: string[]) => string;
78
+ relative: (from: string, to: string) => string;
79
+ dirname: (p: string) => string;
80
+ basename: (p: string, ext?: string) => string;
81
+ };
82
+ }
83
+ /** Route name from a call site: `<file stem>.<line>` namespaced by its
84
+ * directory, e.g. "services.asset_classifier_42". Deterministic so a second
85
+ * run produces the same names and therefore skips the same drafts. */
86
+ export declare function routeNameFor(site: CallSite, basename: (p: string, ext?: string) => string): string;
87
+ /** What the site's structured-output usage implies about capability needs.
88
+ * Conservative: a json_schema request implies native enforcement is wanted;
89
+ * bare JSON mode only implies conformance. */
90
+ export declare function capabilitiesFor(site: CallSite): CapabilityPredicate[];
91
+ /** Price the incumbent against the cheapest probed challenger, honestly. */
92
+ export declare function buildCostOutlook(incumbentKey: string | null, challengerKeys: readonly string[], rows: readonly ModelSelectRow[]): CostOutlook;
93
+ export declare function buildMigrationMarkdown(drafts: readonly InitDraft[], result: {
94
+ sitesFound: number;
95
+ filesScanned: number;
96
+ filesSkipped: readonly string[];
97
+ }): string;
98
+ export declare function runInit(deps: InitDeps, options?: InitOptions): InitResult;
99
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/init/run.ts"],"names":[],"mappings":"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"}