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,142 @@
1
+ """``forge demo`` — one-shot launch-video verb.
2
+
3
+ Run a preset evolve trajectory + post-run CLI invocation + DEMO.md artifact.
4
+ Designed to produce a recordable 90-second showcase from a single command.
5
+
6
+ forge demo run # default preset (calc), auto provider
7
+ forge demo run --preset kv # KV-store preset
8
+ forge demo run --preset slug --provider gemini
9
+ forge demo list # show all presets
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ from pathlib import Path
16
+ from typing import Annotated
17
+
18
+ import click
19
+ import typer
20
+
21
+ from atomadic_forge.a1_at_functions.provider_resolver import (
22
+ PROVIDER_HELP,
23
+ resolve_provider,
24
+ )
25
+ from atomadic_forge.a3_og_features.demo_runner import (
26
+ DemoResult,
27
+ list_presets,
28
+ run_demo,
29
+ )
30
+
31
+ COMMAND_NAME = "demo"
32
+ COMMAND_HELP = "One-shot launch-video verb: preset evolve + DEMO.md artifact."
33
+
34
+
35
+ app = typer.Typer(no_args_is_help=True, help=COMMAND_HELP)
36
+
37
+
38
+ def _cli_demo_failed(result: DemoResult) -> bool:
39
+ return bool(result.cli_demo_command) and result.cli_demo_returncode != 0
40
+
41
+
42
+ def _resolve_provider(name: str) -> object:
43
+ try:
44
+ return resolve_provider(name)
45
+ except ValueError as exc:
46
+ raise typer.BadParameter(str(exc)) from exc
47
+
48
+
49
+ @app.command("list")
50
+ def list_cmd(
51
+ json_out: Annotated[bool, typer.Option("--json")] = False,
52
+ ) -> None:
53
+ """List available demo presets."""
54
+ presets = list_presets()
55
+ if json_out:
56
+ typer.echo(json.dumps([
57
+ {"name": p.name, "headline": p.headline, "package": p.package,
58
+ "rounds": p.rounds, "iterations": p.iterations,
59
+ "target_score": p.target_score}
60
+ for p in presets
61
+ ], indent=2))
62
+ return
63
+ typer.echo("\nForge demo presets:")
64
+ for p in presets:
65
+ typer.echo(f" {p.name:8s} — {p.headline}")
66
+
67
+
68
+ @app.command("run")
69
+ def run_cmd(
70
+ preset: Annotated[str, typer.Option("--preset", "-p",
71
+ help="Preset name (use `forge demo list` to see options).")] = "calc",
72
+ output: Annotated[Path | None, typer.Option("--output", "-o",
73
+ help="Output directory. Defaults to ./forge-demo-<preset>.",
74
+ file_okay=False, dir_okay=True, resolve_path=True)] = None,
75
+ provider: Annotated[str, typer.Option("--provider",
76
+ help=PROVIDER_HELP)] = "auto",
77
+ rounds: Annotated[int | None, typer.Option("--rounds",
78
+ help="Override the preset's round count.")] = None,
79
+ iterations: Annotated[int | None, typer.Option("--iterations",
80
+ help="Override the preset's iterations-per-round.")] = None,
81
+ skip_cli_demo: Annotated[bool, typer.Option("--skip-cli-demo")] = False,
82
+ json_out: Annotated[bool, typer.Option("--json")] = False,
83
+ ) -> None:
84
+ """Run the preset and produce a DEMO.md artifact."""
85
+ llm = _resolve_provider(provider)
86
+ try:
87
+ result = run_demo(
88
+ preset_name=preset,
89
+ output=output,
90
+ llm=llm, # type: ignore[arg-type]
91
+ rounds=rounds,
92
+ iterations=iterations,
93
+ skip_cli_demo=skip_cli_demo,
94
+ )
95
+ except RuntimeError as exc:
96
+ raise click.ClickException(str(exc)) from exc
97
+ if json_out:
98
+ from dataclasses import asdict
99
+ typer.echo(json.dumps(asdict(result), indent=2, default=str))
100
+ if _cli_demo_failed(result):
101
+ raise typer.Exit(code=1)
102
+ return
103
+
104
+ arc = " → ".join(f"{s:.0f}" for s in result.score_trajectory)
105
+ # Showcase presets bypass the LLM entirely — print "static showcase"
106
+ # in the LLM slot so output stays consistent across both kinds.
107
+ from atomadic_forge.a3_og_features.demo_runner import get_preset
108
+ try:
109
+ preset_obj = get_preset(preset)
110
+ except KeyError:
111
+ preset_obj = None
112
+ is_showcase = preset_obj is not None and preset_obj.kind == "showcase"
113
+ llm_label = "static showcase (no LLM)" if is_showcase else llm.name
114
+
115
+ typer.echo("")
116
+ typer.echo("=" * 60)
117
+ typer.echo(f" forge demo: {preset}")
118
+ typer.echo("=" * 60)
119
+ typer.echo("")
120
+ typer.echo(f" {result.headline}")
121
+ typer.echo("")
122
+ typer.echo(f" llm: {llm_label}")
123
+ typer.echo(f" package: {result.package}")
124
+ typer.echo(f" rounds: {result.rounds_completed}")
125
+ typer.echo(f" trajectory: {arc}")
126
+ typer.echo(f" final score: {result.final_score:.0f}/100")
127
+ typer.echo(f" converged: {result.converged}")
128
+ typer.echo(f" duration: {result.duration_s:.1f}s")
129
+ typer.echo("")
130
+ if result.cli_demo_command:
131
+ typer.echo(" Generated CLI:")
132
+ typer.echo(f" $ {' '.join(result.cli_demo_command)}")
133
+ for line in (result.cli_demo_stdout or "(no output)").splitlines()[:6]:
134
+ typer.echo(f" {line}")
135
+ if _cli_demo_failed(result):
136
+ typer.echo(" generated CLI demo failed")
137
+ typer.echo("")
138
+ typer.echo(f" Artifact: {result.artifact_md_path}")
139
+ typer.echo(f" Output: {result.output_root}")
140
+ typer.echo("")
141
+ if _cli_demo_failed(result):
142
+ raise typer.Exit(code=1)
@@ -0,0 +1,124 @@
1
+ """``atomadic-forge emergent`` — discover and synthesise emergent feature candidates.
2
+
3
+ Once you've assimilated a sibling repo (or just grown the catalog), every
4
+ function and class in the tier folders is a *building block*. This command
5
+ walks the catalog, finds typed compositions nobody wrote yet, scores them on
6
+ cross-domain / cross-tier / purity / novelty, and (optionally) materialises
7
+ the top candidate as a new ``a3_og_features/<name>_emergent.py`` for review.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ from pathlib import Path
14
+ from typing import Annotated
15
+
16
+ import typer
17
+
18
+ from atomadic_forge.a3_og_features.emergent_feature import EmergentScan
19
+
20
+ COMMAND_NAME = "emergent"
21
+ COMMAND_HELP = "Synthesise new feature candidates from existing components."
22
+
23
+
24
+ app = typer.Typer(no_args_is_help=True, help=COMMAND_HELP)
25
+
26
+
27
+ def _resolve_src_root() -> Path:
28
+ """Locate ``<repo>/src`` from this file's location."""
29
+ here = Path(__file__).resolve()
30
+ return here.parent.parent.parent # commands/ -> atomadic_forge/ -> src/
31
+
32
+
33
+ @app.command("scan")
34
+ def scan_cmd(
35
+ package: Annotated[str, typer.Option("--package",
36
+ help="Importable package under src/ to scan.")] = "atomadic_forge",
37
+ src_root: Annotated[Path, typer.Option("--src-root",
38
+ help="Path to the src directory containing the package.",
39
+ exists=True, file_okay=False, dir_okay=True, resolve_path=True)] = None, # type: ignore[assignment]
40
+ max_depth: Annotated[int, typer.Option("--max-depth",
41
+ help="Max chain length.")] = 3,
42
+ top_n: Annotated[int, typer.Option("--top-n",
43
+ help="Return at most this many candidates.")] = 15,
44
+ require_pure: Annotated[bool, typer.Option("--require-pure",
45
+ help="Restrict to chains where every step is heuristically pure.")] = False,
46
+ no_domain_jump: Annotated[bool, typer.Option("--no-domain-jump",
47
+ help="Allow same-domain chains (default: require ≥2 distinct domains).")] = False,
48
+ json_out: Annotated[bool, typer.Option("--json",
49
+ help="Emit machine-readable JSON.")] = False,
50
+ save: Annotated[Path | None, typer.Option("--save",
51
+ help="Persist the report at this path.",
52
+ file_okay=True, dir_okay=False, resolve_path=True)] = None,
53
+ ) -> None:
54
+ """Walk the catalog and report top-scoring composition candidates."""
55
+ root = src_root or _resolve_src_root()
56
+ scanner = EmergentScan(src_root=root, package=package)
57
+ report = scanner.scan(
58
+ max_depth=max_depth,
59
+ top_n=top_n,
60
+ require_pure=require_pure,
61
+ domain_jump_required=not no_domain_jump,
62
+ )
63
+ if save:
64
+ EmergentScan.save_report(report, save)
65
+
66
+ if json_out:
67
+ typer.echo(json.dumps(report, indent=2, default=str))
68
+ return
69
+
70
+ typer.echo(f"\nEmergent scan — {package}")
71
+ typer.echo("-" * 60)
72
+ typer.echo(f" catalog size: {report['catalog_size']}")
73
+ typer.echo(f" chains considered: {report['chain_count_considered']}")
74
+ typer.echo(f" domains: {len(report['domain_inventory'])}")
75
+ typer.echo(f" candidates: {len(report['candidates'])}\n")
76
+
77
+ for i, c in enumerate(report["candidates"], 1):
78
+ typer.echo(f" #{i:2d} {c['candidate_id']} score={c['score']:.0f}")
79
+ typer.echo(f" name: {c['name']}")
80
+ typer.echo(f" tier: {c['suggested_tier']}")
81
+ typer.echo(f" chain: {' → '.join(c['chain']['domains'])}")
82
+ if c["novelty_signals"]:
83
+ typer.echo(f" why: {'; '.join(c['novelty_signals'])}")
84
+ typer.echo("")
85
+
86
+
87
+ @app.command("synthesize")
88
+ def synthesize_cmd(
89
+ candidate_id: Annotated[str, typer.Argument(
90
+ help="Candidate id from a prior `scan` (e.g. emrg-abc12345).")],
91
+ report_path: Annotated[Path, typer.Argument(
92
+ help="Saved report JSON from `scan --save …`.",
93
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True)],
94
+ package: Annotated[str, typer.Option("--package")] = "atomadic_forge",
95
+ src_root: Annotated[Path | None, typer.Option("--src-root",
96
+ exists=True, file_okay=False, dir_okay=True, resolve_path=True)] = None,
97
+ out_dir: Annotated[Path | None, typer.Option("--out-dir",
98
+ help="Where to write the new feature file (defaults to a3_og_features/).",
99
+ file_okay=False, dir_okay=True, resolve_path=True)] = None,
100
+ ) -> None:
101
+ """Materialize a candidate as a new a3 feature module."""
102
+ report = json.loads(report_path.read_text(encoding="utf-8"))
103
+ scanner = EmergentScan(src_root=src_root or _resolve_src_root(),
104
+ package=package)
105
+ target = scanner.synthesize(candidate_id, report, out_dir=out_dir)
106
+ typer.echo(f"Wrote {target}")
107
+ typer.echo("Review carefully before committing — this is scaffolding,")
108
+ typer.echo("not a finished feature. Methods need their receiver wired.")
109
+
110
+
111
+ @app.command("show")
112
+ def show_cmd(
113
+ candidate_id: Annotated[str, typer.Argument()],
114
+ report_path: Annotated[Path, typer.Argument(
115
+ exists=True, file_okay=True, dir_okay=False, resolve_path=True)],
116
+ ) -> None:
117
+ """Print one candidate's full chain + score breakdown."""
118
+ report = json.loads(report_path.read_text(encoding="utf-8"))
119
+ match = next((c for c in report["candidates"] if c["candidate_id"] == candidate_id),
120
+ None)
121
+ if match is None:
122
+ typer.secho(f"candidate {candidate_id} not in report", fg="red", err=True)
123
+ raise typer.Exit(1)
124
+ typer.echo(json.dumps(match, indent=2))
@@ -0,0 +1,70 @@
1
+ """
2
+ Auto-synthesized synergy adapter (syn-e3622070).
3
+
4
+ Producer: emergent
5
+ Consumer: synergy
6
+ Kind: json_artifact
7
+ Score: 42
8
+
9
+ Why this synergy was detected:
10
+ - emergent emits json-out
11
+ - synergy 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 = 'emergent-then-synergy'
28
+ COMMAND_HELP = 'Run emergent to emit a JSON artifact, then feed it to synergy for the next phase.'
29
+
30
+ app = typer.Typer(no_args_is_help=False, help='Run emergent to emit a JSON artifact, then feed it to synergy 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 emergent → capture artifact → feed to synergy."""
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
+ 'emergent', *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
+ 'synergy', 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-e3622070',
65
+ 'producer': 'emergent',
66
+ 'consumer': 'synergy',
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
+
@@ -0,0 +1,122 @@
1
+ """``forge evolve`` — recursive self-improvement: iterate that iterates.
2
+
3
+ Each round, the package generated last time becomes the seed catalog for
4
+ the next round. The LLM sees its own prior output as building blocks via
5
+ emergent + reuse feedback. The catalog grows; compositions multiply; the
6
+ LLM (in principle) gets richer context every round.
7
+
8
+ This is **narrow self-improvement** — same shape as AlphaEvolve / Voyager.
9
+ NOT a path to AGI. Bounded by underlying LLM quality and the realism of
10
+ the certify/wire signals. Honest about what it is.
11
+
12
+ Examples
13
+ --------
14
+ forge evolve "discord bot that summarises PDFs" ./out --auto 5
15
+
16
+ # Sit-back mode: 8 rounds, 4 LLM turns per round, max score 90:
17
+ forge evolve "..." ./out --auto 8 --target-score 90
18
+
19
+ # Stop early if score regresses:
20
+ forge evolve "..." ./out --auto 5 --stop-on-regression
21
+
22
+ # Resume from an existing growing seed:
23
+ forge evolve "..." ./out --auto 3 --seed ./out/src/evolved
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import json
29
+ from pathlib import Path
30
+ from typing import Annotated
31
+
32
+ import click
33
+ import typer
34
+
35
+ from atomadic_forge.a1_at_functions.provider_resolver import (
36
+ PROVIDER_HELP,
37
+ resolve_provider,
38
+ )
39
+ from atomadic_forge.a3_og_features.forge_evolve import run_evolve
40
+
41
+ COMMAND_NAME = "evolve"
42
+ COMMAND_HELP = ("Recursive self-improvement: run iterate N times, each "
43
+ "round seeded by the previous round's growing catalog.")
44
+
45
+
46
+ app = typer.Typer(no_args_is_help=True, help=COMMAND_HELP)
47
+
48
+
49
+ def _resolve_provider(name: str) -> object:
50
+ try:
51
+ return resolve_provider(name)
52
+ except ValueError as exc:
53
+ raise typer.BadParameter(str(exc)) from exc
54
+
55
+
56
+ @app.command("run")
57
+ def run_cmd(
58
+ intent: Annotated[str, typer.Argument(help="One-paragraph description.")],
59
+ output: Annotated[Path, typer.Argument(
60
+ file_okay=False, dir_okay=True, resolve_path=True)],
61
+ auto: Annotated[int, typer.Option("--auto",
62
+ help="Number of evolve rounds — sit-back parameter.")] = 3,
63
+ iterations_per_round: Annotated[int, typer.Option("--iterations")] = 4,
64
+ target_score: Annotated[float, typer.Option("--target-score")] = 75.0,
65
+ package: Annotated[str, typer.Option("--package")] = "evolved",
66
+ seed_repo: Annotated[Path | None, typer.Option("--seed",
67
+ exists=True, file_okay=False, dir_okay=True, resolve_path=True)] = None,
68
+ provider: Annotated[str, typer.Option("--provider",
69
+ help=PROVIDER_HELP)] = "auto",
70
+ language: Annotated[str, typer.Option("--language", "-l",
71
+ help="Output language: python | javascript | typescript")] = "python",
72
+ stop_on_regression: Annotated[bool, typer.Option("--stop-on-regression")] = False,
73
+ json_out: Annotated[bool, typer.Option("--json")] = False,
74
+ ) -> None:
75
+ """Run the recursive evolve loop and watch the show."""
76
+ output.mkdir(parents=True, exist_ok=True)
77
+ llm = _resolve_provider(provider)
78
+ try:
79
+ report = run_evolve(
80
+ intent,
81
+ output=output,
82
+ package=package,
83
+ seed_repo=seed_repo,
84
+ llm=llm, # type: ignore[arg-type]
85
+ rounds=auto,
86
+ iterations_per_round=iterations_per_round,
87
+ target_score=target_score,
88
+ stop_on_regression=stop_on_regression,
89
+ language=language,
90
+ )
91
+ except RuntimeError as exc:
92
+ raise click.ClickException(str(exc)) from exc
93
+ if json_out:
94
+ typer.echo(json.dumps(report, indent=2, default=str))
95
+ return
96
+
97
+ # Output path varies by language: python uses src/, JS/TS uses pkg root directly.
98
+ lang = report.get("language", "python")
99
+ pkg_path = (f"src/{report['package']}" if lang == "python"
100
+ else report["package"])
101
+ typer.echo(f"\nForge evolve — {auto} round(s) requested, "
102
+ f"{report['rounds_completed']} completed")
103
+ typer.echo("-" * 60)
104
+ typer.echo(f" llm: {report['llm']}")
105
+ typer.echo(f" package: {report['package']}")
106
+ typer.echo(f" language: {lang}")
107
+ typer.echo(f" output: {report['output_root']}/{pkg_path}")
108
+ typer.echo("")
109
+ typer.echo(" Round │ Iters │ Files │ Symbols (Δ) │ Score (Δ) │ Wire │ Conv")
110
+ typer.echo(" ──────┼───────┼───────┼────────────────┼────────────────┼──────┼──────")
111
+ for r in report["rounds"]:
112
+ sym = f"{r['symbol_count']:3d} ({r['delta_symbols']:+d})"
113
+ sco = f"{r['score']:.0f} ({r['delta_score']:+.1f})"
114
+ wire = r["wire_verdict"][:4]
115
+ conv = "Y" if r["converged"] else "n"
116
+ typer.echo(f" {r['round']:3d} │ {r['iterations']:3d} │ {r['files_written']:3d} │ "
117
+ f"{sym:<14s} │ {sco:<14s} │ {wire:<4s} │ {conv}")
118
+ typer.echo("")
119
+ typer.echo(f" final score: {report['final_score']:.0f}/100")
120
+ typer.echo(f" final symbols: {report['final_symbol_count']}")
121
+ typer.echo(f" trajectory: {report['score_trajectory']}")
122
+ typer.echo(f" converged: {report['converged']}")
@@ -0,0 +1,70 @@
1
+ """
2
+ Auto-synthesized synergy adapter (syn-f69aff67).
3
+
4
+ Producer: evolve
5
+ Consumer: iterate
6
+ Kind: json_artifact
7
+ Score: 39
8
+
9
+ Why this synergy was detected:
10
+ - evolve emits json-out
11
+ - iterate 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 = 'evolve-then-iterate'
28
+ COMMAND_HELP = 'Run evolve to emit a JSON artifact, then feed it to iterate for the next phase.'
29
+
30
+ app = typer.Typer(no_args_is_help=False, help='Run evolve to emit a JSON artifact, then feed it to iterate 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 evolve → capture artifact → feed to iterate."""
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
+ 'evolve', *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
+ 'iterate', 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-f69aff67',
65
+ 'producer': 'evolve',
66
+ 'consumer': 'iterate',
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
+
@@ -0,0 +1,111 @@
1
+ """``atomadic-forge feature-then-emergent`` — universal pipeline:
2
+ run any feature's JSON-emitting verb, then fan its payload into emergent scan.
3
+
4
+ This is the Item-1 synergy from the roadmap: every feature in the catalog
5
+ becomes a producer; emergent scan becomes the universal consumer. The
6
+ adapter is generic (it doesn't bake in any single feature) so adding a new
7
+ feature later automatically participates without code changes.
8
+
9
+ Examples:
10
+ atomadic-forge feature-then-emergent scout C:/path/to/repo --no-llm
11
+ atomadic-forge feature-then-emergent commandsmith discover --json
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import subprocess
18
+ import sys
19
+ import tempfile
20
+ from pathlib import Path
21
+ from typing import Annotated
22
+
23
+ import typer
24
+
25
+ from atomadic_forge.a3_og_features.emergent_pipeline_integration import (
26
+ emergent_overlay_for_path,
27
+ )
28
+
29
+ COMMAND_NAME = "feature-then-emergent"
30
+ COMMAND_HELP = ("Run any feature, then fan its JSON output into emergent "
31
+ "scan to surface novel compositions.")
32
+
33
+
34
+ app = typer.Typer(no_args_is_help=False, help=COMMAND_HELP)
35
+
36
+
37
+ def _try_extract_repo_root(payload: dict) -> Path | None:
38
+ """Try to find a repo path in the producer's JSON payload.
39
+
40
+ We probe a few common keys (``repo``, ``src_root``, ``project_path``,
41
+ ``primary_root``). This makes the adapter useful even when the producer
42
+ isn't scout-shaped.
43
+ """
44
+ for key in ("src_root", "primary_root", "repo", "project_path", "repo_root"):
45
+ if key in payload and isinstance(payload[key], str):
46
+ p = Path(payload[key])
47
+ if p.exists():
48
+ return p
49
+ return None
50
+
51
+
52
+ @app.command("run",
53
+ context_settings={"allow_extra_args": True,
54
+ "ignore_unknown_options": True})
55
+ def run(
56
+ ctx: typer.Context,
57
+ feature: Annotated[str, typer.Argument(
58
+ help="ASS-ADE verb to run as the producer (e.g. scout, commandsmith).")],
59
+ explicit_repo: Annotated[Path | None, typer.Option("--repo",
60
+ help="Override repo root used for the emergent scan; otherwise inferred.",
61
+ exists=True, file_okay=False, dir_okay=True, resolve_path=True)] = None,
62
+ package: Annotated[str, typer.Option("--package")] = "atomadic_forge",
63
+ top_n: Annotated[int, typer.Option("--top-n")] = 10,
64
+ ) -> None:
65
+ """Run ``atomadic-forge <feature> <args>``, then run emergent scan over its scope.
66
+
67
+ Forwarded args go after ``--``. Example::
68
+
69
+ atomadic-forge feature-then-emergent run scout --top-n 3 -- C:/repo --no-llm
70
+ # → producer args = ["C:/repo", "--no-llm"], adapter top_n = 3
71
+ """
72
+ feature_args = list(ctx.args) # everything after the recognised options
73
+ with tempfile.TemporaryDirectory(prefix="feature-emergent-") as tmp:
74
+ artifact = Path(tmp) / "producer.json"
75
+ cmd = [sys.executable, "-m",
76
+ "atomadic_forge.a4_sy_orchestration.unified_cli",
77
+ feature, *feature_args, "--json-out", str(artifact)]
78
+ rc = subprocess.run(cmd, capture_output=False).returncode
79
+ if rc != 0:
80
+ typer.secho(f"producer `{feature}` exited {rc}", fg="red", err=True)
81
+ raise typer.Exit(rc)
82
+
83
+ try:
84
+ payload = json.loads(artifact.read_text(encoding="utf-8"))
85
+ except (OSError, json.JSONDecodeError) as exc:
86
+ typer.secho(f"producer JSON invalid: {exc}", fg="red", err=True)
87
+ raise typer.Exit(2) from exc
88
+
89
+ repo_root = explicit_repo or _try_extract_repo_root(payload)
90
+ if repo_root is None:
91
+ typer.secho(
92
+ "could not infer a repo root from producer payload; "
93
+ "pass --repo PATH to set it explicitly",
94
+ fg="yellow", err=True,
95
+ )
96
+ raise typer.Exit(2)
97
+
98
+ overlay = emergent_overlay_for_path(repo_root, phase="scout",
99
+ package=package)
100
+ # Only keep the top-N candidates the operator actually wants to see.
101
+ overlay["candidates"] = overlay["candidates"][:top_n]
102
+
103
+ typer.echo(json.dumps({
104
+ "synergy": "feature_then_emergent",
105
+ "feature": feature,
106
+ "feature_args": feature_args,
107
+ "producer_payload_keys": sorted(payload.keys()) if isinstance(payload, dict) else None,
108
+ "emergent_repo_root": str(repo_root),
109
+ "emergent_summary": overlay.get("summary_line"),
110
+ "candidates": overlay["candidates"],
111
+ }, indent=2, default=str))