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

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 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.64";
68
+ WAIRON_VERSION = "5.1.1-dev.65";
69
69
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
70
70
  SUPPORTED_ALIASES = ["wai"];
71
71
  SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
@@ -2423,6 +2423,24 @@ var init_specs = __esm({
2423
2423
  * stereotypes should bind tech directly (TECH_ON_LOGIC_COMPONENT).
2424
2424
  */
2425
2425
  technologies: import_zod8.z.array(import_zod8.z.string()).optional(),
2426
+ /**
2427
+ * The parameter names THIS realization takes BEFORE the ones its contract
2428
+ * declares — a config object, a data root, the transport handles a portal is
2429
+ * handed. Supplied by whatever wires the component up, never by the caller
2430
+ * the contract describes, which is why they belong to the realization and
2431
+ * not to the contract: another realization of the same contract may hold
2432
+ * them as fields instead.
2433
+ *
2434
+ * Declared rather than guessed, because a leading parameter the contract
2435
+ * does not name is otherwise indistinguishable from one it named under a
2436
+ * different name — `seed(config)` realized as `bootstrapInstance(cfg)` reads
2437
+ * as a dropped parameter to anything that infers. A leading parameter this
2438
+ * list does not name is a finding (UNDECLARED_PARAM), and only a LEADING run
2439
+ * is dropped: one of these names appearing after the contract's own
2440
+ * parameters is not wiring, it is an argument in the middle of the caller's
2441
+ * list.
2442
+ */
2443
+ injectedParams: import_zod8.z.array(import_zod8.z.string()).optional(),
2426
2444
  methods: import_zod8.z.array(MethodImplementationSchema).default([]).superRefine((methods, ctx) => {
2427
2445
  methods.forEach((m, i) => {
2428
2446
  if (!m.calls?.length || m.narrative.length === 0) return;
@@ -14080,6 +14098,173 @@ var init_type_shape = __esm({
14080
14098
  }
14081
14099
  });
14082
14100
 
14101
+ // src/core/rules/conformance/param-conformance.ts
14102
+ function omittable(optional) {
14103
+ return optional ? "may be left out" : "is required";
14104
+ }
14105
+ function unitOf2(parameter, position) {
14106
+ return parameter.name ?? `#${position + 1}`;
14107
+ }
14108
+ function judge(declared, realized, injected, typeAgrees) {
14109
+ let wiring = 0;
14110
+ while (wiring < realized.length && realized[wiring].name !== void 0 && injected.has(realized[wiring].name)) wiring++;
14111
+ const taken = realized.slice(wiring);
14112
+ const matched = Math.min(declared.length, taken.length);
14113
+ const found = {
14114
+ unrealized: /* @__PURE__ */ new Map(),
14115
+ undeclared: /* @__PURE__ */ new Map(),
14116
+ renamed: /* @__PURE__ */ new Map(),
14117
+ optionality: /* @__PURE__ */ new Map()
14118
+ };
14119
+ for (const param of declared.slice(matched)) {
14120
+ found.unrealized.set(param.name, { unit: param.name, told: `"${param.name}"` });
14121
+ }
14122
+ taken.slice(matched).forEach((param, index) => {
14123
+ const unit = unitOf2(param, wiring + matched + index);
14124
+ found.undeclared.set(unit, { unit, told: `"${unit}"` });
14125
+ });
14126
+ for (let position = 0; position < matched; position++) {
14127
+ const param = declared[position];
14128
+ const at = taken[position];
14129
+ if (at.name !== void 0 && at.name !== param.name && typeAgrees(param, at)) {
14130
+ found.renamed.set(`${param.name}\u2192${at.name}`, {
14131
+ unit: param.name,
14132
+ told: `"${param.name}" (the code calls it "${at.name}")`
14133
+ });
14134
+ }
14135
+ if (!!param.optional !== at.optional) {
14136
+ found.optionality.set(`${param.name}:${at.optional}`, {
14137
+ unit: param.name,
14138
+ told: `"${param.name}" (the contract says it ${omittable(!!param.optional)}, the code says it ${omittable(at.optional)})`
14139
+ });
14140
+ }
14141
+ }
14142
+ return found;
14143
+ }
14144
+ function agreed(judgements, reading) {
14145
+ const [first, ...rest] = judgements;
14146
+ return [...first[reading]].filter(([fact]) => rest.every((other) => other[reading].has(fact))).map(([, finding]) => finding);
14147
+ }
14148
+ var paramConformanceRule;
14149
+ var init_param_conformance = __esm({
14150
+ "src/core/rules/conformance/param-conformance.ts"() {
14151
+ "use strict";
14152
+ init_models();
14153
+ paramConformanceRule = {
14154
+ name: "param-conformance",
14155
+ 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.",
14156
+ codes: [
14157
+ {
14158
+ code: "UNREALIZED_PARAM",
14159
+ defaultSeverity: "warning",
14160
+ 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",
14161
+ // Measured code↔spec drift, parameter by parameter, at a site the
14162
+ // finding names — which is why every one of them hands over `parts`.
14163
+ carryable: true
14164
+ },
14165
+ {
14166
+ code: "UNDECLARED_PARAM",
14167
+ defaultSeverity: "warning",
14168
+ 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",
14169
+ carryable: true
14170
+ },
14171
+ {
14172
+ code: "PARAM_NAME_MISMATCH",
14173
+ defaultSeverity: "warning",
14174
+ 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",
14175
+ carryable: true
14176
+ },
14177
+ {
14178
+ code: "PARAM_OPTIONALITY",
14179
+ defaultSeverity: "warning",
14180
+ 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",
14181
+ carryable: true
14182
+ }
14183
+ ],
14184
+ check(ctx) {
14185
+ const code = ctx.codeIndex();
14186
+ const codeNameOf = /* @__PURE__ */ new Map();
14187
+ for (const type of ctx.types) {
14188
+ const named = type.symbol ?? type.name;
14189
+ codeNameOf.set(type.id, named);
14190
+ if (type.subsystem) codeNameOf.set(`${type.subsystem}.${type.id}`, named);
14191
+ }
14192
+ const normalize3 = (text2) => text2.replace(/\s+/g, " ").trim();
14193
+ const typeAgrees = (declared, realized) => {
14194
+ if (!realized.type) return false;
14195
+ const stated = normalize3(declared.type);
14196
+ return stated === realized.type || codeNameOf.get(stated) === realized.type;
14197
+ };
14198
+ for (const { implementation, method: method2, sourceFile, draftContext } of ctx.implementationMethods()) {
14199
+ const contract = ctx.interfaceMap.get(implementation.contract);
14200
+ const declared = contract?.methods.find((m) => m.name === method2.name)?.params ?? [];
14201
+ if (declared.length === 0 || !sourceFile) continue;
14202
+ const file = pathKey(sourceFile);
14203
+ const facts = code.factsAt(file);
14204
+ if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
14205
+ const symbol = method2.symbol ?? method2.name;
14206
+ const signatures = facts.functionParams;
14207
+ if (!signatures || !Object.prototype.hasOwnProperty.call(signatures, symbol)) continue;
14208
+ const candidates = signatures[symbol];
14209
+ if (candidates.length === 0) continue;
14210
+ const injected = new Set(implementation.injectedParams ?? []);
14211
+ const judgements = candidates.map((realized) => judge(declared, realized, injected, typeAgrees));
14212
+ const subject = candidates.length > 1 ? `every function called "${symbol}" in "${file}"` : `the function "${symbol}" in "${file}"`;
14213
+ const opening = subject.charAt(0).toUpperCase() + subject.slice(1);
14214
+ const unrealized = agreed(judgements, "unrealized");
14215
+ if (unrealized.length > 0) {
14216
+ ctx.addIssue(
14217
+ "warning",
14218
+ "UNREALIZED_PARAM",
14219
+ `Method "${method2.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.`,
14220
+ implementation.id,
14221
+ draftContext,
14222
+ void 0,
14223
+ { at: method2.name, covers: unrealized.map((found) => found.unit) }
14224
+ );
14225
+ }
14226
+ const undeclared = agreed(judgements, "undeclared");
14227
+ if (undeclared.length > 0) {
14228
+ ctx.addIssue(
14229
+ "warning",
14230
+ "UNDECLARED_PARAM",
14231
+ `${opening} realizing method "${method2.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.`,
14232
+ implementation.id,
14233
+ draftContext,
14234
+ void 0,
14235
+ { at: method2.name, covers: undeclared.map((found) => found.unit) }
14236
+ );
14237
+ }
14238
+ const renamed = agreed(judgements, "renamed");
14239
+ if (renamed.length > 0) {
14240
+ ctx.addIssue(
14241
+ "warning",
14242
+ "PARAM_NAME_MISMATCH",
14243
+ `Method "${method2.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.`,
14244
+ implementation.id,
14245
+ draftContext,
14246
+ void 0,
14247
+ { at: method2.name, covers: renamed.map((found) => found.unit) }
14248
+ );
14249
+ }
14250
+ const disagreed = agreed(judgements, "optionality");
14251
+ if (disagreed.length > 0) {
14252
+ ctx.addIssue(
14253
+ "warning",
14254
+ "PARAM_OPTIONALITY",
14255
+ `Method "${method2.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.`,
14256
+ implementation.id,
14257
+ draftContext,
14258
+ void 0,
14259
+ { at: method2.name, covers: disagreed.map((found) => found.unit) }
14260
+ );
14261
+ }
14262
+ }
14263
+ }
14264
+ };
14265
+ }
14266
+ });
14267
+
14083
14268
  // src/core/rules/conformance/unclaimed-source.ts
14084
14269
  var unclaimedSourceRule;
14085
14270
  var init_unclaimed_source = __esm({
@@ -15584,6 +15769,7 @@ var init_repository = __esm({
15584
15769
  init_integration_sim_coverage();
15585
15770
  init_type_realization();
15586
15771
  init_type_shape();
15772
+ init_param_conformance();
15587
15773
  init_unclaimed_source();
15588
15774
  init_export_conformance();
15589
15775
  init_carried_debt();
@@ -15752,11 +15938,14 @@ var init_repository = __esm({
15752
15938
  integrationSimWiringRule,
15753
15939
  integrationSimCoverageRule,
15754
15940
  // 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.
15941
+ // it the SHAPE the spec claims — then the SIGNATURE, the third reading:
15942
+ // does the code take the arguments the contract promises. And then the
15943
+ // question no spec can ask from the spec side: which files are named by
15944
+ // nothing at all. Last in the family because that one reads what all the
15945
+ // others named.
15758
15946
  typeRealizationRule,
15759
15947
  typeShapeRule,
15948
+ paramConformanceRule,
15760
15949
  unclaimedSourceRule,
15761
15950
  exportConformanceRule,
15762
15951
  couplingRule,
@@ -15925,6 +16114,7 @@ function walkExact(ts, sourceText, fileName) {
15925
16114
  const fieldTypes = /* @__PURE__ */ new Map();
15926
16115
  const localTypes = /* @__PURE__ */ new Map();
15927
16116
  const typeShapes = /* @__PURE__ */ new Map();
16117
+ const functionParams = /* @__PURE__ */ new Map();
15928
16118
  const mutableBindings = /* @__PURE__ */ new Set();
15929
16119
  const schemaConstants = /* @__PURE__ */ new Map();
15930
16120
  const derivedAliases = [];
@@ -16054,6 +16244,14 @@ function walkExact(ts, sourceText, fileName) {
16054
16244
  }
16055
16245
  return void 0;
16056
16246
  };
16247
+ const declaredParameters = (fn) => (fn.parameters ?? []).map((parameter) => {
16248
+ const fact = {
16249
+ optional: !!parameter.questionToken || !!parameter.initializer || !!parameter.dotDotDotToken
16250
+ };
16251
+ if (ts.isIdentifier(parameter.name)) fact.name = parameter.name.text;
16252
+ if (parameter.type) fact.type = parameter.type.getText(sf).replace(/\s+/g, " ").trim();
16253
+ return fact;
16254
+ });
16057
16255
  const collectFunctionFacts = (fn) => {
16058
16256
  let score = 1;
16059
16257
  const callees = /* @__PURE__ */ new Map();
@@ -16096,6 +16294,9 @@ function walkExact(ts, sourceText, fileName) {
16096
16294
  const sites = calls.get(fnName) ?? /* @__PURE__ */ new Map();
16097
16295
  for (const [key, site] of facts.callees) sites.set(key, site);
16098
16296
  calls.set(fnName, sites);
16297
+ const signatures = functionParams.get(fnName) ?? [];
16298
+ signatures.push(declaredParameters(node));
16299
+ functionParams.set(fnName, signatures);
16099
16300
  }
16100
16301
  if (ts.isPropertyDeclaration(node)) recordFieldType(node.name, node.type);
16101
16302
  else if (ts.isParameter(node) && node.parent && ts.isConstructorDeclaration(node.parent) && isParameterProperty(node)) {
@@ -16203,7 +16404,7 @@ function walkExact(ts, sourceText, fileName) {
16203
16404
  typeShapes.set(alias.name, { origin: "derived", fields: schemaMembers(literal), methods: [] });
16204
16405
  }
16205
16406
  const reexportOnly = sf.statements.length > 0 && sf.statements.every((st) => ts.isExportDeclaration(st) && !!st.moduleSpecifier);
16206
- return { declared, anchors, exported, imports, reexports, starExports, namedReexports, complexity, calls, importBindings, typeOnlyBindings, fieldTypes, localTypes, typeShapes, mutableBindings, reexportOnly };
16407
+ return { declared, anchors, exported, imports, reexports, starExports, namedReexports, complexity, calls, importBindings, typeOnlyBindings, fieldTypes, localTypes, typeShapes, functionParams, mutableBindings, reexportOnly };
16207
16408
  }
16208
16409
  function resolveRelativeModule(fromFile, specifier) {
16209
16410
  if (!specifier.startsWith(".")) return null;
@@ -16386,6 +16587,7 @@ function buildCodeModel(implementations, types, projectRoot2, sourceRoots = [],
16386
16587
  fieldTypes: Object.fromEntries([...facts.fieldTypes].map(([k, v]) => [k, [...v]])),
16387
16588
  localTypes: Object.fromEntries([...facts.localTypes].map(([k, v]) => [k, [...v]])),
16388
16589
  typeShapes: Object.fromEntries(facts.typeShapes),
16590
+ functionParams: Object.fromEntries(facts.functionParams),
16389
16591
  topLevelMutableBindings: [...facts.mutableBindings],
16390
16592
  reexportOnly: facts.reexportOnly
16391
16593
  };
@@ -30005,6 +30207,7 @@ function createMcpServer(options = {}) {
30005
30207
  sourcePath: import_zod11.z.string().optional().describe("Optional: target source code file path relative to project root \u2014 for a chained subproject's implementation (qualified id), relative to that subproject's root; a path given relative to this root that lands inside the subproject is re-expressed for you"),
30006
30208
  simPath: import_zod11.z.string().optional().describe("Optional: the committed integration-sim harness file (project-relative; N:1 sharing allowed). The validator proves it exists and its import graph wires the REAL modules (this component + each direct dependency; technology adapters may stay faked) \u2014 running it is CI's job. Declaring the first simPath in a subsystem activates MISSING_INTEGRATION_SIM for its other complete non-leaf implementations"),
30007
30209
  technologies: import_zod11.z.array(import_zod11.z.string()).optional().describe(`External technologies this implementation binds to (e.g. ["mysql"]) \u2014 declares this component's ownership tree as the technology's home; references outside it are flagged (TECH_LEAKAGE) and contract identifiers must stay intent-language. Only for Adapter/Store/Registry/Index components.`),
30210
+ injectedParams: import_zod11.z.array(import_zod11.z.string()).optional().describe("The parameter names this realization takes BEFORE the ones its contract declares \u2014 a config object, a data root, the transport handles a portal is handed. Supplied by whatever wires the component up, never by the caller the contract describes, which is why they belong here and not to the contract: another realization may hold them as fields instead. Declared rather than guessed, because a leading parameter the contract does not name cannot be told from one it named under a different name (`seed(config)` realized as `bootstrapInstance(cfg)`); a leading parameter this list does not name is reported (UNDECLARED_PARAM). Only a LEADING run is dropped \u2014 one of these names appearing after the contract's own parameters is an argument in the middle of the caller's list, not wiring"),
30008
30211
  detail: detailEnum.optional().describe("Spec-level narrative detail default for all methods"),
30009
30212
  conformance: conformanceEnum.optional().describe("Spec-level conformance tier default: declared | anchored | off (omitted = stereotype default: Portal \u2192 anchored, else declared)"),
30010
30213
  methods: import_zod11.z.array(import_zod11.z.object(implMethodShape).strict()).optional().describe("Method implementations containing L5 narratives"),
@@ -30015,11 +30218,11 @@ function createMcpServer(options = {}) {
30015
30218
  server,
30016
30219
  "sdd_write_narrative",
30017
30220
  {
30018
- description: "Write L4 Concrete Implementation spec containing L5 method narratives. Narratives are a FLAT ordered step list; flow steps (branch/switch/loop/try/parallel/jump/return/throw) jump by step number \u2014 blocks are just skipped regions. Steps may declare a `label` anchor, and every jump field has a *Label twin (toLabel, onTrueLabel, endLabel, \u2026) resolved to step numbers at write time \u2014 prefer labels over hand-counted numbers; an unresolvable label rejects the write. Detail dial per method: full (narrative required) | calls-only (call choreography suffices) | intent (prose instead of steps); omitted = stereotype default (Portal/Observer/Adapter: calls-only, Store/Index/Registry: intent, else full). A method whose narrative shows no steps declares the calls it makes in `calls` (one \"<component>.<method>\" each): the reachability walk takes exactly those edges and no others, so an intent-level method that reaches a collaborator must name it or that collaborator is reported unused. Conformance dial per method or spec: declared | anchored | off \u2014 how strictly structural conformance requires contract methods to be realized in their source file (omitted = Portal: anchored, else declared). A method whose body lives in its own file names it in the method's sourcePath; the implementation's sourcePath is the default for every method that names none. Re-authoring an existing id REPLACES the method list: a method left out of the input is REMOVED together with its narrative (and reported); spec-level lint/ext are carried forward, and the stored status is kept unless this input states a higher one. The answer carries a write receipt as structured content beside the sentence \u2014 the status written, whether a spec already held the id, and the notices a restatement raised, each as its own entry.",
30221
+ description: "Write L4 Concrete Implementation spec containing L5 method narratives. Narratives are a FLAT ordered step list; flow steps (branch/switch/loop/try/parallel/jump/return/throw) jump by step number \u2014 blocks are just skipped regions. Steps may declare a `label` anchor, and every jump field has a *Label twin (toLabel, onTrueLabel, endLabel, \u2026) resolved to step numbers at write time \u2014 prefer labels over hand-counted numbers; an unresolvable label rejects the write. Detail dial per method: full (narrative required) | calls-only (call choreography suffices) | intent (prose instead of steps); omitted = stereotype default (Portal/Observer/Adapter: calls-only, Store/Index/Registry: intent, else full). A method whose narrative shows no steps declares the calls it makes in `calls` (one \"<component>.<method>\" each): the reachability walk takes exactly those edges and no others, so an intent-level method that reaches a collaborator must name it or that collaborator is reported unused. Conformance dial per method or spec: declared | anchored | off \u2014 how strictly structural conformance requires contract methods to be realized in their source file (omitted = Portal: anchored, else declared). A method whose body lives in its own file names it in the method's sourcePath; the implementation's sourcePath is the default for every method that names none. Parameters the realization takes BEFORE its contract's own \u2014 a config object, a data root, a portal's transport handles \u2014 are wiring, and are declared once for the spec in injectedParams; a leading parameter it does not name is reported against the contract (UNDECLARED_PARAM). Re-authoring an existing id REPLACES the method list: a method left out of the input is REMOVED together with its narrative (and reported); spec-level lint/ext are carried forward, and the stored status is kept unless this input states a higher one. The answer carries a write receipt as structured content beside the sentence \u2014 the status written, whether a spec already held the id, and the notices a restatement raised, each as its own entry.",
30019
30222
  inputSchema: implInput,
30020
30223
  outputSchema: specWriteReceiptOutput
30021
30224
  },
30022
- ({ id, name, description, contract, sourcePath, simPath, technologies, detail, conformance, methods, status: status2 }) => {
30225
+ ({ id, name, description, contract, sourcePath, simPath, technologies, injectedParams, detail, conformance, methods, status: status2 }) => {
30023
30226
  try {
30024
30227
  const { loadInterfaceSpec: loadInterfaceSpec2, loadImplementationSpec: loadImplementationSpec2, saveImplementationSpec: saveImplementationSpec2 } = requireSpecs();
30025
30228
  const intf = loadInterfaceSpec2(contract);
@@ -30051,6 +30254,7 @@ function createMcpServer(options = {}) {
30051
30254
  sourcePath,
30052
30255
  simPath,
30053
30256
  technologies,
30257
+ injectedParams,
30054
30258
  detail,
30055
30259
  conformance,
30056
30260
  // Post-resolution every cases/catches entry has its numeric step —