@cspeach/cli 1.1.10 → 1.1.12
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/loop.js +25 -13
- package/dist/agent/providers/license-gate.js +1 -1
- package/dist/cost/display-mode.js +6 -2
- package/dist/projects/answer-blockers.js +5 -2
- package/dist/projects/workspace.js +41 -2
- package/dist/repl/at-picker.js +42 -5
- package/dist/sap/onboarding.js +1 -1
- package/dist/skills/bundled-skills.js +63 -63
- package/dist/standards/standards-init.js +93 -37
- package/package.json +1 -1
|
@@ -34,47 +34,21 @@ export const STANDARDS_INTERVIEW_QUESTIONS = [
|
|
|
34
34
|
heading: 'Packages for new objects',
|
|
35
35
|
message: 'Package(s) new objects belong in, e.g. ZFI_CORE (Enter to skip):',
|
|
36
36
|
},
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
{
|
|
43
|
-
key: 'forbidden',
|
|
44
|
-
heading: 'Forbidden or discouraged statements',
|
|
45
|
-
message: 'Forbidden or discouraged statements, e.g. "no SELECT *", "no CALL TRANSACTION" (Enter to skip):',
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
key: 'atc_variant',
|
|
49
|
-
heading: 'ATC check variant',
|
|
50
|
-
message: 'ATC check variant name (Enter to skip):',
|
|
51
|
-
},
|
|
37
|
+
// 2026-09-17 (Laeeq): cut from ten questions to four. Naming style,
|
|
38
|
+
// forbidden statements, ATC variant, transport process, header template
|
|
39
|
+
// and unit-test expectation were prose for the model only — nothing read
|
|
40
|
+
// them — and modern ABAP defaults already cover them. Only what the
|
|
41
|
+
// customer alone knows, and that changes the generated code, is asked.
|
|
52
42
|
{
|
|
53
43
|
key: 'clean_core',
|
|
54
44
|
heading: 'Clean core',
|
|
55
45
|
message: 'Clean-core requirement:',
|
|
56
46
|
choices: ['ABAP Cloud only', 'released APIs preferred', 'classic allowed', '(skip)'],
|
|
57
47
|
},
|
|
58
|
-
{
|
|
59
|
-
key: 'transport_process',
|
|
60
|
-
heading: 'Transport / change process',
|
|
61
|
-
message: 'Transport / change process, e.g. "ChaRM, one TR per ticket" (Enter to skip):',
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
key: 'header_template',
|
|
65
|
-
heading: 'Code header template',
|
|
66
|
-
message: 'Required code header / comment template (Enter to skip):',
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
key: 'unit_tests',
|
|
70
|
-
heading: 'Unit tests',
|
|
71
|
-
message: 'Unit-test expectation:',
|
|
72
|
-
choices: ['required for all classes', 'business logic only', 'none', '(skip)'],
|
|
73
|
-
},
|
|
74
48
|
{
|
|
75
49
|
key: 'other',
|
|
76
50
|
heading: 'Anything else',
|
|
77
|
-
message: 'Anything else CSPeach should follow (Enter to skip):',
|
|
51
|
+
message: 'Anything else CSPeach should follow, one line (Enter to skip):',
|
|
78
52
|
},
|
|
79
53
|
];
|
|
80
54
|
/** Header written at the top of an IMPORTED standards document. */
|
|
@@ -121,6 +95,66 @@ export async function writeStandardsFile(cwd, content) {
|
|
|
121
95
|
return homePath;
|
|
122
96
|
}
|
|
123
97
|
}
|
|
98
|
+
/** Sentinel choice in the import dropdown: fall back to a typed path. */
|
|
99
|
+
export const TYPE_A_PATH = '__type_a_path__';
|
|
100
|
+
const STANDARDS_SOURCE_EXTS = ['.md', '.txt', '.docx', '.pdf'];
|
|
101
|
+
const SKIPPED_DIRS = new Set(['node_modules', 'cspeach-out', 'dist', 'build', 'target']);
|
|
102
|
+
function isStandardsSource(name) {
|
|
103
|
+
const lower = name.toLowerCase();
|
|
104
|
+
if (lower === 'cspeach-standards.md')
|
|
105
|
+
return false; // the file we are about to write
|
|
106
|
+
return STANDARDS_SOURCE_EXTS.some((e) => lower.endsWith(e));
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Files a team's standards document could plausibly be, relative to `cwd`:
|
|
110
|
+
* .md/.txt/.docx/.pdf at the top level and one level of subfolders
|
|
111
|
+
* (`docs/abap-guidelines.docx`), sorted by name. Never throws.
|
|
112
|
+
*/
|
|
113
|
+
export async function listStandardsCandidates(cwd) {
|
|
114
|
+
const out = [];
|
|
115
|
+
let entries;
|
|
116
|
+
try {
|
|
117
|
+
entries = await fs.readdir(cwd, { withFileTypes: true });
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
for (const e of entries) {
|
|
123
|
+
if (e.name.startsWith('.'))
|
|
124
|
+
continue;
|
|
125
|
+
if (e.isFile()) {
|
|
126
|
+
if (isStandardsSource(e.name))
|
|
127
|
+
out.push(e.name);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (!e.isDirectory() || SKIPPED_DIRS.has(e.name.toLowerCase()))
|
|
131
|
+
continue;
|
|
132
|
+
let inner;
|
|
133
|
+
try {
|
|
134
|
+
inner = await fs.readdir(path.join(cwd, e.name), { withFileTypes: true });
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
for (const c of inner) {
|
|
140
|
+
if (c.name.startsWith('.') || !c.isFile() || !isStandardsSource(c.name))
|
|
141
|
+
continue;
|
|
142
|
+
out.push(`${e.name}/${c.name}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return out.sort((a, b) => a.localeCompare(b));
|
|
146
|
+
}
|
|
147
|
+
/** Read a standards source as text: Word / PDF through document extraction, otherwise UTF-8. */
|
|
148
|
+
export async function readStandardsSource(src) {
|
|
149
|
+
const lower = src.toLowerCase();
|
|
150
|
+
if (lower.endsWith('.docx') || lower.endsWith('.pdf')) {
|
|
151
|
+
const { extractDocument } = await import('../tools/filesystem/extract-document.js');
|
|
152
|
+
const buf = await fs.readFile(src);
|
|
153
|
+
const res = await extractDocument(buf, lower.endsWith('.pdf') ? '.pdf' : '.docx', { maxPages: 150 });
|
|
154
|
+
return res.text;
|
|
155
|
+
}
|
|
156
|
+
return fs.readFile(src, 'utf8');
|
|
157
|
+
}
|
|
124
158
|
async function defaultPrompts() {
|
|
125
159
|
const { input, select } = await import('@inquirer/prompts');
|
|
126
160
|
return {
|
|
@@ -146,12 +180,34 @@ export async function runStandardsInit(cwd, prompts) {
|
|
|
146
180
|
default: 'defaults',
|
|
147
181
|
});
|
|
148
182
|
if (route === 'import') {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
183
|
+
// 2026-09-17 — offer the files we can see (cwd + one level of
|
|
184
|
+
// subfolders) as a dropdown before falling back to a typed path, and
|
|
185
|
+
// accept Word / PDF standards documents, not only .md/.txt.
|
|
186
|
+
const candidates = await listStandardsCandidates(cwd);
|
|
187
|
+
let src = '';
|
|
188
|
+
if (candidates.length > 0) {
|
|
189
|
+
const picked = await p.select({
|
|
190
|
+
key: 'source-pick',
|
|
191
|
+
message: 'Which file holds your standards?',
|
|
192
|
+
choices: [
|
|
193
|
+
...candidates.map((f) => ({ value: f, name: f })),
|
|
194
|
+
{ value: TYPE_A_PATH, name: 'Type a path…' },
|
|
195
|
+
],
|
|
196
|
+
default: candidates[0],
|
|
197
|
+
});
|
|
198
|
+
// Anything that is not one of the offered files (or the sentinel) falls
|
|
199
|
+
// through to the typed-path prompt — never treat a stray answer as a path.
|
|
200
|
+
if (picked !== TYPE_A_PATH && candidates.includes(picked))
|
|
201
|
+
src = path.resolve(cwd, picked);
|
|
202
|
+
}
|
|
203
|
+
if (src.length === 0) {
|
|
204
|
+
src = (await p.input({
|
|
205
|
+
key: 'source',
|
|
206
|
+
message: 'Path to the standards document (.md, .txt, .docx or .pdf):',
|
|
207
|
+
})).trim();
|
|
208
|
+
}
|
|
153
209
|
try {
|
|
154
|
-
const body = await
|
|
210
|
+
const body = await readStandardsSource(src);
|
|
155
211
|
const content = `${importedStandardsHeader(src)}\n\n${body.trim()}\n`;
|
|
156
212
|
const written = await writeStandardsFile(cwd, content);
|
|
157
213
|
console.log(chalk.green(`✓ Standards imported to ${displayStandardsPath(written, cwd)}`));
|