@north-light/crouter 0.3.164 → 0.3.165

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/dist/build-root.d.ts +6 -6
  2. package/dist/build-root.js +12 -13
  3. package/dist/builtin-memory/00-runtime-base.md +3 -4
  4. package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
  5. package/dist/builtin-memory/02-lifecycle/00-terminal.md +2 -0
  6. package/dist/builtin-memory/04-orchestration-kernel.md +16 -18
  7. package/dist/builtin-memory/05-kinds/advisor/00-base.md +0 -2
  8. package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +3 -9
  9. package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -3
  10. package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +2 -10
  11. package/dist/builtin-memory/05-kinds/explore/00-base.md +2 -2
  12. package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +0 -2
  13. package/dist/builtin-memory/05-kinds/general/00-base.md +3 -5
  14. package/dist/builtin-memory/05-kinds/general/01-orchestrator.md +0 -4
  15. package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +1 -3
  16. package/dist/builtin-memory/05-kinds/plan/reviewers/00-base.md +12 -0
  17. package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +0 -2
  18. package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +0 -2
  19. package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +0 -2
  20. package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +1 -1
  21. package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +0 -2
  22. package/dist/builtin-memory/05-kinds/review/00-base.md +0 -2
  23. package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -3
  24. package/dist/builtin-memory/05-kinds/spec/00-base.md +1 -1
  25. package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +2 -4
  26. package/dist/builtin-memory/05-kinds/spec/requirements.md +1 -1
  27. package/dist/builtin-memory/advisor/council.md +40 -0
  28. package/dist/builtin-memory/design.md +8 -11
  29. package/dist/builtin-memory/development.md +6 -9
  30. package/dist/builtin-memory/internal/INDEX.md +4 -5
  31. package/dist/builtin-memory/internal/agent-shaping.md +5 -7
  32. package/dist/builtin-memory/internal/examples/imessage-assistant.md +3 -3
  33. package/dist/builtin-memory/internal/marketplaces.md +13 -14
  34. package/dist/builtin-memory/internal/nodes-and-canvas.md +4 -4
  35. package/dist/builtin-memory/internal/plugins.md +61 -45
  36. package/dist/builtin-memory/planning.md +4 -7
  37. package/dist/builtin-memory/spec.md +9 -12
  38. package/dist/builtin-memory/wedged-child-on-runaway-bash.md +8 -22
  39. package/dist/cli.js +1 -1
  40. package/dist/clients/attach/__tests__/attach-keybindings.test.js +17 -4
  41. package/dist/clients/attach/__tests__/context-message.test.js +31 -20
  42. package/dist/clients/attach/__tests__/crtr-output.test.js +39 -30
  43. package/dist/clients/attach/render/chat-view.d.ts +20 -20
  44. package/dist/clients/attach/render/chat-view.js +42 -54
  45. package/dist/clients/attach/render/{frozen-history.d.ts → condensed-history.d.ts} +1 -18
  46. package/dist/clients/attach/render/condensed-history.js +60 -0
  47. package/dist/clients/attach/render/context-message.js +9 -14
  48. package/dist/clients/attach/session/bindings.d.ts +5 -3
  49. package/dist/clients/attach/session/bindings.js +7 -13
  50. package/dist/clients/attach/session/keys.d.ts +2 -0
  51. package/dist/clients/attach/session/keys.js +36 -37
  52. package/dist/clients/attach/session/profile-files.d.ts +8 -0
  53. package/dist/clients/attach/session/profile-files.js +157 -0
  54. package/dist/clients/attach/viewer.js +525 -525
  55. package/dist/commands/node.js +2 -2
  56. package/dist/commands/pkg/plugin-inspect.js +6 -7
  57. package/dist/commands/pkg/plugin-manage.d.ts +1 -1
  58. package/dist/commands/pkg/plugin-manage.js +131 -19
  59. package/dist/commands/pkg/plugin.js +2 -2
  60. package/dist/commands/pkg.js +6 -11
  61. package/dist/commands/profile/env.js +3 -3
  62. package/dist/commands/sys/config.js +17 -76
  63. package/dist/commands/sys/doctor.js +5 -91
  64. package/dist/commands/sys/setup-wizard.d.ts +9 -11
  65. package/dist/commands/sys/setup-wizard.js +47 -81
  66. package/dist/core/__tests__/base-worker-prompt.test.js +18 -21
  67. package/dist/core/__tests__/command-plugins-surfaces.test.js +39 -5
  68. package/dist/core/__tests__/command-plugins.test.js +36 -16
  69. package/dist/core/__tests__/review-model-floor.test.js +2 -2
  70. package/dist/core/__tests__/tmux-surface.test.js +10 -1
  71. package/dist/core/command-manifests/manifest.d.ts +24 -0
  72. package/dist/core/{configured-clis → command-manifests}/manifest.js +21 -25
  73. package/dist/core/command-manifests/registry.d.ts +1 -2
  74. package/dist/core/command-manifests/registry.js +2 -2
  75. package/dist/core/command-manifests/schema.d.ts +11 -13
  76. package/dist/core/command-manifests/schema.js +53 -192
  77. package/dist/core/command-plugins/compose.d.ts +0 -6
  78. package/dist/core/command-plugins/compose.js +28 -75
  79. package/dist/core/command-plugins/discovery.d.ts +25 -58
  80. package/dist/core/command-plugins/discovery.js +152 -259
  81. package/dist/core/command-plugins/endpoint.d.ts +24 -0
  82. package/dist/core/command-plugins/endpoint.js +48 -0
  83. package/dist/core/command-plugins/store.d.ts +16 -0
  84. package/dist/core/command-plugins/store.js +64 -0
  85. package/dist/core/command-plugins/{adapter.d.ts → transport/exec-invoke.d.ts} +3 -3
  86. package/dist/core/command-plugins/{adapter.js → transport/exec-invoke.js} +4 -4
  87. package/dist/core/{configured-clis/fetch.d.ts → command-plugins/transport/http-fetch.d.ts} +9 -18
  88. package/dist/core/{configured-clis/fetch.js → command-plugins/transport/http-fetch.js} +15 -35
  89. package/dist/core/{configured-clis/invoker.d.ts → command-plugins/transport/http-invoke.d.ts} +7 -7
  90. package/dist/core/{configured-clis/invoker.js → command-plugins/transport/http-invoke.js} +21 -23
  91. package/dist/core/command.d.ts +8 -9
  92. package/dist/core/command.js +6 -9
  93. package/dist/core/config.js +6 -10
  94. package/dist/core/env-name.d.ts +6 -0
  95. package/dist/core/env-name.js +9 -0
  96. package/dist/core/io.d.ts +1 -1
  97. package/dist/core/keybindings/__tests__/resolve.test.js +40 -3
  98. package/dist/core/keybindings/attach-control.d.ts +37 -0
  99. package/dist/core/keybindings/attach-control.js +38 -0
  100. package/dist/core/keybindings/catalog.d.ts +5 -4
  101. package/dist/core/keybindings/catalog.js +16 -8
  102. package/dist/core/keybindings/index.d.ts +1 -0
  103. package/dist/core/keybindings/index.js +1 -0
  104. package/dist/core/keybindings/types.d.ts +1 -1
  105. package/dist/core/preview-registry.js +41 -74
  106. package/dist/core/runtime/bearings.d.ts +2 -5
  107. package/dist/core/runtime/bearings.js +2 -5
  108. package/dist/core/runtime/front-door.d.ts +1 -1
  109. package/dist/core/runtime/front-door.js +2 -2
  110. package/dist/core/runtime/kickoff.d.ts +3 -3
  111. package/dist/core/runtime/kickoff.js +4 -3
  112. package/dist/core/runtime/situational-context.d.ts +1 -1
  113. package/dist/core/runtime/situational-context.js +1 -1
  114. package/dist/core/runtime/spawn.js +9 -9
  115. package/dist/core/runtime/tmux.js +29 -2
  116. package/dist/core/scope.d.ts +0 -5
  117. package/dist/core/scope.js +0 -10
  118. package/dist/core/user-settings.d.ts +193 -0
  119. package/dist/core/user-settings.js +252 -0
  120. package/dist/index.d.ts +4 -0
  121. package/dist/index.js +3 -0
  122. package/dist/pi-extensions/canvas-stophook.js +3 -2
  123. package/dist/shared/generated-context.d.ts +34 -0
  124. package/dist/shared/generated-context.js +98 -0
  125. package/dist/types.d.ts +23 -16
  126. package/dist/types.js +3 -14
  127. package/dist/web-client/assets/{index-NIuSCOHM.js → index-BMTOGuOZ.js} +19 -19
  128. package/dist/web-client/assets/index-DvA6Zw-R.css +2 -0
  129. package/dist/web-client/index.html +2 -2
  130. package/dist/web-client/sw.js +1 -1
  131. package/docs/public-api.md +1 -0
  132. package/package.json +1 -1
  133. package/runtime.lock.json +2 -2
  134. package/dist/builtin-memory/05-kinds/product/00-base.md +0 -25
  135. package/dist/builtin-memory/05-kinds/product/01-orchestrator.md +0 -15
  136. package/dist/builtin-memory/05-kinds/product/teardown.md +0 -15
  137. package/dist/builtin-memory/internal/workflow-codification.md +0 -82
  138. package/dist/builtin-memory/product.md +0 -80
  139. package/dist/clients/attach/render/frozen-history.js +0 -100
  140. package/dist/commands/pkg/cli-inspect.d.ts +0 -17
  141. package/dist/commands/pkg/cli-inspect.js +0 -190
  142. package/dist/commands/pkg/cli-manage.d.ts +0 -3
  143. package/dist/commands/pkg/cli-manage.js +0 -206
  144. package/dist/commands/pkg/cli.d.ts +0 -1
  145. package/dist/commands/pkg/cli.js +0 -14
  146. package/dist/core/configured-clis/cache.d.ts +0 -16
  147. package/dist/core/configured-clis/cache.js +0 -57
  148. package/dist/core/configured-clis/compose.d.ts +0 -14
  149. package/dist/core/configured-clis/compose.js +0 -60
  150. package/dist/core/configured-clis/discovery.d.ts +0 -47
  151. package/dist/core/configured-clis/discovery.js +0 -173
  152. package/dist/core/configured-clis/manifest.d.ts +0 -24
  153. package/dist/core/configured-clis/registration.d.ts +0 -40
  154. package/dist/core/configured-clis/registration.js +0 -201
  155. package/dist/web-client/assets/index-CqLKj8Xu.css +0 -2
@@ -18,8 +18,8 @@
18
18
  // recoverable error propagation, and every malformed case.
19
19
  // 5. Lifecycle without restart — install/disable/edit/remove all take effect
20
20
  // on the next discovery call (per-invocation, no cache).
21
- // 6/7. Typed issues for path/exec-bit/schema/unknown-key/parent violations
22
- // without executing the fixture binary.
21
+ // 6/7. Typed issues for path/exec-bit/schema/unknown-key violations and nested
22
+ // mount composition without executing the fixture binary.
23
23
  import { test, describe, before, after, beforeEach } from 'node:test';
24
24
  import assert from 'node:assert/strict';
25
25
  import { mkdtempSync, mkdirSync, writeFileSync, rmSync, chmodSync } from 'node:fs';
@@ -30,6 +30,7 @@ import { join, dirname } from 'node:path';
30
30
  import { fileURLToPath } from 'node:url';
31
31
  import { discoverCommandContributions, effectiveCommandPlugins, validatePluginCommands, } from '../command-plugins/discovery.js';
32
32
  import { composeExternalSubtrees, adaptPluginContributions } from '../command-plugins/compose.js';
33
+ import { resolveCommandRegistry } from '../command-manifests/registry.js';
33
34
  import { defineRoot } from '../command.js';
34
35
  import { renderRoot, renderBranch, renderLeafArgv } from '../help.js';
35
36
  import { renderResult } from '../render.js';
@@ -69,7 +70,6 @@ process.stdin.on('end', () => {
69
70
  function commandsJson(topName = 'app') {
70
71
  return {
71
72
  schemaVersion: 1,
72
- executable: 'bin/cmd.js',
73
73
  mounts: [
74
74
  {
75
75
  parent: [],
@@ -117,7 +117,13 @@ function installPlugin(scopeRoot, name, opts = {}) {
117
117
  mkdirSync(join(root, '.crouter-plugin'), { recursive: true });
118
118
  mkdirSync(join(root, 'bin'), { recursive: true });
119
119
  const pointer = opts.commandsPointer ?? 'commands.json';
120
- writeFileSync(join(root, '.crouter-plugin', 'plugin.json'), JSON.stringify({ name, version: '0.1.0', description: 'fixture', commands: pointer }));
120
+ writeFileSync(join(root, '.crouter-plugin', 'plugin.json'), JSON.stringify({
121
+ name,
122
+ version: '0.1.0',
123
+ description: 'fixture',
124
+ commands: pointer,
125
+ transport: { kind: 'exec', executable: opts.executable ?? 'bin/cmd.js' },
126
+ }));
121
127
  writeFileSync(join(root, 'commands.json'), JSON.stringify(opts.manifest ?? commandsJson()));
122
128
  const execPath = join(root, 'bin', 'cmd.js');
123
129
  writeFileSync(execPath, opts.exec ?? FIXTURE_EXEC);
@@ -229,12 +235,13 @@ describe('core collision', () => {
229
235
  test('a fixture claiming a core top-level name is skipped + recorded', () => {
230
236
  installPlugin(userRoot, 'rogue', { manifest: commandsJson('push') });
231
237
  resetScopeCache();
232
- const snapshot = discoverCommandContributions(RESERVED, emptyStart);
233
- const registrySnapshot = adaptPluginContributions(snapshot.contributions);
238
+ const candidates = discoverCommandContributions(new Set(), emptyStart);
239
+ const registrySnapshot = resolveCommandRegistry(adaptPluginContributions(candidates.contributions).contributions, RESERVED);
234
240
  assert.equal(composeExternalSubtrees(registrySnapshot).length, 0, 'the colliding branch must not enter the tree');
235
- const collision = snapshot.issues.find((i) => i.code === 'command_collision');
241
+ const collision = registrySnapshot.issues.find((i) => i.code === 'command_collision');
236
242
  assert.ok(collision, 'a command_collision issue must be recorded for doctor/show');
237
- assert.equal(collision.received, 'push');
243
+ assert.equal(collision.path, 'push');
244
+ assert.equal(collision.contributor.name, 'rogue');
238
245
  });
239
246
  });
240
247
  // ---------------------------------------------------------------------------
@@ -412,27 +419,40 @@ describe('typed validation issues', () => {
412
419
  const v = validate(m);
413
420
  assert.ok(v.issues.some((i) => i.code === 'command_manifest_invalid'));
414
421
  });
415
- test('non-empty parent → command_parent_invalid', () => {
422
+ test('a child mount declared before its non-empty parent materializes into the nested branch', () => {
416
423
  const m = commandsJson();
417
- m.mounts[0].parent = ['sys'];
424
+ const topLevel = m.mounts[0];
425
+ const child = topLevel.node['children'][0];
426
+ topLevel.node['children'] = [{
427
+ kind: 'branch',
428
+ name: 'tools',
429
+ description: 'application tools',
430
+ whenToUse: 'inspect application tools',
431
+ summary: 'application tools',
432
+ children: [],
433
+ }];
434
+ m.mounts = [
435
+ { parent: ['app', 'tools'], node: { ...child, name: 'status' } },
436
+ topLevel,
437
+ ];
418
438
  const v = validate(m);
419
- assert.ok(v.issues.some((i) => i.code === 'command_parent_invalid'));
420
- assert.equal(v.contributions.length, 0);
439
+ assert.equal(v.issues.length, 0, JSON.stringify(v.issues));
440
+ assert.equal(v.contributions.length, 1);
441
+ const tools = (v.contributions[0].node.children.find((node) => node.name === 'tools'));
442
+ assert.equal(tools.kind, 'branch');
443
+ assert.deepEqual(tools.children.map((node) => node.name), ['status']);
421
444
  });
422
445
  test('non-executable file → command_not_executable', () => {
423
446
  const v = validate(commandsJson(), { execBit: false });
424
447
  assert.ok(v.issues.some((i) => i.code === 'command_not_executable'));
425
448
  });
426
449
  test('executable escaping the plugin root → command_path_unsafe', () => {
427
- const m = commandsJson();
428
- m['executable'] = '../../../../bin/sh';
429
- const v = validate(m);
450
+ const v = validate(commandsJson(), { executable: '../../../../bin/sh' });
430
451
  assert.ok(v.issues.some((i) => i.code === 'command_path_unsafe'));
431
452
  });
432
453
  test('a top-level leaf (no rootEntry) → command_node_invalid', () => {
433
454
  const m = {
434
455
  schemaVersion: 1,
435
- executable: 'bin/cmd.js',
436
456
  mounts: [{ parent: [], node: { kind: 'leaf', name: 'x', description: 'd', whenToUse: 'w', summary: 's', params: [], output: [], outputKind: 'object', effects: ['None. Read-only.'] } }],
437
457
  };
438
458
  const v = validate(m);
@@ -46,10 +46,10 @@ test('base review ignores a light override and keeps its medium floor', () => {
46
46
  test('no base kind defaults below the medium rung', () => {
47
47
  const kinds = defaultKindsConfig();
48
48
  for (const kind of [
49
- 'general', 'developer', 'design', 'plan', 'spec', 'product', 'review',
49
+ 'general', 'developer', 'design', 'plan', 'spec', 'review',
50
50
  'plan/reviewers/requirements-coverage', 'plan/reviewers/pattern-consistency',
51
51
  'plan/reviewers/code-smells', 'plan/reviewers/security', 'plan/reviewers/architecture-fit',
52
- 'spec/requirements', 'product/teardown',
52
+ 'spec/requirements',
53
53
  ]) {
54
54
  // Kind defaults are provider-qualified and differ per kind; what must hold
55
55
  // for every one of these is the rung, not the provider or the concrete id.
@@ -24,7 +24,7 @@ import { fileURLToPath } from 'node:url';
24
24
  import { dirname, join, basename } from 'node:path';
25
25
  import { ensureSession } from '../runtime/placement.js';
26
26
  import { installTmuxBindings, legacyInboxKeyFile } from '../runtime/tmux-chrome.js';
27
- import { resolveKeybindings } from '../keybindings/index.js';
27
+ import { ATTACH_CONTROL_BINDINGS, resolveKeybindings } from '../keybindings/index.js';
28
28
  const __dirname = dirname(fileURLToPath(import.meta.url));
29
29
  const SRC_ROOT = join(__dirname, '..', '..'); // .../src
30
30
  const TMUX_TS = join(SRC_ROOT, 'core', 'runtime', 'tmux.ts');
@@ -214,6 +214,9 @@ test('tmux installer remaps owned pairs and preserves occupied user bindings', (
214
214
  }
215
215
  assert.ok(defaultMenu.indexOf('fork into idle root') > lifecycle && defaultMenu.indexOf('fork into idle root') < system, 'fork belongs in Lifecycle');
216
216
  assert.match(defaultMenu, /"view settings ›" w .*display-menu.*fold finished tool calls/);
217
+ assert.match(defaultMenu, /"attach ›" a .*display-menu.*search profile files/);
218
+ assert.match(defaultMenu, /send-keys -l/);
219
+ assert.match(defaultMenu, /crtr\.attach\.profile-files/, 'the menu path must work while the profile-files direct binding is disabled by default');
217
220
  // Matched with its selector, so the Explore group's "view settings ›" item
218
221
  // (selector `w`) cannot satisfy this assertion by substring.
219
222
  assert.ok(defaultMenu.indexOf('settings p ') > system, 'settings belongs in System');
@@ -221,6 +224,12 @@ test('tmux installer remaps owned pairs and preserves occupied user bindings', (
221
224
  assert.match(tmuxBinding(socket, 'root', 'M-v') ?? '', /switch-client/);
222
225
  // The inbox toggle installs at its default key (alt+i → M-i) in the same sweep.
223
226
  assert.match(tmuxBinding(socket, 'root', 'M-i') ?? '', /hl inbox toggle/);
227
+ const attachMenuDisabled = Object.fromEntries(ATTACH_CONTROL_BINDINGS.map(({ menuId }) => [menuId, []]));
228
+ const withoutAttach = installTmuxBindings(resolveKeybindings(attachMenuDisabled));
229
+ assert.equal(withoutAttach.ok, true, JSON.stringify(withoutAttach.diagnostics));
230
+ const menuWithoutAttach = tmuxBinding(socket, 'root', 'M-c') ?? '';
231
+ assert.doesNotMatch(menuWithoutAttach, /attach ›/, 'an empty attach submenu must hide its opener');
232
+ assert.doesNotMatch(menuWithoutAttach, /search profile files/);
224
233
  const remapped = installTmuxBindings(resolveKeybindings({
225
234
  'crtr.tmux.menu.open': ['alt+shift+z'],
226
235
  'crtr.tmux.menu.graph': ['shift+z'],
@@ -0,0 +1,24 @@
1
+ import type { DeclLeaf, TransportKind, DeclBranch, ManifestTimeouts, CommandManifestIssue } from './schema.js';
2
+ export interface ValidatedCommandManifest {
3
+ schemaVersion: 1;
4
+ baseUrl?: string;
5
+ timeouts?: ManifestTimeouts;
6
+ /** Top-level branches, each ready to mount at parent []. */
7
+ roots: DeclBranch<DeclLeaf>[];
8
+ }
9
+ export interface CommandManifestValidation {
10
+ /** The validated, materialized manifest (present only if all issues are fixed). */
11
+ manifest?: ValidatedCommandManifest;
12
+ /** All typed validation issues. Any issue means zero contributions. */
13
+ issues: CommandManifestIssue[];
14
+ }
15
+ /**
16
+ * Validates a HTTP-transport plugin manifest exactly per §4, performing strict atomic
17
+ * validation and order-independent forest materialization. Returns either a
18
+ * valid manifest with roots ready to mount, or a complete issues list with
19
+ * zero contributions.
20
+ */
21
+ export declare function validateCommandManifest(raw: unknown, options: {
22
+ transport: TransportKind;
23
+ reservedCoreNames: ReadonlySet<string>;
24
+ }): CommandManifestValidation;
@@ -1,13 +1,13 @@
1
- // Configured CLI manifest validation and forest materialization.
1
+ // Transport-parameterized command manifest validation and forest materialization.
2
2
  //
3
- // Implements §4 of the configured-CLI spec: strict atomic validation of a remote
4
- // manifest JSON, order-independent self-contained forest materialization,
5
- // REST mapping validation, and the canonical coalesced-body-source rule (exactly
6
- // one stdin + one non-stdin flag/positional sharing a body `as`).
3
+ // Validates command manifests atomically, materializes self-contained forests
4
+ // independent of nested-mount order when the parent is inline, validates REST
5
+ // mappings, and enforces the canonical coalesced-body-source rule (exactly one
6
+ // stdin + one non-stdin flag/positional sharing a body `as`).
7
7
  //
8
8
  // This is pure validation: no fetch, no registration lookup, no file I/O beyond
9
9
  // the raw manifest bytes passed in.
10
- import { validateConfiguredCliNode } from '../command-manifests/schema.js';
10
+ import { validateCommandNode } from './schema.js';
11
11
  // ---------------------------------------------------------------------------
12
12
  // Validation helpers
13
13
  // ---------------------------------------------------------------------------
@@ -27,27 +27,29 @@ function typeName(v) {
27
27
  // Top-level manifest validation
28
28
  // ---------------------------------------------------------------------------
29
29
  /**
30
- * Validates a configured CLI manifest exactly per §4, performing strict atomic
30
+ * Validates a HTTP-transport plugin manifest exactly per §4, performing strict atomic
31
31
  * validation and order-independent forest materialization. Returns either a
32
32
  * valid manifest with roots ready to mount, or a complete issues list with
33
33
  * zero contributions.
34
34
  */
35
- export function validateConfiguredCliManifest(raw, options) {
35
+ export function validateCommandManifest(raw, options) {
36
36
  const issues = [];
37
37
  const issue = (code, message, received, expected, next, path) => {
38
38
  issues.push({ code, message, received, expected, next, ...(path ? { path } : {}) });
39
39
  };
40
40
  // Top-level structure check
41
41
  if (!isObject(raw)) {
42
- issue('command_manifest_invalid', 'manifest must be an object', typeName(raw), '{ schemaVersion, baseUrl?, timeouts?, mounts }', 'Provide a valid JSON manifest object.');
42
+ issue('command_manifest_invalid', 'manifest must be an object', typeName(raw), options.transport === 'http' ? '{ schemaVersion, baseUrl?, timeouts?, mounts }' : '{ schemaVersion, mounts }', 'Provide a valid JSON manifest object.');
43
43
  return { issues };
44
44
  }
45
45
  // Check for unknown top-level keys
46
46
  const topKeys = Object.keys(raw);
47
- const allowedTopKeys = new Set(['schemaVersion', 'baseUrl', 'timeouts', 'mounts']);
47
+ const allowedTopKeys = options.transport === 'http'
48
+ ? new Set(['schemaVersion', 'baseUrl', 'timeouts', 'mounts'])
49
+ : new Set(['schemaVersion', 'mounts']);
48
50
  const unknownKeys = topKeys.filter((k) => !allowedTopKeys.has(k));
49
51
  if (unknownKeys.length > 0) {
50
- issue('command_manifest_invalid', `unknown top-level keys`, unknownKeys.join(', '), 'only: schemaVersion, baseUrl, timeouts, mounts', 'Remove the unknown keys.');
52
+ issue('command_manifest_invalid', `unknown top-level keys`, unknownKeys.join(', '), options.transport === 'http' ? 'only: schemaVersion, baseUrl, timeouts, mounts' : 'only: schemaVersion, mounts', 'Remove the unknown keys.');
51
53
  return { issues };
52
54
  }
53
55
  // Validate schemaVersion
@@ -57,7 +59,7 @@ export function validateConfiguredCliManifest(raw, options) {
57
59
  }
58
60
  // Validate optional baseUrl
59
61
  let baseUrl;
60
- if (raw['baseUrl'] !== undefined) {
62
+ if (options.transport === 'http' && raw['baseUrl'] !== undefined) {
61
63
  if (typeof raw['baseUrl'] !== 'string') {
62
64
  issue('command_manifest_invalid', 'baseUrl must be a string', typeName(raw['baseUrl']), 'an absolute HTTP(S) URL or omitted', 'Provide a valid baseUrl or remove it.', 'baseUrl');
63
65
  return { issues };
@@ -77,7 +79,7 @@ export function validateConfiguredCliManifest(raw, options) {
77
79
  }
78
80
  // Validate optional timeouts
79
81
  let timeouts;
80
- if (raw['timeouts'] !== undefined) {
82
+ if (options.transport === 'http' && raw['timeouts'] !== undefined) {
81
83
  const t = validateTimeouts(raw['timeouts'], issue);
82
84
  if (t === null)
83
85
  return { issues };
@@ -96,7 +98,7 @@ export function validateConfiguredCliManifest(raw, options) {
96
98
  // Validate and collect all mounts
97
99
  const validatedMounts = [];
98
100
  for (let i = 0; i < mounts.length; i++) {
99
- const m = validateMount(mounts[i], i, issue);
101
+ const m = validateMount(mounts[i], i, options.transport, issue);
100
102
  if (m === null)
101
103
  return { issues };
102
104
  validatedMounts.push(m);
@@ -146,7 +148,7 @@ function validateTimeouts(raw, issue) {
146
148
  }
147
149
  return out;
148
150
  }
149
- function validateMount(raw, index, issue) {
151
+ function validateMount(raw, index, transport, issue) {
150
152
  const path = `mounts[${index}]`;
151
153
  if (!isObject(raw)) {
152
154
  issue('command_manifest_invalid', 'mount must be an object', typeName(raw), '{ parent, node }', 'Fix the mount.', path);
@@ -175,7 +177,7 @@ function validateMount(raw, index, issue) {
175
177
  // Validate node
176
178
  const nodeRaw = raw['node'];
177
179
  const topLevel = parent.length === 0;
178
- const node = validateConfiguredCliNode(nodeRaw, `${path}.node`, topLevel, issue);
180
+ const node = validateCommandNode(nodeRaw, [`${path}.node`], topLevel, transport, issue);
179
181
  if (node === null)
180
182
  return null;
181
183
  // Top-level mount must be a branch with rootEntry
@@ -183,7 +185,7 @@ function validateMount(raw, index, issue) {
183
185
  issue('command_node_invalid', 'top-level mount node must be a branch', node.kind, 'a branch with a rootEntry', 'Wrap the command in a top-level branch.', `${path}.node.kind`);
184
186
  return null;
185
187
  }
186
- // Ensure the node is properly typed (should only be ConfiguredCliDeclLeaf at this point)
188
+ // Ensure the node is properly typed (should only be DeclLeaf at this point)
187
189
  const typedNode = node;
188
190
  return { parent, node: typedNode };
189
191
  }
@@ -192,8 +194,7 @@ function validateMount(raw, index, issue) {
192
194
  // ---------------------------------------------------------------------------
193
195
  /**
194
196
  * Materializes a flat list of mounts into a forest of top-level branches,
195
- * resolving parent paths order-independently. Validates self-containment
196
- * (no external parents), no duplicates, and no childless branches.
197
+ * resolving nested mounts whose parent exists in a top-level inline tree regardless of order. Nested mounts that provide another nested mount's parent must appear first. Validates self-containment and no duplicates.
197
198
  */
198
199
  function materializeForest(mounts, reservedCoreNames, issue) {
199
200
  // Separate top-level (parent: []) from nested (parent non-empty)
@@ -202,7 +203,7 @@ function materializeForest(mounts, reservedCoreNames, issue) {
202
203
  // Check for core-name collisions in top-level nodes
203
204
  for (const m of topLevel) {
204
205
  if (reservedCoreNames.has(m.node.name)) {
205
- issue('command_parent_invalid', `node name conflicts with a crtr core command`, m.node.name, `a name not in core (not one of: ${[...reservedCoreNames].join(', ')})`, 'Rename the node or remove this mount.', `${m.node.name}`);
206
+ issue('command_collision', `node name conflicts with a crtr core command`, m.node.name, `a name not in core (not one of: ${[...reservedCoreNames].join(', ')})`, 'Rename the node or remove this mount.', `${m.node.name}`);
206
207
  return null;
207
208
  }
208
209
  }
@@ -277,8 +278,3 @@ function resolveAndAttachMount(roots, mount, issue) {
277
278
  current.children.push(mount.node);
278
279
  return true;
279
280
  }
280
- // ---------------------------------------------------------------------------
281
- // Configured CLI node validator (implements §4.3 + §4.4)
282
- // ---------------------------------------------------------------------------
283
- // Re-export and use the schema validator
284
- export { validateConfiguredCliNode } from '../command-manifests/schema.js';
@@ -1,8 +1,7 @@
1
1
  import type { LeafDef } from '../command.js';
2
2
  import type { DeclLeafBase, DeclBranch, CommandManifestIssue } from './schema.js';
3
- /** Reference to an external contributor (plugin or configured CLI). */
3
+ /** Reference to an external contributor (plugin or HTTP-transport plugin). */
4
4
  export type CommandContributorRef = {
5
- kind: 'plugin' | 'configured-cli';
6
5
  name: string;
7
6
  scope: 'user' | 'project';
8
7
  };
@@ -1,6 +1,6 @@
1
1
  // Unified command registry: transport-neutral collision resolution and
2
2
  // contributor attribution. This module takes raw candidates (from plugins,
3
- // configured CLIs, or any external source) and runs one unified pass that:
3
+ // HTTP-transport plugins, or any external source) and runs one unified pass that:
4
4
  //
5
5
  // 1. Detects collisions by resolved path (shortest collision wins).
6
6
  // 2. Drops all claimants at each collided path.
@@ -63,7 +63,7 @@ export function resolveCommandRegistry(candidates, reservedCoreNames) {
63
63
  if (collisions.has(pathKey)) {
64
64
  const collision = collisions.get(pathKey);
65
65
  const claimantNames = collision.claimants.map((c) => c.contributor.name).join(', ');
66
- const claimantKinds = collision.claimants.map((c) => `${c.contributor.name} (${c.contributor.kind})`).join(', ');
66
+ const claimantKinds = collision.claimants.map((c) => c.contributor.name).join(', ');
67
67
  // Record an issue on this claimant. A core collision has exactly one
68
68
  // claimant contesting a reserved built-in name, not "multiple contributors".
69
69
  issues.push(collision.isCoreCollision
@@ -14,10 +14,10 @@ export interface DeclBranch<L = DeclLeafBase> {
14
14
  rootEntry?: DeclRootEntry;
15
15
  summary: string;
16
16
  model?: string;
17
- /** Plugin dialect only: forward every argv token after this branch to an
17
+ /** Exec transport only: forward every argv token after this branch to an
18
18
  * external binary instead of parsing children. A passthrough branch is
19
- * childless by construction. Forbidden in the configured-CLI dialect — a
20
- * remote manifest naming a local binary to exec is a trust hole. */
19
+ * childless by construction. HTTP manifests reject passthrough because an
20
+ * HTTP transport must not name a local binary to execute. */
21
21
  passthrough?: DeclPassthrough;
22
22
  children: DeclNode<L>[];
23
23
  }
@@ -36,11 +36,11 @@ export interface DeclLeafBase {
36
36
  output: Field[];
37
37
  effects: string[];
38
38
  }
39
- /** Plugin-dialect leaf: requires outputKind: 'object' (no streaming in v1). */
40
- export interface PluginDeclLeaf extends DeclLeafBase {
39
+ /** Exec-transport leaf: requires outputKind: 'object'. */
40
+ export interface ExecDeclLeaf extends DeclLeafBase {
41
41
  outputKind: 'object';
42
42
  }
43
- /** Configured CLI REST mapping (placeholder types; full spec in manifest.ts). */
43
+ /** HTTP-transport plugin REST mapping (placeholder types; full spec in manifest.ts). */
44
44
  export type RestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
45
45
  export type RestParamPlacement = 'path' | 'query' | 'body' | 'header';
46
46
  export interface RestParamMapping {
@@ -64,11 +64,11 @@ export interface ManifestTimeouts {
64
64
  requestMs?: number;
65
65
  streamIdleMs?: number;
66
66
  }
67
- /** Configured CLI-dialect leaf: requires rest (derives outputKind from streaming). */
68
- export interface ConfiguredCliDeclLeaf extends DeclLeafBase {
67
+ /** HTTP-transport leaf: requires rest (derives outputKind from streaming). */
68
+ export interface HttpDeclLeaf extends DeclLeafBase {
69
69
  rest: RestMapping;
70
70
  }
71
- export type DeclLeaf = PluginDeclLeaf | ConfiguredCliDeclLeaf;
71
+ export type DeclLeaf = ExecDeclLeaf | HttpDeclLeaf;
72
72
  export type DeclNode<L = DeclLeafBase> = DeclBranch<L> | (L extends DeclLeafBase ? DeclLeaf : never);
73
73
  export interface CommandManifestIssue {
74
74
  code: CommandIssueCode;
@@ -80,8 +80,6 @@ export interface CommandManifestIssue {
80
80
  }
81
81
  export type CommandIssueCode = 'command_manifest_unreadable' | 'command_manifest_invalid' | 'command_schema_version' | 'command_path_unsafe' | 'command_not_executable' | 'command_parent_invalid' | 'command_node_invalid' | 'command_collision' | 'command_rest_invalid';
82
82
  type IssueFn = (code: CommandIssueCode, message: string, received: string, expected: string, next: string, path?: string) => void;
83
- /** Validate a node for the plugin dialect (leaf must have outputKind: 'object'). */
84
- export declare function validatePluginNode(raw: unknown, path: string, topLevel: boolean, issue: IssueFn): DeclBranch<PluginDeclLeaf> | PluginDeclLeaf | null;
85
- /** Validate a node for the configured CLI dialect (leaf must have rest). */
86
- export declare function validateConfiguredCliNode(raw: unknown, path: string, topLevel: boolean, issue: IssueFn): DeclBranch<ConfiguredCliDeclLeaf> | ConfiguredCliDeclLeaf | null;
83
+ export type TransportKind = 'exec' | 'http';
84
+ export declare function validateCommandNode(raw: unknown, path: string[], topLevel: boolean, transport: TransportKind, issue: IssueFn): DeclBranch<DeclLeaf> | DeclLeaf | null;
87
85
  export {};