@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.
- 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/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 +142 -90
- 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-owner-emit.d.ts +13 -1
- package/dist/realize/per-owner-emit.d.ts.map +1 -1
- package/dist/realize/per-owner-emit.js +64 -1
- package/dist/realize/per-owner-emit.js.map +1 -1
- package/dist/realize/per-owner-runner.d.ts +36 -2
- package/dist/realize/per-owner-runner.d.ts.map +1 -1
- package/dist/realize/per-owner-runner.js +137 -3
- package/dist/realize/per-owner-runner.js.map +1 -1
- package/dist/realize/post-emit-verify/feedback-runner.d.ts +84 -0
- package/dist/realize/post-emit-verify/feedback-runner.d.ts.map +1 -0
- package/dist/realize/post-emit-verify/feedback-runner.js +177 -0
- package/dist/realize/post-emit-verify/feedback-runner.js.map +1 -0
- package/dist/realize/post-emit-verify/index.d.ts +17 -0
- package/dist/realize/post-emit-verify/index.d.ts.map +1 -0
- package/dist/realize/post-emit-verify/index.js +16 -0
- package/dist/realize/post-emit-verify/index.js.map +1 -0
- package/dist/realize/post-emit-verify/reemit.d.ts +61 -0
- package/dist/realize/post-emit-verify/reemit.d.ts.map +1 -0
- package/dist/realize/post-emit-verify/reemit.js +122 -0
- package/dist/realize/post-emit-verify/reemit.js.map +1 -0
- package/dist/realize/post-emit-verify/types.d.ts +138 -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 +55 -0
- package/dist/realize/post-emit-verify/verifier-manifest.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-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/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/services/templates/prisma/behavior-generator.ts +62 -2
- package/libs/instance-factories/services/templates/prisma/controller-generator.ts +42 -20
- 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}',
|
|
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:
|
|
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
|
-
|
|
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
|
-
${
|
|
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"}
|