@metaobjectsdev/cli 1.0.0-rc.5 → 1.0.0-rc.6
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/src/commands/docs.d.ts.map +1 -1
- package/dist/src/commands/docs.js +41 -9
- package/dist/src/commands/docs.js.map +1 -1
- package/dist/src/commands/gen.d.ts +5 -0
- package/dist/src/commands/gen.d.ts.map +1 -1
- package/dist/src/commands/gen.js +12 -1
- package/dist/src/commands/gen.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +27 -3
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/migrate.d.ts +5 -0
- package/dist/src/commands/migrate.d.ts.map +1 -1
- package/dist/src/commands/migrate.js +10 -4
- package/dist/src/commands/migrate.js.map +1 -1
- package/dist/src/commands/prompt-snapshot.d.ts.map +1 -1
- package/dist/src/commands/prompt-snapshot.js +2 -1
- package/dist/src/commands/prompt-snapshot.js.map +1 -1
- package/dist/src/commands/types.d.ts.map +1 -1
- package/dist/src/commands/types.js +12 -3
- package/dist/src/commands/types.js.map +1 -1
- package/dist/src/commands/upgrade.d.ts.map +1 -1
- package/dist/src/commands/upgrade.js +38 -4
- package/dist/src/commands/upgrade.js.map +1 -1
- package/dist/src/commands/verify.d.ts.map +1 -1
- package/dist/src/commands/verify.js +69 -21
- package/dist/src/commands/verify.js.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +37 -18
- package/dist/src/index.js.map +1 -1
- package/dist/src/lib/advisory.d.ts +6 -0
- package/dist/src/lib/advisory.d.ts.map +1 -1
- package/dist/src/lib/advisory.js +13 -0
- package/dist/src/lib/advisory.js.map +1 -1
- package/dist/src/lib/anti-patterns.d.ts.map +1 -1
- package/dist/src/lib/anti-patterns.js +2 -4
- package/dist/src/lib/anti-patterns.js.map +1 -1
- package/dist/src/lib/args.d.ts +23 -12
- package/dist/src/lib/args.d.ts.map +1 -1
- package/dist/src/lib/args.js +33 -22
- package/dist/src/lib/args.js.map +1 -1
- package/dist/src/lib/base-url-advisory.d.ts +37 -0
- package/dist/src/lib/base-url-advisory.d.ts.map +1 -0
- package/dist/src/lib/base-url-advisory.js +162 -0
- package/dist/src/lib/base-url-advisory.js.map +1 -0
- package/dist/src/lib/codegen-drift.d.ts +8 -0
- package/dist/src/lib/codegen-drift.d.ts.map +1 -1
- package/dist/src/lib/codegen-drift.js +43 -10
- package/dist/src/lib/codegen-drift.js.map +1 -1
- package/dist/src/lib/detect-stack.d.ts +17 -0
- package/dist/src/lib/detect-stack.d.ts.map +1 -1
- package/dist/src/lib/detect-stack.js +64 -2
- package/dist/src/lib/detect-stack.js.map +1 -1
- package/dist/src/lib/docs-drift.d.ts.map +1 -1
- package/dist/src/lib/docs-drift.js +16 -7
- package/dist/src/lib/docs-drift.js.map +1 -1
- package/dist/src/lib/git-ignore.d.ts +19 -0
- package/dist/src/lib/git-ignore.d.ts.map +1 -1
- package/dist/src/lib/git-ignore.js +46 -3
- package/dist/src/lib/git-ignore.js.map +1 -1
- package/dist/src/lib/ignored-scaffold-check.d.ts +12 -0
- package/dist/src/lib/ignored-scaffold-check.d.ts.map +1 -0
- package/dist/src/lib/ignored-scaffold-check.js +75 -0
- package/dist/src/lib/ignored-scaffold-check.js.map +1 -0
- package/dist/src/lib/load-error.d.ts +46 -0
- package/dist/src/lib/load-error.d.ts.map +1 -0
- package/dist/src/lib/load-error.js +51 -0
- package/dist/src/lib/load-error.js.map +1 -0
- package/dist/src/lib/manifest-ignored-check.d.ts.map +1 -1
- package/dist/src/lib/manifest-ignored-check.js +19 -6
- package/dist/src/lib/manifest-ignored-check.js.map +1 -1
- package/dist/src/lib/output.d.ts +13 -1
- package/dist/src/lib/output.d.ts.map +1 -1
- package/dist/src/lib/output.js +26 -8
- package/dist/src/lib/output.js.map +1 -1
- package/dist/src/lib/rel-posix.d.ts +5 -0
- package/dist/src/lib/rel-posix.d.ts.map +1 -0
- package/dist/src/lib/rel-posix.js +23 -0
- package/dist/src/lib/rel-posix.js.map +1 -0
- package/dist/src/lib/replay-remedy.d.ts +2 -0
- package/dist/src/lib/replay-remedy.d.ts.map +1 -0
- package/dist/src/lib/replay-remedy.js +69 -0
- package/dist/src/lib/replay-remedy.js.map +1 -0
- package/package.json +10 -10
- package/src/commands/docs.ts +51 -12
- package/src/commands/gen.ts +14 -1
- package/src/commands/init.ts +27 -3
- package/src/commands/migrate.ts +12 -4
- package/src/commands/prompt-snapshot.ts +2 -1
- package/src/commands/types.ts +12 -3
- package/src/commands/upgrade.ts +44 -4
- package/src/commands/verify.ts +74 -23
- package/src/index.ts +37 -18
- package/src/lib/advisory.ts +15 -0
- package/src/lib/anti-patterns.ts +2 -5
- package/src/lib/args.ts +60 -39
- package/src/lib/base-url-advisory.ts +173 -0
- package/src/lib/codegen-drift.ts +52 -9
- package/src/lib/detect-stack.ts +73 -2
- package/src/lib/docs-drift.ts +17 -7
- package/src/lib/git-ignore.ts +47 -3
- package/src/lib/ignored-scaffold-check.ts +78 -0
- package/src/lib/load-error.ts +89 -0
- package/src/lib/manifest-ignored-check.ts +21 -6
- package/src/lib/output.ts +36 -8
- package/src/lib/rel-posix.ts +25 -0
- package/src/lib/replay-remedy.ts +78 -0
package/src/commands/verify.ts
CHANGED
|
@@ -12,12 +12,14 @@ import { parseVerifyArgs, type MigrateFlags } from "../lib/args.js";
|
|
|
12
12
|
import { log } from "../lib/log.js";
|
|
13
13
|
import { emitStructured, type OutputFormat } from "../lib/format.js";
|
|
14
14
|
import {
|
|
15
|
-
antiPatternRows, ranSection, skippedSection, warnCapped,
|
|
15
|
+
antiPatternRows, missingBaseUrlRows, ranSection, skippedSection, warnCapped,
|
|
16
16
|
type AdvisoryDiagnosticRow, type AdvisoryFindingRow, type AdvisorySection,
|
|
17
17
|
} from "../lib/advisory.js";
|
|
18
18
|
import { warnIfAgentContextStale } from "../lib/agent-context-staleness.js";
|
|
19
19
|
import { warnIfManifestIgnored } from "../lib/manifest-ignored-check.js";
|
|
20
20
|
import { scanSourceForAntiPatterns } from "../lib/anti-patterns.js";
|
|
21
|
+
import { scanForMissingBaseUrl, type BaseUrlFinding } from "../lib/base-url-advisory.js";
|
|
22
|
+
import { replayRemedy } from "../lib/replay-remedy.js";
|
|
21
23
|
import { FileProvider } from "../lib/file-provider.js";
|
|
22
24
|
import { derivePayloadFieldTree } from "../lib/payload-field-tree.js";
|
|
23
25
|
import { loadMemoryOptionsFrom, loadMetaobjectsConfig, resolveGenCollection, resolveGenConfigDir } from "../lib/load-metaobjects-config.js";
|
|
@@ -85,6 +87,7 @@ import {
|
|
|
85
87
|
REQUIREMENT_STATUSES,
|
|
86
88
|
} from "@metaobjectsdev/metadata";
|
|
87
89
|
import { verify, ERR_REQUIRED_SLOT_UNUSED, ERR_PARTIAL_UNRESOLVED } from "@metaobjectsdev/render";
|
|
90
|
+
import { describeLoadError, reportLoadError } from "../lib/load-error.js";
|
|
88
91
|
|
|
89
92
|
const DEFAULT_PROMPTS_DIR = "prompts";
|
|
90
93
|
|
|
@@ -250,7 +253,10 @@ export async function verifyCommand(
|
|
|
250
253
|
});
|
|
251
254
|
} catch (err) {
|
|
252
255
|
const msg = (err as Error).message;
|
|
253
|
-
|
|
256
|
+
// Everything the loader's envelope carried, not just the message: the stable code a
|
|
257
|
+
// CI job keys on, and the FILE + json path the message itself can never name.
|
|
258
|
+
const report = describeLoadError(err);
|
|
259
|
+
log.error(`failed to load metadata: ${report.text}`);
|
|
254
260
|
// Strict-load rejection (ADR-0023). Two different failures reach here and they need
|
|
255
261
|
// different advice:
|
|
256
262
|
//
|
|
@@ -263,8 +269,11 @@ export async function verifyCommand(
|
|
|
263
269
|
// retired attribute and the author gets a green `meta verify` over a value that now
|
|
264
270
|
// reaches nothing: a correct, loud failure converted into a quiet, wrong pass. That is
|
|
265
271
|
// strictly worse than the error it replaced.
|
|
266
|
-
|
|
267
|
-
|
|
272
|
+
// Off the REPORT, not the raw error a second time: `describeLoadError` has already
|
|
273
|
+
// decided what the envelope carried (and filtered empty entries), so re-reading the
|
|
274
|
+
// throwable would be a second answer to a question already answered above.
|
|
275
|
+
const code = report.code;
|
|
276
|
+
const suggestions = report.suggestions;
|
|
268
277
|
const strictHint =
|
|
269
278
|
"meta verify is strict (ADR-0023): every authored @attr must be declared. " +
|
|
270
279
|
"Fix: register the attr on a metadata provider, OR move arbitrary " +
|
|
@@ -280,6 +289,14 @@ export async function verifyCommand(
|
|
|
280
289
|
// report — only why there is none.
|
|
281
290
|
emitStructured({
|
|
282
291
|
error: `failed to load metadata: ${msg}`,
|
|
292
|
+
// ADR-0009 promises a stable `code`, and the docs name codes a CI job is meant to
|
|
293
|
+
// branch on — but this payload used to carry the message string and nothing else,
|
|
294
|
+
// so there was nothing to branch on. Each field is present only when the loader
|
|
295
|
+
// actually supplied it; absence here means the loader had none, never that it was
|
|
296
|
+
// dropped in transit.
|
|
297
|
+
...(report.code !== undefined ? { code: report.code } : {}),
|
|
298
|
+
...(report.files !== undefined ? { files: [...report.files] } : {}),
|
|
299
|
+
...(report.jsonPath !== undefined ? { jsonPath: report.jsonPath } : {}),
|
|
283
300
|
hint: suggestions !== undefined && suggestions.length > 0
|
|
284
301
|
? suggestions.join(" ")
|
|
285
302
|
: isStrictAttr ? strictHint : "fix the metadata error above and re-run",
|
|
@@ -449,11 +466,7 @@ export async function verifyCommand(
|
|
|
449
466
|
applied = await applyPending(engine.db, dir, { dryRun: false, dialect });
|
|
450
467
|
} catch (err) {
|
|
451
468
|
log.error(`meta verify --replay: ${(err as Error).message}`);
|
|
452
|
-
log.error(
|
|
453
|
-
`meta verify --replay: the committed chain does not apply to an empty database. ` +
|
|
454
|
-
`Applied migrations are immutable, so fix this with a NEW migration that creates the ` +
|
|
455
|
-
`missing object — not by editing a committed up.sql.`,
|
|
456
|
-
);
|
|
469
|
+
for (const line of replayRemedy(err)) log.error(`meta verify --replay: ${line}`);
|
|
457
470
|
return 1;
|
|
458
471
|
}
|
|
459
472
|
|
|
@@ -711,13 +724,33 @@ export async function verifyCommand(
|
|
|
711
724
|
antiPatternSection = skippedSection(`the scan failed: ${(err as Error).message}`);
|
|
712
725
|
return;
|
|
713
726
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
727
|
+
// F52 — the one gate that can see a provider silently missing its base. Folded into
|
|
728
|
+
// this pass rather than given its own, because it is the same KIND of finding (a
|
|
729
|
+
// teachable warning that never touches the exit code) and one --no-antipatterns
|
|
730
|
+
// should suppress the whole advisory tier, not half of it.
|
|
731
|
+
let baseUrl: BaseUrlFinding[] = [];
|
|
732
|
+
try {
|
|
733
|
+
baseUrl = scanForMissingBaseUrl(projectRoot, forgeConfig?.apiPrefix ?? "");
|
|
734
|
+
} catch {
|
|
735
|
+
// Same discipline as above: an advisory scan never breaks verify. The anti-pattern
|
|
736
|
+
// half already ran, so its rows are still reported rather than lost to this catch.
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
antiPatternSection = ranSection([...antiPatternRows(findings), ...missingBaseUrlRows(baseUrl)]);
|
|
740
|
+
if (findings.length > 0) {
|
|
741
|
+
log.warn(
|
|
742
|
+
`meta verify — ${findings.length} place(s) hand-roll what MetaObjects can model ` +
|
|
743
|
+
`(advisory — does not fail the build):`,
|
|
744
|
+
);
|
|
745
|
+
warnCapped(findings.map((f) => ` ${f.message}`), flags.limit, { structured });
|
|
746
|
+
}
|
|
747
|
+
if (baseUrl.length > 0) {
|
|
748
|
+
log.warn(
|
|
749
|
+
`meta verify — ${baseUrl.length} entity-fetcher provider(s) mounted with no baseUrl ` +
|
|
750
|
+
`while apiPrefix is "${forgeConfig?.apiPrefix ?? ""}" (advisory — does not fail the build):`,
|
|
751
|
+
);
|
|
752
|
+
warnCapped(baseUrl.map((f) => ` ${f.message}`), flags.limit, { structured });
|
|
753
|
+
}
|
|
721
754
|
}
|
|
722
755
|
|
|
723
756
|
// -- template (prompt / output) drift --------------------------------------
|
|
@@ -1200,11 +1233,11 @@ export async function verifyCommand(
|
|
|
1200
1233
|
strict: !flags.lax,
|
|
1201
1234
|
});
|
|
1202
1235
|
} catch (err) {
|
|
1203
|
-
|
|
1204
|
-
//
|
|
1205
|
-
//
|
|
1206
|
-
//
|
|
1207
|
-
|
|
1236
|
+
// The second door onto the same strict load, and it must carry the same provenance
|
|
1237
|
+
// as the first: the ADR-0009 code, the FILE and json path the message cannot name,
|
|
1238
|
+
// and the loader's own remedies. It printed a bare message plus a hand-rolled
|
|
1239
|
+
// suggestions read — the half-true rule this file's sibling comment warns about.
|
|
1240
|
+
reportLoadError(log, "verify --codegen: failed to load this package's metadata", err);
|
|
1208
1241
|
return 2;
|
|
1209
1242
|
}
|
|
1210
1243
|
}
|
|
@@ -1223,7 +1256,11 @@ export async function verifyCommand(
|
|
|
1223
1256
|
}
|
|
1224
1257
|
|
|
1225
1258
|
if (result.clean) {
|
|
1226
|
-
|
|
1259
|
+
// Says what it CHECKED, not more. Since 0.24.3 this gate asks whether each file's
|
|
1260
|
+
// GENERATED contribution is current — a hand edit `meta gen` preserves is exempt —
|
|
1261
|
+
// so "output is in sync with the metadata" claimed a stronger property than it had
|
|
1262
|
+
// verified, and did so over a file whose committed content contradicted the model.
|
|
1263
|
+
say("meta verify — every file's generated contribution is current (no codegen drift).");
|
|
1227
1264
|
return 0;
|
|
1228
1265
|
}
|
|
1229
1266
|
|
|
@@ -1231,7 +1268,21 @@ export async function verifyCommand(
|
|
|
1231
1268
|
`meta verify — codegen drift (${result.driftedFiles.length} file(s) differ from a fresh regen):`,
|
|
1232
1269
|
);
|
|
1233
1270
|
for (const line of result.lines) log.error(` ${line}`);
|
|
1234
|
-
|
|
1271
|
+
// The remedy is PER CONVICTION KIND, not one line for the whole gate. `meta gen`
|
|
1272
|
+
// fixes stale generated output; it cannot fix a hand-edited names artifact, because
|
|
1273
|
+
// it MERGES the edit and exits 0 — so prescribing it there was the unbreakable loop
|
|
1274
|
+
// the hand-edit exemption exists to end, reintroduced for the one file the exemption
|
|
1275
|
+
// deliberately excludes. Both lines print when the drift set holds both kinds.
|
|
1276
|
+
if (result.driftedFiles.length > result.handEditedNames.length) {
|
|
1277
|
+
log.error("Run 'meta gen' to regenerate, then commit the result.");
|
|
1278
|
+
}
|
|
1279
|
+
if (result.handEditedNames.length > 0) {
|
|
1280
|
+
log.error(
|
|
1281
|
+
"For the names artifact(s) above, 'meta gen' will NOT help — it preserves a hand " +
|
|
1282
|
+
"edit by design. Revert the edit (or delete the file and re-run 'meta gen' to " +
|
|
1283
|
+
"re-emit it), and keep constants of your own in a file of your own.",
|
|
1284
|
+
);
|
|
1285
|
+
}
|
|
1235
1286
|
return 1;
|
|
1236
1287
|
}
|
|
1237
1288
|
|
package/src/index.ts
CHANGED
|
@@ -57,10 +57,12 @@ GEN FLAGS:
|
|
|
57
57
|
--dry-run Compute and print, don't write
|
|
58
58
|
--list Print the generator registry (name, tier, options) and exit —
|
|
59
59
|
no config or metadata required
|
|
60
|
-
--baseline <default|fresh>
|
|
61
|
-
First-time-on-existing-file behavior. Default:
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
--baseline <default|adopt|fresh>
|
|
61
|
+
First-time-on-existing-file behavior. Default: refuse a file that
|
|
62
|
+
cannot be proved to be generated output. "adopt" records the files
|
|
63
|
+
you have as the merge base and writes NOTHING — the first run a
|
|
64
|
+
project with no committed .hashes.json can perform. "fresh"
|
|
65
|
+
overwrites them with fresh output and re-baselines (discards edits).
|
|
64
66
|
--no-antipatterns Suppress the advisory "hand-rolled what MetaObjects can model" pass
|
|
65
67
|
--limit <n|all> How many advisory lines TEXT output prints before it truncates
|
|
66
68
|
(default 20). Never applies to --format toon/json, which
|
|
@@ -75,7 +77,11 @@ DOCS FLAGS:
|
|
|
75
77
|
[<project-root>] PROJECT ROOT to resolve metadata from — the directory that CONTAINS
|
|
76
78
|
your metadata, NOT the metadata directory. Passing it SCOPES the run
|
|
77
79
|
(default: cwd)
|
|
78
|
-
--out <dir>, -o Output directory for
|
|
80
|
+
--out <dir>, -o Output directory for THIS run (default: ./docs/generated). It does
|
|
81
|
+
NOT survive the run — set 'docs: { outDir }' in metaobjects.config.ts
|
|
82
|
+
for a directory that persists, which is what 'meta verify --docs'
|
|
83
|
+
measures. The default is a sub-directory on purpose: these pages are
|
|
84
|
+
regenerated and overwritten, and 'docs/' itself is usually yours.
|
|
79
85
|
--templates <dir> Project root to resolve adopter templates/ overrides (default: <project-root>)
|
|
80
86
|
--prompts <dir> Extra dir holding prompt .mustache sources for --site (e.g. data/templates/)
|
|
81
87
|
|
|
@@ -143,10 +149,12 @@ FLAGS:
|
|
|
143
149
|
--dry-run Compute and print, don't write
|
|
144
150
|
--list Print the generator registry (name, tier, options) and exit —
|
|
145
151
|
no config or metadata required
|
|
146
|
-
--baseline <default|fresh>
|
|
147
|
-
First-time-on-existing-file behavior. Default:
|
|
148
|
-
|
|
149
|
-
|
|
152
|
+
--baseline <default|adopt|fresh>
|
|
153
|
+
First-time-on-existing-file behavior. Default: refuse a file that
|
|
154
|
+
cannot be proved to be generated output. "adopt" records the files
|
|
155
|
+
you have as the merge base and writes NOTHING — the first run a
|
|
156
|
+
project with no committed .hashes.json can perform. "fresh"
|
|
157
|
+
overwrites them with fresh output and re-baselines (discards edits).
|
|
150
158
|
--no-antipatterns Suppress the advisory "hand-rolled what MetaObjects can model" pass
|
|
151
159
|
--limit <n|all> Advisory lines TEXT output prints before truncating (default 20)
|
|
152
160
|
--format <toon|json|text> Output format (global flag; default toon off-TTY)
|
|
@@ -191,7 +199,10 @@ USAGE:
|
|
|
191
199
|
|
|
192
200
|
FLAGS:
|
|
193
201
|
--templates Template/prompt {{field}}↔payload drift (default when bare)
|
|
194
|
-
--codegen Codegen drift —
|
|
202
|
+
--codegen Codegen drift — is the GENERATED contribution current? A hand
|
|
203
|
+
edit 'meta gen' preserves is not drift; a stale generated part is.
|
|
204
|
+
An <Entity>.names.ts artifact is the exception: it must match a
|
|
205
|
+
fresh regen exactly — a hand edit in it is itself the defect.
|
|
195
206
|
Needs metaobjects.config.ts; exit 2 if absent.
|
|
196
207
|
--docs Docs drift — run 'meta docs' to a temp dir and diff docs.outDir
|
|
197
208
|
Needs metaobjects.config.ts; exit 2 if absent. A page the
|
|
@@ -269,7 +280,11 @@ FLAGS:
|
|
|
269
280
|
it SCOPES the run to that directory's own sources; no ancestor
|
|
270
281
|
.metaobjects/config.json is consulted. Omitted (default), the project
|
|
271
282
|
is discovered by walking up.
|
|
272
|
-
--out <dir>, -o Output directory for
|
|
283
|
+
--out <dir>, -o Output directory for THIS run (default: ./docs/generated). It does
|
|
284
|
+
NOT survive the run — set 'docs: { outDir }' in metaobjects.config.ts
|
|
285
|
+
for a directory that persists, which is what 'meta verify --docs'
|
|
286
|
+
measures. The default is a sub-directory on purpose: these pages are
|
|
287
|
+
regenerated and overwritten, and 'docs/' itself is usually yours.
|
|
273
288
|
--model Emit the markdown model surface (entity + template pages)
|
|
274
289
|
--api Emit the markdown api surface (generated SDK reference)
|
|
275
290
|
--requirements Emit the declared requirement ledger (requirements.md + .toon).
|
|
@@ -278,7 +293,9 @@ FLAGS:
|
|
|
278
293
|
agent/ui.md (the generated forms and grids) and
|
|
279
294
|
agent/requirements.md (the ledger plus a claimed-node index).
|
|
280
295
|
Needs metaobjects.config.ts (the dialect and physical names come
|
|
281
|
-
from it); each page is skipped when its tier has nothing to describe
|
|
296
|
+
from it); each page is skipped when its tier has nothing to describe
|
|
297
|
+
— agent/ui.md needs a UI generator (a form, hook or grid) in the
|
|
298
|
+
suite, not merely objects that could have one.
|
|
282
299
|
--metamodel Document the built-in metamodel vocabulary (no metadata needed)
|
|
283
300
|
--site Generate the browsable HTML documentation site (<out>/site/)
|
|
284
301
|
--scaffold-site Copy the site's templates + assets into codegen/docs-site/ to own (theme) them
|
|
@@ -297,10 +314,12 @@ FLAGS:
|
|
|
297
314
|
--docs-only Write only the agent-context files — no metadata sources, no
|
|
298
315
|
codegen scaffold, no config. Pair with --refresh-docs to update
|
|
299
316
|
an existing project after upgrading.
|
|
300
|
-
--server <lang> Declare a server language for the agent context (repeatable
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
317
|
+
--server <lang> Declare a server language for the agent context (repeatable):
|
|
318
|
+
typescript | java | kotlin | csharp | python
|
|
319
|
+
('node' is accepted as an alias for typescript; an unknown
|
|
320
|
+
value is refused, never dropped)
|
|
321
|
+
--client <fw> Declare a client framework for the agent context (repeatable):
|
|
322
|
+
react | tanstack | angular
|
|
304
323
|
--no-skills Skip the .claude/skills/ scaffold
|
|
305
324
|
--force Overwrite existing files
|
|
306
325
|
--quiet Suppress output
|
|
@@ -317,8 +336,8 @@ USAGE:
|
|
|
317
336
|
meta agent-docs [--server <lang>]... [--client <fw>]... [--out <dir>] [flags]
|
|
318
337
|
|
|
319
338
|
FLAGS:
|
|
320
|
-
--server <lang> Server language (repeatable
|
|
321
|
-
--client <fw> Client framework (repeatable
|
|
339
|
+
--server <lang> Server language (repeatable): typescript|java|kotlin|csharp|python
|
|
340
|
+
--client <fw> Client framework (repeatable): react|tanstack|angular
|
|
322
341
|
--out <dir> Output directory (default: current directory)
|
|
323
342
|
--no-skills Skip .claude/skills/ scaffold
|
|
324
343
|
--no-wire-root Skip wiring root CLAUDE.md @import
|
package/src/lib/advisory.ts
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import { log } from "./log.js";
|
|
24
24
|
import type { AntiPatternFinding } from "./anti-patterns.js";
|
|
25
|
+
import type { BaseUrlFinding } from "./base-url-advisory.js";
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* How many advisory lines TEXT output prints before it truncates.
|
|
@@ -119,6 +120,20 @@ export function antiPatternRows(findings: readonly AntiPatternFinding[]): Adviso
|
|
|
119
120
|
}));
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
/**
|
|
124
|
+
* F52 findings in the same row shape, so the structured payload has ONE advisory list
|
|
125
|
+
* rather than a second one a consumer has to learn about. `rule` is the discriminator.
|
|
126
|
+
*/
|
|
127
|
+
export function missingBaseUrlRows(findings: readonly BaseUrlFinding[]): AdvisoryFindingRow[] {
|
|
128
|
+
return findings.map((f) => ({
|
|
129
|
+
file: f.file,
|
|
130
|
+
line: f.line,
|
|
131
|
+
rule: "missing-base-url",
|
|
132
|
+
construct: f.construct,
|
|
133
|
+
message: f.message,
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
|
|
122
137
|
// ---------------------------------------------------------------------------
|
|
123
138
|
// text output
|
|
124
139
|
// ---------------------------------------------------------------------------
|
package/src/lib/anti-patterns.ts
CHANGED
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
// nag a legitimate one.
|
|
17
17
|
|
|
18
18
|
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
19
|
-
import { join
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { relPosix } from "./rel-posix.js";
|
|
20
21
|
|
|
21
22
|
export interface AntiPatternFinding {
|
|
22
23
|
file: string; // path relative to the scan root (posix-ish, sep-normalized)
|
|
@@ -220,10 +221,6 @@ function findingFor(file: string, lineNo: number, raw: string, isSql: boolean):
|
|
|
220
221
|
};
|
|
221
222
|
}
|
|
222
223
|
|
|
223
|
-
function relPosix(root: string, abs: string): string {
|
|
224
|
-
return relative(root, abs).split(sep).join("/");
|
|
225
|
-
}
|
|
226
|
-
|
|
227
224
|
function walk(dir: string, root: string, ignore: readonly RegExp[], acc: AntiPatternFinding[]): void {
|
|
228
225
|
let entries;
|
|
229
226
|
try {
|
package/src/lib/args.ts
CHANGED
|
@@ -100,10 +100,10 @@ export function parseAgentDocsArgs(argv: string[]): AgentDocsFlags {
|
|
|
100
100
|
export interface GenFlags {
|
|
101
101
|
dryRun: boolean;
|
|
102
102
|
entities: string[];
|
|
103
|
-
/** First-time-on-existing-file behavior. Default:
|
|
104
|
-
*
|
|
105
|
-
* and re-baseline. */
|
|
106
|
-
baseline: "default" | "fresh";
|
|
103
|
+
/** First-time-on-existing-file behavior. Default: refuse a file that cannot be
|
|
104
|
+
* proved to be ours. "adopt" → record the file as the merge base and write
|
|
105
|
+
* nothing. "fresh" → overwrite from fresh output and re-baseline. */
|
|
106
|
+
baseline: "default" | "fresh" | "adopt";
|
|
107
107
|
/** ADR-0021 D3 — print the stable-name generator registry and exit without
|
|
108
108
|
* running codegen. */
|
|
109
109
|
list: boolean;
|
|
@@ -134,15 +134,20 @@ export function parseGenArgs(argv: string[]): GenFlags {
|
|
|
134
134
|
allowPositionals: true,
|
|
135
135
|
});
|
|
136
136
|
const baselineRaw = values.baseline as string | undefined;
|
|
137
|
-
if (
|
|
137
|
+
if (
|
|
138
|
+
baselineRaw !== undefined &&
|
|
139
|
+
baselineRaw !== "default" &&
|
|
140
|
+
baselineRaw !== "fresh" &&
|
|
141
|
+
baselineRaw !== "adopt"
|
|
142
|
+
) {
|
|
138
143
|
throw new Error(
|
|
139
|
-
`invalid --baseline '${baselineRaw}'; expected 'default' or 'fresh'`,
|
|
144
|
+
`invalid --baseline '${baselineRaw}'; expected 'default', 'adopt' or 'fresh'`,
|
|
140
145
|
);
|
|
141
146
|
}
|
|
142
147
|
return {
|
|
143
148
|
dryRun: !!values["dry-run"],
|
|
144
149
|
entities: positionals,
|
|
145
|
-
baseline: (baselineRaw as "default" | "fresh" | undefined) ?? "default",
|
|
150
|
+
baseline: (baselineRaw as "default" | "fresh" | "adopt" | undefined) ?? "default",
|
|
146
151
|
list: !!values.list,
|
|
147
152
|
noAntipatterns: !!values["no-antipatterns"],
|
|
148
153
|
// Throws on a bad value; the command layer reports it and exits 2, exactly as
|
|
@@ -263,7 +268,7 @@ export interface VerifyFlags {
|
|
|
263
268
|
// command layer applies the bare-verify default (= --templates) when none are.
|
|
264
269
|
/** Run the template/prompt {{field}}↔payload drift gate. */
|
|
265
270
|
templates: boolean;
|
|
266
|
-
/** Run the codegen-drift gate
|
|
271
|
+
/** Run the codegen-drift gate: is the GENERATED contribution of each file current? */
|
|
267
272
|
codegen: boolean;
|
|
268
273
|
/**
|
|
269
274
|
* Run the docs-drift gate: regenerate the `meta docs` surfaces into a temp dir and diff
|
|
@@ -272,8 +277,9 @@ export interface VerifyFlags {
|
|
|
272
277
|
* A SEPARATE subverb rather than part of `--codegen`, because they check different
|
|
273
278
|
* trees under different ownership rules: `--codegen` regenerates `outDir`/`targets` and
|
|
274
279
|
* must respect the hand edits `meta gen` preserves, while the docs tree has no merge,
|
|
275
|
-
* no manifest, and lives in a directory (`./docs` by
|
|
276
|
-
* files it does not own. Folding them together
|
|
280
|
+
* no manifest, and lives in a directory the adopter chose (`./docs/generated` by
|
|
281
|
+
* default) that may hold hand-written files it does not own. Folding them together
|
|
282
|
+
* would have to pick one of those rules
|
|
277
283
|
* for both.
|
|
278
284
|
*/
|
|
279
285
|
docs: boolean;
|
|
@@ -309,11 +315,19 @@ export interface VerifyFlags {
|
|
|
309
315
|
*/
|
|
310
316
|
lax: boolean;
|
|
311
317
|
/**
|
|
312
|
-
* How many advisory lines TEXT output prints per
|
|
313
|
-
* (`DEFAULT_ADVISORY_LIMIT`, or Infinity for `--limit all`).
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
318
|
+
* How many advisory lines TEXT output prints per printed LIST before truncating
|
|
319
|
+
* (`DEFAULT_ADVISORY_LIMIT`, or Infinity for `--limit all`). Never shared across
|
|
320
|
+
* lists — one shared budget lets the biggest list push every other finding off the
|
|
321
|
+
* end. It never applies to a structured payload, which carries everything.
|
|
322
|
+
*
|
|
323
|
+
* Per LIST, not per section, and the distinction is real rather than pedantic: the
|
|
324
|
+
* anti-pattern tier prints TWO separately-headed lists (the hand-rolling findings and
|
|
325
|
+
* the F52 missing-`baseUrl` findings) while being ONE section everywhere else — one
|
|
326
|
+
* structured list keyed by `rule`, one `--no-antipatterns` muting both. So that
|
|
327
|
+
* section's text output can reach a MULTIPLE of the cap. Deliberate: a project with 200
|
|
328
|
+
* money-float sites would otherwise never see its `baseUrl` advisory, which is the one
|
|
329
|
+
* that silently drops the API prefix from every generated hook, and starving the small
|
|
330
|
+
* list is the worse failure. Pinned in advisory-structured-output.test.ts.
|
|
317
331
|
*/
|
|
318
332
|
limit: number;
|
|
319
333
|
}
|
|
@@ -323,7 +337,7 @@ export const VERIFY_OPTIONS = {
|
|
|
323
337
|
prompts: { type: "string" },
|
|
324
338
|
db: { type: "string" },
|
|
325
339
|
dialect: { type: "string" },
|
|
326
|
-
allow: { type: "string" },
|
|
340
|
+
allow: { type: "string", multiple: true },
|
|
327
341
|
"skip-schema": { type: "boolean", default: false },
|
|
328
342
|
templates: { type: "boolean", default: false },
|
|
329
343
|
codegen: { type: "boolean", default: false },
|
|
@@ -338,6 +352,33 @@ export const VERIFY_OPTIONS = {
|
|
|
338
352
|
"limit": { type: "string" },
|
|
339
353
|
} as const;
|
|
340
354
|
|
|
355
|
+
/**
|
|
356
|
+
* The `--allow` tokens for one invocation, from EVERY occurrence of the flag.
|
|
357
|
+
*
|
|
358
|
+
* `--allow` is comma-separated, and repeating it is the natural reading (it is what
|
|
359
|
+
* `--server` does). It used to take the LAST occurrence only, so
|
|
360
|
+
* `--allow drop-fk --allow drop-view` silently discarded `drop-fk` and then printed
|
|
361
|
+
* "re-run with --allow drop-fk to apply" seven times — an instruction the user had just
|
|
362
|
+
* followed. Both spellings, and any mixture of them, now mean the union.
|
|
363
|
+
*
|
|
364
|
+
* Duplicates collapse: asking for the same permission twice is one permission.
|
|
365
|
+
*/
|
|
366
|
+
function parseAllowTokens(raw: string | string[] | undefined): AllowToken[] {
|
|
367
|
+
const occurrences = raw === undefined ? [] : Array.isArray(raw) ? raw : [raw];
|
|
368
|
+
const seen = new Set<string>();
|
|
369
|
+
for (const occurrence of occurrences) {
|
|
370
|
+
for (const tok of occurrence.split(",").map((t) => t.trim()).filter((t) => t.length > 0)) {
|
|
371
|
+
if (!ALLOW_TOKENS.includes(tok as AllowToken)) {
|
|
372
|
+
throw new Error(
|
|
373
|
+
`invalid --allow token '${tok}'; expected one of: ${ALLOW_TOKENS.join(", ")}`,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
seen.add(tok);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return [...seen] as AllowToken[];
|
|
380
|
+
}
|
|
381
|
+
|
|
341
382
|
export function parseVerifyArgs(argv: string[]): VerifyFlags {
|
|
342
383
|
const { values } = parseArgs({
|
|
343
384
|
args: argv,
|
|
@@ -351,17 +392,7 @@ export function parseVerifyArgs(argv: string[]): VerifyFlags {
|
|
|
351
392
|
throw new Error(`invalid --dialect '${dialect}'; expected: ${DIALECTS.join(", ")}`);
|
|
352
393
|
}
|
|
353
394
|
|
|
354
|
-
const
|
|
355
|
-
const allowTokens = allowRaw.length === 0
|
|
356
|
-
? []
|
|
357
|
-
: allowRaw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
358
|
-
for (const tok of allowTokens) {
|
|
359
|
-
if (!ALLOW_TOKENS.includes(tok as AllowToken)) {
|
|
360
|
-
throw new Error(
|
|
361
|
-
`invalid --allow token '${tok}'; expected one of: ${ALLOW_TOKENS.join(", ")}`,
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
395
|
+
const allowTokens = parseAllowTokens(values.allow as string | string[] | undefined);
|
|
365
396
|
|
|
366
397
|
const templates = !!values.templates;
|
|
367
398
|
const codegen = !!values.codegen;
|
|
@@ -472,7 +503,7 @@ export const MIGRATE_OPTIONS = {
|
|
|
472
503
|
"migration-format": { type: "string" },
|
|
473
504
|
"out-dir": { type: "string" },
|
|
474
505
|
"slug": { type: "string" },
|
|
475
|
-
"allow": { type: "string" },
|
|
506
|
+
"allow": { type: "string", multiple: true },
|
|
476
507
|
"on-ambiguous": { type: "string" },
|
|
477
508
|
"dry-run": { type: "boolean", default: false },
|
|
478
509
|
"from-db": { type: "boolean", default: false },
|
|
@@ -518,17 +549,7 @@ export function parseMigrateArgs(argv: string[]): MigrateFlags {
|
|
|
518
549
|
throw new Error(`invalid --on-ambiguous '${onAmb}'; expected: ${ON_AMBIGUOUS.join(", ")}`);
|
|
519
550
|
}
|
|
520
551
|
|
|
521
|
-
const
|
|
522
|
-
const allowTokens = allowRaw.length === 0
|
|
523
|
-
? []
|
|
524
|
-
: allowRaw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
525
|
-
for (const tok of allowTokens) {
|
|
526
|
-
if (!ALLOW_TOKENS.includes(tok as AllowToken)) {
|
|
527
|
-
throw new Error(
|
|
528
|
-
`invalid --allow token '${tok}'; expected one of: ${ALLOW_TOKENS.join(", ")}`,
|
|
529
|
-
);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
552
|
+
const allowTokens = parseAllowTokens(values.allow as string | string[] | undefined);
|
|
532
553
|
|
|
533
554
|
return {
|
|
534
555
|
db: values.db as string | undefined,
|