@wairon/cli 5.1.1-dev.62 → 5.1.1-dev.64
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 +440 -155
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +377 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5767,6 +5767,23 @@ var MethodImplementationSchema = import_zod8.z.object({
|
|
|
5767
5767
|
* name — e.g. a store's `put` realized by `saveSnapshot`.
|
|
5768
5768
|
*/
|
|
5769
5769
|
symbol: import_zod8.z.string().optional(),
|
|
5770
|
+
/**
|
|
5771
|
+
* The exported binding a consumer imports to REACH this method, when the
|
|
5772
|
+
* realization is published as a value that COMPOSES it rather than exported
|
|
5773
|
+
* in its own right — a rule module publishes `callConformanceRule`, and the
|
|
5774
|
+
* method is the `check` inside it.
|
|
5775
|
+
*
|
|
5776
|
+
* `symbol` cannot say this: it names the code-level name REALIZING the
|
|
5777
|
+
* contract method — the function itself — so a file whose whole published
|
|
5778
|
+
* surface is the composing value publishes a name no contract mentions, and
|
|
5779
|
+
* the surface check reads an unpromised export where the design has a
|
|
5780
|
+
* promised one. Naming the handle here is what closes that gap.
|
|
5781
|
+
*
|
|
5782
|
+
* A declaration, never a waiver: a handle the method's own source file
|
|
5783
|
+
* does not actually export is reported (UNREALIZED_EXPORT_HANDLE) and
|
|
5784
|
+
* allows nothing, so the field can never become a free-text suppression.
|
|
5785
|
+
*/
|
|
5786
|
+
exportedVia: import_zod8.z.string().optional(),
|
|
5770
5787
|
/** Opaque pack/tool extension data (see ExtDataSchema) — preserved verbatim. */
|
|
5771
5788
|
ext: ExtDataSchema.optional()
|
|
5772
5789
|
});
|
|
@@ -11532,7 +11549,7 @@ function defaultTargetConfig(type) {
|
|
|
11532
11549
|
enabled: true
|
|
11533
11550
|
};
|
|
11534
11551
|
}
|
|
11535
|
-
var WAIRON_VERSION = "5.1.1-dev.
|
|
11552
|
+
var WAIRON_VERSION = "5.1.1-dev.64";
|
|
11536
11553
|
var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
11537
11554
|
var ARCHITECT_AGENT_ID = "agent-architect";
|
|
11538
11555
|
var ARCHITECT_TEMPLATE_ID = "architect";
|
|
@@ -12219,7 +12236,7 @@ function buildCodeIndex(model) {
|
|
|
12219
12236
|
const f = facts.get(path28);
|
|
12220
12237
|
return f?.status === "analyzed" && f.analysisGrade === "exact" ? { path: path28, facts: f } : void 0;
|
|
12221
12238
|
};
|
|
12222
|
-
const
|
|
12239
|
+
const originOf2 = (site, from) => {
|
|
12223
12240
|
const resolved = scopeOf(site, from);
|
|
12224
12241
|
if (!resolved) return NO_ORIGIN;
|
|
12225
12242
|
const { path: scope, facts: f } = resolved;
|
|
@@ -12240,7 +12257,7 @@ function buildCodeIndex(model) {
|
|
|
12240
12257
|
return NO_ORIGIN;
|
|
12241
12258
|
};
|
|
12242
12259
|
const possibleOriginsOf = (site, from) => {
|
|
12243
|
-
const proven =
|
|
12260
|
+
const proven = originOf2(site, from);
|
|
12244
12261
|
if (!site.field && !site.constructed && !site.via) return proven;
|
|
12245
12262
|
const resolved = scopeOf(site, from);
|
|
12246
12263
|
if (!resolved) return proven;
|
|
@@ -12264,7 +12281,7 @@ function buildCodeIndex(model) {
|
|
|
12264
12281
|
declarationsAt,
|
|
12265
12282
|
anchorsAt: (path28) => namesAt(anchors, pathKey(path28), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames, ...f.anchoredNames])),
|
|
12266
12283
|
findingAnchorsAt: (path28) => namesAt(findingAnchors, pathKey(path28), (f) => new Set(f.anchoredNames)),
|
|
12267
|
-
originOf,
|
|
12284
|
+
originOf: originOf2,
|
|
12268
12285
|
possibleOriginsOf
|
|
12269
12286
|
};
|
|
12270
12287
|
}
|
|
@@ -16770,6 +16787,100 @@ var typeRealizationRule = {
|
|
|
16770
16787
|
}
|
|
16771
16788
|
};
|
|
16772
16789
|
|
|
16790
|
+
// src/core/rules/conformance/type-shape.ts
|
|
16791
|
+
function originOf(shape) {
|
|
16792
|
+
if (shape.origin === "derived") return "derived shape";
|
|
16793
|
+
return shape.inherited ? "declared shape (which extends another)" : "declared shape";
|
|
16794
|
+
}
|
|
16795
|
+
function absence(optional) {
|
|
16796
|
+
return optional ? "may be absent" : "is always there";
|
|
16797
|
+
}
|
|
16798
|
+
var typeShapeRule = {
|
|
16799
|
+
name: "type-shape",
|
|
16800
|
+
description: "Code-to-contract for the DATA: a type spec's `fields` are compared, name by name, against the shape its `sourcePath` actually declares. `typeRealization` asks whether a type EXISTS in code; nothing asked whether it is the shape the spec claims, so a type spec could describe two fields of a six-field record \u2014 and call the two that are optional required \u2014 straight through a lock and a CI gate, while the ERD, the agent briefs and every implementer read it as truth. That is the worst of the code-to-spec gaps, because a wrong signature eventually breaks at a call site and a type spec that lies is only ever read by humans and agents. Two origins answer at exact grade: a DECLARED shape, whose members the file lists, and a DERIVED one, an alias followed one hop to the object literal its schema is built from, where the keys are the members. A shape that extends another is judged on what it shows and never on what it omits, since its inherited members are not in this file to count.",
|
|
16801
|
+
codes: [
|
|
16802
|
+
{
|
|
16803
|
+
code: "UNREALIZED_TYPE_FIELD",
|
|
16804
|
+
defaultSeverity: "warning",
|
|
16805
|
+
summary: "A type spec declares a field the shape at its sourcePath does not carry \u2014 the spec describes data the code does not have, and every reader of the ERD and the briefs has been told it exists",
|
|
16806
|
+
// Measured code↔spec drift, field by field, at a site the finding names
|
|
16807
|
+
// — which is why every one of them hands over `parts`.
|
|
16808
|
+
carryable: true
|
|
16809
|
+
},
|
|
16810
|
+
{
|
|
16811
|
+
code: "UNDECLARED_TYPE_FIELD",
|
|
16812
|
+
defaultSeverity: "warning",
|
|
16813
|
+
summary: "The shape at a type's sourcePath carries a field the type spec does not declare \u2014 data the design never described, so no diagram draws it and no brief hands it to an implementer",
|
|
16814
|
+
carryable: true
|
|
16815
|
+
},
|
|
16816
|
+
{
|
|
16817
|
+
code: "TYPE_FIELD_OPTIONALITY",
|
|
16818
|
+
defaultSeverity: "warning",
|
|
16819
|
+
summary: "A type spec and the shape at its sourcePath disagree about whether a field may be absent \u2014 one of them is telling a reader the data is guaranteed when it is not, or the reverse",
|
|
16820
|
+
carryable: true
|
|
16821
|
+
}
|
|
16822
|
+
],
|
|
16823
|
+
check(ctx) {
|
|
16824
|
+
const code = ctx.codeIndex();
|
|
16825
|
+
for (const type of ctx.types) {
|
|
16826
|
+
if (!type.sourcePath) continue;
|
|
16827
|
+
if (type.subsystem && ctx.isInChainedSubproject(type.subsystem)) continue;
|
|
16828
|
+
const file = pathKey(type.sourcePath);
|
|
16829
|
+
const facts = code.factsAt(file);
|
|
16830
|
+
if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
|
|
16831
|
+
const symbol = type.symbol ?? type.name;
|
|
16832
|
+
const shapes = facts.typeShapes;
|
|
16833
|
+
if (!shapes || !Object.prototype.hasOwnProperty.call(shapes, symbol)) continue;
|
|
16834
|
+
const shape = shapes[symbol];
|
|
16835
|
+
const specFields = type.fields ?? [];
|
|
16836
|
+
const specFieldNames = new Set(specFields.map((field) => field.name));
|
|
16837
|
+
const specMethodNames = new Set((type.methods ?? []).map((method) => method.name));
|
|
16838
|
+
const codeFields = new Map(shape.fields.map((field) => [field.name, field.optional]));
|
|
16839
|
+
const codeMethodNames = new Set(shape.methods);
|
|
16840
|
+
const unrealized = shape.inherited ? [] : specFields.filter((field) => !codeFields.has(field.name) && !codeMethodNames.has(field.name)).map((field) => field.name);
|
|
16841
|
+
if (unrealized.length > 0) {
|
|
16842
|
+
ctx.addIssue(
|
|
16843
|
+
"warning",
|
|
16844
|
+
"UNREALIZED_TYPE_FIELD",
|
|
16845
|
+
`Type "${type.id}" declares ${unrealized.length} field(s) the ${originOf(shape)} "${symbol}" in "${file}" does not carry \u2014 ${unrealized.map((name) => `"${name}"`).join(", ")}. The spec is describing data the code does not have, and nothing breaks at a call site to correct it: the ERD draws those fields and every brief hands them to an implementer as if they were there. Add them to the code, or drop them from the type.`,
|
|
16846
|
+
type.id,
|
|
16847
|
+
void 0,
|
|
16848
|
+
void 0,
|
|
16849
|
+
{ at: symbol, covers: unrealized }
|
|
16850
|
+
);
|
|
16851
|
+
}
|
|
16852
|
+
const undeclared = shape.inherited ? [] : shape.fields.filter((field) => !specFieldNames.has(field.name) && !specMethodNames.has(field.name)).map((field) => field.name);
|
|
16853
|
+
if (undeclared.length > 0) {
|
|
16854
|
+
ctx.addIssue(
|
|
16855
|
+
"warning",
|
|
16856
|
+
"UNDECLARED_TYPE_FIELD",
|
|
16857
|
+
`The ${originOf(shape)} "${symbol}" in "${file}" carries ${undeclared.length} field(s) type "${type.id}" does not declare \u2014 ${undeclared.map((name) => `"${name}"`).join(", ")}. Data the design never described is data no diagram draws and no brief hands to an implementer, which is how a record grows past the model of it without anyone deciding to. Model the field, or take it out of the shape.`,
|
|
16858
|
+
type.id,
|
|
16859
|
+
void 0,
|
|
16860
|
+
void 0,
|
|
16861
|
+
{ at: symbol, covers: undeclared }
|
|
16862
|
+
);
|
|
16863
|
+
}
|
|
16864
|
+
const disagreed = specFields.filter((field) => {
|
|
16865
|
+
const absentable = codeFields.get(field.name);
|
|
16866
|
+
return absentable !== void 0 && absentable !== !!field.optional;
|
|
16867
|
+
});
|
|
16868
|
+
if (disagreed.length > 0) {
|
|
16869
|
+
const told = disagreed.map((field) => `"${field.name}" (the spec says it ${absence(!!field.optional)}, the code says it ${absence(!!codeFields.get(field.name))})`).join("; ");
|
|
16870
|
+
ctx.addIssue(
|
|
16871
|
+
"warning",
|
|
16872
|
+
"TYPE_FIELD_OPTIONALITY",
|
|
16873
|
+
`Type "${type.id}" and the ${originOf(shape)} "${symbol}" in "${file}" disagree about whether ${disagreed.length} field(s) may be absent \u2014 ${told}. One of them is telling a reader the data is guaranteed when it is not, or the reverse. Absent-able is the code's word for it \u2014 a wrapper that lets the key be missing \u2014 and a wrapper that FILLS a missing key leaves the field required in the value the type finally holds, which is the shape the spec describes.`,
|
|
16874
|
+
type.id,
|
|
16875
|
+
void 0,
|
|
16876
|
+
void 0,
|
|
16877
|
+
{ at: symbol, covers: disagreed.map((field) => field.name) }
|
|
16878
|
+
);
|
|
16879
|
+
}
|
|
16880
|
+
}
|
|
16881
|
+
}
|
|
16882
|
+
};
|
|
16883
|
+
|
|
16773
16884
|
// src/core/rules/conformance/unclaimed-source.ts
|
|
16774
16885
|
var unclaimedSourceRule = {
|
|
16775
16886
|
name: "unclaimed-source",
|
|
@@ -16816,6 +16927,126 @@ var unclaimedSourceRule = {
|
|
|
16816
16927
|
}
|
|
16817
16928
|
};
|
|
16818
16929
|
|
|
16930
|
+
// src/core/rules/conformance/export-conformance.ts
|
|
16931
|
+
var NOTHING_PROMISED = /* @__PURE__ */ new Set();
|
|
16932
|
+
var exportConformanceRule = {
|
|
16933
|
+
name: "export-conformance",
|
|
16934
|
+
description: "Code-to-contract for the SURFACE: does this file publish anything the components it realizes never promised, that another component then takes? The rest of the conformance set reads contract-to-code \u2014 it asks whether the code holds what a spec claims \u2014 so a file could export whatever it liked under a component's name and nothing looked. That is one half of the same hole as a contract promising a parameter it never passes. A method's `exportedVia` names the export a consumer imports to REACH it \u2014 the value that composes it, which `symbol` cannot name because `symbol` names the function inside \u2014 and a declared handle the file does not actually export is itself a finding, so naming one can never become a free-text suppression. Both questions are asked only of a file read at exact grade, and a taker counts only when its own import specifier resolves to the file it is accused of taking from: below that nothing separates an export from a mention, or a shared word from a shared module.",
|
|
16935
|
+
codes: [
|
|
16936
|
+
{
|
|
16937
|
+
code: "UNDECLARED_EXPORT",
|
|
16938
|
+
defaultSeverity: "warning",
|
|
16939
|
+
summary: "A file realizing a component exports a name no contract of that component declares, and a file realizing a DIFFERENT component imports it \u2014 a surface reached across a boundary that the design never promised",
|
|
16940
|
+
// Measured code-to-spec drift at a site the finding names, so the
|
|
16941
|
+
// debt register may carry it — which is why it hands over `parts`.
|
|
16942
|
+
carryable: true
|
|
16943
|
+
},
|
|
16944
|
+
{
|
|
16945
|
+
code: "UNREALIZED_EXPORT_HANDLE",
|
|
16946
|
+
defaultSeverity: "warning",
|
|
16947
|
+
summary: "An implementation method declares an `exportedVia` handle its own source file does not export \u2014 the spec names a published route to the method that nobody can import"
|
|
16948
|
+
// Deliberately NOT carryable, and so it hands over no `parts`: a wrong
|
|
16949
|
+
// handle is one string to correct, and a parking space for it could
|
|
16950
|
+
// only ever be a parking space for a lie.
|
|
16951
|
+
}
|
|
16952
|
+
],
|
|
16953
|
+
check(ctx) {
|
|
16954
|
+
const code = ctx.codeIndex();
|
|
16955
|
+
const realization = ctx.realizationIndex();
|
|
16956
|
+
const promised = /* @__PURE__ */ new Map();
|
|
16957
|
+
const promise = (file, name) => {
|
|
16958
|
+
if (!name) return;
|
|
16959
|
+
const key = pathKey(file);
|
|
16960
|
+
let names = promised.get(key);
|
|
16961
|
+
if (!names) promised.set(key, names = /* @__PURE__ */ new Set());
|
|
16962
|
+
names.add(name);
|
|
16963
|
+
};
|
|
16964
|
+
for (const file of realization.paths) {
|
|
16965
|
+
for (const component of realization.componentsAt(file)) {
|
|
16966
|
+
for (const method of ctx.interfaceMethodsOf(component.id)) promise(file, method.name);
|
|
16967
|
+
for (const impl of realization.implementationsOf(component.id)) {
|
|
16968
|
+
for (const method of impl.methods ?? []) {
|
|
16969
|
+
promise(file, method.name);
|
|
16970
|
+
promise(file, method.symbol);
|
|
16971
|
+
}
|
|
16972
|
+
}
|
|
16973
|
+
}
|
|
16974
|
+
}
|
|
16975
|
+
for (const { implementation, method, sourceFile, draftContext } of ctx.implementationMethods()) {
|
|
16976
|
+
const handle = method.exportedVia;
|
|
16977
|
+
if (!handle || !sourceFile) continue;
|
|
16978
|
+
const file = pathKey(sourceFile);
|
|
16979
|
+
const facts = code.factsAt(file);
|
|
16980
|
+
if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
|
|
16981
|
+
if (facts.exportedNames.includes(handle)) {
|
|
16982
|
+
promise(file, handle);
|
|
16983
|
+
continue;
|
|
16984
|
+
}
|
|
16985
|
+
ctx.addIssue(
|
|
16986
|
+
"warning",
|
|
16987
|
+
"UNREALIZED_EXPORT_HANDLE",
|
|
16988
|
+
`Method "${method.name}" in implementation "${implementation.id}" declares exportedVia "${handle}", but "${file}" exports no such name. A handle names the export a consumer imports to REACH the method \u2014 the value that composes it, which \`symbol\` cannot name because \`symbol\` names the function inside \u2014 so one the file does not publish promises a route nobody can import. It allows nothing either: every export of this file is still read against the contracts. Name the binding the file actually exports, export it, or drop the handle.`,
|
|
16989
|
+
implementation.id,
|
|
16990
|
+
draftContext
|
|
16991
|
+
);
|
|
16992
|
+
}
|
|
16993
|
+
for (const type of ctx.types) {
|
|
16994
|
+
if (!type.sourcePath) continue;
|
|
16995
|
+
promise(type.sourcePath, type.symbol ?? type.name);
|
|
16996
|
+
for (const method of type.methods ?? []) {
|
|
16997
|
+
promise(method.sourcePath ?? type.sourcePath, method.symbol ?? method.name);
|
|
16998
|
+
}
|
|
16999
|
+
}
|
|
17000
|
+
const takenFrom = /* @__PURE__ */ new Map();
|
|
17001
|
+
for (const consumer of realization.paths) {
|
|
17002
|
+
if (!code.exactPaths.has(consumer)) continue;
|
|
17003
|
+
for (const [local, binding] of Object.entries(code.factsAt(consumer)?.importBindings ?? {})) {
|
|
17004
|
+
if (binding.namespace) continue;
|
|
17005
|
+
const target = resolveImport(consumer, binding.from, code.paths);
|
|
17006
|
+
if (!target) continue;
|
|
17007
|
+
let names = takenFrom.get(target);
|
|
17008
|
+
if (!names) takenFrom.set(target, names = /* @__PURE__ */ new Map());
|
|
17009
|
+
for (const name of [local, binding.imported]) {
|
|
17010
|
+
if (!name) continue;
|
|
17011
|
+
let takers = names.get(name);
|
|
17012
|
+
if (!takers) names.set(name, takers = /* @__PURE__ */ new Set());
|
|
17013
|
+
takers.add(consumer);
|
|
17014
|
+
}
|
|
17015
|
+
}
|
|
17016
|
+
}
|
|
17017
|
+
for (const file of realization.paths) {
|
|
17018
|
+
const facts = code.factsAt(file);
|
|
17019
|
+
if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
|
|
17020
|
+
const declared = promised.get(file) ?? NOTHING_PROMISED;
|
|
17021
|
+
const taken = takenFrom.get(file);
|
|
17022
|
+
const owners = new Set(realization.componentsAt(file).map((c) => c.id));
|
|
17023
|
+
const crossed = [];
|
|
17024
|
+
for (const name of [...new Set(facts.exportedNames)].sort()) {
|
|
17025
|
+
if (declared.has(name)) continue;
|
|
17026
|
+
const takers = taken?.get(name);
|
|
17027
|
+
if (!takers) continue;
|
|
17028
|
+
const crossing = [...takers].some(
|
|
17029
|
+
(taker) => taker !== file && !realization.componentsAt(taker).some((c) => owners.has(c.id))
|
|
17030
|
+
);
|
|
17031
|
+
if (crossing) crossed.push(name);
|
|
17032
|
+
}
|
|
17033
|
+
if (crossed.length === 0) continue;
|
|
17034
|
+
const [owner] = realization.implementationsAt(file).map((impl) => impl.id).sort();
|
|
17035
|
+
ctx.addIssue(
|
|
17036
|
+
"warning",
|
|
17037
|
+
"UNDECLARED_EXPORT",
|
|
17038
|
+
`Source file "${file}" publishes ${crossed.length} name(s) that no contract of the component(s) it realizes (${[...owners].sort().map((id) => `"${id}"`).join(", ")}) declares, and a file realizing NONE of those components imports each one FROM HERE \u2014 ${crossed.map((n) => `"${n}"`).join(", ")}. An export only this file's own components use is the module's private factoring and no concern of the design; one another component reaches for is a surface the design never promised. Declare it as a contract method, stop exporting it, or move it to the component that owns the name.`,
|
|
17039
|
+
owner,
|
|
17040
|
+
void 0,
|
|
17041
|
+
void 0,
|
|
17042
|
+
// The crossings themselves, named the way the message names them, so
|
|
17043
|
+
// a register entry written for today's set cannot carry tomorrow's.
|
|
17044
|
+
{ at: file, covers: crossed }
|
|
17045
|
+
);
|
|
17046
|
+
}
|
|
17047
|
+
}
|
|
17048
|
+
};
|
|
17049
|
+
|
|
16819
17050
|
// src/core/rules/conformance/carried-debt.ts
|
|
16820
17051
|
var carriedDebtRule = {
|
|
16821
17052
|
name: "carried-debt",
|
|
@@ -18111,11 +18342,14 @@ var SDD_RULES = [
|
|
|
18111
18342
|
integrationSimFileRule,
|
|
18112
18343
|
integrationSimWiringRule,
|
|
18113
18344
|
integrationSimCoverageRule,
|
|
18114
|
-
// The data model's own claim on code
|
|
18115
|
-
//
|
|
18116
|
-
// the
|
|
18345
|
+
// The data model's own claim on code — does the file hold the type, and is
|
|
18346
|
+
// it the SHAPE the spec claims — and then the question no spec can ask from
|
|
18347
|
+
// the spec side: which files are named by nothing at all. Last in the
|
|
18348
|
+
// family because that one reads what all the others named.
|
|
18117
18349
|
typeRealizationRule,
|
|
18350
|
+
typeShapeRule,
|
|
18118
18351
|
unclaimedSourceRule,
|
|
18352
|
+
exportConformanceRule,
|
|
18119
18353
|
couplingRule,
|
|
18120
18354
|
// Target-language fit in two questions: what a CONTRACT may name, and what
|
|
18121
18355
|
// a NARRATIVE may describe.
|
|
@@ -18430,7 +18664,10 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
18430
18664
|
const typeOnlyBindings = /* @__PURE__ */ new Map();
|
|
18431
18665
|
const fieldTypes = /* @__PURE__ */ new Map();
|
|
18432
18666
|
const localTypes = /* @__PURE__ */ new Map();
|
|
18667
|
+
const typeShapes = /* @__PURE__ */ new Map();
|
|
18433
18668
|
const mutableBindings = /* @__PURE__ */ new Set();
|
|
18669
|
+
const schemaConstants = /* @__PURE__ */ new Map();
|
|
18670
|
+
const derivedAliases = [];
|
|
18434
18671
|
const receiverKey = (site) => site.via ?? (site.field ? `this.${site.field}` : site.constructed ? `new ${site.constructed}` : "");
|
|
18435
18672
|
const siteKey = (site) => `${site.member ? "m" : "b"}:${receiverKey(site)}:${site.name}`;
|
|
18436
18673
|
const addBindingNames = (name) => {
|
|
@@ -18464,6 +18701,79 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
18464
18701
|
named.add(typeName);
|
|
18465
18702
|
localTypes.set(name.text, named);
|
|
18466
18703
|
};
|
|
18704
|
+
const recordDeclaredShape = (name, members, inherited) => {
|
|
18705
|
+
const fields = [];
|
|
18706
|
+
const methods = [];
|
|
18707
|
+
for (const member of members) {
|
|
18708
|
+
const memberName = member.name ? propertyNameText(member.name) : void 0;
|
|
18709
|
+
if (!memberName) continue;
|
|
18710
|
+
if (ts.isMethodSignature(member) || ts.isMethodDeclaration(member) || ts.isGetAccessorDeclaration(member)) {
|
|
18711
|
+
methods.push(memberName);
|
|
18712
|
+
} else if (ts.isPropertySignature(member) || ts.isPropertyDeclaration(member)) {
|
|
18713
|
+
fields.push({ name: memberName, optional: !!member.questionToken });
|
|
18714
|
+
}
|
|
18715
|
+
}
|
|
18716
|
+
const shape = { origin: "declared", fields, methods };
|
|
18717
|
+
if (inherited) shape.inherited = true;
|
|
18718
|
+
typeShapes.set(name, shape);
|
|
18719
|
+
};
|
|
18720
|
+
const inferredSchemaConstant = (type) => {
|
|
18721
|
+
if (!ts.isTypeReferenceNode(type)) return void 0;
|
|
18722
|
+
const reference = ts.isIdentifier(type.typeName) ? type.typeName.text : ts.isQualifiedName(type.typeName) ? type.typeName.right.text : void 0;
|
|
18723
|
+
if (reference !== "infer") return void 0;
|
|
18724
|
+
const [argument] = type.typeArguments ?? [];
|
|
18725
|
+
if (!argument || !ts.isTypeQueryNode(argument) || !ts.isIdentifier(argument.exprName)) return void 0;
|
|
18726
|
+
return argument.exprName.text;
|
|
18727
|
+
};
|
|
18728
|
+
const baseObjectLiteral = (expression) => {
|
|
18729
|
+
let node = expression;
|
|
18730
|
+
const walked = /* @__PURE__ */ new Set();
|
|
18731
|
+
while (node && !walked.has(node)) {
|
|
18732
|
+
walked.add(node);
|
|
18733
|
+
if (ts.isCallExpression(node)) {
|
|
18734
|
+
const callee = node.expression;
|
|
18735
|
+
if (ts.isPropertyAccessExpression(callee) && callee.name.text === "object") {
|
|
18736
|
+
const literal = node.arguments.find((argument) => ts.isObjectLiteralExpression(argument));
|
|
18737
|
+
if (literal) return literal;
|
|
18738
|
+
}
|
|
18739
|
+
node = callee;
|
|
18740
|
+
} else if (ts.isPropertyAccessExpression(node)) {
|
|
18741
|
+
node = node.expression;
|
|
18742
|
+
} else break;
|
|
18743
|
+
}
|
|
18744
|
+
return void 0;
|
|
18745
|
+
};
|
|
18746
|
+
const chainedCombinators = (expression) => {
|
|
18747
|
+
const applied = /* @__PURE__ */ new Set();
|
|
18748
|
+
let node = expression;
|
|
18749
|
+
const walked = /* @__PURE__ */ new Set();
|
|
18750
|
+
while (node && !walked.has(node)) {
|
|
18751
|
+
walked.add(node);
|
|
18752
|
+
if (ts.isCallExpression(node)) {
|
|
18753
|
+
const callee = node.expression;
|
|
18754
|
+
if (ts.isPropertyAccessExpression(callee)) applied.add(callee.name.text);
|
|
18755
|
+
node = callee;
|
|
18756
|
+
} else if (ts.isPropertyAccessExpression(node)) {
|
|
18757
|
+
node = node.expression;
|
|
18758
|
+
} else break;
|
|
18759
|
+
}
|
|
18760
|
+
return applied;
|
|
18761
|
+
};
|
|
18762
|
+
const schemaMembers = (literal) => {
|
|
18763
|
+
const fields = [];
|
|
18764
|
+
for (const property of literal.properties) {
|
|
18765
|
+
const memberName = property.name ? propertyNameText(property.name) : void 0;
|
|
18766
|
+
if (!memberName) continue;
|
|
18767
|
+
if (ts.isShorthandPropertyAssignment(property)) {
|
|
18768
|
+
fields.push({ name: memberName, optional: false });
|
|
18769
|
+
} else if (ts.isPropertyAssignment(property)) {
|
|
18770
|
+
const applied = chainedCombinators(property.initializer);
|
|
18771
|
+
const optional = (applied.has("optional") || applied.has("nullish")) && !applied.has("default");
|
|
18772
|
+
fields.push({ name: memberName, optional });
|
|
18773
|
+
}
|
|
18774
|
+
}
|
|
18775
|
+
return fields;
|
|
18776
|
+
};
|
|
18467
18777
|
const hasExportModifier = (node) => {
|
|
18468
18778
|
const mods = node.modifiers;
|
|
18469
18779
|
return !!mods?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
@@ -18532,6 +18842,23 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
18532
18842
|
recordFieldType(node.name, node.type);
|
|
18533
18843
|
}
|
|
18534
18844
|
if (ts.isParameter(node) || ts.isVariableDeclaration(node)) recordLocalType(node.name, node.type);
|
|
18845
|
+
if (ts.isInterfaceDeclaration(node)) {
|
|
18846
|
+
recordDeclaredShape(node.name.text, node.members, (node.heritageClauses?.length ?? 0) > 0);
|
|
18847
|
+
} else if (ts.isClassDeclaration(node) && node.name) {
|
|
18848
|
+
const extendsOther = (node.heritageClauses ?? []).some(
|
|
18849
|
+
(clause) => clause.token === ts.SyntaxKind.ExtendsKeyword
|
|
18850
|
+
);
|
|
18851
|
+
recordDeclaredShape(node.name.text, node.members, extendsOther);
|
|
18852
|
+
} else if (ts.isTypeAliasDeclaration(node)) {
|
|
18853
|
+
if (ts.isTypeLiteralNode(node.type)) recordDeclaredShape(node.name.text, node.type.members, false);
|
|
18854
|
+
else {
|
|
18855
|
+
const constant = inferredSchemaConstant(node.type);
|
|
18856
|
+
if (constant) derivedAliases.push({ name: node.name.text, constant });
|
|
18857
|
+
}
|
|
18858
|
+
}
|
|
18859
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && !schemaConstants.has(node.name.text)) {
|
|
18860
|
+
schemaConstants.set(node.name.text, node.initializer);
|
|
18861
|
+
}
|
|
18535
18862
|
if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isModuleDeclaration(node)) {
|
|
18536
18863
|
const name = node.name && ts.isIdentifier(node.name) ? node.name.text : void 0;
|
|
18537
18864
|
if (name) {
|
|
@@ -18607,8 +18934,16 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
18607
18934
|
ts.forEachChild(node, visit);
|
|
18608
18935
|
};
|
|
18609
18936
|
visit(sf);
|
|
18937
|
+
for (const alias of derivedAliases) {
|
|
18938
|
+
if (typeShapes.has(alias.name)) continue;
|
|
18939
|
+
const initializer = schemaConstants.get(alias.constant);
|
|
18940
|
+
if (!initializer) continue;
|
|
18941
|
+
const literal = baseObjectLiteral(initializer);
|
|
18942
|
+
if (!literal) continue;
|
|
18943
|
+
typeShapes.set(alias.name, { origin: "derived", fields: schemaMembers(literal), methods: [] });
|
|
18944
|
+
}
|
|
18610
18945
|
const reexportOnly = sf.statements.length > 0 && sf.statements.every((st) => ts.isExportDeclaration(st) && !!st.moduleSpecifier);
|
|
18611
|
-
return { declared, anchors, exported, imports, reexports, starExports, namedReexports, complexity, calls, importBindings, typeOnlyBindings, fieldTypes, localTypes, mutableBindings, reexportOnly };
|
|
18946
|
+
return { declared, anchors, exported, imports, reexports, starExports, namedReexports, complexity, calls, importBindings, typeOnlyBindings, fieldTypes, localTypes, typeShapes, mutableBindings, reexportOnly };
|
|
18612
18947
|
}
|
|
18613
18948
|
function resolveRelativeModule(fromFile, specifier) {
|
|
18614
18949
|
if (!specifier.startsWith(".")) return null;
|
|
@@ -18790,6 +19125,7 @@ function buildCodeModel(implementations, types, projectRoot, sourceRoots = [], e
|
|
|
18790
19125
|
typeOnlyBindings: Object.fromEntries(facts.typeOnlyBindings),
|
|
18791
19126
|
fieldTypes: Object.fromEntries([...facts.fieldTypes].map(([k, v]) => [k, [...v]])),
|
|
18792
19127
|
localTypes: Object.fromEntries([...facts.localTypes].map(([k, v]) => [k, [...v]])),
|
|
19128
|
+
typeShapes: Object.fromEntries(facts.typeShapes),
|
|
18793
19129
|
topLevelMutableBindings: [...facts.mutableBindings],
|
|
18794
19130
|
reexportOnly: facts.reexportOnly
|
|
18795
19131
|
};
|
|
@@ -25788,21 +26124,34 @@ function syncContextFiles() {
|
|
|
25788
26124
|
}
|
|
25789
26125
|
|
|
25790
26126
|
// src/core/status.ts
|
|
25791
|
-
|
|
26127
|
+
var asWritten = (text) => text;
|
|
26128
|
+
function fillDecor(decor) {
|
|
26129
|
+
return {
|
|
26130
|
+
structure: decor?.structure ?? asWritten,
|
|
26131
|
+
emphasis: decor?.emphasis ?? asWritten,
|
|
26132
|
+
layer: decor?.layer ?? ((_kind, text) => text),
|
|
26133
|
+
score: decor?.score ?? ((_pct, text) => text),
|
|
26134
|
+
draft: decor?.draft ?? asWritten,
|
|
26135
|
+
present: decor?.present ?? asWritten,
|
|
26136
|
+
missing: decor?.missing ?? asWritten
|
|
26137
|
+
};
|
|
26138
|
+
}
|
|
26139
|
+
function getStatusReport(options = {}, decor) {
|
|
26140
|
+
const mark = fillDecor(decor);
|
|
25792
26141
|
scanAllSpecs({ recursive: options.recursive ?? true });
|
|
25793
26142
|
const system = loadSystemSpec();
|
|
25794
26143
|
const loaderErrors = getLoaderIssues();
|
|
25795
26144
|
if (loaderErrors.length > 0) {
|
|
25796
26145
|
let errText = "Failed to parse specification files:\n";
|
|
25797
26146
|
for (const issue2 of loaderErrors) {
|
|
25798
|
-
const prefix = issue2.specId ? `[${issue2.specId}] ` : "";
|
|
26147
|
+
const prefix = issue2.specId ? mark.structure(`[${issue2.specId}] `) : "";
|
|
25799
26148
|
errText += `${prefix}[${issue2.code}] ${issue2.message}
|
|
25800
26149
|
`;
|
|
25801
26150
|
}
|
|
25802
|
-
return errText;
|
|
26151
|
+
return { text: errText, failed: true };
|
|
25803
26152
|
}
|
|
25804
26153
|
if (!system) {
|
|
25805
|
-
return "L0 System specification (system.yaml) is missing.";
|
|
26154
|
+
return { text: "L0 System specification (system.yaml) is missing.", failed: true };
|
|
25806
26155
|
}
|
|
25807
26156
|
let subsystems = loadSubsystemSpecs();
|
|
25808
26157
|
let components = loadComponentSpecs();
|
|
@@ -25856,7 +26205,7 @@ function getStatusReport(options = {}) {
|
|
|
25856
26205
|
};
|
|
25857
26206
|
const totalSubsystemsScore = subsystems.reduce((acc, s) => acc + getSubsystemScore(s.id), 0);
|
|
25858
26207
|
const systemScore = subsystems.length > 0 ? Math.round(totalSubsystemsScore / subsystems.length) : 0;
|
|
25859
|
-
output +=
|
|
26208
|
+
output += `${mark.layer("system", "\u25CF System:")} ${mark.emphasis(system.name)} ${mark.score(systemScore, `(${systemScore}% Complete)`)}
|
|
25860
26209
|
`;
|
|
25861
26210
|
for (let i = 0; i < subsystems.length; i++) {
|
|
25862
26211
|
const sub = subsystems[i];
|
|
@@ -25864,8 +26213,8 @@ function getStatusReport(options = {}) {
|
|
|
25864
26213
|
const subPrefix = isLastSub ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
25865
26214
|
const subIndent = isLastSub ? " " : "\u2502 ";
|
|
25866
26215
|
const subScore = getSubsystemScore(sub.id);
|
|
25867
|
-
const subStatusStr = sub.status !== "complete" ? ` [${sub.status}]` : "";
|
|
25868
|
-
output += `${subPrefix}[Subsystem] ${sub.id}${subStatusStr} (${subScore}%)
|
|
26216
|
+
const subStatusStr = sub.status !== "complete" ? mark.draft(` [${sub.status}]`) : "";
|
|
26217
|
+
output += `${mark.structure(subPrefix)}${mark.layer("subsystem", `[Subsystem] ${sub.id}`)}${subStatusStr} ${mark.score(subScore, `(${subScore}%)`)}
|
|
25869
26218
|
`;
|
|
25870
26219
|
const subComps = components.filter((c) => c.subsystem === sub.id);
|
|
25871
26220
|
for (let j = 0; j < subComps.length; j++) {
|
|
@@ -25874,32 +26223,32 @@ function getStatusReport(options = {}) {
|
|
|
25874
26223
|
const compPrefix = isLastComp ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
25875
26224
|
const compIndent = isLastComp ? " " : "\u2502 ";
|
|
25876
26225
|
const compScore = componentScores.get(comp.id) ?? 0;
|
|
25877
|
-
const compStatusStr = comp.status !== "complete" ? ` [${comp.status}]` : "";
|
|
25878
|
-
output += `${subIndent}${
|
|
26226
|
+
const compStatusStr = comp.status !== "complete" ? mark.draft(` [${comp.status}]`) : "";
|
|
26227
|
+
output += `${mark.structure(subIndent + compPrefix)}${mark.layer("component", `[Component: ${comp.componentType}] ${comp.id}`)}${compStatusStr} ${mark.score(compScore, `(${compScore}%)`)}
|
|
25879
26228
|
`;
|
|
25880
26229
|
const intf = interfaces.find((inf) => inf.component === comp.id);
|
|
25881
26230
|
const impl = implementations.find((im) => intf && im.contract === intf.id);
|
|
25882
26231
|
const intfPrefix = intf && impl ? "\u251C\u2500\u2500 " : "\u2514\u2500\u2500 ";
|
|
25883
26232
|
if (intf) {
|
|
25884
|
-
const intfStatusStr = intf.status !== "complete" ? ` [${intf.status}]` : "";
|
|
25885
|
-
output += `${subIndent
|
|
26233
|
+
const intfStatusStr = intf.status !== "complete" ? mark.draft(` [${intf.status}]`) : "";
|
|
26234
|
+
output += `${mark.structure(subIndent + compIndent + intfPrefix)}${mark.layer("interface", `Interface: ${intf.id}`)}${intfStatusStr} (${intf.methods.length} methods)
|
|
25886
26235
|
`;
|
|
25887
26236
|
} else {
|
|
25888
|
-
output += `${subIndent
|
|
26237
|
+
output += `${mark.structure(subIndent + compIndent + intfPrefix)}${mark.missing("Interface: Missing (-30%)")}
|
|
25889
26238
|
`;
|
|
25890
26239
|
}
|
|
25891
26240
|
if (impl) {
|
|
25892
|
-
const implStatusStr = impl.status !== "complete" ? ` [${impl.status}]` : "";
|
|
26241
|
+
const implStatusStr = impl.status !== "complete" ? mark.draft(` [${impl.status}]`) : "";
|
|
25893
26242
|
const methodsWithOwnPath = impl.methods.filter((m) => m.sourcePath);
|
|
25894
26243
|
let pathStr;
|
|
25895
26244
|
if (impl.sourcePath) {
|
|
25896
|
-
pathStr = pathExists(fromProjectRoot(impl.sourcePath)) ? ` -> ${impl.sourcePath}` : ` -> ${impl.sourcePath} (File Missing!)
|
|
26245
|
+
pathStr = pathExists(fromProjectRoot(impl.sourcePath)) ? mark.present(` -> ${impl.sourcePath}`) : mark.missing(` -> ${impl.sourcePath} (File Missing!)`);
|
|
25897
26246
|
} else if (methodsWithOwnPath.length > 0) {
|
|
25898
26247
|
pathStr = "";
|
|
25899
26248
|
} else {
|
|
25900
|
-
pathStr = " (No source path)";
|
|
26249
|
+
pathStr = mark.structure(" (No source path)");
|
|
25901
26250
|
}
|
|
25902
|
-
output += `${subIndent
|
|
26251
|
+
output += `${mark.structure(subIndent + compIndent + "\u2514\u2500\u2500 ")}${mark.layer("implementation", `Implementation: ${impl.id}`)}${implStatusStr}${pathStr}
|
|
25903
26252
|
`;
|
|
25904
26253
|
const methodIndent = `${subIndent}${compIndent} `;
|
|
25905
26254
|
for (let k = 0; k < methodsWithOwnPath.length; k++) {
|
|
@@ -25907,17 +26256,17 @@ function getStatusReport(options = {}) {
|
|
|
25907
26256
|
const isLastMethod = k === methodsWithOwnPath.length - 1;
|
|
25908
26257
|
const methodPrefix = isLastMethod ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
25909
26258
|
const methodPath = method.sourcePath;
|
|
25910
|
-
const
|
|
25911
|
-
output += `${methodIndent
|
|
26259
|
+
const methodLine = pathExists(fromProjectRoot(methodPath)) ? mark.present(`method ${method.name} -> ${methodPath}`) : mark.missing(`method ${method.name} -> ${methodPath} (File Missing!)`);
|
|
26260
|
+
output += `${mark.structure(methodIndent + methodPrefix)}${methodLine}
|
|
25912
26261
|
`;
|
|
25913
26262
|
}
|
|
25914
26263
|
} else {
|
|
25915
|
-
output += `${subIndent
|
|
26264
|
+
output += `${mark.structure(subIndent + compIndent + "\u2514\u2500\u2500 ")}${mark.missing("Implementation: Missing (-30%)")}
|
|
25916
26265
|
`;
|
|
25917
26266
|
}
|
|
25918
26267
|
}
|
|
25919
26268
|
}
|
|
25920
|
-
return output;
|
|
26269
|
+
return { text: output, failed: false };
|
|
25921
26270
|
}
|
|
25922
26271
|
|
|
25923
26272
|
// src/core/index.ts
|