@cspeach/cli 0.6.4 → 0.6.5

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 (107) hide show
  1. package/dist/index.js +0 -0
  2. package/package.json +86 -82
  3. package/dist/agent/__tests__/crash-recover.integration.test.js +0 -254
  4. package/dist/agent/__tests__/loop-save-hook.test.js +0 -188
  5. package/dist/agent/__tests__/maybe-build-project-context.test.js +0 -247
  6. package/dist/agent/__tests__/project-context-injection.test.js +0 -161
  7. package/dist/agent/__tests__/repair-partial.test.js +0 -79
  8. package/dist/agent/__tests__/retry-key.test.js +0 -77
  9. package/dist/agent/__tests__/sap-connection-adapter.test.js +0 -132
  10. package/dist/agent/__tests__/skill-checkpoint.test.js +0 -154
  11. package/dist/agent/__tests__/turn-assistant-text.test.js +0 -73
  12. package/dist/agent/__tests__/turn-error-ux.test.js +0 -145
  13. package/dist/agent/__tests__/turn-stream.test.js +0 -100
  14. package/dist/agent/__tests__/turn-watchdog.test.js +0 -126
  15. package/dist/agent/providers/__tests__/ai-hub-provider.test.js +0 -30
  16. package/dist/agent/providers/__tests__/byok-provider.test.js +0 -32
  17. package/dist/agent/providers/__tests__/factory.test.js +0 -33
  18. package/dist/agent/providers/__tests__/local-provider.test.js +0 -55
  19. package/dist/approvals/__tests__/advisory-prompt.test.js +0 -57
  20. package/dist/approvals/__tests__/advisory-render.test.js +0 -54
  21. package/dist/auth/__tests__/auth-file.test.js +0 -42
  22. package/dist/auth/__tests__/me.test.js +0 -43
  23. package/dist/classifier/__tests__/client.test.js +0 -70
  24. package/dist/commands/__tests__/config-set-write-mode.test.js +0 -14
  25. package/dist/commands/__tests__/login.test.js +0 -152
  26. package/dist/commands/__tests__/logout.test.js +0 -39
  27. package/dist/commands/__tests__/project-context-impact.test.js +0 -173
  28. package/dist/commands/__tests__/spec-gap-status.test.js +0 -31
  29. package/dist/commands/__tests__/whoami.test.js +0 -91
  30. package/dist/config/__tests__/llm-config.test.js +0 -28
  31. package/dist/config/__tests__/shell-exec-config.test.js +0 -76
  32. package/dist/config/__tests__/write-mode.test.js +0 -36
  33. package/dist/doctor/__tests__/check-forge-rules.test.js +0 -134
  34. package/dist/doctor/__tests__/check-llm-mode.test.js +0 -21
  35. package/dist/doctor/__tests__/check-write-mode.test.js +0 -34
  36. package/dist/project-context/__tests__/conventions.test.js +0 -221
  37. package/dist/project-context/__tests__/detect.test.js +0 -177
  38. package/dist/project-context/__tests__/domain-abap-cloud.test.js +0 -119
  39. package/dist/project-context/__tests__/domain-abapgit.test.js +0 -161
  40. package/dist/project-context/__tests__/domain-cap.test.js +0 -168
  41. package/dist/project-context/__tests__/domain-fiori.test.js +0 -282
  42. package/dist/project-context/__tests__/git.test.js +0 -131
  43. package/dist/project-context/__tests__/index-files.test.js +0 -195
  44. package/dist/project-context/__tests__/index.test.js +0 -133
  45. package/dist/project-context/__tests__/render.test.js +0 -322
  46. package/dist/project-context/__tests__/types.test.js +0 -73
  47. package/dist/projects/__tests__/build.test.js +0 -52
  48. package/dist/projects/__tests__/canonicalize.test.js +0 -47
  49. package/dist/projects/__tests__/email-template.test.js +0 -71
  50. package/dist/projects/__tests__/expand-text-attachments.test.js +0 -105
  51. package/dist/projects/__tests__/extract-cca.test.js +0 -141
  52. package/dist/projects/__tests__/extract-design.test.js +0 -62
  53. package/dist/projects/__tests__/extract-estimate.test.js +0 -58
  54. package/dist/projects/__tests__/extract-modernize.test.js +0 -127
  55. package/dist/projects/__tests__/extract-spec-gap.test.js +0 -122
  56. package/dist/projects/__tests__/extract-test-coverage.test.js +0 -133
  57. package/dist/projects/__tests__/filename.test.js +0 -32
  58. package/dist/projects/__tests__/promote-command.test.js +0 -170
  59. package/dist/projects/__tests__/promote.test.js +0 -181
  60. package/dist/projects/__tests__/save-command.test.js +0 -216
  61. package/dist/projects/__tests__/save.test.js +0 -41
  62. package/dist/projects/__tests__/status.test.js +0 -208
  63. package/dist/projects/__tests__/types.test.js +0 -41
  64. package/dist/projects/__tests__/validate.test.js +0 -165
  65. package/dist/projects/__tests__/workspace.test.js +0 -337
  66. package/dist/repl/__tests__/current-transport.test.js +0 -43
  67. package/dist/repl/__tests__/diff-display.test.js +0 -59
  68. package/dist/repl/__tests__/file-picker.test.js +0 -97
  69. package/dist/repl/__tests__/rule8-detector.test.js +0 -114
  70. package/dist/repl/__tests__/safety-confirm.test.js +0 -130
  71. package/dist/repl/__tests__/safety-mode-state.test.js +0 -42
  72. package/dist/sap/__tests__/system-info.test.js +0 -344
  73. package/dist/session/__tests__/store-discovery.test.js +0 -114
  74. package/dist/session/__tests__/time-ago.test.js +0 -53
  75. package/dist/skills/__tests__/canonical.test.js +0 -20
  76. package/dist/skills/__tests__/manifest-client.test.js +0 -88
  77. package/dist/skills/__tests__/promotion-dispatch.test.js +0 -28
  78. package/dist/skills/__tests__/source-managed.test.js +0 -60
  79. package/dist/tools/__tests__/_background-shared.test.js +0 -69
  80. package/dist/tools/__tests__/_command-shared.test.js +0 -59
  81. package/dist/tools/__tests__/_filesystem-shared.test.js +0 -42
  82. package/dist/tools/__tests__/_flag.test.js +0 -66
  83. package/dist/tools/__tests__/_helpers.js +0 -17
  84. package/dist/tools/__tests__/_web-shared.test.js +0 -143
  85. package/dist/tools/__tests__/agent_run.test.js +0 -166
  86. package/dist/tools/__tests__/approval-advisory.test.js +0 -66
  87. package/dist/tools/__tests__/background_run.test.js +0 -113
  88. package/dist/tools/__tests__/convention_get.test.js +0 -53
  89. package/dist/tools/__tests__/file-edit.test.js +0 -72
  90. package/dist/tools/__tests__/file-read.test.js +0 -81
  91. package/dist/tools/__tests__/file-write.test.js +0 -68
  92. package/dist/tools/__tests__/glob.test.js +0 -128
  93. package/dist/tools/__tests__/grep.test.js +0 -71
  94. package/dist/tools/__tests__/monitor_emit.test.js +0 -96
  95. package/dist/tools/__tests__/playbook_get.test.js +0 -109
  96. package/dist/tools/__tests__/project_context_get.test.js +0 -71
  97. package/dist/tools/__tests__/registry-category.test.js +0 -60
  98. package/dist/tools/__tests__/sap-read-extras.test.js +0 -196
  99. package/dist/tools/__tests__/sap-write-extras.test.js +0 -573
  100. package/dist/tools/__tests__/schedule_create.test.js +0 -85
  101. package/dist/tools/__tests__/shell_exec.test.js +0 -135
  102. package/dist/tools/__tests__/transport-safety.test.js +0 -106
  103. package/dist/tools/__tests__/update-method-intercept.test.js +0 -128
  104. package/dist/tools/__tests__/web_fetch.test.js +0 -329
  105. package/dist/tools/__tests__/web_search.test.js +0 -215
  106. package/dist/tools/__tests__/write-mode.test.js +0 -32
  107. 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
- });