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,116 @@
|
|
|
1
|
+
"""Tier a0 — generation language constants for forge iterate / evolve.
|
|
2
|
+
|
|
3
|
+
Forge's generator loop (``forge iterate``, ``forge evolve``) emits source
|
|
4
|
+
code in a target language. The default has always been Python; this module
|
|
5
|
+
adds JavaScript and TypeScript as first-class targets so an LLM can produce
|
|
6
|
+
Cloudflare-Worker-shaped or Node-shaped tier scaffolds with the same loop.
|
|
7
|
+
|
|
8
|
+
Pure data. Determines:
|
|
9
|
+
* ``PKG_ROOT_TEMPLATE`` — where Forge writes the generated package
|
|
10
|
+
(Python uses ``output/src/<pkg>/`` for pip-install compatibility;
|
|
11
|
+
JS/TS use ``output/<pkg>/`` because there's no ``src/`` PEP).
|
|
12
|
+
* ``ALLOWED_FILE_EXTS`` — what file suffixes ``_safe_path`` accepts.
|
|
13
|
+
Forge rejects any other suffix to prevent the LLM from emitting
|
|
14
|
+
polyglot junk into a single-language tree.
|
|
15
|
+
* ``MAIN_EXT`` — the canonical primary extension (``.py`` / ``.js`` /
|
|
16
|
+
``.ts``) used in scaffolded tier file names.
|
|
17
|
+
* ``EMITS_INIT_FILES`` — whether Forge generates ``__init__.py``-style
|
|
18
|
+
package indices (Python only).
|
|
19
|
+
* ``EMITS_PYPROJECT`` — whether Forge writes ``pyproject.toml`` (Python
|
|
20
|
+
only). JS/TS scaffolds get a minimal ``package.json`` instead.
|
|
21
|
+
|
|
22
|
+
This is intentionally small. More language-specific knobs (e.g. tsconfig
|
|
23
|
+
generation, vitest vs node:test) belong in scaffold helpers, not here.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from typing import Final, Literal
|
|
29
|
+
|
|
30
|
+
Language = Literal["python", "javascript", "typescript"]
|
|
31
|
+
|
|
32
|
+
LANGUAGES: Final[tuple[Language, ...]] = ("python", "javascript", "typescript")
|
|
33
|
+
|
|
34
|
+
DEFAULT_LANGUAGE: Final[Language] = "python"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Package-root templates. The keys are language names; values are
|
|
38
|
+
# format strings with a single ``{package}`` substitution that gets
|
|
39
|
+
# joined to the user's ``output`` directory.
|
|
40
|
+
PKG_ROOT_TEMPLATE: Final[dict[Language, str]] = {
|
|
41
|
+
"python": "src/{package}",
|
|
42
|
+
"javascript": "{package}",
|
|
43
|
+
"typescript": "{package}",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# File suffixes Forge will accept from an LLM emit, per language. Every
|
|
48
|
+
# other suffix is silently dropped by ``_safe_path``. Note all languages
|
|
49
|
+
# allow ``.md`` (READMEs / per-file documentation) and the relevant config
|
|
50
|
+
# file (``.toml`` for Python, ``.json`` for JS/TS).
|
|
51
|
+
ALLOWED_FILE_EXTS: Final[dict[Language, frozenset[str]]] = {
|
|
52
|
+
"python": frozenset({".py", ".md", ".toml"}),
|
|
53
|
+
"javascript": frozenset({".js", ".mjs", ".cjs", ".jsx",
|
|
54
|
+
".json", ".md"}),
|
|
55
|
+
"typescript": frozenset({".ts", ".tsx", ".js", ".mjs",
|
|
56
|
+
".json", ".md"}),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Canonical primary extension used when Forge scaffolds tier files.
|
|
61
|
+
MAIN_EXT: Final[dict[Language, str]] = {
|
|
62
|
+
"python": ".py",
|
|
63
|
+
"javascript": ".js",
|
|
64
|
+
"typescript": ".ts",
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# Whether Forge emits Python-style package indices (``__init__.py``).
|
|
69
|
+
# JS/TS use ES module exports directly — no per-directory index file
|
|
70
|
+
# is required for imports to resolve.
|
|
71
|
+
EMITS_INIT_FILES: Final[dict[Language, bool]] = {
|
|
72
|
+
"python": True,
|
|
73
|
+
"javascript": False,
|
|
74
|
+
"typescript": False,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# Whether Forge writes a ``pyproject.toml`` at the output root.
|
|
79
|
+
# JS/TS scaffolds get a minimal ``package.json`` from a different helper.
|
|
80
|
+
EMITS_PYPROJECT: Final[dict[Language, bool]] = {
|
|
81
|
+
"python": True,
|
|
82
|
+
"javascript": False,
|
|
83
|
+
"typescript": False,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def normalize_language(value: str | None) -> Language:
|
|
88
|
+
"""Coerce a user-supplied language string into the canonical Language.
|
|
89
|
+
|
|
90
|
+
Accepts ``"python" | "javascript" | "js" | "typescript" | "ts"`` (and
|
|
91
|
+
common aliases). Returns the canonical Language literal. Raises
|
|
92
|
+
``ValueError`` if the input is not recognised.
|
|
93
|
+
|
|
94
|
+
``None`` returns the default language (``"python"``) so callers can
|
|
95
|
+
pass through optional CLI flags without branching.
|
|
96
|
+
"""
|
|
97
|
+
if value is None:
|
|
98
|
+
return DEFAULT_LANGUAGE
|
|
99
|
+
v = value.strip().lower()
|
|
100
|
+
if v in ("python", "py"):
|
|
101
|
+
return "python"
|
|
102
|
+
if v in ("javascript", "js", "node"):
|
|
103
|
+
return "javascript"
|
|
104
|
+
if v in ("typescript", "ts"):
|
|
105
|
+
return "typescript"
|
|
106
|
+
raise ValueError(
|
|
107
|
+
f"unknown language: {value!r} — expected one of {list(LANGUAGES)}"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def pkg_root_for(language: Language, package: str) -> str:
|
|
112
|
+
"""Return the package-root path *relative to the output directory*.
|
|
113
|
+
|
|
114
|
+
Pure: no I/O. Caller joins this against their output ``Path``.
|
|
115
|
+
"""
|
|
116
|
+
return PKG_ROOT_TEMPLATE[language].format(package=package)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Tier a0 — language extensions and ignore lists Forge recognises.
|
|
2
|
+
|
|
3
|
+
Pure data. Determines which files Forge's recon, wire, and certify passes
|
|
4
|
+
include in their walks. Adding a language is a one-line change here.
|
|
5
|
+
|
|
6
|
+
Files Forge classifies into three buckets:
|
|
7
|
+
* **source** — code that participates in tier classification, wire
|
|
8
|
+
checks, and import-graph analysis.
|
|
9
|
+
* **documentation** / **config** / **asset** — files that contribute to
|
|
10
|
+
repo *signals* (a README.md flips ``has_readme``,
|
|
11
|
+
a docs/ARCHITECTURE.md flips ``has_docs``) but are
|
|
12
|
+
NEVER counted as "untiered code" — they have no
|
|
13
|
+
tier identity and harshing the layout score on
|
|
14
|
+
their position would be wrong.
|
|
15
|
+
|
|
16
|
+
Plus an ignore list of directories Forge never recurses into (VCS internals,
|
|
17
|
+
package caches, build outputs, AI-tooling worktrees, etc.).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from typing import Final
|
|
23
|
+
|
|
24
|
+
# Extensions Forge classifies as Python source.
|
|
25
|
+
PYTHON_EXTS: Final[frozenset[str]] = frozenset({".py"})
|
|
26
|
+
|
|
27
|
+
# Extensions Forge classifies as JavaScript source.
|
|
28
|
+
JAVASCRIPT_EXTS: Final[frozenset[str]] = frozenset({".js", ".mjs", ".cjs", ".jsx"})
|
|
29
|
+
|
|
30
|
+
# Extensions Forge classifies as TypeScript source.
|
|
31
|
+
TYPESCRIPT_EXTS: Final[frozenset[str]] = frozenset({".ts", ".tsx"})
|
|
32
|
+
|
|
33
|
+
# All source extensions Forge walks. Order matters only for stable iteration.
|
|
34
|
+
ALL_SOURCE_EXTS: Final[frozenset[str]] = (
|
|
35
|
+
PYTHON_EXTS | JAVASCRIPT_EXTS | TYPESCRIPT_EXTS
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Documentation: contributes positively to the docs signal; never tier-classified.
|
|
39
|
+
DOC_EXTS: Final[frozenset[str]] = frozenset({
|
|
40
|
+
".md", ".markdown", ".mdx", ".rst", ".txt", ".adoc", ".asciidoc",
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
# Config / data files: present-but-not-code. Never count toward tier layout.
|
|
44
|
+
CONFIG_EXTS: Final[frozenset[str]] = frozenset({
|
|
45
|
+
".json", ".yaml", ".yml", ".toml", ".ini", ".cfg",
|
|
46
|
+
".env", ".lock", ".gitignore", ".editorconfig",
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
# Page / image / binary assets. Same treatment as docs/config.
|
|
50
|
+
ASSET_EXTS: Final[frozenset[str]] = frozenset({
|
|
51
|
+
".html", ".htm", ".css", ".scss", ".sass", ".less",
|
|
52
|
+
".svg", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".ico", ".bmp",
|
|
53
|
+
".pdf", ".docx", ".pptx", ".xlsx",
|
|
54
|
+
".woff", ".woff2", ".ttf", ".otf",
|
|
55
|
+
".mp3", ".mp4", ".wav", ".webm",
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
# Map a file extension to a canonical language label.
|
|
59
|
+
LANG_OF_EXT: Final[dict[str, str]] = {
|
|
60
|
+
**{e: "python" for e in PYTHON_EXTS},
|
|
61
|
+
**{e: "javascript" for e in JAVASCRIPT_EXTS},
|
|
62
|
+
**{e: "typescript" for e in TYPESCRIPT_EXTS},
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Directories Forge never recurses into. These hold tooling, caches, build
|
|
66
|
+
# outputs, agent worktrees, and dependencies — none of it is Atomadic's code.
|
|
67
|
+
IGNORED_DIRS: Final[frozenset[str]] = frozenset({
|
|
68
|
+
# Version control
|
|
69
|
+
".git", ".hg", ".svn",
|
|
70
|
+
# Package / language caches
|
|
71
|
+
"node_modules", "__pycache__", ".pytest_cache", ".ruff_cache",
|
|
72
|
+
".mypy_cache", ".tox", ".nox", ".cache",
|
|
73
|
+
# Build outputs
|
|
74
|
+
"dist", "build", "out", "target", ".next", ".nuxt", ".svelte-kit",
|
|
75
|
+
"coverage", ".coverage", "htmlcov",
|
|
76
|
+
# Virtualenvs
|
|
77
|
+
".venv", "venv", "env", ".env",
|
|
78
|
+
# IDE / AI tooling
|
|
79
|
+
".vscode", ".idea", ".cursor", ".codeium", ".aider",
|
|
80
|
+
".claude", ".github",
|
|
81
|
+
# Agent / worktree clutter
|
|
82
|
+
"worktrees", ".worktrees",
|
|
83
|
+
# Cloudflare / wrangler
|
|
84
|
+
".wrangler", ".wrangler-cache",
|
|
85
|
+
# Atomadic-Forge's own scratch dir
|
|
86
|
+
".atomadic-forge",
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def is_ignored_dir_name(name: str) -> bool:
|
|
91
|
+
"""Return True for directory names Forge should never recurse into."""
|
|
92
|
+
return name in IGNORED_DIRS or name.startswith(".pytest_tmp")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def path_parts_contain_ignored_dir(parts: tuple[str, ...]) -> bool:
|
|
96
|
+
"""Return True when any path segment is an ignored directory name."""
|
|
97
|
+
return any(is_ignored_dir_name(part) for part in parts)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def lang_for_path(path: str) -> str | None:
|
|
101
|
+
"""Return ``"python"`` / ``"javascript"`` / ``"typescript"`` or ``None``.
|
|
102
|
+
|
|
103
|
+
Pure: only the suffix is consulted, never the file content.
|
|
104
|
+
"""
|
|
105
|
+
s = path.lower()
|
|
106
|
+
for ext, lang in LANG_OF_EXT.items():
|
|
107
|
+
if s.endswith(ext):
|
|
108
|
+
return lang
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def file_class_for_path(path: str) -> str:
|
|
113
|
+
"""Return ``"source"`` / ``"documentation"`` / ``"config"`` / ``"asset"``
|
|
114
|
+
/ ``"other"`` — the broad class Forge treats this file as.
|
|
115
|
+
|
|
116
|
+
Pure: only the path is consulted. Used by certify to decide whether a
|
|
117
|
+
file's location should affect tier-layout scoring (only ``source`` does).
|
|
118
|
+
"""
|
|
119
|
+
p = path.lower()
|
|
120
|
+
# Filename-based dotfile matches (no extension)
|
|
121
|
+
base = p.rsplit("/", 1)[-1].rsplit("\\", 1)[-1]
|
|
122
|
+
if base in {".gitignore", ".editorconfig", ".env"}:
|
|
123
|
+
return "config"
|
|
124
|
+
if base.startswith(".env."):
|
|
125
|
+
return "config"
|
|
126
|
+
if base in {"license", "license.md", "license.txt", "readme", "readme.md", "readme.rst"}:
|
|
127
|
+
return "documentation"
|
|
128
|
+
# Extension-based
|
|
129
|
+
for ext in ALL_SOURCE_EXTS:
|
|
130
|
+
if p.endswith(ext):
|
|
131
|
+
return "source"
|
|
132
|
+
for ext in DOC_EXTS:
|
|
133
|
+
if p.endswith(ext):
|
|
134
|
+
return "documentation"
|
|
135
|
+
for ext in CONFIG_EXTS:
|
|
136
|
+
if p.endswith(ext):
|
|
137
|
+
return "config"
|
|
138
|
+
for ext in ASSET_EXTS:
|
|
139
|
+
if p.endswith(ext):
|
|
140
|
+
return "asset"
|
|
141
|
+
return "other"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def is_ignored_segment(segment: str) -> bool:
|
|
145
|
+
"""Return True if a path segment matches an ignored directory name.
|
|
146
|
+
|
|
147
|
+
Kept for callers that still ask about one segment at a time; delegates
|
|
148
|
+
to the same predicate used by full-path ignore checks.
|
|
149
|
+
"""
|
|
150
|
+
return is_ignored_dir_name(segment)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Tier a0 — atomadic-forge.policy/v1 schema.
|
|
2
|
+
|
|
3
|
+
Codex #10: 'Let repos declare their own agent rules.'
|
|
4
|
+
|
|
5
|
+
> [tool.forge.agent]
|
|
6
|
+
> protected_files = ["pyproject.toml", "docs/PAPER_v2.md"]
|
|
7
|
+
> release_gate = ["ruff", "pytest", "build", "forge certify"]
|
|
8
|
+
> max_files_per_patch = 8
|
|
9
|
+
> require_human_review_for = ["license", "security", "public_api"]
|
|
10
|
+
|
|
11
|
+
This module declares the v1 policy shape. The reader (a1) parses
|
|
12
|
+
pyproject.toml's [tool.forge.agent] section into this dict. The
|
|
13
|
+
preflight + patch_scorer modules consume it.
|
|
14
|
+
|
|
15
|
+
a0 invariant: imports limited to __future__ + typing.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from typing import TypedDict
|
|
20
|
+
|
|
21
|
+
SCHEMA_VERSION_POLICY_V1 = "atomadic-forge.policy/v1"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Default values when no policy is declared. Lenient — Forge does
|
|
25
|
+
# not block on the absence of a policy file.
|
|
26
|
+
DEFAULT_PROTECTED_FILES: tuple[str, ...] = ()
|
|
27
|
+
DEFAULT_RELEASE_GATE: tuple[str, ...] = (
|
|
28
|
+
"python -m ruff check .",
|
|
29
|
+
"python -m pytest",
|
|
30
|
+
"forge wire src --fail-on-violations",
|
|
31
|
+
"forge certify . --fail-under 75",
|
|
32
|
+
)
|
|
33
|
+
DEFAULT_MAX_FILES_PER_PATCH: int = 8
|
|
34
|
+
DEFAULT_REQUIRE_HUMAN_REVIEW_FOR: tuple[str, ...] = (
|
|
35
|
+
"license", "security", "public_api",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class AgentPolicy(TypedDict, total=False):
|
|
40
|
+
"""v1 policy shape — every field optional; reader fills defaults."""
|
|
41
|
+
schema_version: str
|
|
42
|
+
protected_files: list[str]
|
|
43
|
+
release_gate: list[str]
|
|
44
|
+
max_files_per_patch: int
|
|
45
|
+
require_human_review_for: list[str]
|
|
46
|
+
# Forward-compat: future versions may add fields. Consumers MUST
|
|
47
|
+
# tolerate unknown keys.
|
|
48
|
+
extra: dict[str, object]
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"""Tier a0 — Forge Receipt JSON v1 schema.
|
|
2
|
+
|
|
3
|
+
The Receipt is the canonical wire format Forge emits for every
|
|
4
|
+
``forge auto`` / ``forge certify`` / ``forge enforce`` run. It bundles
|
|
5
|
+
the certify breakdown, wire scan summary, scout digest, optional
|
|
6
|
+
assimilate output, AAAA-Nexus signature placeholder, and Lean4
|
|
7
|
+
attestation citation into one signed-or-signable artifact.
|
|
8
|
+
|
|
9
|
+
Golden Path Lane A W0 deliverable. Lane A W1 fills in
|
|
10
|
+
``a1_at_functions/receipt_emitter.py`` (the pure transformer that
|
|
11
|
+
turns a CertifyResult + WireReport + ScoutReport into a Receipt) and
|
|
12
|
+
``a1_at_functions/card_renderer.py`` (the 60×24 box-drawing renderer
|
|
13
|
+
used by ``forge auto`` and the "62 → 5" viral demo). Lane A W2 fills
|
|
14
|
+
in ``a2_mo_composites/receipt_signer.py`` which calls AAAA-Nexus
|
|
15
|
+
``/v1/verify/forge-receipt`` to obtain a Sigstore Rekor uuid +
|
|
16
|
+
log_index + AAAA-Nexus signature.
|
|
17
|
+
|
|
18
|
+
Schema versioning:
|
|
19
|
+
* v1.0 (this file) — base schema; all signing / lineage /
|
|
20
|
+
attestation fields are optional and
|
|
21
|
+
default to None so unsigned Receipts
|
|
22
|
+
are valid.
|
|
23
|
+
* v1.1 (Golden Path W8) — adds ``polyglot_breakdown`` (per-
|
|
24
|
+
language certify score split).
|
|
25
|
+
* v1.2 (Golden Path W12) — adds ``slsa_attestation`` (a
|
|
26
|
+
Sigstore-bundle-compatible
|
|
27
|
+
``slsa-provenance-ai/v1`` predicate).
|
|
28
|
+
* v2.0 (Golden Path W24) — adds ``bao_rompf_witnesses`` (full
|
|
29
|
+
categorical effect signatures per
|
|
30
|
+
public symbol).
|
|
31
|
+
|
|
32
|
+
Forward-compat rule: every optional field defaults to ``None`` (or
|
|
33
|
+
``[]`` / ``{}`` for collections). Consumers must accept and ignore
|
|
34
|
+
unknown fields. Producers MUST emit ``schema_version`` and SHOULD
|
|
35
|
+
emit every required field; everything else is optional.
|
|
36
|
+
|
|
37
|
+
Wire-format note: ``schema_version`` always matches the regex
|
|
38
|
+
``^atomadic-forge\\.receipt/v\\d+(?:\\.\\d+)?$`` so a single dispatcher
|
|
39
|
+
can route any future Receipt version.
|
|
40
|
+
|
|
41
|
+
This module is pure data shape — no logic, no imports beyond
|
|
42
|
+
``typing``. Keeps a0 invariant under the Atomadic monadic law.
|
|
43
|
+
"""
|
|
44
|
+
from __future__ import annotations
|
|
45
|
+
|
|
46
|
+
from typing import Literal, TypedDict
|
|
47
|
+
|
|
48
|
+
SCHEMA_VERSION_V1 = "atomadic-forge.receipt/v1"
|
|
49
|
+
SCHEMA_VERSION_V1_1 = "atomadic-forge.receipt/v1.1"
|
|
50
|
+
SCHEMA_VERSION_V1_2 = "atomadic-forge.receipt/v1.2"
|
|
51
|
+
SCHEMA_VERSION_V2 = "atomadic-forge.receipt/v2"
|
|
52
|
+
|
|
53
|
+
ReceiptVerdict = Literal["PASS", "FAIL", "REFINE", "QUARANTINE"]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ReceiptVCS(TypedDict, total=False):
|
|
57
|
+
"""Version-control metadata for the project under Receipt.
|
|
58
|
+
|
|
59
|
+
All optional. ``dirty`` is True when the working tree had
|
|
60
|
+
uncommitted changes at Receipt-emission time. ``head_sha`` is the
|
|
61
|
+
full commit SHA, ``branch`` the symbolic ref. Receipts emitted on
|
|
62
|
+
detached HEAD report ``branch=None``.
|
|
63
|
+
"""
|
|
64
|
+
head_sha: str
|
|
65
|
+
short_sha: str
|
|
66
|
+
branch: str | None
|
|
67
|
+
remote_url: str
|
|
68
|
+
dirty: bool
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ReceiptProject(TypedDict, total=False):
|
|
72
|
+
"""Identifies the project this Receipt covers."""
|
|
73
|
+
name: str
|
|
74
|
+
root: str # absolute path at emission time
|
|
75
|
+
package: str | None # python package name (if applicable)
|
|
76
|
+
language: str # primary_language from scout
|
|
77
|
+
languages: dict[str, int] # per-language file counts
|
|
78
|
+
vcs: ReceiptVCS
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ReceiptCertifyAxes(TypedDict):
|
|
82
|
+
"""Per-axis pass/fail flags from CertifyResult.
|
|
83
|
+
|
|
84
|
+
The structural axes (always present in v1):
|
|
85
|
+
* documentation_complete — README.md OR ≥2 docs/*.md
|
|
86
|
+
* tests_present — tests/test_*.py OR tests/*_test.py
|
|
87
|
+
* tier_layout_present — ≥3 tier directories
|
|
88
|
+
* no_upward_imports — wire scan PASS
|
|
89
|
+
"""
|
|
90
|
+
documentation_complete: bool
|
|
91
|
+
tests_present: bool
|
|
92
|
+
tier_layout_present: bool
|
|
93
|
+
no_upward_imports: bool
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class ReceiptCertify(TypedDict):
|
|
97
|
+
"""Compact certify summary embedded in the Receipt.
|
|
98
|
+
|
|
99
|
+
Mirrors CertifyResult's score + axes; full details remain in
|
|
100
|
+
``.atomadic-forge/certify.json`` (referenced by ``artifacts``).
|
|
101
|
+
"""
|
|
102
|
+
score: float # 0..100
|
|
103
|
+
axes: ReceiptCertifyAxes
|
|
104
|
+
issues: list[str]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ReceiptWire(TypedDict):
|
|
108
|
+
"""Compact wire summary embedded in the Receipt.
|
|
109
|
+
|
|
110
|
+
Mirrors WireReport's verdict + counts; the full violation list and
|
|
111
|
+
repair suggestions remain in ``.atomadic-forge/wire.json``.
|
|
112
|
+
"""
|
|
113
|
+
verdict: Literal["PASS", "FAIL"]
|
|
114
|
+
violation_count: int
|
|
115
|
+
auto_fixable: int # populated only when --suggest-repairs
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class ReceiptScout(TypedDict):
|
|
119
|
+
"""Compact scout summary embedded in the Receipt."""
|
|
120
|
+
symbol_count: int
|
|
121
|
+
tier_distribution: dict[str, int]
|
|
122
|
+
effect_distribution: dict[str, int]
|
|
123
|
+
primary_language: str
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class ReceiptArtifact(TypedDict):
|
|
127
|
+
"""Pointer to an evidence file under .atomadic-forge/.
|
|
128
|
+
|
|
129
|
+
A consuming verifier can re-hash the file at ``path`` and compare
|
|
130
|
+
against ``sha256`` to confirm the Receipt references the exact
|
|
131
|
+
artifact at issue. Hashing is the consumer's responsibility; the
|
|
132
|
+
emitter populates the field when it can.
|
|
133
|
+
"""
|
|
134
|
+
name: str # e.g. "scout", "wire", "certify"
|
|
135
|
+
path: str # relative to project root
|
|
136
|
+
sha256: str | None # None when not computed (cheap mode)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class ReceiptSigstoreSignature(TypedDict, total=False):
|
|
140
|
+
"""Sigstore Rekor entry for the Receipt (Lane A W2)."""
|
|
141
|
+
rekor_uuid: str
|
|
142
|
+
log_index: int
|
|
143
|
+
bundle_path: str # path to the sigstore bundle file
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class ReceiptAAAANexusSignature(TypedDict, total=False):
|
|
147
|
+
"""AAAA-Nexus signature attached via /v1/verify/forge-receipt (Lane A W2)."""
|
|
148
|
+
signature: str # base64-encoded
|
|
149
|
+
key_id: str
|
|
150
|
+
issuer: str # e.g. "aaaa-nexus.atomadic.tech"
|
|
151
|
+
issued_at_utc: str
|
|
152
|
+
verify_endpoint: str # e.g. "/v1/verify/forge-receipt"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class ReceiptLocalSignSignature(TypedDict, total=False):
|
|
156
|
+
"""Ed25519 local signing block (Lane G W5).
|
|
157
|
+
|
|
158
|
+
Populated by ``a1_at_functions.local_signer.sign_receipt_local`` when
|
|
159
|
+
the caller passes ``--local-sign``. The signature covers the canonical
|
|
160
|
+
receipt hash (same content-addressed bytes used by the lineage chain),
|
|
161
|
+
so re-signing after a notes append does not change the signed payload.
|
|
162
|
+
"""
|
|
163
|
+
alg: str # always "Ed25519"
|
|
164
|
+
signature: str # base64-encoded 64-byte Ed25519 signature
|
|
165
|
+
public_key: str # base64-encoded 32-byte raw public key
|
|
166
|
+
key_id: str # first 16 hex chars of SHA-256(raw_public_key)
|
|
167
|
+
signed_at_utc: str # YYYY-MM-DDTHH:MM:SSZ
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class ReceiptSignatures(TypedDict, total=False):
|
|
171
|
+
"""All signature claims attached to this Receipt.
|
|
172
|
+
|
|
173
|
+
Receipts can ship unsigned (all fields ``None``) — ``--emit-receipt``
|
|
174
|
+
without ``--sign`` produces a structurally-valid but unattested
|
|
175
|
+
Receipt suitable for local development. Lane A W2's signer fills
|
|
176
|
+
``sigstore`` and ``aaaa_nexus``; Lane G W5's local signer fills
|
|
177
|
+
``local_sign``.
|
|
178
|
+
"""
|
|
179
|
+
sigstore: ReceiptSigstoreSignature | None
|
|
180
|
+
aaaa_nexus: ReceiptAAAANexusSignature | None
|
|
181
|
+
local_sign: ReceiptLocalSignSignature | None # Lane G W5
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class ReceiptLean4Corpus(TypedDict, total=False):
|
|
185
|
+
"""One Lean4 corpus citation. Multiple per Receipt.
|
|
186
|
+
|
|
187
|
+
Cites a machine-checked theorem corpus that backs a claim made by
|
|
188
|
+
this Receipt. The Golden Path threads two corpora through every
|
|
189
|
+
Receipt: ``aethel-nexus-proofs`` (29 theorems, 0 sorry, 0 axioms)
|
|
190
|
+
and ``mhed-toe-codex-v22`` (538 theorems, 0 sorry).
|
|
191
|
+
"""
|
|
192
|
+
name: str # e.g. "aethel-nexus-proofs"
|
|
193
|
+
repo_url: str
|
|
194
|
+
ref_sha: str # commit SHA the Receipt cites
|
|
195
|
+
theorem_count: int
|
|
196
|
+
sorry_count: int # MUST be 0 for an attesting Receipt
|
|
197
|
+
axiom_count: int # SHOULD be 0; record exceptions
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class ReceiptLean4Attestation(TypedDict, total=False):
|
|
201
|
+
"""Lean4 attestation block (Lane A; cited in CS-1 at Lane F W16)."""
|
|
202
|
+
corpora: list[ReceiptLean4Corpus]
|
|
203
|
+
total_theorems: int # sum across corpora — denormalized for ergonomics
|
|
204
|
+
summary: str # human-readable one-liner
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class ReceiptLineage(TypedDict, total=False):
|
|
208
|
+
"""Pointer to the Vanguard structural-change ledger entry (Lane A W4).
|
|
209
|
+
|
|
210
|
+
``lineage_path`` is opaque to the Receipt — Vanguard owns the
|
|
211
|
+
schema. A consumer can dereference it via the AAAA-Nexus
|
|
212
|
+
``/v1/forge/lineage`` endpoint to fetch the full chain.
|
|
213
|
+
"""
|
|
214
|
+
lineage_path: str # opaque ledger path / URL
|
|
215
|
+
parent_receipt_hash: str | None # SHA-256 of the immediately prior Receipt
|
|
216
|
+
chain_depth: int # 1 for first Receipt; n+1 for each successor
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class ReceiptPolyglotBreakdown(TypedDict, total=False):
|
|
220
|
+
"""v1.1 — per-language file + symbol counts (Lane A W8 seed).
|
|
221
|
+
|
|
222
|
+
Forward-compat: v0.4+ will add per-language certify scores; today
|
|
223
|
+
we only ship the file + symbol breakdown so consumers can render
|
|
224
|
+
'this repo is 80% Python, 15% TypeScript' badges from the Receipt
|
|
225
|
+
alone (no separate scout call needed).
|
|
226
|
+
"""
|
|
227
|
+
file_count: int
|
|
228
|
+
languages: dict[str, int] # {lang: file_count}
|
|
229
|
+
symbol_count: int
|
|
230
|
+
symbols_by_language: dict[str, int] # {lang: symbol_count}
|
|
231
|
+
primary_language: str
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class ForgeReceiptV1(TypedDict, total=False):
|
|
235
|
+
"""The Forge Receipt v1.0 wire format.
|
|
236
|
+
|
|
237
|
+
Required fields (every emitter MUST populate):
|
|
238
|
+
* schema_version
|
|
239
|
+
* generated_at_utc
|
|
240
|
+
* forge_version
|
|
241
|
+
* verdict
|
|
242
|
+
* project
|
|
243
|
+
* certify
|
|
244
|
+
* wire
|
|
245
|
+
* scout
|
|
246
|
+
|
|
247
|
+
Optional fields (filled in by W2+ infrastructure or omitted in
|
|
248
|
+
cheap-mode emission):
|
|
249
|
+
* assimilate — present only on --apply runs
|
|
250
|
+
* artifacts — pointers to .atomadic-forge/*.json files
|
|
251
|
+
* signatures — Sigstore + AAAA-Nexus claims
|
|
252
|
+
* lean4_attestation — corpus citations
|
|
253
|
+
* lineage — Vanguard ledger pointer (W4)
|
|
254
|
+
* compliance_mappings — populated at Lane F W18
|
|
255
|
+
* extra — escape hatch for forward-compat fields
|
|
256
|
+
|
|
257
|
+
Reserved (will be required in future minor versions):
|
|
258
|
+
* polyglot_breakdown — v1.1 (Lane A W8)
|
|
259
|
+
* slsa_attestation — v1.2 (Lane A W12)
|
|
260
|
+
* bao_rompf_witnesses — v2.0 (Lane A W24)
|
|
261
|
+
"""
|
|
262
|
+
# ---- required (v1.0) ----
|
|
263
|
+
schema_version: str
|
|
264
|
+
generated_at_utc: str # YYYY-MM-DDTHH:MM:SSZ
|
|
265
|
+
forge_version: str
|
|
266
|
+
verdict: ReceiptVerdict
|
|
267
|
+
project: ReceiptProject
|
|
268
|
+
certify: ReceiptCertify
|
|
269
|
+
wire: ReceiptWire
|
|
270
|
+
scout: ReceiptScout
|
|
271
|
+
# ---- optional (v1.0) ----
|
|
272
|
+
assimilate_digest: str | None
|
|
273
|
+
artifacts: list[ReceiptArtifact]
|
|
274
|
+
signatures: ReceiptSignatures
|
|
275
|
+
lean4_attestation: ReceiptLean4Attestation
|
|
276
|
+
lineage: ReceiptLineage
|
|
277
|
+
compliance_mappings: dict[str, str] # mapping_name → status
|
|
278
|
+
notes: list[str] # free-form human-facing notes
|
|
279
|
+
extra: dict[str, object] # forward-compat escape hatch
|
|
280
|
+
# v1.1 (Lane A W8) — optional in v1.0, required in v1.1+:
|
|
281
|
+
polyglot_breakdown: ReceiptPolyglotBreakdown
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
REQUIRED_RECEIPT_V1_FIELDS: tuple[str, ...] = (
|
|
285
|
+
"schema_version",
|
|
286
|
+
"generated_at_utc",
|
|
287
|
+
"forge_version",
|
|
288
|
+
"verdict",
|
|
289
|
+
"project",
|
|
290
|
+
"certify",
|
|
291
|
+
"wire",
|
|
292
|
+
"scout",
|
|
293
|
+
)
|
|
294
|
+
"""The minimum field set every v1 Receipt MUST populate.
|
|
295
|
+
|
|
296
|
+
Used by ``a1_at_functions/receipt_emitter.py`` to short-circuit
|
|
297
|
+
emission and by tests to guard against silent drift. The list itself
|
|
298
|
+
is part of the schema contract — adding a required field is a major
|
|
299
|
+
version bump.
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
VALID_VERDICTS: tuple[str, ...] = ("PASS", "FAIL", "REFINE", "QUARANTINE")
|
|
304
|
+
"""The four UEP v20 verdict values. Every Receipt's ``verdict`` must
|
|
305
|
+
be exactly one of these strings.
|
|
306
|
+
|
|
307
|
+
PASS — wire PASS AND certify ≥ threshold
|
|
308
|
+
FAIL — at least one structural gate failed
|
|
309
|
+
REFINE — incomplete; re-plan and re-emit
|
|
310
|
+
QUARANTINE — pause; needs human audit
|
|
311
|
+
"""
|