deviatdd 2.5.1__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.
- deviatdd-2.5.1.dist-info/METADATA +386 -0
- deviatdd-2.5.1.dist-info/RECORD +124 -0
- deviatdd-2.5.1.dist-info/WHEEL +4 -0
- deviatdd-2.5.1.dist-info/entry_points.txt +2 -0
- deviatdd-2.5.1.dist-info/licenses/LICENSE +21 -0
- deviate/__init__.py +3 -0
- deviate/cli/__init__.py +824 -0
- deviate/cli/_common.py +155 -0
- deviate/cli/adhoc.py +183 -0
- deviate/cli/constitution.py +113 -0
- deviate/cli/feature.py +94 -0
- deviate/cli/init.py +485 -0
- deviate/cli/inspect.py +208 -0
- deviate/cli/macro.py +937 -0
- deviate/cli/meso.py +1894 -0
- deviate/cli/micro.py +3249 -0
- deviate/cli/review.py +441 -0
- deviate/core/__init__.py +0 -0
- deviate/core/_shared.py +20 -0
- deviate/core/agent.py +505 -0
- deviate/core/cache_discipline.py +65 -0
- deviate/core/commands.py +202 -0
- deviate/core/commit.py +86 -0
- deviate/core/complexity.py +36 -0
- deviate/core/constitution.py +85 -0
- deviate/core/contract.py +17 -0
- deviate/core/convention.py +147 -0
- deviate/core/epic.py +73 -0
- deviate/core/issues.py +46 -0
- deviate/core/prd.py +18 -0
- deviate/core/profile.py +33 -0
- deviate/core/repo.py +47 -0
- deviate/core/run_logger.py +50 -0
- deviate/core/tasks_ledger.py +69 -0
- deviate/core/treesitter/__init__.py +31 -0
- deviate/core/treesitter/analysis.py +457 -0
- deviate/core/treesitter/models.py +35 -0
- deviate/core/treesitter/parser.py +184 -0
- deviate/core/treesitter/queries/bash.scm +7 -0
- deviate/core/treesitter/queries/c_sharp.scm +11 -0
- deviate/core/treesitter/queries/cpp.scm +9 -0
- deviate/core/treesitter/queries/css.scm +4 -0
- deviate/core/treesitter/queries/dockerfile.scm +8 -0
- deviate/core/treesitter/queries/elixir.scm +6 -0
- deviate/core/treesitter/queries/go.scm +9 -0
- deviate/core/treesitter/queries/hcl.scm +3 -0
- deviate/core/treesitter/queries/html.scm +3 -0
- deviate/core/treesitter/queries/javascript.scm +12 -0
- deviate/core/treesitter/queries/json.scm +4 -0
- deviate/core/treesitter/queries/kotlin.scm +8 -0
- deviate/core/treesitter/queries/markdown.scm +4 -0
- deviate/core/treesitter/queries/python.scm +10 -0
- deviate/core/treesitter/queries/rust.scm +12 -0
- deviate/core/treesitter/queries/sql.scm +7 -0
- deviate/core/treesitter/queries/swift.scm +10 -0
- deviate/core/treesitter/queries/toml.scm +3 -0
- deviate/core/treesitter/queries/tsx.scm +14 -0
- deviate/core/treesitter/queries/typescript.scm +14 -0
- deviate/core/treesitter/queries/yaml.scm +4 -0
- deviate/core/validation.py +153 -0
- deviate/core/worktree.py +141 -0
- deviate/main.py +4 -0
- deviate/prompts/__init__.py +0 -0
- deviate/prompts/assembly.py +122 -0
- deviate/prompts/auto/__init__.py +1 -0
- deviate/prompts/auto/execute.md +62 -0
- deviate/prompts/auto/explore.md +103 -0
- deviate/prompts/auto/green.md +137 -0
- deviate/prompts/auto/judge.md +260 -0
- deviate/prompts/auto/plan.md +127 -0
- deviate/prompts/auto/prd.md +108 -0
- deviate/prompts/auto/red.md +156 -0
- deviate/prompts/auto/refactor.md +166 -0
- deviate/prompts/auto/research.md +111 -0
- deviate/prompts/auto/shard.md +90 -0
- deviate/prompts/auto/specify.md +77 -0
- deviate/prompts/auto/tasks.md +191 -0
- deviate/prompts/commands/deviate-adhoc.md +216 -0
- deviate/prompts/commands/deviate-architecture.md +147 -0
- deviate/prompts/commands/deviate-constitution.md +215 -0
- deviate/prompts/commands/deviate-e2e.md +264 -0
- deviate/prompts/commands/deviate-execute.md +223 -0
- deviate/prompts/commands/deviate-explore.md +253 -0
- deviate/prompts/commands/deviate-flows.md +226 -0
- deviate/prompts/commands/deviate-green.md +217 -0
- deviate/prompts/commands/deviate-hotfix.md +188 -0
- deviate/prompts/commands/deviate-init.md +170 -0
- deviate/prompts/commands/deviate-judge.md +193 -0
- deviate/prompts/commands/deviate-merge.md +221 -0
- deviate/prompts/commands/deviate-plan.md +158 -0
- deviate/prompts/commands/deviate-pr.md +144 -0
- deviate/prompts/commands/deviate-prd.md +216 -0
- deviate/prompts/commands/deviate-prune.md +260 -0
- deviate/prompts/commands/deviate-red.md +231 -0
- deviate/prompts/commands/deviate-refactor.md +211 -0
- deviate/prompts/commands/deviate-release.md +123 -0
- deviate/prompts/commands/deviate-research.md +359 -0
- deviate/prompts/commands/deviate-review.md +289 -0
- deviate/prompts/commands/deviate-shard.md +226 -0
- deviate/prompts/commands/deviate-tasks.md +281 -0
- deviate/prompts/commands/deviate-triage.md +141 -0
- deviate/prompts/constitution_seed.md +51 -0
- deviate/prompts/core/core.md +21 -0
- deviate/prompts/core/macro-auto.md +59 -0
- deviate/prompts/core/macro-command.md +54 -0
- deviate/prompts/core/macro-skill.md +54 -0
- deviate/prompts/core/meso-auto.md +63 -0
- deviate/prompts/core/meso-command.md +58 -0
- deviate/prompts/core/meso-skill.md +58 -0
- deviate/prompts/core/micro-auto.md +76 -0
- deviate/prompts/core/micro-command.md +67 -0
- deviate/prompts/core/micro-skill.md +67 -0
- deviate/prompts/extras/deviate-pr-graphite-routing.md +20 -0
- deviate/prompts/governance/__init__.py +0 -0
- deviate/prompts/governance/agents_seed.md +1 -0
- deviate/prompts/governance/claudemd_seed.md +1 -0
- deviate/prompts/governance/graphite_seed.md +18 -0
- deviate/prompts/governance/libref_seed.md +3 -0
- deviate/state/__init__.py +0 -0
- deviate/state/config.py +257 -0
- deviate/state/ledger.py +407 -0
- deviate/ui/__init__.py +5 -0
- deviate/ui/monitor.py +187 -0
- deviate/ui/render.py +26 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class SymbolChange:
|
|
8
|
+
language: str
|
|
9
|
+
kind: str
|
|
10
|
+
name: str
|
|
11
|
+
change: str
|
|
12
|
+
old_name: str = ""
|
|
13
|
+
start_line: int = 0
|
|
14
|
+
end_line: int = 0
|
|
15
|
+
old_start_line: int = 0
|
|
16
|
+
old_end_line: int = 0
|
|
17
|
+
old_signature: str = ""
|
|
18
|
+
new_signature: str = ""
|
|
19
|
+
old_line_count: int = 0
|
|
20
|
+
new_line_count: int = 0
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class DuplicateBlock:
|
|
25
|
+
lines: int
|
|
26
|
+
locations: list[str]
|
|
27
|
+
similarity: float
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class FileStructure:
|
|
32
|
+
filepath: str
|
|
33
|
+
language: str
|
|
34
|
+
symbols: list[dict[str, str | int]] = field(default_factory=list)
|
|
35
|
+
imports: list[str] = field(default_factory=list)
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import pathlib
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
_QUERIES_DIR: pathlib.Path = pathlib.Path(__file__).resolve().parent / "queries"
|
|
13
|
+
|
|
14
|
+
EXTENSION_MAP: dict[str, str] = {
|
|
15
|
+
".py": "python",
|
|
16
|
+
".js": "javascript",
|
|
17
|
+
".mjs": "javascript",
|
|
18
|
+
".cjs": "javascript",
|
|
19
|
+
".ts": "typescript",
|
|
20
|
+
".mts": "typescript",
|
|
21
|
+
".cts": "typescript",
|
|
22
|
+
".tsx": "tsx",
|
|
23
|
+
".rs": "rust",
|
|
24
|
+
".go": "go",
|
|
25
|
+
".cpp": "cpp",
|
|
26
|
+
".cc": "cpp",
|
|
27
|
+
".cxx": "cpp",
|
|
28
|
+
".hpp": "cpp",
|
|
29
|
+
".h": "cpp",
|
|
30
|
+
".ex": "elixir",
|
|
31
|
+
".exs": "elixir",
|
|
32
|
+
".cs": "c_sharp",
|
|
33
|
+
".md": "markdown",
|
|
34
|
+
".mdx": "markdown",
|
|
35
|
+
".sh": "bash",
|
|
36
|
+
".bash": "bash",
|
|
37
|
+
".zsh": "bash",
|
|
38
|
+
".json": "json",
|
|
39
|
+
".toml": "toml",
|
|
40
|
+
".yaml": "yaml",
|
|
41
|
+
".yml": "yaml",
|
|
42
|
+
".html": "html",
|
|
43
|
+
".htm": "html",
|
|
44
|
+
".css": "css",
|
|
45
|
+
".scss": "css",
|
|
46
|
+
".less": "css",
|
|
47
|
+
".sql": "sql",
|
|
48
|
+
"Dockerfile": "dockerfile",
|
|
49
|
+
".dockerfile": "dockerfile",
|
|
50
|
+
".tf": "hcl",
|
|
51
|
+
".tfvars": "hcl",
|
|
52
|
+
".kt": "kotlin",
|
|
53
|
+
".kts": "kotlin",
|
|
54
|
+
".swift": "swift",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
_GRAMMAR_PACKAGES: dict[str, str] = {
|
|
58
|
+
"python": "tree_sitter_python",
|
|
59
|
+
"javascript": "tree_sitter_javascript",
|
|
60
|
+
"typescript": "tree_sitter_typescript",
|
|
61
|
+
"tsx": "tree_sitter_typescript",
|
|
62
|
+
"rust": "tree_sitter_rust",
|
|
63
|
+
"go": "tree_sitter_go",
|
|
64
|
+
"cpp": "tree_sitter_cpp",
|
|
65
|
+
"elixir": "tree_sitter_elixir",
|
|
66
|
+
"c_sharp": "tree_sitter_c_sharp",
|
|
67
|
+
"markdown": "tree_sitter_markdown",
|
|
68
|
+
"bash": "tree_sitter_bash",
|
|
69
|
+
"json": "tree_sitter_json",
|
|
70
|
+
"toml": "tree_sitter_toml",
|
|
71
|
+
"yaml": "tree_sitter_yaml",
|
|
72
|
+
"html": "tree_sitter_html",
|
|
73
|
+
"css": "tree_sitter_css",
|
|
74
|
+
"sql": "tree_sitter_sql",
|
|
75
|
+
"dockerfile": "tree_sitter_dockerfile",
|
|
76
|
+
"hcl": "tree_sitter_hcl",
|
|
77
|
+
"kotlin": "tree_sitter_kotlin",
|
|
78
|
+
"swift": "tree_sitter_swift",
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
_LANGUAGE_ATTRS: dict[str, str] = {
|
|
82
|
+
"typescript": "language_typescript",
|
|
83
|
+
"tsx": "language_tsx",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
_CAP_KIND: dict[str, str] = {
|
|
87
|
+
"function": "function",
|
|
88
|
+
"method": "function",
|
|
89
|
+
"class": "class",
|
|
90
|
+
"struct": "class",
|
|
91
|
+
"interface": "interface",
|
|
92
|
+
"entry": "entry",
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
_tree_sitter_available = True
|
|
96
|
+
_parser_cache: dict[str, Any] = {}
|
|
97
|
+
_query_cache: dict[str, Any] = {}
|
|
98
|
+
_grammar_cache: dict[str, Any] = {}
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
from tree_sitter import Language, Parser, Query, QueryCursor, Tree
|
|
102
|
+
except ImportError:
|
|
103
|
+
_tree_sitter_available = False
|
|
104
|
+
Language = None
|
|
105
|
+
Parser = None
|
|
106
|
+
Query = None
|
|
107
|
+
QueryCursor = None
|
|
108
|
+
Tree = None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def get_language_id(filepath: str) -> str | None:
|
|
112
|
+
ext = _get_extension(filepath)
|
|
113
|
+
return EXTENSION_MAP.get(ext)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def get_parser(filepath: str) -> Any | None:
|
|
117
|
+
if not _tree_sitter_available or Parser is None:
|
|
118
|
+
return None
|
|
119
|
+
lang_id = get_language_id(filepath)
|
|
120
|
+
if lang_id is None:
|
|
121
|
+
return None
|
|
122
|
+
if lang_id in _parser_cache:
|
|
123
|
+
return _parser_cache[lang_id]
|
|
124
|
+
lang_obj = _load_language(lang_id)
|
|
125
|
+
if lang_obj is None:
|
|
126
|
+
return None
|
|
127
|
+
try:
|
|
128
|
+
parser = Parser(lang_obj)
|
|
129
|
+
_parser_cache[lang_id] = parser
|
|
130
|
+
return parser
|
|
131
|
+
except Exception:
|
|
132
|
+
logger.warning("Failed to create parser for %s", lang_id)
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _load_language(grammar_id: str) -> Any | None:
|
|
137
|
+
if not _tree_sitter_available or Language is None:
|
|
138
|
+
return None
|
|
139
|
+
if grammar_id in _grammar_cache:
|
|
140
|
+
return _grammar_cache[grammar_id]
|
|
141
|
+
pkg_name = _GRAMMAR_PACKAGES.get(grammar_id)
|
|
142
|
+
if pkg_name is None:
|
|
143
|
+
return None
|
|
144
|
+
try:
|
|
145
|
+
pkg = importlib.import_module(pkg_name)
|
|
146
|
+
lang_attr = _LANGUAGE_ATTRS.get(grammar_id, "language")
|
|
147
|
+
cap_fn = getattr(pkg, lang_attr, None) or pkg.language
|
|
148
|
+
capsule = cap_fn()
|
|
149
|
+
lang_obj = Language(capsule)
|
|
150
|
+
_grammar_cache[grammar_id] = lang_obj
|
|
151
|
+
return lang_obj
|
|
152
|
+
except Exception:
|
|
153
|
+
logger.warning("Failed to load language %s", grammar_id)
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _get_extension(filepath: str) -> str:
|
|
158
|
+
name = os.path.basename(filepath)
|
|
159
|
+
if name == "Dockerfile":
|
|
160
|
+
return "Dockerfile"
|
|
161
|
+
_, ext = os.path.splitext(name)
|
|
162
|
+
return ext.lower() if ext else ""
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _load_queries(grammar_id: str) -> Any | None:
|
|
166
|
+
if not _tree_sitter_available or Query is None:
|
|
167
|
+
return None
|
|
168
|
+
if grammar_id in _query_cache:
|
|
169
|
+
return _query_cache[grammar_id]
|
|
170
|
+
qfile = _QUERIES_DIR / f"{grammar_id}.scm"
|
|
171
|
+
if not qfile.exists():
|
|
172
|
+
_query_cache[grammar_id] = None
|
|
173
|
+
return None
|
|
174
|
+
lang_obj = _load_language(grammar_id)
|
|
175
|
+
if lang_obj is None:
|
|
176
|
+
return None
|
|
177
|
+
try:
|
|
178
|
+
qtext = qfile.read_text()
|
|
179
|
+
query = Query(lang_obj, qtext)
|
|
180
|
+
_query_cache[grammar_id] = query
|
|
181
|
+
return query
|
|
182
|
+
except Exception:
|
|
183
|
+
logger.warning("Failed to compile query for %s", grammar_id)
|
|
184
|
+
return None
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
; captures: function, class, interface, call, import, conditional, loop
|
|
2
|
+
(method_declaration name: (identifier) @method)
|
|
3
|
+
(class_declaration name: (identifier) @class)
|
|
4
|
+
(struct_declaration name: (identifier) @struct)
|
|
5
|
+
(interface_declaration name: (identifier) @interface)
|
|
6
|
+
(record_declaration name: (identifier) @struct)
|
|
7
|
+
(invocation_expression) @call
|
|
8
|
+
(if_statement) @conditional
|
|
9
|
+
(for_statement) @loop
|
|
10
|
+
(while_statement) @loop
|
|
11
|
+
(switch_statement) @conditional
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
; captures: function, class, call, import, conditional, loop
|
|
2
|
+
(function_definition declarator: (function_declarator declarator: (identifier) @function))
|
|
3
|
+
(class_specifier name: (type_identifier) @class)
|
|
4
|
+
(call_expression) @call
|
|
5
|
+
(using_declaration) @import
|
|
6
|
+
(if_statement) @conditional
|
|
7
|
+
(for_statement) @loop
|
|
8
|
+
(while_statement) @loop
|
|
9
|
+
(switch_statement) @conditional
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
; captures: function, call, import, class, interface
|
|
2
|
+
; Elixir uses 'call' nodes for all def/defmodule/defstruct etc.
|
|
3
|
+
; We capture the first argument as the name
|
|
4
|
+
(call target: (identifier) @call)
|
|
5
|
+
(unary_operator operator: "@" (call target: (identifier) @call))
|
|
6
|
+
(alias) @import
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
; captures: function, class, call, import, conditional, loop
|
|
2
|
+
(function_declaration name: (identifier) @function)
|
|
3
|
+
(method_declaration name: (field_identifier) @method)
|
|
4
|
+
(call_expression) @call
|
|
5
|
+
(import_declaration) @import
|
|
6
|
+
(if_statement) @conditional
|
|
7
|
+
(for_statement) @loop
|
|
8
|
+
(expression_switch_statement) @conditional
|
|
9
|
+
(type_switch_statement) @conditional
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
; captures: function, class, call, import, conditional, loop
|
|
2
|
+
(function_declaration name: (identifier) @function)
|
|
3
|
+
(class_declaration name: (identifier) @class)
|
|
4
|
+
(method_definition name: (property_identifier) @method)
|
|
5
|
+
(arrow_function) @function
|
|
6
|
+
(call_expression) @call
|
|
7
|
+
(import_statement) @import
|
|
8
|
+
(export_statement) @function
|
|
9
|
+
(if_statement) @conditional
|
|
10
|
+
(for_statement) @loop
|
|
11
|
+
(while_statement) @loop
|
|
12
|
+
(switch_statement) @conditional
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
; captures: function, class, call, interface, conditional, loop
|
|
2
|
+
(function_declaration name: (identifier) @function)
|
|
3
|
+
(class_declaration name: (identifier) @class)
|
|
4
|
+
(object_declaration name: (identifier) @class)
|
|
5
|
+
(call_expression) @call
|
|
6
|
+
(if_expression) @conditional
|
|
7
|
+
(for_statement) @loop
|
|
8
|
+
(while_statement) @loop
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
; captures: function, class, call, import, conditional, loop
|
|
2
|
+
(function_definition name: (identifier) @function)
|
|
3
|
+
(class_definition name: (identifier) @class)
|
|
4
|
+
(call (identifier) @call)
|
|
5
|
+
(import_statement) @import
|
|
6
|
+
(import_from_statement) @import
|
|
7
|
+
(if_statement) @conditional
|
|
8
|
+
(for_statement) @loop
|
|
9
|
+
(while_statement) @loop
|
|
10
|
+
(match_statement) @conditional
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
; captures: function, class, interface, call, import, conditional, loop
|
|
2
|
+
(function_item name: (identifier) @function)
|
|
3
|
+
(struct_item name: (type_identifier) @struct)
|
|
4
|
+
(enum_item name: (type_identifier) @enum)
|
|
5
|
+
(trait_item name: (type_identifier) @interface)
|
|
6
|
+
(impl_item) @class
|
|
7
|
+
(call_expression) @call
|
|
8
|
+
(use_declaration) @import
|
|
9
|
+
(if_expression) @conditional
|
|
10
|
+
(for_expression) @loop
|
|
11
|
+
(while_expression) @loop
|
|
12
|
+
(match_expression) @conditional
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
; captures: function, class, interface, call, import, conditional, loop
|
|
2
|
+
(function_declaration name: (identifier) @function)
|
|
3
|
+
(class_declaration name: (type_identifier) @class)
|
|
4
|
+
(protocol_declaration name: (type_identifier) @interface)
|
|
5
|
+
(call_expression) @call
|
|
6
|
+
(import_declaration) @import
|
|
7
|
+
(if_statement) @conditional
|
|
8
|
+
(for_statement) @loop
|
|
9
|
+
(while_statement) @loop
|
|
10
|
+
(switch_statement) @conditional
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
; captures: function, class, interface, call, import, conditional, loop
|
|
2
|
+
(function_declaration name: (identifier) @function)
|
|
3
|
+
(class_declaration name: (type_identifier) @class)
|
|
4
|
+
(method_definition name: (property_identifier) @method)
|
|
5
|
+
(interface_declaration name: (type_identifier) @interface)
|
|
6
|
+
(type_alias_declaration name: (type_identifier) @interface)
|
|
7
|
+
(arrow_function) @function
|
|
8
|
+
(call_expression) @call
|
|
9
|
+
(import_statement) @import
|
|
10
|
+
(export_statement) @function
|
|
11
|
+
(if_statement) @conditional
|
|
12
|
+
(for_statement) @loop
|
|
13
|
+
(while_statement) @loop
|
|
14
|
+
(switch_statement) @conditional
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
; captures: function, class, interface, call, import, conditional, loop
|
|
2
|
+
(function_declaration name: (identifier) @function)
|
|
3
|
+
(class_declaration name: (type_identifier) @class)
|
|
4
|
+
(method_definition name: (property_identifier) @method)
|
|
5
|
+
(interface_declaration name: (type_identifier) @interface)
|
|
6
|
+
(type_alias_declaration name: (type_identifier) @interface)
|
|
7
|
+
(arrow_function) @function
|
|
8
|
+
(call_expression) @call
|
|
9
|
+
(import_statement) @import
|
|
10
|
+
(export_statement) @function
|
|
11
|
+
(if_statement) @conditional
|
|
12
|
+
(for_statement) @loop
|
|
13
|
+
(while_statement) @loop
|
|
14
|
+
(switch_statement) @conditional
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from collections import namedtuple
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
|
|
8
|
+
ValidationResult = namedtuple("ValidationResult", ["passed", "errors", "warnings"])
|
|
9
|
+
|
|
10
|
+
ARTIFACT_VALIDATORS: dict[str, list[str]] = {
|
|
11
|
+
"explore": [
|
|
12
|
+
"Problem Definition",
|
|
13
|
+
"Discovery Audit Results",
|
|
14
|
+
"Constitution Quotes",
|
|
15
|
+
"Architectural Baselines",
|
|
16
|
+
"Ecosystem Research",
|
|
17
|
+
"File Registry",
|
|
18
|
+
"Status Summary",
|
|
19
|
+
],
|
|
20
|
+
"design": [
|
|
21
|
+
"Recommended Architecture",
|
|
22
|
+
"Options Matrix",
|
|
23
|
+
"Rejected Options",
|
|
24
|
+
"Design Trade-Offs",
|
|
25
|
+
"Contrarian Viewpoints",
|
|
26
|
+
"Risk Register",
|
|
27
|
+
"Constitutional Alignment Audit",
|
|
28
|
+
"Pending HITL Decisions",
|
|
29
|
+
"Source Registry",
|
|
30
|
+
"Status Summary",
|
|
31
|
+
],
|
|
32
|
+
"data_model": [
|
|
33
|
+
"Entity Definitions",
|
|
34
|
+
"Relationship Graph",
|
|
35
|
+
"Schema Tables",
|
|
36
|
+
"State Transitions",
|
|
37
|
+
"Data Flow",
|
|
38
|
+
"Source Registry",
|
|
39
|
+
],
|
|
40
|
+
"prd": [
|
|
41
|
+
"Document Control and Metadata",
|
|
42
|
+
"System Objectives and Scope Boundary",
|
|
43
|
+
"Architectural Constraints and Prerequisites",
|
|
44
|
+
"Functional Flow and Sequence Architecture",
|
|
45
|
+
"Functional Requirements and Epics",
|
|
46
|
+
"Issue Sharding Strategy",
|
|
47
|
+
],
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def validate_artifact(content: str | None, artifact_type: str) -> ValidationResult:
|
|
52
|
+
required = ARTIFACT_VALIDATORS.get(artifact_type)
|
|
53
|
+
if required is None:
|
|
54
|
+
return ValidationResult(
|
|
55
|
+
passed=False,
|
|
56
|
+
errors=[f"unknown artifact type: {artifact_type}"],
|
|
57
|
+
warnings=[],
|
|
58
|
+
)
|
|
59
|
+
missing = validate_sections(content, required)
|
|
60
|
+
passed = len(missing) == 0
|
|
61
|
+
return ValidationResult(passed=passed, errors=missing, warnings=[])
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def extract_section_body(content: str, header: str) -> str | None:
|
|
65
|
+
escaped_header = re.escape(header)
|
|
66
|
+
pattern = rf"^## {escaped_header}\s*$(.*?)(?=^## |\Z)"
|
|
67
|
+
match = re.search(pattern, content, re.MULTILINE | re.DOTALL)
|
|
68
|
+
if match:
|
|
69
|
+
return match.group(1)
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def validate_gherkin_syntax(content: str) -> list[str]:
|
|
74
|
+
errors: list[str] = []
|
|
75
|
+
scenario_pattern = re.compile(r"\*\*(?P<label>(?:Scenario \d+|AC-\d+-\d+)):.*?\*\*")
|
|
76
|
+
scenarios = list(scenario_pattern.finditer(content))
|
|
77
|
+
if not scenarios:
|
|
78
|
+
return errors
|
|
79
|
+
for i, match in enumerate(scenarios):
|
|
80
|
+
start = match.end()
|
|
81
|
+
end = scenarios[i + 1].start() if i + 1 < len(scenarios) else len(content)
|
|
82
|
+
body = content[start:end]
|
|
83
|
+
label = match.group("label")
|
|
84
|
+
if "**Given**" not in body:
|
|
85
|
+
errors.append(f"{label}: missing 'Given'")
|
|
86
|
+
if "**When**" not in body:
|
|
87
|
+
errors.append(f"{label}: missing 'When'")
|
|
88
|
+
if "**Then**" not in body:
|
|
89
|
+
errors.append(f"{label}: missing 'Then'")
|
|
90
|
+
return errors
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def validate_sections(content: str | None, required: list[str]) -> list[str]:
|
|
94
|
+
if not content or not content.strip():
|
|
95
|
+
return list(required)
|
|
96
|
+
missing: list[str] = []
|
|
97
|
+
for section in required:
|
|
98
|
+
pattern = rf"^##\s+\[?{re.escape(section)}\]?\s*$"
|
|
99
|
+
if not re.search(pattern, content, re.MULTILINE):
|
|
100
|
+
missing.append(section)
|
|
101
|
+
return missing
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def validate_yaml_frontmatter(content: str) -> bool:
|
|
105
|
+
if not content.lstrip().startswith("---"):
|
|
106
|
+
return False
|
|
107
|
+
end_idx = content.find("---", 3)
|
|
108
|
+
if end_idx == -1:
|
|
109
|
+
return False
|
|
110
|
+
frontmatter = content[3:end_idx].strip()
|
|
111
|
+
try:
|
|
112
|
+
yaml.safe_load(frontmatter)
|
|
113
|
+
return True
|
|
114
|
+
except yaml.YAMLError:
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def validate_task_id(task_id: str) -> bool:
|
|
119
|
+
if not task_id:
|
|
120
|
+
return False
|
|
121
|
+
return bool(re.match(r"^TSK-\d{3}-\d{2}$", task_id))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def validate_source_file(source_file: str, epic_slug: str) -> bool:
|
|
125
|
+
"""Validate a shard manifest's ``source_file`` against the issue registry pattern.
|
|
126
|
+
|
|
127
|
+
The downstream ``deviate meso run`` command parses ``source_file`` via
|
|
128
|
+
``PurePosixPath(source_file).parent.parent.name`` to derive the epic bucket
|
|
129
|
+
slug and ``PurePosixPath(source_file).stem`` to derive the issue slug used
|
|
130
|
+
for branch naming. Both rely on the strict shape
|
|
131
|
+
``specs/<epic_slug>/issues/<file>.md``. Any deviation (e.g. a PRD or
|
|
132
|
+
design reference) silently produces wrong branch names and downstream
|
|
133
|
+
worktree failures.
|
|
134
|
+
|
|
135
|
+
Returns ``True`` only when *source_file* matches the expected pattern for
|
|
136
|
+
*epic_slug*; ``False`` otherwise.
|
|
137
|
+
"""
|
|
138
|
+
if not source_file or not epic_slug:
|
|
139
|
+
return False
|
|
140
|
+
if source_file.startswith("/"):
|
|
141
|
+
return False
|
|
142
|
+
parts = source_file.split("/")
|
|
143
|
+
if len(parts) != 4:
|
|
144
|
+
return False
|
|
145
|
+
if parts[0] != "specs":
|
|
146
|
+
return False
|
|
147
|
+
if parts[1] != epic_slug:
|
|
148
|
+
return False
|
|
149
|
+
if parts[2] != "issues":
|
|
150
|
+
return False
|
|
151
|
+
if not parts[3].endswith(".md"):
|
|
152
|
+
return False
|
|
153
|
+
return True
|