@wairon/cli 5.1.1-dev.41 → 5.1.1-dev.42
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 +583 -116
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +589 -116
- package/dist/index.js.map +1 -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.42";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
SUPPORTED_ALIASES = ["wai"];
|
|
71
71
|
SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -770,7 +770,7 @@ function activeTargetTypes(config) {
|
|
|
770
770
|
const targets = config.targets;
|
|
771
771
|
return targets.filter((t) => typeof t === "string" || t.enabled !== false).map((t) => typeof t === "string" ? t : t.type);
|
|
772
772
|
}
|
|
773
|
-
var import_zod4, BuiltinTargetConfigSchema, CustomTargetConfigSchema, TargetConfigSchema, NamingRuleConfigSchema, DocumentationRuleConfigSchema, ComplexityRuleConfigSchema, ConformanceRuleConfigSchema, DesignDepthSchema, RulesConfigSchema, PathsConfigSchema, PackSelectionSchema, ProfileSelectionSubjectSchema, ProjectProfileSelectionSchema, ProjectConfigSchema;
|
|
773
|
+
var import_zod4, BuiltinTargetConfigSchema, CustomTargetConfigSchema, TargetConfigSchema, NamingRuleConfigSchema, DocumentationRuleConfigSchema, ComplexityRuleConfigSchema, CarriedDebtKindSchema, CarriedFindingSchema, CarriedDebtSchema, ConformanceRuleConfigSchema, DesignDepthSchema, RulesConfigSchema, PathsConfigSchema, PackSelectionSchema, ProfileSelectionSubjectSchema, ProjectProfileSelectionSchema, ProjectConfigSchema;
|
|
774
774
|
var init_project = __esm({
|
|
775
775
|
"src/models/project.ts"() {
|
|
776
776
|
"use strict";
|
|
@@ -867,6 +867,32 @@ var init_project = __esm({
|
|
|
867
867
|
*/
|
|
868
868
|
maxCognitiveLevel: import_zod4.z.string().optional()
|
|
869
869
|
});
|
|
870
|
+
CarriedDebtKindSchema = import_zod4.z.enum(["drift", "undecided", "unreadable"]);
|
|
871
|
+
CarriedFindingSchema = import_zod4.z.object({
|
|
872
|
+
/** The issue code, which must be one a rule declares CARRYABLE — anything else is UNCARRYABLE_FINDING, an error. */
|
|
873
|
+
code: import_zod4.z.string(),
|
|
874
|
+
/** The spec the finding is anchored to. */
|
|
875
|
+
spec: import_zod4.z.string(),
|
|
876
|
+
/** The site inside that spec the finding names — for the code-vs-spec call checks, the contract method. */
|
|
877
|
+
at: import_zod4.z.string(),
|
|
878
|
+
/**
|
|
879
|
+
* The units an aggregating finding covers: the crossings of one
|
|
880
|
+
* UNDECLARED_COLOCATED_CALL, the steps of one CALL_STEP_UNREALIZED. Keyed by
|
|
881
|
+
* code + spec + site ALONE, a 24th crossing added to a finding that already
|
|
882
|
+
* lists 23 would be carried by an entry nobody wrote for it — so a live
|
|
883
|
+
* finding is carried only when every unit it reports is listed here, and a
|
|
884
|
+
* unit that appears is reported as new.
|
|
885
|
+
*/
|
|
886
|
+
covers: import_zod4.z.array(import_zod4.z.string()).optional()
|
|
887
|
+
});
|
|
888
|
+
CarriedDebtSchema = import_zod4.z.object({
|
|
889
|
+
/** Which of the three kinds this reason is (see CarriedDebtKindSchema). */
|
|
890
|
+
kind: CarriedDebtKindSchema,
|
|
891
|
+
/** The reason itself, in the author's own words — what is true here, and what paying it would take. */
|
|
892
|
+
why: import_zod4.z.string().min(1),
|
|
893
|
+
/** The findings this reason explains. */
|
|
894
|
+
findings: import_zod4.z.array(CarriedFindingSchema)
|
|
895
|
+
});
|
|
870
896
|
ConformanceRuleConfigSchema = import_zod4.z.object({
|
|
871
897
|
/**
|
|
872
898
|
* Project-relative paths holding this project's own source code; a
|
|
@@ -889,7 +915,28 @@ var init_project = __esm({
|
|
|
889
915
|
* that is now claimed, proven a barrel, or no longer found is
|
|
890
916
|
* STALE_UNCLAIMED_ENTRY.
|
|
891
917
|
*/
|
|
892
|
-
unclaimed: import_zod4.z.array(import_zod4.z.string()).optional()
|
|
918
|
+
unclaimed: import_zod4.z.array(import_zod4.z.string()).optional(),
|
|
919
|
+
/**
|
|
920
|
+
* The conformance findings this tree carries as declared debt, grouped by
|
|
921
|
+
* the reason that explains them — `unclaimed`'s shape, for findings about
|
|
922
|
+
* code a spec DOES claim.
|
|
923
|
+
*
|
|
924
|
+
* It is not a second lint.allow, and the difference is the claim each makes.
|
|
925
|
+
* An allow says "this finding is wrong here, by design", and is meant to
|
|
926
|
+
* live forever; an entry here says "this finding is RIGHT, and it is not
|
|
927
|
+
* paid yet". A mechanism that cannot tell those apart can never be asked how
|
|
928
|
+
* much the tree owes. Four properties keep it a register:
|
|
929
|
+
* • only a code a rule declares CARRYABLE may appear (UNCARRYABLE_FINDING,
|
|
930
|
+
* an error, which no allow and no --ci waiver can reach);
|
|
931
|
+
* • an entry carries a finding only when it lists EVERY unit that finding
|
|
932
|
+
* reports, so an aggregating finding cannot grow behind it;
|
|
933
|
+
* • an entry that stops applying — gone, or listing a unit no longer
|
|
934
|
+
* reported — is STALE_CARRIED_FINDING, so the register only shrinks;
|
|
935
|
+
* • every entry states its kind and its reason, and `wairon validate`
|
|
936
|
+
* prints the running total, so the debt is loud where a suppression is
|
|
937
|
+
* silent.
|
|
938
|
+
*/
|
|
939
|
+
carried: import_zod4.z.array(CarriedDebtSchema).optional()
|
|
893
940
|
});
|
|
894
941
|
DesignDepthSchema = import_zod4.z.enum(["components", "interfaces", "implementations", "narratives"]);
|
|
895
942
|
RulesConfigSchema = import_zod4.z.object({
|
|
@@ -2518,6 +2565,24 @@ var init_step_config = __esm({
|
|
|
2518
2565
|
function pathKey(sourcePath) {
|
|
2519
2566
|
return sourcePath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
2520
2567
|
}
|
|
2568
|
+
function ownEntry(record2, key) {
|
|
2569
|
+
return record2 && Object.prototype.hasOwnProperty.call(record2, key) ? record2[key] : void 0;
|
|
2570
|
+
}
|
|
2571
|
+
function importBindingOf(facts, name) {
|
|
2572
|
+
return ownEntry(facts.importBindings, name);
|
|
2573
|
+
}
|
|
2574
|
+
function fieldTypesOf(facts, field) {
|
|
2575
|
+
return ownEntry(facts.fieldTypes, field) ?? NO_FIELD_TYPES;
|
|
2576
|
+
}
|
|
2577
|
+
function typeBindingOf(facts, name) {
|
|
2578
|
+
return ownEntry(facts.typeOnlyBindings, name) ?? ownEntry(facts.importBindings, name)?.from;
|
|
2579
|
+
}
|
|
2580
|
+
function callSitesOf(facts, fn) {
|
|
2581
|
+
return ownEntry(facts.functionCallSites, fn);
|
|
2582
|
+
}
|
|
2583
|
+
function hasFunctionBody(facts, symbol) {
|
|
2584
|
+
return callSitesOf(facts, symbol) !== void 0;
|
|
2585
|
+
}
|
|
2521
2586
|
function resolveImport(fromFile, specifier, knownPaths) {
|
|
2522
2587
|
if (!specifier.startsWith(".")) return void 0;
|
|
2523
2588
|
const joined = pathKey(path6.posix.normalize(path6.posix.join(path6.posix.dirname(fromFile), specifier)));
|
|
@@ -2536,11 +2601,12 @@ function resolveImport(fromFile, specifier, knownPaths) {
|
|
|
2536
2601
|
}
|
|
2537
2602
|
return void 0;
|
|
2538
2603
|
}
|
|
2539
|
-
var path6;
|
|
2604
|
+
var path6, NO_FIELD_TYPES;
|
|
2540
2605
|
var init_code_model = __esm({
|
|
2541
2606
|
"src/models/code-model.ts"() {
|
|
2542
2607
|
"use strict";
|
|
2543
2608
|
path6 = __toESM(require("path"));
|
|
2609
|
+
NO_FIELD_TYPES = [];
|
|
2544
2610
|
}
|
|
2545
2611
|
});
|
|
2546
2612
|
|
|
@@ -3255,6 +3321,26 @@ var init_lockfile = __esm({
|
|
|
3255
3321
|
}
|
|
3256
3322
|
});
|
|
3257
3323
|
|
|
3324
|
+
// src/core/spec-files.ts
|
|
3325
|
+
function readSpecFile(filePath) {
|
|
3326
|
+
return readYamlFile(filePath);
|
|
3327
|
+
}
|
|
3328
|
+
function writeSpecFile(filePath, document) {
|
|
3329
|
+
writeYamlFile(filePath, document);
|
|
3330
|
+
}
|
|
3331
|
+
function listSpecFiles(specsDir) {
|
|
3332
|
+
return listFilesRecursive(specsDir, SPEC_FILE_EXTENSION);
|
|
3333
|
+
}
|
|
3334
|
+
var SPEC_FILE_EXTENSION;
|
|
3335
|
+
var init_spec_files = __esm({
|
|
3336
|
+
"src/core/spec-files.ts"() {
|
|
3337
|
+
"use strict";
|
|
3338
|
+
init_fs();
|
|
3339
|
+
init_yaml();
|
|
3340
|
+
SPEC_FILE_EXTENSION = ".yaml";
|
|
3341
|
+
}
|
|
3342
|
+
});
|
|
3343
|
+
|
|
3258
3344
|
// src/core/narrative-labels.ts
|
|
3259
3345
|
function resolveNarrativeLabels(methodName, steps) {
|
|
3260
3346
|
const errors = [];
|
|
@@ -8917,13 +9003,84 @@ function buildCodeIndex(model) {
|
|
|
8917
9003
|
cache2.set(path70, built);
|
|
8918
9004
|
return built;
|
|
8919
9005
|
};
|
|
9006
|
+
const republished = /* @__PURE__ */ new Map();
|
|
9007
|
+
const republishedFrom = (path70) => {
|
|
9008
|
+
const cached = republished.get(path70);
|
|
9009
|
+
if (cached) return cached;
|
|
9010
|
+
const out = /* @__PURE__ */ new Set([path70]);
|
|
9011
|
+
const queue = [path70];
|
|
9012
|
+
while (queue.length) {
|
|
9013
|
+
const from = queue.pop();
|
|
9014
|
+
const f = facts.get(from);
|
|
9015
|
+
if (f?.status !== "analyzed") continue;
|
|
9016
|
+
for (const specifier of f.reexports) {
|
|
9017
|
+
const to = resolveImport(from, specifier, paths);
|
|
9018
|
+
if (to && !out.has(to)) {
|
|
9019
|
+
out.add(to);
|
|
9020
|
+
queue.push(to);
|
|
9021
|
+
}
|
|
9022
|
+
}
|
|
9023
|
+
}
|
|
9024
|
+
republished.set(path70, out);
|
|
9025
|
+
return out;
|
|
9026
|
+
};
|
|
9027
|
+
const NO_ORIGIN = /* @__PURE__ */ new Set();
|
|
9028
|
+
const landingFrom = (scope, specifier) => {
|
|
9029
|
+
const to = resolveImport(scope, specifier, paths);
|
|
9030
|
+
return to ? republishedFrom(to) : NO_ORIGIN;
|
|
9031
|
+
};
|
|
9032
|
+
const scopeOf2 = (site, from) => {
|
|
9033
|
+
const path70 = pathKey(site.from ?? from);
|
|
9034
|
+
const f = facts.get(path70);
|
|
9035
|
+
return f?.status === "analyzed" && f.analysisGrade === "exact" ? { path: path70, facts: f } : void 0;
|
|
9036
|
+
};
|
|
9037
|
+
const originOf = (site, from) => {
|
|
9038
|
+
const resolved = scopeOf2(site, from);
|
|
9039
|
+
if (!resolved) return NO_ORIGIN;
|
|
9040
|
+
const { path: scope, facts: f } = resolved;
|
|
9041
|
+
const landing = (specifier) => landingFrom(scope, specifier);
|
|
9042
|
+
if (!site.member) {
|
|
9043
|
+
const binding = importBindingOf(f, site.name);
|
|
9044
|
+
if (binding) return landing(binding.from);
|
|
9045
|
+
return declarationsAt(scope).has(site.name) ? republishedFrom(scope) : NO_ORIGIN;
|
|
9046
|
+
}
|
|
9047
|
+
if (!site.via) return NO_ORIGIN;
|
|
9048
|
+
const receiver = importBindingOf(f, site.via);
|
|
9049
|
+
if (!receiver) return NO_ORIGIN;
|
|
9050
|
+
if (receiver.namespace) return landing(receiver.from);
|
|
9051
|
+
const through = landing(receiver.from);
|
|
9052
|
+
for (const candidate of through) {
|
|
9053
|
+
if (declarationsAt(candidate).has(site.name)) return through;
|
|
9054
|
+
}
|
|
9055
|
+
return NO_ORIGIN;
|
|
9056
|
+
};
|
|
9057
|
+
const possibleOriginsOf = (site, from) => {
|
|
9058
|
+
const proven = originOf(site, from);
|
|
9059
|
+
if (!site.field && !site.constructed) return proven;
|
|
9060
|
+
const resolved = scopeOf2(site, from);
|
|
9061
|
+
if (!resolved) return proven;
|
|
9062
|
+
const { path: scope, facts: f } = resolved;
|
|
9063
|
+
const out = new Set(proven);
|
|
9064
|
+
const widenThrough = (name, specifier) => {
|
|
9065
|
+
const landings = specifier !== void 0 ? landingFrom(scope, specifier) : declarationsAt(scope).has(name) ? republishedFrom(scope) : NO_ORIGIN;
|
|
9066
|
+
for (const candidate of landings) out.add(candidate);
|
|
9067
|
+
};
|
|
9068
|
+
if (site.field) {
|
|
9069
|
+
for (const typeName of fieldTypesOf(f, site.field)) widenThrough(typeName, typeBindingOf(f, typeName));
|
|
9070
|
+
}
|
|
9071
|
+
if (site.constructed) widenThrough(site.constructed, importBindingOf(f, site.constructed)?.from);
|
|
9072
|
+
return out;
|
|
9073
|
+
};
|
|
9074
|
+
const declarationsAt = (path70) => namesAt(declarations, pathKey(path70), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames]));
|
|
8920
9075
|
return {
|
|
8921
9076
|
paths,
|
|
8922
9077
|
exactPaths,
|
|
8923
9078
|
factsAt: (path70) => facts.get(pathKey(path70)),
|
|
8924
|
-
declarationsAt
|
|
9079
|
+
declarationsAt,
|
|
8925
9080
|
anchorsAt: (path70) => namesAt(anchors, pathKey(path70), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames, ...f.anchoredNames])),
|
|
8926
|
-
findingAnchorsAt: (path70) => namesAt(findingAnchors, pathKey(path70), (f) => new Set(f.anchoredNames))
|
|
9081
|
+
findingAnchorsAt: (path70) => namesAt(findingAnchors, pathKey(path70), (f) => new Set(f.anchoredNames)),
|
|
9082
|
+
originOf,
|
|
9083
|
+
possibleOriginsOf
|
|
8927
9084
|
};
|
|
8928
9085
|
}
|
|
8929
9086
|
function buildRealizationIndex(ctx) {
|
|
@@ -13152,6 +13309,21 @@ var init_source_file_linkage = __esm({
|
|
|
13152
13309
|
});
|
|
13153
13310
|
|
|
13154
13311
|
// src/core/rules/conformance/method-realization.ts
|
|
13312
|
+
function bodyReachable(code, file, symbol) {
|
|
13313
|
+
const facts = code.factsAt(file);
|
|
13314
|
+
if (!facts) return true;
|
|
13315
|
+
const names = /* @__PURE__ */ new Set([symbol]);
|
|
13316
|
+
const binding = importBindingOf(facts, symbol);
|
|
13317
|
+
if (binding?.imported) names.add(binding.imported);
|
|
13318
|
+
const origins = code.originOf({ name: symbol, member: false }, file);
|
|
13319
|
+
if (origins.size === 0) return true;
|
|
13320
|
+
for (const origin of origins) {
|
|
13321
|
+
const at = code.factsAt(origin);
|
|
13322
|
+
if (!at || at.status !== "analyzed" || at.analysisGrade !== "exact") return true;
|
|
13323
|
+
for (const name of names) if (hasFunctionBody(at, name)) return true;
|
|
13324
|
+
}
|
|
13325
|
+
return false;
|
|
13326
|
+
}
|
|
13155
13327
|
var methodRealizationRule;
|
|
13156
13328
|
var init_method_realization = __esm({
|
|
13157
13329
|
"src/core/rules/conformance/method-realization.ts"() {
|
|
@@ -13159,9 +13331,10 @@ var init_method_realization = __esm({
|
|
|
13159
13331
|
init_models();
|
|
13160
13332
|
methodRealizationRule = {
|
|
13161
13333
|
name: "method-realization",
|
|
13162
|
-
description: "Code\u2194spec Level 1: every L3 contract method must be realized in its own source file (the method's sourcePath, else the implementation's) at its conformance tier \u2014 declared | anchored | off; Portals default to anchored, everything else to declared, and a per-method `symbol` maps an intent-language name onto the code name. Findings carry the analysis grade (exact AST | pattern table | generic scan) so weaker analysis is visible. Methods whose file escapes the root, is missing or could not be analyzed are left to source-file-linkage, and implementations under chained subsystems (projectPath) validate standalone in their own project run.",
|
|
13334
|
+
description: "Code\u2194spec Level 1: every L3 contract method must be realized in its own source file (the method's sourcePath, else the implementation's) at its conformance tier \u2014 declared | anchored | off; Portals default to anchored, everything else to declared, and a per-method `symbol` maps an intent-language name onto the code name. A method realized by a DECLARATION owes a function BODY as well: at exact grade one must be reachable under the symbol, here or through the imports and republications this file forwards it by, so a signature, an ambient or interface declaration or a plain value binding stops reading as an implementation (METHOD_BODY_NOT_FOUND). Findings carry the analysis grade (exact AST | pattern table | generic scan) so weaker analysis is visible. Methods whose file escapes the root, is missing or could not be analyzed are left to source-file-linkage, and implementations under chained subsystems (projectPath) validate standalone in their own project run.",
|
|
13163
13335
|
codes: [
|
|
13164
|
-
{ 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" }
|
|
13336
|
+
{ 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" },
|
|
13337
|
+
{ code: "METHOD_BODY_NOT_FOUND", defaultSeverity: "warning", summary: "The method symbol IS declared in its own source file, but the file holds no function-like body under it \u2014 a signature, an ambient or interface declaration, a value binding, an imported or re-exported name", carryable: true }
|
|
13165
13338
|
],
|
|
13166
13339
|
check(ctx) {
|
|
13167
13340
|
const code = ctx.codeIndex();
|
|
@@ -13185,7 +13358,22 @@ var init_method_realization = __esm({
|
|
|
13185
13358
|
const inDeclared = code.declarationsAt(file).has(symbol);
|
|
13186
13359
|
const inAnchored = inDeclared || code.anchorsAt(file).has(symbol);
|
|
13187
13360
|
const realized = tier === "declared" ? inDeclared : inAnchored;
|
|
13188
|
-
if (realized)
|
|
13361
|
+
if (realized) {
|
|
13362
|
+
if (!inDeclared || facts.analysisGrade !== "exact" || bodyReachable(code, file, symbol)) continue;
|
|
13363
|
+
const bodyLabel = methodImpl?.symbol ? `"${method2.name}" (symbol "${symbol}")` : `"${method2.name}"`;
|
|
13364
|
+
ctx.addIssue(
|
|
13365
|
+
"warning",
|
|
13366
|
+
"METHOD_BODY_NOT_FOUND",
|
|
13367
|
+
`Method ${bodyLabel} of contract "${impl.contract}" is declared in "${file}" but has no function body there \u2014 a signature, an ambient or interface declaration, a value binding, or a name this file only imports or re-exports. Every deeper check reads a body (the realized calls of Level 3, the measured complexity of the detail dial), so this method is judged on its name alone. Point the sourcePath at the file that implements it, map the code name via a per-method SYMBOL, or dial this method to "off".`,
|
|
13368
|
+
impl.id,
|
|
13369
|
+
draft,
|
|
13370
|
+
void 0,
|
|
13371
|
+
// One method, one indivisible fact: nothing here aggregates, so
|
|
13372
|
+
// the site alone is the whole identity.
|
|
13373
|
+
{ at: method2.name }
|
|
13374
|
+
);
|
|
13375
|
+
continue;
|
|
13376
|
+
}
|
|
13189
13377
|
const label = methodImpl?.symbol ? `"${method2.name}" (symbol "${symbol}")` : `"${method2.name}"`;
|
|
13190
13378
|
const weakHint = tier === "declared" && inAnchored ? " Only a weak string/word anchor exists \u2014 declare the symbol, map it via a per-method `symbol`, or dial this method to `anchored`." : "";
|
|
13191
13379
|
ctx.addIssue(
|
|
@@ -13252,24 +13440,54 @@ var init_finding_realization = __esm({
|
|
|
13252
13440
|
});
|
|
13253
13441
|
|
|
13254
13442
|
// src/core/rules/conformance/call-conformance.ts
|
|
13255
|
-
function
|
|
13256
|
-
|
|
13257
|
-
|
|
13258
|
-
function closedCallees(facts, fn) {
|
|
13259
|
-
const direct = ownEntry(facts.functionCalls, fn);
|
|
13443
|
+
function closedCallSites(code, file, fn, stopAt) {
|
|
13444
|
+
const facts = code.factsAt(file);
|
|
13445
|
+
const direct = facts && callSitesOf(facts, fn);
|
|
13260
13446
|
if (!direct) return void 0;
|
|
13261
|
-
const
|
|
13262
|
-
const
|
|
13447
|
+
const here = pathKey(file);
|
|
13448
|
+
const out = [];
|
|
13449
|
+
const descended = /* @__PURE__ */ new Set([`${here}|${fn}`]);
|
|
13450
|
+
const queue = direct.map((s) => ({ ...s, from: s.from ?? here }));
|
|
13263
13451
|
while (queue.length) {
|
|
13264
|
-
const
|
|
13265
|
-
|
|
13266
|
-
|
|
13267
|
-
|
|
13268
|
-
|
|
13269
|
-
|
|
13270
|
-
|
|
13452
|
+
const site = queue.pop();
|
|
13453
|
+
out.push(site);
|
|
13454
|
+
if (stopAt?.has(site.name)) continue;
|
|
13455
|
+
const key = `${site.from}|${site.name}`;
|
|
13456
|
+
if (descended.has(key)) continue;
|
|
13457
|
+
descended.add(key);
|
|
13458
|
+
const scope = code.factsAt(site.from);
|
|
13459
|
+
const next = scope && callSitesOf(scope, site.name);
|
|
13460
|
+
if (!next) continue;
|
|
13461
|
+
queue.push(...next.map((s) => ({ ...s, from: s.from ?? site.from })));
|
|
13271
13462
|
}
|
|
13272
|
-
return
|
|
13463
|
+
return out;
|
|
13464
|
+
}
|
|
13465
|
+
function resolveCallTarget(ctx, componentId, methodName) {
|
|
13466
|
+
const accepted = /* @__PURE__ */ new Set([methodName]);
|
|
13467
|
+
const files = /* @__PURE__ */ new Set();
|
|
13468
|
+
for (const intf of ctx.interfacesByComponent.get(componentId) ?? []) {
|
|
13469
|
+
for (const impl of ctx.implementationsByContract.get(intf.id) ?? []) {
|
|
13470
|
+
const method2 = impl.methods.find((m) => m.name === methodName);
|
|
13471
|
+
if (!method2 && !intf.methods.some((m) => m.name === methodName)) continue;
|
|
13472
|
+
if (method2?.symbol) accepted.add(method2.symbol);
|
|
13473
|
+
const file = methodSourceFile(method2 ?? {}, impl.sourcePath);
|
|
13474
|
+
if (file) files.add(pathKey(file));
|
|
13475
|
+
}
|
|
13476
|
+
}
|
|
13477
|
+
return { accepted, files };
|
|
13478
|
+
}
|
|
13479
|
+
function describeSite(site) {
|
|
13480
|
+
if (!site.member) return `${site.name}(\u2026)`;
|
|
13481
|
+
if (site.via) return `${site.via}.${site.name}(\u2026)`;
|
|
13482
|
+
if (site.field) return `this.${site.field}.${site.name}(\u2026)`;
|
|
13483
|
+
if (site.constructed) return `new ${site.constructed}(\u2026).${site.name}(\u2026)`;
|
|
13484
|
+
return `<receiver>.${site.name}(\u2026)`;
|
|
13485
|
+
}
|
|
13486
|
+
function describeMiss(m) {
|
|
13487
|
+
const head = `step ${m.step} \u2192 ${m.target} (looked for ${m.accepted.map((a) => `"${a}"`).join(" / ")}`;
|
|
13488
|
+
if (m.miss.kind === "elsewhere") return `${head}, called but resolved to ${m.miss.landed.map((p) => `"${p}"`).join(", ")})`;
|
|
13489
|
+
if (m.miss.kind === "unresolved") return `${head}, written as ${m.miss.shapes.map((s) => `\`${s}\``).join(" / ")})`;
|
|
13490
|
+
return `${head})`;
|
|
13273
13491
|
}
|
|
13274
13492
|
var callConformanceRule;
|
|
13275
13493
|
var init_call_conformance = __esm({
|
|
@@ -13278,13 +13496,24 @@ var init_call_conformance = __esm({
|
|
|
13278
13496
|
init_models();
|
|
13279
13497
|
callConformanceRule = {
|
|
13280
13498
|
name: "call-conformance",
|
|
13281
|
-
description: "Code\u2194spec Level 3
|
|
13499
|
+
description: "Code\u2194spec Level 3: the narrative `call` step \u2194 realized call relation, judged both ways against the method's own source file (its sourcePath, else the implementation's), at exact analysis grade only. Forward: every `call` step must be realized by a call whose callee RESOLVES TO one of the target method's own source files \u2014 the target's contract name or a per-method `symbol` override, closed transitively over the named helpers the realized function calls, and resolved against every file the call CAN have reached: a `this.<field>.<method>()` receiver followed through the field's DECLARED TYPE, a `new Class(...).<method>()` receiver through the module its CLASS NAME came from. A matching call whose origin a pure model cannot resolve is reported apart as CALL_ORIGIN_UNRESOLVED, which NAMES the shape it could not follow and asks for nothing \u2014 a coverage hole in the reader, reported for the reason CONFORMANCE_DEGRADED is: a silently degraded gate is worse than a degraded one. A finding names a landing only from the PROVEN tier so that widening what a call reached can accept a step but never accuse one, and a target that names no file of its own falls back to name membership. Converse: a call that resolves to a modelled method of ANOTHER component in the SAME file crosses a component boundary while looking local, so the narrative must declare it (UNDECLARED_COLOCATED_CALL) \u2014 judged on the proven tier alone, and a same-file private helper is no modelled method and is never reported. Order, arguments and conditions stay unverified, dispatch steps (runtime-table routed) are skipped, the conformance dial (off) skips, and weaker analysis grades never guess.",
|
|
13282
13500
|
codes: [
|
|
13283
|
-
{ code: "CALL_STEP_UNREALIZED", defaultSeverity: "warning", summary: "Narrative call step
|
|
13501
|
+
{ code: "CALL_STEP_UNREALIZED", defaultSeverity: "warning", summary: "Narrative call step realized by no call that resolves to the target method's own source file \u2014 the call is absent, or it lands in another module", carryable: true },
|
|
13502
|
+
{ code: "CALL_ORIGIN_UNRESOLVED", defaultSeverity: "warning", summary: "Narrative call step whose target name IS called, but only from call sites written in a shape this analysis cannot resolve to a file \u2014 the step was not checked, and is neither proven realized nor accused", carryable: true },
|
|
13503
|
+
{ code: "UNDECLARED_COLOCATED_CALL", defaultSeverity: "warning", summary: "The realized function calls a modelled method of another component living in the same source file, and no narrative step declares that call", carryable: true }
|
|
13284
13504
|
],
|
|
13285
13505
|
check(ctx) {
|
|
13286
13506
|
const code = ctx.codeIndex();
|
|
13287
|
-
|
|
13507
|
+
const methods = ctx.implementationMethods();
|
|
13508
|
+
const modelledAt = /* @__PURE__ */ new Map();
|
|
13509
|
+
for (const entry of methods) {
|
|
13510
|
+
if (!entry.sourceFile) continue;
|
|
13511
|
+
const file = pathKey(entry.sourceFile);
|
|
13512
|
+
const byName = modelledAt.get(file) ?? /* @__PURE__ */ new Map();
|
|
13513
|
+
byName.set(entry.method.symbol ?? entry.method.name, { component: entry.component.id, method: entry.method.name });
|
|
13514
|
+
modelledAt.set(file, byName);
|
|
13515
|
+
}
|
|
13516
|
+
for (const entry of methods) {
|
|
13288
13517
|
const { implementation: impl, method: implMethod, component, sourceFile: file } = entry;
|
|
13289
13518
|
const tier = implMethod.conformance ?? impl.conformance ?? defaultConformanceTier(component);
|
|
13290
13519
|
if (tier === "off") continue;
|
|
@@ -13292,37 +13521,91 @@ var init_call_conformance = __esm({
|
|
|
13292
13521
|
if (!file) continue;
|
|
13293
13522
|
const facts = code.factsAt(file);
|
|
13294
13523
|
if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
|
|
13524
|
+
const here = pathKey(file);
|
|
13295
13525
|
const fnSymbol = implMethod.symbol ?? implMethod.name;
|
|
13296
|
-
const
|
|
13297
|
-
|
|
13298
|
-
|
|
13526
|
+
const colocated = modelledAt.get(here) ?? /* @__PURE__ */ new Map();
|
|
13527
|
+
const sites = closedCallSites(code, file, fnSymbol);
|
|
13528
|
+
if (!sites) continue;
|
|
13529
|
+
const missed = [];
|
|
13299
13530
|
for (const step of implMethod.narrative) {
|
|
13300
13531
|
if (step.type !== "call" || !step.targetComponent || !step.targetMethod) continue;
|
|
13301
13532
|
if (!ctx.componentMap.has(step.targetComponent)) continue;
|
|
13302
|
-
const
|
|
13303
|
-
|
|
13304
|
-
|
|
13305
|
-
|
|
13306
|
-
|
|
13533
|
+
const target = resolveCallTarget(ctx, step.targetComponent, step.targetMethod);
|
|
13534
|
+
if (target.accepted.has(fnSymbol)) continue;
|
|
13535
|
+
const matching = sites.filter((s) => target.accepted.has(s.name));
|
|
13536
|
+
const ref = `${step.targetComponent}.${step.targetMethod}`;
|
|
13537
|
+
if (target.files.size === 0) {
|
|
13538
|
+
if (matching.length === 0) {
|
|
13539
|
+
missed.push({ step: step.stepNumber, target: ref, accepted: [...target.accepted], miss: { kind: "absent" } });
|
|
13307
13540
|
}
|
|
13541
|
+
continue;
|
|
13308
13542
|
}
|
|
13309
|
-
|
|
13310
|
-
|
|
13311
|
-
|
|
13312
|
-
|
|
13313
|
-
|
|
13314
|
-
|
|
13315
|
-
|
|
13543
|
+
const landed = /* @__PURE__ */ new Set();
|
|
13544
|
+
let realized = false;
|
|
13545
|
+
for (const site of matching) {
|
|
13546
|
+
for (const origin of code.originOf(site, file)) landed.add(origin);
|
|
13547
|
+
for (const origin of code.possibleOriginsOf(site, file)) {
|
|
13548
|
+
if (target.files.has(origin)) realized = true;
|
|
13549
|
+
}
|
|
13550
|
+
}
|
|
13551
|
+
if (realized) continue;
|
|
13552
|
+
const miss = matching.length === 0 ? { kind: "absent" } : landed.size === 0 ? { kind: "unresolved", shapes: [...new Set(matching.map(describeSite))].sort() } : { kind: "elsewhere", landed: [...landed].sort() };
|
|
13553
|
+
missed.push({ step: step.stepNumber, target: ref, accepted: [...target.accepted], miss });
|
|
13554
|
+
}
|
|
13555
|
+
const unrealized = missed.filter((m) => m.miss.kind !== "unresolved");
|
|
13556
|
+
const unresolved = missed.filter((m) => m.miss.kind === "unresolved");
|
|
13557
|
+
if (unrealized.length) {
|
|
13558
|
+
ctx.addIssue(
|
|
13559
|
+
"warning",
|
|
13560
|
+
"CALL_STEP_UNREALIZED",
|
|
13561
|
+
`Method "${implMethod.name}" in implementation "${impl.id}": ${unrealized.length} narrative call step(s) are realized by no call of the function "${fnSymbol}" in "${file}" that resolves to the target's own source file \u2014 ${unrealized.map(describeMiss).join("; ")}. Callees are closed over the named helpers the function calls, and each call site is resolved through this file's import bindings (order, arguments and conditions are not checked). Realize the calls, fix the narrative, or map code names via per-method symbols on the targets.`,
|
|
13562
|
+
impl.id,
|
|
13563
|
+
entry.draftContext,
|
|
13564
|
+
void 0,
|
|
13565
|
+
// A step, named by its number and its target: what the register
|
|
13566
|
+
// carries is ONE unresolved step, never "this method's call steps".
|
|
13567
|
+
{ at: implMethod.name, covers: unrealized.map((m) => `${m.step}:${m.target}`) }
|
|
13568
|
+
);
|
|
13569
|
+
}
|
|
13570
|
+
if (unresolved.length) {
|
|
13571
|
+
ctx.addIssue(
|
|
13572
|
+
"warning",
|
|
13573
|
+
"CALL_ORIGIN_UNRESOLVED",
|
|
13574
|
+
`Method "${implMethod.name}" in implementation "${impl.id}": ${unresolved.length} narrative call step(s) ARE called by name inside the function "${fnSymbol}" in "${file}", but every site calling them is written in a shape this analysis cannot resolve to a file \u2014 ${unresolved.map(describeMiss).join("; ")}. What resolves is a name bound to a module of THIS project: a bare or namespaced call through such an import binding, a \`this.<field>\` receiver whose declared type names one, a \`new Class(\u2026)\` receiver whose class does. What does not: an import from a PACKAGE specifier, a receiver holding a value the module assembled, a receiver this model records no name for. This reports what was not checked, not what is wrong \u2014 the step is neither proven realized nor accused of being missing, and no working call is asked to be rewritten to suit the reader.`,
|
|
13575
|
+
impl.id,
|
|
13576
|
+
entry.draftContext,
|
|
13577
|
+
void 0,
|
|
13578
|
+
{ at: implMethod.name, covers: unresolved.map((m) => `${m.step}:${m.target}`) }
|
|
13579
|
+
);
|
|
13580
|
+
}
|
|
13581
|
+
const declared = /* @__PURE__ */ new Set();
|
|
13582
|
+
for (const step of implMethod.narrative) {
|
|
13583
|
+
if (step.targetComponent && step.targetMethod) declared.add(`${step.targetComponent}.${step.targetMethod}`);
|
|
13584
|
+
}
|
|
13585
|
+
const crossings = /* @__PURE__ */ new Map();
|
|
13586
|
+
for (const site of closedCallSites(code, file, fnSymbol, new Set(colocated.keys())) ?? []) {
|
|
13587
|
+
const owner = colocated.get(site.name);
|
|
13588
|
+
if (!owner || owner.component === component.id) continue;
|
|
13589
|
+
const ref = `${owner.component}.${owner.method}`;
|
|
13590
|
+
if (declared.has(ref) || crossings.has(ref)) continue;
|
|
13591
|
+
if (!code.originOf(site, file).has(here)) continue;
|
|
13592
|
+
crossings.set(ref, site.name);
|
|
13593
|
+
}
|
|
13594
|
+
if (crossings.size) {
|
|
13595
|
+
const detail = [...crossings].map(([ref, name]) => `"${name}" (${ref})`).join("; ");
|
|
13596
|
+
ctx.addIssue(
|
|
13597
|
+
"warning",
|
|
13598
|
+
"UNDECLARED_COLOCATED_CALL",
|
|
13599
|
+
`Method "${implMethod.name}" in implementation "${impl.id}": the function "${fnSymbol}" in "${file}" calls ${crossings.size} modelled method(s) of OTHER components living in that same file, and no narrative step declares the call \u2014 ${detail}. Sharing a file does not make the hop internal: it crosses a component boundary nothing imports, so no file-level check can see it. Narrate the call, or move the code so the boundary is real.`,
|
|
13600
|
+
impl.id,
|
|
13601
|
+
entry.draftContext,
|
|
13602
|
+
void 0,
|
|
13603
|
+
// The crossings themselves. Keyed by method alone, a 24th crossing
|
|
13604
|
+
// would ride into a register entry written for 23 - so each one is
|
|
13605
|
+
// named, and one nobody carried fires on the day it appears.
|
|
13606
|
+
{ at: implMethod.name, covers: [...crossings.keys()] }
|
|
13607
|
+
);
|
|
13316
13608
|
}
|
|
13317
|
-
if (missing.length === 0) continue;
|
|
13318
|
-
const detail = missing.map((m) => `step ${m.step} \u2192 ${m.target} (looked for ${m.accepted.map((a) => `"${a}"`).join(" / ")})`).join("; ");
|
|
13319
|
-
ctx.addIssue(
|
|
13320
|
-
"warning",
|
|
13321
|
-
"CALL_STEP_UNREALIZED",
|
|
13322
|
-
`Method "${implMethod.name}" in implementation "${impl.id}": ${missing.length} narrative call step(s) are not realized as calls of the function "${fnSymbol}" in "${file}" \u2014 ${detail}. Callees are matched by name, closed over same-file helpers (exact grade, set membership \u2014 order and arguments are not checked). Realize the calls, fix the narrative, or map code names via per-method symbols on the targets.`,
|
|
13323
|
-
impl.id,
|
|
13324
|
-
entry.draftContext
|
|
13325
|
-
);
|
|
13326
13609
|
}
|
|
13327
13610
|
}
|
|
13328
13611
|
};
|
|
@@ -13832,6 +14115,69 @@ var init_unclaimed_source = __esm({
|
|
|
13832
14115
|
}
|
|
13833
14116
|
});
|
|
13834
14117
|
|
|
14118
|
+
// src/core/rules/conformance/carried-debt.ts
|
|
14119
|
+
var carriedDebtRule;
|
|
14120
|
+
var init_carried_debt = __esm({
|
|
14121
|
+
"src/core/rules/conformance/carried-debt.ts"() {
|
|
14122
|
+
"use strict";
|
|
14123
|
+
carriedDebtRule = {
|
|
14124
|
+
name: "carried-debt",
|
|
14125
|
+
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.",
|
|
14126
|
+
codes: [
|
|
14127
|
+
{ 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" },
|
|
14128
|
+
{ 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" }
|
|
14129
|
+
],
|
|
14130
|
+
check(ctx) {
|
|
14131
|
+
const carried = ctx.carriedFindings;
|
|
14132
|
+
if (carried.length === 0) return;
|
|
14133
|
+
for (const entry of carried) {
|
|
14134
|
+
if (ctx.carryableIssueCodes.has(entry.code)) continue;
|
|
14135
|
+
const why = ctx.knownIssueCodes.has(entry.code) ? "that code is not CARRYABLE \u2014 the register holds measured code\u2194spec debt, and a doctrine, soundness or configuration finding says the design is illegal rather than unfinished" : "no registered rule emits that code";
|
|
14136
|
+
ctx.addIssue(
|
|
14137
|
+
"error",
|
|
14138
|
+
"UNCARRYABLE_FINDING",
|
|
14139
|
+
`The conformance debt register carries "${entry.code}" on "${entry.spec}" (at "${entry.at}"), but ${why}. Remove the entry; if the code belongs in the register, declare it carryable on the rule that emits it.`
|
|
14140
|
+
);
|
|
14141
|
+
}
|
|
14142
|
+
if (ctx.codeModel.files.length === 0) return;
|
|
14143
|
+
const known = new Set(ctx.specIds().map((s) => s.id));
|
|
14144
|
+
const addressed = /* @__PURE__ */ new Set();
|
|
14145
|
+
for (const entry of carried) {
|
|
14146
|
+
if (!ctx.carryableIssueCodes.has(entry.code)) continue;
|
|
14147
|
+
if (ctx.rules?.sddRuleSeverity?.[entry.code] === "off") continue;
|
|
14148
|
+
if (!ctx.isSpecInScope(entry.spec)) continue;
|
|
14149
|
+
const key = `${entry.spec}|${entry.code}|${entry.at}`;
|
|
14150
|
+
if (addressed.has(key)) {
|
|
14151
|
+
ctx.addIssue(
|
|
14152
|
+
"warning",
|
|
14153
|
+
"STALE_CARRIED_FINDING",
|
|
14154
|
+
`The conformance debt register carries "${entry.code}" on "${entry.spec}" (at "${entry.at}") more than once. Only the first entry can ever match, so the rest carry nothing \u2014 delete them and merge their units into the one entry.`
|
|
14155
|
+
);
|
|
14156
|
+
continue;
|
|
14157
|
+
}
|
|
14158
|
+
addressed.add(key);
|
|
14159
|
+
if (!entry.fired) {
|
|
14160
|
+
const why = known.has(entry.spec) ? "no such finding fired this run \u2014 the debt is paid, or the spec no longer reaches that site" : `the tree holds no spec "${entry.spec}"`;
|
|
14161
|
+
ctx.addIssue(
|
|
14162
|
+
"warning",
|
|
14163
|
+
"STALE_CARRIED_FINDING",
|
|
14164
|
+
`The conformance debt register carries "${entry.code}" on "${entry.spec}" (at "${entry.at}" \u2014 ${entry.kind}: ${entry.why}), but ${why}. Delete the entry; the register only shrinks.`
|
|
14165
|
+
);
|
|
14166
|
+
continue;
|
|
14167
|
+
}
|
|
14168
|
+
const paid = entry.covers.filter((unit) => !entry.seen.has(unit));
|
|
14169
|
+
if (paid.length === 0) continue;
|
|
14170
|
+
ctx.addIssue(
|
|
14171
|
+
"warning",
|
|
14172
|
+
"STALE_CARRIED_FINDING",
|
|
14173
|
+
`The conformance debt register carries "${entry.code}" on "${entry.spec}" (at "${entry.at}") together with ${paid.length} unit(s) the finding no longer reports \u2014 ${paid.map((u) => `"${u}"`).join("; ")}. That much of the debt is paid: delete those lines from the entry's \`covers\`; the register only shrinks.`
|
|
14174
|
+
);
|
|
14175
|
+
}
|
|
14176
|
+
}
|
|
14177
|
+
};
|
|
14178
|
+
}
|
|
14179
|
+
});
|
|
14180
|
+
|
|
13835
14181
|
// src/core/rules/heuristic/coupling-health.ts
|
|
13836
14182
|
var DEFAULT_GOD_COMPONENT_THRESHOLD, couplingRule;
|
|
13837
14183
|
var init_coupling_health = __esm({
|
|
@@ -14986,8 +15332,8 @@ function registerPackRules(packRules) {
|
|
|
14986
15332
|
for (const rule of packRules) addRule(rule);
|
|
14987
15333
|
}
|
|
14988
15334
|
function ruleSequence() {
|
|
14989
|
-
const
|
|
14990
|
-
return ruleSet.
|
|
15335
|
+
const audits = [carriedDebtRule, lintAllowsRule].filter((r) => ruleSet.includes(r));
|
|
15336
|
+
return [...ruleSet.filter((r) => !audits.includes(r)), ...audits];
|
|
14991
15337
|
}
|
|
14992
15338
|
function specScopedRules() {
|
|
14993
15339
|
return ruleSequence().filter((r) => r.scope === "spec");
|
|
@@ -15077,6 +15423,7 @@ var init_repository = __esm({
|
|
|
15077
15423
|
init_integration_sim_coverage();
|
|
15078
15424
|
init_type_realization();
|
|
15079
15425
|
init_unclaimed_source();
|
|
15426
|
+
init_carried_debt();
|
|
15080
15427
|
init_coupling_health();
|
|
15081
15428
|
init_signature_language_builtins();
|
|
15082
15429
|
init_narrative_language_constructs();
|
|
@@ -15268,8 +15615,11 @@ var init_repository = __esm({
|
|
|
15268
15615
|
// elsewhere?) rather than spec content.
|
|
15269
15616
|
packResolutionRule,
|
|
15270
15617
|
reproducibilityRule,
|
|
15271
|
-
// MUST run last:
|
|
15272
|
-
//
|
|
15618
|
+
// MUST run last, in this order: each audits what the earlier rules did
|
|
15619
|
+
// with a declared exception. The debt register first (which carried
|
|
15620
|
+
// findings the conformance family actually matched), then the allows
|
|
15621
|
+
// (which suppressions any rule actually consumed).
|
|
15622
|
+
carriedDebtRule,
|
|
15273
15623
|
lintAllowsRule
|
|
15274
15624
|
];
|
|
15275
15625
|
ruleSet = [];
|
|
@@ -15392,9 +15742,15 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
15392
15742
|
const imports = /* @__PURE__ */ new Set();
|
|
15393
15743
|
const reexports = /* @__PURE__ */ new Set();
|
|
15394
15744
|
const starExports = [];
|
|
15745
|
+
const namedReexports = [];
|
|
15395
15746
|
const complexity = /* @__PURE__ */ new Map();
|
|
15396
15747
|
const calls = /* @__PURE__ */ new Map();
|
|
15748
|
+
const importBindings = /* @__PURE__ */ new Map();
|
|
15749
|
+
const typeOnlyBindings = /* @__PURE__ */ new Map();
|
|
15750
|
+
const fieldTypes = /* @__PURE__ */ new Map();
|
|
15397
15751
|
const mutableBindings = /* @__PURE__ */ new Set();
|
|
15752
|
+
const receiverKey = (site) => site.via ?? (site.field ? `this.${site.field}` : site.constructed ? `new ${site.constructed}` : "");
|
|
15753
|
+
const siteKey = (site) => `${site.member ? "m" : "b"}:${receiverKey(site)}:${site.name}`;
|
|
15398
15754
|
const addBindingNames = (name) => {
|
|
15399
15755
|
if (ts.isIdentifier(name)) declared.add(name.text);
|
|
15400
15756
|
else if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) {
|
|
@@ -15408,6 +15764,17 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
15408
15764
|
if (ts.isPrivateIdentifier(name)) return name.text;
|
|
15409
15765
|
return void 0;
|
|
15410
15766
|
};
|
|
15767
|
+
const typeReferenceName = (type) => type && ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName) ? type.typeName.text : void 0;
|
|
15768
|
+
const isParameterProperty = (node) => !!node.modifiers?.some((m) => m.kind === ts.SyntaxKind.PublicKeyword || m.kind === ts.SyntaxKind.PrivateKeyword || m.kind === ts.SyntaxKind.ProtectedKeyword || m.kind === ts.SyntaxKind.ReadonlyKeyword);
|
|
15769
|
+
const recordFieldType = (name, type) => {
|
|
15770
|
+
const typeName = typeReferenceName(type);
|
|
15771
|
+
if (!typeName) return;
|
|
15772
|
+
const field = propertyNameText(name);
|
|
15773
|
+
if (!field) return;
|
|
15774
|
+
const named = fieldTypes.get(field) ?? /* @__PURE__ */ new Set();
|
|
15775
|
+
named.add(typeName);
|
|
15776
|
+
fieldTypes.set(field, named);
|
|
15777
|
+
};
|
|
15411
15778
|
const hasExportModifier = (node) => {
|
|
15412
15779
|
const mods = node.modifiers;
|
|
15413
15780
|
return !!mods?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
@@ -15430,7 +15797,10 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
15430
15797
|
};
|
|
15431
15798
|
const collectFunctionFacts = (fn) => {
|
|
15432
15799
|
let score = 1;
|
|
15433
|
-
const callees = /* @__PURE__ */ new
|
|
15800
|
+
const callees = /* @__PURE__ */ new Map();
|
|
15801
|
+
const addSite = (site) => {
|
|
15802
|
+
callees.set(siteKey(site), site);
|
|
15803
|
+
};
|
|
15434
15804
|
const count = (node) => {
|
|
15435
15805
|
if (namedFunctionName(node) !== void 0) return;
|
|
15436
15806
|
if (ts.isIfStatement(node) || ts.isConditionalExpression(node) || ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node) || ts.isWhileStatement(node) || ts.isDoStatement(node) || ts.isCaseClause(node) || ts.isCatchClause(node)) {
|
|
@@ -15442,8 +15812,17 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
15442
15812
|
}
|
|
15443
15813
|
} else if (ts.isCallExpression(node)) {
|
|
15444
15814
|
const callee = node.expression;
|
|
15445
|
-
if (ts.isIdentifier(callee))
|
|
15446
|
-
else if (ts.isPropertyAccessExpression(callee))
|
|
15815
|
+
if (ts.isIdentifier(callee)) addSite({ name: callee.text, member: false });
|
|
15816
|
+
else if (ts.isPropertyAccessExpression(callee)) {
|
|
15817
|
+
const receiver = callee.expression;
|
|
15818
|
+
const name = callee.name.text;
|
|
15819
|
+
if (ts.isIdentifier(receiver)) addSite({ name, member: true, via: receiver.text });
|
|
15820
|
+
else if (ts.isPropertyAccessExpression(receiver) && receiver.expression.kind === ts.SyntaxKind.ThisKeyword) {
|
|
15821
|
+
addSite({ name, member: true, field: receiver.name.text });
|
|
15822
|
+
} else if (ts.isNewExpression(receiver) && ts.isIdentifier(receiver.expression)) {
|
|
15823
|
+
addSite({ name, member: true, constructed: receiver.expression.text });
|
|
15824
|
+
} else addSite({ name, member: true });
|
|
15825
|
+
}
|
|
15447
15826
|
}
|
|
15448
15827
|
ts.forEachChild(node, count);
|
|
15449
15828
|
};
|
|
@@ -15455,9 +15834,13 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
15455
15834
|
if (fnName && node.body) {
|
|
15456
15835
|
const facts = collectFunctionFacts(node);
|
|
15457
15836
|
complexity.set(fnName, Math.max(complexity.get(fnName) ?? 0, facts.score));
|
|
15458
|
-
const
|
|
15459
|
-
for (const
|
|
15460
|
-
calls.set(fnName,
|
|
15837
|
+
const sites = calls.get(fnName) ?? /* @__PURE__ */ new Map();
|
|
15838
|
+
for (const [key, site] of facts.callees) sites.set(key, site);
|
|
15839
|
+
calls.set(fnName, sites);
|
|
15840
|
+
}
|
|
15841
|
+
if (ts.isPropertyDeclaration(node)) recordFieldType(node.name, node.type);
|
|
15842
|
+
else if (ts.isParameter(node) && node.parent && ts.isConstructorDeclaration(node.parent) && isParameterProperty(node)) {
|
|
15843
|
+
recordFieldType(node.name, node.type);
|
|
15461
15844
|
}
|
|
15462
15845
|
if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isModuleDeclaration(node)) {
|
|
15463
15846
|
const name = node.name && ts.isIdentifier(node.name) ? node.name.text : void 0;
|
|
@@ -15484,11 +15867,24 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
15484
15867
|
} else if (ts.isImportDeclaration(node)) {
|
|
15485
15868
|
const clause = node.importClause;
|
|
15486
15869
|
const typeOnly = clause?.isTypeOnly ?? false;
|
|
15487
|
-
|
|
15488
|
-
if (
|
|
15870
|
+
const spec = ts.isStringLiteral(node.moduleSpecifier) ? node.moduleSpecifier.text : void 0;
|
|
15871
|
+
if (!typeOnly && spec) imports.add(spec);
|
|
15872
|
+
const bind = (name, namespace, imported, elementTypeOnly = false) => {
|
|
15873
|
+
declared.add(name);
|
|
15874
|
+
if (!spec) return;
|
|
15875
|
+
if (typeOnly || elementTypeOnly) {
|
|
15876
|
+
typeOnlyBindings.set(name, spec);
|
|
15877
|
+
return;
|
|
15878
|
+
}
|
|
15879
|
+
const fact = { from: spec };
|
|
15880
|
+
if (namespace) fact.namespace = true;
|
|
15881
|
+
if (imported && imported !== name) fact.imported = imported;
|
|
15882
|
+
importBindings.set(name, fact);
|
|
15883
|
+
};
|
|
15884
|
+
if (clause?.name) bind(clause.name.text, false);
|
|
15489
15885
|
if (clause?.namedBindings) {
|
|
15490
|
-
if (ts.isNamespaceImport(clause.namedBindings))
|
|
15491
|
-
else for (const el of clause.namedBindings.elements)
|
|
15886
|
+
if (ts.isNamespaceImport(clause.namedBindings)) bind(clause.namedBindings.name.text, true);
|
|
15887
|
+
else for (const el of clause.namedBindings.elements) bind(el.name.text, false, el.propertyName?.text, el.isTypeOnly);
|
|
15492
15888
|
}
|
|
15493
15889
|
} else if (ts.isExportDeclaration(node)) {
|
|
15494
15890
|
const spec = node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier) ? node.moduleSpecifier.text : void 0;
|
|
@@ -15497,6 +15893,9 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
15497
15893
|
for (const el of node.exportClause.elements) {
|
|
15498
15894
|
declared.add(el.name.text);
|
|
15499
15895
|
exported.add(el.name.text);
|
|
15896
|
+
if (spec && !node.isTypeOnly && !el.isTypeOnly) {
|
|
15897
|
+
namedReexports.push({ exported: el.name.text, local: (el.propertyName ?? el.name).text, from: spec });
|
|
15898
|
+
}
|
|
15500
15899
|
}
|
|
15501
15900
|
} else if (!node.exportClause && spec) {
|
|
15502
15901
|
starExports.push(spec);
|
|
@@ -15519,7 +15918,7 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
15519
15918
|
};
|
|
15520
15919
|
visit(sf);
|
|
15521
15920
|
const reexportOnly = sf.statements.length > 0 && sf.statements.every((st) => ts.isExportDeclaration(st) && !!st.moduleSpecifier);
|
|
15522
|
-
return { declared, anchors, exported, imports, reexports, starExports, complexity, calls, mutableBindings, reexportOnly };
|
|
15921
|
+
return { declared, anchors, exported, imports, reexports, starExports, namedReexports, complexity, calls, importBindings, typeOnlyBindings, fieldTypes, mutableBindings, reexportOnly };
|
|
15523
15922
|
}
|
|
15524
15923
|
function resolveRelativeModule(fromFile, specifier) {
|
|
15525
15924
|
if (!specifier.startsWith(".")) return null;
|
|
@@ -15542,34 +15941,47 @@ function resolveRelativeModule(fromFile, specifier) {
|
|
|
15542
15941
|
}
|
|
15543
15942
|
return null;
|
|
15544
15943
|
}
|
|
15545
|
-
function
|
|
15546
|
-
|
|
15547
|
-
|
|
15548
|
-
|
|
15549
|
-
|
|
15550
|
-
|
|
15551
|
-
|
|
15552
|
-
|
|
15553
|
-
|
|
15554
|
-
targetFacts = walkExact(ts, fs10.readFileSync(target, "utf8"), target);
|
|
15555
|
-
} catch {
|
|
15556
|
-
targetFacts = null;
|
|
15557
|
-
}
|
|
15558
|
-
exactCache.set(target, targetFacts);
|
|
15944
|
+
function reexportTarget(ts, filePath, specifier, projectRoot2, visited, exactCache) {
|
|
15945
|
+
const target = resolveRelativeModule(filePath, specifier);
|
|
15946
|
+
if (!target || path13.relative(projectRoot2, target).startsWith("..")) return null;
|
|
15947
|
+
let targetFacts = exactCache.get(target);
|
|
15948
|
+
if (targetFacts === void 0) {
|
|
15949
|
+
try {
|
|
15950
|
+
targetFacts = walkExact(ts, fs10.readFileSync(target, "utf8"), target);
|
|
15951
|
+
} catch {
|
|
15952
|
+
targetFacts = null;
|
|
15559
15953
|
}
|
|
15560
|
-
|
|
15561
|
-
|
|
15562
|
-
|
|
15954
|
+
exactCache.set(target, targetFacts);
|
|
15955
|
+
}
|
|
15956
|
+
if (!targetFacts) return null;
|
|
15957
|
+
if (!visited.has(target)) {
|
|
15958
|
+
visited.add(target);
|
|
15959
|
+
chaseReexports(ts, targetFacts, target, projectRoot2, visited, exactCache);
|
|
15960
|
+
}
|
|
15961
|
+
return { path: target, facts: targetFacts };
|
|
15962
|
+
}
|
|
15963
|
+
function carryBody(facts, target, targetPath, local, exported, projectRoot2) {
|
|
15964
|
+
const score = target.complexity.get(local);
|
|
15965
|
+
if (score !== void 0) facts.complexity.set(exported, Math.max(facts.complexity.get(exported) ?? 0, score));
|
|
15966
|
+
const targetCalls = target.calls.get(local);
|
|
15967
|
+
if (!targetCalls) return;
|
|
15968
|
+
const carriedFrom = pathKey(path13.relative(projectRoot2, targetPath));
|
|
15969
|
+
const sites = facts.calls.get(exported) ?? /* @__PURE__ */ new Map();
|
|
15970
|
+
for (const [key, site] of targetCalls) sites.set(carriedFrom + "|" + key, { ...site, from: site.from ?? carriedFrom });
|
|
15971
|
+
facts.calls.set(exported, sites);
|
|
15972
|
+
}
|
|
15973
|
+
function chaseReexports(ts, facts, filePath, projectRoot2, visited, exactCache) {
|
|
15974
|
+
for (const re of facts.namedReexports) {
|
|
15975
|
+
const target = reexportTarget(ts, filePath, re.from, projectRoot2, visited, exactCache);
|
|
15976
|
+
if (target) carryBody(facts, target.facts, target.path, re.local, re.exported, projectRoot2);
|
|
15977
|
+
}
|
|
15978
|
+
for (const spec of facts.starExports) {
|
|
15979
|
+
const target = reexportTarget(ts, filePath, spec, projectRoot2, visited, exactCache);
|
|
15980
|
+
if (!target) continue;
|
|
15981
|
+
for (const name of target.facts.exported) {
|
|
15563
15982
|
facts.exported.add(name);
|
|
15564
15983
|
facts.declared.add(name);
|
|
15565
|
-
|
|
15566
|
-
if (c !== void 0) facts.complexity.set(name, Math.max(facts.complexity.get(name) ?? 0, c));
|
|
15567
|
-
const targetCalls = targetFacts.calls.get(name);
|
|
15568
|
-
if (targetCalls) {
|
|
15569
|
-
const set = facts.calls.get(name) ?? /* @__PURE__ */ new Set();
|
|
15570
|
-
for (const callee of targetCalls) set.add(callee);
|
|
15571
|
-
facts.calls.set(name, set);
|
|
15572
|
-
}
|
|
15984
|
+
carryBody(facts, target.facts, target.path, name, name, projectRoot2);
|
|
15573
15985
|
}
|
|
15574
15986
|
}
|
|
15575
15987
|
}
|
|
@@ -15674,7 +16086,7 @@ function buildCodeModel(implementations, types, projectRoot2, sourceRoots = [],
|
|
|
15674
16086
|
if (ts) {
|
|
15675
16087
|
try {
|
|
15676
16088
|
const facts = walkExact(ts, text2, absolute);
|
|
15677
|
-
|
|
16089
|
+
chaseReexports(ts, facts, absolute, projectRoot2, /* @__PURE__ */ new Set([absolute]), exactCache);
|
|
15678
16090
|
analyzed = {
|
|
15679
16091
|
analysisGrade: "exact",
|
|
15680
16092
|
declaredNames: [...facts.declared],
|
|
@@ -15683,7 +16095,10 @@ function buildCodeModel(implementations, types, projectRoot2, sourceRoots = [],
|
|
|
15683
16095
|
imports: [...facts.imports],
|
|
15684
16096
|
reexports: [...facts.reexports],
|
|
15685
16097
|
functionComplexity: Object.fromEntries(facts.complexity),
|
|
15686
|
-
|
|
16098
|
+
functionCallSites: Object.fromEntries([...facts.calls].map(([k, v]) => [k, [...v.values()]])),
|
|
16099
|
+
importBindings: Object.fromEntries(facts.importBindings),
|
|
16100
|
+
typeOnlyBindings: Object.fromEntries(facts.typeOnlyBindings),
|
|
16101
|
+
fieldTypes: Object.fromEntries([...facts.fieldTypes].map(([k, v]) => [k, [...v]])),
|
|
15687
16102
|
topLevelMutableBindings: [...facts.mutableBindings],
|
|
15688
16103
|
reexportOnly: facts.reexportOnly
|
|
15689
16104
|
};
|
|
@@ -16092,7 +16507,27 @@ function buildRuleContext(opts) {
|
|
|
16092
16507
|
for (const i of interfaces) collectAllows(i.id, i.lint);
|
|
16093
16508
|
for (const im of implementations) collectAllows(im.id, im.lint);
|
|
16094
16509
|
for (const t of types) collectAllows(t.id, t.lint);
|
|
16095
|
-
const
|
|
16510
|
+
const carriedFindings = [];
|
|
16511
|
+
const carriedLookup = /* @__PURE__ */ new Map();
|
|
16512
|
+
const carriedKey = (spec, code, at) => `${spec}\0${code}\0${at}`;
|
|
16513
|
+
for (const group of rules?.conformance?.carried ?? []) {
|
|
16514
|
+
for (const f of group.findings ?? []) {
|
|
16515
|
+
const entry = {
|
|
16516
|
+
kind: group.kind,
|
|
16517
|
+
why: group.why,
|
|
16518
|
+
code: f.code,
|
|
16519
|
+
spec: f.spec,
|
|
16520
|
+
at: f.at,
|
|
16521
|
+
covers: f.covers ?? [],
|
|
16522
|
+
fired: false,
|
|
16523
|
+
seen: /* @__PURE__ */ new Set()
|
|
16524
|
+
};
|
|
16525
|
+
carriedFindings.push(entry);
|
|
16526
|
+
const key = carriedKey(f.spec, f.code, f.at);
|
|
16527
|
+
if (!carriedLookup.has(key)) carriedLookup.set(key, entry);
|
|
16528
|
+
}
|
|
16529
|
+
}
|
|
16530
|
+
const addIssue = (defaultSeverity, code, message, specId, isDraftContext, surfaceResolved, parts) => {
|
|
16096
16531
|
if (scopeSubsystem && specId && !isSpecInScope(specId)) {
|
|
16097
16532
|
return;
|
|
16098
16533
|
}
|
|
@@ -16110,10 +16545,25 @@ function buildRuleContext(opts) {
|
|
|
16110
16545
|
if (severity === "warning") return;
|
|
16111
16546
|
}
|
|
16112
16547
|
}
|
|
16548
|
+
let text2 = message;
|
|
16549
|
+
if (specId && parts && severity === "warning") {
|
|
16550
|
+
const entry = carriedLookup.get(carriedKey(specId, code, parts.at));
|
|
16551
|
+
if (entry) {
|
|
16552
|
+
entry.fired = true;
|
|
16553
|
+
const listed = new Set(entry.covers);
|
|
16554
|
+
const grew = [];
|
|
16555
|
+
for (const unit of parts.covers ?? []) {
|
|
16556
|
+
if (listed.has(unit)) entry.seen.add(unit);
|
|
16557
|
+
else grew.push(unit);
|
|
16558
|
+
}
|
|
16559
|
+
if (grew.length === 0) return;
|
|
16560
|
+
text2 = `${text2} The conformance debt register carries this finding, but not ${grew.length} part(s) of it \u2014 ${grew.map((u) => `"${u}"`).join("; ")} ${grew.length === 1 ? "is" : "are"} new. Fix them, or add them to the entry's \`covers\` with a reason that is actually true.`;
|
|
16561
|
+
}
|
|
16562
|
+
}
|
|
16113
16563
|
issues.push({
|
|
16114
16564
|
severity,
|
|
16115
16565
|
code,
|
|
16116
|
-
message,
|
|
16566
|
+
message: text2,
|
|
16117
16567
|
specId,
|
|
16118
16568
|
...isDraftContext ? { draftContext: true } : {},
|
|
16119
16569
|
...surfaceResolved ? { surfaceResolved: true } : {}
|
|
@@ -16190,6 +16640,8 @@ function buildRuleContext(opts) {
|
|
|
16190
16640
|
roundTripIssues: opts.roundTripIssues,
|
|
16191
16641
|
lintAllows,
|
|
16192
16642
|
knownIssueCodes: opts.knownIssueCodes,
|
|
16643
|
+
carriedFindings,
|
|
16644
|
+
carryableIssueCodes: opts.carryableIssueCodes ?? /* @__PURE__ */ new Set(),
|
|
16193
16645
|
addIssue
|
|
16194
16646
|
};
|
|
16195
16647
|
return ctx;
|
|
@@ -22740,6 +23192,9 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
|
|
|
22740
23192
|
...knownIssueCodes().map((rc) => rc.code),
|
|
22741
23193
|
...extensions.assertions.map((a) => a.fullCode)
|
|
22742
23194
|
]);
|
|
23195
|
+
const carryableCodes = new Set(
|
|
23196
|
+
knownIssueCodes().filter((rc) => rc.carryable).map((rc) => rc.code)
|
|
23197
|
+
);
|
|
22743
23198
|
const ctx = buildRuleContext({
|
|
22744
23199
|
system,
|
|
22745
23200
|
subsystems,
|
|
@@ -22759,6 +23214,7 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
|
|
|
22759
23214
|
codeModel,
|
|
22760
23215
|
roundTripIssues,
|
|
22761
23216
|
knownIssueCodes: knownCodes,
|
|
23217
|
+
carryableIssueCodes: carryableCodes,
|
|
22762
23218
|
issues
|
|
22763
23219
|
});
|
|
22764
23220
|
for (const rule of sequence) {
|
|
@@ -23692,10 +24148,10 @@ function findChainingParent(childRoot, ceiling) {
|
|
|
23692
24148
|
if (bound2 && !isWithin(bound2, dir)) break;
|
|
23693
24149
|
const specsDir = aiPathsAt(dir).specsDir();
|
|
23694
24150
|
if (pathExists(specsDir)) {
|
|
23695
|
-
for (const file of
|
|
24151
|
+
for (const file of listSpecFiles(specsDir)) {
|
|
23696
24152
|
let raw;
|
|
23697
24153
|
try {
|
|
23698
|
-
raw =
|
|
24154
|
+
raw = readSpecFile(file);
|
|
23699
24155
|
} catch {
|
|
23700
24156
|
continue;
|
|
23701
24157
|
}
|
|
@@ -23727,10 +24183,10 @@ function inspectChainedRoots(rootDir = getProjectRoot()) {
|
|
|
23727
24183
|
const walk2 = (projectDir, prefix, ancestors, depth) => {
|
|
23728
24184
|
const specsDir = aiPathsAt(projectDir).specsDir();
|
|
23729
24185
|
if (!pathExists(specsDir)) return;
|
|
23730
|
-
for (const file of
|
|
24186
|
+
for (const file of listSpecFiles(specsDir)) {
|
|
23731
24187
|
let raw;
|
|
23732
24188
|
try {
|
|
23733
|
-
raw =
|
|
24189
|
+
raw = readSpecFile(file);
|
|
23734
24190
|
} catch {
|
|
23735
24191
|
continue;
|
|
23736
24192
|
}
|
|
@@ -23914,7 +24370,7 @@ function computeSpecTreeSignature(dirs) {
|
|
|
23914
24370
|
parts.push(`${dir}:missing`);
|
|
23915
24371
|
continue;
|
|
23916
24372
|
}
|
|
23917
|
-
for (const f of
|
|
24373
|
+
for (const f of listSpecFiles(dir)) {
|
|
23918
24374
|
try {
|
|
23919
24375
|
const st = fs17.statSync(f);
|
|
23920
24376
|
parts.push(`${f}:${st.mtimeMs}:${st.size}`);
|
|
@@ -24374,6 +24830,7 @@ var init_specs2 = __esm({
|
|
|
24374
24830
|
init_canonical_json();
|
|
24375
24831
|
init_lockfile();
|
|
24376
24832
|
init_yaml();
|
|
24833
|
+
init_spec_files();
|
|
24377
24834
|
init_models();
|
|
24378
24835
|
init_narrative_labels();
|
|
24379
24836
|
init_diagram();
|
|
@@ -24452,7 +24909,7 @@ var init_specs2 = __esm({
|
|
|
24452
24909
|
const specsDir = projectPaths.specsDir();
|
|
24453
24910
|
this.scanVisitedSpecDirs.push(specsDir);
|
|
24454
24911
|
if (!pathExists(specsDir)) return index;
|
|
24455
|
-
const files =
|
|
24912
|
+
const files = listSpecFiles(specsDir);
|
|
24456
24913
|
const systemYaml = path23.normalize(projectPaths.specsSystem());
|
|
24457
24914
|
const localSubprojects = [];
|
|
24458
24915
|
for (const file of files) {
|
|
@@ -24461,7 +24918,7 @@ var init_specs2 = __esm({
|
|
|
24461
24918
|
let detectedType = "spec";
|
|
24462
24919
|
let rawId;
|
|
24463
24920
|
try {
|
|
24464
|
-
const raw =
|
|
24921
|
+
const raw = readSpecFile(file);
|
|
24465
24922
|
if (raw === null || typeof raw !== "object") {
|
|
24466
24923
|
this.loaderIssues.push({
|
|
24467
24924
|
severity: "error",
|
|
@@ -24944,7 +25401,7 @@ var init_specs2 = __esm({
|
|
|
24944
25401
|
const p = this.paths.specsSystem();
|
|
24945
25402
|
if (!pathExists(p)) return null;
|
|
24946
25403
|
try {
|
|
24947
|
-
const raw =
|
|
25404
|
+
const raw = readSpecFile(p);
|
|
24948
25405
|
return SystemSpecSchema.parse(raw);
|
|
24949
25406
|
} catch (e) {
|
|
24950
25407
|
this.loaderIssues.push({
|
|
@@ -24959,7 +25416,7 @@ var init_specs2 = __esm({
|
|
|
24959
25416
|
saveSystemSpec(spec) {
|
|
24960
25417
|
const p = this.paths.specsSystem();
|
|
24961
25418
|
ensureDir(path23.dirname(p));
|
|
24962
|
-
|
|
25419
|
+
writeSpecFile(p, parseOrThrow(SystemSpecSchema, spec, "system", spec.name));
|
|
24963
25420
|
invalidateSpecCache();
|
|
24964
25421
|
}
|
|
24965
25422
|
// -------------------------------------------------------------------------
|
|
@@ -24975,7 +25432,7 @@ var init_specs2 = __esm({
|
|
|
24975
25432
|
const p = this.getSubsystemPath(id);
|
|
24976
25433
|
if (!pathExists(p)) return null;
|
|
24977
25434
|
try {
|
|
24978
|
-
const raw =
|
|
25435
|
+
const raw = readSpecFile(p);
|
|
24979
25436
|
return SubsystemSpecSchema.parse(raw);
|
|
24980
25437
|
} catch (e) {
|
|
24981
25438
|
this.loaderIssues.push({
|
|
@@ -25109,7 +25566,7 @@ var init_specs2 = __esm({
|
|
|
25109
25566
|
}
|
|
25110
25567
|
}
|
|
25111
25568
|
specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
25112
|
-
|
|
25569
|
+
writeSpecFile(p, parseOrThrow(SubsystemSpecSchema, specToWrite, "subsystem", spec.id));
|
|
25113
25570
|
invalidateSpecCache();
|
|
25114
25571
|
}
|
|
25115
25572
|
deleteSubsystemSpec(id) {
|
|
@@ -25133,7 +25590,7 @@ var init_specs2 = __esm({
|
|
|
25133
25590
|
const p = this.getComponentPath(id);
|
|
25134
25591
|
if (!pathExists(p)) return null;
|
|
25135
25592
|
try {
|
|
25136
|
-
const raw =
|
|
25593
|
+
const raw = readSpecFile(p);
|
|
25137
25594
|
return ComponentSpecSchema.parse(raw);
|
|
25138
25595
|
} catch (e) {
|
|
25139
25596
|
this.loaderIssues.push({
|
|
@@ -25170,7 +25627,7 @@ var init_specs2 = __esm({
|
|
|
25170
25627
|
);
|
|
25171
25628
|
}
|
|
25172
25629
|
specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
25173
|
-
|
|
25630
|
+
writeSpecFile(p, parseOrThrow(ComponentSpecSchema, specToWrite, "component", spec.id));
|
|
25174
25631
|
invalidateSpecCache();
|
|
25175
25632
|
this.normalizeComponentLayout();
|
|
25176
25633
|
return notices;
|
|
@@ -25231,7 +25688,7 @@ var init_specs2 = __esm({
|
|
|
25231
25688
|
const p = this.getInterfacePath(id);
|
|
25232
25689
|
if (!pathExists(p)) return null;
|
|
25233
25690
|
try {
|
|
25234
|
-
const raw =
|
|
25691
|
+
const raw = readSpecFile(p);
|
|
25235
25692
|
return InterfaceSpecSchema.parse(raw);
|
|
25236
25693
|
} catch (e) {
|
|
25237
25694
|
this.loaderIssues.push({
|
|
@@ -25262,7 +25719,7 @@ var init_specs2 = __esm({
|
|
|
25262
25719
|
if (!pathExists(p)) return;
|
|
25263
25720
|
let occupantId;
|
|
25264
25721
|
try {
|
|
25265
|
-
occupantId =
|
|
25722
|
+
occupantId = readSpecFile(p)?.id;
|
|
25266
25723
|
} catch {
|
|
25267
25724
|
return;
|
|
25268
25725
|
}
|
|
@@ -25299,7 +25756,7 @@ var init_specs2 = __esm({
|
|
|
25299
25756
|
}
|
|
25300
25757
|
}
|
|
25301
25758
|
specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
25302
|
-
|
|
25759
|
+
writeSpecFile(p, parseOrThrow(InterfaceSpecSchema, specToWrite, "interface", spec.id));
|
|
25303
25760
|
invalidateSpecCache();
|
|
25304
25761
|
return notices;
|
|
25305
25762
|
}
|
|
@@ -25324,7 +25781,7 @@ var init_specs2 = __esm({
|
|
|
25324
25781
|
const p = this.getImplementationPath(id);
|
|
25325
25782
|
if (!pathExists(p)) return null;
|
|
25326
25783
|
try {
|
|
25327
|
-
const raw =
|
|
25784
|
+
const raw = readSpecFile(p);
|
|
25328
25785
|
return ImplementationSpecSchema.parse(raw);
|
|
25329
25786
|
} catch (e) {
|
|
25330
25787
|
this.loaderIssues.push({
|
|
@@ -25356,7 +25813,7 @@ var init_specs2 = __esm({
|
|
|
25356
25813
|
}
|
|
25357
25814
|
}
|
|
25358
25815
|
specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
25359
|
-
|
|
25816
|
+
writeSpecFile(p, parseOrThrow(ImplementationSpecSchema, specToWrite, "implementation", spec.id));
|
|
25360
25817
|
invalidateSpecCache();
|
|
25361
25818
|
return notices;
|
|
25362
25819
|
}
|
|
@@ -25409,7 +25866,7 @@ var init_specs2 = __esm({
|
|
|
25409
25866
|
}
|
|
25410
25867
|
}
|
|
25411
25868
|
specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
25412
|
-
|
|
25869
|
+
writeSpecFile(p, parseOrThrow(TypeSpecSchema, specToWrite, "type", spec.id));
|
|
25413
25870
|
invalidateSpecCache();
|
|
25414
25871
|
return notices;
|
|
25415
25872
|
}
|
|
@@ -25440,7 +25897,7 @@ var init_specs2 = __esm({
|
|
|
25440
25897
|
specToWrite.createdAt = existing.createdAt;
|
|
25441
25898
|
}
|
|
25442
25899
|
specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
25443
|
-
|
|
25900
|
+
writeSpecFile(p, parseOrThrow(GroupSpecSchema, specToWrite, "group", spec.id));
|
|
25444
25901
|
invalidateSpecCache();
|
|
25445
25902
|
}
|
|
25446
25903
|
deleteGroupSpec(id) {
|
|
@@ -25604,7 +26061,7 @@ var init_specs2 = __esm({
|
|
|
25604
26061
|
findLegacySpecFiles() {
|
|
25605
26062
|
const specsDir = this.paths.specsDir();
|
|
25606
26063
|
if (!pathExists(specsDir)) return [];
|
|
25607
|
-
const files =
|
|
26064
|
+
const files = listSpecFiles(specsDir);
|
|
25608
26065
|
const legacy = [];
|
|
25609
26066
|
for (const f of files) {
|
|
25610
26067
|
const base = path23.basename(f);
|
|
@@ -30666,6 +31123,16 @@ async function runValidate(options = {}) {
|
|
|
30666
31123
|
}
|
|
30667
31124
|
}
|
|
30668
31125
|
}
|
|
31126
|
+
const carried = projectConfig.rules?.conformance?.carried ?? [];
|
|
31127
|
+
if (carried.length > 0) {
|
|
31128
|
+
const findings = carried.flatMap((g) => g.findings ?? []);
|
|
31129
|
+
const units = findings.reduce((n, f) => n + (f.covers?.length ?? 1), 0);
|
|
31130
|
+
const perKind = (kind) => carried.filter((g) => g.kind === kind).reduce((n, g) => n + (g.findings?.length ?? 0), 0);
|
|
31131
|
+
logger.blank();
|
|
31132
|
+
logger.info(import_chalk7.default.yellow(
|
|
31133
|
+
`Conformance debt register: ${findings.length} finding(s) over ${units} unit(s) carried \u2014 ${perKind("drift")} drift, ${perKind("undecided")} undecided, ${perKind("unreadable")} unreadable (\`rules.conformance.carried\`). Drift and undecided are owed; unreadable is what the analysis cannot follow.`
|
|
31134
|
+
));
|
|
31135
|
+
}
|
|
30669
31136
|
logger.blank();
|
|
30670
31137
|
if (options.ci && waivedWarnings > 0) {
|
|
30671
31138
|
logger.info(import_chalk7.default.gray(`${waivedWarnings} draft-related warning(s) (non-fatal in --ci): excluded from the failure decision because the referenced specs are in draft/design status.`));
|