@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.
@@ -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
- key: 'naming',
39
- heading: 'Naming style',
40
- message: 'Naming style for classes / methods / variables — e.g. "zcl_<area>_<noun>", Hungarian lv_/ls_/lt_ (Enter to skip):',
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
- const src = (await p.input({
150
- key: 'source',
151
- message: 'Path to the standards document (.md or .txt):',
152
- })).trim();
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 fs.readFile(src, 'utf8');
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)}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cspeach/cli",
3
- "version": "1.1.10",
3
+ "version": "1.1.12",
4
4
  "description": "CSPeach — AI-assisted ABAP development CLI. Direct SAP access, 34 skills, safety gates.",
5
5
  "keywords": [
6
6
  "abap",