@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,71 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { renderEmailTemplate } from '../email-template.js';
3
- describe('renderEmailTemplate', () => {
4
- it('renders subject + body with title and filename interpolated', () => {
5
- const out = renderEmailTemplate({
6
- title: 'Customer Aging Report',
7
- artefactType: 'spec-gap',
8
- filename: 'customer-aging-report-spec-gap-a1b2-v1.cspeach.json',
9
- itemCount: 27,
10
- itemNoun: 'questions',
11
- author: 'Laeeq',
12
- });
13
- expect(out).toContain('Subject: Gap analysis — Customer Aging Report');
14
- expect(out).toContain('27 questions');
15
- expect(out).toContain('customer-aging-report-spec-gap-a1b2-v1.cspeach.json');
16
- expect(out).toContain('https://cspeach.dev/edit');
17
- expect(out).toContain('Thanks,\nLaeeq');
18
- expect(out).toContain('rename it to .cspeach.json.txt');
19
- });
20
- it('renders design subject + body with "objects to review" copy', () => {
21
- const out = renderEmailTemplate({
22
- title: 'Order Approval System',
23
- artefactType: 'design',
24
- filename: 'order-approval-system-design-c3d4-v1.cspeach.json',
25
- itemCount: 11,
26
- itemNoun: 'objects',
27
- author: 'Laeeq',
28
- });
29
- expect(out).toContain('Subject: Design — Order Approval System');
30
- expect(out).toContain('11 objects to review');
31
- expect(out).toContain('keep / redesign / defer');
32
- });
33
- it('renders estimate subject + body with "components to review" copy', () => {
34
- const out = renderEmailTemplate({
35
- title: 'ZSD-441',
36
- artefactType: 'estimate',
37
- filename: 'zsd-441-estimate-e5f6-v1.cspeach.json',
38
- itemCount: 8,
39
- itemNoun: 'components',
40
- author: 'Laeeq',
41
- });
42
- expect(out).toContain('Subject: Estimate — ZSD-441');
43
- expect(out).toContain('8 components to review');
44
- expect(out).toContain('confirm / dispute / defer');
45
- });
46
- it('respects CSPEACH_EDITOR_URL env override', async () => {
47
- const original = process.env.CSPEACH_EDITOR_URL;
48
- process.env.CSPEACH_EDITOR_URL = 'http://localhost:5173/edit';
49
- try {
50
- vi.resetModules();
51
- const { renderEmailTemplate: renderFresh } = await import('../email-template.js');
52
- const out = renderFresh({
53
- title: 'Test',
54
- artefactType: 'spec-gap',
55
- filename: 'test-spec-gap-aaaa-v1.cspeach.json',
56
- itemCount: 5,
57
- itemNoun: 'questions',
58
- author: 'Tester',
59
- });
60
- expect(out).toContain('http://localhost:5173/edit');
61
- expect(out).not.toContain('https://cspeach.dev/edit');
62
- }
63
- finally {
64
- if (original === undefined)
65
- delete process.env.CSPEACH_EDITOR_URL;
66
- else
67
- process.env.CSPEACH_EDITOR_URL = original;
68
- vi.resetModules();
69
- }
70
- });
71
- });
@@ -1,105 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
3
- import { join } from 'node:path';
4
- import os from 'node:os';
5
- import { expandTextFileAttachments } from '../workspace.js';
6
- /**
7
- * These tests touch the workspace folder via the resolver, which
8
- * reads the user's home directory at run time. We don't want to
9
- * pollute the user's actual ~/CSPeach, so we set CSPEACH-style
10
- * config to point the workspace at a temp dir for the test process.
11
- *
12
- * The simplest reliable way to redirect ensureWorkspace's lookup
13
- * here is to override HOME / USERPROFILE for the duration of the
14
- * test — the loader's path helpers join from os.homedir(). We
15
- * restore the original value in afterEach.
16
- */
17
- const TEST_HOME = join(os.tmpdir(), `cspeach-expand-test-${process.pid}-${Date.now()}`);
18
- const TEST_WORKSPACE = join(TEST_HOME, 'CSPeach');
19
- function setHome(p) {
20
- const prev = {
21
- HOME: process.env['HOME'],
22
- USERPROFILE: process.env['USERPROFILE'],
23
- };
24
- process.env['HOME'] = p;
25
- process.env['USERPROFILE'] = p;
26
- return {
27
- restore: () => {
28
- process.env['HOME'] = prev.HOME;
29
- process.env['USERPROFILE'] = prev.USERPROFILE;
30
- },
31
- };
32
- }
33
- describe('expandTextFileAttachments', () => {
34
- let homeGuard;
35
- beforeEach(() => {
36
- // 2026-05-08: default workspace is now project-rooted (`<git-root>/
37
- // .cspeach/`) rather than `~/CSPeach/`. The test still uses HOME
38
- // redirection to isolate from the real config, but it now ALSO
39
- // pins workspace_folder via a synthetic ~/.cspeach/config.toml so
40
- // ensureWorkspace returns TEST_WORKSPACE instead of the project's
41
- // .cspeach/.
42
- mkdirSync(TEST_WORKSPACE, { recursive: true });
43
- mkdirSync(join(TEST_HOME, '.cspeach'), { recursive: true });
44
- writeFileSync(join(TEST_HOME, '.cspeach', 'config.toml'), `workspace_folder = "${TEST_WORKSPACE.replace(/\\/g, '/')}"\nmigration_prompted = true\n`);
45
- homeGuard = setHome(TEST_HOME);
46
- });
47
- afterEach(() => {
48
- homeGuard.restore();
49
- try {
50
- rmSync(TEST_HOME, { recursive: true, force: true });
51
- }
52
- catch { /* best-effort */ }
53
- });
54
- it('substitutes @<txt-file> with an <attached> block', async () => {
55
- writeFileSync(join(TEST_WORKSPACE, 'spec.txt'), 'My requirement here.');
56
- const out = await expandTextFileAttachments('Please run /abap-spec-gap on @spec.txt', process.cwd());
57
- expect(out).toContain('<attached file="spec.txt">');
58
- expect(out).toContain('My requirement here.');
59
- expect(out).toContain('</attached>');
60
- expect(out).not.toContain('@spec.txt');
61
- });
62
- it('substitutes @<md-file> the same way as txt', async () => {
63
- writeFileSync(join(TEST_WORKSPACE, 'requirement.md'), '# Requirement\n\nDo X.');
64
- const out = await expandTextFileAttachments('@requirement.md please', process.cwd());
65
- expect(out).toContain('<attached file="requirement.md">');
66
- expect(out).toContain('# Requirement');
67
- expect(out).toContain('Do X.');
68
- });
69
- it('leaves the message unchanged when no @-tokens are present', async () => {
70
- const msg = 'Just plain prose with no attachments.';
71
- const out = await expandTextFileAttachments(msg, process.cwd());
72
- expect(out).toBe(msg);
73
- });
74
- it('leaves @<envelope> tokens alone (handled by --from / --status)', async () => {
75
- writeFileSync(join(TEST_WORKSPACE, 'foo-design-aaaa-v1.cspeach.json'), JSON.stringify({ artefactType: 'design', version: 1, title: 'Foo' }));
76
- const out = await expandTextFileAttachments('inspect @foo-design-aaaa-v1.cspeach.json please', process.cwd());
77
- expect(out).toContain('@foo-design-aaaa-v1.cspeach.json');
78
- expect(out).not.toContain('<attached');
79
- });
80
- it('leaves unmatched @<token> as a literal', async () => {
81
- const out = await expandTextFileAttachments('reference @nothing-here for context', process.cwd());
82
- expect(out).toContain('@nothing-here');
83
- expect(out).not.toContain('<attached');
84
- });
85
- it('handles multiple @<txt> tokens in one message', async () => {
86
- writeFileSync(join(TEST_WORKSPACE, 'a.txt'), 'AAA');
87
- writeFileSync(join(TEST_WORKSPACE, 'b.txt'), 'BBB');
88
- const out = await expandTextFileAttachments('Look at @a.txt and @b.txt together.', process.cwd());
89
- expect(out).toContain('<attached file="a.txt">\nAAA\n</attached>');
90
- expect(out).toContain('<attached file="b.txt">\nBBB\n</attached>');
91
- });
92
- it('truncates files larger than the 64 KB cap', async () => {
93
- const big = 'x'.repeat(70_000);
94
- writeFileSync(join(TEST_WORKSPACE, 'big.txt'), big);
95
- const out = await expandTextFileAttachments('@big.txt', process.cwd());
96
- expect(out).toContain('[truncated, file exceeds 64 KB]');
97
- expect(out.length).toBeLessThan(70_000 + 1000);
98
- });
99
- it('reports attached / not-found through the optional log callback', async () => {
100
- writeFileSync(join(TEST_WORKSPACE, 'real.txt'), 'r');
101
- const lines = [];
102
- await expandTextFileAttachments('mix @real.txt and @ghost-token', process.cwd(), (l) => lines.push(l));
103
- expect(lines.some((l) => l.includes('attached real.txt'))).toBe(true);
104
- });
105
- });
@@ -1,141 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { extractCcaAssessment } from '../extract-cca.js';
3
- const FULL_FIXTURE = `# /abap-cca — ZFI / ZSD estate
4
-
5
- [lots of human-readable content here that the parser ignores]
6
-
7
- <!-- csforge:cca-manifest
8
- artefact: cca-assessment
9
- title: ZFI / ZSD — S/4HANA 2023 estate
10
- detail_path: .abapforge/cca/projects/zfi-zsd-2026-04/project.json
11
- project_id: zfi-zsd-2026-04
12
- scope: ZFI* + ZSD*
13
- atc_variant: S4HANA_READINESS_2023
14
- total_objects: 1842
15
- keep: 421
16
- fix: 738
17
- retire: 124
18
- redesign: 312
19
- uncategorized: 247
20
- classifications:
21
- item-001 | ZFI_BP_SYNC | PROG | fix | high
22
- item-002 | ZCL_FI_DOC | CLAS | redesign | medium
23
- item-003 | ZSD_LEGACY_RPT | PROG | retire | high
24
- item-004 | ZFI_HELPER | CLAS | keep | low
25
- -->
26
- `;
27
- describe('extractCcaAssessment', () => {
28
- it('extracts title from manifest', () => {
29
- const out = extractCcaAssessment(FULL_FIXTURE);
30
- expect(out.title).toBe('ZFI / ZSD — S/4HANA 2023 estate');
31
- });
32
- it('extracts detailPath, projectId, scope, atcVariant', () => {
33
- const out = extractCcaAssessment(FULL_FIXTURE);
34
- expect(out.content.detailPath).toBe('.abapforge/cca/projects/zfi-zsd-2026-04/project.json');
35
- expect(out.content.projectId).toBe('zfi-zsd-2026-04');
36
- expect(out.content.scope).toBe('ZFI* + ZSD*');
37
- expect(out.content.atcVariant).toBe('S4HANA_READINESS_2023');
38
- });
39
- it('extracts summary counts', () => {
40
- const out = extractCcaAssessment(FULL_FIXTURE);
41
- expect(out.content.summary).toEqual({
42
- totalObjects: 1842,
43
- keep: 421,
44
- fix: 738,
45
- retire: 124,
46
- redesign: 312,
47
- uncategorized: 247,
48
- });
49
- });
50
- it('extracts classifications table with all four rows', () => {
51
- const out = extractCcaAssessment(FULL_FIXTURE);
52
- expect(out.content.classifications).toHaveLength(4);
53
- expect(out.content.classifications[0]).toEqual({
54
- id: 'item-001',
55
- objectName: 'ZFI_BP_SYNC',
56
- objectType: 'PROG',
57
- classification: 'fix',
58
- confidence: 'high',
59
- });
60
- expect(out.content.classifications[1]).toEqual({
61
- id: 'item-002',
62
- objectName: 'ZCL_FI_DOC',
63
- objectType: 'CLAS',
64
- classification: 'redesign',
65
- confidence: 'medium',
66
- });
67
- expect(out.content.classifications[2]).toEqual({
68
- id: 'item-003',
69
- objectName: 'ZSD_LEGACY_RPT',
70
- objectType: 'PROG',
71
- classification: 'retire',
72
- confidence: 'high',
73
- });
74
- });
75
- it('emits one InteractionItem per classification with default status open', () => {
76
- const out = extractCcaAssessment(FULL_FIXTURE);
77
- expect(out.items).toHaveLength(4);
78
- for (const it of out.items) {
79
- expect(it.status).toBe('open');
80
- expect(it.answer).toBeNull();
81
- expect(it.answeredBy).toBeNull();
82
- expect(it.comments).toEqual([]);
83
- }
84
- expect(out.items.map((i) => i.id)).toEqual(['item-001', 'item-002', 'item-003', 'item-004']);
85
- });
86
- it('throws when the manifest block is missing', () => {
87
- expect(() => extractCcaAssessment('# Just a heading\nno manifest here')).toThrow(/No <!-- csforge:cca-manifest --> block/);
88
- });
89
- it('throws when artefact field disagrees', () => {
90
- const wrong = `<!-- csforge:cca-manifest
91
- artefact: upgrade-scan
92
- detail_path: .abapforge/foo.json
93
- project_id: abc
94
- -->`;
95
- expect(() => extractCcaAssessment(wrong)).toThrow(/expected 'cca-assessment'/);
96
- });
97
- it('throws when detail_path is missing', () => {
98
- const noDetail = `<!-- csforge:cca-manifest
99
- artefact: cca-assessment
100
- project_id: abc
101
- -->`;
102
- expect(() => extractCcaAssessment(noDetail)).toThrow(/'detail_path'/);
103
- });
104
- it('throws when project_id is missing', () => {
105
- const noProject = `<!-- csforge:cca-manifest
106
- artefact: cca-assessment
107
- detail_path: .abapforge/foo.json
108
- -->`;
109
- expect(() => extractCcaAssessment(noProject)).toThrow(/'project_id'/);
110
- });
111
- it('coerces unknown classification value to safe default keep', () => {
112
- const weird = `<!-- csforge:cca-manifest
113
- artefact: cca-assessment
114
- detail_path: .abapforge/foo.json
115
- project_id: abc
116
- classifications:
117
- item-001 | ZFOO | PROG | refurbish | astonishing
118
- -->`;
119
- const out = extractCcaAssessment(weird);
120
- expect(out.content.classifications[0]?.classification).toBe('keep');
121
- expect(out.content.classifications[0]?.confidence).toBe('medium');
122
- });
123
- it('falls back to defaults when summary fields absent', () => {
124
- const minimal = `<!-- csforge:cca-manifest
125
- artefact: cca-assessment
126
- detail_path: .abapforge/foo.json
127
- project_id: abc
128
- -->`;
129
- const out = extractCcaAssessment(minimal);
130
- expect(out.content.summary).toEqual({
131
- totalObjects: 0,
132
- keep: 0,
133
- fix: 0,
134
- retire: 0,
135
- redesign: 0,
136
- uncategorized: 0,
137
- });
138
- expect(out.content.classifications).toEqual([]);
139
- expect(out.items).toEqual([]);
140
- });
141
- });
@@ -1,62 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { readFileSync } from 'node:fs';
3
- import { fileURLToPath } from 'node:url';
4
- import { dirname, resolve } from 'node:path';
5
- import { extractDesign } from '../extract-design.js';
6
- const __dirname = dirname(fileURLToPath(import.meta.url));
7
- const FIXTURE_PATH = resolve(__dirname, '../../../test/fixtures/abap-design-output.md');
8
- describe('extractDesign', () => {
9
- it('extracts title from heading', () => {
10
- const md = readFileSync(FIXTURE_PATH, 'utf8');
11
- const out = extractDesign(md);
12
- expect(out.title).toBe('Order Approval System');
13
- });
14
- it('extracts all 11 objects from the table', () => {
15
- const md = readFileSync(FIXTURE_PATH, 'utf8');
16
- const out = extractDesign(md);
17
- expect(out.content.objects).toHaveLength(11);
18
- expect(out.content.objects[0]).toMatchObject({
19
- name: 'ZDOM_APPROVAL_STATUS',
20
- type: 'Domain',
21
- package: 'ZDEV_TR001',
22
- });
23
- expect(out.content.objects[8]).toMatchObject({
24
- name: 'ZCL_APPROVAL_BEHAVIOR',
25
- type: 'Class',
26
- });
27
- });
28
- it('assigns sequential ids obj-001..obj-N', () => {
29
- const md = readFileSync(FIXTURE_PATH, 'utf8');
30
- const out = extractDesign(md);
31
- expect(out.content.objects.map((o) => o.id)).toEqual([
32
- 'obj-001', 'obj-002', 'obj-003', 'obj-004', 'obj-005',
33
- 'obj-006', 'obj-007', 'obj-008', 'obj-009', 'obj-010', 'obj-011',
34
- ]);
35
- });
36
- it('populates dependsOn from the Dependency Sequence section', () => {
37
- const md = readFileSync(FIXTURE_PATH, 'utf8');
38
- const out = extractDesign(md);
39
- const byName = new Map(out.content.objects.map((o) => [o.name, o]));
40
- expect(byName.get('ZDE_APPROVAL_STATUS').dependsOn).toContain('obj-001');
41
- expect(byName.get('ZAPPR_REQUEST').dependsOn).toEqual(expect.arrayContaining(['obj-003', 'obj-004']));
42
- expect(byName.get('ZBND_APPROVAL').dependsOn).toEqual(['obj-010']);
43
- });
44
- it('emits one InteractionItem per object with default status keep', () => {
45
- const md = readFileSync(FIXTURE_PATH, 'utf8');
46
- const out = extractDesign(md);
47
- expect(out.items).toHaveLength(11);
48
- for (const it of out.items) {
49
- expect(it.status).toBe('keep');
50
- expect(it.answer).toBeNull();
51
- expect(it.answeredBy).toBeNull();
52
- expect(it.comments).toEqual([]);
53
- }
54
- expect(out.items.map((i) => i.id)).toEqual(out.content.objects.map((o) => o.id));
55
- });
56
- it('throws when the heading is missing', () => {
57
- expect(() => extractDesign('## Some Other Heading\n\nbody')).toThrow(/not a design output/);
58
- });
59
- it('throws when the Objects to Create table is missing', () => {
60
- expect(() => extractDesign('## ABAP Design — X\n\n(no table)')).toThrow(/Objects to Create/);
61
- });
62
- });
@@ -1,58 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { readFileSync } from 'node:fs';
3
- import { fileURLToPath } from 'node:url';
4
- import { dirname, resolve } from 'node:path';
5
- import { extractEstimate } from '../extract-estimate.js';
6
- const __dirname = dirname(fileURLToPath(import.meta.url));
7
- const FIXTURE_PATH = resolve(__dirname, '../../../test/fixtures/abap-estimate-output.md');
8
- describe('extractEstimate', () => {
9
- it('extracts title from heading', () => {
10
- const md = readFileSync(FIXTURE_PATH, 'utf8');
11
- const out = extractEstimate(md);
12
- expect(out.title).toBe('ZSD-441 Material Pricing Report with Enhancement');
13
- });
14
- it('extracts all 11 components, skipping the TOTAL row', () => {
15
- const md = readFileSync(FIXTURE_PATH, 'utf8');
16
- const out = extractEstimate(md);
17
- expect(out.content.components).toHaveLength(11);
18
- expect(out.content.components[0]).toMatchObject({
19
- name: 'CDS interface view — pricing conditions',
20
- hoursOptimistic: 3,
21
- hoursRealistic: 5,
22
- hoursPessimistic: 9,
23
- });
24
- expect(out.content.components[5]).toMatchObject({
25
- name: 'Interface for test doubles',
26
- hoursOptimistic: 0.5,
27
- hoursRealistic: 1,
28
- hoursPessimistic: 1.5,
29
- });
30
- });
31
- it('assigns sequential ids cmp-001..cmp-N', () => {
32
- const md = readFileSync(FIXTURE_PATH, 'utf8');
33
- const out = extractEstimate(md);
34
- expect(out.content.components[0].id).toBe('cmp-001');
35
- expect(out.content.components[10].id).toBe('cmp-011');
36
- });
37
- it('captures Notes column into risks array', () => {
38
- const md = readFileSync(FIXTURE_PATH, 'utf8');
39
- const out = extractEstimate(md);
40
- expect(out.content.components[3].risks).toEqual(['BAdI behavior not fully documented']);
41
- expect(out.content.components[5].risks).toEqual(['Interface design follows from class structure']);
42
- });
43
- it('emits one InteractionItem per component with default status confirmed', () => {
44
- const md = readFileSync(FIXTURE_PATH, 'utf8');
45
- const out = extractEstimate(md);
46
- expect(out.items).toHaveLength(11);
47
- for (const it of out.items) {
48
- expect(it.status).toBe('confirmed');
49
- expect(it.answer).toBeNull();
50
- }
51
- });
52
- it('throws when the heading is missing', () => {
53
- expect(() => extractEstimate('## Some Other Heading\n')).toThrow(/not an estimate output/);
54
- });
55
- it('throws when the Component Breakdown table is missing', () => {
56
- expect(() => extractEstimate('## Effort Estimate — X\n\n(no table)')).toThrow(/Component Breakdown/);
57
- });
58
- });
@@ -1,127 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { extractModernizeResult } from '../extract-modernize.js';
3
- describe('extractModernizeResult', () => {
4
- const FULL_FIXTURE = `# /abap-modernize — ZDPR_PAYMENTS
5
-
6
- [lots of human-readable prose the parser ignores]
7
-
8
- <!-- csforge:modernize-manifest
9
- artefact: modernize-result
10
- title: ZDPR_PAYMENTS — modernization
11
- detail_path: .abapforge/modernize/zdpr-payments/project.json
12
- baseline_path: .cspeach/zdpr-payments-cca-assessment-3467-v1.cspeach.json
13
- baseline_sha256: a7Qj8ibfLTj49kQRTn1DOE_UAExuoBgjhaXxf33+JPY=
14
- transport: S4HK903400
15
- applied: 2
16
- skipped: 1
17
- failed: 0
18
- pending: 1
19
- modernizations:
20
- item-001 | ZBDC_VENDOR_PAYMENTS_CLOUD | FUNC | bdc-against-f53 | fm-based-posting | applied
21
- item-002 | ZPAYMENTS | PROG | select-from-bsig | cds-i_journalentryitem | applied
22
- item-003 | ZFGPAYMENTS | FUGR | classic-procedural | rap-behavior | skipped
23
- item-004 | ZPAYMENTS_OLD | PROG | bdc-against-f53 | retire | pending
24
- -->
25
- `;
26
- it('extracts title from manifest', () => {
27
- const out = extractModernizeResult(FULL_FIXTURE);
28
- expect(out.title).toBe('ZDPR_PAYMENTS — modernization');
29
- });
30
- it('extracts detailPath + baselineRef', () => {
31
- const out = extractModernizeResult(FULL_FIXTURE);
32
- expect(out.content.detailPath).toBe('.abapforge/modernize/zdpr-payments/project.json');
33
- expect(out.content.baselineRef.path).toBe('.cspeach/zdpr-payments-cca-assessment-3467-v1.cspeach.json');
34
- expect(out.content.baselineRef.sha256).toBe('a7Qj8ibfLTj49kQRTn1DOE_UAExuoBgjhaXxf33+JPY=');
35
- });
36
- it('extracts transport id', () => {
37
- const out = extractModernizeResult(FULL_FIXTURE);
38
- expect(out.content.transport).toBe('S4HK903400');
39
- });
40
- it('parses transport=- as null', () => {
41
- const xml = FULL_FIXTURE.replace('transport: S4HK903400', 'transport: -');
42
- const out = extractModernizeResult(xml);
43
- expect(out.content.transport).toBeNull();
44
- });
45
- it('extracts summary counts', () => {
46
- const out = extractModernizeResult(FULL_FIXTURE);
47
- expect(out.content.summary).toEqual({
48
- applied: 2,
49
- skipped: 1,
50
- failed: 0,
51
- pending: 1,
52
- });
53
- });
54
- it('extracts modernizations table with from/to architecture', () => {
55
- const out = extractModernizeResult(FULL_FIXTURE);
56
- expect(out.content.modernizations).toHaveLength(4);
57
- expect(out.content.modernizations[0]).toEqual({
58
- id: 'item-001',
59
- objectName: 'ZBDC_VENDOR_PAYMENTS_CLOUD',
60
- objectType: 'FUNC',
61
- fromArchitecture: 'bdc-against-f53',
62
- toArchitecture: 'fm-based-posting',
63
- status: 'applied',
64
- });
65
- expect(out.content.modernizations[2]?.status).toBe('skipped');
66
- expect(out.content.modernizations[3]?.status).toBe('pending');
67
- });
68
- it('emits one InteractionItem per modernization with matching status', () => {
69
- const out = extractModernizeResult(FULL_FIXTURE);
70
- expect(out.items).toHaveLength(4);
71
- expect(out.items.map((i) => i.status)).toEqual(['applied', 'applied', 'skipped', 'pending']);
72
- for (const it of out.items) {
73
- expect(it.answer).toBeNull();
74
- expect(it.comments).toEqual([]);
75
- }
76
- });
77
- it('coerces unknown status values to pending', () => {
78
- const weird = `<!-- csforge:modernize-manifest
79
- artefact: modernize-result
80
- detail_path: .abapforge/foo.json
81
- baseline_path: bar.json
82
- modernizations:
83
- item-001 | ZFOO | PROG | a | b | maybe-applied
84
- -->`;
85
- const out = extractModernizeResult(weird);
86
- expect(out.content.modernizations[0]?.status).toBe('pending');
87
- });
88
- it('throws when artefact field disagrees', () => {
89
- const wrong = `<!-- csforge:modernize-manifest
90
- artefact: upgrade-scan
91
- detail_path: x
92
- baseline_path: y
93
- -->`;
94
- expect(() => extractModernizeResult(wrong)).toThrow(/expected 'modernize-result'/);
95
- });
96
- it('throws when detail_path is missing', () => {
97
- const noDetail = `<!-- csforge:modernize-manifest
98
- artefact: modernize-result
99
- baseline_path: y
100
- -->`;
101
- expect(() => extractModernizeResult(noDetail)).toThrow(/'detail_path'/);
102
- });
103
- it('throws when baseline_path is missing', () => {
104
- const noBaseline = `<!-- csforge:modernize-manifest
105
- artefact: modernize-result
106
- detail_path: x
107
- -->`;
108
- expect(() => extractModernizeResult(noBaseline)).toThrow(/'baseline_path'/);
109
- });
110
- it('throws when no manifest block present', () => {
111
- expect(() => extractModernizeResult('plain markdown')).toThrow(/No <!-- csforge:modernize-manifest --> block/);
112
- });
113
- it('handles a manifest with zero modernizations gracefully', () => {
114
- const empty = `<!-- csforge:modernize-manifest
115
- artefact: modernize-result
116
- detail_path: x
117
- baseline_path: y
118
- applied: 0
119
- skipped: 0
120
- failed: 0
121
- pending: 0
122
- -->`;
123
- const out = extractModernizeResult(empty);
124
- expect(out.content.modernizations).toEqual([]);
125
- expect(out.items).toEqual([]);
126
- });
127
- });