@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.
- package/dist/ai/analyse-runner.d.ts.map +1 -1
- package/dist/ai/analyse-runner.js +53 -1
- package/dist/ai/analyse-runner.js.map +1 -1
- package/dist/ai/prompt-runner.d.ts +39 -1
- package/dist/ai/prompt-runner.d.ts.map +1 -1
- package/dist/ai/prompt-runner.js +44 -3
- package/dist/ai/prompt-runner.js.map +1 -1
- package/dist/ai/providers/claude-cli.d.ts.map +1 -1
- package/dist/ai/providers/claude-cli.js +8 -1
- package/dist/ai/providers/claude-cli.js.map +1 -1
- package/dist/ai/skill-loader.d.ts +50 -0
- package/dist/ai/skill-loader.d.ts.map +1 -0
- package/dist/ai/skill-loader.js +96 -0
- package/dist/ai/skill-loader.js.map +1 -0
- package/dist/analyse-prepass/adapters/index.d.ts +2 -0
- package/dist/analyse-prepass/adapters/index.d.ts.map +1 -1
- package/dist/analyse-prepass/adapters/index.js +2 -0
- package/dist/analyse-prepass/adapters/index.js.map +1 -1
- package/dist/analyse-prepass/adapters/module-functions.d.ts +95 -0
- package/dist/analyse-prepass/adapters/module-functions.d.ts.map +1 -0
- package/dist/analyse-prepass/adapters/module-functions.js +358 -0
- package/dist/analyse-prepass/adapters/module-functions.js.map +1 -0
- package/dist/analyse-prepass/adapters/naming-convention-fks.d.ts +90 -0
- package/dist/analyse-prepass/adapters/naming-convention-fks.d.ts.map +1 -0
- package/dist/analyse-prepass/adapters/naming-convention-fks.js +181 -0
- package/dist/analyse-prepass/adapters/naming-convention-fks.js.map +1 -0
- package/dist/analyse-prepass/index.d.ts +8 -0
- package/dist/analyse-prepass/index.d.ts.map +1 -1
- package/dist/analyse-prepass/index.js +130 -0
- package/dist/analyse-prepass/index.js.map +1 -1
- package/dist/libs/instance-factories/cli/templates/commander/cli-entry-generator.js +11 -12
- package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +2 -2
- package/dist/libs/instance-factories/controllers/templates/fastify/routes-generator.js +29 -10
- package/dist/libs/instance-factories/services/templates/prisma/ai-behaviors-generator.js +10 -9
- package/dist/libs/instance-factories/services/templates/prisma/behavior-generator.js +24 -2
- package/dist/libs/instance-factories/services/templates/prisma/controller-generator.js +28 -20
- package/dist/normalise/index.d.ts +14 -0
- package/dist/normalise/index.d.ts.map +1 -0
- package/dist/normalise/index.js +14 -0
- package/dist/normalise/index.js.map +1 -0
- package/dist/normalise/load-overrides.d.ts +43 -0
- package/dist/normalise/load-overrides.d.ts.map +1 -0
- package/dist/normalise/load-overrides.js +121 -0
- package/dist/normalise/load-overrides.js.map +1 -0
- package/dist/normalise/normalise-rules.d.ts +181 -0
- package/dist/normalise/normalise-rules.d.ts.map +1 -0
- package/dist/normalise/normalise-rules.js +79 -0
- package/dist/normalise/normalise-rules.js.map +1 -0
- package/dist/normalise/rules/cluster-module-functions.d.ts +31 -0
- package/dist/normalise/rules/cluster-module-functions.d.ts.map +1 -0
- package/dist/normalise/rules/cluster-module-functions.js +238 -0
- package/dist/normalise/rules/cluster-module-functions.js.map +1 -0
- package/dist/normalise/rules/crud-into-curved.d.ts +117 -0
- package/dist/normalise/rules/crud-into-curved.d.ts.map +1 -0
- package/dist/normalise/rules/crud-into-curved.js +303 -0
- package/dist/normalise/rules/crud-into-curved.js.map +1 -0
- package/dist/normalise/rules/drop-trivial-passthrough.d.ts +92 -0
- package/dist/normalise/rules/drop-trivial-passthrough.d.ts.map +1 -0
- package/dist/normalise/rules/drop-trivial-passthrough.js +217 -0
- package/dist/normalise/rules/drop-trivial-passthrough.js.map +1 -0
- package/dist/normalise/runner.d.ts +58 -0
- package/dist/normalise/runner.d.ts.map +1 -0
- package/dist/normalise/runner.js +114 -0
- package/dist/normalise/runner.js.map +1 -0
- package/dist/parser/import-resolver/resolver.js +1 -1
- package/dist/parser/import-resolver/resolver.js.map +1 -1
- package/dist/realize/engines/typescript-engine.js +1 -1
- package/dist/realize/engines/typescript-engine.js.map +1 -1
- package/dist/realize/index.d.ts.map +1 -1
- package/dist/realize/index.js +221 -88
- package/dist/realize/index.js.map +1 -1
- package/dist/realize/library/library.js +1 -1
- package/dist/realize/library/library.js.map +1 -1
- package/dist/realize/library/resolver.d.ts.map +1 -1
- package/dist/realize/library/resolver.js +14 -1
- package/dist/realize/library/resolver.js.map +1 -1
- package/dist/realize/owner-emit-shared.d.ts +114 -0
- package/dist/realize/owner-emit-shared.d.ts.map +1 -0
- package/dist/realize/owner-emit-shared.js +227 -0
- package/dist/realize/owner-emit-shared.js.map +1 -0
- package/dist/realize/per-action-recovery.d.ts +74 -0
- package/dist/realize/per-action-recovery.d.ts.map +1 -0
- package/dist/realize/per-action-recovery.js +268 -0
- package/dist/realize/per-action-recovery.js.map +1 -0
- package/dist/realize/per-owner-emit.d.ts +7 -58
- package/dist/realize/per-owner-emit.d.ts.map +1 -1
- package/dist/realize/per-owner-emit.js +67 -215
- package/dist/realize/per-owner-emit.js.map +1 -1
- package/dist/realize/per-owner-runner.d.ts +24 -4
- package/dist/realize/per-owner-runner.d.ts.map +1 -1
- package/dist/realize/per-owner-runner.js +77 -19
- package/dist/realize/per-owner-runner.js.map +1 -1
- package/dist/realize/post-emit-verify/diagnostics.d.ts +107 -0
- package/dist/realize/post-emit-verify/diagnostics.d.ts.map +1 -0
- package/dist/realize/post-emit-verify/diagnostics.js +148 -0
- package/dist/realize/post-emit-verify/diagnostics.js.map +1 -0
- package/dist/realize/post-emit-verify/feedback-runner.d.ts +123 -0
- package/dist/realize/post-emit-verify/feedback-runner.d.ts.map +1 -0
- package/dist/realize/post-emit-verify/feedback-runner.js +232 -0
- package/dist/realize/post-emit-verify/feedback-runner.js.map +1 -0
- package/dist/realize/post-emit-verify/index.d.ts +19 -0
- package/dist/realize/post-emit-verify/index.d.ts.map +1 -0
- package/dist/realize/post-emit-verify/index.js +18 -0
- package/dist/realize/post-emit-verify/index.js.map +1 -0
- package/dist/realize/post-emit-verify/reemit.d.ts +82 -0
- package/dist/realize/post-emit-verify/reemit.d.ts.map +1 -0
- package/dist/realize/post-emit-verify/reemit.js +124 -0
- package/dist/realize/post-emit-verify/reemit.js.map +1 -0
- package/dist/realize/post-emit-verify/types.d.ts +187 -0
- package/dist/realize/post-emit-verify/types.d.ts.map +1 -0
- package/dist/realize/post-emit-verify/types.js +28 -0
- package/dist/realize/post-emit-verify/types.js.map +1 -0
- package/dist/realize/post-emit-verify/verifier-manifest.d.ts +29 -0
- package/dist/realize/post-emit-verify/verifier-manifest.d.ts.map +1 -0
- package/dist/realize/post-emit-verify/verifier-manifest.js +57 -0
- package/dist/realize/post-emit-verify/verifier-manifest.js.map +1 -0
- package/dist/realize/post-emit-verify/verifiers/stub-completeness.d.ts +85 -0
- package/dist/realize/post-emit-verify/verifiers/stub-completeness.d.ts.map +1 -0
- package/dist/realize/post-emit-verify/verifiers/stub-completeness.js +298 -0
- package/dist/realize/post-emit-verify/verifiers/stub-completeness.js.map +1 -0
- package/dist/realize/post-emit-verify/verifiers/tsc.d.ts +24 -0
- package/dist/realize/post-emit-verify/verifiers/tsc.d.ts.map +1 -0
- package/dist/realize/post-emit-verify/verifiers/tsc.js +148 -0
- package/dist/realize/post-emit-verify/verifiers/tsc.js.map +1 -0
- package/dist/realize/realize-context-snapshot.d.ts +70 -0
- package/dist/realize/realize-context-snapshot.d.ts.map +1 -0
- package/dist/realize/realize-context-snapshot.js +96 -0
- package/dist/realize/realize-context-snapshot.js.map +1 -0
- package/dist/realize/realize-rules.d.ts +113 -0
- package/dist/realize/realize-rules.d.ts.map +1 -0
- package/dist/realize/realize-rules.js +271 -0
- package/dist/realize/realize-rules.js.map +1 -0
- package/dist/realize/structural-validator.d.ts +36 -2
- package/dist/realize/structural-validator.d.ts.map +1 -1
- package/dist/realize/structural-validator.js +50 -7
- package/dist/realize/structural-validator.js.map +1 -1
- package/libs/instance-factories/cli/templates/commander/cli-entry-generator.ts +11 -12
- package/libs/instance-factories/cli/templates/commander/command-generator.ts +2 -2
- package/libs/instance-factories/controllers/templates/fastify/routes-generator.ts +49 -15
- package/libs/instance-factories/services/templates/prisma/ai-behaviors-generator.ts +19 -3
- package/libs/instance-factories/services/templates/prisma/behavior-generator.ts +62 -2
- package/libs/instance-factories/services/templates/prisma/controller-generator.ts +47 -20
- package/package.json +9 -1
|
@@ -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"}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalise rule: `cluster-module-functions`.
|
|
3
|
+
*
|
|
4
|
+
* Phase 2 of the 2026-05-13-NORMALISE-PHASE proposal. Cleans up
|
|
5
|
+
* project-prefix noise on services synthesised by the module-functions
|
|
6
|
+
* adapter (Phase 0 prepass change).
|
|
7
|
+
*
|
|
8
|
+
* Trigger pattern: PowerSync / Drizzle / Supabase-queries codebases
|
|
9
|
+
* tend to prefix module functions with a project tag:
|
|
10
|
+
*
|
|
11
|
+
* export async function smcCreateRound(...)
|
|
12
|
+
* export async function smcGetRound(...)
|
|
13
|
+
* export async function smcUpdateRoundStatus(...)
|
|
14
|
+
*
|
|
15
|
+
* The prepass synthesises one service per file. After Phase 0 the
|
|
16
|
+
* faithful spec looks like:
|
|
17
|
+
*
|
|
18
|
+
* SmcRoundsService:
|
|
19
|
+
* operations:
|
|
20
|
+
* smcCreateRound: { ... }
|
|
21
|
+
* smcGetRound: { ... }
|
|
22
|
+
* smcUpdateRoundStatus: { ... }
|
|
23
|
+
*
|
|
24
|
+
* After this rule fires:
|
|
25
|
+
*
|
|
26
|
+
* RoundsService:
|
|
27
|
+
* operations:
|
|
28
|
+
* createRound: { ... }
|
|
29
|
+
* getRound: { ... }
|
|
30
|
+
* updateRoundStatus: { ... }
|
|
31
|
+
*
|
|
32
|
+
* The `Smc` / `smc` project prefix appears on both the service name
|
|
33
|
+
* (PascalCase) and operation names (camelCase). Stripping it from both
|
|
34
|
+
* removes noise while preserving the rest of the naming so the LLM-
|
|
35
|
+
* authored bodies still reference the right entities.
|
|
36
|
+
*
|
|
37
|
+
* What this rule does NOT do (deferred to later rules):
|
|
38
|
+
* - Rename service from `RoundsService` to `RoundService` based on
|
|
39
|
+
* detected target entity (would be `cluster-module-functions`'s
|
|
40
|
+
* dominant-entity inference, per proposal §5 — separate rule)
|
|
41
|
+
* - Collapse `RoundsService.getRound` → `Round.retrieve` CURVED
|
|
42
|
+
* (that's `crud-into-curved`, Rule 2)
|
|
43
|
+
* - Drop trivial passthrough ops (Rule 3)
|
|
44
|
+
*
|
|
45
|
+
* Conservative invariants:
|
|
46
|
+
* - Prefix must be 2–3 lowercase letters ending just before an
|
|
47
|
+
* uppercase letter (camelCase boundary). The 3-char ceiling is the
|
|
48
|
+
* load-bearing false-positive defence: real project tags are short
|
|
49
|
+
* non-words (`smc`, `db`, `api`, `iam`); English verbs that share
|
|
50
|
+
* a camelCase head (`save`, `claim`, `format`, `calculate`,
|
|
51
|
+
* `invalidate`) are 4+ characters and stay untouched.
|
|
52
|
+
* - At least ONE operation must share the prefix (no service-name-
|
|
53
|
+
* only renames — `SaveGameService` doesn't get stripped to
|
|
54
|
+
* `GameService` just because `Save` appears at the start of the
|
|
55
|
+
* service name; that's a meaningful word, not a project tag).
|
|
56
|
+
* - Skip when stripping would produce a name collision.
|
|
57
|
+
* - Skip when stripping would produce an empty / invalid name.
|
|
58
|
+
* - Skip when ctx.source === 'hand-authored'.
|
|
59
|
+
*/
|
|
60
|
+
const MAX_PREFIX_LENGTH = 3;
|
|
61
|
+
/**
|
|
62
|
+
* Find the longest lowercase prefix shared by `names`, ending strictly
|
|
63
|
+
* before the first uppercase letter (camelCase boundary). Returns the
|
|
64
|
+
* empty string when no such prefix is found.
|
|
65
|
+
*/
|
|
66
|
+
function findCamelPrefix(names) {
|
|
67
|
+
if (names.length === 0)
|
|
68
|
+
return '';
|
|
69
|
+
const reference = names[0];
|
|
70
|
+
let candidate = '';
|
|
71
|
+
for (let i = 0; i < reference.length; i++) {
|
|
72
|
+
const c = reference[i];
|
|
73
|
+
if (c >= 'A' && c <= 'Z')
|
|
74
|
+
break;
|
|
75
|
+
if (!(c >= 'a' && c <= 'z'))
|
|
76
|
+
break;
|
|
77
|
+
if (!names.every((n) => n[i] === c))
|
|
78
|
+
break;
|
|
79
|
+
candidate += c;
|
|
80
|
+
}
|
|
81
|
+
return candidate;
|
|
82
|
+
}
|
|
83
|
+
function stripCamelPrefix(name, prefix) {
|
|
84
|
+
if (!name.startsWith(prefix))
|
|
85
|
+
return name;
|
|
86
|
+
const rest = name.slice(prefix.length);
|
|
87
|
+
if (rest.length === 0)
|
|
88
|
+
return name;
|
|
89
|
+
return rest[0].toLowerCase() + rest.slice(1);
|
|
90
|
+
}
|
|
91
|
+
function stripPascalPrefix(serviceName, prefix) {
|
|
92
|
+
if (prefix.length === 0)
|
|
93
|
+
return null;
|
|
94
|
+
const pascalPrefix = prefix[0].toUpperCase() + prefix.slice(1).toLowerCase();
|
|
95
|
+
if (!serviceName.startsWith(pascalPrefix))
|
|
96
|
+
return null;
|
|
97
|
+
const rest = serviceName.slice(pascalPrefix.length);
|
|
98
|
+
if (rest.length === 0)
|
|
99
|
+
return null;
|
|
100
|
+
if (!(rest[0] >= 'A' && rest[0] <= 'Z'))
|
|
101
|
+
return null;
|
|
102
|
+
return rest;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Detect whether the service with `serviceName` is a cluster-cleanup
|
|
106
|
+
* candidate. See file-header doc for the two detection signals.
|
|
107
|
+
*/
|
|
108
|
+
function detectClusterMatch(componentName, serviceName, serviceBody) {
|
|
109
|
+
const ops = serviceBody.operations;
|
|
110
|
+
if (!ops || typeof ops !== 'object')
|
|
111
|
+
return null;
|
|
112
|
+
const opNames = Object.keys(ops);
|
|
113
|
+
if (opNames.length === 0)
|
|
114
|
+
return null;
|
|
115
|
+
// Signal A — multi-operation common prefix.
|
|
116
|
+
let prefix = '';
|
|
117
|
+
if (opNames.length >= 2) {
|
|
118
|
+
prefix = findCamelPrefix(opNames);
|
|
119
|
+
}
|
|
120
|
+
// Signal B — service-name prefix matched by ≥1 op's camel prefix.
|
|
121
|
+
if ((prefix.length < 2 || prefix.length > MAX_PREFIX_LENGTH) && opNames.length >= 1) {
|
|
122
|
+
for (const op of opNames) {
|
|
123
|
+
const opPrefix = findCamelPrefix([op]);
|
|
124
|
+
if (opPrefix.length < 2 || opPrefix.length > MAX_PREFIX_LENGTH)
|
|
125
|
+
continue;
|
|
126
|
+
if (stripPascalPrefix(serviceName, opPrefix) !== null) {
|
|
127
|
+
prefix = opPrefix;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Length cap: project tags are short. Anything longer is an English
|
|
133
|
+
// verb like `save` / `claim` / `calculate` that we mustn't strip.
|
|
134
|
+
if (prefix.length < 2 || prefix.length > MAX_PREFIX_LENGTH)
|
|
135
|
+
return null;
|
|
136
|
+
// Compute the renames + check for collisions.
|
|
137
|
+
const opRenames = new Map();
|
|
138
|
+
const newNamesSeen = new Set();
|
|
139
|
+
for (const op of opNames) {
|
|
140
|
+
const newName = stripCamelPrefix(op, prefix);
|
|
141
|
+
if (newName === op)
|
|
142
|
+
continue;
|
|
143
|
+
if (newName.length === 0)
|
|
144
|
+
return null;
|
|
145
|
+
if (newNamesSeen.has(newName) || opNames.includes(newName))
|
|
146
|
+
return null;
|
|
147
|
+
opRenames.set(op, newName);
|
|
148
|
+
newNamesSeen.add(newName);
|
|
149
|
+
}
|
|
150
|
+
// Activity requirement: at least one operation must share the prefix.
|
|
151
|
+
// Without this, a service whose NAME happens to begin with a short
|
|
152
|
+
// lowercase word (`SaveGameService`, `IdpFooService`) would get
|
|
153
|
+
// renamed even when no operation evidence supports the prefix being
|
|
154
|
+
// a project tag.
|
|
155
|
+
if (opRenames.size === 0)
|
|
156
|
+
return null;
|
|
157
|
+
const newServiceName = stripPascalPrefix(serviceName, prefix);
|
|
158
|
+
return {
|
|
159
|
+
componentName,
|
|
160
|
+
oldServiceName: serviceName,
|
|
161
|
+
newServiceName: newServiceName ?? serviceName,
|
|
162
|
+
prefix,
|
|
163
|
+
opRenames,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function applyClusterMatch(spec, data) {
|
|
167
|
+
const components = spec.components;
|
|
168
|
+
const component = components?.[data.componentName];
|
|
169
|
+
const services = component?.services;
|
|
170
|
+
if (!services)
|
|
171
|
+
return spec;
|
|
172
|
+
const serviceBody = services[data.oldServiceName];
|
|
173
|
+
if (!serviceBody)
|
|
174
|
+
return spec;
|
|
175
|
+
// Rename operations preserving insertion order.
|
|
176
|
+
const oldOps = serviceBody.operations;
|
|
177
|
+
const renamedOps = {};
|
|
178
|
+
for (const [oldKey, value] of Object.entries(oldOps)) {
|
|
179
|
+
const newKey = data.opRenames.get(oldKey) ?? oldKey;
|
|
180
|
+
renamedOps[newKey] = value;
|
|
181
|
+
}
|
|
182
|
+
serviceBody.operations = renamedOps;
|
|
183
|
+
// Rename service key if changed. We need to preserve insertion order
|
|
184
|
+
// here too — rebuild the services object with the renamed key in the
|
|
185
|
+
// original slot.
|
|
186
|
+
if (data.newServiceName !== data.oldServiceName) {
|
|
187
|
+
const rebuilt = {};
|
|
188
|
+
for (const [k, v] of Object.entries(services)) {
|
|
189
|
+
if (k === data.oldServiceName)
|
|
190
|
+
rebuilt[data.newServiceName] = v;
|
|
191
|
+
else
|
|
192
|
+
rebuilt[k] = v;
|
|
193
|
+
}
|
|
194
|
+
component.services = rebuilt;
|
|
195
|
+
}
|
|
196
|
+
return spec;
|
|
197
|
+
}
|
|
198
|
+
export const CLUSTER_MODULE_FUNCTIONS_RULE = {
|
|
199
|
+
id: 'cluster-module-functions',
|
|
200
|
+
summary: 'Strip project-prefix from synthesised module-function services + their operations (e.g. SmcRoundsService.smcGetRound → RoundsService.getRound).',
|
|
201
|
+
ordering: 0,
|
|
202
|
+
enabledByDefault: true,
|
|
203
|
+
appliesWhen: (ctx) => ctx.source !== 'hand-authored',
|
|
204
|
+
detect: (spec) => {
|
|
205
|
+
const matches = [];
|
|
206
|
+
const components = spec.components;
|
|
207
|
+
if (!components || typeof components !== 'object')
|
|
208
|
+
return matches;
|
|
209
|
+
for (const [componentName, componentBody] of Object.entries(components)) {
|
|
210
|
+
const services = componentBody?.services;
|
|
211
|
+
if (!services || typeof services !== 'object')
|
|
212
|
+
continue;
|
|
213
|
+
for (const [serviceName, serviceBody] of Object.entries(services)) {
|
|
214
|
+
if (!serviceBody || typeof serviceBody !== 'object')
|
|
215
|
+
continue;
|
|
216
|
+
const data = detectClusterMatch(componentName, serviceName, serviceBody);
|
|
217
|
+
if (!data)
|
|
218
|
+
continue;
|
|
219
|
+
matches.push({
|
|
220
|
+
description: `${componentName}.${serviceName}: strip prefix "${data.prefix}" — ${data.opRenames.size} operation(s) renamed${data.newServiceName !== serviceName ? `, service → ${data.newServiceName}` : ''}`,
|
|
221
|
+
data,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return matches;
|
|
226
|
+
},
|
|
227
|
+
transform: (spec, match) => {
|
|
228
|
+
return applyClusterMatch(spec, match.data);
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
// Internal helpers exported for unit testing.
|
|
232
|
+
export const __internal = {
|
|
233
|
+
findCamelPrefix,
|
|
234
|
+
stripCamelPrefix,
|
|
235
|
+
stripPascalPrefix,
|
|
236
|
+
detectClusterMatch,
|
|
237
|
+
};
|
|
238
|
+
//# sourceMappingURL=cluster-module-functions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cluster-module-functions.js","sourceRoot":"","sources":["../../../src/normalise/rules/cluster-module-functions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,117 @@
|
|
|
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
|
+
import type { NormaliseRule, NormaliseSpec } from '../normalise-rules.js';
|
|
78
|
+
interface CrudMatchData {
|
|
79
|
+
/** Component hosting the source Service. */
|
|
80
|
+
sourceComponentName: string;
|
|
81
|
+
/** Component hosting the target Model (may differ from source). */
|
|
82
|
+
targetComponentName: string;
|
|
83
|
+
modelName: string;
|
|
84
|
+
serviceName: string;
|
|
85
|
+
operationName: string;
|
|
86
|
+
curvedOp: 'retrieve' | 'create' | 'update' | 'delete';
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Parse an operation name into (verb, entityName) if it matches the
|
|
90
|
+
* exact `<verb><PascalCaseEntity>` shape. Returns null otherwise.
|
|
91
|
+
*
|
|
92
|
+
* The entity portion must be PascalCase, contain no further uppercase
|
|
93
|
+
* boundaries after the leading letter (i.e. single PascalCase word),
|
|
94
|
+
* AND must end at the end of the string (no trailing qualifier like
|
|
95
|
+
* `getRoundStatus` or `getClubsForUser`).
|
|
96
|
+
*/
|
|
97
|
+
declare function parseCrudOpName(opName: string): {
|
|
98
|
+
verb: string;
|
|
99
|
+
curved: CrudMatchData['curvedOp'];
|
|
100
|
+
entity: string;
|
|
101
|
+
} | null;
|
|
102
|
+
/**
|
|
103
|
+
* Build a model-name → component-name index across all components. Used
|
|
104
|
+
* by the cross-component target lookup. When an entity name is declared
|
|
105
|
+
* in multiple components, it's recorded as ambiguous (the rule then
|
|
106
|
+
* skips matches against that name).
|
|
107
|
+
*/
|
|
108
|
+
declare function buildModelIndex(spec: NormaliseSpec): Map<string, string | 'ambiguous'>;
|
|
109
|
+
declare function detectMatchesInComponent(spec: NormaliseSpec, modelIndex: Map<string, string | 'ambiguous'>, componentName: string, componentBody: Record<string, unknown>): CrudMatchData[];
|
|
110
|
+
export declare const CRUD_INTO_CURVED_RULE: NormaliseRule;
|
|
111
|
+
export declare const __internal: {
|
|
112
|
+
parseCrudOpName: typeof parseCrudOpName;
|
|
113
|
+
buildModelIndex: typeof buildModelIndex;
|
|
114
|
+
detectMatchesInComponent: typeof detectMatchesInComponent;
|
|
115
|
+
};
|
|
116
|
+
export {};
|
|
117
|
+
//# sourceMappingURL=crud-into-curved.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crud-into-curved.d.ts","sourceRoot":"","sources":["../../../src/normalise/rules/crud-into-curved.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AAEH,OAAO,KAAK,EACV,aAAa,EAGb,aAAa,EACd,MAAM,uBAAuB,CAAC;AAE/B,UAAU,aAAa;IACrB,4CAA4C;IAC5C,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mEAAmE;IACnE,mBAAmB,EAAE,MAAM,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;CACvD;AAYD;;;;;;;;GAQG;AACH,iBAAS,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAoBnH;AAED;;;;;GAKG;AACH,iBAAS,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAAC,CAgB/E;AAED,iBAAS,wBAAwB,CAC/B,IAAI,EAAE,aAAa,EACnB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAAC,EAC7C,aAAa,EAAE,MAAM,EACrB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACrC,aAAa,EAAE,CAuCjB;AAmFD,eAAO,MAAM,qBAAqB,EAAE,aAmCnC,CAAC;AAEF,eAAO,MAAM,UAAU;;;;CAItB,CAAC"}
|