@wairon/cli 5.1.1-dev.64 → 5.1.1-dev.66

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/index.js CHANGED
@@ -5819,6 +5819,24 @@ var ImplementationSpecSchema = import_zod8.z.object({
5819
5819
  * stereotypes should bind tech directly (TECH_ON_LOGIC_COMPONENT).
5820
5820
  */
5821
5821
  technologies: import_zod8.z.array(import_zod8.z.string()).optional(),
5822
+ /**
5823
+ * The parameter names THIS realization takes BEFORE the ones its contract
5824
+ * declares — a config object, a data root, the transport handles a portal is
5825
+ * handed. Supplied by whatever wires the component up, never by the caller
5826
+ * the contract describes, which is why they belong to the realization and
5827
+ * not to the contract: another realization of the same contract may hold
5828
+ * them as fields instead.
5829
+ *
5830
+ * Declared rather than guessed, because a leading parameter the contract
5831
+ * does not name is otherwise indistinguishable from one it named under a
5832
+ * different name — `seed(config)` realized as `bootstrapInstance(cfg)` reads
5833
+ * as a dropped parameter to anything that infers. A leading parameter this
5834
+ * list does not name is a finding (UNDECLARED_PARAM), and only a LEADING run
5835
+ * is dropped: one of these names appearing after the contract's own
5836
+ * parameters is not wiring, it is an argument in the middle of the caller's
5837
+ * list.
5838
+ */
5839
+ injectedParams: import_zod8.z.array(import_zod8.z.string()).optional(),
5822
5840
  methods: import_zod8.z.array(MethodImplementationSchema).default([]).superRefine((methods, ctx) => {
5823
5841
  methods.forEach((m, i) => {
5824
5842
  if (!m.calls?.length || m.narrative.length === 0) return;
@@ -11549,7 +11567,7 @@ function defaultTargetConfig(type) {
11549
11567
  enabled: true
11550
11568
  };
11551
11569
  }
11552
- var WAIRON_VERSION = "5.1.1-dev.64";
11570
+ var WAIRON_VERSION = "5.1.1-dev.66";
11553
11571
  var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
11554
11572
  var ARCHITECT_AGENT_ID = "agent-architect";
11555
11573
  var ARCHITECT_TEMPLATE_ID = "architect";
@@ -16881,6 +16899,166 @@ var typeShapeRule = {
16881
16899
  }
16882
16900
  };
16883
16901
 
16902
+ // src/core/rules/conformance/param-conformance.ts
16903
+ function omittable(optional) {
16904
+ return optional ? "may be left out" : "is required";
16905
+ }
16906
+ function unitOf2(parameter, position) {
16907
+ return parameter.name ?? `#${position + 1}`;
16908
+ }
16909
+ function judge(declared, realized, injected, typeAgrees) {
16910
+ let wiring = 0;
16911
+ while (wiring < realized.length && realized[wiring].name !== void 0 && injected.has(realized[wiring].name)) wiring++;
16912
+ const taken = realized.slice(wiring);
16913
+ const matched = Math.min(declared.length, taken.length);
16914
+ const found = {
16915
+ unrealized: /* @__PURE__ */ new Map(),
16916
+ undeclared: /* @__PURE__ */ new Map(),
16917
+ renamed: /* @__PURE__ */ new Map(),
16918
+ optionality: /* @__PURE__ */ new Map()
16919
+ };
16920
+ for (const param of declared.slice(matched)) {
16921
+ found.unrealized.set(param.name, { unit: param.name, told: `"${param.name}"` });
16922
+ }
16923
+ taken.slice(matched).forEach((param, index) => {
16924
+ const unit = unitOf2(param, wiring + matched + index);
16925
+ found.undeclared.set(unit, { unit, told: `"${unit}"` });
16926
+ });
16927
+ for (let position = 0; position < matched; position++) {
16928
+ const param = declared[position];
16929
+ const at = taken[position];
16930
+ if (at.name !== void 0 && at.name !== param.name && typeAgrees(param, at)) {
16931
+ found.renamed.set(`${param.name}\u2192${at.name}`, {
16932
+ unit: param.name,
16933
+ told: `"${param.name}" (the code calls it "${at.name}")`
16934
+ });
16935
+ }
16936
+ if (!!param.optional !== at.optional) {
16937
+ found.optionality.set(`${param.name}:${at.optional}`, {
16938
+ unit: param.name,
16939
+ told: `"${param.name}" (the contract says it ${omittable(!!param.optional)}, the code says it ${omittable(at.optional)})`
16940
+ });
16941
+ }
16942
+ }
16943
+ return found;
16944
+ }
16945
+ function agreed(judgements, reading) {
16946
+ const [first, ...rest] = judgements;
16947
+ return [...first[reading]].filter(([fact]) => rest.every((other) => other[reading].has(fact))).map(([, finding]) => finding);
16948
+ }
16949
+ var paramConformanceRule = {
16950
+ name: "param-conformance",
16951
+ description: "Code-to-contract for the SIGNATURE, the last of the three readings a spec-driven gate never made: a contract declares `params`, and nothing ever compared them to the parameters of the function that realizes the method. A contract could promise an argument the code does not take, take one the contract never mentions \u2014 including a secret \u2014 or name the same argument two different things, and the brief handed to an implementer would carry the contract's version. Parameters are matched by POSITION against the tail of the realization's list, and the declared type is what tells a rename from a dropped argument: `seed(config: HostConfig)` realized as `bootstrapInstance(cfg: HostConfig)` is one parameter under two names, which anything matching on names alone reads as a parameter the code lost. What a realization takes BEFORE the contract's own parameters is wiring, and it is declared on the implementation as `injectedParams` rather than inferred, because an inferred prefix cannot be told from a renamed first argument. A method the named file only CALLS is left to `methodRealization`, which already reports that the body is not here.",
16952
+ codes: [
16953
+ {
16954
+ code: "UNREALIZED_PARAM",
16955
+ defaultSeverity: "warning",
16956
+ summary: "A contract declares a parameter the function realizing the method does not take \u2014 the contract promises an argument that would go nowhere, and every brief and caller built from it passes one",
16957
+ // Measured code↔spec drift, parameter by parameter, at a site the
16958
+ // finding names — which is why every one of them hands over `parts`.
16959
+ carryable: true
16960
+ },
16961
+ {
16962
+ code: "UNDECLARED_PARAM",
16963
+ defaultSeverity: "warning",
16964
+ summary: "The realizing function takes a parameter no contract parameter names and no declared injection accounts for \u2014 an argument a caller must supply that the design never mentions",
16965
+ carryable: true
16966
+ },
16967
+ {
16968
+ code: "PARAM_NAME_MISMATCH",
16969
+ defaultSeverity: "warning",
16970
+ summary: "A contract parameter and the one realizing it agree on position and type but not on name \u2014 the contract, the ERD and every brief say one word while the code says another",
16971
+ carryable: true
16972
+ },
16973
+ {
16974
+ code: "PARAM_OPTIONALITY",
16975
+ defaultSeverity: "warning",
16976
+ summary: "A contract and its realization disagree about whether a parameter may be left out \u2014 one of them is telling a caller an argument is required when it is not, or the reverse",
16977
+ carryable: true
16978
+ }
16979
+ ],
16980
+ check(ctx) {
16981
+ const code = ctx.codeIndex();
16982
+ const codeNameOf = /* @__PURE__ */ new Map();
16983
+ for (const type of ctx.types) {
16984
+ const named = type.symbol ?? type.name;
16985
+ codeNameOf.set(type.id, named);
16986
+ if (type.subsystem) codeNameOf.set(`${type.subsystem}.${type.id}`, named);
16987
+ }
16988
+ const normalize3 = (text) => text.replace(/\s+/g, " ").trim();
16989
+ const typeAgrees = (declared, realized) => {
16990
+ if (!realized.type) return false;
16991
+ const stated = normalize3(declared.type);
16992
+ return stated === realized.type || codeNameOf.get(stated) === realized.type;
16993
+ };
16994
+ for (const { implementation, method, sourceFile, draftContext } of ctx.implementationMethods()) {
16995
+ const contract = ctx.interfaceMap.get(implementation.contract);
16996
+ const declared = contract?.methods.find((m) => m.name === method.name)?.params ?? [];
16997
+ if (declared.length === 0 || !sourceFile) continue;
16998
+ const file = pathKey(sourceFile);
16999
+ const facts = code.factsAt(file);
17000
+ if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
17001
+ const symbol = method.symbol ?? method.name;
17002
+ const signatures = facts.functionParams;
17003
+ if (!signatures || !Object.prototype.hasOwnProperty.call(signatures, symbol)) continue;
17004
+ const candidates = signatures[symbol];
17005
+ if (candidates.length === 0) continue;
17006
+ const injected = new Set(implementation.injectedParams ?? []);
17007
+ const judgements = candidates.map((realized) => judge(declared, realized, injected, typeAgrees));
17008
+ const subject = candidates.length > 1 ? `every function called "${symbol}" in "${file}"` : `the function "${symbol}" in "${file}"`;
17009
+ const opening = subject.charAt(0).toUpperCase() + subject.slice(1);
17010
+ const unrealized = agreed(judgements, "unrealized");
17011
+ if (unrealized.length > 0) {
17012
+ ctx.addIssue(
17013
+ "warning",
17014
+ "UNREALIZED_PARAM",
17015
+ `Method "${method.name}" of contract "${implementation.contract}" declares ${unrealized.length} parameter(s) ${subject} does not take \u2014 ${unrealized.map((found) => found.told).join(", ")}. The contract is promising an argument that would go nowhere, and nothing breaks at a call site to correct it: every brief and every caller built from the contract passes one. Take the parameter in the code, or drop it from the contract.`,
17016
+ implementation.id,
17017
+ draftContext,
17018
+ void 0,
17019
+ { at: method.name, covers: unrealized.map((found) => found.unit) }
17020
+ );
17021
+ }
17022
+ const undeclared = agreed(judgements, "undeclared");
17023
+ if (undeclared.length > 0) {
17024
+ ctx.addIssue(
17025
+ "warning",
17026
+ "UNDECLARED_PARAM",
17027
+ `${opening} realizing method "${method.name}" of contract "${implementation.contract}" takes ${undeclared.length} parameter(s) the contract does not declare and no declared injection accounts for \u2014 ${undeclared.map((found) => found.told).join(", ")}. An argument a caller must supply that the design never mentions is how a credential ends up in a signature nobody has read against its contract. Declare it on the contract, name it in the implementation's \`injectedParams\` when whatever wires this component up supplies it (a LEADING run only), or take it out of the signature.`,
17028
+ implementation.id,
17029
+ draftContext,
17030
+ void 0,
17031
+ { at: method.name, covers: undeclared.map((found) => found.unit) }
17032
+ );
17033
+ }
17034
+ const renamed = agreed(judgements, "renamed");
17035
+ if (renamed.length > 0) {
17036
+ ctx.addIssue(
17037
+ "warning",
17038
+ "PARAM_NAME_MISMATCH",
17039
+ `Method "${method.name}" of contract "${implementation.contract}" and ${subject} agree on position and type but not on name for ${renamed.length} parameter(s) \u2014 ${renamed.map((f) => f.told).join("; ")}. The contract, the ERD and every brief carry one word and the code answers to another, which is a rename nobody recorded rather than a different argument \u2014 the declared type agreeing is what says so. Rename one side to the other.`,
17040
+ implementation.id,
17041
+ draftContext,
17042
+ void 0,
17043
+ { at: method.name, covers: renamed.map((found) => found.unit) }
17044
+ );
17045
+ }
17046
+ const disagreed = agreed(judgements, "optionality");
17047
+ if (disagreed.length > 0) {
17048
+ ctx.addIssue(
17049
+ "warning",
17050
+ "PARAM_OPTIONALITY",
17051
+ `Method "${method.name}" of contract "${implementation.contract}" and ${subject} disagree about whether ${disagreed.length} parameter(s) may be left out \u2014 ${disagreed.map((f) => f.told).join("; ")}. One of them is telling a caller an argument is required when it is not, or the reverse. Omittable is the code's word for it: a default value and a rest parameter make an argument omittable exactly as a question mark does.`,
17052
+ implementation.id,
17053
+ draftContext,
17054
+ void 0,
17055
+ { at: method.name, covers: disagreed.map((found) => found.unit) }
17056
+ );
17057
+ }
17058
+ }
17059
+ }
17060
+ };
17061
+
16884
17062
  // src/core/rules/conformance/unclaimed-source.ts
16885
17063
  var unclaimedSourceRule = {
16886
17064
  name: "unclaimed-source",
@@ -18343,11 +18521,14 @@ var SDD_RULES = [
18343
18521
  integrationSimWiringRule,
18344
18522
  integrationSimCoverageRule,
18345
18523
  // 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.
18524
+ // it the SHAPE the spec claims — then the SIGNATURE, the third reading:
18525
+ // does the code take the arguments the contract promises. And then the
18526
+ // question no spec can ask from the spec side: which files are named by
18527
+ // nothing at all. Last in the family because that one reads what all the
18528
+ // others named.
18349
18529
  typeRealizationRule,
18350
18530
  typeShapeRule,
18531
+ paramConformanceRule,
18351
18532
  unclaimedSourceRule,
18352
18533
  exportConformanceRule,
18353
18534
  couplingRule,
@@ -18665,6 +18846,7 @@ function walkExact(ts, sourceText, fileName) {
18665
18846
  const fieldTypes = /* @__PURE__ */ new Map();
18666
18847
  const localTypes = /* @__PURE__ */ new Map();
18667
18848
  const typeShapes = /* @__PURE__ */ new Map();
18849
+ const functionParams = /* @__PURE__ */ new Map();
18668
18850
  const mutableBindings = /* @__PURE__ */ new Set();
18669
18851
  const schemaConstants = /* @__PURE__ */ new Map();
18670
18852
  const derivedAliases = [];
@@ -18794,6 +18976,14 @@ function walkExact(ts, sourceText, fileName) {
18794
18976
  }
18795
18977
  return void 0;
18796
18978
  };
18979
+ const declaredParameters = (fn) => (fn.parameters ?? []).map((parameter) => {
18980
+ const fact = {
18981
+ optional: !!parameter.questionToken || !!parameter.initializer || !!parameter.dotDotDotToken
18982
+ };
18983
+ if (ts.isIdentifier(parameter.name)) fact.name = parameter.name.text;
18984
+ if (parameter.type) fact.type = parameter.type.getText(sf).replace(/\s+/g, " ").trim();
18985
+ return fact;
18986
+ });
18797
18987
  const collectFunctionFacts = (fn) => {
18798
18988
  let score = 1;
18799
18989
  const callees = /* @__PURE__ */ new Map();
@@ -18836,6 +19026,9 @@ function walkExact(ts, sourceText, fileName) {
18836
19026
  const sites = calls.get(fnName) ?? /* @__PURE__ */ new Map();
18837
19027
  for (const [key, site] of facts.callees) sites.set(key, site);
18838
19028
  calls.set(fnName, sites);
19029
+ const signatures = functionParams.get(fnName) ?? [];
19030
+ signatures.push(declaredParameters(node));
19031
+ functionParams.set(fnName, signatures);
18839
19032
  }
18840
19033
  if (ts.isPropertyDeclaration(node)) recordFieldType(node.name, node.type);
18841
19034
  else if (ts.isParameter(node) && node.parent && ts.isConstructorDeclaration(node.parent) && isParameterProperty(node)) {
@@ -18943,7 +19136,7 @@ function walkExact(ts, sourceText, fileName) {
18943
19136
  typeShapes.set(alias.name, { origin: "derived", fields: schemaMembers(literal), methods: [] });
18944
19137
  }
18945
19138
  const reexportOnly = sf.statements.length > 0 && sf.statements.every((st) => ts.isExportDeclaration(st) && !!st.moduleSpecifier);
18946
- return { declared, anchors, exported, imports, reexports, starExports, namedReexports, complexity, calls, importBindings, typeOnlyBindings, fieldTypes, localTypes, typeShapes, mutableBindings, reexportOnly };
19139
+ return { declared, anchors, exported, imports, reexports, starExports, namedReexports, complexity, calls, importBindings, typeOnlyBindings, fieldTypes, localTypes, typeShapes, functionParams, mutableBindings, reexportOnly };
18947
19140
  }
18948
19141
  function resolveRelativeModule(fromFile, specifier) {
18949
19142
  if (!specifier.startsWith(".")) return null;
@@ -19126,6 +19319,7 @@ function buildCodeModel(implementations, types, projectRoot, sourceRoots = [], e
19126
19319
  fieldTypes: Object.fromEntries([...facts.fieldTypes].map(([k, v]) => [k, [...v]])),
19127
19320
  localTypes: Object.fromEntries([...facts.localTypes].map(([k, v]) => [k, [...v]])),
19128
19321
  typeShapes: Object.fromEntries(facts.typeShapes),
19322
+ functionParams: Object.fromEntries(facts.functionParams),
19129
19323
  topLevelMutableBindings: [...facts.mutableBindings],
19130
19324
  reexportOnly: facts.reexportOnly
19131
19325
  };