@wairon/cli 5.1.1-dev.15 → 5.1.1-dev.16
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 +274 -234
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +273 -233
- 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/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.16";
|
|
1096
1096
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
1097
1097
|
ARCHITECT_AGENT_ID = "agent-architect";
|
|
1098
1098
|
ARCHITECT_TEMPLATE_ID = "architect";
|
|
@@ -2766,15 +2766,13 @@ var init_hierarchy_integrity = __esm({
|
|
|
2766
2766
|
}
|
|
2767
2767
|
}
|
|
2768
2768
|
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
2769
|
if (!ctx.interfaceIds.has(impl.contract)) {
|
|
2772
2770
|
ctx.addIssue(
|
|
2773
2771
|
"error",
|
|
2774
2772
|
"INVALID_INTERFACE_REFERENCE",
|
|
2775
2773
|
`Implementation "${impl.id}" references non-existent interface contract "${impl.contract}".`,
|
|
2776
2774
|
impl.id,
|
|
2777
|
-
|
|
2775
|
+
ctx.isImplementationDraft(impl)
|
|
2778
2776
|
);
|
|
2779
2777
|
}
|
|
2780
2778
|
}
|
|
@@ -2890,11 +2888,7 @@ var init_type_references2 = __esm({
|
|
|
2890
2888
|
for (const field of t.fields) {
|
|
2891
2889
|
const refs = fieldTypeRefs(t, field.type);
|
|
2892
2890
|
for (const ref of refs) {
|
|
2893
|
-
if (ctx.
|
|
2894
|
-
continue;
|
|
2895
|
-
}
|
|
2896
|
-
const resolved = ctx.types.find((spec) => typeMatchesRef(spec, ref));
|
|
2897
|
-
if (!resolved) {
|
|
2891
|
+
if (!ctx.isTypeResolved(ref, /* @__PURE__ */ new Set())) {
|
|
2898
2892
|
ctx.addIssue(
|
|
2899
2893
|
"error",
|
|
2900
2894
|
"UNDEFINED_TYPE_REFERENCE",
|
|
@@ -3126,14 +3120,27 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3126
3120
|
);
|
|
3127
3121
|
continue;
|
|
3128
3122
|
}
|
|
3129
|
-
const
|
|
3130
|
-
const
|
|
3131
|
-
const
|
|
3123
|
+
const target = step.targetComponent;
|
|
3124
|
+
const isCrossTreeForm = ctx.isExternalNamespaceRef(target);
|
|
3125
|
+
const caller = ctx.componentMap.get(contract.component);
|
|
3126
|
+
const fromSubsystem = caller?.subsystem;
|
|
3127
|
+
const isCollapsedForm = !isCrossTreeForm && fromSubsystem !== void 0 && ctx.isCollapsedCrossTreeRef(target, fromSubsystem);
|
|
3128
|
+
const reportUndeclared = (reaches, surfaceResolved) => {
|
|
3129
|
+
if (!caller || target === caller.id || caller.dependsOn.includes(target) || caller.owns.includes(target)) return;
|
|
3130
|
+
ctx.addIssue(
|
|
3131
|
+
"error",
|
|
3132
|
+
"UNDECLARED_DEPENDENCY_CALL",
|
|
3133
|
+
`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.`,
|
|
3134
|
+
impl.id,
|
|
3135
|
+
isDraftCtx || ctx.isComponentDraft(caller.id),
|
|
3136
|
+
surfaceResolved
|
|
3137
|
+
);
|
|
3138
|
+
};
|
|
3132
3139
|
if (step.type === "dispatch") {
|
|
3133
|
-
const dispatchTarget = ctx.componentMap.get(
|
|
3140
|
+
const dispatchTarget = ctx.componentMap.get(target);
|
|
3134
3141
|
if (!dispatchTarget) {
|
|
3135
3142
|
if (isCrossTreeForm || isCollapsedForm) {
|
|
3136
|
-
const resolved = ctx.resolveSurfaceRef(
|
|
3143
|
+
const resolved = ctx.resolveSurfaceRef(target, fromSubsystem);
|
|
3137
3144
|
if (resolved.kind === "ambiguous") {
|
|
3138
3145
|
ctx.addIssue(
|
|
3139
3146
|
"error",
|
|
@@ -3141,7 +3148,7 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3141
3148
|
ambiguityMessage(
|
|
3142
3149
|
resolved,
|
|
3143
3150
|
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches (step ${step.stepNumber}) through`,
|
|
3144
|
-
|
|
3151
|
+
target
|
|
3145
3152
|
),
|
|
3146
3153
|
impl.id,
|
|
3147
3154
|
isDraftCtx
|
|
@@ -3149,11 +3156,12 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3149
3156
|
continue;
|
|
3150
3157
|
}
|
|
3151
3158
|
if (resolved.kind === "resolved") {
|
|
3159
|
+
reportUndeclared("dispatches through", true);
|
|
3152
3160
|
if (step.capability && !(resolved.entry.dispatch ?? []).some((b) => b.capability === step.capability)) {
|
|
3153
3161
|
ctx.addIssue(
|
|
3154
3162
|
"error",
|
|
3155
3163
|
"SURFACE_REF_NOT_EXPOSED",
|
|
3156
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches capability "${step.capability}" through cross-tree portal "${
|
|
3164
|
+
`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
3165
|
impl.id,
|
|
3158
3166
|
isDraftCtx,
|
|
3159
3167
|
true
|
|
@@ -3166,7 +3174,7 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3166
3174
|
ctx.addIssue(
|
|
3167
3175
|
"warning",
|
|
3168
3176
|
"CROSS_TREE_REF_UNRESOLVED",
|
|
3169
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches through cross-tree component "${
|
|
3177
|
+
`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
3178
|
impl.id,
|
|
3171
3179
|
isDraftCtx
|
|
3172
3180
|
);
|
|
@@ -3174,23 +3182,14 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3174
3182
|
ctx.addIssue(
|
|
3175
3183
|
"error",
|
|
3176
3184
|
"INVALID_TARGET_COMPONENT_REFERENCE",
|
|
3177
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches through component "${
|
|
3185
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches through component "${target}" which does not exist (step ${step.stepNumber}).`,
|
|
3178
3186
|
impl.id,
|
|
3179
3187
|
isDraftCtx
|
|
3180
3188
|
);
|
|
3181
3189
|
}
|
|
3182
3190
|
continue;
|
|
3183
3191
|
}
|
|
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
|
-
}
|
|
3192
|
+
reportUndeclared("dispatches through");
|
|
3194
3193
|
continue;
|
|
3195
3194
|
}
|
|
3196
3195
|
const verb = step.type === "register" ? "registers callback" : "calls";
|
|
@@ -3204,10 +3203,10 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3204
3203
|
);
|
|
3205
3204
|
continue;
|
|
3206
3205
|
}
|
|
3207
|
-
const targetComp = ctx.componentMap.get(
|
|
3206
|
+
const targetComp = ctx.componentMap.get(target);
|
|
3208
3207
|
if (!targetComp) {
|
|
3209
3208
|
if (isCrossTreeForm || isCollapsedForm) {
|
|
3210
|
-
const resolved = ctx.resolveSurfaceRef(
|
|
3209
|
+
const resolved = ctx.resolveSurfaceRef(target, fromSubsystem);
|
|
3211
3210
|
if (resolved.kind === "ambiguous") {
|
|
3212
3211
|
ctx.addIssue(
|
|
3213
3212
|
"error",
|
|
@@ -3215,7 +3214,7 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3215
3214
|
ambiguityMessage(
|
|
3216
3215
|
resolved,
|
|
3217
3216
|
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} "${step.targetMethod}" (step ${step.stepNumber}) on`,
|
|
3218
|
-
|
|
3217
|
+
target
|
|
3219
3218
|
),
|
|
3220
3219
|
impl.id,
|
|
3221
3220
|
isDraftCtx
|
|
@@ -3223,12 +3222,13 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3223
3222
|
continue;
|
|
3224
3223
|
}
|
|
3225
3224
|
if (resolved.kind === "resolved") {
|
|
3225
|
+
reportUndeclared(verb, true);
|
|
3226
3226
|
const surfaceMethod = resolved.entry.methods.find((m) => m.name === step.targetMethod);
|
|
3227
3227
|
if (!surfaceMethod) {
|
|
3228
3228
|
ctx.addIssue(
|
|
3229
3229
|
"error",
|
|
3230
3230
|
"SURFACE_REF_NOT_EXPOSED",
|
|
3231
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} "${step.targetMethod}" on cross-tree component "${
|
|
3231
|
+
`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
3232
|
impl.id,
|
|
3233
3233
|
isDraftCtx,
|
|
3234
3234
|
true
|
|
@@ -3255,7 +3255,7 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3255
3255
|
ctx.addIssue(
|
|
3256
3256
|
"warning",
|
|
3257
3257
|
"CROSS_TREE_REF_UNRESOLVED",
|
|
3258
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} cross-tree component "${
|
|
3258
|
+
`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
3259
|
impl.id,
|
|
3260
3260
|
isDraftCtx
|
|
3261
3261
|
);
|
|
@@ -3263,26 +3263,15 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3263
3263
|
ctx.addIssue(
|
|
3264
3264
|
"error",
|
|
3265
3265
|
"INVALID_TARGET_COMPONENT_REFERENCE",
|
|
3266
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} component "${
|
|
3266
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} component "${target}" which does not exist (step ${step.stepNumber}).`,
|
|
3267
3267
|
impl.id,
|
|
3268
3268
|
isDraftCtx
|
|
3269
3269
|
);
|
|
3270
3270
|
}
|
|
3271
3271
|
continue;
|
|
3272
3272
|
}
|
|
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) ?? [];
|
|
3273
|
+
reportUndeclared(verb);
|
|
3274
|
+
const targetInterfaces = ctx.interfacesByComponent.get(target) ?? [];
|
|
3286
3275
|
let targetMethodSpec;
|
|
3287
3276
|
for (const targetIntf of targetInterfaces) {
|
|
3288
3277
|
const found = targetIntf.methods.find((m) => m.name === step.targetMethod);
|
|
@@ -3295,9 +3284,9 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3295
3284
|
ctx.addIssue(
|
|
3296
3285
|
"error",
|
|
3297
3286
|
"INVALID_TARGET_METHOD_REFERENCE",
|
|
3298
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} method "${step.targetMethod}" on component "${
|
|
3287
|
+
`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
3288
|
impl.id,
|
|
3300
|
-
isDraftCtx || ctx.isComponentDraft(
|
|
3289
|
+
isDraftCtx || ctx.isComponentDraft(target)
|
|
3301
3290
|
);
|
|
3302
3291
|
} else {
|
|
3303
3292
|
const declared = new Set(targetMethodSpec.guarantees ?? []);
|
|
@@ -3307,9 +3296,9 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3307
3296
|
ctx.addIssue(
|
|
3308
3297
|
"warning",
|
|
3309
3298
|
"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 "${
|
|
3299
|
+
`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
3300
|
impl.id,
|
|
3312
|
-
isDraftCtx || ctx.isComponentDraft(
|
|
3301
|
+
isDraftCtx || ctx.isComponentDraft(target)
|
|
3313
3302
|
);
|
|
3314
3303
|
}
|
|
3315
3304
|
}
|
|
@@ -3639,43 +3628,47 @@ var init_narrative_flow = __esm({
|
|
|
3639
3628
|
});
|
|
3640
3629
|
|
|
3641
3630
|
// 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
|
-
}
|
|
3631
|
+
function completedStepGraph(steps) {
|
|
3632
|
+
const numbers = steps.flatMap((s) => [
|
|
3633
|
+
s.stepNumber,
|
|
3634
|
+
s.onTrueStep,
|
|
3635
|
+
s.onFalseStep,
|
|
3636
|
+
s.defaultStep,
|
|
3637
|
+
s.endStep,
|
|
3638
|
+
s.finallyStep,
|
|
3639
|
+
s.toStep,
|
|
3640
|
+
...(s.cases ?? []).map((c) => c.step),
|
|
3641
|
+
...(s.catches ?? []).map((c) => c.step),
|
|
3642
|
+
...(s.branches ?? []).map((b) => b.step)
|
|
3643
|
+
]).filter((n) => n !== void 0);
|
|
3644
|
+
const completion = {
|
|
3645
|
+
stepNumber: Math.max(0, ...numbers) + 1,
|
|
3646
|
+
description: "The method completes by falling off the end of its narrative",
|
|
3647
|
+
type: "return"
|
|
3648
|
+
};
|
|
3649
|
+
return stepGraph({ narrative: [...steps, completion] });
|
|
3661
3650
|
}
|
|
3662
3651
|
function isUnavoidable(steps, target) {
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
const
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
const
|
|
3670
|
-
if (
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3652
|
+
if (steps.length === 0) return false;
|
|
3653
|
+
const graph = completedStepGraph(steps);
|
|
3654
|
+
const completes = /* @__PURE__ */ new Set();
|
|
3655
|
+
const canComplete = (n) => {
|
|
3656
|
+
const s = graph.stepAt(n);
|
|
3657
|
+
if (s.type === "return" || s.type === "throw") return true;
|
|
3658
|
+
const arms = s.type === "parallel" ? (s.branches ?? []).map((b) => b.step).filter((e) => graph.stepAt(e) !== void 0) : [];
|
|
3659
|
+
if (arms.length) return arms.every((e) => completes.has(e));
|
|
3660
|
+
return graph.successorsOf(n).some((t) => completes.has(t));
|
|
3661
|
+
};
|
|
3662
|
+
const order = [...graph.stepNumbers].reverse();
|
|
3663
|
+
for (let grew = true; grew; ) {
|
|
3664
|
+
grew = false;
|
|
3665
|
+
for (const n of order) {
|
|
3666
|
+
if (n === target || completes.has(n) || !canComplete(n)) continue;
|
|
3667
|
+
completes.add(n);
|
|
3668
|
+
grew = true;
|
|
3676
3669
|
}
|
|
3677
3670
|
}
|
|
3678
|
-
return
|
|
3671
|
+
return !completes.has(graph.stepNumbers[0]);
|
|
3679
3672
|
}
|
|
3680
3673
|
function stronglyConnected(nums, successorsOf) {
|
|
3681
3674
|
const index = /* @__PURE__ */ new Map();
|
|
@@ -3727,11 +3720,12 @@ function stronglyConnected(nums, successorsOf) {
|
|
|
3727
3720
|
}
|
|
3728
3721
|
return sccs;
|
|
3729
3722
|
}
|
|
3730
|
-
var narrativeAntipatternsRule;
|
|
3723
|
+
var byCodeUnit, narrativeAntipatternsRule;
|
|
3731
3724
|
var init_narrative_antipatterns = __esm({
|
|
3732
3725
|
"src/core/rules/narrative/narrative-antipatterns.ts"() {
|
|
3733
3726
|
"use strict";
|
|
3734
3727
|
init_models();
|
|
3728
|
+
byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
3735
3729
|
narrativeAntipatternsRule = {
|
|
3736
3730
|
name: "narrative-antipatterns",
|
|
3737
3731
|
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 +3744,33 @@ var init_narrative_antipatterns = __esm({
|
|
|
3750
3744
|
const steps = implMethod.narrative;
|
|
3751
3745
|
if (!steps.length) continue;
|
|
3752
3746
|
const where = `Method "${implMethod.name}" in implementation "${impl.id}": `;
|
|
3753
|
-
const graph =
|
|
3747
|
+
const graph = completedStepGraph(steps);
|
|
3748
|
+
const decisionTargets = (stepNumber, explicit, fallThroughField) => {
|
|
3749
|
+
const targets = new Set(explicit);
|
|
3750
|
+
if (fallThroughField !== void 0) targets.add(fallThroughField);
|
|
3751
|
+
else for (const t of graph.successorsOf(stepNumber)) targets.add(t);
|
|
3752
|
+
return [...targets];
|
|
3753
|
+
};
|
|
3754
3754
|
for (const s of steps) {
|
|
3755
3755
|
if (s.type === "branch" && s.onFalseStep !== void 0) {
|
|
3756
|
-
const
|
|
3757
|
-
if (
|
|
3756
|
+
const targets = decisionTargets(s.stepNumber, [s.onFalseStep], s.onTrueStep);
|
|
3757
|
+
if (targets.length === 1) {
|
|
3758
3758
|
ctx.addIssue(
|
|
3759
3759
|
"warning",
|
|
3760
3760
|
"MEANINGLESS_BRANCH",
|
|
3761
|
-
`${where}branch step ${s.stepNumber} sends both arms to step ${
|
|
3761
|
+
`${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
3762
|
impl.id,
|
|
3763
3763
|
isDraftCtx
|
|
3764
3764
|
);
|
|
3765
3765
|
}
|
|
3766
3766
|
}
|
|
3767
3767
|
if (s.type === "switch" && s.cases?.length) {
|
|
3768
|
-
const targets =
|
|
3769
|
-
|
|
3770
|
-
if (def !== void 0) targets.add(def);
|
|
3771
|
-
if (targets.size === 1) {
|
|
3768
|
+
const targets = decisionTargets(s.stepNumber, s.cases.map((c) => c.step), s.defaultStep);
|
|
3769
|
+
if (targets.length === 1) {
|
|
3772
3770
|
ctx.addIssue(
|
|
3773
3771
|
"warning",
|
|
3774
3772
|
"MEANINGLESS_BRANCH",
|
|
3775
|
-
`${where}switch step ${s.stepNumber} sends every case (and the default) to step ${
|
|
3773
|
+
`${where}switch step ${s.stepNumber} sends every case (and the default) to step ${targets[0]} \u2014 the dispatch decides nothing.`,
|
|
3776
3774
|
impl.id,
|
|
3777
3775
|
isDraftCtx
|
|
3778
3776
|
);
|
|
@@ -3845,8 +3843,8 @@ var init_narrative_antipatterns = __esm({
|
|
|
3845
3843
|
if (!isCycle) continue;
|
|
3846
3844
|
const memberEdges = keys.flatMap((k) => (adjacency.get(k) ?? []).filter((e) => inScc.has(e.toKey)));
|
|
3847
3845
|
if (memberEdges.length === 0) continue;
|
|
3848
|
-
const anchor = [...memberEdges].sort((a, b) => a.fromKey.
|
|
3849
|
-
const path27 = [...keys].sort().join(" \u2192 ");
|
|
3846
|
+
const anchor = [...memberEdges].sort((a, b) => byCodeUnit(a.fromKey, b.fromKey) || byCodeUnit(a.impl.id, b.impl.id) || a.stepNumber - b.stepNumber)[0];
|
|
3847
|
+
const path27 = [...keys].sort(byCodeUnit).join(" \u2192 ");
|
|
3850
3848
|
ctx.addIssue(
|
|
3851
3849
|
"warning",
|
|
3852
3850
|
"UNCONDITIONAL_CALL_CYCLE",
|
|
@@ -4098,14 +4096,15 @@ var init_portal_endpoints = __esm({
|
|
|
4098
4096
|
continue;
|
|
4099
4097
|
}
|
|
4100
4098
|
for (const intf of compInterfaces) {
|
|
4099
|
+
const isIntfDraft = intf.status === "draft" || intf.status === "design";
|
|
4101
4100
|
for (const m of intf.methods) {
|
|
4102
4101
|
if (m.endpoint) {
|
|
4103
4102
|
ctx.addIssue(
|
|
4104
4103
|
"error",
|
|
4105
4104
|
"ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT",
|
|
4106
4105
|
`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
|
|
4106
|
+
intf.id,
|
|
4107
|
+
isDraftCtx || isIntfDraft
|
|
4109
4108
|
);
|
|
4110
4109
|
}
|
|
4111
4110
|
}
|
|
@@ -4236,7 +4235,7 @@ var init_stereotype_dependencies = __esm({
|
|
|
4236
4235
|
{ code: "ARCHITECTURE_VIOLATION_ADAPTER_DEP", defaultSeverity: "error", summary: "Adapter depending on Orchestrators or Stores" },
|
|
4237
4236
|
{ code: "ARCHITECTURE_VIOLATION_INDEX_DEP", defaultSeverity: "error", summary: "Index depending on anything but its Store or an Adapter" },
|
|
4238
4237
|
{ code: "ARCHITECTURE_VIOLATION_VIEW_DEP", defaultSeverity: "error", summary: "View depending on logic/persistence layers" },
|
|
4239
|
-
{ code: "PORTAL_WRITE_SHORTCUT", defaultSeverity: "error", summary: "Portal narrative
|
|
4238
|
+
{ 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
4239
|
],
|
|
4241
4240
|
check(ctx) {
|
|
4242
4241
|
for (const comp of ctx.components) {
|
|
@@ -4308,10 +4307,11 @@ var init_stereotype_dependencies = __esm({
|
|
|
4308
4307
|
crossDraft
|
|
4309
4308
|
);
|
|
4310
4309
|
} else if (depComp.componentType !== "Portal" && depComp.componentType !== "Gateway") {
|
|
4310
|
+
const crosser = comp.componentType === "Adapter" ? "client Adapter" : comp.componentType;
|
|
4311
4311
|
ctx.addIssue(
|
|
4312
4312
|
"error",
|
|
4313
4313
|
"CROSS_SUBSYSTEM_TARGET_NON_PORTAL",
|
|
4314
|
-
`Boundary violation:
|
|
4314
|
+
`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
4315
|
comp.id,
|
|
4316
4316
|
crossDraft
|
|
4317
4317
|
);
|
|
@@ -4435,6 +4435,23 @@ var init_stereotype_dependencies = __esm({
|
|
|
4435
4435
|
}
|
|
4436
4436
|
}
|
|
4437
4437
|
}
|
|
4438
|
+
for (const comp of ctx.components) {
|
|
4439
|
+
if (comp.componentType !== "Portal" || !comp.dispatch || comp.dispatch.length === 0) continue;
|
|
4440
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
4441
|
+
for (const binding of comp.dispatch) {
|
|
4442
|
+
const target = ctx.componentMap.get(binding.component);
|
|
4443
|
+
if (!target || target.componentType !== "Repository" && target.componentType !== "Index") continue;
|
|
4444
|
+
const targetMethod = ctx.interfaceMethodsOf(target.id).find((m) => m.name === binding.method);
|
|
4445
|
+
if (targetMethod?.effect !== "write") continue;
|
|
4446
|
+
ctx.addIssue(
|
|
4447
|
+
"error",
|
|
4448
|
+
"PORTAL_WRITE_SHORTCUT",
|
|
4449
|
+
`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.`,
|
|
4450
|
+
comp.id,
|
|
4451
|
+
isDraftCtx || ctx.isComponentDraft(target.id)
|
|
4452
|
+
);
|
|
4453
|
+
}
|
|
4454
|
+
}
|
|
4438
4455
|
}
|
|
4439
4456
|
};
|
|
4440
4457
|
}
|
|
@@ -4458,7 +4475,7 @@ var init_pattern_ownership = __esm({
|
|
|
4458
4475
|
{ code: "REPOSITORY_CONTAINMENT", defaultSeverity: "error", summary: "Repository owning a non Store/Registry/Index/Adapter member" },
|
|
4459
4476
|
{ code: "GATEWAY_CONTAINMENT", defaultSeverity: "error", summary: "Gateway owning a non Portal/Orchestrator/Specialist member" },
|
|
4460
4477
|
{ 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
|
|
4478
|
+
{ code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent not owning exactly one Portal facade, or owning no children to route to" },
|
|
4462
4479
|
{ code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" },
|
|
4463
4480
|
{ 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
4481
|
{ 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 +4497,15 @@ var init_pattern_ownership = __esm({
|
|
|
4480
4497
|
ctx.addIssue("error", "INVALID_OWNED_MEMBER", `Component "${comp.id}" owns "${memberId}" which does not exist.`, comp.id, isDraftCtx);
|
|
4481
4498
|
continue;
|
|
4482
4499
|
}
|
|
4500
|
+
if (!pattern) continue;
|
|
4483
4501
|
if (isPattern(member)) {
|
|
4484
4502
|
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
4503
|
}
|
|
4486
|
-
const
|
|
4487
|
-
if (
|
|
4488
|
-
ctx.addIssue("error", "SHARED_OWNED_MEMBER", `Block "${memberId}" is owned by both "${
|
|
4504
|
+
const firstOwner = ownedBy.get(memberId);
|
|
4505
|
+
if (firstOwner && firstOwner !== comp.id) {
|
|
4506
|
+
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
4507
|
}
|
|
4490
|
-
ownedBy.set(memberId, comp.id);
|
|
4508
|
+
if (!firstOwner) ownedBy.set(memberId, comp.id);
|
|
4491
4509
|
}
|
|
4492
4510
|
if (comp.componentType === "Repository") {
|
|
4493
4511
|
const allowed = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Adapter"]);
|
|
@@ -4522,14 +4540,14 @@ var init_pattern_ownership = __esm({
|
|
|
4522
4540
|
}
|
|
4523
4541
|
}
|
|
4524
4542
|
if (comp.componentType === "RouterComponent") {
|
|
4525
|
-
let
|
|
4543
|
+
let portals = 0;
|
|
4526
4544
|
let hasChildren = false;
|
|
4527
4545
|
for (const memberId of comp.owns) {
|
|
4528
4546
|
const t = ctx.componentMap.get(memberId)?.componentType;
|
|
4529
|
-
if (t === "Portal")
|
|
4547
|
+
if (t === "Portal") portals++;
|
|
4530
4548
|
else if (t) hasChildren = true;
|
|
4531
4549
|
}
|
|
4532
|
-
if (
|
|
4550
|
+
if (portals !== 1) {
|
|
4533
4551
|
ctx.addIssue("error", "ROUTER_COMPONENT_CONTAINMENT", `RouterComponent "${comp.id}" must own exactly one Portal component to act as its facade.`, comp.id, isDraftCtx);
|
|
4534
4552
|
}
|
|
4535
4553
|
if (!hasChildren) {
|
|
@@ -5031,54 +5049,43 @@ var init_dependency_cycles = __esm({
|
|
|
5031
5049
|
"use strict";
|
|
5032
5050
|
cyclesRule = {
|
|
5033
5051
|
name: "dependency-cycles",
|
|
5034
|
-
description: "The component dependsOn graph must be a DAG; the first
|
|
5052
|
+
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
5053
|
codes: [
|
|
5036
5054
|
{ code: "CIRCULAR_DEPENDENCY", defaultSeverity: "error", summary: "Circular dependency between components" }
|
|
5037
5055
|
],
|
|
5038
5056
|
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)) {
|
|
5057
|
+
const cycleFrom = (startId) => {
|
|
5058
|
+
const visited = /* @__PURE__ */ new Set([startId]);
|
|
5059
|
+
const pathTrace = [startId];
|
|
5060
|
+
const search = (compId) => {
|
|
5061
|
+
for (const depId of ctx.componentMap.get(compId)?.dependsOn ?? []) {
|
|
5062
|
+
if (depId === startId) {
|
|
5055
5063
|
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
5064
|
return true;
|
|
5069
5065
|
}
|
|
5066
|
+
if (visited.has(depId)) continue;
|
|
5067
|
+
visited.add(depId);
|
|
5068
|
+
pathTrace.push(depId);
|
|
5069
|
+
if (search(depId)) return true;
|
|
5070
|
+
pathTrace.pop();
|
|
5070
5071
|
}
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
return false;
|
|
5072
|
+
return false;
|
|
5073
|
+
};
|
|
5074
|
+
return search(startId) ? pathTrace : void 0;
|
|
5075
5075
|
};
|
|
5076
5076
|
for (const comp of ctx.components) {
|
|
5077
|
-
if (!
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5077
|
+
if (!ctx.isSpecInScope(comp.id)) continue;
|
|
5078
|
+
const cycle = cycleFrom(comp.id);
|
|
5079
|
+
if (!cycle) continue;
|
|
5080
|
+
const closing = cycle[cycle.length - 2];
|
|
5081
|
+
ctx.addIssue(
|
|
5082
|
+
"error",
|
|
5083
|
+
"CIRCULAR_DEPENDENCY",
|
|
5084
|
+
`Circular dependency detected: ${cycle.join(" -> ")}`,
|
|
5085
|
+
comp.id,
|
|
5086
|
+
ctx.isComponentDraft(comp.id) || ctx.isComponentDraft(closing)
|
|
5087
|
+
);
|
|
5088
|
+
return;
|
|
5082
5089
|
}
|
|
5083
5090
|
}
|
|
5084
5091
|
};
|
|
@@ -5207,19 +5214,18 @@ var init_dispatch_tables = __esm({
|
|
|
5207
5214
|
);
|
|
5208
5215
|
continue;
|
|
5209
5216
|
}
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
}
|
|
5217
|
+
const boundMethod = ctx.interfaceMethodsOf(binding.component).find((m) => m.name === binding.method);
|
|
5218
|
+
if (!boundMethod) continue;
|
|
5219
|
+
const declared = new Set(boundMethod.guarantees ?? []);
|
|
5220
|
+
for (const g of step.assertsGuarantees ?? []) {
|
|
5221
|
+
if (!declared.has(g)) {
|
|
5222
|
+
ctx.addIssue(
|
|
5223
|
+
"warning",
|
|
5224
|
+
"NARRATIVE_SEMANTIC_UNBACKED",
|
|
5225
|
+
`${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.`,
|
|
5226
|
+
impl.id,
|
|
5227
|
+
isDraftCtx || ctx.isComponentDraft(binding.component)
|
|
5228
|
+
);
|
|
5223
5229
|
}
|
|
5224
5230
|
}
|
|
5225
5231
|
}
|
|
@@ -5424,12 +5430,11 @@ var init_unused_detection = __esm({
|
|
|
5424
5430
|
const invokedBySeeds = [];
|
|
5425
5431
|
for (const intf of ctx.interfaces) {
|
|
5426
5432
|
if (!ctx.componentMap.has(intf.component)) continue;
|
|
5427
|
-
const comp = ctx.componentMap.get(intf.component);
|
|
5428
5433
|
for (const m of intf.methods) {
|
|
5429
5434
|
if (!m.invokedBy) continue;
|
|
5430
5435
|
invokedBySeeds.push({ compId: intf.component, methodName: m.name });
|
|
5431
5436
|
if (!ctx.isSpecInScope(intf.id)) continue;
|
|
5432
|
-
const isDraftCtx =
|
|
5437
|
+
const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
|
|
5433
5438
|
if (!passesIntentFloor(m.invokedBy.caller, m.name)) {
|
|
5434
5439
|
ctx.addIssue(
|
|
5435
5440
|
"warning",
|
|
@@ -5454,7 +5459,7 @@ var init_unused_detection = __esm({
|
|
|
5454
5459
|
for (const comp of ctx.components) {
|
|
5455
5460
|
if (!ctx.isSpecInScope(comp.id)) continue;
|
|
5456
5461
|
if (!reach.reachesComponent(comp.id)) {
|
|
5457
|
-
const isDraftCtx =
|
|
5462
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
5458
5463
|
ctx.addIssue(
|
|
5459
5464
|
"warning",
|
|
5460
5465
|
"UNUSED_COMPONENT",
|
|
@@ -5466,12 +5471,12 @@ var init_unused_detection = __esm({
|
|
|
5466
5471
|
for (const intf of ctx.interfacesByComponent.get(comp.id) ?? []) {
|
|
5467
5472
|
for (const m of intf.methods) {
|
|
5468
5473
|
if (!reach.reachesMethod(comp.id, m.name)) {
|
|
5469
|
-
const isDraftCtx =
|
|
5474
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
|
|
5470
5475
|
ctx.addIssue(
|
|
5471
5476
|
"warning",
|
|
5472
5477
|
"UNUSED_METHOD",
|
|
5473
5478
|
`Method "${m.name}" on component "${comp.id}" is defined but never called by any narrative step.`,
|
|
5474
|
-
|
|
5479
|
+
intf.id,
|
|
5475
5480
|
isDraftCtx
|
|
5476
5481
|
);
|
|
5477
5482
|
}
|
|
@@ -5582,20 +5587,20 @@ var init_durability_round_trip = __esm({
|
|
|
5582
5587
|
});
|
|
5583
5588
|
|
|
5584
5589
|
// src/core/rules/wiring/untyped-seams.ts
|
|
5585
|
-
function
|
|
5586
|
-
|
|
5587
|
-
return (m ? m[1] : typeRef).trim();
|
|
5590
|
+
function namesBareType(refs) {
|
|
5591
|
+
return refs.some((ref) => BARE_SEAM_TYPES.has(ref.toLowerCase()));
|
|
5588
5592
|
}
|
|
5589
5593
|
var BARE_SEAM_TYPES, untypedSeamRule;
|
|
5590
5594
|
var init_untyped_seams = __esm({
|
|
5591
5595
|
"src/core/rules/wiring/untyped-seams.ts"() {
|
|
5592
5596
|
"use strict";
|
|
5597
|
+
init_models();
|
|
5593
5598
|
BARE_SEAM_TYPES = /* @__PURE__ */ new Set(["json", "any", "unknown", "object"]);
|
|
5594
5599
|
untypedSeamRule = {
|
|
5595
5600
|
name: "untyped-seams",
|
|
5596
|
-
description: "Methods on a subsystem's published components (its public surface) should not take or return bare Json/any/unknown
|
|
5601
|
+
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
5602
|
codes: [
|
|
5598
|
-
{ code: "UNTYPED_SEAM", defaultSeverity: "warning", summary: "Bare Json/any/unknown parameter or return crossing a subsystem public surface" }
|
|
5603
|
+
{ code: "UNTYPED_SEAM", defaultSeverity: "warning", summary: "Bare Json/any/unknown/object parameter or return crossing a subsystem public surface" }
|
|
5599
5604
|
],
|
|
5600
5605
|
check(ctx) {
|
|
5601
5606
|
for (const sub of ctx.subsystems) {
|
|
@@ -5609,14 +5614,17 @@ var init_untyped_seams = __esm({
|
|
|
5609
5614
|
const isDraftCtx = ctx.isComponentDraft(compId) || intf.status === "draft" || intf.status === "design";
|
|
5610
5615
|
for (const m of intf.methods) {
|
|
5611
5616
|
const offenders = [];
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5617
|
+
if (m.params && m.params.length > 0) {
|
|
5618
|
+
for (const p of m.params) {
|
|
5619
|
+
if (namesBareType(extractTypeIdentifiers(p.type))) {
|
|
5620
|
+
offenders.push(`param "${p.name}: ${p.type}"`);
|
|
5621
|
+
}
|
|
5615
5622
|
}
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5623
|
+
if (namesBareType(extractTypeIdentifiers(m.returns ?? ""))) {
|
|
5624
|
+
offenders.push(`return "${m.returns}"`);
|
|
5625
|
+
}
|
|
5626
|
+
} else if (namesBareType(methodTypeRefs(m))) {
|
|
5627
|
+
offenders.push(`signature "${m.signature}"`);
|
|
5620
5628
|
}
|
|
5621
5629
|
if (offenders.length) {
|
|
5622
5630
|
ctx.addIssue(
|
|
@@ -5641,15 +5649,19 @@ function componentOfImpl(ctx, implContract) {
|
|
|
5641
5649
|
const contract = ctx.interfaceMap.get(implContract);
|
|
5642
5650
|
return contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
5643
5651
|
}
|
|
5644
|
-
|
|
5652
|
+
function withoutQuotedText(text) {
|
|
5653
|
+
return text.replace(QUOTED_SPAN, " ");
|
|
5654
|
+
}
|
|
5655
|
+
var DATA_STEREOTYPES, CLAIM_PHRASES, QUOTED_SPAN, proseClaimRule;
|
|
5645
5656
|
var init_prose_claims = __esm({
|
|
5646
5657
|
"src/core/rules/wiring/prose-claims.ts"() {
|
|
5647
5658
|
"use strict";
|
|
5648
5659
|
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;
|
|
5660
|
+
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;
|
|
5661
|
+
QUOTED_SPAN = /"[^"]*"|“[^”]*”|`[^`]*`|(?<![\p{L}\p{N}])['‘](?:[^'‘’]|(?<=[\p{L}\p{N}])['’](?=[\p{L}\p{N}]))*['’](?![\p{L}\p{N}])/gu;
|
|
5650
5662
|
proseClaimRule = {
|
|
5651
5663
|
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
|
|
5664
|
+
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
5665
|
codes: [
|
|
5654
5666
|
{ code: "UNREALIZED_CLAIM", defaultSeverity: "warning", summary: "Durability/side-effect claim in prose with no matching structural edge" }
|
|
5655
5667
|
],
|
|
@@ -5660,11 +5672,11 @@ var init_prose_claims = __esm({
|
|
|
5660
5672
|
if (DATA_STEREOTYPES.has(comp.componentType)) continue;
|
|
5661
5673
|
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
5662
5674
|
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;
|
|
5675
|
+
const stepClaims = implMethod.narrative.filter((step) => step.type === "local").map((step) => ({ step, claim: CLAIM_PHRASES.exec(withoutQuotedText(step.description)) })).filter((c) => c.claim !== null);
|
|
5676
|
+
const intentClaim = implMethod.intent ? CLAIM_PHRASES.exec(withoutQuotedText(implMethod.intent)) : null;
|
|
5665
5677
|
if (!stepClaims.length && !intentClaim) continue;
|
|
5666
5678
|
const hasDataEdge = implMethod.narrative.some((step) => {
|
|
5667
|
-
if (step.type !== "call" && step.type !== "dispatch") return false;
|
|
5679
|
+
if (step.type !== "call" && step.type !== "register" && step.type !== "dispatch") return false;
|
|
5668
5680
|
if (!step.targetComponent) return false;
|
|
5669
5681
|
const target = ctx.componentMap.get(step.targetComponent);
|
|
5670
5682
|
if (target && DATA_STEREOTYPES.has(target.componentType)) return true;
|
|
@@ -5677,7 +5689,7 @@ var init_prose_claims = __esm({
|
|
|
5677
5689
|
ctx.addIssue(
|
|
5678
5690
|
"warning",
|
|
5679
5691
|
"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.`,
|
|
5692
|
+
`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
5693
|
impl.id,
|
|
5682
5694
|
isDraftCtx
|
|
5683
5695
|
);
|
|
@@ -5736,6 +5748,10 @@ function stepAsserts(impl, methodName, type, invariantId) {
|
|
|
5736
5748
|
(step) => (step.assertsInvariants ?? []).some((ref) => refMatchesInvariant(ref, type, invariantId))
|
|
5737
5749
|
);
|
|
5738
5750
|
}
|
|
5751
|
+
function isEntityDraft(t, ctx) {
|
|
5752
|
+
const sub = t.subsystem ? ctx.subsystems.find((s) => s.id === t.subsystem) : void 0;
|
|
5753
|
+
return sub !== void 0 && isDraftSubsystem(sub);
|
|
5754
|
+
}
|
|
5739
5755
|
function resolveComponentClass(t, ctx) {
|
|
5740
5756
|
if (!t.componentClass) return void 0;
|
|
5741
5757
|
const direct = ctx.componentMap.get(t.componentClass);
|
|
@@ -5762,6 +5778,7 @@ var init_invariant_backing = __esm({
|
|
|
5762
5778
|
for (const t of ctx.types) {
|
|
5763
5779
|
const invariants = t.invariants ?? [];
|
|
5764
5780
|
if (invariants.length === 0) continue;
|
|
5781
|
+
const entityDraft = isEntityDraft(t, ctx);
|
|
5765
5782
|
const seen = /* @__PURE__ */ new Set();
|
|
5766
5783
|
for (const inv of invariants) {
|
|
5767
5784
|
if (seen.has(inv.id)) {
|
|
@@ -5769,7 +5786,8 @@ var init_invariant_backing = __esm({
|
|
|
5769
5786
|
"error",
|
|
5770
5787
|
"DUPLICATE_INVARIANT_ID",
|
|
5771
5788
|
`Entity "${t.id}" declares invariant id "${inv.id}" more than once \u2014 invariant ids must be unique within the entity.`,
|
|
5772
|
-
t.id
|
|
5789
|
+
t.id,
|
|
5790
|
+
entityDraft
|
|
5773
5791
|
);
|
|
5774
5792
|
}
|
|
5775
5793
|
seen.add(inv.id);
|
|
@@ -5780,7 +5798,8 @@ var init_invariant_backing = __esm({
|
|
|
5780
5798
|
"warning",
|
|
5781
5799
|
"INVARIANT_UNANCHORED",
|
|
5782
5800
|
`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
|
|
5801
|
+
t.id,
|
|
5802
|
+
entityDraft
|
|
5784
5803
|
);
|
|
5785
5804
|
continue;
|
|
5786
5805
|
}
|
|
@@ -5794,13 +5813,13 @@ var init_invariant_backing = __esm({
|
|
|
5794
5813
|
"INVARIANT_UNANCHORED",
|
|
5795
5814
|
`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
5815
|
t.id,
|
|
5797
|
-
ctx.isComponentDraft(comp.id)
|
|
5816
|
+
entityDraft || ctx.isComponentDraft(comp.id)
|
|
5798
5817
|
);
|
|
5799
5818
|
continue;
|
|
5800
5819
|
}
|
|
5801
5820
|
for (const { intf, method } of writeMethods) {
|
|
5802
5821
|
for (const impl of ctx.implementationsByContract.get(intf.id) ?? []) {
|
|
5803
|
-
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
5822
|
+
const isDraftCtx = entityDraft || ctx.isImplementationDraft(impl);
|
|
5804
5823
|
for (const inv of invariants) {
|
|
5805
5824
|
if (stepAsserts(impl, method.name, t, inv.id)) continue;
|
|
5806
5825
|
ctx.addIssue(
|
|
@@ -5838,30 +5857,35 @@ var init_invariant_backing = __esm({
|
|
|
5838
5857
|
|
|
5839
5858
|
// src/core/rules/wiring/event-topology.ts
|
|
5840
5859
|
function collectEnds(ctx) {
|
|
5841
|
-
const emitters =
|
|
5842
|
-
const subscribers =
|
|
5860
|
+
const emitters = /* @__PURE__ */ new Map();
|
|
5861
|
+
const subscribers = /* @__PURE__ */ new Map();
|
|
5862
|
+
const addEnd = (ends, topic, compId, via) => {
|
|
5863
|
+
const key = `${compId}#${topic}`;
|
|
5864
|
+
const end = ends.get(key);
|
|
5865
|
+
if (!end) ends.set(key, { topic, compId, vias: [via] });
|
|
5866
|
+
else if (!end.vias.includes(via)) end.vias.push(via);
|
|
5867
|
+
};
|
|
5843
5868
|
for (const comp of ctx.components) {
|
|
5844
5869
|
for (const e of comp.emits ?? []) {
|
|
5845
|
-
emitters
|
|
5870
|
+
addEnd(emitters, e.topic, comp.id, "emits declaration");
|
|
5846
5871
|
}
|
|
5847
5872
|
for (const s of comp.subscribesTo ?? []) {
|
|
5848
|
-
subscribers
|
|
5873
|
+
addEnd(subscribers, s.topic, comp.id, "subscribesTo declaration");
|
|
5849
5874
|
}
|
|
5850
5875
|
}
|
|
5851
5876
|
for (const intf of ctx.interfaces) {
|
|
5852
5877
|
for (const method of intf.methods) {
|
|
5853
5878
|
const ep = method.endpoint;
|
|
5854
5879
|
if (!ep || ep.transport !== "MessageBus") continue;
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
else subscribers.push(end);
|
|
5880
|
+
addEnd(
|
|
5881
|
+
ep.direction === "publish" ? emitters : subscribers,
|
|
5882
|
+
ep.topic,
|
|
5883
|
+
intf.component,
|
|
5884
|
+
`MessageBus endpoint on ${intf.id}.${method.name}`
|
|
5885
|
+
);
|
|
5862
5886
|
}
|
|
5863
5887
|
}
|
|
5864
|
-
return { emitters, subscribers };
|
|
5888
|
+
return { emitters: [...emitters.values()], subscribers: [...subscribers.values()] };
|
|
5865
5889
|
}
|
|
5866
5890
|
var eventTopologyRule;
|
|
5867
5891
|
var init_event_topology = __esm({
|
|
@@ -5869,7 +5893,7 @@ var init_event_topology = __esm({
|
|
|
5869
5893
|
"use strict";
|
|
5870
5894
|
eventTopologyRule = {
|
|
5871
5895
|
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.",
|
|
5896
|
+
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
5897
|
codes: [
|
|
5874
5898
|
{ code: "UNCONSUMED_TOPIC", defaultSeverity: "warning", summary: "Topic is emitted but nothing in the tree subscribes to it" },
|
|
5875
5899
|
{ code: "UNSOURCED_SUBSCRIPTION", defaultSeverity: "warning", summary: "Topic is subscribed to but nothing in the tree emits it" }
|
|
@@ -5884,7 +5908,7 @@ var init_event_topology = __esm({
|
|
|
5884
5908
|
ctx.addIssue(
|
|
5885
5909
|
"warning",
|
|
5886
5910
|
"UNCONSUMED_TOPIC",
|
|
5887
|
-
`Component "${e.compId}" emits topic "${e.topic}" (${e.
|
|
5911
|
+
`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
5912
|
e.compId,
|
|
5889
5913
|
ctx.isComponentDraft(e.compId)
|
|
5890
5914
|
);
|
|
@@ -5894,7 +5918,7 @@ var init_event_topology = __esm({
|
|
|
5894
5918
|
ctx.addIssue(
|
|
5895
5919
|
"warning",
|
|
5896
5920
|
"UNSOURCED_SUBSCRIPTION",
|
|
5897
|
-
`Component "${s.compId}" subscribes to topic "${s.topic}" (${s.
|
|
5921
|
+
`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
5922
|
s.compId,
|
|
5899
5923
|
ctx.isComponentDraft(s.compId)
|
|
5900
5924
|
);
|
|
@@ -5913,13 +5937,13 @@ var init_structural_conformance = __esm({
|
|
|
5913
5937
|
quoteList = (names) => names.map((n) => `"${n}"`).join(", ");
|
|
5914
5938
|
structuralConformanceRule = {
|
|
5915
5939
|
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
|
|
5940
|
+
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
5941
|
codes: [
|
|
5918
5942
|
{ 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
5943
|
{ 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
5944
|
{ 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
5945
|
{ 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
|
|
5946
|
+
{ 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
5947
|
{ 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
5948
|
{ code: "CONFORMANCE_DEGRADED", defaultSeverity: "warning", summary: "TypeScript/JavaScript files were analyzed below exact grade (compiler not resolvable) \u2014 dependency conformance skips them" }
|
|
5925
5949
|
],
|
|
@@ -5929,7 +5953,7 @@ var init_structural_conformance = __esm({
|
|
|
5929
5953
|
lookups.set(pathKey(facts.path), {
|
|
5930
5954
|
declared: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames]),
|
|
5931
5955
|
anchored: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames, ...facts.anchoredNames]),
|
|
5932
|
-
|
|
5956
|
+
findingAnchors: new Set(facts.anchoredNames),
|
|
5933
5957
|
facts
|
|
5934
5958
|
});
|
|
5935
5959
|
}
|
|
@@ -6049,11 +6073,11 @@ var init_structural_conformance = __esm({
|
|
|
6049
6073
|
if (!checked) continue;
|
|
6050
6074
|
const { file, lookup } = checked;
|
|
6051
6075
|
for (const finding of method.findings) {
|
|
6052
|
-
if (lookup.
|
|
6076
|
+
if (lookup.findingAnchors.has(finding.code)) continue;
|
|
6053
6077
|
ctx.addIssue(
|
|
6054
6078
|
"warning",
|
|
6055
6079
|
"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.`,
|
|
6080
|
+
`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
6081
|
impl.id,
|
|
6058
6082
|
draft
|
|
6059
6083
|
);
|
|
@@ -6169,12 +6193,18 @@ var init_hidden_state = __esm({
|
|
|
6169
6193
|
check(ctx) {
|
|
6170
6194
|
const byPath = /* @__PURE__ */ new Map();
|
|
6171
6195
|
for (const impl of ctx.implementations) {
|
|
6172
|
-
if (impl.conformance === "off") continue;
|
|
6173
6196
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
6174
6197
|
const component = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
6175
6198
|
if (!component) continue;
|
|
6176
6199
|
if (ctx.isInChainedSubproject(component.subsystem)) continue;
|
|
6177
|
-
|
|
6200
|
+
const trusted = [];
|
|
6201
|
+
if (impl.sourcePath && impl.conformance !== "off") trusted.push(impl.sourcePath);
|
|
6202
|
+
for (const method of impl.methods) {
|
|
6203
|
+
if ((method.conformance ?? impl.conformance) === "off") continue;
|
|
6204
|
+
const file = methodSourceFile(method, impl.sourcePath);
|
|
6205
|
+
if (file) trusted.push(file);
|
|
6206
|
+
}
|
|
6207
|
+
for (const file of trusted) {
|
|
6178
6208
|
const key = pathKey(file);
|
|
6179
6209
|
const list = byPath.get(key) ?? [];
|
|
6180
6210
|
if (list.some((m) => m.impl === impl)) continue;
|
|
@@ -6325,7 +6355,7 @@ var init_dependency_conformance = __esm({
|
|
|
6325
6355
|
const fromFiles = filesByComponent.get(component.id);
|
|
6326
6356
|
if (!fromFiles) continue;
|
|
6327
6357
|
const isMountingDeclarer = component.componentType === "Portal" || component.componentType === "Observer";
|
|
6328
|
-
const declaredTargets = [...component.dependsOn, ...component.owns ?? []];
|
|
6358
|
+
const declaredTargets = [.../* @__PURE__ */ new Set([...component.dependsOn, ...component.owns ?? []])];
|
|
6329
6359
|
for (const targetId of declaredTargets) {
|
|
6330
6360
|
const target = ctx.componentMap.get(targetId);
|
|
6331
6361
|
if (!target) continue;
|
|
@@ -6346,11 +6376,14 @@ var init_dependency_conformance = __esm({
|
|
|
6346
6376
|
}
|
|
6347
6377
|
const impls = implsByComponent.get(component.id) ?? [];
|
|
6348
6378
|
const anchor = impls[0];
|
|
6379
|
+
const declaredDependsOn = component.dependsOn.includes(targetId);
|
|
6380
|
+
const declaredOwns = (component.owns ?? []).includes(targetId);
|
|
6381
|
+
const relation = declaredDependsOn && declaredOwns ? "dependsOn and owns" : declaredDependsOn ? "dependsOn" : "owns";
|
|
6349
6382
|
const draft = ctx.isComponentDraft(component.id) || ctx.isComponentDraft(targetId) || impls.some((i) => ctx.isImplementationDraft(i));
|
|
6350
6383
|
ctx.addIssue(
|
|
6351
6384
|
"warning",
|
|
6352
6385
|
"UNREALIZED_DEPENDENCY",
|
|
6353
|
-
`Component "${component.id}" declares ${
|
|
6386
|
+
`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
6387
|
anchor?.id ?? component.id,
|
|
6355
6388
|
draft
|
|
6356
6389
|
);
|
|
@@ -6382,28 +6415,28 @@ var init_integration_conformance = __esm({
|
|
|
6382
6415
|
const filesByComponent = /* @__PURE__ */ new Map();
|
|
6383
6416
|
const filesBySubsystem = /* @__PURE__ */ new Map();
|
|
6384
6417
|
const implsByComponent = /* @__PURE__ */ new Map();
|
|
6418
|
+
const adopted = /* @__PURE__ */ new Set();
|
|
6419
|
+
const exists = (p) => {
|
|
6420
|
+
const status = factsByPath.get(p)?.status;
|
|
6421
|
+
return status === "analyzed" || status === "unreadable";
|
|
6422
|
+
};
|
|
6385
6423
|
for (const impl of ctx.implementations) {
|
|
6386
6424
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
6387
6425
|
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
6388
|
-
if (!comp) continue;
|
|
6426
|
+
if (!comp || ctx.isInChainedSubproject(comp.subsystem)) continue;
|
|
6389
6427
|
if (!implsByComponent.has(comp.id)) implsByComponent.set(comp.id, []);
|
|
6390
6428
|
implsByComponent.get(comp.id).push(impl);
|
|
6391
6429
|
for (const file of implementationSourceFiles(impl)) {
|
|
6392
6430
|
const p = pathKey(file);
|
|
6431
|
+
if (!exists(p)) continue;
|
|
6393
6432
|
if (!filesByComponent.has(comp.id)) filesByComponent.set(comp.id, /* @__PURE__ */ new Set());
|
|
6394
6433
|
filesByComponent.get(comp.id).add(p);
|
|
6395
6434
|
if (!filesBySubsystem.has(comp.subsystem)) filesBySubsystem.set(comp.subsystem, /* @__PURE__ */ new Set());
|
|
6396
6435
|
filesBySubsystem.get(comp.subsystem).add(p);
|
|
6397
6436
|
}
|
|
6437
|
+
if (impl.simPath) adopted.add(comp.subsystem);
|
|
6398
6438
|
}
|
|
6399
6439
|
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
6440
|
const reachFrom = (start) => {
|
|
6408
6441
|
const seen = /* @__PURE__ */ new Set([start]);
|
|
6409
6442
|
const stack = [start];
|
|
@@ -6600,8 +6633,8 @@ var init_coupling_health = __esm({
|
|
|
6600
6633
|
);
|
|
6601
6634
|
}
|
|
6602
6635
|
}
|
|
6603
|
-
const threshold = ctx.rules?.complexity?.maxComponentDependencies ?? DEFAULT_GOD_COMPONENT_THRESHOLD;
|
|
6604
6636
|
for (const comp of ctx.components) {
|
|
6637
|
+
const threshold = ctx.complexityConfigFor(comp.subsystem)?.maxComponentDependencies ?? DEFAULT_GOD_COMPONENT_THRESHOLD;
|
|
6605
6638
|
if (comp.dependsOn.length > threshold) {
|
|
6606
6639
|
ctx.addIssue(
|
|
6607
6640
|
"warning",
|
|
@@ -6729,7 +6762,7 @@ var init_target_language = __esm({
|
|
|
6729
6762
|
if (!lang) continue;
|
|
6730
6763
|
const gaps = gapsFor(lang);
|
|
6731
6764
|
if (Object.keys(gaps).length === 0) continue;
|
|
6732
|
-
const isDraftCtx =
|
|
6765
|
+
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
6733
6766
|
for (const implMethod of impl.methods) {
|
|
6734
6767
|
for (const step of implMethod.narrative) {
|
|
6735
6768
|
const constructs = [
|
|
@@ -6831,7 +6864,7 @@ var init_technology_boundaries = __esm({
|
|
|
6831
6864
|
"TECH_ON_LOGIC_COMPONENT",
|
|
6832
6865
|
`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
6866
|
impl.id,
|
|
6834
|
-
ctx.
|
|
6867
|
+
ctx.isImplementationDraft(impl)
|
|
6835
6868
|
);
|
|
6836
6869
|
}
|
|
6837
6870
|
const compSet = ownsClosure(ownershipRoot(comp.id));
|
|
@@ -6869,7 +6902,7 @@ var init_technology_boundaries = __esm({
|
|
|
6869
6902
|
"VENDOR_NAME_IN_CONTRACT",
|
|
6870
6903
|
`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
6904
|
intf.id,
|
|
6872
|
-
ctx.isComponentDraft(intf.component)
|
|
6905
|
+
ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design"
|
|
6873
6906
|
);
|
|
6874
6907
|
}
|
|
6875
6908
|
}
|
|
@@ -6900,7 +6933,8 @@ var init_technology_boundaries = __esm({
|
|
|
6900
6933
|
"warning",
|
|
6901
6934
|
"TECH_LEAKAGE",
|
|
6902
6935
|
`Subsystem "${sub.id}" references "${home.label}" outside the technology's owning boundary (component ${ownersDesc(home)}).`,
|
|
6903
|
-
sub.id
|
|
6936
|
+
sub.id,
|
|
6937
|
+
isDraftSubsystem(sub)
|
|
6904
6938
|
);
|
|
6905
6939
|
}
|
|
6906
6940
|
}
|
|
@@ -6913,7 +6947,7 @@ var init_technology_boundaries = __esm({
|
|
|
6913
6947
|
"TECH_LEAKAGE",
|
|
6914
6948
|
`Interface "${intf.id}" describes "${home.label}" outside its owning boundary (component ${ownersDesc(home)}) \u2014 consumers must not know backend specifics.`,
|
|
6915
6949
|
intf.id,
|
|
6916
|
-
ctx.isComponentDraft(intf.component)
|
|
6950
|
+
ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design"
|
|
6917
6951
|
);
|
|
6918
6952
|
}
|
|
6919
6953
|
}
|
|
@@ -6933,7 +6967,8 @@ var init_technology_boundaries = __esm({
|
|
|
6933
6967
|
"warning",
|
|
6934
6968
|
"TECH_LEAKAGE",
|
|
6935
6969
|
`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
|
|
6970
|
+
impl.id,
|
|
6971
|
+
ctx.isImplementationDraft(impl)
|
|
6937
6972
|
);
|
|
6938
6973
|
}
|
|
6939
6974
|
}
|
|
@@ -6996,6 +7031,7 @@ var casingPatterns, isAllUppercase, namingRule;
|
|
|
6996
7031
|
var init_naming_conventions = __esm({
|
|
6997
7032
|
"src/core/rules/heuristic/naming-conventions.ts"() {
|
|
6998
7033
|
"use strict";
|
|
7034
|
+
init_models();
|
|
6999
7035
|
casingPatterns = {
|
|
7000
7036
|
camelCase: /^[a-z][a-zA-Z0-9]*$/,
|
|
7001
7037
|
PascalCase: /^[A-Z][a-zA-Z0-9]*$/,
|
|
@@ -7016,9 +7052,10 @@ var init_naming_conventions = __esm({
|
|
|
7016
7052
|
for (const sub of ctx.subsystems) {
|
|
7017
7053
|
const namingConfig = ctx.namingConfigFor(sub.id);
|
|
7018
7054
|
if (!namingConfig?.subsystems) continue;
|
|
7055
|
+
const isDraft = isDraftSubsystem(sub);
|
|
7019
7056
|
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);
|
|
7057
|
+
checkNamedValue(ctx, baseId, namingConfig.subsystems, `Subsystem ID "${sub.id}" does not match naming convention "${namingConfig.subsystems}".`, sub.id, isDraft);
|
|
7058
|
+
checkNamedValue(ctx, sub.name, namingConfig.subsystems, `Subsystem Name "${sub.name}" does not match naming convention "${namingConfig.subsystems}".`, sub.id, isDraft);
|
|
7022
7059
|
}
|
|
7023
7060
|
for (const comp of ctx.components) {
|
|
7024
7061
|
const namingConfig = ctx.namingConfigFor(comp.subsystem);
|
|
@@ -7103,7 +7140,7 @@ var init_naming_conventions = __esm({
|
|
|
7103
7140
|
const intf = ctx.interfaceMap.get(impl.contract);
|
|
7104
7141
|
const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
|
|
7105
7142
|
const namingConfig = ctx.namingConfigFor(comp?.subsystem);
|
|
7106
|
-
const isDraft =
|
|
7143
|
+
const isDraft = ctx.isImplementationDraft(impl);
|
|
7107
7144
|
if (namingConfig?.methods) {
|
|
7108
7145
|
for (const m of impl.methods) {
|
|
7109
7146
|
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 +7204,7 @@ var complexityRule;
|
|
|
7167
7204
|
var init_complexity_and_metadata = __esm({
|
|
7168
7205
|
"src/core/rules/heuristic/complexity-and-metadata.ts"() {
|
|
7169
7206
|
"use strict";
|
|
7207
|
+
init_models();
|
|
7170
7208
|
complexityRule = {
|
|
7171
7209
|
name: "complexity-and-metadata",
|
|
7172
7210
|
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 +7220,8 @@ var init_complexity_and_metadata = __esm({
|
|
|
7182
7220
|
check(ctx) {
|
|
7183
7221
|
for (const sub of ctx.subsystems) {
|
|
7184
7222
|
const docConfig = ctx.documentationConfigFor(sub.id);
|
|
7185
|
-
|
|
7223
|
+
const isSubDraft = isDraftSubsystem(sub);
|
|
7224
|
+
checkDescription(ctx, sub.description, docConfig?.requireDescriptions ?? false, docConfig?.minDescriptionLength, sub.id, "Subsystem", isSubDraft);
|
|
7186
7225
|
const complexityConfig = ctx.complexityConfigFor(sub.id);
|
|
7187
7226
|
const directComponents = ctx.components.filter((c) => c.subsystem === sub.id).length;
|
|
7188
7227
|
if (complexityConfig?.maxSubsystemComponents !== void 0 && directComponents > complexityConfig.maxSubsystemComponents) {
|
|
@@ -7190,7 +7229,8 @@ var init_complexity_and_metadata = __esm({
|
|
|
7190
7229
|
"warning",
|
|
7191
7230
|
"EXCESSIVE_SUBSYSTEM_COMPONENTS",
|
|
7192
7231
|
`Subsystem "${sub.id}" has ${directComponents} direct components, exceeding the configured limit of ${complexityConfig.maxSubsystemComponents}.`,
|
|
7193
|
-
sub.id
|
|
7232
|
+
sub.id,
|
|
7233
|
+
isSubDraft
|
|
7194
7234
|
);
|
|
7195
7235
|
}
|
|
7196
7236
|
}
|
|
@@ -7266,7 +7306,7 @@ var init_complexity_and_metadata = __esm({
|
|
|
7266
7306
|
const intf = ctx.interfaceMap.get(impl.contract);
|
|
7267
7307
|
const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
|
|
7268
7308
|
const complexityConfig = ctx.complexityConfigFor(comp?.subsystem);
|
|
7269
|
-
const isDraft =
|
|
7309
|
+
const isDraft = ctx.isImplementationDraft(impl);
|
|
7270
7310
|
if (complexityConfig?.maxNarrativeSteps !== void 0) {
|
|
7271
7311
|
for (const m of impl.methods) {
|
|
7272
7312
|
if (m.narrative.length > complexityConfig.maxNarrativeSteps) {
|
|
@@ -13980,7 +14020,7 @@ function buildRuleContext(opts) {
|
|
|
13980
14020
|
return rules.sddRuleSeverity[ruleCode];
|
|
13981
14021
|
}
|
|
13982
14022
|
const sub = subsystemId ? subsystems.find((s) => s.id === subsystemId) : void 0;
|
|
13983
|
-
const profileSeverity = extensions.profiles[sub?.profile
|
|
14023
|
+
const profileSeverity = extensions.profiles[sub?.profile || projectType]?.rules?.sddRuleSeverity?.[ruleCode];
|
|
13984
14024
|
if (profileSeverity) {
|
|
13985
14025
|
return profileSeverity;
|
|
13986
14026
|
}
|