@north-light/crouter 0.3.163 → 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.
- package/dist/build-root.d.ts +6 -6
- package/dist/build-root.js +12 -13
- package/dist/builtin-memory/00-runtime-base.md +3 -4
- package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
- package/dist/builtin-memory/02-lifecycle/00-terminal.md +2 -0
- package/dist/builtin-memory/04-orchestration-kernel.md +16 -18
- package/dist/builtin-memory/05-kinds/advisor/00-base.md +0 -2
- package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +3 -9
- package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -3
- package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +2 -10
- package/dist/builtin-memory/05-kinds/explore/00-base.md +2 -2
- package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +0 -2
- package/dist/builtin-memory/05-kinds/general/00-base.md +3 -5
- package/dist/builtin-memory/05-kinds/general/01-orchestrator.md +0 -4
- package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +1 -3
- package/dist/builtin-memory/05-kinds/plan/reviewers/00-base.md +12 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +0 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +0 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +0 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +1 -1
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +0 -2
- package/dist/builtin-memory/05-kinds/review/00-base.md +0 -2
- package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -3
- package/dist/builtin-memory/05-kinds/spec/00-base.md +1 -1
- package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +2 -4
- package/dist/builtin-memory/05-kinds/spec/requirements.md +1 -1
- package/dist/builtin-memory/advisor/council.md +40 -0
- package/dist/builtin-memory/design.md +8 -11
- package/dist/builtin-memory/development.md +6 -9
- package/dist/builtin-memory/internal/INDEX.md +5 -5
- package/dist/builtin-memory/internal/agent-shaping.md +5 -7
- package/dist/builtin-memory/internal/examples/imessage-assistant.md +3 -3
- package/dist/builtin-memory/internal/marketplaces.md +13 -14
- package/dist/builtin-memory/internal/memory-loading.md +45 -0
- package/dist/builtin-memory/internal/nodes-and-canvas.md +4 -4
- package/dist/builtin-memory/internal/plugins.md +61 -45
- package/dist/builtin-memory/planning.md +4 -7
- package/dist/builtin-memory/spec.md +9 -12
- package/dist/builtin-memory/wedged-child-on-runaway-bash.md +8 -22
- package/dist/cli.js +1 -1
- package/dist/clients/attach/__tests__/attach-keybindings.test.js +17 -4
- package/dist/clients/attach/__tests__/context-message.test.js +31 -20
- package/dist/clients/attach/__tests__/crtr-output-coverage.test.js +1 -1
- package/dist/clients/attach/__tests__/crtr-output.test.js +39 -30
- package/dist/clients/attach/__tests__/edit-diff.test.js +21 -20
- package/dist/clients/attach/render/chat-view.d.ts +20 -20
- package/dist/clients/attach/render/chat-view.js +88 -89
- package/dist/clients/attach/render/{frozen-history.d.ts → condensed-history.d.ts} +1 -18
- package/dist/clients/attach/render/condensed-history.js +60 -0
- package/dist/clients/attach/render/context-message.js +9 -14
- package/dist/clients/attach/render/crtr-output.d.ts +2 -1
- package/dist/clients/attach/render/crtr-output.js +4 -4
- package/dist/clients/attach/render/edit-diff.d.ts +21 -6
- package/dist/clients/attach/render/edit-diff.js +90 -91
- package/dist/clients/attach/render/tool-calls.d.ts +14 -18
- package/dist/clients/attach/render/tool-calls.js +66 -41
- package/dist/clients/attach/session/bindings.d.ts +5 -3
- package/dist/clients/attach/session/bindings.js +7 -13
- package/dist/clients/attach/session/keys.d.ts +2 -0
- package/dist/clients/attach/session/keys.js +36 -37
- package/dist/clients/attach/session/profile-files.d.ts +8 -0
- package/dist/clients/attach/session/profile-files.js +157 -0
- package/dist/clients/attach/viewer.js +530 -528
- package/dist/commands/memory/lint.js +1 -1
- package/dist/commands/memory/write.js +1 -1
- package/dist/commands/node.js +2 -2
- package/dist/commands/pkg/plugin-inspect.js +6 -7
- package/dist/commands/pkg/plugin-manage.d.ts +1 -1
- package/dist/commands/pkg/plugin-manage.js +131 -19
- package/dist/commands/pkg/plugin.js +2 -2
- package/dist/commands/pkg.js +6 -11
- package/dist/commands/profile/env.js +3 -3
- package/dist/commands/sys/config.js +17 -76
- package/dist/commands/sys/doctor.js +5 -91
- package/dist/commands/sys/setup-wizard.d.ts +9 -11
- package/dist/commands/sys/setup-wizard.js +47 -81
- package/dist/core/__tests__/base-worker-prompt.test.js +18 -21
- package/dist/core/__tests__/command-plugins-surfaces.test.js +39 -5
- package/dist/core/__tests__/command-plugins.test.js +36 -16
- package/dist/core/__tests__/review-model-floor.test.js +2 -2
- package/dist/core/__tests__/tmux-surface.test.js +10 -1
- package/dist/core/command-manifests/manifest.d.ts +24 -0
- package/dist/core/{configured-clis → command-manifests}/manifest.js +21 -25
- package/dist/core/command-manifests/registry.d.ts +1 -2
- package/dist/core/command-manifests/registry.js +2 -2
- package/dist/core/command-manifests/schema.d.ts +11 -13
- package/dist/core/command-manifests/schema.js +53 -192
- package/dist/core/command-plugins/compose.d.ts +0 -6
- package/dist/core/command-plugins/compose.js +28 -75
- package/dist/core/command-plugins/discovery.d.ts +25 -58
- package/dist/core/command-plugins/discovery.js +152 -259
- package/dist/core/command-plugins/endpoint.d.ts +24 -0
- package/dist/core/command-plugins/endpoint.js +48 -0
- package/dist/core/command-plugins/store.d.ts +16 -0
- package/dist/core/command-plugins/store.js +64 -0
- package/dist/core/command-plugins/{adapter.d.ts → transport/exec-invoke.d.ts} +3 -3
- package/dist/core/command-plugins/{adapter.js → transport/exec-invoke.js} +4 -4
- package/dist/core/{configured-clis/fetch.d.ts → command-plugins/transport/http-fetch.d.ts} +9 -18
- package/dist/core/{configured-clis/fetch.js → command-plugins/transport/http-fetch.js} +15 -35
- package/dist/core/{configured-clis/invoker.d.ts → command-plugins/transport/http-invoke.d.ts} +7 -7
- package/dist/core/{configured-clis/invoker.js → command-plugins/transport/http-invoke.js} +21 -23
- package/dist/core/command.d.ts +8 -9
- package/dist/core/command.js +6 -9
- package/dist/core/config.js +6 -10
- package/dist/core/env-name.d.ts +6 -0
- package/dist/core/env-name.js +9 -0
- package/dist/core/io.d.ts +1 -1
- package/dist/core/keybindings/__tests__/resolve.test.js +40 -3
- package/dist/core/keybindings/attach-control.d.ts +37 -0
- package/dist/core/keybindings/attach-control.js +38 -0
- package/dist/core/keybindings/catalog.d.ts +5 -4
- package/dist/core/keybindings/catalog.js +16 -8
- package/dist/core/keybindings/index.d.ts +1 -0
- package/dist/core/keybindings/index.js +1 -0
- package/dist/core/keybindings/types.d.ts +1 -1
- package/dist/core/preview-registry.js +41 -74
- package/dist/core/runtime/bearings.d.ts +2 -5
- package/dist/core/runtime/bearings.js +2 -5
- package/dist/core/runtime/front-door.d.ts +1 -1
- package/dist/core/runtime/front-door.js +2 -2
- package/dist/core/runtime/kickoff.d.ts +3 -3
- package/dist/core/runtime/kickoff.js +4 -3
- package/dist/core/runtime/situational-context.d.ts +1 -1
- package/dist/core/runtime/situational-context.js +1 -1
- package/dist/core/runtime/spawn.js +9 -9
- package/dist/core/runtime/tmux.js +29 -2
- package/dist/core/scope.d.ts +0 -5
- package/dist/core/scope.js +0 -10
- package/dist/core/user-settings.d.ts +193 -0
- package/dist/core/user-settings.js +252 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/pi-extensions/canvas-stophook.js +3 -2
- package/dist/pi-extensions/canvas-tool-guide.js +4 -1
- package/dist/shared/generated-context.d.ts +34 -0
- package/dist/shared/generated-context.js +98 -0
- package/dist/types.d.ts +23 -16
- package/dist/types.js +3 -14
- package/dist/web-client/assets/{index-NIuSCOHM.js → index-BMTOGuOZ.js} +19 -19
- package/dist/web-client/assets/index-DvA6Zw-R.css +2 -0
- package/dist/web-client/index.html +2 -2
- package/dist/web-client/sw.js +1 -1
- package/docs/public-api.md +1 -0
- package/package.json +2 -2
- package/runtime.lock.json +6 -6
- package/dist/builtin-memory/05-kinds/product/00-base.md +0 -25
- package/dist/builtin-memory/05-kinds/product/01-orchestrator.md +0 -15
- package/dist/builtin-memory/05-kinds/product/teardown.md +0 -15
- package/dist/builtin-memory/internal/workflow-codification.md +0 -82
- package/dist/builtin-memory/product.md +0 -80
- package/dist/clients/attach/render/frozen-history.js +0 -100
- package/dist/commands/pkg/cli-inspect.d.ts +0 -17
- package/dist/commands/pkg/cli-inspect.js +0 -190
- package/dist/commands/pkg/cli-manage.d.ts +0 -3
- package/dist/commands/pkg/cli-manage.js +0 -206
- package/dist/commands/pkg/cli.d.ts +0 -1
- package/dist/commands/pkg/cli.js +0 -14
- package/dist/core/configured-clis/cache.d.ts +0 -16
- package/dist/core/configured-clis/cache.js +0 -57
- package/dist/core/configured-clis/compose.d.ts +0 -14
- package/dist/core/configured-clis/compose.js +0 -60
- package/dist/core/configured-clis/discovery.d.ts +0 -47
- package/dist/core/configured-clis/discovery.js +0 -173
- package/dist/core/configured-clis/manifest.d.ts +0 -24
- package/dist/core/configured-clis/registration.d.ts +0 -40
- package/dist/core/configured-clis/registration.js +0 -201
- package/dist/web-client/assets/index-CqLKj8Xu.css +0 -2
|
@@ -13,10 +13,6 @@ import { readFault } from '../../core/runtime/fault.js';
|
|
|
13
13
|
import { detectNerdFont, nerdFontInstallCommand, NERD_FONT_SELECT_HINT } from '../../core/runtime/nerd-font.js';
|
|
14
14
|
import { detectPackageManager } from './setup-core.js';
|
|
15
15
|
import { validateEffectiveCommandPlugins } from '../../core/command-plugins/discovery.js';
|
|
16
|
-
import { buildExternalCommandSnapshot } from '../../core/configured-clis/discovery.js';
|
|
17
|
-
import { effectiveConfiguredClis } from '../../core/configured-clis/registration.js';
|
|
18
|
-
import { readConfiguredCliCacheManifest } from '../../core/configured-clis/cache.js';
|
|
19
|
-
import { validateConfiguredCliManifest } from '../../core/configured-clis/manifest.js';
|
|
20
16
|
import { SUBTREE_NAMES } from '../../build-root.js';
|
|
21
17
|
import { processGeneration, processLocatedGeneration, runtimeManifest, selectedGeneration } from './shared.js';
|
|
22
18
|
function pass(scope, name, message) {
|
|
@@ -132,7 +128,7 @@ function runCommandPluginChecks(scopes) {
|
|
|
132
128
|
v.issues.forEach((issue, i) => {
|
|
133
129
|
const remediation = {
|
|
134
130
|
kind: 'human_action',
|
|
135
|
-
description: `${issue.next}
|
|
131
|
+
description: `${issue.next} Inspect with \`pkg plugin show ${plugin.name}\`; then update (\`pkg plugin update --name ${plugin.name}\`) or remove (\`pkg plugin remove ${plugin.name}\`) the plugin.`,
|
|
136
132
|
};
|
|
137
133
|
const detail = issue.path !== undefined ? ` at ${issue.path}` : '';
|
|
138
134
|
results.push(failCheck(plugin.scope, `plugin:${plugin.name}:commands[${i}]`, `${issue.code}${detail}: ${issue.message} (received ${issue.received}, expected ${issue.expected})`, remediation));
|
|
@@ -140,87 +136,6 @@ function runCommandPluginChecks(scopes) {
|
|
|
140
136
|
}
|
|
141
137
|
return results;
|
|
142
138
|
}
|
|
143
|
-
/**
|
|
144
|
-
* Validate cached manifests and configuration of every effective configured-CLI
|
|
145
|
-
* registration. The snapshot (E1) is used to detect cross-contributor collisions;
|
|
146
|
-
* otherwise this check reads stored manifests only — zero network. Absent store
|
|
147
|
-
* is a PASS (not an issue). Only registrations in the supplied scopes are checked.
|
|
148
|
-
* --fix never fetches or removes.
|
|
149
|
-
*/
|
|
150
|
-
function runConfiguredCliChecks(scopes) {
|
|
151
|
-
const results = [];
|
|
152
|
-
const inScope = new Set(scopes);
|
|
153
|
-
const reserved = new Set(SUBTREE_NAMES);
|
|
154
|
-
// Get collision info from the unified snapshot.
|
|
155
|
-
const snapshot = buildExternalCommandSnapshot(reserved);
|
|
156
|
-
const collisionsPerContributor = new Map();
|
|
157
|
-
for (const issue of snapshot.issues) {
|
|
158
|
-
if (issue.code === 'command_collision') {
|
|
159
|
-
const key = `${issue.contributor.kind}:${issue.contributor.scope}:${issue.contributor.name}`;
|
|
160
|
-
if (!collisionsPerContributor.has(key)) {
|
|
161
|
-
collisionsPerContributor.set(key, new Set());
|
|
162
|
-
}
|
|
163
|
-
collisionsPerContributor.get(key).add(issue.path ?? 'unknown');
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
for (const registration of effectiveConfiguredClis()) {
|
|
167
|
-
if (!inScope.has(registration.scope))
|
|
168
|
-
continue;
|
|
169
|
-
// Check: store state (absent is a PASS)
|
|
170
|
-
const cachedBytes = readConfiguredCliCacheManifest(registration);
|
|
171
|
-
if (cachedBytes === null) {
|
|
172
|
-
// Absent store is a PASS (not a failure); it is hydration-eligible.
|
|
173
|
-
results.push(pass(registration.scope, `configured-cli:${registration.name}:store`, `no stored manifest (hydration-eligible on first use)`));
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
// Have a store: check it's readable + valid JSON
|
|
177
|
-
let raw;
|
|
178
|
-
try {
|
|
179
|
-
raw = JSON.parse(Buffer.from(cachedBytes).toString('utf8'));
|
|
180
|
-
}
|
|
181
|
-
catch {
|
|
182
|
-
const remediation = {
|
|
183
|
-
kind: 'human_action',
|
|
184
|
-
description: `Stored manifest is unparseable JSON. Run \`crtr pkg cli refresh ${registration.name}\` to re-fetch, or \`crtr pkg cli remove ${registration.name}\` to drop the registration.`,
|
|
185
|
-
command: `crtr pkg cli refresh ${registration.name}`,
|
|
186
|
-
};
|
|
187
|
-
results.push(failCheck(registration.scope, `configured-cli:${registration.name}:store`, `stored manifest.json is not valid JSON`, remediation));
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
// Check: manifest §4 validation
|
|
191
|
-
const validation = validateConfiguredCliManifest(raw, { reservedCoreNames: reserved });
|
|
192
|
-
if (validation.issues.length > 0) {
|
|
193
|
-
// Stored but invalid — report issues without suggesting removal (the schema itself may be fixable by a refresh).
|
|
194
|
-
validation.issues.forEach((issue, i) => {
|
|
195
|
-
const remediation = {
|
|
196
|
-
kind: 'human_action',
|
|
197
|
-
description: `${issue.next} Run \`crtr pkg cli refresh ${registration.name}\` to pull a corrected manifest, or \`crtr pkg cli remove ${registration.name}\` to drop the registration entirely.`,
|
|
198
|
-
command: `crtr pkg cli refresh ${registration.name}`,
|
|
199
|
-
};
|
|
200
|
-
const detail = issue.path !== undefined ? ` at ${issue.path}` : '';
|
|
201
|
-
results.push(failCheck(registration.scope, `configured-cli:${registration.name}:manifest[${i}]`, `${issue.code}${detail}: ${issue.message} (received ${issue.received}, expected ${issue.expected})`, remediation));
|
|
202
|
-
});
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
// Store valid: report success + any collisions from the snapshot
|
|
206
|
-
const manifest = validation.manifest; // Safe: we checked validation.issues.length === 0 above
|
|
207
|
-
const collisions = collisionsPerContributor.get(`configured-cli:${registration.scope}:${registration.name}`) ?? new Set();
|
|
208
|
-
if (collisions.size > 0) {
|
|
209
|
-
const paths = Array.from(collisions).sort().join(', ');
|
|
210
|
-
const remediation = {
|
|
211
|
-
kind: 'human_action',
|
|
212
|
-
description: `This configured CLI's top-level command(s) or nested paths collide with core commands or other plugins. The collided paths are not mounted. Review the contributing command plugins/CLIs and resolve naming conflicts. Run \`crtr pkg cli remove ${registration.name}\` to drop this registration.`,
|
|
213
|
-
command: `crtr pkg cli show ${registration.name}`,
|
|
214
|
-
};
|
|
215
|
-
results.push(failCheck(registration.scope, `configured-cli:${registration.name}:collision`, `collision(s) at: ${paths}`, remediation));
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
218
|
-
const topLevelNames = manifest.roots.map((root) => root.name).sort().join(', ');
|
|
219
|
-
results.push(pass(registration.scope, `configured-cli:${registration.name}:manifest`, topLevelNames.length > 0 ? `valid manifest, contributes top-level command(s): ${topLevelNames}` : `valid manifest`));
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return results;
|
|
223
|
-
}
|
|
224
139
|
async function inventory(root) {
|
|
225
140
|
const files = [];
|
|
226
141
|
async function walk(dir) {
|
|
@@ -495,7 +410,7 @@ function runChecksForScope(scope, opts) {
|
|
|
495
410
|
export const sysDoctorLeaf = defineLeaf({
|
|
496
411
|
name: 'doctor',
|
|
497
412
|
description: 'diagnose installation health',
|
|
498
|
-
whenToUse: 'something in your crtr install looks off and you want it diagnosed — a plugin or marketplace manifest is missing, a config entry points at a directory that no longer exists, or a
|
|
413
|
+
whenToUse: 'something in your crtr install looks off and you want it diagnosed — a plugin or marketplace manifest is missing, a config entry points at a directory that no longer exists, or a plugin command manifest/transport is malformed. Reports each problem with a structured remediation, and can apply the safe repairs for you.',
|
|
499
414
|
help: {
|
|
500
415
|
name: 'sys doctor',
|
|
501
416
|
summary: 'diagnose runtime integrity, missing manifests, and broken config entries',
|
|
@@ -505,13 +420,13 @@ export const sysDoctorLeaf = defineLeaf({
|
|
|
505
420
|
{ kind: 'flag', name: 'remote', type: 'bool', required: false, constraint: 'Check git remotes with ls-remote (slow — makes network calls).' },
|
|
506
421
|
],
|
|
507
422
|
output: [
|
|
508
|
-
{ name: 'checks', type: 'object[]', required: true, constraint: 'Each: {scope, name, status, message, fixed?, remediation?}. status: pass | fail. scope may be user | project | canvas. Runtime checks validate selected and process generation manifests, sealing/containment, and full SHA-256 inventories. terminal:nerd-font (user scope) reports whether a Nerd Font is installed for the viewer\'s icon glyphs; its remediation is always a human_action carrying the platform install command, never auto-fixed.
|
|
423
|
+
{ name: 'checks', type: 'object[]', required: true, constraint: 'Each: {scope, name, status, message, fixed?, remediation?}. status: pass | fail. scope may be user | project | canvas. Runtime checks validate selected and process generation manifests, sealing/containment, and full SHA-256 inventories. terminal:nerd-font (user scope) reports whether a Nerd Font is installed for the viewer\'s icon glyphs; its remediation is always a human_action carrying the platform install command, never auto-fixed. Plugin manifest checks appear as plugin:<name>:commands (validated statically, never executed); their remediation is always a human_action (disable/update/remove the plugin). remediation (when present) is {kind, description, ...payload} where kind is remove_config_key | rm_path | human_action; fault human_action remediations include the canonical logs command. Sorted by scope then name.' },
|
|
509
424
|
{ name: 'ok', type: 'boolean', required: true, constraint: 'True when no unresolved fail checks remain.' },
|
|
510
425
|
],
|
|
511
426
|
outputKind: 'object',
|
|
512
427
|
effects: [
|
|
513
|
-
'Read-only unless --fix is passed. Runtime verification reads and hashes every selected/process generation file;
|
|
514
|
-
'With --fix: applies each non-pass check\'s `remediation` — removes stale config entries and deletes dangling plugin/marketplace directories; canvas fault findings and
|
|
428
|
+
'Read-only unless --fix is passed. Runtime verification reads and hashes every selected/process generation file; plugin command manifests are validated statically — the plugin executable is never run, at any time.',
|
|
429
|
+
'With --fix: applies each non-pass check\'s `remediation` — removes stale config entries and deletes dangling plugin/marketplace directories; canvas fault findings and plugin command findings stay read-only (--fix never chmods or rewrites third-party plugin content).',
|
|
515
430
|
'Each non-pass result carries a structured `remediation` describing the fix action (absolute paths, exact config keys, or a human_action with its command) so callers can apply it directly without --fix.',
|
|
516
431
|
],
|
|
517
432
|
},
|
|
@@ -528,7 +443,6 @@ export const sysDoctorLeaf = defineLeaf({
|
|
|
528
443
|
if (scopes.includes('user'))
|
|
529
444
|
allResults.push(runNerdFontCheck());
|
|
530
445
|
allResults.push(...runCommandPluginChecks(scopes));
|
|
531
|
-
allResults.push(...runConfiguredCliChecks(scopes));
|
|
532
446
|
allResults.push(...await runFaultChecks());
|
|
533
447
|
const processLocation = processGeneration();
|
|
534
448
|
allResults.push(...await generationChecks('selected', selectedGeneration(), processLocation === null));
|
|
@@ -63,8 +63,8 @@ export declare class SetupWizard implements Component, Focusable {
|
|
|
63
63
|
private keyNotice;
|
|
64
64
|
private brokerLimitIndex;
|
|
65
65
|
private brokerLimitNotice;
|
|
66
|
-
private
|
|
67
|
-
private
|
|
66
|
+
private settingsIndex;
|
|
67
|
+
private settingsNotice;
|
|
68
68
|
private cachedWidth;
|
|
69
69
|
private cachedHeight;
|
|
70
70
|
private cachedLines;
|
|
@@ -77,14 +77,12 @@ export declare class SetupWizard implements Component, Focusable {
|
|
|
77
77
|
invalidate(): void;
|
|
78
78
|
private setTab;
|
|
79
79
|
private moveTab;
|
|
80
|
-
private
|
|
81
|
-
/** Advance the selected
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
private cycleDisplaySetting;
|
|
87
|
-
private handleDisplayInput;
|
|
80
|
+
private moveSettingsRow;
|
|
81
|
+
/** Advance the selected preference and persist it immediately to user config.
|
|
82
|
+
* The shared descriptor owns cycling, rendering, validation, and applicability,
|
|
83
|
+
* so setup and `sys config set` cannot drift onto different setting catalogs. */
|
|
84
|
+
private cycleSetting;
|
|
85
|
+
private handleSettingsInput;
|
|
88
86
|
private cancel;
|
|
89
87
|
dispose(): void;
|
|
90
88
|
private matches;
|
|
@@ -141,7 +139,7 @@ export declare class SetupWizard implements Component, Focusable {
|
|
|
141
139
|
private renderKeybindings;
|
|
142
140
|
private renderModels;
|
|
143
141
|
private renderBrokerLimits;
|
|
144
|
-
private
|
|
142
|
+
private renderSettings;
|
|
145
143
|
render(width: number): string[];
|
|
146
144
|
}
|
|
147
145
|
export declare function runSetup(): Promise<void>;
|
|
@@ -10,10 +10,10 @@ import { basename, dirname, join } from 'node:path';
|
|
|
10
10
|
import { applyTheme, defaultAgentDir } from '../../clients/attach/config.js';
|
|
11
11
|
import { ANTHROPIC_PROVIDER_ID, OPENAI_CODEX_PROVIDER_ID, isManagedProvider, promoteSubscription, readSubscriptionPool, renameSubscription, } from '../../core/subscription-state.js';
|
|
12
12
|
import { removeProviderRow } from '../../core/provider-management.js';
|
|
13
|
-
import { readConfig, readRawConfigAtRoot, readRawScopeConfig, updateRawConfigAtomically, persistDefaultKindModel, persistModelLadders, readMergedLaunchConfig } from '../../core/config.js';
|
|
13
|
+
import { readConfig, readRawConfigAtRoot, readRawScopeConfig, updateConfig, updateRawConfigAtomically, persistDefaultKindModel, persistModelLadders, readMergedLaunchConfig } from '../../core/config.js';
|
|
14
14
|
import { findProjectScopeRoots, userScopeRoot } from '../../core/scope.js';
|
|
15
15
|
import { loadProfileManifest, profileRoot } from '../../core/profiles/manifest.js';
|
|
16
|
-
import {
|
|
16
|
+
import { USER_SETTINGS } from '../../core/user-settings.js';
|
|
17
17
|
import { BINDING_CATALOG, formatBinding, formatGesture, matchesPiTuiInput, persistUserKeybindings, resolveKeybindings, KeybindingConcurrentEditError, KeybindingValidationError, } from '../../core/keybindings/index.js';
|
|
18
18
|
import { configuredLadderProviders, inferPortableModelSelection } from '../../core/runtime/model-selection.js';
|
|
19
19
|
import { cliClient } from '../api-client.js';
|
|
@@ -657,45 +657,8 @@ class ProviderPanel {
|
|
|
657
657
|
return useFrame ? framed(body, width) : body.map((line) => truncateToWidth(line, width));
|
|
658
658
|
}
|
|
659
659
|
}
|
|
660
|
-
const TAB_LABELS = ['Providers', 'Plugins', 'System', 'Exa', 'Keybindings', 'Models', 'Broker limits', '
|
|
661
|
-
const
|
|
662
|
-
/** The viewer display settings the Display tab edits, in row order. Each row owns
|
|
663
|
-
* its own value cycling and its own formatting, so adding a setting here is the
|
|
664
|
-
* whole change — the tab's input and render loops stay generic. Every one is a
|
|
665
|
-
* user-scope `ScopeConfig` key persisted the moment it changes. */
|
|
666
|
-
const DISPLAY_ROWS = [
|
|
667
|
-
{
|
|
668
|
-
key: 'fold_finished_tools',
|
|
669
|
-
label: 'Fold finished tools',
|
|
670
|
-
description: 'Fold every settled tool call to its call line (path included). Toggle live with /fold-tools.',
|
|
671
|
-
format: (cfg) => (cfg.fold_finished_tools ? 'on' : 'off'),
|
|
672
|
-
next: (cfg) => ({ fold_finished_tools: !cfg.fold_finished_tools }),
|
|
673
|
-
},
|
|
674
|
-
{
|
|
675
|
-
key: 'completion_bell',
|
|
676
|
-
label: 'Completion bell',
|
|
677
|
-
description: 'Ring a short sound in the viewer when the node you are watching finishes a turn.',
|
|
678
|
-
format: (cfg) => (cfg.completion_bell ? 'on' : 'off'),
|
|
679
|
-
next: (cfg) => ({ completion_bell: !cfg.completion_bell }),
|
|
680
|
-
},
|
|
681
|
-
{
|
|
682
|
-
key: 'live_cycles',
|
|
683
|
-
label: 'Live cycles',
|
|
684
|
-
description: 'Trailing refresh cycles rendered at full fidelity; older cycles condense to text. Default 2.',
|
|
685
|
-
format: (cfg) => String(cfg.live_cycles),
|
|
686
|
-
next: (cfg, delta) => ({ live_cycles: ((cfg.live_cycles - 1 + delta + 6) % 6) + 1 }),
|
|
687
|
-
},
|
|
688
|
-
{
|
|
689
|
-
key: 'condensed_history',
|
|
690
|
-
label: 'Condensed history',
|
|
691
|
-
description: 'Which messages of a condensed cycle still render. Tool calls are never kept.',
|
|
692
|
-
format: (cfg) => cfg.condensed_history,
|
|
693
|
-
next: (cfg, delta) => ({
|
|
694
|
-
condensed_history: CONDENSED_HISTORY_MODES[(CONDENSED_HISTORY_MODES.indexOf(cfg.condensed_history) + delta + CONDENSED_HISTORY_MODES.length)
|
|
695
|
-
% CONDENSED_HISTORY_MODES.length],
|
|
696
|
-
}),
|
|
697
|
-
},
|
|
698
|
-
];
|
|
660
|
+
const TAB_LABELS = ['Providers', 'Plugins', 'System', 'Exa', 'Keybindings', 'Models', 'Broker limits', 'Settings'];
|
|
661
|
+
const SETTINGS_TAB = 7;
|
|
699
662
|
const STRENGTHS = ['ultra', 'strong', 'medium', 'light'];
|
|
700
663
|
const LADDER_PROVIDER_KEYS = ['anthropic', 'openai'];
|
|
701
664
|
const THINKING_LEVELS = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
|
|
@@ -1304,20 +1267,21 @@ function buildSetupKeybindingRows() {
|
|
|
1304
1267
|
}
|
|
1305
1268
|
const members = [definition, paired];
|
|
1306
1269
|
const menu = members.find((member) => member.pair?.field === 'menu');
|
|
1307
|
-
const
|
|
1308
|
-
if (menu === undefined ||
|
|
1270
|
+
const second = members.find((member) => member.pair?.field !== 'menu');
|
|
1271
|
+
if (menu === undefined || second === undefined)
|
|
1309
1272
|
throw new Error(`incomplete keybinding pair for ${definition.id}`);
|
|
1273
|
+
const secondLabel = second.pair?.field === 'global' ? 'Global' : 'Direct';
|
|
1310
1274
|
rows.push({
|
|
1311
1275
|
group: menu.group,
|
|
1312
1276
|
name: menu.name,
|
|
1313
1277
|
description: menu.description,
|
|
1314
1278
|
fields: [
|
|
1315
1279
|
{ label: 'Menu', definition: menu },
|
|
1316
|
-
{ label:
|
|
1280
|
+
{ label: secondLabel, definition: second },
|
|
1317
1281
|
],
|
|
1318
1282
|
});
|
|
1319
1283
|
consumed.add(menu.id);
|
|
1320
|
-
consumed.add(
|
|
1284
|
+
consumed.add(second.id);
|
|
1321
1285
|
}
|
|
1322
1286
|
return rows;
|
|
1323
1287
|
}
|
|
@@ -1354,8 +1318,8 @@ export class SetupWizard {
|
|
|
1354
1318
|
keyNotice;
|
|
1355
1319
|
brokerLimitIndex = 0;
|
|
1356
1320
|
brokerLimitNotice;
|
|
1357
|
-
|
|
1358
|
-
|
|
1321
|
+
settingsIndex = 0;
|
|
1322
|
+
settingsNotice;
|
|
1359
1323
|
cachedWidth;
|
|
1360
1324
|
cachedHeight;
|
|
1361
1325
|
cachedLines;
|
|
@@ -1430,33 +1394,29 @@ export class SetupWizard {
|
|
|
1430
1394
|
const next = ((this.activeTab + delta + TAB_LABELS.length) % TAB_LABELS.length);
|
|
1431
1395
|
this.setTab(next);
|
|
1432
1396
|
}
|
|
1433
|
-
|
|
1434
|
-
this.
|
|
1435
|
-
this.
|
|
1397
|
+
moveSettingsRow(delta) {
|
|
1398
|
+
this.settingsIndex = (this.settingsIndex + delta + USER_SETTINGS.length) % USER_SETTINGS.length;
|
|
1399
|
+
this.settingsNotice = undefined;
|
|
1436
1400
|
this.invalidate();
|
|
1437
1401
|
}
|
|
1438
|
-
/** Advance the selected
|
|
1439
|
-
*
|
|
1440
|
-
*
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
const current = readConfig('user');
|
|
1446
|
-
const next = row.next(current, delta);
|
|
1447
|
-
updateRawConfigAtomically('user', (latest) => ({ ...latest, ...next }));
|
|
1448
|
-
this.displayNotice = `Saved ${row.key} = ${row.format({ ...current, ...next })}. Applies on the next attach.`;
|
|
1402
|
+
/** Advance the selected preference and persist it immediately to user config.
|
|
1403
|
+
* The shared descriptor owns cycling, rendering, validation, and applicability,
|
|
1404
|
+
* so setup and `sys config set` cannot drift onto different setting catalogs. */
|
|
1405
|
+
cycleSetting(delta) {
|
|
1406
|
+
const setting = USER_SETTINGS[this.settingsIndex];
|
|
1407
|
+
const config = updateConfig('user', (current) => setting.cycle(current, delta));
|
|
1408
|
+
this.settingsNotice = `Saved ${setting.key} = ${setting.format(config)}. ${setting.applies}`;
|
|
1449
1409
|
this.invalidate();
|
|
1450
1410
|
}
|
|
1451
|
-
|
|
1411
|
+
handleSettingsInput(data) {
|
|
1452
1412
|
if (this.matches('crtr.setup.list-down', data))
|
|
1453
|
-
this.
|
|
1413
|
+
this.moveSettingsRow(1);
|
|
1454
1414
|
else if (this.matches('crtr.setup.list-up', data))
|
|
1455
|
-
this.
|
|
1415
|
+
this.moveSettingsRow(-1);
|
|
1456
1416
|
else if (this.matches('crtr.setup.toggle', data) || this.matches('crtr.setup.cell-next', data))
|
|
1457
|
-
this.
|
|
1417
|
+
this.cycleSetting(1);
|
|
1458
1418
|
else if (this.matches('crtr.setup.cell-previous', data))
|
|
1459
|
-
this.
|
|
1419
|
+
this.cycleSetting(-1);
|
|
1460
1420
|
else if (this.matches('crtr.setup.advance', data))
|
|
1461
1421
|
this.moveTab(1);
|
|
1462
1422
|
}
|
|
@@ -2049,8 +2009,8 @@ export class SetupWizard {
|
|
|
2049
2009
|
this.invalidate();
|
|
2050
2010
|
return;
|
|
2051
2011
|
}
|
|
2052
|
-
if (this.activeTab ===
|
|
2053
|
-
this.
|
|
2012
|
+
if (this.activeTab === SETTINGS_TAB) {
|
|
2013
|
+
this.handleSettingsInput(data);
|
|
2054
2014
|
return;
|
|
2055
2015
|
}
|
|
2056
2016
|
this.handleBrokerLimitsInput(data);
|
|
@@ -2077,7 +2037,7 @@ export class SetupWizard {
|
|
|
2077
2037
|
: `${this.label('crtr.setup.list-down')}/${this.label('crtr.setup.list-up')} row · ${this.label('crtr.setup.cell-previous')}/${this.label('crtr.setup.cell-next')} column · ${this.label('crtr.setup.models.scope-next')} scope · ${this.label('crtr.setup.models.open')} edit · ${this.label('crtr.setup.tab-next')} next · ${this.label('crtr.setup.cancel')} cancel`;
|
|
2078
2038
|
return [truncateToWidth(theme.fg('dim', hint), width)];
|
|
2079
2039
|
}
|
|
2080
|
-
if (this.activeTab ===
|
|
2040
|
+
if (this.activeTab === SETTINGS_TAB) {
|
|
2081
2041
|
return [truncateToWidth(theme.fg('dim', `${this.label('crtr.setup.list-down')}/${this.label('crtr.setup.list-up')} row · ${this.label('crtr.setup.toggle')} or ${this.label('crtr.setup.cell-next')}/${this.label('crtr.setup.cell-previous')} change value · ${tabs} · ${this.label('crtr.setup.cancel')} cancel`), width)];
|
|
2082
2042
|
}
|
|
2083
2043
|
if (this.activeTab === 6) {
|
|
@@ -2406,22 +2366,28 @@ export class SetupWizard {
|
|
|
2406
2366
|
}
|
|
2407
2367
|
return out;
|
|
2408
2368
|
}
|
|
2409
|
-
|
|
2369
|
+
renderSettings(width) {
|
|
2410
2370
|
const out = [];
|
|
2411
|
-
const
|
|
2412
|
-
|
|
2413
|
-
|
|
2371
|
+
const config = readConfig('user');
|
|
2372
|
+
const capacity = Math.max(1, Math.floor((this.terminalRows() - 13) / 2));
|
|
2373
|
+
const range = visibleRange(USER_SETTINGS.length, this.settingsIndex, capacity);
|
|
2374
|
+
out.push(truncateToWidth(`${theme.fg('accent', theme.bold('Settings'))}${theme.fg('dim', ' user scope')}`, width));
|
|
2375
|
+
out.push(truncateToWidth(theme.fg('dim', 'General preferences live here; specialized settings keep their own tabs. Changes save immediately.'), width));
|
|
2414
2376
|
out.push('');
|
|
2415
|
-
for (
|
|
2416
|
-
const
|
|
2377
|
+
for (let index = range.start; index < range.end; index += 1) {
|
|
2378
|
+
const setting = USER_SETTINGS[index];
|
|
2379
|
+
const selected = index === this.settingsIndex;
|
|
2417
2380
|
const marker = selected ? theme.fg('accent', '▶') : ' ';
|
|
2418
|
-
const name = selected ? theme.fg('accent',
|
|
2419
|
-
out.push(truncateToWidth(`${marker} ${padAnsi(name, 24)} ${theme.fg('success',
|
|
2420
|
-
out.push(truncateToWidth(theme.fg('dim', ` ${
|
|
2381
|
+
const name = selected ? theme.fg('accent', setting.label) : setting.label;
|
|
2382
|
+
out.push(truncateToWidth(`${marker} ${padAnsi(name, 24)} ${theme.fg('success', setting.format(config))}`, width));
|
|
2383
|
+
out.push(truncateToWidth(theme.fg('dim', ` ${setting.description}`), width));
|
|
2384
|
+
}
|
|
2385
|
+
if (range.start > 0 || range.end < USER_SETTINGS.length) {
|
|
2386
|
+
out.push(theme.fg('dim', `(${this.settingsIndex + 1}/${USER_SETTINGS.length})`));
|
|
2421
2387
|
}
|
|
2422
|
-
if (this.
|
|
2388
|
+
if (this.settingsNotice) {
|
|
2423
2389
|
out.push('');
|
|
2424
|
-
for (const line of wrapText(this.
|
|
2390
|
+
for (const line of wrapText(this.settingsNotice, width))
|
|
2425
2391
|
out.push(theme.fg('success', line));
|
|
2426
2392
|
}
|
|
2427
2393
|
return out;
|
|
@@ -2450,7 +2416,7 @@ export class SetupWizard {
|
|
|
2450
2416
|
? this.renderModels(width)
|
|
2451
2417
|
: this.activeTab === 6
|
|
2452
2418
|
? this.renderBrokerLimits(width)
|
|
2453
|
-
: this.
|
|
2419
|
+
: this.renderSettings(width)),
|
|
2454
2420
|
'',
|
|
2455
2421
|
...this.renderFooter(width),
|
|
2456
2422
|
];
|
|
@@ -22,39 +22,36 @@ test('base sub-personas receive hands-on guidance instead of universal delegatio
|
|
|
22
22
|
assert.doesNotMatch(orchestrator, /You are a hands-on worker/);
|
|
23
23
|
assert.match(orchestrator, /You are an orchestrator/);
|
|
24
24
|
});
|
|
25
|
-
test('developer orchestrators
|
|
25
|
+
test('developer orchestrators receive the development front door and acceptance bar', () => {
|
|
26
26
|
const prompt = renderPreferencesForSubject(subject('orchestrator', 'developer')).block;
|
|
27
|
-
assert.match(prompt, /
|
|
28
|
-
assert.match(prompt, /
|
|
29
|
-
assert.
|
|
30
|
-
assert.match(prompt, /absolute artifact path plus every relevant report or context path/);
|
|
31
|
-
assert.doesNotMatch(prompt, /first wave explore-only|before launching downstream advisor, spec, design, plan, developer, and review children|Wait for every explore report/);
|
|
27
|
+
assert.match(prompt, /Before you shape a software roadmap, read `crtr memory read development` for development styles, roadmap shapes, and exit criteria that fit the goal's risk/);
|
|
28
|
+
assert.match(prompt, /Treat implementation as complete only when it is \*\*provably correct against the spec's acceptance criteria\*\*, not merely when it compiles/);
|
|
29
|
+
assert.doesNotMatch(prompt, /When a downstream decision depends on unknown source facts|Run the remaining delegation pipeline|Give each substantive implementation batch one independent review assignment/);
|
|
32
30
|
});
|
|
33
|
-
test('review
|
|
34
|
-
const
|
|
35
|
-
assert.match(developer, /one independent review assignment/);
|
|
36
|
-
assert.match(developer, /Validation means executing acceptance criteria.*?evidence, not review/s);
|
|
37
|
-
assert.doesNotMatch(developer, /Hand each lens to its own reviewer|idle capacity is a wasted wake|When in doubt, more rigor/);
|
|
31
|
+
test('review and plan orchestrators use shared review disposition rules', () => {
|
|
32
|
+
const sharedReviewRule = /substantive logic gets one independent review pass;.*?Its verdict is settled: fix or dismiss its findings, then validate the changed behavior rather than asking any reviewer for another opinion or PASS/s;
|
|
38
33
|
const reviewOrchestrator = renderPreferencesForSubject(subject('orchestrator', 'review')).block;
|
|
39
34
|
assert.match(reviewOrchestrator, /Choose the one decomposition axis/);
|
|
40
35
|
assert.match(reviewOrchestrator, /at most five base review children.*?in one wave/s);
|
|
41
|
-
assert.match(reviewOrchestrator,
|
|
42
|
-
assert.
|
|
36
|
+
assert.match(reviewOrchestrator, sharedReviewRule);
|
|
37
|
+
assert.match(reviewOrchestrator, /You own synthesis and evidence reconciliation.*?The owner disposes findings and validates changed behavior/s);
|
|
38
|
+
assert.doesNotMatch(reviewOrchestrator, /Decompose the surface two ways|Never delegate synthesis, adjudication, or validation of the synthesis/);
|
|
43
39
|
const reviewWorker = renderPreferencesForSubject(subject('base', 'review')).block;
|
|
44
40
|
assert.match(reviewWorker, /A slice delegated by another reviewer remains base/);
|
|
45
41
|
const planner = renderPreferencesForSubject(subject('orchestrator', 'plan')).block;
|
|
46
|
-
assert.match(planner,
|
|
47
|
-
assert.doesNotMatch(planner, /
|
|
42
|
+
assert.match(planner, sharedReviewRule);
|
|
43
|
+
assert.doesNotMatch(planner, /Give a consequential synthesized plan one independent review pass|requirements-coverage, pattern-consistency, code-smells, security, and architecture-fit lenses/);
|
|
48
44
|
});
|
|
49
|
-
test('explore personas
|
|
45
|
+
test('explore personas preserve complete evidence without identity or tool manuals', () => {
|
|
50
46
|
const base = renderPreferencesForSubject(subject('base', 'explore')).block;
|
|
51
|
-
assert.match(base, /
|
|
47
|
+
assert.match(base, /Your work is \*\*read-only evidence gathering\*\* — map what exists, where it lives, how it behaves, and which constraints, gaps, or feasibility limits the source proves/);
|
|
52
48
|
assert.match(base, /Root cause and recommendations belong to `advisor`, target architecture to `design`, required behavior and acceptance criteria to `spec`, and implementation decomposition to `plan`/);
|
|
53
49
|
assert.match(base, /A task cannot expand your role: even when it explicitly asks, \*\*never\*\* produce those decisions/);
|
|
54
|
-
assert.match(base, /
|
|
55
|
-
assert.
|
|
50
|
+
assert.match(base, /Done is the \*\*requested factual surface fully mapped\*\* with evidence, not a plausible partial sketch/);
|
|
51
|
+
assert.match(base, /complete findings.*?exact files and line numbers.*?self-contained/s);
|
|
52
|
+
assert.doesNotMatch(base, /You are a fast current-state codebase scout|Use grep, find, and file reads/);
|
|
56
53
|
const orchestrator = renderPreferencesForSubject(subject('orchestrator', 'explore')).block;
|
|
57
|
-
assert.match(orchestrator, /
|
|
58
|
-
assert.match(orchestrator, /
|
|
54
|
+
assert.match(orchestrator, /even when it explicitly asks for diagnosis or a target-state decision, gather only the facts that decision needs and return the unperformed handoff to the matching specialist/);
|
|
55
|
+
assert.match(orchestrator, /The map is complete only when every factual sub-question is answered/);
|
|
59
56
|
assert.match(orchestrator, /factual synthesis.*not a pile of transcripts or a proposed solution/);
|
|
60
57
|
});
|
|
@@ -33,7 +33,6 @@ process.stdout.write(JSON.stringify({ protocolVersion: 1, ok: true, result: { ap
|
|
|
33
33
|
function validManifest() {
|
|
34
34
|
return {
|
|
35
35
|
schemaVersion: 1,
|
|
36
|
-
executable: 'bin/cmd.js',
|
|
37
36
|
mounts: [
|
|
38
37
|
{
|
|
39
38
|
parent: [],
|
|
@@ -67,12 +66,27 @@ function validManifest() {
|
|
|
67
66
|
};
|
|
68
67
|
}
|
|
69
68
|
const CASES = [
|
|
70
|
-
|
|
69
|
+
{ name: 'escaping executable path', code: 'command_path_unsafe', executable: '../../../../bin/sh' },
|
|
71
70
|
{ name: 'non-executable file', code: 'command_not_executable', execBit: false },
|
|
72
71
|
(() => { const m = validManifest(); m['schemaVersion'] = 2; return { name: 'unknown schemaVersion', code: 'command_schema_version', manifest: m }; })(),
|
|
73
72
|
(() => { const m = validManifest(); m['requires'] = { crtr: '>=1' }; return { name: 'unknown top-level key', code: 'command_manifest_invalid', manifest: m }; })(),
|
|
74
|
-
(() => { const m = validManifest(); m['mounts'][0].parent = ['sys']; return { name: 'non-empty parent', code: 'command_parent_invalid', manifest: m }; })(),
|
|
75
73
|
];
|
|
74
|
+
function nestedManifest() {
|
|
75
|
+
const manifest = validManifest();
|
|
76
|
+
const mounts = manifest['mounts'];
|
|
77
|
+
const topLevel = mounts[0];
|
|
78
|
+
const child = topLevel.node['children'][0];
|
|
79
|
+
topLevel.node['children'] = [{
|
|
80
|
+
kind: 'branch',
|
|
81
|
+
name: 'tools',
|
|
82
|
+
description: 'application tools',
|
|
83
|
+
whenToUse: 'inspect application tools',
|
|
84
|
+
summary: 'application tools',
|
|
85
|
+
children: [],
|
|
86
|
+
}];
|
|
87
|
+
mounts.push({ parent: ['app', 'tools'], node: { ...child, name: 'status' } });
|
|
88
|
+
return manifest;
|
|
89
|
+
}
|
|
76
90
|
// ---------------------------------------------------------------------------
|
|
77
91
|
// Isolated home + cwd
|
|
78
92
|
// ---------------------------------------------------------------------------
|
|
@@ -155,7 +169,13 @@ function writePlugin(dir, name, c) {
|
|
|
155
169
|
const root = join(dir, name);
|
|
156
170
|
mkdirSync(join(root, '.crouter-plugin'), { recursive: true });
|
|
157
171
|
mkdirSync(join(root, 'bin'), { recursive: true });
|
|
158
|
-
writeFileSync(join(root, '.crouter-plugin', 'plugin.json'), JSON.stringify({
|
|
172
|
+
writeFileSync(join(root, '.crouter-plugin', 'plugin.json'), JSON.stringify({
|
|
173
|
+
name,
|
|
174
|
+
version: '0.1.0',
|
|
175
|
+
description: 'fixture',
|
|
176
|
+
commands: 'commands.json',
|
|
177
|
+
transport: { kind: 'exec', executable: c.executable ?? 'bin/cmd.js' },
|
|
178
|
+
}));
|
|
159
179
|
writeFileSync(join(root, 'commands.json'), JSON.stringify(c.manifest ?? validManifest()));
|
|
160
180
|
const execPath = join(root, 'bin', 'cmd.js');
|
|
161
181
|
writeFileSync(execPath, SENTINEL_EXEC);
|
|
@@ -189,9 +209,23 @@ describe('pkg plugin show surfaces command validation', () => {
|
|
|
189
209
|
resetScopeCache();
|
|
190
210
|
const out = await pluginShow.run({ name: 'p' });
|
|
191
211
|
const commands = out['commands'];
|
|
212
|
+
const manifest = out['manifest'];
|
|
213
|
+
assert.deepEqual(commands.mounts, ['app']);
|
|
214
|
+
assert.equal(commands.issues.length, 0);
|
|
215
|
+
assert.deepEqual(manifest.transport, { kind: 'exec', executable: 'bin/cmd.js' }, 'the executable belongs to plugin.json transport, not commands.json');
|
|
216
|
+
assertNotExecuted();
|
|
217
|
+
});
|
|
218
|
+
test('a nested mount composes into an empty exec branch instead of being rejected', async () => {
|
|
219
|
+
writePlugin(join(userRoot, 'plugins'), 'p', { name: 'nested mount', code: '', manifest: nestedManifest() });
|
|
220
|
+
resetScopeCache();
|
|
221
|
+
const out = await pluginShow.run({ name: 'p' });
|
|
222
|
+
const commands = out['commands'];
|
|
192
223
|
assert.deepEqual(commands.mounts, ['app']);
|
|
193
224
|
assert.equal(commands.issues.length, 0);
|
|
194
|
-
|
|
225
|
+
const snapshot = discoverCommandContributions(new Set(), emptyStart);
|
|
226
|
+
const tools = snapshot.contributions[0].node.children.find((node) => node.name === 'tools');
|
|
227
|
+
assert.equal(tools.kind, 'branch');
|
|
228
|
+
assert.deepEqual(tools.children.map((node) => node.name), ['status']);
|
|
195
229
|
assertNotExecuted();
|
|
196
230
|
});
|
|
197
231
|
});
|