@wairon/cli 5.1.1-dev.19 → 5.1.1-dev.20
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/cli/index.js +388 -41
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +395 -40
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -65,7 +65,7 @@ var init_defaults = __esm({
|
|
|
65
65
|
copilot: ".github/prompts",
|
|
66
66
|
codex: ".codex/agents"
|
|
67
67
|
};
|
|
68
|
-
WAIRON_VERSION = "5.1.1-dev.
|
|
68
|
+
WAIRON_VERSION = "5.1.1-dev.20";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
SUPPORTED_ALIASES = ["wai"];
|
|
71
71
|
SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -834,7 +834,12 @@ var init_project = __esm({
|
|
|
834
834
|
maxInterfaceMethods: import_zod4.z.number().int().nonnegative().optional(),
|
|
835
835
|
/** Maximum number of dependencies allowed on a single component */
|
|
836
836
|
maxComponentDependencies: import_zod4.z.number().int().nonnegative().optional(),
|
|
837
|
-
/**
|
|
837
|
+
/**
|
|
838
|
+
* Maximum number of narrative steps allowed in a single method
|
|
839
|
+
* implementation, reported as a warning. The narrative-complexity rule
|
|
840
|
+
* applies a default of 25 when this is unset — the default belongs to the
|
|
841
|
+
* rule, not this schema.
|
|
842
|
+
*/
|
|
838
843
|
maxNarrativeSteps: import_zod4.z.number().int().nonnegative().optional(),
|
|
839
844
|
/** Maximum number of direct components allowed in a single subsystem */
|
|
840
845
|
maxSubsystemComponents: import_zod4.z.number().int().nonnegative().optional(),
|
|
@@ -844,7 +849,23 @@ var init_project = __esm({
|
|
|
844
849
|
* narrative — above it the detail-sufficiency lint fires
|
|
845
850
|
* (UNNARRATED_COMPLEXITY). Default 8 when unset.
|
|
846
851
|
*/
|
|
847
|
-
maxUnnarratedComplexity: import_zod4.z.number().int().nonnegative().optional()
|
|
852
|
+
maxUnnarratedComplexity: import_zod4.z.number().int().nonnegative().optional(),
|
|
853
|
+
/**
|
|
854
|
+
* Step count above which a narrative is an error rather than a warning. No
|
|
855
|
+
* default: unset means the step count only ever warns.
|
|
856
|
+
*/
|
|
857
|
+
narrativeStepsHardMax: import_zod4.z.number().int().nonnegative().optional(),
|
|
858
|
+
/**
|
|
859
|
+
* The complexity band (linear | simple | moderate | complex | severe) a
|
|
860
|
+
* narrative may reach before it warns. Defaults to moderate when unset, so
|
|
861
|
+
* complex and severe warn.
|
|
862
|
+
*/
|
|
863
|
+
cognitiveWarnAbove: import_zod4.z.string().optional(),
|
|
864
|
+
/**
|
|
865
|
+
* The complexity band above which a narrative is an error rather than a
|
|
866
|
+
* warning. No default: unset means the cognitive band only ever warns.
|
|
867
|
+
*/
|
|
868
|
+
maxCognitiveLevel: import_zod4.z.string().optional()
|
|
848
869
|
});
|
|
849
870
|
DesignDepthSchema = import_zod4.z.enum(["components", "interfaces", "implementations", "narratives"]);
|
|
850
871
|
RulesConfigSchema = import_zod4.z.object({
|
|
@@ -1138,6 +1159,17 @@ function holdsState(component) {
|
|
|
1138
1159
|
function isRetired(component) {
|
|
1139
1160
|
return RETIRED_STEREOTYPES.has(component.componentType);
|
|
1140
1161
|
}
|
|
1162
|
+
function headNoun(component) {
|
|
1163
|
+
const words2 = component.id.split(/[-_]/).filter(Boolean);
|
|
1164
|
+
return words2[words2.length - 1] ?? "";
|
|
1165
|
+
}
|
|
1166
|
+
function conceptNoun(component) {
|
|
1167
|
+
const words2 = component.id.split(/[-_]/).filter(Boolean);
|
|
1168
|
+
for (let i = words2.length - 1; i >= 0; i--) {
|
|
1169
|
+
if (!BLOCK_NOUNS.has(words2[i])) return words2[i];
|
|
1170
|
+
}
|
|
1171
|
+
return "";
|
|
1172
|
+
}
|
|
1141
1173
|
function passesIntentFloor(text2, methodName) {
|
|
1142
1174
|
if (!text2) return false;
|
|
1143
1175
|
const t = text2.trim();
|
|
@@ -1154,6 +1186,39 @@ function effectiveDetail(method2, implementation, component) {
|
|
|
1154
1186
|
if (implementation.detail) return { level: implementation.detail, explicit: true };
|
|
1155
1187
|
return { level: defaultNarrativeDetail(component), explicit: false };
|
|
1156
1188
|
}
|
|
1189
|
+
function cognitiveScore(method2) {
|
|
1190
|
+
const steps = method2.narrative ?? [];
|
|
1191
|
+
const regions = steps.filter((s) => (s.type === "loop" || s.type === "try" || s.type === "parallel") && s.endStep !== void 0).map((s) => ({ start: s.stepNumber, end: s.endStep }));
|
|
1192
|
+
const nestingDepthAt = (stepNumber) => regions.filter((r) => stepNumber > r.start && stepNumber <= r.end).length;
|
|
1193
|
+
let score = 0;
|
|
1194
|
+
for (const s of steps) {
|
|
1195
|
+
switch (s.type) {
|
|
1196
|
+
case "branch":
|
|
1197
|
+
case "switch":
|
|
1198
|
+
case "loop":
|
|
1199
|
+
case "parallel":
|
|
1200
|
+
score += 1 + nestingDepthAt(s.stepNumber);
|
|
1201
|
+
break;
|
|
1202
|
+
case "try":
|
|
1203
|
+
for (const c of s.catches ?? []) score += 1 + nestingDepthAt(c.step);
|
|
1204
|
+
break;
|
|
1205
|
+
case "jump":
|
|
1206
|
+
score += 1;
|
|
1207
|
+
break;
|
|
1208
|
+
default:
|
|
1209
|
+
break;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
return score;
|
|
1213
|
+
}
|
|
1214
|
+
function complexityLevel(method2) {
|
|
1215
|
+
const score = cognitiveScore(method2);
|
|
1216
|
+
if (score >= 20) return "severe";
|
|
1217
|
+
if (score >= 10) return "complex";
|
|
1218
|
+
if (score >= 5) return "moderate";
|
|
1219
|
+
if (score >= 1) return "simple";
|
|
1220
|
+
return "linear";
|
|
1221
|
+
}
|
|
1157
1222
|
function implementationSourceFiles(impl) {
|
|
1158
1223
|
const files = [];
|
|
1159
1224
|
const add = (file) => {
|
|
@@ -1163,7 +1228,7 @@ function implementationSourceFiles(impl) {
|
|
|
1163
1228
|
for (const method2 of impl.methods ?? []) add(method2.sourcePath);
|
|
1164
1229
|
return files;
|
|
1165
1230
|
}
|
|
1166
|
-
var import_zod7, SpecIdSchema, SpecStatusSchema, BoundaryItemSchema, RequirementItemSchema, DatabaseSpecSchema, DiagramConfigSchema, SURFACE_AUDIENCES, SurfaceAudienceSchema, SystemPublicInterfaceSchema, SystemSpecSchema, PublicInterfaceTypeSchema, PublicInterfaceSchema, TrustedLinkSchema, LintAllowSchema, LintConfigSchema, ExtDataSchema, LifecycleEntrypointSchema, SubsystemSpecSchema, ComponentTypeSchema, PATTERN_TYPES, RETIRED_STEREOTYPES, PortalTypeSchema, DispatchBindingSchema, DurabilitySchema, DependencyClassSchema, PatternRefSchema, EventBindingSchema, ExternalLinkTypeSchema, ExternalLinkSchema, PortalAuthSchemeSchema, PortalAuthSchema, ComponentSpecSchema, HttpMethodSchema, TransportSchema, EndpointSchema, SEMANTIC_GUARANTEES, GuaranteeSchema, MethodParamSchema, FindingDeclarationSchema, MethodSignatureSchema, INTENT_FLOOR_MIN_CHARS, InterfaceSpecSchema, NarrativeStepTypeSchema, LoopKindSchema, SwitchCaseSchema, CatchClauseSchema, ParallelBranchSchema, NarrativeStepSchema, NarrativeDetailSchema, ConformanceTierSchema, MethodImplementationSchema, ImplementationSpecSchema, TypeKindSchema, TypeFieldSchema, TypeMethodSchema, InvariantSchema, TypeSpecSchema, SurfaceOriginSchema, SurfaceTypeDefSchema, SurfaceContractEntrySchema, SurfaceSnapshotSchema, NamedOpenApiSpecSchema, GroupSpecSchema;
|
|
1231
|
+
var import_zod7, SpecIdSchema, SpecStatusSchema, BoundaryItemSchema, RequirementItemSchema, DatabaseSpecSchema, DiagramConfigSchema, SURFACE_AUDIENCES, SurfaceAudienceSchema, SystemPublicInterfaceSchema, SystemSpecSchema, PublicInterfaceTypeSchema, PublicInterfaceSchema, TrustedLinkSchema, LintAllowSchema, LintConfigSchema, ExtDataSchema, LifecycleEntrypointSchema, SubsystemSpecSchema, ComponentTypeSchema, PATTERN_TYPES, RETIRED_STEREOTYPES, PortalTypeSchema, DispatchBindingSchema, DurabilitySchema, DependencyClassSchema, PatternRefSchema, EventBindingSchema, ExternalLinkTypeSchema, ExternalLinkSchema, PortalAuthSchemeSchema, PortalAuthSchema, ComponentSpecSchema, BLOCK_NOUNS, HttpMethodSchema, TransportSchema, EndpointSchema, SEMANTIC_GUARANTEES, GuaranteeSchema, MethodParamSchema, FindingDeclarationSchema, MethodSignatureSchema, INTENT_FLOOR_MIN_CHARS, InterfaceSpecSchema, NarrativeStepTypeSchema, LoopKindSchema, SwitchCaseSchema, CatchClauseSchema, ParallelBranchSchema, NarrativeStepSchema, NarrativeDetailSchema, ConformanceTierSchema, MethodImplementationSchema, ImplementationSpecSchema, TypeKindSchema, TypeFieldSchema, TypeMethodSchema, InvariantSchema, TypeSpecSchema, SurfaceOriginSchema, SurfaceTypeDefSchema, SurfaceContractEntrySchema, SurfaceSnapshotSchema, NamedOpenApiSpecSchema, GroupSpecSchema;
|
|
1167
1232
|
var init_specs = __esm({
|
|
1168
1233
|
"src/models/specs.ts"() {
|
|
1169
1234
|
"use strict";
|
|
@@ -1441,6 +1506,20 @@ var init_specs = __esm({
|
|
|
1441
1506
|
createdAt: import_zod7.z.string().datetime(),
|
|
1442
1507
|
updatedAt: import_zod7.z.string().datetime()
|
|
1443
1508
|
});
|
|
1509
|
+
BLOCK_NOUNS = /* @__PURE__ */ new Set([
|
|
1510
|
+
"portal",
|
|
1511
|
+
"orchestrator",
|
|
1512
|
+
"supervisor",
|
|
1513
|
+
"actor",
|
|
1514
|
+
"store",
|
|
1515
|
+
"index",
|
|
1516
|
+
"query",
|
|
1517
|
+
"registry",
|
|
1518
|
+
"adapter",
|
|
1519
|
+
"observer",
|
|
1520
|
+
"repository",
|
|
1521
|
+
"view"
|
|
1522
|
+
]);
|
|
1444
1523
|
HttpMethodSchema = import_zod7.z.enum(["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"]);
|
|
1445
1524
|
TransportSchema = import_zod7.z.enum(["HTTP", "gRPC", "GraphQL", "MessageBus", "NamedPipe", "IPC", "CLI", "Custom"]);
|
|
1446
1525
|
EndpointSchema = import_zod7.z.discriminatedUnion("transport", [
|
|
@@ -10834,7 +10913,7 @@ var init_declarative_assertions = __esm({
|
|
|
10834
10913
|
"use strict";
|
|
10835
10914
|
declarativeAssertionsRule = {
|
|
10836
10915
|
name: "declarative-assertions",
|
|
10837
|
-
description: "Evaluates the declarative rule assertions loaded packs declare (forbid-edge / require-field / endpoint-shape) \u2014 closed kinds instantiated with pack data, the hosted-safe doctrine channel. Findings carry the pack's namespaced code (<PACK>_<CODE>) and its stated reason; severity is the pack's declaration (project sddRuleSeverity still wins, and error downgrades to warning in draft context).",
|
|
10916
|
+
description: "Evaluates the declarative rule assertions loaded packs declare (forbid-edge / require-field / endpoint-shape) \u2014 closed kinds instantiated with pack data, the hosted-safe doctrine channel. Findings carry the pack's namespaced code (<PACK>_<CODE>) and its stated reason; severity is the pack's declaration (project sddRuleSeverity still wins, and error downgrades to warning in draft context). The codes are the packs' own, so no fixed code list applies.",
|
|
10838
10917
|
// Static codes are unknown here — packs bring their own. The validator
|
|
10839
10918
|
// gathers every loaded assertion's fullCode into knownIssueCodes.
|
|
10840
10919
|
codes: [],
|
|
@@ -12112,7 +12191,7 @@ var init_hidden_state = __esm({
|
|
|
12112
12191
|
isStatelessLogic = (component) => isLogic(component) && !holdsState(component);
|
|
12113
12192
|
hiddenStateRule = {
|
|
12114
12193
|
name: "hidden-state",
|
|
12115
|
-
description: "The fields-vs-Store criterion, statically approximated: module-scope mutable bindings (let/var) in a source file mapped EXCLUSIVELY to
|
|
12194
|
+
description: "The fields-vs-Store criterion, statically approximated: module-scope mutable bindings (let/var) in a source file mapped EXCLUSIVELY to logic that holds no state (a component whose type isLogic() and not holdsState(): an Orchestrator, or a Specialist until it is retired) are flagged as hidden held state. A component maps every file its implementations and their methods name. State a logic component keeps for itself is invisible to the spec, the canvas, and every persistence rule: promote it to a Store (durability: cache for loss-safe memo state), or lint.allow with the reason it is genuinely wiring or ephemeral. Supervisor and Actor files may hold runtime state. Exact analysis grade only; files also mapped to data or boundary components are exempt (N:1 collapse); const-bound container mutation is beyond this check and the finding says so.",
|
|
12116
12195
|
codes: [
|
|
12117
12196
|
{ code: "HIDDEN_STATE", defaultSeverity: "warning", summary: "Module-scope mutable binding in a file mapped only to stateless logic (Orchestrators) \u2014 held state hiding outside a Store, Supervisor or Actor" }
|
|
12118
12197
|
],
|
|
@@ -12723,11 +12802,11 @@ function makeMatcher(tech) {
|
|
|
12723
12802
|
const n = parts.length;
|
|
12724
12803
|
return (text2) => {
|
|
12725
12804
|
if (!text2) return false;
|
|
12726
|
-
const
|
|
12727
|
-
if (
|
|
12805
|
+
const words2 = text2.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
12806
|
+
if (words2.some((w) => w.includes(joined))) return true;
|
|
12728
12807
|
if (n < 2) return false;
|
|
12729
|
-
for (let i = 0; i + n <=
|
|
12730
|
-
if (
|
|
12808
|
+
for (let i = 0; i + n <= words2.length; i++) {
|
|
12809
|
+
if (words2.slice(i, i + n).join("").includes(joined)) return true;
|
|
12731
12810
|
}
|
|
12732
12811
|
return false;
|
|
12733
12812
|
};
|
|
@@ -13133,14 +13212,13 @@ var init_complexity_and_metadata = __esm({
|
|
|
13133
13212
|
init_models();
|
|
13134
13213
|
complexityRule = {
|
|
13135
13214
|
name: "complexity-and-metadata",
|
|
13136
|
-
description: "Enforces metadata documentation completeness (checking if descriptions are missing or too short) and structural complexity caps (limits on methods per interface, dependencies per component, and
|
|
13215
|
+
description: "Enforces metadata documentation completeness (checking if descriptions are missing or too short) and structural complexity caps (limits on methods per interface, parameters per method, dependencies per component, and components per subsystem) configured globally or in architectural profiles. A narrative's own length and shape are judged by narrative-complexity.",
|
|
13137
13216
|
codes: [
|
|
13138
13217
|
{ code: "MISSING_DESCRIPTION", defaultSeverity: "warning", summary: "Required description field is missing or empty" },
|
|
13139
13218
|
{ code: "DESCRIPTION_TOO_SHORT", defaultSeverity: "warning", summary: "Description is shorter than the configured minimum length" },
|
|
13140
13219
|
{ code: "EXCESSIVE_METHODS", defaultSeverity: "warning", summary: "Interface declares more methods than the configured limit" },
|
|
13141
13220
|
{ code: "EXCESSIVE_METHOD_PARAMS", defaultSeverity: "warning", summary: "Interface method declares more parameters than the configured limit" },
|
|
13142
13221
|
{ code: "EXCESSIVE_DEPENDENCIES", defaultSeverity: "warning", summary: "Component has more dependencies than the configured limit" },
|
|
13143
|
-
{ code: "EXCESSIVE_NARRATIVE_STEPS", defaultSeverity: "warning", summary: "Method implementation contains more narrative steps than the configured limit" },
|
|
13144
13222
|
{ code: "EXCESSIVE_SUBSYSTEM_COMPONENTS", defaultSeverity: "warning", summary: "Subsystem has more direct components than the configured limit" }
|
|
13145
13223
|
],
|
|
13146
13224
|
check(ctx) {
|
|
@@ -13228,25 +13306,6 @@ var init_complexity_and_metadata = __esm({
|
|
|
13228
13306
|
}
|
|
13229
13307
|
}
|
|
13230
13308
|
}
|
|
13231
|
-
for (const impl of ctx.implementations) {
|
|
13232
|
-
const intf = ctx.interfaceMap.get(impl.contract);
|
|
13233
|
-
const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
|
|
13234
|
-
const complexityConfig = ctx.complexityConfigFor(comp?.subsystem);
|
|
13235
|
-
const isDraft = ctx.isImplementationDraft(impl);
|
|
13236
|
-
if (complexityConfig?.maxNarrativeSteps !== void 0) {
|
|
13237
|
-
for (const m of impl.methods) {
|
|
13238
|
-
if (m.narrative.length > complexityConfig.maxNarrativeSteps) {
|
|
13239
|
-
ctx.addIssue(
|
|
13240
|
-
"warning",
|
|
13241
|
-
"EXCESSIVE_NARRATIVE_STEPS",
|
|
13242
|
-
`Method "${m.name}" in implementation "${impl.id}" contains ${m.narrative.length} narrative steps, exceeding the configured limit of ${complexityConfig.maxNarrativeSteps}.`,
|
|
13243
|
-
impl.id,
|
|
13244
|
-
isDraft
|
|
13245
|
-
);
|
|
13246
|
-
}
|
|
13247
|
-
}
|
|
13248
|
-
}
|
|
13249
|
-
}
|
|
13250
13309
|
for (const t of ctx.types) {
|
|
13251
13310
|
const docConfig = ctx.documentationConfigFor(t.subsystem);
|
|
13252
13311
|
checkDescription(
|
|
@@ -13286,6 +13345,285 @@ var init_complexity_and_metadata = __esm({
|
|
|
13286
13345
|
}
|
|
13287
13346
|
});
|
|
13288
13347
|
|
|
13348
|
+
// src/core/rules/heuristic/narrative-complexity.ts
|
|
13349
|
+
function bandIndex(band) {
|
|
13350
|
+
return BANDS.indexOf(band ?? "");
|
|
13351
|
+
}
|
|
13352
|
+
var BANDS, DEFAULT_WARN_ABOVE, DEFAULT_MAX_STEPS, narrativeComplexityRule;
|
|
13353
|
+
var init_narrative_complexity = __esm({
|
|
13354
|
+
"src/core/rules/heuristic/narrative-complexity.ts"() {
|
|
13355
|
+
"use strict";
|
|
13356
|
+
init_models();
|
|
13357
|
+
BANDS = ["linear", "simple", "moderate", "complex", "severe"];
|
|
13358
|
+
DEFAULT_WARN_ABOVE = "moderate";
|
|
13359
|
+
DEFAULT_MAX_STEPS = 25;
|
|
13360
|
+
narrativeComplexityRule = {
|
|
13361
|
+
name: "narrative-complexity",
|
|
13362
|
+
description: "Judges a narrative on two independent axes: the cognitive band its shape earns, from branching and nesting, and the number of steps it lists. A flat list of registrations is long but linear; a deeply nested guard is short but severe. Each axis warns above its configured level, and reports an error only where a maximum is configured.",
|
|
13363
|
+
codes: [
|
|
13364
|
+
{ code: "NARRATIVE_COMPLEXITY", defaultSeverity: "warning", summary: "Narrative's cognitive band is above the configured level (default: above moderate)" },
|
|
13365
|
+
{ code: "NARRATIVE_COMPLEXITY_OVER_MAX", defaultSeverity: "error", summary: "Narrative's cognitive band is above the configured maximum band" },
|
|
13366
|
+
{ code: "EXCESSIVE_NARRATIVE_STEPS", defaultSeverity: "warning", summary: "Method implementation contains more narrative steps than the configured limit (default 25)" },
|
|
13367
|
+
{ code: "NARRATIVE_STEPS_OVER_MAX", defaultSeverity: "error", summary: "Narrative step count is above the configured hard maximum" }
|
|
13368
|
+
],
|
|
13369
|
+
check(ctx) {
|
|
13370
|
+
const entries = [];
|
|
13371
|
+
for (const impl of ctx.implementations) {
|
|
13372
|
+
const intf = ctx.interfaceMap.get(impl.contract);
|
|
13373
|
+
const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
|
|
13374
|
+
if (!comp) continue;
|
|
13375
|
+
for (const method2 of impl.methods) {
|
|
13376
|
+
if ((method2.narrative ?? []).length === 0) continue;
|
|
13377
|
+
entries.push({ implementation: impl, method: method2, component: comp, subsystem: comp.subsystem });
|
|
13378
|
+
}
|
|
13379
|
+
}
|
|
13380
|
+
for (const entry of entries) {
|
|
13381
|
+
const cfg = ctx.complexityConfigFor(entry.subsystem);
|
|
13382
|
+
const isDraft = ctx.isImplementationDraft(entry.implementation);
|
|
13383
|
+
const steps = entry.method.narrative.length;
|
|
13384
|
+
const score = cognitiveScore(entry.method);
|
|
13385
|
+
const band = complexityLevel(entry.method);
|
|
13386
|
+
const warnAbove = cfg?.cognitiveWarnAbove ?? DEFAULT_WARN_ABOVE;
|
|
13387
|
+
if (bandIndex(band) > bandIndex(warnAbove)) {
|
|
13388
|
+
ctx.addIssue(
|
|
13389
|
+
"warning",
|
|
13390
|
+
"NARRATIVE_COMPLEXITY",
|
|
13391
|
+
`Method "${entry.method.name}" in implementation "${entry.implementation.id}" has a ${band} narrative (cognitive score ${score}), above the configured "${warnAbove}" band. Split it into steps that call smaller methods.`,
|
|
13392
|
+
entry.implementation.id,
|
|
13393
|
+
isDraft
|
|
13394
|
+
);
|
|
13395
|
+
}
|
|
13396
|
+
if (cfg?.maxCognitiveLevel !== void 0 && bandIndex(band) > bandIndex(cfg.maxCognitiveLevel)) {
|
|
13397
|
+
ctx.addIssue(
|
|
13398
|
+
"error",
|
|
13399
|
+
"NARRATIVE_COMPLEXITY_OVER_MAX",
|
|
13400
|
+
`Method "${entry.method.name}" in implementation "${entry.implementation.id}" has a ${band} narrative (cognitive score ${score}), above the configured maximum band "${cfg.maxCognitiveLevel}". Split it into steps that call smaller methods.`,
|
|
13401
|
+
entry.implementation.id,
|
|
13402
|
+
isDraft
|
|
13403
|
+
);
|
|
13404
|
+
}
|
|
13405
|
+
const maxSteps = cfg?.maxNarrativeSteps ?? DEFAULT_MAX_STEPS;
|
|
13406
|
+
if (steps > maxSteps) {
|
|
13407
|
+
ctx.addIssue(
|
|
13408
|
+
"warning",
|
|
13409
|
+
"EXCESSIVE_NARRATIVE_STEPS",
|
|
13410
|
+
`Method "${entry.method.name}" in implementation "${entry.implementation.id}" contains ${steps} narrative steps, exceeding the configured limit of ${maxSteps}.`,
|
|
13411
|
+
entry.implementation.id,
|
|
13412
|
+
isDraft
|
|
13413
|
+
);
|
|
13414
|
+
}
|
|
13415
|
+
if (cfg?.narrativeStepsHardMax !== void 0 && steps > cfg.narrativeStepsHardMax) {
|
|
13416
|
+
ctx.addIssue(
|
|
13417
|
+
"error",
|
|
13418
|
+
"NARRATIVE_STEPS_OVER_MAX",
|
|
13419
|
+
`Method "${entry.method.name}" in implementation "${entry.implementation.id}" lists ${steps} narrative steps, above the configured hard maximum of ${cfg.narrativeStepsHardMax}. Extract part of the flow into a method of its own.`,
|
|
13420
|
+
entry.implementation.id,
|
|
13421
|
+
isDraft
|
|
13422
|
+
);
|
|
13423
|
+
}
|
|
13424
|
+
}
|
|
13425
|
+
}
|
|
13426
|
+
};
|
|
13427
|
+
}
|
|
13428
|
+
});
|
|
13429
|
+
|
|
13430
|
+
// src/core/rules/heuristic/naming-discipline.ts
|
|
13431
|
+
function words(identifier) {
|
|
13432
|
+
return identifier.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[\s\-_]+/).filter(Boolean).map((w) => w.toLowerCase());
|
|
13433
|
+
}
|
|
13434
|
+
function isSameNoun(word, noun) {
|
|
13435
|
+
if (word === noun) return true;
|
|
13436
|
+
if (word === `${noun}s`) return true;
|
|
13437
|
+
return noun.endsWith("s") && word === noun.slice(0, -1);
|
|
13438
|
+
}
|
|
13439
|
+
function withoutWord(methodWords, dropIndex) {
|
|
13440
|
+
const kept = methodWords.filter((_, i) => i !== dropIndex);
|
|
13441
|
+
return kept.map((w, i) => i === 0 ? w : w[0].toUpperCase() + w.slice(1)).join("");
|
|
13442
|
+
}
|
|
13443
|
+
var BLOCK_WORDS, GENERIC_WORDS, MIN_CONCEPT_LENGTH, namingDisciplineRule;
|
|
13444
|
+
var init_naming_discipline = __esm({
|
|
13445
|
+
"src/core/rules/heuristic/naming-discipline.ts"() {
|
|
13446
|
+
"use strict";
|
|
13447
|
+
init_models();
|
|
13448
|
+
BLOCK_WORDS = {
|
|
13449
|
+
portal: "Portal",
|
|
13450
|
+
orchestrator: "Orchestrator",
|
|
13451
|
+
supervisor: "Supervisor",
|
|
13452
|
+
actor: "Actor",
|
|
13453
|
+
store: "Store",
|
|
13454
|
+
index: "Index",
|
|
13455
|
+
query: "Query",
|
|
13456
|
+
registry: "Registry",
|
|
13457
|
+
adapter: "Adapter",
|
|
13458
|
+
observer: "Observer",
|
|
13459
|
+
repository: "Repository",
|
|
13460
|
+
view: "View"
|
|
13461
|
+
};
|
|
13462
|
+
GENERIC_WORDS = /* @__PURE__ */ new Set([
|
|
13463
|
+
"manager",
|
|
13464
|
+
"helper",
|
|
13465
|
+
"helpers",
|
|
13466
|
+
"util",
|
|
13467
|
+
"utils",
|
|
13468
|
+
"handler",
|
|
13469
|
+
"service",
|
|
13470
|
+
"processor",
|
|
13471
|
+
"coordinator",
|
|
13472
|
+
"engine",
|
|
13473
|
+
"wrapper",
|
|
13474
|
+
"common",
|
|
13475
|
+
"misc",
|
|
13476
|
+
"data",
|
|
13477
|
+
"info"
|
|
13478
|
+
]);
|
|
13479
|
+
MIN_CONCEPT_LENGTH = 4;
|
|
13480
|
+
namingDisciplineRule = {
|
|
13481
|
+
name: "naming-discipline",
|
|
13482
|
+
description: "A name says what a thing is and never repeats itself: a component's head noun names its own block, generic role words say nothing, a method does not repeat its component's head noun, and a component with one method is not merely that method's name. Adapters and Portals are exempt from the repetition check, because a forwarder's method mirrors the command or route it exposes.",
|
|
13483
|
+
codes: [
|
|
13484
|
+
{ code: "MISLEADING_BLOCK_WORD", defaultSeverity: "warning", summary: "Component's head noun names a building block that is not its componentType" },
|
|
13485
|
+
{ code: "GENERIC_COMPONENT_NAME", defaultSeverity: "warning", summary: "Component name or id uses a generic role word that says nothing (manager, helper, utils, handler, service)" },
|
|
13486
|
+
{ code: "METHOD_REPEATS_COMPONENT", defaultSeverity: "warning", summary: "Method name repeats its component's head noun; not reported on Adapter or Portal forwarders" },
|
|
13487
|
+
{ code: "COMPONENT_IS_ITS_ONLY_METHOD", defaultSeverity: "warning", summary: "Component with a single method is named after that method \u2014 fold it into its caller, or name it for its responsibility" }
|
|
13488
|
+
],
|
|
13489
|
+
check(ctx) {
|
|
13490
|
+
for (const comp of ctx.components) {
|
|
13491
|
+
const head = headNoun(comp);
|
|
13492
|
+
const isDraft = ctx.isComponentDraft(comp.id);
|
|
13493
|
+
const idWords = [...words(comp.id), ...words(comp.name)];
|
|
13494
|
+
if (BLOCK_WORDS[head] && BLOCK_WORDS[head] !== comp.componentType) {
|
|
13495
|
+
ctx.addIssue(
|
|
13496
|
+
"warning",
|
|
13497
|
+
"MISLEADING_BLOCK_WORD",
|
|
13498
|
+
`Component "${comp.id}" ends in "${head}", which names a ${BLOCK_WORDS[head]}, but it is declared a ${comp.componentType}. Rename it for what it is, or change its componentType.`,
|
|
13499
|
+
comp.id,
|
|
13500
|
+
isDraft
|
|
13501
|
+
);
|
|
13502
|
+
}
|
|
13503
|
+
const generic = [...new Set(idWords.filter((w) => GENERIC_WORDS.has(w)))];
|
|
13504
|
+
if (generic.length > 0) {
|
|
13505
|
+
ctx.addIssue(
|
|
13506
|
+
"warning",
|
|
13507
|
+
"GENERIC_COMPONENT_NAME",
|
|
13508
|
+
`Component "${comp.id}" leans on the generic role word${generic.length > 1 ? "s" : ""} ${generic.map((w) => `"${w}"`).join(", ")}, which says nothing about what it does. Name it for its responsibility.`,
|
|
13509
|
+
comp.id,
|
|
13510
|
+
isDraft
|
|
13511
|
+
);
|
|
13512
|
+
}
|
|
13513
|
+
const methods = ctx.interfaceMethodsOf(comp.id);
|
|
13514
|
+
if (comp.componentType !== "Adapter" && methods.length === 1) {
|
|
13515
|
+
const only = methods[0];
|
|
13516
|
+
const concept = conceptNoun(comp);
|
|
13517
|
+
const onlyWords = words(only.name);
|
|
13518
|
+
if (onlyWords.length > 1 && onlyWords.some((w) => isSameNoun(w, concept))) {
|
|
13519
|
+
ctx.addIssue(
|
|
13520
|
+
"warning",
|
|
13521
|
+
"COMPONENT_IS_ITS_ONLY_METHOD",
|
|
13522
|
+
`Component "${comp.id}" declares one method, "${only.name}", and is named after it. Fold it into its caller, or name the component for the responsibility it holds.`,
|
|
13523
|
+
comp.id,
|
|
13524
|
+
isDraft
|
|
13525
|
+
);
|
|
13526
|
+
}
|
|
13527
|
+
}
|
|
13528
|
+
}
|
|
13529
|
+
const pairs = [];
|
|
13530
|
+
for (const intf of ctx.interfaces) {
|
|
13531
|
+
const comp = ctx.componentMap.get(intf.component);
|
|
13532
|
+
if (!comp || comp.componentType === "Adapter" || comp.componentType === "Portal") continue;
|
|
13533
|
+
const concept = conceptNoun(comp);
|
|
13534
|
+
if (concept.length < MIN_CONCEPT_LENGTH) continue;
|
|
13535
|
+
for (const method2 of intf.methods) pairs.push({ intf, comp, concept, method: method2 });
|
|
13536
|
+
}
|
|
13537
|
+
for (const { intf, comp, concept, method: method2 } of pairs) {
|
|
13538
|
+
const methodWords = words(method2.name);
|
|
13539
|
+
if (methodWords.length < 2 || !isSameNoun(methodWords[1], concept)) continue;
|
|
13540
|
+
ctx.addIssue(
|
|
13541
|
+
"warning",
|
|
13542
|
+
"METHOD_REPEATS_COMPONENT",
|
|
13543
|
+
`Method "${method2.name}" on component "${comp.id}" repeats the component's concept "${concept}" \u2014 the call site says it twice. Name it "${withoutWord(methodWords, 1)}".`,
|
|
13544
|
+
intf.id,
|
|
13545
|
+
ctx.isComponentDraft(comp.id)
|
|
13546
|
+
);
|
|
13547
|
+
}
|
|
13548
|
+
}
|
|
13549
|
+
};
|
|
13550
|
+
}
|
|
13551
|
+
});
|
|
13552
|
+
|
|
13553
|
+
// src/core/rules/heuristic/method-cohesion.ts
|
|
13554
|
+
function groupsSharingCollaborators(calledBy) {
|
|
13555
|
+
const parent = /* @__PURE__ */ new Map();
|
|
13556
|
+
const find = (m) => {
|
|
13557
|
+
let root = m;
|
|
13558
|
+
while (parent.get(root) !== root) root = parent.get(root);
|
|
13559
|
+
return root;
|
|
13560
|
+
};
|
|
13561
|
+
const union = (a, b) => {
|
|
13562
|
+
const [ra, rb] = [find(a), find(b)];
|
|
13563
|
+
if (ra !== rb) parent.set(ra, rb);
|
|
13564
|
+
};
|
|
13565
|
+
const methods = [...calledBy.keys()];
|
|
13566
|
+
for (const m of methods) parent.set(m, m);
|
|
13567
|
+
for (let i = 0; i < methods.length; i++) {
|
|
13568
|
+
for (let j = i + 1; j < methods.length; j++) {
|
|
13569
|
+
const shares = [...calledBy.get(methods[i])].some((c) => calledBy.get(methods[j])?.has(c));
|
|
13570
|
+
if (shares) union(methods[i], methods[j]);
|
|
13571
|
+
}
|
|
13572
|
+
}
|
|
13573
|
+
const groups = /* @__PURE__ */ new Map();
|
|
13574
|
+
for (const m of methods) {
|
|
13575
|
+
const root = find(m);
|
|
13576
|
+
groups.set(root, [...groups.get(root) ?? [], m]);
|
|
13577
|
+
}
|
|
13578
|
+
return [...groups.values()];
|
|
13579
|
+
}
|
|
13580
|
+
var methodCohesionRule;
|
|
13581
|
+
var init_method_cohesion = __esm({
|
|
13582
|
+
"src/core/rules/heuristic/method-cohesion.ts"() {
|
|
13583
|
+
"use strict";
|
|
13584
|
+
methodCohesionRule = {
|
|
13585
|
+
name: "method-cohesion",
|
|
13586
|
+
description: "An Orchestrator's methods share collaborators; when they split into groups that call no component in common, the component holds more than one responsibility. Reported only where two or more groups each hold two or more methods, so a single specialized method is not a finding.",
|
|
13587
|
+
codes: [
|
|
13588
|
+
{ code: "INCOHESIVE_METHODS", defaultSeverity: "warning", summary: "Orchestrator's methods form two or more groups of two or more that share no called component" }
|
|
13589
|
+
],
|
|
13590
|
+
check(ctx) {
|
|
13591
|
+
const subjects = [];
|
|
13592
|
+
for (const impl of ctx.implementations) {
|
|
13593
|
+
const intf = ctx.interfaceMap.get(impl.contract);
|
|
13594
|
+
const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
|
|
13595
|
+
if (!comp || comp.componentType !== "Orchestrator") continue;
|
|
13596
|
+
const calledBy = /* @__PURE__ */ new Map();
|
|
13597
|
+
for (const method2 of impl.methods) {
|
|
13598
|
+
const reached = /* @__PURE__ */ new Set();
|
|
13599
|
+
for (const step of method2.narrative ?? []) {
|
|
13600
|
+
if ((step.type === "call" || step.type === "dispatch") && step.targetComponent) reached.add(step.targetComponent);
|
|
13601
|
+
}
|
|
13602
|
+
if (reached.size > 0) calledBy.set(method2.name, reached);
|
|
13603
|
+
}
|
|
13604
|
+
subjects.push({ implementation: impl, component: comp, calledBy });
|
|
13605
|
+
}
|
|
13606
|
+
for (const { component, calledBy } of subjects) {
|
|
13607
|
+
if (calledBy.size < 2) continue;
|
|
13608
|
+
const groups = groupsSharingCollaborators(calledBy).filter((g) => g.length >= 2);
|
|
13609
|
+
if (groups.length < 2) continue;
|
|
13610
|
+
const described = groups.map((g) => {
|
|
13611
|
+
const collaborators = [...new Set(g.flatMap((m) => [...calledBy.get(m)]))].sort();
|
|
13612
|
+
return `${g.join(", ")} (calling ${collaborators.join(", ")})`;
|
|
13613
|
+
}).join("; ");
|
|
13614
|
+
ctx.addIssue(
|
|
13615
|
+
"warning",
|
|
13616
|
+
"INCOHESIVE_METHODS",
|
|
13617
|
+
`Orchestrator "${component.id}" holds ${groups.length} groups of methods that share no called component: ${described}. Split it along those groups, or acknowledge a deliberate facade with a reasoned lint.allow for INCOHESIVE_METHODS.`,
|
|
13618
|
+
component.id,
|
|
13619
|
+
ctx.isComponentDraft(component.id)
|
|
13620
|
+
);
|
|
13621
|
+
}
|
|
13622
|
+
}
|
|
13623
|
+
};
|
|
13624
|
+
}
|
|
13625
|
+
});
|
|
13626
|
+
|
|
13289
13627
|
// src/core/rules/repository.ts
|
|
13290
13628
|
function addRule(rule) {
|
|
13291
13629
|
ruleSet.push(rule);
|
|
@@ -13362,6 +13700,9 @@ var init_repository = __esm({
|
|
|
13362
13700
|
init_technology_boundaries();
|
|
13363
13701
|
init_naming_conventions();
|
|
13364
13702
|
init_complexity_and_metadata();
|
|
13703
|
+
init_narrative_complexity();
|
|
13704
|
+
init_naming_discipline();
|
|
13705
|
+
init_method_cohesion();
|
|
13365
13706
|
SDD_RULES = [
|
|
13366
13707
|
hierarchyRule,
|
|
13367
13708
|
// Namespace integrity right after hierarchy: unresolvable/unwritable ids
|
|
@@ -13437,6 +13778,12 @@ var init_repository = __esm({
|
|
|
13437
13778
|
technologyRule,
|
|
13438
13779
|
namingRule,
|
|
13439
13780
|
complexityRule,
|
|
13781
|
+
// The doctrine heuristics ride with the structural caps: same subject
|
|
13782
|
+
// (size, name, shape), judgements rather than errors, and they read the
|
|
13783
|
+
// same complexity dial.
|
|
13784
|
+
narrativeComplexityRule,
|
|
13785
|
+
namingDisciplineRule,
|
|
13786
|
+
methodCohesionRule,
|
|
13440
13787
|
// Pack resolution and reproducibility run late: they are about project
|
|
13441
13788
|
// CONFIGURATION (does the declared pack set resolve, and can it be reproduced
|
|
13442
13789
|
// elsewhere?) rather than spec content.
|
|
@@ -13521,13 +13868,13 @@ function analyzeWithPatterns(text2, patterns) {
|
|
|
13521
13868
|
};
|
|
13522
13869
|
}
|
|
13523
13870
|
function analyzeGeneric(text2) {
|
|
13524
|
-
const
|
|
13525
|
-
for (const m of text2.matchAll(IDENTIFIER_RE))
|
|
13526
|
-
const anchors = new Set(
|
|
13871
|
+
const words2 = /* @__PURE__ */ new Set();
|
|
13872
|
+
for (const m of text2.matchAll(IDENTIFIER_RE)) words2.add(m[0]);
|
|
13873
|
+
const anchors = new Set(words2);
|
|
13527
13874
|
for (const s of collectStrings(text2)) anchors.add(s);
|
|
13528
13875
|
return {
|
|
13529
13876
|
analysisGrade: "generic",
|
|
13530
|
-
declaredNames: [...
|
|
13877
|
+
declaredNames: [...words2],
|
|
13531
13878
|
anchoredNames: [...anchors],
|
|
13532
13879
|
exportedNames: [],
|
|
13533
13880
|
imports: [],
|
|
@@ -14153,7 +14500,7 @@ function buildRuleContext(opts) {
|
|
|
14153
14500
|
const projectComp = rules?.complexity;
|
|
14154
14501
|
const packDef = profileDefFor(subsystemId);
|
|
14155
14502
|
if (packDef?.rules?.complexity) {
|
|
14156
|
-
return { ...
|
|
14503
|
+
return { ...packDef.rules.complexity, ...projectComp };
|
|
14157
14504
|
}
|
|
14158
14505
|
return projectComp;
|
|
14159
14506
|
};
|
|
@@ -14161,7 +14508,7 @@ function buildRuleContext(opts) {
|
|
|
14161
14508
|
const projectDoc = rules?.documentation;
|
|
14162
14509
|
const packDef = profileDefFor(subsystemId);
|
|
14163
14510
|
if (packDef?.rules?.documentation) {
|
|
14164
|
-
return { ...
|
|
14511
|
+
return { ...packDef.rules.documentation, ...projectDoc };
|
|
14165
14512
|
}
|
|
14166
14513
|
return projectDoc;
|
|
14167
14514
|
};
|
|
@@ -14170,11 +14517,11 @@ function buildRuleContext(opts) {
|
|
|
14170
14517
|
const packDef = profileDefFor(subsystemId);
|
|
14171
14518
|
if (packDef?.rules?.naming) {
|
|
14172
14519
|
return {
|
|
14173
|
-
...projectNaming,
|
|
14174
14520
|
...packDef.rules.naming,
|
|
14521
|
+
...projectNaming,
|
|
14175
14522
|
stereotypes: {
|
|
14176
|
-
...
|
|
14177
|
-
...
|
|
14523
|
+
...packDef.rules.naming.stereotypes ?? {},
|
|
14524
|
+
...projectNaming?.stereotypes ?? {}
|
|
14178
14525
|
}
|
|
14179
14526
|
};
|
|
14180
14527
|
}
|
|
@@ -32105,7 +32452,7 @@ var hostCore = {
|
|
|
32105
32452
|
declaredPackNames,
|
|
32106
32453
|
declaredProfileIds,
|
|
32107
32454
|
// Extension-pack loading forwarded to sdd_core — used by the hosted pack store
|
|
32108
|
-
// (
|
|
32455
|
+
// (pack_store) and the policy plane's required/default-pack application.
|
|
32109
32456
|
globalPacksDir,
|
|
32110
32457
|
discoverPacks,
|
|
32111
32458
|
loadExtensionPacks,
|