atomadic-forge 0.3.2__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.
- atomadic_forge/__init__.py +12 -0
- atomadic_forge/__main__.py +5 -0
- atomadic_forge/a0_qk_constants/__init__.py +1 -0
- atomadic_forge/a0_qk_constants/agent_plan_schema.py +120 -0
- atomadic_forge/a0_qk_constants/commandsmith_types.py +49 -0
- atomadic_forge/a0_qk_constants/config_defaults.py +38 -0
- atomadic_forge/a0_qk_constants/emergent_types.py +77 -0
- atomadic_forge/a0_qk_constants/error_codes.py +296 -0
- atomadic_forge/a0_qk_constants/forge_types.py +89 -0
- atomadic_forge/a0_qk_constants/gen_language.py +116 -0
- atomadic_forge/a0_qk_constants/lang_extensions.py +150 -0
- atomadic_forge/a0_qk_constants/policy_schema.py +48 -0
- atomadic_forge/a0_qk_constants/receipt_schema.py +311 -0
- atomadic_forge/a0_qk_constants/roi_constants.py +96 -0
- atomadic_forge/a0_qk_constants/semantic_types.py +61 -0
- atomadic_forge/a0_qk_constants/sidecar_schema.py +81 -0
- atomadic_forge/a0_qk_constants/synergy_types.py +62 -0
- atomadic_forge/a0_qk_constants/tier_names.py +47 -0
- atomadic_forge/a1_at_functions/__init__.py +1 -0
- atomadic_forge/a1_at_functions/agent_context_pack.py +193 -0
- atomadic_forge/a1_at_functions/agent_memory.py +139 -0
- atomadic_forge/a1_at_functions/agent_plan_emitter.py +324 -0
- atomadic_forge/a1_at_functions/agent_summary.py +277 -0
- atomadic_forge/a1_at_functions/body_extractor.py +306 -0
- atomadic_forge/a1_at_functions/card_renderer.py +210 -0
- atomadic_forge/a1_at_functions/certify_checks.py +445 -0
- atomadic_forge/a1_at_functions/chat_context.py +170 -0
- atomadic_forge/a1_at_functions/cherry_pick.py +71 -0
- atomadic_forge/a1_at_functions/classify_tier.py +115 -0
- atomadic_forge/a1_at_functions/commandsmith_discover.py +167 -0
- atomadic_forge/a1_at_functions/commandsmith_render.py +267 -0
- atomadic_forge/a1_at_functions/compiler_feedback.py +94 -0
- atomadic_forge/a1_at_functions/compliance_checker.py +228 -0
- atomadic_forge/a1_at_functions/config_io.py +68 -0
- atomadic_forge/a1_at_functions/cs1_renderer.py +588 -0
- atomadic_forge/a1_at_functions/doc_synthesizer.py +205 -0
- atomadic_forge/a1_at_functions/emergent_compose.py +192 -0
- atomadic_forge/a1_at_functions/emergent_rank.py +116 -0
- atomadic_forge/a1_at_functions/emergent_signature_extract.py +242 -0
- atomadic_forge/a1_at_functions/emergent_synthesize.py +88 -0
- atomadic_forge/a1_at_functions/enforce_planner.py +208 -0
- atomadic_forge/a1_at_functions/error_hints.py +105 -0
- atomadic_forge/a1_at_functions/evolution_log.py +94 -0
- atomadic_forge/a1_at_functions/forge_feedback.py +433 -0
- atomadic_forge/a1_at_functions/generation_quality.py +322 -0
- atomadic_forge/a1_at_functions/import_repair.py +211 -0
- atomadic_forge/a1_at_functions/import_smoke.py +102 -0
- atomadic_forge/a1_at_functions/js_parser.py +539 -0
- atomadic_forge/a1_at_functions/lineage_chain.py +144 -0
- atomadic_forge/a1_at_functions/lineage_reader.py +107 -0
- atomadic_forge/a1_at_functions/llm_client.py +554 -0
- atomadic_forge/a1_at_functions/local_signer.py +134 -0
- atomadic_forge/a1_at_functions/lsp_protocol.py +379 -0
- atomadic_forge/a1_at_functions/manifest_diff.py +314 -0
- atomadic_forge/a1_at_functions/mcp_protocol.py +1066 -0
- atomadic_forge/a1_at_functions/patch_scorer.py +267 -0
- atomadic_forge/a1_at_functions/plan_adapter.py +75 -0
- atomadic_forge/a1_at_functions/policy_loader.py +107 -0
- atomadic_forge/a1_at_functions/preflight_change.py +227 -0
- atomadic_forge/a1_at_functions/progress_reporter.py +81 -0
- atomadic_forge/a1_at_functions/provider_detect.py +157 -0
- atomadic_forge/a1_at_functions/provider_resolver.py +48 -0
- atomadic_forge/a1_at_functions/receipt_emitter.py +291 -0
- atomadic_forge/a1_at_functions/recipes.py +186 -0
- atomadic_forge/a1_at_functions/repo_explainer.py +124 -0
- atomadic_forge/a1_at_functions/roi_calculator.py +265 -0
- atomadic_forge/a1_at_functions/rollback_planner.py +147 -0
- atomadic_forge/a1_at_functions/sbom_emitter.py +155 -0
- atomadic_forge/a1_at_functions/scaffold_js.py +55 -0
- atomadic_forge/a1_at_functions/scaffold_pyproject.py +62 -0
- atomadic_forge/a1_at_functions/scaffold_starter.py +94 -0
- atomadic_forge/a1_at_functions/scout_walk.py +309 -0
- atomadic_forge/a1_at_functions/sidecar_parser.py +161 -0
- atomadic_forge/a1_at_functions/sidecar_validator.py +202 -0
- atomadic_forge/a1_at_functions/stub_detector.py +158 -0
- atomadic_forge/a1_at_functions/synergy_detect.py +166 -0
- atomadic_forge/a1_at_functions/synergy_render.py +252 -0
- atomadic_forge/a1_at_functions/synergy_surface_extract.py +163 -0
- atomadic_forge/a1_at_functions/test_runner.py +196 -0
- atomadic_forge/a1_at_functions/test_selector.py +122 -0
- atomadic_forge/a1_at_functions/tier_init_rebuild.py +122 -0
- atomadic_forge/a1_at_functions/tool_composer.py +130 -0
- atomadic_forge/a1_at_functions/transcript_log.py +70 -0
- atomadic_forge/a1_at_functions/wire_check.py +260 -0
- atomadic_forge/a2_mo_composites/__init__.py +1 -0
- atomadic_forge/a2_mo_composites/lineage_chain_store.py +122 -0
- atomadic_forge/a2_mo_composites/manifest_store.py +46 -0
- atomadic_forge/a2_mo_composites/plan_store.py +164 -0
- atomadic_forge/a2_mo_composites/receipt_signer.py +231 -0
- atomadic_forge/a3_og_features/__init__.py +1 -0
- atomadic_forge/a3_og_features/commandsmith_feature.py +267 -0
- atomadic_forge/a3_og_features/demo_packages/mixed_py_js/src/mixed_pkg/__init__.py +3 -0
- atomadic_forge/a3_og_features/demo_packages/mixed_py_js/src/mixed_pkg/a0_qk_constants/__init__.py +4 -0
- atomadic_forge/a3_og_features/demo_packages/mixed_py_js/src/mixed_pkg/a1_at_functions/__init__.py +14 -0
- atomadic_forge/a3_og_features/demo_packages/mixed_py_js/tests/conftest.py +10 -0
- atomadic_forge/a3_og_features/demo_packages/mixed_py_js/tests/test_mixed.py +18 -0
- atomadic_forge/a3_og_features/demo_runner.py +502 -0
- atomadic_forge/a3_og_features/emergent_feature.py +95 -0
- atomadic_forge/a3_og_features/emergent_pipeline_integration.py +154 -0
- atomadic_forge/a3_og_features/forge_enforce.py +107 -0
- atomadic_forge/a3_og_features/forge_evolve.py +176 -0
- atomadic_forge/a3_og_features/forge_loop.py +528 -0
- atomadic_forge/a3_og_features/forge_pipeline.py +295 -0
- atomadic_forge/a3_og_features/forge_plan_apply.py +222 -0
- atomadic_forge/a3_og_features/lsp_server.py +98 -0
- atomadic_forge/a3_og_features/mcp_server.py +160 -0
- atomadic_forge/a3_og_features/setup_wizard.py +337 -0
- atomadic_forge/a3_og_features/synergy_feature.py +65 -0
- atomadic_forge/a4_sy_orchestration/__init__.py +1 -0
- atomadic_forge/a4_sy_orchestration/cli.py +1284 -0
- atomadic_forge/commands/__init__.py +1 -0
- atomadic_forge/commands/_registry.py +36 -0
- atomadic_forge/commands/audit.py +142 -0
- atomadic_forge/commands/chat.py +133 -0
- atomadic_forge/commands/commandsmith.py +178 -0
- atomadic_forge/commands/config_cmd.py +145 -0
- atomadic_forge/commands/demo.py +142 -0
- atomadic_forge/commands/emergent.py +124 -0
- atomadic_forge/commands/emergent_then_synergy.py +70 -0
- atomadic_forge/commands/evolve.py +122 -0
- atomadic_forge/commands/evolve_then_iterate.py +70 -0
- atomadic_forge/commands/feature_then_emergent.py +111 -0
- atomadic_forge/commands/iterate.py +140 -0
- atomadic_forge/commands/synergy.py +96 -0
- atomadic_forge/commands/synergy_then_emergent.py +70 -0
- atomadic_forge-0.3.2.dist-info/METADATA +471 -0
- atomadic_forge-0.3.2.dist-info/RECORD +131 -0
- atomadic_forge-0.3.2.dist-info/WHEEL +5 -0
- atomadic_forge-0.3.2.dist-info/entry_points.txt +3 -0
- atomadic_forge-0.3.2.dist-info/licenses/LICENSE +15 -0
- atomadic_forge-0.3.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"""Tier a1 — deterministic quality phases for generated packages.
|
|
2
|
+
|
|
3
|
+
These helpers run after an LLM has emitted code. They do not invent
|
|
4
|
+
business behavior; they add baseline documentation, docstrings, and import
|
|
5
|
+
smoke tests so generated packages start with a usable maintenance surface.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import ast
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from ..a0_qk_constants.tier_names import TIER_NAMES
|
|
15
|
+
|
|
16
|
+
_GENERATED_MARKER = "<!-- generated by atomadic-forge quality phase -->"
|
|
17
|
+
|
|
18
|
+
_TIER_LABELS = {
|
|
19
|
+
"a0_qk_constants": "constants and type shapes",
|
|
20
|
+
"a1_at_functions": "pure functions",
|
|
21
|
+
"a2_mo_composites": "stateful composites",
|
|
22
|
+
"a3_og_features": "feature orchestration",
|
|
23
|
+
"a4_sy_orchestration": "entry points and CLI wiring",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def apply_docstring_phase(package_root: Path) -> dict[str, Any]:
|
|
28
|
+
"""Add conservative docstrings to Python modules missing them."""
|
|
29
|
+
package_root = Path(package_root)
|
|
30
|
+
changed: list[str] = []
|
|
31
|
+
scanned = 0
|
|
32
|
+
if not package_root.exists():
|
|
33
|
+
return {"phase": "docstrings", "language": "python",
|
|
34
|
+
"files_scanned": 0, "files_changed": []}
|
|
35
|
+
for path in sorted(package_root.rglob("*.py")):
|
|
36
|
+
if "__pycache__" in path.parts or path.name == "__init__.py":
|
|
37
|
+
continue
|
|
38
|
+
scanned += 1
|
|
39
|
+
try:
|
|
40
|
+
original = path.read_text(encoding="utf-8")
|
|
41
|
+
except OSError:
|
|
42
|
+
continue
|
|
43
|
+
updated = add_missing_docstrings(original, rel_path=path.relative_to(package_root))
|
|
44
|
+
if updated != original:
|
|
45
|
+
path.write_text(updated, encoding="utf-8")
|
|
46
|
+
changed.append(path.relative_to(package_root).as_posix())
|
|
47
|
+
return {"phase": "docstrings", "language": "python",
|
|
48
|
+
"files_scanned": scanned, "files_changed": changed}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def add_missing_docstrings(source: str, *, rel_path: Path | str) -> str:
|
|
52
|
+
"""Return ``source`` with missing module/class/function docstrings added."""
|
|
53
|
+
try:
|
|
54
|
+
tree = ast.parse(source)
|
|
55
|
+
except SyntaxError:
|
|
56
|
+
return source
|
|
57
|
+
lines = source.splitlines()
|
|
58
|
+
if not lines:
|
|
59
|
+
return source
|
|
60
|
+
rel = Path(rel_path).as_posix()
|
|
61
|
+
inserts: list[tuple[int, str]] = []
|
|
62
|
+
|
|
63
|
+
if ast.get_docstring(tree) is None:
|
|
64
|
+
inserts.append((0, f'"""{_module_docstring(rel)}"""'))
|
|
65
|
+
|
|
66
|
+
for node in ast.walk(tree):
|
|
67
|
+
if isinstance(node, ast.ClassDef):
|
|
68
|
+
if node.name.startswith("_") or ast.get_docstring(node) is not None:
|
|
69
|
+
continue
|
|
70
|
+
inserts.append(_docstring_insert(node, _class_docstring(node.name)))
|
|
71
|
+
elif isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
72
|
+
if node.name.startswith("_") and node.name != "__init__":
|
|
73
|
+
continue
|
|
74
|
+
if ast.get_docstring(node) is not None:
|
|
75
|
+
continue
|
|
76
|
+
inserts.append(_docstring_insert(node, _function_docstring(node.name)))
|
|
77
|
+
|
|
78
|
+
for index, text in sorted(inserts, key=lambda item: item[0], reverse=True):
|
|
79
|
+
lines.insert(index, text)
|
|
80
|
+
return "\n".join(lines) + ("\n" if source.endswith("\n") else "")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _docstring_insert(node: ast.ClassDef | ast.FunctionDef | ast.AsyncFunctionDef,
|
|
84
|
+
text: str) -> tuple[int, str]:
|
|
85
|
+
if not node.body:
|
|
86
|
+
return (node.lineno, f' """{text}"""')
|
|
87
|
+
first = node.body[0]
|
|
88
|
+
indent = " " * getattr(first, "col_offset", 4)
|
|
89
|
+
return (max(0, first.lineno - 1), f'{indent}"""{text}"""')
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _module_docstring(rel: str) -> str:
|
|
93
|
+
tier = next((part for part in rel.split("/") if part in TIER_NAMES), "")
|
|
94
|
+
label = _TIER_LABELS.get(tier, "generated package code")
|
|
95
|
+
stem = Path(rel).stem.replace("_", " ")
|
|
96
|
+
return f"{label.capitalize()} for {stem}."
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _class_docstring(name: str) -> str:
|
|
100
|
+
words = _humanize_name(name)
|
|
101
|
+
return f"Coordinate {words} behavior."
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _function_docstring(name: str) -> str:
|
|
105
|
+
if name == "__init__":
|
|
106
|
+
return "Initialize the instance."
|
|
107
|
+
words = _humanize_name(name)
|
|
108
|
+
if name.startswith(("is_", "has_", "can_", "should_")):
|
|
109
|
+
return f"Return whether {words}."
|
|
110
|
+
if name.startswith(("parse_", "load_", "read_")):
|
|
111
|
+
return f"Parse {words} input."
|
|
112
|
+
if name.startswith(("render_", "format_", "build_")):
|
|
113
|
+
return f"Build {words} output."
|
|
114
|
+
if name.startswith(("validate_", "check_")):
|
|
115
|
+
return f"Validate {words}."
|
|
116
|
+
return f"Run the {words} operation."
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _humanize_name(name: str) -> str:
|
|
120
|
+
if name == "__init__":
|
|
121
|
+
return "instance"
|
|
122
|
+
out: list[str] = []
|
|
123
|
+
current = ""
|
|
124
|
+
for ch in name.replace("_", " "):
|
|
125
|
+
if ch.isupper() and current and not current.endswith(" "):
|
|
126
|
+
out.append(current)
|
|
127
|
+
current = ch.lower()
|
|
128
|
+
else:
|
|
129
|
+
current += ch.lower()
|
|
130
|
+
if current:
|
|
131
|
+
out.append(current)
|
|
132
|
+
return " ".join("".join(out).split())
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def apply_docs_phase(*, output_root: Path, package_root: Path,
|
|
136
|
+
package: str, intent: str) -> dict[str, Any]:
|
|
137
|
+
"""Create generated API and testing docs without overwriting custom docs."""
|
|
138
|
+
docs_dir = Path(output_root) / "docs"
|
|
139
|
+
docs_dir.mkdir(parents=True, exist_ok=True)
|
|
140
|
+
written: list[str] = []
|
|
141
|
+
skipped: list[str] = []
|
|
142
|
+
files = {
|
|
143
|
+
docs_dir / "API.md": render_api_doc(package_root=package_root,
|
|
144
|
+
package=package, intent=intent),
|
|
145
|
+
docs_dir / "TESTING.md": render_testing_doc(package=package),
|
|
146
|
+
}
|
|
147
|
+
for path, content in files.items():
|
|
148
|
+
if _write_generated(path, content):
|
|
149
|
+
written.append(path.relative_to(output_root).as_posix())
|
|
150
|
+
else:
|
|
151
|
+
skipped.append(path.relative_to(output_root).as_posix())
|
|
152
|
+
return {"phase": "docs", "files_written": written, "files_skipped": skipped}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def render_api_doc(*, package_root: Path, package: str, intent: str) -> str:
|
|
156
|
+
"""Render a compact API reference from public Python symbols."""
|
|
157
|
+
symbols = _collect_symbols(package_root)
|
|
158
|
+
lines = [
|
|
159
|
+
_GENERATED_MARKER,
|
|
160
|
+
f"# {package} API",
|
|
161
|
+
"",
|
|
162
|
+
"This reference is generated from the package source after the Forge",
|
|
163
|
+
"quality phase has added missing docstrings.",
|
|
164
|
+
"",
|
|
165
|
+
"## Intent",
|
|
166
|
+
"",
|
|
167
|
+
f"> {intent.strip() or '(unspecified)'}",
|
|
168
|
+
"",
|
|
169
|
+
]
|
|
170
|
+
if not symbols:
|
|
171
|
+
lines.extend([
|
|
172
|
+
"## Public Surface",
|
|
173
|
+
"",
|
|
174
|
+
"No public Python symbols were discovered yet.",
|
|
175
|
+
"",
|
|
176
|
+
])
|
|
177
|
+
for tier in TIER_NAMES:
|
|
178
|
+
items = symbols.get(tier, [])
|
|
179
|
+
if not items:
|
|
180
|
+
continue
|
|
181
|
+
lines.extend([f"## {tier}", ""])
|
|
182
|
+
for item in items:
|
|
183
|
+
doc = f" — {item['doc']}" if item["doc"] else ""
|
|
184
|
+
lines.append(f"- `{item['signature']}`{doc} (`{item['module']}`)")
|
|
185
|
+
lines.append("")
|
|
186
|
+
return "\n".join(lines)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def render_testing_doc(*, package: str) -> str:
|
|
190
|
+
"""Render generated testing guidance for the package."""
|
|
191
|
+
return "\n".join([
|
|
192
|
+
_GENERATED_MARKER,
|
|
193
|
+
f"# Testing {package}",
|
|
194
|
+
"",
|
|
195
|
+
"Forge creates a baseline import-smoke test so every generated module",
|
|
196
|
+
"is at least importable. Treat it as a floor, not a substitute for",
|
|
197
|
+
"behavioral tests against real inputs.",
|
|
198
|
+
"",
|
|
199
|
+
"```bash",
|
|
200
|
+
"pytest tests/",
|
|
201
|
+
f"forge certify . --package {package}",
|
|
202
|
+
"```",
|
|
203
|
+
"",
|
|
204
|
+
"Add focused tests beside `tests/test_generated_smoke.py` as the",
|
|
205
|
+
"package behavior becomes clearer.",
|
|
206
|
+
"",
|
|
207
|
+
])
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def apply_test_phase(*, output_root: Path, package_root: Path,
|
|
211
|
+
package: str) -> dict[str, Any]:
|
|
212
|
+
"""Create or refresh the generated import-smoke test."""
|
|
213
|
+
tests_dir = Path(output_root) / "tests"
|
|
214
|
+
tests_dir.mkdir(parents=True, exist_ok=True)
|
|
215
|
+
target = tests_dir / "test_generated_smoke.py"
|
|
216
|
+
content = render_smoke_test(package_root=package_root, package=package)
|
|
217
|
+
target.write_text(content, encoding="utf-8")
|
|
218
|
+
return {"phase": "tests",
|
|
219
|
+
"files_written": [target.relative_to(output_root).as_posix()],
|
|
220
|
+
"module_count": len(_module_names(package_root, package))}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def render_smoke_test(*, package_root: Path, package: str) -> str:
|
|
224
|
+
"""Render a stdlib-only pytest-compatible import-smoke test."""
|
|
225
|
+
modules = _module_names(package_root, package)
|
|
226
|
+
module_lines = "\n".join(f" {name!r}," for name in modules)
|
|
227
|
+
package_import = (
|
|
228
|
+
f"import {package} as generated_package"
|
|
229
|
+
if package.isidentifier()
|
|
230
|
+
else f"generated_package = importlib.import_module({package!r})"
|
|
231
|
+
)
|
|
232
|
+
import_lines = ["import importlib", "", package_import]
|
|
233
|
+
return "\n".join([
|
|
234
|
+
f'"""Generated import-smoke tests for {package}."""',
|
|
235
|
+
"",
|
|
236
|
+
"from __future__ import annotations",
|
|
237
|
+
"",
|
|
238
|
+
*import_lines,
|
|
239
|
+
"",
|
|
240
|
+
"MODULES = [",
|
|
241
|
+
module_lines,
|
|
242
|
+
"]",
|
|
243
|
+
"",
|
|
244
|
+
"",
|
|
245
|
+
"def test_generated_package_imports():",
|
|
246
|
+
" assert generated_package",
|
|
247
|
+
"",
|
|
248
|
+
"",
|
|
249
|
+
"def test_generated_modules_import():",
|
|
250
|
+
" for module_name in MODULES:",
|
|
251
|
+
" assert importlib.import_module(module_name)",
|
|
252
|
+
"",
|
|
253
|
+
])
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _write_generated(path: Path, content: str) -> bool:
|
|
257
|
+
if path.exists():
|
|
258
|
+
existing = path.read_text(encoding="utf-8", errors="replace")
|
|
259
|
+
if _GENERATED_MARKER not in existing:
|
|
260
|
+
return False
|
|
261
|
+
path.write_text(content, encoding="utf-8")
|
|
262
|
+
return True
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _module_names(package_root: Path, package: str) -> list[str]:
|
|
266
|
+
out = [package]
|
|
267
|
+
if package_root.exists():
|
|
268
|
+
for py in sorted(package_root.rglob("*.py")):
|
|
269
|
+
if "__pycache__" in py.parts or py.name == "__init__.py":
|
|
270
|
+
continue
|
|
271
|
+
rel = py.relative_to(package_root).with_suffix("")
|
|
272
|
+
parts = ".".join(rel.parts)
|
|
273
|
+
out.append(f"{package}.{parts}")
|
|
274
|
+
return sorted(dict.fromkeys(out))
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _collect_symbols(package_root: Path) -> dict[str, list[dict[str, str]]]:
|
|
278
|
+
out: dict[str, list[dict[str, str]]] = {}
|
|
279
|
+
if not package_root.exists():
|
|
280
|
+
return out
|
|
281
|
+
for tier in TIER_NAMES:
|
|
282
|
+
tier_dir = package_root / tier
|
|
283
|
+
if not tier_dir.exists():
|
|
284
|
+
continue
|
|
285
|
+
items: list[dict[str, str]] = []
|
|
286
|
+
for py in sorted(tier_dir.glob("*.py")):
|
|
287
|
+
if py.name == "__init__.py":
|
|
288
|
+
continue
|
|
289
|
+
try:
|
|
290
|
+
tree = ast.parse(py.read_text(encoding="utf-8"))
|
|
291
|
+
except (SyntaxError, OSError):
|
|
292
|
+
continue
|
|
293
|
+
module = f"{tier}/{py.name}"
|
|
294
|
+
for node in tree.body:
|
|
295
|
+
if isinstance(node, ast.ClassDef) and not node.name.startswith("_"):
|
|
296
|
+
items.append({
|
|
297
|
+
"module": module,
|
|
298
|
+
"signature": f"class {node.name}",
|
|
299
|
+
"doc": _first_doc_line(node),
|
|
300
|
+
})
|
|
301
|
+
elif isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
302
|
+
if node.name.startswith("_"):
|
|
303
|
+
continue
|
|
304
|
+
items.append({
|
|
305
|
+
"module": module,
|
|
306
|
+
"signature": _signature(node),
|
|
307
|
+
"doc": _first_doc_line(node),
|
|
308
|
+
})
|
|
309
|
+
if items:
|
|
310
|
+
out[tier] = items
|
|
311
|
+
return out
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _first_doc_line(node: ast.AST) -> str:
|
|
315
|
+
doc = ast.get_docstring(node) or ""
|
|
316
|
+
return doc.strip().split("\n", 1)[0] if doc else ""
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _signature(fn: ast.FunctionDef | ast.AsyncFunctionDef) -> str:
|
|
320
|
+
args = [a.arg for a in fn.args.args if a.arg not in {"self", "cls"}]
|
|
321
|
+
prefix = "async def" if isinstance(fn, ast.AsyncFunctionDef) else "def"
|
|
322
|
+
return f"{prefix} {fn.name}({', '.join(args)})"
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Tier a1 — pure import-repair pass for assimilated symbol files.
|
|
2
|
+
|
|
3
|
+
When ``atomadic-forge assimilate`` materialises a symbol from a flat-layout sibling
|
|
4
|
+
repo into the 5-tier monadic package, intra-package references (e.g.
|
|
5
|
+
``from kg import infer_tier``) are not rewritten to the new sibling file
|
|
6
|
+
paths. The result is a thousand-file package that silently refuses to
|
|
7
|
+
import.
|
|
8
|
+
|
|
9
|
+
This module provides a deterministic post-process that:
|
|
10
|
+
|
|
11
|
+
1. Scans a tier folder for ``*.py`` files materialised by the assimilator.
|
|
12
|
+
2. Builds a name-to-relative-module map from the per-source filename
|
|
13
|
+
convention (``a<N>_source_<root>_<symbol>.py`` exposes ``<symbol>``).
|
|
14
|
+
3. Rewrites every broken ``from <flat-name> import X`` line to the matching
|
|
15
|
+
``from .a<N>_source_<root>_<flat-name> import X`` sibling reference.
|
|
16
|
+
|
|
17
|
+
It is pure: takes file paths and returns a diff list of (path, new_text).
|
|
18
|
+
The caller decides whether to apply.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import re
|
|
24
|
+
from collections.abc import Iterable
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
_SOURCE_HEAD = re.compile(r"^a\d_source_")
|
|
28
|
+
_FROM_LINE = re.compile(r"^from\s+([A-Za-z_][\w]*)\s+import\s+(.+)$", re.MULTILINE)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Roots the assimilator can encode in file stems. Add new roots as needed.
|
|
32
|
+
KNOWN_SOURCE_ROOTS: tuple[str, ...] = ("atomadic_forge_seed", "atomadic_v2", "atomadic_engine")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _split_source_stem(stem: str,
|
|
36
|
+
roots: tuple[str, ...] = KNOWN_SOURCE_ROOTS) -> tuple[str, str] | None:
|
|
37
|
+
"""Return ``(root, symbol)`` for ``a<N>_source_<root>_<symbol>``.
|
|
38
|
+
|
|
39
|
+
Uses prefix-stripping with a known root list so symbols containing
|
|
40
|
+
underscores (``infer_tier``, ``cherry_pick_dict``) are split correctly.
|
|
41
|
+
"""
|
|
42
|
+
head = _SOURCE_HEAD.match(stem)
|
|
43
|
+
if not head:
|
|
44
|
+
return None
|
|
45
|
+
rest = stem[head.end():]
|
|
46
|
+
for root in roots:
|
|
47
|
+
if rest.startswith(root + "_"):
|
|
48
|
+
return root, rest[len(root) + 1:]
|
|
49
|
+
if rest == root:
|
|
50
|
+
return root, ""
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def build_symbol_map(tier_dir: Path) -> dict[str, str]:
|
|
55
|
+
"""Return ``{flat_module_name: tier_relative_module}`` for every assimilated file.
|
|
56
|
+
|
|
57
|
+
A file like ``a1_source_atomadic_v2_kg.py`` maps the flat name ``kg``
|
|
58
|
+
to the relative module path ``.a1_source_atomadic_v2_kg``.
|
|
59
|
+
Last-write-wins on collisions (later files override earlier ones).
|
|
60
|
+
"""
|
|
61
|
+
out: dict[str, str] = {}
|
|
62
|
+
for f in sorted(tier_dir.glob("a*_source_*.py")):
|
|
63
|
+
parsed = _split_source_stem(f.stem)
|
|
64
|
+
if not parsed:
|
|
65
|
+
continue
|
|
66
|
+
_root, flat_name = parsed
|
|
67
|
+
out[flat_name] = "." + f.stem
|
|
68
|
+
return out
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def build_name_to_file_map(*tier_dirs: Path) -> dict[str, str]:
|
|
72
|
+
"""Return ``{symbol_name: relative_module}`` across all listed tier dirs.
|
|
73
|
+
|
|
74
|
+
The assimilator emits one file per symbol whose stem encodes the symbol
|
|
75
|
+
in lowercase (``a1_source_atomadic_v2_infer_tier.py`` → ``infer_tier``;
|
|
76
|
+
``a1_source_atomadic_v2_agenticswarm.py`` → ``agenticswarm`` ≈ class
|
|
77
|
+
``AgenticSwarm``). We therefore key on case-folded names and accept
|
|
78
|
+
variants when rewriting.
|
|
79
|
+
"""
|
|
80
|
+
out: dict[str, str] = {}
|
|
81
|
+
for tier_dir in tier_dirs:
|
|
82
|
+
if not tier_dir.exists():
|
|
83
|
+
continue
|
|
84
|
+
for f in sorted(tier_dir.glob("a*_source_*.py")):
|
|
85
|
+
parsed = _split_source_stem(f.stem)
|
|
86
|
+
if not parsed:
|
|
87
|
+
continue
|
|
88
|
+
_root, name = parsed
|
|
89
|
+
out[name.lower()] = "." + f.stem
|
|
90
|
+
return out
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def rewrite_imports(source: str, symbol_map: dict[str, str],
|
|
94
|
+
stdlib_safelist: Iterable[str] = (),
|
|
95
|
+
name_to_file: dict[str, str] | None = None) -> str:
|
|
96
|
+
"""Return ``source`` with broken flat imports rewritten to sibling paths.
|
|
97
|
+
|
|
98
|
+
Two passes:
|
|
99
|
+
|
|
100
|
+
1. ``from <X> import Y`` where ``X`` is in ``symbol_map`` (a known module
|
|
101
|
+
name) → ``from <symbol_map[X]> import Y``.
|
|
102
|
+
2. ``from <X> import Y[, Z, ...]`` where ``X`` is unknown but one or more
|
|
103
|
+
of the imported names are in ``name_to_file`` → split into one
|
|
104
|
+
``from .<file> import <name>`` per imported name.
|
|
105
|
+
|
|
106
|
+
Imports of stdlib / third-party modules in ``stdlib_safelist`` are left
|
|
107
|
+
alone.
|
|
108
|
+
"""
|
|
109
|
+
safe = set(stdlib_safelist)
|
|
110
|
+
name_to_file = name_to_file or {}
|
|
111
|
+
|
|
112
|
+
def replace(match: re.Match[str]) -> str:
|
|
113
|
+
head = match.group(1)
|
|
114
|
+
rest = match.group(2).strip()
|
|
115
|
+
if head in safe:
|
|
116
|
+
return match.group(0)
|
|
117
|
+
if head in symbol_map:
|
|
118
|
+
return f"from {symbol_map[head]} import {rest}"
|
|
119
|
+
# try per-name resolution
|
|
120
|
+
names = [n.strip() for n in rest.split(",") if n.strip()]
|
|
121
|
+
resolved: list[str] = []
|
|
122
|
+
unresolved: list[str] = []
|
|
123
|
+
for n in names:
|
|
124
|
+
base_name = n.split(" as ")[0].strip()
|
|
125
|
+
key = base_name.lower()
|
|
126
|
+
if key in name_to_file:
|
|
127
|
+
resolved.append(f"from {name_to_file[key]} import {n}")
|
|
128
|
+
else:
|
|
129
|
+
unresolved.append(n)
|
|
130
|
+
if resolved and not unresolved:
|
|
131
|
+
return "\n".join(resolved)
|
|
132
|
+
return match.group(0)
|
|
133
|
+
|
|
134
|
+
return _FROM_LINE.sub(replace, source)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def repair_tier_directory(tier_dir: Path,
|
|
138
|
+
dry_run: bool = True,
|
|
139
|
+
name_to_file: dict[str, str] | None = None) -> dict[str, str]:
|
|
140
|
+
"""Repair every ``a*_source_*.py`` file in ``tier_dir``.
|
|
141
|
+
|
|
142
|
+
``name_to_file`` (optional) maps symbol names (case-folded) to relative
|
|
143
|
+
module paths and is used as a fallback resolver when the import head
|
|
144
|
+
isn't a known sibling module. Build it once via
|
|
145
|
+
:func:`build_name_to_file_map` over every tier and pass it to each call.
|
|
146
|
+
|
|
147
|
+
Returns ``{path: new_text}`` for files that would change. When
|
|
148
|
+
``dry_run`` is False the changes are written back to disk.
|
|
149
|
+
"""
|
|
150
|
+
symbol_map = build_symbol_map(tier_dir)
|
|
151
|
+
diffs: dict[str, str] = {}
|
|
152
|
+
for f in sorted(tier_dir.glob("a*_source_*.py")):
|
|
153
|
+
try:
|
|
154
|
+
current = f.read_text(encoding="utf-8")
|
|
155
|
+
except OSError:
|
|
156
|
+
continue
|
|
157
|
+
new_text = rewrite_imports(current, symbol_map,
|
|
158
|
+
_BUILTIN_AND_THIRD_PARTY,
|
|
159
|
+
name_to_file=name_to_file)
|
|
160
|
+
if new_text != current:
|
|
161
|
+
diffs[str(f)] = new_text
|
|
162
|
+
if not dry_run:
|
|
163
|
+
f.write_text(new_text, encoding="utf-8")
|
|
164
|
+
return diffs
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def repair_assimilation_output(package_root: Path,
|
|
168
|
+
dry_run: bool = True) -> dict[str, dict[str, str]]:
|
|
169
|
+
"""Repair every tier under ``package_root`` (e.g. ``…/src/atomadic_forge``).
|
|
170
|
+
|
|
171
|
+
Builds one global ``name_to_file`` map across all tiers (because a tier-a2
|
|
172
|
+
composite often imports a tier-a1 helper) and applies the rewrite pass.
|
|
173
|
+
"""
|
|
174
|
+
tiers = ["a0_qk_constants", "a1_at_functions", "a2_mo_composites",
|
|
175
|
+
"a3_og_features", "a4_sy_orchestration"]
|
|
176
|
+
tier_dirs = [package_root / t for t in tiers if (package_root / t).exists()]
|
|
177
|
+
out: dict[str, dict[str, str]] = {}
|
|
178
|
+
for t, tier_dir in zip(tiers, tier_dirs, strict=False):
|
|
179
|
+
# name_to_file uses tier-internal relative paths. When repairing
|
|
180
|
+
# *a different* tier, we still need relative-from-self paths. The
|
|
181
|
+
# convention here: a single global map keyed by symbol with paths
|
|
182
|
+
# relative to *its own* tier directory still works because the
|
|
183
|
+
# output package re-exports everything via per-tier ``__init__.py``
|
|
184
|
+
# modules — but for cross-tier refs we'd want absolute imports.
|
|
185
|
+
# In practice, the assimilator places nearly all atomadic-v2
|
|
186
|
+
# symbols at a1, so a tier-local map is sufficient for this dataset.
|
|
187
|
+
local_n2f = build_name_to_file_map(tier_dir)
|
|
188
|
+
out[t] = repair_tier_directory(tier_dir, dry_run=dry_run,
|
|
189
|
+
name_to_file=local_n2f)
|
|
190
|
+
return out
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# Common stdlib + third-party top-level names we should never rewrite.
|
|
194
|
+
_BUILTIN_AND_THIRD_PARTY = frozenset({
|
|
195
|
+
# stdlib (representative — extend as needed)
|
|
196
|
+
"abc", "argparse", "ast", "asyncio", "base64", "collections", "concurrent",
|
|
197
|
+
"contextlib", "copy", "csv", "dataclasses", "datetime", "decimal", "enum",
|
|
198
|
+
"functools", "glob", "hashlib", "hmac", "html", "http", "importlib", "inspect",
|
|
199
|
+
"io", "itertools", "json", "logging", "math", "operator", "os", "pathlib",
|
|
200
|
+
"pickle", "pkgutil", "platform", "pprint", "queue", "random", "re", "secrets",
|
|
201
|
+
"shlex", "shutil", "signal", "socket", "sqlite3", "statistics", "string",
|
|
202
|
+
"struct", "subprocess", "sys", "tempfile", "textwrap", "threading", "time",
|
|
203
|
+
"tomllib", "traceback", "types", "typing", "unicodedata", "urllib", "uuid",
|
|
204
|
+
"warnings", "weakref", "xml", "zipfile",
|
|
205
|
+
# third-party
|
|
206
|
+
"click", "cryptography", "fastapi", "httpx", "jinja2", "jsonschema", "numpy",
|
|
207
|
+
"pandas", "peft", "pydantic", "pytest", "rich", "ruff", "torch", "transformers",
|
|
208
|
+
"typer", "uvicorn", "yaml", "edge_tts", "x402", "discord", "datasets",
|
|
209
|
+
# atomadic_forge itself
|
|
210
|
+
"atomadic_forge",
|
|
211
|
+
})
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Tier a1 — runtime import smoke for a generated package.
|
|
2
|
+
|
|
3
|
+
Wire/tier discipline tells you the architecture is legal. It does NOT
|
|
4
|
+
tell you the code actually loads — a syntax error, a missing import, or a
|
|
5
|
+
relative-import gone wrong all pass wire-check but fail at runtime.
|
|
6
|
+
|
|
7
|
+
This module spawns a Python subprocess and tries
|
|
8
|
+
``python -c "import <pkg>"`` with the appropriate ``PYTHONPATH``. Returns
|
|
9
|
+
a structured report Forge can fold into certify and into LLM feedback.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import TypedDict
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ImportSmokeReport(TypedDict):
|
|
21
|
+
schema_version: str # "atomadic-forge.import_smoke/v1"
|
|
22
|
+
package: str
|
|
23
|
+
src_root: str
|
|
24
|
+
importable: bool
|
|
25
|
+
duration_ms: int
|
|
26
|
+
error_kind: str # "" | "SyntaxError" | "ImportError" | "ModuleNotFoundError" | "Other"
|
|
27
|
+
error_message: str # short
|
|
28
|
+
traceback_excerpt: str # last ~600 chars of stderr
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _classify_error(stderr: str) -> str:
|
|
32
|
+
if not stderr:
|
|
33
|
+
return ""
|
|
34
|
+
lowered_lines = [ln for ln in stderr.splitlines() if ln.strip()]
|
|
35
|
+
last = lowered_lines[-1] if lowered_lines else ""
|
|
36
|
+
for kind in ("ModuleNotFoundError", "ImportError", "SyntaxError",
|
|
37
|
+
"AttributeError", "NameError", "TypeError",
|
|
38
|
+
"IndentationError"):
|
|
39
|
+
if kind in last or kind in stderr:
|
|
40
|
+
return kind
|
|
41
|
+
return "Other"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _short_message(stderr: str) -> str:
|
|
45
|
+
if not stderr:
|
|
46
|
+
return ""
|
|
47
|
+
lines = [ln for ln in stderr.splitlines() if ln.strip()]
|
|
48
|
+
return lines[-1][:200] if lines else stderr[:200]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def import_smoke(*, output_root: Path, package: str,
|
|
52
|
+
timeout_s: float = 30.0) -> ImportSmokeReport:
|
|
53
|
+
"""Try ``python -c 'import <package>'`` with src-layout PYTHONPATH.
|
|
54
|
+
|
|
55
|
+
``output_root`` is the parent of ``src/`` (the directory that contains
|
|
56
|
+
a ``src/<package>/`` materialized by Forge).
|
|
57
|
+
"""
|
|
58
|
+
output_root = Path(output_root).resolve()
|
|
59
|
+
src_root = output_root / "src"
|
|
60
|
+
if not (src_root / package).exists():
|
|
61
|
+
return ImportSmokeReport(
|
|
62
|
+
schema_version="atomadic-forge.import_smoke/v1",
|
|
63
|
+
package=package, src_root=str(src_root),
|
|
64
|
+
importable=False, duration_ms=0,
|
|
65
|
+
error_kind="ModuleNotFoundError",
|
|
66
|
+
error_message=f"package {package!r} not present under {src_root}",
|
|
67
|
+
traceback_excerpt="",
|
|
68
|
+
)
|
|
69
|
+
import time
|
|
70
|
+
env = {**__import__("os").environ}
|
|
71
|
+
existing_pp = env.get("PYTHONPATH", "")
|
|
72
|
+
env["PYTHONPATH"] = (str(src_root) + (";" if existing_pp else "") + existing_pp
|
|
73
|
+
if sys.platform == "win32"
|
|
74
|
+
else str(src_root) + (":" if existing_pp else "") + existing_pp)
|
|
75
|
+
env["PYTHONIOENCODING"] = "utf-8"
|
|
76
|
+
start = time.perf_counter()
|
|
77
|
+
try:
|
|
78
|
+
proc = subprocess.run(
|
|
79
|
+
[sys.executable, "-c", f"import {package}"],
|
|
80
|
+
env=env, capture_output=True, text=True, timeout=timeout_s,
|
|
81
|
+
encoding="utf-8", errors="replace",
|
|
82
|
+
)
|
|
83
|
+
duration_ms = int((time.perf_counter() - start) * 1000)
|
|
84
|
+
except subprocess.TimeoutExpired:
|
|
85
|
+
return ImportSmokeReport(
|
|
86
|
+
schema_version="atomadic-forge.import_smoke/v1",
|
|
87
|
+
package=package, src_root=str(src_root),
|
|
88
|
+
importable=False, duration_ms=int(timeout_s * 1000),
|
|
89
|
+
error_kind="TimeoutExpired",
|
|
90
|
+
error_message=f"import timed out after {timeout_s}s",
|
|
91
|
+
traceback_excerpt="",
|
|
92
|
+
)
|
|
93
|
+
importable = proc.returncode == 0
|
|
94
|
+
stderr = proc.stderr or ""
|
|
95
|
+
return ImportSmokeReport(
|
|
96
|
+
schema_version="atomadic-forge.import_smoke/v1",
|
|
97
|
+
package=package, src_root=str(src_root),
|
|
98
|
+
importable=importable, duration_ms=duration_ms,
|
|
99
|
+
error_kind="" if importable else _classify_error(stderr),
|
|
100
|
+
error_message="" if importable else _short_message(stderr),
|
|
101
|
+
traceback_excerpt="" if importable else stderr[-600:],
|
|
102
|
+
)
|