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,147 @@
1
+ """Tier a1 — rollback_plan: reversible-move guidance (Codex #11).
2
+
3
+ Codex's prescription:
4
+
5
+ > Agents need reversible moves. rollback_plan({changed_files})
6
+ > returns: generated files to remove, caches to clean, docs to
7
+ > restore, tests to rerun, risk of rollback.
8
+
9
+ Pure heuristic: classifies each changed file by extension + path
10
+ shape and returns a structured undo plan. The agent decides whether
11
+ to apply the moves; this is advisory.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from pathlib import Path
16
+ from typing import TypedDict
17
+
18
+ SCHEMA_VERSION_ROLLBACK_V1 = "atomadic-forge.rollback/v1"
19
+
20
+
21
+ _GENERATED_DIRS = (
22
+ "build", "dist", ".pytest_cache", "__pycache__",
23
+ "node_modules", ".turbo", ".next", ".nuxt",
24
+ ".atomadic-forge",
25
+ )
26
+ _CACHE_DIRS = (
27
+ "__pycache__", ".pytest_cache", ".mypy_cache", ".ruff_cache",
28
+ ".tox", ".coverage",
29
+ )
30
+ _DOCS_DIRS = ("docs", "doc", "documentation")
31
+ _RELEASE_FILES = (
32
+ "pyproject.toml", "setup.py", "setup.cfg", "package.json",
33
+ "Cargo.toml", "CHANGELOG.md", "VERSION",
34
+ )
35
+
36
+
37
+ class RollbackPlan(TypedDict, total=False):
38
+ schema_version: str
39
+ changed_files: list[str]
40
+ files_to_remove: list[str]
41
+ caches_to_clean: list[str]
42
+ docs_to_restore: list[str]
43
+ tests_to_rerun: list[str]
44
+ risk_level: str
45
+ notes: list[str]
46
+ suggested_commands: list[str]
47
+
48
+
49
+ def _is_generated(path: str) -> bool:
50
+ return any(seg in _GENERATED_DIRS for seg in path.split("/"))
51
+
52
+
53
+ def _is_cache(path: str) -> bool:
54
+ return any(seg in _CACHE_DIRS for seg in path.split("/"))
55
+
56
+
57
+ def _is_docs(path: str) -> bool:
58
+ return any(seg in _DOCS_DIRS for seg in path.split("/"))
59
+
60
+
61
+ def _is_release(path: str) -> bool:
62
+ return Path(path).name in _RELEASE_FILES
63
+
64
+
65
+ def rollback_plan(
66
+ *,
67
+ changed_files: list[str],
68
+ project_root: Path,
69
+ ) -> RollbackPlan:
70
+ """Build a structured rollback plan for ``changed_files``.
71
+
72
+ Pure: walks paths only. Returns the categories the agent should
73
+ consider when undoing the change, with shell sketches.
74
+ """
75
+ files_to_remove: list[str] = []
76
+ caches: list[str] = []
77
+ docs: list[str] = []
78
+ tests: list[str] = []
79
+ notes: list[str] = []
80
+ has_release_change = False
81
+
82
+ for f in changed_files:
83
+ if _is_generated(f):
84
+ files_to_remove.append(f)
85
+ if _is_cache(f):
86
+ caches.append(f)
87
+ if _is_docs(f):
88
+ docs.append(f)
89
+ if "tests/" in f or "test/" in f or f.endswith("_test.py"):
90
+ tests.append(f)
91
+ if _is_release(f):
92
+ has_release_change = True
93
+ notes.append(
94
+ f"{f} is a release-control file — reverting it must "
95
+ "include a corresponding CHANGELOG note + version "
96
+ "decrement decision."
97
+ )
98
+
99
+ # If there are non-generated, non-test, non-doc src changes, those
100
+ # are the riskiest part of a rollback because they may have
101
+ # downstream consumers.
102
+ src_changes = [f for f in changed_files
103
+ if not _is_generated(f) and not _is_cache(f)
104
+ and not _is_docs(f)
105
+ and "tests/" not in f and "test/" not in f]
106
+ if has_release_change:
107
+ risk = "high"
108
+ elif len(src_changes) > 5:
109
+ risk = "high"
110
+ notes.append(
111
+ f"{len(src_changes)} src files in scope — bulk rollback is "
112
+ "high-risk; prefer per-card reverts via plan-step rollbacks."
113
+ )
114
+ elif len(src_changes) > 0:
115
+ risk = "medium"
116
+ else:
117
+ risk = "low"
118
+
119
+ if not tests and src_changes:
120
+ tests = ["python -m pytest"]
121
+ notes.append(
122
+ "no test files in scope but src changed — re-run the full "
123
+ "suite to confirm the rollback didn't break a covered path."
124
+ )
125
+
126
+ suggested: list[str] = []
127
+ if files_to_remove:
128
+ suggested.append("rm -rf " + " ".join(sorted(set(files_to_remove))))
129
+ if caches:
130
+ suggested.append(
131
+ "find . -type d -name __pycache__ -exec rm -rf {} +")
132
+ suggested.append("git restore " + " ".join(src_changes)
133
+ if src_changes else "git status")
134
+ if tests:
135
+ suggested.append("python -m pytest")
136
+
137
+ return RollbackPlan(
138
+ schema_version=SCHEMA_VERSION_ROLLBACK_V1,
139
+ changed_files=list(changed_files),
140
+ files_to_remove=sorted(set(files_to_remove)),
141
+ caches_to_clean=sorted(set(caches)),
142
+ docs_to_restore=sorted(set(docs)),
143
+ tests_to_rerun=sorted(set(tests)),
144
+ risk_level=risk,
145
+ notes=notes,
146
+ suggested_commands=suggested,
147
+ )
@@ -0,0 +1,155 @@
1
+ """Tier a1 — pure CycloneDX 1.5 SBOM emitter.
2
+
3
+ Golden Path Lane G G3. Emits an ``atomadic-forge.sbom/v1`` document
4
+ wrapping a CycloneDX 1.5 JSON SBOM derived from a project's
5
+ ``pyproject.toml`` ``[project].dependencies`` +
6
+ ``[project.optional-dependencies].dev``.
7
+
8
+ Pure: no network, no file writes. Returns a dict ready for
9
+ ``json.dumps``.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import datetime as _dt
14
+ import re
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ from .. import __version__
19
+
20
+ SCHEMA_VERSION_SBOM_V1 = "atomadic-forge.sbom/v1"
21
+ _CYCLONEDX_FORMAT = "CycloneDX"
22
+ _CYCLONEDX_SPEC_VERSION = "1.5"
23
+
24
+
25
+ def _now_utc_iso() -> str:
26
+ return _dt.datetime.now(_dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
27
+
28
+
29
+ def _normalise_name(raw: str) -> str:
30
+ """PEP 503 canonical name: lowercase, dashes/underscores/dots -> hyphens."""
31
+ return re.sub(r"[-_.]+", "-", raw).lower()
32
+
33
+
34
+ def _parse_dep(dep: str) -> tuple[str, str]:
35
+ """Return (canonical_name, version_spec) from a PEP 508 string."""
36
+ dep = dep.strip()
37
+ name_match = re.match(r"^([A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?)", dep)
38
+ name = _normalise_name(name_match.group(1)) if name_match else dep.lower()
39
+ ver_match = re.search(r"[><=!~]{1,3}([0-9][^,;\s\]]*)", dep)
40
+ ver = ver_match.group(1).rstrip(",") if ver_match else "unspecified"
41
+ return name, ver
42
+
43
+
44
+ def _read_pyproject_deps(project_root: Path) -> list[str]:
45
+ """Return combined runtime + dev deps from ``pyproject.toml``."""
46
+ pyproject = project_root / "pyproject.toml"
47
+ if not pyproject.exists():
48
+ return []
49
+ try:
50
+ text = pyproject.read_text(encoding="utf-8")
51
+ except OSError:
52
+ return []
53
+
54
+ try:
55
+ import tomllib # noqa: PLC0415
56
+ except ImportError:
57
+ try:
58
+ import tomli as tomllib # type: ignore[no-redef] # noqa: PLC0415
59
+ except ImportError:
60
+ tomllib = None # type: ignore[assignment]
61
+
62
+ if tomllib is not None:
63
+ try:
64
+ data = tomllib.loads(text)
65
+ project = data.get("project", {})
66
+ deps: list[str] = list(project.get("dependencies") or [])
67
+ opt = project.get("optional-dependencies") or {}
68
+ deps.extend(opt.get("dev") or [])
69
+ return deps
70
+ except Exception: # noqa: BLE001
71
+ pass
72
+
73
+ return _regex_extract_deps(text)
74
+
75
+
76
+ def _regex_extract_deps(text: str) -> list[str]:
77
+ """Minimal TOML-free dep extraction."""
78
+ deps: list[str] = []
79
+ in_section = False
80
+ for line in text.splitlines():
81
+ stripped = line.strip()
82
+ if re.match(r"^dependencies\s*=\s*\[", stripped):
83
+ in_section = True
84
+ inner = re.search(r"\[([^\]]*)\]", stripped)
85
+ if inner:
86
+ for tok in inner.group(1).split(","):
87
+ dep = tok.strip().strip("\"'")
88
+ if dep:
89
+ deps.append(dep)
90
+ in_section = False
91
+ continue
92
+ if in_section:
93
+ if stripped.startswith("]"):
94
+ in_section = False
95
+ continue
96
+ dep = stripped.strip(",").strip("\"'").strip()
97
+ if dep and not dep.startswith("#"):
98
+ deps.append(dep)
99
+ return deps
100
+
101
+
102
+ def _dep_to_component(dep: str) -> dict[str, Any]:
103
+ name, version = _parse_dep(dep)
104
+ return {
105
+ "type": "library",
106
+ "name": name,
107
+ "version": version,
108
+ "purl": f"pkg:pypi/{name}@{version}",
109
+ }
110
+
111
+
112
+ def emit_sbom(
113
+ *,
114
+ project_root: Path | str,
115
+ scout_report: dict[str, Any] | None = None,
116
+ ) -> dict[str, Any]:
117
+ """Return an ``atomadic-forge.sbom/v1`` dict wrapping CycloneDX 1.5 JSON.
118
+
119
+ Pulls deps from ``pyproject.toml`` ``[project].dependencies`` +
120
+ ``[project.optional-dependencies].dev``.
121
+ """
122
+ project_root = Path(project_root).resolve()
123
+ raw_deps = _read_pyproject_deps(project_root)
124
+ components = [_dep_to_component(d) for d in raw_deps if d.strip()]
125
+
126
+ now = _now_utc_iso()
127
+ cyclonedx: dict[str, Any] = {
128
+ "bomFormat": _CYCLONEDX_FORMAT,
129
+ "specVersion": _CYCLONEDX_SPEC_VERSION,
130
+ "version": 1,
131
+ "metadata": {
132
+ "timestamp": now,
133
+ "tools": [
134
+ {
135
+ "vendor": "Atomadic",
136
+ "name": "atomadic-forge",
137
+ "version": __version__,
138
+ }
139
+ ],
140
+ },
141
+ "components": components,
142
+ }
143
+
144
+ sbom: dict[str, Any] = {
145
+ "schema_version": SCHEMA_VERSION_SBOM_V1,
146
+ "project_root": str(project_root),
147
+ "generated_at_utc": now,
148
+ "sbom": cyclonedx,
149
+ }
150
+ if scout_report is not None:
151
+ sbom["scout_summary"] = {
152
+ "symbol_count": int(scout_report.get("symbol_count", 0)),
153
+ "primary_language": str(scout_report.get("primary_language", "python")),
154
+ }
155
+ return sbom
@@ -0,0 +1,55 @@
1
+ """Tier a1 — pure renderers for JS/TS package scaffolds.
2
+
3
+ Forge's iterate loop scaffolds the surrounding package shell so the LLM
4
+ can focus on emitting actual code. For Python this means ``pyproject.toml``,
5
+ ``__init__.py``, ``conftest.py``. For JavaScript / TypeScript it means a
6
+ minimal ``package.json`` plus a README — nothing else is required for ES
7
+ modules to resolve.
8
+
9
+ Pure: no I/O. Each helper returns a string the caller writes to disk.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json as _json
15
+
16
+
17
+ def render_package_json(*, package: str, description: str = "",
18
+ language: str = "javascript") -> str:
19
+ """Render a minimal ``package.json`` for a JS/TS evolved package.
20
+
21
+ Sets ``"type": "module"`` so ES6 ``import`` / ``export`` resolves
22
+ natively. No build step, no dependencies — Forge produces source
23
+ that runs as-is on Node 20+ and Cloudflare Workers.
24
+
25
+ The TypeScript variant is identical except for documentation in the
26
+ description (``ts`` files would compile via ``tsc`` or run via
27
+ ``tsx``; that's a downstream concern).
28
+ """
29
+ pkg = {
30
+ "name": package,
31
+ "version": "0.0.1",
32
+ "description": (description or f"{package} — generated by atomadic-forge").strip(),
33
+ "type": "module",
34
+ "private": True,
35
+ }
36
+ return _json.dumps(pkg, indent=2) + "\n"
37
+
38
+
39
+ def render_js_readme(*, package: str, intent: str, language: str) -> str:
40
+ """Render a README.md for a JS/TS evolved package."""
41
+ lang_label = "JavaScript" if language == "javascript" else "TypeScript"
42
+ return (
43
+ f"# {package}\n\n"
44
+ f"{lang_label} package generated by `atomadic-forge evolve`.\n\n"
45
+ f"## Intent\n\n"
46
+ f"{intent.strip() or '(unspecified)'}\n\n"
47
+ f"## Layout (5-tier monadic)\n\n"
48
+ f"- `a0_qk_constants/` — constants, no logic\n"
49
+ f"- `a1_at_functions/` — pure stateless functions\n"
50
+ f"- `a2_mo_composites/` — stateful classes / clients\n"
51
+ f"- `a3_og_features/` — feature orchestrators\n"
52
+ f"- `a4_sy_orchestration/` — entry points (Worker, CLI)\n\n"
53
+ f"Imports compose **upward only** — a lower tier never imports "
54
+ f"from a higher tier. Forge enforces this in `forge wire`.\n"
55
+ )
@@ -0,0 +1,62 @@
1
+ """Tier a1 — pure renderer for a generated package's ``pyproject.toml``.
2
+
3
+ When ``forge iterate``/``evolve`` materialises a package, it scaffolds a
4
+ real ``pyproject.toml`` so the output is genuinely pip-installable from
5
+ moment zero. The LLM doesn't need to know how to build a pyproject —
6
+ Forge does that, and the LLM just emits the ``.py`` files.
7
+
8
+ The renderer is pure: it takes a name and an optional console-script
9
+ target and returns a TOML string.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from collections.abc import Iterable
15
+
16
+
17
+ def render_pyproject(
18
+ *,
19
+ package: str,
20
+ description: str = "",
21
+ version: str = "0.1.0",
22
+ python_requires: str = ">=3.10",
23
+ console_script_target: str | None = None,
24
+ extra_dependencies: Iterable[str] = (),
25
+ ) -> str:
26
+ """Return a PEP-621 pyproject.toml as a string.
27
+
28
+ ``console_script_target``: e.g. ``"a4_sy_orchestration.cli:main"``.
29
+ The script becomes ``<package> = "<package>.<target>"`` so users can
30
+ run ``<package>`` after install.
31
+ """
32
+ desc = (description or f"{package} — generated by atomadic-forge").replace('"', "'")
33
+ deps = ",\n ".join(f'"{d}"' for d in extra_dependencies)
34
+ deps_block = (
35
+ f"dependencies = [\n {deps},\n]\n" if deps else "dependencies = []\n"
36
+ )
37
+ scripts_block = ""
38
+ if console_script_target:
39
+ scripts_block = (
40
+ "\n[project.scripts]\n"
41
+ f'{package} = "{package}.{console_script_target}"\n'
42
+ )
43
+
44
+ return (
45
+ "# Auto-scaffolded by atomadic-forge. Edit freely.\n"
46
+ "[build-system]\n"
47
+ 'requires = ["setuptools>=68", "wheel"]\n'
48
+ 'build-backend = "setuptools.build_meta"\n'
49
+ "\n"
50
+ "[project]\n"
51
+ f'name = "{package}"\n'
52
+ f'version = "{version}"\n'
53
+ f'description = "{desc}"\n'
54
+ f'readme = "README.md"\n'
55
+ f'requires-python = "{python_requires}"\n'
56
+ + deps_block
57
+ + scripts_block
58
+ + "\n"
59
+ "[tool.setuptools.packages.find]\n"
60
+ 'where = ["src"]\n'
61
+ f'include = ["{package}*"]\n'
62
+ )
@@ -0,0 +1,94 @@
1
+ """Tier a1 — pure renderers for the surrounding package starter files.
2
+
3
+ Forge scaffolds ``README.md``, ``.gitignore``, ``tests/__init__.py``, and
4
+ ``tests/conftest.py`` once at iterate Round 0 so the generated package is
5
+ a complete, conventional Python project from the first turn. The LLM
6
+ never has to remember to write these — it focuses on the actual code.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+
12
+ def render_readme(*, package: str, intent: str) -> str:
13
+ safe_intent = intent.strip().replace("`", "'")
14
+ return (
15
+ f"# {package}\n"
16
+ "\n"
17
+ f"_Generated by [Atomadic Forge](https://atomadic.tech) from the intent below._\n"
18
+ "\n"
19
+ "## Intent\n"
20
+ "\n"
21
+ f"> {safe_intent}\n"
22
+ "\n"
23
+ "## Architecture\n"
24
+ "\n"
25
+ "This package follows the Atomadic 5-tier monadic standard:\n"
26
+ "\n"
27
+ "| Tier | Directory | Lives here |\n"
28
+ "|------|-----------|------------|\n"
29
+ "| a0 | `src/" + package + "/a0_qk_constants/` | constants, enums, TypedDicts |\n"
30
+ "| a1 | `src/" + package + "/a1_at_functions/` | pure functions |\n"
31
+ "| a2 | `src/" + package + "/a2_mo_composites/` | stateful classes |\n"
32
+ "| a3 | `src/" + package + "/a3_og_features/` | feature orchestrators |\n"
33
+ "| a4 | `src/" + package + "/a4_sy_orchestration/` | CLI / entry points |\n"
34
+ "\n"
35
+ "## Install\n"
36
+ "\n"
37
+ "```bash\n"
38
+ "pip install -e .\n"
39
+ "```\n"
40
+ "\n"
41
+ "## Run tests\n"
42
+ "\n"
43
+ "```bash\n"
44
+ "pytest tests/\n"
45
+ "```\n"
46
+ "\n"
47
+ "## Verify with Forge\n"
48
+ "\n"
49
+ "```bash\n"
50
+ f"forge wire src/{package} # zero upward-import violations\n"
51
+ f"forge certify . --package {package}\n"
52
+ "```\n"
53
+ )
54
+
55
+
56
+ def render_gitignore() -> str:
57
+ return (
58
+ "__pycache__/\n"
59
+ "*.py[cod]\n"
60
+ "*.egg-info/\n"
61
+ "build/\n"
62
+ "dist/\n"
63
+ ".venv/\n"
64
+ "venv/\n"
65
+ ".pytest_cache/\n"
66
+ ".ruff_cache/\n"
67
+ ".mypy_cache/\n"
68
+ ".coverage\n"
69
+ ".atomadic-forge/\n"
70
+ ".DS_Store\n"
71
+ ".vscode/\n"
72
+ ".idea/\n"
73
+ )
74
+
75
+
76
+ def render_tests_conftest(*, package: str) -> str:
77
+ return (
78
+ '"""Shared fixtures for ' + package + ' tests."""\n'
79
+ "\n"
80
+ "from __future__ import annotations\n"
81
+ "\n"
82
+ "import sys\n"
83
+ "from pathlib import Path\n"
84
+ "\n"
85
+ "# Make the src/ layout importable when tests run before pip install.\n"
86
+ "ROOT = Path(__file__).resolve().parent.parent\n"
87
+ "SRC = ROOT / 'src'\n"
88
+ "if SRC.exists() and str(SRC) not in sys.path:\n"
89
+ " sys.path.insert(0, str(SRC))\n"
90
+ )
91
+
92
+
93
+ def render_tests_init() -> str:
94
+ return '"""Tests for the package."""\n'