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.
Files changed (131) hide show
  1. atomadic_forge/__init__.py +12 -0
  2. atomadic_forge/__main__.py +5 -0
  3. atomadic_forge/a0_qk_constants/__init__.py +1 -0
  4. atomadic_forge/a0_qk_constants/agent_plan_schema.py +120 -0
  5. atomadic_forge/a0_qk_constants/commandsmith_types.py +49 -0
  6. atomadic_forge/a0_qk_constants/config_defaults.py +38 -0
  7. atomadic_forge/a0_qk_constants/emergent_types.py +77 -0
  8. atomadic_forge/a0_qk_constants/error_codes.py +296 -0
  9. atomadic_forge/a0_qk_constants/forge_types.py +89 -0
  10. atomadic_forge/a0_qk_constants/gen_language.py +116 -0
  11. atomadic_forge/a0_qk_constants/lang_extensions.py +150 -0
  12. atomadic_forge/a0_qk_constants/policy_schema.py +48 -0
  13. atomadic_forge/a0_qk_constants/receipt_schema.py +311 -0
  14. atomadic_forge/a0_qk_constants/roi_constants.py +96 -0
  15. atomadic_forge/a0_qk_constants/semantic_types.py +61 -0
  16. atomadic_forge/a0_qk_constants/sidecar_schema.py +81 -0
  17. atomadic_forge/a0_qk_constants/synergy_types.py +62 -0
  18. atomadic_forge/a0_qk_constants/tier_names.py +47 -0
  19. atomadic_forge/a1_at_functions/__init__.py +1 -0
  20. atomadic_forge/a1_at_functions/agent_context_pack.py +193 -0
  21. atomadic_forge/a1_at_functions/agent_memory.py +139 -0
  22. atomadic_forge/a1_at_functions/agent_plan_emitter.py +324 -0
  23. atomadic_forge/a1_at_functions/agent_summary.py +277 -0
  24. atomadic_forge/a1_at_functions/body_extractor.py +306 -0
  25. atomadic_forge/a1_at_functions/card_renderer.py +210 -0
  26. atomadic_forge/a1_at_functions/certify_checks.py +445 -0
  27. atomadic_forge/a1_at_functions/chat_context.py +170 -0
  28. atomadic_forge/a1_at_functions/cherry_pick.py +71 -0
  29. atomadic_forge/a1_at_functions/classify_tier.py +115 -0
  30. atomadic_forge/a1_at_functions/commandsmith_discover.py +167 -0
  31. atomadic_forge/a1_at_functions/commandsmith_render.py +267 -0
  32. atomadic_forge/a1_at_functions/compiler_feedback.py +94 -0
  33. atomadic_forge/a1_at_functions/compliance_checker.py +228 -0
  34. atomadic_forge/a1_at_functions/config_io.py +68 -0
  35. atomadic_forge/a1_at_functions/cs1_renderer.py +588 -0
  36. atomadic_forge/a1_at_functions/doc_synthesizer.py +205 -0
  37. atomadic_forge/a1_at_functions/emergent_compose.py +192 -0
  38. atomadic_forge/a1_at_functions/emergent_rank.py +116 -0
  39. atomadic_forge/a1_at_functions/emergent_signature_extract.py +242 -0
  40. atomadic_forge/a1_at_functions/emergent_synthesize.py +88 -0
  41. atomadic_forge/a1_at_functions/enforce_planner.py +208 -0
  42. atomadic_forge/a1_at_functions/error_hints.py +105 -0
  43. atomadic_forge/a1_at_functions/evolution_log.py +94 -0
  44. atomadic_forge/a1_at_functions/forge_feedback.py +433 -0
  45. atomadic_forge/a1_at_functions/generation_quality.py +322 -0
  46. atomadic_forge/a1_at_functions/import_repair.py +211 -0
  47. atomadic_forge/a1_at_functions/import_smoke.py +102 -0
  48. atomadic_forge/a1_at_functions/js_parser.py +539 -0
  49. atomadic_forge/a1_at_functions/lineage_chain.py +144 -0
  50. atomadic_forge/a1_at_functions/lineage_reader.py +107 -0
  51. atomadic_forge/a1_at_functions/llm_client.py +554 -0
  52. atomadic_forge/a1_at_functions/local_signer.py +134 -0
  53. atomadic_forge/a1_at_functions/lsp_protocol.py +379 -0
  54. atomadic_forge/a1_at_functions/manifest_diff.py +314 -0
  55. atomadic_forge/a1_at_functions/mcp_protocol.py +1066 -0
  56. atomadic_forge/a1_at_functions/patch_scorer.py +267 -0
  57. atomadic_forge/a1_at_functions/plan_adapter.py +75 -0
  58. atomadic_forge/a1_at_functions/policy_loader.py +107 -0
  59. atomadic_forge/a1_at_functions/preflight_change.py +227 -0
  60. atomadic_forge/a1_at_functions/progress_reporter.py +81 -0
  61. atomadic_forge/a1_at_functions/provider_detect.py +157 -0
  62. atomadic_forge/a1_at_functions/provider_resolver.py +48 -0
  63. atomadic_forge/a1_at_functions/receipt_emitter.py +291 -0
  64. atomadic_forge/a1_at_functions/recipes.py +186 -0
  65. atomadic_forge/a1_at_functions/repo_explainer.py +124 -0
  66. atomadic_forge/a1_at_functions/roi_calculator.py +265 -0
  67. atomadic_forge/a1_at_functions/rollback_planner.py +147 -0
  68. atomadic_forge/a1_at_functions/sbom_emitter.py +155 -0
  69. atomadic_forge/a1_at_functions/scaffold_js.py +55 -0
  70. atomadic_forge/a1_at_functions/scaffold_pyproject.py +62 -0
  71. atomadic_forge/a1_at_functions/scaffold_starter.py +94 -0
  72. atomadic_forge/a1_at_functions/scout_walk.py +309 -0
  73. atomadic_forge/a1_at_functions/sidecar_parser.py +161 -0
  74. atomadic_forge/a1_at_functions/sidecar_validator.py +202 -0
  75. atomadic_forge/a1_at_functions/stub_detector.py +158 -0
  76. atomadic_forge/a1_at_functions/synergy_detect.py +166 -0
  77. atomadic_forge/a1_at_functions/synergy_render.py +252 -0
  78. atomadic_forge/a1_at_functions/synergy_surface_extract.py +163 -0
  79. atomadic_forge/a1_at_functions/test_runner.py +196 -0
  80. atomadic_forge/a1_at_functions/test_selector.py +122 -0
  81. atomadic_forge/a1_at_functions/tier_init_rebuild.py +122 -0
  82. atomadic_forge/a1_at_functions/tool_composer.py +130 -0
  83. atomadic_forge/a1_at_functions/transcript_log.py +70 -0
  84. atomadic_forge/a1_at_functions/wire_check.py +260 -0
  85. atomadic_forge/a2_mo_composites/__init__.py +1 -0
  86. atomadic_forge/a2_mo_composites/lineage_chain_store.py +122 -0
  87. atomadic_forge/a2_mo_composites/manifest_store.py +46 -0
  88. atomadic_forge/a2_mo_composites/plan_store.py +164 -0
  89. atomadic_forge/a2_mo_composites/receipt_signer.py +231 -0
  90. atomadic_forge/a3_og_features/__init__.py +1 -0
  91. atomadic_forge/a3_og_features/commandsmith_feature.py +267 -0
  92. atomadic_forge/a3_og_features/demo_packages/mixed_py_js/src/mixed_pkg/__init__.py +3 -0
  93. atomadic_forge/a3_og_features/demo_packages/mixed_py_js/src/mixed_pkg/a0_qk_constants/__init__.py +4 -0
  94. atomadic_forge/a3_og_features/demo_packages/mixed_py_js/src/mixed_pkg/a1_at_functions/__init__.py +14 -0
  95. atomadic_forge/a3_og_features/demo_packages/mixed_py_js/tests/conftest.py +10 -0
  96. atomadic_forge/a3_og_features/demo_packages/mixed_py_js/tests/test_mixed.py +18 -0
  97. atomadic_forge/a3_og_features/demo_runner.py +502 -0
  98. atomadic_forge/a3_og_features/emergent_feature.py +95 -0
  99. atomadic_forge/a3_og_features/emergent_pipeline_integration.py +154 -0
  100. atomadic_forge/a3_og_features/forge_enforce.py +107 -0
  101. atomadic_forge/a3_og_features/forge_evolve.py +176 -0
  102. atomadic_forge/a3_og_features/forge_loop.py +528 -0
  103. atomadic_forge/a3_og_features/forge_pipeline.py +295 -0
  104. atomadic_forge/a3_og_features/forge_plan_apply.py +222 -0
  105. atomadic_forge/a3_og_features/lsp_server.py +98 -0
  106. atomadic_forge/a3_og_features/mcp_server.py +160 -0
  107. atomadic_forge/a3_og_features/setup_wizard.py +337 -0
  108. atomadic_forge/a3_og_features/synergy_feature.py +65 -0
  109. atomadic_forge/a4_sy_orchestration/__init__.py +1 -0
  110. atomadic_forge/a4_sy_orchestration/cli.py +1284 -0
  111. atomadic_forge/commands/__init__.py +1 -0
  112. atomadic_forge/commands/_registry.py +36 -0
  113. atomadic_forge/commands/audit.py +142 -0
  114. atomadic_forge/commands/chat.py +133 -0
  115. atomadic_forge/commands/commandsmith.py +178 -0
  116. atomadic_forge/commands/config_cmd.py +145 -0
  117. atomadic_forge/commands/demo.py +142 -0
  118. atomadic_forge/commands/emergent.py +124 -0
  119. atomadic_forge/commands/emergent_then_synergy.py +70 -0
  120. atomadic_forge/commands/evolve.py +122 -0
  121. atomadic_forge/commands/evolve_then_iterate.py +70 -0
  122. atomadic_forge/commands/feature_then_emergent.py +111 -0
  123. atomadic_forge/commands/iterate.py +140 -0
  124. atomadic_forge/commands/synergy.py +96 -0
  125. atomadic_forge/commands/synergy_then_emergent.py +70 -0
  126. atomadic_forge-0.3.2.dist-info/METADATA +471 -0
  127. atomadic_forge-0.3.2.dist-info/RECORD +131 -0
  128. atomadic_forge-0.3.2.dist-info/WHEEL +5 -0
  129. atomadic_forge-0.3.2.dist-info/entry_points.txt +3 -0
  130. atomadic_forge-0.3.2.dist-info/licenses/LICENSE +15 -0
  131. 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
+ )