@wairon/cli 5.1.1-dev.40 → 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/README.md +1 -0
- package/dist/cli/index.js +640 -123
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +604 -123
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -436,7 +436,7 @@ function activeTargetTypes(config) {
|
|
|
436
436
|
const targets = config.targets;
|
|
437
437
|
return targets.filter((t) => typeof t === "string" || t.enabled !== false).map((t) => typeof t === "string" ? t : t.type);
|
|
438
438
|
}
|
|
439
|
-
var import_zod3, BuiltinTargetConfigSchema, CustomTargetConfigSchema, TargetConfigSchema, NamingRuleConfigSchema, DocumentationRuleConfigSchema, ComplexityRuleConfigSchema, ConformanceRuleConfigSchema, DesignDepthSchema, RulesConfigSchema, PathsConfigSchema, PackSelectionSchema, ProfileSelectionSubjectSchema, ProjectProfileSelectionSchema, ProjectConfigSchema;
|
|
439
|
+
var import_zod3, BuiltinTargetConfigSchema, CustomTargetConfigSchema, TargetConfigSchema, NamingRuleConfigSchema, DocumentationRuleConfigSchema, ComplexityRuleConfigSchema, CarriedDebtKindSchema, CarriedFindingSchema, CarriedDebtSchema, ConformanceRuleConfigSchema, DesignDepthSchema, RulesConfigSchema, PathsConfigSchema, PackSelectionSchema, ProfileSelectionSubjectSchema, ProjectProfileSelectionSchema, ProjectConfigSchema;
|
|
440
440
|
var init_project = __esm({
|
|
441
441
|
"src/models/project.ts"() {
|
|
442
442
|
"use strict";
|
|
@@ -533,6 +533,32 @@ var init_project = __esm({
|
|
|
533
533
|
*/
|
|
534
534
|
maxCognitiveLevel: import_zod3.z.string().optional()
|
|
535
535
|
});
|
|
536
|
+
CarriedDebtKindSchema = import_zod3.z.enum(["drift", "undecided", "unreadable"]);
|
|
537
|
+
CarriedFindingSchema = import_zod3.z.object({
|
|
538
|
+
/** The issue code, which must be one a rule declares CARRYABLE — anything else is UNCARRYABLE_FINDING, an error. */
|
|
539
|
+
code: import_zod3.z.string(),
|
|
540
|
+
/** The spec the finding is anchored to. */
|
|
541
|
+
spec: import_zod3.z.string(),
|
|
542
|
+
/** The site inside that spec the finding names — for the code-vs-spec call checks, the contract method. */
|
|
543
|
+
at: import_zod3.z.string(),
|
|
544
|
+
/**
|
|
545
|
+
* The units an aggregating finding covers: the crossings of one
|
|
546
|
+
* UNDECLARED_COLOCATED_CALL, the steps of one CALL_STEP_UNREALIZED. Keyed by
|
|
547
|
+
* code + spec + site ALONE, a 24th crossing added to a finding that already
|
|
548
|
+
* lists 23 would be carried by an entry nobody wrote for it — so a live
|
|
549
|
+
* finding is carried only when every unit it reports is listed here, and a
|
|
550
|
+
* unit that appears is reported as new.
|
|
551
|
+
*/
|
|
552
|
+
covers: import_zod3.z.array(import_zod3.z.string()).optional()
|
|
553
|
+
});
|
|
554
|
+
CarriedDebtSchema = import_zod3.z.object({
|
|
555
|
+
/** Which of the three kinds this reason is (see CarriedDebtKindSchema). */
|
|
556
|
+
kind: CarriedDebtKindSchema,
|
|
557
|
+
/** The reason itself, in the author's own words — what is true here, and what paying it would take. */
|
|
558
|
+
why: import_zod3.z.string().min(1),
|
|
559
|
+
/** The findings this reason explains. */
|
|
560
|
+
findings: import_zod3.z.array(CarriedFindingSchema)
|
|
561
|
+
});
|
|
536
562
|
ConformanceRuleConfigSchema = import_zod3.z.object({
|
|
537
563
|
/**
|
|
538
564
|
* Project-relative paths holding this project's own source code; a
|
|
@@ -555,7 +581,28 @@ var init_project = __esm({
|
|
|
555
581
|
* that is now claimed, proven a barrel, or no longer found is
|
|
556
582
|
* STALE_UNCLAIMED_ENTRY.
|
|
557
583
|
*/
|
|
558
|
-
unclaimed: import_zod3.z.array(import_zod3.z.string()).optional()
|
|
584
|
+
unclaimed: import_zod3.z.array(import_zod3.z.string()).optional(),
|
|
585
|
+
/**
|
|
586
|
+
* The conformance findings this tree carries as declared debt, grouped by
|
|
587
|
+
* the reason that explains them — `unclaimed`'s shape, for findings about
|
|
588
|
+
* code a spec DOES claim.
|
|
589
|
+
*
|
|
590
|
+
* It is not a second lint.allow, and the difference is the claim each makes.
|
|
591
|
+
* An allow says "this finding is wrong here, by design", and is meant to
|
|
592
|
+
* live forever; an entry here says "this finding is RIGHT, and it is not
|
|
593
|
+
* paid yet". A mechanism that cannot tell those apart can never be asked how
|
|
594
|
+
* much the tree owes. Four properties keep it a register:
|
|
595
|
+
* • only a code a rule declares CARRYABLE may appear (UNCARRYABLE_FINDING,
|
|
596
|
+
* an error, which no allow and no --ci waiver can reach);
|
|
597
|
+
* • an entry carries a finding only when it lists EVERY unit that finding
|
|
598
|
+
* reports, so an aggregating finding cannot grow behind it;
|
|
599
|
+
* • an entry that stops applying — gone, or listing a unit no longer
|
|
600
|
+
* reported — is STALE_CARRIED_FINDING, so the register only shrinks;
|
|
601
|
+
* • every entry states its kind and its reason, and `wairon validate`
|
|
602
|
+
* prints the running total, so the debt is loud where a suppression is
|
|
603
|
+
* silent.
|
|
604
|
+
*/
|
|
605
|
+
carried: import_zod3.z.array(CarriedDebtSchema).optional()
|
|
559
606
|
});
|
|
560
607
|
DesignDepthSchema = import_zod3.z.enum(["components", "interfaces", "implementations", "narratives"]);
|
|
561
608
|
RulesConfigSchema = import_zod3.z.object({
|
|
@@ -1139,7 +1186,7 @@ var init_defaults = __esm({
|
|
|
1139
1186
|
copilot: ".github/prompts",
|
|
1140
1187
|
codex: ".codex/agents"
|
|
1141
1188
|
};
|
|
1142
|
-
WAIRON_VERSION = "5.1.1-dev.
|
|
1189
|
+
WAIRON_VERSION = "5.1.1-dev.42";
|
|
1143
1190
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
1144
1191
|
ARCHITECT_AGENT_ID = "agent-architect";
|
|
1145
1192
|
ARCHITECT_TEMPLATE_ID = "architect";
|
|
@@ -2812,6 +2859,24 @@ function factsFor(model, sourcePath) {
|
|
|
2812
2859
|
}
|
|
2813
2860
|
return void 0;
|
|
2814
2861
|
}
|
|
2862
|
+
function ownEntry(record, key) {
|
|
2863
|
+
return record && Object.prototype.hasOwnProperty.call(record, key) ? record[key] : void 0;
|
|
2864
|
+
}
|
|
2865
|
+
function importBindingOf(facts, name) {
|
|
2866
|
+
return ownEntry(facts.importBindings, name);
|
|
2867
|
+
}
|
|
2868
|
+
function fieldTypesOf(facts, field) {
|
|
2869
|
+
return ownEntry(facts.fieldTypes, field) ?? NO_FIELD_TYPES;
|
|
2870
|
+
}
|
|
2871
|
+
function typeBindingOf(facts, name) {
|
|
2872
|
+
return ownEntry(facts.typeOnlyBindings, name) ?? ownEntry(facts.importBindings, name)?.from;
|
|
2873
|
+
}
|
|
2874
|
+
function callSitesOf(facts, fn) {
|
|
2875
|
+
return ownEntry(facts.functionCallSites, fn);
|
|
2876
|
+
}
|
|
2877
|
+
function hasFunctionBody(facts, symbol) {
|
|
2878
|
+
return callSitesOf(facts, symbol) !== void 0;
|
|
2879
|
+
}
|
|
2815
2880
|
function resolveImport(fromFile, specifier, knownPaths) {
|
|
2816
2881
|
if (!specifier.startsWith(".")) return void 0;
|
|
2817
2882
|
const joined = pathKey(path5.posix.normalize(path5.posix.join(path5.posix.dirname(fromFile), specifier)));
|
|
@@ -2830,11 +2895,12 @@ function resolveImport(fromFile, specifier, knownPaths) {
|
|
|
2830
2895
|
}
|
|
2831
2896
|
return void 0;
|
|
2832
2897
|
}
|
|
2833
|
-
var path5;
|
|
2898
|
+
var path5, NO_FIELD_TYPES;
|
|
2834
2899
|
var init_code_model = __esm({
|
|
2835
2900
|
"src/models/code-model.ts"() {
|
|
2836
2901
|
"use strict";
|
|
2837
2902
|
path5 = __toESM(require("path"));
|
|
2903
|
+
NO_FIELD_TYPES = [];
|
|
2838
2904
|
}
|
|
2839
2905
|
});
|
|
2840
2906
|
|
|
@@ -3079,6 +3145,10 @@ var init_budget_policy = __esm({
|
|
|
3079
3145
|
function canonicalize(value) {
|
|
3080
3146
|
return JSON.stringify(sortKeys(value));
|
|
3081
3147
|
}
|
|
3148
|
+
function compareOrdinal(a, b) {
|
|
3149
|
+
if (a < b) return -1;
|
|
3150
|
+
return a > b ? 1 : 0;
|
|
3151
|
+
}
|
|
3082
3152
|
function sortKeys(v) {
|
|
3083
3153
|
if (Array.isArray(v)) return v.map(sortKeys);
|
|
3084
3154
|
if (v && typeof v === "object") {
|
|
@@ -3099,14 +3169,17 @@ var init_canonical_json = __esm({
|
|
|
3099
3169
|
});
|
|
3100
3170
|
|
|
3101
3171
|
// src/core/statehash.ts
|
|
3172
|
+
function inIdentityOrder(specs) {
|
|
3173
|
+
return specs.map((spec) => ({ spec, content: canonicalize(spec) })).sort((a, b) => compareOrdinal(a.spec.id, b.spec.id) || compareOrdinal(a.content, b.content)).map((entry) => entry.spec);
|
|
3174
|
+
}
|
|
3102
3175
|
function computeStateId() {
|
|
3103
3176
|
const tree = {
|
|
3104
3177
|
system: loadSystemSpec(),
|
|
3105
|
-
subsystems: loadSubsystemSpecs(),
|
|
3106
|
-
components: loadComponentSpecs(),
|
|
3107
|
-
interfaces: loadInterfaceSpecs(),
|
|
3108
|
-
implementations: loadImplementationSpecs(),
|
|
3109
|
-
types: loadTypeSpecs()
|
|
3178
|
+
subsystems: inIdentityOrder(loadSubsystemSpecs()),
|
|
3179
|
+
components: inIdentityOrder(loadComponentSpecs()),
|
|
3180
|
+
interfaces: inIdentityOrder(loadInterfaceSpecs()),
|
|
3181
|
+
implementations: inIdentityOrder(loadImplementationSpecs()),
|
|
3182
|
+
types: inIdentityOrder(loadTypeSpecs())
|
|
3110
3183
|
};
|
|
3111
3184
|
const digest2 = crypto.createHash("sha256").update(canonicalize(tree)).digest("hex");
|
|
3112
3185
|
return { algorithm: CONTENT_ALGORITHM, digest: digest2 };
|
|
@@ -3191,6 +3264,26 @@ var init_lockfile = __esm({
|
|
|
3191
3264
|
}
|
|
3192
3265
|
});
|
|
3193
3266
|
|
|
3267
|
+
// src/core/spec-files.ts
|
|
3268
|
+
function readSpecFile(filePath) {
|
|
3269
|
+
return readYamlFile(filePath);
|
|
3270
|
+
}
|
|
3271
|
+
function writeSpecFile(filePath, document) {
|
|
3272
|
+
writeYamlFile(filePath, document);
|
|
3273
|
+
}
|
|
3274
|
+
function listSpecFiles(specsDir) {
|
|
3275
|
+
return listFilesRecursive(specsDir, SPEC_FILE_EXTENSION);
|
|
3276
|
+
}
|
|
3277
|
+
var SPEC_FILE_EXTENSION;
|
|
3278
|
+
var init_spec_files = __esm({
|
|
3279
|
+
"src/core/spec-files.ts"() {
|
|
3280
|
+
"use strict";
|
|
3281
|
+
init_fs();
|
|
3282
|
+
init_yaml();
|
|
3283
|
+
SPEC_FILE_EXTENSION = ".yaml";
|
|
3284
|
+
}
|
|
3285
|
+
});
|
|
3286
|
+
|
|
3194
3287
|
// src/core/narrative-labels.ts
|
|
3195
3288
|
function resolveNarrativeLabels(methodName, steps) {
|
|
3196
3289
|
const errors = [];
|
|
@@ -8369,7 +8462,7 @@ function discoverPacks(dir) {
|
|
|
8369
8462
|
return [];
|
|
8370
8463
|
}
|
|
8371
8464
|
const refs = [];
|
|
8372
|
-
for (const e of entries.sort((a, b) => a.name
|
|
8465
|
+
for (const e of entries.sort((a, b) => compareOrdinal(a.name, b.name))) {
|
|
8373
8466
|
const full = path10.join(dir, e.name);
|
|
8374
8467
|
if (e.isDirectory()) {
|
|
8375
8468
|
if (packDirEntry(full)) refs.push(full);
|
|
@@ -8662,6 +8755,7 @@ var init_extensions = __esm({
|
|
|
8662
8755
|
init_fs();
|
|
8663
8756
|
init_project_config();
|
|
8664
8757
|
init_version();
|
|
8758
|
+
init_canonical_json();
|
|
8665
8759
|
init_project();
|
|
8666
8760
|
init_packstore();
|
|
8667
8761
|
ProfileDefSchema = import_zod9.z.object({
|
|
@@ -8855,13 +8949,84 @@ function buildCodeIndex(model) {
|
|
|
8855
8949
|
cache.set(path27, built);
|
|
8856
8950
|
return built;
|
|
8857
8951
|
};
|
|
8952
|
+
const republished = /* @__PURE__ */ new Map();
|
|
8953
|
+
const republishedFrom = (path27) => {
|
|
8954
|
+
const cached = republished.get(path27);
|
|
8955
|
+
if (cached) return cached;
|
|
8956
|
+
const out = /* @__PURE__ */ new Set([path27]);
|
|
8957
|
+
const queue = [path27];
|
|
8958
|
+
while (queue.length) {
|
|
8959
|
+
const from = queue.pop();
|
|
8960
|
+
const f = facts.get(from);
|
|
8961
|
+
if (f?.status !== "analyzed") continue;
|
|
8962
|
+
for (const specifier of f.reexports) {
|
|
8963
|
+
const to = resolveImport(from, specifier, paths);
|
|
8964
|
+
if (to && !out.has(to)) {
|
|
8965
|
+
out.add(to);
|
|
8966
|
+
queue.push(to);
|
|
8967
|
+
}
|
|
8968
|
+
}
|
|
8969
|
+
}
|
|
8970
|
+
republished.set(path27, out);
|
|
8971
|
+
return out;
|
|
8972
|
+
};
|
|
8973
|
+
const NO_ORIGIN = /* @__PURE__ */ new Set();
|
|
8974
|
+
const landingFrom = (scope, specifier) => {
|
|
8975
|
+
const to = resolveImport(scope, specifier, paths);
|
|
8976
|
+
return to ? republishedFrom(to) : NO_ORIGIN;
|
|
8977
|
+
};
|
|
8978
|
+
const scopeOf = (site, from) => {
|
|
8979
|
+
const path27 = pathKey(site.from ?? from);
|
|
8980
|
+
const f = facts.get(path27);
|
|
8981
|
+
return f?.status === "analyzed" && f.analysisGrade === "exact" ? { path: path27, facts: f } : void 0;
|
|
8982
|
+
};
|
|
8983
|
+
const originOf = (site, from) => {
|
|
8984
|
+
const resolved = scopeOf(site, from);
|
|
8985
|
+
if (!resolved) return NO_ORIGIN;
|
|
8986
|
+
const { path: scope, facts: f } = resolved;
|
|
8987
|
+
const landing = (specifier) => landingFrom(scope, specifier);
|
|
8988
|
+
if (!site.member) {
|
|
8989
|
+
const binding = importBindingOf(f, site.name);
|
|
8990
|
+
if (binding) return landing(binding.from);
|
|
8991
|
+
return declarationsAt(scope).has(site.name) ? republishedFrom(scope) : NO_ORIGIN;
|
|
8992
|
+
}
|
|
8993
|
+
if (!site.via) return NO_ORIGIN;
|
|
8994
|
+
const receiver = importBindingOf(f, site.via);
|
|
8995
|
+
if (!receiver) return NO_ORIGIN;
|
|
8996
|
+
if (receiver.namespace) return landing(receiver.from);
|
|
8997
|
+
const through = landing(receiver.from);
|
|
8998
|
+
for (const candidate of through) {
|
|
8999
|
+
if (declarationsAt(candidate).has(site.name)) return through;
|
|
9000
|
+
}
|
|
9001
|
+
return NO_ORIGIN;
|
|
9002
|
+
};
|
|
9003
|
+
const possibleOriginsOf = (site, from) => {
|
|
9004
|
+
const proven = originOf(site, from);
|
|
9005
|
+
if (!site.field && !site.constructed) return proven;
|
|
9006
|
+
const resolved = scopeOf(site, from);
|
|
9007
|
+
if (!resolved) return proven;
|
|
9008
|
+
const { path: scope, facts: f } = resolved;
|
|
9009
|
+
const out = new Set(proven);
|
|
9010
|
+
const widenThrough = (name, specifier) => {
|
|
9011
|
+
const landings = specifier !== void 0 ? landingFrom(scope, specifier) : declarationsAt(scope).has(name) ? republishedFrom(scope) : NO_ORIGIN;
|
|
9012
|
+
for (const candidate of landings) out.add(candidate);
|
|
9013
|
+
};
|
|
9014
|
+
if (site.field) {
|
|
9015
|
+
for (const typeName of fieldTypesOf(f, site.field)) widenThrough(typeName, typeBindingOf(f, typeName));
|
|
9016
|
+
}
|
|
9017
|
+
if (site.constructed) widenThrough(site.constructed, importBindingOf(f, site.constructed)?.from);
|
|
9018
|
+
return out;
|
|
9019
|
+
};
|
|
9020
|
+
const declarationsAt = (path27) => namesAt(declarations, pathKey(path27), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames]));
|
|
8858
9021
|
return {
|
|
8859
9022
|
paths,
|
|
8860
9023
|
exactPaths,
|
|
8861
9024
|
factsAt: (path27) => facts.get(pathKey(path27)),
|
|
8862
|
-
declarationsAt
|
|
9025
|
+
declarationsAt,
|
|
8863
9026
|
anchorsAt: (path27) => namesAt(anchors, pathKey(path27), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames, ...f.anchoredNames])),
|
|
8864
|
-
findingAnchorsAt: (path27) => namesAt(findingAnchors, pathKey(path27), (f) => new Set(f.anchoredNames))
|
|
9027
|
+
findingAnchorsAt: (path27) => namesAt(findingAnchors, pathKey(path27), (f) => new Set(f.anchoredNames)),
|
|
9028
|
+
originOf,
|
|
9029
|
+
possibleOriginsOf
|
|
8865
9030
|
};
|
|
8866
9031
|
}
|
|
8867
9032
|
function buildRealizationIndex(ctx) {
|
|
@@ -13090,6 +13255,21 @@ var init_source_file_linkage = __esm({
|
|
|
13090
13255
|
});
|
|
13091
13256
|
|
|
13092
13257
|
// src/core/rules/conformance/method-realization.ts
|
|
13258
|
+
function bodyReachable(code, file, symbol) {
|
|
13259
|
+
const facts = code.factsAt(file);
|
|
13260
|
+
if (!facts) return true;
|
|
13261
|
+
const names = /* @__PURE__ */ new Set([symbol]);
|
|
13262
|
+
const binding = importBindingOf(facts, symbol);
|
|
13263
|
+
if (binding?.imported) names.add(binding.imported);
|
|
13264
|
+
const origins = code.originOf({ name: symbol, member: false }, file);
|
|
13265
|
+
if (origins.size === 0) return true;
|
|
13266
|
+
for (const origin of origins) {
|
|
13267
|
+
const at = code.factsAt(origin);
|
|
13268
|
+
if (!at || at.status !== "analyzed" || at.analysisGrade !== "exact") return true;
|
|
13269
|
+
for (const name of names) if (hasFunctionBody(at, name)) return true;
|
|
13270
|
+
}
|
|
13271
|
+
return false;
|
|
13272
|
+
}
|
|
13093
13273
|
var methodRealizationRule;
|
|
13094
13274
|
var init_method_realization = __esm({
|
|
13095
13275
|
"src/core/rules/conformance/method-realization.ts"() {
|
|
@@ -13097,9 +13277,10 @@ var init_method_realization = __esm({
|
|
|
13097
13277
|
init_models();
|
|
13098
13278
|
methodRealizationRule = {
|
|
13099
13279
|
name: "method-realization",
|
|
13100
|
-
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.",
|
|
13280
|
+
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.",
|
|
13101
13281
|
codes: [
|
|
13102
|
-
{ 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" }
|
|
13282
|
+
{ 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" },
|
|
13283
|
+
{ 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 }
|
|
13103
13284
|
],
|
|
13104
13285
|
check(ctx) {
|
|
13105
13286
|
const code = ctx.codeIndex();
|
|
@@ -13123,7 +13304,22 @@ var init_method_realization = __esm({
|
|
|
13123
13304
|
const inDeclared = code.declarationsAt(file).has(symbol);
|
|
13124
13305
|
const inAnchored = inDeclared || code.anchorsAt(file).has(symbol);
|
|
13125
13306
|
const realized = tier === "declared" ? inDeclared : inAnchored;
|
|
13126
|
-
if (realized)
|
|
13307
|
+
if (realized) {
|
|
13308
|
+
if (!inDeclared || facts.analysisGrade !== "exact" || bodyReachable(code, file, symbol)) continue;
|
|
13309
|
+
const bodyLabel = methodImpl?.symbol ? `"${method.name}" (symbol "${symbol}")` : `"${method.name}"`;
|
|
13310
|
+
ctx.addIssue(
|
|
13311
|
+
"warning",
|
|
13312
|
+
"METHOD_BODY_NOT_FOUND",
|
|
13313
|
+
`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".`,
|
|
13314
|
+
impl.id,
|
|
13315
|
+
draft,
|
|
13316
|
+
void 0,
|
|
13317
|
+
// One method, one indivisible fact: nothing here aggregates, so
|
|
13318
|
+
// the site alone is the whole identity.
|
|
13319
|
+
{ at: method.name }
|
|
13320
|
+
);
|
|
13321
|
+
continue;
|
|
13322
|
+
}
|
|
13127
13323
|
const label = methodImpl?.symbol ? `"${method.name}" (symbol "${symbol}")` : `"${method.name}"`;
|
|
13128
13324
|
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`." : "";
|
|
13129
13325
|
ctx.addIssue(
|
|
@@ -13190,24 +13386,54 @@ var init_finding_realization = __esm({
|
|
|
13190
13386
|
});
|
|
13191
13387
|
|
|
13192
13388
|
// src/core/rules/conformance/call-conformance.ts
|
|
13193
|
-
function
|
|
13194
|
-
|
|
13195
|
-
|
|
13196
|
-
function closedCallees(facts, fn) {
|
|
13197
|
-
const direct = ownEntry(facts.functionCalls, fn);
|
|
13389
|
+
function closedCallSites(code, file, fn, stopAt) {
|
|
13390
|
+
const facts = code.factsAt(file);
|
|
13391
|
+
const direct = facts && callSitesOf(facts, fn);
|
|
13198
13392
|
if (!direct) return void 0;
|
|
13199
|
-
const
|
|
13200
|
-
const
|
|
13393
|
+
const here = pathKey(file);
|
|
13394
|
+
const out = [];
|
|
13395
|
+
const descended = /* @__PURE__ */ new Set([`${here}|${fn}`]);
|
|
13396
|
+
const queue = direct.map((s) => ({ ...s, from: s.from ?? here }));
|
|
13201
13397
|
while (queue.length) {
|
|
13202
|
-
const
|
|
13203
|
-
|
|
13204
|
-
|
|
13205
|
-
|
|
13206
|
-
|
|
13207
|
-
|
|
13208
|
-
|
|
13398
|
+
const site = queue.pop();
|
|
13399
|
+
out.push(site);
|
|
13400
|
+
if (stopAt?.has(site.name)) continue;
|
|
13401
|
+
const key = `${site.from}|${site.name}`;
|
|
13402
|
+
if (descended.has(key)) continue;
|
|
13403
|
+
descended.add(key);
|
|
13404
|
+
const scope = code.factsAt(site.from);
|
|
13405
|
+
const next = scope && callSitesOf(scope, site.name);
|
|
13406
|
+
if (!next) continue;
|
|
13407
|
+
queue.push(...next.map((s) => ({ ...s, from: s.from ?? site.from })));
|
|
13209
13408
|
}
|
|
13210
|
-
return
|
|
13409
|
+
return out;
|
|
13410
|
+
}
|
|
13411
|
+
function resolveCallTarget(ctx, componentId, methodName) {
|
|
13412
|
+
const accepted = /* @__PURE__ */ new Set([methodName]);
|
|
13413
|
+
const files = /* @__PURE__ */ new Set();
|
|
13414
|
+
for (const intf of ctx.interfacesByComponent.get(componentId) ?? []) {
|
|
13415
|
+
for (const impl of ctx.implementationsByContract.get(intf.id) ?? []) {
|
|
13416
|
+
const method = impl.methods.find((m) => m.name === methodName);
|
|
13417
|
+
if (!method && !intf.methods.some((m) => m.name === methodName)) continue;
|
|
13418
|
+
if (method?.symbol) accepted.add(method.symbol);
|
|
13419
|
+
const file = methodSourceFile(method ?? {}, impl.sourcePath);
|
|
13420
|
+
if (file) files.add(pathKey(file));
|
|
13421
|
+
}
|
|
13422
|
+
}
|
|
13423
|
+
return { accepted, files };
|
|
13424
|
+
}
|
|
13425
|
+
function describeSite(site) {
|
|
13426
|
+
if (!site.member) return `${site.name}(\u2026)`;
|
|
13427
|
+
if (site.via) return `${site.via}.${site.name}(\u2026)`;
|
|
13428
|
+
if (site.field) return `this.${site.field}.${site.name}(\u2026)`;
|
|
13429
|
+
if (site.constructed) return `new ${site.constructed}(\u2026).${site.name}(\u2026)`;
|
|
13430
|
+
return `<receiver>.${site.name}(\u2026)`;
|
|
13431
|
+
}
|
|
13432
|
+
function describeMiss(m) {
|
|
13433
|
+
const head = `step ${m.step} \u2192 ${m.target} (looked for ${m.accepted.map((a) => `"${a}"`).join(" / ")}`;
|
|
13434
|
+
if (m.miss.kind === "elsewhere") return `${head}, called but resolved to ${m.miss.landed.map((p) => `"${p}"`).join(", ")})`;
|
|
13435
|
+
if (m.miss.kind === "unresolved") return `${head}, written as ${m.miss.shapes.map((s) => `\`${s}\``).join(" / ")})`;
|
|
13436
|
+
return `${head})`;
|
|
13211
13437
|
}
|
|
13212
13438
|
var callConformanceRule;
|
|
13213
13439
|
var init_call_conformance = __esm({
|
|
@@ -13216,13 +13442,24 @@ var init_call_conformance = __esm({
|
|
|
13216
13442
|
init_models();
|
|
13217
13443
|
callConformanceRule = {
|
|
13218
13444
|
name: "call-conformance",
|
|
13219
|
-
description: "Code\u2194spec Level 3
|
|
13445
|
+
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.",
|
|
13220
13446
|
codes: [
|
|
13221
|
-
{ code: "CALL_STEP_UNREALIZED", defaultSeverity: "warning", summary: "Narrative call step
|
|
13447
|
+
{ 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 },
|
|
13448
|
+
{ 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 },
|
|
13449
|
+
{ 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 }
|
|
13222
13450
|
],
|
|
13223
13451
|
check(ctx) {
|
|
13224
13452
|
const code = ctx.codeIndex();
|
|
13225
|
-
|
|
13453
|
+
const methods = ctx.implementationMethods();
|
|
13454
|
+
const modelledAt = /* @__PURE__ */ new Map();
|
|
13455
|
+
for (const entry of methods) {
|
|
13456
|
+
if (!entry.sourceFile) continue;
|
|
13457
|
+
const file = pathKey(entry.sourceFile);
|
|
13458
|
+
const byName = modelledAt.get(file) ?? /* @__PURE__ */ new Map();
|
|
13459
|
+
byName.set(entry.method.symbol ?? entry.method.name, { component: entry.component.id, method: entry.method.name });
|
|
13460
|
+
modelledAt.set(file, byName);
|
|
13461
|
+
}
|
|
13462
|
+
for (const entry of methods) {
|
|
13226
13463
|
const { implementation: impl, method: implMethod, component, sourceFile: file } = entry;
|
|
13227
13464
|
const tier = implMethod.conformance ?? impl.conformance ?? defaultConformanceTier(component);
|
|
13228
13465
|
if (tier === "off") continue;
|
|
@@ -13230,37 +13467,91 @@ var init_call_conformance = __esm({
|
|
|
13230
13467
|
if (!file) continue;
|
|
13231
13468
|
const facts = code.factsAt(file);
|
|
13232
13469
|
if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
|
|
13470
|
+
const here = pathKey(file);
|
|
13233
13471
|
const fnSymbol = implMethod.symbol ?? implMethod.name;
|
|
13234
|
-
const
|
|
13235
|
-
|
|
13236
|
-
|
|
13472
|
+
const colocated = modelledAt.get(here) ?? /* @__PURE__ */ new Map();
|
|
13473
|
+
const sites = closedCallSites(code, file, fnSymbol);
|
|
13474
|
+
if (!sites) continue;
|
|
13475
|
+
const missed = [];
|
|
13237
13476
|
for (const step of implMethod.narrative) {
|
|
13238
13477
|
if (step.type !== "call" || !step.targetComponent || !step.targetMethod) continue;
|
|
13239
13478
|
if (!ctx.componentMap.has(step.targetComponent)) continue;
|
|
13240
|
-
const
|
|
13241
|
-
|
|
13242
|
-
|
|
13243
|
-
|
|
13244
|
-
|
|
13479
|
+
const target = resolveCallTarget(ctx, step.targetComponent, step.targetMethod);
|
|
13480
|
+
if (target.accepted.has(fnSymbol)) continue;
|
|
13481
|
+
const matching = sites.filter((s) => target.accepted.has(s.name));
|
|
13482
|
+
const ref = `${step.targetComponent}.${step.targetMethod}`;
|
|
13483
|
+
if (target.files.size === 0) {
|
|
13484
|
+
if (matching.length === 0) {
|
|
13485
|
+
missed.push({ step: step.stepNumber, target: ref, accepted: [...target.accepted], miss: { kind: "absent" } });
|
|
13245
13486
|
}
|
|
13487
|
+
continue;
|
|
13246
13488
|
}
|
|
13247
|
-
|
|
13248
|
-
|
|
13249
|
-
|
|
13250
|
-
|
|
13251
|
-
|
|
13252
|
-
|
|
13253
|
-
|
|
13489
|
+
const landed = /* @__PURE__ */ new Set();
|
|
13490
|
+
let realized = false;
|
|
13491
|
+
for (const site of matching) {
|
|
13492
|
+
for (const origin of code.originOf(site, file)) landed.add(origin);
|
|
13493
|
+
for (const origin of code.possibleOriginsOf(site, file)) {
|
|
13494
|
+
if (target.files.has(origin)) realized = true;
|
|
13495
|
+
}
|
|
13496
|
+
}
|
|
13497
|
+
if (realized) continue;
|
|
13498
|
+
const miss = matching.length === 0 ? { kind: "absent" } : landed.size === 0 ? { kind: "unresolved", shapes: [...new Set(matching.map(describeSite))].sort() } : { kind: "elsewhere", landed: [...landed].sort() };
|
|
13499
|
+
missed.push({ step: step.stepNumber, target: ref, accepted: [...target.accepted], miss });
|
|
13500
|
+
}
|
|
13501
|
+
const unrealized = missed.filter((m) => m.miss.kind !== "unresolved");
|
|
13502
|
+
const unresolved = missed.filter((m) => m.miss.kind === "unresolved");
|
|
13503
|
+
if (unrealized.length) {
|
|
13504
|
+
ctx.addIssue(
|
|
13505
|
+
"warning",
|
|
13506
|
+
"CALL_STEP_UNREALIZED",
|
|
13507
|
+
`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.`,
|
|
13508
|
+
impl.id,
|
|
13509
|
+
entry.draftContext,
|
|
13510
|
+
void 0,
|
|
13511
|
+
// A step, named by its number and its target: what the register
|
|
13512
|
+
// carries is ONE unresolved step, never "this method's call steps".
|
|
13513
|
+
{ at: implMethod.name, covers: unrealized.map((m) => `${m.step}:${m.target}`) }
|
|
13514
|
+
);
|
|
13515
|
+
}
|
|
13516
|
+
if (unresolved.length) {
|
|
13517
|
+
ctx.addIssue(
|
|
13518
|
+
"warning",
|
|
13519
|
+
"CALL_ORIGIN_UNRESOLVED",
|
|
13520
|
+
`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.`,
|
|
13521
|
+
impl.id,
|
|
13522
|
+
entry.draftContext,
|
|
13523
|
+
void 0,
|
|
13524
|
+
{ at: implMethod.name, covers: unresolved.map((m) => `${m.step}:${m.target}`) }
|
|
13525
|
+
);
|
|
13526
|
+
}
|
|
13527
|
+
const declared = /* @__PURE__ */ new Set();
|
|
13528
|
+
for (const step of implMethod.narrative) {
|
|
13529
|
+
if (step.targetComponent && step.targetMethod) declared.add(`${step.targetComponent}.${step.targetMethod}`);
|
|
13530
|
+
}
|
|
13531
|
+
const crossings = /* @__PURE__ */ new Map();
|
|
13532
|
+
for (const site of closedCallSites(code, file, fnSymbol, new Set(colocated.keys())) ?? []) {
|
|
13533
|
+
const owner = colocated.get(site.name);
|
|
13534
|
+
if (!owner || owner.component === component.id) continue;
|
|
13535
|
+
const ref = `${owner.component}.${owner.method}`;
|
|
13536
|
+
if (declared.has(ref) || crossings.has(ref)) continue;
|
|
13537
|
+
if (!code.originOf(site, file).has(here)) continue;
|
|
13538
|
+
crossings.set(ref, site.name);
|
|
13539
|
+
}
|
|
13540
|
+
if (crossings.size) {
|
|
13541
|
+
const detail = [...crossings].map(([ref, name]) => `"${name}" (${ref})`).join("; ");
|
|
13542
|
+
ctx.addIssue(
|
|
13543
|
+
"warning",
|
|
13544
|
+
"UNDECLARED_COLOCATED_CALL",
|
|
13545
|
+
`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.`,
|
|
13546
|
+
impl.id,
|
|
13547
|
+
entry.draftContext,
|
|
13548
|
+
void 0,
|
|
13549
|
+
// The crossings themselves. Keyed by method alone, a 24th crossing
|
|
13550
|
+
// would ride into a register entry written for 23 - so each one is
|
|
13551
|
+
// named, and one nobody carried fires on the day it appears.
|
|
13552
|
+
{ at: implMethod.name, covers: [...crossings.keys()] }
|
|
13553
|
+
);
|
|
13254
13554
|
}
|
|
13255
|
-
if (missing.length === 0) continue;
|
|
13256
|
-
const detail = missing.map((m) => `step ${m.step} \u2192 ${m.target} (looked for ${m.accepted.map((a) => `"${a}"`).join(" / ")})`).join("; ");
|
|
13257
|
-
ctx.addIssue(
|
|
13258
|
-
"warning",
|
|
13259
|
-
"CALL_STEP_UNREALIZED",
|
|
13260
|
-
`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.`,
|
|
13261
|
-
impl.id,
|
|
13262
|
-
entry.draftContext
|
|
13263
|
-
);
|
|
13264
13555
|
}
|
|
13265
13556
|
}
|
|
13266
13557
|
};
|
|
@@ -13770,6 +14061,69 @@ var init_unclaimed_source = __esm({
|
|
|
13770
14061
|
}
|
|
13771
14062
|
});
|
|
13772
14063
|
|
|
14064
|
+
// src/core/rules/conformance/carried-debt.ts
|
|
14065
|
+
var carriedDebtRule;
|
|
14066
|
+
var init_carried_debt = __esm({
|
|
14067
|
+
"src/core/rules/conformance/carried-debt.ts"() {
|
|
14068
|
+
"use strict";
|
|
14069
|
+
carriedDebtRule = {
|
|
14070
|
+
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.",
|
|
14072
|
+
codes: [
|
|
14073
|
+
{ 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
|
+
{ 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" }
|
|
14075
|
+
],
|
|
14076
|
+
check(ctx) {
|
|
14077
|
+
const carried = ctx.carriedFindings;
|
|
14078
|
+
if (carried.length === 0) return;
|
|
14079
|
+
for (const entry of carried) {
|
|
14080
|
+
if (ctx.carryableIssueCodes.has(entry.code)) continue;
|
|
14081
|
+
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";
|
|
14082
|
+
ctx.addIssue(
|
|
14083
|
+
"error",
|
|
14084
|
+
"UNCARRYABLE_FINDING",
|
|
14085
|
+
`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.`
|
|
14086
|
+
);
|
|
14087
|
+
}
|
|
14088
|
+
if (ctx.codeModel.files.length === 0) return;
|
|
14089
|
+
const known = new Set(ctx.specIds().map((s) => s.id));
|
|
14090
|
+
const addressed = /* @__PURE__ */ new Set();
|
|
14091
|
+
for (const entry of carried) {
|
|
14092
|
+
if (!ctx.carryableIssueCodes.has(entry.code)) continue;
|
|
14093
|
+
if (ctx.rules?.sddRuleSeverity?.[entry.code] === "off") continue;
|
|
14094
|
+
if (!ctx.isSpecInScope(entry.spec)) continue;
|
|
14095
|
+
const key = `${entry.spec}|${entry.code}|${entry.at}`;
|
|
14096
|
+
if (addressed.has(key)) {
|
|
14097
|
+
ctx.addIssue(
|
|
14098
|
+
"warning",
|
|
14099
|
+
"STALE_CARRIED_FINDING",
|
|
14100
|
+
`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.`
|
|
14101
|
+
);
|
|
14102
|
+
continue;
|
|
14103
|
+
}
|
|
14104
|
+
addressed.add(key);
|
|
14105
|
+
if (!entry.fired) {
|
|
14106
|
+
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}"`;
|
|
14107
|
+
ctx.addIssue(
|
|
14108
|
+
"warning",
|
|
14109
|
+
"STALE_CARRIED_FINDING",
|
|
14110
|
+
`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.`
|
|
14111
|
+
);
|
|
14112
|
+
continue;
|
|
14113
|
+
}
|
|
14114
|
+
const paid = entry.covers.filter((unit) => !entry.seen.has(unit));
|
|
14115
|
+
if (paid.length === 0) continue;
|
|
14116
|
+
ctx.addIssue(
|
|
14117
|
+
"warning",
|
|
14118
|
+
"STALE_CARRIED_FINDING",
|
|
14119
|
+
`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.`
|
|
14120
|
+
);
|
|
14121
|
+
}
|
|
14122
|
+
}
|
|
14123
|
+
};
|
|
14124
|
+
}
|
|
14125
|
+
});
|
|
14126
|
+
|
|
13773
14127
|
// src/core/rules/heuristic/coupling-health.ts
|
|
13774
14128
|
var DEFAULT_GOD_COMPONENT_THRESHOLD, couplingRule;
|
|
13775
14129
|
var init_coupling_health = __esm({
|
|
@@ -14924,8 +15278,8 @@ function registerPackRules(packRules) {
|
|
|
14924
15278
|
for (const rule of packRules) addRule(rule);
|
|
14925
15279
|
}
|
|
14926
15280
|
function ruleSequence() {
|
|
14927
|
-
const
|
|
14928
|
-
return ruleSet.
|
|
15281
|
+
const audits = [carriedDebtRule, lintAllowsRule].filter((r) => ruleSet.includes(r));
|
|
15282
|
+
return [...ruleSet.filter((r) => !audits.includes(r)), ...audits];
|
|
14929
15283
|
}
|
|
14930
15284
|
function specScopedRules() {
|
|
14931
15285
|
return ruleSequence().filter((r) => r.scope === "spec");
|
|
@@ -15015,6 +15369,7 @@ var init_repository = __esm({
|
|
|
15015
15369
|
init_integration_sim_coverage();
|
|
15016
15370
|
init_type_realization();
|
|
15017
15371
|
init_unclaimed_source();
|
|
15372
|
+
init_carried_debt();
|
|
15018
15373
|
init_coupling_health();
|
|
15019
15374
|
init_signature_language_builtins();
|
|
15020
15375
|
init_narrative_language_constructs();
|
|
@@ -15206,8 +15561,11 @@ var init_repository = __esm({
|
|
|
15206
15561
|
// elsewhere?) rather than spec content.
|
|
15207
15562
|
packResolutionRule,
|
|
15208
15563
|
reproducibilityRule,
|
|
15209
|
-
// MUST run last:
|
|
15210
|
-
//
|
|
15564
|
+
// MUST run last, in this order: each audits what the earlier rules did
|
|
15565
|
+
// with a declared exception. The debt register first (which carried
|
|
15566
|
+
// findings the conformance family actually matched), then the allows
|
|
15567
|
+
// (which suppressions any rule actually consumed).
|
|
15568
|
+
carriedDebtRule,
|
|
15211
15569
|
lintAllowsRule
|
|
15212
15570
|
];
|
|
15213
15571
|
ruleSet = [];
|
|
@@ -15330,9 +15688,15 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
15330
15688
|
const imports = /* @__PURE__ */ new Set();
|
|
15331
15689
|
const reexports = /* @__PURE__ */ new Set();
|
|
15332
15690
|
const starExports = [];
|
|
15691
|
+
const namedReexports = [];
|
|
15333
15692
|
const complexity = /* @__PURE__ */ new Map();
|
|
15334
15693
|
const calls = /* @__PURE__ */ new Map();
|
|
15694
|
+
const importBindings = /* @__PURE__ */ new Map();
|
|
15695
|
+
const typeOnlyBindings = /* @__PURE__ */ new Map();
|
|
15696
|
+
const fieldTypes = /* @__PURE__ */ new Map();
|
|
15335
15697
|
const mutableBindings = /* @__PURE__ */ new Set();
|
|
15698
|
+
const receiverKey = (site) => site.via ?? (site.field ? `this.${site.field}` : site.constructed ? `new ${site.constructed}` : "");
|
|
15699
|
+
const siteKey = (site) => `${site.member ? "m" : "b"}:${receiverKey(site)}:${site.name}`;
|
|
15336
15700
|
const addBindingNames = (name) => {
|
|
15337
15701
|
if (ts.isIdentifier(name)) declared.add(name.text);
|
|
15338
15702
|
else if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) {
|
|
@@ -15346,6 +15710,17 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
15346
15710
|
if (ts.isPrivateIdentifier(name)) return name.text;
|
|
15347
15711
|
return void 0;
|
|
15348
15712
|
};
|
|
15713
|
+
const typeReferenceName = (type) => type && ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName) ? type.typeName.text : void 0;
|
|
15714
|
+
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);
|
|
15715
|
+
const recordFieldType = (name, type) => {
|
|
15716
|
+
const typeName = typeReferenceName(type);
|
|
15717
|
+
if (!typeName) return;
|
|
15718
|
+
const field = propertyNameText(name);
|
|
15719
|
+
if (!field) return;
|
|
15720
|
+
const named = fieldTypes.get(field) ?? /* @__PURE__ */ new Set();
|
|
15721
|
+
named.add(typeName);
|
|
15722
|
+
fieldTypes.set(field, named);
|
|
15723
|
+
};
|
|
15349
15724
|
const hasExportModifier = (node) => {
|
|
15350
15725
|
const mods = node.modifiers;
|
|
15351
15726
|
return !!mods?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
@@ -15368,7 +15743,10 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
15368
15743
|
};
|
|
15369
15744
|
const collectFunctionFacts = (fn) => {
|
|
15370
15745
|
let score = 1;
|
|
15371
|
-
const callees = /* @__PURE__ */ new
|
|
15746
|
+
const callees = /* @__PURE__ */ new Map();
|
|
15747
|
+
const addSite = (site) => {
|
|
15748
|
+
callees.set(siteKey(site), site);
|
|
15749
|
+
};
|
|
15372
15750
|
const count = (node) => {
|
|
15373
15751
|
if (namedFunctionName(node) !== void 0) return;
|
|
15374
15752
|
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)) {
|
|
@@ -15380,8 +15758,17 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
15380
15758
|
}
|
|
15381
15759
|
} else if (ts.isCallExpression(node)) {
|
|
15382
15760
|
const callee = node.expression;
|
|
15383
|
-
if (ts.isIdentifier(callee))
|
|
15384
|
-
else if (ts.isPropertyAccessExpression(callee))
|
|
15761
|
+
if (ts.isIdentifier(callee)) addSite({ name: callee.text, member: false });
|
|
15762
|
+
else if (ts.isPropertyAccessExpression(callee)) {
|
|
15763
|
+
const receiver = callee.expression;
|
|
15764
|
+
const name = callee.name.text;
|
|
15765
|
+
if (ts.isIdentifier(receiver)) addSite({ name, member: true, via: receiver.text });
|
|
15766
|
+
else if (ts.isPropertyAccessExpression(receiver) && receiver.expression.kind === ts.SyntaxKind.ThisKeyword) {
|
|
15767
|
+
addSite({ name, member: true, field: receiver.name.text });
|
|
15768
|
+
} else if (ts.isNewExpression(receiver) && ts.isIdentifier(receiver.expression)) {
|
|
15769
|
+
addSite({ name, member: true, constructed: receiver.expression.text });
|
|
15770
|
+
} else addSite({ name, member: true });
|
|
15771
|
+
}
|
|
15385
15772
|
}
|
|
15386
15773
|
ts.forEachChild(node, count);
|
|
15387
15774
|
};
|
|
@@ -15393,9 +15780,13 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
15393
15780
|
if (fnName && node.body) {
|
|
15394
15781
|
const facts = collectFunctionFacts(node);
|
|
15395
15782
|
complexity.set(fnName, Math.max(complexity.get(fnName) ?? 0, facts.score));
|
|
15396
|
-
const
|
|
15397
|
-
for (const
|
|
15398
|
-
calls.set(fnName,
|
|
15783
|
+
const sites = calls.get(fnName) ?? /* @__PURE__ */ new Map();
|
|
15784
|
+
for (const [key, site] of facts.callees) sites.set(key, site);
|
|
15785
|
+
calls.set(fnName, sites);
|
|
15786
|
+
}
|
|
15787
|
+
if (ts.isPropertyDeclaration(node)) recordFieldType(node.name, node.type);
|
|
15788
|
+
else if (ts.isParameter(node) && node.parent && ts.isConstructorDeclaration(node.parent) && isParameterProperty(node)) {
|
|
15789
|
+
recordFieldType(node.name, node.type);
|
|
15399
15790
|
}
|
|
15400
15791
|
if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isModuleDeclaration(node)) {
|
|
15401
15792
|
const name = node.name && ts.isIdentifier(node.name) ? node.name.text : void 0;
|
|
@@ -15422,11 +15813,24 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
15422
15813
|
} else if (ts.isImportDeclaration(node)) {
|
|
15423
15814
|
const clause = node.importClause;
|
|
15424
15815
|
const typeOnly = clause?.isTypeOnly ?? false;
|
|
15425
|
-
|
|
15426
|
-
if (
|
|
15816
|
+
const spec = ts.isStringLiteral(node.moduleSpecifier) ? node.moduleSpecifier.text : void 0;
|
|
15817
|
+
if (!typeOnly && spec) imports.add(spec);
|
|
15818
|
+
const bind = (name, namespace, imported, elementTypeOnly = false) => {
|
|
15819
|
+
declared.add(name);
|
|
15820
|
+
if (!spec) return;
|
|
15821
|
+
if (typeOnly || elementTypeOnly) {
|
|
15822
|
+
typeOnlyBindings.set(name, spec);
|
|
15823
|
+
return;
|
|
15824
|
+
}
|
|
15825
|
+
const fact = { from: spec };
|
|
15826
|
+
if (namespace) fact.namespace = true;
|
|
15827
|
+
if (imported && imported !== name) fact.imported = imported;
|
|
15828
|
+
importBindings.set(name, fact);
|
|
15829
|
+
};
|
|
15830
|
+
if (clause?.name) bind(clause.name.text, false);
|
|
15427
15831
|
if (clause?.namedBindings) {
|
|
15428
|
-
if (ts.isNamespaceImport(clause.namedBindings))
|
|
15429
|
-
else for (const el of clause.namedBindings.elements)
|
|
15832
|
+
if (ts.isNamespaceImport(clause.namedBindings)) bind(clause.namedBindings.name.text, true);
|
|
15833
|
+
else for (const el of clause.namedBindings.elements) bind(el.name.text, false, el.propertyName?.text, el.isTypeOnly);
|
|
15430
15834
|
}
|
|
15431
15835
|
} else if (ts.isExportDeclaration(node)) {
|
|
15432
15836
|
const spec = node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier) ? node.moduleSpecifier.text : void 0;
|
|
@@ -15435,6 +15839,9 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
15435
15839
|
for (const el of node.exportClause.elements) {
|
|
15436
15840
|
declared.add(el.name.text);
|
|
15437
15841
|
exported.add(el.name.text);
|
|
15842
|
+
if (spec && !node.isTypeOnly && !el.isTypeOnly) {
|
|
15843
|
+
namedReexports.push({ exported: el.name.text, local: (el.propertyName ?? el.name).text, from: spec });
|
|
15844
|
+
}
|
|
15438
15845
|
}
|
|
15439
15846
|
} else if (!node.exportClause && spec) {
|
|
15440
15847
|
starExports.push(spec);
|
|
@@ -15457,7 +15864,7 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
15457
15864
|
};
|
|
15458
15865
|
visit(sf);
|
|
15459
15866
|
const reexportOnly = sf.statements.length > 0 && sf.statements.every((st) => ts.isExportDeclaration(st) && !!st.moduleSpecifier);
|
|
15460
|
-
return { declared, anchors, exported, imports, reexports, starExports, complexity, calls, mutableBindings, reexportOnly };
|
|
15867
|
+
return { declared, anchors, exported, imports, reexports, starExports, namedReexports, complexity, calls, importBindings, typeOnlyBindings, fieldTypes, mutableBindings, reexportOnly };
|
|
15461
15868
|
}
|
|
15462
15869
|
function resolveRelativeModule(fromFile, specifier) {
|
|
15463
15870
|
if (!specifier.startsWith(".")) return null;
|
|
@@ -15480,34 +15887,47 @@ function resolveRelativeModule(fromFile, specifier) {
|
|
|
15480
15887
|
}
|
|
15481
15888
|
return null;
|
|
15482
15889
|
}
|
|
15483
|
-
function
|
|
15484
|
-
|
|
15485
|
-
|
|
15486
|
-
|
|
15487
|
-
|
|
15488
|
-
|
|
15489
|
-
|
|
15490
|
-
|
|
15491
|
-
|
|
15492
|
-
targetFacts = walkExact(ts, fs8.readFileSync(target, "utf8"), target);
|
|
15493
|
-
} catch {
|
|
15494
|
-
targetFacts = null;
|
|
15495
|
-
}
|
|
15496
|
-
exactCache.set(target, targetFacts);
|
|
15890
|
+
function reexportTarget(ts, filePath, specifier, projectRoot, visited, exactCache) {
|
|
15891
|
+
const target = resolveRelativeModule(filePath, specifier);
|
|
15892
|
+
if (!target || path11.relative(projectRoot, target).startsWith("..")) return null;
|
|
15893
|
+
let targetFacts = exactCache.get(target);
|
|
15894
|
+
if (targetFacts === void 0) {
|
|
15895
|
+
try {
|
|
15896
|
+
targetFacts = walkExact(ts, fs8.readFileSync(target, "utf8"), target);
|
|
15897
|
+
} catch {
|
|
15898
|
+
targetFacts = null;
|
|
15497
15899
|
}
|
|
15498
|
-
|
|
15499
|
-
|
|
15500
|
-
|
|
15900
|
+
exactCache.set(target, targetFacts);
|
|
15901
|
+
}
|
|
15902
|
+
if (!targetFacts) return null;
|
|
15903
|
+
if (!visited.has(target)) {
|
|
15904
|
+
visited.add(target);
|
|
15905
|
+
chaseReexports(ts, targetFacts, target, projectRoot, visited, exactCache);
|
|
15906
|
+
}
|
|
15907
|
+
return { path: target, facts: targetFacts };
|
|
15908
|
+
}
|
|
15909
|
+
function carryBody(facts, target, targetPath, local, exported, projectRoot) {
|
|
15910
|
+
const score = target.complexity.get(local);
|
|
15911
|
+
if (score !== void 0) facts.complexity.set(exported, Math.max(facts.complexity.get(exported) ?? 0, score));
|
|
15912
|
+
const targetCalls = target.calls.get(local);
|
|
15913
|
+
if (!targetCalls) return;
|
|
15914
|
+
const carriedFrom = pathKey(path11.relative(projectRoot, targetPath));
|
|
15915
|
+
const sites = facts.calls.get(exported) ?? /* @__PURE__ */ new Map();
|
|
15916
|
+
for (const [key, site] of targetCalls) sites.set(carriedFrom + "|" + key, { ...site, from: site.from ?? carriedFrom });
|
|
15917
|
+
facts.calls.set(exported, sites);
|
|
15918
|
+
}
|
|
15919
|
+
function chaseReexports(ts, facts, filePath, projectRoot, visited, exactCache) {
|
|
15920
|
+
for (const re of facts.namedReexports) {
|
|
15921
|
+
const target = reexportTarget(ts, filePath, re.from, projectRoot, visited, exactCache);
|
|
15922
|
+
if (target) carryBody(facts, target.facts, target.path, re.local, re.exported, projectRoot);
|
|
15923
|
+
}
|
|
15924
|
+
for (const spec of facts.starExports) {
|
|
15925
|
+
const target = reexportTarget(ts, filePath, spec, projectRoot, visited, exactCache);
|
|
15926
|
+
if (!target) continue;
|
|
15927
|
+
for (const name of target.facts.exported) {
|
|
15501
15928
|
facts.exported.add(name);
|
|
15502
15929
|
facts.declared.add(name);
|
|
15503
|
-
|
|
15504
|
-
if (c !== void 0) facts.complexity.set(name, Math.max(facts.complexity.get(name) ?? 0, c));
|
|
15505
|
-
const targetCalls = targetFacts.calls.get(name);
|
|
15506
|
-
if (targetCalls) {
|
|
15507
|
-
const set = facts.calls.get(name) ?? /* @__PURE__ */ new Set();
|
|
15508
|
-
for (const callee of targetCalls) set.add(callee);
|
|
15509
|
-
facts.calls.set(name, set);
|
|
15510
|
-
}
|
|
15930
|
+
carryBody(facts, target.facts, target.path, name, name, projectRoot);
|
|
15511
15931
|
}
|
|
15512
15932
|
}
|
|
15513
15933
|
}
|
|
@@ -15612,7 +16032,7 @@ function buildCodeModel(implementations, types, projectRoot, sourceRoots = [], e
|
|
|
15612
16032
|
if (ts) {
|
|
15613
16033
|
try {
|
|
15614
16034
|
const facts = walkExact(ts, text, absolute);
|
|
15615
|
-
|
|
16035
|
+
chaseReexports(ts, facts, absolute, projectRoot, /* @__PURE__ */ new Set([absolute]), exactCache);
|
|
15616
16036
|
analyzed = {
|
|
15617
16037
|
analysisGrade: "exact",
|
|
15618
16038
|
declaredNames: [...facts.declared],
|
|
@@ -15621,7 +16041,10 @@ function buildCodeModel(implementations, types, projectRoot, sourceRoots = [], e
|
|
|
15621
16041
|
imports: [...facts.imports],
|
|
15622
16042
|
reexports: [...facts.reexports],
|
|
15623
16043
|
functionComplexity: Object.fromEntries(facts.complexity),
|
|
15624
|
-
|
|
16044
|
+
functionCallSites: Object.fromEntries([...facts.calls].map(([k, v]) => [k, [...v.values()]])),
|
|
16045
|
+
importBindings: Object.fromEntries(facts.importBindings),
|
|
16046
|
+
typeOnlyBindings: Object.fromEntries(facts.typeOnlyBindings),
|
|
16047
|
+
fieldTypes: Object.fromEntries([...facts.fieldTypes].map(([k, v]) => [k, [...v]])),
|
|
15625
16048
|
topLevelMutableBindings: [...facts.mutableBindings],
|
|
15626
16049
|
reexportOnly: facts.reexportOnly
|
|
15627
16050
|
};
|
|
@@ -16034,7 +16457,27 @@ function buildRuleContext(opts) {
|
|
|
16034
16457
|
for (const i of interfaces) collectAllows(i.id, i.lint);
|
|
16035
16458
|
for (const im of implementations) collectAllows(im.id, im.lint);
|
|
16036
16459
|
for (const t of types) collectAllows(t.id, t.lint);
|
|
16037
|
-
const
|
|
16460
|
+
const carriedFindings = [];
|
|
16461
|
+
const carriedLookup = /* @__PURE__ */ new Map();
|
|
16462
|
+
const carriedKey = (spec, code, at) => `${spec}\0${code}\0${at}`;
|
|
16463
|
+
for (const group of rules?.conformance?.carried ?? []) {
|
|
16464
|
+
for (const f of group.findings ?? []) {
|
|
16465
|
+
const entry = {
|
|
16466
|
+
kind: group.kind,
|
|
16467
|
+
why: group.why,
|
|
16468
|
+
code: f.code,
|
|
16469
|
+
spec: f.spec,
|
|
16470
|
+
at: f.at,
|
|
16471
|
+
covers: f.covers ?? [],
|
|
16472
|
+
fired: false,
|
|
16473
|
+
seen: /* @__PURE__ */ new Set()
|
|
16474
|
+
};
|
|
16475
|
+
carriedFindings.push(entry);
|
|
16476
|
+
const key = carriedKey(f.spec, f.code, f.at);
|
|
16477
|
+
if (!carriedLookup.has(key)) carriedLookup.set(key, entry);
|
|
16478
|
+
}
|
|
16479
|
+
}
|
|
16480
|
+
const addIssue = (defaultSeverity, code, message, specId, isDraftContext, surfaceResolved, parts) => {
|
|
16038
16481
|
if (scopeSubsystem && specId && !isSpecInScope(specId)) {
|
|
16039
16482
|
return;
|
|
16040
16483
|
}
|
|
@@ -16052,10 +16495,25 @@ function buildRuleContext(opts) {
|
|
|
16052
16495
|
if (severity === "warning") return;
|
|
16053
16496
|
}
|
|
16054
16497
|
}
|
|
16498
|
+
let text = message;
|
|
16499
|
+
if (specId && parts && severity === "warning") {
|
|
16500
|
+
const entry = carriedLookup.get(carriedKey(specId, code, parts.at));
|
|
16501
|
+
if (entry) {
|
|
16502
|
+
entry.fired = true;
|
|
16503
|
+
const listed = new Set(entry.covers);
|
|
16504
|
+
const grew = [];
|
|
16505
|
+
for (const unit of parts.covers ?? []) {
|
|
16506
|
+
if (listed.has(unit)) entry.seen.add(unit);
|
|
16507
|
+
else grew.push(unit);
|
|
16508
|
+
}
|
|
16509
|
+
if (grew.length === 0) return;
|
|
16510
|
+
text = `${text} 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.`;
|
|
16511
|
+
}
|
|
16512
|
+
}
|
|
16055
16513
|
issues.push({
|
|
16056
16514
|
severity,
|
|
16057
16515
|
code,
|
|
16058
|
-
message,
|
|
16516
|
+
message: text,
|
|
16059
16517
|
specId,
|
|
16060
16518
|
...isDraftContext ? { draftContext: true } : {},
|
|
16061
16519
|
...surfaceResolved ? { surfaceResolved: true } : {}
|
|
@@ -16132,6 +16590,8 @@ function buildRuleContext(opts) {
|
|
|
16132
16590
|
roundTripIssues: opts.roundTripIssues,
|
|
16133
16591
|
lintAllows,
|
|
16134
16592
|
knownIssueCodes: opts.knownIssueCodes,
|
|
16593
|
+
carriedFindings,
|
|
16594
|
+
carryableIssueCodes: opts.carryableIssueCodes ?? /* @__PURE__ */ new Set(),
|
|
16135
16595
|
addIssue
|
|
16136
16596
|
};
|
|
16137
16597
|
return ctx;
|
|
@@ -16279,7 +16739,7 @@ var init_candidate = __esm({
|
|
|
16279
16739
|
|
|
16280
16740
|
// src/core/rules/gate-identity.ts
|
|
16281
16741
|
function doctrineIdentity(doctrine, builtinRules, gate) {
|
|
16282
|
-
const byKey = (items, key) => [...items].sort((a, b) => key(a)
|
|
16742
|
+
const byKey = (items, key) => [...items].sort((a, b) => compareOrdinal(key(a), key(b)));
|
|
16283
16743
|
return {
|
|
16284
16744
|
/**
|
|
16285
16745
|
* The BUILTIN rule set, by identity rather than by wairon version.
|
|
@@ -17548,6 +18008,9 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
|
|
|
17548
18008
|
...knownIssueCodes().map((rc) => rc.code),
|
|
17549
18009
|
...extensions.assertions.map((a) => a.fullCode)
|
|
17550
18010
|
]);
|
|
18011
|
+
const carryableCodes = new Set(
|
|
18012
|
+
knownIssueCodes().filter((rc) => rc.carryable).map((rc) => rc.code)
|
|
18013
|
+
);
|
|
17551
18014
|
const ctx = buildRuleContext({
|
|
17552
18015
|
system,
|
|
17553
18016
|
subsystems,
|
|
@@ -17567,6 +18030,7 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
|
|
|
17567
18030
|
codeModel,
|
|
17568
18031
|
roundTripIssues,
|
|
17569
18032
|
knownIssueCodes: knownCodes,
|
|
18033
|
+
carryableIssueCodes: carryableCodes,
|
|
17570
18034
|
issues
|
|
17571
18035
|
});
|
|
17572
18036
|
for (const rule of sequence) {
|
|
@@ -18427,10 +18891,10 @@ function findChainingParent(childRoot, ceiling) {
|
|
|
18427
18891
|
if (bound2 && !isWithin(bound2, dir)) break;
|
|
18428
18892
|
const specsDir = aiPathsAt(dir).specsDir();
|
|
18429
18893
|
if (pathExists(specsDir)) {
|
|
18430
|
-
for (const file of
|
|
18894
|
+
for (const file of listSpecFiles(specsDir)) {
|
|
18431
18895
|
let raw;
|
|
18432
18896
|
try {
|
|
18433
|
-
raw =
|
|
18897
|
+
raw = readSpecFile(file);
|
|
18434
18898
|
} catch {
|
|
18435
18899
|
continue;
|
|
18436
18900
|
}
|
|
@@ -18462,10 +18926,10 @@ function inspectChainedRoots(rootDir = getProjectRoot()) {
|
|
|
18462
18926
|
const walk2 = (projectDir, prefix, ancestors, depth) => {
|
|
18463
18927
|
const specsDir = aiPathsAt(projectDir).specsDir();
|
|
18464
18928
|
if (!pathExists(specsDir)) return;
|
|
18465
|
-
for (const file of
|
|
18929
|
+
for (const file of listSpecFiles(specsDir)) {
|
|
18466
18930
|
let raw;
|
|
18467
18931
|
try {
|
|
18468
|
-
raw =
|
|
18932
|
+
raw = readSpecFile(file);
|
|
18469
18933
|
} catch {
|
|
18470
18934
|
continue;
|
|
18471
18935
|
}
|
|
@@ -18649,7 +19113,7 @@ function computeSpecTreeSignature(dirs) {
|
|
|
18649
19113
|
parts.push(`${dir}:missing`);
|
|
18650
19114
|
continue;
|
|
18651
19115
|
}
|
|
18652
|
-
for (const f of
|
|
19116
|
+
for (const f of listSpecFiles(dir)) {
|
|
18653
19117
|
try {
|
|
18654
19118
|
const st = fs11.statSync(f);
|
|
18655
19119
|
parts.push(`${f}:${st.mtimeMs}:${st.size}`);
|
|
@@ -19109,6 +19573,7 @@ var init_specs2 = __esm({
|
|
|
19109
19573
|
init_canonical_json();
|
|
19110
19574
|
init_lockfile();
|
|
19111
19575
|
init_yaml();
|
|
19576
|
+
init_spec_files();
|
|
19112
19577
|
init_models();
|
|
19113
19578
|
init_narrative_labels();
|
|
19114
19579
|
init_diagram();
|
|
@@ -19187,7 +19652,7 @@ var init_specs2 = __esm({
|
|
|
19187
19652
|
const specsDir = projectPaths.specsDir();
|
|
19188
19653
|
this.scanVisitedSpecDirs.push(specsDir);
|
|
19189
19654
|
if (!pathExists(specsDir)) return index;
|
|
19190
|
-
const files =
|
|
19655
|
+
const files = listSpecFiles(specsDir);
|
|
19191
19656
|
const systemYaml = path16.normalize(projectPaths.specsSystem());
|
|
19192
19657
|
const localSubprojects = [];
|
|
19193
19658
|
for (const file of files) {
|
|
@@ -19196,7 +19661,7 @@ var init_specs2 = __esm({
|
|
|
19196
19661
|
let detectedType = "spec";
|
|
19197
19662
|
let rawId;
|
|
19198
19663
|
try {
|
|
19199
|
-
const raw =
|
|
19664
|
+
const raw = readSpecFile(file);
|
|
19200
19665
|
if (raw === null || typeof raw !== "object") {
|
|
19201
19666
|
this.loaderIssues.push({
|
|
19202
19667
|
severity: "error",
|
|
@@ -19679,7 +20144,7 @@ var init_specs2 = __esm({
|
|
|
19679
20144
|
const p = this.paths.specsSystem();
|
|
19680
20145
|
if (!pathExists(p)) return null;
|
|
19681
20146
|
try {
|
|
19682
|
-
const raw =
|
|
20147
|
+
const raw = readSpecFile(p);
|
|
19683
20148
|
return SystemSpecSchema.parse(raw);
|
|
19684
20149
|
} catch (e) {
|
|
19685
20150
|
this.loaderIssues.push({
|
|
@@ -19694,7 +20159,7 @@ var init_specs2 = __esm({
|
|
|
19694
20159
|
saveSystemSpec(spec) {
|
|
19695
20160
|
const p = this.paths.specsSystem();
|
|
19696
20161
|
ensureDir(path16.dirname(p));
|
|
19697
|
-
|
|
20162
|
+
writeSpecFile(p, parseOrThrow(SystemSpecSchema, spec, "system", spec.name));
|
|
19698
20163
|
invalidateSpecCache();
|
|
19699
20164
|
}
|
|
19700
20165
|
// -------------------------------------------------------------------------
|
|
@@ -19710,7 +20175,7 @@ var init_specs2 = __esm({
|
|
|
19710
20175
|
const p = this.getSubsystemPath(id);
|
|
19711
20176
|
if (!pathExists(p)) return null;
|
|
19712
20177
|
try {
|
|
19713
|
-
const raw =
|
|
20178
|
+
const raw = readSpecFile(p);
|
|
19714
20179
|
return SubsystemSpecSchema.parse(raw);
|
|
19715
20180
|
} catch (e) {
|
|
19716
20181
|
this.loaderIssues.push({
|
|
@@ -19844,7 +20309,7 @@ var init_specs2 = __esm({
|
|
|
19844
20309
|
}
|
|
19845
20310
|
}
|
|
19846
20311
|
specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
19847
|
-
|
|
20312
|
+
writeSpecFile(p, parseOrThrow(SubsystemSpecSchema, specToWrite, "subsystem", spec.id));
|
|
19848
20313
|
invalidateSpecCache();
|
|
19849
20314
|
}
|
|
19850
20315
|
deleteSubsystemSpec(id) {
|
|
@@ -19868,7 +20333,7 @@ var init_specs2 = __esm({
|
|
|
19868
20333
|
const p = this.getComponentPath(id);
|
|
19869
20334
|
if (!pathExists(p)) return null;
|
|
19870
20335
|
try {
|
|
19871
|
-
const raw =
|
|
20336
|
+
const raw = readSpecFile(p);
|
|
19872
20337
|
return ComponentSpecSchema.parse(raw);
|
|
19873
20338
|
} catch (e) {
|
|
19874
20339
|
this.loaderIssues.push({
|
|
@@ -19905,7 +20370,7 @@ var init_specs2 = __esm({
|
|
|
19905
20370
|
);
|
|
19906
20371
|
}
|
|
19907
20372
|
specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
19908
|
-
|
|
20373
|
+
writeSpecFile(p, parseOrThrow(ComponentSpecSchema, specToWrite, "component", spec.id));
|
|
19909
20374
|
invalidateSpecCache();
|
|
19910
20375
|
this.normalizeComponentLayout();
|
|
19911
20376
|
return notices;
|
|
@@ -19966,7 +20431,7 @@ var init_specs2 = __esm({
|
|
|
19966
20431
|
const p = this.getInterfacePath(id);
|
|
19967
20432
|
if (!pathExists(p)) return null;
|
|
19968
20433
|
try {
|
|
19969
|
-
const raw =
|
|
20434
|
+
const raw = readSpecFile(p);
|
|
19970
20435
|
return InterfaceSpecSchema.parse(raw);
|
|
19971
20436
|
} catch (e) {
|
|
19972
20437
|
this.loaderIssues.push({
|
|
@@ -19997,7 +20462,7 @@ var init_specs2 = __esm({
|
|
|
19997
20462
|
if (!pathExists(p)) return;
|
|
19998
20463
|
let occupantId;
|
|
19999
20464
|
try {
|
|
20000
|
-
occupantId =
|
|
20465
|
+
occupantId = readSpecFile(p)?.id;
|
|
20001
20466
|
} catch {
|
|
20002
20467
|
return;
|
|
20003
20468
|
}
|
|
@@ -20034,7 +20499,7 @@ var init_specs2 = __esm({
|
|
|
20034
20499
|
}
|
|
20035
20500
|
}
|
|
20036
20501
|
specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
20037
|
-
|
|
20502
|
+
writeSpecFile(p, parseOrThrow(InterfaceSpecSchema, specToWrite, "interface", spec.id));
|
|
20038
20503
|
invalidateSpecCache();
|
|
20039
20504
|
return notices;
|
|
20040
20505
|
}
|
|
@@ -20059,7 +20524,7 @@ var init_specs2 = __esm({
|
|
|
20059
20524
|
const p = this.getImplementationPath(id);
|
|
20060
20525
|
if (!pathExists(p)) return null;
|
|
20061
20526
|
try {
|
|
20062
|
-
const raw =
|
|
20527
|
+
const raw = readSpecFile(p);
|
|
20063
20528
|
return ImplementationSpecSchema.parse(raw);
|
|
20064
20529
|
} catch (e) {
|
|
20065
20530
|
this.loaderIssues.push({
|
|
@@ -20091,7 +20556,7 @@ var init_specs2 = __esm({
|
|
|
20091
20556
|
}
|
|
20092
20557
|
}
|
|
20093
20558
|
specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
20094
|
-
|
|
20559
|
+
writeSpecFile(p, parseOrThrow(ImplementationSpecSchema, specToWrite, "implementation", spec.id));
|
|
20095
20560
|
invalidateSpecCache();
|
|
20096
20561
|
return notices;
|
|
20097
20562
|
}
|
|
@@ -20144,7 +20609,7 @@ var init_specs2 = __esm({
|
|
|
20144
20609
|
}
|
|
20145
20610
|
}
|
|
20146
20611
|
specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
20147
|
-
|
|
20612
|
+
writeSpecFile(p, parseOrThrow(TypeSpecSchema, specToWrite, "type", spec.id));
|
|
20148
20613
|
invalidateSpecCache();
|
|
20149
20614
|
return notices;
|
|
20150
20615
|
}
|
|
@@ -20175,7 +20640,7 @@ var init_specs2 = __esm({
|
|
|
20175
20640
|
specToWrite.createdAt = existing.createdAt;
|
|
20176
20641
|
}
|
|
20177
20642
|
specToWrite.updatedAt = opts?.preserveUpdatedAt && existing?.updatedAt ? existing.updatedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
20178
|
-
|
|
20643
|
+
writeSpecFile(p, parseOrThrow(GroupSpecSchema, specToWrite, "group", spec.id));
|
|
20179
20644
|
invalidateSpecCache();
|
|
20180
20645
|
}
|
|
20181
20646
|
deleteGroupSpec(id) {
|
|
@@ -20339,7 +20804,7 @@ var init_specs2 = __esm({
|
|
|
20339
20804
|
findLegacySpecFiles() {
|
|
20340
20805
|
const specsDir = this.paths.specsDir();
|
|
20341
20806
|
if (!pathExists(specsDir)) return [];
|
|
20342
|
-
const files =
|
|
20807
|
+
const files = listSpecFiles(specsDir);
|
|
20343
20808
|
const legacy = [];
|
|
20344
20809
|
for (const f of files) {
|
|
20345
20810
|
const base = path16.basename(f);
|
|
@@ -26483,6 +26948,9 @@ __export(src_exports, {
|
|
|
26483
26948
|
BuiltinTargetSchema: () => BuiltinTargetSchema,
|
|
26484
26949
|
BundleNotFoundError: () => BundleNotFoundError,
|
|
26485
26950
|
CONTEXT_PATHS: () => CONTEXT_PATHS,
|
|
26951
|
+
CarriedDebtKindSchema: () => CarriedDebtKindSchema,
|
|
26952
|
+
CarriedDebtSchema: () => CarriedDebtSchema,
|
|
26953
|
+
CarriedFindingSchema: () => CarriedFindingSchema,
|
|
26486
26954
|
CatchClauseSchema: () => CatchClauseSchema,
|
|
26487
26955
|
ClaudeExporter: () => ClaudeExporter,
|
|
26488
26956
|
ComplexityRuleConfigSchema: () => ComplexityRuleConfigSchema,
|
|
@@ -26617,6 +27085,7 @@ __export(src_exports, {
|
|
|
26617
27085
|
buildRuleContext: () => buildRuleContext,
|
|
26618
27086
|
buildServerInstructions: () => buildServerInstructions2,
|
|
26619
27087
|
buildTreeArchive: () => buildTreeArchive2,
|
|
27088
|
+
callSitesOf: () => callSitesOf,
|
|
26620
27089
|
captureApprovedSpecs: () => captureApprovedSpecs,
|
|
26621
27090
|
checkSkillFreshness: () => checkSkillFreshness,
|
|
26622
27091
|
clearLoaderIssues: () => clearLoaderIssues,
|
|
@@ -26676,6 +27145,7 @@ __export(src_exports, {
|
|
|
26676
27145
|
extractTypeIdentifiers: () => extractTypeIdentifiers,
|
|
26677
27146
|
factsFor: () => factsFor,
|
|
26678
27147
|
fieldTypeRefs: () => fieldTypeRefs,
|
|
27148
|
+
fieldTypesOf: () => fieldTypesOf,
|
|
26679
27149
|
findChainingParent: () => findChainingParent,
|
|
26680
27150
|
findChainingSubprojectsMissingConfig: () => findChainingSubprojectsMissingConfig,
|
|
26681
27151
|
findDomain: () => findDomain,
|
|
@@ -26710,10 +27180,12 @@ __export(src_exports, {
|
|
|
26710
27180
|
hasArchitectureContext: () => hasArchitectureContext,
|
|
26711
27181
|
hasAuthoredSpecs: () => hasAuthoredSpecs,
|
|
26712
27182
|
hasContext: () => hasContext,
|
|
27183
|
+
hasFunctionBody: () => hasFunctionBody,
|
|
26713
27184
|
hasSpecTree: () => hasSpecTree,
|
|
26714
27185
|
headNoun: () => headNoun,
|
|
26715
27186
|
holdsState: () => holdsState,
|
|
26716
27187
|
implementationSourceFiles: () => implementationSourceFiles,
|
|
27188
|
+
importBindingOf: () => importBindingOf,
|
|
26717
27189
|
importSpecTree: () => importSpecTree,
|
|
26718
27190
|
importSurface: () => importSurface,
|
|
26719
27191
|
ineffectiveDeltaPaths: () => ineffectiveDeltaPaths,
|
|
@@ -26869,6 +27341,7 @@ __export(src_exports, {
|
|
|
26869
27341
|
toMarkdown: () => toMarkdown,
|
|
26870
27342
|
toOpenApi: () => toOpenApi,
|
|
26871
27343
|
toOpenApiSet: () => toOpenApiSet,
|
|
27344
|
+
typeBindingOf: () => typeBindingOf,
|
|
26872
27345
|
typeGenericParameters: () => typeGenericParameters,
|
|
26873
27346
|
typeMatchesRef: () => typeMatchesRef,
|
|
26874
27347
|
typeSourceFiles: () => typeSourceFiles,
|
|
@@ -27213,6 +27686,9 @@ function loadProjectConfig4() {
|
|
|
27213
27686
|
BuiltinTargetSchema,
|
|
27214
27687
|
BundleNotFoundError,
|
|
27215
27688
|
CONTEXT_PATHS,
|
|
27689
|
+
CarriedDebtKindSchema,
|
|
27690
|
+
CarriedDebtSchema,
|
|
27691
|
+
CarriedFindingSchema,
|
|
27216
27692
|
CatchClauseSchema,
|
|
27217
27693
|
ClaudeExporter,
|
|
27218
27694
|
ComplexityRuleConfigSchema,
|
|
@@ -27347,6 +27823,7 @@ function loadProjectConfig4() {
|
|
|
27347
27823
|
buildRuleContext,
|
|
27348
27824
|
buildServerInstructions,
|
|
27349
27825
|
buildTreeArchive,
|
|
27826
|
+
callSitesOf,
|
|
27350
27827
|
captureApprovedSpecs,
|
|
27351
27828
|
checkSkillFreshness,
|
|
27352
27829
|
clearLoaderIssues,
|
|
@@ -27406,6 +27883,7 @@ function loadProjectConfig4() {
|
|
|
27406
27883
|
extractTypeIdentifiers,
|
|
27407
27884
|
factsFor,
|
|
27408
27885
|
fieldTypeRefs,
|
|
27886
|
+
fieldTypesOf,
|
|
27409
27887
|
findChainingParent,
|
|
27410
27888
|
findChainingSubprojectsMissingConfig,
|
|
27411
27889
|
findDomain,
|
|
@@ -27440,10 +27918,12 @@ function loadProjectConfig4() {
|
|
|
27440
27918
|
hasArchitectureContext,
|
|
27441
27919
|
hasAuthoredSpecs,
|
|
27442
27920
|
hasContext,
|
|
27921
|
+
hasFunctionBody,
|
|
27443
27922
|
hasSpecTree,
|
|
27444
27923
|
headNoun,
|
|
27445
27924
|
holdsState,
|
|
27446
27925
|
implementationSourceFiles,
|
|
27926
|
+
importBindingOf,
|
|
27447
27927
|
importSpecTree,
|
|
27448
27928
|
importSurface,
|
|
27449
27929
|
ineffectiveDeltaPaths,
|
|
@@ -27599,6 +28079,7 @@ function loadProjectConfig4() {
|
|
|
27599
28079
|
toMarkdown,
|
|
27600
28080
|
toOpenApi,
|
|
27601
28081
|
toOpenApiSet,
|
|
28082
|
+
typeBindingOf,
|
|
27602
28083
|
typeGenericParameters,
|
|
27603
28084
|
typeMatchesRef,
|
|
27604
28085
|
typeSourceFiles,
|