@cspeach/cli 0.6.4 → 0.7.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 (153) hide show
  1. package/dist/agent/intent-system-prompt.js +46 -0
  2. package/dist/agent/loop.js +180 -18
  3. package/dist/agent/parallel-write-guard.js +71 -0
  4. package/dist/agent/repair-partial.js +88 -1
  5. package/dist/agent/retry-cap.js +121 -0
  6. package/dist/agent/summarise-via-provider.js +51 -0
  7. package/dist/approvals/jwt.js +33 -12
  8. package/dist/commands/auto-compact.js +94 -0
  9. package/dist/commands/compact.js +265 -0
  10. package/dist/commands/cost.js +56 -0
  11. package/dist/commands/help.js +21 -14
  12. package/dist/config/loader.js +39 -0
  13. package/dist/cost/cost-log.js +113 -0
  14. package/dist/cost/pricing.js +91 -0
  15. package/dist/index.js +0 -0
  16. package/dist/one-shot.js +1 -1
  17. package/dist/renderer/markdown.js +7 -1
  18. package/dist/renderer/status-footer.js +37 -14
  19. package/dist/renderer/thinking-heartbeat.js +15 -3
  20. package/dist/renderer/tool-widget.js +6 -1
  21. package/dist/renderer/tty.js +27 -0
  22. package/dist/repl/cspeach-shell-detect.js +33 -0
  23. package/dist/repl/post-turn-status.js +68 -0
  24. package/dist/repl/slash-completer.js +2 -0
  25. package/dist/repl/slash-picker.js +2 -0
  26. package/dist/repl.js +371 -46
  27. package/dist/sap-errors/parse-adt-exception.js +149 -0
  28. package/dist/session/schema.js +2 -1
  29. package/dist/session/store.js +19 -0
  30. package/dist/tools/_filesystem-shared.js +9 -1
  31. package/dist/tools/ask-question.js +19 -0
  32. package/dist/tools/sap-read.js +56 -4
  33. package/dist/tools/sap-write.js +19 -0
  34. package/dist/tools/subagent/schedule_draft_create.js +137 -0
  35. package/dist/ui/alt-screen.js +120 -0
  36. package/dist/ui/app.js +39 -15
  37. package/dist/ui/ask-question-emitter.js +13 -0
  38. package/dist/ui/body.js +39 -23
  39. package/dist/ui/coaching-picker-classic.js +8 -1
  40. package/dist/ui/footer.js +6 -1
  41. package/dist/ui/header.js +19 -4
  42. package/dist/ui/sap-state-store.js +13 -1
  43. package/dist/ui/sidebar.js +5 -3
  44. package/dist/ui/status-emitter.js +19 -0
  45. package/dist/ui/status-row.js +30 -4
  46. package/dist/ui/widgets/ask-question-modal.js +132 -0
  47. package/dist/ui/widgets/coaching-picker.js +55 -8
  48. package/package.json +86 -82
  49. package/dist/agent/__tests__/crash-recover.integration.test.js +0 -254
  50. package/dist/agent/__tests__/loop-save-hook.test.js +0 -188
  51. package/dist/agent/__tests__/maybe-build-project-context.test.js +0 -247
  52. package/dist/agent/__tests__/project-context-injection.test.js +0 -161
  53. package/dist/agent/__tests__/repair-partial.test.js +0 -79
  54. package/dist/agent/__tests__/retry-key.test.js +0 -77
  55. package/dist/agent/__tests__/sap-connection-adapter.test.js +0 -132
  56. package/dist/agent/__tests__/skill-checkpoint.test.js +0 -154
  57. package/dist/agent/__tests__/turn-assistant-text.test.js +0 -73
  58. package/dist/agent/__tests__/turn-error-ux.test.js +0 -145
  59. package/dist/agent/__tests__/turn-stream.test.js +0 -100
  60. package/dist/agent/__tests__/turn-watchdog.test.js +0 -126
  61. package/dist/agent/providers/__tests__/ai-hub-provider.test.js +0 -30
  62. package/dist/agent/providers/__tests__/byok-provider.test.js +0 -32
  63. package/dist/agent/providers/__tests__/factory.test.js +0 -33
  64. package/dist/agent/providers/__tests__/local-provider.test.js +0 -55
  65. package/dist/approvals/__tests__/advisory-prompt.test.js +0 -57
  66. package/dist/approvals/__tests__/advisory-render.test.js +0 -54
  67. package/dist/auth/__tests__/auth-file.test.js +0 -42
  68. package/dist/auth/__tests__/me.test.js +0 -43
  69. package/dist/classifier/__tests__/client.test.js +0 -70
  70. package/dist/commands/__tests__/config-set-write-mode.test.js +0 -14
  71. package/dist/commands/__tests__/login.test.js +0 -152
  72. package/dist/commands/__tests__/logout.test.js +0 -39
  73. package/dist/commands/__tests__/project-context-impact.test.js +0 -173
  74. package/dist/commands/__tests__/spec-gap-status.test.js +0 -31
  75. package/dist/commands/__tests__/whoami.test.js +0 -91
  76. package/dist/config/__tests__/llm-config.test.js +0 -28
  77. package/dist/config/__tests__/shell-exec-config.test.js +0 -76
  78. package/dist/config/__tests__/write-mode.test.js +0 -36
  79. package/dist/doctor/__tests__/check-forge-rules.test.js +0 -134
  80. package/dist/doctor/__tests__/check-llm-mode.test.js +0 -21
  81. package/dist/doctor/__tests__/check-write-mode.test.js +0 -34
  82. package/dist/project-context/__tests__/conventions.test.js +0 -221
  83. package/dist/project-context/__tests__/detect.test.js +0 -177
  84. package/dist/project-context/__tests__/domain-abap-cloud.test.js +0 -119
  85. package/dist/project-context/__tests__/domain-abapgit.test.js +0 -161
  86. package/dist/project-context/__tests__/domain-cap.test.js +0 -168
  87. package/dist/project-context/__tests__/domain-fiori.test.js +0 -282
  88. package/dist/project-context/__tests__/git.test.js +0 -131
  89. package/dist/project-context/__tests__/index-files.test.js +0 -195
  90. package/dist/project-context/__tests__/index.test.js +0 -133
  91. package/dist/project-context/__tests__/render.test.js +0 -322
  92. package/dist/project-context/__tests__/types.test.js +0 -73
  93. package/dist/projects/__tests__/build.test.js +0 -52
  94. package/dist/projects/__tests__/canonicalize.test.js +0 -47
  95. package/dist/projects/__tests__/email-template.test.js +0 -71
  96. package/dist/projects/__tests__/expand-text-attachments.test.js +0 -105
  97. package/dist/projects/__tests__/extract-cca.test.js +0 -141
  98. package/dist/projects/__tests__/extract-design.test.js +0 -62
  99. package/dist/projects/__tests__/extract-estimate.test.js +0 -58
  100. package/dist/projects/__tests__/extract-modernize.test.js +0 -127
  101. package/dist/projects/__tests__/extract-spec-gap.test.js +0 -122
  102. package/dist/projects/__tests__/extract-test-coverage.test.js +0 -133
  103. package/dist/projects/__tests__/filename.test.js +0 -32
  104. package/dist/projects/__tests__/promote-command.test.js +0 -170
  105. package/dist/projects/__tests__/promote.test.js +0 -181
  106. package/dist/projects/__tests__/save-command.test.js +0 -216
  107. package/dist/projects/__tests__/save.test.js +0 -41
  108. package/dist/projects/__tests__/status.test.js +0 -208
  109. package/dist/projects/__tests__/types.test.js +0 -41
  110. package/dist/projects/__tests__/validate.test.js +0 -165
  111. package/dist/projects/__tests__/workspace.test.js +0 -337
  112. package/dist/repl/__tests__/current-transport.test.js +0 -43
  113. package/dist/repl/__tests__/diff-display.test.js +0 -59
  114. package/dist/repl/__tests__/file-picker.test.js +0 -97
  115. package/dist/repl/__tests__/rule8-detector.test.js +0 -114
  116. package/dist/repl/__tests__/safety-confirm.test.js +0 -130
  117. package/dist/repl/__tests__/safety-mode-state.test.js +0 -42
  118. package/dist/sap/__tests__/system-info.test.js +0 -344
  119. package/dist/session/__tests__/store-discovery.test.js +0 -114
  120. package/dist/session/__tests__/time-ago.test.js +0 -53
  121. package/dist/skills/__tests__/canonical.test.js +0 -20
  122. package/dist/skills/__tests__/manifest-client.test.js +0 -88
  123. package/dist/skills/__tests__/promotion-dispatch.test.js +0 -28
  124. package/dist/skills/__tests__/source-managed.test.js +0 -60
  125. package/dist/tools/__tests__/_background-shared.test.js +0 -69
  126. package/dist/tools/__tests__/_command-shared.test.js +0 -59
  127. package/dist/tools/__tests__/_filesystem-shared.test.js +0 -42
  128. package/dist/tools/__tests__/_flag.test.js +0 -66
  129. package/dist/tools/__tests__/_helpers.js +0 -17
  130. package/dist/tools/__tests__/_web-shared.test.js +0 -143
  131. package/dist/tools/__tests__/agent_run.test.js +0 -166
  132. package/dist/tools/__tests__/approval-advisory.test.js +0 -66
  133. package/dist/tools/__tests__/background_run.test.js +0 -113
  134. package/dist/tools/__tests__/convention_get.test.js +0 -53
  135. package/dist/tools/__tests__/file-edit.test.js +0 -72
  136. package/dist/tools/__tests__/file-read.test.js +0 -81
  137. package/dist/tools/__tests__/file-write.test.js +0 -68
  138. package/dist/tools/__tests__/glob.test.js +0 -128
  139. package/dist/tools/__tests__/grep.test.js +0 -71
  140. package/dist/tools/__tests__/monitor_emit.test.js +0 -96
  141. package/dist/tools/__tests__/playbook_get.test.js +0 -109
  142. package/dist/tools/__tests__/project_context_get.test.js +0 -71
  143. package/dist/tools/__tests__/registry-category.test.js +0 -60
  144. package/dist/tools/__tests__/sap-read-extras.test.js +0 -196
  145. package/dist/tools/__tests__/sap-write-extras.test.js +0 -573
  146. package/dist/tools/__tests__/schedule_create.test.js +0 -85
  147. package/dist/tools/__tests__/shell_exec.test.js +0 -135
  148. package/dist/tools/__tests__/transport-safety.test.js +0 -106
  149. package/dist/tools/__tests__/update-method-intercept.test.js +0 -128
  150. package/dist/tools/__tests__/web_fetch.test.js +0 -329
  151. package/dist/tools/__tests__/web_search.test.js +0 -215
  152. package/dist/tools/__tests__/write-mode.test.js +0 -32
  153. package/dist/ui/__tests__/login-banner.test.js +0 -69
@@ -1,60 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { registerTool, listTools, getToolsByCategory } from '../index.js';
3
- import { TOOL_FLAG_PREFIX } from '../_flag.js';
4
- const FLAG = `${TOOL_FLAG_PREFIX}DEMO_NEW_TOOL`;
5
- beforeEach(() => { delete process.env[FLAG]; });
6
- afterEach(() => { delete process.env[FLAG]; });
7
- describe('Tool.category + getToolsByCategory', () => {
8
- it('a tool registered without category defaults to "sap" (back-compat)', () => {
9
- registerTool({
10
- name: 'demo_legacy_tool',
11
- description: 'legacy',
12
- input_schema: { type: 'object', properties: {}, required: [] },
13
- handler: async () => ({ content: 'ok' }),
14
- isMutating: false,
15
- });
16
- const t = listTools().find((x) => x.name === 'demo_legacy_tool');
17
- expect(t?.category).toBe('sap');
18
- });
19
- it('a tool registered with explicit category preserves it', () => {
20
- registerTool({
21
- name: 'demo_fs_tool',
22
- description: 'fs',
23
- input_schema: { type: 'object', properties: {}, required: [] },
24
- handler: async () => ({ content: 'ok' }),
25
- isMutating: false,
26
- category: 'filesystem',
27
- });
28
- const t = listTools().find((x) => x.name === 'demo_fs_tool');
29
- expect(t?.category).toBe('filesystem');
30
- expect(getToolsByCategory('filesystem').map((x) => x.name)).toContain('demo_fs_tool');
31
- });
32
- });
33
- describe('listTools — Phase 3 visibility gate', () => {
34
- it('a flag-gated new tool is INVISIBLE when its env flag is unset', () => {
35
- registerTool({
36
- name: 'demo_new_tool',
37
- description: 'gated',
38
- input_schema: { type: 'object', properties: {}, required: [] },
39
- handler: async () => ({ content: 'ok' }),
40
- isMutating: false,
41
- category: 'filesystem',
42
- flagGated: true,
43
- });
44
- expect(listTools().some((t) => t.name === 'demo_new_tool')).toBe(false);
45
- });
46
- it('the same tool is VISIBLE when its env flag is on', () => {
47
- process.env[FLAG] = 'on';
48
- expect(listTools().some((t) => t.name === 'demo_new_tool')).toBe(true);
49
- });
50
- it('a tool without flagGated is ALWAYS visible (back-compat for existing 30+ tools)', () => {
51
- registerTool({
52
- name: 'demo_visible_no_flag',
53
- description: 'always-visible',
54
- input_schema: { type: 'object', properties: {}, required: [] },
55
- handler: async () => ({ content: 'ok' }),
56
- isMutating: false,
57
- });
58
- expect(listTools().some((t) => t.name === 'demo_visible_no_flag')).toBe(true);
59
- });
60
- });
@@ -1,196 +0,0 @@
1
- /**
2
- * Tests for the 8 additional read-only SAP tools added in Phase 3-Lite A.4:
3
- * sap_class_includes, sap_inactive_objects, sap_syntax_check, sap_diff,
4
- * sap_preview_change, sap_run_unit_test, sap_odata_test, sap_badi_list
5
- *
6
- * Pattern: partial mock as `{ method: vi.fn() } as unknown as AdtClient`.
7
- * Each tool gets a happy-path test and an error-path test (16 tests total).
8
- */
9
- import '../sap-read.js'; // triggers registration side-effect
10
- import { getTool } from '../index.js';
11
- import { describe, it, expect, vi } from 'vitest';
12
- // ---------------------------------------------------------------------------
13
- // Helper: build a minimal ToolContext stub
14
- // ---------------------------------------------------------------------------
15
- function makeCtx(adt) {
16
- return {
17
- adt: adt,
18
- sapAlias: 'dev',
19
- session: {},
20
- cwd: '.',
21
- };
22
- }
23
- // ---------------------------------------------------------------------------
24
- // 11. sap_class_includes
25
- // ---------------------------------------------------------------------------
26
- describe('sap_class_includes', () => {
27
- it('returns JSON array when no includeType is given', async () => {
28
- const adt = {
29
- classIncludes: vi.fn().mockResolvedValue([{ name: 'definitions' }, { name: 'implementations' }]),
30
- };
31
- const tool = getTool('sap_class_includes');
32
- const result = await tool.handler({ className: 'ZCL_FOO' }, makeCtx(adt));
33
- expect(adt.classIncludes).toHaveBeenCalledWith('ZCL_FOO', undefined);
34
- expect(JSON.parse(result.content)).toEqual([{ name: 'definitions' }, { name: 'implementations' }]);
35
- });
36
- it('propagates error when classIncludes throws', async () => {
37
- const adt = {
38
- classIncludes: vi.fn().mockRejectedValue(new Error('ADT 404')),
39
- };
40
- const tool = getTool('sap_class_includes');
41
- await expect(tool.handler({ className: 'ZCL_MISSING' }, makeCtx(adt))).rejects.toThrow('ADT 404');
42
- });
43
- });
44
- // ---------------------------------------------------------------------------
45
- // 12. sap_inactive_objects
46
- // ---------------------------------------------------------------------------
47
- describe('sap_inactive_objects', () => {
48
- it('returns JSON list of inactive objects', async () => {
49
- const adt = {
50
- inactiveObjects: vi.fn().mockResolvedValue([{ name: 'ZCL_FOO', type: 'CLAS' }]),
51
- };
52
- const tool = getTool('sap_inactive_objects');
53
- const result = await tool.handler({}, makeCtx(adt));
54
- expect(adt.inactiveObjects).toHaveBeenCalledWith();
55
- expect(JSON.parse(result.content)).toEqual([{ name: 'ZCL_FOO', type: 'CLAS' }]);
56
- });
57
- it('propagates error when inactiveObjects throws', async () => {
58
- const adt = {
59
- inactiveObjects: vi.fn().mockRejectedValue(new Error('ADT 503')),
60
- };
61
- const tool = getTool('sap_inactive_objects');
62
- await expect(tool.handler({}, makeCtx(adt))).rejects.toThrow('ADT 503');
63
- });
64
- });
65
- // ---------------------------------------------------------------------------
66
- // 13. sap_syntax_check
67
- // ---------------------------------------------------------------------------
68
- describe('sap_syntax_check', () => {
69
- it('calls syntaxCheck with type first and returns JSON result', async () => {
70
- const fakeSyntaxResult = { hasErrors: false, messages: [] };
71
- const adt = {
72
- syntaxCheck: vi.fn().mockResolvedValue(fakeSyntaxResult),
73
- };
74
- const tool = getTool('sap_syntax_check');
75
- const result = await tool.handler({ type: 'CLAS', name: 'ZCL_FOO' }, makeCtx(adt));
76
- expect(adt.syntaxCheck).toHaveBeenCalledWith('CLAS', 'ZCL_FOO');
77
- expect(JSON.parse(result.content)).toEqual(fakeSyntaxResult);
78
- });
79
- it('propagates error when syntaxCheck throws', async () => {
80
- const adt = {
81
- syntaxCheck: vi.fn().mockRejectedValue(new Error('connection refused')),
82
- };
83
- const tool = getTool('sap_syntax_check');
84
- await expect(tool.handler({ type: 'CLAS', name: 'ZCL_FOO' }, makeCtx(adt))).rejects.toThrow('connection refused');
85
- });
86
- });
87
- // ---------------------------------------------------------------------------
88
- // 14. sap_diff
89
- // ---------------------------------------------------------------------------
90
- describe('sap_diff', () => {
91
- it('calls diff with type first and returns JSON result', async () => {
92
- const fakeDiff = { unified: '--- a\n+++ b\n@@ -1 +1 @@\n-old\n+new' };
93
- const adt = {
94
- diff: vi.fn().mockResolvedValue(fakeDiff),
95
- };
96
- const tool = getTool('sap_diff');
97
- const result = await tool.handler({ type: 'PROG', name: 'ZTEST', version1: 'old', version2: 'new' }, makeCtx(adt));
98
- expect(adt.diff).toHaveBeenCalledWith('PROG', 'ZTEST', 'old', 'new');
99
- expect(JSON.parse(result.content)).toEqual(fakeDiff);
100
- });
101
- it('propagates error when diff throws', async () => {
102
- const adt = {
103
- diff: vi.fn().mockRejectedValue(new Error('ADT 401')),
104
- };
105
- const tool = getTool('sap_diff');
106
- await expect(tool.handler({ type: 'PROG', name: 'ZTEST' }, makeCtx(adt))).rejects.toThrow('ADT 401');
107
- });
108
- });
109
- // ---------------------------------------------------------------------------
110
- // 15. sap_preview_change
111
- // ---------------------------------------------------------------------------
112
- describe('sap_preview_change', () => {
113
- it('calls previewChange with type first and returns JSON result', async () => {
114
- const fakeDiff = { unified: '@@ -1 +1 @@\n-old\n+new' };
115
- const adt = {
116
- previewChange: vi.fn().mockResolvedValue(fakeDiff),
117
- };
118
- const tool = getTool('sap_preview_change');
119
- const result = await tool.handler({ type: 'CLAS', name: 'ZCL_FOO', newSource: 'CLASS ZCL_FOO...' }, makeCtx(adt));
120
- expect(adt.previewChange).toHaveBeenCalledWith('CLAS', 'ZCL_FOO', 'CLASS ZCL_FOO...');
121
- expect(JSON.parse(result.content)).toEqual(fakeDiff);
122
- });
123
- it('propagates error when previewChange throws', async () => {
124
- const adt = {
125
- previewChange: vi.fn().mockRejectedValue(new Error('object locked')),
126
- };
127
- const tool = getTool('sap_preview_change');
128
- await expect(tool.handler({ type: 'CLAS', name: 'ZCL_FOO', newSource: 'x' }, makeCtx(adt))).rejects.toThrow('object locked');
129
- });
130
- });
131
- // ---------------------------------------------------------------------------
132
- // 16. sap_run_unit_test
133
- // ---------------------------------------------------------------------------
134
- describe('sap_run_unit_test', () => {
135
- it('calls runUnitTest with type first and returns JSON result', async () => {
136
- const fakeResult = { passed: 3, failed: 0, errors: 0, testClasses: [] };
137
- const adt = {
138
- runUnitTest: vi.fn().mockResolvedValue(fakeResult),
139
- };
140
- const tool = getTool('sap_run_unit_test');
141
- const result = await tool.handler({ type: 'CLAS', name: 'ZCL_ORDER' }, makeCtx(adt));
142
- expect(adt.runUnitTest).toHaveBeenCalledWith('CLAS', 'ZCL_ORDER');
143
- expect(JSON.parse(result.content)).toEqual(fakeResult);
144
- });
145
- it('propagates error when runUnitTest throws', async () => {
146
- const adt = {
147
- runUnitTest: vi.fn().mockRejectedValue(new Error('ADT 500')),
148
- };
149
- const tool = getTool('sap_run_unit_test');
150
- await expect(tool.handler({ type: 'CLAS', name: 'ZCL_ORDER' }, makeCtx(adt))).rejects.toThrow('ADT 500');
151
- });
152
- });
153
- // ---------------------------------------------------------------------------
154
- // 17. sap_odata_test
155
- // ---------------------------------------------------------------------------
156
- describe('sap_odata_test', () => {
157
- it('calls odataTest with correct args and returns JSON result', async () => {
158
- const fakeResult = { status: 200, body: '<service/>' };
159
- const adt = {
160
- odataTest: vi.fn().mockResolvedValue(fakeResult),
161
- };
162
- const tool = getTool('sap_odata_test');
163
- const result = await tool.handler({ servicePath: '/sap/opu/odata/sap/ZSALES_SRV', entity: 'SalesOrder', method: 'GET' }, makeCtx(adt));
164
- expect(adt.odataTest).toHaveBeenCalledWith('/sap/opu/odata/sap/ZSALES_SRV', 'SalesOrder', 'GET');
165
- expect(JSON.parse(result.content)).toEqual(fakeResult);
166
- });
167
- it('propagates error when odataTest throws', async () => {
168
- const adt = {
169
- odataTest: vi.fn().mockRejectedValue(new Error('service not found')),
170
- };
171
- const tool = getTool('sap_odata_test');
172
- await expect(tool.handler({ servicePath: '/sap/opu/odata/sap/ZMISSING_SRV' }, makeCtx(adt))).rejects.toThrow('service not found');
173
- });
174
- });
175
- // ---------------------------------------------------------------------------
176
- // 18. sap_badi_list
177
- // ---------------------------------------------------------------------------
178
- describe('sap_badi_list', () => {
179
- it('calls badiList with correct args and returns JSON result', async () => {
180
- const fakeResult = { badiDefinitions: [{ name: 'MD_PURCH_REQ_SAVE' }], enhancementSpots: [] };
181
- const adt = {
182
- badiList: vi.fn().mockResolvedValue(fakeResult),
183
- };
184
- const tool = getTool('sap_badi_list');
185
- const result = await tool.handler({ query: 'MD_PURCH*', enhancementSpot: 'ES_MD_PURCH', maxResults: 25 }, makeCtx(adt));
186
- expect(adt.badiList).toHaveBeenCalledWith('MD_PURCH*', 'ES_MD_PURCH', 25);
187
- expect(JSON.parse(result.content)).toEqual(fakeResult);
188
- });
189
- it('propagates error when badiList throws', async () => {
190
- const adt = {
191
- badiList: vi.fn().mockRejectedValue(new Error('ADT 403')),
192
- };
193
- const tool = getTool('sap_badi_list');
194
- await expect(tool.handler({ query: 'ZBADI*' }, makeCtx(adt))).rejects.toThrow('ADT 403');
195
- });
196
- });