@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.
- package/dist/agent/intent-system-prompt.js +46 -0
- package/dist/agent/loop.js +180 -18
- package/dist/agent/parallel-write-guard.js +71 -0
- package/dist/agent/repair-partial.js +88 -1
- package/dist/agent/retry-cap.js +121 -0
- package/dist/agent/summarise-via-provider.js +51 -0
- package/dist/approvals/jwt.js +33 -12
- package/dist/commands/auto-compact.js +94 -0
- package/dist/commands/compact.js +265 -0
- package/dist/commands/cost.js +56 -0
- package/dist/commands/help.js +21 -14
- package/dist/config/loader.js +39 -0
- package/dist/cost/cost-log.js +113 -0
- package/dist/cost/pricing.js +91 -0
- package/dist/index.js +0 -0
- package/dist/one-shot.js +1 -1
- package/dist/renderer/markdown.js +7 -1
- package/dist/renderer/status-footer.js +37 -14
- package/dist/renderer/thinking-heartbeat.js +15 -3
- package/dist/renderer/tool-widget.js +6 -1
- package/dist/renderer/tty.js +27 -0
- package/dist/repl/cspeach-shell-detect.js +33 -0
- package/dist/repl/post-turn-status.js +68 -0
- package/dist/repl/slash-completer.js +2 -0
- package/dist/repl/slash-picker.js +2 -0
- package/dist/repl.js +371 -46
- package/dist/sap-errors/parse-adt-exception.js +149 -0
- package/dist/session/schema.js +2 -1
- package/dist/session/store.js +19 -0
- package/dist/tools/_filesystem-shared.js +9 -1
- package/dist/tools/ask-question.js +19 -0
- package/dist/tools/sap-read.js +56 -4
- package/dist/tools/sap-write.js +19 -0
- package/dist/tools/subagent/schedule_draft_create.js +137 -0
- package/dist/ui/alt-screen.js +120 -0
- package/dist/ui/app.js +39 -15
- package/dist/ui/ask-question-emitter.js +13 -0
- package/dist/ui/body.js +39 -23
- package/dist/ui/coaching-picker-classic.js +8 -1
- package/dist/ui/footer.js +6 -1
- package/dist/ui/header.js +19 -4
- package/dist/ui/sap-state-store.js +13 -1
- package/dist/ui/sidebar.js +5 -3
- package/dist/ui/status-emitter.js +19 -0
- package/dist/ui/status-row.js +30 -4
- package/dist/ui/widgets/ask-question-modal.js +132 -0
- package/dist/ui/widgets/coaching-picker.js +55 -8
- package/package.json +86 -82
- package/dist/agent/__tests__/crash-recover.integration.test.js +0 -254
- package/dist/agent/__tests__/loop-save-hook.test.js +0 -188
- package/dist/agent/__tests__/maybe-build-project-context.test.js +0 -247
- package/dist/agent/__tests__/project-context-injection.test.js +0 -161
- package/dist/agent/__tests__/repair-partial.test.js +0 -79
- package/dist/agent/__tests__/retry-key.test.js +0 -77
- package/dist/agent/__tests__/sap-connection-adapter.test.js +0 -132
- package/dist/agent/__tests__/skill-checkpoint.test.js +0 -154
- package/dist/agent/__tests__/turn-assistant-text.test.js +0 -73
- package/dist/agent/__tests__/turn-error-ux.test.js +0 -145
- package/dist/agent/__tests__/turn-stream.test.js +0 -100
- package/dist/agent/__tests__/turn-watchdog.test.js +0 -126
- package/dist/agent/providers/__tests__/ai-hub-provider.test.js +0 -30
- package/dist/agent/providers/__tests__/byok-provider.test.js +0 -32
- package/dist/agent/providers/__tests__/factory.test.js +0 -33
- package/dist/agent/providers/__tests__/local-provider.test.js +0 -55
- package/dist/approvals/__tests__/advisory-prompt.test.js +0 -57
- package/dist/approvals/__tests__/advisory-render.test.js +0 -54
- package/dist/auth/__tests__/auth-file.test.js +0 -42
- package/dist/auth/__tests__/me.test.js +0 -43
- package/dist/classifier/__tests__/client.test.js +0 -70
- package/dist/commands/__tests__/config-set-write-mode.test.js +0 -14
- package/dist/commands/__tests__/login.test.js +0 -152
- package/dist/commands/__tests__/logout.test.js +0 -39
- package/dist/commands/__tests__/project-context-impact.test.js +0 -173
- package/dist/commands/__tests__/spec-gap-status.test.js +0 -31
- package/dist/commands/__tests__/whoami.test.js +0 -91
- package/dist/config/__tests__/llm-config.test.js +0 -28
- package/dist/config/__tests__/shell-exec-config.test.js +0 -76
- package/dist/config/__tests__/write-mode.test.js +0 -36
- package/dist/doctor/__tests__/check-forge-rules.test.js +0 -134
- package/dist/doctor/__tests__/check-llm-mode.test.js +0 -21
- package/dist/doctor/__tests__/check-write-mode.test.js +0 -34
- package/dist/project-context/__tests__/conventions.test.js +0 -221
- package/dist/project-context/__tests__/detect.test.js +0 -177
- package/dist/project-context/__tests__/domain-abap-cloud.test.js +0 -119
- package/dist/project-context/__tests__/domain-abapgit.test.js +0 -161
- package/dist/project-context/__tests__/domain-cap.test.js +0 -168
- package/dist/project-context/__tests__/domain-fiori.test.js +0 -282
- package/dist/project-context/__tests__/git.test.js +0 -131
- package/dist/project-context/__tests__/index-files.test.js +0 -195
- package/dist/project-context/__tests__/index.test.js +0 -133
- package/dist/project-context/__tests__/render.test.js +0 -322
- package/dist/project-context/__tests__/types.test.js +0 -73
- package/dist/projects/__tests__/build.test.js +0 -52
- package/dist/projects/__tests__/canonicalize.test.js +0 -47
- package/dist/projects/__tests__/email-template.test.js +0 -71
- package/dist/projects/__tests__/expand-text-attachments.test.js +0 -105
- package/dist/projects/__tests__/extract-cca.test.js +0 -141
- package/dist/projects/__tests__/extract-design.test.js +0 -62
- package/dist/projects/__tests__/extract-estimate.test.js +0 -58
- package/dist/projects/__tests__/extract-modernize.test.js +0 -127
- package/dist/projects/__tests__/extract-spec-gap.test.js +0 -122
- package/dist/projects/__tests__/extract-test-coverage.test.js +0 -133
- package/dist/projects/__tests__/filename.test.js +0 -32
- package/dist/projects/__tests__/promote-command.test.js +0 -170
- package/dist/projects/__tests__/promote.test.js +0 -181
- package/dist/projects/__tests__/save-command.test.js +0 -216
- package/dist/projects/__tests__/save.test.js +0 -41
- package/dist/projects/__tests__/status.test.js +0 -208
- package/dist/projects/__tests__/types.test.js +0 -41
- package/dist/projects/__tests__/validate.test.js +0 -165
- package/dist/projects/__tests__/workspace.test.js +0 -337
- package/dist/repl/__tests__/current-transport.test.js +0 -43
- package/dist/repl/__tests__/diff-display.test.js +0 -59
- package/dist/repl/__tests__/file-picker.test.js +0 -97
- package/dist/repl/__tests__/rule8-detector.test.js +0 -114
- package/dist/repl/__tests__/safety-confirm.test.js +0 -130
- package/dist/repl/__tests__/safety-mode-state.test.js +0 -42
- package/dist/sap/__tests__/system-info.test.js +0 -344
- package/dist/session/__tests__/store-discovery.test.js +0 -114
- package/dist/session/__tests__/time-ago.test.js +0 -53
- package/dist/skills/__tests__/canonical.test.js +0 -20
- package/dist/skills/__tests__/manifest-client.test.js +0 -88
- package/dist/skills/__tests__/promotion-dispatch.test.js +0 -28
- package/dist/skills/__tests__/source-managed.test.js +0 -60
- package/dist/tools/__tests__/_background-shared.test.js +0 -69
- package/dist/tools/__tests__/_command-shared.test.js +0 -59
- package/dist/tools/__tests__/_filesystem-shared.test.js +0 -42
- package/dist/tools/__tests__/_flag.test.js +0 -66
- package/dist/tools/__tests__/_helpers.js +0 -17
- package/dist/tools/__tests__/_web-shared.test.js +0 -143
- package/dist/tools/__tests__/agent_run.test.js +0 -166
- package/dist/tools/__tests__/approval-advisory.test.js +0 -66
- package/dist/tools/__tests__/background_run.test.js +0 -113
- package/dist/tools/__tests__/convention_get.test.js +0 -53
- package/dist/tools/__tests__/file-edit.test.js +0 -72
- package/dist/tools/__tests__/file-read.test.js +0 -81
- package/dist/tools/__tests__/file-write.test.js +0 -68
- package/dist/tools/__tests__/glob.test.js +0 -128
- package/dist/tools/__tests__/grep.test.js +0 -71
- package/dist/tools/__tests__/monitor_emit.test.js +0 -96
- package/dist/tools/__tests__/playbook_get.test.js +0 -109
- package/dist/tools/__tests__/project_context_get.test.js +0 -71
- package/dist/tools/__tests__/registry-category.test.js +0 -60
- package/dist/tools/__tests__/sap-read-extras.test.js +0 -196
- package/dist/tools/__tests__/sap-write-extras.test.js +0 -573
- package/dist/tools/__tests__/schedule_create.test.js +0 -85
- package/dist/tools/__tests__/shell_exec.test.js +0 -135
- package/dist/tools/__tests__/transport-safety.test.js +0 -106
- package/dist/tools/__tests__/update-method-intercept.test.js +0 -128
- package/dist/tools/__tests__/web_fetch.test.js +0 -329
- package/dist/tools/__tests__/web_search.test.js +0 -215
- package/dist/tools/__tests__/write-mode.test.js +0 -32
- 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
|
-
});
|