@specverse/engines 6.42.3 → 6.60.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.
Files changed (143) hide show
  1. package/dist/ai/analyse-runner.d.ts.map +1 -1
  2. package/dist/ai/analyse-runner.js +53 -1
  3. package/dist/ai/analyse-runner.js.map +1 -1
  4. package/dist/ai/prompt-runner.d.ts +39 -1
  5. package/dist/ai/prompt-runner.d.ts.map +1 -1
  6. package/dist/ai/prompt-runner.js +44 -3
  7. package/dist/ai/prompt-runner.js.map +1 -1
  8. package/dist/ai/providers/claude-cli.d.ts.map +1 -1
  9. package/dist/ai/providers/claude-cli.js +8 -1
  10. package/dist/ai/providers/claude-cli.js.map +1 -1
  11. package/dist/ai/skill-loader.d.ts +50 -0
  12. package/dist/ai/skill-loader.d.ts.map +1 -0
  13. package/dist/ai/skill-loader.js +96 -0
  14. package/dist/ai/skill-loader.js.map +1 -0
  15. package/dist/analyse-prepass/adapters/index.d.ts +2 -0
  16. package/dist/analyse-prepass/adapters/index.d.ts.map +1 -1
  17. package/dist/analyse-prepass/adapters/index.js +2 -0
  18. package/dist/analyse-prepass/adapters/index.js.map +1 -1
  19. package/dist/analyse-prepass/adapters/module-functions.d.ts +95 -0
  20. package/dist/analyse-prepass/adapters/module-functions.d.ts.map +1 -0
  21. package/dist/analyse-prepass/adapters/module-functions.js +358 -0
  22. package/dist/analyse-prepass/adapters/module-functions.js.map +1 -0
  23. package/dist/analyse-prepass/adapters/naming-convention-fks.d.ts +90 -0
  24. package/dist/analyse-prepass/adapters/naming-convention-fks.d.ts.map +1 -0
  25. package/dist/analyse-prepass/adapters/naming-convention-fks.js +181 -0
  26. package/dist/analyse-prepass/adapters/naming-convention-fks.js.map +1 -0
  27. package/dist/analyse-prepass/index.d.ts +8 -0
  28. package/dist/analyse-prepass/index.d.ts.map +1 -1
  29. package/dist/analyse-prepass/index.js +130 -0
  30. package/dist/analyse-prepass/index.js.map +1 -1
  31. package/dist/libs/instance-factories/cli/templates/commander/cli-entry-generator.js +11 -12
  32. package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +2 -2
  33. package/dist/libs/instance-factories/controllers/templates/fastify/routes-generator.js +29 -10
  34. package/dist/libs/instance-factories/services/templates/prisma/ai-behaviors-generator.js +10 -9
  35. package/dist/libs/instance-factories/services/templates/prisma/behavior-generator.js +24 -2
  36. package/dist/libs/instance-factories/services/templates/prisma/controller-generator.js +28 -20
  37. package/dist/normalise/index.d.ts +14 -0
  38. package/dist/normalise/index.d.ts.map +1 -0
  39. package/dist/normalise/index.js +14 -0
  40. package/dist/normalise/index.js.map +1 -0
  41. package/dist/normalise/load-overrides.d.ts +43 -0
  42. package/dist/normalise/load-overrides.d.ts.map +1 -0
  43. package/dist/normalise/load-overrides.js +121 -0
  44. package/dist/normalise/load-overrides.js.map +1 -0
  45. package/dist/normalise/normalise-rules.d.ts +181 -0
  46. package/dist/normalise/normalise-rules.d.ts.map +1 -0
  47. package/dist/normalise/normalise-rules.js +79 -0
  48. package/dist/normalise/normalise-rules.js.map +1 -0
  49. package/dist/normalise/rules/cluster-module-functions.d.ts +31 -0
  50. package/dist/normalise/rules/cluster-module-functions.d.ts.map +1 -0
  51. package/dist/normalise/rules/cluster-module-functions.js +238 -0
  52. package/dist/normalise/rules/cluster-module-functions.js.map +1 -0
  53. package/dist/normalise/rules/crud-into-curved.d.ts +117 -0
  54. package/dist/normalise/rules/crud-into-curved.d.ts.map +1 -0
  55. package/dist/normalise/rules/crud-into-curved.js +303 -0
  56. package/dist/normalise/rules/crud-into-curved.js.map +1 -0
  57. package/dist/normalise/rules/drop-trivial-passthrough.d.ts +92 -0
  58. package/dist/normalise/rules/drop-trivial-passthrough.d.ts.map +1 -0
  59. package/dist/normalise/rules/drop-trivial-passthrough.js +217 -0
  60. package/dist/normalise/rules/drop-trivial-passthrough.js.map +1 -0
  61. package/dist/normalise/runner.d.ts +58 -0
  62. package/dist/normalise/runner.d.ts.map +1 -0
  63. package/dist/normalise/runner.js +114 -0
  64. package/dist/normalise/runner.js.map +1 -0
  65. package/dist/parser/import-resolver/resolver.js +1 -1
  66. package/dist/parser/import-resolver/resolver.js.map +1 -1
  67. package/dist/realize/engines/typescript-engine.js +1 -1
  68. package/dist/realize/engines/typescript-engine.js.map +1 -1
  69. package/dist/realize/index.d.ts.map +1 -1
  70. package/dist/realize/index.js +221 -88
  71. package/dist/realize/index.js.map +1 -1
  72. package/dist/realize/library/library.js +1 -1
  73. package/dist/realize/library/library.js.map +1 -1
  74. package/dist/realize/library/resolver.d.ts.map +1 -1
  75. package/dist/realize/library/resolver.js +14 -1
  76. package/dist/realize/library/resolver.js.map +1 -1
  77. package/dist/realize/owner-emit-shared.d.ts +114 -0
  78. package/dist/realize/owner-emit-shared.d.ts.map +1 -0
  79. package/dist/realize/owner-emit-shared.js +227 -0
  80. package/dist/realize/owner-emit-shared.js.map +1 -0
  81. package/dist/realize/per-action-recovery.d.ts +74 -0
  82. package/dist/realize/per-action-recovery.d.ts.map +1 -0
  83. package/dist/realize/per-action-recovery.js +268 -0
  84. package/dist/realize/per-action-recovery.js.map +1 -0
  85. package/dist/realize/per-owner-emit.d.ts +7 -58
  86. package/dist/realize/per-owner-emit.d.ts.map +1 -1
  87. package/dist/realize/per-owner-emit.js +67 -215
  88. package/dist/realize/per-owner-emit.js.map +1 -1
  89. package/dist/realize/per-owner-runner.d.ts +24 -4
  90. package/dist/realize/per-owner-runner.d.ts.map +1 -1
  91. package/dist/realize/per-owner-runner.js +77 -19
  92. package/dist/realize/per-owner-runner.js.map +1 -1
  93. package/dist/realize/post-emit-verify/diagnostics.d.ts +107 -0
  94. package/dist/realize/post-emit-verify/diagnostics.d.ts.map +1 -0
  95. package/dist/realize/post-emit-verify/diagnostics.js +148 -0
  96. package/dist/realize/post-emit-verify/diagnostics.js.map +1 -0
  97. package/dist/realize/post-emit-verify/feedback-runner.d.ts +123 -0
  98. package/dist/realize/post-emit-verify/feedback-runner.d.ts.map +1 -0
  99. package/dist/realize/post-emit-verify/feedback-runner.js +232 -0
  100. package/dist/realize/post-emit-verify/feedback-runner.js.map +1 -0
  101. package/dist/realize/post-emit-verify/index.d.ts +19 -0
  102. package/dist/realize/post-emit-verify/index.d.ts.map +1 -0
  103. package/dist/realize/post-emit-verify/index.js +18 -0
  104. package/dist/realize/post-emit-verify/index.js.map +1 -0
  105. package/dist/realize/post-emit-verify/reemit.d.ts +82 -0
  106. package/dist/realize/post-emit-verify/reemit.d.ts.map +1 -0
  107. package/dist/realize/post-emit-verify/reemit.js +124 -0
  108. package/dist/realize/post-emit-verify/reemit.js.map +1 -0
  109. package/dist/realize/post-emit-verify/types.d.ts +187 -0
  110. package/dist/realize/post-emit-verify/types.d.ts.map +1 -0
  111. package/dist/realize/post-emit-verify/types.js +28 -0
  112. package/dist/realize/post-emit-verify/types.js.map +1 -0
  113. package/dist/realize/post-emit-verify/verifier-manifest.d.ts +29 -0
  114. package/dist/realize/post-emit-verify/verifier-manifest.d.ts.map +1 -0
  115. package/dist/realize/post-emit-verify/verifier-manifest.js +57 -0
  116. package/dist/realize/post-emit-verify/verifier-manifest.js.map +1 -0
  117. package/dist/realize/post-emit-verify/verifiers/stub-completeness.d.ts +85 -0
  118. package/dist/realize/post-emit-verify/verifiers/stub-completeness.d.ts.map +1 -0
  119. package/dist/realize/post-emit-verify/verifiers/stub-completeness.js +298 -0
  120. package/dist/realize/post-emit-verify/verifiers/stub-completeness.js.map +1 -0
  121. package/dist/realize/post-emit-verify/verifiers/tsc.d.ts +24 -0
  122. package/dist/realize/post-emit-verify/verifiers/tsc.d.ts.map +1 -0
  123. package/dist/realize/post-emit-verify/verifiers/tsc.js +148 -0
  124. package/dist/realize/post-emit-verify/verifiers/tsc.js.map +1 -0
  125. package/dist/realize/realize-context-snapshot.d.ts +70 -0
  126. package/dist/realize/realize-context-snapshot.d.ts.map +1 -0
  127. package/dist/realize/realize-context-snapshot.js +96 -0
  128. package/dist/realize/realize-context-snapshot.js.map +1 -0
  129. package/dist/realize/realize-rules.d.ts +113 -0
  130. package/dist/realize/realize-rules.d.ts.map +1 -0
  131. package/dist/realize/realize-rules.js +271 -0
  132. package/dist/realize/realize-rules.js.map +1 -0
  133. package/dist/realize/structural-validator.d.ts +36 -2
  134. package/dist/realize/structural-validator.d.ts.map +1 -1
  135. package/dist/realize/structural-validator.js +50 -7
  136. package/dist/realize/structural-validator.js.map +1 -1
  137. package/libs/instance-factories/cli/templates/commander/cli-entry-generator.ts +11 -12
  138. package/libs/instance-factories/cli/templates/commander/command-generator.ts +2 -2
  139. package/libs/instance-factories/controllers/templates/fastify/routes-generator.ts +49 -15
  140. package/libs/instance-factories/services/templates/prisma/ai-behaviors-generator.ts +19 -3
  141. package/libs/instance-factories/services/templates/prisma/behavior-generator.ts +62 -2
  142. package/libs/instance-factories/services/templates/prisma/controller-generator.ts +47 -20
  143. package/package.json +9 -1
@@ -50,6 +50,27 @@ function canonicalise(code) {
50
50
  .replace(/\s+/g, ' ')
51
51
  .trim();
52
52
  }
53
+ /**
54
+ * Default drop-fraction threshold: an owner may drop up to (and
55
+ * including) HALF of its pre-baked snippets and still be accepted
56
+ * (with the drops surfaced as warnings). Above half → γ-fallback.
57
+ *
58
+ * Rationale (2026-05-14, Phase 5 first target of
59
+ * `2026-05-13-VERIFIER-DIAGNOSTIC-TREATMENT-SPLIT.md`): pre-fix the
60
+ * validator rejected an ENTIRE owner if it dropped a SINGLE snippet.
61
+ * For sonnet this rarely fires; for openai-compatible models (Ollama
62
+ * qwen3-coder:30b, MarrBox) it fired on ~13/36 owners in the idle-meta
63
+ * trial — every paraphrased snippet nuked the whole owner to γ-stub.
64
+ * The post-emit feedback loop then recovered those owners anyway via
65
+ * context-complete reemit (which does NOT run this validator), so the
66
+ * strictness bought an extra LLM round-trip and STILL lost determinism.
67
+ * A threshold keeps the gross-failure guard ("the LLM basically
68
+ * ignored the scaffolding") while letting a minority of paraphrases
69
+ * through — they ride the normal tsc + stub-completeness gates instead.
70
+ *
71
+ * Overridable via `SPECVERSE_REALIZE_STRUCTURAL_DROP_THRESHOLD`.
72
+ */
73
+ export const DEFAULT_STRUCTURAL_DROP_THRESHOLD = 0.5;
53
74
  /**
54
75
  * Validate that every pre-baked snippet appears in the LLM-emitted body.
55
76
  *
@@ -58,19 +79,29 @@ function canonicalise(code) {
58
79
  * step number, step text, and the closest near-match found in the body
59
80
  * (or `null` if there's no token-overlap candidate).
60
81
  *
82
+ * `dropThreshold` is the fraction of countable snippets that may be
83
+ * dropped before the result flips to `ok: false`. At or below the
84
+ * threshold, the result is `ok: true` with the dropped snippets in
85
+ * `warnings`. Above it, `ok: false` with the full `missing` list.
86
+ *
61
87
  * Empty `prebakedSnippets` is a vacuous pass — when no convention
62
88
  * matched, there's nothing to preserve.
63
89
  */
64
- export function validateLlmOutputPreservesConventions(llmBody, prebakedSnippets) {
90
+ export function validateLlmOutputPreservesConventions(llmBody, prebakedSnippets, dropThreshold = DEFAULT_STRUCTURAL_DROP_THRESHOLD) {
65
91
  if (prebakedSnippets.length === 0) {
66
92
  return { ok: true };
67
93
  }
68
94
  const llmCanonical = canonicalise(llmBody);
69
95
  const missing = [];
96
+ // Countable = snippets with a non-empty canonical form. Comment-only
97
+ // snippets are skipped (they're vacuously "preserved") and must NOT
98
+ // inflate the denominator, or the drop fraction would understate.
99
+ let countable = 0;
70
100
  for (const snippet of prebakedSnippets) {
71
101
  const expected = canonicalise(snippet.snippet);
72
102
  if (expected.length === 0)
73
103
  continue; // skip comment-only snippets
104
+ countable++;
74
105
  if (llmCanonical.includes(expected)) {
75
106
  continue;
76
107
  }
@@ -103,7 +134,14 @@ export function validateLlmOutputPreservesConventions(llmBody, prebakedSnippets)
103
134
  if (missing.length === 0) {
104
135
  return { ok: true };
105
136
  }
106
- const reason = formatMissingReport(missing);
137
+ // Below/at threshold → accept with warnings; above → γ-fallback.
138
+ // countable is guaranteed > 0 here (missing is non-empty, and every
139
+ // entry in missing incremented countable).
140
+ const dropFraction = missing.length / countable;
141
+ if (dropFraction <= dropThreshold) {
142
+ return { ok: true, warnings: missing };
143
+ }
144
+ const reason = formatMissingReport(missing, countable, dropThreshold);
107
145
  return { ok: false, missing, reason };
108
146
  }
109
147
  /**
@@ -143,11 +181,14 @@ function extractDistinctiveTokens(snippet) {
143
181
  }
144
182
  /**
145
183
  * Format a human-readable diff report for the realize pipeline to print
146
- * when the structural validator rejects an LLM output.
184
+ * when the structural validator rejects an LLM output (drop fraction
185
+ * exceeded the threshold).
147
186
  */
148
- function formatMissingReport(missing) {
187
+ function formatMissingReport(missing, countable, dropThreshold) {
149
188
  const lines = [];
150
- lines.push(`LLM output dropped or rewrote ${missing.length} pre-baked convention snippet${missing.length === 1 ? '' : 's'}:`);
189
+ const pct = Math.round((missing.length / countable) * 100);
190
+ lines.push(`LLM output dropped or rewrote ${missing.length}/${countable} pre-baked convention snippet${countable === 1 ? '' : 's'} ` +
191
+ `(${pct}%, over the ${Math.round(dropThreshold * 100)}% threshold):`);
151
192
  for (const m of missing) {
152
193
  lines.push('');
153
194
  lines.push(` Step ${m.stepNum}: ${m.stepText}`);
@@ -160,8 +201,10 @@ function formatMissingReport(missing) {
160
201
  }
161
202
  }
162
203
  lines.push('');
163
- lines.push('The convention matcher is deterministic; pre-baked snippets must be preserved verbatim. ' +
164
- 'If you believe the matcher is wrong for this case, fix the convention pattern in step-conventions.ts.');
204
+ lines.push('The convention matcher is deterministic; pre-baked snippets should be preserved verbatim. ' +
205
+ 'A minority of drops is tolerated (see SPECVERSE_REALIZE_STRUCTURAL_DROP_THRESHOLD); this owner ' +
206
+ 'exceeded that. If you believe the matcher is wrong for this case, fix the convention pattern ' +
207
+ 'in step-conventions.ts.');
165
208
  return lines.join('\n');
166
209
  }
167
210
  //# sourceMappingURL=structural-validator.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"structural-validator.js","sourceRoot":"","sources":["../../src/realize/structural-validator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"structural-validator.js","sourceRoot":"","sources":["../../src/realize/structural-validator.ts"],"names":[],"mappings":"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"}
@@ -67,20 +67,19 @@ import { fileURLToPath } from 'url';
67
67
  import { dirname, join } from 'path';
68
68
  ${imports}
69
69
 
70
- // Read version: env var (set by bin entry point) or walk up to find package.json
70
+ // Read version: walk up to find the @specverse/self package.json,
71
+ // fall back to the build-time interpolation when not found.
71
72
  const __filename = fileURLToPath(import.meta.url);
72
73
  const __dirname = dirname(__filename);
73
- let _version = process.env.SPECVERSE_VERSION || '${version}';
74
- if (_version === '${version}') {
75
- try {
76
- for (const rel of ['../../package.json', '../../../package.json', '../../../../package.json', '../../../../../package.json']) {
77
- try {
78
- const pkg = JSON.parse(readFileSync(join(__dirname, rel), 'utf8'));
79
- if (pkg.name === '@specverse/self' && pkg.version) { _version = pkg.version; break; }
80
- } catch { /* try next */ }
81
- }
82
- } catch { /* use fallback */ }
83
- }
74
+ let _version = '${version}';
75
+ try {
76
+ for (const rel of ['../../package.json', '../../../package.json', '../../../../package.json', '../../../../../package.json']) {
77
+ try {
78
+ const pkg = JSON.parse(readFileSync(join(__dirname, rel), 'utf8'));
79
+ if (pkg.name === '@specverse/self' && pkg.version) { _version = pkg.version; break; }
80
+ } catch { /* try next */ }
81
+ }
82
+ } catch { /* use fallback */ }
84
83
 
85
84
  const program = new Command();
86
85
 
@@ -208,7 +208,7 @@ import type { ParserEngine } from '@specverse/types';`,
208
208
  } catch (qe: any) {
209
209
  // Don't let quint-gen module-loading issues block normal
210
210
  // validate (e.g. running against a much older engines version).
211
- if (process.env.SPECVERSE_DEBUG) {
211
+ if (process.env.SPECVERSE_VERBOSE === '2') {
212
212
  console.warn('quint-gen check skipped:', qe?.message ?? String(qe));
213
213
  }
214
214
  }
@@ -377,7 +377,7 @@ import type { ParserEngine, InferenceEngine, RealizeEngine } from '@specverse/ty
377
377
  process.exit(1);
378
378
  }
379
379
  } catch (qe: any) {
380
- if (process.env.SPECVERSE_DEBUG) {
380
+ if (process.env.SPECVERSE_VERBOSE === '2') {
381
381
  console.warn('quint-gen check skipped:', qe?.message ?? String(qe));
382
382
  }
383
383
  }
@@ -25,8 +25,10 @@ export default function generateFastifyRoutes(context: TemplateContext): string
25
25
  const isModelController = !!modelName;
26
26
  const handlerName = isModelController ? `${modelName}Controller` : controllerName;
27
27
 
28
- // Generate imports
29
- const imports = generateImports(controller, modelName, handlerName, isModelController, implType);
28
+ // Generate imports — defer until after handlers are built so we
29
+ // know whether FastifyRequest/FastifyReply are referenced.
30
+ // (Variable populated below.)
31
+ let imports: string;
30
32
 
31
33
  // Convert CURED operations to endpoints if endpoints don't exist
32
34
  let endpoints = controller.endpoints;
@@ -85,13 +87,30 @@ export default function generateFastifyRoutes(context: TemplateContext): string
85
87
  .map((endpoint: any) => generateRouteHandler(endpoint, modelName, handlerName, isModelController, implType, controllerName))
86
88
  .join('\n\n');
87
89
 
88
- const externalRoutesExport = externalEndpoints.length > 0 ? `
90
+ // When there are no internal handlers, suppress unused-param TS6133 by
91
+ // prefixing `fastify`/`options` with `_` and skip the `handler` decl
92
+ // entirely. Same for the external-routes export.
93
+ const hasInternalHandlers = internalEndpoints.length > 0;
94
+ const hasExternalHandlers = externalEndpoints.length > 0;
95
+
96
+ // Determine which fastify types the emitted body actually references.
97
+ // FastifyInstance is always used (function signatures). FastifyRequest +
98
+ // FastifyReply only appear inside individual route handlers.
99
+ const handlersText = internalHandlers + '\n' + externalHandlers;
100
+ const usesFastifyRequest = /\bFastifyRequest\b/.test(handlersText);
101
+ const usesFastifyReply = /\bFastifyReply\b/.test(handlersText);
102
+ imports = generateImports(
103
+ controller, modelName, handlerName, isModelController, implType,
104
+ { usesFastifyRequest, usesFastifyReply },
105
+ );
106
+
107
+ const externalRoutesExport = hasExternalHandlers ? `
89
108
  /**
90
109
  * Mount the external (root-prefix) routes for this controller.
91
110
  *
92
111
  * Called by the generated main.ts at root scope (no prefix) so action.path
93
112
  * declarations like '/api/v2/auth/register' land at exactly that URL,
94
- * bypassing the controller's '/api/<plural>' prefix.
113
+ * bypassing the controller's '/api/<prefix>' prefix.
95
114
  */
96
115
  export async function registerExternalRoutes(
97
116
  fastify: FastifyInstance,
@@ -103,6 +122,15 @@ ${externalHandlers.split('\n').map(line => ' ' + line).join('\n')}
103
122
  }
104
123
  ` : '';
105
124
 
125
+ // Body fragments — only declare `handler` when there are handlers to
126
+ // reference it; suppress unused-param TS6133 by underscore-prefixing
127
+ // when the function body is empty.
128
+ const mainBody = hasInternalHandlers
129
+ ? ` const handler = ${isModelController ? 'options.controllers' : 'options.services'}.${handlerName};\n\n${internalHandlers.split('\n').map(line => ' ' + line).join('\n')}`
130
+ : ' // No endpoints declared on this controller — empty routes plugin.';
131
+ const mainFastifyParam = hasInternalHandlers ? 'fastify' : '_fastify';
132
+ const mainOptionsParam = hasInternalHandlers ? 'options' : '_options';
133
+
106
134
  // Generate the complete route file
107
135
  return `${imports}
108
136
 
@@ -114,28 +142,34 @@ ${externalHandlers.split('\n').map(line => ' ' + line).join('\n')}
114
142
  * Operations: ${controller.endpoints?.map((e: any) => e.operation).join(', ') || 'CURED'}
115
143
  */
116
144
  export default async function ${routeName.replace('Controller', '')}Routes(
117
- fastify: FastifyInstance,
118
- options: any
145
+ ${mainFastifyParam}: FastifyInstance,
146
+ ${mainOptionsParam}: any
119
147
  ) {
120
- const handler = ${isModelController ? 'options.controllers' : 'options.services'}.${handlerName};
121
-
122
- ${internalHandlers.split('\n').map(line => ' ' + line).join('\n')}
148
+ ${mainBody}
123
149
  }
124
150
  ${externalRoutesExport}`;
125
151
  }
126
152
 
127
153
  /**
128
- * Generate imports for the route file
154
+ * Generate imports for the route file. The fastify type imports are
155
+ * gated on actual handler usage to avoid unused-import TS6133 on
156
+ * empty-routes plugins (e.g. backend-only ProductController without
157
+ * any CURVED ops or custom actions).
129
158
  */
130
159
  function generateImports(
131
- controller: any,
160
+ _controller: any,
132
161
  modelName: string,
133
- handlerName: string,
134
- isModelController: boolean,
135
- implType: any
162
+ _handlerName: string,
163
+ _isModelController: boolean,
164
+ implType: any,
165
+ usage: { usesFastifyRequest: boolean; usesFastifyReply: boolean } =
166
+ { usesFastifyRequest: true, usesFastifyReply: true },
136
167
  ): string {
168
+ const fastifyTypes = ['FastifyInstance'];
169
+ if (usage.usesFastifyRequest) fastifyTypes.push('FastifyRequest');
170
+ if (usage.usesFastifyReply) fastifyTypes.push('FastifyReply');
137
171
  const imports = [
138
- `import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';`,
172
+ `import { ${fastifyTypes.join(', ')} } from 'fastify';`,
139
173
  ];
140
174
 
141
175
  // Don't import controller/service - they're passed via options
@@ -471,6 +471,22 @@ export async function generateAiBehaviorsFile(opts: {
471
471
  returnType = `{ ${fields} }`;
472
472
  }
473
473
 
474
+ // Build the validation harness EXACTLY as the function is finally
475
+ // emitted: real signature + the wrapper-added destructure + real
476
+ // return type. Pre-fix the harness was a bare
477
+ // `export async function X(input: any)` with NO destructure — but
478
+ // the LLM is explicitly told (see the retry-hint prose below) to
479
+ // emit ONLY the body that goes AFTER `const { ... } = input;`. So a
480
+ // valid body using bare input references (`return userId + 1`)
481
+ // would spuriously fail re-validation with "Cannot find name
482
+ // 'userId'", forcing regeneration / STUB. The harness must match
483
+ // emission or the gate rejects its own correct output.
484
+ const buildTestCode = (rawBody: string): string => {
485
+ const { signature, destructure } = buildSignatureAndDestructure(rawBody);
486
+ const destructureLine = destructure ? destructure + '\n' : '';
487
+ return `export async function ${functionName}(${signature}): Promise<${returnType}> {\n${destructureLine}${rawBody}\n}`;
488
+ };
489
+
474
490
  // Check cache first — skip Claude if we've generated this exact step before
475
491
  const key = cacheKey(step, modelName, operationName, functionName, inputs);
476
492
  let body: string | null = cacheRead(key);
@@ -480,7 +496,7 @@ export async function generateAiBehaviorsFile(opts: {
480
496
  // corrupted on disk OR the validation rules may have tightened
481
497
  // (e.g. tsc type checks added). A cache hit must still be re-validated
482
498
  // through both gates so old bodies don't leak past the new bar.
483
- const testCode = `export async function ${functionName}(input: any): Promise<any> {\n${body}\n}`;
499
+ const testCode = buildTestCode(body);
484
500
  const syntaxError = await validateTypeScript(testCode);
485
501
  const typeError = syntaxError ? null : await validateTypeScriptTypes(testCode);
486
502
  const importError = (syntaxError || typeError) ? null : validateImports(testCode);
@@ -516,7 +532,7 @@ export async function generateAiBehaviorsFile(opts: {
516
532
  // with the error message appended; only ONE retry to bound
517
533
  // cost. If that still fails we keep the body but mark it
518
534
  // AI-INVALID so the user sees it needs review.
519
- const testCode = `export async function ${functionName}(input: any): Promise<any> {\n${body}\n}`;
535
+ const testCode = buildTestCode(body);
520
536
  const syntaxError = await validateTypeScript(testCode);
521
537
  if (syntaxError) {
522
538
  console.warn(` [ai-validate] ${functionName} has syntax error: ${syntaxError}`);
@@ -547,7 +563,7 @@ export async function generateAiBehaviorsFile(opts: {
547
563
  returnType,
548
564
  });
549
565
  if (retried) {
550
- const retryCode = `export async function ${functionName}(input: any): Promise<any> {\n${retried}\n}`;
566
+ const retryCode = buildTestCode(retried);
551
567
  const retrySyntaxError = await validateTypeScript(retryCode);
552
568
  const retryTypeError = retrySyntaxError ? null : await validateTypeScriptTypes(retryCode);
553
569
  const retryImportError = (retrySyntaxError || retryTypeError) ? null : validateImports(retryCode);
@@ -128,6 +128,14 @@ export function generateBehaviorWithHelpers(
128
128
 
129
129
  let body = parts.join('\n\n');
130
130
 
131
+ // Rename precondition-fetched `const X = await prisma.X.findUniqueOrThrow(...)`
132
+ // bindings to `_X` when the body has no other reference to `X`. The
133
+ // existence-check pattern emits a const for "{Model} exists" preconditions;
134
+ // if no subsequent precondition / step / postcondition references it, tsc
135
+ // raises TS6133 unused-decl. Renaming to _-prefix suppresses tsc's check
136
+ // while preserving the throw-on-missing semantics.
137
+ body = renameUnusedFindUniqueBindings(body);
138
+
131
139
  // Wrap in transaction if flagged
132
140
  if (behavior.transactional) {
133
141
  body = generateTransactionWrapper(body, context);
@@ -136,6 +144,50 @@ export function generateBehaviorWithHelpers(
136
144
  return { body, helperMethods, unmatchedSteps };
137
145
  }
138
146
 
147
+ /**
148
+ * Post-process a method body string: find each `const X = await prisma.X.findUniqueOrThrow(...)`
149
+ * binding, count downstream references to `X`, and DROP the `const X =`
150
+ * binding entirely when the var is otherwise unused (keeping just the
151
+ * `await prisma.X.findUniqueOrThrow(...)` expression statement). The
152
+ * throw-on-missing semantic is preserved; tsc's noUnusedLocals stops
153
+ * complaining because there's no local to be unused.
154
+ *
155
+ * Why drop instead of rename to `_X`: TypeScript's `_`-prefix
156
+ * exemption only applies to `noUnusedParameters` (function arg names),
157
+ * NOT to `noUnusedLocals` (var decls). A `_invoice` const still trips
158
+ * TS6133 — dropping the binding is the actual fix.
159
+ *
160
+ * The `prisma.X` namespace reference inside the call is a model
161
+ * lookup — DO NOT touch it.
162
+ */
163
+ function renameUnusedFindUniqueBindings(body: string): string {
164
+ const declRe = /\bconst\s+([a-zA-Z_][\w$]*)\s*=\s*await\s+prisma\.[\w.$]+\.findUniqueOrThrow\b/g;
165
+ const declared: string[] = [];
166
+ let m: RegExpExecArray | null;
167
+ while ((m = declRe.exec(body)) !== null) declared.push(m[1]!);
168
+ if (declared.length === 0) return body;
169
+
170
+ let out = body;
171
+ for (const name of declared) {
172
+ // Count occurrences of the bare identifier as a USAGE reference.
173
+ // The declaration line contributes 2: the `const NAME` binding
174
+ // and the `prisma.NAME` model reference. So 2 total = unused; 3+
175
+ // means at least one downstream reference.
176
+ const refRe = new RegExp(`\\b${name}\\b`, 'g');
177
+ const count = (out.match(refRe) ?? []).length;
178
+ if (count <= 2) {
179
+ // Drop the `const NAME =` prefix from the declaration line,
180
+ // leaving the `await prisma.NAME.findUniqueOrThrow(...);` as a
181
+ // pure side-effect statement.
182
+ out = out.replace(
183
+ new RegExp(`\\bconst\\s+${name}\\s*=\\s*(await\\s+prisma\\.)`),
184
+ `$1`,
185
+ );
186
+ }
187
+ }
188
+ return out;
189
+ }
190
+
139
191
  /**
140
192
  * Generate precondition checks from natural-language strings.
141
193
  * Tracks declared variables to avoid duplicates and reuses fetched entities.
@@ -304,14 +356,22 @@ function inferLogicFromOperationName(context: BehaviorContext): string {
304
356
 
305
357
  if (op.startsWith('handle')) {
306
358
  const event = op.replace('handle', '');
359
+ // Use the first declared parameter name (typically `event`) instead
360
+ // of the hard-coded `params`. The method signature uses the spec-
361
+ // derived param name, so referencing `params` produces TS2304
362
+ // "Cannot find name" errors at compile time.
363
+ const eventParam = context.parameterNames?.[0] ?? '_event';
307
364
  return ` // Event handler: ${op}
308
- console.log('[${context.serviceName}] Processing ${event}', params);
365
+ console.log('[${context.serviceName}] Processing ${event}', ${eventParam});
309
366
  return { handled: true, event: '${event}' };`;
310
367
  }
311
368
 
312
369
  if (op.startsWith('validate')) {
370
+ // Same fix as handle*: use the actual first parameter name. For
371
+ // validateX patterns the first param is conventionally `id`.
372
+ const idParam = context.parameterNames?.[0] ?? 'id';
313
373
  return ` // Validation: ${op}
314
- const records = await prisma.${modelVar}.findMany({ where: { id: params.id } });
374
+ const records = await prisma.${modelVar}.findMany({ where: { id: ${idParam} } });
315
375
  return { valid: records.length > 0, checked: records.length };`;
316
376
  }
317
377
 
@@ -46,34 +46,61 @@ export default function generatePrismaController(context: TemplateContext): stri
46
46
  // Generate custom actions and collect AI behavior stubs
47
47
  const customActions = generateCustomActions(controller, modelName, modelVar);
48
48
 
49
+ // Build the class body first, then introspect to decide which
50
+ // top-of-file declarations are actually needed. Controllers with no
51
+ // CURVED ops and no prisma-using custom actions (e.g. a controller
52
+ // that only delegates to .ai.ts behaviour) end up with empty
53
+ // bodies — emitting `prisma` / `parseId` / `PrismaClient` import
54
+ // unconditionally produced TS6133 unused-decl errors at every
55
+ // realize run. Gating on actual body content drops those.
56
+ const classBody = [
57
+ generateValidateMethod(model, modelName),
58
+ curedOps.create ? generateCreateMethod(model, modelName, modelVar, prismaDelegate, controller, allModels) : '',
59
+ curedOps.retrieve ? generateRetrieveMethod(model, modelName, modelVar, prismaDelegate) : '',
60
+ curedOps.update ? generateUpdateMethod(model, modelName, modelVar, prismaDelegate, controller, allModels) : '',
61
+ curedOps.evolve ? generateEvolveMethod(model, modelName, modelVar, prismaDelegate, controller) : '',
62
+ curedOps.delete ? generateDeleteMethod(model, modelName, modelVar, prismaDelegate, controller) : '',
63
+ customActions.code,
64
+ ].filter(Boolean).join('\n ');
65
+
66
+ const usesPrisma = /\bprisma\b/.test(classBody);
67
+ const usesParseId = /\bparseId\(/.test(classBody);
68
+ // Symbol-presence check on the actual class body — matches the
69
+ // `usesPrisma` pattern. Pre-fix this was a stub `hasEventPublishing`
70
+ // that returned true unconditionally, producing an unused-import
71
+ // TS6133 on every backend-only ProductController (validate-only,
72
+ // no CRUD, no eventBus.publish in the body).
73
+ const usesEventBus = /\beventBus\./.test(classBody);
74
+ const usesAiBehaviors = customActions.needsAiBehaviors;
75
+
76
+ // Build top-of-file declarations conditionally.
77
+ const imports = [
78
+ usesPrisma ? `import { PrismaClient } from '@prisma/client';` : '',
79
+ usesEventBus ? `import { eventBus } from '../events/eventBus.js';` : '',
80
+ usesAiBehaviors ? `import * as aiBehaviors from '../behaviors/${modelName}Controller.ai.js';` : '',
81
+ ].filter(Boolean).join('\n');
82
+
83
+ const declarations = [
84
+ usesPrisma ? `const prisma = new PrismaClient();` : '',
85
+ usesParseId
86
+ ? `/** Parse ID from string to the correct type for this model */
87
+ function parseId(id: string): ${needsIntParse ? 'number' : 'string'} {
88
+ ${needsIntParse ? 'return parseInt(id, 10);' : 'return id;'}
89
+ }`
90
+ : '',
91
+ ].filter(Boolean).join('\n\n');
92
+
49
93
  return `/**
50
94
  * ${controllerName}
51
95
  * Model-specific business logic for ${modelName}
52
96
  * ${controller.description || ''}
53
97
  */
54
-
55
- import { PrismaClient } from '@prisma/client';
56
- ${hasEventPublishing(curedOps, controller) ? `import { eventBus } from '../events/eventBus.js';` : ''}
57
- ${customActions.needsAiBehaviors ? `import * as aiBehaviors from '../behaviors/${modelName}Controller.ai.js';` : ''}
58
-
59
- const prisma = new PrismaClient();
60
-
61
- /** Parse ID from string to the correct type for this model */
62
- function parseId(id: string): ${needsIntParse ? 'number' : 'string'} {
63
- ${needsIntParse ? 'return parseInt(id, 10);' : 'return id;'}
64
- }
65
-
66
- /**
98
+ ${imports ? '\n' + imports + '\n' : ''}
99
+ ${declarations ? declarations + '\n\n' : ''}/**
67
100
  * ${controllerName} class
68
101
  */
69
102
  export class ${controllerName} {
70
- ${generateValidateMethod(model, modelName)}
71
- ${curedOps.create ? generateCreateMethod(model, modelName, modelVar, prismaDelegate, controller, allModels) : ''}
72
- ${curedOps.retrieve ? generateRetrieveMethod(model, modelName, modelVar, prismaDelegate) : ''}
73
- ${curedOps.update ? generateUpdateMethod(model, modelName, modelVar, prismaDelegate, controller, allModels) : ''}
74
- ${curedOps.evolve ? generateEvolveMethod(model, modelName, modelVar, prismaDelegate, controller) : ''}
75
- ${curedOps.delete ? generateDeleteMethod(model, modelName, modelVar, prismaDelegate, controller) : ''}
76
- ${customActions.code}
103
+ ${classBody}
77
104
  }
78
105
 
79
106
  // Export singleton instance
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specverse/engines",
3
- "version": "6.42.3",
3
+ "version": "6.60.1",
4
4
  "description": "SpecVerse toolchain — parser, inference, realize, generators, AI, registry, bundles",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -35,6 +35,14 @@
35
35
  "types": "./dist/realize/index.d.ts",
36
36
  "import": "./dist/realize/index.js"
37
37
  },
38
+ "./realize/post-emit-verify": {
39
+ "types": "./dist/realize/post-emit-verify/index.d.ts",
40
+ "import": "./dist/realize/post-emit-verify/index.js"
41
+ },
42
+ "./normalise": {
43
+ "types": "./dist/normalise/index.d.ts",
44
+ "import": "./dist/normalise/index.js"
45
+ },
38
46
  "./generators": {
39
47
  "types": "./dist/generators/index.d.ts",
40
48
  "import": "./dist/generators/index.js"