@metaobjectsdev/cli 0.24.1 → 0.24.2

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.
@@ -0,0 +1,318 @@
1
+ // `meta verify` — the requirement AUTHORING lint.
2
+ //
3
+ // A different kind of claim from the gate in `requirement-check.ts`, which is why
4
+ // it is a separate function rather than more branches inside `checkRequirements`.
5
+ //
6
+ // THE GATE referential integrity — links sit at or below the floor, nesting
7
+ // agrees with levels, references resolve. A finding there means the
8
+ // ledger DISAGREES WITH THE MODEL.
9
+ // THE LINT authoring quality — the name is addressable, the prose slots hold
10
+ // distinct content, nothing was written into a slot no surface reads.
11
+ // A finding here means the ledger is INTERNALLY WASTEFUL: it still
12
+ // agrees with the model, it just records less than its author thinks.
13
+ //
14
+ // Keeping them apart is not tidiness. `meta verify` prints at most 20 warnings, and
15
+ // a ledger with 240 entries can produce hundreds of prose findings — enough to push
16
+ // every WARN_REQUIREMENT_OBJECT_UNCLAIMED off the end of the list. Two sections with
17
+ // two caps means the lint cannot drown the gate.
18
+ //
19
+ // IT READS THE LOADED MODEL, NEVER THE FILES. Extensions and overlays mean the text
20
+ // on disk is not the effective model: an attr set in one file and overridden in
21
+ // another, or inherited through `extends`, reads differently from every angle except
22
+ // the loaded tree. A raw-file linter would be wrong for any project using either.
23
+ //
24
+ // EVERY FINDING IS A WARNING, BY CONSTRUCTION — not by a switch. Stated as a rule so
25
+ // the next check added here follows it: a check newly added to a shipping gate cannot
26
+ // be shown not to fire on an estate that already exists, and prose findings that turn
27
+ // `meta verify` red on upgrade teach people to switch the gate off, which costs more
28
+ // than the padding they caught. The precedent is object coverage, which stayed a
29
+ // warning because on one real estate it reported every entity in the repository.
30
+ //
31
+ // There is deliberately NO severity constant to flip. `verify` prints this section
32
+ // with `log.warn` unconditionally and computes its exit code from the GATE alone, so
33
+ // a constant here would have promised a promotion it could not deliver. If a check in
34
+ // this file ever has to fail a build, the honest move is to move the check into the
35
+ // gate — which is a decision about what the check IS, not a severity edit.
36
+ import { DOC_ATTR_DESCRIPTION, DOC_ATTR_SUMMARY, DOC_ATTR_TITLE, REQUIREMENT_ATTR_COUNTEREXAMPLE, REQUIREMENT_ATTR_STATEMENT, REQUIREMENT_ATTR_TRACKED_BY,
37
+ // The port's ONE identifier splitter, used here for its word boundaries rather
38
+ // than its underscores: `normalise` lowercases and maps every separator to a
39
+ // space, so `toSnakeCase(name)` and a hand-rolled camel split are identical
40
+ // downstream (checked over 20k random identifiers). Sharing it means the lint
41
+ // splits a name the same way the column/table/kebab namers do — a private copy
42
+ // would silently stop agreeing the day that rule is corrected.
43
+ toSnakeCase, } from "@metaobjectsdev/metadata";
44
+ // The SAME collection the gate walks, so the two sections of one `meta verify` run
45
+ // address a node identically — and the dotted path each reports is the address the
46
+ // requirement-test generator turns into the stub's filename. (Pinned by a test that
47
+ // compares this path set against `walkRequirements()`, the generator's own walk,
48
+ // rather than leaving the two to agree by inspection.)
49
+ import { collectAddressedRequirements, } from "./requirement-check.js";
50
+ export const WARN_REQUIREMENT_NAME_NOT_ADDRESSABLE = "WARN_REQUIREMENT_NAME_NOT_ADDRESSABLE";
51
+ export const WARN_REQUIREMENT_NAME_READS_AS_PROSE = "WARN_REQUIREMENT_NAME_READS_AS_PROSE";
52
+ export const WARN_REQUIREMENT_NAME_RESTATES_STATEMENT = "WARN_REQUIREMENT_NAME_RESTATES_STATEMENT";
53
+ export const WARN_REQUIREMENT_PROSE_DUPLICATED = "WARN_REQUIREMENT_PROSE_DUPLICATED";
54
+ export const WARN_REQUIREMENT_PROSE_EMPTY = "WARN_REQUIREMENT_PROSE_EMPTY";
55
+ export const WARN_REQUIREMENT_INERT_DOC_SLOT = "WARN_REQUIREMENT_INERT_DOC_SLOT";
56
+ export const WARN_REQUIREMENT_TITLE_IS_AN_ID = "WARN_REQUIREMENT_TITLE_IS_AN_ID";
57
+ /**
58
+ * Characters that break the two things a requirement's `name` IS.
59
+ *
60
+ * `.` the dotted-path separator. A name containing one is INDISTINGUISHABLE
61
+ * from nesting: a single node named "Orders.Recorded" and a node "Orders"
62
+ * containing a node "Recorded" both produce the path `Orders.Recorded`, so
63
+ * the address stops identifying one node and the two collide on the same
64
+ * emitted stub file.
65
+ * `/` `\` path separators. The default stub path is
66
+ * `requirements/<path>.test.ts`, so a name of `../../thing` writes OUTSIDE
67
+ * the stub directory — and `owns()`, which is what lets the runner reap a
68
+ * stub whose requirement was deleted, will never claim it back.
69
+ * the rest illegal in a filename on Windows, so the stub cannot be written at
70
+ * all there. `:` doubles as half of the `::` package separator.
71
+ *
72
+ * Every one of these loads today: the loader constrains a requirement's name no
73
+ * more than any other node's, and nothing downstream re-checks it.
74
+ *
75
+ * A SEAM, recorded so the decision stays visible: the general form of this rule is
76
+ * not requirement-specific at all — a `.` in ANY node's name breaks the dotted
77
+ * child-name addressing the whole metamodel uses — so it arguably belongs in the
78
+ * loader as a cross-port warning, beside WARN_ENUM_NORMALIZE_AMBIGUOUS. It is
79
+ * scoped here because the requirement surface is where it currently bites (a name
80
+ * becomes a stub FILENAME only here), and because promoting it means five ports,
81
+ * expected-warnings.json, conformance fixtures and a metamodelVersion question.
82
+ * That is an FR, not a line in this file.
83
+ */
84
+ const UNADDRESSABLE = /[./\\:*?"<>|\p{Cc}]/gu;
85
+ /** How one offending character is named in the message, so the author is told which
86
+ * one to remove rather than left to diff their name against a regex.
87
+ *
88
+ * Only `.` needs a written entry — the rest are self-describing once quoted, and a
89
+ * hand-written entry per character was a table that had to be kept in step with
90
+ * UNADDRESSABLE: adding a character to the regex and forgetting the table printed
91
+ * a bare code point where the character itself was meant. Derived, so it cannot
92
+ * drift. */
93
+ function describeChar(c) {
94
+ if (c === ".")
95
+ return "'.' (the dotted-path separator)";
96
+ // A control character has no printable form to quote, so name its code point.
97
+ return /\p{Cc}/u.test(c) ? `U+${c.charCodeAt(0).toString(16).padStart(4, "0")}` : `'${c}'`;
98
+ }
99
+ /**
100
+ * Word count at which a name stops reading as a label and starts reading as prose.
101
+ *
102
+ * A threshold, and the only one in this file — chosen high on purpose. Five words
103
+ * is still plausibly a label ("Order recording for placed orders"); six is a
104
+ * sentence. Under-firing is the right failure here: renaming a requirement changes
105
+ * its address AND its emitted stub filename, so a false positive asks the author to
106
+ * pay a migration for nothing.
107
+ */
108
+ const PROSE_WORD_COUNT = 6;
109
+ /**
110
+ * Slots that say nothing on a requirement — and the two deliberate exclusions.
111
+ *
112
+ * `summary` qualifies: `@statement` is REQUIRED on both subtypes and is already the
113
+ * one-line sentence, so a summary beside it can only repeat it, and nothing reads
114
+ * it. `spec/capability-ledger.md`'s requirement attribute table does not list it.
115
+ *
116
+ * `title` is NOT on this list, and an earlier version of this file had it there —
117
+ * wrongly. That same attribute table charters it BY NAME on a requirement ("a short
118
+ * noun-phrase label — `name` is an identifier, this is what an index shows"), which
119
+ * is exactly this node type's situation: a requirement's address renders as a dotted
120
+ * camelCase path. Measured against three real ledgers the ban would have told two
121
+ * adopters to delete 355 authored labels, 123 of which carry words the name does not.
122
+ * That the requirements page does not render `title` yet is a gap in the RENDERER,
123
+ * and a tool reporting its own backlog in an adopter's terminal is noise.
124
+ *
125
+ * `notes` is excluded for the opposite reason: chartered internal-only, so being
126
+ * unrendered is the point of it.
127
+ */
128
+ const INERT_SLOTS = [DOC_ATTR_SUMMARY];
129
+ /**
130
+ * A catalogue or ticket id at the head of a label — `FR-448 …`, `PLAT-77 …`.
131
+ *
132
+ * The real failure the id case represents is FIELD OVERLOADING, not an unread slot:
133
+ * a citation lives in the display label because nothing else was offered, and
134
+ * `@trackedBy` (free-form, deliberately never resolved) is what was. The measured
135
+ * values carry an id AND a noun phrase — "FR-448 — prompt construction as typed
136
+ * payloads through a render engine" — so the fix is to SPLIT them. Moving the whole
137
+ * string to `@trackedBy` would throw the label away, which is why the message says
138
+ * split rather than move.
139
+ */
140
+ const TITLE_IS_AN_ID = /^[A-Z]{2,}[- ]?\d+/;
141
+ /** Lowercase, drop everything that is not alphanumeric, collapse the gaps.
142
+ * Two slots "say the same thing" only if they survive this identically — no
143
+ * similarity score, no threshold. A fuzzy match on prose produces findings the
144
+ * author can argue with, and a gate people argue with is a gate people mute. */
145
+ function normalise(text) {
146
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
147
+ }
148
+ /** Whether `text`'s LEADING sentence says the same thing as `key`. False when the
149
+ * text is a single sentence — the caller's whole-string comparison covers that.
150
+ *
151
+ * Newlines are collapsed FIRST. A `description` is routinely authored as a YAML
152
+ * literal block, so the repeated opening sentence usually wraps — and `.` in a JS
153
+ * regex does not cross a newline, which made this arm silently miss exactly the
154
+ * authoring style most likely to trip it. The whole-string arm never had the bug
155
+ * because `normalise` already flattens whitespace. */
156
+ function leadingSentenceMatches(text, key) {
157
+ const flat = text.replace(/\s+/g, " ").trim();
158
+ const m = /^(.+?[.!?])\s+\S/.exec(flat);
159
+ return m?.[1] !== undefined && normalise(m[1]) === key;
160
+ }
161
+ /** A resolving string read, normalised so "declared but blank" and "absent" are
162
+ * distinguishable — the first is a finding, the second usually is not.
163
+ * `attr()` RESOLVES in TypeScript (ADR-0039), so a requirement inheriting its
164
+ * prose through `extends` is linted on what it effectively carries. */
165
+ function readSlot(node, name) {
166
+ const raw = node.attr(name);
167
+ return typeof raw === "string" ? raw : undefined;
168
+ }
169
+ /**
170
+ * An OWN-ONLY string read — one of the two sanctioned `own*()` uses in this file
171
+ * (ADR-0039 requires every such call to name its case).
172
+ *
173
+ * The checks split by what they are ABOUT, and the split decides the accessor:
174
+ *
175
+ * RESOLVING a check about what a node effectively SAYS. Two slots holding one
176
+ * sentence is a property of the effective node — a child may override
177
+ * one slot and inherit the other, and only the resolved pair shows it.
178
+ * OWN-ONLY a check about a DECLARATION, whose fix is a single edit at a single
179
+ * node. `title` set once on an abstract is inherited by every child, so
180
+ * a resolving read reports it once per child at addresses where the
181
+ * author will find no `title` to delete — on a ledger using the shared
182
+ * abstract idiom, one mistake can fill the whole lint cap with lines
183
+ * nobody can act on where they are pointed.
184
+ */
185
+ function readOwnSlot(node, name) {
186
+ const raw = node.ownAttr(name);
187
+ return typeof raw === "string" ? raw : undefined;
188
+ }
189
+ /** Echo an authored value back inside a message without letting a long one take over
190
+ * the terminal. Findings are printed one per line and a `description`-length slot
191
+ * would wrap for a dozen of them. */
192
+ function excerpt(value) {
193
+ const LIMIT = 80;
194
+ const flat = value.replace(/\s+/g, " ").trim();
195
+ return JSON.stringify(flat.length > LIMIT ? `${flat.slice(0, LIMIT - 1)}…` : flat);
196
+ }
197
+ function warn(path, code, message) {
198
+ return { severity: "warn", code, path, message };
199
+ }
200
+ /**
201
+ * Lint every `requirement.*` node in the loaded model.
202
+ *
203
+ * Returns `[]` for a model declaring none — opt-in by declaration, the same way
204
+ * the gate and the docs surface are, so turning this on by default is a no-op for
205
+ * every project without a ledger.
206
+ *
207
+ * What it CANNOT tell you: whether a statement is true, whether a description is
208
+ * useful, or whether the counterexample would actually falsify the claim. Those
209
+ * are the judgements the ledger exists to record and no check reaches them. Every
210
+ * finding below is about a slot's MECHANICS — is this content reachable, is it
211
+ * distinct from its neighbour, is the name still an address.
212
+ */
213
+ export function lintRequirements(root,
214
+ /** The run's already-collected requirements, when one exists. The lint takes the
215
+ * ADDRESSES alone rather than the gate's full `RequirementScan`: it has nothing
216
+ * to do with who claims what, and asking for the claim set would make a
217
+ * standalone call pay for a resolution it never reads. */
218
+ addressed = collectAddressedRequirements(root)) {
219
+ const out = [];
220
+ for (const { node, path } of addressed) {
221
+ const name = node.name;
222
+ // -- the name is an address, not prose ------------------------------------
223
+ // EVERY problem with one name is reported in ONE finding. These were two
224
+ // branches of an if/else, so a name that was both padded and dotted reported
225
+ // only the dot: the author fixed it, re-ran, and was told about the padding on
226
+ // a second pass. One name is one edit, so it is one finding.
227
+ const problems = [];
228
+ const offenders = [...new Set(name.match(UNADDRESSABLE) ?? [])];
229
+ if (offenders.length > 0) {
230
+ problems.push(`contains ${offenders.map(describeChar).join(", ")}`);
231
+ }
232
+ if (name.trim() === "")
233
+ problems.push("is blank");
234
+ else if (name.trim() !== name)
235
+ problems.push("has leading or trailing whitespace");
236
+ if (problems.length > 0) {
237
+ out.push(warn(path, WARN_REQUIREMENT_NAME_NOT_ADDRESSABLE, `name ${JSON.stringify(name)} ${problems.join(", and ")}. A requirement's name is its ` +
238
+ `address — it is the segment of the dotted path '${path}' and the filename of its generated ` +
239
+ `test stub — so this either collides with nesting or produces a path the stub cannot be ` +
240
+ `written to.`));
241
+ }
242
+ const statementKey = normalise(readSlot(node, REQUIREMENT_ATTR_STATEMENT) ?? "");
243
+ // -- the name is not the claim --------------------------------------------
244
+ // Ordered before the prose-shape check and exclusive with it: when the name IS
245
+ // the statement, "rename it" is the wrong instruction. The instruction is "you
246
+ // have written the claim twice; @statement is the one that is read".
247
+ if (statementKey !== "" && normalise(toSnakeCase(name)) === statementKey) {
248
+ out.push(warn(path, WARN_REQUIREMENT_NAME_RESTATES_STATEMENT, `name '${name}' says the same thing as @statement. The claim belongs in @statement, which every ` +
249
+ `surface reads; the name is an address and is better as a short identifier.`));
250
+ }
251
+ else if (name.trim().split(/\s+/).filter((w) => w !== "").length >= PROSE_WORD_COUNT) {
252
+ out.push(warn(path, WARN_REQUIREMENT_NAME_READS_AS_PROSE, `name '${name}' reads as a sentence. The name is an address — the dotted path other entries and ` +
253
+ `the generated stub filename are built from — so a short identifier keeps both legible. Put the ` +
254
+ `prose in @statement.`));
255
+ }
256
+ // -- required prose that is present but says nothing -----------------------
257
+ // The loader enforces PRESENCE (min: 1), never content, so `statement: ""`
258
+ // loads clean today and satisfies a required attribute with nothing in it.
259
+ for (const slot of [REQUIREMENT_ATTR_STATEMENT, REQUIREMENT_ATTR_COUNTEREXAMPLE]) {
260
+ // OWN-ONLY: the fix is deleting or filling one declaration. See readOwnSlot.
261
+ const value = readOwnSlot(node, slot);
262
+ if (value !== undefined && value.trim() === "") {
263
+ out.push(warn(path, WARN_REQUIREMENT_PROSE_EMPTY, `@${slot} is declared but empty. The loader requires the attribute to be present, not to say ` +
264
+ `anything — an empty one passes every check while recording nothing.`));
265
+ }
266
+ }
267
+ // -- two slots holding one sentence ---------------------------------------
268
+ // The padding failure the authoring guidance names: restating the claim under a
269
+ // second heading reads as diligence and makes every later reader trust the
270
+ // ledger less. Only EXACT repeats are reported, whole or as the description's
271
+ // leading sentence; paraphrase is the broader failure and no mechanical rule
272
+ // reaches it without inventing findings.
273
+ if (statementKey !== "") {
274
+ // No blank-check needed: this arm only runs when `statementKey` is non-empty,
275
+ // and a blank description normalises to "" — which cannot equal it. That makes
276
+ // this guard identical to the `counterexample` arm below.
277
+ const description = readSlot(node, DOC_ATTR_DESCRIPTION);
278
+ if (description !== undefined) {
279
+ if (normalise(description) === statementKey) {
280
+ out.push(warn(path, WARN_REQUIREMENT_PROSE_DUPLICATED, `description repeats @statement verbatim. @statement already IS the description of what the ` +
281
+ `requirement is; description holds the SCOPE — what the claim covers, what it deliberately ` +
282
+ `does not, which sibling entry owns the rest. If the scope is obvious, leave it off.`));
283
+ }
284
+ else if (leadingSentenceMatches(description, statementKey)) {
285
+ out.push(warn(path, WARN_REQUIREMENT_PROSE_DUPLICATED, `description opens by repeating @statement verbatim, then continues. Drop the first sentence — ` +
286
+ `it is already read from @statement, and description is only the scope that follows it.`));
287
+ }
288
+ }
289
+ const counterexample = readSlot(node, REQUIREMENT_ATTR_COUNTEREXAMPLE);
290
+ if (counterexample !== undefined && normalise(counterexample) === statementKey) {
291
+ out.push(warn(path, WARN_REQUIREMENT_PROSE_DUPLICATED, `@counterexample repeats @statement verbatim. It must describe what BREAKING the claim looks ` +
292
+ `like — the thing you could point at to falsify it — which is what makes the claim checkable.`));
293
+ }
294
+ }
295
+ // -- content written where nothing reads it --------------------------------
296
+ for (const slot of INERT_SLOTS) {
297
+ // OWN-ONLY: the fix is deleting one attribute. See readOwnSlot.
298
+ const value = readOwnSlot(node, slot);
299
+ if (value === undefined || value.trim() === "")
300
+ continue;
301
+ out.push(warn(path, WARN_REQUIREMENT_INERT_DOC_SLOT, `@${slot} says nothing here that @${REQUIREMENT_ATTR_STATEMENT} does not. A requirement's ` +
302
+ `statement is REQUIRED and is already the one-line sentence, so a summary beside it can only ` +
303
+ `repeat it — and no requirement surface reads it. Its content is invisible: ${excerpt(value)}. ` +
304
+ `Delete it. (@title is different and is NOT flagged: it is chartered as the entry's LABEL, ` +
305
+ `because a requirement's name is an identifier.)`));
306
+ }
307
+ // -- an id is not a label -------------------------------------------------
308
+ const title = readOwnSlot(node, DOC_ATTR_TITLE);
309
+ if (title !== undefined && TITLE_IS_AN_ID.test(title.trim())) {
310
+ out.push(warn(path, WARN_REQUIREMENT_TITLE_IS_AN_ID, `@title opens with a catalogue or ticket id: ${excerpt(title)}. A title is a NOUN PHRASE and ` +
311
+ `an id is not a name, so this is two things in one slot. SPLIT them — put the id in ` +
312
+ `@${REQUIREMENT_ATTR_TRACKED_BY}, which is the free-form reference slot and IS read, and leave ` +
313
+ `the phrase as the title. Moving the whole string would throw the label away.`));
314
+ }
315
+ }
316
+ return out;
317
+ }
318
+ //# sourceMappingURL=requirement-lint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requirement-lint.js","sourceRoot":"","sources":["../../../src/lib/requirement-lint.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaobjectsdev/cli",
3
- "version": "0.24.1",
3
+ "version": "0.24.2",
4
4
  "description": "CLI for MetaObjects: scaffold, codegen, migrate, and drift-detection commands.",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -49,15 +49,15 @@
49
49
  ],
50
50
  "dependencies": {
51
51
  "@libsql/kysely-libsql": "^0.4.0",
52
- "@metaobjectsdev/codegen-ts": "0.24.1",
53
- "@metaobjectsdev/codegen-ts-react": "0.24.1",
54
- "@metaobjectsdev/codegen-ts-tanstack": "0.24.1",
55
- "@metaobjectsdev/docs-site": "0.24.1",
56
- "@metaobjectsdev/metadata": "0.24.1",
57
- "@metaobjectsdev/migrate-ts": "0.24.1",
58
- "@metaobjectsdev/render": "0.24.1",
59
- "@metaobjectsdev/runtime-ts": "0.24.1",
60
- "@metaobjectsdev/sdk": "0.24.1",
52
+ "@metaobjectsdev/codegen-ts": "0.24.2",
53
+ "@metaobjectsdev/codegen-ts-react": "0.24.2",
54
+ "@metaobjectsdev/codegen-ts-tanstack": "0.24.2",
55
+ "@metaobjectsdev/docs-site": "0.24.2",
56
+ "@metaobjectsdev/metadata": "0.24.2",
57
+ "@metaobjectsdev/migrate-ts": "0.24.2",
58
+ "@metaobjectsdev/render": "0.24.2",
59
+ "@metaobjectsdev/runtime-ts": "0.24.2",
60
+ "@metaobjectsdev/sdk": "0.24.2",
61
61
  "@toon-format/toon": "^2.3.0",
62
62
  "jiti": "^2.4.0"
63
63
  },
@@ -1,6 +1,8 @@
1
1
  // server/typescript/packages/cli/src/commands/upgrade.ts
2
2
  //
3
- // `meta upgrade` — rewrite retired vocabulary in this project's metadata.
3
+ // `meta upgrade` — rewrite what the current loader no longer accepts in this project's
4
+ // metadata: RETIRED vocabulary (`retired-vocabulary.ts`) and ATTRIBUTE CONTRADICTIONS —
5
+ // pairs of live attributes that may not sit on one node (`attr-contradictions.ts`).
4
6
  //
5
7
  // DELIBERATELY NOT `meta migrate`. That command owns DATABASE SCHEMA (ADR-0015) and an
6
8
  // adopter reading `migrate` expects DDL. Overloading it with a metadata rewrite would make
@@ -56,9 +58,10 @@ export async function upgradeCommand(args: string[], cwd: string): Promise<numbe
56
58
  if ((err as Error).message === "__help__") {
57
59
  log.info(
58
60
  "meta upgrade [<project>] [--to <version>] [--apply]\n\n" +
59
- " Rewrites retired metadata vocabulary in JSON and YAML metadata alike.\n" +
61
+ " Rewrites metadata the current loader no longer accepts, in JSON and YAML alike:\n" +
62
+ " retired vocabulary, and pairs of live attributes that may no longer sit together.\n" +
60
63
  " Previews by default; --apply writes.\n" +
61
- " Retirements needing a human decision are REFUSED and listed with their guide.\n\n" +
64
+ " Changes needing a human decision are REFUSED and listed with their guide.\n\n" +
62
65
  " Exit: 0 clean · 1 refusals remain · 2 bad usage · 3 some files could not be read.",
63
66
  );
64
67
  return 0;
@@ -139,13 +142,16 @@ export async function upgradeCommand(args: string[], cwd: string): Promise<numbe
139
142
  );
140
143
  }
141
144
 
142
- // Every conclusion states how many files it is a conclusion ABOUT. "No retired vocabulary
143
- // found" read on its own says the estate is clean, and it is the last line, so it is the
144
- // one that sticks — on the estate that reported this, it was the opposite of the truth.
145
+ // Every conclusion states how many files it is a conclusion ABOUT. A bare "nothing found"
146
+ // read on its own says the estate is clean, and it is the last line, so it is the one that
147
+ // sticks — on the estate that reported this, it was the opposite of the truth.
148
+ //
149
+ // It says "nothing to rewrite", not "your metadata loads": this command knows about retired
150
+ // vocabulary and attribute contradictions, and nothing else. `meta verify` owns the verdict.
145
151
  const scope = `${checked} file(s) checked${notChecked.length > 0 ? `, ${notChecked.length} NOT checked` : ""}`;
146
152
 
147
153
  if (totalChanges === 0 && totalRefusals === 0) {
148
- log.info(`meta upgrade — no retired vocabulary found (${scope}).`);
154
+ log.info(`meta upgrade — nothing to rewrite (${scope}).`);
149
155
  } else if (totalChanges === 0) {
150
156
  // Refusals only. Reporting "rewrote 0 declarations", or advertising `--apply`, both
151
157
  // promise an action guaranteed to change nothing and bury the fact that the remaining
@@ -17,7 +17,10 @@ import { FileProvider } from "../lib/file-provider.js";
17
17
  import { derivePayloadFieldTree } from "../lib/payload-field-tree.js";
18
18
  import { loadMemoryOptionsFrom, loadMetaobjectsConfig, resolveGenCollection, resolveGenConfigDir } from "../lib/load-metaobjects-config.js";
19
19
  import { computeCodegenDrift } from "../lib/codegen-drift.js";
20
- import { checkRequirements, summariseRequirements } from "../lib/requirement-check.js";
20
+ import {
21
+ checkRequirements, summariseRequirements, scanRequirements, type Diagnostic,
22
+ } from "../lib/requirement-check.js";
23
+ import { lintRequirements } from "../lib/requirement-lint.js";
21
24
  import { resolveD1Config, resolveMigrateConfig } from "../lib/config.js";
22
25
  import {
23
26
  buildWranglerExecuteArgs,
@@ -461,12 +464,16 @@ export async function verifyCommand(
461
464
  // rule reaches the semantic cases. FR-038 retires the attribute rather than
462
465
  // narrowing it; the replacement generates the test FROM the requirement, so
463
466
  // the link is structural instead of a string the author picks.
464
- const diags = [...checkRequirements(root)];
467
+ // ONE scan for all three passes. The gate and the summary each used to walk the
468
+ // model AND resolve every @implementedBy claim for themselves — the resolution
469
+ // being the expensive half — and the lint added a third walk on top.
470
+ const scan = scanRequirements(root);
471
+ const diags = [...checkRequirements(root, scan)];
465
472
 
466
473
  // Printed on EVERY run, clean or not — a gate that says nothing when it
467
474
  // passes cannot be told apart from a gate that checked nothing, and the
468
475
  // recorded-gap counts are the whole reason to keep a ledger.
469
- const s = summariseRequirements(root);
476
+ const s = summariseRequirements(root, scan);
470
477
  if (s !== undefined) {
471
478
  const order = [...REQUIREMENT_STATUSES];
472
479
  const parts = order
@@ -493,15 +500,51 @@ export async function verifyCommand(
493
500
  }
494
501
  }
495
502
 
496
- if (diags.length === 0) return 0;
497
503
  const errors = diags.filter((d) => d.severity === "error");
498
504
  const warns = diags.filter((d) => d.severity === "warn");
499
505
  const CAP = 20;
500
- for (const d of errors) log.error(` ${d.code}${d.name !== undefined ? ` [${d.name}]` : ""}: ${d.message}`);
501
- for (const d of warns.slice(0, CAP)) {
502
- log.warn(` ${d.code}${d.name !== undefined ? ` [${d.name}]` : ""}: ${d.message}`);
506
+ const fmt = (d: Diagnostic): string =>
507
+ ` ${d.code}${d.path !== undefined ? ` [${d.path}]` : ""}: ${d.message}`;
508
+ /** Print a capped run of warnings. Capped per SECTION, never across them: a
509
+ * ledger of a few hundred entries can produce hundreds of prose findings, and a
510
+ * shared cap would let the advisory lint push every gate warning off the end. */
511
+ const warnCapped = (ds: readonly Diagnostic[]): void => {
512
+ for (const d of ds.slice(0, CAP)) log.warn(fmt(d));
513
+ if (ds.length > CAP) log.warn(` …and ${ds.length - CAP} more.`);
514
+ };
515
+ for (const d of errors) log.error(fmt(d));
516
+ warnCapped(warns);
517
+
518
+ // -- the authoring lint: its own section, its own cap ----------------------
519
+ // Separate from the gate above because it makes a different claim. The gate
520
+ // says the ledger DISAGREES WITH THE MODEL; the lint says it agrees but
521
+ // records less than its author thinks — a name that is not an address, two
522
+ // slots holding one sentence, prose written where no surface reads it.
523
+ //
524
+ // The separate cap is the load-bearing part. A ledger of a few hundred
525
+ // entries can produce hundreds of prose findings, and under one shared cap
526
+ // those would push every WARN_REQUIREMENT_OBJECT_UNCLAIMED off the end of the
527
+ // list — the lint would silence the gate it was added beside. Nothing here
528
+ // reaches the exit code: every lint finding is a warning by construction.
529
+ // Muted with --no-requirement-lint or META_NO_REQUIREMENT_LINT=1, the same pair
530
+ // its sibling advisory offers. It mutes the ADVISORY half only — the gate above
531
+ // still runs and can still fail the build, which is the point of the split.
532
+ // `s === undefined` means the model declares no requirements at all, which the
533
+ // two passes above have already established: opt-in by declaration, decided
534
+ // without a third walk to rediscover it.
535
+ const lint = s === undefined
536
+ || flags.noRequirementLint
537
+ || process.env.META_NO_REQUIREMENT_LINT === "1"
538
+ ? []
539
+ : lintRequirements(root, scan.addressed);
540
+ if (lint.length > 0) {
541
+ log.warn(
542
+ `meta verify — requirements: ${lint.length} authoring warning(s) ` +
543
+ `(advisory — does not fail the build):`,
544
+ );
545
+ warnCapped(lint);
503
546
  }
504
- if (warns.length > CAP) log.warn(` …and ${warns.length - CAP} more.`);
547
+
505
548
  if (errors.length > 0) {
506
549
  log.error(`meta verify — requirements: ${errors.length} error(s).`);
507
550
  return 1;
package/src/index.ts CHANGED
@@ -145,6 +145,7 @@ FLAGS:
145
145
  --remote Target remote D1 instead of local (only with --dialect d1) —
146
146
  the ONLY way to verify the actual deployed D1 database
147
147
  --no-antipatterns Suppress the advisory "hand-rolled what MetaObjects can model" pass
148
+ --no-requirement-lint Suppress the advisory requirement AUTHORING lint (not the gate)
148
149
  --help, -h Print this help
149
150
 
150
151
  A bare 'meta verify' also runs an ADVISORY anti-pattern pass: it scans your authored
@@ -152,6 +153,12 @@ source for hand-rolled aggregates, money-as-float, and CHECK-IN enums and points
152
153
  at the construct that models them (origin.aggregate / field.currency / field.enum).
153
154
  Warnings only — it never fails the build. Opt out with --no-antipatterns or
154
155
  META_NO_ANTIPATTERNS=1.
156
+
157
+ If the project declares requirement.* nodes, verify also prints an ADVISORY authoring
158
+ lint in its own section: names that are not addressable, prose slots holding one
159
+ sentence twice, content written where no surface reads it. Warnings only — it can
160
+ never fail the build. Opt out with --no-requirement-lint or META_NO_REQUIREMENT_LINT=1.
161
+ The requirements GATE itself (dangling refs, link floor, levels) always runs.
155
162
  `,
156
163
  export: `meta export — flatten loaded metadata to one canonical JSON artifact
157
164
 
package/src/lib/args.ts CHANGED
@@ -259,6 +259,14 @@ export interface VerifyFlags {
259
259
  anyExplicit: boolean;
260
260
  /** Suppress the advisory anti-pattern (verify-as-teacher) pass. */
261
261
  noAntipatterns: boolean;
262
+ /**
263
+ * Suppress the advisory requirement AUTHORING lint — the second, prose-quality
264
+ * section, never the gate above it. Its own findings argue that a noisy advisory
265
+ * gets switched off wholesale, and a ledger mid-migration can print its capped
266
+ * twenty lines on every run for weeks; a mute for the advisory half is what keeps
267
+ * the half that CAN fail a build switched on. Same shape as --no-antipatterns.
268
+ */
269
+ noRequirementLint: boolean;
262
270
  /**
263
271
  * ADR-0023 strict-attr load opt-OUT (#96). `verify` is strict-by-default — an
264
272
  * undeclared/typo'd own `@attr` fails verify (ERR_UNKNOWN_ATTR). `--lax`
@@ -281,6 +289,7 @@ export function parseVerifyArgs(argv: string[]): VerifyFlags {
281
289
  replay: { type: "boolean", default: false },
282
290
  "replay-snapshot": { type: "boolean", default: false },
283
291
  "no-antipatterns": { type: "boolean", default: false },
292
+ "no-requirement-lint": { type: "boolean", default: false },
284
293
  lax: { type: "boolean", default: false },
285
294
  "d1": { type: "string" },
286
295
  "remote": { type: "boolean", default: false },
@@ -330,6 +339,7 @@ export function parseVerifyArgs(argv: string[]): VerifyFlags {
330
339
  replaySnapshot,
331
340
  anyExplicit,
332
341
  noAntipatterns: !!values["no-antipatterns"],
342
+ noRequirementLint: !!values["no-requirement-lint"],
333
343
  lax: !!values.lax,
334
344
  d1: values.d1 as string | undefined,
335
345
  remote: !!values.remote,