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,205 @@
|
|
|
1
|
+
"""Tier a1 — pure renderer that builds a stunning README from a generated package.
|
|
2
|
+
|
|
3
|
+
After evolve converges, Forge walks the produced package and synthesises a
|
|
4
|
+
showcase-quality README from the actual code, tests, and certify report.
|
|
5
|
+
This replaces the bare scaffolded README with one that reflects what was
|
|
6
|
+
actually built — function names, class signatures, working CLI invocation,
|
|
7
|
+
score breakdown, evidence.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import ast
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _collect_public_symbols(package_root: Path) -> dict[str, list[dict[str, str]]]:
|
|
18
|
+
"""Return ``{tier: [{name, kind, signature, doc}]}`` for the package."""
|
|
19
|
+
out: dict[str, list[dict[str, str]]] = {}
|
|
20
|
+
for tier_dir in sorted(package_root.iterdir()):
|
|
21
|
+
if not tier_dir.is_dir():
|
|
22
|
+
continue
|
|
23
|
+
if not tier_dir.name.startswith(("a0_", "a1_", "a2_", "a3_", "a4_")):
|
|
24
|
+
continue
|
|
25
|
+
items: list[dict[str, str]] = []
|
|
26
|
+
for py in sorted(tier_dir.glob("*.py")):
|
|
27
|
+
if py.name == "__init__.py":
|
|
28
|
+
continue
|
|
29
|
+
try:
|
|
30
|
+
tree = ast.parse(py.read_text(encoding="utf-8"))
|
|
31
|
+
except (SyntaxError, OSError):
|
|
32
|
+
continue
|
|
33
|
+
for node in tree.body:
|
|
34
|
+
if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
35
|
+
if node.name.startswith("_"):
|
|
36
|
+
continue
|
|
37
|
+
sig = _function_signature(node)
|
|
38
|
+
items.append({
|
|
39
|
+
"name": node.name, "kind": "function", "signature": sig,
|
|
40
|
+
"doc": (ast.get_docstring(node) or "").split("\n")[0],
|
|
41
|
+
"module": py.stem,
|
|
42
|
+
})
|
|
43
|
+
elif isinstance(node, ast.ClassDef) and not node.name.startswith("_"):
|
|
44
|
+
items.append({
|
|
45
|
+
"name": node.name, "kind": "class",
|
|
46
|
+
"signature": f"class {node.name}",
|
|
47
|
+
"doc": (ast.get_docstring(node) or "").split("\n")[0],
|
|
48
|
+
"module": py.stem,
|
|
49
|
+
})
|
|
50
|
+
if items:
|
|
51
|
+
out[tier_dir.name] = items
|
|
52
|
+
return out
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _function_signature(fn: ast.FunctionDef | ast.AsyncFunctionDef) -> str:
|
|
56
|
+
args: list[str] = []
|
|
57
|
+
for arg in fn.args.args:
|
|
58
|
+
if arg.arg in ("self", "cls"):
|
|
59
|
+
continue
|
|
60
|
+
ann = ast.unparse(arg.annotation) if arg.annotation else None
|
|
61
|
+
args.append(f"{arg.arg}: {ann}" if ann else arg.arg)
|
|
62
|
+
ret = ast.unparse(fn.returns) if fn.returns else "Any"
|
|
63
|
+
return f"def {fn.name}({', '.join(args)}) -> {ret}"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
_TIER_LABEL = {
|
|
67
|
+
"a0_qk_constants": "a0 — constants",
|
|
68
|
+
"a1_at_functions": "a1 — pure functions",
|
|
69
|
+
"a2_mo_composites": "a2 — stateful classes",
|
|
70
|
+
"a3_og_features": "a3 — feature orchestrators",
|
|
71
|
+
"a4_sy_orchestration": "a4 — CLI / entry points",
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def render_showcase_readme(*, package: str, intent: str,
|
|
76
|
+
output_root: Path,
|
|
77
|
+
certify_report: dict[str, Any] | None = None,
|
|
78
|
+
cli_demo: dict[str, Any] | None = None,
|
|
79
|
+
llm_name: str = "?") -> str:
|
|
80
|
+
"""Build a stunning README for a generated package.
|
|
81
|
+
|
|
82
|
+
``cli_demo`` (optional): ``{"command": [...], "stdout": "...", "rc": int}``
|
|
83
|
+
captured from a successful invocation of the generated CLI.
|
|
84
|
+
"""
|
|
85
|
+
pkg_root = output_root / "src" / package
|
|
86
|
+
symbols = _collect_public_symbols(pkg_root) if pkg_root.exists() else {}
|
|
87
|
+
|
|
88
|
+
# Build a hero score badge from the certify report.
|
|
89
|
+
score_line = ""
|
|
90
|
+
if certify_report:
|
|
91
|
+
score = certify_report.get("score", 0)
|
|
92
|
+
comps = certify_report.get("score_components", {})
|
|
93
|
+
score_line = (
|
|
94
|
+
f"}%2F100-brightgreen)"
|
|
96
|
+
if score >= 80 else
|
|
97
|
+
f"}%2F100-yellow)"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
lines: list[str] = [
|
|
102
|
+
f"# `{package}`",
|
|
103
|
+
"",
|
|
104
|
+
score_line,
|
|
105
|
+
"",
|
|
106
|
+
"_Generated by [Atomadic Forge](https://atomadic.tech) — absorb · enforce · emerge._",
|
|
107
|
+
"",
|
|
108
|
+
"## Intent",
|
|
109
|
+
"",
|
|
110
|
+
f"> {intent.strip()}",
|
|
111
|
+
"",
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
if certify_report:
|
|
115
|
+
score = certify_report.get("score", 0)
|
|
116
|
+
comps = certify_report.get("score_components", {})
|
|
117
|
+
(certify_report.get("detail") or {}).get("import_smoke") or {}
|
|
118
|
+
test_run = (certify_report.get("detail") or {}).get("test_run") or {}
|
|
119
|
+
lines.extend([
|
|
120
|
+
"## Forge certification",
|
|
121
|
+
"",
|
|
122
|
+
"| Component | Result |",
|
|
123
|
+
"|-----------|--------|",
|
|
124
|
+
f"| Documentation present | {'✅' if certify_report.get('documentation_complete') else '❌'} |",
|
|
125
|
+
f"| Tests present | {'✅' if certify_report.get('tests_present') else '❌'} |",
|
|
126
|
+
f"| 5-tier layout | {'✅' if certify_report.get('tier_layout_present') else '❌'} |",
|
|
127
|
+
f"| Wire (no upward imports) | {'✅' if certify_report.get('no_upward_imports') else '❌'} |",
|
|
128
|
+
f"| Package importable | {'✅' if certify_report.get('package_importable') else '❌'} |",
|
|
129
|
+
f"| No stub bodies | {'✅' if certify_report.get('no_stub_bodies') else '❌'} |",
|
|
130
|
+
f"| Tests pass-ratio | {certify_report.get('test_pass_ratio', 0):.0%} ({test_run.get('passed', 0)}/{test_run.get('total', 0)}) |",
|
|
131
|
+
"",
|
|
132
|
+
f"**Score: {score:.0f}/100** "
|
|
133
|
+
+ (f"(structural {comps.get('structural', 0)} · "
|
|
134
|
+
f"runtime {comps.get('runtime', 0)} · "
|
|
135
|
+
f"behavioral {comps.get('behavioral', 0)})"
|
|
136
|
+
if comps else ""),
|
|
137
|
+
"",
|
|
138
|
+
])
|
|
139
|
+
|
|
140
|
+
# Surface the generated symbols, organised by tier.
|
|
141
|
+
if symbols:
|
|
142
|
+
lines.append("## What was built")
|
|
143
|
+
lines.append("")
|
|
144
|
+
for tier in ("a0_qk_constants", "a1_at_functions", "a2_mo_composites",
|
|
145
|
+
"a3_og_features", "a4_sy_orchestration"):
|
|
146
|
+
if tier not in symbols:
|
|
147
|
+
continue
|
|
148
|
+
lines.append(f"### {_TIER_LABEL[tier]}")
|
|
149
|
+
lines.append("")
|
|
150
|
+
for s in symbols[tier]:
|
|
151
|
+
doc = s["doc"] or ""
|
|
152
|
+
trailer = f" — _{doc}_" if doc else ""
|
|
153
|
+
lines.append(f"- **`{s['signature']}`**{trailer} "
|
|
154
|
+
f"<sub>(`{tier}/{s['module']}.py`)</sub>")
|
|
155
|
+
lines.append("")
|
|
156
|
+
|
|
157
|
+
if cli_demo:
|
|
158
|
+
lines.extend([
|
|
159
|
+
"## Live invocation",
|
|
160
|
+
"",
|
|
161
|
+
"```bash",
|
|
162
|
+
"$ " + " ".join(cli_demo.get("command", []))[:200],
|
|
163
|
+
(cli_demo.get("stdout") or "").strip()[:500] or "(no stdout)",
|
|
164
|
+
"```",
|
|
165
|
+
"",
|
|
166
|
+
f"Exit code: `{cli_demo.get('rc', '?')}`",
|
|
167
|
+
"",
|
|
168
|
+
])
|
|
169
|
+
|
|
170
|
+
lines.extend([
|
|
171
|
+
"## Architecture",
|
|
172
|
+
"",
|
|
173
|
+
"This package follows the Atomadic 5-tier monadic standard. Every",
|
|
174
|
+
"file lives in exactly one tier and tiers compose **upward only**.",
|
|
175
|
+
"Every public symbol is reachable via the tier package's "
|
|
176
|
+
"auto-generated `__init__.py` re-exports.",
|
|
177
|
+
"",
|
|
178
|
+
"```",
|
|
179
|
+
f"src/{package}/",
|
|
180
|
+
"├── a0_qk_constants/ constants, enums, TypedDicts",
|
|
181
|
+
"├── a1_at_functions/ pure stateless functions",
|
|
182
|
+
"├── a2_mo_composites/ stateful classes",
|
|
183
|
+
"├── a3_og_features/ feature orchestrators",
|
|
184
|
+
"└── a4_sy_orchestration/ CLI / entry points",
|
|
185
|
+
"```",
|
|
186
|
+
"",
|
|
187
|
+
"## Install",
|
|
188
|
+
"",
|
|
189
|
+
"```bash",
|
|
190
|
+
"pip install -e .",
|
|
191
|
+
"```",
|
|
192
|
+
"",
|
|
193
|
+
"## Verify with Forge",
|
|
194
|
+
"",
|
|
195
|
+
"```bash",
|
|
196
|
+
f"forge wire src/{package}",
|
|
197
|
+
f"forge certify . --package {package}",
|
|
198
|
+
"```",
|
|
199
|
+
"",
|
|
200
|
+
"---",
|
|
201
|
+
"",
|
|
202
|
+
f"_Generated by Atomadic Forge with `{llm_name}` as the generation engine._",
|
|
203
|
+
"",
|
|
204
|
+
])
|
|
205
|
+
return "\n".join(line for line in lines if line is not None)
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Tier a1 — pure composition discovery.
|
|
2
|
+
|
|
3
|
+
Given a list of :class:`SymbolSignatureCard`, find chains where the output of
|
|
4
|
+
one symbol is type-compatible with an input of another.
|
|
5
|
+
|
|
6
|
+
Type compatibility is intentionally loose — we work on annotation text:
|
|
7
|
+
|
|
8
|
+
* exact identical text → match
|
|
9
|
+
* ``T`` ↔ ``Optional[T]`` / ``T | None`` → match
|
|
10
|
+
* ``Iterable[T]`` ↔ ``list[T]`` / ``Sequence[T]`` / ``Collection[T]`` → match (T equal)
|
|
11
|
+
* ``Any`` ↔ anything → match
|
|
12
|
+
* generic vs concrete (``list`` ↔ ``list[str]``) → match (looser side wins)
|
|
13
|
+
|
|
14
|
+
Chain enumeration is bounded to depth ``max_depth`` (default 3) so the search
|
|
15
|
+
space stays tractable even on a 600-symbol catalog.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import re
|
|
21
|
+
from collections.abc import Iterable
|
|
22
|
+
|
|
23
|
+
from ..a0_qk_constants.emergent_types import (
|
|
24
|
+
CompositionChain,
|
|
25
|
+
SymbolSignatureCard,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
_OPTIONAL_RE = re.compile(r"^Optional\[(.+)\]$|^(.+)\s*\|\s*None$|^None\s*\|\s*(.+)$")
|
|
29
|
+
_GENERIC_RE = re.compile(r"^(\w+)\[(.+)\]$")
|
|
30
|
+
_COLLECTION_FAMILY = {"list", "List", "Sequence", "Iterable", "Collection",
|
|
31
|
+
"tuple", "Tuple", "set", "Set", "frozenset"}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _strip_optional(t: str) -> str:
|
|
35
|
+
t = t.strip()
|
|
36
|
+
m = _OPTIONAL_RE.match(t)
|
|
37
|
+
if m:
|
|
38
|
+
return next(g for g in m.groups() if g is not None).strip()
|
|
39
|
+
return t
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _outer(t: str) -> tuple[str, str | None]:
|
|
43
|
+
"""Return (outer name, inner) for ``Outer[Inner]``, else (t, None)."""
|
|
44
|
+
m = _GENERIC_RE.match(t.strip())
|
|
45
|
+
if not m:
|
|
46
|
+
return t.strip(), None
|
|
47
|
+
return m.group(1), m.group(2).strip()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _normalize(t: str) -> str:
|
|
51
|
+
return _strip_optional(t).replace(" ", "")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def types_compatible(produced: str, consumed: str,
|
|
55
|
+
*, strict: bool = False) -> bool:
|
|
56
|
+
"""Return True if the producer's output text can flow into the consumer.
|
|
57
|
+
|
|
58
|
+
``strict`` (default False keeps behaviour permissive for backwards-compat):
|
|
59
|
+
when True, treat ``Any`` as a non-match unless the partner is also ``Any``,
|
|
60
|
+
and refuse generic-collection variance unless inner types align. Strict
|
|
61
|
+
mode is what :func:`find_chains` uses to filter out the noisy ``dict[str,
|
|
62
|
+
Any] → dict[str, Any]`` chains that dominate the catalog.
|
|
63
|
+
"""
|
|
64
|
+
p = _normalize(produced)
|
|
65
|
+
c = _normalize(consumed)
|
|
66
|
+
if not p or not c:
|
|
67
|
+
return False
|
|
68
|
+
if p == c:
|
|
69
|
+
return True
|
|
70
|
+
if "Any" in (p, c):
|
|
71
|
+
if strict:
|
|
72
|
+
return False
|
|
73
|
+
return True
|
|
74
|
+
p_outer, p_inner = _outer(p)
|
|
75
|
+
c_outer, c_inner = _outer(c)
|
|
76
|
+
if p_outer in _COLLECTION_FAMILY and c_outer in _COLLECTION_FAMILY:
|
|
77
|
+
if p_inner is None and c_inner is None:
|
|
78
|
+
return True
|
|
79
|
+
if p_inner is None or c_inner is None:
|
|
80
|
+
return not strict
|
|
81
|
+
return types_compatible(p_inner, c_inner, strict=strict)
|
|
82
|
+
if p_outer == c_outer and (p_inner is None or c_inner is None):
|
|
83
|
+
return not strict
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def is_anyish(t: str) -> bool:
|
|
88
|
+
"""Is this type spec a noisy 'Any-shaped' carrier?"""
|
|
89
|
+
n = _normalize(t)
|
|
90
|
+
if n == "Any":
|
|
91
|
+
return True
|
|
92
|
+
outer, inner = _outer(n)
|
|
93
|
+
if inner and "Any" in inner:
|
|
94
|
+
return True
|
|
95
|
+
if outer in {"dict", "Dict", "Mapping"} and (inner is None or "Any" in (inner or "")):
|
|
96
|
+
return True
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _consumer_inputs(card: SymbolSignatureCard) -> list[str]:
|
|
101
|
+
return [t for _, t in card["inputs"]]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def find_chains(
|
|
105
|
+
cards: Iterable[SymbolSignatureCard],
|
|
106
|
+
*,
|
|
107
|
+
max_depth: int = 3,
|
|
108
|
+
max_chains: int = 5_000,
|
|
109
|
+
require_pure: bool = False,
|
|
110
|
+
domain_jump_required: bool = False,
|
|
111
|
+
strict_types: bool = True,
|
|
112
|
+
drop_anyish_seeds: bool = True,
|
|
113
|
+
) -> list[CompositionChain]:
|
|
114
|
+
"""Enumerate type-compatible chains across the catalog.
|
|
115
|
+
|
|
116
|
+
A chain is a sequence ``[c1, c2, …]`` where ``c_{i+1}`` has at least one
|
|
117
|
+
input compatible with ``c_i``'s output, and no card appears twice.
|
|
118
|
+
|
|
119
|
+
Parameters bound the search:
|
|
120
|
+
|
|
121
|
+
* ``max_depth`` — chain length cap.
|
|
122
|
+
* ``max_chains`` — early-stop hard cap on returned chains.
|
|
123
|
+
* ``require_pure`` — only purely-inferred symbols can participate.
|
|
124
|
+
* ``domain_jump_required`` — at least two distinct domains in the chain.
|
|
125
|
+
* ``strict_types`` — refuse ``Any``-as-bridge matches. Cuts the noise
|
|
126
|
+
from ``dict[str, Any] → dict[str, Any]`` chains that dominate the
|
|
127
|
+
catalog when most signatures are loosely typed.
|
|
128
|
+
* ``drop_anyish_seeds`` — skip seeding chains from symbols whose output
|
|
129
|
+
is a generic ``dict``/``Mapping``/``Any`` carrier. Such symbols are
|
|
130
|
+
bottlenecks (they connect to half the catalog) and produce many
|
|
131
|
+
look-alike candidates.
|
|
132
|
+
"""
|
|
133
|
+
catalog = [c for c in cards if not (require_pure and not c["is_pure"])]
|
|
134
|
+
by_qual = {c["qualname"]: c for c in catalog}
|
|
135
|
+
out: list[CompositionChain] = []
|
|
136
|
+
|
|
137
|
+
def consumers_of(produced: str) -> list[SymbolSignatureCard]:
|
|
138
|
+
return [
|
|
139
|
+
c for c in catalog
|
|
140
|
+
if any(types_compatible(produced, ti, strict=strict_types)
|
|
141
|
+
for ti in _consumer_inputs(c))
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
def extend(prefix: list[SymbolSignatureCard]) -> None:
|
|
145
|
+
if len(out) >= max_chains:
|
|
146
|
+
return
|
|
147
|
+
if len(prefix) >= max_depth:
|
|
148
|
+
_record(prefix)
|
|
149
|
+
return
|
|
150
|
+
last = prefix[-1]
|
|
151
|
+
nexts = consumers_of(last["output"])
|
|
152
|
+
used = {c["qualname"] for c in prefix}
|
|
153
|
+
progressed = False
|
|
154
|
+
for nxt in nexts:
|
|
155
|
+
if nxt["qualname"] in used:
|
|
156
|
+
continue
|
|
157
|
+
progressed = True
|
|
158
|
+
extend(prefix + [nxt])
|
|
159
|
+
if len(out) >= max_chains:
|
|
160
|
+
return
|
|
161
|
+
if not progressed:
|
|
162
|
+
_record(prefix)
|
|
163
|
+
|
|
164
|
+
def _record(prefix: list[SymbolSignatureCard]) -> None:
|
|
165
|
+
if len(prefix) < 2:
|
|
166
|
+
return
|
|
167
|
+
domains = [p["domain"] for p in prefix]
|
|
168
|
+
tiers = [p["tier"] for p in prefix]
|
|
169
|
+
if domain_jump_required and len(set(domains)) < 2:
|
|
170
|
+
return
|
|
171
|
+
bridges: list[str] = []
|
|
172
|
+
for i in range(len(prefix) - 1):
|
|
173
|
+
bridges.append(prefix[i]["output"])
|
|
174
|
+
chain = CompositionChain(
|
|
175
|
+
chain=[p["qualname"] for p in prefix],
|
|
176
|
+
bridges=bridges,
|
|
177
|
+
tiers=tiers,
|
|
178
|
+
domains=domains,
|
|
179
|
+
crosses_domains=len(set(domains)),
|
|
180
|
+
crosses_tiers=len(set(tiers)),
|
|
181
|
+
final_output_type=prefix[-1]["output"],
|
|
182
|
+
pure=all(by_qual[q]["is_pure"] for q in (p["qualname"] for p in prefix)),
|
|
183
|
+
)
|
|
184
|
+
out.append(chain)
|
|
185
|
+
|
|
186
|
+
seeds = [c for c in catalog
|
|
187
|
+
if not (drop_anyish_seeds and is_anyish(c["output"]))]
|
|
188
|
+
for seed in seeds:
|
|
189
|
+
extend([seed])
|
|
190
|
+
if len(out) >= max_chains:
|
|
191
|
+
break
|
|
192
|
+
return out
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Tier a1 — pure ranker for the Emergent Scan.
|
|
2
|
+
|
|
3
|
+
Score each :class:`CompositionChain` on how 'emergent' it is, then return
|
|
4
|
+
the top-N as :class:`EmergentCandidateCard`.
|
|
5
|
+
|
|
6
|
+
Score components (max 100):
|
|
7
|
+
|
|
8
|
+
* cross-domain bonus ``+ 10 * (crosses_domains - 1)`` (0..30)
|
|
9
|
+
* cross-tier bonus ``+ 8 * (crosses_tiers - 1)`` (0..32)
|
|
10
|
+
* purity bonus ``+ 10`` if all steps pure
|
|
11
|
+
* depth bonus ``+ 6 * (len(chain) - 1)`` (0..18)
|
|
12
|
+
* novelty bonus ``+ 10`` if final output type isn't already
|
|
13
|
+
the output of any single existing symbol
|
|
14
|
+
|
|
15
|
+
Output names are heuristic kebab-case combinations of distinct domains.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import hashlib
|
|
21
|
+
from collections.abc import Iterable
|
|
22
|
+
|
|
23
|
+
from ..a0_qk_constants.emergent_types import (
|
|
24
|
+
CompositionChain,
|
|
25
|
+
EmergentCandidateCard,
|
|
26
|
+
SymbolSignatureCard,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _candidate_id(chain: CompositionChain) -> str:
|
|
31
|
+
h = hashlib.sha256("→".join(chain["chain"]).encode("utf-8")).hexdigest()
|
|
32
|
+
return f"emrg-{h[:8]}"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _suggest_name(chain: CompositionChain) -> str:
|
|
36
|
+
seen: list[str] = []
|
|
37
|
+
for d in chain["domains"]:
|
|
38
|
+
if d not in seen:
|
|
39
|
+
seen.append(d)
|
|
40
|
+
return "-".join(seen[:4]) + "-pipeline"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _suggested_tier(chain: CompositionChain) -> str:
|
|
44
|
+
"""A composition that touches multiple a2 composites is itself a3."""
|
|
45
|
+
tiers = set(chain["tiers"])
|
|
46
|
+
if "a4_sy_orchestration" in tiers:
|
|
47
|
+
return "a4_sy_orchestration"
|
|
48
|
+
if "a3_og_features" in tiers or len(tiers) >= 3:
|
|
49
|
+
return "a3_og_features"
|
|
50
|
+
if "a2_mo_composites" in tiers:
|
|
51
|
+
return "a2_mo_composites"
|
|
52
|
+
return "a1_at_functions"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _summary(chain: CompositionChain) -> str:
|
|
56
|
+
domains = " → ".join(chain["domains"])
|
|
57
|
+
return (f"Compose {len(chain['chain'])} steps across {chain['crosses_domains']} "
|
|
58
|
+
f"domain(s): {domains}; final output {chain['final_output_type']}")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def rank_chains(
|
|
62
|
+
chains: Iterable[CompositionChain],
|
|
63
|
+
*,
|
|
64
|
+
catalog: list[SymbolSignatureCard],
|
|
65
|
+
top_n: int = 25,
|
|
66
|
+
novelty_unknown_outputs: bool = True,
|
|
67
|
+
) -> list[EmergentCandidateCard]:
|
|
68
|
+
from .emergent_compose import is_anyish
|
|
69
|
+
|
|
70
|
+
chains = list(chains)
|
|
71
|
+
existing_outputs = {c["output"] for c in catalog}
|
|
72
|
+
|
|
73
|
+
out: list[EmergentCandidateCard] = []
|
|
74
|
+
for chain in chains:
|
|
75
|
+
breakdown: dict[str, float] = {}
|
|
76
|
+
breakdown["cross_domain"] = min(30, 10 * max(0, chain["crosses_domains"] - 1))
|
|
77
|
+
breakdown["cross_tier"] = min(32, 8 * max(0, chain["crosses_tiers"] - 1))
|
|
78
|
+
breakdown["pure"] = 10 if chain["pure"] else 0
|
|
79
|
+
breakdown["depth"] = min(18, 6 * max(0, len(chain["chain"]) - 1))
|
|
80
|
+
novelty_signals: list[str] = []
|
|
81
|
+
novel = (chain["final_output_type"] not in existing_outputs)
|
|
82
|
+
breakdown["novelty"] = 10 if (novelty_unknown_outputs and novel) else 0
|
|
83
|
+
# Penalty for chains whose every bridge is Any-shaped — they're real
|
|
84
|
+
# but uninformative.
|
|
85
|
+
anyish_bridges = sum(1 for b in chain["bridges"] if is_anyish(b))
|
|
86
|
+
if anyish_bridges:
|
|
87
|
+
breakdown["any_penalty"] = -min(20, 6 * anyish_bridges)
|
|
88
|
+
if novel:
|
|
89
|
+
novelty_signals.append("final output type not produced by any single existing symbol")
|
|
90
|
+
if chain["crosses_domains"] >= 3:
|
|
91
|
+
novelty_signals.append("touches three or more domains")
|
|
92
|
+
if chain["crosses_tiers"] >= 3:
|
|
93
|
+
novelty_signals.append("spans three or more tiers")
|
|
94
|
+
if chain["pure"]:
|
|
95
|
+
novelty_signals.append("entirely pure — safe to materialise as a1/a3 with no I/O risk")
|
|
96
|
+
score = sum(breakdown.values())
|
|
97
|
+
if score <= 0:
|
|
98
|
+
continue
|
|
99
|
+
out.append(EmergentCandidateCard(
|
|
100
|
+
candidate_id=_candidate_id(chain),
|
|
101
|
+
name=_suggest_name(chain),
|
|
102
|
+
summary=_summary(chain),
|
|
103
|
+
chain=chain,
|
|
104
|
+
score=float(score),
|
|
105
|
+
score_breakdown=breakdown,
|
|
106
|
+
suggested_tier=_suggested_tier(chain),
|
|
107
|
+
novelty_signals=novelty_signals,
|
|
108
|
+
))
|
|
109
|
+
out.sort(key=lambda c: c["score"], reverse=True)
|
|
110
|
+
# Dedupe candidate names, keep top by score per name
|
|
111
|
+
seen: dict[str, EmergentCandidateCard] = {}
|
|
112
|
+
for c in out:
|
|
113
|
+
if c["name"] not in seen or seen[c["name"]]["score"] < c["score"]:
|
|
114
|
+
seen[c["name"]] = c
|
|
115
|
+
deduped = sorted(seen.values(), key=lambda c: c["score"], reverse=True)
|
|
116
|
+
return deduped[:top_n]
|