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,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
+ """