@metaobjectsdev/cli 0.9.0-rc.1 → 0.10.0
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 +2 -0
- package/dist/src/commands/docs.d.ts.map +1 -0
- package/dist/src/commands/docs.js +395 -0
- package/dist/src/commands/docs.js.map +1 -0
- package/dist/src/commands/gen.d.ts.map +1 -1
- package/dist/src/commands/gen.js +41 -1
- package/dist/src/commands/gen.js.map +1 -1
- package/dist/src/commands/init.d.ts +6 -0
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +102 -29
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/verify.d.ts.map +1 -1
- package/dist/src/commands/verify.js +69 -15
- package/dist/src/commands/verify.js.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +20 -32
- package/dist/src/index.js.map +1 -1
- package/dist/src/lib/agent-context-staleness.d.ts +7 -0
- package/dist/src/lib/agent-context-staleness.d.ts.map +1 -0
- package/dist/src/lib/agent-context-staleness.js +26 -0
- package/dist/src/lib/agent-context-staleness.js.map +1 -0
- package/dist/src/lib/args.d.ts +14 -0
- package/dist/src/lib/args.d.ts.map +1 -1
- package/dist/src/lib/args.js +22 -0
- package/dist/src/lib/args.js.map +1 -1
- package/dist/src/lib/codegen-drift.d.ts +21 -0
- package/dist/src/lib/codegen-drift.d.ts.map +1 -0
- package/dist/src/lib/codegen-drift.js +142 -0
- package/dist/src/lib/codegen-drift.js.map +1 -0
- package/dist/src/lib/detect-stack.d.ts +7 -0
- package/dist/src/lib/detect-stack.d.ts.map +1 -0
- package/dist/src/lib/detect-stack.js +38 -0
- package/dist/src/lib/detect-stack.js.map +1 -0
- package/dist/src/lib/load-metaobjects-config.d.ts.map +1 -1
- package/dist/src/lib/load-metaobjects-config.js +8 -0
- package/dist/src/lib/load-metaobjects-config.js.map +1 -1
- package/dist/src/lib/version.d.ts +7 -0
- package/dist/src/lib/version.d.ts.map +1 -0
- package/dist/src/lib/version.js +30 -0
- package/dist/src/lib/version.js.map +1 -0
- package/package.json +56 -45
- package/src/commands/docs.ts +438 -0
- package/src/commands/gen.ts +44 -1
- package/src/commands/init.ts +106 -31
- package/src/commands/verify.ts +81 -15
- package/src/index.ts +20 -30
- package/src/lib/agent-context-staleness.ts +24 -0
- package/src/lib/args.ts +41 -0
- package/src/lib/codegen-drift.ts +173 -0
- package/src/lib/detect-stack.ts +41 -0
- package/src/lib/load-metaobjects-config.ts +8 -0
- package/src/lib/version.ts +27 -0
package/src/commands/init.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { mkdir, writeFile, readFile, stat } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { basename } from "node:path";
|
|
3
|
+
import { basename, dirname } from "node:path";
|
|
4
|
+
import { existsSync as existsSyncWrap, readFileSync as readFileSyncWrap } from "node:fs";
|
|
4
5
|
import { DEFAULT_CONFIG, ConfigSchema, saveConfig, PACKAGE_MANIFEST_FILE, DEFAULT_METADATA_DIR, DEFAULT_METAOBJECTS_DIR } from "@metaobjectsdev/sdk";
|
|
6
|
+
import {
|
|
7
|
+
assemble, resolveAgentContextRoot, planScaffold,
|
|
8
|
+
AGENT_CONTEXT_MANIFEST_PATH, type Manifest,
|
|
9
|
+
} from "@metaobjectsdev/sdk/agent-context";
|
|
10
|
+
import { resolveStack } from "../lib/detect-stack.js";
|
|
5
11
|
import { parseInitArgs } from "../lib/args.js";
|
|
6
12
|
import { log } from "../lib/log.js";
|
|
7
|
-
import {
|
|
13
|
+
import { cliVersion } from "../lib/version.js";
|
|
8
14
|
import { findWranglerConfig, parseWranglerConfig } from "@metaobjectsdev/migrate-ts";
|
|
9
15
|
|
|
10
16
|
const META_COMMON_JSON = JSON.stringify(
|
|
@@ -32,7 +38,7 @@ import {
|
|
|
32
38
|
} from "@metaobjectsdev/codegen-ts/generators";
|
|
33
39
|
|
|
34
40
|
export default defineConfig({
|
|
35
|
-
outDir: "
|
|
41
|
+
outDir: "src/generated",
|
|
36
42
|
extStyle: "none",
|
|
37
43
|
dbImport: "../db",
|
|
38
44
|
dialect: "${dialect}",
|
|
@@ -43,6 +49,11 @@ export default defineConfig({
|
|
|
43
49
|
routesFile(),
|
|
44
50
|
barrel(),
|
|
45
51
|
],
|
|
52
|
+
docs: {
|
|
53
|
+
outDir: "./docs", // model + api surfaces both land here (run: meta docs)
|
|
54
|
+
layout: "flat", // or "package" for multi-package models
|
|
55
|
+
surfaces: ["model", "api"],
|
|
56
|
+
},
|
|
46
57
|
});
|
|
47
58
|
`;
|
|
48
59
|
}
|
|
@@ -53,12 +64,11 @@ Initialized metaobjects/ + .metaobjects/ + metaobjects.config.ts
|
|
|
53
64
|
Next steps (when later sub-projects ship):
|
|
54
65
|
meta ingest # propose entities from your existing TS code
|
|
55
66
|
meta gen # codegen TS targets from entities
|
|
67
|
+
meta docs # neutral model docs (entity + template pages, incl. linked template source)
|
|
56
68
|
meta serve # local viewer
|
|
57
69
|
meta install-hooks # register MCP server + Claude Code hooks
|
|
58
70
|
`;
|
|
59
71
|
|
|
60
|
-
const AGENT_DOC_FILES = ["AGENTS.md", "CLAUDE.md"] as const;
|
|
61
|
-
|
|
62
72
|
export interface InitOptions {
|
|
63
73
|
cwd: string;
|
|
64
74
|
force?: boolean;
|
|
@@ -66,6 +76,12 @@ export interface InitOptions {
|
|
|
66
76
|
printOnly?: boolean;
|
|
67
77
|
refreshDocs?: boolean;
|
|
68
78
|
d1?: boolean;
|
|
79
|
+
servers?: string[];
|
|
80
|
+
clients?: string[];
|
|
81
|
+
noSkills?: boolean;
|
|
82
|
+
wireRoot?: boolean;
|
|
83
|
+
/** Scaffold ONLY the agent-context (always-on + skills + root wiring), skipping the metaobjects/ project scaffold — for dropping context into an existing/polyglot repo. */
|
|
84
|
+
docsOnly?: boolean;
|
|
69
85
|
}
|
|
70
86
|
|
|
71
87
|
export interface InitResult {
|
|
@@ -74,29 +90,71 @@ export interface InitResult {
|
|
|
74
90
|
warnings: string[];
|
|
75
91
|
}
|
|
76
92
|
|
|
77
|
-
async function
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
async function readManifest(cwd: string): Promise<Manifest | undefined> {
|
|
94
|
+
const p = join(cwd, AGENT_CONTEXT_MANIFEST_PATH);
|
|
95
|
+
if (!(await fileExists(p))) return undefined;
|
|
96
|
+
try { return JSON.parse(await readFile(p, "utf8")) as Manifest; } catch { return undefined; }
|
|
97
|
+
}
|
|
82
98
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
99
|
+
async function writeAgentContext(opts: InitOptions, result: InitResult): Promise<void> {
|
|
100
|
+
const stack = resolveStack(opts.cwd, { servers: opts.servers ?? [], clients: opts.clients ?? [] });
|
|
101
|
+
let assembled = assemble({ contentRoot: resolveAgentContextRoot(), stack });
|
|
102
|
+
if (opts.noSkills) assembled = assembled.filter((f) => !f.path.startsWith(".claude/skills/"));
|
|
103
|
+
|
|
104
|
+
const prior = await readManifest(opts.cwd);
|
|
105
|
+
const decision = planScaffold({
|
|
106
|
+
stack, assembled, prior,
|
|
107
|
+
readCurrent: (rel) => {
|
|
108
|
+
const abs = join(opts.cwd, rel);
|
|
109
|
+
return existsSyncWrap(abs) ? readFileSyncWrap(abs, "utf8") : undefined;
|
|
110
|
+
},
|
|
111
|
+
generatedBy: cliVersion(),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
for (const w of decision.writes) {
|
|
115
|
+
const abs = join(opts.cwd, w.path);
|
|
116
|
+
await mkdir(dirname(abs), { recursive: true });
|
|
117
|
+
await writeFile(abs, w.contents, "utf8");
|
|
118
|
+
result.created.push(w.path);
|
|
119
|
+
}
|
|
120
|
+
for (const c of decision.conflicts) {
|
|
121
|
+
const abs = join(opts.cwd, c.newPath);
|
|
122
|
+
await mkdir(dirname(abs), { recursive: true });
|
|
123
|
+
await writeFile(abs, c.contents, "utf8");
|
|
124
|
+
result.created.push(c.newPath);
|
|
125
|
+
result.warnings.push(`${c.path} appears hand-edited; refreshed version written to ${c.newPath}`);
|
|
126
|
+
}
|
|
127
|
+
const manifestAbs = join(opts.cwd, AGENT_CONTEXT_MANIFEST_PATH);
|
|
128
|
+
await mkdir(dirname(manifestAbs), { recursive: true });
|
|
129
|
+
await writeFile(manifestAbs, JSON.stringify(decision.manifest, null, 2) + "\n", "utf8");
|
|
88
130
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
131
|
+
for (const orphan of decision.removed) {
|
|
132
|
+
result.warnings.push(`${orphan} is no longer part of this stack; orphaned (safe to delete).`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (opts.wireRoot) await wireRootMemory(opts.cwd, result);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const ROOT_IMPORT_LINE = "@.metaobjects/AGENTS.md";
|
|
139
|
+
async function wireRootMemory(cwd: string, result: InitResult): Promise<void> {
|
|
140
|
+
const claudePath = join(cwd, "CLAUDE.md");
|
|
141
|
+
const agentsPath = join(cwd, "AGENTS.md");
|
|
142
|
+
const claudeExists = await fileExists(claudePath);
|
|
143
|
+
const agentsExists = await fileExists(agentsPath);
|
|
144
|
+
|
|
145
|
+
// If neither root memory file exists, create CLAUDE.md (Claude Code's canonical) with the import.
|
|
146
|
+
if (!claudeExists && !agentsExists) {
|
|
147
|
+
await writeFile(claudePath, `# Project memory\n\n${ROOT_IMPORT_LINE}\n`, "utf8");
|
|
148
|
+
result.created.push("CLAUDE.md (created with MetaObjects @import)");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// Otherwise append the import to whichever exist (idempotent — never double-add).
|
|
152
|
+
for (const [path, exists] of [[claudePath, claudeExists], [agentsPath, agentsExists]] as const) {
|
|
153
|
+
if (!exists) continue;
|
|
154
|
+
const body = await readFile(path, "utf8");
|
|
155
|
+
if (body.includes(ROOT_IMPORT_LINE)) continue;
|
|
156
|
+
await writeFile(path, `${body.replace(/\n*$/, "\n")}\n${ROOT_IMPORT_LINE}\n`, "utf8");
|
|
157
|
+
result.warnings.push(`wired ${ROOT_IMPORT_LINE} into ${path.endsWith("AGENTS.md") ? "AGENTS.md" : "CLAUDE.md"} so the MetaObjects context loads`);
|
|
100
158
|
}
|
|
101
159
|
}
|
|
102
160
|
|
|
@@ -109,9 +167,15 @@ export async function init(opts: InitOptions): Promise<InitResult> {
|
|
|
109
167
|
const metaobjectsExists = await dirExists(metaobjectsDir);
|
|
110
168
|
const exists = agentDirExists || metaobjectsExists;
|
|
111
169
|
|
|
170
|
+
if (opts.docsOnly) {
|
|
171
|
+
// Agent-context only: scaffold the always-on + skills + root wiring, never the metaobjects/ project.
|
|
172
|
+
await writeAgentContext(opts, result);
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
|
|
112
176
|
if (opts.refreshDocs && exists && !opts.force) {
|
|
113
|
-
// Refresh-only path: scaffold agent
|
|
114
|
-
await
|
|
177
|
+
// Refresh-only path: scaffold the agent-context, leave everything else alone.
|
|
178
|
+
await writeAgentContext(opts, result);
|
|
115
179
|
return result;
|
|
116
180
|
}
|
|
117
181
|
|
|
@@ -135,7 +199,7 @@ export async function init(opts: InitOptions): Promise<InitResult> {
|
|
|
135
199
|
".metaobjects/.gitignore",
|
|
136
200
|
`.metaobjects/${PACKAGE_MANIFEST_FILE}`,
|
|
137
201
|
);
|
|
138
|
-
|
|
202
|
+
result.created.push(".metaobjects/AGENTS.md", ".metaobjects/CLAUDE.md", ".claude/skills/metaobjects-*", AGENT_CONTEXT_MANIFEST_PATH);
|
|
139
203
|
result.created.push("metaobjects.config.ts");
|
|
140
204
|
return result;
|
|
141
205
|
}
|
|
@@ -214,7 +278,7 @@ export async function init(opts: InitOptions): Promise<InitResult> {
|
|
|
214
278
|
result.preserved.push(`.metaobjects/${PACKAGE_MANIFEST_FILE}`);
|
|
215
279
|
}
|
|
216
280
|
|
|
217
|
-
await
|
|
281
|
+
await writeAgentContext(opts, result);
|
|
218
282
|
|
|
219
283
|
// Scaffold metaobjects.config.ts at the project root. Never overwrite if it exists.
|
|
220
284
|
const forgeConfigPath = join(opts.cwd, "metaobjects.config.ts");
|
|
@@ -283,6 +347,11 @@ export async function initCommand(args: string[], cwd: string): Promise<number>
|
|
|
283
347
|
printOnly: flags.printOnly,
|
|
284
348
|
refreshDocs: flags.refreshDocs,
|
|
285
349
|
d1: flags.d1,
|
|
350
|
+
servers: flags.servers,
|
|
351
|
+
clients: flags.clients,
|
|
352
|
+
noSkills: flags.noSkills,
|
|
353
|
+
wireRoot: flags.wireRoot,
|
|
354
|
+
docsOnly: flags.docsOnly,
|
|
286
355
|
});
|
|
287
356
|
|
|
288
357
|
if (flags.printOnly) {
|
|
@@ -292,7 +361,13 @@ export async function initCommand(args: string[], cwd: string): Promise<number>
|
|
|
292
361
|
}
|
|
293
362
|
|
|
294
363
|
if (!flags.quiet) {
|
|
295
|
-
|
|
364
|
+
if (flags.docsOnly) {
|
|
365
|
+
log.info(`Scaffolded the MetaObjects agent context (${result.created.length} files): .metaobjects/AGENTS.md + .claude/skills/metaobjects-*.`);
|
|
366
|
+
for (const w of result.warnings) log.info(` ${w}`);
|
|
367
|
+
log.info("Re-run --docs-only --refresh-docs to update; --no-wire-root to skip the root CLAUDE.md @import.");
|
|
368
|
+
} else {
|
|
369
|
+
log.info(nextStepsBlock());
|
|
370
|
+
}
|
|
296
371
|
}
|
|
297
372
|
return 0;
|
|
298
373
|
} catch (err) {
|
package/src/commands/verify.ts
CHANGED
|
@@ -10,9 +10,12 @@
|
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
import { parseVerifyArgs } from "../lib/args.js";
|
|
12
12
|
import { log } from "../lib/log.js";
|
|
13
|
+
import { warnIfAgentContextStale } from "../lib/agent-context-staleness.js";
|
|
13
14
|
import { FileProvider } from "../lib/file-provider.js";
|
|
14
15
|
import { derivePayloadFieldTree } from "../lib/payload-field-tree.js";
|
|
15
16
|
import { loadMetaobjectsConfig } from "../lib/load-metaobjects-config.js";
|
|
17
|
+
import { computeCodegenDrift } from "../lib/codegen-drift.js";
|
|
18
|
+
import type { MetaobjectsGenConfig } from "@metaobjectsdev/codegen-ts";
|
|
16
19
|
import { buildKyselyFromUrl, type Dialect } from "../lib/kysely.js";
|
|
17
20
|
import { tokensToAllowOptions, describeChange } from "../lib/allow.js";
|
|
18
21
|
import { computeDrift, type Change } from "@metaobjectsdev/migrate-ts";
|
|
@@ -46,18 +49,38 @@ export async function verifyCommand(args: string[], cwd: string): Promise<number
|
|
|
46
49
|
return 2;
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// —
|
|
53
|
-
//
|
|
54
|
-
|
|
52
|
+
// Advisory: nudge to refresh the .claude/skills docs if they predate this CLI.
|
|
53
|
+
warnIfAgentContextStale(cwd);
|
|
54
|
+
|
|
55
|
+
// ADR-0021 D2 — explicit verify subverbs. Each flag selects one drift mode;
|
|
56
|
+
// any combination runs each and the overall exit code is the MAX (non-zero on
|
|
57
|
+
// any drift). A bare `verify` (no explicit subverb) keeps its documented
|
|
58
|
+
// back-compat default: the template/prompt drift gate — plus a one-line note
|
|
59
|
+
// advertising the explicit subverbs.
|
|
60
|
+
const runTemplates = flags.templates || !flags.anyExplicit;
|
|
61
|
+
// The schema (--db) gate is selected by the presence of --db; that check lives
|
|
62
|
+
// inside runSchemaVerify (where `flags.db === undefined` also narrows the type).
|
|
63
|
+
const runCodegen = flags.codegen;
|
|
64
|
+
if (!flags.anyExplicit) {
|
|
65
|
+
log.info(
|
|
66
|
+
"meta verify — running --templates (default). Explicit subverbs: " +
|
|
67
|
+
"--templates (prompt drift), --db (schema drift), --codegen (codegen drift).",
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Best-effort load of metaobjects.config.ts. Two consumers:
|
|
72
|
+
// 1) consumer-supplied providers (e.g. a `template.toolcall` subtype) threaded
|
|
73
|
+
// into loadMemory — verify doesn't REQUIRE codegen config for templates/db;
|
|
74
|
+
// 2) the full config object, which `--codegen` needs to locate outDir/targets.
|
|
75
|
+
// If absent/invalid we fall back to defaults; `--codegen` then reports a clear
|
|
76
|
+
// error (it can't diff without knowing where the committed output lives).
|
|
77
|
+
let forgeConfig: MetaobjectsGenConfig | undefined;
|
|
55
78
|
try {
|
|
56
|
-
|
|
57
|
-
configProviders = forgeConfig.providers;
|
|
79
|
+
forgeConfig = await loadMetaobjectsConfig(cwd);
|
|
58
80
|
} catch {
|
|
59
|
-
|
|
81
|
+
forgeConfig = undefined;
|
|
60
82
|
}
|
|
83
|
+
const configProviders = forgeConfig?.providers;
|
|
61
84
|
|
|
62
85
|
let root: Awaited<ReturnType<typeof loadMemory>>;
|
|
63
86
|
try {
|
|
@@ -77,13 +100,13 @@ export async function verifyCommand(args: string[], cwd: string): Promise<number
|
|
|
77
100
|
const promptsDir = join(cwd, flags.prompts ?? DEFAULT_PROMPTS_DIR);
|
|
78
101
|
const provider = new FileProvider(promptsDir);
|
|
79
102
|
|
|
80
|
-
// Exit-code composition: the overall result is
|
|
81
|
-
// so
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
const templateExit = runTemplateVerify();
|
|
103
|
+
// Exit-code composition: the overall result is the MAX across every selected
|
|
104
|
+
// subverb so ANY kind of drift fails CI. Each gate only runs when its mode is
|
|
105
|
+
// selected; an unselected gate contributes 0.
|
|
106
|
+
const templateExit = runTemplates ? runTemplateVerify() : 0;
|
|
85
107
|
const schemaExit = await runSchemaVerify();
|
|
86
|
-
|
|
108
|
+
const codegenExit = runCodegen ? await runCodegenVerify() : 0;
|
|
109
|
+
return Math.max(templateExit, schemaExit, codegenExit);
|
|
87
110
|
|
|
88
111
|
// -- template (prompt / output) drift --------------------------------------
|
|
89
112
|
function runTemplateVerify(): number {
|
|
@@ -168,6 +191,8 @@ export async function verifyCommand(args: string[], cwd: string): Promise<number
|
|
|
168
191
|
// Gated on --db. With no --db (or --skip-schema), this is a no-op returning 0
|
|
169
192
|
// — the DB-free default behavior is unchanged.
|
|
170
193
|
async function runSchemaVerify(): Promise<number> {
|
|
194
|
+
// `flags.db === undefined` is exactly `!runDb`; written this way so TS
|
|
195
|
+
// narrows flags.db to `string` for buildKyselyFromUrl below.
|
|
171
196
|
if (flags.db === undefined || flags.skipSchema) return 0;
|
|
172
197
|
|
|
173
198
|
// d1 has no Kysely-driver introspection path, so the schema-drift gate
|
|
@@ -216,6 +241,47 @@ export async function verifyCommand(args: string[], cwd: string): Promise<number
|
|
|
216
241
|
}
|
|
217
242
|
}
|
|
218
243
|
}
|
|
244
|
+
|
|
245
|
+
// -- codegen drift (ADR-0021 D2) -------------------------------------------
|
|
246
|
+
// Gated on --codegen. Regenerates to a temp dir and diffs against the
|
|
247
|
+
// committed output (config outDir / per-target outDirs). Requires a config:
|
|
248
|
+
// without one, there's no committed-output location to diff against, so it
|
|
249
|
+
// errors clearly (exit 2 — a usage/configuration problem, not a drift result).
|
|
250
|
+
async function runCodegenVerify(): Promise<number> {
|
|
251
|
+
if (forgeConfig === undefined) {
|
|
252
|
+
log.error(
|
|
253
|
+
"verify --codegen: no metaobjects.config.ts found (or it is invalid) — " +
|
|
254
|
+
"cannot locate the committed generated output to diff against. " +
|
|
255
|
+
"Run 'meta init' to scaffold one, or run without --codegen.",
|
|
256
|
+
);
|
|
257
|
+
return 2;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
let result;
|
|
261
|
+
try {
|
|
262
|
+
result = await computeCodegenDrift(forgeConfig, root, cwd);
|
|
263
|
+
} catch (err) {
|
|
264
|
+
log.error(`verify --codegen: regeneration failed: ${(err as Error).message}`);
|
|
265
|
+
return 1;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (result.error !== undefined) {
|
|
269
|
+
log.error(result.error);
|
|
270
|
+
return 2;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (result.clean) {
|
|
274
|
+
log.info("meta verify — generated output is in sync with the metadata (no codegen drift).");
|
|
275
|
+
return 0;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
log.error(
|
|
279
|
+
`meta verify — codegen drift (${result.driftedFiles.length} file(s) differ from a fresh regen):`,
|
|
280
|
+
);
|
|
281
|
+
for (const line of result.lines) log.error(` ${line}`);
|
|
282
|
+
log.error("Run 'meta gen' to regenerate, then commit the result.");
|
|
283
|
+
return 1;
|
|
284
|
+
}
|
|
219
285
|
}
|
|
220
286
|
|
|
221
287
|
/**
|
package/src/index.ts
CHANGED
|
@@ -1,33 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { dirname, join, resolve } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
1
|
+
import { resolve } from "node:path";
|
|
4
2
|
import { log } from "./lib/log.js";
|
|
3
|
+
import { cliVersion } from "./lib/version.js";
|
|
5
4
|
export { defineConfig } from "@metaobjectsdev/codegen-ts";
|
|
6
5
|
export type { MetaobjectsGenConfig } from "@metaobjectsdev/codegen-ts";
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
// The compiled entry is dist/src/index.js while package.json sits at the package
|
|
10
|
-
// root, so walk up from the module location until @metaobjectsdev/cli's manifest.
|
|
11
|
-
function readCliVersion(): string {
|
|
12
|
-
let dir = dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
for (let i = 0; i < 6; i++) {
|
|
14
|
-
const candidate = join(dir, "package.json");
|
|
15
|
-
if (existsSync(candidate)) {
|
|
16
|
-
try {
|
|
17
|
-
const pkg = JSON.parse(readFileSync(candidate, "utf8")) as { name?: string; version?: string };
|
|
18
|
-
if (pkg.name === "@metaobjectsdev/cli" && pkg.version) return pkg.version;
|
|
19
|
-
} catch {
|
|
20
|
-
// not our manifest / unreadable — keep walking up
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
const parent = dirname(dir);
|
|
24
|
-
if (parent === dir) break;
|
|
25
|
-
dir = parent;
|
|
26
|
-
}
|
|
27
|
-
return "0.0.0";
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const VERSION = readCliVersion();
|
|
7
|
+
const VERSION = cliVersion();
|
|
31
8
|
|
|
32
9
|
const HELP_TEXT = `meta — MetaObjects CLI (v${VERSION})
|
|
33
10
|
|
|
@@ -39,7 +16,8 @@ COMMANDS:
|
|
|
39
16
|
init --refresh-docs Refresh .metaobjects/AGENTS.md + CLAUDE.md after CLI upgrades
|
|
40
17
|
gen [<entity>...] Codegen TS targets from metaobjects/ entities
|
|
41
18
|
export Flatten loaded metadata to one canonical JSON artifact
|
|
42
|
-
|
|
19
|
+
docs <metadata> --out <dir> Generate neutral metadata documentation (entity + template pages)
|
|
20
|
+
verify Drift gate — subverbs: --templates / --db / --codegen (bare = --templates)
|
|
43
21
|
prompt-snapshot Snapshot rendered template.* output; --check gates drift
|
|
44
22
|
migrate Diff metadata vs live DB; emit migration SQL files
|
|
45
23
|
--version, -v Print version
|
|
@@ -56,10 +34,18 @@ GEN FLAGS:
|
|
|
56
34
|
EXPORT FLAGS:
|
|
57
35
|
--out <file> Write output to a file (default: stdout)
|
|
58
36
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
--
|
|
37
|
+
DOCS FLAGS:
|
|
38
|
+
<metadata> Project root holding metaobjects/ (default: current directory)
|
|
39
|
+
--out <dir>, -o Output directory for the pages (default: ./docs)
|
|
40
|
+
--templates <dir> Project root to resolve adopter templates/ overrides (default: <metadata>)
|
|
41
|
+
|
|
42
|
+
VERIFY FLAGS (ADR-0021 D2 — explicit subverbs; combine any; exit 1 on ANY drift):
|
|
43
|
+
--templates Template/prompt {{field}}↔payload drift (the bare-verify default)
|
|
44
|
+
--codegen Codegen drift — regenerate to a temp dir and diff the committed
|
|
45
|
+
output (config outDir/targets). Needs metaobjects.config.ts; exit 2 if absent.
|
|
46
|
+
--db <url> Schema drift — live DB URL enables the schema-drift gate.
|
|
62
47
|
Supports: file:, libsql:, postgres:, postgresql:. Omit to skip.
|
|
48
|
+
--prompts <dir> Directory of provider-resolved template text (default: prompts)
|
|
63
49
|
--dialect sqlite|postgres Optional override (auto-detected from --db URL scheme)
|
|
64
50
|
--allow <csv> Accepted for parity with 'migrate'; does NOT affect the
|
|
65
51
|
verify drift gate (the gate fails on ANY detected change)
|
|
@@ -140,6 +126,10 @@ export async function run(argv: string[]): Promise<number> {
|
|
|
140
126
|
const { exportCommand } = await import("./commands/export.js");
|
|
141
127
|
return exportCommand(rest, cwd);
|
|
142
128
|
}
|
|
129
|
+
case "docs": {
|
|
130
|
+
const { docsCommand } = await import("./commands/docs.js");
|
|
131
|
+
return docsCommand(rest, cwd);
|
|
132
|
+
}
|
|
143
133
|
case "verify": {
|
|
144
134
|
const { verifyCommand } = await import("./commands/verify.js");
|
|
145
135
|
return verifyCommand(rest, cwd);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { AGENT_CONTEXT_MANIFEST_PATH, agentContextStaleness, type Manifest } from "@metaobjectsdev/sdk";
|
|
4
|
+
import { cliVersion } from "./version.js";
|
|
5
|
+
import { log } from "./log.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Advisory: if a scaffolded MetaObjects agent context predates this CLI version,
|
|
9
|
+
* print a one-line nudge to re-scaffold. Never throws, never blocks — an absent or
|
|
10
|
+
* corrupt manifest is silently ignored (this is a reminder, not a gate).
|
|
11
|
+
*/
|
|
12
|
+
export function warnIfAgentContextStale(cwd: string): void {
|
|
13
|
+
const p = join(cwd, AGENT_CONTEXT_MANIFEST_PATH);
|
|
14
|
+
let manifest: Manifest | undefined;
|
|
15
|
+
if (existsSync(p)) {
|
|
16
|
+
try {
|
|
17
|
+
manifest = JSON.parse(readFileSync(p, "utf8")) as Manifest;
|
|
18
|
+
} catch {
|
|
19
|
+
return; // unreadable/corrupt — say nothing
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const msg = agentContextStaleness({ manifest, currentVersion: cliVersion() });
|
|
23
|
+
if (msg !== null) log.warn(msg);
|
|
24
|
+
}
|
package/src/lib/args.ts
CHANGED
|
@@ -10,6 +10,11 @@ export interface InitFlags {
|
|
|
10
10
|
printOnly: boolean;
|
|
11
11
|
refreshDocs: boolean;
|
|
12
12
|
d1: boolean;
|
|
13
|
+
servers: string[];
|
|
14
|
+
clients: string[];
|
|
15
|
+
noSkills: boolean;
|
|
16
|
+
wireRoot: boolean;
|
|
17
|
+
docsOnly: boolean;
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
export function parseInitArgs(argv: string[]): InitFlags {
|
|
@@ -21,6 +26,11 @@ export function parseInitArgs(argv: string[]): InitFlags {
|
|
|
21
26
|
"print-only": { type: "boolean", default: false },
|
|
22
27
|
"refresh-docs": { type: "boolean", default: false },
|
|
23
28
|
d1: { type: "boolean", default: false },
|
|
29
|
+
server: { type: "string", multiple: true },
|
|
30
|
+
client: { type: "string", multiple: true },
|
|
31
|
+
"no-skills": { type: "boolean", default: false },
|
|
32
|
+
"no-wire-root": { type: "boolean", default: false },
|
|
33
|
+
"docs-only": { type: "boolean", default: false },
|
|
24
34
|
},
|
|
25
35
|
strict: true,
|
|
26
36
|
allowPositionals: false,
|
|
@@ -31,6 +41,11 @@ export function parseInitArgs(argv: string[]): InitFlags {
|
|
|
31
41
|
printOnly: !!values["print-only"],
|
|
32
42
|
refreshDocs: !!values["refresh-docs"],
|
|
33
43
|
d1: !!values.d1,
|
|
44
|
+
servers: (values.server as string[] | undefined) ?? [],
|
|
45
|
+
clients: (values.client as string[] | undefined) ?? [],
|
|
46
|
+
noSkills: !!values["no-skills"],
|
|
47
|
+
wireRoot: !values["no-wire-root"],
|
|
48
|
+
docsOnly: !!values["docs-only"],
|
|
34
49
|
};
|
|
35
50
|
}
|
|
36
51
|
|
|
@@ -45,6 +60,9 @@ export interface GenFlags {
|
|
|
45
60
|
* (existing content becomes the canonical baseline). "fresh" → overwrite
|
|
46
61
|
* and re-baseline. */
|
|
47
62
|
baseline: "default" | "fresh";
|
|
63
|
+
/** ADR-0021 D3 — print the stable-name generator registry and exit without
|
|
64
|
+
* running codegen. */
|
|
65
|
+
list: boolean;
|
|
48
66
|
}
|
|
49
67
|
|
|
50
68
|
export function parseGenArgs(argv: string[]): GenFlags {
|
|
@@ -53,6 +71,7 @@ export function parseGenArgs(argv: string[]): GenFlags {
|
|
|
53
71
|
options: {
|
|
54
72
|
"dry-run": { type: "boolean", default: false },
|
|
55
73
|
"baseline": { type: "string" },
|
|
74
|
+
"list": { type: "boolean", default: false },
|
|
56
75
|
},
|
|
57
76
|
strict: true,
|
|
58
77
|
allowPositionals: true,
|
|
@@ -67,6 +86,7 @@ export function parseGenArgs(argv: string[]): GenFlags {
|
|
|
67
86
|
dryRun: !!values["dry-run"],
|
|
68
87
|
entities: positionals,
|
|
69
88
|
baseline: (baselineRaw as "default" | "fresh" | undefined) ?? "default",
|
|
89
|
+
list: !!values.list,
|
|
70
90
|
};
|
|
71
91
|
}
|
|
72
92
|
|
|
@@ -124,6 +144,16 @@ export interface VerifyFlags {
|
|
|
124
144
|
allow: AllowToken[];
|
|
125
145
|
/** Skip the schema-drift gate even when --db is present. */
|
|
126
146
|
skipSchema: boolean;
|
|
147
|
+
// ADR-0021 D2 — explicit verify subverbs. Each selects one drift mode; any
|
|
148
|
+
// combination may be passed and the exit code aggregates (non-zero on any
|
|
149
|
+
// drift). The boolean flags record which modes were explicitly requested; the
|
|
150
|
+
// command layer applies the bare-verify default (= --templates) when none are.
|
|
151
|
+
/** Run the template/prompt {{field}}↔payload drift gate. */
|
|
152
|
+
templates: boolean;
|
|
153
|
+
/** Run the codegen-drift gate (regenerate-to-temp and diff committed output). */
|
|
154
|
+
codegen: boolean;
|
|
155
|
+
/** Whether ANY explicit subverb flag (--templates/--db/--codegen) was passed. */
|
|
156
|
+
anyExplicit: boolean;
|
|
127
157
|
}
|
|
128
158
|
|
|
129
159
|
export function parseVerifyArgs(argv: string[]): VerifyFlags {
|
|
@@ -135,6 +165,8 @@ export function parseVerifyArgs(argv: string[]): VerifyFlags {
|
|
|
135
165
|
dialect: { type: "string" },
|
|
136
166
|
allow: { type: "string" },
|
|
137
167
|
"skip-schema": { type: "boolean", default: false },
|
|
168
|
+
templates: { type: "boolean", default: false },
|
|
169
|
+
codegen: { type: "boolean", default: false },
|
|
138
170
|
},
|
|
139
171
|
strict: true,
|
|
140
172
|
allowPositionals: false,
|
|
@@ -157,12 +189,21 @@ export function parseVerifyArgs(argv: string[]): VerifyFlags {
|
|
|
157
189
|
}
|
|
158
190
|
}
|
|
159
191
|
|
|
192
|
+
const templates = !!values.templates;
|
|
193
|
+
const codegen = !!values.codegen;
|
|
194
|
+
// --db is itself an explicit subverb selector: passing a connection URL means
|
|
195
|
+
// "run the schema-drift mode". So "any explicit subverb" is templates|codegen|db.
|
|
196
|
+
const anyExplicit = templates || codegen || values.db !== undefined;
|
|
197
|
+
|
|
160
198
|
return {
|
|
161
199
|
prompts: values.prompts,
|
|
162
200
|
db: values.db as string | undefined,
|
|
163
201
|
dialect: dialect as Dialect | undefined,
|
|
164
202
|
allow: allowTokens as AllowToken[],
|
|
165
203
|
skipSchema: !!values["skip-schema"],
|
|
204
|
+
templates,
|
|
205
|
+
codegen,
|
|
206
|
+
anyExplicit,
|
|
166
207
|
};
|
|
167
208
|
}
|
|
168
209
|
|