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,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'
|