@wairon/cli 5.1.1-dev.42 → 5.1.1-dev.44
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 +245 -60
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +225 -49
- package/dist/index.js.map +1 -1
- package/dist/templates/skills/sdd-narrative.md +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -556,6 +556,31 @@ var init_project = __esm({
|
|
|
556
556
|
kind: CarriedDebtKindSchema,
|
|
557
557
|
/** The reason itself, in the author's own words — what is true here, and what paying it would take. */
|
|
558
558
|
why: import_zod3.z.string().min(1),
|
|
559
|
+
/**
|
|
560
|
+
* This classification is PROVISIONAL, and this is what would settle it.
|
|
561
|
+
*
|
|
562
|
+
* A confident-sounding `why` that is wrong is worse than a missing one: it
|
|
563
|
+
* reads as settled, so nobody looks again, and the register keeps counting
|
|
564
|
+
* the finding under a kind that was never true. Nothing can check a reason
|
|
565
|
+
* for truth — `kind` and `why` are prose, and STALE_CARRIED_FINDING only
|
|
566
|
+
* ever catches "this stopped applying", never "this still applies for a
|
|
567
|
+
* reason that has become false". What a register CAN do is let an author say
|
|
568
|
+
* out loud that they are not sure yet, and then keep saying it: every run
|
|
569
|
+
* counts the groups marked here beside the kind totals, so the uncertainty
|
|
570
|
+
* is as loud as the debt.
|
|
571
|
+
*
|
|
572
|
+
* It is deliberately a SENTENCE and not a flag, and deliberately on the
|
|
573
|
+
* reason GROUP rather than the finding: what is provisional is the reason,
|
|
574
|
+
* and a reader deciding whether to pick this up needs to know what to
|
|
575
|
+
* measure — "is the adapter really realized by the file that consumes it, or
|
|
576
|
+
* is that a modelling error?" — not merely that somebody once hesitated. A
|
|
577
|
+
* finding whose own classification is uncertain while its neighbours' is not
|
|
578
|
+
* is a different reason, and belongs in its own group.
|
|
579
|
+
*
|
|
580
|
+
* There is no counterpart on `lint.allow`, and that is a decision, not an
|
|
581
|
+
* omission: see the note on ConformanceRuleConfig.carried.
|
|
582
|
+
*/
|
|
583
|
+
revisit: import_zod3.z.string().min(1).optional(),
|
|
559
584
|
/** The findings this reason explains. */
|
|
560
585
|
findings: import_zod3.z.array(CarriedFindingSchema)
|
|
561
586
|
});
|
|
@@ -601,6 +626,17 @@ var init_project = __esm({
|
|
|
601
626
|
* • every entry states its kind and its reason, and `wairon validate`
|
|
602
627
|
* prints the running total, so the debt is loud where a suppression is
|
|
603
628
|
* silent.
|
|
629
|
+
*
|
|
630
|
+
* A reason group may also declare itself PROVISIONAL (`revisit`), and the
|
|
631
|
+
* total says how many did. There is no such marker on a `lint.allow`, and
|
|
632
|
+
* the asymmetry is the point: an entry here does not silence anything — the
|
|
633
|
+
* finding is counted out loud on every run — so marking one uncertain adds a
|
|
634
|
+
* second dial to something already visible. An allow DOES silence, and a
|
|
635
|
+
* "provisional allow" would buy the silence and defer the decision, which is
|
|
636
|
+
* the one combination that cannot be reviewed: the finding is gone and the
|
|
637
|
+
* doubt is in a field nobody opens. The mechanism for a decision nobody has
|
|
638
|
+
* taken is to not take it — leave the warning firing — or, where the code is
|
|
639
|
+
* carryable, an entry of kind `undecided`, which is exactly that sentence.
|
|
604
640
|
*/
|
|
605
641
|
carried: import_zod3.z.array(CarriedDebtSchema).optional()
|
|
606
642
|
});
|
|
@@ -1186,7 +1222,7 @@ var init_defaults = __esm({
|
|
|
1186
1222
|
copilot: ".github/prompts",
|
|
1187
1223
|
codex: ".codex/agents"
|
|
1188
1224
|
};
|
|
1189
|
-
WAIRON_VERSION = "5.1.1-dev.
|
|
1225
|
+
WAIRON_VERSION = "5.1.1-dev.44";
|
|
1190
1226
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
1191
1227
|
ARCHITECT_AGENT_ID = "agent-architect";
|
|
1192
1228
|
ARCHITECT_TEMPLATE_ID = "architect";
|
|
@@ -1538,6 +1574,11 @@ function passesIntentFloor(text, methodName) {
|
|
|
1538
1574
|
if (norm(t) === norm(methodName)) return false;
|
|
1539
1575
|
return true;
|
|
1540
1576
|
}
|
|
1577
|
+
function parseDeclaredCall(ref) {
|
|
1578
|
+
const at = ref.lastIndexOf(".");
|
|
1579
|
+
if (at <= 0 || at === ref.length - 1) return null;
|
|
1580
|
+
return { compId: ref.slice(0, at), methodName: ref.slice(at + 1) };
|
|
1581
|
+
}
|
|
1541
1582
|
function methodSourceFile(method, implementationSourcePath) {
|
|
1542
1583
|
return method.sourcePath || implementationSourcePath || void 0;
|
|
1543
1584
|
}
|
|
@@ -1698,8 +1739,28 @@ var init_specs = __esm({
|
|
|
1698
1739
|
LintAllowSchema = import_zod8.z.object({
|
|
1699
1740
|
/** The issue code being allowed (see `wairon rules list`). */
|
|
1700
1741
|
code: import_zod8.z.string(),
|
|
1742
|
+
/**
|
|
1743
|
+
* The SITE inside this spec the allow covers, named exactly as the finding
|
|
1744
|
+
* names it (a contract method, an import edge "from -> to", a declared edge
|
|
1745
|
+
* "component -> target"). A finding that names a site is covered ONLY by an
|
|
1746
|
+
* allow naming that same site; a finding that names none is covered only by
|
|
1747
|
+
* an allow that names none either.
|
|
1748
|
+
*/
|
|
1749
|
+
at: import_zod8.z.string().min(1).optional(),
|
|
1750
|
+
/**
|
|
1751
|
+
* The units of an AGGREGATING finding this allow covers — the steps of one
|
|
1752
|
+
* CALL_STEP_UNREALIZED, the crossings of one UNDECLARED_COLOCATED_CALL —
|
|
1753
|
+
* each named the way the finding's own message names it. The allow silences
|
|
1754
|
+
* the finding only when it lists EVERY unit reported, so a unit nobody
|
|
1755
|
+
* decided on surfaces on the day it appears instead of inheriting a decision
|
|
1756
|
+
* taken about its neighbours. Meaningless without `at`, and refused there.
|
|
1757
|
+
*/
|
|
1758
|
+
covers: import_zod8.z.array(import_zod8.z.string()).optional(),
|
|
1701
1759
|
/** Why this finding is acceptable here (e.g. "dispatcher — fan-out is the point"). */
|
|
1702
1760
|
reason: import_zod8.z.string().min(1)
|
|
1761
|
+
}).refine((a) => !a.covers?.length || !!a.at, {
|
|
1762
|
+
message: "`covers` names the units of ONE finding, so it needs the `at` that says which finding \u2014 add the site, or drop covers.",
|
|
1763
|
+
path: ["covers"]
|
|
1703
1764
|
});
|
|
1704
1765
|
LintConfigSchema = import_zod8.z.object({
|
|
1705
1766
|
allow: import_zod8.z.array(LintAllowSchema).default([])
|
|
@@ -2148,6 +2209,31 @@ var init_specs = __esm({
|
|
|
2148
2209
|
intent: import_zod8.z.string().optional(),
|
|
2149
2210
|
/** Conformance tier for THIS method (overrides the spec-level default). */
|
|
2150
2211
|
conformance: ConformanceTierSchema.optional(),
|
|
2212
|
+
/**
|
|
2213
|
+
* The calls this method makes, when its narrative does not show them.
|
|
2214
|
+
*
|
|
2215
|
+
* The mirror of the contract's `invokedBy`: that declares the caller
|
|
2216
|
+
* OUTSIDE the modeled graph, this declares the callees INSIDE it. It lives
|
|
2217
|
+
* here and not on the L3 method because what a method CALLS is a property
|
|
2218
|
+
* of the realization — the detail dial that hides the steps is here, and two
|
|
2219
|
+
* implementations of one contract may reach different collaborators —
|
|
2220
|
+
* whereas an external caller is a fact about the contract's role that must
|
|
2221
|
+
* hold for every realization.
|
|
2222
|
+
*
|
|
2223
|
+
* Each entry names one target the way the debt register and a lint allow
|
|
2224
|
+
* name a unit: `<component>.<method>` (a `covers` entry is the same
|
|
2225
|
+
* spelling with the step number a declaration has no equivalent of). A
|
|
2226
|
+
* third vocabulary for "which method" would be the worse outcome.
|
|
2227
|
+
*
|
|
2228
|
+
* The narrative graph walk takes exactly these edges for a method with no
|
|
2229
|
+
* narrative, and NOTHING else: an undeclared collaborator is not reached,
|
|
2230
|
+
* so a lower detail dial no longer vouches for everything the component
|
|
2231
|
+
* declares. Each entry gets the same target validation a `call` step gets
|
|
2232
|
+
* (the component resolves, the caller declares it, the method is on its
|
|
2233
|
+
* contract). Refused beside a non-empty narrative, where the steps already
|
|
2234
|
+
* say what is called and a second spelling could only disagree.
|
|
2235
|
+
*/
|
|
2236
|
+
calls: import_zod8.z.array(import_zod8.z.string().min(1)).optional(),
|
|
2151
2237
|
/**
|
|
2152
2238
|
* The code-level name realizing this contract method in the sourcePath
|
|
2153
2239
|
* file, when it legitimately differs from the intent-language contract
|
|
@@ -2184,7 +2270,16 @@ var init_specs = __esm({
|
|
|
2184
2270
|
* stereotypes should bind tech directly (TECH_ON_LOGIC_COMPONENT).
|
|
2185
2271
|
*/
|
|
2186
2272
|
technologies: import_zod8.z.array(import_zod8.z.string()).optional(),
|
|
2187
|
-
methods: import_zod8.z.array(MethodImplementationSchema).default([]),
|
|
2273
|
+
methods: import_zod8.z.array(MethodImplementationSchema).default([]).superRefine((methods, ctx) => {
|
|
2274
|
+
methods.forEach((m, i) => {
|
|
2275
|
+
if (!m.calls?.length || m.narrative.length === 0) return;
|
|
2276
|
+
ctx.addIssue({
|
|
2277
|
+
code: import_zod8.z.ZodIssueCode.custom,
|
|
2278
|
+
path: [i, "calls"],
|
|
2279
|
+
message: `Method "${m.name}" declares calls AND a narrative \u2014 \`calls\` says what a method calls when its narrative does not show it, and this one has ${m.narrative.length} step(s) that already do. Add the call step, or drop the declaration.`
|
|
2280
|
+
});
|
|
2281
|
+
});
|
|
2282
|
+
}),
|
|
2188
2283
|
/** Spec-level narrative detail default for all methods (each may override). */
|
|
2189
2284
|
detail: NarrativeDetailSchema.optional(),
|
|
2190
2285
|
/** Spec-level conformance tier default (each method may override). */
|
|
@@ -9640,10 +9735,10 @@ var init_lint_allows = __esm({
|
|
|
9640
9735
|
"use strict";
|
|
9641
9736
|
lintAllowsRule = {
|
|
9642
9737
|
name: "lint-allows",
|
|
9643
|
-
description: "Per-spec lint suppressions (lint.allow) must name real issue codes and actually suppress a finding \u2014 unknown codes and stale allows are flagged. Allows silence warnings only; errors always surface.",
|
|
9738
|
+
description: "Per-spec lint suppressions (lint.allow) must name real issue codes and actually suppress a finding \u2014 unknown codes and stale allows are flagged. An allow covers exactly the occurrence it names: a finding that reports a site is silenced only by an allow whose `at` is that site, a finding that reports none only by an allow that names none, and an aggregating finding only by an allow whose `covers` lists every unit it reports \u2014 a unit nobody listed is named back as new instead of inheriting a decision taken about its neighbours. So a coarse allow left on a rule that names sites, and an allow whose site the run no longer reports, are both UNUSED_LINT_ALLOW, and the finding names the sites that did fire. Allows silence warnings only; errors always surface.",
|
|
9644
9739
|
codes: [
|
|
9645
9740
|
{ code: "UNKNOWN_LINT_ALLOW_CODE", defaultSeverity: "warning", summary: "lint.allow names an issue code no registered rule emits" },
|
|
9646
|
-
{ code: "UNUSED_LINT_ALLOW", defaultSeverity: "warning", summary: "lint.allow entry matched no finding this run \u2014
|
|
9741
|
+
{ code: "UNUSED_LINT_ALLOW", defaultSeverity: "warning", summary: "lint.allow entry matched no finding this run \u2014 the code never fired, or it fired at sites this allow does not name" }
|
|
9647
9742
|
],
|
|
9648
9743
|
check(ctx) {
|
|
9649
9744
|
for (const a of ctx.lintAllows) {
|
|
@@ -9656,14 +9751,24 @@ var init_lint_allows = __esm({
|
|
|
9656
9751
|
);
|
|
9657
9752
|
continue;
|
|
9658
9753
|
}
|
|
9659
|
-
if (
|
|
9660
|
-
|
|
9661
|
-
|
|
9662
|
-
|
|
9663
|
-
|
|
9664
|
-
|
|
9665
|
-
|
|
9754
|
+
if (a.used) continue;
|
|
9755
|
+
const reported = ctx.sitesReported(a.specId, a.code);
|
|
9756
|
+
const at = a.at ? ` at "${a.at}"` : "";
|
|
9757
|
+
let why;
|
|
9758
|
+
if (a.at && reported.unsited && reported.sites.length === 0) {
|
|
9759
|
+
why = `findings of "${a.code}" on this spec name no site at all, so this allow must not name one \u2014 drop the \`at\``;
|
|
9760
|
+
} else if (reported.sites.length > 0) {
|
|
9761
|
+
const sites = reported.sites.map((s) => `"${s}"`).join("; ");
|
|
9762
|
+
why = a.at ? `that code fired at ${sites} instead \u2014 retarget the allow, or remove it` : `that code fired at ${sites}, and a sited finding is covered only by an allow naming its site \u2014 give this allow an \`at\` (one per site, each with its own reason), or remove it`;
|
|
9763
|
+
} else {
|
|
9764
|
+
why = "no such finding fired this run \u2014 remove the stale allow";
|
|
9666
9765
|
}
|
|
9766
|
+
ctx.addIssue(
|
|
9767
|
+
"warning",
|
|
9768
|
+
"UNUSED_LINT_ALLOW",
|
|
9769
|
+
`Spec "${a.specId}" allows "${a.code}"${at} (reason: ${a.reason}), but ${why}.`,
|
|
9770
|
+
a.specId
|
|
9771
|
+
);
|
|
9667
9772
|
}
|
|
9668
9773
|
}
|
|
9669
9774
|
};
|
|
@@ -9718,16 +9823,36 @@ var init_contract_symmetry = __esm({
|
|
|
9718
9823
|
});
|
|
9719
9824
|
|
|
9720
9825
|
// src/core/rules/narrative/narrative-target-references.ts
|
|
9721
|
-
var narrativeTargetReferencesRule;
|
|
9826
|
+
var targetsOf, narrativeTargetReferencesRule;
|
|
9722
9827
|
var init_narrative_target_references = __esm({
|
|
9723
9828
|
"src/core/rules/narrative/narrative-target-references.ts"() {
|
|
9724
9829
|
"use strict";
|
|
9830
|
+
init_models();
|
|
9831
|
+
targetsOf = (implMethod) => {
|
|
9832
|
+
const entries = [];
|
|
9833
|
+
for (const step of implMethod.narrative) {
|
|
9834
|
+
if (step.type !== "call" && step.type !== "dispatch" && step.type !== "register") continue;
|
|
9835
|
+
entries.push({
|
|
9836
|
+
kind: step.type,
|
|
9837
|
+
stepNumber: step.stepNumber,
|
|
9838
|
+
targetComponent: step.targetComponent,
|
|
9839
|
+
targetMethod: step.targetMethod,
|
|
9840
|
+
assertsGuarantees: step.assertsGuarantees
|
|
9841
|
+
});
|
|
9842
|
+
}
|
|
9843
|
+
for (const ref of implMethod.calls ?? []) {
|
|
9844
|
+
const parsed = parseDeclaredCall(ref);
|
|
9845
|
+
entries.push(parsed ? { kind: "declared", targetComponent: parsed.compId, targetMethod: parsed.methodName } : { kind: "declared", unparsed: ref });
|
|
9846
|
+
}
|
|
9847
|
+
return entries;
|
|
9848
|
+
};
|
|
9725
9849
|
narrativeTargetReferencesRule = {
|
|
9726
9850
|
name: "narrative-target-references",
|
|
9727
|
-
description:
|
|
9851
|
+
description: `A narrative call, dispatch or register step must name a target component, and a target method unless it dispatches; a method's declared calls name the same pair as one "<component>.<method>" reference, the spelling the conformance debt register and a lint allow name a unit with. A target this tree contains must be a collaborator the calling component declares, must carry the named method on one of its interfaces, and that method must declare every semantic guarantee the entry asserts.`,
|
|
9728
9852
|
codes: [
|
|
9729
9853
|
{ code: "MISSING_TARGET_COMPONENT", defaultSeverity: "error", summary: "Call/register step missing targetComponent" },
|
|
9730
9854
|
{ code: "MISSING_TARGET_METHOD", defaultSeverity: "error", summary: "Call/register step missing targetMethod" },
|
|
9855
|
+
{ code: "MALFORMED_DECLARED_CALL", defaultSeverity: "error", summary: 'Declared call that is not a "<component>.<method>" reference' },
|
|
9731
9856
|
{ code: "UNDECLARED_DEPENDENCY_CALL", defaultSeverity: "error", summary: "Call step targets a component the caller does not depend on or own" },
|
|
9732
9857
|
{ code: "INVALID_TARGET_METHOD_REFERENCE", defaultSeverity: "error", summary: "Call step targets a method not on any target interface" },
|
|
9733
9858
|
{ code: "NARRATIVE_SEMANTIC_UNBACKED", defaultSeverity: "warning", summary: "Narrative asserts a guarantee the called contract does not declare" }
|
|
@@ -9739,25 +9864,35 @@ var init_narrative_target_references = __esm({
|
|
|
9739
9864
|
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
9740
9865
|
const caller = ctx.componentMap.get(contract.component);
|
|
9741
9866
|
for (const implMethod of impl.methods) {
|
|
9742
|
-
for (const
|
|
9743
|
-
|
|
9744
|
-
if (
|
|
9867
|
+
for (const entry of targetsOf(implMethod)) {
|
|
9868
|
+
const where = entry.stepNumber === void 0 ? "" : ` (step ${entry.stepNumber})`;
|
|
9869
|
+
if (entry.unparsed !== void 0) {
|
|
9870
|
+
ctx.addIssue(
|
|
9871
|
+
"error",
|
|
9872
|
+
"MALFORMED_DECLARED_CALL",
|
|
9873
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" declares the call "${entry.unparsed}", which is not a "<component>.<method>" reference \u2014 the spelling the conformance debt register and a lint allow name a unit with.`,
|
|
9874
|
+
impl.id,
|
|
9875
|
+
isDraftCtx
|
|
9876
|
+
);
|
|
9877
|
+
continue;
|
|
9878
|
+
}
|
|
9879
|
+
if (!entry.targetComponent) {
|
|
9745
9880
|
ctx.addIssue(
|
|
9746
9881
|
"error",
|
|
9747
9882
|
"MISSING_TARGET_COMPONENT",
|
|
9748
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" has a ${
|
|
9883
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" has a ${entry.kind} step (${entry.stepNumber}) missing "targetComponent".`,
|
|
9749
9884
|
impl.id,
|
|
9750
9885
|
isDraftCtx
|
|
9751
9886
|
);
|
|
9752
9887
|
continue;
|
|
9753
9888
|
}
|
|
9754
|
-
const target =
|
|
9755
|
-
const verb =
|
|
9756
|
-
if (
|
|
9889
|
+
const target = entry.targetComponent;
|
|
9890
|
+
const verb = entry.kind === "dispatch" ? "dispatches through" : entry.kind === "register" ? "registers callback" : entry.kind === "declared" ? "declares a call to" : "calls";
|
|
9891
|
+
if (entry.kind !== "dispatch" && !entry.targetMethod) {
|
|
9757
9892
|
ctx.addIssue(
|
|
9758
9893
|
"error",
|
|
9759
9894
|
"MISSING_TARGET_METHOD",
|
|
9760
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" has a ${
|
|
9895
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" has a ${entry.kind} step (${entry.stepNumber}) missing "targetMethod".`,
|
|
9761
9896
|
impl.id,
|
|
9762
9897
|
isDraftCtx
|
|
9763
9898
|
);
|
|
@@ -9768,16 +9903,16 @@ var init_narrative_target_references = __esm({
|
|
|
9768
9903
|
ctx.addIssue(
|
|
9769
9904
|
"error",
|
|
9770
9905
|
"UNDECLARED_DEPENDENCY_CALL",
|
|
9771
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" (component "${caller.id}") ${verb} component "${target}"
|
|
9906
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" (component "${caller.id}") ${verb} component "${target}"${where} but component "${caller.id}" does not list "${target}" as a dependency.`,
|
|
9772
9907
|
impl.id,
|
|
9773
9908
|
isDraftCtx || ctx.isComponentDraft(caller.id)
|
|
9774
9909
|
);
|
|
9775
9910
|
}
|
|
9776
|
-
if (
|
|
9911
|
+
if (entry.kind === "dispatch") continue;
|
|
9777
9912
|
const targetInterfaces = ctx.interfacesByComponent.get(target) ?? [];
|
|
9778
9913
|
let targetMethodSpec;
|
|
9779
9914
|
for (const targetIntf of targetInterfaces) {
|
|
9780
|
-
const found = targetIntf.methods.find((m) => m.name ===
|
|
9915
|
+
const found = targetIntf.methods.find((m) => m.name === entry.targetMethod);
|
|
9781
9916
|
if (found) {
|
|
9782
9917
|
targetMethodSpec = found;
|
|
9783
9918
|
break;
|
|
@@ -9787,19 +9922,19 @@ var init_narrative_target_references = __esm({
|
|
|
9787
9922
|
ctx.addIssue(
|
|
9788
9923
|
"error",
|
|
9789
9924
|
"INVALID_TARGET_METHOD_REFERENCE",
|
|
9790
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} method "${
|
|
9925
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} method "${entry.targetMethod}" on component "${target}" which is not defined on any of its interfaces${where}.`,
|
|
9791
9926
|
impl.id,
|
|
9792
9927
|
isDraftCtx || ctx.isComponentDraft(target)
|
|
9793
9928
|
);
|
|
9794
9929
|
continue;
|
|
9795
9930
|
}
|
|
9796
9931
|
const declared = new Set(targetMethodSpec.guarantees ?? []);
|
|
9797
|
-
for (const g of
|
|
9932
|
+
for (const g of entry.assertsGuarantees ?? []) {
|
|
9798
9933
|
if (!declared.has(g)) {
|
|
9799
9934
|
ctx.addIssue(
|
|
9800
9935
|
"warning",
|
|
9801
9936
|
"NARRATIVE_SEMANTIC_UNBACKED",
|
|
9802
|
-
`Step ${
|
|
9937
|
+
`Step ${entry.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" explicitly asserts guarantee "${g}", but the method it calls \u2014 "${entry.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.`,
|
|
9803
9938
|
impl.id,
|
|
9804
9939
|
isDraftCtx || ctx.isComponentDraft(target)
|
|
9805
9940
|
);
|
|
@@ -12490,15 +12625,11 @@ function walk(ctx, seeds, options = {}) {
|
|
|
12490
12625
|
if (edge.methodName) enqueueMethod(edge.compId, edge.methodName);
|
|
12491
12626
|
}
|
|
12492
12627
|
}
|
|
12493
|
-
|
|
12494
|
-
const
|
|
12495
|
-
if (
|
|
12496
|
-
|
|
12497
|
-
|
|
12498
|
-
reachComponent(depId);
|
|
12499
|
-
enqueueAllMethods(depId);
|
|
12500
|
-
}
|
|
12501
|
-
}
|
|
12628
|
+
for (const ref of methodImpl.calls ?? []) {
|
|
12629
|
+
const parsed = parseDeclaredCall(ref);
|
|
12630
|
+
if (!parsed || !ctx.componentMap.has(parsed.compId)) continue;
|
|
12631
|
+
reachComponent(parsed.compId);
|
|
12632
|
+
enqueueMethod(parsed.compId, parsed.methodName);
|
|
12502
12633
|
}
|
|
12503
12634
|
}
|
|
12504
12635
|
return {
|
|
@@ -12563,7 +12694,9 @@ var init_unused_detection = __esm({
|
|
|
12563
12694
|
"INVOKED_BY_REDUNDANT",
|
|
12564
12695
|
`Method "${m.name}" on component "${intf.component}" declares invokedBy (${m.invokedBy.kind}), but the internal narrative walk already reaches it \u2014 the declaration is stale; remove it.`,
|
|
12565
12696
|
intf.id,
|
|
12566
|
-
isDraftCtx
|
|
12697
|
+
isDraftCtx,
|
|
12698
|
+
void 0,
|
|
12699
|
+
{ at: m.name }
|
|
12567
12700
|
);
|
|
12568
12701
|
}
|
|
12569
12702
|
}
|
|
@@ -12591,7 +12724,11 @@ var init_unused_detection = __esm({
|
|
|
12591
12724
|
"UNUSED_METHOD",
|
|
12592
12725
|
`Method "${m.name}" on component "${comp.id}" is defined but never called by any narrative step.`,
|
|
12593
12726
|
intf.id,
|
|
12594
|
-
isDraftCtx
|
|
12727
|
+
isDraftCtx,
|
|
12728
|
+
void 0,
|
|
12729
|
+
// The site is the method, so an allow covers exactly the one it
|
|
12730
|
+
// names - never its neighbours on the same interface.
|
|
12731
|
+
{ at: m.name }
|
|
12595
12732
|
);
|
|
12596
12733
|
}
|
|
12597
12734
|
}
|
|
@@ -13682,7 +13819,13 @@ var init_dependency_conformance = __esm({
|
|
|
13682
13819
|
"UNDECLARED_DEPENDENCY",
|
|
13683
13820
|
`"${fromPath}" (realizing ${fromComponents.map((c) => c.id).join(", ")}) imports "${toPath}" (realizing ${toComponents.map((c) => c.id).join(", ")}) but no declared dependsOn/owns edge justifies it \u2014 declare the collaboration on the component that actually uses it, or route the cross-subsystem hop through the target's published surface.`,
|
|
13684
13821
|
realization.implementationsAt(fromPath)[0]?.id,
|
|
13685
|
-
draftAt(fromPath) || draftAt(toPath)
|
|
13822
|
+
draftAt(fromPath) || draftAt(toPath),
|
|
13823
|
+
void 0,
|
|
13824
|
+
// One import edge, one indivisible fact — but one implementation
|
|
13825
|
+
// maps many files and many edges, so the EDGE is the site, not the
|
|
13826
|
+
// spec. Without it a single allow on the spec covers every crossing
|
|
13827
|
+
// that file ever grows.
|
|
13828
|
+
{ at: `${fromPath} -> ${toPath}` }
|
|
13686
13829
|
);
|
|
13687
13830
|
}
|
|
13688
13831
|
}
|
|
@@ -13709,7 +13852,12 @@ var init_dependency_conformance = __esm({
|
|
|
13709
13852
|
"UNREALIZED_DEPENDENCY",
|
|
13710
13853
|
`Component "${component.id}" declares ${relation} "${targetId}", but no runtime import connects their source files (${fromFiles.join(", ")} \u219B ${crossSubsystem ? `subsystem ${target.subsystem}` : filesOf(targetId).join(", ")}) \u2014 either the collaboration is wired indirectly (DI) or the declared edge is stale.`,
|
|
13711
13854
|
impls[0]?.id ?? component.id,
|
|
13712
|
-
draft
|
|
13855
|
+
draft,
|
|
13856
|
+
void 0,
|
|
13857
|
+
// One declared edge, one indivisible fact. A component declares many,
|
|
13858
|
+
// and they all anchor on its one implementation spec, so the EDGE is
|
|
13859
|
+
// the site.
|
|
13860
|
+
{ at: `${component.id} -> ${targetId}` }
|
|
13713
13861
|
);
|
|
13714
13862
|
}
|
|
13715
13863
|
}
|
|
@@ -14068,7 +14216,7 @@ var init_carried_debt = __esm({
|
|
|
14068
14216
|
"use strict";
|
|
14069
14217
|
carriedDebtRule = {
|
|
14070
14218
|
name: "carried-debt",
|
|
14071
|
-
description: "The conformance debt register (`rules.conformance.carried`) holds the code\u2194spec findings this tree carries as declared, classified debt \u2014 `unclaimed`'s one-way shape, for findings about code a spec does name. It is not a second lint.allow: an allow says a finding is wrong here by design, an entry here says the finding is right and unpaid, and each entry states which of the three kinds it is (drift: spec and code disagree; undecided: the fix waits on a modelling decision nobody has taken; unreadable: the analysis cannot follow the shape the code is written in) and why. Only a code a rule declares CARRYABLE may appear, and naming any other is an error, so the register can never widen into general-purpose suppression. An entry carries a finding only when it lists EVERY unit that finding reports, so an aggregating finding cannot grow behind it; an entry that matched nothing, that duplicates another, that names a spec the tree does not hold, or that lists a unit no longer reported is STALE_CARRIED_FINDING, so the register only shrinks. The audit is silent in a run that could not have fired the findings at all: one that built no code model, the specs outside a scoped run, and a code the project switched off.",
|
|
14219
|
+
description: "The conformance debt register (`rules.conformance.carried`) holds the code\u2194spec findings this tree carries as declared, classified debt \u2014 `unclaimed`'s one-way shape, for findings about code a spec does name. It is not a second lint.allow: an allow says a finding is wrong here by design, an entry here says the finding is right and unpaid, and each entry states which of the three kinds it is (drift: spec and code disagree; undecided: the fix waits on a modelling decision nobody has taken; unreadable: the analysis cannot follow the shape the code is written in) and why. Only a code a rule declares CARRYABLE may appear, and naming any other is an error, so the register can never widen into general-purpose suppression. An entry carries a finding only when it lists EVERY unit that finding reports, so an aggregating finding cannot grow behind it; an entry that matched nothing, that duplicates another, that names a spec the tree does not hold, or that lists a unit no longer reported is STALE_CARRIED_FINDING, so the register only shrinks. The audit is silent in a run that could not have fired the findings at all: one that built no code model, the specs outside a scoped run, and a code the project switched off. Nothing can check a reason for TRUTH \u2014 a stale entry is one that stopped applying, never one that still applies for a reason that has become false \u2014 so a reason group may declare its own classification provisional (`revisit`: what would settle it), and every run counts those findings beside the kind totals.",
|
|
14072
14220
|
codes: [
|
|
14073
14221
|
{ code: "UNCARRYABLE_FINDING", defaultSeverity: "error", summary: "The conformance debt register names an issue code no rule emits, or one no rule declares carryable \u2014 the register holds measured code\u2194spec debt, never a design rule somebody wants quiet" },
|
|
14074
14222
|
{ code: "STALE_CARRIED_FINDING", defaultSeverity: "warning", summary: "An entry of the conformance debt register carries a finding that no longer fires, or lists a unit that finding no longer reports \u2014 delete it, the register only shrinks" }
|
|
@@ -16446,12 +16594,20 @@ function buildRuleContext(opts) {
|
|
|
16446
16594
|
const allowLookup = /* @__PURE__ */ new Map();
|
|
16447
16595
|
const collectAllows = (specId, lint) => {
|
|
16448
16596
|
for (const a of lint?.allow ?? []) {
|
|
16449
|
-
const entry = { specId, code: a.code, reason: a.reason, used: false };
|
|
16597
|
+
const entry = { specId, code: a.code, at: a.at, covers: a.covers, reason: a.reason, used: false };
|
|
16450
16598
|
lintAllows.push(entry);
|
|
16451
16599
|
if (!allowLookup.has(specId)) allowLookup.set(specId, /* @__PURE__ */ new Map());
|
|
16452
|
-
allowLookup.get(specId)
|
|
16600
|
+
const forSpec = allowLookup.get(specId);
|
|
16601
|
+
const forCode = forSpec.get(a.code) ?? [];
|
|
16602
|
+
forCode.push(entry);
|
|
16603
|
+
forSpec.set(a.code, forCode);
|
|
16453
16604
|
}
|
|
16454
16605
|
};
|
|
16606
|
+
const sitesSeen = /* @__PURE__ */ new Map();
|
|
16607
|
+
const sitesReported = (specId, code) => {
|
|
16608
|
+
const seen = sitesSeen.get(`${specId}\0${code}`);
|
|
16609
|
+
return { sites: [...seen?.sites ?? []], unsited: seen?.unsited ?? false };
|
|
16610
|
+
};
|
|
16455
16611
|
for (const s of subsystems) collectAllows(s.id, s.lint);
|
|
16456
16612
|
for (const c of components) collectAllows(c.id, c.lint);
|
|
16457
16613
|
for (const i of interfaces) collectAllows(i.id, i.lint);
|
|
@@ -16488,15 +16644,29 @@ function buildRuleContext(opts) {
|
|
|
16488
16644
|
}
|
|
16489
16645
|
const severity = getRuleSeverity(code, defaultSeverity, isDraftContext, owner);
|
|
16490
16646
|
if (severity === "off") return;
|
|
16647
|
+
let text = message;
|
|
16648
|
+
if (specId) {
|
|
16649
|
+
const key = `${specId}\0${code}`;
|
|
16650
|
+
let seen = sitesSeen.get(key);
|
|
16651
|
+
if (!seen) sitesSeen.set(key, seen = { sites: /* @__PURE__ */ new Set(), unsited: false });
|
|
16652
|
+
if (parts) seen.sites.add(parts.at);
|
|
16653
|
+
else seen.unsited = true;
|
|
16654
|
+
}
|
|
16655
|
+
let allowClaimed = false;
|
|
16491
16656
|
if (specId) {
|
|
16492
|
-
const allow = allowLookup.get(specId)?.get(code);
|
|
16657
|
+
const allow = (allowLookup.get(specId)?.get(code) ?? []).find((a) => parts ? a.at === parts.at : a.at === void 0);
|
|
16493
16658
|
if (allow) {
|
|
16494
16659
|
allow.used = true;
|
|
16495
|
-
|
|
16660
|
+
allowClaimed = true;
|
|
16661
|
+
const grew = (parts?.covers ?? []).filter((unit) => !(allow.covers ?? []).includes(unit));
|
|
16662
|
+
if (grew.length === 0) {
|
|
16663
|
+
if (severity === "warning") return;
|
|
16664
|
+
} else {
|
|
16665
|
+
text = `${text} A lint.allow covers this site, but not ${grew.length} part(s) of it \u2014 ${grew.map((u) => `"${u}"`).join("; ")} ${grew.length === 1 ? "is" : "are"} new. Decide on them: add them to the allow's \`covers\` with a reason that is actually true, or fix them.`;
|
|
16666
|
+
}
|
|
16496
16667
|
}
|
|
16497
16668
|
}
|
|
16498
|
-
|
|
16499
|
-
if (specId && parts && severity === "warning") {
|
|
16669
|
+
if (specId && parts && severity === "warning" && !allowClaimed) {
|
|
16500
16670
|
const entry = carriedLookup.get(carriedKey(specId, code, parts.at));
|
|
16501
16671
|
if (entry) {
|
|
16502
16672
|
entry.fired = true;
|
|
@@ -16589,6 +16759,7 @@ function buildRuleContext(opts) {
|
|
|
16589
16759
|
codeModel: opts.codeModel ?? emptyCodeModel(),
|
|
16590
16760
|
roundTripIssues: opts.roundTripIssues,
|
|
16591
16761
|
lintAllows,
|
|
16762
|
+
sitesReported,
|
|
16592
16763
|
knownIssueCodes: opts.knownIssueCodes,
|
|
16593
16764
|
carriedFindings,
|
|
16594
16765
|
carryableIssueCodes: opts.carryableIssueCodes ?? /* @__PURE__ */ new Set(),
|
|
@@ -19143,9 +19314,11 @@ function identityKeyOf(field, item) {
|
|
|
19143
19314
|
return `${String(o.topic)} ${o.event === void 0 ? "" : String(o.event)}`;
|
|
19144
19315
|
case "trustedLinks":
|
|
19145
19316
|
return str(o.subsystem);
|
|
19317
|
+
// lint.allow: an allow is one decision about one OCCURRENCE, so several
|
|
19318
|
+
// may share a code on one spec, each naming its own site. Keyed by code
|
|
19319
|
+
// alone a delta for one site would overwrite its neighbours.
|
|
19146
19320
|
case "allow":
|
|
19147
|
-
return
|
|
19148
|
-
// lint.allow
|
|
19321
|
+
return `${String(o.code)} ${o.at === void 0 ? "" : String(o.at)}`;
|
|
19149
19322
|
case "findings":
|
|
19150
19323
|
return str(o.code);
|
|
19151
19324
|
// an interface method's findings
|
|
@@ -19233,6 +19406,7 @@ function identityFieldsOf(field) {
|
|
|
19233
19406
|
case "trustedLinks":
|
|
19234
19407
|
return ["subsystem"];
|
|
19235
19408
|
case "allow":
|
|
19409
|
+
return ["code", "at"];
|
|
19236
19410
|
case "findings":
|
|
19237
19411
|
return ["code"];
|
|
19238
19412
|
case "invariants":
|
|
@@ -27258,6 +27432,7 @@ __export(src_exports, {
|
|
|
27258
27432
|
packEntryLabel: () => packEntryLabel,
|
|
27259
27433
|
packEntryRef: () => packEntryRef,
|
|
27260
27434
|
packStoreDir: () => packStoreDir,
|
|
27435
|
+
parseDeclaredCall: () => parseDeclaredCall,
|
|
27261
27436
|
parseTemplate: () => parseTemplate,
|
|
27262
27437
|
parseYaml: () => parseYaml,
|
|
27263
27438
|
passesIntentFloor: () => passesIntentFloor,
|
|
@@ -27996,6 +28171,7 @@ function loadProjectConfig4() {
|
|
|
27996
28171
|
packEntryLabel,
|
|
27997
28172
|
packEntryRef,
|
|
27998
28173
|
packStoreDir,
|
|
28174
|
+
parseDeclaredCall,
|
|
27999
28175
|
parseTemplate,
|
|
28000
28176
|
parseYaml,
|
|
28001
28177
|
passesIntentFloor,
|