deepwork 0.5.1__py3-none-any.whl → 0.7.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.
- deepwork/__init__.py +1 -1
- deepwork/cli/hook.py +3 -4
- deepwork/cli/install.py +70 -117
- deepwork/cli/main.py +2 -2
- deepwork/cli/serve.py +133 -0
- deepwork/cli/sync.py +93 -58
- deepwork/core/adapters.py +91 -102
- deepwork/core/generator.py +19 -386
- deepwork/core/hooks_syncer.py +1 -1
- deepwork/core/parser.py +270 -1
- deepwork/hooks/README.md +0 -44
- deepwork/hooks/__init__.py +3 -6
- deepwork/hooks/check_version.sh +54 -21
- deepwork/mcp/__init__.py +23 -0
- deepwork/mcp/quality_gate.py +347 -0
- deepwork/mcp/schemas.py +263 -0
- deepwork/mcp/server.py +253 -0
- deepwork/mcp/state.py +422 -0
- deepwork/mcp/tools.py +394 -0
- deepwork/schemas/job.schema.json +347 -0
- deepwork/schemas/job_schema.py +27 -239
- deepwork/standard_jobs/deepwork_jobs/doc_specs/job_spec.md +9 -15
- deepwork/standard_jobs/deepwork_jobs/job.yml +146 -46
- deepwork/standard_jobs/deepwork_jobs/steps/define.md +100 -33
- deepwork/standard_jobs/deepwork_jobs/steps/errata.md +154 -0
- deepwork/standard_jobs/deepwork_jobs/steps/fix_jobs.md +207 -0
- deepwork/standard_jobs/deepwork_jobs/steps/fix_settings.md +177 -0
- deepwork/standard_jobs/deepwork_jobs/steps/implement.md +22 -138
- deepwork/standard_jobs/deepwork_jobs/steps/iterate.md +221 -0
- deepwork/standard_jobs/deepwork_jobs/steps/learn.md +2 -26
- deepwork/standard_jobs/deepwork_jobs/steps/test.md +154 -0
- deepwork/standard_jobs/deepwork_jobs/templates/job.yml.template +2 -0
- deepwork/templates/claude/settings.json +16 -0
- deepwork/templates/claude/skill-deepwork.md.jinja +37 -0
- deepwork/templates/gemini/skill-deepwork.md.jinja +37 -0
- deepwork-0.7.0.dist-info/METADATA +317 -0
- deepwork-0.7.0.dist-info/RECORD +64 -0
- deepwork/cli/rules.py +0 -32
- deepwork/core/command_executor.py +0 -190
- deepwork/core/pattern_matcher.py +0 -271
- deepwork/core/rules_parser.py +0 -559
- deepwork/core/rules_queue.py +0 -321
- deepwork/hooks/rules_check.py +0 -759
- deepwork/schemas/rules_schema.py +0 -135
- deepwork/standard_jobs/deepwork_jobs/steps/review_job_spec.md +0 -208
- deepwork/standard_jobs/deepwork_jobs/templates/doc_spec.md.example +0 -86
- deepwork/standard_jobs/deepwork_rules/hooks/capture_prompt_work_tree.sh +0 -38
- deepwork/standard_jobs/deepwork_rules/hooks/global_hooks.yml +0 -8
- deepwork/standard_jobs/deepwork_rules/hooks/user_prompt_submit.sh +0 -16
- deepwork/standard_jobs/deepwork_rules/job.yml +0 -49
- deepwork/standard_jobs/deepwork_rules/rules/.gitkeep +0 -13
- deepwork/standard_jobs/deepwork_rules/rules/api-documentation-sync.md.example +0 -10
- deepwork/standard_jobs/deepwork_rules/rules/readme-documentation.md.example +0 -10
- deepwork/standard_jobs/deepwork_rules/rules/security-review.md.example +0 -11
- deepwork/standard_jobs/deepwork_rules/rules/skill-md-validation.md +0 -46
- deepwork/standard_jobs/deepwork_rules/rules/source-test-pairing.md.example +0 -13
- deepwork/standard_jobs/deepwork_rules/steps/define.md +0 -249
- deepwork/templates/claude/skill-job-meta.md.jinja +0 -77
- deepwork/templates/claude/skill-job-step.md.jinja +0 -235
- deepwork/templates/gemini/skill-job-meta.toml.jinja +0 -76
- deepwork/templates/gemini/skill-job-step.toml.jinja +0 -162
- deepwork-0.5.1.dist-info/METADATA +0 -381
- deepwork-0.5.1.dist-info/RECORD +0 -72
- {deepwork-0.5.1.dist-info → deepwork-0.7.0.dist-info}/WHEEL +0 -0
- {deepwork-0.5.1.dist-info → deepwork-0.7.0.dist-info}/entry_points.txt +0 -0
- {deepwork-0.5.1.dist-info → deepwork-0.7.0.dist-info}/licenses/LICENSE.md +0 -0
deepwork/core/pattern_matcher.py
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
"""Pattern matching with variable extraction for rule file correspondence."""
|
|
2
|
-
|
|
3
|
-
import re
|
|
4
|
-
from dataclasses import dataclass
|
|
5
|
-
from fnmatch import fnmatch
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class PatternError(Exception):
|
|
9
|
-
"""Exception raised for invalid pattern syntax."""
|
|
10
|
-
|
|
11
|
-
pass
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@dataclass
|
|
15
|
-
class MatchResult:
|
|
16
|
-
"""Result of matching a file against a pattern."""
|
|
17
|
-
|
|
18
|
-
matched: bool
|
|
19
|
-
variables: dict[str, str] # Captured variable values
|
|
20
|
-
|
|
21
|
-
@classmethod
|
|
22
|
-
def no_match(cls) -> "MatchResult":
|
|
23
|
-
return cls(matched=False, variables={})
|
|
24
|
-
|
|
25
|
-
@classmethod
|
|
26
|
-
def match(cls, variables: dict[str, str] | None = None) -> "MatchResult":
|
|
27
|
-
return cls(matched=True, variables=variables or {})
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def validate_pattern(pattern: str) -> None:
|
|
31
|
-
"""
|
|
32
|
-
Validate pattern syntax.
|
|
33
|
-
|
|
34
|
-
Raises:
|
|
35
|
-
PatternError: If pattern has invalid syntax
|
|
36
|
-
"""
|
|
37
|
-
# Check for unbalanced braces
|
|
38
|
-
brace_depth = 0
|
|
39
|
-
for i, char in enumerate(pattern):
|
|
40
|
-
if char == "{":
|
|
41
|
-
brace_depth += 1
|
|
42
|
-
elif char == "}":
|
|
43
|
-
brace_depth -= 1
|
|
44
|
-
if brace_depth < 0:
|
|
45
|
-
raise PatternError(f"Unmatched closing brace at position {i}")
|
|
46
|
-
|
|
47
|
-
if brace_depth > 0:
|
|
48
|
-
raise PatternError("Unclosed brace in pattern")
|
|
49
|
-
|
|
50
|
-
# Extract and validate variable names
|
|
51
|
-
var_pattern = r"\{([^}]*)\}"
|
|
52
|
-
seen_vars: set[str] = set()
|
|
53
|
-
|
|
54
|
-
for match in re.finditer(var_pattern, pattern):
|
|
55
|
-
var_name = match.group(1)
|
|
56
|
-
|
|
57
|
-
# Check for empty variable name
|
|
58
|
-
if not var_name:
|
|
59
|
-
raise PatternError("Empty variable name in pattern")
|
|
60
|
-
|
|
61
|
-
# Strip leading ** or * for validation
|
|
62
|
-
clean_name = var_name.lstrip("*")
|
|
63
|
-
if not clean_name:
|
|
64
|
-
# Just {*} or {**} is valid
|
|
65
|
-
continue
|
|
66
|
-
|
|
67
|
-
# Check for invalid characters in variable name
|
|
68
|
-
if "/" in clean_name or "\\" in clean_name:
|
|
69
|
-
raise PatternError(f"Invalid character in variable name: {var_name}")
|
|
70
|
-
|
|
71
|
-
# Check for duplicates (use clean name for comparison)
|
|
72
|
-
if clean_name in seen_vars:
|
|
73
|
-
raise PatternError(f"Duplicate variable: {clean_name}")
|
|
74
|
-
seen_vars.add(clean_name)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def pattern_to_regex(pattern: str) -> tuple[str, list[str]]:
|
|
78
|
-
"""
|
|
79
|
-
Convert a pattern with {var} placeholders to a regex.
|
|
80
|
-
|
|
81
|
-
Variables:
|
|
82
|
-
- {path} or {**name} - Matches multiple path segments (.+)
|
|
83
|
-
- {name} or {*name} - Matches single path segment ([^/]+)
|
|
84
|
-
|
|
85
|
-
Args:
|
|
86
|
-
pattern: Pattern string like "src/{path}.py"
|
|
87
|
-
|
|
88
|
-
Returns:
|
|
89
|
-
Tuple of (regex_pattern, list_of_variable_names)
|
|
90
|
-
|
|
91
|
-
Raises:
|
|
92
|
-
PatternError: If pattern has invalid syntax
|
|
93
|
-
"""
|
|
94
|
-
validate_pattern(pattern)
|
|
95
|
-
|
|
96
|
-
# Normalize path separators
|
|
97
|
-
pattern = pattern.replace("\\", "/")
|
|
98
|
-
|
|
99
|
-
result: list[str] = []
|
|
100
|
-
var_names: list[str] = []
|
|
101
|
-
pos = 0
|
|
102
|
-
|
|
103
|
-
# Parse pattern segments
|
|
104
|
-
while pos < len(pattern):
|
|
105
|
-
# Look for next variable
|
|
106
|
-
brace_start = pattern.find("{", pos)
|
|
107
|
-
|
|
108
|
-
if brace_start == -1:
|
|
109
|
-
# No more variables, escape the rest
|
|
110
|
-
result.append(re.escape(pattern[pos:]))
|
|
111
|
-
break
|
|
112
|
-
|
|
113
|
-
# Escape literal part before variable
|
|
114
|
-
if brace_start > pos:
|
|
115
|
-
result.append(re.escape(pattern[pos:brace_start]))
|
|
116
|
-
|
|
117
|
-
# Find end of variable
|
|
118
|
-
brace_end = pattern.find("}", brace_start)
|
|
119
|
-
if brace_end == -1:
|
|
120
|
-
raise PatternError("Unclosed brace in pattern")
|
|
121
|
-
|
|
122
|
-
var_spec = pattern[brace_start + 1 : brace_end]
|
|
123
|
-
|
|
124
|
-
# Determine variable type and name
|
|
125
|
-
if var_spec.startswith("**"):
|
|
126
|
-
# Explicit multi-segment: {**name}
|
|
127
|
-
var_name = var_spec[2:] or "path"
|
|
128
|
-
regex_part = f"(?P<{re.escape(var_name)}>.+)"
|
|
129
|
-
elif var_spec.startswith("*"):
|
|
130
|
-
# Explicit single-segment: {*name}
|
|
131
|
-
var_name = var_spec[1:] or "name"
|
|
132
|
-
regex_part = f"(?P<{re.escape(var_name)}>[^/]+)"
|
|
133
|
-
elif var_spec == "path":
|
|
134
|
-
# Conventional multi-segment
|
|
135
|
-
var_name = "path"
|
|
136
|
-
regex_part = "(?P<path>.+)"
|
|
137
|
-
else:
|
|
138
|
-
# Default single-segment (including custom names)
|
|
139
|
-
var_name = var_spec
|
|
140
|
-
regex_part = f"(?P<{re.escape(var_name)}>[^/]+)"
|
|
141
|
-
|
|
142
|
-
result.append(regex_part)
|
|
143
|
-
var_names.append(var_name)
|
|
144
|
-
pos = brace_end + 1
|
|
145
|
-
|
|
146
|
-
return "^" + "".join(result) + "$", var_names
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def match_pattern(pattern: str, filepath: str) -> MatchResult:
|
|
150
|
-
"""
|
|
151
|
-
Match a filepath against a pattern, extracting variables.
|
|
152
|
-
|
|
153
|
-
Args:
|
|
154
|
-
pattern: Pattern with {var} placeholders
|
|
155
|
-
filepath: File path to match
|
|
156
|
-
|
|
157
|
-
Returns:
|
|
158
|
-
MatchResult with matched=True and captured variables, or matched=False
|
|
159
|
-
"""
|
|
160
|
-
# Normalize path separators
|
|
161
|
-
filepath = filepath.replace("\\", "/")
|
|
162
|
-
|
|
163
|
-
try:
|
|
164
|
-
regex, _ = pattern_to_regex(pattern)
|
|
165
|
-
except PatternError:
|
|
166
|
-
return MatchResult.no_match()
|
|
167
|
-
|
|
168
|
-
match = re.fullmatch(regex, filepath)
|
|
169
|
-
if match:
|
|
170
|
-
return MatchResult.match(match.groupdict())
|
|
171
|
-
return MatchResult.no_match()
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
def resolve_pattern(pattern: str, variables: dict[str, str]) -> str:
|
|
175
|
-
"""
|
|
176
|
-
Substitute variables into a pattern to generate a filepath.
|
|
177
|
-
|
|
178
|
-
Args:
|
|
179
|
-
pattern: Pattern with {var} placeholders
|
|
180
|
-
variables: Dict of variable name -> value
|
|
181
|
-
|
|
182
|
-
Returns:
|
|
183
|
-
Resolved filepath string
|
|
184
|
-
"""
|
|
185
|
-
result = pattern
|
|
186
|
-
for name, value in variables.items():
|
|
187
|
-
# Handle both {name} and {*name} / {**name} forms
|
|
188
|
-
result = result.replace(f"{{{name}}}", value)
|
|
189
|
-
result = result.replace(f"{{*{name}}}", value)
|
|
190
|
-
result = result.replace(f"{{**{name}}}", value)
|
|
191
|
-
return result
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
def matches_glob(file_path: str, pattern: str) -> bool:
|
|
195
|
-
"""
|
|
196
|
-
Match a file path against a glob pattern, supporting ** for recursive matching.
|
|
197
|
-
|
|
198
|
-
This is for simple glob patterns without variable capture.
|
|
199
|
-
|
|
200
|
-
Args:
|
|
201
|
-
file_path: File path to check
|
|
202
|
-
pattern: Glob pattern (supports *, **, ?)
|
|
203
|
-
|
|
204
|
-
Returns:
|
|
205
|
-
True if matches
|
|
206
|
-
"""
|
|
207
|
-
# Normalize path separators
|
|
208
|
-
file_path = file_path.replace("\\", "/")
|
|
209
|
-
pattern = pattern.replace("\\", "/")
|
|
210
|
-
|
|
211
|
-
# Handle ** patterns (recursive directory matching)
|
|
212
|
-
if "**" in pattern:
|
|
213
|
-
# Split pattern by **
|
|
214
|
-
parts = pattern.split("**")
|
|
215
|
-
|
|
216
|
-
if len(parts) == 2:
|
|
217
|
-
prefix, suffix = parts[0], parts[1]
|
|
218
|
-
|
|
219
|
-
# Remove leading/trailing slashes from suffix
|
|
220
|
-
suffix = suffix.lstrip("/")
|
|
221
|
-
|
|
222
|
-
# Check if prefix matches the start of the path
|
|
223
|
-
if prefix:
|
|
224
|
-
prefix = prefix.rstrip("/")
|
|
225
|
-
if not file_path.startswith(prefix + "/") and file_path != prefix:
|
|
226
|
-
return False
|
|
227
|
-
# Get the remaining path after prefix
|
|
228
|
-
remaining = file_path[len(prefix) :].lstrip("/")
|
|
229
|
-
else:
|
|
230
|
-
remaining = file_path
|
|
231
|
-
|
|
232
|
-
# If no suffix, any remaining path matches
|
|
233
|
-
if not suffix:
|
|
234
|
-
return True
|
|
235
|
-
|
|
236
|
-
# Check if suffix matches the end of any remaining path segment
|
|
237
|
-
remaining_parts = remaining.split("/")
|
|
238
|
-
for i in range(len(remaining_parts)):
|
|
239
|
-
test_path = "/".join(remaining_parts[i:])
|
|
240
|
-
if fnmatch(test_path, suffix):
|
|
241
|
-
return True
|
|
242
|
-
# Also try just the filename
|
|
243
|
-
if fnmatch(remaining_parts[-1], suffix):
|
|
244
|
-
return True
|
|
245
|
-
|
|
246
|
-
return False
|
|
247
|
-
|
|
248
|
-
# Simple pattern without **
|
|
249
|
-
return fnmatch(file_path, pattern)
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
def matches_any_pattern(file_path: str, patterns: list[str]) -> bool:
|
|
253
|
-
"""
|
|
254
|
-
Check if a file path matches any of the given glob patterns.
|
|
255
|
-
|
|
256
|
-
Args:
|
|
257
|
-
file_path: File path to check (relative path)
|
|
258
|
-
patterns: List of glob patterns to match against
|
|
259
|
-
|
|
260
|
-
Returns:
|
|
261
|
-
True if the file matches any pattern
|
|
262
|
-
"""
|
|
263
|
-
for pattern in patterns:
|
|
264
|
-
if matches_glob(file_path, pattern):
|
|
265
|
-
return True
|
|
266
|
-
return False
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
def has_variables(pattern: str) -> bool:
|
|
270
|
-
"""Check if a pattern contains variable placeholders."""
|
|
271
|
-
return "{" in pattern and "}" in pattern
|