@wairon/cli 5.1.1-dev.15 → 5.1.1-dev.17
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 +411 -318
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +397 -256
- package/dist/index.js.map +1 -1
- package/dist/templates/skills/sdd-architect.md +1 -1
- package/dist/templates/skills/sdd-implement.md +1 -1
- package/dist/templates/skills/sdd-narrative.md +3 -1
- package/dist/templates/variants/logic-shapes.yaml +59 -0
- package/dist/templates/variants/portal-shapes.yaml +13 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1092,7 +1092,7 @@ var init_defaults = __esm({
|
|
|
1092
1092
|
copilot: ".github/prompts",
|
|
1093
1093
|
codex: ".codex/agents"
|
|
1094
1094
|
};
|
|
1095
|
-
WAIRON_VERSION = "5.1.1-dev.
|
|
1095
|
+
WAIRON_VERSION = "5.1.1-dev.17";
|
|
1096
1096
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
1097
1097
|
ARCHITECT_AGENT_ID = "agent-architect";
|
|
1098
1098
|
ARCHITECT_TEMPLATE_ID = "architect";
|
|
@@ -1406,7 +1406,7 @@ function defaultNarrativeDetail(component) {
|
|
|
1406
1406
|
if (componentType === "Portal" || componentType === "Observer" || componentType === "Adapter") {
|
|
1407
1407
|
return "calls-only";
|
|
1408
1408
|
}
|
|
1409
|
-
if (componentType === "Store" || componentType === "Index" || componentType === "Registry") {
|
|
1409
|
+
if (componentType === "Store" || componentType === "Index" || componentType === "Query" || componentType === "Registry") {
|
|
1410
1410
|
return "intent";
|
|
1411
1411
|
}
|
|
1412
1412
|
return "full";
|
|
@@ -1414,6 +1414,17 @@ function defaultNarrativeDetail(component) {
|
|
|
1414
1414
|
function isPattern(component) {
|
|
1415
1415
|
return PATTERN_TYPES.has(component.componentType);
|
|
1416
1416
|
}
|
|
1417
|
+
function isLogic(component) {
|
|
1418
|
+
const componentType = component.componentType;
|
|
1419
|
+
return componentType === "Orchestrator" || componentType === "Supervisor" || componentType === "Actor" || componentType === "Specialist";
|
|
1420
|
+
}
|
|
1421
|
+
function holdsState(component) {
|
|
1422
|
+
const componentType = component.componentType;
|
|
1423
|
+
return componentType === "Store" || componentType === "Index" || componentType === "Supervisor" || componentType === "Actor";
|
|
1424
|
+
}
|
|
1425
|
+
function isRetired(component) {
|
|
1426
|
+
return RETIRED_STEREOTYPES.has(component.componentType);
|
|
1427
|
+
}
|
|
1417
1428
|
function passesIntentFloor(text, methodName) {
|
|
1418
1429
|
if (!text) return false;
|
|
1419
1430
|
const t = text.trim();
|
|
@@ -1439,7 +1450,7 @@ function implementationSourceFiles(impl) {
|
|
|
1439
1450
|
for (const method of impl.methods ?? []) add(method.sourcePath);
|
|
1440
1451
|
return files;
|
|
1441
1452
|
}
|
|
1442
|
-
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, PortalTypeSchema, DispatchBindingSchema, DurabilitySchema, 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;
|
|
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;
|
|
1443
1454
|
var init_specs = __esm({
|
|
1444
1455
|
"src/models/specs.ts"() {
|
|
1445
1456
|
"use strict";
|
|
@@ -1605,21 +1616,28 @@ var init_specs = __esm({
|
|
|
1605
1616
|
"Actor",
|
|
1606
1617
|
"Store",
|
|
1607
1618
|
"Index",
|
|
1619
|
+
"Query",
|
|
1620
|
+
// Repository member for computed reads over its Store
|
|
1608
1621
|
"Registry",
|
|
1609
1622
|
"Adapter",
|
|
1610
1623
|
"Observer",
|
|
1611
|
-
"Specialist",
|
|
1612
1624
|
"View",
|
|
1613
1625
|
// Pure presenter/UI component
|
|
1614
1626
|
// Patterns (compositions of blocks)
|
|
1615
1627
|
"Repository",
|
|
1616
|
-
"Gateway",
|
|
1617
1628
|
"FeatureComponent",
|
|
1618
1629
|
// Logic Hook + View Presenter pattern
|
|
1619
|
-
"RouterComponent"
|
|
1630
|
+
"RouterComponent",
|
|
1620
1631
|
// Switch/routing component pattern
|
|
1632
|
+
// Retired stereotypes: still parsed so a tree using them loads and the
|
|
1633
|
+
// rules can report them (STEREOTYPE_RETIRED), never authored new
|
|
1634
|
+
"Specialist",
|
|
1635
|
+
// logic is an Orchestrator with a dependencyClass
|
|
1636
|
+
"Gateway"
|
|
1637
|
+
// a gateway is a Portal with the gateway variant
|
|
1621
1638
|
]);
|
|
1622
|
-
PATTERN_TYPES = /* @__PURE__ */ new Set(["Repository", "
|
|
1639
|
+
PATTERN_TYPES = /* @__PURE__ */ new Set(["Repository", "FeatureComponent", "RouterComponent"]);
|
|
1640
|
+
RETIRED_STEREOTYPES = /* @__PURE__ */ new Set(["Specialist", "Gateway"]);
|
|
1623
1641
|
PortalTypeSchema = import_zod8.z.enum(["HTTP_API", "gRPC", "GraphQL", "MessageBus", "CLI", "NamedPipe", "IPC", "Custom"]);
|
|
1624
1642
|
DispatchBindingSchema = import_zod8.z.object({
|
|
1625
1643
|
/** Capability name exactly as dispatched at runtime (e.g. "shadow_module.get"). */
|
|
@@ -1632,6 +1650,7 @@ var init_specs = __esm({
|
|
|
1632
1650
|
description: import_zod8.z.string().optional()
|
|
1633
1651
|
});
|
|
1634
1652
|
DurabilitySchema = import_zod8.z.enum(["ram-projection", "durable", "read-through", "cache"]);
|
|
1653
|
+
DependencyClassSchema = import_zod8.z.enum(["pure", "read"]);
|
|
1635
1654
|
PatternRefSchema = import_zod8.z.object({
|
|
1636
1655
|
id: import_zod8.z.string(),
|
|
1637
1656
|
version: import_zod8.z.string().optional()
|
|
@@ -1686,6 +1705,8 @@ var init_specs = __esm({
|
|
|
1686
1705
|
dispatch: import_zod8.z.array(DispatchBindingSchema).optional(),
|
|
1687
1706
|
/** Store-only: whether held state survives restart (see DurabilitySchema). */
|
|
1688
1707
|
durability: DurabilitySchema.optional(),
|
|
1708
|
+
/** Orchestrator-only: what the logic may depend on; unset means a workflow (see DependencyClassSchema). */
|
|
1709
|
+
dependencyClass: DependencyClassSchema.optional(),
|
|
1689
1710
|
/** Topics this component publishes to (see EventBindingSchema). */
|
|
1690
1711
|
emits: import_zod8.z.array(EventBindingSchema).optional(),
|
|
1691
1712
|
/** Topics this component consumes (see EventBindingSchema) — typical on Observers. */
|
|
@@ -1776,9 +1797,10 @@ var init_specs = __esm({
|
|
|
1776
1797
|
}).optional(),
|
|
1777
1798
|
/**
|
|
1778
1799
|
* The finding codes this method can report, each with its default severity
|
|
1779
|
-
* and summary (see FindingDeclarationSchema). Each declared code must
|
|
1780
|
-
*
|
|
1781
|
-
* code is declared once per
|
|
1800
|
+
* and summary (see FindingDeclarationSchema). Each declared code must be
|
|
1801
|
+
* anchored in the method's source file as a string literal or a
|
|
1802
|
+
* property-access name (UNREALIZED_FINDING). A code is declared once per
|
|
1803
|
+
* method.
|
|
1782
1804
|
*/
|
|
1783
1805
|
findings: import_zod8.z.array(FindingDeclarationSchema).superRefine((findings, ctx) => {
|
|
1784
1806
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -2766,15 +2788,13 @@ var init_hierarchy_integrity = __esm({
|
|
|
2766
2788
|
}
|
|
2767
2789
|
}
|
|
2768
2790
|
for (const impl of ctx.implementations) {
|
|
2769
|
-
const contract = ctx.interfaces.find((i) => i.id === impl.contract);
|
|
2770
|
-
const isDraftCtx = impl.status === "draft" || impl.status === "design" || contract && (ctx.isComponentDraft(contract.component) || contract.status === "draft" || contract.status === "design");
|
|
2771
2791
|
if (!ctx.interfaceIds.has(impl.contract)) {
|
|
2772
2792
|
ctx.addIssue(
|
|
2773
2793
|
"error",
|
|
2774
2794
|
"INVALID_INTERFACE_REFERENCE",
|
|
2775
2795
|
`Implementation "${impl.id}" references non-existent interface contract "${impl.contract}".`,
|
|
2776
2796
|
impl.id,
|
|
2777
|
-
|
|
2797
|
+
ctx.isImplementationDraft(impl)
|
|
2778
2798
|
);
|
|
2779
2799
|
}
|
|
2780
2800
|
}
|
|
@@ -2890,11 +2910,7 @@ var init_type_references2 = __esm({
|
|
|
2890
2910
|
for (const field of t.fields) {
|
|
2891
2911
|
const refs = fieldTypeRefs(t, field.type);
|
|
2892
2912
|
for (const ref of refs) {
|
|
2893
|
-
if (ctx.
|
|
2894
|
-
continue;
|
|
2895
|
-
}
|
|
2896
|
-
const resolved = ctx.types.find((spec) => typeMatchesRef(spec, ref));
|
|
2897
|
-
if (!resolved) {
|
|
2913
|
+
if (!ctx.isTypeResolved(ref, /* @__PURE__ */ new Set())) {
|
|
2898
2914
|
ctx.addIssue(
|
|
2899
2915
|
"error",
|
|
2900
2916
|
"UNDEFINED_TYPE_REFERENCE",
|
|
@@ -3126,14 +3142,27 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3126
3142
|
);
|
|
3127
3143
|
continue;
|
|
3128
3144
|
}
|
|
3129
|
-
const
|
|
3130
|
-
const
|
|
3131
|
-
const
|
|
3145
|
+
const target = step.targetComponent;
|
|
3146
|
+
const isCrossTreeForm = ctx.isExternalNamespaceRef(target);
|
|
3147
|
+
const caller = ctx.componentMap.get(contract.component);
|
|
3148
|
+
const fromSubsystem = caller?.subsystem;
|
|
3149
|
+
const isCollapsedForm = !isCrossTreeForm && fromSubsystem !== void 0 && ctx.isCollapsedCrossTreeRef(target, fromSubsystem);
|
|
3150
|
+
const reportUndeclared = (reaches, surfaceResolved) => {
|
|
3151
|
+
if (!caller || target === caller.id || caller.dependsOn.includes(target) || caller.owns.includes(target)) return;
|
|
3152
|
+
ctx.addIssue(
|
|
3153
|
+
"error",
|
|
3154
|
+
"UNDECLARED_DEPENDENCY_CALL",
|
|
3155
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" (component "${caller.id}") ${reaches} component "${target}" (step ${step.stepNumber}) but component "${caller.id}" does not list "${target}" as a dependency.`,
|
|
3156
|
+
impl.id,
|
|
3157
|
+
isDraftCtx || ctx.isComponentDraft(caller.id),
|
|
3158
|
+
surfaceResolved
|
|
3159
|
+
);
|
|
3160
|
+
};
|
|
3132
3161
|
if (step.type === "dispatch") {
|
|
3133
|
-
const dispatchTarget = ctx.componentMap.get(
|
|
3162
|
+
const dispatchTarget = ctx.componentMap.get(target);
|
|
3134
3163
|
if (!dispatchTarget) {
|
|
3135
3164
|
if (isCrossTreeForm || isCollapsedForm) {
|
|
3136
|
-
const resolved = ctx.resolveSurfaceRef(
|
|
3165
|
+
const resolved = ctx.resolveSurfaceRef(target, fromSubsystem);
|
|
3137
3166
|
if (resolved.kind === "ambiguous") {
|
|
3138
3167
|
ctx.addIssue(
|
|
3139
3168
|
"error",
|
|
@@ -3141,7 +3170,7 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3141
3170
|
ambiguityMessage(
|
|
3142
3171
|
resolved,
|
|
3143
3172
|
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches (step ${step.stepNumber}) through`,
|
|
3144
|
-
|
|
3173
|
+
target
|
|
3145
3174
|
),
|
|
3146
3175
|
impl.id,
|
|
3147
3176
|
isDraftCtx
|
|
@@ -3149,11 +3178,12 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3149
3178
|
continue;
|
|
3150
3179
|
}
|
|
3151
3180
|
if (resolved.kind === "resolved") {
|
|
3181
|
+
reportUndeclared("dispatches through", true);
|
|
3152
3182
|
if (step.capability && !(resolved.entry.dispatch ?? []).some((b) => b.capability === step.capability)) {
|
|
3153
3183
|
ctx.addIssue(
|
|
3154
3184
|
"error",
|
|
3155
3185
|
"SURFACE_REF_NOT_EXPOSED",
|
|
3156
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches capability "${step.capability}" through cross-tree portal "${
|
|
3186
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches capability "${step.capability}" through cross-tree portal "${target}" (step ${step.stepNumber}), but the surface snapshot of "${resolved.snapshot.projectName}" does not serve that capability on "${resolved.entry.id}".`,
|
|
3157
3187
|
impl.id,
|
|
3158
3188
|
isDraftCtx,
|
|
3159
3189
|
true
|
|
@@ -3166,7 +3196,7 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3166
3196
|
ctx.addIssue(
|
|
3167
3197
|
"warning",
|
|
3168
3198
|
"CROSS_TREE_REF_UNRESOLVED",
|
|
3169
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches through cross-tree component "${
|
|
3199
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches through cross-tree component "${target}" (step ${step.stepNumber}), and no surface snapshot covers it \u2014 validate from the parent project, pin the family surfaces ("wairon surface pin"), or import the producing project's surface.`,
|
|
3170
3200
|
impl.id,
|
|
3171
3201
|
isDraftCtx
|
|
3172
3202
|
);
|
|
@@ -3174,23 +3204,14 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3174
3204
|
ctx.addIssue(
|
|
3175
3205
|
"error",
|
|
3176
3206
|
"INVALID_TARGET_COMPONENT_REFERENCE",
|
|
3177
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches through component "${
|
|
3207
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches through component "${target}" which does not exist (step ${step.stepNumber}).`,
|
|
3178
3208
|
impl.id,
|
|
3179
3209
|
isDraftCtx
|
|
3180
3210
|
);
|
|
3181
3211
|
}
|
|
3182
3212
|
continue;
|
|
3183
3213
|
}
|
|
3184
|
-
|
|
3185
|
-
if (dispatchCaller && step.targetComponent !== dispatchCaller.id && !dispatchCaller.dependsOn.includes(step.targetComponent) && !dispatchCaller.owns.includes(step.targetComponent)) {
|
|
3186
|
-
ctx.addIssue(
|
|
3187
|
-
"error",
|
|
3188
|
-
"UNDECLARED_DEPENDENCY_CALL",
|
|
3189
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" (component "${dispatchCaller.id}") dispatches through component "${step.targetComponent}" (step ${step.stepNumber}) but component "${dispatchCaller.id}" does not list "${step.targetComponent}" as a dependency.`,
|
|
3190
|
-
impl.id,
|
|
3191
|
-
isDraftCtx || ctx.isComponentDraft(dispatchCaller.id)
|
|
3192
|
-
);
|
|
3193
|
-
}
|
|
3214
|
+
reportUndeclared("dispatches through");
|
|
3194
3215
|
continue;
|
|
3195
3216
|
}
|
|
3196
3217
|
const verb = step.type === "register" ? "registers callback" : "calls";
|
|
@@ -3204,10 +3225,10 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3204
3225
|
);
|
|
3205
3226
|
continue;
|
|
3206
3227
|
}
|
|
3207
|
-
const targetComp = ctx.componentMap.get(
|
|
3228
|
+
const targetComp = ctx.componentMap.get(target);
|
|
3208
3229
|
if (!targetComp) {
|
|
3209
3230
|
if (isCrossTreeForm || isCollapsedForm) {
|
|
3210
|
-
const resolved = ctx.resolveSurfaceRef(
|
|
3231
|
+
const resolved = ctx.resolveSurfaceRef(target, fromSubsystem);
|
|
3211
3232
|
if (resolved.kind === "ambiguous") {
|
|
3212
3233
|
ctx.addIssue(
|
|
3213
3234
|
"error",
|
|
@@ -3215,7 +3236,7 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3215
3236
|
ambiguityMessage(
|
|
3216
3237
|
resolved,
|
|
3217
3238
|
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} "${step.targetMethod}" (step ${step.stepNumber}) on`,
|
|
3218
|
-
|
|
3239
|
+
target
|
|
3219
3240
|
),
|
|
3220
3241
|
impl.id,
|
|
3221
3242
|
isDraftCtx
|
|
@@ -3223,12 +3244,13 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3223
3244
|
continue;
|
|
3224
3245
|
}
|
|
3225
3246
|
if (resolved.kind === "resolved") {
|
|
3247
|
+
reportUndeclared(verb, true);
|
|
3226
3248
|
const surfaceMethod = resolved.entry.methods.find((m) => m.name === step.targetMethod);
|
|
3227
3249
|
if (!surfaceMethod) {
|
|
3228
3250
|
ctx.addIssue(
|
|
3229
3251
|
"error",
|
|
3230
3252
|
"SURFACE_REF_NOT_EXPOSED",
|
|
3231
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} "${step.targetMethod}" on cross-tree component "${
|
|
3253
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} "${step.targetMethod}" on cross-tree component "${target}" (step ${step.stepNumber}), but the surface snapshot of "${resolved.snapshot.projectName}" does not expose that method on "${resolved.entry.id}".`,
|
|
3232
3254
|
impl.id,
|
|
3233
3255
|
isDraftCtx,
|
|
3234
3256
|
true
|
|
@@ -3255,7 +3277,7 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3255
3277
|
ctx.addIssue(
|
|
3256
3278
|
"warning",
|
|
3257
3279
|
"CROSS_TREE_REF_UNRESOLVED",
|
|
3258
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} cross-tree component "${
|
|
3280
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} cross-tree component "${target}" (step ${step.stepNumber}), and no surface snapshot covers it \u2014 validate from the parent project, pin the family surfaces ("wairon surface pin"), or import the producing project's surface.`,
|
|
3259
3281
|
impl.id,
|
|
3260
3282
|
isDraftCtx
|
|
3261
3283
|
);
|
|
@@ -3263,26 +3285,15 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3263
3285
|
ctx.addIssue(
|
|
3264
3286
|
"error",
|
|
3265
3287
|
"INVALID_TARGET_COMPONENT_REFERENCE",
|
|
3266
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} component "${
|
|
3288
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} component "${target}" which does not exist (step ${step.stepNumber}).`,
|
|
3267
3289
|
impl.id,
|
|
3268
3290
|
isDraftCtx
|
|
3269
3291
|
);
|
|
3270
3292
|
}
|
|
3271
3293
|
continue;
|
|
3272
3294
|
}
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
if (!callingComponent.dependsOn.includes(step.targetComponent) && !callingComponent.owns.includes(step.targetComponent)) {
|
|
3276
|
-
ctx.addIssue(
|
|
3277
|
-
"error",
|
|
3278
|
-
"UNDECLARED_DEPENDENCY_CALL",
|
|
3279
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" (component "${callingComponent.id}") ${verb} component "${step.targetComponent}" (step ${step.stepNumber}) but component "${callingComponent.id}" does not list "${step.targetComponent}" as a dependency.`,
|
|
3280
|
-
impl.id,
|
|
3281
|
-
isDraftCtx || ctx.isComponentDraft(callingComponent.id)
|
|
3282
|
-
);
|
|
3283
|
-
}
|
|
3284
|
-
}
|
|
3285
|
-
const targetInterfaces = ctx.interfacesByComponent.get(step.targetComponent) ?? [];
|
|
3295
|
+
reportUndeclared(verb);
|
|
3296
|
+
const targetInterfaces = ctx.interfacesByComponent.get(target) ?? [];
|
|
3286
3297
|
let targetMethodSpec;
|
|
3287
3298
|
for (const targetIntf of targetInterfaces) {
|
|
3288
3299
|
const found = targetIntf.methods.find((m) => m.name === step.targetMethod);
|
|
@@ -3295,9 +3306,9 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3295
3306
|
ctx.addIssue(
|
|
3296
3307
|
"error",
|
|
3297
3308
|
"INVALID_TARGET_METHOD_REFERENCE",
|
|
3298
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} method "${step.targetMethod}" on component "${
|
|
3309
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} method "${step.targetMethod}" on component "${target}" which is not defined on any of its interfaces (step ${step.stepNumber}).`,
|
|
3299
3310
|
impl.id,
|
|
3300
|
-
isDraftCtx || ctx.isComponentDraft(
|
|
3311
|
+
isDraftCtx || ctx.isComponentDraft(target)
|
|
3301
3312
|
);
|
|
3302
3313
|
} else {
|
|
3303
3314
|
const declared = new Set(targetMethodSpec.guarantees ?? []);
|
|
@@ -3307,9 +3318,9 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3307
3318
|
ctx.addIssue(
|
|
3308
3319
|
"warning",
|
|
3309
3320
|
"NARRATIVE_SEMANTIC_UNBACKED",
|
|
3310
|
-
`Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" explicitly asserts guarantee "${g}", but the method it calls \u2014 "${step.targetMethod}" on "${
|
|
3321
|
+
`Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" explicitly asserts guarantee "${g}", but the method it calls \u2014 "${step.targetMethod}" on "${target}" \u2014 does not list "${g}" among its L3 contract guarantees. Declare it on that method (and ensure its shape can deliver it), or revise the narrative.`,
|
|
3311
3322
|
impl.id,
|
|
3312
|
-
isDraftCtx || ctx.isComponentDraft(
|
|
3323
|
+
isDraftCtx || ctx.isComponentDraft(target)
|
|
3313
3324
|
);
|
|
3314
3325
|
}
|
|
3315
3326
|
}
|
|
@@ -3639,43 +3650,47 @@ var init_narrative_flow = __esm({
|
|
|
3639
3650
|
});
|
|
3640
3651
|
|
|
3641
3652
|
// src/core/rules/narrative/narrative-antipatterns.ts
|
|
3642
|
-
function
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
}
|
|
3653
|
+
function completedStepGraph(steps) {
|
|
3654
|
+
const numbers = steps.flatMap((s) => [
|
|
3655
|
+
s.stepNumber,
|
|
3656
|
+
s.onTrueStep,
|
|
3657
|
+
s.onFalseStep,
|
|
3658
|
+
s.defaultStep,
|
|
3659
|
+
s.endStep,
|
|
3660
|
+
s.finallyStep,
|
|
3661
|
+
s.toStep,
|
|
3662
|
+
...(s.cases ?? []).map((c) => c.step),
|
|
3663
|
+
...(s.catches ?? []).map((c) => c.step),
|
|
3664
|
+
...(s.branches ?? []).map((b) => b.step)
|
|
3665
|
+
]).filter((n) => n !== void 0);
|
|
3666
|
+
const completion = {
|
|
3667
|
+
stepNumber: Math.max(0, ...numbers) + 1,
|
|
3668
|
+
description: "The method completes by falling off the end of its narrative",
|
|
3669
|
+
type: "return"
|
|
3670
|
+
};
|
|
3671
|
+
return stepGraph({ narrative: [...steps, completion] });
|
|
3661
3672
|
}
|
|
3662
3673
|
function isUnavoidable(steps, target) {
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
const
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
const
|
|
3670
|
-
if (
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3674
|
+
if (steps.length === 0) return false;
|
|
3675
|
+
const graph = completedStepGraph(steps);
|
|
3676
|
+
const completes = /* @__PURE__ */ new Set();
|
|
3677
|
+
const canComplete = (n) => {
|
|
3678
|
+
const s = graph.stepAt(n);
|
|
3679
|
+
if (s.type === "return" || s.type === "throw") return true;
|
|
3680
|
+
const arms = s.type === "parallel" ? (s.branches ?? []).map((b) => b.step).filter((e) => graph.stepAt(e) !== void 0) : [];
|
|
3681
|
+
if (arms.length) return arms.every((e) => completes.has(e));
|
|
3682
|
+
return graph.successorsOf(n).some((t) => completes.has(t));
|
|
3683
|
+
};
|
|
3684
|
+
const order = [...graph.stepNumbers].reverse();
|
|
3685
|
+
for (let grew = true; grew; ) {
|
|
3686
|
+
grew = false;
|
|
3687
|
+
for (const n of order) {
|
|
3688
|
+
if (n === target || completes.has(n) || !canComplete(n)) continue;
|
|
3689
|
+
completes.add(n);
|
|
3690
|
+
grew = true;
|
|
3676
3691
|
}
|
|
3677
3692
|
}
|
|
3678
|
-
return
|
|
3693
|
+
return !completes.has(graph.stepNumbers[0]);
|
|
3679
3694
|
}
|
|
3680
3695
|
function stronglyConnected(nums, successorsOf) {
|
|
3681
3696
|
const index = /* @__PURE__ */ new Map();
|
|
@@ -3727,11 +3742,12 @@ function stronglyConnected(nums, successorsOf) {
|
|
|
3727
3742
|
}
|
|
3728
3743
|
return sccs;
|
|
3729
3744
|
}
|
|
3730
|
-
var narrativeAntipatternsRule;
|
|
3745
|
+
var byCodeUnit, narrativeAntipatternsRule;
|
|
3731
3746
|
var init_narrative_antipatterns = __esm({
|
|
3732
3747
|
"src/core/rules/narrative/narrative-antipatterns.ts"() {
|
|
3733
3748
|
"use strict";
|
|
3734
3749
|
init_models();
|
|
3750
|
+
byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
3735
3751
|
narrativeAntipatternsRule = {
|
|
3736
3752
|
name: "narrative-antipatterns",
|
|
3737
3753
|
description: "Provable narrative bugs, caught at spec level before implementation: a step-graph cycle with no exit edge and no return/throw member never terminates by construction (INESCAPABLE_CYCLE); a branch or switch whose arms all target the same step decides nothing (MEANINGLESS_BRANCH); a method-level call cycle across components in which every call edge is unavoidable on every entry-to-exit path is unbounded recursion (UNCONDITIONAL_CALL_CYCLE \u2014 dispatch steps resolved through their portal tables; a cycle with even one guarded edge is NOT flagged, since prose conditions are never judged). Structure-only claims \u2014 warnings, lint.allow-suppressible.",
|
|
@@ -3750,29 +3766,33 @@ var init_narrative_antipatterns = __esm({
|
|
|
3750
3766
|
const steps = implMethod.narrative;
|
|
3751
3767
|
if (!steps.length) continue;
|
|
3752
3768
|
const where = `Method "${implMethod.name}" in implementation "${impl.id}": `;
|
|
3753
|
-
const graph =
|
|
3769
|
+
const graph = completedStepGraph(steps);
|
|
3770
|
+
const decisionTargets = (stepNumber, explicit, fallThroughField) => {
|
|
3771
|
+
const targets = new Set(explicit);
|
|
3772
|
+
if (fallThroughField !== void 0) targets.add(fallThroughField);
|
|
3773
|
+
else for (const t of graph.successorsOf(stepNumber)) targets.add(t);
|
|
3774
|
+
return [...targets];
|
|
3775
|
+
};
|
|
3754
3776
|
for (const s of steps) {
|
|
3755
3777
|
if (s.type === "branch" && s.onFalseStep !== void 0) {
|
|
3756
|
-
const
|
|
3757
|
-
if (
|
|
3778
|
+
const targets = decisionTargets(s.stepNumber, [s.onFalseStep], s.onTrueStep);
|
|
3779
|
+
if (targets.length === 1) {
|
|
3758
3780
|
ctx.addIssue(
|
|
3759
3781
|
"warning",
|
|
3760
3782
|
"MEANINGLESS_BRANCH",
|
|
3761
|
-
`${where}branch step ${s.stepNumber} sends both arms to step ${
|
|
3783
|
+
`${where}branch step ${s.stepNumber} sends both arms to step ${targets[0]} \u2014 the condition decides nothing. Point the arms at different steps, or replace the branch with a local step.`,
|
|
3762
3784
|
impl.id,
|
|
3763
3785
|
isDraftCtx
|
|
3764
3786
|
);
|
|
3765
3787
|
}
|
|
3766
3788
|
}
|
|
3767
3789
|
if (s.type === "switch" && s.cases?.length) {
|
|
3768
|
-
const targets =
|
|
3769
|
-
|
|
3770
|
-
if (def !== void 0) targets.add(def);
|
|
3771
|
-
if (targets.size === 1) {
|
|
3790
|
+
const targets = decisionTargets(s.stepNumber, s.cases.map((c) => c.step), s.defaultStep);
|
|
3791
|
+
if (targets.length === 1) {
|
|
3772
3792
|
ctx.addIssue(
|
|
3773
3793
|
"warning",
|
|
3774
3794
|
"MEANINGLESS_BRANCH",
|
|
3775
|
-
`${where}switch step ${s.stepNumber} sends every case (and the default) to step ${
|
|
3795
|
+
`${where}switch step ${s.stepNumber} sends every case (and the default) to step ${targets[0]} \u2014 the dispatch decides nothing.`,
|
|
3776
3796
|
impl.id,
|
|
3777
3797
|
isDraftCtx
|
|
3778
3798
|
);
|
|
@@ -3845,8 +3865,8 @@ var init_narrative_antipatterns = __esm({
|
|
|
3845
3865
|
if (!isCycle) continue;
|
|
3846
3866
|
const memberEdges = keys.flatMap((k) => (adjacency.get(k) ?? []).filter((e) => inScc.has(e.toKey)));
|
|
3847
3867
|
if (memberEdges.length === 0) continue;
|
|
3848
|
-
const anchor = [...memberEdges].sort((a, b) => a.fromKey.
|
|
3849
|
-
const path27 = [...keys].sort().join(" \u2192 ");
|
|
3868
|
+
const anchor = [...memberEdges].sort((a, b) => byCodeUnit(a.fromKey, b.fromKey) || byCodeUnit(a.impl.id, b.impl.id) || a.stepNumber - b.stepNumber)[0];
|
|
3869
|
+
const path27 = [...keys].sort(byCodeUnit).join(" \u2192 ");
|
|
3850
3870
|
ctx.addIssue(
|
|
3851
3871
|
"warning",
|
|
3852
3872
|
"UNCONDITIONAL_CALL_CYCLE",
|
|
@@ -4098,14 +4118,15 @@ var init_portal_endpoints = __esm({
|
|
|
4098
4118
|
continue;
|
|
4099
4119
|
}
|
|
4100
4120
|
for (const intf of compInterfaces) {
|
|
4121
|
+
const isIntfDraft = intf.status === "draft" || intf.status === "design";
|
|
4101
4122
|
for (const m of intf.methods) {
|
|
4102
4123
|
if (m.endpoint) {
|
|
4103
4124
|
ctx.addIssue(
|
|
4104
4125
|
"error",
|
|
4105
4126
|
"ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT",
|
|
4106
4127
|
`Architectural violation: Component "${comp.id}" is a ${comp.componentType}, but method "${m.name}" on its interface "${intf.id}" declares an endpoint. Only Portal components may carry endpoints.`,
|
|
4107
|
-
|
|
4108
|
-
isDraftCtx
|
|
4128
|
+
intf.id,
|
|
4129
|
+
isDraftCtx || isIntfDraft
|
|
4109
4130
|
);
|
|
4110
4131
|
}
|
|
4111
4132
|
}
|
|
@@ -4236,7 +4257,7 @@ var init_stereotype_dependencies = __esm({
|
|
|
4236
4257
|
{ code: "ARCHITECTURE_VIOLATION_ADAPTER_DEP", defaultSeverity: "error", summary: "Adapter depending on Orchestrators or Stores" },
|
|
4237
4258
|
{ code: "ARCHITECTURE_VIOLATION_INDEX_DEP", defaultSeverity: "error", summary: "Index depending on anything but its Store or an Adapter" },
|
|
4238
4259
|
{ code: "ARCHITECTURE_VIOLATION_VIEW_DEP", defaultSeverity: "error", summary: "View depending on logic/persistence layers" },
|
|
4239
|
-
{ code: "PORTAL_WRITE_SHORTCUT", defaultSeverity: "error", summary: "Portal narrative
|
|
4260
|
+
{ code: "PORTAL_WRITE_SHORTCUT", defaultSeverity: "error", summary: "Portal narrative call or dispatch-table binding reaches a write-effect method on a Repository/Index directly \u2014 reads may shortcut, writes route through an Orchestrator (judged on effect-tagged facade methods; untagged methods are not yet judged)" }
|
|
4240
4261
|
],
|
|
4241
4262
|
check(ctx) {
|
|
4242
4263
|
for (const comp of ctx.components) {
|
|
@@ -4308,10 +4329,11 @@ var init_stereotype_dependencies = __esm({
|
|
|
4308
4329
|
crossDraft
|
|
4309
4330
|
);
|
|
4310
4331
|
} else if (depComp.componentType !== "Portal" && depComp.componentType !== "Gateway") {
|
|
4332
|
+
const crosser = comp.componentType === "Adapter" ? "client Adapter" : comp.componentType;
|
|
4311
4333
|
ctx.addIssue(
|
|
4312
4334
|
"error",
|
|
4313
4335
|
"CROSS_SUBSYSTEM_TARGET_NON_PORTAL",
|
|
4314
|
-
`Boundary violation:
|
|
4336
|
+
`Boundary violation: ${crosser} "${comp.id}" enters subsystem "${depComp.subsystem}" through "${depComp.id}" (${depComp.componentType}), not its inbound Portal. A cross-subsystem hop must target the remote subsystem's Portal (its front door), which dispatches inward \u2014 publishing/depending on an internal ${depComp.componentType} leaks the boundary and breaks the distribution seam. Expose a Portal for "${depComp.subsystem}" and point "${comp.id}" at it.`,
|
|
4315
4337
|
comp.id,
|
|
4316
4338
|
crossDraft
|
|
4317
4339
|
);
|
|
@@ -4435,6 +4457,23 @@ var init_stereotype_dependencies = __esm({
|
|
|
4435
4457
|
}
|
|
4436
4458
|
}
|
|
4437
4459
|
}
|
|
4460
|
+
for (const comp of ctx.components) {
|
|
4461
|
+
if (comp.componentType !== "Portal" || !comp.dispatch || comp.dispatch.length === 0) continue;
|
|
4462
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
4463
|
+
for (const binding of comp.dispatch) {
|
|
4464
|
+
const target = ctx.componentMap.get(binding.component);
|
|
4465
|
+
if (!target || target.componentType !== "Repository" && target.componentType !== "Index") continue;
|
|
4466
|
+
const targetMethod = ctx.interfaceMethodsOf(target.id).find((m) => m.name === binding.method);
|
|
4467
|
+
if (targetMethod?.effect !== "write") continue;
|
|
4468
|
+
ctx.addIssue(
|
|
4469
|
+
"error",
|
|
4470
|
+
"PORTAL_WRITE_SHORTCUT",
|
|
4471
|
+
`Portal "${comp.id}": dispatch binding "${binding.capability}" routes to write-effect method ${target.id}.${binding.method} directly. The Portal\u2192${target.componentType} shortcut is licensed for READS only \u2014 route the write through an Orchestrator that owns the workflow.`,
|
|
4472
|
+
comp.id,
|
|
4473
|
+
isDraftCtx || ctx.isComponentDraft(target.id)
|
|
4474
|
+
);
|
|
4475
|
+
}
|
|
4476
|
+
}
|
|
4438
4477
|
}
|
|
4439
4478
|
};
|
|
4440
4479
|
}
|
|
@@ -4458,7 +4497,7 @@ var init_pattern_ownership = __esm({
|
|
|
4458
4497
|
{ code: "REPOSITORY_CONTAINMENT", defaultSeverity: "error", summary: "Repository owning a non Store/Registry/Index/Adapter member" },
|
|
4459
4498
|
{ code: "GATEWAY_CONTAINMENT", defaultSeverity: "error", summary: "Gateway owning a non Portal/Orchestrator/Specialist member" },
|
|
4460
4499
|
{ code: "FEATURE_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "FeatureComponent not owning exactly one Orchestrator + one or more Views" },
|
|
4461
|
-
{ code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent
|
|
4500
|
+
{ code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent not owning exactly one Portal facade, or owning no children to route to" },
|
|
4462
4501
|
{ code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" },
|
|
4463
4502
|
{ code: "UNOWNED_STORE", defaultSeverity: "warning", summary: "Store not owned by any pattern \u2014 recommended shape is a Repository; a deliberate standalone Store needs a lint.allow" },
|
|
4464
4503
|
{ code: "REGISTRY_WITHOUT_STORE", defaultSeverity: "warning", summary: "Standalone Registry with no Store to write to \u2014 either mistyped (a fused file-backed store belongs typed Store) or orphaned" }
|
|
@@ -4480,14 +4519,15 @@ var init_pattern_ownership = __esm({
|
|
|
4480
4519
|
ctx.addIssue("error", "INVALID_OWNED_MEMBER", `Component "${comp.id}" owns "${memberId}" which does not exist.`, comp.id, isDraftCtx);
|
|
4481
4520
|
continue;
|
|
4482
4521
|
}
|
|
4522
|
+
if (!pattern) continue;
|
|
4483
4523
|
if (isPattern(member)) {
|
|
4484
4524
|
ctx.addIssue("error", "PATTERN_OWNS_PATTERN", `Pattern "${comp.id}" owns "${memberId}", which is itself a pattern. Patterns own only building blocks \u2014 compose patterns at the subsystem (L1) level.`, comp.id, isDraftCtx);
|
|
4485
4525
|
}
|
|
4486
|
-
const
|
|
4487
|
-
if (
|
|
4488
|
-
ctx.addIssue("error", "SHARED_OWNED_MEMBER", `Block "${memberId}" is owned by both "${
|
|
4526
|
+
const firstOwner = ownedBy.get(memberId);
|
|
4527
|
+
if (firstOwner && firstOwner !== comp.id) {
|
|
4528
|
+
ctx.addIssue("error", "SHARED_OWNED_MEMBER", `Block "${memberId}" is owned by both "${firstOwner}" and "${comp.id}"; a block has exactly one owner.`, comp.id, isDraftCtx);
|
|
4489
4529
|
}
|
|
4490
|
-
ownedBy.set(memberId, comp.id);
|
|
4530
|
+
if (!firstOwner) ownedBy.set(memberId, comp.id);
|
|
4491
4531
|
}
|
|
4492
4532
|
if (comp.componentType === "Repository") {
|
|
4493
4533
|
const allowed = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Adapter"]);
|
|
@@ -4522,14 +4562,14 @@ var init_pattern_ownership = __esm({
|
|
|
4522
4562
|
}
|
|
4523
4563
|
}
|
|
4524
4564
|
if (comp.componentType === "RouterComponent") {
|
|
4525
|
-
let
|
|
4565
|
+
let portals = 0;
|
|
4526
4566
|
let hasChildren = false;
|
|
4527
4567
|
for (const memberId of comp.owns) {
|
|
4528
4568
|
const t = ctx.componentMap.get(memberId)?.componentType;
|
|
4529
|
-
if (t === "Portal")
|
|
4569
|
+
if (t === "Portal") portals++;
|
|
4530
4570
|
else if (t) hasChildren = true;
|
|
4531
4571
|
}
|
|
4532
|
-
if (
|
|
4572
|
+
if (portals !== 1) {
|
|
4533
4573
|
ctx.addIssue("error", "ROUTER_COMPONENT_CONTAINMENT", `RouterComponent "${comp.id}" must own exactly one Portal component to act as its facade.`, comp.id, isDraftCtx);
|
|
4534
4574
|
}
|
|
4535
4575
|
if (!hasChildren) {
|
|
@@ -5031,54 +5071,43 @@ var init_dependency_cycles = __esm({
|
|
|
5031
5071
|
"use strict";
|
|
5032
5072
|
cyclesRule = {
|
|
5033
5073
|
name: "dependency-cycles",
|
|
5034
|
-
description: "The component dependsOn graph must be a DAG; the first
|
|
5074
|
+
description: "The component dependsOn graph must be a DAG; the first cycle through a component in the run's scope is reported with its full path.",
|
|
5035
5075
|
codes: [
|
|
5036
5076
|
{ code: "CIRCULAR_DEPENDENCY", defaultSeverity: "error", summary: "Circular dependency between components" }
|
|
5037
5077
|
],
|
|
5038
5078
|
check(ctx) {
|
|
5039
|
-
const
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
const comp = ctx.componentMap.get(compId);
|
|
5046
|
-
if (comp) {
|
|
5047
|
-
for (const depId of comp.dependsOn) {
|
|
5048
|
-
if (!visited.has(depId)) {
|
|
5049
|
-
if (dfs(depId, pathTrace)) {
|
|
5050
|
-
recStack.delete(compId);
|
|
5051
|
-
pathTrace.pop();
|
|
5052
|
-
return true;
|
|
5053
|
-
}
|
|
5054
|
-
} else if (recStack.has(depId)) {
|
|
5079
|
+
const cycleFrom = (startId) => {
|
|
5080
|
+
const visited = /* @__PURE__ */ new Set([startId]);
|
|
5081
|
+
const pathTrace = [startId];
|
|
5082
|
+
const search = (compId) => {
|
|
5083
|
+
for (const depId of ctx.componentMap.get(compId)?.dependsOn ?? []) {
|
|
5084
|
+
if (depId === startId) {
|
|
5055
5085
|
pathTrace.push(depId);
|
|
5056
|
-
const cyclePath = pathTrace.slice(pathTrace.indexOf(depId)).join(" -> ");
|
|
5057
|
-
const isDraftCtx = ctx.isComponentDraft(compId) || ctx.isComponentDraft(depId);
|
|
5058
|
-
ctx.addIssue(
|
|
5059
|
-
"error",
|
|
5060
|
-
"CIRCULAR_DEPENDENCY",
|
|
5061
|
-
`Circular dependency detected: ${cyclePath}`,
|
|
5062
|
-
compId,
|
|
5063
|
-
isDraftCtx
|
|
5064
|
-
);
|
|
5065
|
-
pathTrace.pop();
|
|
5066
|
-
recStack.delete(compId);
|
|
5067
|
-
pathTrace.pop();
|
|
5068
5086
|
return true;
|
|
5069
5087
|
}
|
|
5088
|
+
if (visited.has(depId)) continue;
|
|
5089
|
+
visited.add(depId);
|
|
5090
|
+
pathTrace.push(depId);
|
|
5091
|
+
if (search(depId)) return true;
|
|
5092
|
+
pathTrace.pop();
|
|
5070
5093
|
}
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
return false;
|
|
5094
|
+
return false;
|
|
5095
|
+
};
|
|
5096
|
+
return search(startId) ? pathTrace : void 0;
|
|
5075
5097
|
};
|
|
5076
5098
|
for (const comp of ctx.components) {
|
|
5077
|
-
if (!
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5099
|
+
if (!ctx.isSpecInScope(comp.id)) continue;
|
|
5100
|
+
const cycle = cycleFrom(comp.id);
|
|
5101
|
+
if (!cycle) continue;
|
|
5102
|
+
const closing = cycle[cycle.length - 2];
|
|
5103
|
+
ctx.addIssue(
|
|
5104
|
+
"error",
|
|
5105
|
+
"CIRCULAR_DEPENDENCY",
|
|
5106
|
+
`Circular dependency detected: ${cycle.join(" -> ")}`,
|
|
5107
|
+
comp.id,
|
|
5108
|
+
ctx.isComponentDraft(comp.id) || ctx.isComponentDraft(closing)
|
|
5109
|
+
);
|
|
5110
|
+
return;
|
|
5082
5111
|
}
|
|
5083
5112
|
}
|
|
5084
5113
|
};
|
|
@@ -5207,19 +5236,18 @@ var init_dispatch_tables = __esm({
|
|
|
5207
5236
|
);
|
|
5208
5237
|
continue;
|
|
5209
5238
|
}
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
}
|
|
5239
|
+
const boundMethod = ctx.interfaceMethodsOf(binding.component).find((m) => m.name === binding.method);
|
|
5240
|
+
if (!boundMethod) continue;
|
|
5241
|
+
const declared = new Set(boundMethod.guarantees ?? []);
|
|
5242
|
+
for (const g of step.assertsGuarantees ?? []) {
|
|
5243
|
+
if (!declared.has(g)) {
|
|
5244
|
+
ctx.addIssue(
|
|
5245
|
+
"warning",
|
|
5246
|
+
"NARRATIVE_SEMANTIC_UNBACKED",
|
|
5247
|
+
`${where} asserts guarantee "${g}", but capability "${step.capability}" resolves to "${binding.component}.${binding.method}", which does not list "${g}" among its L3 contract guarantees. Declare it there (and ensure its shape can deliver it), or revise the narrative.`,
|
|
5248
|
+
impl.id,
|
|
5249
|
+
isDraftCtx || ctx.isComponentDraft(binding.component)
|
|
5250
|
+
);
|
|
5223
5251
|
}
|
|
5224
5252
|
}
|
|
5225
5253
|
}
|
|
@@ -5424,12 +5452,11 @@ var init_unused_detection = __esm({
|
|
|
5424
5452
|
const invokedBySeeds = [];
|
|
5425
5453
|
for (const intf of ctx.interfaces) {
|
|
5426
5454
|
if (!ctx.componentMap.has(intf.component)) continue;
|
|
5427
|
-
const comp = ctx.componentMap.get(intf.component);
|
|
5428
5455
|
for (const m of intf.methods) {
|
|
5429
5456
|
if (!m.invokedBy) continue;
|
|
5430
5457
|
invokedBySeeds.push({ compId: intf.component, methodName: m.name });
|
|
5431
5458
|
if (!ctx.isSpecInScope(intf.id)) continue;
|
|
5432
|
-
const isDraftCtx =
|
|
5459
|
+
const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
|
|
5433
5460
|
if (!passesIntentFloor(m.invokedBy.caller, m.name)) {
|
|
5434
5461
|
ctx.addIssue(
|
|
5435
5462
|
"warning",
|
|
@@ -5454,7 +5481,7 @@ var init_unused_detection = __esm({
|
|
|
5454
5481
|
for (const comp of ctx.components) {
|
|
5455
5482
|
if (!ctx.isSpecInScope(comp.id)) continue;
|
|
5456
5483
|
if (!reach.reachesComponent(comp.id)) {
|
|
5457
|
-
const isDraftCtx =
|
|
5484
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
5458
5485
|
ctx.addIssue(
|
|
5459
5486
|
"warning",
|
|
5460
5487
|
"UNUSED_COMPONENT",
|
|
@@ -5466,12 +5493,12 @@ var init_unused_detection = __esm({
|
|
|
5466
5493
|
for (const intf of ctx.interfacesByComponent.get(comp.id) ?? []) {
|
|
5467
5494
|
for (const m of intf.methods) {
|
|
5468
5495
|
if (!reach.reachesMethod(comp.id, m.name)) {
|
|
5469
|
-
const isDraftCtx =
|
|
5496
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
|
|
5470
5497
|
ctx.addIssue(
|
|
5471
5498
|
"warning",
|
|
5472
5499
|
"UNUSED_METHOD",
|
|
5473
5500
|
`Method "${m.name}" on component "${comp.id}" is defined but never called by any narrative step.`,
|
|
5474
|
-
|
|
5501
|
+
intf.id,
|
|
5475
5502
|
isDraftCtx
|
|
5476
5503
|
);
|
|
5477
5504
|
}
|
|
@@ -5582,20 +5609,20 @@ var init_durability_round_trip = __esm({
|
|
|
5582
5609
|
});
|
|
5583
5610
|
|
|
5584
5611
|
// src/core/rules/wiring/untyped-seams.ts
|
|
5585
|
-
function
|
|
5586
|
-
|
|
5587
|
-
return (m ? m[1] : typeRef).trim();
|
|
5612
|
+
function namesBareType(refs) {
|
|
5613
|
+
return refs.some((ref) => BARE_SEAM_TYPES.has(ref.toLowerCase()));
|
|
5588
5614
|
}
|
|
5589
5615
|
var BARE_SEAM_TYPES, untypedSeamRule;
|
|
5590
5616
|
var init_untyped_seams = __esm({
|
|
5591
5617
|
"src/core/rules/wiring/untyped-seams.ts"() {
|
|
5592
5618
|
"use strict";
|
|
5619
|
+
init_models();
|
|
5593
5620
|
BARE_SEAM_TYPES = /* @__PURE__ */ new Set(["json", "any", "unknown", "object"]);
|
|
5594
5621
|
untypedSeamRule = {
|
|
5595
5622
|
name: "untyped-seams",
|
|
5596
|
-
description: "Methods on a subsystem's published components (its public surface) should not take or return bare Json/any/unknown
|
|
5623
|
+
description: "Methods on a subsystem's published components (its public surface) should not take or return bare Json/any/unknown/object, judged through each method's type references so a prose signature is judged like structured params: cross-subsystem contracts are the swap seam and must be typed. Generic-dispatch portals carry per-capability types via their dispatch table instead.",
|
|
5597
5624
|
codes: [
|
|
5598
|
-
{ code: "UNTYPED_SEAM", defaultSeverity: "warning", summary: "Bare Json/any/unknown parameter or return crossing a subsystem public surface" }
|
|
5625
|
+
{ code: "UNTYPED_SEAM", defaultSeverity: "warning", summary: "Bare Json/any/unknown/object parameter or return crossing a subsystem public surface" }
|
|
5599
5626
|
],
|
|
5600
5627
|
check(ctx) {
|
|
5601
5628
|
for (const sub of ctx.subsystems) {
|
|
@@ -5609,14 +5636,17 @@ var init_untyped_seams = __esm({
|
|
|
5609
5636
|
const isDraftCtx = ctx.isComponentDraft(compId) || intf.status === "draft" || intf.status === "design";
|
|
5610
5637
|
for (const m of intf.methods) {
|
|
5611
5638
|
const offenders = [];
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5639
|
+
if (m.params && m.params.length > 0) {
|
|
5640
|
+
for (const p of m.params) {
|
|
5641
|
+
if (namesBareType(extractTypeIdentifiers(p.type))) {
|
|
5642
|
+
offenders.push(`param "${p.name}: ${p.type}"`);
|
|
5643
|
+
}
|
|
5615
5644
|
}
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5645
|
+
if (namesBareType(extractTypeIdentifiers(m.returns ?? ""))) {
|
|
5646
|
+
offenders.push(`return "${m.returns}"`);
|
|
5647
|
+
}
|
|
5648
|
+
} else if (namesBareType(methodTypeRefs(m))) {
|
|
5649
|
+
offenders.push(`signature "${m.signature}"`);
|
|
5620
5650
|
}
|
|
5621
5651
|
if (offenders.length) {
|
|
5622
5652
|
ctx.addIssue(
|
|
@@ -5641,15 +5671,19 @@ function componentOfImpl(ctx, implContract) {
|
|
|
5641
5671
|
const contract = ctx.interfaceMap.get(implContract);
|
|
5642
5672
|
return contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
5643
5673
|
}
|
|
5644
|
-
|
|
5674
|
+
function withoutQuotedText(text) {
|
|
5675
|
+
return text.replace(QUOTED_SPAN, " ");
|
|
5676
|
+
}
|
|
5677
|
+
var DATA_STEREOTYPES, CLAIM_PHRASES, QUOTED_SPAN, proseClaimRule;
|
|
5645
5678
|
var init_prose_claims = __esm({
|
|
5646
5679
|
"src/core/rules/wiring/prose-claims.ts"() {
|
|
5647
5680
|
"use strict";
|
|
5648
5681
|
DATA_STEREOTYPES = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Adapter", "Repository"]);
|
|
5649
|
-
CLAIM_PHRASES = /\b(persist(s|ed|ent)?|survives?\s+(a\s+)?restart|writ(es?|ten)\s+to\s+disk|registered\s+into|durabl[ey])\b/i;
|
|
5682
|
+
CLAIM_PHRASES = /\b(persist(s|ed|ent|ing|ence)?|survives?\s+(a\s+)?restart|writ(es?|ten)\s+to\s+disk|registered\s+into|durabl[ey])\b/i;
|
|
5683
|
+
QUOTED_SPAN = /"[^"]*"|“[^”]*”|`[^`]*`|(?<![\p{L}\p{N}])['‘](?:[^'‘’]|(?<=[\p{L}\p{N}])['’](?=[\p{L}\p{N}]))*['’](?![\p{L}\p{N}])/gu;
|
|
5650
5684
|
proseClaimRule = {
|
|
5651
5685
|
name: "prose-claims",
|
|
5652
|
-
description: 'Flags durability/side-effect claims that exist only in prose: a local step description or an intent paragraph claiming persistence ("persisted", "survives restart", "registered into") on a logic component whose narrative has no call
|
|
5686
|
+
description: 'Flags durability/side-effect claims that exist only in prose: a local step description or an intent paragraph claiming persistence ("persisted", "persistence", "survives restart", "registered into") on a logic component whose narrative has no call, register or dispatch edge to any data-layer component (Store/Registry/Index/Adapter/Repository). Text inside quotes or backticks names a value or quotes a message and claims nothing. Data-layer components are exempt \u2014 they ARE the persistence.',
|
|
5653
5687
|
codes: [
|
|
5654
5688
|
{ code: "UNREALIZED_CLAIM", defaultSeverity: "warning", summary: "Durability/side-effect claim in prose with no matching structural edge" }
|
|
5655
5689
|
],
|
|
@@ -5660,11 +5694,11 @@ var init_prose_claims = __esm({
|
|
|
5660
5694
|
if (DATA_STEREOTYPES.has(comp.componentType)) continue;
|
|
5661
5695
|
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
5662
5696
|
for (const implMethod of impl.methods) {
|
|
5663
|
-
const stepClaims = implMethod.narrative.filter((step) => step.type === "local").map((step) => ({ step, claim: CLAIM_PHRASES.exec(step.description) })).filter((c) => c.claim !== null);
|
|
5664
|
-
const intentClaim = implMethod.intent ? CLAIM_PHRASES.exec(implMethod.intent) : null;
|
|
5697
|
+
const stepClaims = implMethod.narrative.filter((step) => step.type === "local").map((step) => ({ step, claim: CLAIM_PHRASES.exec(withoutQuotedText(step.description)) })).filter((c) => c.claim !== null);
|
|
5698
|
+
const intentClaim = implMethod.intent ? CLAIM_PHRASES.exec(withoutQuotedText(implMethod.intent)) : null;
|
|
5665
5699
|
if (!stepClaims.length && !intentClaim) continue;
|
|
5666
5700
|
const hasDataEdge = implMethod.narrative.some((step) => {
|
|
5667
|
-
if (step.type !== "call" && step.type !== "dispatch") return false;
|
|
5701
|
+
if (step.type !== "call" && step.type !== "register" && step.type !== "dispatch") return false;
|
|
5668
5702
|
if (!step.targetComponent) return false;
|
|
5669
5703
|
const target = ctx.componentMap.get(step.targetComponent);
|
|
5670
5704
|
if (target && DATA_STEREOTYPES.has(target.componentType)) return true;
|
|
@@ -5677,7 +5711,7 @@ var init_prose_claims = __esm({
|
|
|
5677
5711
|
ctx.addIssue(
|
|
5678
5712
|
"warning",
|
|
5679
5713
|
"UNREALIZED_CLAIM",
|
|
5680
|
-
`Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" claims "${claim[0]}" but no call/dispatch edge in this narrative reaches a Store/Registry/Index/Adapter \u2014 realize the claim as a structural edge (and durability tags), or reword the prose.`,
|
|
5714
|
+
`Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" claims "${claim[0]}" but no call/register/dispatch edge in this narrative reaches a Store/Registry/Index/Adapter \u2014 realize the claim as a structural edge (and durability tags), or reword the prose.`,
|
|
5681
5715
|
impl.id,
|
|
5682
5716
|
isDraftCtx
|
|
5683
5717
|
);
|
|
@@ -5736,6 +5770,10 @@ function stepAsserts(impl, methodName, type, invariantId) {
|
|
|
5736
5770
|
(step) => (step.assertsInvariants ?? []).some((ref) => refMatchesInvariant(ref, type, invariantId))
|
|
5737
5771
|
);
|
|
5738
5772
|
}
|
|
5773
|
+
function isEntityDraft(t, ctx) {
|
|
5774
|
+
const sub = t.subsystem ? ctx.subsystems.find((s) => s.id === t.subsystem) : void 0;
|
|
5775
|
+
return sub !== void 0 && isDraftSubsystem(sub);
|
|
5776
|
+
}
|
|
5739
5777
|
function resolveComponentClass(t, ctx) {
|
|
5740
5778
|
if (!t.componentClass) return void 0;
|
|
5741
5779
|
const direct = ctx.componentMap.get(t.componentClass);
|
|
@@ -5762,6 +5800,7 @@ var init_invariant_backing = __esm({
|
|
|
5762
5800
|
for (const t of ctx.types) {
|
|
5763
5801
|
const invariants = t.invariants ?? [];
|
|
5764
5802
|
if (invariants.length === 0) continue;
|
|
5803
|
+
const entityDraft = isEntityDraft(t, ctx);
|
|
5765
5804
|
const seen = /* @__PURE__ */ new Set();
|
|
5766
5805
|
for (const inv of invariants) {
|
|
5767
5806
|
if (seen.has(inv.id)) {
|
|
@@ -5769,7 +5808,8 @@ var init_invariant_backing = __esm({
|
|
|
5769
5808
|
"error",
|
|
5770
5809
|
"DUPLICATE_INVARIANT_ID",
|
|
5771
5810
|
`Entity "${t.id}" declares invariant id "${inv.id}" more than once \u2014 invariant ids must be unique within the entity.`,
|
|
5772
|
-
t.id
|
|
5811
|
+
t.id,
|
|
5812
|
+
entityDraft
|
|
5773
5813
|
);
|
|
5774
5814
|
}
|
|
5775
5815
|
seen.add(inv.id);
|
|
@@ -5780,7 +5820,8 @@ var init_invariant_backing = __esm({
|
|
|
5780
5820
|
"warning",
|
|
5781
5821
|
"INVARIANT_UNANCHORED",
|
|
5782
5822
|
`Entity "${t.id}" declares ${invariants.length} invariant(s) but ${t.componentClass ? `its componentClass "${t.componentClass}" does not resolve to a component` : "has no componentClass"} \u2014 without an owning component there is no write path to hold the invariant against. Link the lifecycle owner via componentClass.`,
|
|
5783
|
-
t.id
|
|
5823
|
+
t.id,
|
|
5824
|
+
entityDraft
|
|
5784
5825
|
);
|
|
5785
5826
|
continue;
|
|
5786
5827
|
}
|
|
@@ -5794,13 +5835,13 @@ var init_invariant_backing = __esm({
|
|
|
5794
5835
|
"INVARIANT_UNANCHORED",
|
|
5795
5836
|
`Entity "${t.id}" declares ${invariants.length} invariant(s) anchored to "${comp.id}", but none of that component's contract methods declare effect: write \u2014 the validator cannot identify the write paths that must assert them. Tag the mutating methods with effect: write.`,
|
|
5796
5837
|
t.id,
|
|
5797
|
-
ctx.isComponentDraft(comp.id)
|
|
5838
|
+
entityDraft || ctx.isComponentDraft(comp.id)
|
|
5798
5839
|
);
|
|
5799
5840
|
continue;
|
|
5800
5841
|
}
|
|
5801
5842
|
for (const { intf, method } of writeMethods) {
|
|
5802
5843
|
for (const impl of ctx.implementationsByContract.get(intf.id) ?? []) {
|
|
5803
|
-
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
5844
|
+
const isDraftCtx = entityDraft || ctx.isImplementationDraft(impl);
|
|
5804
5845
|
for (const inv of invariants) {
|
|
5805
5846
|
if (stepAsserts(impl, method.name, t, inv.id)) continue;
|
|
5806
5847
|
ctx.addIssue(
|
|
@@ -5838,30 +5879,35 @@ var init_invariant_backing = __esm({
|
|
|
5838
5879
|
|
|
5839
5880
|
// src/core/rules/wiring/event-topology.ts
|
|
5840
5881
|
function collectEnds(ctx) {
|
|
5841
|
-
const emitters =
|
|
5842
|
-
const subscribers =
|
|
5882
|
+
const emitters = /* @__PURE__ */ new Map();
|
|
5883
|
+
const subscribers = /* @__PURE__ */ new Map();
|
|
5884
|
+
const addEnd = (ends, topic, compId, via) => {
|
|
5885
|
+
const key = `${compId}#${topic}`;
|
|
5886
|
+
const end = ends.get(key);
|
|
5887
|
+
if (!end) ends.set(key, { topic, compId, vias: [via] });
|
|
5888
|
+
else if (!end.vias.includes(via)) end.vias.push(via);
|
|
5889
|
+
};
|
|
5843
5890
|
for (const comp of ctx.components) {
|
|
5844
5891
|
for (const e of comp.emits ?? []) {
|
|
5845
|
-
emitters
|
|
5892
|
+
addEnd(emitters, e.topic, comp.id, "emits declaration");
|
|
5846
5893
|
}
|
|
5847
5894
|
for (const s of comp.subscribesTo ?? []) {
|
|
5848
|
-
subscribers
|
|
5895
|
+
addEnd(subscribers, s.topic, comp.id, "subscribesTo declaration");
|
|
5849
5896
|
}
|
|
5850
5897
|
}
|
|
5851
5898
|
for (const intf of ctx.interfaces) {
|
|
5852
5899
|
for (const method of intf.methods) {
|
|
5853
5900
|
const ep = method.endpoint;
|
|
5854
5901
|
if (!ep || ep.transport !== "MessageBus") continue;
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
else subscribers.push(end);
|
|
5902
|
+
addEnd(
|
|
5903
|
+
ep.direction === "publish" ? emitters : subscribers,
|
|
5904
|
+
ep.topic,
|
|
5905
|
+
intf.component,
|
|
5906
|
+
`MessageBus endpoint on ${intf.id}.${method.name}`
|
|
5907
|
+
);
|
|
5862
5908
|
}
|
|
5863
5909
|
}
|
|
5864
|
-
return { emitters, subscribers };
|
|
5910
|
+
return { emitters: [...emitters.values()], subscribers: [...subscribers.values()] };
|
|
5865
5911
|
}
|
|
5866
5912
|
var eventTopologyRule;
|
|
5867
5913
|
var init_event_topology = __esm({
|
|
@@ -5869,7 +5915,7 @@ var init_event_topology = __esm({
|
|
|
5869
5915
|
"use strict";
|
|
5870
5916
|
eventTopologyRule = {
|
|
5871
5917
|
name: "event-topology",
|
|
5872
|
-
description: "Bipartite completeness of the declared pub/sub graph: every topic a component emits (emits declarations, MessageBus publish endpoints) must have at least one subscriber (subscribesTo declarations, MessageBus subscribe endpoints), and every subscription must have at least one source. Pairing is by exact topic name. Trees that declare no event edges see nothing \u2014 the rule costs request/response systems zero noise. Warnings + lint.allow: a topic produced for (or consumed from) an EXTERNAL system is a legitimate, declarable exception.",
|
|
5918
|
+
description: "Bipartite completeness of the declared pub/sub graph: every topic a component emits (emits declarations, MessageBus publish endpoints) must have at least one subscriber (subscribesTo declarations, MessageBus subscribe endpoints), and every subscription must have at least one source. Pairing is by exact topic name, and a topic is reported once per component however many declarations bind it there. Trees that declare no event edges see nothing \u2014 the rule costs request/response systems zero noise. Warnings + lint.allow: a topic produced for (or consumed from) an EXTERNAL system is a legitimate, declarable exception.",
|
|
5873
5919
|
codes: [
|
|
5874
5920
|
{ code: "UNCONSUMED_TOPIC", defaultSeverity: "warning", summary: "Topic is emitted but nothing in the tree subscribes to it" },
|
|
5875
5921
|
{ code: "UNSOURCED_SUBSCRIPTION", defaultSeverity: "warning", summary: "Topic is subscribed to but nothing in the tree emits it" }
|
|
@@ -5884,7 +5930,7 @@ var init_event_topology = __esm({
|
|
|
5884
5930
|
ctx.addIssue(
|
|
5885
5931
|
"warning",
|
|
5886
5932
|
"UNCONSUMED_TOPIC",
|
|
5887
|
-
`Component "${e.compId}" emits topic "${e.topic}" (${e.
|
|
5933
|
+
`Component "${e.compId}" emits topic "${e.topic}" (${e.vias.join(", ")}), but nothing in this tree subscribes to it \u2014 the event goes nowhere. Wire a subscriber (subscribesTo, or a MessageBus subscribe endpoint), fix the topic name, or lint.allow with the external consumer named.`,
|
|
5888
5934
|
e.compId,
|
|
5889
5935
|
ctx.isComponentDraft(e.compId)
|
|
5890
5936
|
);
|
|
@@ -5894,7 +5940,7 @@ var init_event_topology = __esm({
|
|
|
5894
5940
|
ctx.addIssue(
|
|
5895
5941
|
"warning",
|
|
5896
5942
|
"UNSOURCED_SUBSCRIPTION",
|
|
5897
|
-
`Component "${s.compId}" subscribes to topic "${s.topic}" (${s.
|
|
5943
|
+
`Component "${s.compId}" subscribes to topic "${s.topic}" (${s.vias.join(", ")}), but nothing in this tree emits it \u2014 the handler can never fire from inside this system. Wire the emitter (emits, or a MessageBus publish endpoint), fix the topic name, or lint.allow with the external source named.`,
|
|
5898
5944
|
s.compId,
|
|
5899
5945
|
ctx.isComponentDraft(s.compId)
|
|
5900
5946
|
);
|
|
@@ -5913,13 +5959,13 @@ var init_structural_conformance = __esm({
|
|
|
5913
5959
|
quoteList = (names) => names.map((n) => `"${n}"`).join(", ");
|
|
5914
5960
|
structuralConformanceRule = {
|
|
5915
5961
|
name: "structural-conformance",
|
|
5916
|
-
description: "Code\u2194spec Level 1: every source file an implementation names \u2014 its own sourcePath and each method's \u2014 must resolve to a real file inside the project root, and every L3 contract method must be realized in its own source file (the method's sourcePath, else the implementation's) at its conformance tier (declared | anchored | off; Portals default to anchored, everything else to declared; per-method `symbol` maps intent-language names to code names), and every finding code a contract method declares must
|
|
5962
|
+
description: "Code\u2194spec Level 1: every source file an implementation names \u2014 its own sourcePath and each method's \u2014 must resolve to a real file inside the project root, and every L3 contract method must be realized in its own source file (the method's sourcePath, else the implementation's) at its conformance tier (declared | anchored | off; Portals default to anchored, everything else to declared; per-method `symbol` maps intent-language names to code names), and every finding code a contract method declares must be anchored in that file \u2014 as a string literal or, at exact grade, a property-access name (UNREALIZED_FINDING). A file that escapes the root, is missing or cannot be read is reported once and blocks only the methods realized in it. Findings carry the analysis grade (exact AST | pattern table | generic scan) so weaker analysis is visible. Implementations under chained subsystems (projectPath) validate standalone in their own project run and are skipped here.",
|
|
5917
5963
|
codes: [
|
|
5918
5964
|
{ code: "MISSING_SOURCE_PATH", defaultSeverity: "warning", summary: "An implementation names no source file at all, or a contract method is left without one \u2014 structural conformance cannot link it to code" },
|
|
5919
5965
|
{ code: "MISSING_SOURCE_FILE", defaultSeverity: "error", summary: "A source file an implementation or one of its methods names does not resolve to a file on disk" },
|
|
5920
5966
|
{ code: "SOURCE_PATH_ESCAPES_ROOT", defaultSeverity: "error", summary: "A source file an implementation or one of its methods names is absolute or escapes the project root (containment refusal)" },
|
|
5921
5967
|
{ code: "UNREALIZED_METHOD", defaultSeverity: "warning", summary: "An L3 contract method has no anchor in its own source file (the method's sourcePath, else the implementation's) at the required conformance tier" },
|
|
5922
|
-
{ code: "UNREALIZED_FINDING", defaultSeverity: "warning", summary: "A finding code a contract method declares
|
|
5968
|
+
{ code: "UNREALIZED_FINDING", defaultSeverity: "warning", summary: "A finding code a contract method declares appears in the method's source file neither as a string literal nor as a property-access name (below exact grade, any identifier counts)" },
|
|
5923
5969
|
{ code: "CONFORMANCE_ANALYSIS_SKIPPED", defaultSeverity: "warning", summary: "A source file an implementation or one of its methods names could not be analyzed (binary/unreadable) \u2014 realization of the methods in it was not checked" },
|
|
5924
5970
|
{ code: "CONFORMANCE_DEGRADED", defaultSeverity: "warning", summary: "TypeScript/JavaScript files were analyzed below exact grade (compiler not resolvable) \u2014 dependency conformance skips them" }
|
|
5925
5971
|
],
|
|
@@ -5929,7 +5975,7 @@ var init_structural_conformance = __esm({
|
|
|
5929
5975
|
lookups.set(pathKey(facts.path), {
|
|
5930
5976
|
declared: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames]),
|
|
5931
5977
|
anchored: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames, ...facts.anchoredNames]),
|
|
5932
|
-
|
|
5978
|
+
findingAnchors: new Set(facts.anchoredNames),
|
|
5933
5979
|
facts
|
|
5934
5980
|
});
|
|
5935
5981
|
}
|
|
@@ -6049,11 +6095,11 @@ var init_structural_conformance = __esm({
|
|
|
6049
6095
|
if (!checked) continue;
|
|
6050
6096
|
const { file, lookup } = checked;
|
|
6051
6097
|
for (const finding of method.findings) {
|
|
6052
|
-
if (lookup.
|
|
6098
|
+
if (lookup.findingAnchors.has(finding.code)) continue;
|
|
6053
6099
|
ctx.addIssue(
|
|
6054
6100
|
"warning",
|
|
6055
6101
|
"UNREALIZED_FINDING",
|
|
6056
|
-
`Method "${method.name}" of contract "${impl.contract}" declares finding "${finding.code}", but "${file}" has no string literal "${finding.code}" (analysis grade: ${lookup.facts.analysisGrade}) \u2014 report the finding under its declared code, or remove the declaration.`,
|
|
6102
|
+
`Method "${method.name}" of contract "${impl.contract}" declares finding "${finding.code}", but "${file}" has no string literal or property-access name "${finding.code}" (analysis grade: ${lookup.facts.analysisGrade}) \u2014 report the finding under its declared code, or remove the declaration.`,
|
|
6057
6103
|
impl.id,
|
|
6058
6104
|
draft
|
|
6059
6105
|
);
|
|
@@ -6169,12 +6215,18 @@ var init_hidden_state = __esm({
|
|
|
6169
6215
|
check(ctx) {
|
|
6170
6216
|
const byPath = /* @__PURE__ */ new Map();
|
|
6171
6217
|
for (const impl of ctx.implementations) {
|
|
6172
|
-
if (impl.conformance === "off") continue;
|
|
6173
6218
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
6174
6219
|
const component = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
6175
6220
|
if (!component) continue;
|
|
6176
6221
|
if (ctx.isInChainedSubproject(component.subsystem)) continue;
|
|
6177
|
-
|
|
6222
|
+
const trusted = [];
|
|
6223
|
+
if (impl.sourcePath && impl.conformance !== "off") trusted.push(impl.sourcePath);
|
|
6224
|
+
for (const method of impl.methods) {
|
|
6225
|
+
if ((method.conformance ?? impl.conformance) === "off") continue;
|
|
6226
|
+
const file = methodSourceFile(method, impl.sourcePath);
|
|
6227
|
+
if (file) trusted.push(file);
|
|
6228
|
+
}
|
|
6229
|
+
for (const file of trusted) {
|
|
6178
6230
|
const key = pathKey(file);
|
|
6179
6231
|
const list = byPath.get(key) ?? [];
|
|
6180
6232
|
if (list.some((m) => m.impl === impl)) continue;
|
|
@@ -6325,7 +6377,7 @@ var init_dependency_conformance = __esm({
|
|
|
6325
6377
|
const fromFiles = filesByComponent.get(component.id);
|
|
6326
6378
|
if (!fromFiles) continue;
|
|
6327
6379
|
const isMountingDeclarer = component.componentType === "Portal" || component.componentType === "Observer";
|
|
6328
|
-
const declaredTargets = [...component.dependsOn, ...component.owns ?? []];
|
|
6380
|
+
const declaredTargets = [.../* @__PURE__ */ new Set([...component.dependsOn, ...component.owns ?? []])];
|
|
6329
6381
|
for (const targetId of declaredTargets) {
|
|
6330
6382
|
const target = ctx.componentMap.get(targetId);
|
|
6331
6383
|
if (!target) continue;
|
|
@@ -6346,11 +6398,14 @@ var init_dependency_conformance = __esm({
|
|
|
6346
6398
|
}
|
|
6347
6399
|
const impls = implsByComponent.get(component.id) ?? [];
|
|
6348
6400
|
const anchor = impls[0];
|
|
6401
|
+
const declaredDependsOn = component.dependsOn.includes(targetId);
|
|
6402
|
+
const declaredOwns = (component.owns ?? []).includes(targetId);
|
|
6403
|
+
const relation = declaredDependsOn && declaredOwns ? "dependsOn and owns" : declaredDependsOn ? "dependsOn" : "owns";
|
|
6349
6404
|
const draft = ctx.isComponentDraft(component.id) || ctx.isComponentDraft(targetId) || impls.some((i) => ctx.isImplementationDraft(i));
|
|
6350
6405
|
ctx.addIssue(
|
|
6351
6406
|
"warning",
|
|
6352
6407
|
"UNREALIZED_DEPENDENCY",
|
|
6353
|
-
`Component "${component.id}" declares ${
|
|
6408
|
+
`Component "${component.id}" declares ${relation} "${targetId}", but no runtime import connects their source files (${[...fromFiles].join(", ")} \u219B ${target.subsystem !== component.subsystem ? `subsystem ${target.subsystem}` : [...filesByComponent.get(targetId) ?? []].join(", ")}) \u2014 either the collaboration is wired indirectly (DI) or the declared edge is stale.`,
|
|
6354
6409
|
anchor?.id ?? component.id,
|
|
6355
6410
|
draft
|
|
6356
6411
|
);
|
|
@@ -6382,28 +6437,28 @@ var init_integration_conformance = __esm({
|
|
|
6382
6437
|
const filesByComponent = /* @__PURE__ */ new Map();
|
|
6383
6438
|
const filesBySubsystem = /* @__PURE__ */ new Map();
|
|
6384
6439
|
const implsByComponent = /* @__PURE__ */ new Map();
|
|
6440
|
+
const adopted = /* @__PURE__ */ new Set();
|
|
6441
|
+
const exists = (p) => {
|
|
6442
|
+
const status = factsByPath.get(p)?.status;
|
|
6443
|
+
return status === "analyzed" || status === "unreadable";
|
|
6444
|
+
};
|
|
6385
6445
|
for (const impl of ctx.implementations) {
|
|
6386
6446
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
6387
6447
|
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
6388
|
-
if (!comp) continue;
|
|
6448
|
+
if (!comp || ctx.isInChainedSubproject(comp.subsystem)) continue;
|
|
6389
6449
|
if (!implsByComponent.has(comp.id)) implsByComponent.set(comp.id, []);
|
|
6390
6450
|
implsByComponent.get(comp.id).push(impl);
|
|
6391
6451
|
for (const file of implementationSourceFiles(impl)) {
|
|
6392
6452
|
const p = pathKey(file);
|
|
6453
|
+
if (!exists(p)) continue;
|
|
6393
6454
|
if (!filesByComponent.has(comp.id)) filesByComponent.set(comp.id, /* @__PURE__ */ new Set());
|
|
6394
6455
|
filesByComponent.get(comp.id).add(p);
|
|
6395
6456
|
if (!filesBySubsystem.has(comp.subsystem)) filesBySubsystem.set(comp.subsystem, /* @__PURE__ */ new Set());
|
|
6396
6457
|
filesBySubsystem.get(comp.subsystem).add(p);
|
|
6397
6458
|
}
|
|
6459
|
+
if (impl.simPath) adopted.add(comp.subsystem);
|
|
6398
6460
|
}
|
|
6399
6461
|
const isTechBoundary = (compId) => (implsByComponent.get(compId) ?? []).some((i) => (i.technologies ?? []).length > 0);
|
|
6400
|
-
const adopted = /* @__PURE__ */ new Set();
|
|
6401
|
-
for (const impl of ctx.implementations) {
|
|
6402
|
-
if (!impl.simPath) continue;
|
|
6403
|
-
const contract = ctx.interfaceMap.get(impl.contract);
|
|
6404
|
-
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
6405
|
-
if (comp) adopted.add(comp.subsystem);
|
|
6406
|
-
}
|
|
6407
6462
|
const reachFrom = (start) => {
|
|
6408
6463
|
const seen = /* @__PURE__ */ new Set([start]);
|
|
6409
6464
|
const stack = [start];
|
|
@@ -6600,8 +6655,8 @@ var init_coupling_health = __esm({
|
|
|
6600
6655
|
);
|
|
6601
6656
|
}
|
|
6602
6657
|
}
|
|
6603
|
-
const threshold = ctx.rules?.complexity?.maxComponentDependencies ?? DEFAULT_GOD_COMPONENT_THRESHOLD;
|
|
6604
6658
|
for (const comp of ctx.components) {
|
|
6659
|
+
const threshold = ctx.complexityConfigFor(comp.subsystem)?.maxComponentDependencies ?? DEFAULT_GOD_COMPONENT_THRESHOLD;
|
|
6605
6660
|
if (comp.dependsOn.length > threshold) {
|
|
6606
6661
|
ctx.addIssue(
|
|
6607
6662
|
"warning",
|
|
@@ -6729,7 +6784,7 @@ var init_target_language = __esm({
|
|
|
6729
6784
|
if (!lang) continue;
|
|
6730
6785
|
const gaps = gapsFor(lang);
|
|
6731
6786
|
if (Object.keys(gaps).length === 0) continue;
|
|
6732
|
-
const isDraftCtx =
|
|
6787
|
+
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
6733
6788
|
for (const implMethod of impl.methods) {
|
|
6734
6789
|
for (const step of implMethod.narrative) {
|
|
6735
6790
|
const constructs = [
|
|
@@ -6831,7 +6886,7 @@ var init_technology_boundaries = __esm({
|
|
|
6831
6886
|
"TECH_ON_LOGIC_COMPONENT",
|
|
6832
6887
|
`Implementation "${impl.id}" binds technology (${impl.technologies.join(", ")}) on component "${comp.id}" (${comp.componentType}). Technology belongs behind a data-layer seam \u2014 extract an Adapter (or Store/Registry/Index) behind an intent interface and let this component depend on that.`,
|
|
6833
6888
|
impl.id,
|
|
6834
|
-
ctx.
|
|
6889
|
+
ctx.isImplementationDraft(impl)
|
|
6835
6890
|
);
|
|
6836
6891
|
}
|
|
6837
6892
|
const compSet = ownsClosure(ownershipRoot(comp.id));
|
|
@@ -6869,7 +6924,7 @@ var init_technology_boundaries = __esm({
|
|
|
6869
6924
|
"VENDOR_NAME_IN_CONTRACT",
|
|
6870
6925
|
`Interface "${intf.id}" exposes technology "${home.label}" in contract identifiers (method${offending.length > 1 ? "s" : ""}: ${offending.join(", ")}). Name the intent, not the vendor \u2014 the technology is owned by component ${ownersDesc(home)} and must stay swappable behind this contract.`,
|
|
6871
6926
|
intf.id,
|
|
6872
|
-
ctx.isComponentDraft(intf.component)
|
|
6927
|
+
ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design"
|
|
6873
6928
|
);
|
|
6874
6929
|
}
|
|
6875
6930
|
}
|
|
@@ -6900,7 +6955,8 @@ var init_technology_boundaries = __esm({
|
|
|
6900
6955
|
"warning",
|
|
6901
6956
|
"TECH_LEAKAGE",
|
|
6902
6957
|
`Subsystem "${sub.id}" references "${home.label}" outside the technology's owning boundary (component ${ownersDesc(home)}).`,
|
|
6903
|
-
sub.id
|
|
6958
|
+
sub.id,
|
|
6959
|
+
isDraftSubsystem(sub)
|
|
6904
6960
|
);
|
|
6905
6961
|
}
|
|
6906
6962
|
}
|
|
@@ -6913,7 +6969,7 @@ var init_technology_boundaries = __esm({
|
|
|
6913
6969
|
"TECH_LEAKAGE",
|
|
6914
6970
|
`Interface "${intf.id}" describes "${home.label}" outside its owning boundary (component ${ownersDesc(home)}) \u2014 consumers must not know backend specifics.`,
|
|
6915
6971
|
intf.id,
|
|
6916
|
-
ctx.isComponentDraft(intf.component)
|
|
6972
|
+
ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design"
|
|
6917
6973
|
);
|
|
6918
6974
|
}
|
|
6919
6975
|
}
|
|
@@ -6933,7 +6989,8 @@ var init_technology_boundaries = __esm({
|
|
|
6933
6989
|
"warning",
|
|
6934
6990
|
"TECH_LEAKAGE",
|
|
6935
6991
|
`Implementation "${impl.id}" references "${home.label}" outside its owning boundary \u2014 call the intent interface of component ${ownersDesc(home)} instead of naming its technology.`,
|
|
6936
|
-
impl.id
|
|
6992
|
+
impl.id,
|
|
6993
|
+
ctx.isImplementationDraft(impl)
|
|
6937
6994
|
);
|
|
6938
6995
|
}
|
|
6939
6996
|
}
|
|
@@ -6996,6 +7053,7 @@ var casingPatterns, isAllUppercase, namingRule;
|
|
|
6996
7053
|
var init_naming_conventions = __esm({
|
|
6997
7054
|
"src/core/rules/heuristic/naming-conventions.ts"() {
|
|
6998
7055
|
"use strict";
|
|
7056
|
+
init_models();
|
|
6999
7057
|
casingPatterns = {
|
|
7000
7058
|
camelCase: /^[a-z][a-zA-Z0-9]*$/,
|
|
7001
7059
|
PascalCase: /^[A-Z][a-zA-Z0-9]*$/,
|
|
@@ -7016,9 +7074,10 @@ var init_naming_conventions = __esm({
|
|
|
7016
7074
|
for (const sub of ctx.subsystems) {
|
|
7017
7075
|
const namingConfig = ctx.namingConfigFor(sub.id);
|
|
7018
7076
|
if (!namingConfig?.subsystems) continue;
|
|
7077
|
+
const isDraft = isDraftSubsystem(sub);
|
|
7019
7078
|
const baseId = getBaseId(sub.id);
|
|
7020
|
-
checkNamedValue(ctx, baseId, namingConfig.subsystems, `Subsystem ID "${sub.id}" does not match naming convention "${namingConfig.subsystems}".`, sub.id);
|
|
7021
|
-
checkNamedValue(ctx, sub.name, namingConfig.subsystems, `Subsystem Name "${sub.name}" does not match naming convention "${namingConfig.subsystems}".`, sub.id);
|
|
7079
|
+
checkNamedValue(ctx, baseId, namingConfig.subsystems, `Subsystem ID "${sub.id}" does not match naming convention "${namingConfig.subsystems}".`, sub.id, isDraft);
|
|
7080
|
+
checkNamedValue(ctx, sub.name, namingConfig.subsystems, `Subsystem Name "${sub.name}" does not match naming convention "${namingConfig.subsystems}".`, sub.id, isDraft);
|
|
7022
7081
|
}
|
|
7023
7082
|
for (const comp of ctx.components) {
|
|
7024
7083
|
const namingConfig = ctx.namingConfigFor(comp.subsystem);
|
|
@@ -7103,7 +7162,7 @@ var init_naming_conventions = __esm({
|
|
|
7103
7162
|
const intf = ctx.interfaceMap.get(impl.contract);
|
|
7104
7163
|
const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
|
|
7105
7164
|
const namingConfig = ctx.namingConfigFor(comp?.subsystem);
|
|
7106
|
-
const isDraft =
|
|
7165
|
+
const isDraft = ctx.isImplementationDraft(impl);
|
|
7107
7166
|
if (namingConfig?.methods) {
|
|
7108
7167
|
for (const m of impl.methods) {
|
|
7109
7168
|
checkNamedValue(ctx, m.name, namingConfig.methods, `Implementation method "${m.name}" on "${impl.id}" does not match naming convention "${namingConfig.methods}".`, impl.id, isDraft);
|
|
@@ -7167,6 +7226,7 @@ var complexityRule;
|
|
|
7167
7226
|
var init_complexity_and_metadata = __esm({
|
|
7168
7227
|
"src/core/rules/heuristic/complexity-and-metadata.ts"() {
|
|
7169
7228
|
"use strict";
|
|
7229
|
+
init_models();
|
|
7170
7230
|
complexityRule = {
|
|
7171
7231
|
name: "complexity-and-metadata",
|
|
7172
7232
|
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.",
|
|
@@ -7182,7 +7242,8 @@ var init_complexity_and_metadata = __esm({
|
|
|
7182
7242
|
check(ctx) {
|
|
7183
7243
|
for (const sub of ctx.subsystems) {
|
|
7184
7244
|
const docConfig = ctx.documentationConfigFor(sub.id);
|
|
7185
|
-
|
|
7245
|
+
const isSubDraft = isDraftSubsystem(sub);
|
|
7246
|
+
checkDescription(ctx, sub.description, docConfig?.requireDescriptions ?? false, docConfig?.minDescriptionLength, sub.id, "Subsystem", isSubDraft);
|
|
7186
7247
|
const complexityConfig = ctx.complexityConfigFor(sub.id);
|
|
7187
7248
|
const directComponents = ctx.components.filter((c) => c.subsystem === sub.id).length;
|
|
7188
7249
|
if (complexityConfig?.maxSubsystemComponents !== void 0 && directComponents > complexityConfig.maxSubsystemComponents) {
|
|
@@ -7190,7 +7251,8 @@ var init_complexity_and_metadata = __esm({
|
|
|
7190
7251
|
"warning",
|
|
7191
7252
|
"EXCESSIVE_SUBSYSTEM_COMPONENTS",
|
|
7192
7253
|
`Subsystem "${sub.id}" has ${directComponents} direct components, exceeding the configured limit of ${complexityConfig.maxSubsystemComponents}.`,
|
|
7193
|
-
sub.id
|
|
7254
|
+
sub.id,
|
|
7255
|
+
isSubDraft
|
|
7194
7256
|
);
|
|
7195
7257
|
}
|
|
7196
7258
|
}
|
|
@@ -7266,7 +7328,7 @@ var init_complexity_and_metadata = __esm({
|
|
|
7266
7328
|
const intf = ctx.interfaceMap.get(impl.contract);
|
|
7267
7329
|
const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
|
|
7268
7330
|
const complexityConfig = ctx.complexityConfigFor(comp?.subsystem);
|
|
7269
|
-
const isDraft =
|
|
7331
|
+
const isDraft = ctx.isImplementationDraft(impl);
|
|
7270
7332
|
if (complexityConfig?.maxNarrativeSteps !== void 0) {
|
|
7271
7333
|
for (const m of impl.methods) {
|
|
7272
7334
|
if (m.narrative.length > complexityConfig.maxNarrativeSteps) {
|
|
@@ -13980,7 +14042,7 @@ function buildRuleContext(opts) {
|
|
|
13980
14042
|
return rules.sddRuleSeverity[ruleCode];
|
|
13981
14043
|
}
|
|
13982
14044
|
const sub = subsystemId ? subsystems.find((s) => s.id === subsystemId) : void 0;
|
|
13983
|
-
const profileSeverity = extensions.profiles[sub?.profile
|
|
14045
|
+
const profileSeverity = extensions.profiles[sub?.profile || projectType]?.rules?.sddRuleSeverity?.[ruleCode];
|
|
13984
14046
|
if (profileSeverity) {
|
|
13985
14047
|
return profileSeverity;
|
|
13986
14048
|
}
|
|
@@ -14219,28 +14281,52 @@ var init_candidate = __esm({
|
|
|
14219
14281
|
function globalVariantsDir() {
|
|
14220
14282
|
return process.env.WAIRON_VARIANTS_DIR ?? path12.join(os3.homedir(), ".wairon", "variants");
|
|
14221
14283
|
}
|
|
14222
|
-
function
|
|
14284
|
+
function builtinVariantsDir() {
|
|
14285
|
+
const candidates = [
|
|
14286
|
+
path12.resolve(__dirname, "..", "templates", "variants"),
|
|
14287
|
+
// src/core & dist/cli
|
|
14288
|
+
path12.resolve(__dirname, "templates", "variants")
|
|
14289
|
+
// dist (library entry)
|
|
14290
|
+
];
|
|
14291
|
+
return candidates.find((dir) => fs9.existsSync(dir)) ?? candidates[0];
|
|
14292
|
+
}
|
|
14293
|
+
function projectVariantsDir() {
|
|
14294
|
+
return path12.join(getProjectRoot(), ".wai", "variants");
|
|
14295
|
+
}
|
|
14296
|
+
function variantFiles(dir) {
|
|
14223
14297
|
let entries;
|
|
14224
14298
|
try {
|
|
14225
14299
|
entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
14226
14300
|
} catch {
|
|
14227
14301
|
return [];
|
|
14228
14302
|
}
|
|
14303
|
+
return entries.filter((e) => e.isFile() && /\.ya?ml$/i.test(e.name)).sort((a, b) => a.name.localeCompare(b.name)).map((e) => path12.join(dir, e.name));
|
|
14304
|
+
}
|
|
14305
|
+
function parseVariants(raw) {
|
|
14306
|
+
if (raw == null) return [];
|
|
14307
|
+
return (Array.isArray(raw) ? raw : [raw]).map((item) => VariantDefSchema.parse(item));
|
|
14308
|
+
}
|
|
14309
|
+
function reportSkippedFile(file, err) {
|
|
14310
|
+
console.error(`[variants] skipped "${file}": ${err instanceof Error ? err.message : String(err)}`);
|
|
14311
|
+
}
|
|
14312
|
+
function readVariantsDir(dir) {
|
|
14229
14313
|
const out = [];
|
|
14230
|
-
for (const
|
|
14231
|
-
if (!e.isFile() || !/\.ya?ml$/i.test(e.name)) continue;
|
|
14232
|
-
const full = path12.join(dir, e.name);
|
|
14314
|
+
for (const file of variantFiles(dir)) {
|
|
14233
14315
|
try {
|
|
14234
|
-
|
|
14235
|
-
if (raw == null) continue;
|
|
14236
|
-
const items = Array.isArray(raw) ? raw : [raw];
|
|
14237
|
-
for (const item of items) out.push(VariantDefSchema.parse(item));
|
|
14316
|
+
out.push(...parseVariants(readYamlFile(file)));
|
|
14238
14317
|
} catch (err) {
|
|
14239
|
-
|
|
14318
|
+
reportSkippedFile(file, err);
|
|
14240
14319
|
}
|
|
14241
14320
|
}
|
|
14242
14321
|
return out;
|
|
14243
14322
|
}
|
|
14323
|
+
function mergeById(...layers) {
|
|
14324
|
+
const byId = /* @__PURE__ */ new Map();
|
|
14325
|
+
for (const layer of layers) {
|
|
14326
|
+
for (const v of layer) byId.set(v.id, v);
|
|
14327
|
+
}
|
|
14328
|
+
return [...byId.values()];
|
|
14329
|
+
}
|
|
14244
14330
|
function resolveVariantGuidance(component, allComponents, variantsById) {
|
|
14245
14331
|
if (!component.variant) return null;
|
|
14246
14332
|
const def = variantsById.get(component.variant);
|
|
@@ -14274,14 +14360,55 @@ function composeVariantGuidance(comps, allComponents, variantsById) {
|
|
|
14274
14360
|
}
|
|
14275
14361
|
function loadProjectVariants() {
|
|
14276
14362
|
try {
|
|
14277
|
-
|
|
14278
|
-
|
|
14279
|
-
|
|
14280
|
-
|
|
14363
|
+
return mergeById(
|
|
14364
|
+
readVariantsDir(builtinVariantsDir()),
|
|
14365
|
+
readVariantsDir(globalVariantsDir()),
|
|
14366
|
+
readVariantsDir(projectVariantsDir())
|
|
14367
|
+
);
|
|
14281
14368
|
} catch {
|
|
14282
14369
|
return [];
|
|
14283
14370
|
}
|
|
14284
14371
|
}
|
|
14372
|
+
function listProjectVariants() {
|
|
14373
|
+
return mergeById(readVariantsDir(projectVariantsDir()));
|
|
14374
|
+
}
|
|
14375
|
+
function rebaseProjectVariants(fromBase, toBase) {
|
|
14376
|
+
const rebased = [];
|
|
14377
|
+
for (const file of variantFiles(projectVariantsDir())) {
|
|
14378
|
+
const content = readFileOrNull(file);
|
|
14379
|
+
if (content === null) continue;
|
|
14380
|
+
let raw;
|
|
14381
|
+
let variants;
|
|
14382
|
+
try {
|
|
14383
|
+
raw = parseYaml(content, file);
|
|
14384
|
+
variants = parseVariants(raw);
|
|
14385
|
+
} catch (err) {
|
|
14386
|
+
reportSkippedFile(file, err);
|
|
14387
|
+
continue;
|
|
14388
|
+
}
|
|
14389
|
+
const matching = variants.filter((v) => v.base === fromBase);
|
|
14390
|
+
if (matching.length === 0) continue;
|
|
14391
|
+
writeFile(file, rebasedText(content, fromBase, toBase, withRebasedEntries(raw, fromBase, toBase)));
|
|
14392
|
+
for (const v of matching) {
|
|
14393
|
+
if (!rebased.includes(v.id)) rebased.push(v.id);
|
|
14394
|
+
}
|
|
14395
|
+
}
|
|
14396
|
+
return rebased;
|
|
14397
|
+
}
|
|
14398
|
+
function withRebasedEntries(raw, fromBase, toBase) {
|
|
14399
|
+
const rebase = (entry) => entry.base === fromBase ? { ...entry, base: toBase } : entry;
|
|
14400
|
+
return Array.isArray(raw) ? raw.map(rebase) : rebase(raw);
|
|
14401
|
+
}
|
|
14402
|
+
function rebasedText(content, fromBase, toBase, rebasedContent) {
|
|
14403
|
+
const escaped = fromBase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
14404
|
+
const baseLine = new RegExp(`^([ \\t]*(?:-[ \\t]+)?base:[ \\t]*)(["']?)${escaped}\\2(?=[ \\t]*(?:#.*)?$)`, "gm");
|
|
14405
|
+
const edited = content.replace(baseLine, (_line, key, quote) => `${key}${quote}${toBase}${quote}`);
|
|
14406
|
+
try {
|
|
14407
|
+
if (JSON.stringify(parseYaml(edited)) === JSON.stringify(rebasedContent)) return edited;
|
|
14408
|
+
} catch {
|
|
14409
|
+
}
|
|
14410
|
+
return serializeYaml(rebasedContent);
|
|
14411
|
+
}
|
|
14285
14412
|
var fs9, os3, path12, import_zod10, VariantDefSchema;
|
|
14286
14413
|
var init_variants = __esm({
|
|
14287
14414
|
"src/core/variants.ts"() {
|
|
@@ -14295,7 +14422,7 @@ var init_variants = __esm({
|
|
|
14295
14422
|
VariantDefSchema = import_zod10.z.object({
|
|
14296
14423
|
/** Variant id referenced by a component's `variant` (e.g. "publisher", "org/external-config-adapter"). */
|
|
14297
14424
|
id: import_zod10.z.string().min(1),
|
|
14298
|
-
/** The core stereotype this variant specializes — authoritative for generic semantics (Adapter,
|
|
14425
|
+
/** The core stereotype this variant specializes — authoritative for generic semantics (Adapter, Orchestrator, …). */
|
|
14299
14426
|
base: import_zod10.z.string().min(1),
|
|
14300
14427
|
/** How to implement a component of this variant — the recipe the implementer follows and reuses across same-variant components. */
|
|
14301
14428
|
guidance: import_zod10.z.string().min(1),
|
|
@@ -23525,6 +23652,7 @@ __export(src_exports, {
|
|
|
23525
23652
|
DEFAULT_TARGET_DIRS: () => DEFAULT_TARGET_DIRS,
|
|
23526
23653
|
DatabaseSpecSchema: () => DatabaseSpecSchema,
|
|
23527
23654
|
DeclarativePackSchema: () => DeclarativePackSchema,
|
|
23655
|
+
DependencyClassSchema: () => DependencyClassSchema,
|
|
23528
23656
|
DesignDepthSchema: () => DesignDepthSchema,
|
|
23529
23657
|
DiagramConfigSchema: () => DiagramConfigSchema,
|
|
23530
23658
|
DispatchBindingSchema: () => DispatchBindingSchema,
|
|
@@ -23582,6 +23710,7 @@ __export(src_exports, {
|
|
|
23582
23710
|
ProjectProfileSelectionSchema: () => ProjectProfileSelectionSchema,
|
|
23583
23711
|
PublicInterfaceSchema: () => PublicInterfaceSchema,
|
|
23584
23712
|
PublicInterfaceTypeSchema: () => PublicInterfaceTypeSchema,
|
|
23713
|
+
RETIRED_STEREOTYPES: () => RETIRED_STEREOTYPES,
|
|
23585
23714
|
RegistrySchema: () => RegistrySchema,
|
|
23586
23715
|
RequirementItemSchema: () => RequirementItemSchema,
|
|
23587
23716
|
RulesConfigSchema: () => RulesConfigSchema,
|
|
@@ -23732,6 +23861,7 @@ __export(src_exports, {
|
|
|
23732
23861
|
hasContext: () => hasContext,
|
|
23733
23862
|
hasSpecTree: () => hasSpecTree,
|
|
23734
23863
|
hashGateState: () => hashGateState,
|
|
23864
|
+
holdsState: () => holdsState,
|
|
23735
23865
|
implementationSourceFiles: () => implementationSourceFiles,
|
|
23736
23866
|
importSpecTree: () => importSpecTree,
|
|
23737
23867
|
importSurface: () => importSurface,
|
|
@@ -23742,10 +23872,12 @@ __export(src_exports, {
|
|
|
23742
23872
|
internalizeSubsystem: () => internalizeSubsystem,
|
|
23743
23873
|
invalidateSpecCache: () => invalidateSpecCache,
|
|
23744
23874
|
isDraftSubsystem: () => isDraftSubsystem,
|
|
23875
|
+
isLogic: () => isLogic,
|
|
23745
23876
|
isOpenApiDocument: () => isOpenApiDocument,
|
|
23746
23877
|
isPattern: () => isPattern,
|
|
23747
23878
|
isProjectInitialized: () => isProjectInitialized,
|
|
23748
23879
|
isProvidedBy: () => isProvidedBy,
|
|
23880
|
+
isRetired: () => isRetired,
|
|
23749
23881
|
listChainedRoots: () => listChainedRoots,
|
|
23750
23882
|
listDirectChainedSubprojects: () => listDirectChainedSubprojects,
|
|
23751
23883
|
listExternalInterfaces: () => listExternalInterfaces,
|
|
@@ -23754,6 +23886,7 @@ __export(src_exports, {
|
|
|
23754
23886
|
listFreeStandingDomains: () => listFreeStandingDomains,
|
|
23755
23887
|
listInstalledPacks: () => listInstalledPacks,
|
|
23756
23888
|
listMountSnapshots: () => listMountSnapshots,
|
|
23889
|
+
listProjectVariants: () => listProjectVariants,
|
|
23757
23890
|
listResources: () => listResources,
|
|
23758
23891
|
listRules: () => listRules2,
|
|
23759
23892
|
listSkillNames: () => listSkillNames,
|
|
@@ -23824,6 +23957,7 @@ __export(src_exports, {
|
|
|
23824
23957
|
readResource: () => readResource,
|
|
23825
23958
|
readSkillResource: () => readSkillResource,
|
|
23826
23959
|
readYamlFile: () => readYamlFile,
|
|
23960
|
+
rebaseProjectVariants: () => rebaseProjectVariants,
|
|
23827
23961
|
rebaseReference: () => rebaseReference,
|
|
23828
23962
|
recordProfileSelection: () => recordProfileSelection,
|
|
23829
23963
|
registerExporter: () => registerExporter,
|
|
@@ -24231,6 +24365,7 @@ function loadProjectConfig4() {
|
|
|
24231
24365
|
DEFAULT_TARGET_DIRS,
|
|
24232
24366
|
DatabaseSpecSchema,
|
|
24233
24367
|
DeclarativePackSchema,
|
|
24368
|
+
DependencyClassSchema,
|
|
24234
24369
|
DesignDepthSchema,
|
|
24235
24370
|
DiagramConfigSchema,
|
|
24236
24371
|
DispatchBindingSchema,
|
|
@@ -24288,6 +24423,7 @@ function loadProjectConfig4() {
|
|
|
24288
24423
|
ProjectProfileSelectionSchema,
|
|
24289
24424
|
PublicInterfaceSchema,
|
|
24290
24425
|
PublicInterfaceTypeSchema,
|
|
24426
|
+
RETIRED_STEREOTYPES,
|
|
24291
24427
|
RegistrySchema,
|
|
24292
24428
|
RequirementItemSchema,
|
|
24293
24429
|
RulesConfigSchema,
|
|
@@ -24438,6 +24574,7 @@ function loadProjectConfig4() {
|
|
|
24438
24574
|
hasContext,
|
|
24439
24575
|
hasSpecTree,
|
|
24440
24576
|
hashGateState,
|
|
24577
|
+
holdsState,
|
|
24441
24578
|
implementationSourceFiles,
|
|
24442
24579
|
importSpecTree,
|
|
24443
24580
|
importSurface,
|
|
@@ -24448,10 +24585,12 @@ function loadProjectConfig4() {
|
|
|
24448
24585
|
internalizeSubsystem,
|
|
24449
24586
|
invalidateSpecCache,
|
|
24450
24587
|
isDraftSubsystem,
|
|
24588
|
+
isLogic,
|
|
24451
24589
|
isOpenApiDocument,
|
|
24452
24590
|
isPattern,
|
|
24453
24591
|
isProjectInitialized,
|
|
24454
24592
|
isProvidedBy,
|
|
24593
|
+
isRetired,
|
|
24455
24594
|
listChainedRoots,
|
|
24456
24595
|
listDirectChainedSubprojects,
|
|
24457
24596
|
listExternalInterfaces,
|
|
@@ -24460,6 +24599,7 @@ function loadProjectConfig4() {
|
|
|
24460
24599
|
listFreeStandingDomains,
|
|
24461
24600
|
listInstalledPacks,
|
|
24462
24601
|
listMountSnapshots,
|
|
24602
|
+
listProjectVariants,
|
|
24463
24603
|
listResources,
|
|
24464
24604
|
listRules,
|
|
24465
24605
|
listSkillNames,
|
|
@@ -24530,6 +24670,7 @@ function loadProjectConfig4() {
|
|
|
24530
24670
|
readResource,
|
|
24531
24671
|
readSkillResource,
|
|
24532
24672
|
readYamlFile,
|
|
24673
|
+
rebaseProjectVariants,
|
|
24533
24674
|
rebaseReference,
|
|
24534
24675
|
recordProfileSelection,
|
|
24535
24676
|
registerExporter,
|