@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
@@ -9,7 +9,7 @@ function generateFastifyRoutes(context) {
9
9
  const routeName = controllerName || `${modelName}Controller`;
10
10
  const isModelController = !!modelName;
11
11
  const handlerName = isModelController ? `${modelName}Controller` : controllerName;
12
- const imports = generateImports(controller, modelName, handlerName, isModelController, implType);
12
+ let imports;
13
13
  let endpoints = controller.endpoints;
14
14
  if (!endpoints || endpoints.length === 0) {
15
15
  if (controller.cured) {
@@ -41,13 +41,26 @@ function generateFastifyRoutes(context) {
41
41
  const externalEndpoints = endpoints?.filter((e) => e.external) || [];
42
42
  const internalHandlers = internalEndpoints.map((endpoint) => generateRouteHandler(endpoint, modelName, handlerName, isModelController, implType, controllerName)).join("\n\n");
43
43
  const externalHandlers = externalEndpoints.map((endpoint) => generateRouteHandler(endpoint, modelName, handlerName, isModelController, implType, controllerName)).join("\n\n");
44
- const externalRoutesExport = externalEndpoints.length > 0 ? `
44
+ const hasInternalHandlers = internalEndpoints.length > 0;
45
+ const hasExternalHandlers = externalEndpoints.length > 0;
46
+ const handlersText = internalHandlers + "\n" + externalHandlers;
47
+ const usesFastifyRequest = /\bFastifyRequest\b/.test(handlersText);
48
+ const usesFastifyReply = /\bFastifyReply\b/.test(handlersText);
49
+ imports = generateImports(
50
+ controller,
51
+ modelName,
52
+ handlerName,
53
+ isModelController,
54
+ implType,
55
+ { usesFastifyRequest, usesFastifyReply }
56
+ );
57
+ const externalRoutesExport = hasExternalHandlers ? `
45
58
  /**
46
59
  * Mount the external (root-prefix) routes for this controller.
47
60
  *
48
61
  * Called by the generated main.ts at root scope (no prefix) so action.path
49
62
  * declarations like '/api/v2/auth/register' land at exactly that URL,
50
- * bypassing the controller's '/api/<plural>' prefix.
63
+ * bypassing the controller's '/api/<prefix>' prefix.
51
64
  */
52
65
  export async function registerExternalRoutes(
53
66
  fastify: FastifyInstance,
@@ -58,6 +71,11 @@ export async function registerExternalRoutes(
58
71
  ${externalHandlers.split("\n").map((line) => " " + line).join("\n")}
59
72
  }
60
73
  ` : "";
74
+ const mainBody = hasInternalHandlers ? ` const handler = ${isModelController ? "options.controllers" : "options.services"}.${handlerName};
75
+
76
+ ${internalHandlers.split("\n").map((line) => " " + line).join("\n")}` : " // No endpoints declared on this controller \u2014 empty routes plugin.";
77
+ const mainFastifyParam = hasInternalHandlers ? "fastify" : "_fastify";
78
+ const mainOptionsParam = hasInternalHandlers ? "options" : "_options";
61
79
  return `${imports}
62
80
 
63
81
  /**
@@ -68,18 +86,19 @@ ${externalHandlers.split("\n").map((line) => " " + line).join("\n")}
68
86
  * Operations: ${controller.endpoints?.map((e) => e.operation).join(", ") || "CURED"}
69
87
  */
70
88
  export default async function ${routeName.replace("Controller", "")}Routes(
71
- fastify: FastifyInstance,
72
- options: any
89
+ ${mainFastifyParam}: FastifyInstance,
90
+ ${mainOptionsParam}: any
73
91
  ) {
74
- const handler = ${isModelController ? "options.controllers" : "options.services"}.${handlerName};
75
-
76
- ${internalHandlers.split("\n").map((line) => " " + line).join("\n")}
92
+ ${mainBody}
77
93
  }
78
94
  ${externalRoutesExport}`;
79
95
  }
80
- function generateImports(controller, modelName, handlerName, isModelController, implType) {
96
+ function generateImports(_controller, modelName, _handlerName, _isModelController, implType, usage = { usesFastifyRequest: true, usesFastifyReply: true }) {
97
+ const fastifyTypes = ["FastifyInstance"];
98
+ if (usage.usesFastifyRequest) fastifyTypes.push("FastifyRequest");
99
+ if (usage.usesFastifyReply) fastifyTypes.push("FastifyReply");
81
100
  const imports = [
82
- `import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';`
101
+ `import { ${fastifyTypes.join(", ")} } from 'fastify';`
83
102
  ];
84
103
  if (implType?.technology?.validation === "zod" && modelName) {
85
104
  imports.push(`import { ${modelName}Schema } from '../validation/${modelName}.schema.js';`);
@@ -246,13 +246,18 @@ async function generateAiBehaviorsFile(opts) {
246
246
  const fields = Object.entries(returns).map(([k, v]) => `${k}: ${v}`).join("; ");
247
247
  returnType = `{ ${fields} }`;
248
248
  }
249
+ const buildTestCode = (rawBody) => {
250
+ const { signature, destructure } = buildSignatureAndDestructure(rawBody);
251
+ const destructureLine = destructure ? destructure + "\n" : "";
252
+ return `export async function ${functionName}(${signature}): Promise<${returnType}> {
253
+ ${destructureLine}${rawBody}
254
+ }`;
255
+ };
249
256
  const key = cacheKey(step, modelName, operationName, functionName, inputs);
250
257
  let body = cacheRead(key);
251
258
  let source = body ? "AI-CACHED" : "STUB";
252
259
  if (body) {
253
- const testCode = `export async function ${functionName}(input: any): Promise<any> {
254
- ${body}
255
- }`;
260
+ const testCode = buildTestCode(body);
256
261
  const syntaxError = await validateTypeScript(testCode);
257
262
  const typeError = syntaxError ? null : await validateTypeScriptTypes(testCode);
258
263
  const importError = syntaxError || typeError ? null : validateImports(testCode);
@@ -280,9 +285,7 @@ ${body}
280
285
  // Pass declared return type to Claude
281
286
  });
282
287
  if (body) {
283
- const testCode = `export async function ${functionName}(input: any): Promise<any> {
284
- ${body}
285
- }`;
288
+ const testCode = buildTestCode(body);
286
289
  const syntaxError = await validateTypeScript(testCode);
287
290
  if (syntaxError) {
288
291
  console.warn(` [ai-validate] ${functionName} has syntax error: ${syntaxError}`);
@@ -324,9 +327,7 @@ ${retryHint}`,
324
327
  returnType
325
328
  });
326
329
  if (retried) {
327
- const retryCode = `export async function ${functionName}(input: any): Promise<any> {
328
- ${retried}
329
- }`;
330
+ const retryCode = buildTestCode(retried);
330
331
  const retrySyntaxError = await validateTypeScript(retryCode);
331
332
  const retryTypeError = retrySyntaxError ? null : await validateTypeScriptTypes(retryCode);
332
333
  const retryImportError = retrySyntaxError || retryTypeError ? null : validateImports(retryCode);
@@ -31,11 +31,31 @@ function generateBehaviorWithHelpers(behavior, opMeta, context) {
31
31
  );
32
32
  if (events) parts.push(events);
33
33
  let body = parts.join("\n\n");
34
+ body = renameUnusedFindUniqueBindings(body);
34
35
  if (behavior.transactional) {
35
36
  body = generateTransactionWrapper(body, context);
36
37
  }
37
38
  return { body, helperMethods, unmatchedSteps };
38
39
  }
40
+ function renameUnusedFindUniqueBindings(body) {
41
+ const declRe = /\bconst\s+([a-zA-Z_][\w$]*)\s*=\s*await\s+prisma\.[\w.$]+\.findUniqueOrThrow\b/g;
42
+ const declared = [];
43
+ let m;
44
+ while ((m = declRe.exec(body)) !== null) declared.push(m[1]);
45
+ if (declared.length === 0) return body;
46
+ let out = body;
47
+ for (const name of declared) {
48
+ const refRe = new RegExp(`\\b${name}\\b`, "g");
49
+ const count = (out.match(refRe) ?? []).length;
50
+ if (count <= 2) {
51
+ out = out.replace(
52
+ new RegExp(`\\bconst\\s+${name}\\s*=\\s*(await\\s+prisma\\.)`),
53
+ `$1`
54
+ );
55
+ }
56
+ }
57
+ return out;
58
+ }
39
59
  function generatePreconditionChecks(preconditions, context, declared) {
40
60
  if (preconditions.length === 0) return "";
41
61
  if (!declared) declared = /* @__PURE__ */ new Set();
@@ -155,13 +175,15 @@ function inferLogicFromOperationName(context) {
155
175
  const modelVar = prismaModel.charAt(0).toLowerCase() + prismaModel.slice(1);
156
176
  if (op.startsWith("handle")) {
157
177
  const event = op.replace("handle", "");
178
+ const eventParam = context.parameterNames?.[0] ?? "_event";
158
179
  return ` // Event handler: ${op}
159
- console.log('[${context.serviceName}] Processing ${event}', params);
180
+ console.log('[${context.serviceName}] Processing ${event}', ${eventParam});
160
181
  return { handled: true, event: '${event}' };`;
161
182
  }
162
183
  if (op.startsWith("validate")) {
184
+ const idParam = context.parameterNames?.[0] ?? "id";
163
185
  return ` // Validation: ${op}
164
- const records = await prisma.${modelVar}.findMany({ where: { id: params.id } });
186
+ const records = await prisma.${modelVar}.findMany({ where: { id: ${idParam} } });
165
187
  return { valid: records.length > 0, checked: records.length };`;
166
188
  }
167
189
  if (op.startsWith("get") || op.startsWith("list") || op.startsWith("find")) {
@@ -19,34 +19,42 @@ function generatePrismaController(context) {
19
19
  const idType = idAttr?.type || "UUID";
20
20
  const needsIntParse = idType === "Integer" || idType === "Int" || idType === "Number";
21
21
  const customActions = generateCustomActions(controller, modelName, modelVar);
22
+ const classBody = [
23
+ generateValidateMethod(model, modelName),
24
+ curedOps.create ? generateCreateMethod(model, modelName, modelVar, prismaDelegate, controller, allModels) : "",
25
+ curedOps.retrieve ? generateRetrieveMethod(model, modelName, modelVar, prismaDelegate) : "",
26
+ curedOps.update ? generateUpdateMethod(model, modelName, modelVar, prismaDelegate, controller, allModels) : "",
27
+ curedOps.evolve ? generateEvolveMethod(model, modelName, modelVar, prismaDelegate, controller) : "",
28
+ curedOps.delete ? generateDeleteMethod(model, modelName, modelVar, prismaDelegate, controller) : "",
29
+ customActions.code
30
+ ].filter(Boolean).join("\n ");
31
+ const usesPrisma = /\bprisma\b/.test(classBody);
32
+ const usesParseId = /\bparseId\(/.test(classBody);
33
+ const usesEventBus = /\beventBus\./.test(classBody);
34
+ const usesAiBehaviors = customActions.needsAiBehaviors;
35
+ const imports = [
36
+ usesPrisma ? `import { PrismaClient } from '@prisma/client';` : "",
37
+ usesEventBus ? `import { eventBus } from '../events/eventBus.js';` : "",
38
+ usesAiBehaviors ? `import * as aiBehaviors from '../behaviors/${modelName}Controller.ai.js';` : ""
39
+ ].filter(Boolean).join("\n");
40
+ const declarations = [
41
+ usesPrisma ? `const prisma = new PrismaClient();` : "",
42
+ usesParseId ? `/** Parse ID from string to the correct type for this model */
43
+ function parseId(id: string): ${needsIntParse ? "number" : "string"} {
44
+ ${needsIntParse ? "return parseInt(id, 10);" : "return id;"}
45
+ }` : ""
46
+ ].filter(Boolean).join("\n\n");
22
47
  return `/**
23
48
  * ${controllerName}
24
49
  * Model-specific business logic for ${modelName}
25
50
  * ${controller.description || ""}
26
51
  */
27
-
28
- import { PrismaClient } from '@prisma/client';
29
- ${hasEventPublishing(curedOps, controller) ? `import { eventBus } from '../events/eventBus.js';` : ""}
30
- ${customActions.needsAiBehaviors ? `import * as aiBehaviors from '../behaviors/${modelName}Controller.ai.js';` : ""}
31
-
32
- const prisma = new PrismaClient();
33
-
34
- /** Parse ID from string to the correct type for this model */
35
- function parseId(id: string): ${needsIntParse ? "number" : "string"} {
36
- ${needsIntParse ? "return parseInt(id, 10);" : "return id;"}
37
- }
38
-
39
- /**
52
+ ${imports ? "\n" + imports + "\n" : ""}
53
+ ${declarations ? declarations + "\n\n" : ""}/**
40
54
  * ${controllerName} class
41
55
  */
42
56
  export class ${controllerName} {
43
- ${generateValidateMethod(model, modelName)}
44
- ${curedOps.create ? generateCreateMethod(model, modelName, modelVar, prismaDelegate, controller, allModels) : ""}
45
- ${curedOps.retrieve ? generateRetrieveMethod(model, modelName, modelVar, prismaDelegate) : ""}
46
- ${curedOps.update ? generateUpdateMethod(model, modelName, modelVar, prismaDelegate, controller, allModels) : ""}
47
- ${curedOps.evolve ? generateEvolveMethod(model, modelName, modelVar, prismaDelegate, controller) : ""}
48
- ${curedOps.delete ? generateDeleteMethod(model, modelName, modelVar, prismaDelegate, controller) : ""}
49
- ${customActions.code}
57
+ ${classBody}
50
58
  }
51
59
 
52
60
  // Export singleton instance
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @specverse/engines/normalise — rule-based spec normalisation pass.
3
+ *
4
+ * Reshapes a faithful-extract spec (output of `analyse`) into a
5
+ * canonical SpecVerse-idiomatic form before inference/realize.
6
+ * Deterministic, manifest-driven, mirrors the realize-rules pattern.
7
+ *
8
+ * See `specverse-self/docs/proposals/2026-05-13-NORMALISE-PHASE.md`
9
+ * for the design + phasing.
10
+ */
11
+ export { type NormaliseRule, type NormaliseRuleMatch, type NormaliseRuleResult, type NormaliseContext, type NormaliseSpec, RULES, rulesApplicableTo, getRule, } from './normalise-rules.js';
12
+ export { runNormalise, type NormaliseRunResult, type NormaliseRunOptions, } from './runner.js';
13
+ export { loadNormaliseOverrides, type NormaliseOverrides, } from './load-overrides.js';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/normalise/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,EACL,iBAAiB,EACjB,OAAO,GACR,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,YAAY,EACZ,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,sBAAsB,EACtB,KAAK,kBAAkB,GACxB,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @specverse/engines/normalise — rule-based spec normalisation pass.
3
+ *
4
+ * Reshapes a faithful-extract spec (output of `analyse`) into a
5
+ * canonical SpecVerse-idiomatic form before inference/realize.
6
+ * Deterministic, manifest-driven, mirrors the realize-rules pattern.
7
+ *
8
+ * See `specverse-self/docs/proposals/2026-05-13-NORMALISE-PHASE.md`
9
+ * for the design + phasing.
10
+ */
11
+ export { RULES, rulesApplicableTo, getRule, } from './normalise-rules.js';
12
+ export { runNormalise, } from './runner.js';
13
+ export { loadNormaliseOverrides, } from './load-overrides.js';
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/normalise/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAML,KAAK,EACL,iBAAiB,EACjB,OAAO,GACR,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,YAAY,GAGb,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,sBAAsB,GAEvB,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Load project-level normalise overrides from a `.normalise.json` file.
3
+ *
4
+ * Phase 5 of `2026-05-13-NORMALISE-PHASE.md`. Allows users to disable
5
+ * default-on rules or enable opt-in-only rules without editing engine
6
+ * code. The override file lives at the project root (the directory
7
+ * passed to `spv ai analyse <source>`).
8
+ *
9
+ * File shape:
10
+ *
11
+ * {
12
+ * "$schema": "https://specverse.dev/normalise.schema.json",
13
+ * "disabled": ["cluster-module-functions", "drop-trivial-passthrough"],
14
+ * "enabled": ["some-opt-in-rule"]
15
+ * }
16
+ *
17
+ * `disabled` lists rule IDs to skip even though they're enabled by
18
+ * default. `enabled` lists rule IDs to run even though they're opt-in.
19
+ * Both arrays are optional and default to empty.
20
+ *
21
+ * Unknown rule IDs produce a warning (returned via `warnings`) but
22
+ * don't fail the load — engine versions evolve and the user may have
23
+ * a config from a newer version. The runner skips unknown IDs silently.
24
+ */
25
+ export interface NormaliseOverrides {
26
+ disabledRules: Set<string>;
27
+ enabledRules: Set<string>;
28
+ /** Path that was loaded (absolute), or null if no file existed. */
29
+ source: string | null;
30
+ /** Non-fatal issues — unknown rule IDs, malformed entries. Caller
31
+ * surfaces these to the audit log so the user sees them. */
32
+ warnings: string[];
33
+ }
34
+ /**
35
+ * Load `.normalise.json` from `projectDir`. Returns empty sets when:
36
+ * - the file doesn't exist (no overrides configured)
37
+ * - the file is unreadable / not valid JSON / not the expected shape
38
+ *
39
+ * The function NEVER throws — analyse should proceed even when the
40
+ * config is broken. Errors land in `warnings`.
41
+ */
42
+ export declare function loadNormaliseOverrides(projectDir: string): NormaliseOverrides;
43
+ //# sourceMappingURL=load-overrides.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-overrides.d.ts","sourceRoot":"","sources":["../../src/normalise/load-overrides.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,mEAAmE;IACnE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;iEAC6D;IAC7D,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAID;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,kBAAkB,CAuF7E"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Load project-level normalise overrides from a `.normalise.json` file.
3
+ *
4
+ * Phase 5 of `2026-05-13-NORMALISE-PHASE.md`. Allows users to disable
5
+ * default-on rules or enable opt-in-only rules without editing engine
6
+ * code. The override file lives at the project root (the directory
7
+ * passed to `spv ai analyse <source>`).
8
+ *
9
+ * File shape:
10
+ *
11
+ * {
12
+ * "$schema": "https://specverse.dev/normalise.schema.json",
13
+ * "disabled": ["cluster-module-functions", "drop-trivial-passthrough"],
14
+ * "enabled": ["some-opt-in-rule"]
15
+ * }
16
+ *
17
+ * `disabled` lists rule IDs to skip even though they're enabled by
18
+ * default. `enabled` lists rule IDs to run even though they're opt-in.
19
+ * Both arrays are optional and default to empty.
20
+ *
21
+ * Unknown rule IDs produce a warning (returned via `warnings`) but
22
+ * don't fail the load — engine versions evolve and the user may have
23
+ * a config from a newer version. The runner skips unknown IDs silently.
24
+ */
25
+ import { readFileSync, existsSync } from 'fs';
26
+ import { join } from 'path';
27
+ import { RULES } from './normalise-rules.js';
28
+ const CONFIG_FILENAME = '.normalise.json';
29
+ /**
30
+ * Load `.normalise.json` from `projectDir`. Returns empty sets when:
31
+ * - the file doesn't exist (no overrides configured)
32
+ * - the file is unreadable / not valid JSON / not the expected shape
33
+ *
34
+ * The function NEVER throws — analyse should proceed even when the
35
+ * config is broken. Errors land in `warnings`.
36
+ */
37
+ export function loadNormaliseOverrides(projectDir) {
38
+ const empty = {
39
+ disabledRules: new Set(),
40
+ enabledRules: new Set(),
41
+ source: null,
42
+ warnings: [],
43
+ };
44
+ const configPath = join(projectDir, CONFIG_FILENAME);
45
+ if (!existsSync(configPath))
46
+ return empty;
47
+ let raw;
48
+ try {
49
+ raw = readFileSync(configPath, 'utf8');
50
+ }
51
+ catch (err) {
52
+ return {
53
+ ...empty,
54
+ source: configPath,
55
+ warnings: [`Could not read ${CONFIG_FILENAME}: ${err?.message ?? String(err)}`],
56
+ };
57
+ }
58
+ let parsed;
59
+ try {
60
+ parsed = JSON.parse(raw);
61
+ }
62
+ catch (err) {
63
+ return {
64
+ ...empty,
65
+ source: configPath,
66
+ warnings: [`${CONFIG_FILENAME} is not valid JSON: ${err?.message ?? String(err)}`],
67
+ };
68
+ }
69
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
70
+ return {
71
+ ...empty,
72
+ source: configPath,
73
+ warnings: [`${CONFIG_FILENAME} must contain a JSON object at the top level`],
74
+ };
75
+ }
76
+ const config = parsed;
77
+ const warnings = [];
78
+ const knownIds = new Set(RULES.map((r) => r.id));
79
+ const disabledRules = new Set();
80
+ const enabledRules = new Set();
81
+ if ('disabled' in config) {
82
+ const disabled = config.disabled;
83
+ if (!Array.isArray(disabled)) {
84
+ warnings.push(`${CONFIG_FILENAME}: "disabled" must be an array of rule IDs (got ${typeof disabled})`);
85
+ }
86
+ else {
87
+ for (const id of disabled) {
88
+ if (typeof id !== 'string') {
89
+ warnings.push(`${CONFIG_FILENAME}: entry in "disabled" is not a string (got ${typeof id})`);
90
+ continue;
91
+ }
92
+ if (!knownIds.has(id)) {
93
+ warnings.push(`${CONFIG_FILENAME}: unknown rule ID "${id}" in "disabled" (known: ${[...knownIds].join(', ')})`);
94
+ }
95
+ // We still add unknown IDs to the set — they're harmless (no
96
+ // rule with that ID will match) and the warning is enough.
97
+ disabledRules.add(id);
98
+ }
99
+ }
100
+ }
101
+ if ('enabled' in config) {
102
+ const enabled = config.enabled;
103
+ if (!Array.isArray(enabled)) {
104
+ warnings.push(`${CONFIG_FILENAME}: "enabled" must be an array of rule IDs (got ${typeof enabled})`);
105
+ }
106
+ else {
107
+ for (const id of enabled) {
108
+ if (typeof id !== 'string') {
109
+ warnings.push(`${CONFIG_FILENAME}: entry in "enabled" is not a string (got ${typeof id})`);
110
+ continue;
111
+ }
112
+ if (!knownIds.has(id)) {
113
+ warnings.push(`${CONFIG_FILENAME}: unknown rule ID "${id}" in "enabled"`);
114
+ }
115
+ enabledRules.add(id);
116
+ }
117
+ }
118
+ }
119
+ return { disabledRules, enabledRules, source: configPath, warnings };
120
+ }
121
+ //# sourceMappingURL=load-overrides.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-overrides.js","sourceRoot":"","sources":["../../src/normalise/load-overrides.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Normalise rule manifest.
3
+ *
4
+ * Single source of truth for every deterministic spec-reshape rule
5
+ * applied between analyse (faithful extraction) and inference. Mirrors
6
+ * the realize-rules manifest pattern (see `engines/src/realize/realize-rules.ts`):
7
+ * one declaration per rule, three consumers (runner, per-rule corpus,
8
+ * project-override loader).
9
+ *
10
+ * Phase 1a (engines 6.49.x) of the 2026-05-13-NORMALISE-PHASE proposal —
11
+ * INFRASTRUCTURE only. The RULES array is empty in this phase; the
12
+ * runner, manifest interface, and tests land first. Real rules
13
+ * (`cluster-module-functions`, `crud-into-curved`,
14
+ * `drop-trivial-passthrough`) land in Phase 2+.
15
+ *
16
+ * Why in engines/src/normalise/ (a new subsystem):
17
+ * Normalise is its own phase between analyse and inference, with its
18
+ * own pluggable rule set. Putting it under realize/ would conflate
19
+ * two different rule-classes (one for post-LLM-emit validation, one
20
+ * for pre-realize spec reshape). Following the entity-module
21
+ * colocation principle (R12), normalise gets its own home.
22
+ */
23
+ /**
24
+ * Normalise operates on the YAML-object representation of a spec
25
+ * (the output of `yaml.load(content)`), NOT the parsed NormaliseSpec.
26
+ * Reasons:
27
+ *
28
+ * 1. The YAML object mirrors the .specly file structure 1:1 (services
29
+ * as object-keyed maps, not arrays with name fields). Renaming a
30
+ * service key is one delete + one set; the AST form would need
31
+ * an additional `name` field update.
32
+ * 2. There is no AST → .specly serialiser for the full NormaliseSpec
33
+ * shape (only LogicalComponentSpec via SpeclyConverter, which is
34
+ * a different inference-internal shape). YAML round-trip via
35
+ * `yaml.load` + `yaml.dump` is the existing canonical path.
36
+ * 3. Normalise is structurally simple — rename / drop / rewrap. The
37
+ * strong typing the AST provides isn't needed for these
38
+ * transforms; each rule narrows the loose YAML object as needed.
39
+ *
40
+ * The shape is `{ components: { [name]: { services?: { [name]: { operations?: { [name]: any } } } } }, ... }`.
41
+ * `Record<string, unknown>` is intentional — Normalise rules MAY operate
42
+ * on top-level fields beyond `components` (e.g. `deployments`,
43
+ * `manifests`), and the YAML may carry extension fields the typed AST
44
+ * doesn't model.
45
+ */
46
+ export type NormaliseSpec = Record<string, unknown>;
47
+ /**
48
+ * Context passed to every rule. Drives applicability gates +
49
+ * provenance signals (e.g. "is this a hand-authored spec we should
50
+ * leave alone?").
51
+ */
52
+ export interface NormaliseContext {
53
+ /**
54
+ * Where the spec originated. Normalise rules are NOT applied to
55
+ * hand-authored specs by default — those are already in the form the
56
+ * author intended. `'analyse'` indicates output from the analyse
57
+ * pipeline; `'create'` from the create flow; `'hand-authored'` is
58
+ * any user-edited spec.
59
+ *
60
+ * Loading default: orchestrator passes `'analyse'` when invoked
61
+ * from the analyse pipeline; CLI invocations default to
62
+ * `'hand-authored'` (which makes default-on rules no-op unless the
63
+ * user passes `--force`).
64
+ */
65
+ source: 'analyse' | 'create' | 'hand-authored' | 'normalised';
66
+ /**
67
+ * Project-level rule overrides loaded from `.normalise.json` (if
68
+ * present). When a rule's ID appears here, the runner skips it.
69
+ * Rule overrides are append-only — a user explicitly opting OUT.
70
+ */
71
+ disabledRules?: Set<string>;
72
+ /**
73
+ * Project-level rule overrides — explicit opt-IN for rules whose
74
+ * `enabledByDefault: false`.
75
+ */
76
+ enabledRules?: Set<string>;
77
+ }
78
+ /**
79
+ * A single match produced by a rule's `detect` phase. Carries enough
80
+ * detail for the transform step + audit-trail emission.
81
+ */
82
+ export interface NormaliseRuleMatch {
83
+ /** Short human-language description for the audit log. */
84
+ description: string;
85
+ /**
86
+ * Free-form rule-private data — populated by `detect` and consumed
87
+ * by `transform`. The runner doesn't inspect this field. Used to
88
+ * carry e.g. "service-name candidates", "function-list to cluster",
89
+ * "entity to attach action to".
90
+ */
91
+ data?: unknown;
92
+ }
93
+ /**
94
+ * Result of running a rule's full pipeline (detect + transform) on
95
+ * one spec.
96
+ */
97
+ export interface NormaliseRuleResult {
98
+ ruleId: string;
99
+ /** Number of matches detect() returned. */
100
+ matchCount: number;
101
+ /**
102
+ * Brief descriptions of what was transformed. One entry per match.
103
+ * Used by the audit-trail emission.
104
+ */
105
+ matches: Array<{
106
+ description: string;
107
+ }>;
108
+ /**
109
+ * Whether this rule was SKIPPED (e.g. disabled in overrides, or
110
+ * `appliesWhen` returned false). Skipped rules report `skipped: true`
111
+ * with `matchCount: 0`.
112
+ */
113
+ skipped: boolean;
114
+ /** Reason for skip when `skipped: true`. */
115
+ skipReason?: string;
116
+ }
117
+ /**
118
+ * One normalise rule. Each field has a single purpose:
119
+ *
120
+ * - `id`: stable identifier for tests, audit, override config. Kebab-case.
121
+ * - `summary`: one-line human description.
122
+ * - `ordering`: pipeline position. Rules apply in ascending order.
123
+ * Conventions:
124
+ * 0-9 foundational shape (clustering, container synthesis)
125
+ * 10-19 shape normalisation (CRUD → CURVED, etc.)
126
+ * 20-29 cleanup (passthrough drop, trivial-action drop)
127
+ * 30+ reserved for future categories
128
+ * - `enabledByDefault`: whether the rule applies without explicit opt-in.
129
+ * Default-on rules can be disabled in `.normalise.json`. Default-off
130
+ * rules require explicit opt-in.
131
+ * - `appliesWhen` (optional): predicate gating execution. Most rules
132
+ * should skip hand-authored specs — check `ctx.source !== 'hand-authored'`.
133
+ * - `detect`: scan spec, return zero+ matches. PURE — must not mutate
134
+ * the input spec.
135
+ * - `transform`: apply ONE match to the spec, returning the
136
+ * transformed spec. The runner calls this once per match in the order
137
+ * `detect` returned them. The implementation may mutate the input
138
+ * spec in-place (the runner doesn't deep-clone between transforms);
139
+ * downstream rules see the updated state.
140
+ */
141
+ export interface NormaliseRule {
142
+ id: string;
143
+ summary: string;
144
+ ordering: number;
145
+ enabledByDefault: boolean;
146
+ appliesWhen?: (ctx: NormaliseContext) => boolean;
147
+ detect: (spec: NormaliseSpec, ctx: NormaliseContext) => NormaliseRuleMatch[];
148
+ transform: (spec: NormaliseSpec, match: NormaliseRuleMatch, ctx: NormaliseContext) => NormaliseSpec;
149
+ }
150
+ /**
151
+ * The canonical rule manifest. Append rules as they land. Order in
152
+ * the array is informational only — the runner sorts by `ordering`.
153
+ *
154
+ * Phase 2 of `2026-05-13-NORMALISE-PHASE.md` §5:
155
+ * - `cluster-module-functions` (ordering 0): strip project-prefix
156
+ * from services synthesised by the module-functions adapter.
157
+ *
158
+ * Remaining rules (Phase 3-4 in §5):
159
+ * - `crud-into-curved`: collapse Service ops 1:1 with CURVED to entity
160
+ * - `drop-trivial-passthrough`: drop single-delegate-call ops
161
+ */
162
+ export declare const RULES: NormaliseRule[];
163
+ /**
164
+ * Filter the manifest to rules applicable in `ctx`. Applies the
165
+ * standard inclusion logic in one place:
166
+ *
167
+ * 1. `disabledRules` in ctx wins — always skip.
168
+ * 2. `enabledByDefault: true` AND not in disabledRules → include.
169
+ * 3. `enabledByDefault: false` AND id is in enabledRules → include.
170
+ * 4. `appliesWhen` (when present) must return true.
171
+ *
172
+ * Returned rules are sorted by `ordering` ascending.
173
+ */
174
+ export declare function rulesApplicableTo(ctx: NormaliseContext): NormaliseRule[];
175
+ /**
176
+ * Find a rule by id. Returns undefined when no rule with that id is
177
+ * registered. Used by tests, project-override loaders, audit-trail
178
+ * emission.
179
+ */
180
+ export declare function getRule(id: string): NormaliseRule | undefined;
181
+ //# sourceMappingURL=normalise-rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalise-rules.d.ts","sourceRoot":"","sources":["../../src/normalise/normalise-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAMH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpD;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;;;;OAWG;IACH,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,eAAe,GAAG,YAAY,CAAC;IAC9D;;;;OAIG;IACH,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B;;;OAGG;IACH,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,0DAA0D;IAC1D,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,OAAO,EAAE,KAAK,CAAC;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxC;;;;OAIG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC;IACjD,MAAM,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,gBAAgB,KAAK,kBAAkB,EAAE,CAAC;IAC7E,SAAS,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,gBAAgB,KAAK,aAAa,CAAC;CACrG;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,KAAK,EAAE,aAAa,EAIhC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,gBAAgB,GAAG,aAAa,EAAE,CAaxE;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAE7D"}