devrel-origin 0.2.14__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 (98) hide show
  1. devrel_origin/__init__.py +15 -0
  2. devrel_origin/cli/__init__.py +92 -0
  3. devrel_origin/cli/_common.py +243 -0
  4. devrel_origin/cli/analytics.py +28 -0
  5. devrel_origin/cli/argus.py +497 -0
  6. devrel_origin/cli/auth.py +227 -0
  7. devrel_origin/cli/config.py +108 -0
  8. devrel_origin/cli/content.py +259 -0
  9. devrel_origin/cli/cost.py +108 -0
  10. devrel_origin/cli/cro.py +298 -0
  11. devrel_origin/cli/deliverables.py +65 -0
  12. devrel_origin/cli/docs.py +91 -0
  13. devrel_origin/cli/doctor.py +178 -0
  14. devrel_origin/cli/experiment.py +29 -0
  15. devrel_origin/cli/growth.py +97 -0
  16. devrel_origin/cli/init.py +472 -0
  17. devrel_origin/cli/intel.py +27 -0
  18. devrel_origin/cli/kb.py +96 -0
  19. devrel_origin/cli/listen.py +31 -0
  20. devrel_origin/cli/marketing.py +66 -0
  21. devrel_origin/cli/migrate.py +45 -0
  22. devrel_origin/cli/run.py +46 -0
  23. devrel_origin/cli/sales.py +57 -0
  24. devrel_origin/cli/schedule.py +62 -0
  25. devrel_origin/cli/synthesize.py +28 -0
  26. devrel_origin/cli/triage.py +29 -0
  27. devrel_origin/cli/video.py +35 -0
  28. devrel_origin/core/__init__.py +58 -0
  29. devrel_origin/core/agent_config.py +75 -0
  30. devrel_origin/core/argus.py +964 -0
  31. devrel_origin/core/atlas.py +1450 -0
  32. devrel_origin/core/base.py +372 -0
  33. devrel_origin/core/cyra.py +563 -0
  34. devrel_origin/core/dex.py +708 -0
  35. devrel_origin/core/echo.py +614 -0
  36. devrel_origin/core/growth/__init__.py +27 -0
  37. devrel_origin/core/growth/recommendations.py +219 -0
  38. devrel_origin/core/growth/target_kinds.py +51 -0
  39. devrel_origin/core/iris.py +513 -0
  40. devrel_origin/core/kai.py +1367 -0
  41. devrel_origin/core/llm.py +542 -0
  42. devrel_origin/core/llm_backends.py +274 -0
  43. devrel_origin/core/mox.py +514 -0
  44. devrel_origin/core/nova.py +349 -0
  45. devrel_origin/core/pax.py +1205 -0
  46. devrel_origin/core/rex.py +532 -0
  47. devrel_origin/core/sage.py +486 -0
  48. devrel_origin/core/sentinel.py +385 -0
  49. devrel_origin/core/types.py +98 -0
  50. devrel_origin/core/video/__init__.py +22 -0
  51. devrel_origin/core/video/assembler.py +131 -0
  52. devrel_origin/core/video/browser_recorder.py +118 -0
  53. devrel_origin/core/video/desktop_recorder.py +254 -0
  54. devrel_origin/core/video/overlay_renderer.py +143 -0
  55. devrel_origin/core/video/script_parser.py +147 -0
  56. devrel_origin/core/video/tts_engine.py +82 -0
  57. devrel_origin/core/vox.py +268 -0
  58. devrel_origin/core/watchdog.py +321 -0
  59. devrel_origin/project/__init__.py +1 -0
  60. devrel_origin/project/config.py +75 -0
  61. devrel_origin/project/cost_sink.py +61 -0
  62. devrel_origin/project/init.py +104 -0
  63. devrel_origin/project/paths.py +75 -0
  64. devrel_origin/project/state.py +241 -0
  65. devrel_origin/project/templates/__init__.py +4 -0
  66. devrel_origin/project/templates/config.toml +24 -0
  67. devrel_origin/project/templates/devrel.gitignore +10 -0
  68. devrel_origin/project/templates/slop-blocklist.md +45 -0
  69. devrel_origin/project/templates/style.md +24 -0
  70. devrel_origin/project/templates/voice.md +29 -0
  71. devrel_origin/quality/__init__.py +66 -0
  72. devrel_origin/quality/editorial.py +357 -0
  73. devrel_origin/quality/persona.py +84 -0
  74. devrel_origin/quality/readability.py +148 -0
  75. devrel_origin/quality/slop.py +167 -0
  76. devrel_origin/quality/style.py +110 -0
  77. devrel_origin/quality/voice.py +15 -0
  78. devrel_origin/tools/__init__.py +9 -0
  79. devrel_origin/tools/analytics.py +304 -0
  80. devrel_origin/tools/api_client.py +393 -0
  81. devrel_origin/tools/apollo_client.py +305 -0
  82. devrel_origin/tools/code_validator.py +428 -0
  83. devrel_origin/tools/github_tools.py +297 -0
  84. devrel_origin/tools/instantly_client.py +412 -0
  85. devrel_origin/tools/kb_harvester.py +340 -0
  86. devrel_origin/tools/mcp_server.py +578 -0
  87. devrel_origin/tools/notifications.py +245 -0
  88. devrel_origin/tools/run_report.py +193 -0
  89. devrel_origin/tools/scheduler.py +231 -0
  90. devrel_origin/tools/search_tools.py +321 -0
  91. devrel_origin/tools/self_improve.py +168 -0
  92. devrel_origin/tools/sheets.py +236 -0
  93. devrel_origin-0.2.14.dist-info/METADATA +354 -0
  94. devrel_origin-0.2.14.dist-info/RECORD +98 -0
  95. devrel_origin-0.2.14.dist-info/WHEEL +5 -0
  96. devrel_origin-0.2.14.dist-info/entry_points.txt +2 -0
  97. devrel_origin-0.2.14.dist-info/licenses/LICENSE +21 -0
  98. devrel_origin-0.2.14.dist-info/top_level.txt +1 -0
@@ -0,0 +1,428 @@
1
+ """
2
+ Code Validator — Syntax validation for code snippets in generated content.
3
+
4
+ Extracts fenced code blocks from markdown and validates syntax per language.
5
+ Supports: Python (ast.parse), JavaScript (esprima/basic checks), JSON (json.loads),
6
+ HTML (basic tag balance), and reports on unsupported languages without failing.
7
+ """
8
+
9
+ import ast
10
+ import json
11
+ import logging
12
+ import re
13
+ import shlex
14
+ from dataclasses import dataclass, field
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ CODE_BLOCK_RE = re.compile(r"```(\w*)\n(.*?)```", re.DOTALL)
19
+
20
+ # Languages where we can validate syntax or safety heuristics
21
+ SUPPORTED_LANGUAGES = {
22
+ "python",
23
+ "py",
24
+ "javascript",
25
+ "js",
26
+ "json",
27
+ "html",
28
+ "sql",
29
+ "yaml",
30
+ "yml",
31
+ "bash",
32
+ "sh",
33
+ "shell",
34
+ "zsh",
35
+ }
36
+
37
+ # Languages we skip validation for.
38
+ SKIP_LANGUAGES = {"toml", "nginx", "css", "text", ""}
39
+
40
+
41
+ @dataclass
42
+ class CodeBlock:
43
+ """A single extracted code block."""
44
+
45
+ language: str
46
+ code: str
47
+ line_number: int # approximate line in the source markdown
48
+
49
+
50
+ @dataclass
51
+ class ValidationResult:
52
+ """Result of validating a single code block."""
53
+
54
+ block: CodeBlock
55
+ is_valid: bool
56
+ error: str = ""
57
+
58
+
59
+ @dataclass
60
+ class ValidationReport:
61
+ """Aggregated validation results for a piece of content."""
62
+
63
+ total_blocks: int = 0
64
+ validated: int = 0
65
+ passed: int = 0
66
+ failed: int = 0
67
+ skipped: int = 0
68
+ errors: list[ValidationResult] = field(default_factory=list)
69
+
70
+ @property
71
+ def all_passed(self) -> bool:
72
+ return self.failed == 0
73
+
74
+
75
+ class CodeValidator:
76
+ """
77
+ Validates code blocks extracted from markdown content.
78
+
79
+ Performs syntax-level validation:
80
+ - Python: ast.parse() for syntax correctness
81
+ - JavaScript: checks balanced braces/brackets/parens and basic syntax
82
+ - JSON: json.loads() for valid JSON
83
+ - HTML: checks balanced tags
84
+
85
+ Does NOT execute code or check runtime correctness.
86
+ """
87
+
88
+ def extract_code_blocks(self, markdown: str) -> list[CodeBlock]:
89
+ """Extract all fenced code blocks from markdown content."""
90
+ blocks = []
91
+ for match in CODE_BLOCK_RE.finditer(markdown):
92
+ lang = match.group(1).lower().strip()
93
+ code = match.group(2).strip()
94
+ # Approximate line number
95
+ line_num = markdown[: match.start()].count("\n") + 1
96
+ if code:
97
+ blocks.append(CodeBlock(language=lang, code=code, line_number=line_num))
98
+ return blocks
99
+
100
+ def validate_block(self, block: CodeBlock) -> ValidationResult:
101
+ """Validate a single code block based on its language."""
102
+ lang = block.language
103
+
104
+ if lang in SKIP_LANGUAGES:
105
+ return ValidationResult(block=block, is_valid=True)
106
+
107
+ if lang in ("python", "py"):
108
+ return self._validate_python(block)
109
+ elif lang in ("javascript", "js"):
110
+ return self._validate_javascript(block)
111
+ elif lang == "json":
112
+ return self._validate_json(block)
113
+ elif lang == "html":
114
+ return self._validate_html(block)
115
+ elif lang == "sql":
116
+ return self._validate_sql(block)
117
+ elif lang in ("yaml", "yml"):
118
+ return self._validate_yaml(block)
119
+ elif lang in ("bash", "sh", "shell", "zsh"):
120
+ return self._validate_shell(block)
121
+ else:
122
+ # Unknown language — skip, don't fail
123
+ return ValidationResult(block=block, is_valid=True)
124
+
125
+ def validate_content(self, markdown: str) -> ValidationReport:
126
+ """Validate all code blocks in a markdown document."""
127
+ blocks = self.extract_code_blocks(markdown)
128
+ report = ValidationReport(total_blocks=len(blocks))
129
+
130
+ for block in blocks:
131
+ if block.language in SKIP_LANGUAGES or block.language not in SUPPORTED_LANGUAGES:
132
+ report.skipped += 1
133
+ continue
134
+
135
+ result = self.validate_block(block)
136
+ report.validated += 1
137
+
138
+ if result.is_valid:
139
+ report.passed += 1
140
+ else:
141
+ report.failed += 1
142
+ report.errors.append(result)
143
+ logger.warning(
144
+ f"Code validation failed (line ~{block.line_number}, "
145
+ f"{block.language}): {result.error}"
146
+ )
147
+
148
+ return report
149
+
150
+ def _validate_python(self, block: CodeBlock) -> ValidationResult:
151
+ """Validate Python syntax using ast.parse()."""
152
+ code = block.code
153
+
154
+ # Strip common non-parseable patterns that are valid in tutorials
155
+ # e.g., lines with "..." for ellipsis/placeholder
156
+ lines = code.splitlines()
157
+ cleaned_lines = []
158
+ for line in lines:
159
+ stripped = line.strip()
160
+ # Skip pure ellipsis lines (common in tutorial snippets)
161
+ if stripped == "...":
162
+ cleaned_lines.append(line.replace("...", "pass"))
163
+ # Skip lines that are just comments
164
+ elif stripped.startswith("#"):
165
+ cleaned_lines.append(line)
166
+ else:
167
+ cleaned_lines.append(line)
168
+ cleaned = "\n".join(cleaned_lines)
169
+
170
+ try:
171
+ ast.parse(cleaned)
172
+ return ValidationResult(block=block, is_valid=True)
173
+ except SyntaxError as e:
174
+ return ValidationResult(
175
+ block=block,
176
+ is_valid=False,
177
+ error=f"Python syntax error at line {e.lineno}: {e.msg}",
178
+ )
179
+
180
+ def _validate_javascript(self, block: CodeBlock) -> ValidationResult:
181
+ """Basic JavaScript syntax validation — checks balanced delimiters and common errors."""
182
+ code = block.code
183
+
184
+ # Strip string literals and comments to avoid false positives
185
+ # Remove single-line comments
186
+ stripped = re.sub(r"//.*$", "", code, flags=re.MULTILINE)
187
+ # Remove multi-line comments
188
+ stripped = re.sub(r"/\*.*?\*/", "", stripped, flags=re.DOTALL)
189
+ # Remove template literals
190
+ stripped = re.sub(r"`[^`]*`", '""', stripped)
191
+ # Remove double-quoted strings
192
+ stripped = re.sub(r'"(?:[^"\\]|\\.)*"', '""', stripped)
193
+ # Remove single-quoted strings
194
+ stripped = re.sub(r"'(?:[^'\\]|\\.)*'", "''", stripped)
195
+
196
+ # Check balanced delimiters
197
+ stack = []
198
+ pairs = {")": "(", "]": "[", "}": "{"}
199
+ for i, ch in enumerate(stripped):
200
+ if ch in "({[":
201
+ stack.append(ch)
202
+ elif ch in ")}]":
203
+ if not stack:
204
+ return ValidationResult(
205
+ block=block,
206
+ is_valid=False,
207
+ error=f"Unmatched closing '{ch}' at position {i}",
208
+ )
209
+ if stack[-1] != pairs[ch]:
210
+ return ValidationResult(
211
+ block=block,
212
+ is_valid=False,
213
+ error=f"Mismatched '{stack[-1]}' and '{ch}' at position {i}",
214
+ )
215
+ stack.pop()
216
+
217
+ if stack:
218
+ return ValidationResult(
219
+ block=block,
220
+ is_valid=False,
221
+ error=f"Unclosed delimiter: '{stack[-1]}'",
222
+ )
223
+
224
+ return ValidationResult(block=block, is_valid=True)
225
+
226
+ def _validate_yaml(self, block: CodeBlock) -> ValidationResult:
227
+ """Validate YAML syntax and flag stale GitHub Actions examples."""
228
+ if re.search(r"actions/(?:upload|download)-artifact@v3\b", block.code):
229
+ return ValidationResult(
230
+ block=block,
231
+ is_valid=False,
232
+ error="Deprecated GitHub Actions artifact action v3; use v4.",
233
+ )
234
+
235
+ try:
236
+ import yaml
237
+ except ImportError:
238
+ return ValidationResult(
239
+ block=block,
240
+ is_valid=False,
241
+ error="PyYAML is required for YAML validation.",
242
+ )
243
+
244
+ try:
245
+ yaml.safe_load(block.code)
246
+ return ValidationResult(block=block, is_valid=True)
247
+ except yaml.YAMLError as e:
248
+ return ValidationResult(block=block, is_valid=False, error=f"Invalid YAML: {e}")
249
+
250
+ def _validate_shell(self, block: CodeBlock) -> ValidationResult:
251
+ """Validate shell snippets for parseability and obvious destructive commands."""
252
+ pending = ""
253
+ pending_start = 0
254
+ for offset, line in enumerate(block.code.splitlines(), start=1):
255
+ stripped = line.strip()
256
+ if not pending and (not stripped or stripped.startswith("#")):
257
+ continue
258
+
259
+ if re.search(r"\brm\s+-[rfRf-]*\s+/(?:\s|$)", stripped):
260
+ return ValidationResult(
261
+ block=block,
262
+ is_valid=False,
263
+ error=f"Unsafe shell command at line {offset}: refuses to delete root.",
264
+ )
265
+
266
+ if pending:
267
+ current = f"{pending}\n{stripped}"
268
+ start = pending_start
269
+ else:
270
+ current = stripped
271
+ start = offset
272
+
273
+ if current.rstrip().endswith("\\"):
274
+ pending = current.rstrip()[:-1].rstrip() + " "
275
+ pending_start = start
276
+ continue
277
+
278
+ try:
279
+ shlex.split(current)
280
+ except ValueError as e:
281
+ if "No closing quotation" in str(e):
282
+ pending = current
283
+ pending_start = start
284
+ continue
285
+ return ValidationResult(
286
+ block=block,
287
+ is_valid=False,
288
+ error=f"Shell parse error at line {start}: {e}",
289
+ )
290
+ pending = ""
291
+ pending_start = 0
292
+
293
+ if pending:
294
+ try:
295
+ shlex.split(pending)
296
+ except ValueError as e:
297
+ return ValidationResult(
298
+ block=block,
299
+ is_valid=False,
300
+ error=f"Shell parse error at line {pending_start}: {e}",
301
+ )
302
+
303
+ return ValidationResult(block=block, is_valid=True)
304
+
305
+ def _validate_json(self, block: CodeBlock) -> ValidationResult:
306
+ """Validate JSON using json.loads()."""
307
+ try:
308
+ json.loads(block.code)
309
+ return ValidationResult(block=block, is_valid=True)
310
+ except json.JSONDecodeError as e:
311
+ return ValidationResult(
312
+ block=block,
313
+ is_valid=False,
314
+ error=f"Invalid JSON at line {e.lineno}, col {e.colno}: {e.msg}",
315
+ )
316
+
317
+ def _validate_html(self, block: CodeBlock) -> ValidationResult:
318
+ """Basic HTML validation — checks that opened tags are closed."""
319
+ from html.parser import HTMLParser
320
+
321
+ class TagChecker(HTMLParser):
322
+ def __init__(self):
323
+ super().__init__()
324
+ self.stack: list[str] = []
325
+ self.error: str = ""
326
+ # Void elements that don't need closing tags
327
+ self.void_tags = {
328
+ "area",
329
+ "base",
330
+ "br",
331
+ "col",
332
+ "embed",
333
+ "hr",
334
+ "img",
335
+ "input",
336
+ "link",
337
+ "meta",
338
+ "param",
339
+ "source",
340
+ "track",
341
+ "wbr",
342
+ }
343
+
344
+ def handle_starttag(self, tag, _attrs):
345
+ # _attrs is required by HTMLParser's interface but unused here.
346
+ if tag.lower() not in self.void_tags:
347
+ self.stack.append(tag.lower())
348
+
349
+ def handle_endtag(self, tag):
350
+ tag = tag.lower()
351
+ if tag in self.void_tags:
352
+ return
353
+ if not self.stack:
354
+ self.error = f"Unexpected closing tag </{tag}>"
355
+ elif self.stack[-1] != tag:
356
+ self.error = f"Mismatched tags: expected </{self.stack[-1]}>, got </{tag}>"
357
+ else:
358
+ self.stack.pop()
359
+
360
+ checker = TagChecker()
361
+ try:
362
+ checker.feed(block.code)
363
+ except Exception as e:
364
+ return ValidationResult(block=block, is_valid=False, error=f"HTML parse error: {e}")
365
+
366
+ if checker.error:
367
+ return ValidationResult(block=block, is_valid=False, error=checker.error)
368
+ if checker.stack:
369
+ return ValidationResult(
370
+ block=block,
371
+ is_valid=False,
372
+ error=f"Unclosed tags: {', '.join(f'<{t}>' for t in checker.stack)}",
373
+ )
374
+ return ValidationResult(block=block, is_valid=True)
375
+
376
+ def _validate_sql(self, block: CodeBlock) -> ValidationResult:
377
+ """Basic SQL validation — checks balanced parentheses and statement structure."""
378
+ code = block.code.strip()
379
+
380
+ # Check balanced parentheses
381
+ depth = 0
382
+ for ch in code:
383
+ if ch == "(":
384
+ depth += 1
385
+ elif ch == ")":
386
+ depth -= 1
387
+ if depth < 0:
388
+ return ValidationResult(
389
+ block=block, is_valid=False, error="Unmatched closing parenthesis"
390
+ )
391
+ if depth != 0:
392
+ return ValidationResult(
393
+ block=block, is_valid=False, error="Unclosed parenthesis in SQL"
394
+ )
395
+
396
+ # Check that it starts with a known SQL keyword
397
+ first_word = code.split()[0].upper() if code.split() else ""
398
+ sql_keywords = {
399
+ "SELECT",
400
+ "INSERT",
401
+ "UPDATE",
402
+ "DELETE",
403
+ "CREATE",
404
+ "ALTER",
405
+ "DROP",
406
+ "WITH",
407
+ "EXPLAIN",
408
+ "SET",
409
+ "SYSTEM",
410
+ "GRANT",
411
+ "REVOKE",
412
+ "BEGIN",
413
+ "COMMIT",
414
+ "ROLLBACK",
415
+ "TRUNCATE",
416
+ "MERGE",
417
+ "CALL",
418
+ "DECLARE",
419
+ "--",
420
+ }
421
+ if first_word and first_word not in sql_keywords:
422
+ return ValidationResult(
423
+ block=block,
424
+ is_valid=False,
425
+ error=f"SQL doesn't start with a known keyword: '{first_word}'",
426
+ )
427
+
428
+ return ValidationResult(block=block, is_valid=True)