praxis-sec 1.0.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.
Files changed (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +170 -0
  3. package/ai-defense/cost-protection.md +292 -0
  4. package/ai-defense/llm-security-checklist.md +324 -0
  5. package/ai-defense/prompt-injection-patterns.js +283 -0
  6. package/ai-defense/system-prompt-armor.md +327 -0
  7. package/checklists/launch-day.md +168 -0
  8. package/cli/agents/abom-generator.js +225 -0
  9. package/cli/agents/agent-attestation-agent.js +318 -0
  10. package/cli/agents/agent-config-scanner.js +787 -0
  11. package/cli/agents/agent-telemetry-agent.js +415 -0
  12. package/cli/agents/agentic-security-agent.js +296 -0
  13. package/cli/agents/agentic-supply-chain-agent.js +463 -0
  14. package/cli/agents/ai-infra-inventory-agent.js +449 -0
  15. package/cli/agents/api-fuzzer.js +345 -0
  16. package/cli/agents/auth-bypass-agent.js +348 -0
  17. package/cli/agents/base-agent.js +280 -0
  18. package/cli/agents/cicd-scanner.js +300 -0
  19. package/cli/agents/config-auditor.js +757 -0
  20. package/cli/agents/deep-analyzer.js +776 -0
  21. package/cli/agents/endpoint-agent-abuse-agent.js +404 -0
  22. package/cli/agents/exception-handler-agent.js +187 -0
  23. package/cli/agents/git-history-scanner.js +169 -0
  24. package/cli/agents/governance-audits.js +138 -0
  25. package/cli/agents/hermes-security-agent.js +536 -0
  26. package/cli/agents/html-reporter.js +1125 -0
  27. package/cli/agents/index.js +147 -0
  28. package/cli/agents/injection-tester.js +502 -0
  29. package/cli/agents/legal-risk-agent.js +328 -0
  30. package/cli/agents/llm-redteam.js +199 -0
  31. package/cli/agents/managed-agent-scanner.js +333 -0
  32. package/cli/agents/mcp-security-agent.js +588 -0
  33. package/cli/agents/memory-poisoning-agent.js +305 -0
  34. package/cli/agents/mobile-scanner.js +231 -0
  35. package/cli/agents/model-file-scanner.js +259 -0
  36. package/cli/agents/orchestrator.js +355 -0
  37. package/cli/agents/pii-compliance-agent.js +301 -0
  38. package/cli/agents/policy-engine.js +229 -0
  39. package/cli/agents/prompt-injection-prober.js +224 -0
  40. package/cli/agents/rag-security-agent.js +204 -0
  41. package/cli/agents/recon-agent.js +207 -0
  42. package/cli/agents/sbom-generator.js +265 -0
  43. package/cli/agents/scoring-engine.js +273 -0
  44. package/cli/agents/ssrf-prober.js +130 -0
  45. package/cli/agents/stateful-watcher.js +238 -0
  46. package/cli/agents/supabase-rls-agent.js +154 -0
  47. package/cli/agents/supply-chain-agent.js +857 -0
  48. package/cli/agents/swarm-orchestrator.js +200 -0
  49. package/cli/agents/verifier-agent.js +303 -0
  50. package/cli/agents/vibe-coding-agent.js +250 -0
  51. package/cli/bin/praxis.js +866 -0
  52. package/cli/commands/abom.js +73 -0
  53. package/cli/commands/agent-fix.js +1245 -0
  54. package/cli/commands/audit.js +1180 -0
  55. package/cli/commands/autofix.js +383 -0
  56. package/cli/commands/baseline.js +193 -0
  57. package/cli/commands/benchmark.js +327 -0
  58. package/cli/commands/checklist.js +223 -0
  59. package/cli/commands/ci.js +403 -0
  60. package/cli/commands/deps.js +516 -0
  61. package/cli/commands/diff.js +200 -0
  62. package/cli/commands/doctor.js +195 -0
  63. package/cli/commands/env-audit.js +349 -0
  64. package/cli/commands/fix.js +218 -0
  65. package/cli/commands/guard.js +396 -0
  66. package/cli/commands/hooks.js +278 -0
  67. package/cli/commands/init.js +514 -0
  68. package/cli/commands/legal.js +158 -0
  69. package/cli/commands/live-advisories.js +241 -0
  70. package/cli/commands/mcp.js +660 -0
  71. package/cli/commands/openclaw.js +386 -0
  72. package/cli/commands/red-team.js +350 -0
  73. package/cli/commands/redteam.js +78 -0
  74. package/cli/commands/remediate.js +797 -0
  75. package/cli/commands/rotate.js +768 -0
  76. package/cli/commands/rules.js +196 -0
  77. package/cli/commands/scan-mcp.js +534 -0
  78. package/cli/commands/scan-skill.js +588 -0
  79. package/cli/commands/scan-standard.js +251 -0
  80. package/cli/commands/scan.js +524 -0
  81. package/cli/commands/score.js +449 -0
  82. package/cli/commands/shell.js +514 -0
  83. package/cli/commands/team-report.js +398 -0
  84. package/cli/commands/undo.js +161 -0
  85. package/cli/commands/update-intel.js +126 -0
  86. package/cli/commands/vibe-check.js +276 -0
  87. package/cli/commands/watch.js +757 -0
  88. package/cli/commands/web.js +63 -0
  89. package/cli/core/ast/guardrail-detector.js +141 -0
  90. package/cli/core/ast/index.js +22 -0
  91. package/cli/core/ast/parser.js +676 -0
  92. package/cli/core/ast/scope-tree.js +287 -0
  93. package/cli/core/ast/taint-tracker.js +158 -0
  94. package/cli/core/branding.js +37 -0
  95. package/cli/core/env.js +38 -0
  96. package/cli/core/errors.js +61 -0
  97. package/cli/core/fs.js +62 -0
  98. package/cli/core/output/compliance.js +90 -0
  99. package/cli/core/output/html-theme.js +158 -0
  100. package/cli/core/output/index.js +57 -0
  101. package/cli/core/output/json.js +48 -0
  102. package/cli/core/output/sarif.js +240 -0
  103. package/cli/core/version.js +67 -0
  104. package/cli/core/web/jobs.js +183 -0
  105. package/cli/core/web/projects.js +146 -0
  106. package/cli/core/web/server.js +439 -0
  107. package/cli/data/atlas-knowledge.json +5640 -0
  108. package/cli/data/eaa-catalog.json +39 -0
  109. package/cli/data/known-mcps.json +26 -0
  110. package/cli/data/probes/prompt-injection-corpus.json +271 -0
  111. package/cli/data/threat-intel.json +85 -0
  112. package/cli/data/threatpacks/latest.json +41 -0
  113. package/cli/hooks/patterns.js +313 -0
  114. package/cli/hooks/post-tool-use.js +140 -0
  115. package/cli/hooks/pre-tool-use.js +186 -0
  116. package/cli/index.js +90 -0
  117. package/cli/providers/llm-provider.js +766 -0
  118. package/cli/utils/autofix-rules.js +74 -0
  119. package/cli/utils/cache-manager.js +310 -0
  120. package/cli/utils/compliance-map.js +191 -0
  121. package/cli/utils/entropy.js +132 -0
  122. package/cli/utils/fix-ledger.js +127 -0
  123. package/cli/utils/hermes-tool-registry.js +252 -0
  124. package/cli/utils/intel/cache.js +61 -0
  125. package/cli/utils/intel/http.js +88 -0
  126. package/cli/utils/intel/index.js +235 -0
  127. package/cli/utils/intel/merge.js +229 -0
  128. package/cli/utils/intel/sources/epss.js +54 -0
  129. package/cli/utils/intel/sources/ghsa.js +81 -0
  130. package/cli/utils/intel/sources/gitguardian.js +40 -0
  131. package/cli/utils/intel/sources/gitleaks.js +101 -0
  132. package/cli/utils/intel/sources/kev.js +38 -0
  133. package/cli/utils/intel/sources/nvd.js +84 -0
  134. package/cli/utils/intel/sources/osv.js +132 -0
  135. package/cli/utils/intel/sources/phylum.js +44 -0
  136. package/cli/utils/intel/sources/snyk.js +46 -0
  137. package/cli/utils/intel/sources/socket.js +69 -0
  138. package/cli/utils/intel/sources/sonatype.js +84 -0
  139. package/cli/utils/intel/sources/threatpack.js +69 -0
  140. package/cli/utils/mcp-trust.js +60 -0
  141. package/cli/utils/output.js +251 -0
  142. package/cli/utils/patterns.js +1130 -0
  143. package/cli/utils/pdf-generator.js +94 -0
  144. package/cli/utils/plugin-loader.js +364 -0
  145. package/cli/utils/rule-import.js +228 -0
  146. package/cli/utils/rule-registry.js +426 -0
  147. package/cli/utils/scan-fingerprint.js +109 -0
  148. package/cli/utils/scan-playbook.js +312 -0
  149. package/cli/utils/score-history.js +119 -0
  150. package/cli/utils/secrets-verifier.js +247 -0
  151. package/cli/utils/security-memory.js +296 -0
  152. package/cli/utils/standards/atlas-knowledge.js +87 -0
  153. package/cli/utils/standards/index.js +127 -0
  154. package/cli/utils/standards/sources/avid.js +45 -0
  155. package/cli/utils/standards/sources/eu-ai-act.js +89 -0
  156. package/cli/utils/standards/sources/google-saif.js +39 -0
  157. package/cli/utils/standards/sources/iso-42001.js +94 -0
  158. package/cli/utils/standards/sources/mitre-atlas.js +54 -0
  159. package/cli/utils/standards/sources/nist-ai-600-1.js +45 -0
  160. package/cli/utils/standards/sources/owasp-llm.js +45 -0
  161. package/cli/utils/standards/sources/owasp-ml.js +45 -0
  162. package/cli/utils/threat-intel.js +265 -0
  163. package/configs/firebase/firestore-rules.txt +215 -0
  164. package/configs/firebase/security-checklist.md +236 -0
  165. package/configs/firebase/storage-rules.txt +206 -0
  166. package/configs/gitignore-template +258 -0
  167. package/configs/nextjs-security-headers.js +220 -0
  168. package/configs/praxisignore-template +50 -0
  169. package/configs/supabase/secure-client.ts +225 -0
  170. package/configs/supabase/security-checklist.md +278 -0
  171. package/docs/THIRD_PARTY_NOTICES.md +26 -0
  172. package/docs/THREAT_INTEL.md +292 -0
  173. package/docs/USAGE.md +1205 -0
  174. package/docs/design/WEB-UI.md +82 -0
  175. package/package.json +71 -0
  176. package/scripts/check-determinism.mjs +119 -0
  177. package/snippets/README.md +122 -0
  178. package/snippets/api-security/api-security-checklist.md +412 -0
  179. package/snippets/api-security/cors-config.ts +322 -0
  180. package/snippets/api-security/input-validation.ts +430 -0
  181. package/snippets/auth/jwt-checklist.md +322 -0
  182. package/snippets/rate-limiting/nextjs-middleware.ts +211 -0
  183. package/snippets/rate-limiting/upstash-ratelimit.ts +229 -0
@@ -0,0 +1,676 @@
1
+ /**
2
+ * Praxis AST & CST Parser
3
+ * =======================
4
+ *
5
+ * Fast, pure ESM, zero-native-dependency code parser for JavaScript, TypeScript,
6
+ * JSX, TSX, and Python.
7
+ *
8
+ * Designed to provide:
9
+ * 1. Concrete Syntax Tokens (CST): accurate tokenization with line/column positions.
10
+ * 2. Abstract Syntax Tree (AST): functions, classes, variables, assignments, calls,
11
+ * imports, template literals, try/catch, and block scopes.
12
+ * 3. Fault-tolerant (never throws, returns best-effort AST on syntax errors).
13
+ */
14
+
15
+ // =============================================================================
16
+ // TOKEN TYPES & NODE TYPES
17
+ // =============================================================================
18
+
19
+ export const TokenType = {
20
+ KEYWORD: 'KEYWORD',
21
+ IDENTIFIER: 'IDENTIFIER',
22
+ STRING: 'STRING',
23
+ TEMPLATE: 'TEMPLATE',
24
+ NUMBER: 'NUMBER',
25
+ OPERATOR: 'OPERATOR',
26
+ PUNCTUATION: 'PUNCTUATION',
27
+ COMMENT: 'COMMENT',
28
+ WHITESPACE: 'WHITESPACE',
29
+ NEWLINE: 'NEWLINE',
30
+ };
31
+
32
+ export const NodeType = {
33
+ PROGRAM: 'Program',
34
+ FUNCTION_DECLARATION: 'FunctionDeclaration',
35
+ CLASS_DECLARATION: 'ClassDeclaration',
36
+ VARIABLE_DECLARATION: 'VariableDeclaration',
37
+ ASSIGNMENT: 'AssignmentExpression',
38
+ CALL_EXPRESSION: 'CallExpression',
39
+ MEMBER_EXPRESSION: 'MemberExpression',
40
+ TEMPLATE_LITERAL: 'TemplateLiteral',
41
+ IMPORT_DECLARATION: 'ImportDeclaration',
42
+ TRY_STATEMENT: 'TryStatement',
43
+ RETURN_STATEMENT: 'ReturnStatement',
44
+ THROW_STATEMENT: 'ThrowStatement',
45
+ IF_STATEMENT: 'IfStatement',
46
+ BLOCK: 'BlockStatement',
47
+ COMMENT: 'Comment',
48
+ };
49
+
50
+ // =============================================================================
51
+ // TOKENIZER
52
+ // =============================================================================
53
+
54
+ const JS_KEYWORDS = new Set([
55
+ 'async', 'await', 'break', 'case', 'catch', 'class', 'const', 'continue',
56
+ 'debugger', 'default', 'delete', 'do', 'else', 'export', 'extends', 'finally',
57
+ 'for', 'function', 'if', 'import', 'in', 'instanceof', 'let', 'new', 'return',
58
+ 'super', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void', 'while',
59
+ 'with', 'yield', 'from', 'as', 'type', 'interface', 'implements',
60
+ ]);
61
+
62
+ const PY_KEYWORDS = new Set([
63
+ 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def',
64
+ 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if',
65
+ 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise',
66
+ 'return', 'try', 'while', 'with', 'yield',
67
+ ]);
68
+
69
+ export class Tokenizer {
70
+ constructor(code, lang = 'javascript') {
71
+ this.code = code;
72
+ this.lang = lang.toLowerCase();
73
+ this.pos = 0;
74
+ this.line = 1;
75
+ this.col = 1;
76
+ this.tokens = [];
77
+ }
78
+
79
+ tokenize() {
80
+ const len = this.code.length;
81
+ const isPython = this.lang === 'python' || this.lang === 'py';
82
+ const keywords = isPython ? PY_KEYWORDS : JS_KEYWORDS;
83
+
84
+ while (this.pos < len) {
85
+ const char = this.code[this.pos];
86
+ const startLine = this.line;
87
+ const startCol = this.col;
88
+
89
+ // ── 1. Newline ──────────────────────────────────────────────────────────
90
+ if (char === '\n') {
91
+ this.tokens.push({
92
+ type: TokenType.NEWLINE,
93
+ value: '\n',
94
+ line: startLine,
95
+ col: startCol,
96
+ start: this.pos,
97
+ end: this.pos + 1,
98
+ });
99
+ this.pos++;
100
+ this.line++;
101
+ this.col = 1;
102
+ continue;
103
+ }
104
+
105
+ // ── 2. Whitespace ───────────────────────────────────────────────────────
106
+ if (/[ \t\r\f\v]/.test(char)) {
107
+ let val = '';
108
+ const start = this.pos;
109
+ while (this.pos < len && /[ \t\r\f\v]/.test(this.code[this.pos])) {
110
+ val += this.code[this.pos];
111
+ this.pos++;
112
+ this.col++;
113
+ }
114
+ this.tokens.push({
115
+ type: TokenType.WHITESPACE,
116
+ value: val,
117
+ line: startLine,
118
+ col: startCol,
119
+ start,
120
+ end: this.pos,
121
+ });
122
+ continue;
123
+ }
124
+
125
+ // ── 3. Single-line comment ──────────────────────────────────────────────
126
+ if ((!isPython && char === '/' && this.code[this.pos + 1] === '/') ||
127
+ (isPython && char === '#')) {
128
+ let val = '';
129
+ const start = this.pos;
130
+ while (this.pos < len && this.code[this.pos] !== '\n') {
131
+ val += this.code[this.pos];
132
+ this.pos++;
133
+ this.col++;
134
+ }
135
+ this.tokens.push({
136
+ type: TokenType.COMMENT,
137
+ value: val,
138
+ line: startLine,
139
+ col: startCol,
140
+ start,
141
+ end: this.pos,
142
+ });
143
+ continue;
144
+ }
145
+
146
+ // ── 4. Multi-line comment (JS/TS /* ... */) ─────────────────────────────
147
+ if (!isPython && char === '/' && this.code[this.pos + 1] === '*') {
148
+ let val = '/*';
149
+ const start = this.pos;
150
+ this.pos += 2;
151
+ this.col += 2;
152
+ while (this.pos < len && !(this.code[this.pos] === '*' && this.code[this.pos + 1] === '/')) {
153
+ if (this.code[this.pos] === '\n') {
154
+ this.line++;
155
+ this.col = 1;
156
+ } else {
157
+ this.col++;
158
+ }
159
+ val += this.code[this.pos];
160
+ this.pos++;
161
+ }
162
+ if (this.pos < len) {
163
+ val += '*/';
164
+ this.pos += 2;
165
+ this.col += 2;
166
+ }
167
+ this.tokens.push({
168
+ type: TokenType.COMMENT,
169
+ value: val,
170
+ line: startLine,
171
+ col: startCol,
172
+ start,
173
+ end: this.pos,
174
+ });
175
+ continue;
176
+ }
177
+
178
+ // ── 5. Strings (single, double, or python triple-quotes) ─────────────────
179
+ if (char === '"' || char === "'") {
180
+ const quote = char;
181
+ const isTriple = isPython && (this.code.slice(this.pos, this.pos + 3) === quote.repeat(3));
182
+ const quoteStr = isTriple ? quote.repeat(3) : quote;
183
+ let val = quoteStr;
184
+ const start = this.pos;
185
+ this.pos += quoteStr.length;
186
+ this.col += quoteStr.length;
187
+
188
+ while (this.pos < len) {
189
+ if (this.code[this.pos] === '\\') {
190
+ val += this.code[this.pos] + (this.code[this.pos + 1] || '');
191
+ this.pos += 2;
192
+ this.col += 2;
193
+ continue;
194
+ }
195
+ if (isTriple) {
196
+ if (this.code.slice(this.pos, this.pos + 3) === quoteStr) {
197
+ val += quoteStr;
198
+ this.pos += 3;
199
+ this.col += 3;
200
+ break;
201
+ }
202
+ } else if (this.code[this.pos] === quote) {
203
+ val += quote;
204
+ this.pos++;
205
+ this.col++;
206
+ break;
207
+ }
208
+ if (this.code[this.pos] === '\n') {
209
+ this.line++;
210
+ this.col = 1;
211
+ } else {
212
+ this.col++;
213
+ }
214
+ val += this.code[this.pos];
215
+ this.pos++;
216
+ }
217
+
218
+ this.tokens.push({
219
+ type: TokenType.STRING,
220
+ value: val,
221
+ quote: quoteStr,
222
+ line: startLine,
223
+ col: startCol,
224
+ start,
225
+ end: this.pos,
226
+ });
227
+ continue;
228
+ }
229
+
230
+ // ── 6. JS Template Literal (` ... `) ────────────────────────────────────
231
+ if (!isPython && char === '`') {
232
+ let val = '`';
233
+ const start = this.pos;
234
+ this.pos++;
235
+ this.col++;
236
+ while (this.pos < len && this.code[this.pos] !== '`') {
237
+ if (this.code[this.pos] === '\\') {
238
+ val += this.code[this.pos] + (this.code[this.pos + 1] || '');
239
+ this.pos += 2;
240
+ this.col += 2;
241
+ continue;
242
+ }
243
+ if (this.code[this.pos] === '\n') {
244
+ this.line++;
245
+ this.col = 1;
246
+ } else {
247
+ this.col++;
248
+ }
249
+ val += this.code[this.pos];
250
+ this.pos++;
251
+ }
252
+ if (this.pos < len) {
253
+ val += '`';
254
+ this.pos++;
255
+ this.col++;
256
+ }
257
+ this.tokens.push({
258
+ type: TokenType.TEMPLATE,
259
+ value: val,
260
+ line: startLine,
261
+ col: startCol,
262
+ start,
263
+ end: this.pos,
264
+ });
265
+ continue;
266
+ }
267
+
268
+ // ── 7. Numbers ──────────────────────────────────────────────────────────
269
+ if (/[0-9]/.test(char) || (char === '.' && /[0-9]/.test(this.code[this.pos + 1] || ''))) {
270
+ let val = '';
271
+ const start = this.pos;
272
+ while (this.pos < len && /[0-9a-fA-FxXoObB._]/.test(this.code[this.pos])) {
273
+ val += this.code[this.pos];
274
+ this.pos++;
275
+ this.col++;
276
+ }
277
+ this.tokens.push({
278
+ type: TokenType.NUMBER,
279
+ value: val,
280
+ line: startLine,
281
+ col: startCol,
282
+ start,
283
+ end: this.pos,
284
+ });
285
+ continue;
286
+ }
287
+
288
+ // ── 8. Identifiers & Keywords ───────────────────────────────────────────
289
+ if (/[a-zA-Z_$]/.test(char)) {
290
+ let val = '';
291
+ const start = this.pos;
292
+ while (this.pos < len && /[a-zA-Z0-9_$]/.test(this.code[this.pos])) {
293
+ val += this.code[this.pos];
294
+ this.pos++;
295
+ this.col++;
296
+ }
297
+ const isKw = keywords.has(val);
298
+ this.tokens.push({
299
+ type: isKw ? TokenType.KEYWORD : TokenType.IDENTIFIER,
300
+ value: val,
301
+ line: startLine,
302
+ col: startCol,
303
+ start,
304
+ end: this.pos,
305
+ });
306
+ continue;
307
+ }
308
+
309
+ // ── 9. Operators & Punctuation ───────────────────────────────────────────
310
+ const punct2 = this.code.slice(this.pos, this.pos + 2);
311
+ const punct3 = this.code.slice(this.pos, this.pos + 3);
312
+ if (['===', '!==', '...', '>>>', '<<=', '>>='].includes(punct3)) {
313
+ this.tokens.push({ type: TokenType.OPERATOR, value: punct3, line: startLine, col: startCol, start: this.pos, end: this.pos + 3 });
314
+ this.pos += 3;
315
+ this.col += 3;
316
+ continue;
317
+ }
318
+ if (['==', '!=', '<=', '>=', '=>', '&&', '||', '??', '+=', '-=', '*=', '/=', '%=', '**', '->', '::'].includes(punct2)) {
319
+ this.tokens.push({ type: TokenType.OPERATOR, value: punct2, line: startLine, col: startCol, start: this.pos, end: this.pos + 2 });
320
+ this.pos += 2;
321
+ this.col += 2;
322
+ continue;
323
+ }
324
+
325
+ const isOp = /[+\-*/%^&|!~=<>?:@]/.test(char);
326
+ this.tokens.push({
327
+ type: isOp ? TokenType.OPERATOR : TokenType.PUNCTUATION,
328
+ value: char,
329
+ line: startLine,
330
+ col: startCol,
331
+ start: this.pos,
332
+ end: this.pos + 1,
333
+ });
334
+ this.pos++;
335
+ this.col++;
336
+ }
337
+
338
+ return this.tokens;
339
+ }
340
+ }
341
+
342
+ // =============================================================================
343
+ // AST PARSER
344
+ // =============================================================================
345
+
346
+ export class ASTParser {
347
+ /**
348
+ * Parse source code into an AST and token stream.
349
+ *
350
+ * @param {string} code — Source code text
351
+ * @param {string} filePath — File path for language inference
352
+ * @returns {object} — { ast, tokens, lines, comments, lang }
353
+ */
354
+ static parse(code, filePath = '') {
355
+ if (!code || typeof code !== 'string') {
356
+ return { ast: { type: NodeType.PROGRAM, body: [], range: { startLine: 1, endLine: 1 } }, tokens: [], lines: [], comments: [] };
357
+ }
358
+
359
+ const ext = (filePath.split('.').pop() || '').toLowerCase();
360
+ const lang = ['py', 'python'].includes(ext) ? 'python' : 'javascript';
361
+
362
+ const tokenizer = new Tokenizer(code, lang);
363
+ const allTokens = tokenizer.tokenize();
364
+ const meaningfulTokens = allTokens.filter(t => t.type !== TokenType.WHITESPACE);
365
+ const comments = allTokens.filter(t => t.type === TokenType.COMMENT);
366
+ const lines = code.split('\n');
367
+
368
+ const parser = new ASTParserInstance(meaningfulTokens, code, lines, lang);
369
+ const ast = parser.parseProgram();
370
+
371
+ return {
372
+ ast,
373
+ tokens: allTokens,
374
+ lines,
375
+ comments,
376
+ lang,
377
+ };
378
+ }
379
+ }
380
+
381
+ class ASTParserInstance {
382
+ constructor(tokens, code, lines, lang) {
383
+ this.tokens = tokens;
384
+ this.code = code;
385
+ this.lines = lines;
386
+ this.lang = lang;
387
+ this.pos = 0;
388
+ }
389
+
390
+ peek(offset = 0) {
391
+ return this.tokens[this.pos + offset] || null;
392
+ }
393
+
394
+ next() {
395
+ return this.tokens[this.pos++] || null;
396
+ }
397
+
398
+ parseProgram() {
399
+ const body = [];
400
+ while (this.pos < this.tokens.length) {
401
+ const node = this.parseStatement();
402
+ if (node) {
403
+ body.push(node);
404
+ } else {
405
+ this.pos++;
406
+ }
407
+ }
408
+ return {
409
+ type: NodeType.PROGRAM,
410
+ body,
411
+ range: {
412
+ startLine: 1,
413
+ endLine: this.lines.length,
414
+ },
415
+ };
416
+ }
417
+
418
+ parseStatement() {
419
+ const t = this.peek();
420
+ if (!t) return null;
421
+
422
+ // ── Function Declarations (JS: function / async function, Py: def) ────────
423
+ if (t.type === TokenType.KEYWORD && (t.value === 'function' || t.value === 'def')) {
424
+ return this.parseFunctionDeclaration();
425
+ }
426
+ if (t.type === TokenType.KEYWORD && t.value === 'async' && this.peek(1)?.value === 'function') {
427
+ this.next(); // consume async
428
+ return this.parseFunctionDeclaration(true);
429
+ }
430
+ if (t.type === TokenType.KEYWORD && t.value === 'async' && this.peek(1)?.value === 'def') {
431
+ this.next(); // consume async
432
+ return this.parseFunctionDeclaration(true);
433
+ }
434
+
435
+ // ── Class Declarations (class Foo) ───────────────────────────────────────
436
+ if (t.type === TokenType.KEYWORD && t.value === 'class') {
437
+ return this.parseClassDeclaration();
438
+ }
439
+
440
+ // ── Variable Declarations (const / let / var) ───────────────────────────
441
+ if (t.type === TokenType.KEYWORD && ['const', 'let', 'var'].includes(t.value)) {
442
+ return this.parseVariableDeclaration();
443
+ }
444
+
445
+ // ── Import Declarations (import ... from ... / from ... import ...) ─────
446
+ if (t.type === TokenType.KEYWORD && (t.value === 'import' || (this.lang === 'python' && t.value === 'from'))) {
447
+ return this.parseImportDeclaration();
448
+ }
449
+
450
+ // ── Try Statement ────────────────────────────────────────────────────────
451
+ if (t.type === TokenType.KEYWORD && t.value === 'try') {
452
+ return this.parseTryStatement();
453
+ }
454
+
455
+ // ── Return / Throw ───────────────────────────────────────────────────────
456
+ if (t.type === TokenType.KEYWORD && t.value === 'return') {
457
+ return { type: NodeType.RETURN_STATEMENT, line: t.line, col: t.col, token: this.next() };
458
+ }
459
+ if (t.type === TokenType.KEYWORD && (t.value === 'throw' || t.value === 'raise')) {
460
+ return { type: NodeType.THROW_STATEMENT, line: t.line, col: t.col, token: this.next() };
461
+ }
462
+
463
+ // ── Expressions (Calls, Assignments, Member access) ─────────────────────
464
+ if (t.type === TokenType.IDENTIFIER) {
465
+ // Check for Python assignment `x = ...` or JS assignment `x = ...`
466
+ if (this.peek(1)?.value === '=') {
467
+ return this.parseAssignment();
468
+ }
469
+ // Check for function call `foo(...)` or `obj.foo(...)`
470
+ return this.parseExpressionStatement();
471
+ }
472
+
473
+ return null;
474
+ }
475
+
476
+ parseFunctionDeclaration(isAsync = false) {
477
+ const fnKw = this.next(); // function or def
478
+ const nameToken = this.peek()?.type === TokenType.IDENTIFIER ? this.next() : null;
479
+ const name = nameToken ? nameToken.value : '(anonymous)';
480
+ const params = [];
481
+
482
+ // Parse parameter list inside (...)
483
+ if (this.peek()?.value === '(') {
484
+ this.next(); // consume (
485
+ while (this.pos < this.tokens.length && this.peek()?.value !== ')') {
486
+ const paramTok = this.peek();
487
+ if (paramTok?.type === TokenType.IDENTIFIER) {
488
+ params.push(paramTok.value);
489
+ }
490
+ this.next();
491
+ }
492
+ if (this.peek()?.value === ')') this.next(); // consume )
493
+ }
494
+
495
+ // Find function body range
496
+ const startLine = fnKw.line;
497
+ let endLine = startLine;
498
+
499
+ // In JS/TS: look for matching { ... }
500
+ if (this.lang !== 'python') {
501
+ let depth = 0;
502
+ let started = false;
503
+ while (this.pos < this.tokens.length) {
504
+ const tok = this.next();
505
+ if (tok.value === '{') {
506
+ depth++;
507
+ started = true;
508
+ } else if (tok.value === '}') {
509
+ depth--;
510
+ if (started && depth <= 0) {
511
+ endLine = tok.line;
512
+ break;
513
+ }
514
+ }
515
+ }
516
+ } else {
517
+ // In Python: indent-based estimate
518
+ endLine = Math.min(this.lines.length, startLine + 40);
519
+ }
520
+
521
+ return {
522
+ type: NodeType.FUNCTION_DECLARATION,
523
+ name,
524
+ params,
525
+ isAsync,
526
+ range: { startLine, endLine },
527
+ line: startLine,
528
+ col: fnKw.col,
529
+ };
530
+ }
531
+
532
+ parseClassDeclaration() {
533
+ const classKw = this.next();
534
+ const nameToken = this.peek()?.type === TokenType.IDENTIFIER ? this.next() : null;
535
+ const name = nameToken ? nameToken.value : '(anonymous)';
536
+ return {
537
+ type: NodeType.CLASS_DECLARATION,
538
+ name,
539
+ line: classKw.line,
540
+ col: classKw.col,
541
+ range: { startLine: classKw.line, endLine: classKw.line + 30 },
542
+ };
543
+ }
544
+
545
+ parseVariableDeclaration() {
546
+ const kindTok = this.next(); // const, let, var
547
+ const declarations = [];
548
+
549
+ while (this.pos < this.tokens.length) {
550
+ const idTok = this.peek();
551
+ if (idTok?.type !== TokenType.IDENTIFIER) break;
552
+ const id = this.next().value;
553
+ let init = null;
554
+ let isStatic = false;
555
+
556
+ if (this.peek()?.value === '=') {
557
+ this.next(); // consume =
558
+ const valTok = this.peek();
559
+ if (valTok) {
560
+ init = valTok.value;
561
+ if (valTok.type === TokenType.STRING || valTok.type === TokenType.NUMBER) {
562
+ isStatic = true;
563
+ }
564
+ this.next();
565
+ }
566
+ }
567
+
568
+ declarations.push({
569
+ name: id,
570
+ kind: kindTok.value,
571
+ init,
572
+ isStatic,
573
+ line: kindTok.line,
574
+ col: kindTok.col,
575
+ });
576
+
577
+ if (this.peek()?.value === ',') {
578
+ this.next();
579
+ } else {
580
+ break;
581
+ }
582
+ }
583
+
584
+ return {
585
+ type: NodeType.VARIABLE_DECLARATION,
586
+ kind: kindTok.value,
587
+ declarations,
588
+ line: kindTok.line,
589
+ };
590
+ }
591
+
592
+ parseAssignment() {
593
+ const target = this.next().value; // identifier
594
+ this.next(); // consume =
595
+ const valueToken = this.next();
596
+ const isStatic = valueToken?.type === TokenType.STRING || valueToken?.type === TokenType.NUMBER;
597
+
598
+ return {
599
+ type: NodeType.ASSIGNMENT,
600
+ target,
601
+ value: valueToken?.value || null,
602
+ isStatic,
603
+ line: valueToken?.line || 1,
604
+ };
605
+ }
606
+
607
+ parseImportDeclaration() {
608
+ const importKw = this.next();
609
+ let source = '';
610
+ const importedNames = [];
611
+
612
+ while (this.pos < this.tokens.length && this.peek()?.value !== '\n' && this.peek()?.value !== ';') {
613
+ const tok = this.next();
614
+ if (tok.type === TokenType.IDENTIFIER && tok.value !== 'from' && tok.value !== 'as' && tok.value !== 'import') {
615
+ importedNames.push(tok.value);
616
+ } else if (tok.type === TokenType.STRING) {
617
+ source = tok.value.replace(/['"]/g, '');
618
+ }
619
+ }
620
+
621
+ return {
622
+ type: NodeType.IMPORT_DECLARATION,
623
+ source,
624
+ importedNames,
625
+ line: importKw.line,
626
+ };
627
+ }
628
+
629
+ parseTryStatement() {
630
+ const tryKw = this.next();
631
+ return {
632
+ type: NodeType.TRY_STATEMENT,
633
+ line: tryKw.line,
634
+ range: { startLine: tryKw.line, endLine: tryKw.line + 15 },
635
+ };
636
+ }
637
+
638
+ parseExpressionStatement() {
639
+ const firstTok = this.next();
640
+ let callChain = [firstTok.value];
641
+
642
+ while (this.pos < this.tokens.length) {
643
+ const nextTok = this.peek();
644
+ if (nextTok?.value === '.' || nextTok?.value === '?.') {
645
+ this.next(); // consume .
646
+ const propTok = this.next();
647
+ if (propTok) callChain.push(propTok.value);
648
+ } else if (nextTok?.value === '(') {
649
+ // Call expression
650
+ this.next(); // consume (
651
+ const args = [];
652
+ while (this.pos < this.tokens.length && this.peek()?.value !== ')') {
653
+ const argTok = this.next();
654
+ if (argTok && argTok.value !== ',') {
655
+ args.push(argTok.value);
656
+ }
657
+ }
658
+ if (this.peek()?.value === ')') this.next();
659
+
660
+ return {
661
+ type: NodeType.CALL_EXPRESSION,
662
+ callee: callChain.join('.'),
663
+ args,
664
+ line: firstTok.line,
665
+ col: firstTok.col,
666
+ };
667
+ } else {
668
+ break;
669
+ }
670
+ }
671
+
672
+ return null;
673
+ }
674
+ }
675
+
676
+ export default ASTParser;