opencode-arch 1.0.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.
- opencode_arch/__init__.py +3 -0
- opencode_arch/artifacts/__init__.py +48 -0
- opencode_arch/artifacts/context.py +451 -0
- opencode_arch/artifacts/diagrams.py +451 -0
- opencode_arch/artifacts/selector.py +331 -0
- opencode_arch/artifacts/templates.py +444 -0
- opencode_arch/cli/__init__.py +1 -0
- opencode_arch/cli/bench.py +25 -0
- opencode_arch/cli/calibrate.py +208 -0
- opencode_arch/cli/confidence.py +66 -0
- opencode_arch/cli/docs.py +333 -0
- opencode_arch/cli/docs_validator.py +295 -0
- opencode_arch/cli/export_data.py +133 -0
- opencode_arch/cli/extract.py +93 -0
- opencode_arch/cli/gap_analyzer.py +107 -0
- opencode_arch/cli/generate.py +68 -0
- opencode_arch/cli/launch.py +264 -0
- opencode_arch/cli/main.py +360 -0
- opencode_arch/cli/metrics.py +186 -0
- opencode_arch/cli/prompts.py +20 -0
- opencode_arch/cli/regen_loop.py +1028 -0
- opencode_arch/context/__init__.py +29 -0
- opencode_arch/context/formatter.py +492 -0
- opencode_arch/context/pipeline_bridge.py +201 -0
- opencode_arch/extract/__init__.py +8 -0
- opencode_arch/extract/constraint_detector.py +398 -0
- opencode_arch/extract/from_artifacts.py +837 -0
- opencode_arch/extract/from_code.py +646 -0
- opencode_arch/extract/route_detector.py +400 -0
- opencode_arch/extract/table_parser.py +177 -0
- opencode_arch/learning/__init__.py +19 -0
- opencode_arch/learning/adapter.py +157 -0
- opencode_arch/learning/assessor.py +170 -0
- opencode_arch/learning/classifier.py +144 -0
- opencode_arch/learning/lessons.py +139 -0
- opencode_arch/learning/maintainer.py +281 -0
- opencode_arch/learning/patterns.py +51 -0
- opencode_arch/mcp/__init__.py +1 -0
- opencode_arch/mcp/__main__.py +8 -0
- opencode_arch/mcp/server.py +183 -0
- opencode_arch/mcp/tools/__init__.py +1 -0
- opencode_arch/mcp/tools/check.py +159 -0
- opencode_arch/mcp/tools/extract.py +107 -0
- opencode_arch/mcp/tools/feedback.py +65 -0
- opencode_arch/mcp/tools/generate.py +104 -0
- opencode_arch/mcp/tools/group.py +62 -0
- opencode_arch/mcp/tools/ingest.py +101 -0
- opencode_arch/mcp/tools/require.py +77 -0
- opencode_arch/mcp/tools/scan.py +53 -0
- opencode_arch/mcp/tools/slice.py +235 -0
- opencode_arch/mcp/tools/validate.py +59 -0
- opencode_arch/prompts/__init__.py +1 -0
- opencode_arch/prompts/regen.py +36 -0
- opencode_arch/runner/__init__.py +5 -0
- opencode_arch/runner/base.py +21 -0
- opencode_arch/runner/opencode.py +66 -0
- opencode_arch/telemetry/__init__.py +6 -0
- opencode_arch/telemetry/collector.py +40 -0
- opencode_arch/telemetry/recorder.py +12 -0
- opencode_arch/telemetry/store.py +537 -0
- opencode_arch-1.0.0.dist-info/METADATA +247 -0
- opencode_arch-1.0.0.dist-info/RECORD +65 -0
- opencode_arch-1.0.0.dist-info/WHEEL +4 -0
- opencode_arch-1.0.0.dist-info/entry_points.txt +2 -0
- opencode_arch-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""Model-driven artifact selector.
|
|
2
|
+
|
|
3
|
+
Determines which SE documentation artifacts are appropriate to generate
|
|
4
|
+
based on the richness of an ArchitectureModel. Pure function — no I/O, no LLM calls.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
|
|
11
|
+
from architecture_model.core.types import ArchitectureModel
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class ArtifactSpec:
|
|
16
|
+
"""Specification for a generatable SE documentation artifact."""
|
|
17
|
+
|
|
18
|
+
id: str # e.g. "api-reference"
|
|
19
|
+
name: str # e.g. "API Reference"
|
|
20
|
+
category: str # "architecture" | "design" | "operations" | "requirements"
|
|
21
|
+
requires: list[str] # what model content is needed
|
|
22
|
+
priority: int # 1=always if data exists, 2=recommended, 3=optional
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Artifact Registry — the 12 supported artifacts
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
ARTIFACT_REGISTRY: list[ArtifactSpec] = [
|
|
30
|
+
ArtifactSpec(
|
|
31
|
+
id="system-overview",
|
|
32
|
+
name="System Overview",
|
|
33
|
+
category="architecture",
|
|
34
|
+
requires=["components"],
|
|
35
|
+
priority=1,
|
|
36
|
+
),
|
|
37
|
+
ArtifactSpec(
|
|
38
|
+
id="component-catalog",
|
|
39
|
+
name="Component Catalog",
|
|
40
|
+
category="architecture",
|
|
41
|
+
requires=["components"],
|
|
42
|
+
priority=1,
|
|
43
|
+
),
|
|
44
|
+
ArtifactSpec(
|
|
45
|
+
id="api-reference",
|
|
46
|
+
name="API Reference",
|
|
47
|
+
category="design",
|
|
48
|
+
requires=["interfaces"],
|
|
49
|
+
priority=1,
|
|
50
|
+
),
|
|
51
|
+
ArtifactSpec(
|
|
52
|
+
id="capability-map",
|
|
53
|
+
name="Capability Map",
|
|
54
|
+
category="requirements",
|
|
55
|
+
requires=["capabilities"],
|
|
56
|
+
priority=1,
|
|
57
|
+
),
|
|
58
|
+
ArtifactSpec(
|
|
59
|
+
id="behavior-flows",
|
|
60
|
+
name="Behavior Flows",
|
|
61
|
+
category="design",
|
|
62
|
+
requires=["behaviors"],
|
|
63
|
+
priority=2,
|
|
64
|
+
),
|
|
65
|
+
ArtifactSpec(
|
|
66
|
+
id="constraint-register",
|
|
67
|
+
name="Constraint Register",
|
|
68
|
+
category="requirements",
|
|
69
|
+
requires=["constraints"],
|
|
70
|
+
priority=2,
|
|
71
|
+
),
|
|
72
|
+
ArtifactSpec(
|
|
73
|
+
id="dependency-graph",
|
|
74
|
+
name="Dependency Graph",
|
|
75
|
+
category="architecture",
|
|
76
|
+
requires=["components", "relationships"],
|
|
77
|
+
priority=2,
|
|
78
|
+
),
|
|
79
|
+
ArtifactSpec(
|
|
80
|
+
id="layer-architecture",
|
|
81
|
+
name="Layer Architecture",
|
|
82
|
+
category="architecture",
|
|
83
|
+
requires=["layers"],
|
|
84
|
+
priority=2,
|
|
85
|
+
),
|
|
86
|
+
ArtifactSpec(
|
|
87
|
+
id="deployment-view",
|
|
88
|
+
name="Deployment View",
|
|
89
|
+
category="operations",
|
|
90
|
+
requires=["components", "layers"],
|
|
91
|
+
priority=2,
|
|
92
|
+
),
|
|
93
|
+
ArtifactSpec(
|
|
94
|
+
id="integration-guide",
|
|
95
|
+
name="Integration Guide",
|
|
96
|
+
category="design",
|
|
97
|
+
requires=["interfaces", "components"],
|
|
98
|
+
priority=3,
|
|
99
|
+
),
|
|
100
|
+
ArtifactSpec(
|
|
101
|
+
id="test-strategy",
|
|
102
|
+
name="Test Strategy",
|
|
103
|
+
category="operations",
|
|
104
|
+
requires=["manifest.tests"],
|
|
105
|
+
priority=3,
|
|
106
|
+
),
|
|
107
|
+
ArtifactSpec(
|
|
108
|
+
id="metrics-dashboard",
|
|
109
|
+
name="Metrics Dashboard",
|
|
110
|
+
category="operations",
|
|
111
|
+
requires=["manifest.metrics"],
|
|
112
|
+
priority=3,
|
|
113
|
+
),
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _requirement_met(req: str, model: ArchitectureModel, manifest: dict | None) -> bool:
|
|
118
|
+
"""Check whether a single requirement string is satisfied."""
|
|
119
|
+
if req == "components":
|
|
120
|
+
return len(model.entities.components) > 0
|
|
121
|
+
elif req == "interfaces":
|
|
122
|
+
return len(model.entities.interfaces) > 0
|
|
123
|
+
elif req == "capabilities":
|
|
124
|
+
return len(model.entities.capabilities) > 0
|
|
125
|
+
elif req == "behaviors":
|
|
126
|
+
return len(model.entities.behaviors) > 0
|
|
127
|
+
elif req == "constraints":
|
|
128
|
+
return len(model.entities.constraints) > 0
|
|
129
|
+
elif req == "layers":
|
|
130
|
+
return len(model.entities.layers) > 0
|
|
131
|
+
elif req == "relationships":
|
|
132
|
+
return len(model.relationships) > 0
|
|
133
|
+
elif req == "manifest.tests":
|
|
134
|
+
if manifest is None:
|
|
135
|
+
return False
|
|
136
|
+
tests = manifest.get("test_files") or manifest.get("tests")
|
|
137
|
+
return bool(tests)
|
|
138
|
+
elif req == "manifest.metrics":
|
|
139
|
+
if manifest is None:
|
|
140
|
+
return False
|
|
141
|
+
return bool(manifest.get("metrics"))
|
|
142
|
+
else:
|
|
143
|
+
return False
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def select_artifacts(
|
|
147
|
+
model: ArchitectureModel,
|
|
148
|
+
manifest: dict | None = None,
|
|
149
|
+
include_capability_details: bool = True,
|
|
150
|
+
) -> list[ArtifactSpec]:
|
|
151
|
+
"""Return artifacts appropriate for this model's richness.
|
|
152
|
+
|
|
153
|
+
For each artifact in the registry, check if the model has the required entities.
|
|
154
|
+
Return only those artifacts whose requirements are met.
|
|
155
|
+
Results sorted by priority (1 first), then alphabetically by id.
|
|
156
|
+
|
|
157
|
+
If include_capability_details is True, also generates per-capability
|
|
158
|
+
api-detail-{cap_id} artifacts for every realized capability.
|
|
159
|
+
"""
|
|
160
|
+
selected: list[ArtifactSpec] = []
|
|
161
|
+
|
|
162
|
+
for spec in ARTIFACT_REGISTRY:
|
|
163
|
+
if all(_requirement_met(req, model, manifest) for req in spec.requires):
|
|
164
|
+
selected.append(spec)
|
|
165
|
+
|
|
166
|
+
# Add per-capability detail artifacts
|
|
167
|
+
if include_capability_details:
|
|
168
|
+
selected.extend(select_capability_detail_artifacts(model))
|
|
169
|
+
|
|
170
|
+
selected.sort(key=lambda s: (s.priority, s.id))
|
|
171
|
+
return selected
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def get_artifact_spec(artifact_id: str) -> ArtifactSpec | None:
|
|
175
|
+
"""Look up a single artifact spec by ID."""
|
|
176
|
+
for spec in ARTIFACT_REGISTRY:
|
|
177
|
+
if spec.id == artifact_id:
|
|
178
|
+
return spec
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# ---------------------------------------------------------------------------
|
|
183
|
+
# Subsystem Decomposition Support
|
|
184
|
+
# ---------------------------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@dataclass
|
|
188
|
+
class SubsystemInfo:
|
|
189
|
+
"""Description of a subsystem within a larger system."""
|
|
190
|
+
|
|
191
|
+
id: str # e.g. "F1", "core", "cli"
|
|
192
|
+
name: str # e.g. "Core Engine", "CLI Commands"
|
|
193
|
+
components: list[str] # component IDs belonging to this subsystem
|
|
194
|
+
file_count: int = 0 # number of source files
|
|
195
|
+
test_count: int = 0 # number of test files
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def should_decompose(model: ArchitectureModel, manifest: dict | None = None) -> bool:
|
|
199
|
+
"""Determine if system is complex enough to warrant per-subsystem docs.
|
|
200
|
+
|
|
201
|
+
Heuristic: returns True if ANY of:
|
|
202
|
+
- More than 5 functional blocks (distinct f_block values on components)
|
|
203
|
+
- More than 50 source files in manifest
|
|
204
|
+
- More than 20 components
|
|
205
|
+
"""
|
|
206
|
+
# Check component count
|
|
207
|
+
if len(model.entities.components) > 20:
|
|
208
|
+
return True
|
|
209
|
+
|
|
210
|
+
# Check distinct f_block values
|
|
211
|
+
fblocks = {c.f_block for c in model.entities.components if c.f_block}
|
|
212
|
+
if len(fblocks) > 5:
|
|
213
|
+
return True
|
|
214
|
+
|
|
215
|
+
# Check file count from manifest
|
|
216
|
+
if manifest is not None:
|
|
217
|
+
# Try metrics.total_files first
|
|
218
|
+
total_files = manifest.get("metrics", {}).get("total_files", 0)
|
|
219
|
+
if total_files > 50:
|
|
220
|
+
return True
|
|
221
|
+
# Fall back to counting modules list
|
|
222
|
+
modules = manifest.get("modules")
|
|
223
|
+
if modules and len(modules) > 50:
|
|
224
|
+
return True
|
|
225
|
+
|
|
226
|
+
return False
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# Artifacts appropriate for subsystem-level generation
|
|
230
|
+
SUBSYSTEM_ARTIFACTS: list[str] = [
|
|
231
|
+
"component-catalog",
|
|
232
|
+
"api-reference",
|
|
233
|
+
"behavior-flows",
|
|
234
|
+
"test-strategy",
|
|
235
|
+
]
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _subsystem_requirement_met(
|
|
239
|
+
req: str,
|
|
240
|
+
subsystem: SubsystemInfo,
|
|
241
|
+
model: ArchitectureModel,
|
|
242
|
+
manifest: dict | None,
|
|
243
|
+
) -> bool:
|
|
244
|
+
"""Check whether a requirement is met scoped to a subsystem's components."""
|
|
245
|
+
if req == "components":
|
|
246
|
+
return len(subsystem.components) > 0
|
|
247
|
+
elif req == "interfaces":
|
|
248
|
+
comp_set = set(subsystem.components)
|
|
249
|
+
return any(
|
|
250
|
+
i.provider in comp_set or i.consumer in comp_set
|
|
251
|
+
for i in model.entities.interfaces
|
|
252
|
+
)
|
|
253
|
+
elif req == "behaviors":
|
|
254
|
+
comp_set = set(subsystem.components)
|
|
255
|
+
return any(b.actor in comp_set for b in model.entities.behaviors)
|
|
256
|
+
elif req == "manifest.tests":
|
|
257
|
+
return subsystem.test_count > 0
|
|
258
|
+
else:
|
|
259
|
+
# Fall back to system-level logic for other requirements
|
|
260
|
+
return _requirement_met(req, model, manifest)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def select_subsystem_artifacts(
|
|
264
|
+
subsystem: SubsystemInfo,
|
|
265
|
+
model: ArchitectureModel,
|
|
266
|
+
manifest: dict | None = None,
|
|
267
|
+
) -> list[ArtifactSpec]:
|
|
268
|
+
"""Select artifacts appropriate for a subsystem (subset of system-level).
|
|
269
|
+
|
|
270
|
+
Only returns artifacts from SUBSYSTEM_ARTIFACTS that:
|
|
271
|
+
1. Are in the SUBSYSTEM_ARTIFACTS list
|
|
272
|
+
2. Have their requirements met (scoped to subsystem's components)
|
|
273
|
+
|
|
274
|
+
Returns empty list if subsystem has no components.
|
|
275
|
+
"""
|
|
276
|
+
if not subsystem.components:
|
|
277
|
+
return []
|
|
278
|
+
|
|
279
|
+
selected: list[ArtifactSpec] = []
|
|
280
|
+
|
|
281
|
+
for spec in ARTIFACT_REGISTRY:
|
|
282
|
+
if spec.id not in SUBSYSTEM_ARTIFACTS:
|
|
283
|
+
continue
|
|
284
|
+
if all(
|
|
285
|
+
_subsystem_requirement_met(req, subsystem, model, manifest)
|
|
286
|
+
for req in spec.requires
|
|
287
|
+
):
|
|
288
|
+
selected.append(spec)
|
|
289
|
+
|
|
290
|
+
selected.sort(key=lambda s: (s.priority, s.id))
|
|
291
|
+
return selected
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
# ---------------------------------------------------------------------------
|
|
295
|
+
# Per-Capability API Detail Artifacts
|
|
296
|
+
# ---------------------------------------------------------------------------
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def select_capability_detail_artifacts(
|
|
300
|
+
model: ArchitectureModel,
|
|
301
|
+
) -> list[ArtifactSpec]:
|
|
302
|
+
"""Generate one ArtifactSpec per capability for detailed API docs.
|
|
303
|
+
|
|
304
|
+
Only includes capabilities that have at least one realizing component
|
|
305
|
+
(via 'realizes' relationship). Each gets artifact_id = "api-detail-{cap.id}".
|
|
306
|
+
"""
|
|
307
|
+
from architecture_model.core.types import RelationType
|
|
308
|
+
|
|
309
|
+
# Find which capabilities have realizing components
|
|
310
|
+
realized_caps: set[str] = set()
|
|
311
|
+
for rel in model.relationships:
|
|
312
|
+
if rel.type == RelationType.REALIZES:
|
|
313
|
+
realized_caps.add(rel.to_id)
|
|
314
|
+
|
|
315
|
+
cap_ids = {c.id for c in model.entities.capabilities}
|
|
316
|
+
realized_caps = realized_caps & cap_ids # only actual capability targets
|
|
317
|
+
|
|
318
|
+
specs: list[ArtifactSpec] = []
|
|
319
|
+
for cap in model.entities.capabilities:
|
|
320
|
+
if cap.id not in realized_caps:
|
|
321
|
+
continue
|
|
322
|
+
specs.append(ArtifactSpec(
|
|
323
|
+
id=f"api-detail-{cap.id.lower()}",
|
|
324
|
+
name=f"API Detail — {cap.name}",
|
|
325
|
+
category="design",
|
|
326
|
+
requires=["capabilities"],
|
|
327
|
+
priority=2,
|
|
328
|
+
))
|
|
329
|
+
|
|
330
|
+
specs.sort(key=lambda s: s.id)
|
|
331
|
+
return specs
|