@chem-x/starter-kit 26.9.28-1234 → 26.10.4-235

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/cli/languages.js CHANGED
@@ -76,6 +76,14 @@ export const LANGUAGE_DEFINITIONS = {
76
76
  parser: 'jvm',
77
77
  commentPrefix: '//',
78
78
  blockComment: { start: '/*', end: '*/' }
79
+ },
80
+ cpp: {
81
+ id: 'cpp',
82
+ name: 'C / C++',
83
+ extensions: new Set(['.c', '.h', '.cpp', '.cc', '.cxx', '.hpp', '.hxx', '.hh', '.inl']),
84
+ parser: 'cpp',
85
+ commentPrefix: '//',
86
+ blockComment: { start: '/*', end: '*/' }
79
87
  }
80
88
  };
81
89
 
@@ -42,3 +42,24 @@ test('languages: resolves language metadata', () => {
42
42
  assert.equal(py.id, 'python');
43
43
  assert.equal(py.commentPrefix, '#');
44
44
  });
45
+
46
+ test('languages: recognizes C and C++ source extensions', () => {
47
+ assert.equal(isSourceFile('StreamDecoder.cpp'), true);
48
+ assert.equal(isSourceFile('StreamDecoder.cc'), true);
49
+ assert.equal(isSourceFile('StreamDecoder.cxx'), true);
50
+ assert.equal(isSourceFile('StreamDecoder.hpp'), true);
51
+ assert.equal(isSourceFile('StreamDecoder.h'), true);
52
+ assert.equal(isSourceFile('legacy_shim.c'), true);
53
+ });
54
+
55
+ test('languages: maps C and C++ extensions to the cpp language', () => {
56
+ assert.equal(getLanguageForFile('StreamDecoder.cpp').id, 'cpp');
57
+ assert.equal(getLanguageForFile('StreamDecoder.h').id, 'cpp');
58
+ assert.equal(getLanguageForFile('legacy_shim.c').id, 'cpp');
59
+ assert.equal(LANGUAGE_DEFINITIONS.cpp.name, 'C / C++');
60
+ });
61
+
62
+ test('languages: C and C++ are not babel parsable', () => {
63
+ assert.equal(isBabelParsable('StreamDecoder.cpp'), false);
64
+ assert.equal(isBabelParsable('StreamDecoder.h'), false);
65
+ });
@@ -109,3 +109,114 @@ func TestDummy(t *testing.T) {
109
109
  assert.equal(goViolations.some((v) => v.rule === 'SYNTAX_PARSE_ERROR'), false);
110
110
  assert.equal(goViolations.some((v) => v.rule === 'SYNTHETIC_MOCK_DATA'), true);
111
111
  });
112
+
113
+ test('polyglot: extracts C++ classes, structs, functions and includes', () => {
114
+ const cppCode = `
115
+ #include <memory>
116
+ #include "vendor/codec.h"
117
+
118
+ namespace audio {
119
+
120
+ struct FrameHeader {
121
+ uint32_t size;
122
+ };
123
+
124
+ enum class Codec { Opus, Flac };
125
+
126
+ class StreamDecoder : public IDecoder {
127
+ public:
128
+ bool DecodeFrame(const FrameHeader& header);
129
+ };
130
+
131
+ bool StreamDecoder::DecodeFrame(const FrameHeader& header) {
132
+ return header.size > 0;
133
+ }
134
+
135
+ } // namespace audio
136
+ `;
137
+
138
+ const meta = extractAstMetadata(cppCode, 'src/audio/StreamDecoder.cpp');
139
+ const names = meta.symbols.map((s) => s.name);
140
+ assert.ok(names.includes('StreamDecoder'), 'must extract class StreamDecoder');
141
+ assert.ok(names.includes('FrameHeader'), 'must extract struct FrameHeader');
142
+ assert.ok(names.includes('Codec'), 'must extract enum class Codec');
143
+ assert.ok(names.includes('DecodeFrame'), 'must extract method DecodeFrame');
144
+ assert.ok(meta.imports.some((i) => i.sourceModule === 'vendor/codec.h'), 'must map #include to imports');
145
+ assert.ok(meta.imports.some((i) => i.sourceModule === 'memory'), 'must map angle-bracket includes');
146
+ });
147
+
148
+ test('polyglot: audits C++ without Babel syntax errors', () => {
149
+ const cppCode = `
150
+ #include <string>
151
+ void Connect() {
152
+ std::string email = "user@example.com";
153
+ }
154
+ `;
155
+ const violations = auditCode(cppCode, 'src/net/Client.cpp', 'src/net/Client.cpp');
156
+ assert.equal(violations.some((v) => v.rule === 'SYNTAX_PARSE_ERROR'), false, 'C++ must not trigger Babel SYNTAX_PARSE_ERROR');
157
+ assert.equal(violations.some((v) => v.rule === 'SYNTHETIC_MOCK_DATA'), true, 'must detect placeholder email in C++');
158
+ });
159
+
160
+ test('polyglot: extracts Rust items and use imports', () => {
161
+ const rustCode = `
162
+ use std::sync::Arc;
163
+
164
+ pub struct Decoder {
165
+ buffer: Vec<u8>,
166
+ }
167
+
168
+ pub enum State { Idle, Running }
169
+
170
+ pub trait Sink {
171
+ fn write(&self, data: &[u8]);
172
+ }
173
+
174
+ impl Decoder {
175
+ pub fn new() -> Self { Decoder { buffer: Vec::new() } }
176
+ }
177
+ `;
178
+ const meta = extractAstMetadata(rustCode, 'src/decoder.rs');
179
+ const names = meta.symbols.map((s) => s.name);
180
+ assert.ok(names.includes('Decoder'), 'must extract pub struct');
181
+ assert.ok(names.includes('State'), 'must extract pub enum');
182
+ assert.ok(names.includes('Sink'), 'must extract pub trait');
183
+ assert.ok(names.includes('new'), 'must extract fn');
184
+ assert.ok(meta.imports.some((i) => i.sourceModule === 'std::sync::Arc'), 'must map use statements');
185
+ });
186
+
187
+ test('polyglot: extracts Kotlin declarations and imports', () => {
188
+ const kotlinCode = `
189
+ import kotlinx.coroutines.flow.Flow
190
+
191
+ class ConsentRepository(private val api: Api) {
192
+ fun observe(): Flow<List<Consent>> = api.stream()
193
+ }
194
+
195
+ data class Consent(val id: String)
196
+
197
+ object Registry {
198
+ fun lookup(id: String) = id
199
+ }
200
+ `;
201
+ const meta = extractAstMetadata(kotlinCode, 'src/main/kotlin/ConsentRepository.kt');
202
+ const names = meta.symbols.map((s) => s.name);
203
+ assert.ok(names.includes('ConsentRepository'), 'must extract class');
204
+ assert.ok(names.includes('Consent'), 'must extract data class');
205
+ assert.ok(names.includes('Registry'), 'must extract object');
206
+ assert.ok(names.includes('observe'), 'must extract fun');
207
+ assert.ok(meta.imports.some((i) => i.sourceModule === 'kotlinx.coroutines.flow.Flow'), 'must map kotlin imports');
208
+ });
209
+
210
+ test('polyglot: declaration keywords never leak in as symbol names', () => {
211
+ const cppCode = `
212
+ enum class Codec { Opus, Flac };
213
+ struct Frame { int size; };
214
+ namespace audio { }
215
+ `;
216
+ const meta = extractAstMetadata(cppCode, 'src/audio/Codec.h');
217
+ const names = meta.symbols.map((s) => s.name);
218
+ assert.equal(names.includes('class'), false, 'bare "class" must not be extracted as a symbol');
219
+ assert.equal(names.includes('struct'), false, 'bare "struct" must not be extracted as a symbol');
220
+ assert.ok(names.includes('Codec'));
221
+ assert.ok(names.includes('Frame'));
222
+ });
package/cli/search-ast.js CHANGED
@@ -21,6 +21,17 @@ export const resolveArchitectureTier = (relativePath) => {
21
21
  return 'utility';
22
22
  };
23
23
 
24
+ const CPP_NON_DECLARATION_KEYWORDS = new Set([
25
+ 'if', 'for', 'while', 'switch', 'catch', 'return', 'sizeof', 'else', 'do', 'throw', 'new', 'delete'
26
+ ]);
27
+
28
+ // Declaration keywords are never symbol names. Guards cross-language regex overlap,
29
+ // e.g. the Rust `enum` pattern matching C++ `enum class Codec` and capturing `class`.
30
+ const RESERVED_SYMBOL_NAMES = new Set([
31
+ 'class', 'struct', 'enum', 'union', 'interface', 'object', 'fun', 'trait', 'impl', 'mod',
32
+ 'namespace', 'typename', 'template', 'public', 'private', 'protected', 'static', 'const'
33
+ ]);
34
+
24
35
  const extractRegexFallback = (content, filePath = '') => {
25
36
  const symbols = [];
26
37
  const imports = [];
@@ -47,6 +58,49 @@ const extractRegexFallback = (content, filePath = '') => {
47
58
  symbols.push({ name: m[1], kind: 'function', isExport: true, startLine: 1, endLine: 1, signature: '' });
48
59
  }
49
60
 
61
+ const cppTypeMatches = content.matchAll(/\b(?:class|struct|union|enum(?:\s+class)?)\s+([A-Za-z_][A-Za-z0-9_]*)/g);
62
+ for (const m of cppTypeMatches) {
63
+ if (!RESERVED_SYMBOL_NAMES.has(m[1])) {
64
+ symbols.push({ name: m[1], kind: 'class', isExport: true, startLine: 1, endLine: 1, signature: '' });
65
+ }
66
+ }
67
+
68
+ const cppQualifiedMatches = content.matchAll(/\b[A-Za-z_][A-Za-z0-9_]*\s*::\s*([A-Za-z_~][A-Za-z0-9_]*)\s*\(/g);
69
+ for (const m of cppQualifiedMatches) {
70
+ symbols.push({ name: m[1], kind: 'function', isExport: true, startLine: 1, endLine: 1, signature: '' });
71
+ }
72
+
73
+ const cppDeclMatches = content.matchAll(/^[ \t]*(?:[A-Za-z_][A-Za-z0-9_:<>,*& \t]*?)\s+\*?([A-Za-z_][A-Za-z0-9_]*)\s*\([^)]*\)\s*(?:const\s*)?[;{]/gm);
74
+ for (const m of cppDeclMatches) {
75
+ if (!CPP_NON_DECLARATION_KEYWORDS.has(m[1])) {
76
+ symbols.push({ name: m[1], kind: 'function', isExport: true, startLine: 1, endLine: 1, signature: '' });
77
+ }
78
+ }
79
+
80
+ const rustMatches = content.matchAll(/\b(?:pub\s+)?(?:struct|enum|trait|impl|mod|fn)\s+([A-Za-z_][A-Za-z0-9_]*)/g);
81
+ for (const m of rustMatches) {
82
+ if (!RESERVED_SYMBOL_NAMES.has(m[1])) {
83
+ symbols.push({ name: m[1], kind: 'symbol', isExport: true, startLine: 1, endLine: 1, signature: '' });
84
+ }
85
+ }
86
+
87
+ const kotlinMatches = content.matchAll(/\b(?:data\s+|sealed\s+|open\s+|abstract\s+|inner\s+)?(?:class|object|interface|fun)\s+([A-Za-z_][A-Za-z0-9_]*)/g);
88
+ for (const m of kotlinMatches) {
89
+ if (!RESERVED_SYMBOL_NAMES.has(m[1])) {
90
+ symbols.push({ name: m[1], kind: 'symbol', isExport: true, startLine: 1, endLine: 1, signature: '' });
91
+ }
92
+ }
93
+
94
+ const cppIncludes = content.matchAll(/#include\s*[<"]([^>"]+)[>"]/g);
95
+ for (const m of cppIncludes) {
96
+ imports.push({ importedSymbol: '*', sourceModule: m[1], line: 1 });
97
+ }
98
+
99
+ const rustUses = content.matchAll(/\buse\s+([A-Za-z_][A-Za-z0-9_]*(?:::[A-Za-z_][A-Za-z0-9_]*)+)/g);
100
+ for (const m of rustUses) {
101
+ imports.push({ importedSymbol: '*', sourceModule: m[1], line: 1 });
102
+ }
103
+
50
104
  const csImports = content.matchAll(/using\s+([A-Za-z0-9_.]+);/g);
51
105
  for (const m of csImports) {
52
106
  imports.push({ importedSymbol: '*', sourceModule: m[1], line: 1 });
package/cli/search.js CHANGED
@@ -36,6 +36,7 @@ import {
36
36
  import { runGenerateWizard } from './generator.js';
37
37
  import { runMutatorCli } from './mutators.js';
38
38
  import { toColumnar } from './columnar.js';
39
+ import { resolveTargetDir } from './path-scope.js';
39
40
  import { ANSI } from './theme.js';
40
41
 
41
42
  export { toColumnar, fromColumnar } from './columnar.js';
@@ -207,7 +208,7 @@ const formatTierBadge = (tier) => {
207
208
  return map[tier] || `${ANSI.DIM}[${tier}]${ANSI.RESET}`;
208
209
  };
209
210
 
210
- export { resolveTargetDir } from './path-scope.js';
211
+ export { resolveTargetDir };
211
212
 
212
213
  export const printSearchHelp = () => {
213
214
  const BOLD = '\x1b[1m';
@@ -277,3 +277,18 @@ test('hybrid search: natural language "toggle a task item" ranks useTaskListCont
277
277
  } catch {}
278
278
  });
279
279
 
280
+
281
+ test('runSearch: resolveTargetDir is bound in module scope, not only re-exported', async () => {
282
+ const { runSearch } = await import('./search.js');
283
+ let caught = null;
284
+ try {
285
+ await runSearch(['__chemx_nonexistent_symbol__'], false);
286
+ } catch (err) {
287
+ caught = err;
288
+ }
289
+ assert.equal(
290
+ caught instanceof ReferenceError,
291
+ false,
292
+ `runSearch threw a ReferenceError: ${caught && caught.message}`
293
+ );
294
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chem-x/starter-kit",
3
- "version": "26.9.28-1234",
3
+ "version": "26.10.4-235",
4
4
  "description": "Chemical X Protocol: Private drop-in architecture starter kit and capsule generator",
5
5
  "type": "module",
6
6
  "bin": {