@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 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.19";
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
- /** Maximum number of narrative steps allowed in a single method implementation */
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 stateless logic (Orchestrators, and Specialists until they are migrated) \u2014 a component maps every file its implementations and their methods name \u2014 are flagged as hidden held state \u2014 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/ephemeral. Exact analysis grade only; files also mapped to data or boundary components, or to a Supervisor or Actor (which hold runtime state by definition), are exempt (N:1 collapse); const-bound container mutation is beyond this check and the finding says so.",
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 words = text2.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
12727
- if (words.some((w) => w.includes(joined))) return true;
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 <= words.length; i++) {
12730
- if (words.slice(i, i + n).join("").includes(joined)) return true;
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 narrative steps per method) configured globally or in architectural profiles.",
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 words = /* @__PURE__ */ new Set();
13525
- for (const m of text2.matchAll(IDENTIFIER_RE)) words.add(m[0]);
13526
- const anchors = new Set(words);
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: [...words],
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 { ...projectComp, ...packDef.rules.complexity };
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 { ...projectDoc, ...packDef.rules.documentation };
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
- ...projectNaming?.stereotypes ?? {},
14177
- ...packDef.rules.naming.stereotypes ?? {}
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
- // (pack_registry) and the policy plane's required/default-pack application.
32455
+ // (pack_store) and the policy plane's required/default-pack application.
32109
32456
  globalPacksDir,
32110
32457
  discoverPacks,
32111
32458
  loadExtensionPacks,