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,157 @@
|
|
|
1
|
+
"""Adaptive prompt optimization based on classified failure patterns.
|
|
2
|
+
|
|
3
|
+
Queries historical telemetry for similar subsystems and applies learned
|
|
4
|
+
strategies to modify prompt construction BEFORE the first attempt.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from opencode_arch.learning.patterns import (
|
|
13
|
+
FailureClassification,
|
|
14
|
+
PatternType,
|
|
15
|
+
Strategy,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class PromptAdaptation:
|
|
21
|
+
"""A specific adaptation to apply to prompt construction."""
|
|
22
|
+
strategy: Strategy
|
|
23
|
+
reason: str # why this adaptation is being applied
|
|
24
|
+
params: dict[str, Any] = field(default_factory=dict)
|
|
25
|
+
# params vary by strategy:
|
|
26
|
+
# EXPAND_DEP_CONTEXT: {"dep_modules": ["_base", "_config"], "include_body_hints": True}
|
|
27
|
+
# INCLUDE_SOURCE_EXCERPT: {"functions": ["complex_func"]}
|
|
28
|
+
# PRIORITIZE_CONSTANTS: {"boost_factor": 2.0}
|
|
29
|
+
# FIX_SIGNATURES: {"symbols": ["MyClass.method"]}
|
|
30
|
+
# INCREASE_CONTRACT_CAP: {"new_cap": 100}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_adaptations(
|
|
34
|
+
subsystem_name: str,
|
|
35
|
+
dependency_count: int,
|
|
36
|
+
signature_count: int,
|
|
37
|
+
contract_count: int,
|
|
38
|
+
body_hint_coverage: float,
|
|
39
|
+
historical_patterns: list[dict[str, Any]] | None = None,
|
|
40
|
+
) -> list[PromptAdaptation]:
|
|
41
|
+
"""Determine prompt adaptations based on subsystem characteristics and history.
|
|
42
|
+
|
|
43
|
+
This is called BEFORE the first prompt attempt to proactively adjust
|
|
44
|
+
based on what we've learned from previous repos.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
subsystem_name: Name of the current subsystem.
|
|
48
|
+
dependency_count: Number of upstream dependencies.
|
|
49
|
+
signature_count: Number of function signatures in model.
|
|
50
|
+
contract_count: Number of test contracts available.
|
|
51
|
+
body_hint_coverage: Fraction of functions with body_hints (0.0-1.0).
|
|
52
|
+
historical_patterns: Past failure patterns from telemetry for similar subsystems.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
List of adaptations to apply to prompt construction.
|
|
56
|
+
"""
|
|
57
|
+
adaptations: list[PromptAdaptation] = []
|
|
58
|
+
|
|
59
|
+
# Rule 1: High dependency count → expand dep context proactively
|
|
60
|
+
if dependency_count >= 3:
|
|
61
|
+
adaptations.append(PromptAdaptation(
|
|
62
|
+
strategy=Strategy.EXPAND_DEP_CONTEXT,
|
|
63
|
+
reason=f"Subsystem has {dependency_count} dependencies (threshold: 3)",
|
|
64
|
+
params={"include_body_hints": True},
|
|
65
|
+
))
|
|
66
|
+
|
|
67
|
+
# Rule 2: Low contract count → increase cap and prioritize what we have
|
|
68
|
+
if contract_count < 10:
|
|
69
|
+
adaptations.append(PromptAdaptation(
|
|
70
|
+
strategy=Strategy.INCREASE_CONTRACT_CAP,
|
|
71
|
+
reason=f"Only {contract_count} contracts available (low coverage)",
|
|
72
|
+
params={"new_cap": 200}, # Don't cap at all for low-contract subsystems
|
|
73
|
+
))
|
|
74
|
+
|
|
75
|
+
# Rule 3: Low body_hint coverage → likely to fail on implementation details
|
|
76
|
+
if body_hint_coverage < 0.5 and signature_count > 5:
|
|
77
|
+
adaptations.append(PromptAdaptation(
|
|
78
|
+
strategy=Strategy.INCLUDE_SOURCE_EXCERPT,
|
|
79
|
+
reason=f"Body hint coverage is {body_hint_coverage:.0%} (threshold: 50%)",
|
|
80
|
+
params={"coverage_threshold": body_hint_coverage},
|
|
81
|
+
))
|
|
82
|
+
|
|
83
|
+
# Rule 4: Historical patterns suggest specific strategies
|
|
84
|
+
if historical_patterns:
|
|
85
|
+
pattern_counts: dict[str, int] = {}
|
|
86
|
+
for entry in historical_patterns:
|
|
87
|
+
pattern = entry.get("pattern", "")
|
|
88
|
+
if pattern:
|
|
89
|
+
pattern_counts[pattern] = pattern_counts.get(pattern, 0) + 1
|
|
90
|
+
|
|
91
|
+
# If cross_dep failures dominate history for similar subsystems
|
|
92
|
+
if pattern_counts.get("cross_dep", 0) >= 2:
|
|
93
|
+
if not any(a.strategy == Strategy.EXPAND_DEP_CONTEXT for a in adaptations):
|
|
94
|
+
adaptations.append(PromptAdaptation(
|
|
95
|
+
strategy=Strategy.EXPAND_DEP_CONTEXT,
|
|
96
|
+
reason=f"Historical: {pattern_counts['cross_dep']} cross-dep failures in similar subsystems",
|
|
97
|
+
params={"include_body_hints": True, "historical": True},
|
|
98
|
+
))
|
|
99
|
+
|
|
100
|
+
# If wrong_constant failures are common
|
|
101
|
+
if pattern_counts.get("wrong_constant", 0) >= 2:
|
|
102
|
+
adaptations.append(PromptAdaptation(
|
|
103
|
+
strategy=Strategy.PRIORITIZE_CONSTANTS,
|
|
104
|
+
reason=f"Historical: {pattern_counts['wrong_constant']} constant mismatches in similar subsystems",
|
|
105
|
+
params={"boost_factor": 2.0},
|
|
106
|
+
))
|
|
107
|
+
|
|
108
|
+
return adaptations
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def apply_adaptations(
|
|
112
|
+
adaptations: list[PromptAdaptation],
|
|
113
|
+
contract_cap: int = 50,
|
|
114
|
+
include_dep_body_hints: bool = False,
|
|
115
|
+
) -> dict[str, Any]:
|
|
116
|
+
"""Apply adaptations and return modified prompt parameters.
|
|
117
|
+
|
|
118
|
+
Returns a dict of overrides to pass to _build_prompt:
|
|
119
|
+
- contract_cap: int (max contracts to include)
|
|
120
|
+
- include_dep_body_hints: bool (include body_hints in dep context)
|
|
121
|
+
- extra_context: str (additional context to append)
|
|
122
|
+
- source_excerpts: list[str] (functions needing full source)
|
|
123
|
+
"""
|
|
124
|
+
result: dict[str, Any] = {
|
|
125
|
+
"contract_cap": contract_cap,
|
|
126
|
+
"include_dep_body_hints": include_dep_body_hints,
|
|
127
|
+
"extra_context": "",
|
|
128
|
+
"source_excerpts": [],
|
|
129
|
+
"adaptations_applied": [],
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for adaptation in adaptations:
|
|
133
|
+
result["adaptations_applied"].append(
|
|
134
|
+
f"{adaptation.strategy.value}: {adaptation.reason}"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if adaptation.strategy == Strategy.EXPAND_DEP_CONTEXT:
|
|
138
|
+
result["include_dep_body_hints"] = True
|
|
139
|
+
|
|
140
|
+
elif adaptation.strategy == Strategy.INCREASE_CONTRACT_CAP:
|
|
141
|
+
new_cap = adaptation.params.get("new_cap", 100)
|
|
142
|
+
result["contract_cap"] = max(result["contract_cap"], new_cap)
|
|
143
|
+
|
|
144
|
+
elif adaptation.strategy == Strategy.INCLUDE_SOURCE_EXCERPT:
|
|
145
|
+
functions = adaptation.params.get("functions", [])
|
|
146
|
+
result["source_excerpts"].extend(functions)
|
|
147
|
+
|
|
148
|
+
elif adaptation.strategy == Strategy.PRIORITIZE_CONSTANTS:
|
|
149
|
+
# Signal to put constants section BEFORE signatures in prompt
|
|
150
|
+
result["extra_context"] += "\n# NOTE: Constants are critical for this subsystem — use exact values.\n"
|
|
151
|
+
|
|
152
|
+
elif adaptation.strategy == Strategy.FIX_SIGNATURES:
|
|
153
|
+
symbols = adaptation.params.get("symbols", [])
|
|
154
|
+
if symbols:
|
|
155
|
+
result["extra_context"] += f"\n# IMPORTANT: Match exact signatures for: {', '.join(symbols)}\n"
|
|
156
|
+
|
|
157
|
+
return result
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Report card generation and grading for regen loop runs.
|
|
2
|
+
|
|
3
|
+
Produces a self-assessment after each repo run: grade (A-F),
|
|
4
|
+
trending metrics, failure pattern breakdown, and improvement actions.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class ReportCard:
|
|
15
|
+
"""Self-assessment report for a regen loop run."""
|
|
16
|
+
repo: str
|
|
17
|
+
mode: str # "normal" or "blind"
|
|
18
|
+
|
|
19
|
+
# Core metrics
|
|
20
|
+
fidelity: float # converged/total (0.0-1.0)
|
|
21
|
+
compression_ratio: float # source_equiv / prompt_tokens
|
|
22
|
+
time_per_subsystem: float # avg seconds
|
|
23
|
+
total_subsystems: int
|
|
24
|
+
converged_subsystems: int
|
|
25
|
+
|
|
26
|
+
# Trend (vs previous)
|
|
27
|
+
fidelity_trend: str = "STABLE" # "UP", "DOWN", "STABLE"
|
|
28
|
+
compression_trend: str = "STABLE"
|
|
29
|
+
|
|
30
|
+
# Pattern breakdown
|
|
31
|
+
failure_patterns: dict[str, int] = field(default_factory=dict)
|
|
32
|
+
novel_patterns: int = 0
|
|
33
|
+
|
|
34
|
+
# Grade + actions
|
|
35
|
+
grade: str = "C"
|
|
36
|
+
improvement_actions: list[str] = field(default_factory=list)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _compute_grade(fidelity: float, compression_ratio: float, novel_patterns: int) -> str:
|
|
40
|
+
"""Compute letter grade from metrics.
|
|
41
|
+
|
|
42
|
+
A: >90% fidelity, compression >5x, 0 novel patterns
|
|
43
|
+
B: >75% fidelity, compression >3x
|
|
44
|
+
C: >60% fidelity
|
|
45
|
+
D: >40% fidelity
|
|
46
|
+
F: <40% fidelity
|
|
47
|
+
"""
|
|
48
|
+
if fidelity >= 0.9 and compression_ratio >= 5.0 and novel_patterns == 0:
|
|
49
|
+
return "A"
|
|
50
|
+
elif fidelity >= 0.75 and compression_ratio >= 3.0:
|
|
51
|
+
return "B"
|
|
52
|
+
elif fidelity >= 0.6:
|
|
53
|
+
return "C"
|
|
54
|
+
elif fidelity >= 0.4:
|
|
55
|
+
return "D"
|
|
56
|
+
else:
|
|
57
|
+
return "F"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _compute_trend(current: float, previous: float | None) -> str:
|
|
61
|
+
"""Determine trend direction."""
|
|
62
|
+
if previous is None:
|
|
63
|
+
return "STABLE"
|
|
64
|
+
diff = current - previous
|
|
65
|
+
if diff > 0.05:
|
|
66
|
+
return "UP"
|
|
67
|
+
elif diff < -0.05:
|
|
68
|
+
return "DOWN"
|
|
69
|
+
return "STABLE"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _compute_improvement_actions(
|
|
73
|
+
fidelity: float,
|
|
74
|
+
compression_ratio: float,
|
|
75
|
+
failure_patterns: dict[str, int],
|
|
76
|
+
novel_patterns: int,
|
|
77
|
+
) -> list[str]:
|
|
78
|
+
"""Generate actionable improvement suggestions."""
|
|
79
|
+
actions = []
|
|
80
|
+
|
|
81
|
+
if fidelity < 0.7:
|
|
82
|
+
dominant_pattern = max(failure_patterns, key=failure_patterns.get) if failure_patterns else None
|
|
83
|
+
if dominant_pattern == "cross_dep":
|
|
84
|
+
actions.append("Expand dependency context: include body_hints for upstream modules")
|
|
85
|
+
elif dominant_pattern == "missing_impl":
|
|
86
|
+
actions.append("Increase body_hint detail level for complex functions")
|
|
87
|
+
elif dominant_pattern == "wrong_constant":
|
|
88
|
+
actions.append("Verify constant extraction captures all test-expected values")
|
|
89
|
+
else:
|
|
90
|
+
actions.append(f"Investigate dominant failure pattern: {dominant_pattern}")
|
|
91
|
+
|
|
92
|
+
if compression_ratio < 3.0:
|
|
93
|
+
actions.append("Model is not providing enough compression — review token allocation")
|
|
94
|
+
|
|
95
|
+
if novel_patterns > 0:
|
|
96
|
+
actions.append(f"Classify {novel_patterns} novel failure patterns and add to taxonomy")
|
|
97
|
+
|
|
98
|
+
if fidelity >= 0.9 and compression_ratio >= 5.0:
|
|
99
|
+
actions.append("System performing well — consider adding a new benchmark repo")
|
|
100
|
+
|
|
101
|
+
return actions
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def generate_report_card(
|
|
105
|
+
repo: str,
|
|
106
|
+
mode: str,
|
|
107
|
+
subsystem_results: dict[str, dict[str, Any]],
|
|
108
|
+
previous_fidelity: float | None = None,
|
|
109
|
+
previous_compression: float | None = None,
|
|
110
|
+
) -> ReportCard:
|
|
111
|
+
"""Generate a report card from regen loop results.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
repo: Repository name.
|
|
115
|
+
mode: "normal" or "blind".
|
|
116
|
+
subsystem_results: Dict of {subsystem_name: result_dict} from run_regen_loop.
|
|
117
|
+
previous_fidelity: Fidelity from previous repo (for trend).
|
|
118
|
+
previous_compression: Compression from previous repo (for trend).
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
ReportCard with grade, trends, and improvement actions.
|
|
122
|
+
"""
|
|
123
|
+
total = len(subsystem_results)
|
|
124
|
+
converged = sum(1 for r in subsystem_results.values() if r.get("converged", False))
|
|
125
|
+
fidelity = converged / total if total > 0 else 0.0
|
|
126
|
+
|
|
127
|
+
# Compute avg compression from token metrics
|
|
128
|
+
compressions = []
|
|
129
|
+
times = []
|
|
130
|
+
for r in subsystem_results.values():
|
|
131
|
+
metrics = r.get("token_metrics", {})
|
|
132
|
+
if metrics.get("compression_ratio", 0) > 0:
|
|
133
|
+
compressions.append(metrics["compression_ratio"])
|
|
134
|
+
times.append(r.get("time_seconds", 0))
|
|
135
|
+
|
|
136
|
+
avg_compression = sum(compressions) / len(compressions) if compressions else 0.0
|
|
137
|
+
avg_time = sum(times) / len(times) if times else 0.0
|
|
138
|
+
|
|
139
|
+
# Aggregate failure patterns from classifications
|
|
140
|
+
failure_patterns: dict[str, int] = {}
|
|
141
|
+
novel = 0
|
|
142
|
+
for r in subsystem_results.values():
|
|
143
|
+
patterns = r.get("failure_patterns", {})
|
|
144
|
+
for pattern, count in patterns.items():
|
|
145
|
+
if pattern == "unknown":
|
|
146
|
+
novel += count
|
|
147
|
+
else:
|
|
148
|
+
failure_patterns[pattern] = failure_patterns.get(pattern, 0) + count
|
|
149
|
+
|
|
150
|
+
# Compute grade and trends
|
|
151
|
+
grade = _compute_grade(fidelity, avg_compression, novel)
|
|
152
|
+
fidelity_trend = _compute_trend(fidelity, previous_fidelity)
|
|
153
|
+
compression_trend = _compute_trend(avg_compression, previous_compression)
|
|
154
|
+
actions = _compute_improvement_actions(fidelity, avg_compression, failure_patterns, novel)
|
|
155
|
+
|
|
156
|
+
return ReportCard(
|
|
157
|
+
repo=repo,
|
|
158
|
+
mode=mode,
|
|
159
|
+
fidelity=fidelity,
|
|
160
|
+
compression_ratio=avg_compression,
|
|
161
|
+
time_per_subsystem=avg_time,
|
|
162
|
+
total_subsystems=total,
|
|
163
|
+
converged_subsystems=converged,
|
|
164
|
+
fidelity_trend=fidelity_trend,
|
|
165
|
+
compression_trend=compression_trend,
|
|
166
|
+
failure_patterns=failure_patterns,
|
|
167
|
+
novel_patterns=novel,
|
|
168
|
+
grade=grade,
|
|
169
|
+
improvement_actions=actions,
|
|
170
|
+
)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Dual-level failure pattern classifier.
|
|
2
|
+
|
|
3
|
+
Level 1 (Raw): Regex matching on pytest output text
|
|
4
|
+
Level 2 (Structured): Parsed test results cross-referenced with model data
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from opencode_arch.learning.patterns import (
|
|
13
|
+
FailureClassification,
|
|
14
|
+
PatternType,
|
|
15
|
+
Strategy,
|
|
16
|
+
PATTERN_STRATEGIES,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# --- Level 1: Raw regex patterns ---
|
|
21
|
+
|
|
22
|
+
_RAW_PATTERNS: list[tuple[str, PatternType, float]] = [
|
|
23
|
+
# CROSS_DEP: ImportError from project module
|
|
24
|
+
(r"ImportError: cannot import name '(\w+)' from '([^']+)'", PatternType.CROSS_DEP, 0.95),
|
|
25
|
+
(r"ModuleNotFoundError: No module named '(\w[\w.]*)'", PatternType.CROSS_DEP, 0.8),
|
|
26
|
+
(r"NameError: name '(\w+)' is not defined", PatternType.CROSS_DEP, 0.6),
|
|
27
|
+
|
|
28
|
+
# MISSING_IMPL: AttributeError on generated code
|
|
29
|
+
(r"AttributeError: (?:module |type object )?'(\w+)' has no attribute '(\w+)'", PatternType.MISSING_IMPL, 0.9),
|
|
30
|
+
(r"AttributeError: '(\w+)' object has no attribute '(\w+)'", PatternType.MISSING_IMPL, 0.9),
|
|
31
|
+
|
|
32
|
+
# WRONG_CONSTANT: Assertion with literal values
|
|
33
|
+
(r"AssertionError: assert ['\"](.+)['\"] == ['\"](.+)['\"]", PatternType.WRONG_CONSTANT, 0.85),
|
|
34
|
+
(r"AssertionError: assert (\d+) == (\d+)", PatternType.WRONG_CONSTANT, 0.85),
|
|
35
|
+
(r"AssertionError: (?:assert )?(.+) != (.+)", PatternType.WRONG_CONSTANT, 0.7),
|
|
36
|
+
|
|
37
|
+
# API_MISMATCH: Wrong number of arguments
|
|
38
|
+
(r"TypeError: (\w+)\(\) takes (\d+) positional argument", PatternType.API_MISMATCH, 0.9),
|
|
39
|
+
(r"TypeError: (\w+)\(\) got an unexpected keyword argument '(\w+)'", PatternType.API_MISMATCH, 0.9),
|
|
40
|
+
(r"TypeError: (\w+)\(\) missing (\d+) required positional", PatternType.API_MISMATCH, 0.9),
|
|
41
|
+
|
|
42
|
+
# TEST_INFRA: Test helper imports failing
|
|
43
|
+
(r"ModuleNotFoundError: No module named 'tests\.", PatternType.TEST_INFRA, 0.95),
|
|
44
|
+
(r"ModuleNotFoundError: No module named 'conftest'", PatternType.TEST_INFRA, 0.95),
|
|
45
|
+
(r"ImportError: cannot import name .+ from 'tests\.", PatternType.TEST_INFRA, 0.9),
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _classify_raw(test_output: str) -> list[FailureClassification]:
|
|
50
|
+
"""Level 1: Classify failures from raw pytest output using regex."""
|
|
51
|
+
classifications: list[FailureClassification] = []
|
|
52
|
+
seen_patterns: set[tuple[PatternType, str]] = set()
|
|
53
|
+
|
|
54
|
+
for regex, pattern_type, confidence in _RAW_PATTERNS:
|
|
55
|
+
for match in re.finditer(regex, test_output):
|
|
56
|
+
# Deduplicate: same pattern + same primary symbol
|
|
57
|
+
key = (pattern_type, match.group(1) if match.groups() else "")
|
|
58
|
+
if key in seen_patterns:
|
|
59
|
+
continue
|
|
60
|
+
seen_patterns.add(key)
|
|
61
|
+
|
|
62
|
+
# Extract affected symbols
|
|
63
|
+
symbols = [g for g in match.groups() if g]
|
|
64
|
+
|
|
65
|
+
classifications.append(FailureClassification(
|
|
66
|
+
pattern=pattern_type,
|
|
67
|
+
confidence=confidence,
|
|
68
|
+
raw_signal=match.group(0),
|
|
69
|
+
structured_signal={"groups": symbols, "regex": regex},
|
|
70
|
+
suggested_strategy=PATTERN_STRATEGIES[pattern_type],
|
|
71
|
+
affected_symbols=symbols,
|
|
72
|
+
))
|
|
73
|
+
|
|
74
|
+
return classifications
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _classify_structured(
|
|
78
|
+
test_output: str,
|
|
79
|
+
pass_rate: float,
|
|
80
|
+
total_tests: int,
|
|
81
|
+
failed_tests: int,
|
|
82
|
+
) -> list[FailureClassification]:
|
|
83
|
+
"""Level 2: Structured classification from parsed test results."""
|
|
84
|
+
classifications: list[FailureClassification] = []
|
|
85
|
+
|
|
86
|
+
# COMPLEX_BEHAVIOR: Many failures in same test module suggest behavioral complexity
|
|
87
|
+
if failed_tests > 5 and pass_rate < 0.5:
|
|
88
|
+
classifications.append(FailureClassification(
|
|
89
|
+
pattern=PatternType.COMPLEX_BEHAVIOR,
|
|
90
|
+
confidence=0.7,
|
|
91
|
+
raw_signal=f"{failed_tests}/{total_tests} tests failed (pass_rate={pass_rate:.0%})",
|
|
92
|
+
structured_signal={
|
|
93
|
+
"failed_count": failed_tests,
|
|
94
|
+
"total_count": total_tests,
|
|
95
|
+
"pass_rate": pass_rate,
|
|
96
|
+
},
|
|
97
|
+
suggested_strategy=Strategy.INCREASE_CONTRACT_CAP,
|
|
98
|
+
))
|
|
99
|
+
|
|
100
|
+
# If ALL tests fail with import errors, it's likely a single root cause
|
|
101
|
+
import_errors = len(re.findall(r"(?:Import|Module)Error", test_output))
|
|
102
|
+
if import_errors > 0 and import_errors >= failed_tests * 0.8:
|
|
103
|
+
# Most failures are import-related — likely a single cross-dep issue
|
|
104
|
+
classifications.append(FailureClassification(
|
|
105
|
+
pattern=PatternType.CROSS_DEP,
|
|
106
|
+
confidence=0.9,
|
|
107
|
+
raw_signal=f"{import_errors} import errors out of {failed_tests} failures",
|
|
108
|
+
structured_signal={"import_error_ratio": import_errors / max(failed_tests, 1)},
|
|
109
|
+
suggested_strategy=Strategy.EXPAND_DEP_CONTEXT,
|
|
110
|
+
))
|
|
111
|
+
|
|
112
|
+
return classifications
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def classify_failures(
|
|
116
|
+
test_output: str,
|
|
117
|
+
pass_rate: float = 0.0,
|
|
118
|
+
total_tests: int = 0,
|
|
119
|
+
failed_tests: int = 0,
|
|
120
|
+
) -> list[FailureClassification]:
|
|
121
|
+
"""Classify test failures using both raw and structured analysis.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
test_output: Raw pytest stdout/stderr text.
|
|
125
|
+
pass_rate: Overall pass rate (0.0-1.0).
|
|
126
|
+
total_tests: Total number of tests run.
|
|
127
|
+
failed_tests: Number of failed tests.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
List of classified failures, sorted by confidence (highest first).
|
|
131
|
+
"""
|
|
132
|
+
raw_results = _classify_raw(test_output)
|
|
133
|
+
structured_results = _classify_structured(test_output, pass_rate, total_tests, failed_tests)
|
|
134
|
+
|
|
135
|
+
# Merge: structured adds context but don't duplicate pattern types
|
|
136
|
+
raw_patterns = {c.pattern for c in raw_results}
|
|
137
|
+
merged = list(raw_results)
|
|
138
|
+
for sc in structured_results:
|
|
139
|
+
if sc.pattern not in raw_patterns:
|
|
140
|
+
merged.append(sc)
|
|
141
|
+
|
|
142
|
+
# Sort by confidence descending
|
|
143
|
+
merged.sort(key=lambda c: c.confidence, reverse=True)
|
|
144
|
+
return merged
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Lesson extraction and storage.
|
|
2
|
+
|
|
3
|
+
Extracts insights from regen loop outcomes that can inform future runs.
|
|
4
|
+
Lessons are automatically derived from patterns and stored for retrieval.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import hashlib
|
|
9
|
+
import json
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class Lesson:
|
|
16
|
+
"""A learned insight from regen loop experience."""
|
|
17
|
+
lesson_id: str # unique hash
|
|
18
|
+
discovered_repo: str # where it was first observed
|
|
19
|
+
category: str # "pattern", "optimization", "limitation", "success"
|
|
20
|
+
description: str # human-readable insight
|
|
21
|
+
evidence: dict[str, Any] = field(default_factory=dict)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _make_lesson_id(category: str, description: str) -> str:
|
|
25
|
+
"""Generate a stable lesson ID from content."""
|
|
26
|
+
content = f"{category}:{description}"
|
|
27
|
+
return hashlib.sha256(content.encode()).hexdigest()[:12]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def extract_lessons(
|
|
31
|
+
repo: str,
|
|
32
|
+
mode: str,
|
|
33
|
+
subsystem_results: dict[str, dict[str, Any]],
|
|
34
|
+
) -> list[Lesson]:
|
|
35
|
+
"""Extract lessons from a completed regen loop run.
|
|
36
|
+
|
|
37
|
+
Looks for:
|
|
38
|
+
- Correlation between features and convergence
|
|
39
|
+
- Novel patterns not in taxonomy
|
|
40
|
+
- Success patterns worth replicating
|
|
41
|
+
- Limitations to document
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
repo: Repository name.
|
|
45
|
+
mode: "normal" or "blind".
|
|
46
|
+
subsystem_results: Results from run_regen_loop.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
List of lessons extracted.
|
|
50
|
+
"""
|
|
51
|
+
lessons: list[Lesson] = []
|
|
52
|
+
|
|
53
|
+
# Analyze convergence patterns
|
|
54
|
+
converged_features: list[dict] = []
|
|
55
|
+
failed_features: list[dict] = []
|
|
56
|
+
|
|
57
|
+
for name, result in subsystem_results.items():
|
|
58
|
+
features = result.get("features", {})
|
|
59
|
+
if result.get("converged", False):
|
|
60
|
+
converged_features.append(features)
|
|
61
|
+
else:
|
|
62
|
+
failed_features.append(features)
|
|
63
|
+
|
|
64
|
+
# Lesson: Contract count threshold
|
|
65
|
+
if converged_features and failed_features:
|
|
66
|
+
avg_conv_contracts = sum(f.get("contract_count", 0) for f in converged_features) / len(converged_features)
|
|
67
|
+
avg_fail_contracts = sum(f.get("contract_count", 0) for f in failed_features) / len(failed_features)
|
|
68
|
+
|
|
69
|
+
if avg_conv_contracts > avg_fail_contracts * 2:
|
|
70
|
+
desc = (f"Subsystems with >{int(avg_fail_contracts)} contracts converge "
|
|
71
|
+
f"({avg_conv_contracts:.0f} avg vs {avg_fail_contracts:.0f} avg)")
|
|
72
|
+
lessons.append(Lesson(
|
|
73
|
+
lesson_id=_make_lesson_id("pattern", desc),
|
|
74
|
+
discovered_repo=repo,
|
|
75
|
+
category="pattern",
|
|
76
|
+
description=desc,
|
|
77
|
+
evidence={
|
|
78
|
+
"avg_converged_contracts": avg_conv_contracts,
|
|
79
|
+
"avg_failed_contracts": avg_fail_contracts,
|
|
80
|
+
"mode": mode,
|
|
81
|
+
},
|
|
82
|
+
))
|
|
83
|
+
|
|
84
|
+
# Lesson: Signature count correlation
|
|
85
|
+
if converged_features and failed_features:
|
|
86
|
+
avg_conv_sigs = sum(f.get("signature_count", 0) for f in converged_features) / len(converged_features)
|
|
87
|
+
avg_fail_sigs = sum(f.get("signature_count", 0) for f in failed_features) / len(failed_features)
|
|
88
|
+
|
|
89
|
+
if avg_fail_sigs > avg_conv_sigs * 1.5:
|
|
90
|
+
desc = (f"High signature count ({avg_fail_sigs:.0f}) correlates with failure — "
|
|
91
|
+
"complex modules need more context")
|
|
92
|
+
lessons.append(Lesson(
|
|
93
|
+
lesson_id=_make_lesson_id("limitation", desc),
|
|
94
|
+
discovered_repo=repo,
|
|
95
|
+
category="limitation",
|
|
96
|
+
description=desc,
|
|
97
|
+
evidence={
|
|
98
|
+
"avg_converged_signatures": avg_conv_sigs,
|
|
99
|
+
"avg_failed_signatures": avg_fail_sigs,
|
|
100
|
+
},
|
|
101
|
+
))
|
|
102
|
+
|
|
103
|
+
# Lesson: Perfect fidelity on simple subsystems
|
|
104
|
+
simple_converged = [
|
|
105
|
+
name for name, r in subsystem_results.items()
|
|
106
|
+
if r.get("converged") and r.get("features", {}).get("signature_count", 0) < 10
|
|
107
|
+
]
|
|
108
|
+
if len(simple_converged) >= 3:
|
|
109
|
+
desc = f"Simple subsystems (<10 signatures) reliably converge: {', '.join(simple_converged[:5])}"
|
|
110
|
+
lessons.append(Lesson(
|
|
111
|
+
lesson_id=_make_lesson_id("success", desc),
|
|
112
|
+
discovered_repo=repo,
|
|
113
|
+
category="success",
|
|
114
|
+
description=desc,
|
|
115
|
+
evidence={"subsystems": simple_converged},
|
|
116
|
+
))
|
|
117
|
+
|
|
118
|
+
# Lesson: All failures are same pattern (systemic issue)
|
|
119
|
+
all_patterns: dict[str, int] = {}
|
|
120
|
+
for r in subsystem_results.values():
|
|
121
|
+
if not r.get("converged"):
|
|
122
|
+
for pattern, count in r.get("failure_patterns", {}).items():
|
|
123
|
+
all_patterns[pattern] = all_patterns.get(pattern, 0) + count
|
|
124
|
+
|
|
125
|
+
if all_patterns:
|
|
126
|
+
dominant = max(all_patterns, key=all_patterns.get)
|
|
127
|
+
total_failures = sum(all_patterns.values())
|
|
128
|
+
if all_patterns[dominant] / total_failures > 0.7:
|
|
129
|
+
desc = (f"Dominant failure pattern '{dominant}' accounts for "
|
|
130
|
+
f"{all_patterns[dominant]}/{total_failures} failures — systemic issue")
|
|
131
|
+
lessons.append(Lesson(
|
|
132
|
+
lesson_id=_make_lesson_id("pattern", desc),
|
|
133
|
+
discovered_repo=repo,
|
|
134
|
+
category="pattern",
|
|
135
|
+
description=desc,
|
|
136
|
+
evidence={"pattern_distribution": all_patterns},
|
|
137
|
+
))
|
|
138
|
+
|
|
139
|
+
return lessons
|