@nseng-ai/kernel 0.1.2

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.
@@ -0,0 +1,700 @@
1
+ import { existsSync, readdirSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import process from "node:process";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ import {
7
+ commandInfoForLoadedCommand,
8
+ commandKey,
9
+ toCommandCliInfo,
10
+ commandPathMatches,
11
+ commandSegments,
12
+ listBuiltInNsCommandCandidates,
13
+ validateNsExtensionContribution,
14
+ type BuiltInNsCommandCandidate,
15
+ type NsCommandCandidate,
16
+ type NsCommandCliInfo,
17
+ type NsCommandPath,
18
+ type NsCommandSourceInfo,
19
+ type NsCommandSourceLevel,
20
+ } from "./command-registry.ts";
21
+ import { NS_COMMAND_NAME_PATTERN, NS_COMMAND_NAME_RULE } from "../sdk/command-name.ts";
22
+ import {
23
+ discoverExtensionsInRoot,
24
+ discoverNsPackageCommands,
25
+ type DiscoveredExtensionCommand,
26
+ type ExtensionDiscoveryDiagnostic,
27
+ } from "./discovery.ts";
28
+ import { loadNsExtensionContribution, type ExtensionLoadDiagnostic } from "./loader.ts";
29
+ import {
30
+ fileModuleReference,
31
+ loadedModuleReference,
32
+ moduleReferenceDisplay,
33
+ packageModuleReference,
34
+ type NsCommandModuleLoader,
35
+ type NsCommandModuleReference,
36
+ } from "./module-reference.ts";
37
+ import { isPathInside, type ExplicitUndefined } from "@nseng-ai/foundation/primitives";
38
+ import { requireXdgPath, resolveNsXdgPath } from "@nseng-ai/foundation/xdg-path";
39
+ import type { NsCommand } from "../sdk/index.ts";
40
+
41
+ export type ExtensionSourceLevel = NsCommandSourceLevel;
42
+ export type ExtensionSourceInfo = NsCommandSourceInfo;
43
+
44
+ export interface NsCommandCatalog {
45
+ candidates: ReadonlyMap<string, ExtensionCommandCandidate>;
46
+ commandInfos: readonly NsCommandCliInfo[];
47
+ diagnostics: readonly ExtensionDiagnostic[];
48
+ }
49
+
50
+ export type ExtensionCommandCandidate = BuiltInNsCommandCandidate | ExternalNsCommandCandidate;
51
+
52
+ export interface ExternalNsCommandCandidate extends NsCommandCandidate {
53
+ moduleReference: NsCommandModuleReference;
54
+ entryPath?: string;
55
+ hasStaticCommandInfo: boolean;
56
+ }
57
+
58
+ export type ExtensionDiagnostic = ExtensionErrorDiagnostic | ExtensionOverrideDiagnostic;
59
+
60
+ export interface ExtensionErrorDiagnostic {
61
+ severity: "error";
62
+ code: string;
63
+ message: string;
64
+ path?: string;
65
+ sourceLevel?: ExtensionSourceLevel;
66
+ commandName?: string;
67
+ }
68
+
69
+ export interface ExtensionOverrideDiagnostic {
70
+ severity: "info";
71
+ code: "extension_command_override";
72
+ message: string;
73
+ commandName: string;
74
+ overriddenSource: ExtensionSourceInfo;
75
+ overridingSource: ExtensionSourceInfo;
76
+ }
77
+
78
+ export type SelectedNsCommandLoadResult =
79
+ | { ok: true; command: NsCommand; source: ExtensionSourceInfo; path: NsCommandPath }
80
+ | { ok: false; diagnostic: ExtensionErrorDiagnostic };
81
+
82
+ export interface DiagnosticClassification {
83
+ fatal: readonly ExtensionErrorDiagnostic[];
84
+ warnings: readonly ExtensionErrorDiagnostic[];
85
+ }
86
+
87
+ export type PreinstalledNsCommandCatalogEntry =
88
+ | PreinstalledNsCommandPackageCatalogEntry
89
+ | PreinstalledNsCommandLoadedCatalogEntry;
90
+
91
+ export interface PreinstalledNsCommandCatalogEntryBase {
92
+ readonly group?: string;
93
+ readonly groupDescription?: string;
94
+ readonly name: string;
95
+ readonly description: string;
96
+ readonly fullDescription: string;
97
+ readonly path?: readonly string[];
98
+ }
99
+
100
+ export interface PreinstalledNsCommandPackageCatalogEntry extends PreinstalledNsCommandCatalogEntryBase {
101
+ readonly moduleSpecifier: string;
102
+ readonly load?: undefined;
103
+ }
104
+
105
+ export interface PreinstalledNsCommandLoadedCatalogEntry extends PreinstalledNsCommandCatalogEntryBase {
106
+ readonly displayPath: string;
107
+ readonly load: NsCommandModuleLoader;
108
+ }
109
+
110
+ export type PreinstalledNsCommandCatalogLoader = () =>
111
+ | readonly PreinstalledNsCommandCatalogEntry[]
112
+ | Promise<readonly PreinstalledNsCommandCatalogEntry[]>;
113
+
114
+ export interface LoadNsCommandCatalogOptions {
115
+ cwd: string;
116
+ homeDir?: string;
117
+ env?: ExplicitUndefined<"env-map", Record<string, string | undefined>>;
118
+ preinstalledCommandCatalog?: PreinstalledNsCommandCatalogLoader;
119
+ }
120
+
121
+ const ORDERED_SOURCE_LEVELS = [
122
+ "built-in",
123
+ "preinstalled",
124
+ "global",
125
+ "project",
126
+ ] as const satisfies readonly ExtensionSourceLevel[];
127
+
128
+ export async function loadNsCommandCatalog(
129
+ options: LoadNsCommandCatalogOptions,
130
+ ): Promise<NsCommandCatalog> {
131
+ const diagnostics: ExtensionDiagnostic[] = [];
132
+ const builtInCandidates = listBuiltInNsCommandCandidates();
133
+ const env = catalogEnv(options);
134
+ const globalRoots = [
135
+ requireXdgPath(resolveNsXdgPath({ kind: "data", env, segments: ["extensions"] })),
136
+ ];
137
+ const orderedSources: Array<{
138
+ level: ExtensionSourceLevel;
139
+ label: string;
140
+ candidates: readonly ExtensionCommandCandidate[];
141
+ }> = [{ level: "built-in", label: "built-in", candidates: builtInCandidates }];
142
+ const preinstalledCandidates = await loadPreinstalledCandidates(
143
+ options.preinstalledCommandCatalog,
144
+ options.cwd,
145
+ );
146
+ diagnostics.push(...preinstalledCandidates.diagnostics);
147
+ orderedSources.push({
148
+ level: "preinstalled",
149
+ label: "preinstalled extension metadata",
150
+ candidates: preinstalledCandidates.candidates,
151
+ });
152
+ for (const rootDir of uniquePaths(globalRoots)) {
153
+ const loaded = loadRootCandidates({ level: "global", rootDir });
154
+ diagnostics.push(...loaded.diagnostics);
155
+ orderedSources.push({ level: "global", label: rootDir, candidates: loaded.candidates });
156
+ }
157
+ const projectCandidates = loadRootCandidates({
158
+ level: "project",
159
+ rootDir: join(options.cwd, ".ns", "extensions"),
160
+ });
161
+ diagnostics.push(...projectCandidates.diagnostics);
162
+ orderedSources.push({
163
+ level: "project",
164
+ label: join(options.cwd, ".ns", "extensions"),
165
+ candidates: projectCandidates.candidates,
166
+ });
167
+
168
+ const merged = new Map<string, ExtensionCommandCandidate>();
169
+ for (const source of orderedSources) {
170
+ const validation = validateSourceCandidates(source.level, source.label, source.candidates);
171
+ diagnostics.push(...validation.diagnostics);
172
+ for (const candidate of validation.candidates) {
173
+ const key = commandKey(candidate);
174
+ const existing = merged.get(key);
175
+ if (existing !== undefined) {
176
+ diagnostics.push({
177
+ severity: "info",
178
+ code: "extension_command_override",
179
+ message: `ns command ${key} from ${formatSource(candidate.source)} overrides ${formatSource(existing.source)}.`,
180
+ commandName: key,
181
+ overriddenSource: existing.source,
182
+ overridingSource: candidate.source,
183
+ });
184
+ }
185
+ merged.set(key, candidate);
186
+ }
187
+ }
188
+
189
+ const sortedCandidates = [...merged.values()].sort((left, right) =>
190
+ commandKey(left).localeCompare(commandKey(right)),
191
+ );
192
+ const collisionFilter = filterGroupCommandCollisions(sortedCandidates);
193
+ diagnostics.push(...collisionFilter.diagnostics);
194
+ const finalCandidates = collisionFilter.candidates;
195
+ return {
196
+ candidates: new Map(finalCandidates.map((candidate) => [commandKey(candidate), candidate])),
197
+ commandInfos: finalCandidates.map(toCommandCliInfo),
198
+ diagnostics,
199
+ };
200
+ }
201
+
202
+ export async function loadSelectedNsCommand(
203
+ candidate: ExtensionCommandCandidate,
204
+ ): Promise<SelectedNsCommandLoadResult> {
205
+ if (isBuiltInCandidate(candidate)) {
206
+ return { ok: true, command: candidate.command, source: candidate.source, path: candidate };
207
+ }
208
+
209
+ const loaded = await loadNsExtensionContribution(candidate.moduleReference);
210
+ if (!loaded.ok) {
211
+ return {
212
+ ok: false,
213
+ diagnostic: fromLoadDiagnostic(
214
+ loaded.diagnostic,
215
+ candidate.source.level,
216
+ commandKey(candidate),
217
+ ),
218
+ };
219
+ }
220
+ const validation = validateNsExtensionContribution(
221
+ loaded.defaultExport,
222
+ candidate,
223
+ formatSource(candidate.source),
224
+ );
225
+ if (!validation.ok) {
226
+ return {
227
+ ok: false,
228
+ diagnostic: {
229
+ severity: "error",
230
+ code: "extension_command_invalid",
231
+ message: validation.message,
232
+ path: candidateDiagnosticPath(candidate),
233
+ sourceLevel: candidate.source.level,
234
+ commandName: commandKey(candidate),
235
+ },
236
+ };
237
+ }
238
+ return { ok: true, command: validation.command, source: candidate.source, path: candidate };
239
+ }
240
+
241
+ export async function loadListingCommandInfos(catalog: NsCommandCatalog): Promise<{
242
+ commandInfos: readonly NsCommandCliInfo[];
243
+ diagnostics: readonly ExtensionErrorDiagnostic[];
244
+ }> {
245
+ const loadedInfos = await Promise.all(
246
+ [...catalog.candidates.values()].map(async (candidate) => {
247
+ if (isBuiltInCandidate(candidate)) {
248
+ return { commandInfo: toCommandCliInfo(candidate), diagnostic: undefined };
249
+ }
250
+ if (candidate.moduleReference.type === "package" || candidate.hasStaticCommandInfo) {
251
+ return { commandInfo: toCommandCliInfo(candidate), diagnostic: undefined };
252
+ }
253
+ const loaded = await loadSelectedNsCommand(candidate);
254
+ if (!loaded.ok) {
255
+ return { commandInfo: toCommandCliInfo(candidate), diagnostic: loaded.diagnostic };
256
+ }
257
+ return {
258
+ commandInfo: commandInfoForLoadedCommand(loaded.command, loaded.source.level, loaded.path),
259
+ diagnostic: undefined,
260
+ };
261
+ }),
262
+ );
263
+ return {
264
+ commandInfos: loadedInfos.map((loaded) => loaded.commandInfo),
265
+ diagnostics: loadedInfos.flatMap((loaded) =>
266
+ loaded.diagnostic === undefined ? [] : [loaded.diagnostic],
267
+ ),
268
+ };
269
+ }
270
+
271
+ export function commandInfosForSelectedCommand(
272
+ commandInfos: readonly NsCommandCliInfo[],
273
+ loaded: { command: NsCommand; source: ExtensionSourceInfo; path: NsCommandPath } | undefined,
274
+ ): readonly NsCommandCliInfo[] {
275
+ if (loaded === undefined) return commandInfos;
276
+ const loadedInfo = commandInfoForLoadedCommand(loaded.command, loaded.source.level, loaded.path);
277
+ return commandInfos.map((info) => (commandPathMatches(info, loadedInfo) ? loadedInfo : info));
278
+ }
279
+
280
+ export function classifyExtensionDiagnosticsForInvocation(options: {
281
+ diagnostics: readonly ExtensionDiagnostic[];
282
+ requestedCommandName: string | undefined;
283
+ selectedCandidate: ExtensionCommandCandidate | undefined;
284
+ }): DiagnosticClassification {
285
+ const errorDiagnostics = options.diagnostics.filter(
286
+ (diagnostic): diagnostic is ExtensionErrorDiagnostic => diagnostic.severity === "error",
287
+ );
288
+ if (options.requestedCommandName === undefined) {
289
+ return { fatal: [], warnings: errorDiagnostics };
290
+ }
291
+
292
+ const fatal: ExtensionErrorDiagnostic[] = [];
293
+ const warnings: ExtensionErrorDiagnostic[] = [];
294
+ for (const diagnostic of errorDiagnostics) {
295
+ if (diagnostic.commandName !== options.requestedCommandName) {
296
+ warnings.push(diagnostic);
297
+ continue;
298
+ }
299
+ if (isFatalForSelectedCandidate(diagnostic, options.selectedCandidate)) {
300
+ fatal.push(diagnostic);
301
+ continue;
302
+ }
303
+ warnings.push(diagnostic);
304
+ }
305
+ return { fatal, warnings };
306
+ }
307
+
308
+ export function hasExtensionErrors(diagnostics: readonly ExtensionDiagnostic[]): boolean {
309
+ return diagnostics.some((diagnostic) => diagnostic.severity === "error");
310
+ }
311
+
312
+ export function formatExtensionErrorDiagnostics(
313
+ diagnostics: readonly ExtensionDiagnostic[],
314
+ ): string {
315
+ return formatExtensionDiagnosticMessages(
316
+ diagnostics.filter(
317
+ (diagnostic): diagnostic is ExtensionErrorDiagnostic => diagnostic.severity === "error",
318
+ ),
319
+ );
320
+ }
321
+
322
+ export function formatExtensionWarningDiagnostics(
323
+ diagnostics: readonly ExtensionErrorDiagnostic[],
324
+ ): string {
325
+ return formatExtensionDiagnosticMessages(diagnostics, { prefix: "Warning: " });
326
+ }
327
+
328
+ function formatExtensionDiagnosticMessages(
329
+ diagnostics: readonly ExtensionErrorDiagnostic[],
330
+ options: { prefix?: string } = {},
331
+ ): string {
332
+ const prefix = options.prefix ?? "";
333
+ return diagnostics.map((diagnostic) => `${prefix}${diagnostic.message}`).join("\n");
334
+ }
335
+
336
+ function isFatalForSelectedCandidate(
337
+ diagnostic: ExtensionErrorDiagnostic,
338
+ selectedCandidate: ExtensionCommandCandidate | undefined,
339
+ ): boolean {
340
+ if (selectedCandidate === undefined) return true;
341
+ if (diagnostic.sourceLevel === undefined) return true;
342
+ return sourceLevelRank(diagnostic.sourceLevel) >= sourceLevelRank(selectedCandidate.source.level);
343
+ }
344
+
345
+ function loadRootCandidates(options: { level: "global" | "project"; rootDir: string }): {
346
+ diagnostics: readonly ExtensionDiagnostic[];
347
+ candidates: readonly ExtensionCommandCandidate[];
348
+ } {
349
+ const discovered = discoverExtensionsInRoot(options.rootDir);
350
+ return {
351
+ diagnostics: discovered.diagnostics.map((diagnostic) =>
352
+ fromDiscoveryDiagnostic(diagnostic, options.level),
353
+ ),
354
+ candidates: discovered.commands.map((command) =>
355
+ discoveredCommandCandidateForLevel(command, options.level),
356
+ ),
357
+ };
358
+ }
359
+
360
+ async function loadPreinstalledCandidates(
361
+ catalogLoader: PreinstalledNsCommandCatalogLoader | undefined,
362
+ cwd: string,
363
+ ): Promise<{
364
+ diagnostics: readonly ExtensionDiagnostic[];
365
+ candidates: readonly ExtensionCommandCandidate[];
366
+ }> {
367
+ const catalogEntries = catalogLoader === undefined ? [] : await catalogLoader();
368
+ const catalogCandidates = catalogEntries.map(preinstalledCandidateForCatalogEntry);
369
+ const sourceDevCandidates = loadSourceDevPreinstalledCandidates(
370
+ cwd,
371
+ new Set(catalogCandidates.map((candidate) => commandKey(candidate))),
372
+ );
373
+ return {
374
+ diagnostics: sourceDevCandidates.diagnostics,
375
+ candidates: [...catalogCandidates, ...sourceDevCandidates.candidates],
376
+ };
377
+ }
378
+
379
+ function loadSourceDevPreinstalledCandidates(
380
+ cwd: string,
381
+ catalogKeys: ReadonlySet<string>,
382
+ ): {
383
+ diagnostics: readonly ExtensionDiagnostic[];
384
+ candidates: readonly ExtensionCommandCandidate[];
385
+ } {
386
+ const packagesRoot = sourceDevWorkspacePackagesRoot(cwd);
387
+ if (packagesRoot === undefined) return { diagnostics: [], candidates: [] };
388
+ const packageDirs = discoverWorkspacePackageDirs(packagesRoot);
389
+ const diagnostics: ExtensionDiagnostic[] = [];
390
+ const candidates: ExtensionCommandCandidate[] = [];
391
+ for (const packageDir of packageDirs) {
392
+ const discovered = discoverNsPackageCommands(packagesRoot, packageDir);
393
+ diagnostics.push(
394
+ ...discovered.diagnostics.map((diagnostic) =>
395
+ fromDiscoveryDiagnostic(diagnostic, "preinstalled"),
396
+ ),
397
+ );
398
+ candidates.push(
399
+ ...discovered.commands
400
+ .filter((command) => !catalogKeys.has(commandKey(command)))
401
+ .map(sourceDevDiscoveredCommandCandidate),
402
+ );
403
+ }
404
+ return { diagnostics, candidates };
405
+ }
406
+
407
+ function sourceDevWorkspacePackagesRoot(cwd: string): string | undefined {
408
+ const packagesRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
409
+ const checkoutRoot = resolve(packagesRoot, "..", "..");
410
+ const kernelSourceDir = join(packagesRoot, "kernel", "src");
411
+ if (!existsSync(kernelSourceDir)) return undefined;
412
+ return isPathInside(checkoutRoot, cwd) ? packagesRoot : undefined;
413
+ }
414
+
415
+ function discoverWorkspacePackageDirs(packagesRoot: string): readonly string[] {
416
+ const packageDirs: string[] = [];
417
+ collectPackageDirs({ root: packagesRoot, current: packagesRoot, depth: 0, packageDirs });
418
+ return packageDirs.sort((left, right) => left.localeCompare(right));
419
+ }
420
+
421
+ function collectPackageDirs(options: {
422
+ root: string;
423
+ current: string;
424
+ depth: number;
425
+ packageDirs: string[];
426
+ }): void {
427
+ if (options.depth > 3) return;
428
+ const packageJsonPath = join(options.current, "package.json");
429
+ if (options.current !== options.root && existsSync(packageJsonPath)) {
430
+ options.packageDirs.push(options.current);
431
+ return;
432
+ }
433
+ let entries;
434
+ try {
435
+ entries = readdirSync(options.current, { withFileTypes: true });
436
+ } catch {
437
+ // Source-dev package discovery is best-effort; unreadable subtrees cannot contribute commands.
438
+ return;
439
+ }
440
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
441
+ if (!entry.isDirectory() || entry.name === "node_modules") continue;
442
+ collectPackageDirs({
443
+ root: options.root,
444
+ current: join(options.current, entry.name),
445
+ depth: options.depth + 1,
446
+ packageDirs: options.packageDirs,
447
+ });
448
+ }
449
+ }
450
+
451
+ function preinstalledCandidateForCatalogEntry(
452
+ entry: PreinstalledNsCommandCatalogEntry,
453
+ ): ExternalNsCommandCandidate {
454
+ const displayPath = preinstalledCatalogEntryDisplayPath(entry);
455
+ return {
456
+ ...preinstalledCatalogEntryCommandInfo(entry),
457
+ moduleReference: preinstalledCatalogEntryModuleReference(entry),
458
+ hasStaticCommandInfo: true,
459
+ source: {
460
+ level: "preinstalled",
461
+ label: `preinstalled package ${displayPath}`,
462
+ path: displayPath,
463
+ },
464
+ };
465
+ }
466
+
467
+ function preinstalledCatalogEntryModuleReference(
468
+ entry: PreinstalledNsCommandCatalogEntry,
469
+ ): NsCommandModuleReference {
470
+ if (entry.load !== undefined) return loadedModuleReference(entry.displayPath, entry.load);
471
+ return packageModuleReference(entry.moduleSpecifier);
472
+ }
473
+
474
+ function preinstalledCatalogEntryDisplayPath(entry: PreinstalledNsCommandCatalogEntry): string {
475
+ if (entry.load !== undefined) return entry.displayPath;
476
+ return entry.moduleSpecifier;
477
+ }
478
+
479
+ function preinstalledCatalogEntryCommandInfo(
480
+ entry: PreinstalledNsCommandCatalogEntry,
481
+ ): NsCommandCliInfo {
482
+ return toCommandCliInfo({
483
+ ...entry,
484
+ ...(entry.path === undefined ? {} : { segments: entry.path }),
485
+ });
486
+ }
487
+
488
+ function sourceDevDiscoveredCommandCandidate(
489
+ command: DiscoveredExtensionCommand,
490
+ ): ExternalNsCommandCandidate {
491
+ return discoveredCommandCandidate({
492
+ command,
493
+ level: "preinstalled",
494
+ label: `source-dev package ${command.displayPath}`,
495
+ });
496
+ }
497
+
498
+ function discoveredCommandCandidateForLevel(
499
+ command: DiscoveredExtensionCommand,
500
+ level: "global" | "project",
501
+ ): ExternalNsCommandCandidate {
502
+ return discoveredCommandCandidate({
503
+ command,
504
+ level,
505
+ label: command.displayPath,
506
+ });
507
+ }
508
+
509
+ function discoveredCommandCandidate(options: {
510
+ command: DiscoveredExtensionCommand;
511
+ level: ExtensionSourceLevel;
512
+ label: string;
513
+ }): ExternalNsCommandCandidate {
514
+ return {
515
+ ...toCommandCliInfo(options.command),
516
+ moduleReference:
517
+ options.command.moduleReference ?? fileModuleReference(options.command.entryPath),
518
+ entryPath: options.command.entryPath,
519
+ hasStaticCommandInfo: options.command.hasStaticCommandInfo,
520
+ source: {
521
+ level: options.level,
522
+ label: options.label,
523
+ path: options.command.entryPath,
524
+ },
525
+ };
526
+ }
527
+
528
+ function validateSourceCandidates(
529
+ level: ExtensionSourceLevel,
530
+ sourceLabel: string,
531
+ candidates: readonly ExtensionCommandCandidate[],
532
+ ): {
533
+ candidates: readonly ExtensionCommandCandidate[];
534
+ diagnostics: readonly ExtensionDiagnostic[];
535
+ } {
536
+ const diagnostics: ExtensionDiagnostic[] = [];
537
+ const validated: ExtensionCommandCandidate[] = [];
538
+ for (const candidate of candidates) {
539
+ if (!NS_COMMAND_NAME_PATTERN.test(candidate.name)) {
540
+ diagnostics.push({
541
+ severity: "error",
542
+ code: "extension_command_name_invalid",
543
+ message: `Invalid ns command candidate from ${formatSource(candidate.source)}: command name must match ${NS_COMMAND_NAME_RULE}.`,
544
+ ...(candidate.source.path === undefined ? {} : { path: candidate.source.path }),
545
+ sourceLevel: candidate.source.level,
546
+ commandName: commandKey(candidate),
547
+ });
548
+ continue;
549
+ }
550
+ if (candidate.group !== undefined && !NS_COMMAND_NAME_PATTERN.test(candidate.group)) {
551
+ diagnostics.push({
552
+ severity: "error",
553
+ code: "extension_command_group_invalid",
554
+ message: `Invalid ns command candidate from ${formatSource(candidate.source)}: command group must match ${NS_COMMAND_NAME_RULE}.`,
555
+ ...(candidate.source.path === undefined ? {} : { path: candidate.source.path }),
556
+ sourceLevel: candidate.source.level,
557
+ commandName: commandKey(candidate),
558
+ });
559
+ continue;
560
+ }
561
+ if (commandSegments(candidate).some((segment) => !NS_COMMAND_NAME_PATTERN.test(segment))) {
562
+ diagnostics.push({
563
+ severity: "error",
564
+ code: "extension_command_path_invalid",
565
+ message: `Invalid ns command candidate from ${formatSource(candidate.source)}: command path segments must match ${NS_COMMAND_NAME_RULE}.`,
566
+ ...(candidate.source.path === undefined ? {} : { path: candidate.source.path }),
567
+ sourceLevel: candidate.source.level,
568
+ commandName: commandKey(candidate),
569
+ });
570
+ continue;
571
+ }
572
+ validated.push(candidate);
573
+ }
574
+
575
+ const candidatesByName = new Map<string, readonly ExtensionCommandCandidate[]>();
576
+ for (const candidate of validated) {
577
+ const key = commandKey(candidate);
578
+ const existing = candidatesByName.get(key) ?? [];
579
+ candidatesByName.set(key, [...existing, candidate]);
580
+ }
581
+
582
+ const duplicateNames = new Set(
583
+ [...candidatesByName.entries()]
584
+ .filter(([, matches]) => matches.length > 1)
585
+ .map(([name]) => name),
586
+ );
587
+ for (const name of duplicateNames) {
588
+ const matches = candidatesByName.get(name) ?? [];
589
+ diagnostics.push({
590
+ severity: "error",
591
+ code: "extension_command_duplicate_in_level",
592
+ message: `Duplicate ns command ${name} within ${level} extension source ${sourceLabel}: ${matches.map((match) => formatSource(match.source)).join(", ")}.`,
593
+ sourceLevel: level,
594
+ commandName: name,
595
+ });
596
+ }
597
+ return {
598
+ candidates: validated.filter((candidate) => !duplicateNames.has(commandKey(candidate))),
599
+ diagnostics,
600
+ };
601
+ }
602
+
603
+ function filterGroupCommandCollisions(candidates: readonly ExtensionCommandCandidate[]): {
604
+ candidates: readonly ExtensionCommandCandidate[];
605
+ diagnostics: readonly ExtensionErrorDiagnostic[];
606
+ } {
607
+ const topLevelByName = new Map<string, ExtensionCommandCandidate>();
608
+ for (const candidate of candidates) {
609
+ const segments = commandSegments(candidate);
610
+ if (segments.length !== 1) continue;
611
+ const name = segments[0];
612
+ if (name === undefined) continue;
613
+ topLevelByName.set(name, candidate);
614
+ }
615
+ if (topLevelByName.size === 0) {
616
+ return { candidates, diagnostics: [] };
617
+ }
618
+
619
+ const collidingGroups = new Set<string>();
620
+ const diagnostics: ExtensionErrorDiagnostic[] = [];
621
+ for (const candidate of candidates) {
622
+ const segments = commandSegments(candidate);
623
+ if (segments.length < 2) continue;
624
+ const topSegment = segments[0];
625
+ if (topSegment === undefined) continue;
626
+ const topLevel = topLevelByName.get(topSegment);
627
+ if (topLevel === undefined) continue;
628
+ collidingGroups.add(topSegment);
629
+ diagnostics.push({
630
+ severity: "error",
631
+ code: "extension_command_group_collision",
632
+ message: `ns command ${commandKey(candidate)} from ${formatSource(candidate.source)} cannot load because top-level command ${topSegment} from ${formatSource(topLevel.source)} already exists.`,
633
+ path: candidateDiagnosticPath(candidate),
634
+ sourceLevel: candidate.source.level,
635
+ commandName: commandKey(candidate),
636
+ });
637
+ }
638
+ if (collidingGroups.size === 0) {
639
+ return { candidates, diagnostics: [] };
640
+ }
641
+
642
+ return {
643
+ candidates: candidates.filter((candidate) => {
644
+ const segments = commandSegments(candidate);
645
+ const topSegment = segments[0];
646
+ return segments.length < 2 || topSegment === undefined || !collidingGroups.has(topSegment);
647
+ }),
648
+ diagnostics,
649
+ };
650
+ }
651
+
652
+ function catalogEnv(options: LoadNsCommandCatalogOptions): Record<string, string | undefined> {
653
+ return {
654
+ ...process.env,
655
+ ...(options.env ?? {}),
656
+ ...(options.homeDir === undefined ? {} : { HOME: options.homeDir }),
657
+ };
658
+ }
659
+
660
+ function uniquePaths(paths: readonly string[]): readonly string[] {
661
+ return [...new Set(paths)];
662
+ }
663
+
664
+ function sourceLevelRank(level: ExtensionSourceLevel): number {
665
+ const rank = ORDERED_SOURCE_LEVELS.indexOf(level);
666
+ if (rank === -1) {
667
+ throw new Error(`Missing ns extension source-level order for ${level}.`);
668
+ }
669
+ return rank;
670
+ }
671
+
672
+ function isBuiltInCandidate(
673
+ candidate: ExtensionCommandCandidate,
674
+ ): candidate is BuiltInNsCommandCandidate {
675
+ return candidate.source.level === "built-in";
676
+ }
677
+
678
+ function fromDiscoveryDiagnostic(
679
+ diagnostic: ExtensionDiscoveryDiagnostic,
680
+ sourceLevel: ExtensionSourceLevel,
681
+ ): ExtensionErrorDiagnostic {
682
+ return { ...diagnostic, sourceLevel };
683
+ }
684
+
685
+ function fromLoadDiagnostic(
686
+ diagnostic: ExtensionLoadDiagnostic,
687
+ sourceLevel: ExtensionSourceLevel,
688
+ commandName: string,
689
+ ): ExtensionErrorDiagnostic {
690
+ return { ...diagnostic, sourceLevel, commandName };
691
+ }
692
+
693
+ function candidateDiagnosticPath(candidate: ExtensionCommandCandidate): string {
694
+ if (isBuiltInCandidate(candidate)) return formatSource(candidate.source);
695
+ return moduleReferenceDisplay(candidate.moduleReference);
696
+ }
697
+
698
+ function formatSource(source: ExtensionSourceInfo): string {
699
+ return source.path === undefined ? source.label : `${source.label} (${source.path})`;
700
+ }