@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
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Normalise rule: `crud-into-curved`.
3
+ *
4
+ * Phase 3 of `2026-05-13-NORMALISE-PHASE.md` §5 Rule 2. Lifts Service
5
+ * operations that match the canonical CRUD shape onto their target
6
+ * entity's Controller `cured:` block. This eliminates redundant service
7
+ * shells for plain CRUD wrappers — SpecVerse generates
8
+ * `cured.retrieve/create/update/delete` from a controller's cured
9
+ * declaration, so an explicit `Service.getClub` that just does
10
+ * "look up by id" is duplicating convention.
11
+ *
12
+ * Schema note: CURVED operations live on Controllers, NOT on Models.
13
+ * If the target entity has no Controller declared, the rule synthesises
14
+ * a `<Entity>Controller` (with `model: <Entity>`) in the same component
15
+ * as the Model. The cured op lands there.
16
+ *
17
+ * Before (post-Rule-1 / cluster-module-functions):
18
+ *
19
+ * components:
20
+ * App:
21
+ * models:
22
+ * Club:
23
+ * attributes: [...]
24
+ * services:
25
+ * ClubsService:
26
+ * operations:
27
+ * getClub:
28
+ * steps: [Look up Club by id, Return Club or null]
29
+ * requires: [id is provided]
30
+ * getClubPositions: # NOT lifted (filtered query)
31
+ * steps: [...]
32
+ *
33
+ * After:
34
+ *
35
+ * components:
36
+ * App:
37
+ * models:
38
+ * Club:
39
+ * attributes: [...]
40
+ * controllers:
41
+ * ClubController: # synthesised by this rule
42
+ * model: Club
43
+ * cured:
44
+ * retrieve:
45
+ * steps: [Look up Club by id, Return Club or null]
46
+ * requires: [id is provided]
47
+ * services:
48
+ * ClubsService:
49
+ * operations:
50
+ * getClubPositions: { ... } # remains
51
+ *
52
+ * If the service becomes empty after lifting, it is dropped entirely.
53
+ *
54
+ * Conservative matching:
55
+ * - Op name must be EXACTLY `<verb><EntityName>` — no trailing suffix
56
+ * (`getRound` ✓, `getRoundsForUser` ✗, `getRoundStatus` ✗)
57
+ * - Target Entity name must resolve UNIQUELY to ONE model across all
58
+ * components (the cured block goes to whichever component the model
59
+ * lives in — cross-component lifts are fine since the cured: lives
60
+ * ON the model, not at the service site)
61
+ * - Skip when the entity name is declared in multiple components (rare,
62
+ * but ambiguous — caller can rename the duplicate)
63
+ * - Target model's `cured.<curvedOp>` must NOT already be declared
64
+ * (don't overwrite spec-author intent)
65
+ * - Verb patterns recognised:
66
+ * get / retrieve / find → retrieve
67
+ * create / add → create (Phase 3: only `create` to stay safe)
68
+ * update → update
69
+ * delete / remove → delete (Phase 3: only `delete` to stay safe)
70
+ *
71
+ * NOT in Phase 3 scope:
72
+ * - retrieve_many (list / getAll / search — naming varies too widely)
73
+ * - Evolve transitions (would need lifecycle state-machine signal)
74
+ * - Cross-component lifts (target model in a different component)
75
+ * - Multi-arg create variants (createClubWithPositions)
76
+ */
77
+ /** Verb → CURVED op mapping. Order matters for prefix matching (longer first). */
78
+ const VERB_MAP = [
79
+ { verb: 'retrieve', curved: 'retrieve' },
80
+ { verb: 'create', curved: 'create' },
81
+ { verb: 'update', curved: 'update' },
82
+ { verb: 'delete', curved: 'delete' },
83
+ { verb: 'find', curved: 'retrieve' },
84
+ { verb: 'get', curved: 'retrieve' },
85
+ ];
86
+ /**
87
+ * Parse an operation name into (verb, entityName) if it matches the
88
+ * exact `<verb><PascalCaseEntity>` shape. Returns null otherwise.
89
+ *
90
+ * The entity portion must be PascalCase, contain no further uppercase
91
+ * boundaries after the leading letter (i.e. single PascalCase word),
92
+ * AND must end at the end of the string (no trailing qualifier like
93
+ * `getRoundStatus` or `getClubsForUser`).
94
+ */
95
+ function parseCrudOpName(opName) {
96
+ for (const { verb, curved } of VERB_MAP) {
97
+ if (!opName.startsWith(verb))
98
+ continue;
99
+ const rest = opName.slice(verb.length);
100
+ if (rest.length === 0)
101
+ continue;
102
+ // First character must be uppercase (start of entity name).
103
+ if (!(rest[0] >= 'A' && rest[0] <= 'Z'))
104
+ continue;
105
+ // Rest must be a SINGLE PascalCase word — no further capital letters
106
+ // after the first character. This rules out compound names like
107
+ // `getClubPositions` (Positions is a 2nd PascalCase token →
108
+ // domain-filtered query, NOT plain retrieve).
109
+ let hasSecondCapital = false;
110
+ for (let i = 1; i < rest.length; i++) {
111
+ const c = rest[i];
112
+ if (c >= 'A' && c <= 'Z') {
113
+ hasSecondCapital = true;
114
+ break;
115
+ }
116
+ }
117
+ if (hasSecondCapital)
118
+ continue;
119
+ return { verb, curved, entity: rest };
120
+ }
121
+ return null;
122
+ }
123
+ /**
124
+ * Build a model-name → component-name index across all components. Used
125
+ * by the cross-component target lookup. When an entity name is declared
126
+ * in multiple components, it's recorded as ambiguous (the rule then
127
+ * skips matches against that name).
128
+ */
129
+ function buildModelIndex(spec) {
130
+ const out = new Map();
131
+ const components = spec.components;
132
+ if (!components)
133
+ return out;
134
+ for (const [componentName, componentBody] of Object.entries(components)) {
135
+ const models = componentBody?.models;
136
+ if (!models)
137
+ continue;
138
+ for (const modelName of Object.keys(models)) {
139
+ const existing = out.get(modelName);
140
+ if (existing === undefined)
141
+ out.set(modelName, componentName);
142
+ else if (existing !== componentName)
143
+ out.set(modelName, 'ambiguous');
144
+ }
145
+ }
146
+ return out;
147
+ }
148
+ function detectMatchesInComponent(spec, modelIndex, componentName, componentBody) {
149
+ const out = [];
150
+ const services = componentBody.services;
151
+ if (!services)
152
+ return out;
153
+ for (const [serviceName, serviceBody] of Object.entries(services)) {
154
+ if (!serviceBody || typeof serviceBody !== 'object')
155
+ continue;
156
+ const ops = serviceBody.operations;
157
+ if (!ops || typeof ops !== 'object')
158
+ continue;
159
+ for (const opName of Object.keys(ops)) {
160
+ const parsed = parseCrudOpName(opName);
161
+ if (!parsed)
162
+ continue;
163
+ const targetComponent = modelIndex.get(parsed.entity);
164
+ if (!targetComponent || targetComponent === 'ambiguous')
165
+ continue;
166
+ // Look up the target controller's cured block (if a controller
167
+ // already exists for this model). Skip if the op is already
168
+ // declared. Controllers may be missing — that's fine; the
169
+ // transform will synthesise one.
170
+ const components = spec.components;
171
+ const targetComponentBody = components[targetComponent];
172
+ const existingController = findControllerForModel(targetComponentBody, parsed.entity);
173
+ if (existingController) {
174
+ const existingCured = existingController.cured;
175
+ if (existingCured && parsed.curved in existingCured)
176
+ continue;
177
+ }
178
+ out.push({
179
+ sourceComponentName: componentName,
180
+ targetComponentName: targetComponent,
181
+ modelName: parsed.entity,
182
+ serviceName,
183
+ operationName: opName,
184
+ curvedOp: parsed.curved,
185
+ });
186
+ }
187
+ }
188
+ return out;
189
+ }
190
+ /**
191
+ * Find the controller in `componentBody` whose `model:` field references
192
+ * the given entity name. Returns the controller body, or null if none.
193
+ * The controller's KEY in the controllers object is the controller name
194
+ * (e.g. ClubController); the `model:` field declares which model it
195
+ * operates on.
196
+ */
197
+ function findControllerForModel(componentBody, modelName) {
198
+ const controllers = componentBody.controllers;
199
+ if (!controllers || typeof controllers !== 'object')
200
+ return null;
201
+ for (const ctrlBody of Object.values(controllers)) {
202
+ if (!ctrlBody || typeof ctrlBody !== 'object')
203
+ continue;
204
+ if (ctrlBody.model === modelName) {
205
+ return ctrlBody;
206
+ }
207
+ }
208
+ return null;
209
+ }
210
+ function applyCrudMatch(spec, data) {
211
+ const components = spec.components;
212
+ const sourceComponent = components[data.sourceComponentName];
213
+ const targetComponent = components[data.targetComponentName];
214
+ const services = sourceComponent.services;
215
+ const serviceBody = services[data.serviceName];
216
+ const ops = serviceBody.operations;
217
+ const opBody = ops[data.operationName];
218
+ // Locate the target controller (synthesise one if absent).
219
+ let targetController = findControllerForModel(targetComponent, data.modelName);
220
+ let targetControllerName = null;
221
+ if (!targetController) {
222
+ targetControllerName = `${data.modelName}Controller`;
223
+ targetController = { model: data.modelName };
224
+ // Insert into the component's controllers map. Create the map if
225
+ // it doesn't exist (preserving the standard position in the
226
+ // component — models then controllers then services).
227
+ const existingControllers = targetComponent.controllers ?? {};
228
+ existingControllers[targetControllerName] = targetController;
229
+ targetComponent.controllers = existingControllers;
230
+ }
231
+ else {
232
+ // Find the key of the existing controller (we have its body, need its name for description).
233
+ const allControllers = targetComponent.controllers;
234
+ for (const [k, v] of Object.entries(allControllers)) {
235
+ if (v === targetController) {
236
+ targetControllerName = k;
237
+ break;
238
+ }
239
+ }
240
+ }
241
+ // Insert under controller.cured.<curvedOp>. Preserve standard order.
242
+ const existingCured = targetController.cured ?? {};
243
+ const newCured = {};
244
+ const STANDARD_ORDER = ['create', 'update', 'retrieve', 'retrieve_many', 'validate', 'evolve', 'delete'];
245
+ const allKeys = new Set([...Object.keys(existingCured), data.curvedOp]);
246
+ for (const k of STANDARD_ORDER) {
247
+ if (allKeys.has(k)) {
248
+ newCured[k] = k === data.curvedOp ? opBody : existingCured[k];
249
+ }
250
+ }
251
+ for (const [k, v] of Object.entries(existingCured)) {
252
+ if (!STANDARD_ORDER.includes(k))
253
+ newCured[k] = v;
254
+ }
255
+ targetController.cured = newCured;
256
+ // Remove operation from service.
257
+ delete ops[data.operationName];
258
+ // If the service has no remaining operations, drop the service. If
259
+ // doing so leaves an empty services map, drop that too.
260
+ if (Object.keys(ops).length === 0) {
261
+ delete services[data.serviceName];
262
+ if (Object.keys(services).length === 0) {
263
+ delete sourceComponent.services;
264
+ }
265
+ }
266
+ return spec;
267
+ }
268
+ export const CRUD_INTO_CURVED_RULE = {
269
+ id: 'crud-into-curved',
270
+ summary: 'Lift Service operations matching <verb><Entity> exactly (where Entity is a model in the same component) to the model\'s cured: block. Drop service if empty.',
271
+ ordering: 10,
272
+ enabledByDefault: true,
273
+ appliesWhen: (ctx) => ctx.source !== 'hand-authored',
274
+ detect: (spec) => {
275
+ const matches = [];
276
+ const components = spec.components;
277
+ if (!components || typeof components !== 'object')
278
+ return matches;
279
+ const modelIndex = buildModelIndex(spec);
280
+ for (const [componentName, componentBody] of Object.entries(components)) {
281
+ if (!componentBody || typeof componentBody !== 'object')
282
+ continue;
283
+ const found = detectMatchesInComponent(spec, modelIndex, componentName, componentBody);
284
+ for (const data of found) {
285
+ const crossComp = data.sourceComponentName !== data.targetComponentName;
286
+ matches.push({
287
+ description: `${data.sourceComponentName}.${data.serviceName}.${data.operationName} → ${data.targetComponentName}.${data.modelName}.cured.${data.curvedOp}${crossComp ? ' (cross-component)' : ''}`,
288
+ data,
289
+ });
290
+ }
291
+ }
292
+ return matches;
293
+ },
294
+ transform: (spec, match) => {
295
+ return applyCrudMatch(spec, match.data);
296
+ },
297
+ };
298
+ export const __internal = {
299
+ parseCrudOpName,
300
+ buildModelIndex,
301
+ detectMatchesInComponent,
302
+ };
303
+ //# sourceMappingURL=crud-into-curved.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crud-into-curved.js","sourceRoot":"","sources":["../../../src/normalise/rules/crud-into-curved.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Normalise rule: `drop-trivial-passthrough`.
3
+ *
4
+ * Phase 4 of `2026-05-13-NORMALISE-PHASE.md` §5 Rule 3. Removes
5
+ * Controller.cured operations whose declared body is just the framework
6
+ * default — a single look-up / insert / update / delete with no
7
+ * preconditions, no published events, and no branching.
8
+ *
9
+ * Rationale: SpecVerse's realize phase auto-emits standard CURVED
10
+ * implementations for every Model. When an explicit `cured.retrieve:`
11
+ * block exists in the spec, realize uses it INSTEAD of the framework
12
+ * default. If the explicit body is semantically identical to the
13
+ * default ("Look up X by id, return X or null"), declaring it is
14
+ * noise — drop it and let inference fill in the default at realize
15
+ * time.
16
+ *
17
+ * Before (post-Rule-2):
18
+ *
19
+ * ClubController:
20
+ * model: Club
21
+ * cured:
22
+ * retrieve:
23
+ * steps:
24
+ * - Look up Club by id
25
+ * - Return Club or null
26
+ * requires: [id is provided]
27
+ *
28
+ * After:
29
+ *
30
+ * (Whole ClubController removed — synthesised by Rule 2,
31
+ * body was trivial, no other content remains.)
32
+ *
33
+ * Triviality criteria (ALL must hold):
34
+ * - `steps:` has ≤ 2 entries
35
+ * - No `publishes:` field, or `publishes: []`
36
+ * - Step text matches the operation's verb pattern:
37
+ * retrieve → look up / find / fetch / retrieve / select
38
+ * create → create / insert / store / persist
39
+ * update → update / set / modify
40
+ * delete → delete / remove
41
+ *
42
+ * Cleanup after dropping ops:
43
+ * - If `cured` becomes empty, delete it
44
+ * - If the Controller is now empty except for `model:` (i.e. we
45
+ * synthesised it via Rule 2 and there's nothing else), drop the
46
+ * whole controller. Be conservative: keep controllers that have
47
+ * `actions:`, `subscriptions:`, `path:`, or other content the user
48
+ * might have added.
49
+ * - If the component's controllers map is empty, delete it.
50
+ *
51
+ * NOT in scope:
52
+ * - Service operations (would require knowing the inference default
53
+ * applies, which it doesn't for filtered/predicate queries)
54
+ * - retrieve_many / list operations (default inference may or may not
55
+ * match the explicit body)
56
+ * - Bodies with non-trivial preconditions/postconditions (those carry
57
+ * real spec intent worth preserving even when steps look default)
58
+ */
59
+ import type { NormaliseRule, NormaliseSpec } from '../normalise-rules.js';
60
+ interface DropMatchData {
61
+ componentName: string;
62
+ controllerName: string;
63
+ curvedOp: string;
64
+ }
65
+ /**
66
+ * Is the body of a cured op trivial enough to drop? Examines steps +
67
+ * publishes + the keyword match for the curved op.
68
+ *
69
+ * Trivial rules:
70
+ * - No `publishes:` (event-driven side effect = non-trivial)
71
+ * - ≤2 steps
72
+ * - 0 steps → trivial (already minimal)
73
+ * - 1 step → trivial as long as it doesn't contain FORBIDDEN keywords
74
+ * (a single step IS the operation; verb-keyword check is meaningless
75
+ * because LLMs often collapse "look up X / return X" into one step
76
+ * like "Return Club by id")
77
+ * - 2 steps → require at least one step matches the verb keyword AND
78
+ * the other matches TRIVIAL_FOLLOWUP (e.g. "Return X or null"); no
79
+ * step may match FORBIDDEN
80
+ */
81
+ declare function isTrivialBody(body: unknown, curvedOp: string): boolean;
82
+ /**
83
+ * Detect drop candidates by walking spec.components[*].controllers[*].cured[*].
84
+ */
85
+ declare function detectDropMatches(spec: NormaliseSpec): DropMatchData[];
86
+ export declare const DROP_TRIVIAL_PASSTHROUGH_RULE: NormaliseRule;
87
+ export declare const __internal: {
88
+ isTrivialBody: typeof isTrivialBody;
89
+ detectDropMatches: typeof detectDropMatches;
90
+ };
91
+ export {};
92
+ //# sourceMappingURL=drop-trivial-passthrough.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drop-trivial-passthrough.d.ts","sourceRoot":"","sources":["../../../src/normalise/rules/drop-trivial-passthrough.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AAEH,OAAO,KAAK,EACV,aAAa,EAGb,aAAa,EACd,MAAM,uBAAuB,CAAC;AAE/B,UAAU,aAAa;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB;AA2BD;;;;;;;;;;;;;;;GAeG;AACH,iBAAS,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAqC/D;AAED;;GAEG;AACH,iBAAS,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,EAAE,CAsB/D;AAkCD,eAAO,MAAM,6BAA6B,EAAE,aAsB3C,CAAC;AAEF,eAAO,MAAM,UAAU;;;CAGtB,CAAC"}
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Normalise rule: `drop-trivial-passthrough`.
3
+ *
4
+ * Phase 4 of `2026-05-13-NORMALISE-PHASE.md` §5 Rule 3. Removes
5
+ * Controller.cured operations whose declared body is just the framework
6
+ * default — a single look-up / insert / update / delete with no
7
+ * preconditions, no published events, and no branching.
8
+ *
9
+ * Rationale: SpecVerse's realize phase auto-emits standard CURVED
10
+ * implementations for every Model. When an explicit `cured.retrieve:`
11
+ * block exists in the spec, realize uses it INSTEAD of the framework
12
+ * default. If the explicit body is semantically identical to the
13
+ * default ("Look up X by id, return X or null"), declaring it is
14
+ * noise — drop it and let inference fill in the default at realize
15
+ * time.
16
+ *
17
+ * Before (post-Rule-2):
18
+ *
19
+ * ClubController:
20
+ * model: Club
21
+ * cured:
22
+ * retrieve:
23
+ * steps:
24
+ * - Look up Club by id
25
+ * - Return Club or null
26
+ * requires: [id is provided]
27
+ *
28
+ * After:
29
+ *
30
+ * (Whole ClubController removed — synthesised by Rule 2,
31
+ * body was trivial, no other content remains.)
32
+ *
33
+ * Triviality criteria (ALL must hold):
34
+ * - `steps:` has ≤ 2 entries
35
+ * - No `publishes:` field, or `publishes: []`
36
+ * - Step text matches the operation's verb pattern:
37
+ * retrieve → look up / find / fetch / retrieve / select
38
+ * create → create / insert / store / persist
39
+ * update → update / set / modify
40
+ * delete → delete / remove
41
+ *
42
+ * Cleanup after dropping ops:
43
+ * - If `cured` becomes empty, delete it
44
+ * - If the Controller is now empty except for `model:` (i.e. we
45
+ * synthesised it via Rule 2 and there's nothing else), drop the
46
+ * whole controller. Be conservative: keep controllers that have
47
+ * `actions:`, `subscriptions:`, `path:`, or other content the user
48
+ * might have added.
49
+ * - If the component's controllers map is empty, delete it.
50
+ *
51
+ * NOT in scope:
52
+ * - Service operations (would require knowing the inference default
53
+ * applies, which it doesn't for filtered/predicate queries)
54
+ * - retrieve_many / list operations (default inference may or may not
55
+ * match the explicit body)
56
+ * - Bodies with non-trivial preconditions/postconditions (those carry
57
+ * real spec intent worth preserving even when steps look default)
58
+ */
59
+ const VERB_KEYWORDS = {
60
+ retrieve: /\b(look\s*up|find|fetch|retrieve|select|get|read|query|load|pull)\b/i,
61
+ create: /\b(create|insert|store|persist|add|save)\b/i,
62
+ update: /\b(update|set|modify|change|patch)\b/i,
63
+ delete: /\b(delete|remove|destroy|drop|erase)\b/i,
64
+ };
65
+ /**
66
+ * Patterns indicating that a step is a pure description of default
67
+ * behavior — "Return X", "Throw NotFoundError", "Yield records". Steps
68
+ * matching this MAY appear alongside a verb-keyword step without
69
+ * disqualifying the body from being trivial. Used to allow common
70
+ * second-step language like "Return Club or null".
71
+ */
72
+ const TRIVIAL_FOLLOWUP_RE = /^\s*(return|throw|yield|raise)\b/i;
73
+ /**
74
+ * Patterns indicating non-default behavior — side effects, validation
75
+ * logic, business operations. Any step matching one of these
76
+ * disqualifies the body. The intent is to catch real spec content
77
+ * (`Validate permissions`, `Notify subscribers`, `Audit access`) that
78
+ * a developer wouldn't want silently discarded.
79
+ */
80
+ const FORBIDDEN_STEP_RE = /\b(validate|notify|publish|log|audit|check|verify|ensure|calculate|compute|enrich|transform|merge|join|filter|sort|paginate)\b/i;
81
+ /**
82
+ * Is the body of a cured op trivial enough to drop? Examines steps +
83
+ * publishes + the keyword match for the curved op.
84
+ *
85
+ * Trivial rules:
86
+ * - No `publishes:` (event-driven side effect = non-trivial)
87
+ * - ≤2 steps
88
+ * - 0 steps → trivial (already minimal)
89
+ * - 1 step → trivial as long as it doesn't contain FORBIDDEN keywords
90
+ * (a single step IS the operation; verb-keyword check is meaningless
91
+ * because LLMs often collapse "look up X / return X" into one step
92
+ * like "Return Club by id")
93
+ * - 2 steps → require at least one step matches the verb keyword AND
94
+ * the other matches TRIVIAL_FOLLOWUP (e.g. "Return X or null"); no
95
+ * step may match FORBIDDEN
96
+ */
97
+ function isTrivialBody(body, curvedOp) {
98
+ if (!body || typeof body !== 'object') {
99
+ return true;
100
+ }
101
+ const b = body;
102
+ const publishes = b.publishes;
103
+ if (Array.isArray(publishes) && publishes.length > 0)
104
+ return false;
105
+ const steps = b.steps;
106
+ if (!steps)
107
+ return true;
108
+ if (!Array.isArray(steps))
109
+ return false;
110
+ if (steps.length > 2)
111
+ return false;
112
+ if (steps.length === 0)
113
+ return true;
114
+ const verbRe = VERB_KEYWORDS[curvedOp];
115
+ if (!verbRe)
116
+ return false;
117
+ // Quick guard: any step containing forbidden business-logic keywords
118
+ // disqualifies the body regardless of step count.
119
+ for (const step of steps) {
120
+ if (typeof step !== 'string')
121
+ return false;
122
+ if (FORBIDDEN_STEP_RE.test(step))
123
+ return false;
124
+ }
125
+ // Single-step body — trivial as long as no forbidden keywords appear.
126
+ if (steps.length === 1)
127
+ return true;
128
+ // Two-step body — require ≥1 verb-keyword match, others must be
129
+ // trivial follow-ups (Return / Throw etc).
130
+ let sawVerb = false;
131
+ for (const step of steps) {
132
+ const s = step;
133
+ if (verbRe.test(s))
134
+ sawVerb = true;
135
+ else if (TRIVIAL_FOLLOWUP_RE.test(s))
136
+ continue;
137
+ else
138
+ return false;
139
+ }
140
+ return sawVerb;
141
+ }
142
+ /**
143
+ * Detect drop candidates by walking spec.components[*].controllers[*].cured[*].
144
+ */
145
+ function detectDropMatches(spec) {
146
+ const out = [];
147
+ const components = spec.components;
148
+ if (!components)
149
+ return out;
150
+ for (const [componentName, componentBody] of Object.entries(components)) {
151
+ const controllers = componentBody?.controllers;
152
+ if (!controllers)
153
+ continue;
154
+ for (const [controllerName, controllerBody] of Object.entries(controllers)) {
155
+ const cured = controllerBody?.cured;
156
+ if (!cured)
157
+ continue;
158
+ for (const [curvedOp, body] of Object.entries(cured)) {
159
+ if (isTrivialBody(body, curvedOp)) {
160
+ out.push({ componentName, controllerName, curvedOp });
161
+ }
162
+ }
163
+ }
164
+ }
165
+ return out;
166
+ }
167
+ function applyDropMatch(spec, data) {
168
+ const components = spec.components;
169
+ const component = components[data.componentName];
170
+ const controllers = component.controllers;
171
+ const controller = controllers[data.controllerName];
172
+ const cured = controller.cured;
173
+ delete cured[data.curvedOp];
174
+ // Clean up empty cured.
175
+ if (Object.keys(cured).length === 0) {
176
+ delete controller.cured;
177
+ }
178
+ // Drop the controller only when its remaining content is just `model:`
179
+ // (a synthesised shell from Rule 2). Conservatively keep controllers
180
+ // that have any of: actions, subscriptions, path, description, or
181
+ // other fields the user might have authored.
182
+ const remainingKeys = Object.keys(controller);
183
+ const isJustModelShell = remainingKeys.length === 1 && remainingKeys[0] === 'model';
184
+ const isEmpty = remainingKeys.length === 0;
185
+ if (isJustModelShell || isEmpty) {
186
+ delete controllers[data.controllerName];
187
+ if (Object.keys(controllers).length === 0) {
188
+ delete component.controllers;
189
+ }
190
+ }
191
+ return spec;
192
+ }
193
+ export const DROP_TRIVIAL_PASSTHROUGH_RULE = {
194
+ id: 'drop-trivial-passthrough',
195
+ summary: 'Remove explicit cured: operations whose body is just the framework default (≤2 steps, verb-keyword match, no events). Inference re-emits the default at realize time.',
196
+ ordering: 20,
197
+ enabledByDefault: true,
198
+ appliesWhen: (ctx) => ctx.source !== 'hand-authored',
199
+ detect: (spec) => {
200
+ const matches = [];
201
+ for (const data of detectDropMatches(spec)) {
202
+ matches.push({
203
+ description: `${data.componentName}.${data.controllerName}.cured.${data.curvedOp} — trivial body, drop (inference will emit framework default)`,
204
+ data,
205
+ });
206
+ }
207
+ return matches;
208
+ },
209
+ transform: (spec, match) => {
210
+ return applyDropMatch(spec, match.data);
211
+ },
212
+ };
213
+ export const __internal = {
214
+ isTrivialBody,
215
+ detectDropMatches,
216
+ };
217
+ //# sourceMappingURL=drop-trivial-passthrough.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drop-trivial-passthrough.js","sourceRoot":"","sources":["../../../src/normalise/rules/drop-trivial-passthrough.ts"],"names":[],"mappings":"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"}