@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/cli/index.js
CHANGED
|
@@ -65,7 +65,7 @@ var init_defaults = __esm({
|
|
|
65
65
|
copilot: ".github/prompts",
|
|
66
66
|
codex: ".codex/agents"
|
|
67
67
|
};
|
|
68
|
-
WAIRON_VERSION = "5.1.1-dev.
|
|
68
|
+
WAIRON_VERSION = "5.1.1-dev.64";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
SUPPORTED_ALIASES = ["wai"];
|
|
71
71
|
SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -2376,6 +2376,23 @@ var init_specs = __esm({
|
|
|
2376
2376
|
* name — e.g. a store's `put` realized by `saveSnapshot`.
|
|
2377
2377
|
*/
|
|
2378
2378
|
symbol: import_zod8.z.string().optional(),
|
|
2379
|
+
/**
|
|
2380
|
+
* The exported binding a consumer imports to REACH this method, when the
|
|
2381
|
+
* realization is published as a value that COMPOSES it rather than exported
|
|
2382
|
+
* in its own right — a rule module publishes `callConformanceRule`, and the
|
|
2383
|
+
* method is the `check` inside it.
|
|
2384
|
+
*
|
|
2385
|
+
* `symbol` cannot say this: it names the code-level name REALIZING the
|
|
2386
|
+
* contract method — the function itself — so a file whose whole published
|
|
2387
|
+
* surface is the composing value publishes a name no contract mentions, and
|
|
2388
|
+
* the surface check reads an unpromised export where the design has a
|
|
2389
|
+
* promised one. Naming the handle here is what closes that gap.
|
|
2390
|
+
*
|
|
2391
|
+
* A declaration, never a waiver: a handle the method's own source file
|
|
2392
|
+
* does not actually export is reported (UNREALIZED_EXPORT_HANDLE) and
|
|
2393
|
+
* allows nothing, so the field can never become a free-text suppression.
|
|
2394
|
+
*/
|
|
2395
|
+
exportedVia: import_zod8.z.string().optional(),
|
|
2379
2396
|
/** Opaque pack/tool extension data (see ExtDataSchema) — preserved verbatim. */
|
|
2380
2397
|
ext: ExtDataSchema.optional()
|
|
2381
2398
|
});
|
|
@@ -8861,7 +8878,7 @@ function buildCodeIndex(model) {
|
|
|
8861
8878
|
const f = facts.get(path71);
|
|
8862
8879
|
return f?.status === "analyzed" && f.analysisGrade === "exact" ? { path: path71, facts: f } : void 0;
|
|
8863
8880
|
};
|
|
8864
|
-
const
|
|
8881
|
+
const originOf2 = (site, from) => {
|
|
8865
8882
|
const resolved = scopeOf2(site, from);
|
|
8866
8883
|
if (!resolved) return NO_ORIGIN;
|
|
8867
8884
|
const { path: scope, facts: f } = resolved;
|
|
@@ -8882,7 +8899,7 @@ function buildCodeIndex(model) {
|
|
|
8882
8899
|
return NO_ORIGIN;
|
|
8883
8900
|
};
|
|
8884
8901
|
const possibleOriginsOf = (site, from) => {
|
|
8885
|
-
const proven =
|
|
8902
|
+
const proven = originOf2(site, from);
|
|
8886
8903
|
if (!site.field && !site.constructed && !site.via) return proven;
|
|
8887
8904
|
const resolved = scopeOf2(site, from);
|
|
8888
8905
|
if (!resolved) return proven;
|
|
@@ -8906,7 +8923,7 @@ function buildCodeIndex(model) {
|
|
|
8906
8923
|
declarationsAt,
|
|
8907
8924
|
anchorsAt: (path71) => namesAt(anchors, pathKey(path71), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames, ...f.anchoredNames])),
|
|
8908
8925
|
findingAnchorsAt: (path71) => namesAt(findingAnchors, pathKey(path71), (f) => new Set(f.anchoredNames)),
|
|
8909
|
-
originOf,
|
|
8926
|
+
originOf: originOf2,
|
|
8910
8927
|
possibleOriginsOf
|
|
8911
8928
|
};
|
|
8912
8929
|
}
|
|
@@ -13962,6 +13979,107 @@ var init_type_realization = __esm({
|
|
|
13962
13979
|
}
|
|
13963
13980
|
});
|
|
13964
13981
|
|
|
13982
|
+
// src/core/rules/conformance/type-shape.ts
|
|
13983
|
+
function originOf(shape) {
|
|
13984
|
+
if (shape.origin === "derived") return "derived shape";
|
|
13985
|
+
return shape.inherited ? "declared shape (which extends another)" : "declared shape";
|
|
13986
|
+
}
|
|
13987
|
+
function absence(optional) {
|
|
13988
|
+
return optional ? "may be absent" : "is always there";
|
|
13989
|
+
}
|
|
13990
|
+
var typeShapeRule;
|
|
13991
|
+
var init_type_shape = __esm({
|
|
13992
|
+
"src/core/rules/conformance/type-shape.ts"() {
|
|
13993
|
+
"use strict";
|
|
13994
|
+
init_models();
|
|
13995
|
+
typeShapeRule = {
|
|
13996
|
+
name: "type-shape",
|
|
13997
|
+
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.",
|
|
13998
|
+
codes: [
|
|
13999
|
+
{
|
|
14000
|
+
code: "UNREALIZED_TYPE_FIELD",
|
|
14001
|
+
defaultSeverity: "warning",
|
|
14002
|
+
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",
|
|
14003
|
+
// Measured code↔spec drift, field by field, at a site the finding names
|
|
14004
|
+
// — which is why every one of them hands over `parts`.
|
|
14005
|
+
carryable: true
|
|
14006
|
+
},
|
|
14007
|
+
{
|
|
14008
|
+
code: "UNDECLARED_TYPE_FIELD",
|
|
14009
|
+
defaultSeverity: "warning",
|
|
14010
|
+
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",
|
|
14011
|
+
carryable: true
|
|
14012
|
+
},
|
|
14013
|
+
{
|
|
14014
|
+
code: "TYPE_FIELD_OPTIONALITY",
|
|
14015
|
+
defaultSeverity: "warning",
|
|
14016
|
+
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",
|
|
14017
|
+
carryable: true
|
|
14018
|
+
}
|
|
14019
|
+
],
|
|
14020
|
+
check(ctx) {
|
|
14021
|
+
const code = ctx.codeIndex();
|
|
14022
|
+
for (const type of ctx.types) {
|
|
14023
|
+
if (!type.sourcePath) continue;
|
|
14024
|
+
if (type.subsystem && ctx.isInChainedSubproject(type.subsystem)) continue;
|
|
14025
|
+
const file = pathKey(type.sourcePath);
|
|
14026
|
+
const facts = code.factsAt(file);
|
|
14027
|
+
if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
|
|
14028
|
+
const symbol = type.symbol ?? type.name;
|
|
14029
|
+
const shapes = facts.typeShapes;
|
|
14030
|
+
if (!shapes || !Object.prototype.hasOwnProperty.call(shapes, symbol)) continue;
|
|
14031
|
+
const shape = shapes[symbol];
|
|
14032
|
+
const specFields = type.fields ?? [];
|
|
14033
|
+
const specFieldNames = new Set(specFields.map((field) => field.name));
|
|
14034
|
+
const specMethodNames = new Set((type.methods ?? []).map((method2) => method2.name));
|
|
14035
|
+
const codeFields = new Map(shape.fields.map((field) => [field.name, field.optional]));
|
|
14036
|
+
const codeMethodNames = new Set(shape.methods);
|
|
14037
|
+
const unrealized = shape.inherited ? [] : specFields.filter((field) => !codeFields.has(field.name) && !codeMethodNames.has(field.name)).map((field) => field.name);
|
|
14038
|
+
if (unrealized.length > 0) {
|
|
14039
|
+
ctx.addIssue(
|
|
14040
|
+
"warning",
|
|
14041
|
+
"UNREALIZED_TYPE_FIELD",
|
|
14042
|
+
`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.`,
|
|
14043
|
+
type.id,
|
|
14044
|
+
void 0,
|
|
14045
|
+
void 0,
|
|
14046
|
+
{ at: symbol, covers: unrealized }
|
|
14047
|
+
);
|
|
14048
|
+
}
|
|
14049
|
+
const undeclared = shape.inherited ? [] : shape.fields.filter((field) => !specFieldNames.has(field.name) && !specMethodNames.has(field.name)).map((field) => field.name);
|
|
14050
|
+
if (undeclared.length > 0) {
|
|
14051
|
+
ctx.addIssue(
|
|
14052
|
+
"warning",
|
|
14053
|
+
"UNDECLARED_TYPE_FIELD",
|
|
14054
|
+
`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.`,
|
|
14055
|
+
type.id,
|
|
14056
|
+
void 0,
|
|
14057
|
+
void 0,
|
|
14058
|
+
{ at: symbol, covers: undeclared }
|
|
14059
|
+
);
|
|
14060
|
+
}
|
|
14061
|
+
const disagreed = specFields.filter((field) => {
|
|
14062
|
+
const absentable = codeFields.get(field.name);
|
|
14063
|
+
return absentable !== void 0 && absentable !== !!field.optional;
|
|
14064
|
+
});
|
|
14065
|
+
if (disagreed.length > 0) {
|
|
14066
|
+
const told = disagreed.map((field) => `"${field.name}" (the spec says it ${absence(!!field.optional)}, the code says it ${absence(!!codeFields.get(field.name))})`).join("; ");
|
|
14067
|
+
ctx.addIssue(
|
|
14068
|
+
"warning",
|
|
14069
|
+
"TYPE_FIELD_OPTIONALITY",
|
|
14070
|
+
`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.`,
|
|
14071
|
+
type.id,
|
|
14072
|
+
void 0,
|
|
14073
|
+
void 0,
|
|
14074
|
+
{ at: symbol, covers: disagreed.map((field) => field.name) }
|
|
14075
|
+
);
|
|
14076
|
+
}
|
|
14077
|
+
}
|
|
14078
|
+
}
|
|
14079
|
+
};
|
|
14080
|
+
}
|
|
14081
|
+
});
|
|
14082
|
+
|
|
13965
14083
|
// src/core/rules/conformance/unclaimed-source.ts
|
|
13966
14084
|
var unclaimedSourceRule;
|
|
13967
14085
|
var init_unclaimed_source = __esm({
|
|
@@ -14015,6 +14133,133 @@ var init_unclaimed_source = __esm({
|
|
|
14015
14133
|
}
|
|
14016
14134
|
});
|
|
14017
14135
|
|
|
14136
|
+
// src/core/rules/conformance/export-conformance.ts
|
|
14137
|
+
var NOTHING_PROMISED, exportConformanceRule;
|
|
14138
|
+
var init_export_conformance = __esm({
|
|
14139
|
+
"src/core/rules/conformance/export-conformance.ts"() {
|
|
14140
|
+
"use strict";
|
|
14141
|
+
init_models();
|
|
14142
|
+
NOTHING_PROMISED = /* @__PURE__ */ new Set();
|
|
14143
|
+
exportConformanceRule = {
|
|
14144
|
+
name: "export-conformance",
|
|
14145
|
+
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.",
|
|
14146
|
+
codes: [
|
|
14147
|
+
{
|
|
14148
|
+
code: "UNDECLARED_EXPORT",
|
|
14149
|
+
defaultSeverity: "warning",
|
|
14150
|
+
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",
|
|
14151
|
+
// Measured code-to-spec drift at a site the finding names, so the
|
|
14152
|
+
// debt register may carry it — which is why it hands over `parts`.
|
|
14153
|
+
carryable: true
|
|
14154
|
+
},
|
|
14155
|
+
{
|
|
14156
|
+
code: "UNREALIZED_EXPORT_HANDLE",
|
|
14157
|
+
defaultSeverity: "warning",
|
|
14158
|
+
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"
|
|
14159
|
+
// Deliberately NOT carryable, and so it hands over no `parts`: a wrong
|
|
14160
|
+
// handle is one string to correct, and a parking space for it could
|
|
14161
|
+
// only ever be a parking space for a lie.
|
|
14162
|
+
}
|
|
14163
|
+
],
|
|
14164
|
+
check(ctx) {
|
|
14165
|
+
const code = ctx.codeIndex();
|
|
14166
|
+
const realization = ctx.realizationIndex();
|
|
14167
|
+
const promised = /* @__PURE__ */ new Map();
|
|
14168
|
+
const promise = (file, name) => {
|
|
14169
|
+
if (!name) return;
|
|
14170
|
+
const key = pathKey(file);
|
|
14171
|
+
let names = promised.get(key);
|
|
14172
|
+
if (!names) promised.set(key, names = /* @__PURE__ */ new Set());
|
|
14173
|
+
names.add(name);
|
|
14174
|
+
};
|
|
14175
|
+
for (const file of realization.paths) {
|
|
14176
|
+
for (const component of realization.componentsAt(file)) {
|
|
14177
|
+
for (const method2 of ctx.interfaceMethodsOf(component.id)) promise(file, method2.name);
|
|
14178
|
+
for (const impl of realization.implementationsOf(component.id)) {
|
|
14179
|
+
for (const method2 of impl.methods ?? []) {
|
|
14180
|
+
promise(file, method2.name);
|
|
14181
|
+
promise(file, method2.symbol);
|
|
14182
|
+
}
|
|
14183
|
+
}
|
|
14184
|
+
}
|
|
14185
|
+
}
|
|
14186
|
+
for (const { implementation, method: method2, sourceFile, draftContext } of ctx.implementationMethods()) {
|
|
14187
|
+
const handle = method2.exportedVia;
|
|
14188
|
+
if (!handle || !sourceFile) continue;
|
|
14189
|
+
const file = pathKey(sourceFile);
|
|
14190
|
+
const facts = code.factsAt(file);
|
|
14191
|
+
if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
|
|
14192
|
+
if (facts.exportedNames.includes(handle)) {
|
|
14193
|
+
promise(file, handle);
|
|
14194
|
+
continue;
|
|
14195
|
+
}
|
|
14196
|
+
ctx.addIssue(
|
|
14197
|
+
"warning",
|
|
14198
|
+
"UNREALIZED_EXPORT_HANDLE",
|
|
14199
|
+
`Method "${method2.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.`,
|
|
14200
|
+
implementation.id,
|
|
14201
|
+
draftContext
|
|
14202
|
+
);
|
|
14203
|
+
}
|
|
14204
|
+
for (const type of ctx.types) {
|
|
14205
|
+
if (!type.sourcePath) continue;
|
|
14206
|
+
promise(type.sourcePath, type.symbol ?? type.name);
|
|
14207
|
+
for (const method2 of type.methods ?? []) {
|
|
14208
|
+
promise(method2.sourcePath ?? type.sourcePath, method2.symbol ?? method2.name);
|
|
14209
|
+
}
|
|
14210
|
+
}
|
|
14211
|
+
const takenFrom = /* @__PURE__ */ new Map();
|
|
14212
|
+
for (const consumer of realization.paths) {
|
|
14213
|
+
if (!code.exactPaths.has(consumer)) continue;
|
|
14214
|
+
for (const [local, binding] of Object.entries(code.factsAt(consumer)?.importBindings ?? {})) {
|
|
14215
|
+
if (binding.namespace) continue;
|
|
14216
|
+
const target = resolveImport(consumer, binding.from, code.paths);
|
|
14217
|
+
if (!target) continue;
|
|
14218
|
+
let names = takenFrom.get(target);
|
|
14219
|
+
if (!names) takenFrom.set(target, names = /* @__PURE__ */ new Map());
|
|
14220
|
+
for (const name of [local, binding.imported]) {
|
|
14221
|
+
if (!name) continue;
|
|
14222
|
+
let takers = names.get(name);
|
|
14223
|
+
if (!takers) names.set(name, takers = /* @__PURE__ */ new Set());
|
|
14224
|
+
takers.add(consumer);
|
|
14225
|
+
}
|
|
14226
|
+
}
|
|
14227
|
+
}
|
|
14228
|
+
for (const file of realization.paths) {
|
|
14229
|
+
const facts = code.factsAt(file);
|
|
14230
|
+
if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
|
|
14231
|
+
const declared = promised.get(file) ?? NOTHING_PROMISED;
|
|
14232
|
+
const taken = takenFrom.get(file);
|
|
14233
|
+
const owners = new Set(realization.componentsAt(file).map((c) => c.id));
|
|
14234
|
+
const crossed = [];
|
|
14235
|
+
for (const name of [...new Set(facts.exportedNames)].sort()) {
|
|
14236
|
+
if (declared.has(name)) continue;
|
|
14237
|
+
const takers = taken?.get(name);
|
|
14238
|
+
if (!takers) continue;
|
|
14239
|
+
const crossing = [...takers].some(
|
|
14240
|
+
(taker) => taker !== file && !realization.componentsAt(taker).some((c) => owners.has(c.id))
|
|
14241
|
+
);
|
|
14242
|
+
if (crossing) crossed.push(name);
|
|
14243
|
+
}
|
|
14244
|
+
if (crossed.length === 0) continue;
|
|
14245
|
+
const [owner] = realization.implementationsAt(file).map((impl) => impl.id).sort();
|
|
14246
|
+
ctx.addIssue(
|
|
14247
|
+
"warning",
|
|
14248
|
+
"UNDECLARED_EXPORT",
|
|
14249
|
+
`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.`,
|
|
14250
|
+
owner,
|
|
14251
|
+
void 0,
|
|
14252
|
+
void 0,
|
|
14253
|
+
// The crossings themselves, named the way the message names them, so
|
|
14254
|
+
// a register entry written for today's set cannot carry tomorrow's.
|
|
14255
|
+
{ at: file, covers: crossed }
|
|
14256
|
+
);
|
|
14257
|
+
}
|
|
14258
|
+
}
|
|
14259
|
+
};
|
|
14260
|
+
}
|
|
14261
|
+
});
|
|
14262
|
+
|
|
14018
14263
|
// src/core/rules/conformance/carried-debt.ts
|
|
14019
14264
|
var carriedDebtRule;
|
|
14020
14265
|
var init_carried_debt = __esm({
|
|
@@ -15338,7 +15583,9 @@ var init_repository = __esm({
|
|
|
15338
15583
|
init_integration_sim_wiring();
|
|
15339
15584
|
init_integration_sim_coverage();
|
|
15340
15585
|
init_type_realization();
|
|
15586
|
+
init_type_shape();
|
|
15341
15587
|
init_unclaimed_source();
|
|
15588
|
+
init_export_conformance();
|
|
15342
15589
|
init_carried_debt();
|
|
15343
15590
|
init_coupling_health();
|
|
15344
15591
|
init_signature_language_builtins();
|
|
@@ -15504,11 +15751,14 @@ var init_repository = __esm({
|
|
|
15504
15751
|
integrationSimFileRule,
|
|
15505
15752
|
integrationSimWiringRule,
|
|
15506
15753
|
integrationSimCoverageRule,
|
|
15507
|
-
// The data model's own claim on code
|
|
15508
|
-
//
|
|
15509
|
-
// the
|
|
15754
|
+
// The data model's own claim on code — does the file hold the type, and is
|
|
15755
|
+
// it the SHAPE the spec claims — and then the question no spec can ask from
|
|
15756
|
+
// the spec side: which files are named by nothing at all. Last in the
|
|
15757
|
+
// family because that one reads what all the others named.
|
|
15510
15758
|
typeRealizationRule,
|
|
15759
|
+
typeShapeRule,
|
|
15511
15760
|
unclaimedSourceRule,
|
|
15761
|
+
exportConformanceRule,
|
|
15512
15762
|
couplingRule,
|
|
15513
15763
|
// Target-language fit in two questions: what a CONTRACT may name, and what
|
|
15514
15764
|
// a NARRATIVE may describe.
|
|
@@ -15674,7 +15924,10 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
15674
15924
|
const typeOnlyBindings = /* @__PURE__ */ new Map();
|
|
15675
15925
|
const fieldTypes = /* @__PURE__ */ new Map();
|
|
15676
15926
|
const localTypes = /* @__PURE__ */ new Map();
|
|
15927
|
+
const typeShapes = /* @__PURE__ */ new Map();
|
|
15677
15928
|
const mutableBindings = /* @__PURE__ */ new Set();
|
|
15929
|
+
const schemaConstants = /* @__PURE__ */ new Map();
|
|
15930
|
+
const derivedAliases = [];
|
|
15678
15931
|
const receiverKey = (site) => site.via ?? (site.field ? `this.${site.field}` : site.constructed ? `new ${site.constructed}` : "");
|
|
15679
15932
|
const siteKey = (site) => `${site.member ? "m" : "b"}:${receiverKey(site)}:${site.name}`;
|
|
15680
15933
|
const addBindingNames = (name) => {
|
|
@@ -15708,6 +15961,79 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
15708
15961
|
named.add(typeName);
|
|
15709
15962
|
localTypes.set(name.text, named);
|
|
15710
15963
|
};
|
|
15964
|
+
const recordDeclaredShape = (name, members, inherited) => {
|
|
15965
|
+
const fields = [];
|
|
15966
|
+
const methods = [];
|
|
15967
|
+
for (const member of members) {
|
|
15968
|
+
const memberName = member.name ? propertyNameText(member.name) : void 0;
|
|
15969
|
+
if (!memberName) continue;
|
|
15970
|
+
if (ts.isMethodSignature(member) || ts.isMethodDeclaration(member) || ts.isGetAccessorDeclaration(member)) {
|
|
15971
|
+
methods.push(memberName);
|
|
15972
|
+
} else if (ts.isPropertySignature(member) || ts.isPropertyDeclaration(member)) {
|
|
15973
|
+
fields.push({ name: memberName, optional: !!member.questionToken });
|
|
15974
|
+
}
|
|
15975
|
+
}
|
|
15976
|
+
const shape = { origin: "declared", fields, methods };
|
|
15977
|
+
if (inherited) shape.inherited = true;
|
|
15978
|
+
typeShapes.set(name, shape);
|
|
15979
|
+
};
|
|
15980
|
+
const inferredSchemaConstant = (type) => {
|
|
15981
|
+
if (!ts.isTypeReferenceNode(type)) return void 0;
|
|
15982
|
+
const reference = ts.isIdentifier(type.typeName) ? type.typeName.text : ts.isQualifiedName(type.typeName) ? type.typeName.right.text : void 0;
|
|
15983
|
+
if (reference !== "infer") return void 0;
|
|
15984
|
+
const [argument] = type.typeArguments ?? [];
|
|
15985
|
+
if (!argument || !ts.isTypeQueryNode(argument) || !ts.isIdentifier(argument.exprName)) return void 0;
|
|
15986
|
+
return argument.exprName.text;
|
|
15987
|
+
};
|
|
15988
|
+
const baseObjectLiteral = (expression) => {
|
|
15989
|
+
let node = expression;
|
|
15990
|
+
const walked = /* @__PURE__ */ new Set();
|
|
15991
|
+
while (node && !walked.has(node)) {
|
|
15992
|
+
walked.add(node);
|
|
15993
|
+
if (ts.isCallExpression(node)) {
|
|
15994
|
+
const callee = node.expression;
|
|
15995
|
+
if (ts.isPropertyAccessExpression(callee) && callee.name.text === "object") {
|
|
15996
|
+
const literal = node.arguments.find((argument) => ts.isObjectLiteralExpression(argument));
|
|
15997
|
+
if (literal) return literal;
|
|
15998
|
+
}
|
|
15999
|
+
node = callee;
|
|
16000
|
+
} else if (ts.isPropertyAccessExpression(node)) {
|
|
16001
|
+
node = node.expression;
|
|
16002
|
+
} else break;
|
|
16003
|
+
}
|
|
16004
|
+
return void 0;
|
|
16005
|
+
};
|
|
16006
|
+
const chainedCombinators = (expression) => {
|
|
16007
|
+
const applied = /* @__PURE__ */ new Set();
|
|
16008
|
+
let node = expression;
|
|
16009
|
+
const walked = /* @__PURE__ */ new Set();
|
|
16010
|
+
while (node && !walked.has(node)) {
|
|
16011
|
+
walked.add(node);
|
|
16012
|
+
if (ts.isCallExpression(node)) {
|
|
16013
|
+
const callee = node.expression;
|
|
16014
|
+
if (ts.isPropertyAccessExpression(callee)) applied.add(callee.name.text);
|
|
16015
|
+
node = callee;
|
|
16016
|
+
} else if (ts.isPropertyAccessExpression(node)) {
|
|
16017
|
+
node = node.expression;
|
|
16018
|
+
} else break;
|
|
16019
|
+
}
|
|
16020
|
+
return applied;
|
|
16021
|
+
};
|
|
16022
|
+
const schemaMembers = (literal) => {
|
|
16023
|
+
const fields = [];
|
|
16024
|
+
for (const property of literal.properties) {
|
|
16025
|
+
const memberName = property.name ? propertyNameText(property.name) : void 0;
|
|
16026
|
+
if (!memberName) continue;
|
|
16027
|
+
if (ts.isShorthandPropertyAssignment(property)) {
|
|
16028
|
+
fields.push({ name: memberName, optional: false });
|
|
16029
|
+
} else if (ts.isPropertyAssignment(property)) {
|
|
16030
|
+
const applied = chainedCombinators(property.initializer);
|
|
16031
|
+
const optional = (applied.has("optional") || applied.has("nullish")) && !applied.has("default");
|
|
16032
|
+
fields.push({ name: memberName, optional });
|
|
16033
|
+
}
|
|
16034
|
+
}
|
|
16035
|
+
return fields;
|
|
16036
|
+
};
|
|
15711
16037
|
const hasExportModifier = (node) => {
|
|
15712
16038
|
const mods = node.modifiers;
|
|
15713
16039
|
return !!mods?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
@@ -15776,6 +16102,23 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
15776
16102
|
recordFieldType(node.name, node.type);
|
|
15777
16103
|
}
|
|
15778
16104
|
if (ts.isParameter(node) || ts.isVariableDeclaration(node)) recordLocalType(node.name, node.type);
|
|
16105
|
+
if (ts.isInterfaceDeclaration(node)) {
|
|
16106
|
+
recordDeclaredShape(node.name.text, node.members, (node.heritageClauses?.length ?? 0) > 0);
|
|
16107
|
+
} else if (ts.isClassDeclaration(node) && node.name) {
|
|
16108
|
+
const extendsOther = (node.heritageClauses ?? []).some(
|
|
16109
|
+
(clause) => clause.token === ts.SyntaxKind.ExtendsKeyword
|
|
16110
|
+
);
|
|
16111
|
+
recordDeclaredShape(node.name.text, node.members, extendsOther);
|
|
16112
|
+
} else if (ts.isTypeAliasDeclaration(node)) {
|
|
16113
|
+
if (ts.isTypeLiteralNode(node.type)) recordDeclaredShape(node.name.text, node.type.members, false);
|
|
16114
|
+
else {
|
|
16115
|
+
const constant = inferredSchemaConstant(node.type);
|
|
16116
|
+
if (constant) derivedAliases.push({ name: node.name.text, constant });
|
|
16117
|
+
}
|
|
16118
|
+
}
|
|
16119
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && !schemaConstants.has(node.name.text)) {
|
|
16120
|
+
schemaConstants.set(node.name.text, node.initializer);
|
|
16121
|
+
}
|
|
15779
16122
|
if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isModuleDeclaration(node)) {
|
|
15780
16123
|
const name = node.name && ts.isIdentifier(node.name) ? node.name.text : void 0;
|
|
15781
16124
|
if (name) {
|
|
@@ -15851,8 +16194,16 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
15851
16194
|
ts.forEachChild(node, visit);
|
|
15852
16195
|
};
|
|
15853
16196
|
visit(sf);
|
|
16197
|
+
for (const alias of derivedAliases) {
|
|
16198
|
+
if (typeShapes.has(alias.name)) continue;
|
|
16199
|
+
const initializer = schemaConstants.get(alias.constant);
|
|
16200
|
+
if (!initializer) continue;
|
|
16201
|
+
const literal = baseObjectLiteral(initializer);
|
|
16202
|
+
if (!literal) continue;
|
|
16203
|
+
typeShapes.set(alias.name, { origin: "derived", fields: schemaMembers(literal), methods: [] });
|
|
16204
|
+
}
|
|
15854
16205
|
const reexportOnly = sf.statements.length > 0 && sf.statements.every((st) => ts.isExportDeclaration(st) && !!st.moduleSpecifier);
|
|
15855
|
-
return { declared, anchors, exported, imports, reexports, starExports, namedReexports, complexity, calls, importBindings, typeOnlyBindings, fieldTypes, localTypes, mutableBindings, reexportOnly };
|
|
16206
|
+
return { declared, anchors, exported, imports, reexports, starExports, namedReexports, complexity, calls, importBindings, typeOnlyBindings, fieldTypes, localTypes, typeShapes, mutableBindings, reexportOnly };
|
|
15856
16207
|
}
|
|
15857
16208
|
function resolveRelativeModule(fromFile, specifier) {
|
|
15858
16209
|
if (!specifier.startsWith(".")) return null;
|
|
@@ -16034,6 +16385,7 @@ function buildCodeModel(implementations, types, projectRoot2, sourceRoots = [],
|
|
|
16034
16385
|
typeOnlyBindings: Object.fromEntries(facts.typeOnlyBindings),
|
|
16035
16386
|
fieldTypes: Object.fromEntries([...facts.fieldTypes].map(([k, v]) => [k, [...v]])),
|
|
16036
16387
|
localTypes: Object.fromEntries([...facts.localTypes].map(([k, v]) => [k, [...v]])),
|
|
16388
|
+
typeShapes: Object.fromEntries(facts.typeShapes),
|
|
16037
16389
|
topLevelMutableBindings: [...facts.mutableBindings],
|
|
16038
16390
|
reexportOnly: facts.reexportOnly
|
|
16039
16391
|
};
|
|
@@ -27947,21 +28299,33 @@ var init_context = __esm({
|
|
|
27947
28299
|
});
|
|
27948
28300
|
|
|
27949
28301
|
// src/core/status.ts
|
|
27950
|
-
function
|
|
28302
|
+
function fillDecor(decor) {
|
|
28303
|
+
return {
|
|
28304
|
+
structure: decor?.structure ?? asWritten,
|
|
28305
|
+
emphasis: decor?.emphasis ?? asWritten,
|
|
28306
|
+
layer: decor?.layer ?? ((_kind, text2) => text2),
|
|
28307
|
+
score: decor?.score ?? ((_pct, text2) => text2),
|
|
28308
|
+
draft: decor?.draft ?? asWritten,
|
|
28309
|
+
present: decor?.present ?? asWritten,
|
|
28310
|
+
missing: decor?.missing ?? asWritten
|
|
28311
|
+
};
|
|
28312
|
+
}
|
|
28313
|
+
function getStatusReport(options = {}, decor) {
|
|
28314
|
+
const mark = fillDecor(decor);
|
|
27951
28315
|
scanAllSpecs({ recursive: options.recursive ?? true });
|
|
27952
28316
|
const system = loadSystemSpec();
|
|
27953
28317
|
const loaderErrors = getLoaderIssues();
|
|
27954
28318
|
if (loaderErrors.length > 0) {
|
|
27955
28319
|
let errText2 = "Failed to parse specification files:\n";
|
|
27956
28320
|
for (const issue2 of loaderErrors) {
|
|
27957
|
-
const prefix = issue2.specId ? `[${issue2.specId}] ` : "";
|
|
28321
|
+
const prefix = issue2.specId ? mark.structure(`[${issue2.specId}] `) : "";
|
|
27958
28322
|
errText2 += `${prefix}[${issue2.code}] ${issue2.message}
|
|
27959
28323
|
`;
|
|
27960
28324
|
}
|
|
27961
|
-
return errText2;
|
|
28325
|
+
return { text: errText2, failed: true };
|
|
27962
28326
|
}
|
|
27963
28327
|
if (!system) {
|
|
27964
|
-
return "L0 System specification (system.yaml) is missing.";
|
|
28328
|
+
return { text: "L0 System specification (system.yaml) is missing.", failed: true };
|
|
27965
28329
|
}
|
|
27966
28330
|
let subsystems = loadSubsystemSpecs();
|
|
27967
28331
|
let components = loadComponentSpecs();
|
|
@@ -28015,7 +28379,7 @@ function getStatusReport(options = {}) {
|
|
|
28015
28379
|
};
|
|
28016
28380
|
const totalSubsystemsScore = subsystems.reduce((acc, s) => acc + getSubsystemScore(s.id), 0);
|
|
28017
28381
|
const systemScore = subsystems.length > 0 ? Math.round(totalSubsystemsScore / subsystems.length) : 0;
|
|
28018
|
-
output +=
|
|
28382
|
+
output += `${mark.layer("system", "\u25CF System:")} ${mark.emphasis(system.name)} ${mark.score(systemScore, `(${systemScore}% Complete)`)}
|
|
28019
28383
|
`;
|
|
28020
28384
|
for (let i = 0; i < subsystems.length; i++) {
|
|
28021
28385
|
const sub = subsystems[i];
|
|
@@ -28023,8 +28387,8 @@ function getStatusReport(options = {}) {
|
|
|
28023
28387
|
const subPrefix = isLastSub ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
28024
28388
|
const subIndent = isLastSub ? " " : "\u2502 ";
|
|
28025
28389
|
const subScore = getSubsystemScore(sub.id);
|
|
28026
|
-
const subStatusStr = sub.status !== "complete" ? ` [${sub.status}]` : "";
|
|
28027
|
-
output += `${subPrefix}[Subsystem] ${sub.id}${subStatusStr} (${subScore}%)
|
|
28390
|
+
const subStatusStr = sub.status !== "complete" ? mark.draft(` [${sub.status}]`) : "";
|
|
28391
|
+
output += `${mark.structure(subPrefix)}${mark.layer("subsystem", `[Subsystem] ${sub.id}`)}${subStatusStr} ${mark.score(subScore, `(${subScore}%)`)}
|
|
28028
28392
|
`;
|
|
28029
28393
|
const subComps = components.filter((c) => c.subsystem === sub.id);
|
|
28030
28394
|
for (let j = 0; j < subComps.length; j++) {
|
|
@@ -28033,32 +28397,32 @@ function getStatusReport(options = {}) {
|
|
|
28033
28397
|
const compPrefix = isLastComp ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
28034
28398
|
const compIndent = isLastComp ? " " : "\u2502 ";
|
|
28035
28399
|
const compScore = componentScores.get(comp.id) ?? 0;
|
|
28036
|
-
const compStatusStr = comp.status !== "complete" ? ` [${comp.status}]` : "";
|
|
28037
|
-
output += `${subIndent}${
|
|
28400
|
+
const compStatusStr = comp.status !== "complete" ? mark.draft(` [${comp.status}]`) : "";
|
|
28401
|
+
output += `${mark.structure(subIndent + compPrefix)}${mark.layer("component", `[Component: ${comp.componentType}] ${comp.id}`)}${compStatusStr} ${mark.score(compScore, `(${compScore}%)`)}
|
|
28038
28402
|
`;
|
|
28039
28403
|
const intf = interfaces.find((inf) => inf.component === comp.id);
|
|
28040
28404
|
const impl = implementations.find((im) => intf && im.contract === intf.id);
|
|
28041
28405
|
const intfPrefix = intf && impl ? "\u251C\u2500\u2500 " : "\u2514\u2500\u2500 ";
|
|
28042
28406
|
if (intf) {
|
|
28043
|
-
const intfStatusStr = intf.status !== "complete" ? ` [${intf.status}]` : "";
|
|
28044
|
-
output += `${subIndent
|
|
28407
|
+
const intfStatusStr = intf.status !== "complete" ? mark.draft(` [${intf.status}]`) : "";
|
|
28408
|
+
output += `${mark.structure(subIndent + compIndent + intfPrefix)}${mark.layer("interface", `Interface: ${intf.id}`)}${intfStatusStr} (${intf.methods.length} methods)
|
|
28045
28409
|
`;
|
|
28046
28410
|
} else {
|
|
28047
|
-
output += `${subIndent
|
|
28411
|
+
output += `${mark.structure(subIndent + compIndent + intfPrefix)}${mark.missing("Interface: Missing (-30%)")}
|
|
28048
28412
|
`;
|
|
28049
28413
|
}
|
|
28050
28414
|
if (impl) {
|
|
28051
|
-
const implStatusStr = impl.status !== "complete" ? ` [${impl.status}]` : "";
|
|
28415
|
+
const implStatusStr = impl.status !== "complete" ? mark.draft(` [${impl.status}]`) : "";
|
|
28052
28416
|
const methodsWithOwnPath = impl.methods.filter((m) => m.sourcePath);
|
|
28053
28417
|
let pathStr;
|
|
28054
28418
|
if (impl.sourcePath) {
|
|
28055
|
-
pathStr = pathExists(fromProjectRoot(impl.sourcePath)) ? ` -> ${impl.sourcePath}` : ` -> ${impl.sourcePath} (File Missing!)
|
|
28419
|
+
pathStr = pathExists(fromProjectRoot(impl.sourcePath)) ? mark.present(` -> ${impl.sourcePath}`) : mark.missing(` -> ${impl.sourcePath} (File Missing!)`);
|
|
28056
28420
|
} else if (methodsWithOwnPath.length > 0) {
|
|
28057
28421
|
pathStr = "";
|
|
28058
28422
|
} else {
|
|
28059
|
-
pathStr = " (No source path)";
|
|
28423
|
+
pathStr = mark.structure(" (No source path)");
|
|
28060
28424
|
}
|
|
28061
|
-
output += `${subIndent
|
|
28425
|
+
output += `${mark.structure(subIndent + compIndent + "\u2514\u2500\u2500 ")}${mark.layer("implementation", `Implementation: ${impl.id}`)}${implStatusStr}${pathStr}
|
|
28062
28426
|
`;
|
|
28063
28427
|
const methodIndent = `${subIndent}${compIndent} `;
|
|
28064
28428
|
for (let k = 0; k < methodsWithOwnPath.length; k++) {
|
|
@@ -28066,24 +28430,26 @@ function getStatusReport(options = {}) {
|
|
|
28066
28430
|
const isLastMethod = k === methodsWithOwnPath.length - 1;
|
|
28067
28431
|
const methodPrefix = isLastMethod ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
28068
28432
|
const methodPath = method2.sourcePath;
|
|
28069
|
-
const
|
|
28070
|
-
output += `${methodIndent
|
|
28433
|
+
const methodLine = pathExists(fromProjectRoot(methodPath)) ? mark.present(`method ${method2.name} -> ${methodPath}`) : mark.missing(`method ${method2.name} -> ${methodPath} (File Missing!)`);
|
|
28434
|
+
output += `${mark.structure(methodIndent + methodPrefix)}${methodLine}
|
|
28071
28435
|
`;
|
|
28072
28436
|
}
|
|
28073
28437
|
} else {
|
|
28074
|
-
output += `${subIndent
|
|
28438
|
+
output += `${mark.structure(subIndent + compIndent + "\u2514\u2500\u2500 ")}${mark.missing("Implementation: Missing (-30%)")}
|
|
28075
28439
|
`;
|
|
28076
28440
|
}
|
|
28077
28441
|
}
|
|
28078
28442
|
}
|
|
28079
|
-
return output;
|
|
28443
|
+
return { text: output, failed: false };
|
|
28080
28444
|
}
|
|
28445
|
+
var asWritten;
|
|
28081
28446
|
var init_status = __esm({
|
|
28082
28447
|
"src/core/status.ts"() {
|
|
28083
28448
|
"use strict";
|
|
28084
28449
|
init_specs2();
|
|
28085
28450
|
init_specs();
|
|
28086
28451
|
init_fs();
|
|
28452
|
+
asWritten = (text2) => text2;
|
|
28087
28453
|
}
|
|
28088
28454
|
});
|
|
28089
28455
|
|
|
@@ -29625,6 +29991,7 @@ function createMcpServer(options = {}) {
|
|
|
29625
29991
|
intent: import_zod11.z.string().optional().describe("detail: intent \u2014 behavioral prose (what it does and how it fails); substitute for a narrative"),
|
|
29626
29992
|
conformance: conformanceEnum.optional().describe("Conformance tier for this method (overrides the spec default)"),
|
|
29627
29993
|
symbol: import_zod11.z.string().optional().describe("Code-level name realizing this contract method in the sourcePath file, when it legitimately differs from the intent-language contract name (e.g. put realized by saveSnapshot)"),
|
|
29994
|
+
exportedVia: import_zod11.z.string().optional().describe("The exported binding a consumer imports to REACH this method, when the realization is published as a value that composes it rather than exported in its own right (a rule module publishes callConformanceRule, and the method is the check inside it). Not symbol, which names the function itself: this names the route to it, so the surface check reads that export as promised. A handle the method's own source file does not actually export is reported (UNREALIZED_EXPORT_HANDLE) and allows nothing"),
|
|
29628
29995
|
ext: import_zod11.z.record(import_zod11.z.unknown()).optional().describe("Opaque pack/tool extension data for this method (namespaced keys) \u2014 preserved verbatim"),
|
|
29629
29996
|
narrative: import_zod11.z.array(narrativeStepInput).optional(),
|
|
29630
29997
|
calls: import_zod11.z.array(import_zod11.z.string().min(1)).optional().describe('The calls this method makes, when its narrative does not show them (detail: intent, or calls-only with no steps authored) \u2014 each as "<component>.<method>", the spelling the debt register and lint allows name a unit with. The narrative walk takes exactly these edges and no others, so an undeclared collaborator is NOT reached: this is what keeps unused-detection honest at a lower detail dial. Each entry is validated like a call step (the component resolves, this component declares it, the method is on its contract). Refused beside a non-empty narrative \u2014 add the call step there instead')
|
|
@@ -30003,7 +30370,7 @@ function createMcpServer(options = {}) {
|
|
|
30003
30370
|
});
|
|
30004
30371
|
const family = statusFamilyContext();
|
|
30005
30372
|
const verdict = approvalVerdict();
|
|
30006
|
-
return text(`${family}${report2}${verdict.text}`);
|
|
30373
|
+
return text(`${family}${report2.text}${verdict.text}`);
|
|
30007
30374
|
} catch (e) {
|
|
30008
30375
|
return errText(String(e));
|
|
30009
30376
|
}
|
|
@@ -31010,6 +31377,12 @@ function hasContext2() {
|
|
|
31010
31377
|
function derivedDocPaths2() {
|
|
31011
31378
|
return derivedDocPaths();
|
|
31012
31379
|
}
|
|
31380
|
+
function getStatusReport2(options, decor) {
|
|
31381
|
+
return getStatusReport(options, decor);
|
|
31382
|
+
}
|
|
31383
|
+
function approvalVerdict2() {
|
|
31384
|
+
return approvalVerdict();
|
|
31385
|
+
}
|
|
31013
31386
|
async function runSubsystemAdd(id, options = {}) {
|
|
31014
31387
|
logger.header("wairon subsystem add");
|
|
31015
31388
|
if (!projectConfigExists2()) {
|
|
@@ -32460,137 +32833,49 @@ var isNewer = isNewerVersion;
|
|
|
32460
32833
|
var import_chalk9 = __toESM(require("chalk"));
|
|
32461
32834
|
init_logger();
|
|
32462
32835
|
init_paths();
|
|
32463
|
-
|
|
32464
|
-
|
|
32465
|
-
|
|
32466
|
-
|
|
32836
|
+
var LAYER_COLOUR = {
|
|
32837
|
+
system: (text2) => import_chalk9.default.bold.blue(text2),
|
|
32838
|
+
subsystem: (text2) => import_chalk9.default.bold.cyan(text2),
|
|
32839
|
+
component: (text2) => import_chalk9.default.magenta(text2),
|
|
32840
|
+
interface: (text2) => import_chalk9.default.blue(text2),
|
|
32841
|
+
implementation: (text2) => import_chalk9.default.green(text2)
|
|
32842
|
+
};
|
|
32843
|
+
function scoreColour(pct) {
|
|
32844
|
+
if (pct === 100) return (text2) => import_chalk9.default.green(text2);
|
|
32845
|
+
if (pct >= 50) return (text2) => import_chalk9.default.yellow(text2);
|
|
32846
|
+
return (text2) => import_chalk9.default.red(text2);
|
|
32847
|
+
}
|
|
32848
|
+
var TERMINAL_DECOR = {
|
|
32849
|
+
structure: (text2) => import_chalk9.default.gray(text2),
|
|
32850
|
+
emphasis: (text2) => import_chalk9.default.bold(text2),
|
|
32851
|
+
layer: (kind, text2) => (LAYER_COLOUR[kind] ?? ((plain) => plain))(text2),
|
|
32852
|
+
score: (pct, text2) => scoreColour(pct)(text2),
|
|
32853
|
+
draft: (text2) => import_chalk9.default.yellow(text2),
|
|
32854
|
+
present: (text2) => import_chalk9.default.green(text2),
|
|
32855
|
+
missing: (text2) => import_chalk9.default.red(text2)
|
|
32856
|
+
};
|
|
32857
|
+
var NO_SYSTEM_SPEC = "L0 System specification (system.yaml) is missing.";
|
|
32858
|
+
var INIT_HINT = " Run `wairon init` first.";
|
|
32859
|
+
function refuseUnreadableTree(explanation) {
|
|
32860
|
+
const lines = withoutTrailingNewline(explanation).split("\n");
|
|
32861
|
+
const last = lines.length - 1;
|
|
32862
|
+
if (lines[last] === NO_SYSTEM_SPEC) lines[last] += INIT_HINT;
|
|
32863
|
+
for (const line2 of lines) logger.error(line2);
|
|
32864
|
+
process.exit(1);
|
|
32865
|
+
}
|
|
32866
|
+
function withoutTrailingNewline(text2) {
|
|
32867
|
+
return text2.endsWith("\n") ? text2.slice(0, -1) : text2;
|
|
32868
|
+
}
|
|
32467
32869
|
async function runStatus(options = {}) {
|
|
32468
32870
|
assertProjectInitialized();
|
|
32469
|
-
|
|
32470
|
-
|
|
32471
|
-
|
|
32472
|
-
if (loaderErrors.length > 0) {
|
|
32473
|
-
logger.error("Failed to parse specification files:");
|
|
32474
|
-
for (const issue2 of loaderErrors) {
|
|
32475
|
-
const prefix = issue2.specId ? import_chalk9.default.gray(`[${issue2.specId}] `) : "";
|
|
32476
|
-
logger.error(`${prefix}[${issue2.code}] ${issue2.message}`);
|
|
32477
|
-
}
|
|
32478
|
-
process.exit(1);
|
|
32479
|
-
}
|
|
32480
|
-
let subsystems = loadSubsystemSpecs();
|
|
32481
|
-
let components = loadComponentSpecs();
|
|
32482
|
-
let interfaces = loadInterfaceSpecs();
|
|
32483
|
-
let implementations = loadImplementationSpecs();
|
|
32484
|
-
if (options.subsystem) {
|
|
32485
|
-
subsystems = subsystems.filter((s) => s.id === options.subsystem || s.id.startsWith(`${options.subsystem}::`));
|
|
32486
|
-
components = components.filter((c) => c.subsystem === options.subsystem || c.subsystem.startsWith(`${options.subsystem}::`));
|
|
32487
|
-
interfaces = interfaces.filter((i) => {
|
|
32488
|
-
const c = components.find((comp) => comp.id === i.component);
|
|
32489
|
-
return c !== void 0;
|
|
32490
|
-
});
|
|
32491
|
-
implementations = implementations.filter((im) => {
|
|
32492
|
-
const inf = interfaces.find((i) => i.id === im.contract);
|
|
32493
|
-
return inf !== void 0;
|
|
32494
|
-
});
|
|
32495
|
-
}
|
|
32496
|
-
if (!system) {
|
|
32497
|
-
logger.error("L0 System specification (system.yaml) is missing. Run `wairon init` first.");
|
|
32498
|
-
process.exit(1);
|
|
32871
|
+
const report2 = getStatusReport2(options, TERMINAL_DECOR);
|
|
32872
|
+
if (report2.failed) {
|
|
32873
|
+
refuseUnreadableTree(report2.text);
|
|
32499
32874
|
}
|
|
32500
32875
|
logger.header("Architecture Status Dashboard");
|
|
32501
32876
|
logger.blank();
|
|
32502
|
-
|
|
32503
|
-
|
|
32504
|
-
let score = 20;
|
|
32505
|
-
const intf = interfaces.find((i) => i.component === comp.id);
|
|
32506
|
-
if (intf) {
|
|
32507
|
-
score += 30;
|
|
32508
|
-
}
|
|
32509
|
-
const impl = implementations.find((im) => intf && im.contract === intf.id);
|
|
32510
|
-
if (impl) {
|
|
32511
|
-
score += 30;
|
|
32512
|
-
const sourceFiles = implementationSourceFiles(impl);
|
|
32513
|
-
if (sourceFiles.length > 0 && sourceFiles.every((f) => pathExists(fromProjectRoot(f)))) {
|
|
32514
|
-
score += 20;
|
|
32515
|
-
}
|
|
32516
|
-
}
|
|
32517
|
-
const isDraft = comp.status === "draft" || comp.status === "design" || intf && (intf.status === "draft" || intf.status === "design") || impl && (impl.status === "draft" || impl.status === "design");
|
|
32518
|
-
if (isDraft) {
|
|
32519
|
-
score = Math.min(score, 50);
|
|
32520
|
-
}
|
|
32521
|
-
componentScores.set(comp.id, score);
|
|
32522
|
-
}
|
|
32523
|
-
const getSubsystemScore = (subId) => {
|
|
32524
|
-
const sub = subsystems.find((s) => s.id === subId);
|
|
32525
|
-
if (!sub) return 0;
|
|
32526
|
-
const subComps = components.filter((c) => c.subsystem === subId);
|
|
32527
|
-
if (subComps.length === 0) return 0;
|
|
32528
|
-
const totalScore = subComps.reduce((acc, c) => acc + (componentScores.get(c.id) ?? 0), 0);
|
|
32529
|
-
let avg = Math.round(totalScore / subComps.length);
|
|
32530
|
-
if (sub.status === "draft" || sub.status === "design") {
|
|
32531
|
-
avg = Math.min(avg, 50);
|
|
32532
|
-
}
|
|
32533
|
-
return avg;
|
|
32534
|
-
};
|
|
32535
|
-
const totalSubsystemsScore = subsystems.reduce((acc, s) => acc + getSubsystemScore(s.id), 0);
|
|
32536
|
-
const systemScore = subsystems.length > 0 ? Math.round(totalSubsystemsScore / subsystems.length) : 0;
|
|
32537
|
-
const systemColor = systemScore === 100 ? import_chalk9.default.green : systemScore >= 50 ? import_chalk9.default.yellow : import_chalk9.default.red;
|
|
32538
|
-
console.log(`${import_chalk9.default.bold.blue("\u25CF System:")} ${import_chalk9.default.bold(system.name)} ${systemColor(`(${systemScore}% Complete)`)}`);
|
|
32539
|
-
for (let i = 0; i < subsystems.length; i++) {
|
|
32540
|
-
const sub = subsystems[i];
|
|
32541
|
-
const isLastSub = i === subsystems.length - 1;
|
|
32542
|
-
const subPrefix = isLastSub ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
32543
|
-
const subIndent = isLastSub ? " " : "\u2502 ";
|
|
32544
|
-
const subScore = getSubsystemScore(sub.id);
|
|
32545
|
-
const subStatusStr = sub.status !== "complete" ? import_chalk9.default.yellow(` [${sub.status}]`) : "";
|
|
32546
|
-
const subScoreColor = subScore === 100 ? import_chalk9.default.green : subScore >= 50 ? import_chalk9.default.yellow : import_chalk9.default.red;
|
|
32547
|
-
console.log(`${import_chalk9.default.gray(subPrefix)}${import_chalk9.default.bold.cyan(`[Subsystem] ${sub.id}`)}${subStatusStr} ${subScoreColor(`(${subScore}%)`)}`);
|
|
32548
|
-
const subComps = components.filter((c) => c.subsystem === sub.id);
|
|
32549
|
-
for (let j = 0; j < subComps.length; j++) {
|
|
32550
|
-
const comp = subComps[j];
|
|
32551
|
-
const isLastComp = j === subComps.length - 1;
|
|
32552
|
-
const compPrefix = isLastComp ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
32553
|
-
const compIndent = isLastComp ? " " : "\u2502 ";
|
|
32554
|
-
const compScore = componentScores.get(comp.id) ?? 0;
|
|
32555
|
-
const compStatusStr = comp.status !== "complete" ? import_chalk9.default.yellow(` [${comp.status}]`) : "";
|
|
32556
|
-
const compScoreColor = compScore === 100 ? import_chalk9.default.green : compScore >= 50 ? import_chalk9.default.yellow : import_chalk9.default.red;
|
|
32557
|
-
console.log(`${import_chalk9.default.gray(subIndent + compPrefix)}${import_chalk9.default.magenta(`[Component: ${comp.componentType}] ${comp.id}`)}${compStatusStr} ${compScoreColor(`(${compScore}%)`)}`);
|
|
32558
|
-
const intf = interfaces.find((inf) => inf.component === comp.id);
|
|
32559
|
-
const impl = implementations.find((im) => intf && im.contract === intf.id);
|
|
32560
|
-
const intfPrefix = intf && impl ? "\u251C\u2500\u2500 " : "\u2514\u2500\u2500 ";
|
|
32561
|
-
if (intf) {
|
|
32562
|
-
const intfStatusStr = intf.status !== "complete" ? import_chalk9.default.yellow(` [${intf.status}]`) : "";
|
|
32563
|
-
console.log(`${import_chalk9.default.gray(subIndent + compIndent + intfPrefix)}${import_chalk9.default.blue(`Interface: ${intf.id}`)}${intfStatusStr} (${intf.methods.length} methods)`);
|
|
32564
|
-
} else {
|
|
32565
|
-
console.log(`${import_chalk9.default.gray(subIndent + compIndent + intfPrefix)}${import_chalk9.default.red("Interface: Missing (-30%)")}`);
|
|
32566
|
-
}
|
|
32567
|
-
if (impl) {
|
|
32568
|
-
const implStatusStr = impl.status !== "complete" ? import_chalk9.default.yellow(` [${impl.status}]`) : "";
|
|
32569
|
-
const methodsWithOwnPath = impl.methods.filter((m) => m.sourcePath);
|
|
32570
|
-
let pathStr;
|
|
32571
|
-
if (impl.sourcePath) {
|
|
32572
|
-
pathStr = pathExists(fromProjectRoot(impl.sourcePath)) ? import_chalk9.default.green(` -> ${impl.sourcePath}`) : import_chalk9.default.red(` -> ${impl.sourcePath} (File Missing!)`);
|
|
32573
|
-
} else if (methodsWithOwnPath.length > 0) {
|
|
32574
|
-
pathStr = "";
|
|
32575
|
-
} else {
|
|
32576
|
-
pathStr = import_chalk9.default.gray(" (No source path)");
|
|
32577
|
-
}
|
|
32578
|
-
console.log(`${import_chalk9.default.gray(subIndent + compIndent + "\u2514\u2500\u2500 ")}${import_chalk9.default.green(`Implementation: ${impl.id}`)}${implStatusStr}${pathStr}`);
|
|
32579
|
-
const methodIndent = subIndent + compIndent + " ";
|
|
32580
|
-
for (let k = 0; k < methodsWithOwnPath.length; k++) {
|
|
32581
|
-
const method2 = methodsWithOwnPath[k];
|
|
32582
|
-
const isLastMethod = k === methodsWithOwnPath.length - 1;
|
|
32583
|
-
const methodPrefix = isLastMethod ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
32584
|
-
const methodPath = method2.sourcePath;
|
|
32585
|
-
const methodLine = pathExists(fromProjectRoot(methodPath)) ? import_chalk9.default.green(`method ${method2.name} -> ${methodPath}`) : import_chalk9.default.red(`method ${method2.name} -> ${methodPath} (File Missing!)`);
|
|
32586
|
-
console.log(`${import_chalk9.default.gray(methodIndent + methodPrefix)}${methodLine}`);
|
|
32587
|
-
}
|
|
32588
|
-
} else {
|
|
32589
|
-
console.log(`${import_chalk9.default.gray(subIndent + compIndent + "\u2514\u2500\u2500 ")}${import_chalk9.default.red("Implementation: Missing (-30%)")}`);
|
|
32590
|
-
}
|
|
32591
|
-
}
|
|
32592
|
-
}
|
|
32593
|
-
const lock = approvalVerdict();
|
|
32877
|
+
console.log(withoutTrailingNewline(report2.text));
|
|
32878
|
+
const lock = approvalVerdict2();
|
|
32594
32879
|
if (lock.text.trim()) {
|
|
32595
32880
|
logger.blank();
|
|
32596
32881
|
if (lock.drifted) logger.warn(lock.text.trim());
|