@vijaypjavvadi/bdd2pw 2.0.1 → 2.2.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.
- package/CHANGELOG.md +247 -0
- package/dist/cli.js +13 -0
- package/dist/cli.js.map +1 -1
- package/dist/emitters/facade.js +35 -13
- package/dist/emitters/facade.js.map +1 -1
- package/dist/index.js +47 -2
- package/dist/index.js.map +1 -1
- package/dist/llm/anthropicClient.d.ts +18 -0
- package/dist/llm/anthropicClient.js +62 -3
- package/dist/llm/anthropicClient.js.map +1 -1
- package/dist/llm/cache.d.ts +41 -6
- package/dist/llm/cache.js +93 -56
- package/dist/llm/cache.js.map +1 -1
- package/dist/llm/governanceClient.d.ts +2 -1
- package/dist/llm/governanceClient.js +13 -2
- package/dist/llm/governanceClient.js.map +1 -1
- package/dist/llm/llmStepMatcher.d.ts +11 -0
- package/dist/llm/llmStepMatcher.js +26 -1
- package/dist/llm/llmStepMatcher.js.map +1 -1
- package/dist/llm/types.d.ts +43 -1
- package/dist/transformers/stepMatcher.js +15 -2
- package/dist/transformers/stepMatcher.js.map +1 -1
- package/dist/transformers/stepNormalizer.d.ts +36 -0
- package/dist/transformers/stepNormalizer.js +153 -0
- package/dist/transformers/stepNormalizer.js.map +1 -0
- package/dist/types.d.ts +6 -0
- package/package.json +1 -1
- package/templates/heal.ts.tmpl +302 -58
|
@@ -27,23 +27,64 @@ const cache_1 = require("./cache");
|
|
|
27
27
|
const DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
28
28
|
const DEFAULT_GOVERNANCE_URL = "http://localhost:4900";
|
|
29
29
|
const DEFAULT_MAX_CALLS = 50;
|
|
30
|
+
// v2.2.0 — explicit timeouts. Without these, a wedged sidecar / slow
|
|
31
|
+
// Anthropic response stalls the scaffold for 8+ minutes (cloud-jobs
|
|
32
|
+
// container hang). Defaults aren't conservative — production usually
|
|
33
|
+
// completes in <5s; if a single step exceeds these, the operator
|
|
34
|
+
// almost certainly wants to bail out and look at it.
|
|
35
|
+
const DEFAULT_STEP_TIMEOUT_MS = 60000;
|
|
36
|
+
const DEFAULT_PROVIDER_TIMEOUT_MS = 30000;
|
|
37
|
+
const DEFAULT_GOVERNANCE_TIMEOUT_MS = 15000;
|
|
30
38
|
class AnthropicLLMClient {
|
|
31
39
|
constructor(opts, cachePathDefault) {
|
|
32
40
|
this.cache = null;
|
|
33
41
|
this.callsCounter = 0;
|
|
34
42
|
this.attemptsCounter = 0;
|
|
35
43
|
this.anthropic = null; // lazy-loaded SDK instance
|
|
44
|
+
/**
|
|
45
|
+
* v2.0.2 — when SQLite cache fails to load, we fall back to an
|
|
46
|
+
* in-memory cache and surface this once. `cacheFallbackReason` carries
|
|
47
|
+
* the load error so callers can log it.
|
|
48
|
+
*/
|
|
49
|
+
this.cachePersistent = null;
|
|
36
50
|
this.model = opts.model ?? DEFAULT_MODEL;
|
|
37
51
|
this.apiKey = opts.apiKey ?? process.env.ANTHROPIC_API_KEY ?? "";
|
|
38
52
|
this.maxCalls = opts.maxCalls ?? DEFAULT_MAX_CALLS;
|
|
39
53
|
this.skipGovernance = opts.skipGovernance ?? false;
|
|
40
54
|
this.log = opts.log ?? (() => { });
|
|
41
|
-
this.
|
|
55
|
+
this.stepTimeoutMs = opts.stepTimeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
|
|
56
|
+
this.providerTimeoutMs =
|
|
57
|
+
opts.providerTimeoutMs ?? DEFAULT_PROVIDER_TIMEOUT_MS;
|
|
58
|
+
this.governance = new governanceClient_1.GovernanceClient(opts.governanceUrl ?? DEFAULT_GOVERNANCE_URL, opts.governanceTimeoutMs ?? DEFAULT_GOVERNANCE_TIMEOUT_MS);
|
|
42
59
|
this.cachePath = opts.cachePath ?? cachePathDefault;
|
|
43
60
|
}
|
|
44
61
|
async ensureCache() {
|
|
45
62
|
if (!this.cache) {
|
|
46
|
-
|
|
63
|
+
// v2.0.2: openSqliteCache no longer throws — on any failure it
|
|
64
|
+
// falls back to in-memory and surfaces the reason. We stash the
|
|
65
|
+
// result so callers / scaffold() can log a single warning instead
|
|
66
|
+
// of one per LLM call.
|
|
67
|
+
const result = await (0, cache_1.openSqliteCache)(this.cachePath);
|
|
68
|
+
this.cache = result.cache;
|
|
69
|
+
this.cachePersistent = result.persistent;
|
|
70
|
+
this.cacheFallbackReason = result.fallbackReason;
|
|
71
|
+
if (!result.persistent && result.fallbackReason) {
|
|
72
|
+
// v2.2.0 — emit BOTH a structured cache_fallback event (for the
|
|
73
|
+
// wired pino logger to surface visibly in the scaffold log) AND
|
|
74
|
+
// the legacy `error` event (for backwards-compat with callers
|
|
75
|
+
// already consuming the LLMLogEvent stream). The scaffold log
|
|
76
|
+
// gets a one-line warning at the moment the fallback fires —
|
|
77
|
+
// operators no longer have to grep BDD_REVIEW.md to know.
|
|
78
|
+
this.log({
|
|
79
|
+
kind: "cache_fallback",
|
|
80
|
+
reason: result.fallbackReason,
|
|
81
|
+
});
|
|
82
|
+
this.log({
|
|
83
|
+
kind: "error",
|
|
84
|
+
phase: "cache_load",
|
|
85
|
+
message: `Cache backend unavailable, falling back to in-memory for this run. Reason: ${result.fallbackReason}`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
47
88
|
}
|
|
48
89
|
return this.cache;
|
|
49
90
|
}
|
|
@@ -60,6 +101,19 @@ class AnthropicLLMClient {
|
|
|
60
101
|
callsAttempted() {
|
|
61
102
|
return this.attemptsCounter;
|
|
62
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* v2.0.2 — true when the SQLite cache loaded successfully, false when
|
|
106
|
+
* we degraded to in-memory. `null` until ensureCache() is called for
|
|
107
|
+
* the first time. Callers can use this to print a one-time warning at
|
|
108
|
+
* the end of a scaffold run.
|
|
109
|
+
*/
|
|
110
|
+
cacheBackendPersistent() {
|
|
111
|
+
return this.cachePersistent;
|
|
112
|
+
}
|
|
113
|
+
/** v2.0.2 — when persistent is false, the underlying load error string. */
|
|
114
|
+
cacheBackendFallbackReason() {
|
|
115
|
+
return this.cacheFallbackReason;
|
|
116
|
+
}
|
|
63
117
|
async generateBinding(input) {
|
|
64
118
|
const start = Date.now();
|
|
65
119
|
const key = (0, prompt_1.cacheKey)(input, this.model);
|
|
@@ -142,13 +196,18 @@ class AnthropicLLMClient {
|
|
|
142
196
|
promptBytes: sanitisedUserPrompt.length,
|
|
143
197
|
});
|
|
144
198
|
const callStart = Date.now();
|
|
199
|
+
// v2.2.0 — explicit per-call timeout. The Anthropic SDK accepts a
|
|
200
|
+
// `timeout` option (milliseconds). Without it, a stalled response
|
|
201
|
+
// would wait the SDK's default (10 minutes) which is far too long
|
|
202
|
+
// for a scaffold loop; cloud-jobs runs hit the 8-minute container
|
|
203
|
+
// limit before bdd2pw printed any diagnostic.
|
|
145
204
|
const resp = await anthropic.messages.create({
|
|
146
205
|
model: this.model,
|
|
147
206
|
max_tokens: 1024,
|
|
148
207
|
temperature: 0,
|
|
149
208
|
system: prompt_1.SYSTEM_PROMPT,
|
|
150
209
|
messages: [{ role: "user", content: sanitisedUserPrompt }],
|
|
151
|
-
});
|
|
210
|
+
}, { timeout: this.providerTimeoutMs });
|
|
152
211
|
this.callsCounter += 1;
|
|
153
212
|
const callLatency = Date.now() - callStart;
|
|
154
213
|
// The SDK returns content as an array of blocks; first block is text.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"anthropicClient.js","sourceRoot":"","sources":["../../src/llm/anthropicClient.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;GAkBG;;;
|
|
1
|
+
{"version":3,"file":"anthropicClient.js","sourceRoot":"","sources":["../../src/llm/anthropicClient.ts"],"names":[],"mappings":"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"}
|
package/dist/llm/cache.d.ts
CHANGED
|
@@ -42,10 +42,45 @@ export declare class InMemoryCache implements BindingCache {
|
|
|
42
42
|
close(): Promise<void>;
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* in-memory
|
|
48
|
-
*
|
|
49
|
-
* aren't reused across runs).
|
|
45
|
+
* Result returned by openSqliteCache — exposes both the cache and whether
|
|
46
|
+
* the SQLite backend was actually loaded (vs. having fallen back to
|
|
47
|
+
* in-memory). Callers use the flag to log a single warning per scaffold
|
|
48
|
+
* run, not per LLM call.
|
|
50
49
|
*/
|
|
51
|
-
export
|
|
50
|
+
export interface OpenCacheResult {
|
|
51
|
+
cache: BindingCache;
|
|
52
|
+
/** True when the SQLite backend loaded successfully. */
|
|
53
|
+
persistent: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* When persistent === false, this carries the underlying load error so
|
|
56
|
+
* the caller can log a useful warning (e.g. "NODE_MODULE_VERSION 127
|
|
57
|
+
* mismatch — re-run with `npm rebuild better-sqlite3`").
|
|
58
|
+
*/
|
|
59
|
+
fallbackReason?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Open a SQLite-backed cache at the given path. `better-sqlite3` is an
|
|
63
|
+
* optional dep; the load can fail for several reasons:
|
|
64
|
+
*
|
|
65
|
+
* 1. Module not installed at all (`require("better-sqlite3")` throws).
|
|
66
|
+
* 2. Module loads (JS) but the native `.node` binding doesn't —
|
|
67
|
+
* classic NODE_MODULE_VERSION mismatch when a Node 22-prebuilt
|
|
68
|
+
* binary runs in a Node 18 / 20 environment.
|
|
69
|
+
* 3. File system errors — `ensureDir` denied, read-only mount, etc.
|
|
70
|
+
* 4. Corrupted .sqlite file from a previous run.
|
|
71
|
+
*
|
|
72
|
+
* v2.0.2: ALL of these now fall back to an in-memory cache. v2.0/2.0.1
|
|
73
|
+
* only caught case 1 — case 2 (the common one in cloud-jobs runs)
|
|
74
|
+
* propagated up and aborted the entire LLM fallback. See CHANGELOG.
|
|
75
|
+
*
|
|
76
|
+
* The caller decides whether to log/surface the fallback by inspecting
|
|
77
|
+
* `result.persistent` and `result.fallbackReason`.
|
|
78
|
+
*/
|
|
79
|
+
export declare function openSqliteCache(cachePath: string,
|
|
80
|
+
/**
|
|
81
|
+
* Test-only injection point. Production callers leave this undefined
|
|
82
|
+
* and we use `require("better-sqlite3")` directly. Tests pass a
|
|
83
|
+
* throwing stub to simulate native binding load failures (vi.mock
|
|
84
|
+
* doesn't intercept require()). Not part of the public API.
|
|
85
|
+
*/
|
|
86
|
+
_loader?: () => unknown): Promise<OpenCacheResult>;
|
package/dist/llm/cache.js
CHANGED
|
@@ -56,6 +56,7 @@ exports.InMemoryCache = void 0;
|
|
|
56
56
|
exports.openSqliteCache = openSqliteCache;
|
|
57
57
|
const fs = __importStar(require("fs-extra"));
|
|
58
58
|
const path = __importStar(require("path"));
|
|
59
|
+
const commentSafe_1 = require("../utils/commentSafe");
|
|
59
60
|
/**
|
|
60
61
|
* In-memory cache — used when SQLite isn't available (better-sqlite3
|
|
61
62
|
* not installed) or when the caller passes :memory: as the path. Tests
|
|
@@ -80,71 +81,107 @@ class InMemoryCache {
|
|
|
80
81
|
}
|
|
81
82
|
exports.InMemoryCache = InMemoryCache;
|
|
82
83
|
/**
|
|
83
|
-
* Open a SQLite-backed cache at the given path. better-sqlite3 is an
|
|
84
|
-
* optional dep;
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
84
|
+
* Open a SQLite-backed cache at the given path. `better-sqlite3` is an
|
|
85
|
+
* optional dep; the load can fail for several reasons:
|
|
86
|
+
*
|
|
87
|
+
* 1. Module not installed at all (`require("better-sqlite3")` throws).
|
|
88
|
+
* 2. Module loads (JS) but the native `.node` binding doesn't —
|
|
89
|
+
* classic NODE_MODULE_VERSION mismatch when a Node 22-prebuilt
|
|
90
|
+
* binary runs in a Node 18 / 20 environment.
|
|
91
|
+
* 3. File system errors — `ensureDir` denied, read-only mount, etc.
|
|
92
|
+
* 4. Corrupted .sqlite file from a previous run.
|
|
93
|
+
*
|
|
94
|
+
* v2.0.2: ALL of these now fall back to an in-memory cache. v2.0/2.0.1
|
|
95
|
+
* only caught case 1 — case 2 (the common one in cloud-jobs runs)
|
|
96
|
+
* propagated up and aborted the entire LLM fallback. See CHANGELOG.
|
|
97
|
+
*
|
|
98
|
+
* The caller decides whether to log/surface the fallback by inspecting
|
|
99
|
+
* `result.persistent` and `result.fallbackReason`.
|
|
100
|
+
*/
|
|
101
|
+
async function openSqliteCache(cachePath,
|
|
102
|
+
/**
|
|
103
|
+
* Test-only injection point. Production callers leave this undefined
|
|
104
|
+
* and we use `require("better-sqlite3")` directly. Tests pass a
|
|
105
|
+
* throwing stub to simulate native binding load failures (vi.mock
|
|
106
|
+
* doesn't intercept require()). Not part of the public API.
|
|
88
107
|
*/
|
|
89
|
-
|
|
108
|
+
_loader) {
|
|
90
109
|
// Special case: ":memory:" → always in-memory, no disk.
|
|
91
110
|
if (cachePath === ":memory:") {
|
|
92
|
-
return new InMemoryCache();
|
|
111
|
+
return { cache: new InMemoryCache(), persistent: false };
|
|
93
112
|
}
|
|
94
|
-
let sqlite;
|
|
95
113
|
try {
|
|
96
114
|
// better-sqlite3 is in optionalDependencies. require() so a missing
|
|
97
115
|
// module doesn't break import-time of this file.
|
|
98
116
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
99
|
-
sqlite =
|
|
117
|
+
const sqlite = _loader
|
|
118
|
+
? _loader()
|
|
119
|
+
: require("better-sqlite3");
|
|
120
|
+
await fs.ensureDir(path.dirname(cachePath));
|
|
121
|
+
// v2.0.2: this line in particular is what previously escaped the
|
|
122
|
+
// try/catch — `sqlite(cachePath)` triggers the native binding load,
|
|
123
|
+
// which throws on NODE_MODULE_VERSION mismatch.
|
|
124
|
+
const db = sqlite(cachePath);
|
|
125
|
+
db.pragma("journal_mode = WAL");
|
|
126
|
+
db.exec(`
|
|
127
|
+
CREATE TABLE IF NOT EXISTS llm_cache (
|
|
128
|
+
key TEXT PRIMARY KEY,
|
|
129
|
+
value TEXT NOT NULL,
|
|
130
|
+
model TEXT NOT NULL,
|
|
131
|
+
created_at TEXT NOT NULL
|
|
132
|
+
)
|
|
133
|
+
`);
|
|
134
|
+
const getStmt = db.prepare("SELECT value, model, created_at FROM llm_cache WHERE key = ?");
|
|
135
|
+
const setStmt = db.prepare("INSERT OR REPLACE INTO llm_cache (key, value, model, created_at) VALUES (?, ?, ?, ?)");
|
|
136
|
+
const sizeStmt = db.prepare("SELECT COUNT(*) AS n FROM llm_cache");
|
|
137
|
+
const cache = {
|
|
138
|
+
async get(key) {
|
|
139
|
+
const row = getStmt.get(key);
|
|
140
|
+
if (!row)
|
|
141
|
+
return undefined;
|
|
142
|
+
try {
|
|
143
|
+
return {
|
|
144
|
+
binding: JSON.parse(row.value),
|
|
145
|
+
model: row.model,
|
|
146
|
+
createdAt: row.created_at,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Corrupted row — pretend it doesn't exist.
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
async set(key, entry) {
|
|
155
|
+
setStmt.run(key, JSON.stringify(entry.binding), entry.model, entry.createdAt);
|
|
156
|
+
},
|
|
157
|
+
async size() {
|
|
158
|
+
const row = sizeStmt.get();
|
|
159
|
+
return row.n;
|
|
160
|
+
},
|
|
161
|
+
async close() {
|
|
162
|
+
db.close();
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
return { cache, persistent: true };
|
|
100
166
|
}
|
|
101
|
-
catch {
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
|
|
167
|
+
catch (err) {
|
|
168
|
+
// ANY failure during SQLite setup → fall back to in-memory.
|
|
169
|
+
// Bindings won't persist across runs; LLM is called every time
|
|
170
|
+
// (subject to the per-scaffold cache the InMemoryCache still
|
|
171
|
+
// provides within a single run). The scaffolder still works.
|
|
172
|
+
//
|
|
173
|
+
// Use flattenForComment (the same helper that protects // TODO
|
|
174
|
+
// lines in 2.0.1) so the fallback reason is single-line but
|
|
175
|
+
// PRESERVES all the diagnostic info — a typical native-binding
|
|
176
|
+
// load error spans 4-5 lines, and the most useful piece
|
|
177
|
+
// (NODE_MODULE_VERSION number) is on line 3. `.split("\n")[0]`
|
|
178
|
+
// would lose it; flattenForComment keeps everything joined
|
|
179
|
+
// with ` | ` separators.
|
|
180
|
+
return {
|
|
181
|
+
cache: new InMemoryCache(),
|
|
182
|
+
persistent: false,
|
|
183
|
+
fallbackReason: (0, commentSafe_1.flattenForComment)(err instanceof Error ? err.message : err),
|
|
184
|
+
};
|
|
106
185
|
}
|
|
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
186
|
}
|
|
150
187
|
//# sourceMappingURL=cache.js.map
|
package/dist/llm/cache.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/llm/cache.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;GAkBG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/llm/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -37,7 +37,8 @@ export declare class GovernanceError extends Error {
|
|
|
37
37
|
}
|
|
38
38
|
export declare class GovernanceClient {
|
|
39
39
|
private baseUrl;
|
|
40
|
-
|
|
40
|
+
private timeoutMs;
|
|
41
|
+
constructor(baseUrl: string, timeoutMs?: number);
|
|
41
42
|
sanitiseCode(content: string): Promise<SanitiseResult>;
|
|
42
43
|
/** Probe `/health` — used at startup to fail-fast before the first scaffold. */
|
|
43
44
|
health(): Promise<boolean>;
|
|
@@ -47,17 +47,25 @@ class GovernanceError extends Error {
|
|
|
47
47
|
}
|
|
48
48
|
exports.GovernanceError = GovernanceError;
|
|
49
49
|
class GovernanceClient {
|
|
50
|
-
constructor(baseUrl) {
|
|
50
|
+
constructor(baseUrl, timeoutMs = 15000) {
|
|
51
51
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
52
|
+
this.timeoutMs = timeoutMs;
|
|
52
53
|
}
|
|
53
54
|
async sanitiseCode(content) {
|
|
54
55
|
const url = `${this.baseUrl}/sanitize`;
|
|
55
56
|
let res;
|
|
56
57
|
try {
|
|
58
|
+
// v2.2.0 — add explicit timeouts. Default undici behaviour is no
|
|
59
|
+
// timeout; a wedged sidecar would stall the entire scaffold for
|
|
60
|
+
// 8+ minutes (production cloud-jobs hang). bodyTimeout covers the
|
|
61
|
+
// POST upload + response stream; headersTimeout covers initial
|
|
62
|
+
// connection handshake.
|
|
57
63
|
res = await (0, undici_1.request)(url, {
|
|
58
64
|
method: "POST",
|
|
59
65
|
headers: { "content-type": "application/json" },
|
|
60
66
|
body: JSON.stringify({ kind: "code", content }),
|
|
67
|
+
bodyTimeout: this.timeoutMs,
|
|
68
|
+
headersTimeout: this.timeoutMs,
|
|
61
69
|
});
|
|
62
70
|
}
|
|
63
71
|
catch (err) {
|
|
@@ -76,7 +84,10 @@ class GovernanceClient {
|
|
|
76
84
|
/** Probe `/health` — used at startup to fail-fast before the first scaffold. */
|
|
77
85
|
async health() {
|
|
78
86
|
try {
|
|
79
|
-
const res = await (0, undici_1.request)(`${this.baseUrl}/health
|
|
87
|
+
const res = await (0, undici_1.request)(`${this.baseUrl}/health`, {
|
|
88
|
+
bodyTimeout: this.timeoutMs,
|
|
89
|
+
headersTimeout: this.timeoutMs,
|
|
90
|
+
});
|
|
80
91
|
return res.statusCode === 200;
|
|
81
92
|
}
|
|
82
93
|
catch {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"governanceClient.js","sourceRoot":"","sources":["../../src/llm/governanceClient.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;;;AAEH,mCAAiC;AAOjC,MAAa,0BAA2B,SAAQ,KAAK;IACnD,YAAY,GAAW,EAAE,KAAe;QACtC,KAAK,CACH,wCAAwC,GAAG,mBAAmB;YAC5D,qEAAqE;YACrE,uEAAuE;YACvE,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,qBAAqB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CACvE,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;IAC3C,CAAC;CACF;AAVD,gEAUC;AAED,wEAAwE;AACxE,MAAa,eAAgB,SAAQ,KAAK;IACxC,YAAY,OAAe,EAAkB,KAAe;QAC1D,KAAK,CAAC,OAAO,CAAC,CAAC;QAD4B,UAAK,GAAL,KAAK,CAAU;QAE1D,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AALD,0CAKC;AAED,MAAa,gBAAgB;
|
|
1
|
+
{"version":3,"file":"governanceClient.js","sourceRoot":"","sources":["../../src/llm/governanceClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,6 +24,17 @@ export interface MatchStepWithLLMContext {
|
|
|
24
24
|
scaffoldId: string;
|
|
25
25
|
/** Target URL — passed to LLM for additional context. */
|
|
26
26
|
url?: string;
|
|
27
|
+
/**
|
|
28
|
+
* v2.2.0 — per-step deadline for the LLM fallback path. If
|
|
29
|
+
* `llm.generateBinding()` doesn't resolve within this many ms, we
|
|
30
|
+
* abandon the step (TODO + warning) and proceed to the next. Default:
|
|
31
|
+
* 60_000 (60 s). Independent from the AnthropicLLMClient's own
|
|
32
|
+
* `stepTimeoutMs` — this is the OUTER guard at the orchestration
|
|
33
|
+
* layer; the client's is the INNER guard around the SDK call. Both
|
|
34
|
+
* exist because either layer alone could deadlock (e.g. cache lookup
|
|
35
|
+
* stalled on disk, governance fetch stalled below the SDK).
|
|
36
|
+
*/
|
|
37
|
+
stepDeadlineMs?: number;
|
|
27
38
|
}
|
|
28
39
|
export declare function matchStepWithLLM(step: StepIR, pom: PageObjectIR, pageVar: string, ctx: MatchStepWithLLMContext): Promise<StepBinding>;
|
|
29
40
|
/**
|
|
@@ -32,14 +32,37 @@ async function matchStepWithLLM(step, pom, pageVar, ctx) {
|
|
|
32
32
|
if (ctx.llm.budgetExhausted())
|
|
33
33
|
return ruleBased;
|
|
34
34
|
// 5) Try the LLM. Soft-fail back to rule-based on any error.
|
|
35
|
+
// v2.2.0 — Promise.race against a step-level deadline so a wedged
|
|
36
|
+
// call doesn't hang the entire scaffold loop. Default 60s; override
|
|
37
|
+
// via ctx.stepDeadlineMs. Returning a synthetic error result on
|
|
38
|
+
// timeout means the original rule warning gets the
|
|
39
|
+
// "(LLM fallback also failed: step deadline exceeded)" annotation
|
|
40
|
+
// and execution continues.
|
|
41
|
+
const deadlineMs = ctx.stepDeadlineMs ?? 60000;
|
|
42
|
+
let timeoutId;
|
|
35
43
|
try {
|
|
36
|
-
const
|
|
44
|
+
const generation = ctx.llm.generateBinding({
|
|
37
45
|
step,
|
|
38
46
|
pom,
|
|
39
47
|
pageVar,
|
|
40
48
|
url: ctx.url,
|
|
41
49
|
scaffoldId: ctx.scaffoldId,
|
|
42
50
|
});
|
|
51
|
+
const deadline = new Promise((resolve) => {
|
|
52
|
+
timeoutId = setTimeout(() => resolve({
|
|
53
|
+
error: `step deadline exceeded after ${deadlineMs}ms`,
|
|
54
|
+
fromCache: false,
|
|
55
|
+
}), deadlineMs);
|
|
56
|
+
// Don't keep the process alive just for a pending watchdog; the
|
|
57
|
+
// scaffold's HTTP server (when used) needs to exit on SIGINT even
|
|
58
|
+
// if a step is mid-flight.
|
|
59
|
+
timeoutId.unref?.();
|
|
60
|
+
});
|
|
61
|
+
const result = await Promise.race([generation, deadline]);
|
|
62
|
+
// Cancel the watchdog if the LLM resolved first — otherwise we'd
|
|
63
|
+
// leak one timer per step until the deadline fires.
|
|
64
|
+
if (timeoutId)
|
|
65
|
+
clearTimeout(timeoutId);
|
|
43
66
|
if (!result.binding) {
|
|
44
67
|
// Annotate the warning with the LLM error so BDD_REVIEW shows why
|
|
45
68
|
// the LLM didn't help. The step still falls through to TODO.
|
|
@@ -78,6 +101,8 @@ async function matchStepWithLLM(step, pom, pageVar, ctx) {
|
|
|
78
101
|
// v2.0.1: flattenForComment so multi-line error messages (the classic
|
|
79
102
|
// case: better-sqlite3 NODE_MODULE_VERSION mismatch — a 5-line stack
|
|
80
103
|
// trace) don't bleed past the `//` in the emitted spec.
|
|
104
|
+
if (timeoutId)
|
|
105
|
+
clearTimeout(timeoutId);
|
|
81
106
|
const message = (0, commentSafe_1.flattenForComment)(err instanceof Error ? err.message : err);
|
|
82
107
|
return {
|
|
83
108
|
...ruleBased,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llmStepMatcher.js","sourceRoot":"","sources":["../../src/llm/llmStepMatcher.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;
|
|
1
|
+
{"version":3,"file":"llmStepMatcher.js","sourceRoot":"","sources":["../../src/llm/llmStepMatcher.ts"],"names":[],"mappings":"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"}
|
package/dist/llm/types.d.ts
CHANGED
|
@@ -39,9 +39,34 @@ export interface LLMClientOptions {
|
|
|
39
39
|
skipGovernance?: boolean;
|
|
40
40
|
/**
|
|
41
41
|
* Optional logger callback (defaults to no-op). Useful in tests to
|
|
42
|
-
* capture prompts/responses without polluting stdout.
|
|
42
|
+
* capture prompts/responses without polluting stdout. v2.2.0 — the
|
|
43
|
+
* scaffold orchestrator wires bdd2pw's pino logger here so cache
|
|
44
|
+
* fallbacks, governance latencies, and provider-call timings show up
|
|
45
|
+
* in the standard JSON-formatted scaffold log without operators
|
|
46
|
+
* having to parse review items.
|
|
43
47
|
*/
|
|
44
48
|
log?: (event: LLMLogEvent) => void;
|
|
49
|
+
/**
|
|
50
|
+
* v2.2.0 — per-step deadline for the WHOLE LLM fallback path
|
|
51
|
+
* (cache lookup → governance sanitise → Anthropic call → parse).
|
|
52
|
+
* On expiry, generateBinding() returns `{ error: "deadline exceeded" }`
|
|
53
|
+
* so the step lands as `// TODO:` instead of hanging the scaffold.
|
|
54
|
+
* Default: 60_000 (60 s).
|
|
55
|
+
*/
|
|
56
|
+
stepTimeoutMs?: number;
|
|
57
|
+
/**
|
|
58
|
+
* v2.2.0 — per-call timeout passed to the Anthropic SDK. Independent
|
|
59
|
+
* from `stepTimeoutMs` — a single Anthropic call must finish in
|
|
60
|
+
* `providerTimeoutMs`, while the entire step (including governance
|
|
61
|
+
* sanitisation, cache, parse) must finish in `stepTimeoutMs`.
|
|
62
|
+
* Default: 30_000 (30 s).
|
|
63
|
+
*/
|
|
64
|
+
providerTimeoutMs?: number;
|
|
65
|
+
/**
|
|
66
|
+
* v2.2.0 — per-call timeout for governance sidecar `/sanitize`.
|
|
67
|
+
* Default: 15_000 (15 s).
|
|
68
|
+
*/
|
|
69
|
+
governanceTimeoutMs?: number;
|
|
45
70
|
}
|
|
46
71
|
export type LLMLogEvent = {
|
|
47
72
|
kind: "cache_hit";
|
|
@@ -49,6 +74,9 @@ export type LLMLogEvent = {
|
|
|
49
74
|
} | {
|
|
50
75
|
kind: "cache_miss";
|
|
51
76
|
key: string;
|
|
77
|
+
} | {
|
|
78
|
+
kind: "cache_fallback";
|
|
79
|
+
reason: string;
|
|
52
80
|
} | {
|
|
53
81
|
kind: "sanitise_start";
|
|
54
82
|
bytes: number;
|
|
@@ -69,6 +97,10 @@ export type LLMLogEvent = {
|
|
|
69
97
|
} | {
|
|
70
98
|
kind: "binding_parsed";
|
|
71
99
|
binding: StepBinding;
|
|
100
|
+
} | {
|
|
101
|
+
kind: "step_deadline_exceeded";
|
|
102
|
+
stepText: string;
|
|
103
|
+
deadlineMs: number;
|
|
72
104
|
} | {
|
|
73
105
|
kind: "error";
|
|
74
106
|
phase: string;
|
|
@@ -117,6 +149,16 @@ export interface LLMClient {
|
|
|
117
149
|
* should fall back to `callsMade()` for their stats line.
|
|
118
150
|
*/
|
|
119
151
|
callsAttempted?(): number;
|
|
152
|
+
/**
|
|
153
|
+
* v2.0.2+ — null until first generateBinding(), then true if the
|
|
154
|
+
* persistent SQLite cache loaded, false if we fell back to in-memory
|
|
155
|
+
* (native binding mismatch, missing module, fs error, etc.). Callers
|
|
156
|
+
* surface this in the scaffold review report so operators know cache
|
|
157
|
+
* isn't durable across runs.
|
|
158
|
+
*/
|
|
159
|
+
cacheBackendPersistent?(): boolean | null;
|
|
160
|
+
/** v2.0.2+ — when persistent is false, the underlying load reason. */
|
|
161
|
+
cacheBackendFallbackReason?(): string | undefined;
|
|
120
162
|
/** Close any underlying resources (cache db handle, etc.). */
|
|
121
163
|
close?(): Promise<void>;
|
|
122
164
|
}
|
|
@@ -19,6 +19,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
19
19
|
exports.matchSteps = matchSteps;
|
|
20
20
|
exports.matchStep = matchStep;
|
|
21
21
|
const naming_1 = require("../utils/naming");
|
|
22
|
+
const stepNormalizer_1 = require("./stepNormalizer");
|
|
22
23
|
/**
|
|
23
24
|
* Match every step in every scenario to a POM call / assertion / TODO.
|
|
24
25
|
* Background steps are also threaded through if the orchestrator passes
|
|
@@ -36,10 +37,22 @@ async function matchSteps(input) {
|
|
|
36
37
|
* Falls through to a warning binding when nothing matches.
|
|
37
38
|
*/
|
|
38
39
|
function matchStep(step, pom, pageVar) {
|
|
40
|
+
// Belt-and-suspenders normalisation (Block 2 #7). Apply BEFORE the rule
|
|
41
|
+
// table runs so common LLM phrasing drift ("in the field" vs "into the
|
|
42
|
+
// field", smart curly quotes from copy-paste, trailing parentheticals,
|
|
43
|
+
// "user remains on login page" vs canonical "URL remains on the login
|
|
44
|
+
// page") all map back to the canonical forms the rule regexes expect.
|
|
45
|
+
// The `step` object itself is treated as immutable — we work off a
|
|
46
|
+
// local copy so the original Gherkin text is preserved for warnings,
|
|
47
|
+
// test.step labels, and downstream review reports.
|
|
48
|
+
const normalisedText = (0, stepNormalizer_1.normalizeStepText)(step.text);
|
|
49
|
+
const normalisedStep = normalisedText === step.text
|
|
50
|
+
? step
|
|
51
|
+
: { ...step, text: normalisedText };
|
|
39
52
|
for (const rule of RULES) {
|
|
40
|
-
const m =
|
|
53
|
+
const m = normalisedStep.text.match(rule.pattern);
|
|
41
54
|
if (m) {
|
|
42
|
-
const result = rule.build(m,
|
|
55
|
+
const result = rule.build(m, normalisedStep, pom, pageVar);
|
|
43
56
|
if (result)
|
|
44
57
|
return result;
|
|
45
58
|
}
|