@specverse/engines 6.41.1 → 6.53.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 (119) 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/services/templates/prisma/behavior-generator.js +24 -2
  34. package/dist/libs/instance-factories/services/templates/prisma/controller-generator.js +28 -20
  35. package/dist/normalise/index.d.ts +14 -0
  36. package/dist/normalise/index.d.ts.map +1 -0
  37. package/dist/normalise/index.js +14 -0
  38. package/dist/normalise/index.js.map +1 -0
  39. package/dist/normalise/load-overrides.d.ts +43 -0
  40. package/dist/normalise/load-overrides.d.ts.map +1 -0
  41. package/dist/normalise/load-overrides.js +121 -0
  42. package/dist/normalise/load-overrides.js.map +1 -0
  43. package/dist/normalise/normalise-rules.d.ts +181 -0
  44. package/dist/normalise/normalise-rules.d.ts.map +1 -0
  45. package/dist/normalise/normalise-rules.js +79 -0
  46. package/dist/normalise/normalise-rules.js.map +1 -0
  47. package/dist/normalise/rules/cluster-module-functions.d.ts +31 -0
  48. package/dist/normalise/rules/cluster-module-functions.d.ts.map +1 -0
  49. package/dist/normalise/rules/cluster-module-functions.js +238 -0
  50. package/dist/normalise/rules/cluster-module-functions.js.map +1 -0
  51. package/dist/normalise/rules/crud-into-curved.d.ts +117 -0
  52. package/dist/normalise/rules/crud-into-curved.d.ts.map +1 -0
  53. package/dist/normalise/rules/crud-into-curved.js +303 -0
  54. package/dist/normalise/rules/crud-into-curved.js.map +1 -0
  55. package/dist/normalise/rules/drop-trivial-passthrough.d.ts +92 -0
  56. package/dist/normalise/rules/drop-trivial-passthrough.d.ts.map +1 -0
  57. package/dist/normalise/rules/drop-trivial-passthrough.js +217 -0
  58. package/dist/normalise/rules/drop-trivial-passthrough.js.map +1 -0
  59. package/dist/normalise/runner.d.ts +58 -0
  60. package/dist/normalise/runner.d.ts.map +1 -0
  61. package/dist/normalise/runner.js +114 -0
  62. package/dist/normalise/runner.js.map +1 -0
  63. package/dist/parser/import-resolver/resolver.js +1 -1
  64. package/dist/parser/import-resolver/resolver.js.map +1 -1
  65. package/dist/realize/engines/typescript-engine.js +1 -1
  66. package/dist/realize/engines/typescript-engine.js.map +1 -1
  67. package/dist/realize/index.d.ts.map +1 -1
  68. package/dist/realize/index.js +142 -90
  69. package/dist/realize/index.js.map +1 -1
  70. package/dist/realize/library/library.js +1 -1
  71. package/dist/realize/library/library.js.map +1 -1
  72. package/dist/realize/library/resolver.d.ts.map +1 -1
  73. package/dist/realize/library/resolver.js +14 -1
  74. package/dist/realize/library/resolver.js.map +1 -1
  75. package/dist/realize/owner-emit-shared.d.ts +114 -0
  76. package/dist/realize/owner-emit-shared.d.ts.map +1 -0
  77. package/dist/realize/owner-emit-shared.js +227 -0
  78. package/dist/realize/owner-emit-shared.js.map +1 -0
  79. package/dist/realize/per-owner-emit.d.ts +13 -1
  80. package/dist/realize/per-owner-emit.d.ts.map +1 -1
  81. package/dist/realize/per-owner-emit.js +64 -1
  82. package/dist/realize/per-owner-emit.js.map +1 -1
  83. package/dist/realize/per-owner-runner.d.ts +36 -2
  84. package/dist/realize/per-owner-runner.d.ts.map +1 -1
  85. package/dist/realize/per-owner-runner.js +137 -3
  86. package/dist/realize/per-owner-runner.js.map +1 -1
  87. package/dist/realize/post-emit-verify/feedback-runner.d.ts +84 -0
  88. package/dist/realize/post-emit-verify/feedback-runner.d.ts.map +1 -0
  89. package/dist/realize/post-emit-verify/feedback-runner.js +177 -0
  90. package/dist/realize/post-emit-verify/feedback-runner.js.map +1 -0
  91. package/dist/realize/post-emit-verify/index.d.ts +17 -0
  92. package/dist/realize/post-emit-verify/index.d.ts.map +1 -0
  93. package/dist/realize/post-emit-verify/index.js +16 -0
  94. package/dist/realize/post-emit-verify/index.js.map +1 -0
  95. package/dist/realize/post-emit-verify/reemit.d.ts +61 -0
  96. package/dist/realize/post-emit-verify/reemit.d.ts.map +1 -0
  97. package/dist/realize/post-emit-verify/reemit.js +122 -0
  98. package/dist/realize/post-emit-verify/reemit.js.map +1 -0
  99. package/dist/realize/post-emit-verify/types.d.ts +138 -0
  100. package/dist/realize/post-emit-verify/types.d.ts.map +1 -0
  101. package/dist/realize/post-emit-verify/types.js +28 -0
  102. package/dist/realize/post-emit-verify/types.js.map +1 -0
  103. package/dist/realize/post-emit-verify/verifier-manifest.d.ts +29 -0
  104. package/dist/realize/post-emit-verify/verifier-manifest.d.ts.map +1 -0
  105. package/dist/realize/post-emit-verify/verifier-manifest.js +55 -0
  106. package/dist/realize/post-emit-verify/verifier-manifest.js.map +1 -0
  107. package/dist/realize/post-emit-verify/verifiers/tsc.d.ts +24 -0
  108. package/dist/realize/post-emit-verify/verifiers/tsc.d.ts.map +1 -0
  109. package/dist/realize/post-emit-verify/verifiers/tsc.js +148 -0
  110. package/dist/realize/post-emit-verify/verifiers/tsc.js.map +1 -0
  111. package/dist/realize/realize-rules.d.ts +113 -0
  112. package/dist/realize/realize-rules.d.ts.map +1 -0
  113. package/dist/realize/realize-rules.js +271 -0
  114. package/dist/realize/realize-rules.js.map +1 -0
  115. package/libs/instance-factories/cli/templates/commander/cli-entry-generator.ts +11 -12
  116. package/libs/instance-factories/cli/templates/commander/command-generator.ts +2 -2
  117. package/libs/instance-factories/services/templates/prisma/behavior-generator.ts +62 -2
  118. package/libs/instance-factories/services/templates/prisma/controller-generator.ts +42 -20
  119. package/package.json +9 -1
@@ -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 = hasEventPublishing(curedOps, controller);
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"}
@@ -0,0 +1,79 @@
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
+ import { CLUSTER_MODULE_FUNCTIONS_RULE } from './rules/cluster-module-functions.js';
24
+ import { CRUD_INTO_CURVED_RULE } from './rules/crud-into-curved.js';
25
+ import { DROP_TRIVIAL_PASSTHROUGH_RULE } from './rules/drop-trivial-passthrough.js';
26
+ /**
27
+ * The canonical rule manifest. Append rules as they land. Order in
28
+ * the array is informational only — the runner sorts by `ordering`.
29
+ *
30
+ * Phase 2 of `2026-05-13-NORMALISE-PHASE.md` §5:
31
+ * - `cluster-module-functions` (ordering 0): strip project-prefix
32
+ * from services synthesised by the module-functions adapter.
33
+ *
34
+ * Remaining rules (Phase 3-4 in §5):
35
+ * - `crud-into-curved`: collapse Service ops 1:1 with CURVED to entity
36
+ * - `drop-trivial-passthrough`: drop single-delegate-call ops
37
+ */
38
+ export const RULES = [
39
+ CLUSTER_MODULE_FUNCTIONS_RULE,
40
+ CRUD_INTO_CURVED_RULE,
41
+ DROP_TRIVIAL_PASSTHROUGH_RULE,
42
+ ];
43
+ /**
44
+ * Filter the manifest to rules applicable in `ctx`. Applies the
45
+ * standard inclusion logic in one place:
46
+ *
47
+ * 1. `disabledRules` in ctx wins — always skip.
48
+ * 2. `enabledByDefault: true` AND not in disabledRules → include.
49
+ * 3. `enabledByDefault: false` AND id is in enabledRules → include.
50
+ * 4. `appliesWhen` (when present) must return true.
51
+ *
52
+ * Returned rules are sorted by `ordering` ascending.
53
+ */
54
+ export function rulesApplicableTo(ctx) {
55
+ const disabled = ctx.disabledRules ?? new Set();
56
+ const enabled = ctx.enabledRules ?? new Set();
57
+ return RULES
58
+ .filter((r) => {
59
+ if (disabled.has(r.id))
60
+ return false;
61
+ const enabledByConfig = r.enabledByDefault || enabled.has(r.id);
62
+ if (!enabledByConfig)
63
+ return false;
64
+ if (r.appliesWhen && !r.appliesWhen(ctx))
65
+ return false;
66
+ return true;
67
+ })
68
+ .slice()
69
+ .sort((a, b) => a.ordering - b.ordering);
70
+ }
71
+ /**
72
+ * Find a rule by id. Returns undefined when no rule with that id is
73
+ * registered. Used by tests, project-override loaders, audit-trail
74
+ * emission.
75
+ */
76
+ export function getRule(id) {
77
+ return RULES.find((r) => r.id === id);
78
+ }
79
+ //# sourceMappingURL=normalise-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalise-rules.js","sourceRoot":"","sources":["../../src/normalise/normalise-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,6BAA6B,EAAE,MAAM,qCAAqC,CAAC;AACpF,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,6BAA6B,EAAE,MAAM,qCAAqC,CAAC;AAoIpF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,KAAK,GAAoB;IACpC,6BAA6B;IAC7B,qBAAqB;IACrB,6BAA6B;CAC9B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAqB;IACrD,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,IAAI,IAAI,GAAG,EAAU,CAAC;IACxD,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,IAAI,IAAI,GAAG,EAAU,CAAC;IACtD,OAAO,KAAK;SACT,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACZ,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAAE,OAAO,KAAK,CAAC;QACrC,MAAM,eAAe,GAAG,CAAC,CAAC,gBAAgB,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAChE,IAAI,CAAC,eAAe;YAAE,OAAO,KAAK,CAAC;QACnC,IAAI,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QACvD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;SACD,KAAK,EAAE;SACP,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;AAC7C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,EAAU;IAChC,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AACxC,CAAC"}
@@ -0,0 +1,31 @@
1
+ import type { NormaliseRule } from '../normalise-rules.js';
2
+ interface ClusterMatchData {
3
+ componentName: string;
4
+ oldServiceName: string;
5
+ newServiceName: string;
6
+ prefix: string;
7
+ /** Map from old operation key to new operation key. */
8
+ opRenames: Map<string, string>;
9
+ }
10
+ /**
11
+ * Find the longest lowercase prefix shared by `names`, ending strictly
12
+ * before the first uppercase letter (camelCase boundary). Returns the
13
+ * empty string when no such prefix is found.
14
+ */
15
+ declare function findCamelPrefix(names: string[]): string;
16
+ declare function stripCamelPrefix(name: string, prefix: string): string;
17
+ declare function stripPascalPrefix(serviceName: string, prefix: string): string | null;
18
+ /**
19
+ * Detect whether the service with `serviceName` is a cluster-cleanup
20
+ * candidate. See file-header doc for the two detection signals.
21
+ */
22
+ declare function detectClusterMatch(componentName: string, serviceName: string, serviceBody: Record<string, unknown>): ClusterMatchData | null;
23
+ export declare const CLUSTER_MODULE_FUNCTIONS_RULE: NormaliseRule;
24
+ export declare const __internal: {
25
+ findCamelPrefix: typeof findCamelPrefix;
26
+ stripCamelPrefix: typeof stripCamelPrefix;
27
+ stripPascalPrefix: typeof stripPascalPrefix;
28
+ detectClusterMatch: typeof detectClusterMatch;
29
+ };
30
+ export {};
31
+ //# sourceMappingURL=cluster-module-functions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cluster-module-functions.d.ts","sourceRoot":"","sources":["../../../src/normalise/rules/cluster-module-functions.ts"],"names":[],"mappings":"AA6DA,OAAO,KAAK,EACV,aAAa,EAId,MAAM,uBAAuB,CAAC;AAE/B,UAAU,gBAAgB;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED;;;;GAIG;AACH,iBAAS,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAYhD;AAED,iBAAS,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAK9D;AAED,iBAAS,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQ7E;AAED;;;GAGG;AACH,iBAAS,kBAAkB,CACzB,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,gBAAgB,GAAG,IAAI,CAwDzB;AAkCD,eAAO,MAAM,6BAA6B,EAAE,aAuC3C,CAAC;AAGF,eAAO,MAAM,UAAU;;;;;CAKtB,CAAC"}