@cspeach/cli 0.9.0 → 1.1.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.
Files changed (195) hide show
  1. package/README.md +1 -1
  2. package/dist/agent/intent-system-prompt.js +1 -1
  3. package/dist/agent/loop.js +228 -26
  4. package/dist/agent/providers/license-gate.js +44 -0
  5. package/dist/agent/skill-checkpoint.js +1 -1
  6. package/dist/agent/tool-dispatch.js +15 -0
  7. package/dist/approvals/canonical.js +91 -0
  8. package/dist/approvals/jwt.js +39 -2
  9. package/dist/approvals/op-labels.js +124 -0
  10. package/dist/approvals/render.js +42 -36
  11. package/dist/auth/org-anthropic-key.js +25 -0
  12. package/dist/classifier/client.js +18 -3
  13. package/dist/cli.js +15 -0
  14. package/dist/commands/compact.js +28 -2
  15. package/dist/commands/config-set.js +284 -0
  16. package/dist/commands/config-show.js +20 -0
  17. package/dist/commands/export-audit.js +43 -0
  18. package/dist/commands/help.js +5 -0
  19. package/dist/commands/login.js +31 -14
  20. package/dist/commands/plan-audit-evidence.js +266 -0
  21. package/dist/commands/plan-audit.js +692 -0
  22. package/dist/commands/plan-chain.js +671 -0
  23. package/dist/commands/plan-continue.js +179 -0
  24. package/dist/commands/plan-gate.js +154 -0
  25. package/dist/commands/plan-model-tier.js +83 -0
  26. package/dist/commands/plan-resume.js +728 -46
  27. package/dist/config/loader.js +223 -5
  28. package/dist/config/model-defaults.js +14 -0
  29. package/dist/cost/pricing.js +27 -1
  30. package/dist/doctor/checks/_http-probe.js +1 -0
  31. package/dist/doctor/checks/cert.js +14 -3
  32. package/dist/doctor/checks/sap.js +30 -8
  33. package/dist/doctor/checks/system-roles.js +41 -0
  34. package/dist/doctor/checks/zcspeach.js +19 -4
  35. package/dist/doctor/run.js +2 -0
  36. package/dist/models/resolve.js +61 -0
  37. package/dist/models/server-config.js +155 -0
  38. package/dist/one-shot.js +76 -6
  39. package/dist/projects/answer-blockers.js +137 -0
  40. package/dist/projects/extract-cca.js +111 -17
  41. package/dist/projects/extract-modernize.js +4 -2
  42. package/dist/projects/extract-plan.js +184 -37
  43. package/dist/projects/extract-spec-gap.js +34 -7
  44. package/dist/projects/extract-test-coverage.js +4 -2
  45. package/dist/projects/extract-upgrade.js +116 -23
  46. package/dist/projects/handover-md.js +195 -0
  47. package/dist/projects/index.js +5 -2
  48. package/dist/projects/merge-cca.js +292 -0
  49. package/dist/projects/merge-upgrade.js +173 -0
  50. package/dist/projects/migration.js +103 -1
  51. package/dist/projects/output-paths.js +27 -0
  52. package/dist/projects/plan-run.js +285 -27
  53. package/dist/projects/plan-schema.js +136 -3
  54. package/dist/projects/promote-command.js +25 -2
  55. package/dist/projects/promote.js +128 -0
  56. package/dist/projects/run-lease.js +157 -0
  57. package/dist/projects/save-command.js +259 -21
  58. package/dist/projects/status.js +3 -1
  59. package/dist/projects/validate.js +1 -1
  60. package/dist/projects/workspace.js +164 -20
  61. package/dist/renderer/notices.js +64 -0
  62. package/dist/renderer/progress-chatter.js +8 -0
  63. package/dist/renderer/status-footer.js +22 -12
  64. package/dist/renderer/thinking-heartbeat.js +64 -8
  65. package/dist/renderer/todo-block.js +51 -0
  66. package/dist/renderer/tool-widget.js +55 -4
  67. package/dist/renderer/tty.js +43 -4
  68. package/dist/renderer/verify-chain.js +77 -0
  69. package/dist/repl/at-picker.js +60 -7
  70. package/dist/repl/bracketed-paste.js +28 -19
  71. package/dist/repl/builtin-commands.js +42 -0
  72. package/dist/repl/current-transport.js +10 -0
  73. package/dist/repl/early-line-buffer.js +68 -0
  74. package/dist/repl/history.js +86 -0
  75. package/dist/repl/ink-stdin-guard.js +64 -0
  76. package/dist/repl/inquirer-guard.js +70 -5
  77. package/dist/repl/mode-ceiling.js +16 -0
  78. package/dist/repl/mode-cycle.js +104 -0
  79. package/dist/repl/numbered-menu.js +131 -0
  80. package/dist/repl/post-turn-status.js +26 -6
  81. package/dist/repl/rule8-detector.js +17 -2
  82. package/dist/repl/safety-confirm.js +111 -2
  83. package/dist/repl/safety-mode-state.js +19 -3
  84. package/dist/repl/slash-completer.js +5 -0
  85. package/dist/repl/slash-picker.js +10 -15
  86. package/dist/repl.js +1232 -95
  87. package/dist/rewind/candidates.js +194 -0
  88. package/dist/rewind/cli.js +137 -0
  89. package/dist/rewind/format.js +27 -0
  90. package/dist/rewind/restore.js +245 -0
  91. package/dist/router/classifier.js +150 -6
  92. package/dist/sap/capability-matrix.js +20 -0
  93. package/dist/sap/capability-matrix.json +11236 -0
  94. package/dist/sap/capability.js +146 -0
  95. package/dist/sap/connection-manager.js +19 -1
  96. package/dist/sap/onboarding.js +42 -4
  97. package/dist/session/audit-export.js +459 -0
  98. package/dist/session/context-report.js +163 -0
  99. package/dist/session/pending.js +27 -0
  100. package/dist/session/recap.js +160 -0
  101. package/dist/skill-catalog.js +51 -40
  102. package/dist/skills/bundled-skills.js +272 -1
  103. package/dist/skills/promotion-dispatch.js +23 -0
  104. package/dist/tools/_command-shared.js +36 -12
  105. package/dist/tools/_filesystem-shared.js +139 -4
  106. package/dist/tools/_flag.js +25 -0
  107. package/dist/tools/approval.js +177 -26
  108. package/dist/tools/ask-question.js +400 -7
  109. package/dist/tools/capability/tool.js +74 -0
  110. package/dist/tools/dispatch-skill.js +22 -1
  111. package/dist/tools/extend-model/anchored-insert.js +1414 -0
  112. package/dist/tools/extend-model/tool.js +340 -0
  113. package/dist/tools/filesystem/extract-document.js +57 -0
  114. package/dist/tools/filesystem/file-edit.js +12 -2
  115. package/dist/tools/filesystem/file-read.js +2 -2
  116. package/dist/tools/filesystem/file-write.js +11 -2
  117. package/dist/tools/filesystem/glob.js +11 -0
  118. package/dist/tools/filesystem/grep.js +10 -0
  119. package/dist/tools/filesystem/read-document.js +107 -0
  120. package/dist/tools/fiori/apply.js +50 -0
  121. package/dist/tools/fiori/bin.js +3 -0
  122. package/dist/tools/fiori/catalog/index.js +27 -0
  123. package/dist/tools/fiori/catalog/value-help.js +230 -0
  124. package/dist/tools/fiori/catalog/viz-chart.js +177 -0
  125. package/dist/tools/fiori/cli.js +71 -0
  126. package/dist/tools/fiori/deploy-config.js +73 -0
  127. package/dist/tools/fiori/fe-extend.js +76 -0
  128. package/dist/tools/fiori/fe-scaffold.js +71 -0
  129. package/dist/tools/fiori/floorplan-map.js +19 -0
  130. package/dist/tools/fiori/i18n.js +39 -0
  131. package/dist/tools/fiori/manifest.js +70 -0
  132. package/dist/tools/fiori/render.js +77 -0
  133. package/dist/tools/fiori/samples/data/index.json +13602 -0
  134. package/dist/tools/fiori/samples/data/sources.generated.js +808 -0
  135. package/dist/tools/fiori/samples/loader.js +248 -0
  136. package/dist/tools/fiori/samples/search.js +63 -0
  137. package/dist/tools/fiori/samples/types.js +2 -0
  138. package/dist/tools/fiori/scaffold.js +39 -0
  139. package/dist/tools/fiori/smoke/assertions.js +74 -0
  140. package/dist/tools/fiori/smoke/browser.js +52 -0
  141. package/dist/tools/fiori/smoke/driver.js +89 -0
  142. package/dist/tools/fiori/smoke/freestyle-spec.js +317 -0
  143. package/dist/tools/fiori/smoke/run-smoke.js +149 -0
  144. package/dist/tools/fiori/tools.js +681 -0
  145. package/dist/tools/fiori/types.js +1 -0
  146. package/dist/tools/local-build.js +86 -0
  147. package/dist/tools/local-files.js +31 -0
  148. package/dist/tools/project/_merge-shared.js +68 -0
  149. package/dist/tools/project/cca_merge.js +164 -0
  150. package/dist/tools/project/playbook_get.js +1 -1
  151. package/dist/tools/project/upgrade_merge_progress.js +206 -0
  152. package/dist/tools/sap-read.js +132 -20
  153. package/dist/tools/sap-write.js +550 -21
  154. package/dist/tools/shell/shell_exec.js +41 -6
  155. package/dist/tools/snapshot.js +63 -14
  156. package/dist/tools/subagent/agent_run.js +27 -3
  157. package/dist/tools/subagent/background_run.js +17 -1
  158. package/dist/tools/todo.js +144 -0
  159. package/dist/tools/transport-resolution.js +86 -0
  160. package/dist/tools/transport.js +224 -5
  161. package/dist/tools/write-mode.js +4 -0
  162. package/dist/ui/app.js +378 -21
  163. package/dist/ui/approval-modal.js +49 -16
  164. package/dist/ui/ask-question-emitter.js +14 -0
  165. package/dist/ui/body.js +13 -0
  166. package/dist/ui/context-grid.js +108 -0
  167. package/dist/ui/footer.js +120 -27
  168. package/dist/ui/header.js +7 -0
  169. package/dist/ui/line-resolution.js +35 -8
  170. package/dist/ui/rewind-emitter.js +10 -0
  171. package/dist/ui/rewind-panel.js +81 -0
  172. package/dist/ui/sap-state-store.js +1 -0
  173. package/dist/ui/session-timeline.js +1 -0
  174. package/dist/ui/status-line.js +43 -0
  175. package/dist/ui/text-input.js +214 -0
  176. package/dist/ui/todo-emitter.js +25 -0
  177. package/dist/ui/todo-panel.js +64 -0
  178. package/dist/ui/turn-status-emitter.js +50 -4
  179. package/dist/ui/turn-status.js +18 -3
  180. package/dist/ui/widgets/ask-form.js +242 -0
  181. package/dist/ui/widgets/ask-question-modal.js +21 -8
  182. package/package.json +22 -3
  183. package/bench/README.md +0 -78
  184. package/bench/prompts/abap-document-cds.md +0 -44
  185. package/bench/prompts/abap-explain-bdef-handler.md +0 -57
  186. package/bench/prompts/abap-test-method.md +0 -42
  187. package/bench/results/abap-document-cds/claude-haiku-4-5.md +0 -189
  188. package/bench/results/abap-document-cds/claude-opus-4-7.md +0 -120
  189. package/bench/results/abap-document-cds/claude-sonnet-4-6.md +0 -151
  190. package/bench/results/abap-explain-bdef-handler/claude-haiku-4-5.md +0 -112
  191. package/bench/results/abap-explain-bdef-handler/claude-opus-4-7.md +0 -101
  192. package/bench/results/abap-explain-bdef-handler/claude-sonnet-4-6.md +0 -101
  193. package/bench/results/abap-test-method/claude-haiku-4-5.md +0 -186
  194. package/bench/results/abap-test-method/claude-opus-4-7.md +0 -193
  195. 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
- sslVerify: cfg.sslVerify,
51
+ caCertPath: tls.caCertPath,
52
+ insecureSkipTlsVerify: tls.insecureSkipTlsVerify,
35
53
  credentialRef: alias,
36
54
  authType: 'basic',
37
55
  }, cfg.username, passwordValue ?? '');
@@ -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
- const sslVerify = useSsl
27
- ? await confirm({ message: 'Verify server certificate? (Say no for self-signed SAP certs)', default: false })
28
- : false;
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
- sslVerify,
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,