@cspeach/cli 0.8.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/agent/intent-system-prompt.js +1 -1
- package/dist/agent/loop.js +330 -36
- package/dist/agent/providers/license-gate.js +44 -0
- package/dist/agent/skill-checkpoint.js +1 -1
- package/dist/agent/steering-queue.js +27 -0
- package/dist/agent/tool-dispatch.js +15 -0
- package/dist/approvals/canonical.js +91 -0
- package/dist/approvals/jwt.js +84 -7
- package/dist/approvals/render.js +38 -0
- package/dist/auth/org-anthropic-key.js +25 -0
- package/dist/classifier/client.js +24 -5
- package/dist/commands/config-set.js +95 -0
- package/dist/commands/login.js +31 -14
- package/dist/commands/plan-model-tier.js +83 -0
- package/dist/commands/plan-resume.js +435 -0
- package/dist/config/loader.js +102 -3
- package/dist/cost/pricing.js +14 -5
- package/dist/doctor/checks/_http-probe.js +1 -0
- package/dist/doctor/checks/cert.js +14 -3
- package/dist/doctor/checks/sap.js +30 -8
- package/dist/doctor/checks/zcspeach.js +19 -4
- package/dist/one-shot.js +52 -4
- package/dist/projects/answer-blockers.js +137 -0
- package/dist/projects/email-template.js +2 -0
- package/dist/projects/extract-cca.js +108 -16
- package/dist/projects/extract-modernize.js +1 -1
- package/dist/projects/extract-plan.js +178 -0
- package/dist/projects/extract-spec-gap.js +34 -7
- package/dist/projects/extract-test-coverage.js +1 -1
- package/dist/projects/extract-upgrade.js +113 -22
- package/dist/projects/index.js +7 -1
- package/dist/projects/merge-cca.js +292 -0
- package/dist/projects/merge-upgrade.js +173 -0
- package/dist/projects/migration.js +103 -1
- package/dist/projects/output-paths.js +27 -0
- package/dist/projects/plan-run.js +254 -0
- package/dist/projects/plan-schema.js +210 -0
- package/dist/projects/promote-command.js +26 -2
- package/dist/projects/promote.js +128 -0
- package/dist/projects/save-command.js +263 -20
- package/dist/projects/status.js +22 -0
- package/dist/projects/validate.js +2 -0
- package/dist/projects/workspace.js +164 -20
- package/dist/renderer/notices.js +64 -0
- package/dist/renderer/progress-chatter.js +8 -0
- package/dist/renderer/syntax.js +16 -1
- package/dist/renderer/thinking-heartbeat.js +13 -1
- package/dist/renderer/tool-widget.js +18 -4
- package/dist/renderer/tty.js +43 -4
- package/dist/renderer/verify-chain.js +77 -0
- package/dist/repl/at-picker.js +93 -21
- package/dist/repl/builtin-commands.js +37 -0
- package/dist/repl/early-line-buffer.js +68 -0
- package/dist/repl/inquirer-guard.js +70 -5
- package/dist/repl/numbered-menu.js +131 -0
- package/dist/repl/post-turn-status.js +2 -2
- package/dist/repl/rule8-detector.js +17 -2
- package/dist/repl/safety-confirm.js +111 -2
- package/dist/repl/safety-mode-state.js +19 -3
- package/dist/repl/slash-picker.js +25 -19
- package/dist/repl.js +470 -22
- package/dist/router/classifier.js +150 -6
- package/dist/sap/capability-matrix.js +20 -0
- package/dist/sap/capability-matrix.json +11236 -0
- package/dist/sap/capability.js +146 -0
- package/dist/sap/connection-manager.js +19 -1
- package/dist/sap/onboarding.js +42 -4
- package/dist/session/pending.js +27 -0
- package/dist/skill-catalog.js +54 -43
- package/dist/skills/bundled-skills.js +279 -1
- package/dist/skills/promotion-dispatch.js +23 -0
- package/dist/tools/_command-shared.js +36 -12
- package/dist/tools/_filesystem-shared.js +139 -4
- package/dist/tools/_flag.js +25 -0
- package/dist/tools/approval.js +64 -21
- package/dist/tools/ask-question.js +96 -4
- package/dist/tools/capability/tool.js +74 -0
- package/dist/tools/dispatch-skill.js +22 -1
- package/dist/tools/extend-model/anchored-insert.js +810 -0
- package/dist/tools/extend-model/tool.js +188 -0
- package/dist/tools/filesystem/extract-document.js +57 -0
- package/dist/tools/filesystem/file-edit.js +12 -2
- package/dist/tools/filesystem/file-read.js +2 -2
- package/dist/tools/filesystem/file-write.js +11 -2
- package/dist/tools/filesystem/glob.js +11 -0
- package/dist/tools/filesystem/grep.js +10 -0
- package/dist/tools/filesystem/read-document.js +107 -0
- package/dist/tools/fiori/apply.js +50 -0
- package/dist/tools/fiori/bin.js +3 -0
- package/dist/tools/fiori/catalog/index.js +27 -0
- package/dist/tools/fiori/catalog/value-help.js +230 -0
- package/dist/tools/fiori/catalog/viz-chart.js +177 -0
- package/dist/tools/fiori/cli.js +71 -0
- package/dist/tools/fiori/deploy-config.js +73 -0
- package/dist/tools/fiori/fe-scaffold.js +45 -0
- package/dist/tools/fiori/i18n.js +39 -0
- package/dist/tools/fiori/manifest.js +70 -0
- package/dist/tools/fiori/render.js +77 -0
- package/dist/tools/fiori/scaffold.js +39 -0
- package/dist/tools/fiori/tools.js +356 -0
- package/dist/tools/fiori/types.js +1 -0
- package/dist/tools/local-build.js +76 -0
- package/dist/tools/local-files.js +31 -0
- package/dist/tools/project/_merge-shared.js +68 -0
- package/dist/tools/project/cca_merge.js +164 -0
- package/dist/tools/project/playbook_get.js +1 -1
- package/dist/tools/project/upgrade_merge_progress.js +206 -0
- package/dist/tools/sap-read.js +53 -9
- package/dist/tools/sap-write.js +530 -21
- package/dist/tools/shell/shell_exec.js +41 -6
- package/dist/tools/snapshot.js +37 -14
- package/dist/tools/subagent/background_run.js +17 -1
- package/dist/tools/transport-resolution.js +86 -0
- package/dist/tools/transport.js +224 -5
- package/dist/tools/write-mode.js +4 -0
- package/dist/ui/app.js +84 -11
- package/dist/ui/body.js +13 -0
- package/dist/ui/command-palette.js +46 -10
- package/dist/ui/file-palette.js +44 -0
- package/dist/ui/footer.js +28 -11
- package/dist/ui/line-resolution.js +92 -0
- package/dist/ui/session-timeline.js +1 -0
- package/dist/ui/text-input.js +150 -0
- package/dist/ui/turn-status-emitter.js +52 -0
- package/dist/ui/turn-status.js +59 -0
- package/dist/ui/widgets/ask-question-modal.js +30 -2
- package/package.json +23 -4
- package/bench/README.md +0 -78
- package/bench/prompts/abap-document-cds.md +0 -44
- package/bench/prompts/abap-explain-bdef-handler.md +0 -57
- package/bench/prompts/abap-test-method.md +0 -42
- package/bench/results/abap-document-cds/claude-haiku-4-5.md +0 -189
- package/bench/results/abap-document-cds/claude-opus-4-7.md +0 -120
- package/bench/results/abap-document-cds/claude-sonnet-4-6.md +0 -151
- package/bench/results/abap-explain-bdef-handler/claude-haiku-4-5.md +0 -112
- package/bench/results/abap-explain-bdef-handler/claude-opus-4-7.md +0 -101
- package/bench/results/abap-explain-bdef-handler/claude-sonnet-4-6.md +0 -101
- package/bench/results/abap-test-method/claude-haiku-4-5.md +0 -186
- package/bench/results/abap-test-method/claude-opus-4-7.md +0 -193
- package/bench/results/abap-test-method/claude-sonnet-4-6.md +0 -234
|
@@ -5,13 +5,21 @@
|
|
|
5
5
|
* surface, every rule-triggering operation gets a card showing which rule
|
|
6
6
|
* applies, what's about to happen, and what rollback is available.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Three presentation surfaces, decided per call (headless first):
|
|
9
|
+
* - headless → fail-fast decline, no prompt (B5)
|
|
10
|
+
* - Ink → askQuestionEmitter choice modal (UX-5 review I1) —
|
|
11
|
+
* inquirer must NEVER run under Ink: it fights the
|
|
12
|
+
* React frame for stdin and paints over it
|
|
13
|
+
* - classic → inquirer select via withInquirer (unchanged)
|
|
10
14
|
*/
|
|
11
15
|
import chalk from 'chalk';
|
|
12
16
|
import { select } from '@inquirer/prompts';
|
|
13
17
|
import { withInquirer } from './inquirer-guard.js';
|
|
14
18
|
import { inquirerTheme } from './inquirer-theme.js';
|
|
19
|
+
import { isHeadless, shouldUseInk } from '../renderer/tty.js';
|
|
20
|
+
import { askQuestionEmitter } from '../ui/ask-question-emitter.js';
|
|
21
|
+
import { clearActiveSpinner } from '../renderer/tool-widget.js';
|
|
22
|
+
import { turnStatusEmitter } from '../ui/turn-status-emitter.js';
|
|
15
23
|
const RULE_LABEL = {
|
|
16
24
|
RULE_7_SNAPSHOT_BEFORE_WRITE: 'RULE 7 — snapshot before write',
|
|
17
25
|
RULE_7A_USE_UPDATE_METHOD: 'RULE 7a — use sap_update_method (not sap_set_source)',
|
|
@@ -21,7 +29,100 @@ const RULE_LABEL = {
|
|
|
21
29
|
RULE_10_VERIFY_AFTER_WRITE: 'RULE 10 — verify after write',
|
|
22
30
|
};
|
|
23
31
|
export async function presentSafetyConfirmation(action) {
|
|
32
|
+
// B5 (2026-06-11) — headless fail-fast. This card is a stdin modal (the
|
|
33
|
+
// Rule 8 batch gate reaches it on the 2nd+ mutating call per turn even in
|
|
34
|
+
// one-shot, e.g. two local file writes); a headless run would hang on it
|
|
35
|
+
// forever. Headless runs NEVER auto-confirm safety gates — decline with a
|
|
36
|
+
// clear reason so the model can wrap up gracefully.
|
|
37
|
+
if (isHeadless()) {
|
|
38
|
+
console.error(chalk.red(`headless: ${RULE_LABEL[action.rule]} requires interactive confirmation — ` +
|
|
39
|
+
'declining (headless runs never auto-confirm safety gates; run interactively).'));
|
|
40
|
+
return {
|
|
41
|
+
confirmed: false,
|
|
42
|
+
reason: 'headless run cannot confirm safety gates — run interactively',
|
|
43
|
+
headless: true,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
24
46
|
const card = renderCard(action);
|
|
47
|
+
// UX-5 review I1 (2026-06-12) — Ink-native branch. /safety-mode on made
|
|
48
|
+
// this card reachable under Ink, where the raw inquirer select() below
|
|
49
|
+
// toggled raw mode out from under the React frame and painted over it.
|
|
50
|
+
// Mirror of renderPlanGate (approvals/render.ts): route through the
|
|
51
|
+
// askQuestionEmitter choice modal — one input system, no stdin handoff.
|
|
52
|
+
// The modal renders `context` itself in dimColor, so strip the chalk ANSI
|
|
53
|
+
// codes (they'd show as raw escape text). Same choice semantics as the
|
|
54
|
+
// classic path below: Apply/Cancel, plus the Rule 9 irreversibility ack.
|
|
55
|
+
// Esc / no listener resolve as cancelled → decline (safe default).
|
|
56
|
+
if (shouldUseInk()) {
|
|
57
|
+
// CRITICAL race fix (2026-06-13) — this gate fires from INSIDE
|
|
58
|
+
// dispatchTool, on the 2nd+ mutating op of a turn. For local-FS ops
|
|
59
|
+
// (file_write / shell_exec, the fiori-build path) the agent loop
|
|
60
|
+
// classifies the tool as NON-interactive: it starts the in-place tool
|
|
61
|
+
// spinner, a per-second labelled heartbeat, AND keeps the turn-status
|
|
62
|
+
// row ticking — none of which it pauses (only ask_question /
|
|
63
|
+
// request_approval get paused at the loop level). So when this modal
|
|
64
|
+
// opens, three render sources are still repainting the Ink frame
|
|
65
|
+
// underneath it. That produced the observed live bug: the RULE 8 card
|
|
66
|
+
// rendered DOUBLED, Enter was not consumed by the modal, the keystroke
|
|
67
|
+
// LEAKED to the outer prompt (history-replay of the slash command), and
|
|
68
|
+
// the turn wedged for ~10 minutes while the tool spinner kept ticking.
|
|
69
|
+
//
|
|
70
|
+
// The gate must claim exclusive input focus itself (it can't rely on the
|
|
71
|
+
// loop, which doesn't know a non-interactive tool is about to block on
|
|
72
|
+
// the user). Mirror what the loop does for interactive tools:
|
|
73
|
+
// - clearActiveSpinner(): stop the live in-place tool spinner so only
|
|
74
|
+
// ONE surface (this modal) renders.
|
|
75
|
+
// - turnStatusEmitter.pause(): freeze the ticking status row so it
|
|
76
|
+
// stops repainting the dynamic frame and overdrawing the modal.
|
|
77
|
+
// resume() in the finally restores normal rendering the moment the gate
|
|
78
|
+
// resolves. (The per-second heartbeat is loop-local and is stopped on
|
|
79
|
+
// the loop side by ordering the gate ahead of heartbeat start — see
|
|
80
|
+
// tool-dispatch.ts / loop.ts.)
|
|
81
|
+
clearActiveSpinner();
|
|
82
|
+
const statusWasActive = turnStatusEmitter.snapshot().active;
|
|
83
|
+
if (statusWasActive)
|
|
84
|
+
turnStatusEmitter.pause();
|
|
85
|
+
try {
|
|
86
|
+
const first = await askQuestionEmitter.request({
|
|
87
|
+
id: 'safety-confirm',
|
|
88
|
+
question: `⚠ ${RULE_LABEL[action.rule]}`,
|
|
89
|
+
context: stripAnsi(card).trim(),
|
|
90
|
+
kind: 'choice',
|
|
91
|
+
choices: [
|
|
92
|
+
{ value: 'apply', label: 'Apply' },
|
|
93
|
+
{ value: 'cancel', label: 'Cancel' },
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
if (first.cancelled || first.answer !== 'apply') {
|
|
97
|
+
return { confirmed: false, reason: 'user declined first prompt' };
|
|
98
|
+
}
|
|
99
|
+
// RULE 9 transport-release is irreversible — require a second explicit ack.
|
|
100
|
+
if (action.rule === 'RULE_9_TRANSPORT_RELEASE') {
|
|
101
|
+
const second = await askQuestionEmitter.request({
|
|
102
|
+
id: 'safety-confirm-irreversible',
|
|
103
|
+
question: `IRREVERSIBLE — releasing ${action.transport} cannot be undone from this side. ` +
|
|
104
|
+
`Are you absolutely sure?`,
|
|
105
|
+
kind: 'choice',
|
|
106
|
+
// Cancel first → the modal cursor starts there (mirrors the classic
|
|
107
|
+
// path's `default: 'cancel'`).
|
|
108
|
+
choices: [
|
|
109
|
+
{ value: 'cancel', label: 'Cancel' },
|
|
110
|
+
{ value: 'release', label: 'Release transport' },
|
|
111
|
+
],
|
|
112
|
+
});
|
|
113
|
+
if (second.cancelled || second.answer !== 'release') {
|
|
114
|
+
return { confirmed: false, reason: 'user declined irreversibility ack' };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { confirmed: true };
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
// Restore normal rendering. resume() is a no-op if the turn already
|
|
121
|
+
// ended (e.g. the user Ctrl+C'd mid-modal) or was never paused.
|
|
122
|
+
if (statusWasActive)
|
|
123
|
+
turnStatusEmitter.resume();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
25
126
|
// 2026-05-01: switched from confirm() to select() to fix the "press Enter
|
|
26
127
|
// first, then 'y'" race observed in dispatched-tool rule-8 prompts. confirm
|
|
27
128
|
// has a brief window after rendering its multi-line message before raw
|
|
@@ -60,6 +161,14 @@ export async function presentSafetyConfirmation(action) {
|
|
|
60
161
|
}
|
|
61
162
|
return { confirmed: true };
|
|
62
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Strip chalk ANSI escape codes. The Ink ask-question modal renders the
|
|
166
|
+
* card via its own <Text dimColor> — embedded escapes would display as
|
|
167
|
+
* literal `[33m` noise there. (Same approach as renderPlanGate.)
|
|
168
|
+
*/
|
|
169
|
+
function stripAnsi(s) {
|
|
170
|
+
return s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
171
|
+
}
|
|
63
172
|
/**
|
|
64
173
|
* Minimal indented summary block — replaces the chunky ASCII-art box.
|
|
65
174
|
* Renders as:
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Module-scope state for the safety-confirm-as-mode toggle.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Module-scope state for the safety-confirm-as-mode toggle. Off by default;
|
|
3
|
+
* the user turns it on with the `/safety-mode on` slash command at runtime
|
|
4
|
+
* (M3, 2026-06-12: there is no startup flag — an earlier version of this
|
|
5
|
+
* header claimed a `--safety-confirm` flag that was never implemented).
|
|
6
|
+
* Tool dispatchers that hit a Forge Rule (snapshot, write, batch,
|
|
5
7
|
* transport release, activation) read isSafetyConfirmEnabled() and choose
|
|
6
8
|
* presentSafetyConfirmation() vs the legacy ad-hoc inquirer prompt.
|
|
7
9
|
*
|
|
@@ -34,3 +36,17 @@ export function handleSafetyModeCommand(args) {
|
|
|
34
36
|
}
|
|
35
37
|
return `Safety-confirm mode is currently ${enabled ? 'ON' : 'OFF'}. Use \`/safety-mode on\` or \`/safety-mode off\`.`;
|
|
36
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* REPL slash-dispatch entry. Returns the status string when `trimmed` is a
|
|
41
|
+
* `/safety-mode` command (bare or with args), or null when the line is not
|
|
42
|
+
* a /safety-mode command at all. Both REPL paths (Ink handleSubmit and the
|
|
43
|
+
* classic readline loop) route through this single matcher so the command
|
|
44
|
+
* behaves identically everywhere — mirrors how /transport dispatches via
|
|
45
|
+
* handleTransportCommand.
|
|
46
|
+
*/
|
|
47
|
+
export function matchSafetyModeCommand(trimmed) {
|
|
48
|
+
if (trimmed !== '/safety-mode' && !trimmed.startsWith('/safety-mode '))
|
|
49
|
+
return null;
|
|
50
|
+
const args = trimmed === '/safety-mode' ? '' : trimmed.slice('/safety-mode '.length);
|
|
51
|
+
return handleSafetyModeCommand(args);
|
|
52
|
+
}
|
|
@@ -21,21 +21,10 @@
|
|
|
21
21
|
import { search } from '@inquirer/prompts';
|
|
22
22
|
import { withInquirer } from './inquirer-guard.js';
|
|
23
23
|
import { inquirerTheme } from './inquirer-theme.js';
|
|
24
|
-
import { SKILL_CATALOG } from '../skill-catalog.js';
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
{ name: '/compact', description: 'Summarise older turns into a compact context block — cuts subsequent turn cost by 80-90%' },
|
|
29
|
-
{ name: '/exit', description: 'Exit CSPeach' },
|
|
30
|
-
{ name: '/quit', description: 'Exit CSPeach (alias)' },
|
|
31
|
-
{ name: '/help', description: 'Show all skills and commands' },
|
|
32
|
-
{ name: '/skills', description: 'Show all skills (alias)' },
|
|
33
|
-
{ name: '/new', description: 'Reset routing — next prompt is classified afresh' },
|
|
34
|
-
{ name: '/reset', description: 'Reset routing (alias)' },
|
|
35
|
-
{ name: '/ui', description: 'View or change rendering mode (auto / ink / classic)' },
|
|
36
|
-
{ name: '/reroute', description: 'Re-dispatch the previous prompt to a different skill' },
|
|
37
|
-
{ name: '/transport', description: 'View or set the active transport for this session' },
|
|
38
|
-
];
|
|
24
|
+
import { SKILL_CATALOG, SKILL_ALIASES } from '../skill-catalog.js';
|
|
25
|
+
// 2026-06-11 — builtin list extracted to builtin-commands.ts (pure data)
|
|
26
|
+
// so the router layer can check builtin heads without importing inquirer.
|
|
27
|
+
import { BUILTIN_ENTRIES } from './builtin-commands.js';
|
|
39
28
|
function buildCatalogue() {
|
|
40
29
|
const skillEntries = SKILL_CATALOG.map((s) => ({
|
|
41
30
|
name: `/${s.name}`,
|
|
@@ -44,6 +33,20 @@ function buildCatalogue() {
|
|
|
44
33
|
return [...BUILTIN_ENTRIES, ...skillEntries].sort((a, b) => a.name.localeCompare(b.name));
|
|
45
34
|
}
|
|
46
35
|
const ALL_ENTRIES = buildCatalogue();
|
|
36
|
+
/**
|
|
37
|
+
* 2026-06-06 (Ink picker parity): the full command catalogue (builtins +
|
|
38
|
+
* skills) and the substring pre-filter, exported so the Ink CommandPalette
|
|
39
|
+
* shares the exact same data + seeding semantics as the classic inquirer
|
|
40
|
+
* picker instead of maintaining a drifting copy.
|
|
41
|
+
*/
|
|
42
|
+
export const ALL_SLASH_ENTRIES = ALL_ENTRIES;
|
|
43
|
+
/** Substring filter over the catalogue; empty prefix returns everything. */
|
|
44
|
+
export function filterSlashEntries(prefix) {
|
|
45
|
+
const lower = prefix.trim().toLowerCase();
|
|
46
|
+
return lower.length === 0
|
|
47
|
+
? [...ALL_ENTRIES]
|
|
48
|
+
: ALL_ENTRIES.filter((e) => e.name.toLowerCase().includes(lower));
|
|
49
|
+
}
|
|
47
50
|
/**
|
|
48
51
|
* Open the slash picker. Returns the picked command WITHOUT a leading
|
|
49
52
|
* slash (caller is expected to construct the full prompt) or null if
|
|
@@ -58,10 +61,7 @@ export async function openSlashPicker(prefix) {
|
|
|
58
61
|
// contains the typed prefix as a substring — substring rather than
|
|
59
62
|
// startsWith because users often remember a fragment in the middle of
|
|
60
63
|
// a name (e.g. 'fix' should bring up /abap-atc-fix and /abap-upgrade-fix).
|
|
61
|
-
const
|
|
62
|
-
const seeded = lowerPrefix.length === 0
|
|
63
|
-
? ALL_ENTRIES
|
|
64
|
-
: ALL_ENTRIES.filter((e) => e.name.toLowerCase().includes(lowerPrefix));
|
|
64
|
+
const seeded = filterSlashEntries(prefix);
|
|
65
65
|
// If the prefix narrows down to exactly one command, just return it
|
|
66
66
|
// without opening a picker — saves the user a keystroke.
|
|
67
67
|
if (seeded.length === 1) {
|
|
@@ -122,5 +122,11 @@ export function isExactSlashMatch(trimmed) {
|
|
|
122
122
|
if (!trimmed.startsWith('/'))
|
|
123
123
|
return false;
|
|
124
124
|
const head = trimmed.split(/\s+/, 1)[0].toLowerCase();
|
|
125
|
+
// Retired-but-redirected names (SKILL_ALIASES) count as exact so the
|
|
126
|
+
// line passes through to parseShortcut, which resolves the redirect and
|
|
127
|
+
// emits the "renamed" notice — instead of dumping the user into the
|
|
128
|
+
// picker. Aliases are deliberately NOT in the picker/completer lists.
|
|
129
|
+
if (head.startsWith('/') && head.slice(1) in SKILL_ALIASES)
|
|
130
|
+
return true;
|
|
125
131
|
return ALL_ENTRIES.some((e) => e.name === head);
|
|
126
132
|
}
|