Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,46 @@ until you regenerate.

### Fixed

- **A doc generator skipped by `meta gen` is now reported in every output form.** `meta docs`
is the door for documentation (ADR-0025), so `meta gen` warns about and skips a `docsFile()`
or `apiDocsFile()` listed under `generators`. That has not changed. What changed is who hears
about it:
- **`meta gen --format json|toon`** (the default on a pipe) printed the runner's warnings to
stderr only, and the result's `warnings` carried unknown-attribute findings alone, so the
document described a clean run with no API docs and no reason. `warnings[]` now carries
every warning the run printed: all of the runner's (not only this one), a generator's own
`ctx.warn`, the runtime-boundary advisory, and the stale-agent-context and ignored-manifest
advisories. `help[]` gains a `meta docs` next step when a doc generator was skipped. Text
output is unchanged.
- **A config that wires only doc generators** was told `no entities to generate` and to
`author entities in this project's metadata sources`, for a project that has them. The
summary, the next step and the text line now name the real reason.
- **`meta gen --list`** presented `api-docs` as one more generator to wire, and printed no
note on any row: the registry's notes for `docs` and `mermaid-er` stopped being rendered
when the catalog was rewritten. The `api-docs` and `docs` rows now read ``run by `meta
docs` `` with the reason under the row, a wired one reads ``WIRED but skipped by `meta
gen` ``, and `--probe` no longer prints `would emit 0` for them. In `--format json` the row
carries `note` and `door: "meta docs"`, and `project.wouldEmit` is `null` instead of `0`.
The `mermaid-er` row's note now states the back-compat fact: it is deprecated, a
`mermaidErDiagram()` already wired in `generators` still runs, and `meta docs` draws the
same diagram on its overview page. A neutral name wired as a string (`"docs"`,
`"mermaid-er"`), which `meta gen` refuses, reads ``WIRED by name, which `meta gen`
refuses`` (`project.refused` in `--format json`).
- **`meta verify`** said nothing about a config wired this way. It now reports each one as an
advisory row (rule `doc-generator-in-gen`, in `antiPatterns.rows[]`) that names
`meta verify --docs` as the gate that checks documentation. A neutral name wired as a
string is reported under rule `doc-generator-refused-by-gen`, because `meta gen` fails on
it rather than skipping it. Advisory: the exit code is unchanged.
- **The unknown-generator error** listed `api-docs` among the names `meta gen` accepts. It no
longer does, and it says documentation comes from `meta docs`.

`runGen()` returns a new `skippedDocGenerators: string[]` beside `warnings`, and
`@metaobjectsdev/codegen-ts` exports `isPackagedDocsGenerator` (skipped) and
`isNeutralGeneratorName` (refused), so a caller embedding the runner can ask rather than
match a sentence. TypeScript only: in the C#, Java, Kotlin and Python
ports documentation is a separate command (`dotnet meta docs`, `mvn metaobjects:docs`,
`metaobjects docs`) and no doc generator is registered for code generation, so there is
nothing to wire and nothing to skip.
- **One shared-enum rule in all five ports: a field keeps the shared type through any number
of `extends` hops.** An enum field that extends another field which itself reaches a shared
(package-level abstract) enum was resolved only when it extended the abstract directly, so a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ Other flags: `--layout flat|package`, `--base-url <url>`. Configure defaults in
(it documents what the codegen produced); with no config it is skipped with a note,
and the model surface still emits from metadata alone.

**Never wire `docsFile()` or `apiDocsFile()` into `generators`.** They are the engines
behind `meta docs`, so `meta gen` warns about each one it finds in the suite and skips it:
no docs are written by that run. The warning is on stderr, and in a structured run
(`--format json|toon`, the default on a pipe) it is in the result's `warnings[]` with a
`meta docs` next step in `help[]`. `meta gen --list` marks both rows ``run by `meta docs` ``
(`door: "meta docs"` in `--format json`) and `--probe` gives them no file count.
`meta verify` reports the wiring as an advisory row, rule `doc-generator-in-gen`. Remove
the entry and run `meta docs`.

**Do not add `mermaidErDiagram()` to a new config.** It is deprecated: `meta docs` already
draws the ER diagram on its overview page with nothing wired. A `mermaidErDiagram()`
already wired in `generators` still runs under `meta gen`. The stable names `"docs"` and
`"mermaid-er"` written as strings in `generators` are refused, so `meta gen` fails on
them; `meta verify` reports that as rule `doc-generator-refused-by-gen`.

**Before calling any generated code, read `./docs/api/AGENT-API.md`** — it has the
exact imports, signatures, payload field shapes, and runnable examples for this
project's generated API, so you don't have to guess them.
Expand Down
18 changes: 17 additions & 1 deletion docs/features/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,8 @@ on both commands.
the total:

```
meta gen --format json → { gen[], summary, help[], antiPatterns: { status, total, rows[] } }
meta gen --format json → { gen[], summary, help[], antiPatterns: { status, total, rows[] },
warnings[]? }
meta verify --format json → { verify[], exitCode, summary, help[],
antiPatterns: { status, total, rows[] },
requirements: { status, total, rows[] },
Expand All @@ -567,6 +568,21 @@ metadata and is recreated only because the migration alters a table it reads is
drift, so it appears in neither. `meta migrate` still emits that drop/create pair and
names the views in a note (`notes[]` in its structured output).

`meta gen`'s `warnings[]` carries **every warning the run printed**, in the order it
printed them: the runner's own (a generator that emitted nothing, an unsatisfied
`requires`, a hand-edited file it refused), a generator's `ctx.warn`, an unknown
attribute, and the CLI's advisories about a stale agent context or an ignored hash
manifest. The key is absent on a run that printed none. One of them is worth knowing by
name: a doc generator (`docsFile()`, `apiDocsFile()`) listed under `generators` is
**warned about and skipped**, because `meta docs` is the door for documentation
(ADR-0025). The result then says so in `warnings[]` and adds a `meta docs` next step to
`help[]`; `meta gen --list` marks those rows ``run by `meta docs` `` (`door` in
`--format json`), and `meta verify` reports the same wiring as an advisory row with rule
`doc-generator-in-gen`. A neutral stable name written as a string (`"docs"`,
`"mermaid-er"`) is refused rather than skipped, so `meta gen` fails on it: `meta gen --list`
marks that row ``WIRED by name, which `meta gen` refuses`` (`project.refused` in
`--format json`), and the advisory row's rule is `doc-generator-refused-by-gen`.

In a structured run every narration line moves to **stderr**, so stdout is one
parseable document. `--format` is honored by `gen`, `verify` and `migrate`; any
other command prints text and says so if you pass it. There is no `--json` flag —
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ Other flags: `--layout flat|package`, `--base-url <url>`. Configure defaults in
(it documents what the codegen produced); with no config it is skipped with a note,
and the model surface still emits from metadata alone.

**Never wire `docsFile()` or `apiDocsFile()` into `generators`.** They are the engines
behind `meta docs`, so `meta gen` warns about each one it finds in the suite and skips it:
no docs are written by that run. The warning is on stderr, and in a structured run
(`--format json|toon`, the default on a pipe) it is in the result's `warnings[]` with a
`meta docs` next step in `help[]`. `meta gen --list` marks both rows ``run by `meta docs` ``
(`door: "meta docs"` in `--format json`) and `--probe` gives them no file count.
`meta verify` reports the wiring as an advisory row, rule `doc-generator-in-gen`. Remove
the entry and run `meta docs`.

**Do not add `mermaidErDiagram()` to a new config.** It is deprecated: `meta docs` already
draws the ER diagram on its overview page with nothing wired. A `mermaidErDiagram()`
already wired in `generators` still runs under `meta gen`. The stable names `"docs"` and
`"mermaid-er"` written as strings in `generators` are refused, so `meta gen` fails on
them; `meta verify` reports that as rule `doc-generator-refused-by-gen`.

**Before calling any generated code, read `./docs/api/AGENT-API.md`** — it has the
exact imports, signatures, payload field shapes, and runnable examples for this
project's generated API, so you don't have to guess them.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ Other flags: `--layout flat|package`, `--base-url <url>`. Configure defaults in
(it documents what the codegen produced); with no config it is skipped with a note,
and the model surface still emits from metadata alone.

**Never wire `docsFile()` or `apiDocsFile()` into `generators`.** They are the engines
behind `meta docs`, so `meta gen` warns about each one it finds in the suite and skips it:
no docs are written by that run. The warning is on stderr, and in a structured run
(`--format json|toon`, the default on a pipe) it is in the result's `warnings[]` with a
`meta docs` next step in `help[]`. `meta gen --list` marks both rows ``run by `meta docs` ``
(`door: "meta docs"` in `--format json`) and `--probe` gives them no file count.
`meta verify` reports the wiring as an advisory row, rule `doc-generator-in-gen`. Remove
the entry and run `meta docs`.

**Do not add `mermaidErDiagram()` to a new config.** It is deprecated: `meta docs` already
draws the ER diagram on its overview page with nothing wired. A `mermaidErDiagram()`
already wired in `generators` still runs under `meta gen`. The stable names `"docs"` and
`"mermaid-er"` written as strings in `generators` are refused, so `meta gen` fails on
them; `meta verify` reports that as rule `doc-generator-refused-by-gen`.

**Before calling any generated code, read `./docs/api/AGENT-API.md`** — it has the
exact imports, signatures, payload field shapes, and runnable examples for this
project's generated API, so you don't have to guess them.
Expand Down
3 changes: 2 additions & 1 deletion fixtures/generator-registry-conformance/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
"concept": "Per-entity/template SDK API reference (generated-code API; human + agent forms).",
"tier": "native",
"layer": "docs",
"note": "Run by `meta docs` as its api surface (ADR-0025), not by `meta gen`: a `meta gen` config that lists it is warned about and skipped.",
"ports": ["typescript"]
},
"trace-helper": {
Expand Down Expand Up @@ -207,7 +208,7 @@
"concept": "Neutral Mermaid ER diagram of the entity/relationship model.",
"tier": "neutral",
"layer": "docs",
"note": "Neutral artifact owned by the docs engine (ADR-0020); surfaced via `meta docs`.",
"note": "Neutral artifact owned by the docs engine (ADR-0020). Deprecated under `meta gen`: a `mermaidErDiagram()` already wired in `generators` still runs; `meta docs` draws the same diagram on its overview page.",
"ports": ["typescript"]
}
}
Expand Down
21 changes: 15 additions & 6 deletions server/typescript/packages/cli/src/commands/gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,20 @@ export async function genCommand(args: string[], cwd: string, fmt: OutputFormat
// would find no manifest there and silently skip the nudge. `meta verify` makes
// the same call for the same reason; the two commands describe this and the
// anti-pattern scan below as one advisory pass, so they must scan one tree.
warnIfAgentContextStale(projectRoot);
//
// EVERY warning this run prints is also collected here, in the order printed, because
// the structured result carries them. They used to reach stderr only — and on a pipe,
// where the default format is the document, a reader of that document saw a clean run:
// a wired `apiDocsFile()` produced no API docs and the result gave no reason (ADR-0025
// has `meta gen` warn about and skip it). A new warning site adds to this list.
const warnings: string[] = [];
const advise = (line: string | undefined): void => { if (line !== undefined) warnings.push(line); };
advise(warnIfAgentContextStale(projectRoot));
// Advisory: the committed hash manifest is what makes hand-edit detection work on a
// machine that did not generate the output. Silent unless it is ignored. Keyed on
// projectRoot, not cwd, for the same reason its neighbour is — the manifest belongs to
// whichever directory `resolveCollection` decided the metadata lives in.
warnIfManifestIgnored(projectRoot);
advise(warnIfManifestIgnored(projectRoot));

let forgeConfig;
try {
Expand Down Expand Up @@ -162,7 +170,7 @@ export async function genCommand(args: string[], cwd: string, fmt: OutputFormat
const unknownAttrs = unknownAttrFindings.length > 0
? [...unknownAttrFindings, UNKNOWN_ATTR_GEN_ADVICE]
: [];
for (const w of unknownAttrs) log.warn(`warning: ${w}`);
for (const w of unknownAttrs) { log.warn(`warning: ${w}`); warnings.push(w); }

// FR-023 §11.1 item 2 — refuse a positional that names ONLY objects imported
// from a dependency and excluded from output by the default exclusion rule.
Expand Down Expand Up @@ -210,7 +218,7 @@ export async function genCommand(args: string[], cwd: string, fmt: OutputFormat
return 1;
}

for (const w of result.warnings) { log.warn(w); }
for (const w of result.warnings) { log.warn(w); warnings.push(w); }

// A first-run POINTER, not a warning: an empty selection is the designed state of a
// fresh `meta init`, so calling it a problem would make every new project start with
Expand Down Expand Up @@ -243,7 +251,7 @@ export async function genCommand(args: string[], cwd: string, fmt: OutputFormat
if (!cliConfig.dryRun) {
for (const w of runtimeBoundaryWarnings(
findRuntimeBoundaryCrossings(projectRoot, result.files.map((f) => f.path)),
)) log.warn(`warning: ${w}`);
)) { log.warn(`warning: ${w}`); warnings.push(w); }
}

const targetDirs = Array.from(new Set(
Expand Down Expand Up @@ -277,9 +285,10 @@ export async function genCommand(args: string[], cwd: string, fmt: OutputFormat
dialect: forgeConfig.dialect,
dryRun: cliConfig.dryRun,
// Text mode already printed these to stderr above; the structured forms carry them.
warnings: fmt === "text" ? [] : unknownAttrs,
warnings: fmt === "text" ? [] : warnings,
antiPatterns,
generatorCount: forgeConfig.generators?.length ?? 0,
skippedDocGenerators: result.skippedDocGenerators,
...(migrateAdvice !== undefined ? { migrateAdvice } : {}),
};
const output =
Expand Down
21 changes: 21 additions & 0 deletions server/typescript/packages/cli/src/commands/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ import {
libraryPrefixRows,
unindexedFkRows,
referentialActionConflictRows,
docGeneratorRows,
advisoryHelpLines,
} from "../lib/advisory.js";
import { scanForDocGeneratorsInGen, type DocGeneratorFinding } from "../lib/doc-generator-advisory.js";
import { scanForUnprovenancedLibraryPrefix } from "../lib/library-prefix-advisory.js";
import { scanForUnindexedForeignKeys, type UnindexedFkFinding } from "../lib/fk-index-advisory.js";
import { scanForReferentialActionConflicts, type ReferentialActionConflictFinding } from "../lib/referential-action-advisory.js";
Expand Down Expand Up @@ -1021,13 +1023,25 @@ export async function verifyCommand(
// advisory only — a model the schema builder refuses is migrate's to report.
}

// A doc generator wired under `generators`: `meta gen` skips it (or refuses its
// neutral name), and `--codegen` regenerates through the same runner without relaying
// its warnings — so this is the only place `verify` can say that the listed generator
// produces nothing here.
let docGenerators: DocGeneratorFinding[] = [];
try {
docGenerators = scanForDocGeneratorsInGen(forgeConfig);
} catch {
// advisory only — a config whose factory throws is `meta gen`'s to report.
}

antiPatternSection = ranSection([
...antiPatternRows(findings),
...missingBaseUrlRows(baseUrl),
...removedPropRows(removedProps),
...libraryPrefixRows(libraryPrefix),
...unindexedFkRows(unindexedFks),
...referentialActionConflictRows(actionConflicts),
...docGeneratorRows(docGenerators),
]);
if (findings.length > 0) {
log.warn(
Expand Down Expand Up @@ -1071,6 +1085,13 @@ export async function verifyCommand(
);
warnCapped(libraryPrefix.map((f) => ` ${f.message}`), flags.limit, { structured });
}
if (docGenerators.length > 0) {
log.warn(
`meta verify — ${docGenerators.length} doc generator(s) listed under \`generators\`, ` +
`which \`meta gen\` does not run (advisory — does not fail the build):`,
);
warnCapped(docGenerators.map((f) => ` ${f.message}`), flags.limit, { structured });
}
}

// -- template (prompt / output) drift --------------------------------------
Expand Down
29 changes: 29 additions & 0 deletions server/typescript/packages/cli/src/lib/advisory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ export const RULE_REMOVED_PROP_PREFIX = "removed-prop:";
export const RULE_LIBRARY_PREFIX = "library-prefix-unprovenanced";
export const RULE_UNINDEXED_FK = "unindexed-foreign-key";
export const RULE_REFERENTIAL_ACTION_CONFLICT = "overridden-referential-action";
export const RULE_DOC_GENERATOR_IN_GEN = "doc-generator-in-gen";
export const RULE_DOC_GENERATOR_REFUSED = "doc-generator-refused-by-gen";

/** The next-step line for `n` rows of one advisory kind, each naming what the rows are. */
const ADVISORY_HELP: ReadonlyArray<{ matches: (rule: string) => boolean; line: (n: number) => string }> = [
Expand Down Expand Up @@ -143,6 +145,16 @@ const ADVISORY_HELP: ReadonlyArray<{ matches: (rule: string) => boolean; line: (
line: (n) =>
`${n} node(s) declared under "metaobjects::" with no ejection provenance — see antiPatterns.rows[] (rule "${RULE_LIBRARY_PREFIX}")`,
},
{
matches: (r) => r === RULE_DOC_GENERATOR_IN_GEN,
line: (n) =>
`${n} doc generator(s) listed under \`generators\`, which \`meta gen\` skips — see antiPatterns.rows[] (rule "${RULE_DOC_GENERATOR_IN_GEN}"); \`meta docs\` writes documentation`,
},
{
matches: (r) => r === RULE_DOC_GENERATOR_REFUSED,
line: (n) =>
`${n} doc generator name(s) listed under \`generators\`, which \`meta gen\` refuses, so the run fails — see antiPatterns.rows[] (rule "${RULE_DOC_GENERATOR_REFUSED}"); \`meta docs\` writes documentation`,
},
];

/**
Expand Down Expand Up @@ -262,6 +274,23 @@ export function referentialActionConflictRows(
}));
}

/**
* Doc generators wired under `generators` in the same row shape. There is no line: the
* finding is about the loaded config, whose entries carry no source position. The rule
* says which outcome `meta gen` gives the entry: skipped, or refused (the run fails).
*/
export function docGeneratorRows(
findings: readonly { file: string; construct: string; refused: boolean; message: string }[],
): AdvisoryFindingRow[] {
return findings.map((f) => ({
file: f.file,
line: 0,
rule: f.refused ? RULE_DOC_GENERATOR_REFUSED : RULE_DOC_GENERATOR_IN_GEN,
construct: f.construct,
message: f.message,
}));
}

// ---------------------------------------------------------------------------
// text output
// ---------------------------------------------------------------------------
Expand Down
Loading
Loading