@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/cli/index.js
CHANGED
|
@@ -65,7 +65,7 @@ var init_defaults = __esm({
|
|
|
65
65
|
copilot: ".github/prompts",
|
|
66
66
|
codex: ".codex/agents"
|
|
67
67
|
};
|
|
68
|
-
WAIRON_VERSION = "5.1.1-dev.
|
|
68
|
+
WAIRON_VERSION = "5.1.1-dev.16";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
SUPPORTED_ALIASES = ["wai"];
|
|
71
71
|
SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -500,7 +500,7 @@ This project uses **wairon**. System specs live under \`.wai/specs/\` (L0 System
|
|
|
500
500
|
1. **Design before code**: Complete spec and pass validator before writing source code.
|
|
501
501
|
2. **Human-in-the-loop**: Ask user approval for each spec layer before proceeding.
|
|
502
502
|
3. **Spec consistency**: If a 1:1 narrative match is incorrect or conflicts with L0 requirements, escalate a spec revision first. Never ship mismatched code.
|
|
503
|
-
4. **No persistence shortcuts & strict layers**: A Portal must never depend directly on a Store, Registry, or Adapter. Portal reads MAY go through a Repository/Index facade (passthrough reads need no per-entity Orchestrator ceremony), but a Portal narrative
|
|
503
|
+
4. **No persistence shortcuts & strict layers**: A Portal must never depend directly on a Store, Registry, or Adapter. Portal reads MAY go through a Repository/Index facade (passthrough reads need no per-entity Orchestrator ceremony), but a Portal narrative call or dispatch-table binding that reaches a write-effect facade method is an error (\`PORTAL_WRITE_SHORTCUT\`) \u2014 writes always route through an Orchestrator. Held domain state always lives in a dedicated data component, never as fields inside an Orchestrator or Specialist. Two sanctioned shapes: the RECOMMENDED Repository pattern (owns Store + Registry + Index; consumers depend on the facade), or \u2014 for genuinely simple state \u2014 a deliberately standalone Store (workflow-layer consumers only, acknowledged with a lint.allow reason on the UNOWNED_STORE warning). Never combine Store/Registry/Index roles into one component, and never fold state into a consuming component because a link was refused.
|
|
504
504
|
|
|
505
505
|
### Component Vocabulary
|
|
506
506
|
* **Blocks**: Portal, Orchestrator, Supervisor, Actor, Store, Index, Registry, Adapter, Observer, Specialist.
|
|
@@ -2822,15 +2822,13 @@ var init_hierarchy_integrity = __esm({
|
|
|
2822
2822
|
}
|
|
2823
2823
|
}
|
|
2824
2824
|
for (const impl of ctx.implementations) {
|
|
2825
|
-
const contract = ctx.interfaces.find((i) => i.id === impl.contract);
|
|
2826
|
-
const isDraftCtx = impl.status === "draft" || impl.status === "design" || contract && (ctx.isComponentDraft(contract.component) || contract.status === "draft" || contract.status === "design");
|
|
2827
2825
|
if (!ctx.interfaceIds.has(impl.contract)) {
|
|
2828
2826
|
ctx.addIssue(
|
|
2829
2827
|
"error",
|
|
2830
2828
|
"INVALID_INTERFACE_REFERENCE",
|
|
2831
2829
|
`Implementation "${impl.id}" references non-existent interface contract "${impl.contract}".`,
|
|
2832
2830
|
impl.id,
|
|
2833
|
-
|
|
2831
|
+
ctx.isImplementationDraft(impl)
|
|
2834
2832
|
);
|
|
2835
2833
|
}
|
|
2836
2834
|
}
|
|
@@ -2946,11 +2944,7 @@ var init_type_references2 = __esm({
|
|
|
2946
2944
|
for (const field of t.fields) {
|
|
2947
2945
|
const refs = fieldTypeRefs(t, field.type);
|
|
2948
2946
|
for (const ref of refs) {
|
|
2949
|
-
if (ctx.
|
|
2950
|
-
continue;
|
|
2951
|
-
}
|
|
2952
|
-
const resolved = ctx.types.find((spec) => typeMatchesRef(spec, ref));
|
|
2953
|
-
if (!resolved) {
|
|
2947
|
+
if (!ctx.isTypeResolved(ref, /* @__PURE__ */ new Set())) {
|
|
2954
2948
|
ctx.addIssue(
|
|
2955
2949
|
"error",
|
|
2956
2950
|
"UNDEFINED_TYPE_REFERENCE",
|
|
@@ -3182,14 +3176,27 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3182
3176
|
);
|
|
3183
3177
|
continue;
|
|
3184
3178
|
}
|
|
3185
|
-
const
|
|
3186
|
-
const
|
|
3187
|
-
const
|
|
3179
|
+
const target = step.targetComponent;
|
|
3180
|
+
const isCrossTreeForm = ctx.isExternalNamespaceRef(target);
|
|
3181
|
+
const caller = ctx.componentMap.get(contract.component);
|
|
3182
|
+
const fromSubsystem = caller?.subsystem;
|
|
3183
|
+
const isCollapsedForm = !isCrossTreeForm && fromSubsystem !== void 0 && ctx.isCollapsedCrossTreeRef(target, fromSubsystem);
|
|
3184
|
+
const reportUndeclared = (reaches, surfaceResolved) => {
|
|
3185
|
+
if (!caller || target === caller.id || caller.dependsOn.includes(target) || caller.owns.includes(target)) return;
|
|
3186
|
+
ctx.addIssue(
|
|
3187
|
+
"error",
|
|
3188
|
+
"UNDECLARED_DEPENDENCY_CALL",
|
|
3189
|
+
`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.`,
|
|
3190
|
+
impl.id,
|
|
3191
|
+
isDraftCtx || ctx.isComponentDraft(caller.id),
|
|
3192
|
+
surfaceResolved
|
|
3193
|
+
);
|
|
3194
|
+
};
|
|
3188
3195
|
if (step.type === "dispatch") {
|
|
3189
|
-
const dispatchTarget = ctx.componentMap.get(
|
|
3196
|
+
const dispatchTarget = ctx.componentMap.get(target);
|
|
3190
3197
|
if (!dispatchTarget) {
|
|
3191
3198
|
if (isCrossTreeForm || isCollapsedForm) {
|
|
3192
|
-
const resolved = ctx.resolveSurfaceRef(
|
|
3199
|
+
const resolved = ctx.resolveSurfaceRef(target, fromSubsystem);
|
|
3193
3200
|
if (resolved.kind === "ambiguous") {
|
|
3194
3201
|
ctx.addIssue(
|
|
3195
3202
|
"error",
|
|
@@ -3197,7 +3204,7 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3197
3204
|
ambiguityMessage(
|
|
3198
3205
|
resolved,
|
|
3199
3206
|
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches (step ${step.stepNumber}) through`,
|
|
3200
|
-
|
|
3207
|
+
target
|
|
3201
3208
|
),
|
|
3202
3209
|
impl.id,
|
|
3203
3210
|
isDraftCtx
|
|
@@ -3205,11 +3212,12 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3205
3212
|
continue;
|
|
3206
3213
|
}
|
|
3207
3214
|
if (resolved.kind === "resolved") {
|
|
3215
|
+
reportUndeclared("dispatches through", true);
|
|
3208
3216
|
if (step.capability && !(resolved.entry.dispatch ?? []).some((b) => b.capability === step.capability)) {
|
|
3209
3217
|
ctx.addIssue(
|
|
3210
3218
|
"error",
|
|
3211
3219
|
"SURFACE_REF_NOT_EXPOSED",
|
|
3212
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches capability "${step.capability}" through cross-tree portal "${
|
|
3220
|
+
`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}".`,
|
|
3213
3221
|
impl.id,
|
|
3214
3222
|
isDraftCtx,
|
|
3215
3223
|
true
|
|
@@ -3222,7 +3230,7 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3222
3230
|
ctx.addIssue(
|
|
3223
3231
|
"warning",
|
|
3224
3232
|
"CROSS_TREE_REF_UNRESOLVED",
|
|
3225
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches through cross-tree component "${
|
|
3233
|
+
`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.`,
|
|
3226
3234
|
impl.id,
|
|
3227
3235
|
isDraftCtx
|
|
3228
3236
|
);
|
|
@@ -3230,23 +3238,14 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3230
3238
|
ctx.addIssue(
|
|
3231
3239
|
"error",
|
|
3232
3240
|
"INVALID_TARGET_COMPONENT_REFERENCE",
|
|
3233
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches through component "${
|
|
3241
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches through component "${target}" which does not exist (step ${step.stepNumber}).`,
|
|
3234
3242
|
impl.id,
|
|
3235
3243
|
isDraftCtx
|
|
3236
3244
|
);
|
|
3237
3245
|
}
|
|
3238
3246
|
continue;
|
|
3239
3247
|
}
|
|
3240
|
-
|
|
3241
|
-
if (dispatchCaller && step.targetComponent !== dispatchCaller.id && !dispatchCaller.dependsOn.includes(step.targetComponent) && !dispatchCaller.owns.includes(step.targetComponent)) {
|
|
3242
|
-
ctx.addIssue(
|
|
3243
|
-
"error",
|
|
3244
|
-
"UNDECLARED_DEPENDENCY_CALL",
|
|
3245
|
-
`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.`,
|
|
3246
|
-
impl.id,
|
|
3247
|
-
isDraftCtx || ctx.isComponentDraft(dispatchCaller.id)
|
|
3248
|
-
);
|
|
3249
|
-
}
|
|
3248
|
+
reportUndeclared("dispatches through");
|
|
3250
3249
|
continue;
|
|
3251
3250
|
}
|
|
3252
3251
|
const verb = step.type === "register" ? "registers callback" : "calls";
|
|
@@ -3260,10 +3259,10 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3260
3259
|
);
|
|
3261
3260
|
continue;
|
|
3262
3261
|
}
|
|
3263
|
-
const targetComp = ctx.componentMap.get(
|
|
3262
|
+
const targetComp = ctx.componentMap.get(target);
|
|
3264
3263
|
if (!targetComp) {
|
|
3265
3264
|
if (isCrossTreeForm || isCollapsedForm) {
|
|
3266
|
-
const resolved = ctx.resolveSurfaceRef(
|
|
3265
|
+
const resolved = ctx.resolveSurfaceRef(target, fromSubsystem);
|
|
3267
3266
|
if (resolved.kind === "ambiguous") {
|
|
3268
3267
|
ctx.addIssue(
|
|
3269
3268
|
"error",
|
|
@@ -3271,7 +3270,7 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3271
3270
|
ambiguityMessage(
|
|
3272
3271
|
resolved,
|
|
3273
3272
|
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} "${step.targetMethod}" (step ${step.stepNumber}) on`,
|
|
3274
|
-
|
|
3273
|
+
target
|
|
3275
3274
|
),
|
|
3276
3275
|
impl.id,
|
|
3277
3276
|
isDraftCtx
|
|
@@ -3279,12 +3278,13 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3279
3278
|
continue;
|
|
3280
3279
|
}
|
|
3281
3280
|
if (resolved.kind === "resolved") {
|
|
3281
|
+
reportUndeclared(verb, true);
|
|
3282
3282
|
const surfaceMethod = resolved.entry.methods.find((m) => m.name === step.targetMethod);
|
|
3283
3283
|
if (!surfaceMethod) {
|
|
3284
3284
|
ctx.addIssue(
|
|
3285
3285
|
"error",
|
|
3286
3286
|
"SURFACE_REF_NOT_EXPOSED",
|
|
3287
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} "${step.targetMethod}" on cross-tree component "${
|
|
3287
|
+
`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}".`,
|
|
3288
3288
|
impl.id,
|
|
3289
3289
|
isDraftCtx,
|
|
3290
3290
|
true
|
|
@@ -3311,7 +3311,7 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3311
3311
|
ctx.addIssue(
|
|
3312
3312
|
"warning",
|
|
3313
3313
|
"CROSS_TREE_REF_UNRESOLVED",
|
|
3314
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} cross-tree component "${
|
|
3314
|
+
`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.`,
|
|
3315
3315
|
impl.id,
|
|
3316
3316
|
isDraftCtx
|
|
3317
3317
|
);
|
|
@@ -3319,26 +3319,15 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3319
3319
|
ctx.addIssue(
|
|
3320
3320
|
"error",
|
|
3321
3321
|
"INVALID_TARGET_COMPONENT_REFERENCE",
|
|
3322
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} component "${
|
|
3322
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} component "${target}" which does not exist (step ${step.stepNumber}).`,
|
|
3323
3323
|
impl.id,
|
|
3324
3324
|
isDraftCtx
|
|
3325
3325
|
);
|
|
3326
3326
|
}
|
|
3327
3327
|
continue;
|
|
3328
3328
|
}
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
if (!callingComponent.dependsOn.includes(step.targetComponent) && !callingComponent.owns.includes(step.targetComponent)) {
|
|
3332
|
-
ctx.addIssue(
|
|
3333
|
-
"error",
|
|
3334
|
-
"UNDECLARED_DEPENDENCY_CALL",
|
|
3335
|
-
`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.`,
|
|
3336
|
-
impl.id,
|
|
3337
|
-
isDraftCtx || ctx.isComponentDraft(callingComponent.id)
|
|
3338
|
-
);
|
|
3339
|
-
}
|
|
3340
|
-
}
|
|
3341
|
-
const targetInterfaces = ctx.interfacesByComponent.get(step.targetComponent) ?? [];
|
|
3329
|
+
reportUndeclared(verb);
|
|
3330
|
+
const targetInterfaces = ctx.interfacesByComponent.get(target) ?? [];
|
|
3342
3331
|
let targetMethodSpec;
|
|
3343
3332
|
for (const targetIntf of targetInterfaces) {
|
|
3344
3333
|
const found = targetIntf.methods.find((m) => m.name === step.targetMethod);
|
|
@@ -3351,9 +3340,9 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3351
3340
|
ctx.addIssue(
|
|
3352
3341
|
"error",
|
|
3353
3342
|
"INVALID_TARGET_METHOD_REFERENCE",
|
|
3354
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} method "${step.targetMethod}" on component "${
|
|
3343
|
+
`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}).`,
|
|
3355
3344
|
impl.id,
|
|
3356
|
-
isDraftCtx || ctx.isComponentDraft(
|
|
3345
|
+
isDraftCtx || ctx.isComponentDraft(target)
|
|
3357
3346
|
);
|
|
3358
3347
|
} else {
|
|
3359
3348
|
const declared = new Set(targetMethodSpec.guarantees ?? []);
|
|
@@ -3363,9 +3352,9 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3363
3352
|
ctx.addIssue(
|
|
3364
3353
|
"warning",
|
|
3365
3354
|
"NARRATIVE_SEMANTIC_UNBACKED",
|
|
3366
|
-
`Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" explicitly asserts guarantee "${g}", but the method it calls \u2014 "${step.targetMethod}" on "${
|
|
3355
|
+
`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.`,
|
|
3367
3356
|
impl.id,
|
|
3368
|
-
isDraftCtx || ctx.isComponentDraft(
|
|
3357
|
+
isDraftCtx || ctx.isComponentDraft(target)
|
|
3369
3358
|
);
|
|
3370
3359
|
}
|
|
3371
3360
|
}
|
|
@@ -3695,43 +3684,47 @@ var init_narrative_flow = __esm({
|
|
|
3695
3684
|
});
|
|
3696
3685
|
|
|
3697
3686
|
// src/core/rules/narrative/narrative-antipatterns.ts
|
|
3698
|
-
function
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
}
|
|
3687
|
+
function completedStepGraph(steps) {
|
|
3688
|
+
const numbers = steps.flatMap((s) => [
|
|
3689
|
+
s.stepNumber,
|
|
3690
|
+
s.onTrueStep,
|
|
3691
|
+
s.onFalseStep,
|
|
3692
|
+
s.defaultStep,
|
|
3693
|
+
s.endStep,
|
|
3694
|
+
s.finallyStep,
|
|
3695
|
+
s.toStep,
|
|
3696
|
+
...(s.cases ?? []).map((c) => c.step),
|
|
3697
|
+
...(s.catches ?? []).map((c) => c.step),
|
|
3698
|
+
...(s.branches ?? []).map((b) => b.step)
|
|
3699
|
+
]).filter((n) => n !== void 0);
|
|
3700
|
+
const completion = {
|
|
3701
|
+
stepNumber: Math.max(0, ...numbers) + 1,
|
|
3702
|
+
description: "The method completes by falling off the end of its narrative",
|
|
3703
|
+
type: "return"
|
|
3704
|
+
};
|
|
3705
|
+
return stepGraph({ narrative: [...steps, completion] });
|
|
3717
3706
|
}
|
|
3718
3707
|
function isUnavoidable(steps, target) {
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
const
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
const
|
|
3726
|
-
if (
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3708
|
+
if (steps.length === 0) return false;
|
|
3709
|
+
const graph = completedStepGraph(steps);
|
|
3710
|
+
const completes = /* @__PURE__ */ new Set();
|
|
3711
|
+
const canComplete = (n) => {
|
|
3712
|
+
const s = graph.stepAt(n);
|
|
3713
|
+
if (s.type === "return" || s.type === "throw") return true;
|
|
3714
|
+
const arms = s.type === "parallel" ? (s.branches ?? []).map((b) => b.step).filter((e) => graph.stepAt(e) !== void 0) : [];
|
|
3715
|
+
if (arms.length) return arms.every((e) => completes.has(e));
|
|
3716
|
+
return graph.successorsOf(n).some((t) => completes.has(t));
|
|
3717
|
+
};
|
|
3718
|
+
const order = [...graph.stepNumbers].reverse();
|
|
3719
|
+
for (let grew = true; grew; ) {
|
|
3720
|
+
grew = false;
|
|
3721
|
+
for (const n of order) {
|
|
3722
|
+
if (n === target || completes.has(n) || !canComplete(n)) continue;
|
|
3723
|
+
completes.add(n);
|
|
3724
|
+
grew = true;
|
|
3732
3725
|
}
|
|
3733
3726
|
}
|
|
3734
|
-
return
|
|
3727
|
+
return !completes.has(graph.stepNumbers[0]);
|
|
3735
3728
|
}
|
|
3736
3729
|
function stronglyConnected(nums, successorsOf) {
|
|
3737
3730
|
const index = /* @__PURE__ */ new Map();
|
|
@@ -3783,11 +3776,12 @@ function stronglyConnected(nums, successorsOf) {
|
|
|
3783
3776
|
}
|
|
3784
3777
|
return sccs;
|
|
3785
3778
|
}
|
|
3786
|
-
var narrativeAntipatternsRule;
|
|
3779
|
+
var byCodeUnit, narrativeAntipatternsRule;
|
|
3787
3780
|
var init_narrative_antipatterns = __esm({
|
|
3788
3781
|
"src/core/rules/narrative/narrative-antipatterns.ts"() {
|
|
3789
3782
|
"use strict";
|
|
3790
3783
|
init_models();
|
|
3784
|
+
byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
3791
3785
|
narrativeAntipatternsRule = {
|
|
3792
3786
|
name: "narrative-antipatterns",
|
|
3793
3787
|
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.",
|
|
@@ -3806,29 +3800,33 @@ var init_narrative_antipatterns = __esm({
|
|
|
3806
3800
|
const steps = implMethod.narrative;
|
|
3807
3801
|
if (!steps.length) continue;
|
|
3808
3802
|
const where = `Method "${implMethod.name}" in implementation "${impl.id}": `;
|
|
3809
|
-
const graph =
|
|
3803
|
+
const graph = completedStepGraph(steps);
|
|
3804
|
+
const decisionTargets = (stepNumber, explicit, fallThroughField) => {
|
|
3805
|
+
const targets = new Set(explicit);
|
|
3806
|
+
if (fallThroughField !== void 0) targets.add(fallThroughField);
|
|
3807
|
+
else for (const t of graph.successorsOf(stepNumber)) targets.add(t);
|
|
3808
|
+
return [...targets];
|
|
3809
|
+
};
|
|
3810
3810
|
for (const s of steps) {
|
|
3811
3811
|
if (s.type === "branch" && s.onFalseStep !== void 0) {
|
|
3812
|
-
const
|
|
3813
|
-
if (
|
|
3812
|
+
const targets = decisionTargets(s.stepNumber, [s.onFalseStep], s.onTrueStep);
|
|
3813
|
+
if (targets.length === 1) {
|
|
3814
3814
|
ctx.addIssue(
|
|
3815
3815
|
"warning",
|
|
3816
3816
|
"MEANINGLESS_BRANCH",
|
|
3817
|
-
`${where}branch step ${s.stepNumber} sends both arms to step ${
|
|
3817
|
+
`${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.`,
|
|
3818
3818
|
impl.id,
|
|
3819
3819
|
isDraftCtx
|
|
3820
3820
|
);
|
|
3821
3821
|
}
|
|
3822
3822
|
}
|
|
3823
3823
|
if (s.type === "switch" && s.cases?.length) {
|
|
3824
|
-
const targets =
|
|
3825
|
-
|
|
3826
|
-
if (def !== void 0) targets.add(def);
|
|
3827
|
-
if (targets.size === 1) {
|
|
3824
|
+
const targets = decisionTargets(s.stepNumber, s.cases.map((c) => c.step), s.defaultStep);
|
|
3825
|
+
if (targets.length === 1) {
|
|
3828
3826
|
ctx.addIssue(
|
|
3829
3827
|
"warning",
|
|
3830
3828
|
"MEANINGLESS_BRANCH",
|
|
3831
|
-
`${where}switch step ${s.stepNumber} sends every case (and the default) to step ${
|
|
3829
|
+
`${where}switch step ${s.stepNumber} sends every case (and the default) to step ${targets[0]} \u2014 the dispatch decides nothing.`,
|
|
3832
3830
|
impl.id,
|
|
3833
3831
|
isDraftCtx
|
|
3834
3832
|
);
|
|
@@ -3901,8 +3899,8 @@ var init_narrative_antipatterns = __esm({
|
|
|
3901
3899
|
if (!isCycle) continue;
|
|
3902
3900
|
const memberEdges = keys.flatMap((k) => (adjacency.get(k) ?? []).filter((e) => inScc.has(e.toKey)));
|
|
3903
3901
|
if (memberEdges.length === 0) continue;
|
|
3904
|
-
const anchor = [...memberEdges].sort((a, b) => a.fromKey.
|
|
3905
|
-
const path70 = [...keys].sort().join(" \u2192 ");
|
|
3902
|
+
const anchor = [...memberEdges].sort((a, b) => byCodeUnit(a.fromKey, b.fromKey) || byCodeUnit(a.impl.id, b.impl.id) || a.stepNumber - b.stepNumber)[0];
|
|
3903
|
+
const path70 = [...keys].sort(byCodeUnit).join(" \u2192 ");
|
|
3906
3904
|
ctx.addIssue(
|
|
3907
3905
|
"warning",
|
|
3908
3906
|
"UNCONDITIONAL_CALL_CYCLE",
|
|
@@ -4154,14 +4152,15 @@ var init_portal_endpoints = __esm({
|
|
|
4154
4152
|
continue;
|
|
4155
4153
|
}
|
|
4156
4154
|
for (const intf of compInterfaces) {
|
|
4155
|
+
const isIntfDraft = intf.status === "draft" || intf.status === "design";
|
|
4157
4156
|
for (const m of intf.methods) {
|
|
4158
4157
|
if (m.endpoint) {
|
|
4159
4158
|
ctx.addIssue(
|
|
4160
4159
|
"error",
|
|
4161
4160
|
"ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT",
|
|
4162
4161
|
`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.`,
|
|
4163
|
-
|
|
4164
|
-
isDraftCtx
|
|
4162
|
+
intf.id,
|
|
4163
|
+
isDraftCtx || isIntfDraft
|
|
4165
4164
|
);
|
|
4166
4165
|
}
|
|
4167
4166
|
}
|
|
@@ -4292,7 +4291,7 @@ var init_stereotype_dependencies = __esm({
|
|
|
4292
4291
|
{ code: "ARCHITECTURE_VIOLATION_ADAPTER_DEP", defaultSeverity: "error", summary: "Adapter depending on Orchestrators or Stores" },
|
|
4293
4292
|
{ code: "ARCHITECTURE_VIOLATION_INDEX_DEP", defaultSeverity: "error", summary: "Index depending on anything but its Store or an Adapter" },
|
|
4294
4293
|
{ code: "ARCHITECTURE_VIOLATION_VIEW_DEP", defaultSeverity: "error", summary: "View depending on logic/persistence layers" },
|
|
4295
|
-
{ code: "PORTAL_WRITE_SHORTCUT", defaultSeverity: "error", summary: "Portal narrative
|
|
4294
|
+
{ 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)" }
|
|
4296
4295
|
],
|
|
4297
4296
|
check(ctx) {
|
|
4298
4297
|
for (const comp of ctx.components) {
|
|
@@ -4364,10 +4363,11 @@ var init_stereotype_dependencies = __esm({
|
|
|
4364
4363
|
crossDraft
|
|
4365
4364
|
);
|
|
4366
4365
|
} else if (depComp.componentType !== "Portal" && depComp.componentType !== "Gateway") {
|
|
4366
|
+
const crosser = comp.componentType === "Adapter" ? "client Adapter" : comp.componentType;
|
|
4367
4367
|
ctx.addIssue(
|
|
4368
4368
|
"error",
|
|
4369
4369
|
"CROSS_SUBSYSTEM_TARGET_NON_PORTAL",
|
|
4370
|
-
`Boundary violation:
|
|
4370
|
+
`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.`,
|
|
4371
4371
|
comp.id,
|
|
4372
4372
|
crossDraft
|
|
4373
4373
|
);
|
|
@@ -4491,6 +4491,23 @@ var init_stereotype_dependencies = __esm({
|
|
|
4491
4491
|
}
|
|
4492
4492
|
}
|
|
4493
4493
|
}
|
|
4494
|
+
for (const comp of ctx.components) {
|
|
4495
|
+
if (comp.componentType !== "Portal" || !comp.dispatch || comp.dispatch.length === 0) continue;
|
|
4496
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
4497
|
+
for (const binding of comp.dispatch) {
|
|
4498
|
+
const target = ctx.componentMap.get(binding.component);
|
|
4499
|
+
if (!target || target.componentType !== "Repository" && target.componentType !== "Index") continue;
|
|
4500
|
+
const targetMethod = ctx.interfaceMethodsOf(target.id).find((m) => m.name === binding.method);
|
|
4501
|
+
if (targetMethod?.effect !== "write") continue;
|
|
4502
|
+
ctx.addIssue(
|
|
4503
|
+
"error",
|
|
4504
|
+
"PORTAL_WRITE_SHORTCUT",
|
|
4505
|
+
`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.`,
|
|
4506
|
+
comp.id,
|
|
4507
|
+
isDraftCtx || ctx.isComponentDraft(target.id)
|
|
4508
|
+
);
|
|
4509
|
+
}
|
|
4510
|
+
}
|
|
4494
4511
|
}
|
|
4495
4512
|
};
|
|
4496
4513
|
}
|
|
@@ -4514,7 +4531,7 @@ var init_pattern_ownership = __esm({
|
|
|
4514
4531
|
{ code: "REPOSITORY_CONTAINMENT", defaultSeverity: "error", summary: "Repository owning a non Store/Registry/Index/Adapter member" },
|
|
4515
4532
|
{ code: "GATEWAY_CONTAINMENT", defaultSeverity: "error", summary: "Gateway owning a non Portal/Orchestrator/Specialist member" },
|
|
4516
4533
|
{ code: "FEATURE_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "FeatureComponent not owning exactly one Orchestrator + one or more Views" },
|
|
4517
|
-
{ code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent
|
|
4534
|
+
{ code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent not owning exactly one Portal facade, or owning no children to route to" },
|
|
4518
4535
|
{ code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" },
|
|
4519
4536
|
{ 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" },
|
|
4520
4537
|
{ 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" }
|
|
@@ -4536,14 +4553,15 @@ var init_pattern_ownership = __esm({
|
|
|
4536
4553
|
ctx.addIssue("error", "INVALID_OWNED_MEMBER", `Component "${comp.id}" owns "${memberId}" which does not exist.`, comp.id, isDraftCtx);
|
|
4537
4554
|
continue;
|
|
4538
4555
|
}
|
|
4556
|
+
if (!pattern) continue;
|
|
4539
4557
|
if (isPattern(member)) {
|
|
4540
4558
|
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);
|
|
4541
4559
|
}
|
|
4542
|
-
const
|
|
4543
|
-
if (
|
|
4544
|
-
ctx.addIssue("error", "SHARED_OWNED_MEMBER", `Block "${memberId}" is owned by both "${
|
|
4560
|
+
const firstOwner = ownedBy.get(memberId);
|
|
4561
|
+
if (firstOwner && firstOwner !== comp.id) {
|
|
4562
|
+
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);
|
|
4545
4563
|
}
|
|
4546
|
-
ownedBy.set(memberId, comp.id);
|
|
4564
|
+
if (!firstOwner) ownedBy.set(memberId, comp.id);
|
|
4547
4565
|
}
|
|
4548
4566
|
if (comp.componentType === "Repository") {
|
|
4549
4567
|
const allowed = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Adapter"]);
|
|
@@ -4578,14 +4596,14 @@ var init_pattern_ownership = __esm({
|
|
|
4578
4596
|
}
|
|
4579
4597
|
}
|
|
4580
4598
|
if (comp.componentType === "RouterComponent") {
|
|
4581
|
-
let
|
|
4599
|
+
let portals = 0;
|
|
4582
4600
|
let hasChildren = false;
|
|
4583
4601
|
for (const memberId of comp.owns) {
|
|
4584
4602
|
const t = ctx.componentMap.get(memberId)?.componentType;
|
|
4585
|
-
if (t === "Portal")
|
|
4603
|
+
if (t === "Portal") portals++;
|
|
4586
4604
|
else if (t) hasChildren = true;
|
|
4587
4605
|
}
|
|
4588
|
-
if (
|
|
4606
|
+
if (portals !== 1) {
|
|
4589
4607
|
ctx.addIssue("error", "ROUTER_COMPONENT_CONTAINMENT", `RouterComponent "${comp.id}" must own exactly one Portal component to act as its facade.`, comp.id, isDraftCtx);
|
|
4590
4608
|
}
|
|
4591
4609
|
if (!hasChildren) {
|
|
@@ -5087,54 +5105,43 @@ var init_dependency_cycles = __esm({
|
|
|
5087
5105
|
"use strict";
|
|
5088
5106
|
cyclesRule = {
|
|
5089
5107
|
name: "dependency-cycles",
|
|
5090
|
-
description: "The component dependsOn graph must be a DAG; the first
|
|
5108
|
+
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.",
|
|
5091
5109
|
codes: [
|
|
5092
5110
|
{ code: "CIRCULAR_DEPENDENCY", defaultSeverity: "error", summary: "Circular dependency between components" }
|
|
5093
5111
|
],
|
|
5094
5112
|
check(ctx) {
|
|
5095
|
-
const
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
const comp = ctx.componentMap.get(compId);
|
|
5102
|
-
if (comp) {
|
|
5103
|
-
for (const depId of comp.dependsOn) {
|
|
5104
|
-
if (!visited.has(depId)) {
|
|
5105
|
-
if (dfs(depId, pathTrace)) {
|
|
5106
|
-
recStack.delete(compId);
|
|
5107
|
-
pathTrace.pop();
|
|
5108
|
-
return true;
|
|
5109
|
-
}
|
|
5110
|
-
} else if (recStack.has(depId)) {
|
|
5113
|
+
const cycleFrom = (startId) => {
|
|
5114
|
+
const visited = /* @__PURE__ */ new Set([startId]);
|
|
5115
|
+
const pathTrace = [startId];
|
|
5116
|
+
const search = (compId) => {
|
|
5117
|
+
for (const depId of ctx.componentMap.get(compId)?.dependsOn ?? []) {
|
|
5118
|
+
if (depId === startId) {
|
|
5111
5119
|
pathTrace.push(depId);
|
|
5112
|
-
const cyclePath = pathTrace.slice(pathTrace.indexOf(depId)).join(" -> ");
|
|
5113
|
-
const isDraftCtx = ctx.isComponentDraft(compId) || ctx.isComponentDraft(depId);
|
|
5114
|
-
ctx.addIssue(
|
|
5115
|
-
"error",
|
|
5116
|
-
"CIRCULAR_DEPENDENCY",
|
|
5117
|
-
`Circular dependency detected: ${cyclePath}`,
|
|
5118
|
-
compId,
|
|
5119
|
-
isDraftCtx
|
|
5120
|
-
);
|
|
5121
|
-
pathTrace.pop();
|
|
5122
|
-
recStack.delete(compId);
|
|
5123
|
-
pathTrace.pop();
|
|
5124
5120
|
return true;
|
|
5125
5121
|
}
|
|
5122
|
+
if (visited.has(depId)) continue;
|
|
5123
|
+
visited.add(depId);
|
|
5124
|
+
pathTrace.push(depId);
|
|
5125
|
+
if (search(depId)) return true;
|
|
5126
|
+
pathTrace.pop();
|
|
5126
5127
|
}
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
return false;
|
|
5128
|
+
return false;
|
|
5129
|
+
};
|
|
5130
|
+
return search(startId) ? pathTrace : void 0;
|
|
5131
5131
|
};
|
|
5132
5132
|
for (const comp of ctx.components) {
|
|
5133
|
-
if (!
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5133
|
+
if (!ctx.isSpecInScope(comp.id)) continue;
|
|
5134
|
+
const cycle = cycleFrom(comp.id);
|
|
5135
|
+
if (!cycle) continue;
|
|
5136
|
+
const closing = cycle[cycle.length - 2];
|
|
5137
|
+
ctx.addIssue(
|
|
5138
|
+
"error",
|
|
5139
|
+
"CIRCULAR_DEPENDENCY",
|
|
5140
|
+
`Circular dependency detected: ${cycle.join(" -> ")}`,
|
|
5141
|
+
comp.id,
|
|
5142
|
+
ctx.isComponentDraft(comp.id) || ctx.isComponentDraft(closing)
|
|
5143
|
+
);
|
|
5144
|
+
return;
|
|
5138
5145
|
}
|
|
5139
5146
|
}
|
|
5140
5147
|
};
|
|
@@ -5263,19 +5270,18 @@ var init_dispatch_tables = __esm({
|
|
|
5263
5270
|
);
|
|
5264
5271
|
continue;
|
|
5265
5272
|
}
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
}
|
|
5273
|
+
const boundMethod = ctx.interfaceMethodsOf(binding.component).find((m) => m.name === binding.method);
|
|
5274
|
+
if (!boundMethod) continue;
|
|
5275
|
+
const declared = new Set(boundMethod.guarantees ?? []);
|
|
5276
|
+
for (const g of step.assertsGuarantees ?? []) {
|
|
5277
|
+
if (!declared.has(g)) {
|
|
5278
|
+
ctx.addIssue(
|
|
5279
|
+
"warning",
|
|
5280
|
+
"NARRATIVE_SEMANTIC_UNBACKED",
|
|
5281
|
+
`${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.`,
|
|
5282
|
+
impl.id,
|
|
5283
|
+
isDraftCtx || ctx.isComponentDraft(binding.component)
|
|
5284
|
+
);
|
|
5279
5285
|
}
|
|
5280
5286
|
}
|
|
5281
5287
|
}
|
|
@@ -5480,12 +5486,11 @@ var init_unused_detection = __esm({
|
|
|
5480
5486
|
const invokedBySeeds = [];
|
|
5481
5487
|
for (const intf of ctx.interfaces) {
|
|
5482
5488
|
if (!ctx.componentMap.has(intf.component)) continue;
|
|
5483
|
-
const comp = ctx.componentMap.get(intf.component);
|
|
5484
5489
|
for (const m of intf.methods) {
|
|
5485
5490
|
if (!m.invokedBy) continue;
|
|
5486
5491
|
invokedBySeeds.push({ compId: intf.component, methodName: m.name });
|
|
5487
5492
|
if (!ctx.isSpecInScope(intf.id)) continue;
|
|
5488
|
-
const isDraftCtx =
|
|
5493
|
+
const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
|
|
5489
5494
|
if (!passesIntentFloor(m.invokedBy.caller, m.name)) {
|
|
5490
5495
|
ctx.addIssue(
|
|
5491
5496
|
"warning",
|
|
@@ -5510,7 +5515,7 @@ var init_unused_detection = __esm({
|
|
|
5510
5515
|
for (const comp of ctx.components) {
|
|
5511
5516
|
if (!ctx.isSpecInScope(comp.id)) continue;
|
|
5512
5517
|
if (!reach.reachesComponent(comp.id)) {
|
|
5513
|
-
const isDraftCtx =
|
|
5518
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
5514
5519
|
ctx.addIssue(
|
|
5515
5520
|
"warning",
|
|
5516
5521
|
"UNUSED_COMPONENT",
|
|
@@ -5522,12 +5527,12 @@ var init_unused_detection = __esm({
|
|
|
5522
5527
|
for (const intf of ctx.interfacesByComponent.get(comp.id) ?? []) {
|
|
5523
5528
|
for (const m of intf.methods) {
|
|
5524
5529
|
if (!reach.reachesMethod(comp.id, m.name)) {
|
|
5525
|
-
const isDraftCtx =
|
|
5530
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
|
|
5526
5531
|
ctx.addIssue(
|
|
5527
5532
|
"warning",
|
|
5528
5533
|
"UNUSED_METHOD",
|
|
5529
5534
|
`Method "${m.name}" on component "${comp.id}" is defined but never called by any narrative step.`,
|
|
5530
|
-
|
|
5535
|
+
intf.id,
|
|
5531
5536
|
isDraftCtx
|
|
5532
5537
|
);
|
|
5533
5538
|
}
|
|
@@ -5638,20 +5643,20 @@ var init_durability_round_trip = __esm({
|
|
|
5638
5643
|
});
|
|
5639
5644
|
|
|
5640
5645
|
// src/core/rules/wiring/untyped-seams.ts
|
|
5641
|
-
function
|
|
5642
|
-
|
|
5643
|
-
return (m ? m[1] : typeRef).trim();
|
|
5646
|
+
function namesBareType(refs) {
|
|
5647
|
+
return refs.some((ref) => BARE_SEAM_TYPES.has(ref.toLowerCase()));
|
|
5644
5648
|
}
|
|
5645
5649
|
var BARE_SEAM_TYPES, untypedSeamRule;
|
|
5646
5650
|
var init_untyped_seams = __esm({
|
|
5647
5651
|
"src/core/rules/wiring/untyped-seams.ts"() {
|
|
5648
5652
|
"use strict";
|
|
5653
|
+
init_models();
|
|
5649
5654
|
BARE_SEAM_TYPES = /* @__PURE__ */ new Set(["json", "any", "unknown", "object"]);
|
|
5650
5655
|
untypedSeamRule = {
|
|
5651
5656
|
name: "untyped-seams",
|
|
5652
|
-
description: "Methods on a subsystem's published components (its public surface) should not take or return bare Json/any/unknown
|
|
5657
|
+
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.",
|
|
5653
5658
|
codes: [
|
|
5654
|
-
{ code: "UNTYPED_SEAM", defaultSeverity: "warning", summary: "Bare Json/any/unknown parameter or return crossing a subsystem public surface" }
|
|
5659
|
+
{ code: "UNTYPED_SEAM", defaultSeverity: "warning", summary: "Bare Json/any/unknown/object parameter or return crossing a subsystem public surface" }
|
|
5655
5660
|
],
|
|
5656
5661
|
check(ctx) {
|
|
5657
5662
|
for (const sub of ctx.subsystems) {
|
|
@@ -5665,14 +5670,17 @@ var init_untyped_seams = __esm({
|
|
|
5665
5670
|
const isDraftCtx = ctx.isComponentDraft(compId) || intf.status === "draft" || intf.status === "design";
|
|
5666
5671
|
for (const m of intf.methods) {
|
|
5667
5672
|
const offenders = [];
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5673
|
+
if (m.params && m.params.length > 0) {
|
|
5674
|
+
for (const p of m.params) {
|
|
5675
|
+
if (namesBareType(extractTypeIdentifiers(p.type))) {
|
|
5676
|
+
offenders.push(`param "${p.name}: ${p.type}"`);
|
|
5677
|
+
}
|
|
5671
5678
|
}
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5679
|
+
if (namesBareType(extractTypeIdentifiers(m.returns ?? ""))) {
|
|
5680
|
+
offenders.push(`return "${m.returns}"`);
|
|
5681
|
+
}
|
|
5682
|
+
} else if (namesBareType(methodTypeRefs(m))) {
|
|
5683
|
+
offenders.push(`signature "${m.signature}"`);
|
|
5676
5684
|
}
|
|
5677
5685
|
if (offenders.length) {
|
|
5678
5686
|
ctx.addIssue(
|
|
@@ -5697,15 +5705,19 @@ function componentOfImpl(ctx, implContract) {
|
|
|
5697
5705
|
const contract = ctx.interfaceMap.get(implContract);
|
|
5698
5706
|
return contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
5699
5707
|
}
|
|
5700
|
-
|
|
5708
|
+
function withoutQuotedText(text2) {
|
|
5709
|
+
return text2.replace(QUOTED_SPAN, " ");
|
|
5710
|
+
}
|
|
5711
|
+
var DATA_STEREOTYPES, CLAIM_PHRASES, QUOTED_SPAN, proseClaimRule;
|
|
5701
5712
|
var init_prose_claims = __esm({
|
|
5702
5713
|
"src/core/rules/wiring/prose-claims.ts"() {
|
|
5703
5714
|
"use strict";
|
|
5704
5715
|
DATA_STEREOTYPES = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Adapter", "Repository"]);
|
|
5705
|
-
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;
|
|
5716
|
+
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;
|
|
5717
|
+
QUOTED_SPAN = /"[^"]*"|“[^”]*”|`[^`]*`|(?<![\p{L}\p{N}])['‘](?:[^'‘’]|(?<=[\p{L}\p{N}])['’](?=[\p{L}\p{N}]))*['’](?![\p{L}\p{N}])/gu;
|
|
5706
5718
|
proseClaimRule = {
|
|
5707
5719
|
name: "prose-claims",
|
|
5708
|
-
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
|
|
5720
|
+
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.',
|
|
5709
5721
|
codes: [
|
|
5710
5722
|
{ code: "UNREALIZED_CLAIM", defaultSeverity: "warning", summary: "Durability/side-effect claim in prose with no matching structural edge" }
|
|
5711
5723
|
],
|
|
@@ -5716,11 +5728,11 @@ var init_prose_claims = __esm({
|
|
|
5716
5728
|
if (DATA_STEREOTYPES.has(comp.componentType)) continue;
|
|
5717
5729
|
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
5718
5730
|
for (const implMethod of impl.methods) {
|
|
5719
|
-
const stepClaims = implMethod.narrative.filter((step) => step.type === "local").map((step) => ({ step, claim: CLAIM_PHRASES.exec(step.description) })).filter((c) => c.claim !== null);
|
|
5720
|
-
const intentClaim = implMethod.intent ? CLAIM_PHRASES.exec(implMethod.intent) : null;
|
|
5731
|
+
const stepClaims = implMethod.narrative.filter((step) => step.type === "local").map((step) => ({ step, claim: CLAIM_PHRASES.exec(withoutQuotedText(step.description)) })).filter((c) => c.claim !== null);
|
|
5732
|
+
const intentClaim = implMethod.intent ? CLAIM_PHRASES.exec(withoutQuotedText(implMethod.intent)) : null;
|
|
5721
5733
|
if (!stepClaims.length && !intentClaim) continue;
|
|
5722
5734
|
const hasDataEdge = implMethod.narrative.some((step) => {
|
|
5723
|
-
if (step.type !== "call" && step.type !== "dispatch") return false;
|
|
5735
|
+
if (step.type !== "call" && step.type !== "register" && step.type !== "dispatch") return false;
|
|
5724
5736
|
if (!step.targetComponent) return false;
|
|
5725
5737
|
const target = ctx.componentMap.get(step.targetComponent);
|
|
5726
5738
|
if (target && DATA_STEREOTYPES.has(target.componentType)) return true;
|
|
@@ -5733,7 +5745,7 @@ var init_prose_claims = __esm({
|
|
|
5733
5745
|
ctx.addIssue(
|
|
5734
5746
|
"warning",
|
|
5735
5747
|
"UNREALIZED_CLAIM",
|
|
5736
|
-
`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.`,
|
|
5748
|
+
`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.`,
|
|
5737
5749
|
impl.id,
|
|
5738
5750
|
isDraftCtx
|
|
5739
5751
|
);
|
|
@@ -5792,6 +5804,10 @@ function stepAsserts(impl, methodName, type, invariantId) {
|
|
|
5792
5804
|
(step) => (step.assertsInvariants ?? []).some((ref) => refMatchesInvariant(ref, type, invariantId))
|
|
5793
5805
|
);
|
|
5794
5806
|
}
|
|
5807
|
+
function isEntityDraft(t, ctx) {
|
|
5808
|
+
const sub = t.subsystem ? ctx.subsystems.find((s) => s.id === t.subsystem) : void 0;
|
|
5809
|
+
return sub !== void 0 && isDraftSubsystem(sub);
|
|
5810
|
+
}
|
|
5795
5811
|
function resolveComponentClass(t, ctx) {
|
|
5796
5812
|
if (!t.componentClass) return void 0;
|
|
5797
5813
|
const direct = ctx.componentMap.get(t.componentClass);
|
|
@@ -5818,6 +5834,7 @@ var init_invariant_backing = __esm({
|
|
|
5818
5834
|
for (const t of ctx.types) {
|
|
5819
5835
|
const invariants = t.invariants ?? [];
|
|
5820
5836
|
if (invariants.length === 0) continue;
|
|
5837
|
+
const entityDraft = isEntityDraft(t, ctx);
|
|
5821
5838
|
const seen = /* @__PURE__ */ new Set();
|
|
5822
5839
|
for (const inv of invariants) {
|
|
5823
5840
|
if (seen.has(inv.id)) {
|
|
@@ -5825,7 +5842,8 @@ var init_invariant_backing = __esm({
|
|
|
5825
5842
|
"error",
|
|
5826
5843
|
"DUPLICATE_INVARIANT_ID",
|
|
5827
5844
|
`Entity "${t.id}" declares invariant id "${inv.id}" more than once \u2014 invariant ids must be unique within the entity.`,
|
|
5828
|
-
t.id
|
|
5845
|
+
t.id,
|
|
5846
|
+
entityDraft
|
|
5829
5847
|
);
|
|
5830
5848
|
}
|
|
5831
5849
|
seen.add(inv.id);
|
|
@@ -5836,7 +5854,8 @@ var init_invariant_backing = __esm({
|
|
|
5836
5854
|
"warning",
|
|
5837
5855
|
"INVARIANT_UNANCHORED",
|
|
5838
5856
|
`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.`,
|
|
5839
|
-
t.id
|
|
5857
|
+
t.id,
|
|
5858
|
+
entityDraft
|
|
5840
5859
|
);
|
|
5841
5860
|
continue;
|
|
5842
5861
|
}
|
|
@@ -5850,13 +5869,13 @@ var init_invariant_backing = __esm({
|
|
|
5850
5869
|
"INVARIANT_UNANCHORED",
|
|
5851
5870
|
`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.`,
|
|
5852
5871
|
t.id,
|
|
5853
|
-
ctx.isComponentDraft(comp.id)
|
|
5872
|
+
entityDraft || ctx.isComponentDraft(comp.id)
|
|
5854
5873
|
);
|
|
5855
5874
|
continue;
|
|
5856
5875
|
}
|
|
5857
5876
|
for (const { intf, method: method2 } of writeMethods) {
|
|
5858
5877
|
for (const impl of ctx.implementationsByContract.get(intf.id) ?? []) {
|
|
5859
|
-
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
5878
|
+
const isDraftCtx = entityDraft || ctx.isImplementationDraft(impl);
|
|
5860
5879
|
for (const inv of invariants) {
|
|
5861
5880
|
if (stepAsserts(impl, method2.name, t, inv.id)) continue;
|
|
5862
5881
|
ctx.addIssue(
|
|
@@ -5894,30 +5913,35 @@ var init_invariant_backing = __esm({
|
|
|
5894
5913
|
|
|
5895
5914
|
// src/core/rules/wiring/event-topology.ts
|
|
5896
5915
|
function collectEnds(ctx) {
|
|
5897
|
-
const emitters =
|
|
5898
|
-
const subscribers =
|
|
5916
|
+
const emitters = /* @__PURE__ */ new Map();
|
|
5917
|
+
const subscribers = /* @__PURE__ */ new Map();
|
|
5918
|
+
const addEnd = (ends, topic, compId, via) => {
|
|
5919
|
+
const key = `${compId}#${topic}`;
|
|
5920
|
+
const end = ends.get(key);
|
|
5921
|
+
if (!end) ends.set(key, { topic, compId, vias: [via] });
|
|
5922
|
+
else if (!end.vias.includes(via)) end.vias.push(via);
|
|
5923
|
+
};
|
|
5899
5924
|
for (const comp of ctx.components) {
|
|
5900
5925
|
for (const e of comp.emits ?? []) {
|
|
5901
|
-
emitters
|
|
5926
|
+
addEnd(emitters, e.topic, comp.id, "emits declaration");
|
|
5902
5927
|
}
|
|
5903
5928
|
for (const s of comp.subscribesTo ?? []) {
|
|
5904
|
-
subscribers
|
|
5929
|
+
addEnd(subscribers, s.topic, comp.id, "subscribesTo declaration");
|
|
5905
5930
|
}
|
|
5906
5931
|
}
|
|
5907
5932
|
for (const intf of ctx.interfaces) {
|
|
5908
5933
|
for (const method2 of intf.methods) {
|
|
5909
5934
|
const ep = method2.endpoint;
|
|
5910
5935
|
if (!ep || ep.transport !== "MessageBus") continue;
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
else subscribers.push(end);
|
|
5936
|
+
addEnd(
|
|
5937
|
+
ep.direction === "publish" ? emitters : subscribers,
|
|
5938
|
+
ep.topic,
|
|
5939
|
+
intf.component,
|
|
5940
|
+
`MessageBus endpoint on ${intf.id}.${method2.name}`
|
|
5941
|
+
);
|
|
5918
5942
|
}
|
|
5919
5943
|
}
|
|
5920
|
-
return { emitters, subscribers };
|
|
5944
|
+
return { emitters: [...emitters.values()], subscribers: [...subscribers.values()] };
|
|
5921
5945
|
}
|
|
5922
5946
|
var eventTopologyRule;
|
|
5923
5947
|
var init_event_topology = __esm({
|
|
@@ -5925,7 +5949,7 @@ var init_event_topology = __esm({
|
|
|
5925
5949
|
"use strict";
|
|
5926
5950
|
eventTopologyRule = {
|
|
5927
5951
|
name: "event-topology",
|
|
5928
|
-
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.",
|
|
5952
|
+
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.",
|
|
5929
5953
|
codes: [
|
|
5930
5954
|
{ code: "UNCONSUMED_TOPIC", defaultSeverity: "warning", summary: "Topic is emitted but nothing in the tree subscribes to it" },
|
|
5931
5955
|
{ code: "UNSOURCED_SUBSCRIPTION", defaultSeverity: "warning", summary: "Topic is subscribed to but nothing in the tree emits it" }
|
|
@@ -5940,7 +5964,7 @@ var init_event_topology = __esm({
|
|
|
5940
5964
|
ctx.addIssue(
|
|
5941
5965
|
"warning",
|
|
5942
5966
|
"UNCONSUMED_TOPIC",
|
|
5943
|
-
`Component "${e.compId}" emits topic "${e.topic}" (${e.
|
|
5967
|
+
`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.`,
|
|
5944
5968
|
e.compId,
|
|
5945
5969
|
ctx.isComponentDraft(e.compId)
|
|
5946
5970
|
);
|
|
@@ -5950,7 +5974,7 @@ var init_event_topology = __esm({
|
|
|
5950
5974
|
ctx.addIssue(
|
|
5951
5975
|
"warning",
|
|
5952
5976
|
"UNSOURCED_SUBSCRIPTION",
|
|
5953
|
-
`Component "${s.compId}" subscribes to topic "${s.topic}" (${s.
|
|
5977
|
+
`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.`,
|
|
5954
5978
|
s.compId,
|
|
5955
5979
|
ctx.isComponentDraft(s.compId)
|
|
5956
5980
|
);
|
|
@@ -5969,13 +5993,13 @@ var init_structural_conformance = __esm({
|
|
|
5969
5993
|
quoteList = (names) => names.map((n) => `"${n}"`).join(", ");
|
|
5970
5994
|
structuralConformanceRule = {
|
|
5971
5995
|
name: "structural-conformance",
|
|
5972
|
-
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
|
|
5996
|
+
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.",
|
|
5973
5997
|
codes: [
|
|
5974
5998
|
{ 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" },
|
|
5975
5999
|
{ 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" },
|
|
5976
6000
|
{ 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)" },
|
|
5977
6001
|
{ 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" },
|
|
5978
|
-
{ code: "UNREALIZED_FINDING", defaultSeverity: "warning", summary: "A finding code a contract method declares
|
|
6002
|
+
{ 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)" },
|
|
5979
6003
|
{ 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" },
|
|
5980
6004
|
{ code: "CONFORMANCE_DEGRADED", defaultSeverity: "warning", summary: "TypeScript/JavaScript files were analyzed below exact grade (compiler not resolvable) \u2014 dependency conformance skips them" }
|
|
5981
6005
|
],
|
|
@@ -5985,7 +6009,7 @@ var init_structural_conformance = __esm({
|
|
|
5985
6009
|
lookups.set(pathKey(facts.path), {
|
|
5986
6010
|
declared: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames]),
|
|
5987
6011
|
anchored: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames, ...facts.anchoredNames]),
|
|
5988
|
-
|
|
6012
|
+
findingAnchors: new Set(facts.anchoredNames),
|
|
5989
6013
|
facts
|
|
5990
6014
|
});
|
|
5991
6015
|
}
|
|
@@ -6105,11 +6129,11 @@ var init_structural_conformance = __esm({
|
|
|
6105
6129
|
if (!checked) continue;
|
|
6106
6130
|
const { file, lookup } = checked;
|
|
6107
6131
|
for (const finding of method2.findings) {
|
|
6108
|
-
if (lookup.
|
|
6132
|
+
if (lookup.findingAnchors.has(finding.code)) continue;
|
|
6109
6133
|
ctx.addIssue(
|
|
6110
6134
|
"warning",
|
|
6111
6135
|
"UNREALIZED_FINDING",
|
|
6112
|
-
`Method "${method2.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.`,
|
|
6136
|
+
`Method "${method2.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.`,
|
|
6113
6137
|
impl.id,
|
|
6114
6138
|
draft
|
|
6115
6139
|
);
|
|
@@ -6225,12 +6249,18 @@ var init_hidden_state = __esm({
|
|
|
6225
6249
|
check(ctx) {
|
|
6226
6250
|
const byPath = /* @__PURE__ */ new Map();
|
|
6227
6251
|
for (const impl of ctx.implementations) {
|
|
6228
|
-
if (impl.conformance === "off") continue;
|
|
6229
6252
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
6230
6253
|
const component = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
6231
6254
|
if (!component) continue;
|
|
6232
6255
|
if (ctx.isInChainedSubproject(component.subsystem)) continue;
|
|
6233
|
-
|
|
6256
|
+
const trusted = [];
|
|
6257
|
+
if (impl.sourcePath && impl.conformance !== "off") trusted.push(impl.sourcePath);
|
|
6258
|
+
for (const method2 of impl.methods) {
|
|
6259
|
+
if ((method2.conformance ?? impl.conformance) === "off") continue;
|
|
6260
|
+
const file = methodSourceFile(method2, impl.sourcePath);
|
|
6261
|
+
if (file) trusted.push(file);
|
|
6262
|
+
}
|
|
6263
|
+
for (const file of trusted) {
|
|
6234
6264
|
const key = pathKey(file);
|
|
6235
6265
|
const list2 = byPath.get(key) ?? [];
|
|
6236
6266
|
if (list2.some((m) => m.impl === impl)) continue;
|
|
@@ -6381,7 +6411,7 @@ var init_dependency_conformance = __esm({
|
|
|
6381
6411
|
const fromFiles = filesByComponent.get(component.id);
|
|
6382
6412
|
if (!fromFiles) continue;
|
|
6383
6413
|
const isMountingDeclarer = component.componentType === "Portal" || component.componentType === "Observer";
|
|
6384
|
-
const declaredTargets = [...component.dependsOn, ...component.owns ?? []];
|
|
6414
|
+
const declaredTargets = [.../* @__PURE__ */ new Set([...component.dependsOn, ...component.owns ?? []])];
|
|
6385
6415
|
for (const targetId of declaredTargets) {
|
|
6386
6416
|
const target = ctx.componentMap.get(targetId);
|
|
6387
6417
|
if (!target) continue;
|
|
@@ -6402,11 +6432,14 @@ var init_dependency_conformance = __esm({
|
|
|
6402
6432
|
}
|
|
6403
6433
|
const impls = implsByComponent.get(component.id) ?? [];
|
|
6404
6434
|
const anchor = impls[0];
|
|
6435
|
+
const declaredDependsOn = component.dependsOn.includes(targetId);
|
|
6436
|
+
const declaredOwns = (component.owns ?? []).includes(targetId);
|
|
6437
|
+
const relation = declaredDependsOn && declaredOwns ? "dependsOn and owns" : declaredDependsOn ? "dependsOn" : "owns";
|
|
6405
6438
|
const draft = ctx.isComponentDraft(component.id) || ctx.isComponentDraft(targetId) || impls.some((i) => ctx.isImplementationDraft(i));
|
|
6406
6439
|
ctx.addIssue(
|
|
6407
6440
|
"warning",
|
|
6408
6441
|
"UNREALIZED_DEPENDENCY",
|
|
6409
|
-
`Component "${component.id}" declares ${
|
|
6442
|
+
`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.`,
|
|
6410
6443
|
anchor?.id ?? component.id,
|
|
6411
6444
|
draft
|
|
6412
6445
|
);
|
|
@@ -6438,28 +6471,28 @@ var init_integration_conformance = __esm({
|
|
|
6438
6471
|
const filesByComponent = /* @__PURE__ */ new Map();
|
|
6439
6472
|
const filesBySubsystem = /* @__PURE__ */ new Map();
|
|
6440
6473
|
const implsByComponent = /* @__PURE__ */ new Map();
|
|
6474
|
+
const adopted = /* @__PURE__ */ new Set();
|
|
6475
|
+
const exists = (p) => {
|
|
6476
|
+
const status2 = factsByPath.get(p)?.status;
|
|
6477
|
+
return status2 === "analyzed" || status2 === "unreadable";
|
|
6478
|
+
};
|
|
6441
6479
|
for (const impl of ctx.implementations) {
|
|
6442
6480
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
6443
6481
|
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
6444
|
-
if (!comp) continue;
|
|
6482
|
+
if (!comp || ctx.isInChainedSubproject(comp.subsystem)) continue;
|
|
6445
6483
|
if (!implsByComponent.has(comp.id)) implsByComponent.set(comp.id, []);
|
|
6446
6484
|
implsByComponent.get(comp.id).push(impl);
|
|
6447
6485
|
for (const file of implementationSourceFiles(impl)) {
|
|
6448
6486
|
const p = pathKey(file);
|
|
6487
|
+
if (!exists(p)) continue;
|
|
6449
6488
|
if (!filesByComponent.has(comp.id)) filesByComponent.set(comp.id, /* @__PURE__ */ new Set());
|
|
6450
6489
|
filesByComponent.get(comp.id).add(p);
|
|
6451
6490
|
if (!filesBySubsystem.has(comp.subsystem)) filesBySubsystem.set(comp.subsystem, /* @__PURE__ */ new Set());
|
|
6452
6491
|
filesBySubsystem.get(comp.subsystem).add(p);
|
|
6453
6492
|
}
|
|
6493
|
+
if (impl.simPath) adopted.add(comp.subsystem);
|
|
6454
6494
|
}
|
|
6455
6495
|
const isTechBoundary = (compId) => (implsByComponent.get(compId) ?? []).some((i) => (i.technologies ?? []).length > 0);
|
|
6456
|
-
const adopted = /* @__PURE__ */ new Set();
|
|
6457
|
-
for (const impl of ctx.implementations) {
|
|
6458
|
-
if (!impl.simPath) continue;
|
|
6459
|
-
const contract = ctx.interfaceMap.get(impl.contract);
|
|
6460
|
-
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
6461
|
-
if (comp) adopted.add(comp.subsystem);
|
|
6462
|
-
}
|
|
6463
6496
|
const reachFrom = (start) => {
|
|
6464
6497
|
const seen = /* @__PURE__ */ new Set([start]);
|
|
6465
6498
|
const stack = [start];
|
|
@@ -6656,8 +6689,8 @@ var init_coupling_health = __esm({
|
|
|
6656
6689
|
);
|
|
6657
6690
|
}
|
|
6658
6691
|
}
|
|
6659
|
-
const threshold = ctx.rules?.complexity?.maxComponentDependencies ?? DEFAULT_GOD_COMPONENT_THRESHOLD;
|
|
6660
6692
|
for (const comp of ctx.components) {
|
|
6693
|
+
const threshold = ctx.complexityConfigFor(comp.subsystem)?.maxComponentDependencies ?? DEFAULT_GOD_COMPONENT_THRESHOLD;
|
|
6661
6694
|
if (comp.dependsOn.length > threshold) {
|
|
6662
6695
|
ctx.addIssue(
|
|
6663
6696
|
"warning",
|
|
@@ -6785,7 +6818,7 @@ var init_target_language = __esm({
|
|
|
6785
6818
|
if (!lang) continue;
|
|
6786
6819
|
const gaps = gapsFor(lang);
|
|
6787
6820
|
if (Object.keys(gaps).length === 0) continue;
|
|
6788
|
-
const isDraftCtx =
|
|
6821
|
+
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
6789
6822
|
for (const implMethod of impl.methods) {
|
|
6790
6823
|
for (const step of implMethod.narrative) {
|
|
6791
6824
|
const constructs = [
|
|
@@ -6887,7 +6920,7 @@ var init_technology_boundaries = __esm({
|
|
|
6887
6920
|
"TECH_ON_LOGIC_COMPONENT",
|
|
6888
6921
|
`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.`,
|
|
6889
6922
|
impl.id,
|
|
6890
|
-
ctx.
|
|
6923
|
+
ctx.isImplementationDraft(impl)
|
|
6891
6924
|
);
|
|
6892
6925
|
}
|
|
6893
6926
|
const compSet = ownsClosure(ownershipRoot(comp.id));
|
|
@@ -6925,7 +6958,7 @@ var init_technology_boundaries = __esm({
|
|
|
6925
6958
|
"VENDOR_NAME_IN_CONTRACT",
|
|
6926
6959
|
`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.`,
|
|
6927
6960
|
intf.id,
|
|
6928
|
-
ctx.isComponentDraft(intf.component)
|
|
6961
|
+
ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design"
|
|
6929
6962
|
);
|
|
6930
6963
|
}
|
|
6931
6964
|
}
|
|
@@ -6956,7 +6989,8 @@ var init_technology_boundaries = __esm({
|
|
|
6956
6989
|
"warning",
|
|
6957
6990
|
"TECH_LEAKAGE",
|
|
6958
6991
|
`Subsystem "${sub.id}" references "${home.label}" outside the technology's owning boundary (component ${ownersDesc(home)}).`,
|
|
6959
|
-
sub.id
|
|
6992
|
+
sub.id,
|
|
6993
|
+
isDraftSubsystem(sub)
|
|
6960
6994
|
);
|
|
6961
6995
|
}
|
|
6962
6996
|
}
|
|
@@ -6969,7 +7003,7 @@ var init_technology_boundaries = __esm({
|
|
|
6969
7003
|
"TECH_LEAKAGE",
|
|
6970
7004
|
`Interface "${intf.id}" describes "${home.label}" outside its owning boundary (component ${ownersDesc(home)}) \u2014 consumers must not know backend specifics.`,
|
|
6971
7005
|
intf.id,
|
|
6972
|
-
ctx.isComponentDraft(intf.component)
|
|
7006
|
+
ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design"
|
|
6973
7007
|
);
|
|
6974
7008
|
}
|
|
6975
7009
|
}
|
|
@@ -6989,7 +7023,8 @@ var init_technology_boundaries = __esm({
|
|
|
6989
7023
|
"warning",
|
|
6990
7024
|
"TECH_LEAKAGE",
|
|
6991
7025
|
`Implementation "${impl.id}" references "${home.label}" outside its owning boundary \u2014 call the intent interface of component ${ownersDesc(home)} instead of naming its technology.`,
|
|
6992
|
-
impl.id
|
|
7026
|
+
impl.id,
|
|
7027
|
+
ctx.isImplementationDraft(impl)
|
|
6993
7028
|
);
|
|
6994
7029
|
}
|
|
6995
7030
|
}
|
|
@@ -7052,6 +7087,7 @@ var casingPatterns, isAllUppercase, namingRule;
|
|
|
7052
7087
|
var init_naming_conventions = __esm({
|
|
7053
7088
|
"src/core/rules/heuristic/naming-conventions.ts"() {
|
|
7054
7089
|
"use strict";
|
|
7090
|
+
init_models();
|
|
7055
7091
|
casingPatterns = {
|
|
7056
7092
|
camelCase: /^[a-z][a-zA-Z0-9]*$/,
|
|
7057
7093
|
PascalCase: /^[A-Z][a-zA-Z0-9]*$/,
|
|
@@ -7072,9 +7108,10 @@ var init_naming_conventions = __esm({
|
|
|
7072
7108
|
for (const sub of ctx.subsystems) {
|
|
7073
7109
|
const namingConfig = ctx.namingConfigFor(sub.id);
|
|
7074
7110
|
if (!namingConfig?.subsystems) continue;
|
|
7111
|
+
const isDraft = isDraftSubsystem(sub);
|
|
7075
7112
|
const baseId = getBaseId(sub.id);
|
|
7076
|
-
checkNamedValue(ctx, baseId, namingConfig.subsystems, `Subsystem ID "${sub.id}" does not match naming convention "${namingConfig.subsystems}".`, sub.id);
|
|
7077
|
-
checkNamedValue(ctx, sub.name, namingConfig.subsystems, `Subsystem Name "${sub.name}" does not match naming convention "${namingConfig.subsystems}".`, sub.id);
|
|
7113
|
+
checkNamedValue(ctx, baseId, namingConfig.subsystems, `Subsystem ID "${sub.id}" does not match naming convention "${namingConfig.subsystems}".`, sub.id, isDraft);
|
|
7114
|
+
checkNamedValue(ctx, sub.name, namingConfig.subsystems, `Subsystem Name "${sub.name}" does not match naming convention "${namingConfig.subsystems}".`, sub.id, isDraft);
|
|
7078
7115
|
}
|
|
7079
7116
|
for (const comp of ctx.components) {
|
|
7080
7117
|
const namingConfig = ctx.namingConfigFor(comp.subsystem);
|
|
@@ -7159,7 +7196,7 @@ var init_naming_conventions = __esm({
|
|
|
7159
7196
|
const intf = ctx.interfaceMap.get(impl.contract);
|
|
7160
7197
|
const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
|
|
7161
7198
|
const namingConfig = ctx.namingConfigFor(comp?.subsystem);
|
|
7162
|
-
const isDraft =
|
|
7199
|
+
const isDraft = ctx.isImplementationDraft(impl);
|
|
7163
7200
|
if (namingConfig?.methods) {
|
|
7164
7201
|
for (const m of impl.methods) {
|
|
7165
7202
|
checkNamedValue(ctx, m.name, namingConfig.methods, `Implementation method "${m.name}" on "${impl.id}" does not match naming convention "${namingConfig.methods}".`, impl.id, isDraft);
|
|
@@ -7223,6 +7260,7 @@ var complexityRule;
|
|
|
7223
7260
|
var init_complexity_and_metadata = __esm({
|
|
7224
7261
|
"src/core/rules/heuristic/complexity-and-metadata.ts"() {
|
|
7225
7262
|
"use strict";
|
|
7263
|
+
init_models();
|
|
7226
7264
|
complexityRule = {
|
|
7227
7265
|
name: "complexity-and-metadata",
|
|
7228
7266
|
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.",
|
|
@@ -7238,7 +7276,8 @@ var init_complexity_and_metadata = __esm({
|
|
|
7238
7276
|
check(ctx) {
|
|
7239
7277
|
for (const sub of ctx.subsystems) {
|
|
7240
7278
|
const docConfig = ctx.documentationConfigFor(sub.id);
|
|
7241
|
-
|
|
7279
|
+
const isSubDraft = isDraftSubsystem(sub);
|
|
7280
|
+
checkDescription(ctx, sub.description, docConfig?.requireDescriptions ?? false, docConfig?.minDescriptionLength, sub.id, "Subsystem", isSubDraft);
|
|
7242
7281
|
const complexityConfig = ctx.complexityConfigFor(sub.id);
|
|
7243
7282
|
const directComponents = ctx.components.filter((c) => c.subsystem === sub.id).length;
|
|
7244
7283
|
if (complexityConfig?.maxSubsystemComponents !== void 0 && directComponents > complexityConfig.maxSubsystemComponents) {
|
|
@@ -7246,7 +7285,8 @@ var init_complexity_and_metadata = __esm({
|
|
|
7246
7285
|
"warning",
|
|
7247
7286
|
"EXCESSIVE_SUBSYSTEM_COMPONENTS",
|
|
7248
7287
|
`Subsystem "${sub.id}" has ${directComponents} direct components, exceeding the configured limit of ${complexityConfig.maxSubsystemComponents}.`,
|
|
7249
|
-
sub.id
|
|
7288
|
+
sub.id,
|
|
7289
|
+
isSubDraft
|
|
7250
7290
|
);
|
|
7251
7291
|
}
|
|
7252
7292
|
}
|
|
@@ -7322,7 +7362,7 @@ var init_complexity_and_metadata = __esm({
|
|
|
7322
7362
|
const intf = ctx.interfaceMap.get(impl.contract);
|
|
7323
7363
|
const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
|
|
7324
7364
|
const complexityConfig = ctx.complexityConfigFor(comp?.subsystem);
|
|
7325
|
-
const isDraft =
|
|
7365
|
+
const isDraft = ctx.isImplementationDraft(impl);
|
|
7326
7366
|
if (complexityConfig?.maxNarrativeSteps !== void 0) {
|
|
7327
7367
|
for (const m of impl.methods) {
|
|
7328
7368
|
if (m.narrative.length > complexityConfig.maxNarrativeSteps) {
|
|
@@ -14029,7 +14069,7 @@ function buildRuleContext(opts) {
|
|
|
14029
14069
|
return rules.sddRuleSeverity[ruleCode];
|
|
14030
14070
|
}
|
|
14031
14071
|
const sub = subsystemId ? subsystems.find((s) => s.id === subsystemId) : void 0;
|
|
14032
|
-
const profileSeverity = extensions.profiles[sub?.profile
|
|
14072
|
+
const profileSeverity = extensions.profiles[sub?.profile || projectType]?.rules?.sddRuleSeverity?.[ruleCode];
|
|
14033
14073
|
if (profileSeverity) {
|
|
14034
14074
|
return profileSeverity;
|
|
14035
14075
|
}
|