@kb-labs/cli-commands 2.94.0 → 2.96.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.
@@ -3,11 +3,11 @@ import path2 from 'path';
3
3
  import { fileURLToPath, pathToFileURL } from 'url';
4
4
  import { existsSync, promises } from 'fs';
5
5
  import { createHash } from 'crypto';
6
+ import { computeManifestIntegrity } from '@kb-labs/core-discovery';
6
7
  import { parse } from 'yaml';
7
8
  import { glob } from 'glob';
8
9
  import { toPosixPath } from '@kb-labs/shared-cli-ui';
9
10
  import { z } from 'zod';
10
- import Ajv from 'ajv';
11
11
  import { platform } from '@kb-labs/core-runtime';
12
12
  import { getLogLevel } from '@kb-labs/cli-runtime';
13
13
 
@@ -107,62 +107,37 @@ var FlagDefinitionSchema = z.object({
107
107
  choices: z.array(z.string()).optional(),
108
108
  required: z.boolean().optional()
109
109
  }).refine(
110
- (data) => {
111
- if (data.choices && data.type !== "string") {
112
- return false;
113
- }
114
- return true;
115
- },
110
+ (data) => !(data.choices && data.type !== "string"),
116
111
  { message: "Choices are only allowed for string type flags" }
117
112
  );
118
113
  var EngineSchema = z.object({
119
114
  node: z.string().optional(),
120
- // e.g., ">=18", "^18.0.0"
121
115
  kbCli: z.string().optional(),
122
- // e.g., "^1.5.0"
123
116
  module: z.enum(["esm", "cjs"]).optional()
124
117
  }).optional();
125
118
  var CommandManifestSchema = z.object({
126
119
  manifestVersion: z.literal("1.0"),
127
- // Legacy fields (still supported)
128
- id: z.string().min(1).regex(/^[a-z0-9-]+:[a-z0-9-]+(?:[:a-z0-9-]+)*$/, 'Command ID must be in format "namespace:command"'),
129
- aliases: z.array(z.string()).optional(),
120
+ segments: z.array(z.string().min(1)).min(1),
121
+ id: z.string().min(1),
130
122
  group: z.string().min(1),
123
+ subgroup: z.string().optional(),
124
+ aliases: z.array(z.string()).optional(),
125
+ category: z.string().optional(),
131
126
  describe: z.string().min(1),
132
127
  longDescription: z.string().optional(),
133
128
  requires: z.array(z.string()).optional(),
134
- // Package names with optional semver
135
129
  flags: z.array(FlagDefinitionSchema).optional(),
136
130
  examples: z.array(z.string()).optional(),
137
131
  loader: z.any(),
138
- // Function validation happens at runtime
139
- // New fields (optional for backward compatibility)
140
132
  package: z.string().optional(),
141
- // Full package name
142
- namespace: z.string().optional(),
143
- // Derived from id if not provided
144
133
  engine: EngineSchema,
145
134
  permissions: z.array(z.string()).optional(),
146
- // e.g., ["fs.read", "git.read", "net.fetch"]
147
135
  telemetry: z.enum(["opt-in", "off"]).optional(),
148
136
  manifestV2: z.any().optional(),
149
- // Full ManifestV3 for sandbox execution
150
- // Internal flags for auto-generated commands
151
- isSetup: z.boolean().optional(),
152
- // Auto-generated setup command
153
- isSetupRollback: z.boolean().optional(),
154
- // Auto-generated setup rollback command
155
- pkgRoot: z.string().optional()
156
- // Package root path for handler resolution
137
+ pkgRoot: z.string().optional(),
138
+ _synthetic: z.boolean().optional(),
139
+ operationType: z.enum(["read", "mutate", "execute", "analyze"]).optional()
157
140
  }).refine(
158
- (data) => {
159
- if (data.namespace && data.group && data.namespace !== data.group) {
160
- return false;
161
- }
162
- return true;
163
- },
164
- { message: "namespace must match group" }
165
- ).refine(
166
141
  (data) => {
167
142
  if (data.requires) {
168
143
  for (const req2 of data.requires) {
@@ -189,46 +164,36 @@ function validateManifests(manifests) {
189
164
  if (result.success) {
190
165
  validated.push(result.data);
191
166
  } else {
192
- const errorWithIndex = new z.ZodError([
193
- ...result.error.issues.map((issue) => ({
194
- ...issue,
195
- path: [`[${i}]`, ...issue.path]
196
- }))
197
- ]);
198
- errors.push(errorWithIndex);
167
+ errors.push(
168
+ new z.ZodError(
169
+ result.error.issues.map((issue) => ({ ...issue, path: [`[${i}]`, ...issue.path] }))
170
+ )
171
+ );
199
172
  }
200
173
  }
201
- if (errors.length === 0) {
202
- return { success: true, data: validated };
203
- }
204
- return { success: false, errors };
174
+ return errors.length === 0 ? { success: true, data: validated } : { success: false, errors };
205
175
  }
206
- function normalizeManifest(manifest, packageName, namespace) {
207
- const normalized = { ...manifest };
208
- if (!normalized.namespace && normalized.group) {
209
- normalized.namespace = normalized.group;
210
- } else if (!normalized.namespace && namespace) {
211
- normalized.namespace = namespace;
212
- }
213
- if (!normalized.group && normalized.namespace) {
214
- normalized.group = normalized.namespace;
215
- }
216
- if (!normalized.package) {
217
- normalized.package = packageName;
218
- }
219
- return normalized;
176
+ function normalizeManifest(manifest, packageName) {
177
+ const segs = manifest.segments;
178
+ return {
179
+ ...manifest,
180
+ id: segs[segs.length - 1] ?? "",
181
+ group: segs[0] ?? "",
182
+ subgroup: segs.length >= 3 ? segs[1] : void 0,
183
+ package: manifest.package ?? packageName
184
+ };
220
185
  }
221
186
 
222
187
  // src/registry/discover.ts
188
+ var DISCOVERY_VERSION = 2;
223
189
  var DEBUG_MODE = process.env.DEBUG_SANDBOX === "1" || process.env.NODE_ENV === "development";
224
190
  var log = (level, message, fields) => {
225
191
  if (!DEBUG_MODE) {
226
192
  return;
227
193
  }
228
194
  const prefix = level === "error" ? "\u2717" : level === "warn" ? "\u26A0" : level === "info" ? "\u2139" : "\u{1F50D}";
229
- const logFn = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
230
195
  {
231
- logFn(`${prefix} [discover] ${message}`);
196
+ console.error(`${prefix} [discover] ${message}`);
232
197
  }
233
198
  };
234
199
  function createManifestV3Loader(commandId) {
@@ -250,6 +215,14 @@ var __test = {
250
215
  createManifestV3Loader
251
216
  // resetInProcCache is exported directly (not via __test) since it's part of the public API
252
217
  };
218
+ var NonPluginManifestError = class extends Error {
219
+ schema;
220
+ constructor(pkgName, schema) {
221
+ super(`${pkgName} uses manifest schema "${schema}", not kb.plugin/3 \u2014 skipping`);
222
+ this.name = "NonPluginManifestError";
223
+ this.schema = schema;
224
+ }
225
+ };
253
226
  function createUnavailableManifest(pkgName, error) {
254
227
  const rawMsg = (error instanceof Error ? error.message : String(error) || "").toString();
255
228
  let missing = null;
@@ -264,9 +237,11 @@ function createUnavailableManifest(pkgName, error) {
264
237
  const group = (seg || pkgName).replace(/-cli$/, "");
265
238
  const short = seg || pkgName;
266
239
  const requires = missing ? [missing] : [];
240
+ const unavailableId = `manifest:${short}`;
267
241
  const manifest = {
268
242
  manifestVersion: "1.0",
269
- id: `${group}:manifest:${short}`,
243
+ segments: [group, unavailableId],
244
+ id: unavailableId,
270
245
  group,
271
246
  describe: `Commands from ${pkgName} are unavailable`,
272
247
  requires,
@@ -289,14 +264,6 @@ var inProcDiscoveryCache = null;
289
264
  function resetInProcCache() {
290
265
  inProcDiscoveryCache = null;
291
266
  }
292
- async function computeManifestHash(manifestPath) {
293
- try {
294
- const content = await promises.readFile(manifestPath, "utf8");
295
- return createHash("sha256").update(content).digest("hex");
296
- } catch {
297
- return "unknown";
298
- }
299
- }
300
267
  async function computeLockfileHash(cwd) {
301
268
  const lockfilePath = path2.join(cwd, "pnpm-lock.yaml");
302
269
  try {
@@ -402,8 +369,12 @@ async function loadManifest(manifestPath, pkgName, pkgRoot) {
402
369
  const mod = await import(fileUrl);
403
370
  const modTyped = mod;
404
371
  const rawManifest = modTyped.manifest || modTyped.default;
405
- if (!rawManifest || typeof rawManifest !== "object" || rawManifest.schema !== "kb.plugin/3") {
406
- throw new Error(`Unsupported manifest format in ${pkgName}. Only kb.plugin/3 schema is supported.`);
372
+ if (!rawManifest || typeof rawManifest !== "object") {
373
+ throw new Error(`No manifest export found in ${pkgName}`);
374
+ }
375
+ const schema = rawManifest.schema;
376
+ if (schema !== "kb.plugin/3") {
377
+ throw new NonPluginManifestError(pkgName, typeof schema === "string" ? schema : String(schema ?? "unknown"));
407
378
  }
408
379
  const manifest = rawManifest;
409
380
  const namespace = getNamespaceFromManifest(manifest, pkgName);
@@ -414,20 +385,23 @@ async function loadManifest(manifestPath, pkgName, pkgRoot) {
414
385
  log("warn", `ManifestV3 ${manifest.id || pkgName} has no CLI commands or setup entry`);
415
386
  }
416
387
  const commandManifests = cliCommands.map((cmd) => {
417
- const commandId = cmd.id;
388
+ const segments = cmd.path.trim().split(/\s+/).filter(Boolean);
389
+ const commandId = segments[segments.length - 1] ?? namespace;
418
390
  const commandManifest = {
419
391
  manifestVersion: "1.0",
392
+ segments,
420
393
  id: commandId,
421
- group: cmd.group || namespace,
422
- subgroup: cmd.subgroup,
394
+ group: segments[0] ?? namespace,
395
+ subgroup: segments.length >= 3 ? segments[1] : void 0,
396
+ category: cmd.category,
423
397
  describe: cmd.describe || "",
424
398
  longDescription: cmd.longDescription,
425
399
  aliases: cmd.aliases,
426
400
  flags: cmd.flags,
427
401
  examples: cmd.examples,
428
- loader: createManifestV3Loader(commandId),
402
+ loader: createManifestV3Loader(cmd.path),
429
403
  package: pkgName,
430
- namespace: cmd.group || namespace
404
+ operationType: cmd.operationType
431
405
  };
432
406
  commandManifest.manifestV2 = manifest;
433
407
  commandManifest.pkgRoot = baseRoot;
@@ -441,7 +415,7 @@ async function loadManifest(manifestPath, pkgName, pkgRoot) {
441
415
  log("warn", `ManifestV3 validation warnings for ${pkgName}: ${errorMessages}`);
442
416
  }
443
417
  return (validation.success ? validation.data : commandManifests).map(
444
- (m) => normalizeManifest(m, pkgName, namespace)
418
+ (m) => normalizeManifest(m, pkgName)
445
419
  );
446
420
  }
447
421
  async function readPackageJson(pkgPath) {
@@ -512,10 +486,11 @@ function validateUniqueIds(manifests, pkgName) {
512
486
  const ids = /* @__PURE__ */ new Set();
513
487
  const aliases = /* @__PURE__ */ new Set();
514
488
  for (const m of manifests) {
515
- if (ids.has(m.id)) {
489
+ const key = m.segments.join("/");
490
+ if (ids.has(key)) {
516
491
  throw new Error(`Duplicate command ID "${m.id}" in package ${pkgName}`);
517
492
  }
518
- ids.add(m.id);
493
+ ids.add(key);
519
494
  if (m.aliases) {
520
495
  for (const alias of m.aliases) {
521
496
  if (aliases.has(alias) || ids.has(alias)) {
@@ -608,6 +583,10 @@ async function loadManifestsForPackages(packageInfos, source, scope) {
608
583
  log("debug", `[plugins][perf] ${pkgName} failed after ${pkgTime}ms`);
609
584
  const errMsg = err instanceof Error ? err.message : String(err);
610
585
  const errCode = err.code ?? "UNKNOWN";
586
+ if (err instanceof NonPluginManifestError) {
587
+ log("debug", `[plugins] ${pkgName} skipped: ${err.message}`);
588
+ return null;
589
+ }
611
590
  log("warn", JSON.stringify({
612
591
  code: "DISCOVERY_MANIFEST_LOAD_FAIL",
613
592
  packageName: pkgName,
@@ -778,6 +757,10 @@ async function discoverNodeModules(cwd) {
778
757
  } catch (err) {
779
758
  const errMsg = err instanceof Error ? err.message : String(err);
780
759
  const errCode = err.code ?? "UNKNOWN";
760
+ if (err instanceof NonPluginManifestError) {
761
+ log("debug", `[plugins] ${pkgName} skipped: ${err.message}`);
762
+ return null;
763
+ }
781
764
  log("warn", JSON.stringify({
782
765
  code: "DISCOVERY_MANIFEST_LOAD_FAIL",
783
766
  packageName: pkgName,
@@ -867,6 +850,10 @@ async function loadCache(cwd, roots) {
867
850
  log("debug", "Cache invalidated: Node version changed");
868
851
  return null;
869
852
  }
853
+ if ((cache.discoveryVersion ?? 0) !== DISCOVERY_VERSION) {
854
+ log("debug", `Cache invalidated: discovery logic changed (${cache.discoveryVersion ?? 0} \u2192 ${DISCOVERY_VERSION})`);
855
+ return null;
856
+ }
870
857
  const currentCliVersion = process.env.CLI_VERSION || "0.1.0";
871
858
  if (cache.cliVersion !== currentCliVersion) {
872
859
  log("debug", "Cache invalidated: CLI version changed");
@@ -920,7 +907,7 @@ async function loadCache(cwd, roots) {
920
907
  return null;
921
908
  }
922
909
  }
923
- async function isPackageCacheStale(entry, options) {
910
+ async function isPackageCacheStale(entry) {
924
911
  const manifestFsPath = entry.manifestPath.split("/").join(path2.sep);
925
912
  const pkgJsonPath = path2.join(entry.result.pkgRoot.split("/").join(path2.sep), PACKAGE_JSON);
926
913
  try {
@@ -933,26 +920,23 @@ async function isPackageCacheStale(entry, options) {
933
920
  log("debug", `Package cache invalidated: missing package.json for ${entry.result.packageName} (${error instanceof Error ? error.message : "unknown"})`);
934
921
  return true;
935
922
  }
936
- let manifestStat;
923
+ let manifestMtimeChanged = false;
937
924
  try {
938
- manifestStat = await promises.stat(manifestFsPath);
939
- if (manifestStat.mtimeMs !== entry.manifestMtime) {
940
- log("debug", `Package cache invalidated: manifest mtime changed for ${entry.result.packageName}`);
941
- return true;
942
- }
925
+ const manifestStat = await promises.stat(manifestFsPath);
926
+ manifestMtimeChanged = manifestStat.mtimeMs !== entry.manifestMtime;
943
927
  } catch (error) {
944
928
  log("debug", `Package cache invalidated: manifest deleted for ${entry.result.packageName} (${error instanceof Error ? error.message : "unknown"})`);
945
929
  return true;
946
930
  }
947
- if (options.validateHash) {
931
+ if (manifestMtimeChanged) {
948
932
  try {
949
- const currentHash = await computeManifestHash(manifestFsPath);
933
+ const currentHash = await computeManifestIntegrity(manifestFsPath);
950
934
  if (currentHash !== entry.manifestHash) {
951
- log("debug", `Package cache invalidated: manifest hash changed for ${entry.result.packageName}`);
935
+ log("debug", `Package cache invalidated: manifest content changed for ${entry.result.packageName}`);
952
936
  return true;
953
937
  }
954
938
  } catch (error) {
955
- log("debug", `Package cache hash validation failed for ${entry.result.packageName}: ${error instanceof Error ? error.message : "unknown"}`);
939
+ log("debug", `Package cache hash check failed for ${entry.result.packageName}: ${error instanceof Error ? error.message : "unknown"}`);
956
940
  return true;
957
941
  }
958
942
  }
@@ -971,7 +955,7 @@ async function saveCache(cwd, results, roots) {
971
955
  continue;
972
956
  }
973
957
  try {
974
- const manifestHash = await computeManifestHash(result.manifestPath);
958
+ const manifestHash = await computeManifestIntegrity(result.manifestPath);
975
959
  const pkgJsonPath = path2.join(result.pkgRoot.split("/").join(path2.sep), PACKAGE_JSON);
976
960
  const pkgStat = await promises.stat(pkgJsonPath);
977
961
  const pkg = await readPackageJson(pkgJsonPath);
@@ -1026,6 +1010,7 @@ async function saveCache(cwd, results, roots) {
1026
1010
  const cache = {
1027
1011
  version: process.version,
1028
1012
  cliVersion: process.env.CLI_VERSION || "0.1.0",
1013
+ discoveryVersion: DISCOVERY_VERSION,
1029
1014
  timestamp: now,
1030
1015
  ttlMs: DISK_CACHE_TTL_MS,
1031
1016
  stateHash: stateHasher.digest("hex"),
@@ -1073,11 +1058,10 @@ async function discoverManifests(cwd, noCache = false, options = {}) {
1073
1058
  const freshResults = [];
1074
1059
  const cacheAge = Date.now() - cached.timestamp;
1075
1060
  const ttlMs = cached.ttlMs ?? DISK_CACHE_TTL_MS;
1076
- const enforceHashValidation = cacheAge >= ttlMs;
1077
- log("debug", `[plugins][cache] hit age=${cacheAge}ms ttl=${ttlMs}ms validateHash=${enforceHashValidation}`);
1061
+ log("debug", `[plugins][cache] hit age=${cacheAge}ms ttl=${ttlMs}ms`);
1078
1062
  let staleCount = 0;
1079
1063
  for (const entry of Object.values(cached.packages)) {
1080
- const stale = await isPackageCacheStale(entry, { validateHash: enforceHashValidation });
1064
+ const stale = await isPackageCacheStale(entry);
1081
1065
  if (!stale) {
1082
1066
  freshResults.push(entry.result);
1083
1067
  } else {
@@ -1144,7 +1128,19 @@ async function discoverManifests(cwd, noCache = false, options = {}) {
1144
1128
  if (projectWorkspace.length > 0) {
1145
1129
  log("info", `Discovered ${projectWorkspace.length} project workspace packages with CLI manifests`);
1146
1130
  }
1147
- } catch {
1131
+ } catch (err) {
1132
+ const msg = err instanceof Error ? err.message : String(err);
1133
+ const isMissingYaml = msg.includes("ENOENT") || msg.includes("no such file");
1134
+ if (isMissingYaml) {
1135
+ log("debug", `[plugins][discover] no pnpm-workspace.yaml at projectRoot=${roots.projectRoot} \u2014 skipping project workspace scan`);
1136
+ } else {
1137
+ log("warn", JSON.stringify({
1138
+ code: "PROJECT_WORKSPACE_DISCOVERY_FAILED",
1139
+ projectRoot: roots.projectRoot,
1140
+ error: msg,
1141
+ hint: "Project workspace plugins may be missing from CLI discovery"
1142
+ }));
1143
+ }
1148
1144
  }
1149
1145
  }
1150
1146
  const dedupStart = Date.now();
@@ -1170,81 +1166,37 @@ async function discoverManifestsByNamespace(cwd, namespace, noCache = false) {
1170
1166
  const allResults = await discoverManifests(cwd, noCache);
1171
1167
  return allResults.filter((result) => {
1172
1168
  return result.manifests.some((m) => {
1173
- const manifestNamespace = m.namespace || m.group;
1174
- return manifestNamespace === namespace;
1169
+ return m.group === namespace;
1175
1170
  });
1176
1171
  });
1177
1172
  }
1178
- var ajv = new Ajv();
1179
- var flagSchema = {
1180
- type: "object",
1181
- required: ["name", "type"],
1182
- additionalProperties: false,
1183
- properties: {
1184
- name: { type: "string" },
1185
- type: { enum: ["string", "boolean", "number", "array"] },
1186
- alias: { type: "string", pattern: "^[a-z]$" },
1187
- // single letter
1188
- description: { type: "string" },
1189
- choices: { type: "array", items: { type: "string" } },
1190
- required: { type: "boolean" },
1191
- default: {}
1192
- // must match type
1193
- }
1194
- };
1195
- var manifestSchema = {
1196
- type: "object",
1197
- required: ["manifestVersion", "id", "group", "describe", "loader"],
1198
- additionalProperties: true,
1199
- properties: {
1200
- manifestVersion: { type: "string", enum: ["1.0"] },
1201
- id: { type: "string", pattern: "^[a-z0-9-]+(?::[a-z0-9-]+)*$" },
1202
- // Simple or namespaced (colon-separated)
1203
- aliases: { type: "array", items: { type: "string" } },
1204
- group: { type: "string" },
1205
- describe: { type: "string" },
1206
- longDescription: { type: "string" },
1207
- requires: { type: "array", items: { type: "string" } },
1208
- flags: {
1209
- type: "array",
1210
- items: flagSchema
1211
- },
1212
- examples: { type: "array", items: { type: "string" } }
1213
- }
1214
- };
1215
- var validateManifest = ajv.compile(manifestSchema);
1216
- var validateFlag = ajv.compile(flagSchema);
1217
- function validateFlagDef(flag, manifestId) {
1218
- if (!validateFlag(flag)) {
1173
+ function validateManifestStructure(manifest) {
1174
+ if (manifest.manifestVersion !== "1.0") {
1219
1175
  throw new Error(
1220
- `Invalid flag "${flag.name}" in ${manifestId}: ${ajv.errorsText(validateFlag.errors)}`
1176
+ `Unsupported manifestVersion "${manifest.manifestVersion}" in ${manifest.segments?.join(" ") ?? manifest.id} (expected "1.0")`
1221
1177
  );
1222
1178
  }
1223
- if (flag.choices && flag.type !== "string") {
1224
- throw new Error(`Flag "${flag.name}" in ${manifestId}: choices allowed only for string type`);
1225
- }
1226
- if (flag.default !== void 0) {
1227
- const typeMatches = flag.type === "string" && typeof flag.default === "string" || flag.type === "boolean" && typeof flag.default === "boolean" || flag.type === "number" && typeof flag.default === "number" || flag.type === "array" && Array.isArray(flag.default);
1228
- if (!typeMatches) {
1229
- throw new Error(
1230
- `Flag "${flag.name}" in ${manifestId}: default value type mismatch (expected ${flag.type})`
1231
- );
1232
- }
1179
+ if (!Array.isArray(manifest.segments) || manifest.segments.length === 0) {
1180
+ throw new Error(`Missing or empty segments in manifest ${manifest.id}`);
1233
1181
  }
1234
- }
1235
- function validateManifestStructure(manifest) {
1236
- if (!validateManifest(manifest)) {
1237
- throw new Error(`Invalid manifest ${manifest.id}: ${ajv.errorsText(validateManifest.errors)}`);
1182
+ if (!manifest.describe) {
1183
+ throw new Error(`Missing describe in manifest ${manifest.segments.join(" ")}`);
1238
1184
  }
1239
- if (manifest.manifestVersion !== "1.0") {
1240
- throw new Error(
1241
- `Unsupported manifestVersion "${manifest.manifestVersion}" in ${manifest.id} (expected "1.0")`
1242
- );
1185
+ if (!manifest.loader && !manifest.manifestV2) {
1186
+ throw new Error(`Command ${manifest.segments.join(" ")} must have either loader or manifestV2`);
1243
1187
  }
1244
- if (manifest.flags && Array.isArray(manifest.flags) && manifest.id) {
1188
+ if (manifest.flags) {
1245
1189
  for (const flag of manifest.flags) {
1246
- if (flag && typeof flag === "object" && flag.name) {
1247
- validateFlagDef(flag, manifest.id);
1190
+ if (!flag.name || !flag.type) {
1191
+ throw new Error(`Invalid flag in ${manifest.segments.join(" ")}: missing name or type`);
1192
+ }
1193
+ if (flag.alias && flag.alias.length !== 1) {
1194
+ throw new Error(`Flag "${flag.name}" in ${manifest.segments.join(" ")}: alias must be a single character`);
1195
+ }
1196
+ if (flag.choices && flag.type !== "string") {
1197
+ throw new Error(
1198
+ `Flag "${flag.name}" in ${manifest.segments.join(" ")}: choices only allowed for string type`
1199
+ );
1248
1200
  }
1249
1201
  }
1250
1202
  }
@@ -1255,61 +1207,8 @@ var SOURCE_PRIORITY = {
1255
1207
  linked: 2,
1256
1208
  node_modules: 1
1257
1209
  };
1258
- function normalizeCommandId(id, _namespace) {
1259
- return id;
1260
- }
1261
- function generateWhitespaceAliases(id) {
1262
- if (!id.includes(":")) {
1263
- return [];
1264
- }
1265
- return [id.replace(":", " ")];
1266
- }
1267
- function normalizeAliases(manifest, logger) {
1268
- const log2 = logger ?? platform.logger;
1269
- const aliases = /* @__PURE__ */ new Set();
1270
- manifest.namespace || manifest.group;
1271
- if (manifest.aliases) {
1272
- for (const alias of manifest.aliases) {
1273
- if (!/^[a-z0-9-:]+$/i.test(alias)) {
1274
- log2.warn(`Invalid alias "${alias}" in ${manifest.id}: aliases must be alphanumeric with hyphens or colons`);
1275
- continue;
1276
- }
1277
- aliases.add(alias);
1278
- }
1279
- }
1280
- const whitespaceAliases = generateWhitespaceAliases(manifest.id);
1281
- for (const alias of whitespaceAliases) {
1282
- aliases.add(alias);
1283
- }
1284
- return Array.from(aliases);
1285
- }
1286
- function checkNamespaceCollision(manifest, existing, namespace) {
1287
- const existingGroup = existing.manifest.group || "";
1288
- const currentGroup = manifest.group || "";
1289
- if (existingGroup === currentGroup && existingGroup === namespace && existing.manifest.id === manifest.id) {
1290
- throw new Error(
1291
- `Command collision in group "${namespace}": "${manifest.id}" conflicts with existing "${existing.manifest.id}". Rename one of the commands to use a different ID (e.g., "${manifest.id}2" or use --alias to create a different alias).`
1292
- );
1293
- }
1294
- }
1295
1210
  function getSourcePriority(source) {
1296
- return SOURCE_PRIORITY[source] || 0;
1297
- }
1298
- function checkCollision(manifest, existing, currentSource, namespace) {
1299
- const existingPriority = getSourcePriority(existing.source);
1300
- const currentPriority = getSourcePriority(currentSource);
1301
- checkNamespaceCollision(manifest, existing, namespace);
1302
- if (currentSource === "workspace" && existing.source === "workspace") {
1303
- throw new Error(
1304
- `Command ID collision: "${manifest.id}" is exported by multiple workspace packages. This is not allowed. Please rename one of the commands to use a different ID.`
1305
- );
1306
- }
1307
- if (currentPriority > existingPriority) {
1308
- return { shouldShadow: true };
1309
- } else if (currentPriority < existingPriority) {
1310
- return { shouldShadow: false };
1311
- }
1312
- return { shouldShadow: false };
1211
+ return SOURCE_PRIORITY[source] ?? 0;
1313
1212
  }
1314
1213
  function preflightManifests(discoveryResults, logger) {
1315
1214
  const log2 = logger ?? platform.logger;
@@ -1324,24 +1223,17 @@ function preflightManifests(discoveryResults, logger) {
1324
1223
  allowed.push(manifest);
1325
1224
  } catch (error) {
1326
1225
  const reason = error instanceof Error ? error.message : "Validation failed";
1327
- const manifestId = manifest?.id || manifest?.group || result.packageName || "unknown";
1328
- skipped.push({
1329
- id: manifestId,
1330
- source: result.source,
1331
- reason
1332
- });
1333
- log2.warn(`Preflight skipped manifest ${manifestId}: ${reason}`);
1226
+ const id = manifest?.segments?.join(" ") ?? manifest?.id ?? result.packageName ?? "unknown";
1227
+ skipped.push({ id, source: result.source, reason });
1228
+ log2.warn(`Preflight skipped manifest ${id}: ${reason}`);
1334
1229
  if (logLevel === "debug") {
1335
- process.stderr.write(`[debug][preflight] skipped ${manifestId} (${result.source}): ${reason}
1230
+ process.stderr.write(`[debug][preflight] skipped ${id} (${result.source}): ${reason}
1336
1231
  `);
1337
1232
  }
1338
1233
  }
1339
1234
  }
1340
1235
  if (allowed.length > 0) {
1341
- valid.push({
1342
- ...result,
1343
- manifests: allowed
1344
- });
1236
+ valid.push({ ...result, manifests: allowed });
1345
1237
  }
1346
1238
  }
1347
1239
  return { valid, skipped };
@@ -1351,41 +1243,27 @@ async function registerManifests(discoveryResults, registry2, options = {}) {
1351
1243
  const registered = [];
1352
1244
  const skipped = [];
1353
1245
  const globalIds = /* @__PURE__ */ new Map();
1354
- const globalAliases = /* @__PURE__ */ new Map();
1355
1246
  const logLevel = getLogLevel();
1356
1247
  let collisions = 0;
1357
1248
  let errors = 0;
1358
1249
  const sorted = [...discoveryResults].sort((a, b) => {
1359
- const priorityA = getSourcePriority(a.source);
1360
- const priorityB = getSourcePriority(b.source);
1361
- return priorityB - priorityA;
1250
+ return getSourcePriority(b.source) - getSourcePriority(a.source);
1362
1251
  });
1363
1252
  for (const result of sorted) {
1364
1253
  for (const manifest of result.manifests) {
1365
- const manifestId = manifest.id || manifest.group || "unknown";
1366
- const namespace = manifest.namespace || manifest.group;
1254
+ const manifestId = manifest.segments?.join(" ") ?? manifest.id ?? "unknown";
1367
1255
  try {
1368
1256
  try {
1369
1257
  validateManifestStructure(manifest);
1370
1258
  } catch (err) {
1371
1259
  throw new Error(`Validation failed: ${err instanceof Error ? err.message : String(err)}`);
1372
1260
  }
1373
- const normalizedId = normalizeCommandId(manifest.id, namespace);
1374
- if (normalizedId !== manifest.id) {
1375
- log2.warn(`Command ID "${manifest.id}" normalized to "${normalizedId}"`);
1376
- manifest.id = normalizedId;
1377
- }
1378
- const normalizedAliases = normalizeAliases(manifest, log2);
1379
- if (normalizedAliases.length > 0) {
1380
- manifest.aliases = normalizedAliases;
1381
- }
1382
1261
  const availability = checkRequires(manifest, {
1383
1262
  cwd: options.cwd ?? result.pkgRoot
1384
1263
  });
1385
1264
  const cmd = {
1386
1265
  manifest,
1387
1266
  v3Manifest: manifest.manifestV2,
1388
- // Extract V3 manifest from legacy field
1389
1267
  available: availability.available,
1390
1268
  unavailableReason: availability.available ? void 0 : availability.reason,
1391
1269
  hint: availability.available ? void 0 : availability.hint,
@@ -1396,14 +1274,14 @@ async function registerManifests(discoveryResults, registry2, options = {}) {
1396
1274
  };
1397
1275
  try {
1398
1276
  const manifestModule = await import(result.manifestPath);
1399
- if (manifestModule.init && typeof manifestModule.init === "function") {
1277
+ if (typeof manifestModule.init === "function") {
1400
1278
  await manifestModule.init({
1401
1279
  cwd: result.pkgRoot,
1402
1280
  package: result.packageName,
1403
1281
  manifest: cmd.manifest
1404
1282
  });
1405
1283
  }
1406
- if (manifestModule.register && typeof manifestModule.register === "function") {
1284
+ if (typeof manifestModule.register === "function") {
1407
1285
  await manifestModule.register({
1408
1286
  registry: registry2,
1409
1287
  command: cmd,
@@ -1411,18 +1289,25 @@ async function registerManifests(discoveryResults, registry2, options = {}) {
1411
1289
  package: result.packageName
1412
1290
  });
1413
1291
  }
1414
- if (manifestModule.dispose && typeof manifestModule.dispose === "function") {
1292
+ if (typeof manifestModule.dispose === "function") {
1415
1293
  cmd._disposeHook = manifestModule.dispose;
1416
1294
  }
1417
1295
  } catch (hookError) {
1418
1296
  const hookMsg = hookError instanceof Error ? hookError.message : String(hookError);
1419
- log2.debug(`Lifecycle hooks unavailable for ${manifest.id}: ${hookMsg}`);
1297
+ log2.debug(`Lifecycle hooks unavailable for ${manifestId}: ${hookMsg}`);
1420
1298
  }
1421
- const canonicalKey = manifest.group ? `${manifest.group}:${manifest.id}` : manifest.id;
1299
+ const canonicalKey = manifest.segments.join(":");
1422
1300
  const existing = globalIds.get(canonicalKey);
1423
1301
  if (existing) {
1424
- const collision = checkCollision(manifest, existing, result.source, namespace);
1425
- if (collision.shouldShadow) {
1302
+ if (existing.source === result.source && existing.source === "workspace") {
1303
+ collisions++;
1304
+ throw new Error(
1305
+ `Command path collision: "${manifestId}" exported by multiple workspace packages.`
1306
+ );
1307
+ }
1308
+ const existPri = getSourcePriority(existing.source);
1309
+ const curPri = getSourcePriority(result.source);
1310
+ if (curPri > existPri) {
1426
1311
  existing.shadowed = true;
1427
1312
  globalIds.set(canonicalKey, cmd);
1428
1313
  if (logLevel === "info" || logLevel === "debug") {
@@ -1437,63 +1322,15 @@ async function registerManifests(discoveryResults, registry2, options = {}) {
1437
1322
  } else {
1438
1323
  globalIds.set(canonicalKey, cmd);
1439
1324
  }
1440
- const aliasesToCheck = manifest.aliases || [];
1441
- for (const alias of aliasesToCheck) {
1442
- const existingAlias = globalAliases.get(alias);
1443
- if (existingAlias) {
1444
- if (existingAlias.manifest.id === manifest.id) {
1445
- continue;
1446
- }
1447
- const existingPriority = getSourcePriority(existingAlias.source);
1448
- const currentPriority = getSourcePriority(result.source);
1449
- if (currentPriority > existingPriority) {
1450
- existingAlias.shadowed = true;
1451
- globalAliases.set(alias, cmd);
1452
- if (logLevel === "info" || logLevel === "debug") {
1453
- log2.info(`Alias "${alias}" from ${result.source} shadows ${existingAlias.source} version`);
1454
- }
1455
- } else if (currentPriority < existingPriority) {
1456
- if (logLevel === "info" || logLevel === "debug") {
1457
- log2.info(`Alias "${alias}" from ${result.source} shadowed by ${existingAlias.source} version`);
1458
- }
1459
- continue;
1460
- } else {
1461
- collisions++;
1462
- throw new Error(
1463
- `Alias collision: "${alias}" used by both ${manifest.id} and ${existingAlias.manifest.id}. Rename one alias or use a different namespace.`
1464
- );
1465
- }
1466
- }
1467
- if (globalIds.has(alias)) {
1468
- const conflictingCmd = globalIds.get(alias);
1469
- const existingPriority = getSourcePriority(conflictingCmd.source);
1470
- const currentPriority = getSourcePriority(result.source);
1471
- if (currentPriority > existingPriority) {
1472
- log2.warn(`Alias "${alias}" conflicts with command ID "${alias}". Alias will shadow command.`);
1473
- globalAliases.set(alias, cmd);
1474
- } else {
1475
- throw new Error(
1476
- `Alias "${alias}" conflicts with existing command ID "${alias}". Rename the alias or use a different name.`
1477
- );
1478
- }
1479
- } else {
1480
- globalAliases.set(alias, cmd);
1481
- }
1482
- }
1483
1325
  if (!cmd.shadowed) {
1484
1326
  registry2.registerManifest(cmd);
1327
+ registered.push(cmd);
1485
1328
  }
1486
- registered.push(cmd);
1487
1329
  } catch (error) {
1488
1330
  errors++;
1489
1331
  const reason = error instanceof Error ? error.message : String(error);
1490
- skipped.push({
1491
- id: manifestId,
1492
- source: result.source,
1493
- reason
1494
- });
1332
+ skipped.push({ id: manifestId, source: result.source, reason });
1495
1333
  log2.error(`Skipped manifest ${manifestId} (${result.source}): ${reason}`);
1496
- continue;
1497
1334
  }
1498
1335
  }
1499
1336
  }
@@ -1503,24 +1340,18 @@ async function registerManifests(discoveryResults, registry2, options = {}) {
1503
1340
  log2.warn(` \u2022 ${skip.id} [${skip.source}] \u2192 ${skip.reason}`);
1504
1341
  }
1505
1342
  }
1506
- return {
1507
- registered,
1508
- skipped,
1509
- collisions,
1510
- errors
1511
- };
1343
+ return { registered, skipped, collisions, errors };
1512
1344
  }
1513
1345
  async function disposeAllPlugins(registry2, logger) {
1514
1346
  const log2 = logger ?? platform.logger;
1515
- const manifests = registry2.listManifests();
1347
+ const manifests = registry2.listCommands();
1516
1348
  const disposePromises = [];
1517
1349
  for (const cmd of manifests) {
1518
1350
  const disposeHook = cmd._disposeHook;
1519
- if (disposeHook && typeof disposeHook === "function") {
1351
+ if (typeof disposeHook === "function") {
1520
1352
  disposePromises.push(
1521
1353
  Promise.resolve(disposeHook()).catch((err) => {
1522
- const errMsg = err instanceof Error ? err.message : String(err);
1523
- log2.warn(`Dispose hook failed for ${cmd.manifest.id}: ${errMsg}`);
1354
+ log2.warn(`Dispose hook failed for ${cmd.manifest.segments.join(" ")}: ${err instanceof Error ? err.message : String(err)}`);
1524
1355
  })
1525
1356
  );
1526
1357
  }
@@ -1551,421 +1382,512 @@ async function runCommand(cmd, ctx, argv, flags) {
1551
1382
  return typeof result === "number" ? result : 0;
1552
1383
  }
1553
1384
 
1554
- // src/registry/service.ts
1555
- function manifestToCommand(registered) {
1556
- return {
1557
- name: registered.manifest.id,
1558
- category: registered.manifest.group,
1559
- describe: registered.manifest.describe,
1560
- longDescription: registered.manifest.longDescription,
1561
- aliases: registered.manifest.aliases || [],
1562
- flags: registered.manifest.flags,
1563
- examples: registered.manifest.examples,
1564
- async run() {
1565
- throw new Error(`Command ${registered.manifest.id} should be executed via plugin-executor, not via legacy run() path.`);
1566
- }
1567
- };
1385
+ // src/registry/trie-router.ts
1386
+ function makeNode() {
1387
+ return { children: /* @__PURE__ */ new Map() };
1568
1388
  }
1569
- function buildCanonicalId(manifest) {
1570
- const { id, group, subgroup } = manifest;
1571
- if (group && subgroup) {
1572
- return `${group}:${subgroup}:${id}`;
1389
+ function levenshtein(a, b) {
1390
+ const m = a.length;
1391
+ const n = b.length;
1392
+ const dp = Array.from(
1393
+ { length: m + 1 },
1394
+ (_, i) => Array.from({ length: n + 1 }, (__, j) => i === 0 ? j : j === 0 ? i : 0)
1395
+ );
1396
+ for (let i = 1; i <= m; i++) {
1397
+ for (let j = 1; j <= n; j++) {
1398
+ if (a[i - 1] === b[j - 1]) {
1399
+ dp[i][j] = dp[i - 1][j - 1];
1400
+ } else {
1401
+ dp[i][j] = 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
1402
+ }
1403
+ }
1573
1404
  }
1574
- if (group) {
1575
- return `${group}:${id}`;
1405
+ return dp[m][n];
1406
+ }
1407
+ function findDeep(node, targetSegments, prefix) {
1408
+ const results = [];
1409
+ function dfs(current, path3) {
1410
+ if (current.command) {
1411
+ if (targetSegments.length <= path3.length) {
1412
+ const tail = path3.slice(path3.length - targetSegments.length);
1413
+ const matches = tail.every((seg, i) => seg === targetSegments[i]);
1414
+ if (matches) {
1415
+ results.push(path3.join(" "));
1416
+ }
1417
+ }
1418
+ }
1419
+ for (const [key, child] of current.children) {
1420
+ dfs(child, [...path3, key]);
1421
+ }
1576
1422
  }
1577
- return id;
1423
+ dfs(node, [...prefix]);
1424
+ return results;
1578
1425
  }
1579
- var InMemoryRegistry = class {
1580
- // System commands (in-process): key = any registered name/alias
1581
- systemCommands = /* @__PURE__ */ new Map();
1582
- // Plugin commands: key = canonicalId only
1583
- pluginByCanonical = /* @__PURE__ */ new Map();
1584
- // Alias → canonicalId: covers all user-input variants that resolve to a plugin
1585
- pluginAliases = /* @__PURE__ */ new Map();
1586
- // Legacy unified collection for backward compatibility (display/get)
1587
- byName = /* @__PURE__ */ new Map();
1588
- groups = /* @__PURE__ */ new Map();
1589
- // manifests: any key RegisteredCommand (for listing/lookup; still stores multi-key for compat)
1590
- manifests = /* @__PURE__ */ new Map();
1591
- partial = false;
1592
- // ─── System command registration ────────────────────────────────────────
1593
- register(cmd) {
1594
- this.systemCommands.set(cmd.name, cmd);
1595
- this.byName.set(cmd.name, cmd);
1596
- for (const a of cmd.aliases || []) {
1597
- this.systemCommands.set(a, cmd);
1598
- this.byName.set(a, cmd);
1426
+ var TrieRouter = class {
1427
+ root = makeNode();
1428
+ /**
1429
+ * Insert a plugin command into the trie.
1430
+ *
1431
+ * Enforces 1-manifest-1-namespace: the first packageName to register any command
1432
+ * under a top-level group becomes the owner. Subsequent packages with a different
1433
+ * packageName are rejected (collides: true) the caller is responsible for warning
1434
+ * and marking the command as shadowed.
1435
+ *
1436
+ * Returns { collides: false } on success, or { collides: true, ownerPackage } on conflict.
1437
+ */
1438
+ insertCommand(segments, cmd) {
1439
+ if (segments.length === 0) {
1440
+ return { collides: false };
1441
+ }
1442
+ const incoming = cmd.packageName ?? "__unknown__";
1443
+ const topSeg = segments[0];
1444
+ if (!this.root.children.has(topSeg)) {
1445
+ this.root.children.set(topSeg, makeNode());
1446
+ }
1447
+ const groupNode = this.root.children.get(topSeg);
1448
+ if (groupNode.ownerPackage === void 0) {
1449
+ groupNode.ownerPackage = incoming;
1450
+ } else if (groupNode.ownerPackage !== incoming) {
1451
+ return { collides: true, ownerPackage: groupNode.ownerPackage };
1452
+ }
1453
+ let node = groupNode;
1454
+ for (const seg of segments.slice(1)) {
1455
+ if (!node.children.has(seg)) {
1456
+ node.children.set(seg, makeNode());
1457
+ }
1458
+ node = node.children.get(seg);
1599
1459
  }
1460
+ node.command = cmd;
1461
+ return { collides: false };
1600
1462
  }
1601
- registerGroup(group) {
1602
- this.groups.set(group.name, group);
1603
- this.byName.set(group.name, group);
1463
+ insertSystemCommand(cmd) {
1464
+ const segments = [cmd.name];
1465
+ let node = this.root;
1466
+ for (const seg of segments) {
1467
+ if (!node.children.has(seg)) {
1468
+ node.children.set(seg, makeNode());
1469
+ }
1470
+ node = node.children.get(seg);
1471
+ }
1472
+ node.systemCommand = cmd;
1473
+ for (const alias of cmd.aliases ?? []) {
1474
+ let aliasNode = this.root;
1475
+ if (!aliasNode.children.has(alias)) {
1476
+ aliasNode.children.set(alias, makeNode());
1477
+ }
1478
+ aliasNode = aliasNode.children.get(alias);
1479
+ aliasNode.systemCommand = cmd;
1480
+ }
1481
+ }
1482
+ insertSystemGroup(group) {
1483
+ let groupNode = this.root;
1484
+ if (!groupNode.children.has(group.name)) {
1485
+ groupNode.children.set(group.name, makeNode());
1486
+ }
1487
+ groupNode = groupNode.children.get(group.name);
1488
+ groupNode.systemGroup = group;
1489
+ groupNode.groupDescribe = group.describe;
1604
1490
  for (const cmd of group.commands) {
1605
- this.systemCommands.set(cmd.name, cmd);
1606
- const fullName = `${group.name} ${cmd.name}`;
1607
- this.systemCommands.set(fullName, cmd);
1608
- this.byName.set(fullName, cmd);
1609
- for (const alias of cmd.aliases || []) {
1610
- this.systemCommands.set(alias, cmd);
1611
- this.byName.set(alias, cmd);
1491
+ let cmdNode = groupNode;
1492
+ if (!cmdNode.children.has(cmd.name)) {
1493
+ cmdNode.children.set(cmd.name, makeNode());
1494
+ }
1495
+ cmdNode = cmdNode.children.get(cmd.name);
1496
+ cmdNode.systemCommand = cmd;
1497
+ for (const alias of cmd.aliases ?? []) {
1498
+ let aliasNode = groupNode;
1499
+ if (!aliasNode.children.has(alias)) {
1500
+ aliasNode.children.set(alias, makeNode());
1501
+ }
1502
+ aliasNode = aliasNode.children.get(alias);
1503
+ aliasNode.systemCommand = cmd;
1612
1504
  }
1613
1505
  }
1614
- if (group.subgroups) {
1615
- for (const sub of group.subgroups) {
1616
- const subName = `${group.name} ${sub.name}`;
1617
- this.groups.set(subName, sub);
1618
- this.byName.set(subName, sub);
1619
- for (const cmd of sub.commands) {
1620
- const fullName = `${group.name} ${sub.name} ${cmd.name}`;
1621
- this.systemCommands.set(fullName, cmd);
1622
- this.byName.set(fullName, cmd);
1623
- for (const alias of cmd.aliases || []) {
1624
- this.systemCommands.set(alias, cmd);
1625
- this.byName.set(alias, cmd);
1626
- }
1506
+ for (const sub of group.subgroups ?? []) {
1507
+ let subNode = groupNode;
1508
+ if (!subNode.children.has(sub.name)) {
1509
+ subNode.children.set(sub.name, makeNode());
1510
+ }
1511
+ subNode = subNode.children.get(sub.name);
1512
+ subNode.systemGroup = sub;
1513
+ subNode.groupDescribe = sub.describe;
1514
+ for (const cmd of sub.commands) {
1515
+ let cmdNode = subNode;
1516
+ if (!cmdNode.children.has(cmd.name)) {
1517
+ cmdNode.children.set(cmd.name, makeNode());
1627
1518
  }
1519
+ cmdNode = cmdNode.children.get(cmd.name);
1520
+ cmdNode.systemCommand = cmd;
1628
1521
  }
1629
1522
  }
1630
1523
  }
1631
- // ─── Plugin command registration ────────────────────────────────────────
1632
- registerManifest(cmd) {
1633
- const canonicalId = buildCanonicalId(cmd.manifest);
1634
- const collisionKey = this._findSystemCollision(cmd.manifest, canonicalId);
1635
- if (collisionKey) {
1636
- console.warn(`[registry] Plugin command "${canonicalId}" collides with system command "${collisionKey}". System command takes priority.`);
1637
- cmd.shadowed = true;
1524
+ getGroupDescribe(segments) {
1525
+ let node = this.root;
1526
+ for (const seg of segments) {
1527
+ const child = node.children.get(seg);
1528
+ if (!child) {
1529
+ return void 0;
1530
+ }
1531
+ node = child;
1638
1532
  }
1639
- const collisionAliases = /* @__PURE__ */ new Set();
1640
- for (const alias of cmd.manifest.aliases || []) {
1641
- if (this.systemCommands.has(alias)) {
1642
- console.warn(`[registry] Plugin alias "${alias}" collides with system command. System command takes priority.`);
1643
- collisionAliases.add(alias);
1533
+ return node.groupDescribe;
1534
+ }
1535
+ setGroupDescribe(segments, describe) {
1536
+ let node = this.root;
1537
+ for (const seg of segments) {
1538
+ if (!node.children.has(seg)) {
1539
+ node.children.set(seg, makeNode());
1644
1540
  }
1541
+ node = node.children.get(seg);
1645
1542
  }
1646
- this.pluginByCanonical.set(canonicalId, cmd);
1647
- this.manifests.set(canonicalId, cmd);
1648
- this.manifests.set(cmd.manifest.id, cmd);
1649
- if (cmd.shadowed) {
1650
- return;
1543
+ if (!node.groupDescribe) {
1544
+ node.groupDescribe = describe;
1651
1545
  }
1652
- this._registerPluginAliases(cmd, canonicalId, collisionAliases);
1653
- const commandAdapter = manifestToCommand(cmd);
1654
- this.byName.set(canonicalId, commandAdapter);
1655
- const spaceCanonical = canonicalId.replace(/:/g, " ");
1656
- this.byName.set(spaceCanonical, commandAdapter);
1657
- this._registerSyntheticGroups(cmd, commandAdapter);
1658
1546
  }
1659
- /**
1660
- * Check whether any system command already owns the canonical id or its parts.
1661
- * Returns the colliding key if found, null otherwise.
1662
- */
1663
- _findSystemCollision(manifest, canonicalId) {
1664
- if (this.systemCommands.has(canonicalId)) {
1665
- return canonicalId;
1547
+ resolve(tokens) {
1548
+ if (tokens.length === 0) {
1549
+ return {
1550
+ type: "group",
1551
+ segments: [],
1552
+ childKeys: [...this.root.children.keys()]
1553
+ };
1554
+ }
1555
+ let node = this.root;
1556
+ let i = 0;
1557
+ while (i < tokens.length) {
1558
+ const tok = tokens[i];
1559
+ const child = node.children.get(tok);
1560
+ if (!child) {
1561
+ return this._notFound(node, tokens, i);
1562
+ }
1563
+ node = child;
1564
+ i++;
1565
+ if (node.command && i < tokens.length) {
1566
+ return { type: "command", command: node.command, rest: tokens.slice(i) };
1567
+ }
1568
+ if (node.systemCommand && i < tokens.length) {
1569
+ return { type: "system-cmd", cmd: node.systemCommand, rest: tokens.slice(i) };
1570
+ }
1666
1571
  }
1667
- const space = canonicalId.replace(/:/g, " ");
1668
- if (this.systemCommands.has(space)) {
1669
- return space;
1572
+ if (node.command) {
1573
+ return { type: "command", command: node.command, rest: [] };
1670
1574
  }
1671
- return null;
1575
+ if (node.systemCommand) {
1576
+ return { type: "system-cmd", cmd: node.systemCommand, rest: [] };
1577
+ }
1578
+ if (node.systemGroup) {
1579
+ return { type: "system-group", group: node.systemGroup, rest: [] };
1580
+ }
1581
+ if (node.children.size > 0 || node.groupDescribe !== void 0) {
1582
+ const segs = tokens.slice(0, i);
1583
+ return {
1584
+ type: "group",
1585
+ segments: segs,
1586
+ describe: node.groupDescribe,
1587
+ childKeys: [...node.children.keys()]
1588
+ };
1589
+ }
1590
+ return { type: "not-found", input: tokens, suggestions: [] };
1672
1591
  }
1673
- /**
1674
- * Register all lookup aliases for a plugin command.
1675
- *
1676
- * Priority (what beats what) is handled in resolveToCanonical at query time.
1677
- * Here we just build the mapping.
1678
- *
1679
- * Aliases registered:
1680
- * - canonicalId itself ("marketplace:plugins:list")
1681
- * - bare id ("list") ← low priority, may clash
1682
- * - 2-part shorthand ("marketplace:list", "marketplace list")
1683
- * - manifest.aliases[] (user-defined aliases, except collisions)
1684
- */
1685
- _registerPluginAliases(cmd, canonicalId, collisionAliases) {
1686
- const { id, group, subgroup } = cmd.manifest;
1687
- const register = (key) => {
1688
- if (!this.systemCommands.has(key) && !this.systemCommands.has(key.replace(/:/g, " "))) {
1689
- if (!this.pluginAliases.has(key)) {
1690
- this.pluginAliases.set(key, canonicalId);
1691
- this.manifests.set(key, cmd);
1692
- }
1592
+ complete(tokens) {
1593
+ if (tokens.length === 0) {
1594
+ return [...this.root.children.keys()];
1595
+ }
1596
+ let node = this.root;
1597
+ for (let i = 0; i < tokens.length - 1; i++) {
1598
+ const child = node.children.get(tokens[i]);
1599
+ if (!child) {
1600
+ return [];
1693
1601
  }
1694
- };
1695
- this.pluginAliases.set(canonicalId, canonicalId);
1696
- const spaceCanonical = canonicalId.replace(/:/g, " ");
1697
- this.pluginAliases.set(spaceCanonical, canonicalId);
1698
- this.manifests.set(spaceCanonical, cmd);
1699
- if (group && subgroup) {
1700
- const twoPartColon = `${group}:${id}`;
1701
- const twoPartSpace = `${group} ${id}`;
1702
- register(twoPartColon);
1703
- register(twoPartSpace);
1704
- this.manifests.set(twoPartColon, cmd);
1705
- this.manifests.set(twoPartSpace, cmd);
1706
- const fullPath = `${group} ${subgroup} ${id}`;
1707
- this.pluginAliases.set(fullPath, canonicalId);
1708
- this.manifests.set(fullPath, cmd);
1709
- const colonPath = `${group}:${subgroup}:${id}`;
1710
- this.pluginAliases.set(colonPath, canonicalId);
1711
- this.manifests.set(colonPath, cmd);
1712
- } else if (group) {
1713
- const colonName = `${group}:${id}`;
1714
- const spaceName = `${group} ${id}`;
1715
- this.pluginAliases.set(colonName, canonicalId);
1716
- this.pluginAliases.set(spaceName, canonicalId);
1717
- this.manifests.set(colonName, cmd);
1718
- this.manifests.set(spaceName, cmd);
1719
- }
1720
- register(id);
1721
- for (const alias of cmd.manifest.aliases || []) {
1722
- if (!collisionAliases.has(alias)) {
1723
- register(alias);
1602
+ node = child;
1603
+ }
1604
+ const partial = tokens[tokens.length - 1] ?? "";
1605
+ const results = [];
1606
+ const prefix = tokens.slice(0, tokens.length - 1);
1607
+ for (const [key] of node.children) {
1608
+ if (key.startsWith(partial)) {
1609
+ results.push([...prefix, key].join(" "));
1724
1610
  }
1725
1611
  }
1612
+ return results;
1726
1613
  }
1727
1614
  /**
1728
- * Register synthetic subgroups for help display.
1615
+ * List all leaf plugin commands under a given node path.
1729
1616
  */
1730
- _registerSyntheticGroups(cmd, commandAdapter) {
1731
- const { id, group, subgroup } = cmd.manifest;
1732
- if (group && subgroup) {
1733
- const subgroupKey = `${group} ${subgroup}`;
1734
- if (!this.groups.has(subgroupKey)) {
1735
- this.groups.set(subgroupKey, {
1736
- name: subgroupKey,
1737
- describe: subgroup,
1738
- commands: []
1739
- });
1740
- this.byName.set(subgroupKey, this.groups.get(subgroupKey));
1617
+ listUnder(segments) {
1618
+ let node = this.root;
1619
+ for (const seg of segments) {
1620
+ const child = node.children.get(seg);
1621
+ if (!child) {
1622
+ return [];
1741
1623
  }
1742
- this.groups.get(subgroupKey).commands.push(commandAdapter);
1743
- const twoPartSpace = `${group} ${id}`;
1744
- if (!this.byName.has(twoPartSpace)) {
1745
- this.byName.set(twoPartSpace, commandAdapter);
1624
+ node = child;
1625
+ }
1626
+ const results = [];
1627
+ this._collectCommands(node, results);
1628
+ return results;
1629
+ }
1630
+ /**
1631
+ * Get exact command at path.
1632
+ */
1633
+ getAt(segments) {
1634
+ let node = this.root;
1635
+ for (const seg of segments) {
1636
+ const child = node.children.get(seg);
1637
+ if (!child) {
1638
+ return null;
1746
1639
  }
1747
- const twoPartColon = `${group}:${id}`;
1748
- if (!this.byName.has(twoPartColon)) {
1749
- this.byName.set(twoPartColon, commandAdapter);
1640
+ node = child;
1641
+ }
1642
+ return node.command ?? null;
1643
+ }
1644
+ /**
1645
+ * List top-level system groups and standalone system commands (depth=1 nodes).
1646
+ */
1647
+ listSystemTopLevel() {
1648
+ const results = [];
1649
+ for (const [name, node] of this.root.children) {
1650
+ if (node.systemGroup) {
1651
+ results.push({ name, describe: node.systemGroup.describe, isGroup: true });
1652
+ } else if (node.systemCommand) {
1653
+ results.push({ name, describe: node.systemCommand.describe, isGroup: false });
1750
1654
  }
1751
- const fullPath = `${group} ${subgroup} ${id}`;
1752
- this.byName.set(fullPath, commandAdapter);
1753
- } else if (group) {
1754
- const fullName = `${group} ${id}`;
1755
- const colonName = `${group}:${id}`;
1756
- this.byName.set(fullName, commandAdapter);
1757
- this.byName.set(colonName, commandAdapter);
1758
1655
  }
1656
+ return results;
1759
1657
  }
1760
- // ─── Resolution ──────────────────────────────────────────────────────────
1761
1658
  /**
1762
- * Resolve any user input to its canonical plugin ID.
1763
- *
1764
- * Returns canonicalId if found in plugin aliases, undefined otherwise.
1659
+ * Collect all plugin commands reachable from node.
1765
1660
  */
1766
- resolveToCanonical(input) {
1767
- if (this.pluginAliases.has(input)) {
1768
- return this.pluginAliases.get(input);
1661
+ listAll() {
1662
+ const results = [];
1663
+ this._collectCommands(this.root, results);
1664
+ return results;
1665
+ }
1666
+ _collectCommands(node, out) {
1667
+ if (node.command) {
1668
+ out.push(node.command);
1769
1669
  }
1770
- const converted = input.includes(":") ? input.replace(/:/g, " ") : input.replace(/ /g, ":");
1771
- if (this.pluginAliases.has(converted)) {
1772
- return this.pluginAliases.get(converted);
1670
+ for (const child of node.children.values()) {
1671
+ this._collectCommands(child, out);
1773
1672
  }
1774
- return void 0;
1775
1673
  }
1776
- // ─── Public API ──────────────────────────────────────────────────────────
1777
- markPartial(partial) {
1778
- this.partial = partial;
1674
+ _notFound(node, tokens, failIdx) {
1675
+ const failToken = tokens[failIdx];
1676
+ const prefix = tokens.slice(0, failIdx);
1677
+ const siblings = [...node.children.keys()];
1678
+ const fuzzy = siblings.map((s) => ({ s, d: levenshtein(failToken, s) })).filter(({ d }) => d <= 2).sort((a, b) => a.d - b.d).map(({ s }) => [...prefix, s, ...tokens.slice(failIdx + 1)].join(" "));
1679
+ if (fuzzy.length === 1) {
1680
+ return { type: "not-found", input: tokens, suggestions: fuzzy };
1681
+ }
1682
+ if (fuzzy.length > 1) {
1683
+ return { type: "ambiguous", input: tokens, candidates: fuzzy };
1684
+ }
1685
+ const remaining = tokens.slice(failIdx);
1686
+ const deep = findDeep(node, remaining, prefix);
1687
+ if (deep.length === 0) {
1688
+ return { type: "not-found", input: tokens, suggestions: [] };
1689
+ }
1690
+ if (deep.length === 1) {
1691
+ return { type: "not-found", input: tokens, suggestions: deep };
1692
+ }
1693
+ return { type: "ambiguous", input: tokens, candidates: deep };
1779
1694
  }
1780
- isPartial() {
1781
- return this.partial;
1695
+ };
1696
+
1697
+ // src/registry/archetype-flags.ts
1698
+ var outputFlag = { name: "output", type: "string", choices: ["json", "table", "csv"], description: "Output format" };
1699
+ var dryRunFlag = { name: "dry-run", type: "boolean", description: "Show what would happen without executing" };
1700
+ var yesFlag = { name: "yes", type: "boolean", alias: "y", description: "Skip confirmation prompts" };
1701
+ var waitFlag = { name: "wait", type: "boolean", description: "Block until execution completes" };
1702
+ var watchFlag = { name: "watch", type: "boolean", description: "Stream events as NDJSON" };
1703
+ var timeoutFlag = { name: "timeout", type: "string", description: "Max wait time (e.g. 30s, 5m)" };
1704
+ var limitFlag = { name: "limit", type: "number", description: "Max results to return" };
1705
+ var offsetFlag = { name: "offset", type: "number", description: "Offset for pagination" };
1706
+ var formatFlag = { name: "format", type: "string", choices: ["json", "text", "md"], description: "Output format" };
1707
+ var streamFlag = { name: "stream", type: "boolean", description: "Stream output progressively" };
1708
+ var schemaFlag = {
1709
+ name: "schema",
1710
+ type: "boolean",
1711
+ description: "Output JSON Schema for this command (flags, types, examples)"
1712
+ };
1713
+ var ARCHETYPE_FLAGS = {
1714
+ read: [outputFlag, limitFlag, offsetFlag],
1715
+ mutate: [outputFlag, dryRunFlag, yesFlag],
1716
+ execute: [outputFlag, waitFlag, watchFlag, timeoutFlag, yesFlag],
1717
+ analyze: [outputFlag, formatFlag, streamFlag]
1718
+ };
1719
+ function getArchetypeFlags(operationType, existing = []) {
1720
+ const toInject = ARCHETYPE_FLAGS[operationType] ?? [];
1721
+ const existingNames = new Set(existing.map((f) => f.name));
1722
+ return toInject.filter((f) => !existingNames.has(f.name));
1723
+ }
1724
+
1725
+ // src/registry/service.ts
1726
+ var RESERVED_NAMESPACES = /* @__PURE__ */ new Set(["__complete", "__internal"]);
1727
+ var MAX_PATH_DEPTH = 6;
1728
+ function validateSegments(segs) {
1729
+ if (segs.length === 0) {
1730
+ return "empty path \u2014 segments must not be empty";
1782
1731
  }
1783
- getManifest(id) {
1784
- return this.manifests.get(id);
1732
+ if (segs.length > MAX_PATH_DEPTH) {
1733
+ return `path too deep: ${segs.length} segments (max ${MAX_PATH_DEPTH})`;
1785
1734
  }
1786
- listManifests() {
1787
- const unique = /* @__PURE__ */ new Set();
1788
- for (const cmd of this.pluginByCanonical.values()) {
1789
- unique.add(cmd);
1790
- }
1791
- return Array.from(unique);
1735
+ if (RESERVED_NAMESPACES.has(segs[0])) {
1736
+ return `"${segs[0]}" is a reserved namespace`;
1737
+ }
1738
+ return null;
1739
+ }
1740
+ var noopLogger = {
1741
+ debug: () => {
1742
+ },
1743
+ info: () => {
1744
+ },
1745
+ warn: () => {
1746
+ },
1747
+ error: () => {
1748
+ },
1749
+ child: () => noopLogger
1750
+ };
1751
+ var TrieBackedRegistry = class {
1752
+ systemRouter = new TrieRouter();
1753
+ pluginRouter = new TrieRouter();
1754
+ partial = false;
1755
+ logger = noopLogger;
1756
+ // ─── Logger ────────────────────────────────────────────────────────────────
1757
+ setLogger(logger) {
1758
+ this.logger = logger;
1792
1759
  }
1793
- has(name) {
1794
- return this.byName.has(name) || this.pluginAliases.has(name);
1760
+ // ─── Registration ──────────────────────────────────────────────────────────
1761
+ register(cmd) {
1762
+ this.systemRouter.insertSystemCommand(cmd);
1795
1763
  }
1796
- /**
1797
- * Get command with type information for secure routing.
1798
- *
1799
- * System commands ALWAYS win — checked first before any plugin lookup.
1800
- * Returns type='system' for system commands (in-process execution).
1801
- * Returns type='plugin' for plugin commands (subprocess execution).
1802
- */
1803
- getWithType(nameOrPath) {
1804
- const normalized = typeof nameOrPath === "string" ? nameOrPath : nameOrPath.join(" ");
1805
- if (this.systemCommands.has(normalized)) {
1806
- return { cmd: this.systemCommands.get(normalized), type: "system" };
1807
- }
1808
- const colonVariant = normalized.replace(/ /g, ":");
1809
- if (this.systemCommands.has(colonVariant)) {
1810
- return { cmd: this.systemCommands.get(colonVariant), type: "system" };
1811
- }
1812
- if (this.groups.has(normalized)) {
1813
- return { cmd: this.groups.get(normalized), type: "system" };
1814
- }
1815
- const canonicalId = this.resolveToCanonical(normalized);
1816
- if (canonicalId) {
1817
- const pluginCmd = this.pluginByCanonical.get(canonicalId);
1818
- if (pluginCmd && !pluginCmd.shadowed) {
1819
- const cmd2 = this.byName.get(canonicalId) ?? this.byName.get(normalized);
1820
- if (cmd2) {
1821
- return { cmd: cmd2, type: "plugin" };
1822
- }
1764
+ registerGroup(group) {
1765
+ this.systemRouter.insertSystemGroup(group);
1766
+ if (group.subgroups) {
1767
+ for (const sub of group.subgroups) {
1768
+ this.systemRouter.setGroupDescribe([group.name, sub.name], sub.describe ?? "");
1823
1769
  }
1824
1770
  }
1825
- const cmd = this.get(nameOrPath);
1826
- if (!cmd) {
1827
- return void 0;
1828
- }
1829
- if ("commands" in cmd) {
1830
- return { cmd, type: "system" };
1771
+ }
1772
+ registerManifest(cmd) {
1773
+ if (cmd.manifest._synthetic) {
1774
+ return;
1831
1775
  }
1832
- if (this.systemCommands.has(normalized) || this.systemCommands.has(colonVariant)) {
1833
- return { cmd, type: "system" };
1776
+ const segs = cmd.manifest.segments;
1777
+ const pkg = cmd.packageName ?? "unknown";
1778
+ const validationError = validateSegments(segs);
1779
+ if (validationError) {
1780
+ this.logger.warn(`[registry] Plugin "${pkg}" skipped: ${validationError} (path: "${segs.join(" ")}")`);
1781
+ cmd.shadowed = true;
1782
+ return;
1834
1783
  }
1835
- const manifestCmd = this.getManifestCommand(normalized);
1836
- if (manifestCmd && !manifestCmd.shadowed) {
1837
- return { cmd, type: "plugin" };
1784
+ const sysResult = this.systemRouter.resolve([...segs]);
1785
+ if (sysResult.type === "system-cmd" || sysResult.type === "system-group") {
1786
+ this.logger.warn(`[registry] Plugin "${pkg}" tried to register "${segs.join(" ")}" but it collides with a system command. Command will be shadowed.`);
1787
+ cmd.shadowed = true;
1788
+ return;
1838
1789
  }
1839
- return { cmd, type: "system" };
1840
- }
1841
- get(nameOrPath) {
1842
- if (typeof nameOrPath === "string") {
1843
- if (this.byName.has(nameOrPath)) {
1844
- return this.byName.get(nameOrPath);
1845
- }
1846
- if (nameOrPath.includes(":")) {
1847
- const spaceKey = nameOrPath.replace(/:/g, " ");
1848
- if (this.byName.has(spaceKey)) {
1849
- return this.byName.get(spaceKey);
1850
- }
1851
- const parts = nameOrPath.split(":");
1852
- if (parts.length === 2) {
1853
- const spaceKey2 = parts.join(" ");
1854
- if (this.byName.has(spaceKey2)) {
1855
- return this.byName.get(spaceKey2);
1856
- }
1857
- }
1790
+ if (cmd.manifest.operationType) {
1791
+ const injected = getArchetypeFlags(cmd.manifest.operationType, cmd.manifest.flags ?? []);
1792
+ if (injected.length > 0) {
1793
+ cmd.manifest.flags = [...cmd.manifest.flags ?? [], ...injected];
1858
1794
  }
1859
1795
  }
1860
- const key = Array.isArray(nameOrPath) ? nameOrPath.join(" ") : nameOrPath;
1861
- if (this.byName.has(key)) {
1862
- return this.byName.get(key);
1796
+ if (cmd.manifest.operationType && !cmd.manifest.flags?.some((f) => f.name === "schema")) {
1797
+ cmd.manifest.flags = [...cmd.manifest.flags ?? [], schemaFlag];
1863
1798
  }
1864
- if (Array.isArray(nameOrPath) && nameOrPath.length === 1 && nameOrPath[0]?.includes(":")) {
1865
- if (this.byName.has(nameOrPath[0])) {
1866
- return this.byName.get(nameOrPath[0]);
1799
+ const result = this.pluginRouter.insertCommand(segs, cmd);
1800
+ if (result.collides) {
1801
+ this.logger.warn(
1802
+ `[registry] Plugin "${pkg}" tried to register "${segs.join(" ")}" but namespace "${segs[0]}" is owned by "${result.ownerPackage ?? "unknown"}". Command will be shadowed.`
1803
+ );
1804
+ cmd.shadowed = true;
1805
+ return;
1806
+ }
1807
+ for (const alias of cmd.manifest.aliases ?? []) {
1808
+ const aliasSegs = alias.trim().split(/\s+/).filter(Boolean);
1809
+ if (aliasSegs.length === 0) {
1810
+ continue;
1867
1811
  }
1868
- const [group, command] = nameOrPath[0].split(":");
1869
- const legacyKey = `${group} ${command}`;
1870
- if (this.byName.has(legacyKey)) {
1871
- return this.byName.get(legacyKey);
1812
+ const aliasValidationError = validateSegments(aliasSegs);
1813
+ if (aliasValidationError) {
1814
+ this.logger.warn(`[registry] Plugin "${pkg}" alias "${alias}" skipped: ${aliasValidationError}`);
1815
+ continue;
1872
1816
  }
1873
- }
1874
- if (Array.isArray(nameOrPath)) {
1875
- const dot = nameOrPath.join(".");
1876
- if (this.byName.has(dot)) {
1877
- return this.byName.get(dot);
1817
+ const aliasSysResult = this.systemRouter.resolve([...aliasSegs]);
1818
+ if (aliasSysResult.type === "system-cmd" || aliasSysResult.type === "system-group") {
1819
+ this.logger.warn(`[registry] Plugin "${pkg}" alias "${alias}" collides with a system command and will be skipped.`);
1820
+ continue;
1821
+ }
1822
+ const aliasResult = this.pluginRouter.insertCommand(aliasSegs, cmd);
1823
+ if (aliasResult.collides) {
1824
+ this.logger.warn(
1825
+ `[registry] Plugin "${pkg}" alias "${alias}" conflicts with namespace "${aliasSegs[0]}" owned by "${aliasResult.ownerPackage ?? "unknown"}". Alias skipped.`
1826
+ );
1878
1827
  }
1879
1828
  }
1880
- if (Array.isArray(nameOrPath) && nameOrPath.length >= 2) {
1881
- const [groupPrefix, ...cmdParts] = nameOrPath;
1882
- const cmdName = cmdParts.join(" ");
1883
- for (const group of this.groups.values()) {
1884
- if (group.name === groupPrefix || group.name.startsWith(groupPrefix + ":")) {
1885
- const fullName = `${group.name} ${cmdName}`;
1886
- if (this.byName.has(fullName)) {
1887
- return this.byName.get(fullName);
1888
- }
1889
- }
1829
+ const v3 = cmd.manifest.manifestV2;
1830
+ if (v3?.cli?.groupMeta) {
1831
+ for (const meta of v3.cli.groupMeta) {
1832
+ const metaSegs = meta.path.trim().split(/\s+/).filter(Boolean);
1833
+ this.pluginRouter.setGroupDescribe(metaSegs, meta.describe);
1890
1834
  }
1891
1835
  }
1892
- return void 0;
1893
1836
  }
1894
- list() {
1895
- const commands = /* @__PURE__ */ new Set();
1896
- for (const value of this.byName.values()) {
1897
- if ("run" in value) {
1898
- commands.add(value);
1837
+ // ─── Resolution ────────────────────────────────────────────────────────────
1838
+ resolve(tokens) {
1839
+ if (tokens.length > 0) {
1840
+ const sysResult = this.systemRouter.resolve(tokens);
1841
+ if (sysResult.type !== "not-found") {
1842
+ return sysResult;
1899
1843
  }
1900
1844
  }
1901
- return Array.from(commands);
1845
+ return this.pluginRouter.resolve(tokens);
1902
1846
  }
1903
- listGroups() {
1904
- return Array.from(this.groups.values());
1847
+ // ─── Tab completion ─────────────────────────────────────────────────────────
1848
+ complete(tokens) {
1849
+ const sysCompletions = this.systemRouter.complete(tokens);
1850
+ const pluginCompletions = this.pluginRouter.complete(tokens);
1851
+ return [.../* @__PURE__ */ new Set([...sysCompletions, ...pluginCompletions])];
1905
1852
  }
1906
- getGroupsByPrefix(prefix) {
1907
- const result = [];
1908
- for (const group of this.groups.values()) {
1909
- if (group.name === prefix || group.name.startsWith(prefix + ":")) {
1910
- result.push(group);
1911
- }
1912
- }
1913
- return result;
1853
+ // ─── Query ──────────────────────────────────────────────────────────────────
1854
+ listSystemTopLevel() {
1855
+ return this.systemRouter.listSystemTopLevel();
1914
1856
  }
1915
- getCommandsByGroupPrefix(prefix) {
1916
- const result = [];
1917
- for (const group of this.groups.values()) {
1918
- if (group.name === prefix || group.name.startsWith(prefix + ":")) {
1919
- result.push(...group.commands);
1920
- }
1921
- }
1922
- return result;
1923
- }
1924
- listProductGroups() {
1925
- const groups = /* @__PURE__ */ new Map();
1926
- for (const cmd of this.listManifests()) {
1927
- const groupName = cmd.manifest.group;
1928
- if (!groups.has(groupName)) {
1929
- groups.set(groupName, {
1930
- name: groupName,
1931
- describe: cmd.manifest.group,
1932
- commands: []
1933
- });
1934
- }
1935
- groups.get(groupName).commands.push(cmd);
1936
- }
1937
- return Array.from(groups.values());
1857
+ getPluginGroupDescribe(segments) {
1858
+ return this.pluginRouter.getGroupDescribe(segments);
1938
1859
  }
1939
- getCommandsByGroup(group) {
1940
- return this.listManifests().filter((cmd) => cmd.manifest.group === group).sort((a, b) => a.manifest.id.localeCompare(b.manifest.id));
1860
+ listCommands() {
1861
+ return this.pluginRouter.listAll();
1941
1862
  }
1942
- getManifestCommand(idOrAlias) {
1943
- if (this.manifests.has(idOrAlias)) {
1944
- return this.manifests.get(idOrAlias);
1945
- }
1946
- const canonicalId = this.resolveToCanonical(idOrAlias);
1947
- if (canonicalId) {
1948
- return this.pluginByCanonical.get(canonicalId);
1949
- }
1950
- for (const cmd of this.pluginByCanonical.values()) {
1951
- if (cmd.manifest.aliases?.includes(idOrAlias)) {
1952
- return cmd;
1953
- }
1954
- if (cmd.manifest.id.replace(/:/g, " ") === idOrAlias) {
1955
- return cmd;
1956
- }
1957
- }
1958
- return void 0;
1863
+ listCommandsUnder(segments) {
1864
+ return this.pluginRouter.listUnder(segments);
1865
+ }
1866
+ getCommandAt(segments) {
1867
+ return this.pluginRouter.getAt(segments);
1868
+ }
1869
+ // ─── Partial loading state ─────────────────────────────────────────────────
1870
+ markPartial(v) {
1871
+ this.partial = v;
1872
+ }
1873
+ isPartial() {
1874
+ return this.partial;
1875
+ }
1876
+ // ─── Diagnostics ──────────────────────────────────────────────────────────
1877
+ getDiagnostics() {
1878
+ return {
1879
+ systemCommandCount: this.systemRouter.listAll().length,
1880
+ systemGroupCount: 0,
1881
+ pluginCommandCount: this.pluginRouter.listAll().length,
1882
+ partialLoad: this.partial
1883
+ };
1959
1884
  }
1960
1885
  };
1961
- var registry = new InMemoryRegistry();
1962
- function findCommand(nameOrPath) {
1963
- return registry.get(nameOrPath);
1964
- }
1965
- function findCommandWithType(nameOrPath) {
1966
- return registry.getWithType(nameOrPath);
1886
+ function createRegistry() {
1887
+ return new TrieBackedRegistry();
1967
1888
  }
1889
+ var registry = new TrieBackedRegistry();
1968
1890
 
1969
- export { __test, checkRequires, discoverManifests, discoverManifestsByNamespace, disposeAllPlugins, findCommand, findCommandWithType, preflightManifests, registerManifests, registry, resetInProcCache, runCommand };
1891
+ export { TrieBackedRegistry, TrieRouter, __test, checkRequires, createRegistry, discoverManifests, discoverManifestsByNamespace, disposeAllPlugins, levenshtein, loadConfig, preflightManifests, registerManifests, registry, resetInProcCache, runCommand };
1970
1892
  //# sourceMappingURL=index.js.map
1971
1893
  //# sourceMappingURL=index.js.map