monoco-toolkit 0.3.11__py3-none-any.whl → 0.4.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.
- monoco/core/automation/__init__.py +40 -0
- monoco/core/automation/field_watcher.py +296 -0
- monoco/core/automation/handlers.py +805 -0
- monoco/core/config.py +29 -11
- monoco/core/daemon/__init__.py +5 -0
- monoco/core/daemon/pid.py +290 -0
- monoco/core/git.py +15 -0
- monoco/core/hooks/context.py +74 -13
- monoco/core/injection.py +86 -8
- monoco/core/integrations.py +0 -24
- monoco/core/router/__init__.py +17 -0
- monoco/core/router/action.py +202 -0
- monoco/core/scheduler/__init__.py +63 -0
- monoco/core/scheduler/base.py +152 -0
- monoco/core/scheduler/engines.py +175 -0
- monoco/core/scheduler/events.py +197 -0
- monoco/core/scheduler/local.py +377 -0
- monoco/core/setup.py +9 -0
- monoco/core/sync.py +199 -4
- monoco/core/watcher/__init__.py +63 -0
- monoco/core/watcher/base.py +382 -0
- monoco/core/watcher/dropzone.py +152 -0
- monoco/core/watcher/im.py +460 -0
- monoco/core/watcher/issue.py +303 -0
- monoco/core/watcher/memo.py +192 -0
- monoco/core/watcher/task.py +238 -0
- monoco/daemon/app.py +3 -60
- monoco/daemon/commands.py +459 -25
- monoco/daemon/events.py +34 -0
- monoco/daemon/scheduler.py +157 -201
- monoco/daemon/services.py +42 -243
- monoco/features/agent/__init__.py +25 -7
- monoco/features/agent/cli.py +91 -57
- monoco/features/agent/engines.py +31 -170
- monoco/features/agent/resources/en/AGENTS.md +14 -14
- monoco/features/agent/resources/en/skills/monoco_role_engineer/SKILL.md +101 -0
- monoco/features/agent/resources/en/skills/monoco_role_manager/SKILL.md +95 -0
- monoco/features/agent/resources/en/skills/monoco_role_planner/SKILL.md +177 -0
- monoco/features/agent/resources/en/skills/monoco_role_reviewer/SKILL.md +139 -0
- monoco/features/agent/resources/zh/skills/monoco_role_engineer/SKILL.md +101 -0
- monoco/features/agent/resources/zh/skills/monoco_role_manager/SKILL.md +95 -0
- monoco/features/agent/resources/zh/skills/monoco_role_planner/SKILL.md +177 -0
- monoco/features/agent/resources/zh/skills/monoco_role_reviewer/SKILL.md +139 -0
- monoco/features/agent/worker.py +1 -1
- monoco/features/hooks/__init__.py +61 -6
- monoco/features/hooks/commands.py +281 -271
- monoco/features/hooks/dispatchers/__init__.py +23 -0
- monoco/features/hooks/dispatchers/agent_dispatcher.py +486 -0
- monoco/features/hooks/dispatchers/git_dispatcher.py +478 -0
- monoco/features/hooks/manager.py +357 -0
- monoco/features/hooks/models.py +262 -0
- monoco/features/hooks/parser.py +322 -0
- monoco/features/hooks/universal_interceptor.py +503 -0
- monoco/features/im/__init__.py +67 -0
- monoco/features/im/core.py +782 -0
- monoco/features/im/models.py +311 -0
- monoco/features/issue/commands.py +133 -60
- monoco/features/issue/core.py +385 -40
- monoco/features/issue/domain_commands.py +0 -19
- monoco/features/issue/resources/en/AGENTS.md +17 -122
- monoco/features/issue/resources/hooks/agent/before-tool.sh +102 -0
- monoco/features/issue/resources/hooks/agent/session-start.sh +88 -0
- monoco/features/issue/resources/hooks/{post-checkout.sh → git/git-post-checkout.sh} +10 -9
- monoco/features/issue/resources/hooks/git/git-pre-commit.sh +31 -0
- monoco/features/issue/resources/hooks/{pre-push.sh → git/git-pre-push.sh} +7 -13
- monoco/features/issue/resources/zh/AGENTS.md +18 -123
- monoco/features/memo/cli.py +15 -64
- monoco/features/memo/core.py +6 -34
- monoco/features/memo/models.py +24 -15
- monoco/features/memo/resources/en/AGENTS.md +31 -0
- monoco/features/memo/resources/zh/AGENTS.md +28 -5
- monoco/features/spike/commands.py +5 -3
- monoco/main.py +5 -3
- {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/METADATA +1 -1
- monoco_toolkit-0.4.0.dist-info/RECORD +170 -0
- monoco/core/execution.py +0 -67
- monoco/features/agent/apoptosis.py +0 -44
- monoco/features/agent/manager.py +0 -127
- monoco/features/agent/resources/atoms/atom-code-dev.yaml +0 -61
- monoco/features/agent/resources/atoms/atom-issue-lifecycle.yaml +0 -73
- monoco/features/agent/resources/atoms/atom-knowledge.yaml +0 -55
- monoco/features/agent/resources/atoms/atom-review.yaml +0 -60
- monoco/features/agent/resources/en/skills/monoco_atom_core/SKILL.md +0 -99
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_manager/SKILL.md +0 -93
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_planner/SKILL.md +0 -85
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -114
- monoco/features/agent/resources/workflows/workflow-dev.yaml +0 -83
- monoco/features/agent/resources/workflows/workflow-issue-create.yaml +0 -72
- monoco/features/agent/resources/workflows/workflow-review.yaml +0 -94
- monoco/features/agent/resources/zh/roles/monoco_role_engineer.yaml +0 -49
- monoco/features/agent/resources/zh/roles/monoco_role_manager.yaml +0 -46
- monoco/features/agent/resources/zh/roles/monoco_role_planner.yaml +0 -46
- monoco/features/agent/resources/zh/roles/monoco_role_reviewer.yaml +0 -47
- monoco/features/agent/resources/zh/skills/monoco_atom_core/SKILL.md +0 -99
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_manager/SKILL.md +0 -88
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_planner/SKILL.md +0 -259
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -137
- monoco/features/agent/session.py +0 -169
- monoco/features/artifact/resources/zh/skills/monoco_atom_artifact/SKILL.md +0 -278
- monoco/features/glossary/resources/en/skills/monoco_atom_glossary/SKILL.md +0 -35
- monoco/features/glossary/resources/zh/skills/monoco_atom_glossary/SKILL.md +0 -35
- monoco/features/hooks/adapter.py +0 -67
- monoco/features/hooks/core.py +0 -441
- monoco/features/i18n/resources/en/skills/monoco_atom_i18n/SKILL.md +0 -96
- monoco/features/i18n/resources/en/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
- monoco/features/i18n/resources/zh/skills/monoco_atom_i18n/SKILL.md +0 -96
- monoco/features/i18n/resources/zh/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
- monoco/features/issue/resources/en/skills/monoco_atom_issue/SKILL.md +0 -165
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_development/SKILL.md +0 -224
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_management/SKILL.md +0 -159
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
- monoco/features/issue/resources/hooks/pre-commit.sh +0 -41
- monoco/features/issue/resources/zh/skills/monoco_atom_issue_lifecycle/SKILL.md +0 -190
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_development/SKILL.md +0 -224
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_management/SKILL.md +0 -159
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
- monoco/features/memo/resources/en/skills/monoco_atom_memo/SKILL.md +0 -77
- monoco/features/memo/resources/en/skills/monoco_workflow_note_processing/SKILL.md +0 -140
- monoco/features/memo/resources/zh/skills/monoco_atom_memo/SKILL.md +0 -77
- monoco/features/memo/resources/zh/skills/monoco_workflow_note_processing/SKILL.md +0 -140
- monoco/features/spike/resources/en/skills/monoco_atom_spike/SKILL.md +0 -76
- monoco/features/spike/resources/en/skills/monoco_workflow_research/SKILL.md +0 -121
- monoco/features/spike/resources/zh/skills/monoco_atom_spike/SKILL.md +0 -76
- monoco/features/spike/resources/zh/skills/monoco_workflow_research/SKILL.md +0 -121
- monoco_toolkit-0.3.11.dist-info/RECORD +0 -181
- {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/WHEEL +0 -0
- {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/entry_points.txt +0 -0
- {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Universal Hooks: Front Matter Parser
|
|
3
|
+
|
|
4
|
+
Parses YAML Front Matter from hook scripts with support for multiple
|
|
5
|
+
comment styles and languages.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
import yaml
|
|
14
|
+
|
|
15
|
+
from .models import HookMetadata, ParsedHook
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class ParseError:
|
|
20
|
+
"""Error information from parsing a hook script."""
|
|
21
|
+
|
|
22
|
+
path: Path
|
|
23
|
+
line_number: int
|
|
24
|
+
message: str
|
|
25
|
+
raw_content: Optional[str] = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class HookParser:
|
|
29
|
+
"""
|
|
30
|
+
Parser for extracting YAML Front Matter from hook scripts.
|
|
31
|
+
|
|
32
|
+
Supports multiple comment styles:
|
|
33
|
+
- Shell/Python/Ruby: `# ---` / `# ---`
|
|
34
|
+
- JavaScript/TypeScript/C/C++/Java/Rust/Go: `// ---` / `// ---`
|
|
35
|
+
- Lua/SQL/Haskell: `-- ---` / `-- ---`
|
|
36
|
+
- HTML/XML: `<!-- ---` / `--- -->`
|
|
37
|
+
|
|
38
|
+
The parser detects the comment style automatically based on file extension
|
|
39
|
+
or the first line of the script.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
# Comment style definitions: (prefix, suffix, file_extensions)
|
|
43
|
+
COMMENT_STYLES = {
|
|
44
|
+
"shell": ("#", "", {"sh", "bash", "zsh", "fish", "py", "rb", "pl", "r", "ps1", "Makefile", "Dockerfile"}),
|
|
45
|
+
"c_style": ("//", "", {"js", "ts", "jsx", "tsx", "c", "cpp", "cc", "cxx", "h", "hpp", "java", "cs", "go", "rs", "swift", "kt", "scala", "php", "dart"}),
|
|
46
|
+
"double_dash": ("--", "", {"lua", "sql", "hs", "lhs", "elm", "sql", "mysql", "pgsql"}),
|
|
47
|
+
"html": ("<!--", "-->", {"html", "htm", "xml", "svg", "vue", "svelte"}),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Regex patterns for front matter detection
|
|
51
|
+
FRONT_MATTER_DELIMITER = "---"
|
|
52
|
+
|
|
53
|
+
def __init__(self):
|
|
54
|
+
"""Initialize the parser."""
|
|
55
|
+
self.errors: list[ParseError] = []
|
|
56
|
+
|
|
57
|
+
def _detect_comment_style(self, path: Path, first_line: str) -> tuple[str, str]:
|
|
58
|
+
"""
|
|
59
|
+
Detect the comment style for a file.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
path: Path to the script file
|
|
63
|
+
first_line: First line of the file content
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Tuple of (prefix, suffix) for the comment style
|
|
67
|
+
"""
|
|
68
|
+
# First, try to detect from file extension
|
|
69
|
+
ext = path.suffix.lstrip(".").lower()
|
|
70
|
+
name = path.name.lower()
|
|
71
|
+
|
|
72
|
+
for style_name, (prefix, suffix, extensions) in self.COMMENT_STYLES.items():
|
|
73
|
+
if ext in extensions or name in extensions:
|
|
74
|
+
return (prefix, suffix)
|
|
75
|
+
|
|
76
|
+
# If no extension match, try to detect from shebang or first line
|
|
77
|
+
if first_line.startswith("#!/"):
|
|
78
|
+
# Detect from shebang
|
|
79
|
+
shebang = first_line.lower()
|
|
80
|
+
if any(shell in shebang for shell in ["bash", "sh", "zsh", "python", "ruby", "perl"]):
|
|
81
|
+
return ("#", "")
|
|
82
|
+
if "node" in shebang or "deno" in shebang or "bun" in shebang:
|
|
83
|
+
return ("//", "")
|
|
84
|
+
if "lua" in shebang:
|
|
85
|
+
return ("--", "")
|
|
86
|
+
|
|
87
|
+
# Default to shell style if uncertain
|
|
88
|
+
return ("#", "")
|
|
89
|
+
|
|
90
|
+
def _strip_comment_prefix(self, line: str, prefix: str, suffix: str) -> str:
|
|
91
|
+
"""
|
|
92
|
+
Remove comment prefix and suffix from a line.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
line: The line to process
|
|
96
|
+
prefix: The comment prefix (e.g., "#", "//", "--")
|
|
97
|
+
suffix: The comment suffix (e.g., "-->")
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
The line with comment markers stripped
|
|
101
|
+
"""
|
|
102
|
+
# Remove leading/trailing whitespace first
|
|
103
|
+
stripped = line.strip()
|
|
104
|
+
|
|
105
|
+
# Remove prefix
|
|
106
|
+
if prefix and stripped.startswith(prefix):
|
|
107
|
+
stripped = stripped[len(prefix):]
|
|
108
|
+
|
|
109
|
+
# Remove suffix
|
|
110
|
+
if suffix and stripped.endswith(suffix):
|
|
111
|
+
stripped = stripped[:-len(suffix)]
|
|
112
|
+
|
|
113
|
+
return stripped.strip()
|
|
114
|
+
|
|
115
|
+
def _extract_front_matter_lines(
|
|
116
|
+
self,
|
|
117
|
+
lines: list[str],
|
|
118
|
+
prefix: str,
|
|
119
|
+
suffix: str,
|
|
120
|
+
) -> Optional[tuple[list[str], int, int]]:
|
|
121
|
+
"""
|
|
122
|
+
Extract front matter lines from script content.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
lines: All lines of the script
|
|
126
|
+
prefix: Comment prefix
|
|
127
|
+
suffix: Comment suffix
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Tuple of (front_matter_lines, start_line, end_line) or None if no front matter
|
|
131
|
+
"""
|
|
132
|
+
if not lines:
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
start_idx = 0
|
|
136
|
+
|
|
137
|
+
# Skip shebang line if present
|
|
138
|
+
if lines[0].startswith("#!/"):
|
|
139
|
+
start_idx = 1
|
|
140
|
+
|
|
141
|
+
# Look for opening delimiter
|
|
142
|
+
opening_line_idx = None
|
|
143
|
+
for i in range(start_idx, len(lines)):
|
|
144
|
+
stripped = self._strip_comment_prefix(lines[i], prefix, suffix)
|
|
145
|
+
if stripped == self.FRONT_MATTER_DELIMITER:
|
|
146
|
+
opening_line_idx = i
|
|
147
|
+
break
|
|
148
|
+
|
|
149
|
+
if opening_line_idx is None:
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
# Look for closing delimiter
|
|
153
|
+
closing_line_idx = None
|
|
154
|
+
for i in range(opening_line_idx + 1, len(lines)):
|
|
155
|
+
stripped = self._strip_comment_prefix(lines[i], prefix, suffix)
|
|
156
|
+
if stripped == self.FRONT_MATTER_DELIMITER:
|
|
157
|
+
closing_line_idx = i
|
|
158
|
+
break
|
|
159
|
+
|
|
160
|
+
if closing_line_idx is None:
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
# Extract front matter lines (between delimiters)
|
|
164
|
+
front_matter_lines = []
|
|
165
|
+
for i in range(opening_line_idx + 1, closing_line_idx):
|
|
166
|
+
stripped = self._strip_comment_prefix(lines[i], prefix, suffix)
|
|
167
|
+
front_matter_lines.append(stripped)
|
|
168
|
+
|
|
169
|
+
# Line numbers are 1-based for error reporting
|
|
170
|
+
return (
|
|
171
|
+
front_matter_lines,
|
|
172
|
+
opening_line_idx + 1,
|
|
173
|
+
closing_line_idx + 1,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
def parse_file(self, path: Path) -> Optional[ParsedHook]:
|
|
177
|
+
"""
|
|
178
|
+
Parse a hook script file and extract its metadata.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
path: Path to the hook script
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
ParsedHook if successful, None if parsing fails
|
|
185
|
+
"""
|
|
186
|
+
try:
|
|
187
|
+
content = path.read_text(encoding="utf-8")
|
|
188
|
+
except Exception as e:
|
|
189
|
+
self.errors.append(ParseError(
|
|
190
|
+
path=path,
|
|
191
|
+
line_number=0,
|
|
192
|
+
message=f"Failed to read file: {e}",
|
|
193
|
+
))
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
return self.parse_content(path, content)
|
|
197
|
+
|
|
198
|
+
def parse_content(self, path: Path, content: str) -> Optional[ParsedHook]:
|
|
199
|
+
"""
|
|
200
|
+
Parse hook content and extract metadata.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
path: Path to the hook script (for error reporting and style detection)
|
|
204
|
+
content: The script content
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
ParsedHook if successful, None if no valid front matter or parsing fails
|
|
208
|
+
"""
|
|
209
|
+
lines = content.splitlines()
|
|
210
|
+
|
|
211
|
+
if not lines:
|
|
212
|
+
self.errors.append(ParseError(
|
|
213
|
+
path=path,
|
|
214
|
+
line_number=0,
|
|
215
|
+
message="Empty file",
|
|
216
|
+
))
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
# Detect comment style
|
|
220
|
+
first_line = lines[0] if lines else ""
|
|
221
|
+
prefix, suffix = self._detect_comment_style(path, first_line)
|
|
222
|
+
|
|
223
|
+
# Extract front matter
|
|
224
|
+
result = self._extract_front_matter_lines(lines, prefix, suffix)
|
|
225
|
+
if result is None:
|
|
226
|
+
# No front matter found - this is OK, just return None
|
|
227
|
+
return None
|
|
228
|
+
|
|
229
|
+
front_matter_lines, start_line, end_line = result
|
|
230
|
+
yaml_content = "\n".join(front_matter_lines)
|
|
231
|
+
|
|
232
|
+
# Parse YAML
|
|
233
|
+
try:
|
|
234
|
+
data = yaml.safe_load(yaml_content)
|
|
235
|
+
except yaml.YAMLError as e:
|
|
236
|
+
# Try to extract line number from error
|
|
237
|
+
line_no = getattr(e, 'problem_mark', None)
|
|
238
|
+
if line_no:
|
|
239
|
+
actual_line = start_line + line_no.line + 1
|
|
240
|
+
else:
|
|
241
|
+
actual_line = start_line
|
|
242
|
+
|
|
243
|
+
self.errors.append(ParseError(
|
|
244
|
+
path=path,
|
|
245
|
+
line_number=actual_line,
|
|
246
|
+
message=f"YAML parsing error: {e}",
|
|
247
|
+
raw_content=yaml_content,
|
|
248
|
+
))
|
|
249
|
+
return None
|
|
250
|
+
|
|
251
|
+
if not isinstance(data, dict):
|
|
252
|
+
self.errors.append(ParseError(
|
|
253
|
+
path=path,
|
|
254
|
+
line_number=start_line,
|
|
255
|
+
message="Front matter must be a YAML mapping (key: value)",
|
|
256
|
+
raw_content=yaml_content,
|
|
257
|
+
))
|
|
258
|
+
return None
|
|
259
|
+
|
|
260
|
+
# Parse metadata using Pydantic
|
|
261
|
+
try:
|
|
262
|
+
metadata = HookMetadata.model_validate(data)
|
|
263
|
+
except Exception as e:
|
|
264
|
+
self.errors.append(ParseError(
|
|
265
|
+
path=path,
|
|
266
|
+
line_number=start_line,
|
|
267
|
+
message=f"Metadata validation error: {e}",
|
|
268
|
+
raw_content=yaml_content,
|
|
269
|
+
))
|
|
270
|
+
return None
|
|
271
|
+
|
|
272
|
+
return ParsedHook(
|
|
273
|
+
metadata=metadata,
|
|
274
|
+
script_path=path,
|
|
275
|
+
content=content,
|
|
276
|
+
front_matter_start_line=start_line,
|
|
277
|
+
front_matter_end_line=end_line,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
def parse_directory(
|
|
281
|
+
self,
|
|
282
|
+
directory: Path,
|
|
283
|
+
pattern: str = "*",
|
|
284
|
+
) -> list[ParsedHook]:
|
|
285
|
+
"""
|
|
286
|
+
Parse all hook scripts in a directory.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
directory: Directory to scan
|
|
290
|
+
pattern: Glob pattern for matching files (default: "*")
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
List of successfully parsed hooks
|
|
294
|
+
"""
|
|
295
|
+
parsed_hooks: list[ParsedHook] = []
|
|
296
|
+
|
|
297
|
+
if not directory.exists():
|
|
298
|
+
return parsed_hooks
|
|
299
|
+
|
|
300
|
+
for file_path in directory.rglob(pattern):
|
|
301
|
+
if not file_path.is_file():
|
|
302
|
+
continue
|
|
303
|
+
|
|
304
|
+
# Skip hidden files and common non-script files
|
|
305
|
+
if file_path.name.startswith("."):
|
|
306
|
+
continue
|
|
307
|
+
if file_path.suffix.lower() in {".md",".txt",".json",".lock"}:
|
|
308
|
+
continue
|
|
309
|
+
|
|
310
|
+
hook = self.parse_file(file_path)
|
|
311
|
+
if hook is not None:
|
|
312
|
+
parsed_hooks.append(hook)
|
|
313
|
+
|
|
314
|
+
return parsed_hooks
|
|
315
|
+
|
|
316
|
+
def get_errors(self) -> list[ParseError]:
|
|
317
|
+
"""Get all parsing errors encountered."""
|
|
318
|
+
return self.errors.copy()
|
|
319
|
+
|
|
320
|
+
def clear_errors(self) -> None:
|
|
321
|
+
"""Clear the error list."""
|
|
322
|
+
self.errors.clear()
|