@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
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pillar A — data-driven capability resolver.
|
|
3
|
+
*
|
|
4
|
+
* Answers "can the connected SAP release do feature X?" from the committed
|
|
5
|
+
* ABAP feature-matrix snapshot — so skills stop hardcoding version prose
|
|
6
|
+
* (e.g. the old "FILTER needs 7.50" guess that the matrix proves wrong on
|
|
7
|
+
* a 7.40 SP08 system with the feature backported).
|
|
8
|
+
*
|
|
9
|
+
* This module is PURE: the matrix is passed in, so it is unit-testable
|
|
10
|
+
* without filesystem or network. The snapshot loader lives in
|
|
11
|
+
* capability-matrix.ts; the tool wrapper in tools/capability/tool.ts.
|
|
12
|
+
*
|
|
13
|
+
* NO hardcoded version thresholds — every yes/no/with-fallback decision is
|
|
14
|
+
* read out of the snapshot data.
|
|
15
|
+
*
|
|
16
|
+
* See docs/superpowers/specs/2026-06-24-revision-aware-cspeach-design.md.
|
|
17
|
+
*/
|
|
18
|
+
export const CAPABILITY_REGISTRY = {
|
|
19
|
+
'cds.viewEntity': { feature: 'Define View Entity', fallback: "use classic 'define view' (DDIC-based) instead of 'define view entity'" },
|
|
20
|
+
'rap.managed': { feature: 'Managed Scenario', fallback: '' }, // empty = no fallback
|
|
21
|
+
'rap.behaviorDefinition': { feature: 'Behavior Definitions', fallback: '' }, // no classic equivalent if BDEF unsupported
|
|
22
|
+
'abap.filterExpr': { feature: 'FILTER', fallback: 'use LOOP AT ... / READ TABLE instead of FILTER( )' },
|
|
23
|
+
'abap.inlineData': { feature: 'Inline Declaration', fallback: 'declare the variable explicitly with DATA: before use' },
|
|
24
|
+
};
|
|
25
|
+
export const CAPABILITY_IDS = Object.keys(CAPABILITY_REGISTRY);
|
|
26
|
+
function isCapabilityId(s) {
|
|
27
|
+
return Object.prototype.hasOwnProperty.call(CAPABILITY_REGISTRY, s);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Map an `abapVersion` (as produced by system-info.ts: "7.57", "7.40",
|
|
31
|
+
* "unknown") to one of the snapshot's column keys. The column keys are
|
|
32
|
+
* DERIVED from the snapshot row passed in — never hardcoded — so adding or
|
|
33
|
+
* renaming columns in the snapshot needs no code change here.
|
|
34
|
+
*
|
|
35
|
+
* Rules:
|
|
36
|
+
* - falsy / 'unknown' → null
|
|
37
|
+
* - '7.40' → the 7.40 column containing 'SP08' (the release
|
|
38
|
+
* floor); else any 7.40 column
|
|
39
|
+
* - else → the column whose key contains the substring
|
|
40
|
+
* `(<abapVersion>)`, e.g. '7.57' → '2022 (7.57)'
|
|
41
|
+
*/
|
|
42
|
+
export function mapReleaseToColumn(abapVersion, columns) {
|
|
43
|
+
if (!abapVersion || abapVersion === 'unknown')
|
|
44
|
+
return null;
|
|
45
|
+
if (abapVersion === '7.40') {
|
|
46
|
+
const sp08 = columns.find((c) => c.startsWith('7.40') && c.includes('SP08'));
|
|
47
|
+
if (sp08)
|
|
48
|
+
return sp08;
|
|
49
|
+
const any740 = columns.find((c) => c.includes('7.40'));
|
|
50
|
+
return any740 ?? null;
|
|
51
|
+
}
|
|
52
|
+
const needle = `(${abapVersion})`;
|
|
53
|
+
return columns.find((c) => c.includes(needle)) ?? null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Pure resolver. `capability` may be a registry id (cds.viewEntity, …) or a
|
|
57
|
+
* raw matrix feature name. Implements the design's decision table exactly.
|
|
58
|
+
*/
|
|
59
|
+
export function resolveCapability(info, matrix, capability) {
|
|
60
|
+
// 1. Resolve target feature + fallback.
|
|
61
|
+
let feature;
|
|
62
|
+
let fallback;
|
|
63
|
+
if (isCapabilityId(capability)) {
|
|
64
|
+
feature = CAPABILITY_REGISTRY[capability].feature;
|
|
65
|
+
fallback = CAPABILITY_REGISTRY[capability].fallback || undefined;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
feature = capability;
|
|
69
|
+
fallback = undefined;
|
|
70
|
+
}
|
|
71
|
+
const release = info.release;
|
|
72
|
+
const base = (overrides) => ({
|
|
73
|
+
capability,
|
|
74
|
+
feature,
|
|
75
|
+
status: 'no',
|
|
76
|
+
reason: '',
|
|
77
|
+
release,
|
|
78
|
+
column: null,
|
|
79
|
+
source: 'matrix-snapshot',
|
|
80
|
+
...overrides,
|
|
81
|
+
});
|
|
82
|
+
// 3. Find the feature row (case-insensitive exact match). Done before
|
|
83
|
+
// the column map so column-derivation can use the row's own keys.
|
|
84
|
+
const wanted = feature.toLowerCase();
|
|
85
|
+
const row = matrix.features.find((f) => f.feature.toLowerCase() === wanted);
|
|
86
|
+
// 2. Determine the matrix column. Prefer the keys of the found row; if the
|
|
87
|
+
// feature is missing, fall back to any feature's keys so the verdict can
|
|
88
|
+
// still report the column it would have used.
|
|
89
|
+
const columnKeys = row
|
|
90
|
+
? Object.keys(row.statusByRelease)
|
|
91
|
+
: matrix.features.length > 0
|
|
92
|
+
? Object.keys(matrix.features[0].statusByRelease)
|
|
93
|
+
: [];
|
|
94
|
+
const column = mapReleaseToColumn(info.abapVersion, columnKeys);
|
|
95
|
+
// 3 (cont). Feature not found.
|
|
96
|
+
if (!row) {
|
|
97
|
+
return base({
|
|
98
|
+
status: fallback ? 'with-fallback' : 'no',
|
|
99
|
+
reason: `feature '${feature}' not found in the matrix snapshot`,
|
|
100
|
+
column,
|
|
101
|
+
...(fallback ? { fallback } : {}),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
// 4. Column could not be mapped.
|
|
105
|
+
if (column === null) {
|
|
106
|
+
return base({
|
|
107
|
+
status: fallback ? 'with-fallback' : 'no',
|
|
108
|
+
reason: `could not map release '${info.abapVersion}' to a matrix column`,
|
|
109
|
+
column: null,
|
|
110
|
+
...(fallback ? { fallback } : {}),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// 5. Read + map the cell value.
|
|
114
|
+
const raw = row.statusByRelease[column];
|
|
115
|
+
switch (raw) {
|
|
116
|
+
case 'available':
|
|
117
|
+
return base({ status: 'yes', reason: `available on ${release}`, column });
|
|
118
|
+
case 'downport':
|
|
119
|
+
return base({ status: 'yes', reason: `available on ${release} (backported)`, column });
|
|
120
|
+
case 'unavailable':
|
|
121
|
+
return base({
|
|
122
|
+
status: fallback ? 'with-fallback' : 'no',
|
|
123
|
+
reason: `not available on ${release}`,
|
|
124
|
+
column,
|
|
125
|
+
...(fallback ? { fallback } : {}),
|
|
126
|
+
});
|
|
127
|
+
case 'deprecated':
|
|
128
|
+
if (fallback) {
|
|
129
|
+
return base({
|
|
130
|
+
status: 'with-fallback',
|
|
131
|
+
reason: `available but deprecated on ${release}`,
|
|
132
|
+
column,
|
|
133
|
+
fallback,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return base({ status: 'yes', reason: `available but deprecated on ${release}`, column });
|
|
137
|
+
default:
|
|
138
|
+
// needs_review / unknown / undefined / anything else.
|
|
139
|
+
return base({
|
|
140
|
+
status: fallback ? 'with-fallback' : 'no',
|
|
141
|
+
reason: `matrix status '${raw ?? ''}' for ${release} is inconclusive`,
|
|
142
|
+
column,
|
|
143
|
+
...(fallback ? { fallback } : {}),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import keytar from 'keytar';
|
|
2
2
|
import { SapConnection } from '@cspeach/sap-client';
|
|
3
|
+
import { resolveSapTls } from '../config/loader.js';
|
|
3
4
|
import { password as promptPassword } from '@inquirer/prompts';
|
|
5
|
+
import { isHeadless } from '../renderer/tty.js';
|
|
4
6
|
const SAP_SERVICE = 'cspeach-sap';
|
|
5
7
|
const cache = new Map();
|
|
6
8
|
export async function getConnection(alias, cfg) {
|
|
@@ -14,6 +16,14 @@ export async function getConnection(alias, cfg) {
|
|
|
14
16
|
}
|
|
15
17
|
catch { /* keychain unavailable */ }
|
|
16
18
|
if (!passwordValue) {
|
|
19
|
+
// B5 (2026-06-11) — headless guard: the keytar-miss fallback is a
|
|
20
|
+
// stdin password prompt, which would hang a headless run forever
|
|
21
|
+
// (same defect family as D1). Fail fast with an actionable error
|
|
22
|
+
// instead of prompting.
|
|
23
|
+
if (isHeadless()) {
|
|
24
|
+
throw new Error(`no stored password for SAP alias '${alias}' — store credentials first ` +
|
|
25
|
+
'(run interactively once or use cspeach config) — headless runs cannot prompt');
|
|
26
|
+
}
|
|
17
27
|
passwordValue = await promptPassword({
|
|
18
28
|
message: `SAP password for ${alias}/${cfg.username}:`,
|
|
19
29
|
mask: '*',
|
|
@@ -26,12 +36,20 @@ export async function getConnection(alias, cfg) {
|
|
|
26
36
|
// SapConnection constructor is (config, username, password) — 3 positional args.
|
|
27
37
|
// SapConfig.authType is typed as 'basic' only (v1); SapSystemConfig carries the
|
|
28
38
|
// wider union so we cast to satisfy the narrower SapConfig shape.
|
|
39
|
+
//
|
|
40
|
+
// TLS posture (2026-06-14): verification is ON by default. resolveSapTls maps
|
|
41
|
+
// the per-alias config (ca_cert_path / insecure_skip_tls_verify, with the
|
|
42
|
+
// legacy sslVerify:false → insecure fallback) into the sap-client options.
|
|
43
|
+
// We pass caCertPath / insecureSkipTlsVerify explicitly — NOT the legacy
|
|
44
|
+
// sslVerify — so the new verify-by-default contract holds.
|
|
45
|
+
const tls = resolveSapTls(cfg);
|
|
29
46
|
const conn = new SapConnection({
|
|
30
47
|
host: cfg.host,
|
|
31
48
|
port: cfg.port,
|
|
32
49
|
client: cfg.client,
|
|
33
50
|
useSsl: cfg.useSsl,
|
|
34
|
-
|
|
51
|
+
caCertPath: tls.caCertPath,
|
|
52
|
+
insecureSkipTlsVerify: tls.insecureSkipTlsVerify,
|
|
35
53
|
credentialRef: alias,
|
|
36
54
|
authType: 'basic',
|
|
37
55
|
}, cfg.username, passwordValue ?? '');
|
package/dist/sap/onboarding.js
CHANGED
|
@@ -23,9 +23,45 @@ export async function runAddSapWizard() {
|
|
|
23
23
|
validate: (v) => /^\d{3}$/.test(v) || 'Must be a 3-digit client number',
|
|
24
24
|
});
|
|
25
25
|
const useSsl = await confirm({ message: 'Use HTTPS/TLS?', default: true });
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
// TLS posture (2026-06-14): verification is ON by default. For self-signed /
|
|
27
|
+
// internal-CA SAP systems, the production-grade answer is to trust the
|
|
28
|
+
// corporate CA (verification stays ON). Disabling verification is an explicit
|
|
29
|
+
// dev-only opt-out, never the default.
|
|
30
|
+
let caCertPath;
|
|
31
|
+
let insecureSkipTlsVerify = false;
|
|
32
|
+
if (useSsl) {
|
|
33
|
+
const tlsChoice = await select({
|
|
34
|
+
message: 'TLS certificate handling:',
|
|
35
|
+
choices: [
|
|
36
|
+
{
|
|
37
|
+
name: 'verify (recommended) — trust the system certificate via the OS/Node trust store',
|
|
38
|
+
value: 'verify',
|
|
39
|
+
description: 'Use this when the SAP cert is issued by a public or already-trusted CA.',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'trust a corporate/internal CA — verification stays ON',
|
|
43
|
+
value: 'ca',
|
|
44
|
+
description: 'Best for self-signed / internal-CA SAP systems. Point CSPeach at your CA PEM file; verification remains enabled.',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'INSECURE — disable verification (dev only)',
|
|
48
|
+
value: 'insecure',
|
|
49
|
+
description: 'Last resort for a dev box with no CA available. Logs a loud warning on every connect. NEVER use in production.',
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
default: 'verify',
|
|
53
|
+
});
|
|
54
|
+
if (tlsChoice === 'ca') {
|
|
55
|
+
caCertPath = await input({
|
|
56
|
+
message: 'Path to the CA certificate PEM file (e.g. /etc/ssl/corp-ca.pem):',
|
|
57
|
+
validate: (v) => v.length > 0 || 'Required',
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
else if (tlsChoice === 'insecure') {
|
|
61
|
+
console.log(chalk.yellow(' ⚠ TLS verification will be DISABLED for this system — dev only, NEVER use in production.'));
|
|
62
|
+
insecureSkipTlsVerify = true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
29
65
|
const authType = await select({
|
|
30
66
|
message: 'Authentication type:',
|
|
31
67
|
choices: [
|
|
@@ -164,7 +200,9 @@ export async function runAddSapWizard() {
|
|
|
164
200
|
port: Number(port),
|
|
165
201
|
client,
|
|
166
202
|
useSsl,
|
|
167
|
-
|
|
203
|
+
// New TLS keys — verify by default; trust a CA, or explicit dev-only insecure.
|
|
204
|
+
...(caCertPath ? { ca_cert_path: caCertPath } : {}),
|
|
205
|
+
...(insecureSkipTlsVerify ? { insecure_skip_tls_verify: true } : {}),
|
|
168
206
|
username,
|
|
169
207
|
authType,
|
|
170
208
|
certPath,
|
package/dist/session/pending.js
CHANGED
|
@@ -35,3 +35,30 @@ export async function finalizeToolCall(session, toolUseId, result, isError, dura
|
|
|
35
35
|
export function hasUnfinishedMutations(session) {
|
|
36
36
|
return session.toolCalls.some((c) => !c.completed);
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* D19 (2026-06-11) — loop-side ledger record, upsert semantics.
|
|
40
|
+
*
|
|
41
|
+
* runTurn records every dispatched tool call in session.toolCalls after the
|
|
42
|
+
* handler returns. Write handlers ALSO journal the same call through the WAL
|
|
43
|
+
* (appendPending → finalizeToolCall) — and since the WAL is now keyed by the
|
|
44
|
+
* REAL tool_use_id (ctx.toolUseId), the loop's record must UPDATE that entry
|
|
45
|
+
* rather than push a sibling. Previously the loop pushed unconditionally and
|
|
46
|
+
* the WAL used a random UUID, so every approval-gated write produced TWO
|
|
47
|
+
* ledger entries per logical call (the D19 "double execution" signature).
|
|
48
|
+
*
|
|
49
|
+
* The loop's version of the entry wins: its `result` is the handler's full
|
|
50
|
+
* returned content (the WAL finalize stores a leaner internal summary) and
|
|
51
|
+
* its duration includes dispatch overhead.
|
|
52
|
+
*
|
|
53
|
+
* In-memory only — the caller persists (unlike appendPending/finalizeToolCall,
|
|
54
|
+
* which saveSession themselves).
|
|
55
|
+
*/
|
|
56
|
+
export function recordCompletedToolCall(session, entry) {
|
|
57
|
+
const idx = session.toolCalls.findIndex((c) => c.tool_use_id === entry.tool_use_id);
|
|
58
|
+
if (idx >= 0) {
|
|
59
|
+
session.toolCalls[idx] = entry;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
session.toolCalls.push(entry);
|
|
63
|
+
}
|
|
64
|
+
}
|
package/dist/skill-catalog.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Static skill catalog for CSPeach CLI v0.2.
|
|
3
3
|
*
|
|
4
|
-
* STALENESS RISK (spec §3.3): This constant is hardcoded against the
|
|
5
|
-
* in .claude/skills/ as of 2026-
|
|
4
|
+
* STALENESS RISK (spec §3.3): This constant is hardcoded against the 32 skills
|
|
5
|
+
* in .claude/skills/ as of 2026-06-28 (abap-dump-analyze + abap-dump-triage
|
|
6
|
+
* merged into abap-dump 2026-06-12; abap-extend-model added 2026-06-25;
|
|
7
|
+
* abap-radar/abap-document/abap-performance/abap-cloud-check merged into
|
|
8
|
+
* abap-explain/abap-handover/abap-review 2026-06-28 per
|
|
9
|
+
* plan 2026-06-28-skill-portfolio-merge). It will drift if skills are added or
|
|
6
10
|
* removed. At Plan 4b kickoff decision the build-time generation option
|
|
7
11
|
* (scripts/generate-skill-catalog.ts reads SKILL.md frontmatter) or runtime
|
|
8
12
|
* fetch (/v1/skills endpoint) will be chosen to replace this file. Until then,
|
|
@@ -10,65 +14,35 @@
|
|
|
10
14
|
*/
|
|
11
15
|
export const SKILL_CATALOG = [
|
|
12
16
|
// ── Read / Analyze ───────────────────────────────────────────────────────
|
|
13
|
-
{
|
|
14
|
-
name: 'abap-radar',
|
|
15
|
-
category: 'Read / Analyze',
|
|
16
|
-
description: 'Understand an ABAP object quickly — dependencies, tables, risk areas, modernisation summary',
|
|
17
|
-
whenToUse: 'pick this for a 3-min cold read of an object (not /abap-review — no quality grade)',
|
|
18
|
-
},
|
|
19
17
|
{
|
|
20
18
|
name: 'abap-impact',
|
|
21
19
|
category: 'Read / Analyze',
|
|
22
20
|
description: 'Impact analysis before changes — traces usage, callers, transports',
|
|
23
|
-
whenToUse: 'pick this for blast radius before a change (not /abap-
|
|
21
|
+
whenToUse: 'pick this for blast radius before a change (not /abap-explain — explain = teach the code)',
|
|
24
22
|
},
|
|
25
23
|
{
|
|
26
24
|
name: 'abap-explain',
|
|
27
25
|
category: 'Read / Analyze',
|
|
28
|
-
description: '
|
|
29
|
-
whenToUse: `
|
|
26
|
+
description: 'Understand an object — plain-language (default) or `technical` orientation (deps, tables, risks)',
|
|
27
|
+
whenToUse: `plain teaching, or technical=fast orientation (not /abap-impact = blast radius)`,
|
|
30
28
|
},
|
|
31
29
|
{
|
|
32
30
|
name: 'abap-review',
|
|
33
31
|
category: 'Read / Analyze',
|
|
34
|
-
description: '
|
|
35
|
-
whenToUse: `
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
name: 'abap-performance',
|
|
39
|
-
category: 'Read / Analyze',
|
|
40
|
-
description: 'Identify N+1 queries, missing indexes, SELECT *, FOR ALL ENTRIES pitfalls',
|
|
41
|
-
whenToUse: 'pick this for deep SQL/loop bottleneck hunt (not /abap-review — review only skims perf)',
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
name: 'abap-cloud-check',
|
|
45
|
-
category: 'Read / Analyze',
|
|
46
|
-
description: 'ABAP Cloud / Clean Core readiness — restricted APIs, tier violations, released object alternatives',
|
|
47
|
-
whenToUse: `pick this for ABAP Cloud verdict on one object (not /abap-cca — that's whole-system)`,
|
|
32
|
+
description: 'Read-only review across lenses: quality (default), performance, cloud-readiness',
|
|
33
|
+
whenToUse: `grade source: quality (default) + performance/cloud lenses`,
|
|
48
34
|
},
|
|
49
35
|
{
|
|
50
36
|
name: 'abap-handover',
|
|
51
37
|
category: 'Read / Analyze',
|
|
52
|
-
description: '
|
|
53
|
-
whenToUse: `
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
name: 'abap-document',
|
|
57
|
-
category: 'Read / Analyze',
|
|
58
|
-
description: 'Reference doc for ONE ABAP object — purpose, interface, logic flow, dependencies, where-used',
|
|
59
|
-
whenToUse: `pick this for a reference doc on ONE object (not /abap-handover — that's whole-package)`,
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
name: 'abap-dump-analyze',
|
|
63
|
-
category: 'Read / Analyze',
|
|
64
|
-
description: 'Analyze ABAP short dumps (ST22) — root cause, call stack, fix suggestions',
|
|
65
|
-
whenToUse: `pick this to diagnose an ST22 dump AND apply the fix (not /abap-dump-triage — read-only)`,
|
|
38
|
+
description: 'Documentation for one object (`object` scope) or a whole package (`package` scope)',
|
|
39
|
+
whenToUse: `object doc OR full package handover dossier`,
|
|
66
40
|
},
|
|
67
41
|
{
|
|
68
|
-
name: 'abap-dump
|
|
42
|
+
name: 'abap-dump',
|
|
69
43
|
category: 'Read / Analyze',
|
|
70
|
-
description: '
|
|
71
|
-
whenToUse: `pick this for
|
|
44
|
+
description: 'Diagnose ABAP short dumps (ST22) and fix them — root cause, call stack, fix with approval; ask for "triage" for ranked read-only hypotheses',
|
|
45
|
+
whenToUse: `pick this for any ST22 dump — diagnose + fix by default, say "triage" for read-only ranking (not /abap-incident — that's the full dump-to-transport pipeline)`,
|
|
72
46
|
},
|
|
73
47
|
// ── Modify / Fix ─────────────────────────────────────────────────────────
|
|
74
48
|
{
|
|
@@ -89,11 +63,17 @@ export const SKILL_CATALOG = [
|
|
|
89
63
|
description: 'Extend SAP standard via BAdI implementations, enhancement spots, user exits',
|
|
90
64
|
whenToUse: `pick this to hook into SAP-STANDARD code via BAdI/exit (not /abap-refactor — that's for YOUR Z code)`,
|
|
91
65
|
},
|
|
66
|
+
{
|
|
67
|
+
name: 'abap-extend-model',
|
|
68
|
+
category: 'Modify / Fix',
|
|
69
|
+
description: 'Add a field or @UI column to an EXISTING custom CDS view / DDLX metadata extension in place — anchored insertion, snapshot, syntax-check, activate, republish-if-needed. The Fiori Elements column-add.',
|
|
70
|
+
whenToUse: `pick this to add a field/column to an EXISTING custom CDS/DDLX in place (not /abap-rap — that's a new stack; not /abap-refactor — that's ABAP code, not CDS)`,
|
|
71
|
+
},
|
|
92
72
|
{
|
|
93
73
|
name: 'abap-incident',
|
|
94
74
|
category: 'Modify / Fix',
|
|
95
75
|
description: 'End-to-end incident pipeline — dump to fix deployed to transport in one skill',
|
|
96
|
-
whenToUse: `pick this for full ST22-to-transport pipeline with formal report (not /abap-dump
|
|
76
|
+
whenToUse: `pick this for full ST22-to-transport pipeline with formal report (not /abap-dump — single fix only)`,
|
|
97
77
|
},
|
|
98
78
|
{
|
|
99
79
|
name: 'abap-test',
|
|
@@ -126,6 +106,12 @@ export const SKILL_CATALOG = [
|
|
|
126
106
|
description: 'Scaffold a complete RAP stack on S/4HANA 1909+ — table, CDS interface, projection, BDEF, BP class, SRVD, SRVB, DDLX, DCL. ECC/older S/4 → use /abap-segw.',
|
|
127
107
|
whenToUse: `pick this for a full RAP stack on S/4 1909+ (10 objects, table → SRVB; not /abap-segw — that's ECC/older S/4 OData; not /abap-generate — that's one object)`,
|
|
128
108
|
},
|
|
109
|
+
{
|
|
110
|
+
name: 'abap-fiori-build',
|
|
111
|
+
category: 'Create / Generate',
|
|
112
|
+
description: 'Build a working freestyle SAPUI5 app on the laptop from a published OData service — scaffold, pages, dialogs, chart, value help, local preview. Freestyle only, not Fiori Elements.',
|
|
113
|
+
whenToUse: `pick this for a freestyle UI5 frontend on a published OData service (not /abap-rap — that's the backend; produces local files, not ADT objects)`,
|
|
114
|
+
},
|
|
129
115
|
{
|
|
130
116
|
name: 'abap-data-model',
|
|
131
117
|
category: 'Create / Generate',
|
|
@@ -219,4 +205,29 @@ export const SKILL_CATALOG = [
|
|
|
219
205
|
description: 'Effort estimation for ABAP tickets — component breakdown with hour ranges (optimistic / realistic / pessimistic) + risk adjustments',
|
|
220
206
|
whenToUse: `pick this for hour ranges + risk adjustments on a confirmed object list (step 3 of 3 — needs /abap-design first; high uncertainty without it)`,
|
|
221
207
|
},
|
|
208
|
+
{
|
|
209
|
+
name: 'abap-plan',
|
|
210
|
+
category: 'Pre-Coding',
|
|
211
|
+
description: 'Multi-session project plan — phase envelope holds state across sessions; create from a goal or spec-gap file, resume one bounded phase per session',
|
|
212
|
+
whenToUse: `pick this when the work spans multiple sessions or components (not /abap-design — that's one design in one session; plan calls design per phase)`,
|
|
213
|
+
},
|
|
222
214
|
];
|
|
215
|
+
/**
|
|
216
|
+
* Portfolio-merge redirects (2026-06-12). Old skill names that were merged
|
|
217
|
+
* into a surviving skill keep routing for ONE release: `/abap-dump-analyze`
|
|
218
|
+
* resolves to `abap-dump` with a dim "renamed" notice instead of the
|
|
219
|
+
* unknown-skill error. Resolution happens in parseShortcut (slash input)
|
|
220
|
+
* and classifyPrompt (stale proxy-classifier outputs). Remove the entries
|
|
221
|
+
* — and the proxy-side alias seeds — one release after the rename ships.
|
|
222
|
+
*/
|
|
223
|
+
export const SKILL_ALIASES = {
|
|
224
|
+
'abap-dump-analyze': 'abap-dump',
|
|
225
|
+
'abap-dump-triage': 'abap-dump',
|
|
226
|
+
};
|
|
227
|
+
/**
|
|
228
|
+
* Resolve a (possibly retired) skill name to its current catalog name.
|
|
229
|
+
* Returns the input unchanged when it is not an alias.
|
|
230
|
+
*/
|
|
231
|
+
export function resolveSkillAlias(skill) {
|
|
232
|
+
return SKILL_ALIASES[skill] ?? skill;
|
|
233
|
+
}
|