@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/index.js CHANGED
@@ -500,7 +500,12 @@ var init_project = __esm({
500
500
  maxInterfaceMethods: import_zod3.z.number().int().nonnegative().optional(),
501
501
  /** Maximum number of dependencies allowed on a single component */
502
502
  maxComponentDependencies: import_zod3.z.number().int().nonnegative().optional(),
503
- /** Maximum number of narrative steps allowed in a single method implementation */
503
+ /**
504
+ * Maximum number of narrative steps allowed in a single method
505
+ * implementation, reported as a warning. The narrative-complexity rule
506
+ * applies a default of 25 when this is unset — the default belongs to the
507
+ * rule, not this schema.
508
+ */
504
509
  maxNarrativeSteps: import_zod3.z.number().int().nonnegative().optional(),
505
510
  /** Maximum number of direct components allowed in a single subsystem */
506
511
  maxSubsystemComponents: import_zod3.z.number().int().nonnegative().optional(),
@@ -510,7 +515,23 @@ var init_project = __esm({
510
515
  * narrative — above it the detail-sufficiency lint fires
511
516
  * (UNNARRATED_COMPLEXITY). Default 8 when unset.
512
517
  */
513
- maxUnnarratedComplexity: import_zod3.z.number().int().nonnegative().optional()
518
+ maxUnnarratedComplexity: import_zod3.z.number().int().nonnegative().optional(),
519
+ /**
520
+ * Step count above which a narrative is an error rather than a warning. No
521
+ * default: unset means the step count only ever warns.
522
+ */
523
+ narrativeStepsHardMax: import_zod3.z.number().int().nonnegative().optional(),
524
+ /**
525
+ * The complexity band (linear | simple | moderate | complex | severe) a
526
+ * narrative may reach before it warns. Defaults to moderate when unset, so
527
+ * complex and severe warn.
528
+ */
529
+ cognitiveWarnAbove: import_zod3.z.string().optional(),
530
+ /**
531
+ * The complexity band above which a narrative is an error rather than a
532
+ * warning. No default: unset means the cognitive band only ever warns.
533
+ */
534
+ maxCognitiveLevel: import_zod3.z.string().optional()
514
535
  });
515
536
  DesignDepthSchema = import_zod3.z.enum(["components", "interfaces", "implementations", "narratives"]);
516
537
  RulesConfigSchema = import_zod3.z.object({
@@ -1092,7 +1113,7 @@ var init_defaults = __esm({
1092
1113
  copilot: ".github/prompts",
1093
1114
  codex: ".codex/agents"
1094
1115
  };
1095
- WAIRON_VERSION = "5.1.1-dev.19";
1116
+ WAIRON_VERSION = "5.1.1-dev.20";
1096
1117
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
1097
1118
  ARCHITECT_AGENT_ID = "agent-architect";
1098
1119
  ARCHITECT_TEMPLATE_ID = "architect";
@@ -1425,6 +1446,17 @@ function holdsState(component) {
1425
1446
  function isRetired(component) {
1426
1447
  return RETIRED_STEREOTYPES.has(component.componentType);
1427
1448
  }
1449
+ function headNoun(component) {
1450
+ const words2 = component.id.split(/[-_]/).filter(Boolean);
1451
+ return words2[words2.length - 1] ?? "";
1452
+ }
1453
+ function conceptNoun(component) {
1454
+ const words2 = component.id.split(/[-_]/).filter(Boolean);
1455
+ for (let i = words2.length - 1; i >= 0; i--) {
1456
+ if (!BLOCK_NOUNS.has(words2[i])) return words2[i];
1457
+ }
1458
+ return "";
1459
+ }
1428
1460
  function passesIntentFloor(text, methodName) {
1429
1461
  if (!text) return false;
1430
1462
  const t = text.trim();
@@ -1441,6 +1473,39 @@ function effectiveDetail(method, implementation, component) {
1441
1473
  if (implementation.detail) return { level: implementation.detail, explicit: true };
1442
1474
  return { level: defaultNarrativeDetail(component), explicit: false };
1443
1475
  }
1476
+ function cognitiveScore(method) {
1477
+ const steps = method.narrative ?? [];
1478
+ 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 }));
1479
+ const nestingDepthAt = (stepNumber) => regions.filter((r) => stepNumber > r.start && stepNumber <= r.end).length;
1480
+ let score = 0;
1481
+ for (const s of steps) {
1482
+ switch (s.type) {
1483
+ case "branch":
1484
+ case "switch":
1485
+ case "loop":
1486
+ case "parallel":
1487
+ score += 1 + nestingDepthAt(s.stepNumber);
1488
+ break;
1489
+ case "try":
1490
+ for (const c of s.catches ?? []) score += 1 + nestingDepthAt(c.step);
1491
+ break;
1492
+ case "jump":
1493
+ score += 1;
1494
+ break;
1495
+ default:
1496
+ break;
1497
+ }
1498
+ }
1499
+ return score;
1500
+ }
1501
+ function complexityLevel(method) {
1502
+ const score = cognitiveScore(method);
1503
+ if (score >= 20) return "severe";
1504
+ if (score >= 10) return "complex";
1505
+ if (score >= 5) return "moderate";
1506
+ if (score >= 1) return "simple";
1507
+ return "linear";
1508
+ }
1444
1509
  function implementationSourceFiles(impl) {
1445
1510
  const files = [];
1446
1511
  const add = (file) => {
@@ -1450,7 +1515,7 @@ function implementationSourceFiles(impl) {
1450
1515
  for (const method of impl.methods ?? []) add(method.sourcePath);
1451
1516
  return files;
1452
1517
  }
1453
- var import_zod8, 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;
1518
+ var import_zod8, 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;
1454
1519
  var init_specs = __esm({
1455
1520
  "src/models/specs.ts"() {
1456
1521
  "use strict";
@@ -1728,6 +1793,20 @@ var init_specs = __esm({
1728
1793
  createdAt: import_zod8.z.string().datetime(),
1729
1794
  updatedAt: import_zod8.z.string().datetime()
1730
1795
  });
1796
+ BLOCK_NOUNS = /* @__PURE__ */ new Set([
1797
+ "portal",
1798
+ "orchestrator",
1799
+ "supervisor",
1800
+ "actor",
1801
+ "store",
1802
+ "index",
1803
+ "query",
1804
+ "registry",
1805
+ "adapter",
1806
+ "observer",
1807
+ "repository",
1808
+ "view"
1809
+ ]);
1731
1810
  HttpMethodSchema = import_zod8.z.enum(["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"]);
1732
1811
  TransportSchema = import_zod8.z.enum(["HTTP", "gRPC", "GraphQL", "MessageBus", "NamedPipe", "IPC", "CLI", "Custom"]);
1733
1812
  EndpointSchema = import_zod8.z.discriminatedUnion("transport", [
@@ -10780,7 +10859,7 @@ var init_declarative_assertions = __esm({
10780
10859
  "use strict";
10781
10860
  declarativeAssertionsRule = {
10782
10861
  name: "declarative-assertions",
10783
- 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).",
10862
+ 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.",
10784
10863
  // Static codes are unknown here — packs bring their own. The validator
10785
10864
  // gathers every loaded assertion's fullCode into knownIssueCodes.
10786
10865
  codes: [],
@@ -12058,7 +12137,7 @@ var init_hidden_state = __esm({
12058
12137
  isStatelessLogic = (component) => isLogic(component) && !holdsState(component);
12059
12138
  hiddenStateRule = {
12060
12139
  name: "hidden-state",
12061
- 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.",
12140
+ 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.",
12062
12141
  codes: [
12063
12142
  { 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" }
12064
12143
  ],
@@ -12669,11 +12748,11 @@ function makeMatcher(tech) {
12669
12748
  const n = parts.length;
12670
12749
  return (text) => {
12671
12750
  if (!text) return false;
12672
- const words = text.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
12673
- if (words.some((w) => w.includes(joined))) return true;
12751
+ const words2 = text.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
12752
+ if (words2.some((w) => w.includes(joined))) return true;
12674
12753
  if (n < 2) return false;
12675
- for (let i = 0; i + n <= words.length; i++) {
12676
- if (words.slice(i, i + n).join("").includes(joined)) return true;
12754
+ for (let i = 0; i + n <= words2.length; i++) {
12755
+ if (words2.slice(i, i + n).join("").includes(joined)) return true;
12677
12756
  }
12678
12757
  return false;
12679
12758
  };
@@ -13079,14 +13158,13 @@ var init_complexity_and_metadata = __esm({
13079
13158
  init_models();
13080
13159
  complexityRule = {
13081
13160
  name: "complexity-and-metadata",
13082
- 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.",
13161
+ 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.",
13083
13162
  codes: [
13084
13163
  { code: "MISSING_DESCRIPTION", defaultSeverity: "warning", summary: "Required description field is missing or empty" },
13085
13164
  { code: "DESCRIPTION_TOO_SHORT", defaultSeverity: "warning", summary: "Description is shorter than the configured minimum length" },
13086
13165
  { code: "EXCESSIVE_METHODS", defaultSeverity: "warning", summary: "Interface declares more methods than the configured limit" },
13087
13166
  { code: "EXCESSIVE_METHOD_PARAMS", defaultSeverity: "warning", summary: "Interface method declares more parameters than the configured limit" },
13088
13167
  { code: "EXCESSIVE_DEPENDENCIES", defaultSeverity: "warning", summary: "Component has more dependencies than the configured limit" },
13089
- { code: "EXCESSIVE_NARRATIVE_STEPS", defaultSeverity: "warning", summary: "Method implementation contains more narrative steps than the configured limit" },
13090
13168
  { code: "EXCESSIVE_SUBSYSTEM_COMPONENTS", defaultSeverity: "warning", summary: "Subsystem has more direct components than the configured limit" }
13091
13169
  ],
13092
13170
  check(ctx) {
@@ -13174,25 +13252,6 @@ var init_complexity_and_metadata = __esm({
13174
13252
  }
13175
13253
  }
13176
13254
  }
13177
- for (const impl of ctx.implementations) {
13178
- const intf = ctx.interfaceMap.get(impl.contract);
13179
- const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
13180
- const complexityConfig = ctx.complexityConfigFor(comp?.subsystem);
13181
- const isDraft = ctx.isImplementationDraft(impl);
13182
- if (complexityConfig?.maxNarrativeSteps !== void 0) {
13183
- for (const m of impl.methods) {
13184
- if (m.narrative.length > complexityConfig.maxNarrativeSteps) {
13185
- ctx.addIssue(
13186
- "warning",
13187
- "EXCESSIVE_NARRATIVE_STEPS",
13188
- `Method "${m.name}" in implementation "${impl.id}" contains ${m.narrative.length} narrative steps, exceeding the configured limit of ${complexityConfig.maxNarrativeSteps}.`,
13189
- impl.id,
13190
- isDraft
13191
- );
13192
- }
13193
- }
13194
- }
13195
- }
13196
13255
  for (const t of ctx.types) {
13197
13256
  const docConfig = ctx.documentationConfigFor(t.subsystem);
13198
13257
  checkDescription(
@@ -13232,6 +13291,285 @@ var init_complexity_and_metadata = __esm({
13232
13291
  }
13233
13292
  });
13234
13293
 
13294
+ // src/core/rules/heuristic/narrative-complexity.ts
13295
+ function bandIndex(band) {
13296
+ return BANDS.indexOf(band ?? "");
13297
+ }
13298
+ var BANDS, DEFAULT_WARN_ABOVE, DEFAULT_MAX_STEPS, narrativeComplexityRule;
13299
+ var init_narrative_complexity = __esm({
13300
+ "src/core/rules/heuristic/narrative-complexity.ts"() {
13301
+ "use strict";
13302
+ init_models();
13303
+ BANDS = ["linear", "simple", "moderate", "complex", "severe"];
13304
+ DEFAULT_WARN_ABOVE = "moderate";
13305
+ DEFAULT_MAX_STEPS = 25;
13306
+ narrativeComplexityRule = {
13307
+ name: "narrative-complexity",
13308
+ 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.",
13309
+ codes: [
13310
+ { code: "NARRATIVE_COMPLEXITY", defaultSeverity: "warning", summary: "Narrative's cognitive band is above the configured level (default: above moderate)" },
13311
+ { code: "NARRATIVE_COMPLEXITY_OVER_MAX", defaultSeverity: "error", summary: "Narrative's cognitive band is above the configured maximum band" },
13312
+ { code: "EXCESSIVE_NARRATIVE_STEPS", defaultSeverity: "warning", summary: "Method implementation contains more narrative steps than the configured limit (default 25)" },
13313
+ { code: "NARRATIVE_STEPS_OVER_MAX", defaultSeverity: "error", summary: "Narrative step count is above the configured hard maximum" }
13314
+ ],
13315
+ check(ctx) {
13316
+ const entries = [];
13317
+ for (const impl of ctx.implementations) {
13318
+ const intf = ctx.interfaceMap.get(impl.contract);
13319
+ const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
13320
+ if (!comp) continue;
13321
+ for (const method of impl.methods) {
13322
+ if ((method.narrative ?? []).length === 0) continue;
13323
+ entries.push({ implementation: impl, method, component: comp, subsystem: comp.subsystem });
13324
+ }
13325
+ }
13326
+ for (const entry of entries) {
13327
+ const cfg = ctx.complexityConfigFor(entry.subsystem);
13328
+ const isDraft = ctx.isImplementationDraft(entry.implementation);
13329
+ const steps = entry.method.narrative.length;
13330
+ const score = cognitiveScore(entry.method);
13331
+ const band = complexityLevel(entry.method);
13332
+ const warnAbove = cfg?.cognitiveWarnAbove ?? DEFAULT_WARN_ABOVE;
13333
+ if (bandIndex(band) > bandIndex(warnAbove)) {
13334
+ ctx.addIssue(
13335
+ "warning",
13336
+ "NARRATIVE_COMPLEXITY",
13337
+ `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.`,
13338
+ entry.implementation.id,
13339
+ isDraft
13340
+ );
13341
+ }
13342
+ if (cfg?.maxCognitiveLevel !== void 0 && bandIndex(band) > bandIndex(cfg.maxCognitiveLevel)) {
13343
+ ctx.addIssue(
13344
+ "error",
13345
+ "NARRATIVE_COMPLEXITY_OVER_MAX",
13346
+ `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.`,
13347
+ entry.implementation.id,
13348
+ isDraft
13349
+ );
13350
+ }
13351
+ const maxSteps = cfg?.maxNarrativeSteps ?? DEFAULT_MAX_STEPS;
13352
+ if (steps > maxSteps) {
13353
+ ctx.addIssue(
13354
+ "warning",
13355
+ "EXCESSIVE_NARRATIVE_STEPS",
13356
+ `Method "${entry.method.name}" in implementation "${entry.implementation.id}" contains ${steps} narrative steps, exceeding the configured limit of ${maxSteps}.`,
13357
+ entry.implementation.id,
13358
+ isDraft
13359
+ );
13360
+ }
13361
+ if (cfg?.narrativeStepsHardMax !== void 0 && steps > cfg.narrativeStepsHardMax) {
13362
+ ctx.addIssue(
13363
+ "error",
13364
+ "NARRATIVE_STEPS_OVER_MAX",
13365
+ `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.`,
13366
+ entry.implementation.id,
13367
+ isDraft
13368
+ );
13369
+ }
13370
+ }
13371
+ }
13372
+ };
13373
+ }
13374
+ });
13375
+
13376
+ // src/core/rules/heuristic/naming-discipline.ts
13377
+ function words(identifier) {
13378
+ return identifier.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[\s\-_]+/).filter(Boolean).map((w) => w.toLowerCase());
13379
+ }
13380
+ function isSameNoun(word, noun) {
13381
+ if (word === noun) return true;
13382
+ if (word === `${noun}s`) return true;
13383
+ return noun.endsWith("s") && word === noun.slice(0, -1);
13384
+ }
13385
+ function withoutWord(methodWords, dropIndex) {
13386
+ const kept = methodWords.filter((_, i) => i !== dropIndex);
13387
+ return kept.map((w, i) => i === 0 ? w : w[0].toUpperCase() + w.slice(1)).join("");
13388
+ }
13389
+ var BLOCK_WORDS, GENERIC_WORDS, MIN_CONCEPT_LENGTH, namingDisciplineRule;
13390
+ var init_naming_discipline = __esm({
13391
+ "src/core/rules/heuristic/naming-discipline.ts"() {
13392
+ "use strict";
13393
+ init_models();
13394
+ BLOCK_WORDS = {
13395
+ portal: "Portal",
13396
+ orchestrator: "Orchestrator",
13397
+ supervisor: "Supervisor",
13398
+ actor: "Actor",
13399
+ store: "Store",
13400
+ index: "Index",
13401
+ query: "Query",
13402
+ registry: "Registry",
13403
+ adapter: "Adapter",
13404
+ observer: "Observer",
13405
+ repository: "Repository",
13406
+ view: "View"
13407
+ };
13408
+ GENERIC_WORDS = /* @__PURE__ */ new Set([
13409
+ "manager",
13410
+ "helper",
13411
+ "helpers",
13412
+ "util",
13413
+ "utils",
13414
+ "handler",
13415
+ "service",
13416
+ "processor",
13417
+ "coordinator",
13418
+ "engine",
13419
+ "wrapper",
13420
+ "common",
13421
+ "misc",
13422
+ "data",
13423
+ "info"
13424
+ ]);
13425
+ MIN_CONCEPT_LENGTH = 4;
13426
+ namingDisciplineRule = {
13427
+ name: "naming-discipline",
13428
+ 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.",
13429
+ codes: [
13430
+ { code: "MISLEADING_BLOCK_WORD", defaultSeverity: "warning", summary: "Component's head noun names a building block that is not its componentType" },
13431
+ { code: "GENERIC_COMPONENT_NAME", defaultSeverity: "warning", summary: "Component name or id uses a generic role word that says nothing (manager, helper, utils, handler, service)" },
13432
+ { code: "METHOD_REPEATS_COMPONENT", defaultSeverity: "warning", summary: "Method name repeats its component's head noun; not reported on Adapter or Portal forwarders" },
13433
+ { 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" }
13434
+ ],
13435
+ check(ctx) {
13436
+ for (const comp of ctx.components) {
13437
+ const head = headNoun(comp);
13438
+ const isDraft = ctx.isComponentDraft(comp.id);
13439
+ const idWords = [...words(comp.id), ...words(comp.name)];
13440
+ if (BLOCK_WORDS[head] && BLOCK_WORDS[head] !== comp.componentType) {
13441
+ ctx.addIssue(
13442
+ "warning",
13443
+ "MISLEADING_BLOCK_WORD",
13444
+ `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.`,
13445
+ comp.id,
13446
+ isDraft
13447
+ );
13448
+ }
13449
+ const generic = [...new Set(idWords.filter((w) => GENERIC_WORDS.has(w)))];
13450
+ if (generic.length > 0) {
13451
+ ctx.addIssue(
13452
+ "warning",
13453
+ "GENERIC_COMPONENT_NAME",
13454
+ `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.`,
13455
+ comp.id,
13456
+ isDraft
13457
+ );
13458
+ }
13459
+ const methods = ctx.interfaceMethodsOf(comp.id);
13460
+ if (comp.componentType !== "Adapter" && methods.length === 1) {
13461
+ const only = methods[0];
13462
+ const concept = conceptNoun(comp);
13463
+ const onlyWords = words(only.name);
13464
+ if (onlyWords.length > 1 && onlyWords.some((w) => isSameNoun(w, concept))) {
13465
+ ctx.addIssue(
13466
+ "warning",
13467
+ "COMPONENT_IS_ITS_ONLY_METHOD",
13468
+ `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.`,
13469
+ comp.id,
13470
+ isDraft
13471
+ );
13472
+ }
13473
+ }
13474
+ }
13475
+ const pairs = [];
13476
+ for (const intf of ctx.interfaces) {
13477
+ const comp = ctx.componentMap.get(intf.component);
13478
+ if (!comp || comp.componentType === "Adapter" || comp.componentType === "Portal") continue;
13479
+ const concept = conceptNoun(comp);
13480
+ if (concept.length < MIN_CONCEPT_LENGTH) continue;
13481
+ for (const method of intf.methods) pairs.push({ intf, comp, concept, method });
13482
+ }
13483
+ for (const { intf, comp, concept, method } of pairs) {
13484
+ const methodWords = words(method.name);
13485
+ if (methodWords.length < 2 || !isSameNoun(methodWords[1], concept)) continue;
13486
+ ctx.addIssue(
13487
+ "warning",
13488
+ "METHOD_REPEATS_COMPONENT",
13489
+ `Method "${method.name}" on component "${comp.id}" repeats the component's concept "${concept}" \u2014 the call site says it twice. Name it "${withoutWord(methodWords, 1)}".`,
13490
+ intf.id,
13491
+ ctx.isComponentDraft(comp.id)
13492
+ );
13493
+ }
13494
+ }
13495
+ };
13496
+ }
13497
+ });
13498
+
13499
+ // src/core/rules/heuristic/method-cohesion.ts
13500
+ function groupsSharingCollaborators(calledBy) {
13501
+ const parent = /* @__PURE__ */ new Map();
13502
+ const find = (m) => {
13503
+ let root = m;
13504
+ while (parent.get(root) !== root) root = parent.get(root);
13505
+ return root;
13506
+ };
13507
+ const union = (a, b) => {
13508
+ const [ra, rb] = [find(a), find(b)];
13509
+ if (ra !== rb) parent.set(ra, rb);
13510
+ };
13511
+ const methods = [...calledBy.keys()];
13512
+ for (const m of methods) parent.set(m, m);
13513
+ for (let i = 0; i < methods.length; i++) {
13514
+ for (let j = i + 1; j < methods.length; j++) {
13515
+ const shares = [...calledBy.get(methods[i])].some((c) => calledBy.get(methods[j])?.has(c));
13516
+ if (shares) union(methods[i], methods[j]);
13517
+ }
13518
+ }
13519
+ const groups = /* @__PURE__ */ new Map();
13520
+ for (const m of methods) {
13521
+ const root = find(m);
13522
+ groups.set(root, [...groups.get(root) ?? [], m]);
13523
+ }
13524
+ return [...groups.values()];
13525
+ }
13526
+ var methodCohesionRule;
13527
+ var init_method_cohesion = __esm({
13528
+ "src/core/rules/heuristic/method-cohesion.ts"() {
13529
+ "use strict";
13530
+ methodCohesionRule = {
13531
+ name: "method-cohesion",
13532
+ 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.",
13533
+ codes: [
13534
+ { code: "INCOHESIVE_METHODS", defaultSeverity: "warning", summary: "Orchestrator's methods form two or more groups of two or more that share no called component" }
13535
+ ],
13536
+ check(ctx) {
13537
+ const subjects = [];
13538
+ for (const impl of ctx.implementations) {
13539
+ const intf = ctx.interfaceMap.get(impl.contract);
13540
+ const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
13541
+ if (!comp || comp.componentType !== "Orchestrator") continue;
13542
+ const calledBy = /* @__PURE__ */ new Map();
13543
+ for (const method of impl.methods) {
13544
+ const reached = /* @__PURE__ */ new Set();
13545
+ for (const step of method.narrative ?? []) {
13546
+ if ((step.type === "call" || step.type === "dispatch") && step.targetComponent) reached.add(step.targetComponent);
13547
+ }
13548
+ if (reached.size > 0) calledBy.set(method.name, reached);
13549
+ }
13550
+ subjects.push({ implementation: impl, component: comp, calledBy });
13551
+ }
13552
+ for (const { component, calledBy } of subjects) {
13553
+ if (calledBy.size < 2) continue;
13554
+ const groups = groupsSharingCollaborators(calledBy).filter((g) => g.length >= 2);
13555
+ if (groups.length < 2) continue;
13556
+ const described = groups.map((g) => {
13557
+ const collaborators = [...new Set(g.flatMap((m) => [...calledBy.get(m)]))].sort();
13558
+ return `${g.join(", ")} (calling ${collaborators.join(", ")})`;
13559
+ }).join("; ");
13560
+ ctx.addIssue(
13561
+ "warning",
13562
+ "INCOHESIVE_METHODS",
13563
+ `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.`,
13564
+ component.id,
13565
+ ctx.isComponentDraft(component.id)
13566
+ );
13567
+ }
13568
+ }
13569
+ };
13570
+ }
13571
+ });
13572
+
13235
13573
  // src/core/rules/repository.ts
13236
13574
  function addRule(rule) {
13237
13575
  ruleSet.push(rule);
@@ -13308,6 +13646,9 @@ var init_repository = __esm({
13308
13646
  init_technology_boundaries();
13309
13647
  init_naming_conventions();
13310
13648
  init_complexity_and_metadata();
13649
+ init_narrative_complexity();
13650
+ init_naming_discipline();
13651
+ init_method_cohesion();
13311
13652
  SDD_RULES = [
13312
13653
  hierarchyRule,
13313
13654
  // Namespace integrity right after hierarchy: unresolvable/unwritable ids
@@ -13383,6 +13724,12 @@ var init_repository = __esm({
13383
13724
  technologyRule,
13384
13725
  namingRule,
13385
13726
  complexityRule,
13727
+ // The doctrine heuristics ride with the structural caps: same subject
13728
+ // (size, name, shape), judgements rather than errors, and they read the
13729
+ // same complexity dial.
13730
+ narrativeComplexityRule,
13731
+ namingDisciplineRule,
13732
+ methodCohesionRule,
13386
13733
  // Pack resolution and reproducibility run late: they are about project
13387
13734
  // CONFIGURATION (does the declared pack set resolve, and can it be reproduced
13388
13735
  // elsewhere?) rather than spec content.
@@ -13467,13 +13814,13 @@ function analyzeWithPatterns(text, patterns) {
13467
13814
  };
13468
13815
  }
13469
13816
  function analyzeGeneric(text) {
13470
- const words = /* @__PURE__ */ new Set();
13471
- for (const m of text.matchAll(IDENTIFIER_RE)) words.add(m[0]);
13472
- const anchors = new Set(words);
13817
+ const words2 = /* @__PURE__ */ new Set();
13818
+ for (const m of text.matchAll(IDENTIFIER_RE)) words2.add(m[0]);
13819
+ const anchors = new Set(words2);
13473
13820
  for (const s of collectStrings(text)) anchors.add(s);
13474
13821
  return {
13475
13822
  analysisGrade: "generic",
13476
- declaredNames: [...words],
13823
+ declaredNames: [...words2],
13477
13824
  anchoredNames: [...anchors],
13478
13825
  exportedNames: [],
13479
13826
  imports: [],
@@ -14103,7 +14450,7 @@ function buildRuleContext(opts) {
14103
14450
  const projectComp = rules?.complexity;
14104
14451
  const packDef = profileDefFor(subsystemId);
14105
14452
  if (packDef?.rules?.complexity) {
14106
- return { ...projectComp, ...packDef.rules.complexity };
14453
+ return { ...packDef.rules.complexity, ...projectComp };
14107
14454
  }
14108
14455
  return projectComp;
14109
14456
  };
@@ -14111,7 +14458,7 @@ function buildRuleContext(opts) {
14111
14458
  const projectDoc = rules?.documentation;
14112
14459
  const packDef = profileDefFor(subsystemId);
14113
14460
  if (packDef?.rules?.documentation) {
14114
- return { ...projectDoc, ...packDef.rules.documentation };
14461
+ return { ...packDef.rules.documentation, ...projectDoc };
14115
14462
  }
14116
14463
  return projectDoc;
14117
14464
  };
@@ -14120,11 +14467,11 @@ function buildRuleContext(opts) {
14120
14467
  const packDef = profileDefFor(subsystemId);
14121
14468
  if (packDef?.rules?.naming) {
14122
14469
  return {
14123
- ...projectNaming,
14124
14470
  ...packDef.rules.naming,
14471
+ ...projectNaming,
14125
14472
  stereotypes: {
14126
- ...projectNaming?.stereotypes ?? {},
14127
- ...packDef.rules.naming.stereotypes ?? {}
14473
+ ...packDef.rules.naming.stereotypes ?? {},
14474
+ ...projectNaming?.stereotypes ?? {}
14128
14475
  }
14129
14476
  };
14130
14477
  }
@@ -24157,8 +24504,10 @@ __export(src_exports, {
24157
24504
  captureApprovedSpecs: () => captureApprovedSpecs,
24158
24505
  checkSkillFreshness: () => checkSkillFreshness,
24159
24506
  clearLoaderIssues: () => clearLoaderIssues,
24507
+ cognitiveScore: () => cognitiveScore,
24160
24508
  collectPromotableSpecs: () => collectPromotableSpecs,
24161
24509
  commitStagedTree: () => commitStagedTree,
24510
+ complexityLevel: () => complexityLevel,
24162
24511
  composeAgentBrief: () => composeAgentBrief,
24163
24512
  composeRuleSequence: () => composeRuleSequence,
24164
24513
  composeVariantGuidance: () => composeVariantGuidance,
@@ -24166,6 +24515,7 @@ __export(src_exports, {
24166
24515
  computePackDigest: () => computePackDigest,
24167
24516
  computeStateId: () => computeStateId,
24168
24517
  computeStateIdAt: () => computeStateIdAt,
24518
+ conceptNoun: () => conceptNoun,
24169
24519
  consumedContractInputs: () => consumedContractInputs,
24170
24520
  contextDir: () => contextDir,
24171
24521
  createAgentRecord: () => createAgentRecord,
@@ -24245,6 +24595,7 @@ __export(src_exports, {
24245
24595
  hasAuthoredSpecs: () => hasAuthoredSpecs,
24246
24596
  hasContext: () => hasContext,
24247
24597
  hasSpecTree: () => hasSpecTree,
24598
+ headNoun: () => headNoun,
24248
24599
  holdsState: () => holdsState,
24249
24600
  implementationSourceFiles: () => implementationSourceFiles,
24250
24601
  importSpecTree: () => importSpecTree,
@@ -24871,8 +25222,10 @@ function loadProjectConfig4() {
24871
25222
  captureApprovedSpecs,
24872
25223
  checkSkillFreshness,
24873
25224
  clearLoaderIssues,
25225
+ cognitiveScore,
24874
25226
  collectPromotableSpecs,
24875
25227
  commitStagedTree,
25228
+ complexityLevel,
24876
25229
  composeAgentBrief,
24877
25230
  composeRuleSequence,
24878
25231
  composeVariantGuidance,
@@ -24880,6 +25233,7 @@ function loadProjectConfig4() {
24880
25233
  computePackDigest,
24881
25234
  computeStateId,
24882
25235
  computeStateIdAt,
25236
+ conceptNoun,
24883
25237
  consumedContractInputs,
24884
25238
  contextDir,
24885
25239
  createAgentRecord,
@@ -24959,6 +25313,7 @@ function loadProjectConfig4() {
24959
25313
  hasAuthoredSpecs,
24960
25314
  hasContext,
24961
25315
  hasSpecTree,
25316
+ headNoun,
24962
25317
  holdsState,
24963
25318
  implementationSourceFiles,
24964
25319
  importSpecTree,