devsquad 3.6.0__py3-none-any.whl

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 (95) hide show
  1. devsquad-3.6.0.dist-info/METADATA +944 -0
  2. devsquad-3.6.0.dist-info/RECORD +95 -0
  3. devsquad-3.6.0.dist-info/WHEEL +5 -0
  4. devsquad-3.6.0.dist-info/entry_points.txt +2 -0
  5. devsquad-3.6.0.dist-info/licenses/LICENSE +21 -0
  6. devsquad-3.6.0.dist-info/top_level.txt +2 -0
  7. scripts/__init__.py +0 -0
  8. scripts/ai_semantic_matcher.py +512 -0
  9. scripts/alert_manager.py +505 -0
  10. scripts/api/__init__.py +43 -0
  11. scripts/api/models.py +386 -0
  12. scripts/api/routes/__init__.py +20 -0
  13. scripts/api/routes/dispatch.py +348 -0
  14. scripts/api/routes/lifecycle.py +330 -0
  15. scripts/api/routes/metrics_gates.py +347 -0
  16. scripts/api_server.py +318 -0
  17. scripts/auth.py +451 -0
  18. scripts/cli/__init__.py +1 -0
  19. scripts/cli/cli_visual.py +642 -0
  20. scripts/cli.py +1094 -0
  21. scripts/collaboration/__init__.py +212 -0
  22. scripts/collaboration/_version.py +1 -0
  23. scripts/collaboration/agent_briefing.py +656 -0
  24. scripts/collaboration/ai_semantic_matcher.py +260 -0
  25. scripts/collaboration/anchor_checker.py +281 -0
  26. scripts/collaboration/anti_rationalization.py +470 -0
  27. scripts/collaboration/async_integration_example.py +255 -0
  28. scripts/collaboration/batch_scheduler.py +149 -0
  29. scripts/collaboration/checkpoint_manager.py +561 -0
  30. scripts/collaboration/ci_feedback_adapter.py +351 -0
  31. scripts/collaboration/code_map_generator.py +247 -0
  32. scripts/collaboration/concern_pack_loader.py +352 -0
  33. scripts/collaboration/confidence_score.py +496 -0
  34. scripts/collaboration/config_loader.py +188 -0
  35. scripts/collaboration/consensus.py +244 -0
  36. scripts/collaboration/context_compressor.py +533 -0
  37. scripts/collaboration/coordinator.py +668 -0
  38. scripts/collaboration/dispatcher.py +1636 -0
  39. scripts/collaboration/dual_layer_context.py +128 -0
  40. scripts/collaboration/enhanced_worker.py +539 -0
  41. scripts/collaboration/feature_usage_tracker.py +206 -0
  42. scripts/collaboration/five_axis_consensus.py +334 -0
  43. scripts/collaboration/input_validator.py +401 -0
  44. scripts/collaboration/integration_example.py +287 -0
  45. scripts/collaboration/intent_workflow_mapper.py +350 -0
  46. scripts/collaboration/language_parsers.py +269 -0
  47. scripts/collaboration/lifecycle_protocol.py +1446 -0
  48. scripts/collaboration/llm_backend.py +453 -0
  49. scripts/collaboration/llm_cache.py +448 -0
  50. scripts/collaboration/llm_cache_async.py +347 -0
  51. scripts/collaboration/llm_retry.py +387 -0
  52. scripts/collaboration/llm_retry_async.py +389 -0
  53. scripts/collaboration/mce_adapter.py +597 -0
  54. scripts/collaboration/memory_bridge.py +1607 -0
  55. scripts/collaboration/models.py +537 -0
  56. scripts/collaboration/null_providers.py +297 -0
  57. scripts/collaboration/operation_classifier.py +289 -0
  58. scripts/collaboration/output_slicer.py +225 -0
  59. scripts/collaboration/performance_monitor.py +462 -0
  60. scripts/collaboration/permission_guard.py +865 -0
  61. scripts/collaboration/prompt_assembler.py +756 -0
  62. scripts/collaboration/prompt_variant_generator.py +483 -0
  63. scripts/collaboration/protocols.py +267 -0
  64. scripts/collaboration/report_formatter.py +352 -0
  65. scripts/collaboration/retrospective.py +279 -0
  66. scripts/collaboration/role_matcher.py +92 -0
  67. scripts/collaboration/role_template_market.py +352 -0
  68. scripts/collaboration/rule_collector.py +678 -0
  69. scripts/collaboration/scratchpad.py +346 -0
  70. scripts/collaboration/skill_registry.py +151 -0
  71. scripts/collaboration/skillifier.py +878 -0
  72. scripts/collaboration/standardized_role_template.py +317 -0
  73. scripts/collaboration/task_completion_checker.py +237 -0
  74. scripts/collaboration/test_quality_guard.py +695 -0
  75. scripts/collaboration/unified_gate_engine.py +598 -0
  76. scripts/collaboration/usage_tracker.py +309 -0
  77. scripts/collaboration/user_friendly_error.py +176 -0
  78. scripts/collaboration/verification_gate.py +312 -0
  79. scripts/collaboration/warmup_manager.py +635 -0
  80. scripts/collaboration/worker.py +513 -0
  81. scripts/collaboration/workflow_engine.py +684 -0
  82. scripts/dashboard.py +1088 -0
  83. scripts/generate_benchmark_report.py +786 -0
  84. scripts/history_manager.py +604 -0
  85. scripts/mcp_server.py +289 -0
  86. skills/__init__.py +32 -0
  87. skills/dispatch/handler.py +52 -0
  88. skills/intent/handler.py +59 -0
  89. skills/registry.py +67 -0
  90. skills/retrospective/__init__.py +0 -0
  91. skills/retrospective/handler.py +125 -0
  92. skills/review/handler.py +356 -0
  93. skills/security/handler.py +454 -0
  94. skills/test/__init__.py +0 -0
  95. skills/test/handler.py +78 -0
@@ -0,0 +1,350 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ IntentWorkflowMapper - Maps user intent to workflow chains
5
+
6
+ Extends RoleMatcher with semantic understanding of WHAT user wants to do,
7
+ not just WHICH keywords they used.
8
+
9
+ When user says "fix the login bug", system should know this requires
10
+ debugging workflow, not generic coding.
11
+
12
+ Integration point: Called by MultiAgentDispatcher.dispatch() before role matching.
13
+ The detected IntentMatch is stored and passed to Coordinator/Workers.
14
+
15
+ Spec reference: SPEC_V35_Agent_Skills_Quality_Framework.md Section 6.3
16
+ """
17
+
18
+ import logging
19
+ from dataclasses import dataclass, field
20
+ from typing import Any, Dict, List, Optional
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ @dataclass
26
+ class WorkflowChainDef:
27
+ """Definition of a workflow chain for a specific intent type."""
28
+ trigger_keywords: Dict[str, List[str]]
29
+ workflow_chain: List[str]
30
+ required_roles: List[str]
31
+ optional_roles: List[str] = field(default_factory=list)
32
+ gate: Optional[str] = None
33
+ gate_description: str = ""
34
+ anti_skip_message: str = ""
35
+
36
+
37
+ @dataclass
38
+ class IntentMatch:
39
+ """Result of intent detection against task description."""
40
+ intent_type: str
41
+ confidence: float
42
+ workflow_chain: List[str] = field(default_factory=list)
43
+ required_roles: List[str] = field(default_factory=list)
44
+ optional_roles: List[str] = field(default_factory=list)
45
+ gate: Optional[str] = None
46
+ gate_description: str = ""
47
+ anti_skip_message: str = ""
48
+
49
+
50
+ class IntentWorkflowMapper:
51
+ """
52
+ Maps user intent to workflow chains and required roles.
53
+
54
+ Extends RoleMatcher with semantic understanding of user INTENT.
55
+ Supports 3 languages (zh/en/ja) for keyword detection.
56
+
57
+ Inspired by AGENTS.md intent mapping from Agent Skills (addyosmani/agent-skills).
58
+ """
59
+
60
+ WORKFLOW_CHAINS: Dict[str, WorkflowChainDef] = {
61
+ "bug_fix": WorkflowChainDef(
62
+ trigger_keywords={
63
+ "zh": [
64
+ "修复", "修", "bug", "错误", "报错", "失败",
65
+ "异常", "崩溃", "缺陷", "问题",
66
+ ],
67
+ "en": [
68
+ "fix", "bug", "error", "fail", "crash", "broken",
69
+ "issue", "defect", "problem", "exception",
70
+ ],
71
+ "ja": [
72
+ "修正", "バグ", "エラー", "失敗", "異常", "クラッシュ",
73
+ "欠陥", "問題",
74
+ ],
75
+ },
76
+ workflow_chain=[
77
+ "debugging_and_error_recovery",
78
+ "test_driven_development",
79
+ ],
80
+ required_roles=["solo-coder", "tester"],
81
+ optional_roles=["security"],
82
+ gate="prove_it_pattern",
83
+ gate_description=(
84
+ "Must have a failing reproduction test before implementing fix"
85
+ ),
86
+ anti_skip_message=(
87
+ "Do NOT implement the fix first. "
88
+ "Write the test that demonstrates the bug."
89
+ ),
90
+ ),
91
+ "new_feature": WorkflowChainDef(
92
+ trigger_keywords={
93
+ "zh": [
94
+ "实现", "开发", "新增", "添加", "创建", "构建",
95
+ "功能", "特性", "做一个", "写一个",
96
+ ],
97
+ "en": [
98
+ "implement", "develop", "add", "create", "build",
99
+ "feature", "new", "make a", "write a",
100
+ ],
101
+ "ja": [
102
+ "実装", "開発", "追加", "作成", "構築", "機能",
103
+ "新規", "作る",
104
+ ],
105
+ },
106
+ workflow_chain=[
107
+ "spec_driven_development",
108
+ "planning_and_task_breakdown",
109
+ "incremental_implementation",
110
+ "test_driven_development",
111
+ ],
112
+ required_roles=["architect", "solo-coder", "tester"],
113
+ optional_roles=["product-manager", "ui-designer"],
114
+ gate="spec_first",
115
+ gate_description=(
116
+ "Must produce or validate a spec before writing implementation code"
117
+ ),
118
+ anti_skip_message=(
119
+ "Do NOT start coding until the spec is reviewed and approved."
120
+ ),
121
+ ),
122
+ "security_review": WorkflowChainDef(
123
+ trigger_keywords={
124
+ "zh": [
125
+ "安全", "漏洞", "渗透", "审计", "加固", "注入",
126
+ "XSS", "SQL注入", "越权", "CSRF",
127
+ ],
128
+ "en": [
129
+ "security", "vulnerability", "penetration", "audit",
130
+ "harden", "injection", "XSS", "SQL injection",
131
+ "OWASP", "CSRF", "auth bypass",
132
+ ],
133
+ "ja": [
134
+ "セキュリティ", "脆弱性", "侵入", "監査", "強化",
135
+ "インジェクション", "OWASP", "認証バイパス",
136
+ ],
137
+ },
138
+ workflow_chain=[
139
+ "security_and_hardening",
140
+ "code_review_and_quality",
141
+ ],
142
+ required_roles=["security", "architect"],
143
+ optional_roles=["solo-coder"],
144
+ gate="owasp_checklist",
145
+ gate_description=(
146
+ "Must complete OWASP Top 10 checklist before approving"
147
+ ),
148
+ anti_skip_message=(
149
+ "Do NOT mark as secure without systematic vulnerability assessment."
150
+ ),
151
+ ),
152
+ "code_review": WorkflowChainDef(
153
+ trigger_keywords={
154
+ "zh": ["审查", "review", "代码质量", "重构", "优化", "简化", "走查"],
155
+ "en": [
156
+ "review", "code quality", "refactor", "optimize",
157
+ "simplify", "walkthrough", "inspect", "quality",
158
+ ],
159
+ "ja": [
160
+ "レビュー", "コード品質", "リファクタ", "最適化",
161
+ "単純化", "ウォークスルー",
162
+ ],
163
+ },
164
+ workflow_chain=[
165
+ "code_review_and_quality",
166
+ "code_simplification",
167
+ ],
168
+ required_roles=["solo-coder", "security", "tester"],
169
+ optional_roles=["architect"],
170
+ gate="change_size_limit",
171
+ gate_description="Changes must be ~100 lines or split into smaller reviews",
172
+ anti_skip_message="Do NOT approve large changesets without splitting.",
173
+ ),
174
+ "performance_optimization": WorkflowChainDef(
175
+ trigger_keywords={
176
+ "zh": [
177
+ "性能", "优化", "慢", "加速", "延迟", "吞吐",
178
+ "瓶颈", "卡顿", "超时",
179
+ ],
180
+ "en": [
181
+ "performance", "optimize", "slow", "speedup", "latency",
182
+ "throughput", "bottleneck", "timeout",
183
+ ],
184
+ "ja": [
185
+ "パフォーマンス", "最適化", "遅い", "高速化",
186
+ "レイテンシ", "スループット", "ボトルネック",
187
+ ],
188
+ },
189
+ workflow_chain=[
190
+ "performance_optimization",
191
+ "code_review_and_quality",
192
+ ],
193
+ required_roles=["architect", "devops"],
194
+ optional_roles=["solo-coder"],
195
+ gate="measure_first",
196
+ gate_description="Must have baseline measurements before optimizing",
197
+ anti_skip_message=(
198
+ "Do NOT optimize without measurements. "
199
+ "You're likely optimizing the wrong thing."
200
+ ),
201
+ ),
202
+ "deployment": WorkflowChainDef(
203
+ trigger_keywords={
204
+ "zh": ["部署", "发布", "上线", "部署", "CI", "CD", "发布", "部署到"],
205
+ "en": [
206
+ "deploy", "release", "ship", "launch", "CI", "CD",
207
+ "publish", "rollout",
208
+ ],
209
+ "ja": ["デプロイ", "リリース", "公開", "CI", "CD"],
210
+ },
211
+ workflow_chain=[
212
+ "ci_cd_and_automation",
213
+ "shipping_and_launch",
214
+ ],
215
+ required_roles=["devops", "security"],
216
+ optional_roles=["architect"],
217
+ gate="pre_launch_checklist",
218
+ gate_description="Complete all 6 categories of pre-launch checks",
219
+ anti_skip_message=(
220
+ "Do NOT deploy without rollback plan and monitoring setup."
221
+ ),
222
+ ),
223
+ }
224
+
225
+ def __init__(self, confidence_threshold: float = 0.3):
226
+ self._confidence_threshold = confidence_threshold
227
+ self._cache: Dict[str, Optional[IntentMatch]] = {}
228
+ self._MAX_CACHE_SIZE = 128
229
+
230
+ def detect_intent(
231
+ self,
232
+ task_description: str,
233
+ lang: str = "zh"
234
+ ) -> Optional[IntentMatch]:
235
+ """
236
+ Detect user intent from task description.
237
+
238
+ Args:
239
+ task_description: The user's task text
240
+ lang: Language code ("zh", "en", or "ja")
241
+
242
+ Returns:
243
+ IntentMatch if confidence >= threshold, else None
244
+ """
245
+ cache_key = f"{lang}:{task_description[:100]}"
246
+ if cache_key in self._cache:
247
+ return self._cache[cache_key]
248
+
249
+ task_lower = task_description.lower()
250
+
251
+ best_match = None
252
+ best_score = 0.0
253
+
254
+ for intent_type, chain_def in self.WORKFLOW_CHAINS.items():
255
+ score = self._calculate_score(task_lower, chain_def, lang)
256
+
257
+ if score > 0 and score > best_score:
258
+ best_score = score
259
+ best_match = IntentMatch(
260
+ intent_type=intent_type,
261
+ confidence=score,
262
+ workflow_chain=list(chain_def.workflow_chain),
263
+ required_roles=list(chain_def.required_roles),
264
+ optional_roles=list(chain_def.optional_roles),
265
+ gate=chain_def.gate,
266
+ gate_description=chain_def.gate_description,
267
+ anti_skip_message=chain_def.anti_skip_message,
268
+ )
269
+
270
+ if best_match and best_match.confidence >= self._confidence_threshold:
271
+ if len(self._cache) >= self._MAX_CACHE_SIZE:
272
+ oldest_key = next(iter(self._cache))
273
+ del self._cache[oldest_key]
274
+ self._cache[cache_key] = best_match
275
+ logger.info(
276
+ "Intent detected: type=%s confidence=%.2f roles=%s",
277
+ best_match.intent_type,
278
+ best_match.confidence,
279
+ best_match.required_roles,
280
+ )
281
+ return best_match
282
+
283
+ if len(self._cache) >= self._MAX_CACHE_SIZE:
284
+ oldest_key = next(iter(self._cache))
285
+ del self._cache[oldest_key]
286
+ self._cache[cache_key] = None
287
+ return None
288
+
289
+ def _calculate_score(
290
+ self,
291
+ task_lower: str,
292
+ chain_def: WorkflowChainDef,
293
+ lang: str,
294
+ ) -> float:
295
+ primary_keywords = list(chain_def.trigger_keywords.get(lang, []))
296
+ secondary_keywords = list(chain_def.trigger_keywords.get("en", [])) if lang != "en" else []
297
+
298
+ if not primary_keywords and not secondary_keywords:
299
+ return 0.0
300
+
301
+ primary_matches = sum(1 for kw in primary_keywords if kw.lower() in task_lower)
302
+ secondary_matches = sum(1 for kw in secondary_keywords if kw.lower() in task_lower)
303
+
304
+ if primary_matches == 0 and secondary_matches == 0:
305
+ return 0.0
306
+
307
+ score = 0.0
308
+ if primary_matches > 0:
309
+ score = min(0.4 + (primary_matches - 1) * 0.2, 1.0)
310
+ elif secondary_matches > 0:
311
+ score = min(0.25 + (secondary_matches - 1) * 0.15, 0.9)
312
+
313
+ return score
314
+
315
+ def get_available_intents(self) -> List[str]:
316
+ """Return all available intent types."""
317
+ return sorted(self.WORKFLOW_CHAINS.keys())
318
+
319
+ def get_intent_details(self, intent_type: str) -> Optional[Dict[str, Any]]:
320
+ """Get full details for an intent type."""
321
+ chain_def = self.WORKFLOW_CHAINS.get(intent_type)
322
+ if not chain_def:
323
+ return None
324
+ return {
325
+ "intent_type": intent_type,
326
+ "trigger_keywords": chain_def.trigger_keywords,
327
+ "workflow_chain": chain_def.workflow_chain,
328
+ "required_roles": chain_def.required_roles,
329
+ "optional_roles": chain_def.optional_roles,
330
+ "gate": chain_def.gate,
331
+ "gate_description": chain_def.gate_description,
332
+ "anti_skip_message": chain_def.anti_skip_message,
333
+ }
334
+
335
+
336
+ def get_shared_mapper(confidence_threshold: float = 0.3) -> IntentWorkflowMapper:
337
+ """
338
+ Get or create shared singleton instance.
339
+
340
+ Args:
341
+ confidence_threshold: Minimum confidence to accept intent match
342
+
343
+ Returns:
344
+ Shared IntentWorkflowMapper instance
345
+ """
346
+ if not hasattr(get_shared_mapper, "_instance"):
347
+ get_shared_mapper._instance = IntentWorkflowMapper(
348
+ confidence_threshold=confidence_threshold
349
+ )
350
+ return get_shared_mapper._instance
@@ -0,0 +1,269 @@
1
+ #!/usr/bin/env python3
2
+ import ast
3
+ import re
4
+ import logging
5
+ from typing import Dict, List, Any, Optional
6
+ from pathlib import Path
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class LanguageParser:
12
+ """Base class for language-specific code parsers."""
13
+
14
+ def file_patterns(self) -> List[str]:
15
+ raise NotImplementedError
16
+
17
+ def exclude_patterns(self) -> List[str]:
18
+ return []
19
+
20
+ def parse_file(self, source: str, file_path: str) -> Optional[Dict[str, Any]]:
21
+ raise NotImplementedError
22
+
23
+ def extract_dependencies(self, source: str) -> List[str]:
24
+ raise NotImplementedError
25
+
26
+ def is_available(self) -> bool:
27
+ return True
28
+
29
+
30
+ class PythonParser(LanguageParser):
31
+ """Python AST-based language parser."""
32
+
33
+ def file_patterns(self) -> List[str]:
34
+ return ["*.py"]
35
+
36
+ def exclude_patterns(self) -> List[str]:
37
+ return ["__pycache__", "test_", "_test.py", ".venv"]
38
+
39
+ def parse_file(self, source: str, file_path: str) -> Optional[Dict[str, Any]]:
40
+ try:
41
+ tree = ast.parse(source)
42
+ except (SyntaxError, UnicodeDecodeError) as e:
43
+ logger.debug("Python parse failed for %s: %s", file_path, e)
44
+ return None
45
+
46
+ file_imports = []
47
+ top_level_nodes = []
48
+
49
+ for node in ast.iter_child_nodes(tree):
50
+ if isinstance(node, ast.Import):
51
+ for alias in node.names:
52
+ file_imports.append(alias.name)
53
+ elif isinstance(node, ast.ImportFrom):
54
+ module = node.module or ""
55
+ file_imports.append(module)
56
+
57
+ if isinstance(node, ast.ClassDef):
58
+ top_level_nodes.append(self._parse_class(node, file_path))
59
+ elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
60
+ top_level_nodes.append(self._parse_function(node, file_path))
61
+
62
+ return {
63
+ "file": Path(file_path).name,
64
+ "language": "python",
65
+ "imports": file_imports[:20],
66
+ "nodes": top_level_nodes,
67
+ "total_classes": sum(1 for n in top_level_nodes if n["type"] == "class"),
68
+ "total_functions": sum(1 for n in top_level_nodes if n["type"] in ("function", "method")),
69
+ }
70
+
71
+ def _parse_class(self, node, file_path: str) -> Dict[str, Any]:
72
+ docstring = ast.get_docstring(node) or ""
73
+ methods = []
74
+ for item in node.body:
75
+ if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
76
+ methods.append(self._parse_function(item, file_path))
77
+ return {
78
+ "name": node.name, "type": "class", "file": file_path,
79
+ "lines": f"{node.lineno}-{node.end_lineno or node.lineno}",
80
+ "docstring": docstring[:100], "children": methods,
81
+ }
82
+
83
+ def _parse_function(self, node, file_path: str) -> Dict[str, Any]:
84
+ docstring = ast.get_docstring(node) or ""
85
+ calls = []
86
+ for child in ast.walk(node):
87
+ if isinstance(child, ast.Call):
88
+ if isinstance(child.func, ast.Name):
89
+ calls.append(child.func.id)
90
+ elif isinstance(child.func, ast.Attribute):
91
+ calls.append(child.func.attr)
92
+ return {
93
+ "name": node.name, "type": "function", "file": file_path,
94
+ "lines": f"{node.lineno}-{node.end_lineno or node.lineno}",
95
+ "docstring": docstring[:100], "calls": list(set(calls))[:10],
96
+ }
97
+
98
+ def extract_dependencies(self, source: str) -> List[str]:
99
+ try:
100
+ tree = ast.parse(source)
101
+ except (SyntaxError, UnicodeDecodeError) as e:
102
+ logger.debug("Python dependency extraction failed: %s", e)
103
+ return []
104
+ deps = set()
105
+ for node in ast.walk(tree):
106
+ if isinstance(node, ast.ImportFrom) and node.module:
107
+ deps.add(node.module)
108
+ elif isinstance(node, ast.Import):
109
+ for alias in node.names:
110
+ deps.add(alias.name)
111
+ return sorted(deps)
112
+
113
+
114
+ class JavaScriptParser(LanguageParser):
115
+ """JavaScript/TypeScript regex-based parser."""
116
+
117
+ _CLASS_RE = re.compile(
118
+ r'(?:export\s+)?(?:default\s+)?class\s+(\w+)', re.MULTILINE
119
+ )
120
+ _FUNC_RE = re.compile(
121
+ r'(?:export\s+)?(?:async\s+)?function\s+(\w+)|'
122
+ r'(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[a-zA-Z_]\w*)\s*=>',
123
+ re.MULTILINE,
124
+ )
125
+ _IMPORT_RE = re.compile(
126
+ r"""import\s+.*?\s+from\s+['"]([^'"]+)['"]|"""
127
+ r"""require\s*\(\s*['"]([^'"]+)['"]\s*\)""",
128
+ re.MULTILINE,
129
+ )
130
+
131
+ def file_patterns(self) -> List[str]:
132
+ return ["*.js", "*.jsx", "*.ts", "*.tsx"]
133
+
134
+ def exclude_patterns(self) -> List[str]:
135
+ return ["node_modules", "dist", ".next", "coverage", "build"]
136
+
137
+ def parse_file(self, source: str, file_path: str) -> Optional[Dict[str, Any]]:
138
+ try:
139
+ classes = []
140
+ for m in self._CLASS_RE.finditer(source):
141
+ classes.append({
142
+ "name": m.group(1), "type": "class", "file": file_path,
143
+ "lines": "", "docstring": "", "children": [],
144
+ })
145
+
146
+ functions = []
147
+ for m in self._FUNC_RE.finditer(source):
148
+ name = m.group(1) or m.group(2)
149
+ if name:
150
+ functions.append({
151
+ "name": name, "type": "function", "file": file_path,
152
+ "lines": "", "docstring": "", "calls": [],
153
+ })
154
+
155
+ if not classes and not functions:
156
+ return None
157
+
158
+ return {
159
+ "file": Path(file_path).name,
160
+ "language": "javascript",
161
+ "imports": self.extract_dependencies(source)[:20],
162
+ "nodes": classes + functions,
163
+ "total_classes": len(classes),
164
+ "total_functions": len(functions),
165
+ }
166
+ except Exception as e:
167
+ logger.debug("JavaScript parse failed for %s: %s", file_path, e)
168
+ return None
169
+
170
+ def extract_dependencies(self, source: str) -> List[str]:
171
+ deps = set()
172
+ for m in self._IMPORT_RE.finditer(source):
173
+ dep = m.group(1) or m.group(2)
174
+ if dep:
175
+ deps.add(dep)
176
+ return sorted(deps)
177
+
178
+
179
+ class GoParser(LanguageParser):
180
+ """Go regex-based parser."""
181
+
182
+ _FUNC_RE = re.compile(
183
+ r'func\s+(?:\([^)]+\)\s*)?(\w+)\s*\(', re.MULTILINE
184
+ )
185
+ _TYPE_STRUCT_RE = re.compile(
186
+ r'type\s+(\w+)\s+struct\s*\{', re.MULTILINE
187
+ )
188
+ _TYPE_INTERFACE_RE = re.compile(
189
+ r'type\s+(\w+)\s+interface\s*\{', re.MULTILINE
190
+ )
191
+ _IMPORT_RE = re.compile(
192
+ r'import\s+(?:\(\s*([^)]+)\s*\)|"([^"]+)")', re.MULTILINE | re.DOTALL
193
+ )
194
+ _IMPORT_LINE_RE = re.compile(r'"([^"]+)"')
195
+
196
+ def file_patterns(self) -> List[str]:
197
+ return ["*.go"]
198
+
199
+ def exclude_patterns(self) -> List[str]:
200
+ return ["vendor"]
201
+
202
+ def parse_file(self, source: str, file_path: str) -> Optional[Dict[str, Any]]:
203
+ try:
204
+ structs = []
205
+ for m in self._TYPE_STRUCT_RE.finditer(source):
206
+ structs.append({
207
+ "name": m.group(1), "type": "struct", "file": file_path,
208
+ "lines": "", "docstring": "", "children": [],
209
+ })
210
+
211
+ interfaces = []
212
+ for m in self._TYPE_INTERFACE_RE.finditer(source):
213
+ interfaces.append({
214
+ "name": m.group(1), "type": "interface", "file": file_path,
215
+ "lines": "", "docstring": "", "children": [],
216
+ })
217
+
218
+ functions = []
219
+ for m in self._FUNC_RE.finditer(source):
220
+ functions.append({
221
+ "name": m.group(1), "type": "function", "file": file_path,
222
+ "lines": "", "docstring": "", "calls": [],
223
+ })
224
+
225
+ if not structs and not interfaces and not functions:
226
+ return None
227
+
228
+ return {
229
+ "file": Path(file_path).name,
230
+ "language": "go",
231
+ "imports": self.extract_dependencies(source)[:20],
232
+ "nodes": structs + interfaces + functions,
233
+ "total_classes": len(structs) + len(interfaces),
234
+ "total_functions": len(functions),
235
+ }
236
+ except Exception as e:
237
+ logger.debug("Go parse failed for %s: %s", file_path, e)
238
+ return None
239
+
240
+ def extract_dependencies(self, source: str) -> List[str]:
241
+ deps = set()
242
+ for m in self._IMPORT_RE.finditer(source):
243
+ block = m.group(1)
244
+ single = m.group(2)
245
+ if block:
246
+ for lm in self._IMPORT_LINE_RE.finditer(block):
247
+ deps.add(lm.group(1))
248
+ elif single:
249
+ deps.add(single)
250
+ return sorted(deps)
251
+
252
+
253
+ class NullLanguageParser(LanguageParser):
254
+ """Null parser for graceful degradation."""
255
+
256
+ def file_patterns(self) -> List[str]:
257
+ return []
258
+
259
+ def parse_file(self, source: str, file_path: str) -> Optional[Dict[str, Any]]:
260
+ return None
261
+
262
+ def extract_dependencies(self, source: str) -> List[str]:
263
+ return []
264
+
265
+ def is_available(self) -> bool:
266
+ return False
267
+
268
+
269
+ DEFAULT_PARSERS = [PythonParser(), JavaScriptParser(), GoParser()]