@vijaypjavvadi/bdd2pw 1.1.7 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +228 -0
- package/README.md +78 -0
- package/dist/cli.js +22 -2
- package/dist/cli.js.map +1 -1
- package/dist/emitters/facade.js +7 -1
- package/dist/emitters/facade.js.map +1 -1
- package/dist/index.js +43 -7
- package/dist/index.js.map +1 -1
- package/dist/llm/anthropicClient.d.ts +53 -0
- package/dist/llm/anthropicClient.js +291 -0
- package/dist/llm/anthropicClient.js.map +1 -0
- package/dist/llm/cache.d.ts +51 -0
- package/dist/llm/cache.js +150 -0
- package/dist/llm/cache.js.map +1 -0
- package/dist/llm/candidateRules.d.ts +30 -0
- package/dist/llm/candidateRules.js +89 -0
- package/dist/llm/candidateRules.js.map +1 -0
- package/dist/llm/governanceClient.d.ts +36 -16
- package/dist/llm/governanceClient.js +73 -7
- package/dist/llm/governanceClient.js.map +1 -1
- package/dist/llm/index.d.ts +28 -0
- package/dist/llm/index.js +83 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/llmStepMatcher.d.ts +35 -0
- package/dist/llm/llmStepMatcher.js +101 -0
- package/dist/llm/llmStepMatcher.js.map +1 -0
- package/dist/llm/prompt.d.ts +39 -0
- package/dist/llm/prompt.js +113 -0
- package/dist/llm/prompt.js.map +1 -0
- package/dist/llm/types.d.ts +149 -0
- package/dist/llm/types.js +17 -0
- package/dist/llm/types.js.map +1 -0
- package/dist/types.d.ts +32 -0
- package/dist/utils/commentSafe.d.ts +30 -0
- package/dist/utils/commentSafe.js +39 -0
- package/dist/utils/commentSafe.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Anthropic-backed LLMClient.
|
|
4
|
+
*
|
|
5
|
+
* Flow per generateBinding():
|
|
6
|
+
* 1. Cache lookup — if hit, return immediately, no provider call.
|
|
7
|
+
* 2. Build prompt (system + user) via `src/llm/prompt.ts`.
|
|
8
|
+
* 3. Sanitise user prompt via the governance sidecar (kind="code").
|
|
9
|
+
* Fail-closed if the sidecar is unreachable.
|
|
10
|
+
* 4. Call Anthropic API with temperature=0, JSON response, the model
|
|
11
|
+
* from options (default claude-sonnet-4-6).
|
|
12
|
+
* 5. Parse the response as JSON. Validate it as a StepBinding
|
|
13
|
+
* (echoes step text, has at most one of pomCall/assertion/customBody).
|
|
14
|
+
* 6. Cache the result, return.
|
|
15
|
+
*
|
|
16
|
+
* Soft-fail: any error in steps 3–5 returns `{ error: "<reason>" }`. The
|
|
17
|
+
* caller (matchStep wrapper) falls through to the existing TODO + warning
|
|
18
|
+
* binding. The scaffold still completes; just the affected step lands as
|
|
19
|
+
* a TODO instead of LLM-generated code.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.AnthropicLLMClient = void 0;
|
|
23
|
+
exports.parseBindingJson = parseBindingJson;
|
|
24
|
+
const prompt_1 = require("./prompt");
|
|
25
|
+
const governanceClient_1 = require("./governanceClient");
|
|
26
|
+
const cache_1 = require("./cache");
|
|
27
|
+
const DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
28
|
+
const DEFAULT_GOVERNANCE_URL = "http://localhost:4900";
|
|
29
|
+
const DEFAULT_MAX_CALLS = 50;
|
|
30
|
+
class AnthropicLLMClient {
|
|
31
|
+
constructor(opts, cachePathDefault) {
|
|
32
|
+
this.cache = null;
|
|
33
|
+
this.callsCounter = 0;
|
|
34
|
+
this.attemptsCounter = 0;
|
|
35
|
+
this.anthropic = null; // lazy-loaded SDK instance
|
|
36
|
+
this.model = opts.model ?? DEFAULT_MODEL;
|
|
37
|
+
this.apiKey = opts.apiKey ?? process.env.ANTHROPIC_API_KEY ?? "";
|
|
38
|
+
this.maxCalls = opts.maxCalls ?? DEFAULT_MAX_CALLS;
|
|
39
|
+
this.skipGovernance = opts.skipGovernance ?? false;
|
|
40
|
+
this.log = opts.log ?? (() => { });
|
|
41
|
+
this.governance = new governanceClient_1.GovernanceClient(opts.governanceUrl ?? DEFAULT_GOVERNANCE_URL);
|
|
42
|
+
this.cachePath = opts.cachePath ?? cachePathDefault;
|
|
43
|
+
}
|
|
44
|
+
async ensureCache() {
|
|
45
|
+
if (!this.cache) {
|
|
46
|
+
this.cache = await (0, cache_1.openSqliteCache)(this.cachePath);
|
|
47
|
+
}
|
|
48
|
+
return this.cache;
|
|
49
|
+
}
|
|
50
|
+
budgetExhausted() {
|
|
51
|
+
// Budget is enforced against ATTEMPTS, not just successes — otherwise a
|
|
52
|
+
// run that hits 50 consecutive failures would loop forever trying.
|
|
53
|
+
return this.attemptsCounter >= this.maxCalls;
|
|
54
|
+
}
|
|
55
|
+
/** Successful provider responses (parseable bindings). */
|
|
56
|
+
callsMade() {
|
|
57
|
+
return this.callsCounter;
|
|
58
|
+
}
|
|
59
|
+
/** All provider call attempts, including failures. */
|
|
60
|
+
callsAttempted() {
|
|
61
|
+
return this.attemptsCounter;
|
|
62
|
+
}
|
|
63
|
+
async generateBinding(input) {
|
|
64
|
+
const start = Date.now();
|
|
65
|
+
const key = (0, prompt_1.cacheKey)(input, this.model);
|
|
66
|
+
const cache = await this.ensureCache();
|
|
67
|
+
// 1) Cache lookup
|
|
68
|
+
const hit = await cache.get(key);
|
|
69
|
+
if (hit) {
|
|
70
|
+
this.log({ kind: "cache_hit", key });
|
|
71
|
+
return {
|
|
72
|
+
binding: hit.binding,
|
|
73
|
+
fromCache: true,
|
|
74
|
+
model: hit.model,
|
|
75
|
+
latencyMs: Date.now() - start,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
this.log({ kind: "cache_miss", key });
|
|
79
|
+
// Budget check — refuse if exhausted (caller will fall back to TODO).
|
|
80
|
+
if (this.budgetExhausted()) {
|
|
81
|
+
return {
|
|
82
|
+
error: `LLM budget exhausted (${this.callsCounter}/${this.maxCalls} calls). Increase via --llm-max-calls.`,
|
|
83
|
+
fromCache: false,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// 2) Build prompt
|
|
87
|
+
const userPrompt = (0, prompt_1.buildUserPrompt)(input);
|
|
88
|
+
// 3) Sanitise via governance sidecar (unless skipped for tests).
|
|
89
|
+
let sanitisedUserPrompt = userPrompt;
|
|
90
|
+
if (!this.skipGovernance) {
|
|
91
|
+
try {
|
|
92
|
+
this.log({ kind: "sanitise_start", bytes: userPrompt.length });
|
|
93
|
+
const result = await this.governance.sanitiseCode(userPrompt);
|
|
94
|
+
sanitisedUserPrompt = result.sanitised;
|
|
95
|
+
this.log({
|
|
96
|
+
kind: "sanitise_done",
|
|
97
|
+
bytes: sanitisedUserPrompt.length,
|
|
98
|
+
findings: result.findings.length,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
const isUnreachable = err instanceof governanceClient_1.GovernanceUnreachableError;
|
|
103
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
104
|
+
this.log({
|
|
105
|
+
kind: "error",
|
|
106
|
+
phase: "governance",
|
|
107
|
+
message,
|
|
108
|
+
});
|
|
109
|
+
return {
|
|
110
|
+
error: isUnreachable
|
|
111
|
+
? `Governance sidecar unreachable; refusing to call LLM (fail-closed). ${message}`
|
|
112
|
+
: `Governance sanitise failed: ${message}`,
|
|
113
|
+
fromCache: false,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// 4) Anthropic call
|
|
118
|
+
if (!this.apiKey) {
|
|
119
|
+
return {
|
|
120
|
+
error: "ANTHROPIC_API_KEY is not set; pass apiKey or set the env var.",
|
|
121
|
+
fromCache: false,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const anthropic = await this.lazyClient();
|
|
125
|
+
if (!anthropic) {
|
|
126
|
+
return {
|
|
127
|
+
error: "@anthropic-ai/sdk not installed. Run: npm install @anthropic-ai/sdk",
|
|
128
|
+
fromCache: false,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
let responseText = "";
|
|
132
|
+
let inputTokens = 0;
|
|
133
|
+
let outputTokens = 0;
|
|
134
|
+
// v2.0.1 — count the ATTEMPT before the await. A failed call still cost
|
|
135
|
+
// a round-trip + tokens at the provider; operators need to see them in
|
|
136
|
+
// the scaffold log to debug "LLM fallback: 0 successful / N attempted".
|
|
137
|
+
this.attemptsCounter += 1;
|
|
138
|
+
try {
|
|
139
|
+
this.log({
|
|
140
|
+
kind: "provider_call_start",
|
|
141
|
+
model: this.model,
|
|
142
|
+
promptBytes: sanitisedUserPrompt.length,
|
|
143
|
+
});
|
|
144
|
+
const callStart = Date.now();
|
|
145
|
+
const resp = await anthropic.messages.create({
|
|
146
|
+
model: this.model,
|
|
147
|
+
max_tokens: 1024,
|
|
148
|
+
temperature: 0,
|
|
149
|
+
system: prompt_1.SYSTEM_PROMPT,
|
|
150
|
+
messages: [{ role: "user", content: sanitisedUserPrompt }],
|
|
151
|
+
});
|
|
152
|
+
this.callsCounter += 1;
|
|
153
|
+
const callLatency = Date.now() - callStart;
|
|
154
|
+
// The SDK returns content as an array of blocks; first block is text.
|
|
155
|
+
const firstBlock = (resp.content ?? [])[0];
|
|
156
|
+
if (firstBlock && firstBlock.type === "text") {
|
|
157
|
+
responseText = firstBlock.text;
|
|
158
|
+
}
|
|
159
|
+
inputTokens = resp.usage?.input_tokens ?? 0;
|
|
160
|
+
outputTokens = resp.usage?.output_tokens ?? 0;
|
|
161
|
+
this.log({
|
|
162
|
+
kind: "provider_call_done",
|
|
163
|
+
model: this.model,
|
|
164
|
+
latencyMs: callLatency,
|
|
165
|
+
inputTokens,
|
|
166
|
+
outputTokens,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
171
|
+
this.log({ kind: "error", phase: "anthropic", message });
|
|
172
|
+
return {
|
|
173
|
+
error: `Anthropic API call failed: ${message}`,
|
|
174
|
+
fromCache: false,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
// 5) Parse + validate
|
|
178
|
+
const binding = parseBindingJson(responseText, input);
|
|
179
|
+
if (!binding) {
|
|
180
|
+
this.log({
|
|
181
|
+
kind: "error",
|
|
182
|
+
phase: "parse",
|
|
183
|
+
message: `Could not parse JSON binding from LLM output: ${responseText.slice(0, 200)}`,
|
|
184
|
+
});
|
|
185
|
+
return {
|
|
186
|
+
error: `Could not parse JSON binding from LLM output (first 200 chars): ${responseText.slice(0, 200)}`,
|
|
187
|
+
fromCache: false,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
this.log({ kind: "binding_parsed", binding });
|
|
191
|
+
// 6) Cache + return
|
|
192
|
+
await cache.set(key, {
|
|
193
|
+
binding,
|
|
194
|
+
model: this.model,
|
|
195
|
+
createdAt: new Date().toISOString(),
|
|
196
|
+
});
|
|
197
|
+
return {
|
|
198
|
+
binding,
|
|
199
|
+
fromCache: false,
|
|
200
|
+
model: this.model,
|
|
201
|
+
latencyMs: Date.now() - start,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
async lazyClient() {
|
|
205
|
+
if (this.anthropic)
|
|
206
|
+
return this.anthropic;
|
|
207
|
+
try {
|
|
208
|
+
// @anthropic-ai/sdk is in optionalDependencies. require() defensively.
|
|
209
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
210
|
+
const mod = require("@anthropic-ai/sdk");
|
|
211
|
+
const Anthropic = mod.Anthropic ?? mod.default ?? mod;
|
|
212
|
+
this.anthropic = new Anthropic({ apiKey: this.apiKey });
|
|
213
|
+
return this.anthropic;
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async close() {
|
|
220
|
+
if (this.cache) {
|
|
221
|
+
await this.cache.close().catch(() => undefined);
|
|
222
|
+
this.cache = null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
exports.AnthropicLLMClient = AnthropicLLMClient;
|
|
227
|
+
/**
|
|
228
|
+
* Parse the LLM's text response as JSON and validate it has the expected
|
|
229
|
+
* StepBinding shape. Returns undefined on any failure (caller soft-fails).
|
|
230
|
+
*
|
|
231
|
+
* The LLM occasionally wraps JSON in ```json ... ``` fences despite the
|
|
232
|
+
* system prompt — strip those before parsing.
|
|
233
|
+
*/
|
|
234
|
+
function parseBindingJson(text, input) {
|
|
235
|
+
let trimmed = text.trim();
|
|
236
|
+
// Strip ```json ... ``` fences if present.
|
|
237
|
+
const fenceMatch = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/);
|
|
238
|
+
if (fenceMatch)
|
|
239
|
+
trimmed = fenceMatch[1].trim();
|
|
240
|
+
let parsed;
|
|
241
|
+
try {
|
|
242
|
+
parsed = JSON.parse(trimmed);
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
if (!parsed || typeof parsed !== "object")
|
|
248
|
+
return undefined;
|
|
249
|
+
// Echo the step from the input — the LLM's "step" field is sometimes
|
|
250
|
+
// slightly different (it normalises whitespace, capitalises, etc.). Use
|
|
251
|
+
// ours so downstream comments stay consistent.
|
|
252
|
+
const out = {
|
|
253
|
+
step: { ...input.step },
|
|
254
|
+
};
|
|
255
|
+
if (parsed.pomCall && typeof parsed.pomCall === "object") {
|
|
256
|
+
const pc = parsed.pomCall;
|
|
257
|
+
if (typeof pc.page === "string" && typeof pc.method === "string") {
|
|
258
|
+
out.pomCall = {
|
|
259
|
+
page: pc.page,
|
|
260
|
+
method: pc.method,
|
|
261
|
+
args: Array.isArray(pc.args) ? pc.args.map(String) : [],
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (parsed.assertion && typeof parsed.assertion === "object") {
|
|
266
|
+
const a = parsed.assertion;
|
|
267
|
+
if (typeof a.locator === "string" && typeof a.matcher === "string") {
|
|
268
|
+
out.assertion = {
|
|
269
|
+
locator: a.locator,
|
|
270
|
+
matcher: a.matcher,
|
|
271
|
+
expected: typeof a.expected === "string" ? a.expected : undefined,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (typeof parsed.customBody === "string") {
|
|
276
|
+
out.customBody = parsed.customBody;
|
|
277
|
+
}
|
|
278
|
+
if (typeof parsed.warning === "string") {
|
|
279
|
+
out.warning = parsed.warning;
|
|
280
|
+
}
|
|
281
|
+
// At least one of the four must be set; otherwise the LLM produced an
|
|
282
|
+
// empty binding which we reject.
|
|
283
|
+
if (!out.pomCall &&
|
|
284
|
+
!out.assertion &&
|
|
285
|
+
!out.customBody &&
|
|
286
|
+
!out.warning) {
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
291
|
+
//# sourceMappingURL=anthropicClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropicClient.js","sourceRoot":"","sources":["../../src/llm/anthropicClient.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;GAkBG;;;AA8PH,4CA8DC;AAlTD,qCAAoE;AACpE,yDAG4B;AAC5B,mCAIiB;AAEjB,MAAM,aAAa,GAAG,mBAAmB,CAAC;AAC1C,MAAM,sBAAsB,GAAG,uBAAuB,CAAC;AACvD,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B,MAAa,kBAAkB;IAa7B,YAAY,IAAsB,EAAE,gBAAwB;QANpD,UAAK,GAAwB,IAAI,CAAC;QAElC,iBAAY,GAAG,CAAC,CAAC;QACjB,oBAAe,GAAG,CAAC,CAAC;QACpB,cAAS,GAAe,IAAI,CAAC,CAAC,2BAA2B;QAG/D,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,aAAa,CAAC;QACzC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,EAAE,CAAC;QACjE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,iBAAiB,CAAC;QACnD,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,cAAc,IAAI,KAAK,CAAC;QACnD,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAClC,IAAI,CAAC,UAAU,GAAG,IAAI,mCAAgB,CACpC,IAAI,CAAC,aAAa,IAAI,sBAAsB,CAC7C,CAAC;QACF,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,gBAAgB,CAAC;IACtD,CAAC;IAED,KAAK,CAAC,WAAW;QACf,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,KAAK,GAAG,MAAM,IAAA,uBAAe,EAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACrD,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,eAAe;QACb,wEAAwE;QACxE,mEAAmE;QACnE,OAAO,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,QAAQ,CAAC;IAC/C,CAAC;IACD,0DAA0D;IAC1D,SAAS;QACP,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IACD,sDAAsD;IACtD,cAAc;QACZ,OAAO,IAAI,CAAC,eAAe,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,eAAe,CACnB,KAA2B;QAE3B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,IAAA,iBAAQ,EAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACxC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;QAEvC,kBAAkB;QAClB,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,GAAG,EAAE,CAAC;YACR,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC;YACrC,OAAO;gBACL,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,SAAS,EAAE,IAAI;gBACf,KAAK,EAAE,GAAG,CAAC,KAAK;gBAChB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;aAC9B,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC;QAEtC,sEAAsE;QACtE,IAAI,IAAI,CAAC,eAAe,EAAE,EAAE,CAAC;YAC3B,OAAO;gBACL,KAAK,EAAE,yBAAyB,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,QAAQ,wCAAwC;gBAC1G,SAAS,EAAE,KAAK;aACjB,CAAC;QACJ,CAAC;QAED,kBAAkB;QAClB,MAAM,UAAU,GAAG,IAAA,wBAAe,EAAC,KAAK,CAAC,CAAC;QAE1C,iEAAiE;QACjE,IAAI,mBAAmB,GAAG,UAAU,CAAC;QACrC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YACzB,IAAI,CAAC;gBACH,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;gBAC/D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;gBAC9D,mBAAmB,GAAG,MAAM,CAAC,SAAS,CAAC;gBACvC,IAAI,CAAC,GAAG,CAAC;oBACP,IAAI,EAAE,eAAe;oBACrB,KAAK,EAAE,mBAAmB,CAAC,MAAM;oBACjC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM;iBACjC,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,aAAa,GAAG,GAAG,YAAY,6CAA0B,CAAC;gBAChE,MAAM,OAAO,GACX,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACnD,IAAI,CAAC,GAAG,CAAC;oBACP,IAAI,EAAE,OAAO;oBACb,KAAK,EAAE,YAAY;oBACnB,OAAO;iBACR,CAAC,CAAC;gBACH,OAAO;oBACL,KAAK,EAAE,aAAa;wBAClB,CAAC,CAAC,uEAAuE,OAAO,EAAE;wBAClF,CAAC,CAAC,+BAA+B,OAAO,EAAE;oBAC5C,SAAS,EAAE,KAAK;iBACjB,CAAC;YACJ,CAAC;QACH,CAAC;QAED,oBAAoB;QACpB,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,OAAO;gBACL,KAAK,EACH,+DAA+D;gBACjE,SAAS,EAAE,KAAK;aACjB,CAAC;QACJ,CAAC;QACD,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;QAC1C,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO;gBACL,KAAK,EACH,qEAAqE;gBACvE,SAAS,EAAE,KAAK;aACjB,CAAC;QACJ,CAAC;QAED,IAAI,YAAY,GAAG,EAAE,CAAC;QACtB,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,wEAAwE;QACxE,uEAAuE;QACvE,wEAAwE;QACxE,IAAI,CAAC,eAAe,IAAI,CAAC,CAAC;QAC1B,IAAI,CAAC;YACH,IAAI,CAAC,GAAG,CAAC;gBACP,IAAI,EAAE,qBAAqB;gBAC3B,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,WAAW,EAAE,mBAAmB,CAAC,MAAM;aACxC,CAAC,CAAC;YACH,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAC3C,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,UAAU,EAAE,IAAI;gBAChB,WAAW,EAAE,CAAC;gBACd,MAAM,EAAE,sBAAa;gBACrB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC;aAC3D,CAAC,CAAC;YACH,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC;YACvB,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAC3C,sEAAsE;YACtE,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3C,IAAI,UAAU,IAAI,UAAU,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBAC7C,YAAY,GAAG,UAAU,CAAC,IAAI,CAAC;YACjC,CAAC;YACD,WAAW,GAAG,IAAI,CAAC,KAAK,EAAE,YAAY,IAAI,CAAC,CAAC;YAC5C,YAAY,GAAG,IAAI,CAAC,KAAK,EAAE,aAAa,IAAI,CAAC,CAAC;YAC9C,IAAI,CAAC,GAAG,CAAC;gBACP,IAAI,EAAE,oBAAoB;gBAC1B,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,SAAS,EAAE,WAAW;gBACtB,WAAW;gBACX,YAAY;aACb,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;YACzD,OAAO;gBACL,KAAK,EAAE,8BAA8B,OAAO,EAAE;gBAC9C,SAAS,EAAE,KAAK;aACjB,CAAC;QACJ,CAAC;QAED,sBAAsB;QACtB,MAAM,OAAO,GAAG,gBAAgB,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;QACtD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,IAAI,CAAC,GAAG,CAAC;gBACP,IAAI,EAAE,OAAO;gBACb,KAAK,EAAE,OAAO;gBACd,OAAO,EAAE,iDAAiD,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;aACvF,CAAC,CAAC;YACH,OAAO;gBACL,KAAK,EAAE,mEAAmE,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;gBACtG,SAAS,EAAE,KAAK;aACjB,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC,CAAC;QAE9C,oBAAoB;QACpB,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE;YACnB,OAAO;YACP,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC,CAAC;QAEH,OAAO;YACL,OAAO;YACP,SAAS,EAAE,KAAK;YAChB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;SAC9B,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,UAAU;QACtB,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO,IAAI,CAAC,SAAS,CAAC;QAC1C,IAAI,CAAC;YACH,uEAAuE;YACvE,8DAA8D;YAC9D,MAAM,GAAG,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;YACzC,MAAM,SAAS,GAAG,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC;YACtD,IAAI,CAAC,SAAS,GAAG,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;YACxD,OAAO,IAAI,CAAC,SAAS,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAChD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACpB,CAAC;IACH,CAAC;CACF;AA5ND,gDA4NC;AAED;;;;;;GAMG;AACH,SAAgB,gBAAgB,CAC9B,IAAY,EACZ,KAA2B;IAE3B,IAAI,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC1B,2CAA2C;IAC3C,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACtE,IAAI,UAAU;QAAE,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/C,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAE5D,qEAAqE;IACrE,wEAAwE;IACxE,+CAA+C;IAC/C,MAAM,GAAG,GAAgB;QACvB,IAAI,EAAE,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE;KACxB,CAAC;IAEF,IAAI,MAAM,CAAC,OAAO,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzD,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC;QAC1B,IAAI,OAAO,EAAE,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,EAAE,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YACjE,GAAG,CAAC,OAAO,GAAG;gBACZ,IAAI,EAAE,EAAE,CAAC,IAAI;gBACb,MAAM,EAAE,EAAE,CAAC,MAAM;gBACjB,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE;aACxD,CAAC;QACJ,CAAC;IACH,CAAC;IACD,IAAI,MAAM,CAAC,SAAS,IAAI,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;QAC7D,MAAM,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC;QAC3B,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YACnE,GAAG,CAAC,SAAS,GAAG;gBACd,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,QAAQ,EACN,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;aAC1D,CAAC;QACJ,CAAC;IACH,CAAC;IACD,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,EAAE,CAAC;QAC1C,GAAG,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;IACrC,CAAC;IACD,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACvC,GAAG,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;IAC/B,CAAC;IAED,sEAAsE;IACtE,iCAAiC;IACjC,IACE,CAAC,GAAG,CAAC,OAAO;QACZ,CAAC,GAAG,CAAC,SAAS;QACd,CAAC,GAAG,CAAC,UAAU;QACf,CAAC,GAAG,CAAC,OAAO,EACZ,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite-backed cache for LLM-generated step bindings.
|
|
3
|
+
*
|
|
4
|
+
* Why SQLite (not JSON file): better-sqlite3 already an optional dep,
|
|
5
|
+
* cheap concurrent reads, single-file, plays nicely with the .gitignore
|
|
6
|
+
* we already have for `.bdd2pw/`.
|
|
7
|
+
*
|
|
8
|
+
* Schema:
|
|
9
|
+
* llm_cache(
|
|
10
|
+
* key TEXT PRIMARY KEY, -- hash(model + step text + POM signature)
|
|
11
|
+
* value TEXT, -- JSON-stringified StepBinding
|
|
12
|
+
* model TEXT, -- LLM model identifier (audit only)
|
|
13
|
+
* created_at TEXT -- ISO-8601 timestamp
|
|
14
|
+
* )
|
|
15
|
+
*
|
|
16
|
+
* Cache invalidation: keys include the model name, so swapping models
|
|
17
|
+
* (e.g. claude-sonnet-4-6 → claude-opus-4-6) invalidates old entries
|
|
18
|
+
* automatically. To force a full reset, delete the file.
|
|
19
|
+
*/
|
|
20
|
+
import type { StepBinding } from "../types";
|
|
21
|
+
export interface CacheEntry {
|
|
22
|
+
binding: StepBinding;
|
|
23
|
+
model: string;
|
|
24
|
+
createdAt: string;
|
|
25
|
+
}
|
|
26
|
+
export interface BindingCache {
|
|
27
|
+
get(key: string): Promise<CacheEntry | undefined>;
|
|
28
|
+
set(key: string, value: CacheEntry): Promise<void>;
|
|
29
|
+
size(): Promise<number>;
|
|
30
|
+
close(): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* In-memory cache — used when SQLite isn't available (better-sqlite3
|
|
34
|
+
* not installed) or when the caller passes :memory: as the path. Tests
|
|
35
|
+
* use this exclusively to avoid filesystem I/O.
|
|
36
|
+
*/
|
|
37
|
+
export declare class InMemoryCache implements BindingCache {
|
|
38
|
+
private store;
|
|
39
|
+
get(key: string): Promise<CacheEntry | undefined>;
|
|
40
|
+
set(key: string, value: CacheEntry): Promise<void>;
|
|
41
|
+
size(): Promise<number>;
|
|
42
|
+
close(): Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Open a SQLite-backed cache at the given path. better-sqlite3 is an
|
|
46
|
+
* optional dep; if it's not installed (or load fails), fall back to an
|
|
47
|
+
* in-memory cache and warn. The caller can detect the fallback by checking
|
|
48
|
+
* if `cache instanceof InMemoryCache` (or just observe that bindings
|
|
49
|
+
* aren't reused across runs).
|
|
50
|
+
*/
|
|
51
|
+
export declare function openSqliteCache(cachePath: string): Promise<BindingCache>;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SQLite-backed cache for LLM-generated step bindings.
|
|
4
|
+
*
|
|
5
|
+
* Why SQLite (not JSON file): better-sqlite3 already an optional dep,
|
|
6
|
+
* cheap concurrent reads, single-file, plays nicely with the .gitignore
|
|
7
|
+
* we already have for `.bdd2pw/`.
|
|
8
|
+
*
|
|
9
|
+
* Schema:
|
|
10
|
+
* llm_cache(
|
|
11
|
+
* key TEXT PRIMARY KEY, -- hash(model + step text + POM signature)
|
|
12
|
+
* value TEXT, -- JSON-stringified StepBinding
|
|
13
|
+
* model TEXT, -- LLM model identifier (audit only)
|
|
14
|
+
* created_at TEXT -- ISO-8601 timestamp
|
|
15
|
+
* )
|
|
16
|
+
*
|
|
17
|
+
* Cache invalidation: keys include the model name, so swapping models
|
|
18
|
+
* (e.g. claude-sonnet-4-6 → claude-opus-4-6) invalidates old entries
|
|
19
|
+
* automatically. To force a full reset, delete the file.
|
|
20
|
+
*/
|
|
21
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
24
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
25
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
26
|
+
}
|
|
27
|
+
Object.defineProperty(o, k2, desc);
|
|
28
|
+
}) : (function(o, m, k, k2) {
|
|
29
|
+
if (k2 === undefined) k2 = k;
|
|
30
|
+
o[k2] = m[k];
|
|
31
|
+
}));
|
|
32
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
33
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
34
|
+
}) : function(o, v) {
|
|
35
|
+
o["default"] = v;
|
|
36
|
+
});
|
|
37
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
38
|
+
var ownKeys = function(o) {
|
|
39
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
40
|
+
var ar = [];
|
|
41
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
42
|
+
return ar;
|
|
43
|
+
};
|
|
44
|
+
return ownKeys(o);
|
|
45
|
+
};
|
|
46
|
+
return function (mod) {
|
|
47
|
+
if (mod && mod.__esModule) return mod;
|
|
48
|
+
var result = {};
|
|
49
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
50
|
+
__setModuleDefault(result, mod);
|
|
51
|
+
return result;
|
|
52
|
+
};
|
|
53
|
+
})();
|
|
54
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
55
|
+
exports.InMemoryCache = void 0;
|
|
56
|
+
exports.openSqliteCache = openSqliteCache;
|
|
57
|
+
const fs = __importStar(require("fs-extra"));
|
|
58
|
+
const path = __importStar(require("path"));
|
|
59
|
+
/**
|
|
60
|
+
* In-memory cache — used when SQLite isn't available (better-sqlite3
|
|
61
|
+
* not installed) or when the caller passes :memory: as the path. Tests
|
|
62
|
+
* use this exclusively to avoid filesystem I/O.
|
|
63
|
+
*/
|
|
64
|
+
class InMemoryCache {
|
|
65
|
+
constructor() {
|
|
66
|
+
this.store = new Map();
|
|
67
|
+
}
|
|
68
|
+
async get(key) {
|
|
69
|
+
return this.store.get(key);
|
|
70
|
+
}
|
|
71
|
+
async set(key, value) {
|
|
72
|
+
this.store.set(key, value);
|
|
73
|
+
}
|
|
74
|
+
async size() {
|
|
75
|
+
return this.store.size;
|
|
76
|
+
}
|
|
77
|
+
async close() {
|
|
78
|
+
/* nothing to close */
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.InMemoryCache = InMemoryCache;
|
|
82
|
+
/**
|
|
83
|
+
* Open a SQLite-backed cache at the given path. better-sqlite3 is an
|
|
84
|
+
* optional dep; if it's not installed (or load fails), fall back to an
|
|
85
|
+
* in-memory cache and warn. The caller can detect the fallback by checking
|
|
86
|
+
* if `cache instanceof InMemoryCache` (or just observe that bindings
|
|
87
|
+
* aren't reused across runs).
|
|
88
|
+
*/
|
|
89
|
+
async function openSqliteCache(cachePath) {
|
|
90
|
+
// Special case: ":memory:" → always in-memory, no disk.
|
|
91
|
+
if (cachePath === ":memory:") {
|
|
92
|
+
return new InMemoryCache();
|
|
93
|
+
}
|
|
94
|
+
let sqlite;
|
|
95
|
+
try {
|
|
96
|
+
// better-sqlite3 is in optionalDependencies. require() so a missing
|
|
97
|
+
// module doesn't break import-time of this file.
|
|
98
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
99
|
+
sqlite = require("better-sqlite3");
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Fall back to in-memory if the optional dep isn't installed. This
|
|
103
|
+
// means LLM bindings won't persist across runs — every call hits the
|
|
104
|
+
// provider — but the scaffolder still works.
|
|
105
|
+
return new InMemoryCache();
|
|
106
|
+
}
|
|
107
|
+
await fs.ensureDir(path.dirname(cachePath));
|
|
108
|
+
const db = sqlite(cachePath);
|
|
109
|
+
db.pragma("journal_mode = WAL");
|
|
110
|
+
db.exec(`
|
|
111
|
+
CREATE TABLE IF NOT EXISTS llm_cache (
|
|
112
|
+
key TEXT PRIMARY KEY,
|
|
113
|
+
value TEXT NOT NULL,
|
|
114
|
+
model TEXT NOT NULL,
|
|
115
|
+
created_at TEXT NOT NULL
|
|
116
|
+
)
|
|
117
|
+
`);
|
|
118
|
+
const getStmt = db.prepare("SELECT value, model, created_at FROM llm_cache WHERE key = ?");
|
|
119
|
+
const setStmt = db.prepare("INSERT OR REPLACE INTO llm_cache (key, value, model, created_at) VALUES (?, ?, ?, ?)");
|
|
120
|
+
const sizeStmt = db.prepare("SELECT COUNT(*) AS n FROM llm_cache");
|
|
121
|
+
return {
|
|
122
|
+
async get(key) {
|
|
123
|
+
const row = getStmt.get(key);
|
|
124
|
+
if (!row)
|
|
125
|
+
return undefined;
|
|
126
|
+
try {
|
|
127
|
+
return {
|
|
128
|
+
binding: JSON.parse(row.value),
|
|
129
|
+
model: row.model,
|
|
130
|
+
createdAt: row.created_at,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Corrupted row — pretend it doesn't exist.
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
async set(key, entry) {
|
|
139
|
+
setStmt.run(key, JSON.stringify(entry.binding), entry.model, entry.createdAt);
|
|
140
|
+
},
|
|
141
|
+
async size() {
|
|
142
|
+
const row = sizeStmt.get();
|
|
143
|
+
return row.n;
|
|
144
|
+
},
|
|
145
|
+
async close() {
|
|
146
|
+
db.close();
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/llm/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Append every successful LLM-generated binding to
|
|
3
|
+
* `<repo>/artefacts/candidate-rules.jsonl`. The offline review pipeline
|
|
4
|
+
* reads this file to propose new deterministic rules — auto-write back
|
|
5
|
+
* into stepMatcher.ts is deferred to v2.1 (see docs/SCOPE.md §17).
|
|
6
|
+
*
|
|
7
|
+
* Why JSONL not a single JSON array: append-only writes are cheaper and
|
|
8
|
+
* safe under concurrent scaffold runs. Newline-delimited JSON also plays
|
|
9
|
+
* well with `jq`, `wc -l`, etc.
|
|
10
|
+
*
|
|
11
|
+
* Why `artefacts/` not `.bdd2pw/`: candidate rules are CONTENT users may
|
|
12
|
+
* commit to source control (the offline pipeline lives in their repo
|
|
13
|
+
* too). `.bdd2pw/` is for pure runtime cache (gitignored). We already
|
|
14
|
+
* use `artefacts/` for heal-events.jsonl from v1.1's self-healing
|
|
15
|
+
* scaffold, so the convention is established.
|
|
16
|
+
*/
|
|
17
|
+
import type { CandidateRuleEntry } from "./types";
|
|
18
|
+
export declare class CandidateRulesWriter {
|
|
19
|
+
private filePath;
|
|
20
|
+
private initialised;
|
|
21
|
+
constructor(repoRoot: string, filename?: string);
|
|
22
|
+
/**
|
|
23
|
+
* Append one entry. Best-effort: if the directory can't be created or
|
|
24
|
+
* the file can't be written, swallow — generation already succeeded;
|
|
25
|
+
* we don't want to break the build for a logging failure.
|
|
26
|
+
*/
|
|
27
|
+
append(entry: CandidateRuleEntry): Promise<void>;
|
|
28
|
+
/** For tests: return all entries written so far. */
|
|
29
|
+
readAll(): Promise<CandidateRuleEntry[]>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Append every successful LLM-generated binding to
|
|
4
|
+
* `<repo>/artefacts/candidate-rules.jsonl`. The offline review pipeline
|
|
5
|
+
* reads this file to propose new deterministic rules — auto-write back
|
|
6
|
+
* into stepMatcher.ts is deferred to v2.1 (see docs/SCOPE.md §17).
|
|
7
|
+
*
|
|
8
|
+
* Why JSONL not a single JSON array: append-only writes are cheaper and
|
|
9
|
+
* safe under concurrent scaffold runs. Newline-delimited JSON also plays
|
|
10
|
+
* well with `jq`, `wc -l`, etc.
|
|
11
|
+
*
|
|
12
|
+
* Why `artefacts/` not `.bdd2pw/`: candidate rules are CONTENT users may
|
|
13
|
+
* commit to source control (the offline pipeline lives in their repo
|
|
14
|
+
* too). `.bdd2pw/` is for pure runtime cache (gitignored). We already
|
|
15
|
+
* use `artefacts/` for heal-events.jsonl from v1.1's self-healing
|
|
16
|
+
* scaffold, so the convention is established.
|
|
17
|
+
*/
|
|
18
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
21
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
22
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
23
|
+
}
|
|
24
|
+
Object.defineProperty(o, k2, desc);
|
|
25
|
+
}) : (function(o, m, k, k2) {
|
|
26
|
+
if (k2 === undefined) k2 = k;
|
|
27
|
+
o[k2] = m[k];
|
|
28
|
+
}));
|
|
29
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
30
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
31
|
+
}) : function(o, v) {
|
|
32
|
+
o["default"] = v;
|
|
33
|
+
});
|
|
34
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
35
|
+
var ownKeys = function(o) {
|
|
36
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
37
|
+
var ar = [];
|
|
38
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
39
|
+
return ar;
|
|
40
|
+
};
|
|
41
|
+
return ownKeys(o);
|
|
42
|
+
};
|
|
43
|
+
return function (mod) {
|
|
44
|
+
if (mod && mod.__esModule) return mod;
|
|
45
|
+
var result = {};
|
|
46
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
47
|
+
__setModuleDefault(result, mod);
|
|
48
|
+
return result;
|
|
49
|
+
};
|
|
50
|
+
})();
|
|
51
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
+
exports.CandidateRulesWriter = void 0;
|
|
53
|
+
const fs = __importStar(require("fs-extra"));
|
|
54
|
+
const path = __importStar(require("path"));
|
|
55
|
+
class CandidateRulesWriter {
|
|
56
|
+
constructor(repoRoot, filename = "candidate-rules.jsonl") {
|
|
57
|
+
this.initialised = false;
|
|
58
|
+
this.filePath = path.join(repoRoot, "artefacts", filename);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Append one entry. Best-effort: if the directory can't be created or
|
|
62
|
+
* the file can't be written, swallow — generation already succeeded;
|
|
63
|
+
* we don't want to break the build for a logging failure.
|
|
64
|
+
*/
|
|
65
|
+
async append(entry) {
|
|
66
|
+
try {
|
|
67
|
+
if (!this.initialised) {
|
|
68
|
+
await fs.ensureDir(path.dirname(this.filePath));
|
|
69
|
+
this.initialised = true;
|
|
70
|
+
}
|
|
71
|
+
await fs.appendFile(this.filePath, JSON.stringify(entry) + "\n", "utf8");
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
/* best-effort */
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** For tests: return all entries written so far. */
|
|
78
|
+
async readAll() {
|
|
79
|
+
if (!(await fs.pathExists(this.filePath)))
|
|
80
|
+
return [];
|
|
81
|
+
const raw = await fs.readFile(this.filePath, "utf8");
|
|
82
|
+
return raw
|
|
83
|
+
.split("\n")
|
|
84
|
+
.filter((l) => l.trim())
|
|
85
|
+
.map((l) => JSON.parse(l));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
exports.CandidateRulesWriter = CandidateRulesWriter;
|
|
89
|
+
//# sourceMappingURL=candidateRules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"candidateRules.js","sourceRoot":"","sources":["../../src/llm/candidateRules.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,6CAA+B;AAC/B,2CAA6B;AAG7B,MAAa,oBAAoB;IAI/B,YAAY,QAAgB,EAAE,QAAQ,GAAG,uBAAuB;QAFxD,gBAAW,GAAG,KAAK,CAAC;QAG1B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;IAC7D,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,MAAM,CAAC,KAAyB;QACpC,IAAI,CAAC;YACH,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;gBACtB,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAChD,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;YAC1B,CAAC;YACD,MAAM,EAAE,CAAC,UAAU,CACjB,IAAI,CAAC,QAAQ,EACb,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,EAC5B,MAAM,CACP,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,iBAAiB;QACnB,CAAC;IACH,CAAC;IAED,oDAAoD;IACpD,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAAE,OAAO,EAAE,CAAC;QACrD,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACrD,OAAO,GAAG;aACP,KAAK,CAAC,IAAI,CAAC;aACX,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aACvB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAuB,CAAC,CAAC;IACrD,CAAC;CACF;AAtCD,oDAsCC"}
|