@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/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
|
-
/**
|
|
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.
|
|
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
|
|
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
|
|
12673
|
-
if (
|
|
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 <=
|
|
12676
|
-
if (
|
|
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
|
|
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
|
|
13471
|
-
for (const m of text.matchAll(IDENTIFIER_RE))
|
|
13472
|
-
const anchors = new Set(
|
|
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: [...
|
|
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 { ...
|
|
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 { ...
|
|
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
|
-
...
|
|
14127
|
-
...
|
|
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,
|