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,140 @@
|
|
|
1
|
+
"""``forge iterate`` — LLM ↔ Forge loop (the headline play).
|
|
2
|
+
|
|
3
|
+
Plug Forge's tier-law substrate in front of any LLM (Anthropic / OpenAI /
|
|
4
|
+
Ollama / a stub for offline runs) and produce architecturally-coherent
|
|
5
|
+
code from intent. Forge enforces the 5-tier law every turn; the LLM only
|
|
6
|
+
ships when wire passes and certify clears the threshold.
|
|
7
|
+
|
|
8
|
+
Examples
|
|
9
|
+
--------
|
|
10
|
+
# With Anthropic (set ANTHROPIC_API_KEY):
|
|
11
|
+
forge iterate "discord bot that summarises uploaded PDFs" ./out
|
|
12
|
+
|
|
13
|
+
# Pre-flight (no LLM call) — show the system + first prompt:
|
|
14
|
+
forge iterate "..." ./out --no-apply
|
|
15
|
+
|
|
16
|
+
# Local Ollama:
|
|
17
|
+
FORGE_OLLAMA=1 FORGE_OLLAMA_MODEL=qwen2.5-coder:7b \\
|
|
18
|
+
forge iterate "..." ./out
|
|
19
|
+
|
|
20
|
+
# Stub mode (deterministic, for tests):
|
|
21
|
+
forge iterate "..." ./out --provider stub
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import json
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Annotated
|
|
29
|
+
|
|
30
|
+
import click
|
|
31
|
+
import typer
|
|
32
|
+
|
|
33
|
+
from atomadic_forge.a1_at_functions.provider_resolver import (
|
|
34
|
+
PROVIDER_HELP,
|
|
35
|
+
resolve_provider,
|
|
36
|
+
)
|
|
37
|
+
from atomadic_forge.a3_og_features.forge_loop import run_iterate
|
|
38
|
+
|
|
39
|
+
COMMAND_NAME = "iterate"
|
|
40
|
+
COMMAND_HELP = ("Architecturally-coherent code generation: LLM emits, "
|
|
41
|
+
"Forge enforces, loop iterates until certify clears.")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
app = typer.Typer(no_args_is_help=True, help=COMMAND_HELP)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _resolve_provider(name: str) -> object:
|
|
48
|
+
try:
|
|
49
|
+
return resolve_provider(name)
|
|
50
|
+
except ValueError as exc:
|
|
51
|
+
raise typer.BadParameter(str(exc)) from exc
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@app.command("run")
|
|
55
|
+
def run_cmd(
|
|
56
|
+
intent: Annotated[str, typer.Argument(help="One-paragraph description "
|
|
57
|
+
"of what to build.")],
|
|
58
|
+
output: Annotated[Path, typer.Argument(
|
|
59
|
+
file_okay=False, dir_okay=True, resolve_path=True)],
|
|
60
|
+
package: Annotated[str, typer.Option("--package")] = "generated",
|
|
61
|
+
seed_repo: Annotated[list[Path] | None, typer.Option("--seed",
|
|
62
|
+
exists=True, file_okay=False, dir_okay=True, resolve_path=True,
|
|
63
|
+
help="Sibling repo(s) whose catalog is offered to the LLM as building blocks. Repeatable.")] = None,
|
|
64
|
+
provider: Annotated[str, typer.Option("--provider",
|
|
65
|
+
help=PROVIDER_HELP)] = "auto",
|
|
66
|
+
max_iterations: Annotated[int, typer.Option("--max-iterations")] = 5,
|
|
67
|
+
max_fix_rounds: Annotated[int, typer.Option(
|
|
68
|
+
"--max-fix-rounds",
|
|
69
|
+
help="Per-turn budget for compiler-feedback fix rounds (Lane A W3). "
|
|
70
|
+
"When the just-emitted package fails import_smoke, the loop "
|
|
71
|
+
"sends the LLM the error trace and asks for a minimal patch, "
|
|
72
|
+
"up to N times before continuing to the next iterate turn. "
|
|
73
|
+
"Default 0 = disabled.")] = 0,
|
|
74
|
+
target_score: Annotated[float, typer.Option("--target-score")] = 75.0,
|
|
75
|
+
apply: Annotated[bool, typer.Option("--apply/--no-apply")] = True,
|
|
76
|
+
json_out: Annotated[bool, typer.Option("--json")] = False,
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Run the iterate loop."""
|
|
79
|
+
output.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
llm = _resolve_provider(provider)
|
|
81
|
+
try:
|
|
82
|
+
report = run_iterate(
|
|
83
|
+
intent,
|
|
84
|
+
output=output,
|
|
85
|
+
package=package,
|
|
86
|
+
seed_repo=seed_repo,
|
|
87
|
+
llm=llm, # type: ignore[arg-type]
|
|
88
|
+
max_iterations=max_iterations,
|
|
89
|
+
max_fix_rounds=max_fix_rounds,
|
|
90
|
+
target_score=target_score,
|
|
91
|
+
apply=apply,
|
|
92
|
+
)
|
|
93
|
+
except RuntimeError as exc:
|
|
94
|
+
raise click.ClickException(str(exc)) from exc
|
|
95
|
+
if json_out:
|
|
96
|
+
typer.echo(json.dumps(report, indent=2, default=str))
|
|
97
|
+
return
|
|
98
|
+
typer.echo(f"\nForge iterate ({'APPLY' if apply else 'PRE-FLIGHT'})")
|
|
99
|
+
typer.echo("-" * 60)
|
|
100
|
+
typer.echo(f" llm: {report.get('llm')}")
|
|
101
|
+
typer.echo(f" package: {report.get('package')}")
|
|
102
|
+
if not apply:
|
|
103
|
+
typer.echo(" output_root: (none — pre-flight)")
|
|
104
|
+
typer.echo(f" first_prompt: {len(report.get('first_prompt', ''))} chars")
|
|
105
|
+
typer.echo(f" system_prompt: {len(report.get('system_prompt', ''))} chars")
|
|
106
|
+
return
|
|
107
|
+
typer.echo(f" output: {report['output_root']}/src/{report['package']}")
|
|
108
|
+
typer.echo(f" iterations: {report['iterations']}")
|
|
109
|
+
typer.echo(f" files written: {report['files_written_total']}")
|
|
110
|
+
typer.echo(f" converged: {report['converged']}")
|
|
111
|
+
final_wire = report.get('final_wire') or {}
|
|
112
|
+
final_cert = report.get('final_certify') or {}
|
|
113
|
+
typer.echo(f" final wire: {final_wire.get('verdict', '?')} "
|
|
114
|
+
f"({final_wire.get('violation_count', 0)} violations)")
|
|
115
|
+
typer.echo(f" final score: {final_cert.get('score', 0)}/100")
|
|
116
|
+
if final_cert.get("issues"):
|
|
117
|
+
for issue in final_cert["issues"]:
|
|
118
|
+
typer.echo(f" - {issue}")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@app.command("preflight")
|
|
122
|
+
def preflight_cmd(
|
|
123
|
+
intent: Annotated[str, typer.Argument()],
|
|
124
|
+
package: Annotated[str, typer.Option("--package")] = "generated",
|
|
125
|
+
seed_repo: Annotated[Path | None, typer.Option("--seed",
|
|
126
|
+
exists=True, file_okay=False, dir_okay=True, resolve_path=True)] = None,
|
|
127
|
+
) -> None:
|
|
128
|
+
"""Print the system prompt + first user prompt without calling any LLM."""
|
|
129
|
+
from atomadic_forge.a1_at_functions.forge_feedback import (
|
|
130
|
+
pack_initial_intent,
|
|
131
|
+
system_prompt,
|
|
132
|
+
)
|
|
133
|
+
from atomadic_forge.a1_at_functions.scout_walk import harvest_repo
|
|
134
|
+
|
|
135
|
+
seeds = harvest_repo(seed_repo)["symbols"] if seed_repo else None
|
|
136
|
+
typer.echo("# === SYSTEM PROMPT ===\n")
|
|
137
|
+
typer.echo(system_prompt())
|
|
138
|
+
typer.echo("\n# === FIRST USER PROMPT ===\n")
|
|
139
|
+
typer.echo(pack_initial_intent(intent, package=package,
|
|
140
|
+
seed_catalog=seeds))
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""``atomadic-forge synergy`` — find producer/consumer pairs that aren't wired yet,
|
|
2
|
+
optionally implement an adapter that wires them.
|
|
3
|
+
|
|
4
|
+
Operates one level above ``emergent``: emergent looks at *symbol* compositions
|
|
5
|
+
inside the type graph; synergy looks at *feature/CLI verb* relationships
|
|
6
|
+
across the operator surface (file artifacts, schemas, phase order).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Annotated
|
|
14
|
+
|
|
15
|
+
import typer
|
|
16
|
+
|
|
17
|
+
from atomadic_forge.a3_og_features.synergy_feature import SynergyScan
|
|
18
|
+
|
|
19
|
+
COMMAND_NAME = "synergy"
|
|
20
|
+
COMMAND_HELP = ("Find feature/CLI synergies (producer-consumer pairs that "
|
|
21
|
+
"aren't wired together) and optionally implement adapters.")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
app = typer.Typer(no_args_is_help=True, help=COMMAND_HELP)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _resolve_src_root() -> Path:
|
|
28
|
+
here = Path(__file__).resolve()
|
|
29
|
+
return here.parent.parent.parent # commands/ -> atomadic_forge/ -> src/
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@app.command("scan")
|
|
33
|
+
def scan_cmd(
|
|
34
|
+
package: Annotated[str, typer.Option("--package")] = "atomadic_forge",
|
|
35
|
+
src_root: Annotated[Path | None, typer.Option("--src-root",
|
|
36
|
+
exists=True, file_okay=False, dir_okay=True, resolve_path=True)] = None,
|
|
37
|
+
top_n: Annotated[int, typer.Option("--top-n")] = 20,
|
|
38
|
+
json_out: Annotated[bool, typer.Option("--json")] = False,
|
|
39
|
+
save: Annotated[Path | None, typer.Option("--save",
|
|
40
|
+
file_okay=True, dir_okay=False, resolve_path=True)] = None,
|
|
41
|
+
) -> None:
|
|
42
|
+
"""Walk the CLI surface and report top-scoring synergy candidates."""
|
|
43
|
+
root = src_root or _resolve_src_root()
|
|
44
|
+
scanner = SynergyScan(src_root=root, package=package)
|
|
45
|
+
report = scanner.scan(top_n=top_n)
|
|
46
|
+
if save:
|
|
47
|
+
SynergyScan.save_report(report, save)
|
|
48
|
+
if json_out:
|
|
49
|
+
typer.echo(json.dumps(report, indent=2, default=str))
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
typer.echo(f"\nSynergy scan — {package}")
|
|
53
|
+
typer.echo("-" * 60)
|
|
54
|
+
typer.echo(f" features harvested: {report['feature_count']}")
|
|
55
|
+
typer.echo(f" candidates: {report['candidate_count']}\n")
|
|
56
|
+
for i, c in enumerate(report["candidates"], 1):
|
|
57
|
+
typer.echo(f" #{i:2d} {c['candidate_id']} score={c['score']:.0f}")
|
|
58
|
+
typer.echo(f" kind: {c['kind']}")
|
|
59
|
+
typer.echo(f" wire: {c['producer']} → {c['consumer']}")
|
|
60
|
+
typer.echo(f" adapter: {c['proposed_adapter_name']}")
|
|
61
|
+
typer.echo(f" why: {'; '.join(c['why'])}")
|
|
62
|
+
typer.echo("")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@app.command("implement")
|
|
66
|
+
def implement_cmd(
|
|
67
|
+
candidate_id: Annotated[str, typer.Argument()],
|
|
68
|
+
report_path: Annotated[Path, typer.Argument(
|
|
69
|
+
exists=True, file_okay=True, dir_okay=False, resolve_path=True)],
|
|
70
|
+
package: Annotated[str, typer.Option("--package")] = "atomadic_forge",
|
|
71
|
+
src_root: Annotated[Path | None, typer.Option("--src-root",
|
|
72
|
+
exists=True, file_okay=False, dir_okay=True, resolve_path=True)] = None,
|
|
73
|
+
) -> None:
|
|
74
|
+
"""Materialize one candidate as a new commands/<name>.py adapter."""
|
|
75
|
+
report = json.loads(report_path.read_text(encoding="utf-8"))
|
|
76
|
+
scanner = SynergyScan(src_root=src_root or _resolve_src_root(),
|
|
77
|
+
package=package)
|
|
78
|
+
target = scanner.implement(candidate_id, report)
|
|
79
|
+
typer.echo(f"Wrote {target}")
|
|
80
|
+
typer.echo("Run ``atomadic-forge commandsmith sync`` to register the new verb.")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@app.command("show")
|
|
84
|
+
def show_cmd(
|
|
85
|
+
candidate_id: Annotated[str, typer.Argument()],
|
|
86
|
+
report_path: Annotated[Path, typer.Argument(
|
|
87
|
+
exists=True, file_okay=True, dir_okay=False, resolve_path=True)],
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Print one candidate's full breakdown."""
|
|
90
|
+
report = json.loads(report_path.read_text(encoding="utf-8"))
|
|
91
|
+
match = next((c for c in report["candidates"]
|
|
92
|
+
if c["candidate_id"] == candidate_id), None)
|
|
93
|
+
if match is None:
|
|
94
|
+
typer.secho(f"candidate {candidate_id} not in report", fg="red", err=True)
|
|
95
|
+
raise typer.Exit(1)
|
|
96
|
+
typer.echo(json.dumps(match, indent=2))
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Auto-synthesized synergy adapter (syn-4a31a10c).
|
|
3
|
+
|
|
4
|
+
Producer: synergy
|
|
5
|
+
Consumer: emergent
|
|
6
|
+
Kind: json_artifact
|
|
7
|
+
Score: 42
|
|
8
|
+
|
|
9
|
+
Why this synergy was detected:
|
|
10
|
+
- synergy emits json-out
|
|
11
|
+
- emergent accepts json_out
|
|
12
|
+
|
|
13
|
+
Re-emit with ``atomadic-forge synergy implement <id>`` after surfaces change.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
import tempfile
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Annotated
|
|
24
|
+
|
|
25
|
+
import typer
|
|
26
|
+
|
|
27
|
+
COMMAND_NAME = 'synergy-then-emergent'
|
|
28
|
+
COMMAND_HELP = 'Run synergy to emit a JSON artifact, then feed it to emergent for the next phase.'
|
|
29
|
+
|
|
30
|
+
app = typer.Typer(no_args_is_help=False, help='Run synergy to emit a JSON artifact, then feed it to emergent for the next phase.')
|
|
31
|
+
|
|
32
|
+
@app.callback(invoke_without_command=True)
|
|
33
|
+
def run(
|
|
34
|
+
ctx: typer.Context,
|
|
35
|
+
producer_args: Annotated[list[str] | None, typer.Argument(
|
|
36
|
+
help='Args forwarded to the producer command.')] = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Run synergy → capture artifact → feed to emergent."""
|
|
39
|
+
if ctx.invoked_subcommand is not None:
|
|
40
|
+
return
|
|
41
|
+
producer_args = producer_args or []
|
|
42
|
+
with tempfile.TemporaryDirectory(prefix='synergy-') as tmp:
|
|
43
|
+
artifact = Path(tmp) / 'producer.json'
|
|
44
|
+
cmd_a = [sys.executable, '-m',
|
|
45
|
+
'atomadic_forge.a4_sy_orchestration.cli',
|
|
46
|
+
'synergy', *producer_args,
|
|
47
|
+
'--json-out', str(artifact)]
|
|
48
|
+
rc = subprocess.run(cmd_a, capture_output=False).returncode
|
|
49
|
+
if rc != 0:
|
|
50
|
+
typer.secho(f'producer exited {rc}', fg='red', err=True)
|
|
51
|
+
raise typer.Exit(rc)
|
|
52
|
+
cmd_b = [sys.executable, '-m',
|
|
53
|
+
'atomadic_forge.a4_sy_orchestration.cli',
|
|
54
|
+
'emergent', str(artifact)]
|
|
55
|
+
rc = subprocess.run(cmd_b, capture_output=False).returncode
|
|
56
|
+
if rc != 0:
|
|
57
|
+
typer.secho(f'consumer exited {rc}', fg='red', err=True)
|
|
58
|
+
raise typer.Exit(rc)
|
|
59
|
+
try:
|
|
60
|
+
data = json.loads(artifact.read_text(encoding='utf-8'))
|
|
61
|
+
except (OSError, json.JSONDecodeError):
|
|
62
|
+
data = None
|
|
63
|
+
typer.echo(json.dumps({
|
|
64
|
+
'synergy': 'syn-4a31a10c',
|
|
65
|
+
'producer': 'synergy',
|
|
66
|
+
'consumer': 'emergent',
|
|
67
|
+
'artifact_size_bytes': artifact.stat().st_size,
|
|
68
|
+
'producer_payload_keys': sorted(data.keys()) if isinstance(data, dict) else None,
|
|
69
|
+
}, indent=2))
|
|
70
|
+
|