@vmz/vmz 0.0.1 → 0.0.3

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.
Files changed (79) hide show
  1. package/README.md +52 -2
  2. package/bin/vmz.js +4 -0
  3. package/dist/application-cmd.d.ts +21 -0
  4. package/dist/application-cmd.js +347 -0
  5. package/dist/bundler-adapter.d.ts +63 -0
  6. package/dist/bundler-adapter.js +110 -0
  7. package/dist/cli.d.ts +23 -0
  8. package/dist/cli.js +474 -0
  9. package/dist/dev-session.d.ts +35 -0
  10. package/dist/dev-session.js +290 -0
  11. package/dist/document-build.d.ts +99 -0
  12. package/dist/document-build.js +273 -0
  13. package/dist/document-check.d.ts +44 -0
  14. package/dist/document-check.js +246 -0
  15. package/dist/document-cmd.d.ts +8 -0
  16. package/dist/document-cmd.js +146 -0
  17. package/dist/document-designs.d.ts +9 -0
  18. package/dist/document-designs.js +126 -0
  19. package/dist/document-enrich.d.ts +23 -0
  20. package/dist/document-enrich.js +233 -0
  21. package/dist/document-evidence.d.ts +49 -0
  22. package/dist/document-evidence.js +509 -0
  23. package/dist/document-integrate.d.ts +34 -0
  24. package/dist/document-integrate.js +88 -0
  25. package/dist/document-interactive.d.ts +69 -0
  26. package/dist/document-interactive.js +254 -0
  27. package/dist/document-locale.d.ts +31 -0
  28. package/dist/document-locale.js +59 -0
  29. package/dist/document-markdown.d.ts +12 -0
  30. package/dist/document-markdown.js +45 -0
  31. package/dist/document-scan.d.ts +21 -0
  32. package/dist/document-scan.js +151 -0
  33. package/dist/document-schema.d.ts +86 -0
  34. package/dist/document-schema.js +87 -0
  35. package/dist/explain-cmd.d.ts +5 -0
  36. package/dist/explain-cmd.js +123 -0
  37. package/dist/index.d.ts +359 -0
  38. package/dist/index.js +580 -0
  39. package/dist/invocation.d.ts +91 -0
  40. package/dist/invocation.js +190 -0
  41. package/dist/locale-check.d.ts +106 -0
  42. package/dist/locale-check.js +736 -0
  43. package/dist/locale-cmd.d.ts +5 -0
  44. package/dist/locale-cmd.js +442 -0
  45. package/dist/locale-delivery.d.ts +298 -0
  46. package/dist/locale-delivery.js +443 -0
  47. package/dist/locale-router.d.ts +206 -0
  48. package/dist/locale-router.js +507 -0
  49. package/dist/locale-runtime.d.ts +406 -0
  50. package/dist/locale-runtime.js +541 -0
  51. package/dist/locale-schema.d.ts +8 -0
  52. package/dist/locale-schema.js +9 -0
  53. package/dist/locale-tooling.d.ts +118 -0
  54. package/dist/locale-tooling.js +357 -0
  55. package/dist/log.d.ts +19 -0
  56. package/dist/log.js +42 -0
  57. package/dist/packages.d.ts +26 -0
  58. package/dist/packages.js +146 -0
  59. package/dist/plugin-host.d.ts +29 -0
  60. package/dist/plugin-host.js +369 -0
  61. package/dist/refactor-cmd.d.ts +8 -0
  62. package/dist/refactor-cmd.js +156 -0
  63. package/dist/resolve-native-cli.d.ts +14 -0
  64. package/dist/resolve-native-cli.js +84 -0
  65. package/dist/resolve.d.ts +24 -0
  66. package/dist/resolve.js +55 -0
  67. package/dist/test-cmd.d.ts +9 -0
  68. package/dist/test-cmd.js +363 -0
  69. package/dist/test-compile.d.ts +2 -0
  70. package/dist/test-compile.js +3 -0
  71. package/dist/test-discover.d.ts +2 -0
  72. package/dist/test-discover.js +3 -0
  73. package/dist/test-logic.d.ts +2 -0
  74. package/dist/test-logic.js +3 -0
  75. package/dist/test-protocol.d.ts +2 -0
  76. package/dist/test-protocol.js +3 -0
  77. package/dist/watch-diff.d.ts +17 -0
  78. package/dist/watch-diff.js +56 -0
  79. package/package.json +96 -3
@@ -0,0 +1,369 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Plugin protocol v1 helpers + typed config loading.
4
+ */
5
+ import { existsSync } from 'node:fs';
6
+ import path from 'node:path';
7
+ import { pathToFileURL } from 'node:url';
8
+ import { createJiti } from 'jiti';
9
+ import { PLUGIN_PROTOCOL as PLUGIN_PROTOCOL_PKG, contentHash, defineConfig, definePlugin } from '@vmz/plugin';
10
+ import { log } from './log.js';
11
+ import { resolveWorkspacePackages } from './packages.js';
12
+ export { contentHash, defineConfig, definePlugin };
13
+ export const PLUGIN_PROTOCOL = PLUGIN_PROTOCOL_PKG;
14
+ const CONFIG_NAMES = ['vmz.config.ts', 'vmz.config.mts', 'vmz.config.mjs', 'vmz.config.js'];
15
+ const ROOT_PLUGIN_NAMES = ['vmz.plugin.ts', 'vmz.plugin.mts', 'vmz.plugin.mjs', 'vmz.plugin.js'];
16
+ /**
17
+ * @param {string} full
18
+ * @returns {Promise<any>}
19
+ */
20
+ export async function importMaybeTs(full) {
21
+ const ext = path.extname(full).toLowerCase();
22
+ if (ext === '.ts' || ext === '.mts') {
23
+ const jiti = createJiti(import.meta.url, {
24
+ interopDefault: true,
25
+ moduleCache: false,
26
+ });
27
+ return jiti(full);
28
+ }
29
+ const mod = await import(pathToFileURL(full).href);
30
+ return mod.default ?? mod;
31
+ }
32
+ /**
33
+ * Load `vmz.config.*` from project root (+ optional root `vmz.plugin.*`).
34
+ * @param {string} project
35
+ * @returns {Promise<{ plugins: import('@vmz/plugin').VmzPlugin[], engines: import('@vmz/plugin').VmzEngines, path: string | null, pluginPath: string | null }>}
36
+ */
37
+ export async function loadVmzConfig(project) {
38
+ /** @type {import('@vmz/plugin').VmzPlugin[]} */
39
+ const plugins = [];
40
+ /** @type {import('@vmz/plugin').VmzEngines} */
41
+ let engines = {};
42
+ /** @type {string | null} */
43
+ let configPath = null;
44
+ /** @type {string | null} */
45
+ let pluginPath = null;
46
+ for (const name of CONFIG_NAMES) {
47
+ const full = path.join(project, name);
48
+ if (!existsSync(full))
49
+ continue;
50
+ configPath = full;
51
+ const cfg = await importMaybeTs(full);
52
+ const raw = cfg?.plugins ?? [];
53
+ engines = cfg?.engines && typeof cfg.engines === 'object' ? { ...cfg.engines } : {};
54
+ for (const entry of raw) {
55
+ plugins.push(await resolvePluginEntry(project, entry));
56
+ }
57
+ break;
58
+ }
59
+ for (const name of ROOT_PLUGIN_NAMES) {
60
+ const full = path.join(project, name);
61
+ if (!existsSync(full))
62
+ continue;
63
+ pluginPath = full;
64
+ plugins.push(await resolvePluginEntry(project, full));
65
+ break;
66
+ }
67
+ return { plugins, engines, path: configPath, pluginPath };
68
+ }
69
+ /**
70
+ * @param {string} project
71
+ * @param {string | import('@vmz/plugin').VmzPlugin | Promise<import('@vmz/plugin').VmzPlugin>} entry
72
+ */
73
+ async function resolvePluginEntry(project, entry) {
74
+ let value = entry;
75
+ if (typeof value === 'string') {
76
+ const resolved = path.isAbsolute(value) ? value : path.join(project, value);
77
+ value = await importMaybeTs(resolved);
78
+ if (value && typeof value === 'object' && 'default' in value && value.default) {
79
+ value = value.default;
80
+ }
81
+ }
82
+ value = await value;
83
+ if (value?.manifest && (value.contribute || value.manifest.stages))
84
+ return value;
85
+ if (value?.name && value?.stages)
86
+ return definePlugin(value);
87
+ throw new Error(`invalid vmz plugin entry: ${String(entry)}`);
88
+ }
89
+ /**
90
+ * Collect + apply contribution batches for the given stages onto a Workspace.
91
+ * @param {import('../index.js').Workspace} workspace
92
+ * @param {import('@vmz/plugin').VmzPlugin[]} plugins
93
+ * @param {{ project: string, outDir: string, stages?: string[], engines?: import('@vmz/plugin').VmzEngines }} opts
94
+ */
95
+ export async function applyPlugins(workspace, plugins, opts) {
96
+ const stages = opts.stages ?? ['workspace_resolve', 'source_adapter', 'analyzer', 'target'];
97
+ const packages = resolveWorkspacePackages(opts.project);
98
+ const engines = opts.engines ?? {};
99
+ /** @type {import('../index.js').ApplyContributionsReport[]} */
100
+ const reports = [];
101
+ /** @type {{ code: Set<string>, math: Set<string>, markdown: Set<string> }} */
102
+ const registered = {
103
+ code: new Set(),
104
+ math: new Set(),
105
+ markdown: new Set(),
106
+ };
107
+ for (const stage of stages) {
108
+ for (const plugin of plugins) {
109
+ if (!plugin.manifest.stages.includes(stage))
110
+ continue;
111
+ if (!plugin.contribute)
112
+ continue;
113
+ const ctx = {
114
+ project: opts.project,
115
+ outDir: opts.outDir,
116
+ stage,
117
+ protocol: PLUGIN_PROTOCOL,
118
+ packages,
119
+ engines,
120
+ };
121
+ const raw = await plugin.contribute(ctx);
122
+ const batches = Array.isArray(raw) ? raw : [raw];
123
+ for (const batch of batches) {
124
+ if (!batch || (batch.stage && batch.stage !== stage)) {
125
+ if (batch?.stage && batch.stage !== stage)
126
+ continue;
127
+ }
128
+ for (const item of batch.items ?? []) {
129
+ noteEngineRegistration(registered, item);
130
+ }
131
+ const payload = {
132
+ pluginName: plugin.manifest.name,
133
+ pluginVersion: plugin.manifest.version,
134
+ protocol: plugin.manifest.protocol ?? PLUGIN_PROTOCOL,
135
+ stage: batch.stage ?? stage,
136
+ cacheKey: batch.cacheKey ?? `${plugin.manifest.name}@${plugin.manifest.version}:${stage}`,
137
+ deterministic: batch.deterministic ?? plugin.manifest.deterministic ?? true,
138
+ items: (batch.items ?? []).map(normalizeItem),
139
+ };
140
+ const report = workspace.applyPluginContributions(payload);
141
+ reports.push(report);
142
+ if (report.rejected?.length) {
143
+ for (const r of report.rejected) {
144
+ log.warn(`plugin reject ${r.plugin}::${r.itemId}: ${r.reason}`);
145
+ }
146
+ }
147
+ else {
148
+ log.info(`plugin ${plugin.manifest.name} stage=${stage} accepted=${report.accepted}`);
149
+ }
150
+ }
151
+ }
152
+ }
153
+ if (registered.math.size || registered.code.size) {
154
+ const facadeReports = await materializeEngineFacades(workspace, opts.project, engines, registered);
155
+ reports.push(...facadeReports);
156
+ }
157
+ return reports;
158
+ }
159
+ /**
160
+ * Only math/code/markdown register interchangeable engines (see design .
161
+ * @param {{ code: Set<string>, math: Set<string>, markdown: Set<string> }} registered
162
+ * @param {any} item
163
+ */
164
+ function noteEngineRegistration(registered, item) {
165
+ const engine = item?.engine;
166
+ if (!engine || typeof engine !== 'string')
167
+ return;
168
+ const kind = item.engineKind ?? item.engine_kind;
169
+ if (kind === 'math')
170
+ registered.math.add(engine);
171
+ else if (kind === 'code')
172
+ registered.code.add(engine);
173
+ else if (kind === 'markdown')
174
+ registered.markdown.add(engine);
175
+ }
176
+ /**
177
+ * Host-generated facades from registered engines + config defaults.
178
+ * @param {import('../index.js').Workspace} workspace
179
+ * @param {string} project
180
+ * @param {import('@vmz/plugin').VmzEngines} engines
181
+ * @param {{ code: Set<string>, math: Set<string>, markdown: Set<string> }} registered
182
+ */
183
+ async function materializeEngineFacades(workspace, project, engines, registered) {
184
+ /** @type {import('../index.js').ApplyContributionsReport[]} */
185
+ const reports = [];
186
+ const items = [];
187
+ if (registered.math.size) {
188
+ const defaultMath = pickDefault(engines.math, registered.math, 'katex');
189
+ const content = buildMathFacade(defaultMath, [...registered.math]);
190
+ items.push(sourceItem('facade-math', 'src/components/Math.vmz', content));
191
+ }
192
+ if (registered.code.size) {
193
+ const defaultCode = pickDefault(engines.code, registered.code, 'shiki');
194
+ const content = buildCodeFacade(defaultCode, [...registered.code]);
195
+ items.push(sourceItem('facade-code', 'src/components/Code.vmz', content));
196
+ }
197
+ if (registered.markdown.size) {
198
+ const defaultMd = pickDefault(engines.markdown, registered.markdown, 'markdown-it');
199
+ const content = buildMarkdownFacade(defaultMd, [...registered.markdown]);
200
+ items.push(sourceItem('facade-markdown', 'src/components/Markdown.vmz', content));
201
+ }
202
+ if (!items.length)
203
+ return reports;
204
+ const cacheKey = [
205
+ 'engine-facades',
206
+ [...registered.math].sort().join(','),
207
+ [...registered.code].sort().join(','),
208
+ [...registered.markdown].sort().join(','),
209
+ engines.math ?? '',
210
+ engines.code ?? '',
211
+ engines.markdown ?? '',
212
+ ].join('|');
213
+ const report = workspace.applyPluginContributions({
214
+ pluginName: 'vmz:engine-facades',
215
+ pluginVersion: '0.1.0',
216
+ protocol: PLUGIN_PROTOCOL,
217
+ stage: 'workspace_resolve',
218
+ cacheKey,
219
+ deterministic: true,
220
+ items: items.map(normalizeItem),
221
+ });
222
+ reports.push(report);
223
+ if (report.rejected?.length) {
224
+ for (const r of report.rejected) {
225
+ log.warn(`plugin reject ${r.plugin}::${r.itemId}: ${r.reason}`);
226
+ }
227
+ }
228
+ else {
229
+ log.info(`plugin vmz:engine-facades stage=workspace_resolve accepted=${report.accepted}`);
230
+ }
231
+ return reports;
232
+ }
233
+ /**
234
+ * @param {string | undefined} configured
235
+ * @param {Set<string>} registered
236
+ * @param {string} preferred
237
+ */
238
+ function pickDefault(configured, registered, preferred) {
239
+ if (configured && registered.has(configured))
240
+ return configured;
241
+ if (registered.has(preferred))
242
+ return preferred;
243
+ return [...registered][0];
244
+ }
245
+ /** @param {string} id @param {string} path @param {string} content */
246
+ function sourceItem(id, path, content) {
247
+ return {
248
+ id,
249
+ kind: 'source',
250
+ path,
251
+ content,
252
+ contentHash: contentHash(content),
253
+ materialize: true,
254
+ };
255
+ }
256
+ /**
257
+ * @param {string} defaultEngine
258
+ * @param {string[]} engines
259
+ */
260
+ function buildMathFacade(defaultEngine, engines) {
261
+ const defaultLit = JSON.stringify(defaultEngine);
262
+ const branches = engines
263
+ .map((eng, i) => {
264
+ const tag = engineToTag(eng);
265
+ const kw = i === 0 ? 'if' : 'else-if';
266
+ const engLit = JSON.stringify(eng);
267
+ return ` <${tag} ${kw}={(engine || ${defaultLit}) === ${engLit}} tex={tex} display={display} />`;
268
+ })
269
+ .join('\n');
270
+ return `<template>
271
+ ${branches}
272
+ </template>
273
+
274
+ <script client>
275
+ export default class Math {
276
+ public tex: string = '';
277
+ public display: boolean = false;
278
+ public engine: string | null = null;
279
+ }
280
+ </script>
281
+ `;
282
+ }
283
+ /**
284
+ * @param {string} defaultEngine
285
+ * @param {string[]} engines
286
+ */
287
+ function buildCodeFacade(defaultEngine, engines) {
288
+ const defaultLit = JSON.stringify(defaultEngine);
289
+ const branches = engines
290
+ .map((eng, i) => {
291
+ const tag = engineToTag(eng);
292
+ const kw = i === 0 ? 'if' : 'else-if';
293
+ const engLit = JSON.stringify(eng);
294
+ return ` <${tag} ${kw}={(engine || ${defaultLit}) === ${engLit}} code={code} lang={lang} theme={theme} />`;
295
+ })
296
+ .join('\n');
297
+ return `<template>
298
+ ${branches}
299
+ </template>
300
+
301
+ <script client>
302
+ export default class Code {
303
+ public code: string = '';
304
+ public lang: string = 'text';
305
+ public theme: string | null = null;
306
+ public engine: string | null = null;
307
+ }
308
+ </script>
309
+ `;
310
+ }
311
+ /**
312
+ * @param {string} defaultEngine
313
+ * @param {string[]} engines
314
+ */
315
+ function buildMarkdownFacade(defaultEngine, engines) {
316
+ const defaultLit = JSON.stringify(defaultEngine);
317
+ const branches = engines
318
+ .map((eng, i) => {
319
+ const tag = engineToTag(eng);
320
+ const kw = i === 0 ? 'if' : 'else-if';
321
+ const engLit = JSON.stringify(eng);
322
+ return ` <${tag} ${kw}={(engine || ${defaultLit}) === ${engLit}} source={source} />`;
323
+ })
324
+ .join('\n');
325
+ return `<template>
326
+ ${branches}
327
+ </template>
328
+
329
+ <script client>
330
+ export default class Markdown {
331
+ public source: string = '';
332
+ public engine: string | null = null;
333
+ }
334
+ </script>
335
+ `;
336
+ }
337
+ /** @param {string} engine */
338
+ function engineToTag(engine) {
339
+ const map = {
340
+ katex: 'Katex',
341
+ mathjax: 'Mathjax',
342
+ shiki: 'Shiki',
343
+ 'markdown-it': 'MarkdownIt',
344
+ };
345
+ if (map[engine])
346
+ return map[engine];
347
+ return engine
348
+ .split(/[-_]/)
349
+ .map((p) => p.charAt(0).toUpperCase() + p.slice(1))
350
+ .join('');
351
+ }
352
+ /** @param {any} item */
353
+ function normalizeItem(item) {
354
+ return {
355
+ id: item.id,
356
+ kind: item.kind,
357
+ path: item.path,
358
+ content: item.content,
359
+ contentHash: item.contentHash ?? item.content_hash,
360
+ materialize: item.materialize,
361
+ severity: item.severity,
362
+ message: item.message,
363
+ code: item.code,
364
+ targetId: item.targetId ?? item.target_id,
365
+ targetKind: item.targetKind ?? item.target_kind ?? item.type,
366
+ manifestJson: item.manifestJson ?? item.manifest_json ?? (item.manifest != null ? JSON.stringify(item.manifest) : undefined),
367
+ detail: item.detail,
368
+ };
369
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `vmz refactor` — RouteId/field safe rename (plan + atomic apply).
3
+ */
4
+ /**
5
+ * @param {string[]} argv args after `refactor`
6
+ * @returns {Promise<number>}
7
+ */
8
+ export declare function cmdRefactor(argv: any): Promise<0 | 1>;
@@ -0,0 +1,156 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * `vmz refactor` — RouteId/field safe rename (plan + atomic apply).
4
+ */
5
+ import { createWorkspace } from './index.js';
6
+ import { log } from './log.js';
7
+ import { resolveWorkspaceDirs } from './resolve.js';
8
+ /**
9
+ * @param {string[]} argv args after `refactor`
10
+ * @returns {Promise<number>}
11
+ */
12
+ export async function cmdRefactor(argv) {
13
+ const [sub, ...rest] = argv;
14
+ if (!sub || sub === 'help' || sub === '-h' || sub === '--help') {
15
+ printRefactorHelp();
16
+ return 0;
17
+ }
18
+ if (sub === 'rename') {
19
+ return cmdRename(rest);
20
+ }
21
+ log.error(`unknown refactor subcommand \`${sub}\``);
22
+ printRefactorHelp();
23
+ return 1;
24
+ }
25
+ function printRefactorHelp() {
26
+ console.log(`vmz refactor — workspace edit plans
27
+
28
+ Usage:
29
+ vmz refactor rename --kind <route_id|field|method|component|capability> --from <id> --to <id> [path]
30
+ [--scope <chunk>] [--json] [--apply] [--explain]
31
+
32
+ Notes:
33
+ Returns WorkspaceEditPlan (\`vmz.dx.workspace_edit.v0\`).
34
+ route_id / field emit proven TextEdits; --apply writes atomically when status=ready.
35
+ `);
36
+ }
37
+ /**
38
+ * @param {string[]} argv
39
+ */
40
+ function parseRefactorArgs(argv) {
41
+ /** @type {Record<string, string | boolean> & { _: string[] }} */
42
+ const out = { _: [] };
43
+ for (let i = 0; i < argv.length; i++) {
44
+ const a = argv[i];
45
+ if (a.startsWith('--')) {
46
+ const eq = a.indexOf('=');
47
+ if (eq !== -1) {
48
+ out[a.slice(2, eq)] = a.slice(eq + 1);
49
+ continue;
50
+ }
51
+ const key = a.slice(2);
52
+ const next = argv[i + 1];
53
+ if (next && !next.startsWith('-')) {
54
+ out[key] = next;
55
+ i += 1;
56
+ }
57
+ else {
58
+ out[key] = true;
59
+ }
60
+ continue;
61
+ }
62
+ out._.push(a);
63
+ }
64
+ return out;
65
+ }
66
+ /**
67
+ * @param {string[]} argv
68
+ */
69
+ function cmdRename(argv) {
70
+ const args = parseRefactorArgs(argv);
71
+ const kind = typeof args.kind === 'string' ? args.kind : '';
72
+ const from = typeof args.from === 'string' ? args.from : '';
73
+ const to = typeof args.to === 'string' ? args.to : '';
74
+ const scope = typeof args.scope === 'string' ? args.scope : undefined;
75
+ const wantJson = args.json === true || typeof args.json === 'string';
76
+ const wantApply = args.apply === true;
77
+ const wantExplain = args.explain === true;
78
+ if (!kind || !from || !to) {
79
+ log.error('rename requires --kind, --from, and --to');
80
+ printRefactorHelp();
81
+ return 1;
82
+ }
83
+ const pathArg = args._[0] ?? '.';
84
+ const { project, outDir } = resolveWorkspaceDirs({
85
+ path: pathArg,
86
+ outDir: typeof args['out-dir'] === 'string' ? args['out-dir'] : undefined,
87
+ });
88
+ const intent = {
89
+ schema: 'vmz.dx.rename.v0',
90
+ kind,
91
+ from,
92
+ to,
93
+ ...(scope ? { scope } : {}),
94
+ };
95
+ const ws = createWorkspace({ root: project, outDir });
96
+ try {
97
+ if (typeof ws.planRename !== 'function') {
98
+ log.error('planRename missing on Workspace — rebuild native (`pnpm napi:build`)');
99
+ return 1;
100
+ }
101
+ let planRaw = ws.planRename(JSON.stringify(intent));
102
+ let plan;
103
+ try {
104
+ plan = JSON.parse(planRaw);
105
+ }
106
+ catch (e) {
107
+ log.error(`plan_rename not JSON: ${e}`);
108
+ return 1;
109
+ }
110
+ if (wantApply) {
111
+ if (typeof ws.applyWorkspaceEdit !== 'function') {
112
+ log.error('applyWorkspaceEdit missing — rebuild native (`pnpm napi:build`)');
113
+ return 1;
114
+ }
115
+ planRaw = ws.applyWorkspaceEdit(planRaw);
116
+ try {
117
+ plan = JSON.parse(planRaw);
118
+ }
119
+ catch (e) {
120
+ log.error(`apply_workspace_edit not JSON: ${e}`);
121
+ return 1;
122
+ }
123
+ }
124
+ if (wantExplain && typeof ws.explainRenameChain === 'function') {
125
+ const explainRaw = ws.explainRenameChain(JSON.stringify(intent));
126
+ if (wantJson) {
127
+ console.log(JSON.stringify({ plan, explain: JSON.parse(explainRaw) }, null, 2));
128
+ return plan.status === 'rejected' ? 1 : 0;
129
+ }
130
+ const explain = JSON.parse(explainRaw);
131
+ log.info(`explain chain edges=${(explain.chain || []).length}`);
132
+ }
133
+ if (wantJson) {
134
+ console.log(JSON.stringify(plan, null, 2));
135
+ }
136
+ else {
137
+ log.info(`rename ${kind} \`${from}\` → \`${to}\` → ${plan.status}`);
138
+ for (const p of plan.preconditions || []) {
139
+ console.log(` precondition: ${p}`);
140
+ }
141
+ for (const e of plan.edits || []) {
142
+ console.log(` edit ${e.path} @${e.start}..${e.end} → ${JSON.stringify(e.newText)}`);
143
+ }
144
+ for (const d of plan.diagnostics || []) {
145
+ console.log(` ${d.severity || 'info'}: ${d.message}`);
146
+ }
147
+ if ((plan.edits || []).length === 0) {
148
+ console.log(' edits: (none)');
149
+ }
150
+ }
151
+ return plan.status === 'rejected' ? 1 : 0;
152
+ }
153
+ finally {
154
+ ws.dispose();
155
+ }
156
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Resolve the native `vmz` CLI binary (vmz-tools), never the Node wrapper.
3
+ * Used by `vmz lsp` / `vmz mcp` so stdio servers stay one binary for all hosts.
4
+ */
5
+ /**
6
+ * @param {string} [startDir]
7
+ * @returns {string | null}
8
+ */
9
+ export declare function findRepoRoot(startDir?: string): string;
10
+ /**
11
+ * @param {{ cwd?: string }} [opts]
12
+ * @returns {string | null} absolute path to native vmz binary
13
+ */
14
+ export declare function resolveNativeVmzCli(opts?: {}): any;
@@ -0,0 +1,84 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Resolve the native `vmz` CLI binary (vmz-tools), never the Node wrapper.
4
+ * Used by `vmz lsp` / `vmz mcp` so stdio servers stay one binary for all hosts.
5
+ */
6
+ import { existsSync, statSync } from 'node:fs';
7
+ import path from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ const exe = process.platform === 'win32' ? 'vmz.exe' : 'vmz';
10
+ /**
11
+ * @param {string} [startDir]
12
+ * @returns {string | null}
13
+ */
14
+ export function findRepoRoot(startDir = process.cwd()) {
15
+ let dir = path.resolve(startDir);
16
+ for (let i = 0; i < 12; i++) {
17
+ if (existsSync(path.join(dir, 'Cargo.toml')) && existsSync(path.join(dir, 'package.json'))) {
18
+ return dir;
19
+ }
20
+ const parent = path.dirname(dir);
21
+ if (parent === dir)
22
+ break;
23
+ dir = parent;
24
+ }
25
+ return null;
26
+ }
27
+ /**
28
+ * Prefer the newer of release/debug so a fresh `cargo build` is not shadowed by a stale release.
29
+ * @param {string} repo
30
+ * @returns {string | null}
31
+ */
32
+ function pickNewestProfileBinary(repo) {
33
+ /** @type {{ path: string, mtime: number } | null} */
34
+ let best = null;
35
+ for (const profile of ['release', 'debug']) {
36
+ const candidate = path.join(repo, 'target', profile, exe);
37
+ if (!existsSync(candidate))
38
+ continue;
39
+ let mtime = 0;
40
+ try {
41
+ mtime = statSync(candidate).mtimeMs;
42
+ }
43
+ catch {
44
+ continue;
45
+ }
46
+ if (!best || mtime > best.mtime)
47
+ best = { path: candidate, mtime };
48
+ }
49
+ return best?.path ?? null;
50
+ }
51
+ /**
52
+ * @param {{ cwd?: string }} [opts]
53
+ * @returns {string | null} absolute path to native vmz binary
54
+ */
55
+ export function resolveNativeVmzCli(opts = {}) {
56
+ if (typeof process.env.VMZ_NATIVE === 'string' && process.env.VMZ_NATIVE.trim()) {
57
+ const p = path.resolve(process.env.VMZ_NATIVE.trim());
58
+ if (existsSync(p))
59
+ return p;
60
+ }
61
+ const roots = [];
62
+ if (opts.cwd)
63
+ roots.push(path.resolve(opts.cwd));
64
+ roots.push(process.cwd());
65
+ try {
66
+ const here = path.dirname(fileURLToPath(import.meta.url));
67
+ // packages/runtimes/vmz/dist → repo root
68
+ roots.push(path.resolve(here, '../../../..'));
69
+ }
70
+ catch {
71
+ /* ignore */
72
+ }
73
+ const seen = new Set();
74
+ for (const start of roots) {
75
+ const repo = findRepoRoot(start);
76
+ if (!repo || seen.has(repo))
77
+ continue;
78
+ seen.add(repo);
79
+ const picked = pickNewestProfileBinary(repo);
80
+ if (picked)
81
+ return picked;
82
+ }
83
+ return null;
84
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared project path resolution for the Node CLI host .
3
+ */
4
+ /**
5
+ * @param {string} startDir
6
+ * @returns {string | null}
7
+ */
8
+ export declare function findPackageJson(startDir: any): string;
9
+ /**
10
+ * Resolve project root + out dir from CLI args / cwd.
11
+ * Prefers an explicit path; otherwise walks up for package.json with `src/`.
12
+ *
13
+ * @param {{ cwd?: string, path?: string, outDir?: string }} opts
14
+ */
15
+ export declare function resolveWorkspaceDirs(opts?: {}): {
16
+ project: string;
17
+ outDir: any;
18
+ cwd: any;
19
+ };
20
+ /**
21
+ * @param {string} projectRoot
22
+ * @returns {{ name?: string, private?: boolean } | null}
23
+ */
24
+ export declare function readPackageMeta(projectRoot: any): any;