@specverse/engines 6.53.1 → 6.63.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/dist/ai/analyse-runner.d.ts.map +1 -1
- package/dist/ai/analyse-runner.js +22 -1
- package/dist/ai/analyse-runner.js.map +1 -1
- package/dist/analyse-prepass/adapters/module-functions.d.ts +25 -0
- package/dist/analyse-prepass/adapters/module-functions.d.ts.map +1 -1
- package/dist/analyse-prepass/adapters/module-functions.js +54 -0
- package/dist/analyse-prepass/adapters/module-functions.js.map +1 -1
- package/dist/analyse-prepass/backends/gitnexus.d.ts +28 -0
- package/dist/analyse-prepass/backends/gitnexus.d.ts.map +1 -1
- package/dist/analyse-prepass/backends/gitnexus.js +36 -2
- package/dist/analyse-prepass/backends/gitnexus.js.map +1 -1
- package/dist/analyse-prepass/index.d.ts.map +1 -1
- package/dist/analyse-prepass/index.js +17 -1
- package/dist/analyse-prepass/index.js.map +1 -1
- package/dist/libs/instance-factories/controllers/templates/fastify/routes-generator.js +29 -10
- package/dist/libs/instance-factories/services/templates/_shared/step-matching.js +11 -0
- package/dist/libs/instance-factories/services/templates/mongodb-native/step-conventions.js +39 -19
- package/dist/libs/instance-factories/services/templates/postgres-native/step-conventions.js +35 -18
- package/dist/libs/instance-factories/services/templates/prisma/ai-behaviors-generator.js +16 -11
- package/dist/libs/instance-factories/services/templates/prisma/controller-generator.js +1 -1
- package/dist/libs/instance-factories/services/templates/prisma/step-conventions.js +34 -12
- package/dist/libs/instance-factories/services/templates/shared-patterns.js +5 -5
- package/dist/realize/index.d.ts.map +1 -1
- package/dist/realize/index.js +91 -10
- package/dist/realize/index.js.map +1 -1
- package/dist/realize/per-action-recovery.d.ts +74 -0
- package/dist/realize/per-action-recovery.d.ts.map +1 -0
- package/dist/realize/per-action-recovery.js +255 -0
- package/dist/realize/per-action-recovery.js.map +1 -0
- package/dist/realize/per-owner-emit.d.ts +6 -0
- package/dist/realize/per-owner-emit.d.ts.map +1 -1
- package/dist/realize/per-owner-emit.js +22 -6
- package/dist/realize/per-owner-emit.js.map +1 -1
- package/dist/realize/per-owner-runner.d.ts +23 -2
- package/dist/realize/per-owner-runner.d.ts.map +1 -1
- package/dist/realize/per-owner-runner.js +91 -46
- package/dist/realize/per-owner-runner.js.map +1 -1
- package/dist/realize/post-emit-verify/diagnostics.d.ts +107 -0
- package/dist/realize/post-emit-verify/diagnostics.d.ts.map +1 -0
- package/dist/realize/post-emit-verify/diagnostics.js +148 -0
- package/dist/realize/post-emit-verify/diagnostics.js.map +1 -0
- package/dist/realize/post-emit-verify/feedback-runner.d.ts +41 -1
- package/dist/realize/post-emit-verify/feedback-runner.d.ts.map +1 -1
- package/dist/realize/post-emit-verify/feedback-runner.js +62 -6
- package/dist/realize/post-emit-verify/feedback-runner.js.map +1 -1
- package/dist/realize/post-emit-verify/index.d.ts +4 -2
- package/dist/realize/post-emit-verify/index.d.ts.map +1 -1
- package/dist/realize/post-emit-verify/index.js +3 -1
- package/dist/realize/post-emit-verify/index.js.map +1 -1
- package/dist/realize/post-emit-verify/reemit.d.ts +22 -1
- package/dist/realize/post-emit-verify/reemit.d.ts.map +1 -1
- package/dist/realize/post-emit-verify/reemit.js +20 -18
- package/dist/realize/post-emit-verify/reemit.js.map +1 -1
- package/dist/realize/post-emit-verify/types.d.ts +49 -0
- package/dist/realize/post-emit-verify/types.d.ts.map +1 -1
- package/dist/realize/post-emit-verify/verifier-manifest.d.ts.map +1 -1
- package/dist/realize/post-emit-verify/verifier-manifest.js +2 -0
- package/dist/realize/post-emit-verify/verifier-manifest.js.map +1 -1
- package/dist/realize/post-emit-verify/verifiers/stub-completeness.d.ts +127 -0
- package/dist/realize/post-emit-verify/verifiers/stub-completeness.d.ts.map +1 -0
- package/dist/realize/post-emit-verify/verifiers/stub-completeness.js +423 -0
- package/dist/realize/post-emit-verify/verifiers/stub-completeness.js.map +1 -0
- package/dist/realize/realize-context-snapshot.d.ts +70 -0
- package/dist/realize/realize-context-snapshot.d.ts.map +1 -0
- package/dist/realize/realize-context-snapshot.js +96 -0
- package/dist/realize/realize-context-snapshot.js.map +1 -0
- package/dist/realize/structural-validator.d.ts +36 -2
- package/dist/realize/structural-validator.d.ts.map +1 -1
- package/dist/realize/structural-validator.js +50 -7
- package/dist/realize/structural-validator.js.map +1 -1
- package/libs/instance-factories/controllers/templates/fastify/routes-generator.ts +49 -15
- package/libs/instance-factories/services/templates/_shared/step-matching.ts +43 -0
- package/libs/instance-factories/services/templates/mongodb-native/step-conventions.ts +39 -19
- package/libs/instance-factories/services/templates/postgres-native/step-conventions.ts +35 -18
- package/libs/instance-factories/services/templates/prisma/__tests__/step-conventions-create.test.ts +184 -0
- package/libs/instance-factories/services/templates/prisma/ai-behaviors-generator.ts +34 -5
- package/libs/instance-factories/services/templates/prisma/controller-generator.ts +6 -1
- package/libs/instance-factories/services/templates/prisma/step-conventions.ts +34 -12
- package/libs/instance-factories/services/templates/shared-patterns.ts +20 -10
- package/package.json +1 -1
- package/libs/instance-factories/services/templates/_shared/step-matching.d.ts +0 -39
- package/libs/instance-factories/services/templates/_shared/step-matching.d.ts.map +0 -1
- package/libs/instance-factories/services/templates/_shared/step-matching.js +0 -90
- package/libs/instance-factories/services/templates/_shared/step-matching.js.map +0 -1
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
toVar,
|
|
3
3
|
matchAgainstConventions
|
|
4
4
|
} from "../_shared/step-matching.js";
|
|
5
|
+
import { safeEntityName } from "@specverse/types";
|
|
5
6
|
function deriveModelDefaults(modelName, ctx) {
|
|
6
7
|
if (!ctx.models) return [];
|
|
7
8
|
const model = ctx.models[modelName] || ctx.models[modelName.charAt(0).toUpperCase() + modelName.slice(1)];
|
|
@@ -88,7 +89,8 @@ const PG_STEP_CONVENTIONS = [
|
|
|
88
89
|
generateCall: (m, ctx) => {
|
|
89
90
|
const model = m[1];
|
|
90
91
|
const field = m[2];
|
|
91
|
-
const modelVar =
|
|
92
|
+
const modelVar = safeEntityName(model);
|
|
93
|
+
if (!modelVar) return "";
|
|
92
94
|
const table = toTable(model);
|
|
93
95
|
const params = ctx.parameterNames || [];
|
|
94
96
|
const declared = ctx.declaredVars || /* @__PURE__ */ new Set();
|
|
@@ -111,7 +113,8 @@ const PG_STEP_CONVENTIONS = [
|
|
|
111
113
|
const model = m[1];
|
|
112
114
|
const f1 = m[2];
|
|
113
115
|
const f2 = m[3];
|
|
114
|
-
const modelVar =
|
|
116
|
+
const modelVar = safeEntityName(model);
|
|
117
|
+
if (!modelVar) return "";
|
|
115
118
|
const table = toTable(model);
|
|
116
119
|
const declared = ctx.declaredVars || /* @__PURE__ */ new Set();
|
|
117
120
|
if (declared.has(modelVar)) {
|
|
@@ -137,10 +140,13 @@ const PG_STEP_CONVENTIONS = [
|
|
|
137
140
|
// --- Create model record ---
|
|
138
141
|
{
|
|
139
142
|
name: "create",
|
|
140
|
-
|
|
143
|
+
// Leading-adjective skip (NL_LEADING_ADJECTIVES) + isUnsafeEntityName
|
|
144
|
+
// safety net — see comment in prisma/step-conventions.ts "find" block.
|
|
145
|
+
pattern: /^create\s+(?:(?:new|existing|current|the|a|an)\s+)?(\w+)(?:\s+(?:with\s+)?(.+))?/i,
|
|
141
146
|
generateCall: (m, ctx) => {
|
|
142
147
|
const model = m[1];
|
|
143
|
-
const modelVar =
|
|
148
|
+
const modelVar = safeEntityName(model);
|
|
149
|
+
if (!modelVar) return "";
|
|
144
150
|
const table = toTable(model);
|
|
145
151
|
const params = ctx.parameterNames || [];
|
|
146
152
|
const declared = ctx.declaredVars || /* @__PURE__ */ new Set();
|
|
@@ -153,12 +159,13 @@ const PG_STEP_CONVENTIONS = [
|
|
|
153
159
|
// --- Update specific field on previously-loaded model ---
|
|
154
160
|
{
|
|
155
161
|
name: "update-field",
|
|
156
|
-
pattern: /^update\s+(\w+)\s+(\w+)\s+to\s+(.+)/i,
|
|
162
|
+
pattern: /^update\s+(?:(?:new|existing|current|the|a|an)\s+)?(\w+)\s+(\w+)\s+to\s+(.+)/i,
|
|
157
163
|
generateCall: (m, ctx) => {
|
|
158
164
|
const model = m[1];
|
|
165
|
+
const modelVar = safeEntityName(model);
|
|
166
|
+
if (!modelVar) return "";
|
|
159
167
|
const field = m[2];
|
|
160
168
|
const rawValue = m[3];
|
|
161
|
-
const modelVar = toVar(model);
|
|
162
169
|
if (!ctx.declaredVars?.has(modelVar)) return "";
|
|
163
170
|
const table = toTable(model);
|
|
164
171
|
const val = resolveValue(rawValue, ctx);
|
|
@@ -169,11 +176,12 @@ const PG_STEP_CONVENTIONS = [
|
|
|
169
176
|
// --- Update field timestamp ---
|
|
170
177
|
{
|
|
171
178
|
name: "update-field-timestamp",
|
|
172
|
-
pattern: /^update\s+(\w+)\s+(\w+)\s+timestamp$/i,
|
|
179
|
+
pattern: /^update\s+(?:(?:new|existing|current|the|a|an)\s+)?(\w+)\s+(\w+)\s+timestamp$/i,
|
|
173
180
|
generateCall: (m, ctx) => {
|
|
174
181
|
const model = m[1];
|
|
182
|
+
const modelVar = safeEntityName(model);
|
|
183
|
+
if (!modelVar) return "";
|
|
175
184
|
const field = m[2];
|
|
176
|
-
const modelVar = toVar(model);
|
|
177
185
|
if (!ctx.declaredVars?.has(modelVar)) return "";
|
|
178
186
|
const table = toTable(model);
|
|
179
187
|
return ` // Step ${ctx.stepNum}: Update ${model}.${field} timestamp
|
|
@@ -183,10 +191,11 @@ const PG_STEP_CONVENTIONS = [
|
|
|
183
191
|
// --- Generic "Update X" (writes args back) ---
|
|
184
192
|
{
|
|
185
193
|
name: "update",
|
|
186
|
-
pattern: /^update\s+(\w+)(?:\s+(.+))?$/i,
|
|
194
|
+
pattern: /^update\s+(?:(?:new|existing|current|the|a|an)\s+)?(\w+)(?:\s+(.+))?$/i,
|
|
187
195
|
generateCall: (m, ctx) => {
|
|
188
196
|
const model = m[1];
|
|
189
|
-
const modelVar =
|
|
197
|
+
const modelVar = safeEntityName(model);
|
|
198
|
+
if (!modelVar) return "";
|
|
190
199
|
if (!ctx.declaredVars?.has(modelVar)) return "";
|
|
191
200
|
const table = toTable(model);
|
|
192
201
|
return ` // Step ${ctx.stepNum}: Update ${model}
|
|
@@ -196,10 +205,11 @@ const PG_STEP_CONVENTIONS = [
|
|
|
196
205
|
// --- Delete model record ---
|
|
197
206
|
{
|
|
198
207
|
name: "delete",
|
|
199
|
-
pattern: /^delete\s+(\w+)/i,
|
|
208
|
+
pattern: /^delete\s+(?:(?:new|existing|current|the|a|an)\s+)?(\w+)/i,
|
|
200
209
|
generateCall: (m, ctx) => {
|
|
201
210
|
const model = m[1];
|
|
202
|
-
const modelVar =
|
|
211
|
+
const modelVar = safeEntityName(model);
|
|
212
|
+
if (!modelVar) return "";
|
|
203
213
|
if (!ctx.declaredVars?.has(modelVar)) return "";
|
|
204
214
|
const table = toTable(model);
|
|
205
215
|
return ` // Step ${ctx.stepNum}: Delete ${model}
|
|
@@ -213,7 +223,8 @@ const PG_STEP_CONVENTIONS = [
|
|
|
213
223
|
generateCall: (m, ctx) => {
|
|
214
224
|
const model = m[1];
|
|
215
225
|
const state = m[2];
|
|
216
|
-
const modelVar =
|
|
226
|
+
const modelVar = safeEntityName(model);
|
|
227
|
+
if (!modelVar) return "";
|
|
217
228
|
if (!ctx.declaredVars?.has(modelVar)) return "";
|
|
218
229
|
const table = toTable(model);
|
|
219
230
|
return ` // Step ${ctx.stepNum}: Transition ${model} to ${state}
|
|
@@ -265,7 +276,8 @@ const PG_STEP_CONVENTIONS = [
|
|
|
265
276
|
name: "persist",
|
|
266
277
|
pattern: /^(?:persist|save|store)\s+(\w+(?:\s+\w+)?)(?:\s+(?:for|to|record).*)?$/i,
|
|
267
278
|
generateCall: (m, ctx) => {
|
|
268
|
-
const target =
|
|
279
|
+
const target = safeEntityName(m[1].replace(/\s+(.)/g, (_, c) => c.toUpperCase()));
|
|
280
|
+
if (!target) return "";
|
|
269
281
|
const table = toTable(target);
|
|
270
282
|
const recordSrc = mostRecentStepResult(ctx) ?? "args";
|
|
271
283
|
return ` // Step ${ctx.stepNum}: Persist ${m[1]}
|
|
@@ -281,7 +293,8 @@ const PG_STEP_CONVENTIONS = [
|
|
|
281
293
|
name: "conditional-create",
|
|
282
294
|
pattern: /^if\s+(\w+)\s+does\s+not\s+exist,?\s+create\s+new\s+(\w+)(?:\s+with\s+.+)?$/i,
|
|
283
295
|
generateCall: (m, ctx) => {
|
|
284
|
-
const modelVar =
|
|
296
|
+
const modelVar = safeEntityName(m[1]);
|
|
297
|
+
if (!modelVar) return "";
|
|
285
298
|
const Model = pascal(m[2]);
|
|
286
299
|
const table = toTable(Model);
|
|
287
300
|
if (!ctx.declaredVars?.has(modelVar)) return "";
|
|
@@ -299,7 +312,8 @@ const PG_STEP_CONVENTIONS = [
|
|
|
299
312
|
name: "conditional-update",
|
|
300
313
|
pattern: /^if\s+(\w+)\s+exists,?\s+update\s+(\w+)(?:\s+(.+))?$/i,
|
|
301
314
|
generateCall: (m, ctx) => {
|
|
302
|
-
const modelVar =
|
|
315
|
+
const modelVar = safeEntityName(m[1]);
|
|
316
|
+
if (!modelVar) return "";
|
|
303
317
|
const field = m[2];
|
|
304
318
|
const table = toTable(m[1]);
|
|
305
319
|
if (!ctx.declaredVars?.has(modelVar)) return "";
|
|
@@ -347,7 +361,8 @@ const PG_STEP_CONVENTIONS = [
|
|
|
347
361
|
pattern: /^otherwise\s+create\s+(?:new\s+)?(\w+)\s+record$/i,
|
|
348
362
|
generateCall: (m, ctx) => {
|
|
349
363
|
const Model = pascal(m[1]);
|
|
350
|
-
const modelVar =
|
|
364
|
+
const modelVar = safeEntityName(Model);
|
|
365
|
+
if (!modelVar) return "";
|
|
351
366
|
const table = toTable(Model);
|
|
352
367
|
const wasDeclared = ctx.declaredVars?.has(modelVar);
|
|
353
368
|
const declared = Array.from(ctx.declaredVars || []);
|
|
@@ -391,9 +406,11 @@ const PG_STEP_CONVENTIONS = [
|
|
|
391
406
|
generateCall: (m, ctx) => {
|
|
392
407
|
const service = m[1];
|
|
393
408
|
const method = m[2];
|
|
409
|
+
const serviceVar = safeEntityName(service);
|
|
410
|
+
if (!serviceVar) return "";
|
|
394
411
|
const args = (ctx.parameterNames || []).join(", ");
|
|
395
412
|
return ` // Step ${ctx.stepNum}: Call ${service}.${method}
|
|
396
|
-
await (${
|
|
413
|
+
await (${serviceVar} as any).${method}({ ${args} });`;
|
|
397
414
|
}
|
|
398
415
|
},
|
|
399
416
|
// --- Return ---
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { safeFunctionName } from "@specverse/types";
|
|
1
2
|
import { matchStep } from "./step-conventions.js";
|
|
2
3
|
import { validateImportWhitelist } from "@specverse/engines/ai";
|
|
3
4
|
import { createHash } from "crypto";
|
|
@@ -190,6 +191,7 @@ async function generateAiBehaviorsFile(opts) {
|
|
|
190
191
|
let cacheHits = 0;
|
|
191
192
|
let cacheMisses = 0;
|
|
192
193
|
for (const { functionName, step, operationName, parameterNames, inputs, returns, modelName } of unmatchedFunctions) {
|
|
194
|
+
const { name: safeName, reserved: nameReserved } = safeFunctionName(functionName);
|
|
193
195
|
const stripLiteralsAndComments = (src) => {
|
|
194
196
|
let out = src.replace(/\/\*[\s\S]*?\*\//g, (m) => " ".repeat(m.length)).replace(/\/\/[^\n]*/g, (m) => " ".repeat(m.length)).replace(/(['"])(?:\\.|(?!\1).)*\1/g, (m) => m[0] + " ".repeat(m.length - 2) + m[0]);
|
|
195
197
|
out = out.replace(/`((?:\\.|\$\{[^}]*\}|(?!`).)*)`/g, (full, content) => {
|
|
@@ -246,13 +248,18 @@ async function generateAiBehaviorsFile(opts) {
|
|
|
246
248
|
const fields = Object.entries(returns).map(([k, v]) => `${k}: ${v}`).join("; ");
|
|
247
249
|
returnType = `{ ${fields} }`;
|
|
248
250
|
}
|
|
251
|
+
const buildTestCode = (rawBody) => {
|
|
252
|
+
const { signature, destructure } = buildSignatureAndDestructure(rawBody);
|
|
253
|
+
const destructureLine = destructure ? destructure + "\n" : "";
|
|
254
|
+
return `export async function ${safeName}(${signature}): Promise<${returnType}> {
|
|
255
|
+
${destructureLine}${rawBody}
|
|
256
|
+
}`;
|
|
257
|
+
};
|
|
249
258
|
const key = cacheKey(step, modelName, operationName, functionName, inputs);
|
|
250
259
|
let body = cacheRead(key);
|
|
251
260
|
let source = body ? "AI-CACHED" : "STUB";
|
|
252
261
|
if (body) {
|
|
253
|
-
const testCode =
|
|
254
|
-
${body}
|
|
255
|
-
}`;
|
|
262
|
+
const testCode = buildTestCode(body);
|
|
256
263
|
const syntaxError = await validateTypeScript(testCode);
|
|
257
264
|
const typeError = syntaxError ? null : await validateTypeScriptTypes(testCode);
|
|
258
265
|
const importError = syntaxError || typeError ? null : validateImports(testCode);
|
|
@@ -280,9 +287,7 @@ ${body}
|
|
|
280
287
|
// Pass declared return type to Claude
|
|
281
288
|
});
|
|
282
289
|
if (body) {
|
|
283
|
-
const testCode =
|
|
284
|
-
${body}
|
|
285
|
-
}`;
|
|
290
|
+
const testCode = buildTestCode(body);
|
|
286
291
|
const syntaxError = await validateTypeScript(testCode);
|
|
287
292
|
if (syntaxError) {
|
|
288
293
|
console.warn(` [ai-validate] ${functionName} has syntax error: ${syntaxError}`);
|
|
@@ -324,9 +329,7 @@ ${retryHint}`,
|
|
|
324
329
|
returnType
|
|
325
330
|
});
|
|
326
331
|
if (retried) {
|
|
327
|
-
const retryCode =
|
|
328
|
-
${retried}
|
|
329
|
-
}`;
|
|
332
|
+
const retryCode = buildTestCode(retried);
|
|
330
333
|
const retrySyntaxError = await validateTypeScript(retryCode);
|
|
331
334
|
const retryTypeError = retrySyntaxError ? null : await validateTypeScriptTypes(retryCode);
|
|
332
335
|
const retryImportError = retrySyntaxError || retryTypeError ? null : validateImports(retryCode);
|
|
@@ -364,6 +367,8 @@ ${retried}
|
|
|
364
367
|
` : "";
|
|
365
368
|
const returnsDoc = returnType !== "any" ? ` * Returns: ${returnType}
|
|
366
369
|
` : "";
|
|
370
|
+
const reExportLine = nameReserved ? `
|
|
371
|
+
export { ${safeName} as ${functionName} };` : "";
|
|
367
372
|
functions.push(`/**
|
|
368
373
|
* ${functionName}
|
|
369
374
|
*
|
|
@@ -376,7 +381,7 @@ ${inputsDoc}${returnsDoc} * Source: ${source}
|
|
|
376
381
|
* All data comes in via \`input\`; all effects happen in the calling controller.
|
|
377
382
|
* ${source === "AI-GENERATED" ? "AI-generated implementation. Review and test before deploying." : source === "AI-CACHED" ? "Restored from AI cache (.specverse/ai-cache/). Delete cache entry to regenerate." : source === "AI-INVALID" ? "AI returned code with syntax errors \u2014 function throws at runtime. Fix or regenerate." : "STUB \u2014 Claude CLI unavailable. Install Claude Code or implement manually."}
|
|
378
383
|
*/
|
|
379
|
-
export async function ${
|
|
384
|
+
${nameReserved ? "async" : "export async"} function ${safeName}(${(() => {
|
|
380
385
|
const { signature: sig } = buildSignatureAndDestructure(body);
|
|
381
386
|
return sig;
|
|
382
387
|
})()}): Promise<${returnType}> {
|
|
@@ -384,7 +389,7 @@ ${(() => {
|
|
|
384
389
|
const { destructure } = buildSignatureAndDestructure(body);
|
|
385
390
|
return destructure ? destructure + "\n" : "";
|
|
386
391
|
})()}${body}
|
|
387
|
-
}`);
|
|
392
|
+
}${reExportLine}`);
|
|
388
393
|
}
|
|
389
394
|
if (aiService?.endSession) aiService.endSession();
|
|
390
395
|
if (cacheHits > 0 || cacheMisses > 0) {
|
|
@@ -30,7 +30,7 @@ function generatePrismaController(context) {
|
|
|
30
30
|
].filter(Boolean).join("\n ");
|
|
31
31
|
const usesPrisma = /\bprisma\b/.test(classBody);
|
|
32
32
|
const usesParseId = /\bparseId\(/.test(classBody);
|
|
33
|
-
const usesEventBus =
|
|
33
|
+
const usesEventBus = /\beventBus\./.test(classBody);
|
|
34
34
|
const usesAiBehaviors = customActions.needsAiBehaviors;
|
|
35
35
|
const imports = [
|
|
36
36
|
usesPrisma ? `import { PrismaClient } from '@prisma/client';` : "",
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
toVar,
|
|
4
4
|
matchAgainstConventions
|
|
5
5
|
} from "../_shared/step-matching.js";
|
|
6
|
+
import { safeEntityName } from "@specverse/types";
|
|
6
7
|
function resolveValue(rawValue, ctx) {
|
|
7
8
|
const value = rawValue.trim().replace(/^['"]|['"]$/g, "");
|
|
8
9
|
if (/^(current\s*time|now|timestamp)$/i.test(value)) {
|
|
@@ -60,13 +61,27 @@ const STEP_CONVENTIONS = [
|
|
|
60
61
|
}
|
|
61
62
|
},
|
|
62
63
|
// --- Find / Lookup ---
|
|
64
|
+
//
|
|
65
|
+
// All entity-targeting patterns (find / create / update / delete) share
|
|
66
|
+
// two safeguards:
|
|
67
|
+
// 1. A pattern-level leading-adjective skip
|
|
68
|
+
// `(?:(?:new|existing|current|the|a|an)\s+)?` so NL phrasings like
|
|
69
|
+
// "Find existing Player by id" or "Create new Player with ..."
|
|
70
|
+
// extract the noun, not the adjective. Kept in sync with
|
|
71
|
+
// `NL_LEADING_ADJECTIVES` in `_shared/step-matching.ts`.
|
|
72
|
+
// 2. `safeEntityName(m[1])` — the reserved-word-safe primitive from
|
|
73
|
+
// `@specverse/types`. Returns the lowerCamel identifier, or `null`
|
|
74
|
+
// when the extraction would emit a TS reserved word
|
|
75
|
+
// (e.g. `prisma.new.create`). On null, generateCall returns `''` so
|
|
76
|
+
// the matcher falls through to the AI [WRITE] fallback.
|
|
63
77
|
{
|
|
64
78
|
name: "find",
|
|
65
|
-
pattern: /^find\s+(\w+)\s+by\s+(\w+)/i,
|
|
79
|
+
pattern: /^find\s+(?:(?:new|existing|current|the|a|an)\s+)?(\w+)\s+by\s+(\w+)/i,
|
|
66
80
|
generateCall: (m, ctx) => {
|
|
67
81
|
const model = m[1];
|
|
82
|
+
const modelVar = safeEntityName(model);
|
|
83
|
+
if (!modelVar) return "";
|
|
68
84
|
const field = m[2];
|
|
69
|
-
const modelVar = toVar(model);
|
|
70
85
|
const params = ctx.parameterNames || [];
|
|
71
86
|
const declared = ctx.declaredVars || /* @__PURE__ */ new Set();
|
|
72
87
|
const idParam = field === "id" ? params.find((p) => p === `${modelVar}Id`) || params.find((p) => p === "id") || `${modelVar}Id` : params.find((p) => p === field) || `params.${field}`;
|
|
@@ -81,10 +96,11 @@ const STEP_CONVENTIONS = [
|
|
|
81
96
|
// --- Create ---
|
|
82
97
|
{
|
|
83
98
|
name: "create",
|
|
84
|
-
pattern: /^create\s+(\w+)(?:\s+(?:with\s+)?(.+))?/i,
|
|
99
|
+
pattern: /^create\s+(?:(?:new|existing|current|the|a|an)\s+)?(\w+)(?:\s+(?:with\s+)?(.+))?/i,
|
|
85
100
|
generateCall: (m, ctx) => {
|
|
86
101
|
const model = m[1];
|
|
87
|
-
const modelVar =
|
|
102
|
+
const modelVar = safeEntityName(model);
|
|
103
|
+
if (!modelVar) return "";
|
|
88
104
|
const paramNames = ctx.parameterNames || [];
|
|
89
105
|
const dataFields = paramNames.length > 0 ? `{ ${paramNames.join(", ")} }` : "data";
|
|
90
106
|
return ` // Step ${ctx.stepNum}: Create ${model}
|
|
@@ -94,12 +110,13 @@ const STEP_CONVENTIONS = [
|
|
|
94
110
|
// --- Update field ---
|
|
95
111
|
{
|
|
96
112
|
name: "update-field",
|
|
97
|
-
pattern: /^update\s+(\w+)\s+(\w+)\s+to\s+(.+)/i,
|
|
113
|
+
pattern: /^update\s+(?:(?:new|existing|current|the|a|an)\s+)?(\w+)\s+(\w+)\s+to\s+(.+)/i,
|
|
98
114
|
generateCall: (m, ctx) => {
|
|
99
115
|
const model = m[1];
|
|
116
|
+
const modelVar = safeEntityName(model);
|
|
117
|
+
if (!modelVar) return "";
|
|
100
118
|
const field = m[2];
|
|
101
119
|
const rawValue = m[3];
|
|
102
|
-
const modelVar = toVar(model);
|
|
103
120
|
const val = resolveValue(rawValue, ctx);
|
|
104
121
|
return ` // Step ${ctx.stepNum}: Update ${model} ${field} to ${rawValue.trim()}
|
|
105
122
|
await prisma.${modelVar}.update({
|
|
@@ -111,10 +128,11 @@ const STEP_CONVENTIONS = [
|
|
|
111
128
|
// --- Generic update ---
|
|
112
129
|
{
|
|
113
130
|
name: "update",
|
|
114
|
-
pattern: /^update\s+(\w+)(?:\s+(.+))?/i,
|
|
131
|
+
pattern: /^update\s+(?:(?:new|existing|current|the|a|an)\s+)?(\w+)(?:\s+(.+))?/i,
|
|
115
132
|
generateCall: (m, ctx) => {
|
|
116
133
|
const model = m[1];
|
|
117
|
-
const modelVar =
|
|
134
|
+
const modelVar = safeEntityName(model);
|
|
135
|
+
if (!modelVar) return "";
|
|
118
136
|
return ` // Step ${ctx.stepNum}: Update ${model}
|
|
119
137
|
await prisma.${modelVar}.update({
|
|
120
138
|
where: { id: ${modelVar}.id },
|
|
@@ -125,10 +143,11 @@ const STEP_CONVENTIONS = [
|
|
|
125
143
|
// --- Delete ---
|
|
126
144
|
{
|
|
127
145
|
name: "delete",
|
|
128
|
-
pattern: /^delete\s+(\w+)/i,
|
|
146
|
+
pattern: /^delete\s+(?:(?:new|existing|current|the|a|an)\s+)?(\w+)/i,
|
|
129
147
|
generateCall: (m, ctx) => {
|
|
130
148
|
const model = m[1];
|
|
131
|
-
const modelVar =
|
|
149
|
+
const modelVar = safeEntityName(model);
|
|
150
|
+
if (!modelVar) return "";
|
|
132
151
|
return ` // Step ${ctx.stepNum}: Delete ${model}
|
|
133
152
|
await prisma.${modelVar}.delete({ where: { id: ${modelVar}.id } });`;
|
|
134
153
|
}
|
|
@@ -140,7 +159,8 @@ const STEP_CONVENTIONS = [
|
|
|
140
159
|
generateCall: (m, ctx) => {
|
|
141
160
|
const model = m[1];
|
|
142
161
|
const state = m[2];
|
|
143
|
-
const modelVar =
|
|
162
|
+
const modelVar = safeEntityName(model);
|
|
163
|
+
if (!modelVar) return "";
|
|
144
164
|
return ` // Step ${ctx.stepNum}: Transition ${model} to ${state}
|
|
145
165
|
if (${modelVar}.status === '${state}') throw new Error('${model} is already ${state}');
|
|
146
166
|
await prisma.${modelVar}.update({
|
|
@@ -227,8 +247,10 @@ const STEP_CONVENTIONS = [
|
|
|
227
247
|
generateCall: (m, ctx) => {
|
|
228
248
|
const service = m[1];
|
|
229
249
|
const method = m[2];
|
|
250
|
+
const serviceVar = safeEntityName(service);
|
|
251
|
+
if (!serviceVar) return "";
|
|
230
252
|
return ` // Step ${ctx.stepNum}: Call ${service}.${method}
|
|
231
|
-
await ${
|
|
253
|
+
await ${serviceVar}.${method}({ ${(ctx.parameterNames || []).join(", ")} });`;
|
|
232
254
|
}
|
|
233
255
|
},
|
|
234
256
|
// --- Return ---
|
|
@@ -2,13 +2,13 @@ const CONVENTION_PATTERNS = [
|
|
|
2
2
|
{ name: "validate", pattern: /^validate\s+(.+)/i, description: "Validate input data" },
|
|
3
3
|
{ name: "check-no-existing", pattern: /^check\s+(?:no\s+existing|.*has\s+no\s+existing)\s+(\w+)\s+for\s+(\w+)\s+in\s+(\w+)/i, description: "Check no duplicate entity exists for a pair" },
|
|
4
4
|
{ name: "check", pattern: /^check\s+(.+)/i, description: "Check a condition" },
|
|
5
|
-
{ name: "find", pattern: /^find\s+(\w+)\s+by\s+(\w+)/i, description: "Find entity by field" },
|
|
5
|
+
{ name: "find", pattern: /^find\s+(?:(?:new|existing|current|the|a|an)\s+)?(\w+)\s+by\s+(\w+)/i, description: "Find entity by field" },
|
|
6
6
|
{ name: "find-all-for", pattern: /^find\s+(?:all\s+)?(\w+?)s?\s+(?:for|of|belonging\s+to)\s+(\w+)/i, description: "Cross-model query via foreign key" },
|
|
7
7
|
{ name: "count-per", pattern: /^count\s+(\w+?)s?\s+(?:per|by|for\s+each)\s+(\w+)/i, description: "Group-by aggregation count" },
|
|
8
|
-
{ name: "create", pattern: /^create\s+(\w+)(?:\s+record)?/i, description: "Create a new entity" },
|
|
9
|
-
{ name: "update-field", pattern: /^update\s+(\w+)\s+(\w+)\s+to\s+(.+)/i, description: "Update specific field to value" },
|
|
10
|
-
{ name: "update", pattern: /^update\s+(\w+)$/i, description: "Update entity with params" },
|
|
11
|
-
{ name: "delete", pattern: /^delete\s+(\w+)/i, description: "Delete an entity" },
|
|
8
|
+
{ name: "create", pattern: /^create\s+(?:(?:new|existing|current|the|a|an)\s+)?(\w+)(?:\s+record)?/i, description: "Create a new entity" },
|
|
9
|
+
{ name: "update-field", pattern: /^update\s+(?:(?:new|existing|current|the|a|an)\s+)?(\w+)\s+(\w+)\s+to\s+(.+)/i, description: "Update specific field to value" },
|
|
10
|
+
{ name: "update", pattern: /^update\s+(?:(?:new|existing|current|the|a|an)\s+)?(\w+)$/i, description: "Update entity with params" },
|
|
11
|
+
{ name: "delete", pattern: /^delete\s+(?:(?:new|existing|current|the|a|an)\s+)?(\w+)/i, description: "Delete an entity" },
|
|
12
12
|
{ name: "transition", pattern: /^transition\s+(\w+)\s+to\s+(\w+)/i, description: "Lifecycle state transition" },
|
|
13
13
|
{ name: "set", pattern: /^set\s+(\w+)\s+to\s+(.+)/i, description: "Set a field value" },
|
|
14
14
|
{ name: "increment", pattern: /^increment\s+(\w+)\s+by\s+(\w+)/i, description: "Increment numeric field" },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/realize/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,cAAc,kBAAkB,CAAC;AAGjC,cAAc,kBAAkB,CAAC;AAGjC,cAAc,uBAAuB,CAAC;AAItC,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACpF,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EACL,wCAAwC,EACxC,eAAe,EACf,KAAK,YAAY,GAClB,MAAM,2BAA2B,CAAC;AAMnC,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AA2DnF,cAAM,sBAAuB,YAAW,aAAa;IACnD,IAAI,SAAa;IACjB,OAAO,SAAW;IAClB,YAAY,WAA+E;IAE3F,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,WAAW,CAAS;IAEtB,UAAU,CAAC,MAAM,CAAC,EAAE;QACxB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC;IAwDjB,OAAO,IAAI,UAAU;IAIrB,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG;IAK1B,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,eAAe,CAAC;IAKvF;;;OAGG;IACG,UAAU,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/realize/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,cAAc,kBAAkB,CAAC;AAGjC,cAAc,kBAAkB,CAAC;AAGjC,cAAc,uBAAuB,CAAC;AAItC,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACpF,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EACL,wCAAwC,EACxC,eAAe,EACf,KAAK,YAAY,GAClB,MAAM,2BAA2B,CAAC;AAMnC,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AA2DnF,cAAM,sBAAuB,YAAW,aAAa;IACnD,IAAI,SAAa;IACjB,OAAO,SAAW;IAClB,YAAY,WAA+E;IAE3F,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,WAAW,CAAS;IAEtB,UAAU,CAAC,MAAM,CAAC,EAAE;QACxB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC;IAwDjB,OAAO,IAAI,UAAU;IAIrB,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG;IAK1B,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,eAAe,CAAC;IAKvF;;;OAGG;IACG,UAAU,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAu2C9F;;;OAGG;YACW,UAAU;IA8DxB,OAAO,CAAC,oBAAoB;CAuB7B;AAED,eAAO,MAAM,MAAM,wBAA+B,CAAC;AACnD,eAAe,MAAM,CAAC;AACtB,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
|
package/dist/realize/index.js
CHANGED
|
@@ -423,26 +423,67 @@ class SpecVerseRealizeEngine {
|
|
|
423
423
|
// savings, warm cache, and skill state persists across calls).
|
|
424
424
|
const { runPerOwnerEmission, buildAvailableCapabilitiesString } = await import('./per-owner-runner.js');
|
|
425
425
|
const { createRealizeOwnerLlmEmit } = await import('./per-owner-emit.js');
|
|
426
|
+
const { createPerActionRecovery } = await import('./per-action-recovery.js');
|
|
427
|
+
const { resolveProviderId: resolveProviderIdForRecovery } = await import('../ai/model-resolver.js');
|
|
426
428
|
const perOwnerEmit = createRealizeOwnerLlmEmit({ model: sharedModel });
|
|
429
|
+
// Tier 2 — per-action recovery (Phase 5a of
|
|
430
|
+
// 2026-05-14-PER-ACTION-RECOVERY-TIER.md). When the whole-owner
|
|
431
|
+
// emit fails, the runner retries the owner action-by-action with
|
|
432
|
+
// full owner context before γ-stubbing. Default-on; the runner
|
|
433
|
+
// honours SPECVERSE_REALIZE_NO_RECOVERY_TIER=1 + the action-count
|
|
434
|
+
// cap. Stub provider has no LLM — skip wiring it then.
|
|
435
|
+
const perActionRecovery = resolveProviderIdForRecovery() === 'stub'
|
|
436
|
+
? undefined
|
|
437
|
+
: createPerActionRecovery({ model: sharedModel });
|
|
427
438
|
const availableCapabilities = buildAvailableCapabilitiesString(this.manifest);
|
|
428
439
|
const ownerResult = await runPerOwnerEmission({
|
|
429
440
|
spec, outputDir,
|
|
430
441
|
matcher: sharedMatcher,
|
|
431
442
|
conventions,
|
|
432
443
|
perOwnerEmit,
|
|
444
|
+
perActionRecovery,
|
|
433
445
|
availableClientsByOwner,
|
|
434
446
|
availableCapabilities,
|
|
435
447
|
silent: false,
|
|
436
448
|
});
|
|
437
449
|
if (ownerResult.totals.actionCount > 0) {
|
|
438
450
|
const llmCount = ownerResult.totals.ownersEmittedByLLM;
|
|
451
|
+
const tier2Count = ownerResult.totals.ownersRecoveredByTier2;
|
|
439
452
|
const fallbackCount = ownerResult.totals.ownersFellBack;
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
453
|
+
const tailParts = [];
|
|
454
|
+
if (llmCount > 0)
|
|
455
|
+
tailParts.push(`${llmCount} LLM-emitted`);
|
|
456
|
+
if (tier2Count > 0)
|
|
457
|
+
tailParts.push(`${tier2Count} via per-action recovery`);
|
|
458
|
+
if (fallbackCount > 0)
|
|
459
|
+
tailParts.push(`${fallbackCount} fell back to placeholder`);
|
|
460
|
+
const tail = tailParts.length > 0 ? ` (${tailParts.join(', ')})` : '';
|
|
443
461
|
console.log(` ✅ Per-OWNER AI behaviors: ${ownerResult.totals.actionCount} action(s) across ${ownerResult.totals.ownerCount} owner(s)${tail}`);
|
|
444
462
|
}
|
|
445
|
-
// 2.
|
|
463
|
+
// 2.5a Diagnostic sidecar (default-on; opt-out via SPECVERSE_REALIZE_NO_DIAGNOSTICS=1).
|
|
464
|
+
//
|
|
465
|
+
// Phase 1 of 2026-05-13-VERIFIER-DIAGNOSTIC-TREATMENT-SPLIT.
|
|
466
|
+
// Runs the stub-completeness verifier in diagnostic-only mode
|
|
467
|
+
// (separate from the feedback runner), writes a sidecar with
|
|
468
|
+
// per-owner stub breakdown, prints a one-line CLI summary if
|
|
469
|
+
// there's anything to flag. Zero LLM calls, milliseconds runtime.
|
|
470
|
+
if (process.env.SPECVERSE_REALIZE_NO_DIAGNOSTICS !== '1') {
|
|
471
|
+
try {
|
|
472
|
+
const { runDiagnostics, formatSummaryLine } = await import('./post-emit-verify/index.js');
|
|
473
|
+
const summary = await runDiagnostics({
|
|
474
|
+
outputDir,
|
|
475
|
+
targetLanguage: 'typescript',
|
|
476
|
+
});
|
|
477
|
+
const line = formatSummaryLine(summary);
|
|
478
|
+
if (line)
|
|
479
|
+
console.log(line);
|
|
480
|
+
}
|
|
481
|
+
catch (e) {
|
|
482
|
+
// Diagnostic failure must never crash the realize pipeline.
|
|
483
|
+
errors.push(`Realize diagnostics: ${e?.message ?? String(e)}`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
// 2.5b Post-emit feedback (default-on; escape hatch retained).
|
|
446
487
|
//
|
|
447
488
|
// After all per-owner emits complete, run language-specific
|
|
448
489
|
// verifiers (tsc, mypy, etc.) on the realized output and feed
|
|
@@ -453,7 +494,12 @@ class SpecVerseRealizeEngine {
|
|
|
453
494
|
// Flip history:
|
|
454
495
|
// 6.51.0 — opt-in via SPECVERSE_REALIZE_POST_VERIFY=1
|
|
455
496
|
// 6.52.0 — default-on; opt-out via SPECVERSE_REALIZE_NO_POST_VERIFY=1
|
|
456
|
-
// 6.53.0 — env hatch retired
|
|
497
|
+
// 6.53.0 — env hatch retired
|
|
498
|
+
// 6.53.2 — escape hatch restored (this version) after
|
|
499
|
+
// real-npm publish surfaced the need for a safety
|
|
500
|
+
// valve. Default-on stays; users with a genuine
|
|
501
|
+
// regression can set SPECVERSE_REALIZE_NO_POST_VERIFY=1
|
|
502
|
+
// until upstream ships a fix.
|
|
457
503
|
//
|
|
458
504
|
// Empirical evidence at flip time (Phase A corpus harness):
|
|
459
505
|
// - scoremyclays-ollama: 9 → 0 tsc errors
|
|
@@ -462,12 +508,31 @@ class SpecVerseRealizeEngine {
|
|
|
462
508
|
// generator errors as unmappable; no spurious re-emits)
|
|
463
509
|
// - zero regressions across the corpus
|
|
464
510
|
//
|
|
465
|
-
// Env tuning
|
|
511
|
+
// Env tuning:
|
|
512
|
+
// SPECVERSE_REALIZE_NO_POST_VERIFY=1 — opt out (escape hatch)
|
|
466
513
|
// SPECVERSE_REALIZE_POST_VERIFY_PASSES=N — tune retry count (default 1)
|
|
467
514
|
// SPECVERSE_REALIZE_POST_VERIFY_MAX_FILES=N — cap LLM spend per pass (default 20)
|
|
515
|
+
// SPECVERSE_REALIZE_POST_VERIFY_ENABLE=id,… — opt in to verifiers
|
|
516
|
+
// with enabledByDefault: false
|
|
517
|
+
// (e.g. stub-completeness)
|
|
518
|
+
//
|
|
519
|
+
// Gate: run feedback whenever there are owners AT ALL — not just
|
|
520
|
+
// LLM-emitted ones. Pre-6.58.0 the gate was
|
|
521
|
+
// `ownersEmittedByLLM > 0`, which skipped feedback entirely when
|
|
522
|
+
// EVERY owner γ-fell-back (structural-preservation rejection,
|
|
523
|
+
// parse failure, etc.). That defeated the stub-completeness
|
|
524
|
+
// verifier (engines 6.54.0+) in its single highest-value case:
|
|
525
|
+
// an all-fallback realize is ALL STUB001 γ-stubs, exactly what
|
|
526
|
+
// auto-reemit-with-context (Phase 3) is built to recover. The
|
|
527
|
+
// Phase 3 trial (2026-05-14, nestjs-billing × MarrBox/Ollama)
|
|
528
|
+
// surfaced this: both providers had all owners rejected by
|
|
529
|
+
// structural-preservation → 0 LLM-emitted → feedback skipped →
|
|
530
|
+
// 7 STUB001 stubs shipped unfixed. Now: `ownerCount > 0`. The
|
|
531
|
+
// only true skip is "no behaviors at all" (validate-only
|
|
532
|
+
// backends) — and the stub provider is still skipped below.
|
|
468
533
|
//
|
|
469
534
|
// Stub provider is auto-skipped (no LLM → can't fix).
|
|
470
|
-
if (ownerResult.totals.
|
|
535
|
+
if (process.env.SPECVERSE_REALIZE_NO_POST_VERIFY !== '1' && ownerResult.totals.ownerCount > 0) {
|
|
471
536
|
// Detect stub provider early — there's no LLM, so feedback
|
|
472
537
|
// can't fire. Stub returns the prompt verbatim which would
|
|
473
538
|
// appear as a "fix" but is garbage. Log + skip cleanly.
|
|
@@ -478,11 +543,27 @@ class SpecVerseRealizeEngine {
|
|
|
478
543
|
}
|
|
479
544
|
else {
|
|
480
545
|
try {
|
|
481
|
-
const { runPostEmitFeedback, buildLlmReemit } = await import('./post-emit-verify/index.js');
|
|
546
|
+
const { runPostEmitFeedback, buildLlmReemit, appendSpecGaps, formatSpecGapLine } = await import('./post-emit-verify/index.js');
|
|
482
547
|
const maxPasses = parseInt(process.env.SPECVERSE_REALIZE_POST_VERIFY_PASSES ?? '1', 10) || 1;
|
|
483
548
|
const maxFilesPerPass = parseInt(process.env.SPECVERSE_REALIZE_POST_VERIFY_MAX_FILES ?? '20', 10) || 20;
|
|
484
|
-
const
|
|
485
|
-
const
|
|
549
|
+
const enableCsv = process.env.SPECVERSE_REALIZE_POST_VERIFY_ENABLE ?? '';
|
|
550
|
+
const enabledOverrides = enableCsv
|
|
551
|
+
? new Set(enableCsv.split(',').map((s) => s.trim()).filter(Boolean))
|
|
552
|
+
: undefined;
|
|
553
|
+
const { loadContextSnapshot } = await import('./realize-context-snapshot.js');
|
|
554
|
+
const reemit = buildLlmReemit({
|
|
555
|
+
model: sharedModel,
|
|
556
|
+
contextLoader: (ownerName) => loadContextSnapshot(outputDir, ownerName),
|
|
557
|
+
});
|
|
558
|
+
const feedbackResult = await runPostEmitFeedback({ outputDir, targetLanguage: 'typescript' }, reemit, { maxPasses, maxFilesPerPass, enabledOverrides });
|
|
559
|
+
// Phase 2: surface spec gaps (STUB002s the treatment selector
|
|
560
|
+
// routed away from auto-reemit) into the diagnostic sidecar.
|
|
561
|
+
if (feedbackResult.specGaps.length > 0) {
|
|
562
|
+
appendSpecGaps(outputDir, feedbackResult.specGaps);
|
|
563
|
+
const line = formatSpecGapLine(feedbackResult.specGaps.length);
|
|
564
|
+
if (line)
|
|
565
|
+
console.log(line);
|
|
566
|
+
}
|
|
486
567
|
const before = feedbackResult.initialErrors.length;
|
|
487
568
|
const after = feedbackResult.finalErrors.length;
|
|
488
569
|
if (before > 0) {
|