atomadic-forge 0.5.2__tar.gz → 0.6.0__tar.gz

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 (291) hide show
  1. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/CHANGELOG.md +112 -0
  2. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/PKG-INFO +15 -8
  3. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/README.md +14 -7
  4. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/AGENTS_GUIDE.md +2 -2
  5. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/COMMANDS.md +2 -2
  6. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/FIRST_10_MINUTES.md +1 -1
  7. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/pyproject.toml +1 -1
  8. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/__init__.py +1 -1
  9. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a0_qk_constants/auth_constants.py +0 -1
  10. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/agent_plan_emitter.py +2 -1
  11. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/certify_checks.py +73 -3
  12. atomadic_forge-0.6.0/src/atomadic_forge/a1_at_functions/code_signature.py +197 -0
  13. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/exported_api_check.py +2 -2
  14. atomadic_forge-0.6.0/src/atomadic_forge/a1_at_functions/intent_similarity.py +113 -0
  15. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/llm_client.py +6 -5
  16. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/mcp_protocol.py +5 -1
  17. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/provider_resolver.py +3 -1
  18. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/recipes.py +67 -0
  19. atomadic_forge-0.6.0/src/atomadic_forge/a1_at_functions/research_note_distiller.py +95 -0
  20. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/test_runner.py +23 -25
  21. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/trust_gate_response.py +2 -2
  22. atomadic_forge-0.6.0/src/atomadic_forge/a2_mo_composites/cost_circuit_breaker.py +266 -0
  23. atomadic_forge-0.6.0/src/atomadic_forge/a2_mo_composites/cross_agent_intent_deduplicator.py +69 -0
  24. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a2_mo_composites/forge_auth_client.py +0 -1
  25. atomadic_forge-0.6.0/src/atomadic_forge/a2_mo_composites/hierarchical_memory.py +307 -0
  26. atomadic_forge-0.6.0/src/atomadic_forge/a3_og_features/agent_hire_protocol.py +228 -0
  27. atomadic_forge-0.6.0/src/atomadic_forge/a3_og_features/dedup_engine.py +220 -0
  28. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a4_sy_orchestration/copilots_cmd.py +5 -5
  29. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge.egg-info/PKG-INFO +15 -8
  30. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge.egg-info/SOURCES.txt +13 -0
  31. atomadic_forge-0.6.0/tests/test_agent_hire_protocol.py +99 -0
  32. atomadic_forge-0.6.0/tests/test_cost_circuit_breaker.py +95 -0
  33. atomadic_forge-0.6.0/tests/test_dedup_engine.py +155 -0
  34. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_exported_api_check.py +8 -4
  35. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_forge_auth_a1.py +0 -1
  36. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_forge_auth_a2.py +1 -2
  37. atomadic_forge-0.6.0/tests/test_hierarchical_memory.py +90 -0
  38. atomadic_forge-0.6.0/tests/test_ling_provider.py +36 -0
  39. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_trust_gate_response.py +4 -2
  40. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  41. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  42. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  43. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/.github/actions/forge-action/README.md +0 -0
  44. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/.github/actions/forge-action/action.yml +0 -0
  45. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/.github/dependabot.yml +0 -0
  46. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/.github/pull_request_template.md +0 -0
  47. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/.github/workflows/ci.yml +0 -0
  48. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/.github/workflows/customer-refactor.yml +0 -0
  49. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/.github/workflows/forge-self-certify.yml +0 -0
  50. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/.github/workflows/forge-studio-ci.yml +0 -0
  51. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/.github/workflows/release.yml +0 -0
  52. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/ARCHITECTURE.md +0 -0
  53. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/CONTRIBUTING.md +0 -0
  54. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/LICENSE +0 -0
  55. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/MANIFEST.in +0 -0
  56. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/SECURITY.md +0 -0
  57. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/assets/Atomadic-Forge-01.png +0 -0
  58. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/01-getting-started.md +0 -0
  59. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/02-commands.md +0 -0
  60. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/03-tutorial.md +0 -0
  61. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/04-llm-loops.md +0 -0
  62. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/05-faq.md +0 -0
  63. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/AIR_GAPPED.md +0 -0
  64. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/CI_CD.md +0 -0
  65. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/CODEX_WALKTHROUGH.md +0 -0
  66. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/FORMALIZATION.md +0 -0
  67. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/LANDSCAPE.md +0 -0
  68. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/MARKET_POSITIONING.md +0 -0
  69. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/MULTI_REPO.md +0 -0
  70. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/README.md +0 -0
  71. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/RECEIPT.md +0 -0
  72. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/RELEASE_CHECKLIST.md +0 -0
  73. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/RELEASE_MESSAGING.md +0 -0
  74. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/ROADMAP.md +0 -0
  75. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/SHOWCASE.md +0 -0
  76. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/SIDECAR.md +0 -0
  77. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/STUDIO.md +0 -0
  78. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/WHY_NOW.md +0 -0
  79. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/commands/INDEX.md +0 -0
  80. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/commands/chat.md +0 -0
  81. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/commands/commandsmith.md +0 -0
  82. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/commands/config.md +0 -0
  83. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/commands/demo.md +0 -0
  84. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/commands/emergent-then-synergy.md +0 -0
  85. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/commands/emergent.md +0 -0
  86. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/commands/evolve-then-iterate.md +0 -0
  87. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/commands/evolve.md +0 -0
  88. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/commands/feature-then-emergent.md +0 -0
  89. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/commands/iterate.md +0 -0
  90. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/commands/synergy-then-emergent.md +0 -0
  91. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/commands/synergy.md +0 -0
  92. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/compliance/CMMC_AI_MAPPING.md +0 -0
  93. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/compliance/CS-1.md +0 -0
  94. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/compliance/EU_AI_ACT_ANNEX_IV.md +0 -0
  95. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/compliance/FDA_PCCP_MAPPING.md +0 -0
  96. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/compliance/SR_11-7_MAPPING.md +0 -0
  97. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/tutorials/01-quickstart.md +0 -0
  98. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/tutorials/02-your-first-package.md +0 -0
  99. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/tutorials/03-the-five-tier-law.md +0 -0
  100. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/tutorials/04-plug-in-llms.md +0 -0
  101. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/tutorials/05-multi-repo-absorb.md +0 -0
  102. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/docs/tutorials/06-javascript-quickstart.md +0 -0
  103. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/setup.cfg +0 -0
  104. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/__main__.py +0 -0
  105. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a0_qk_constants/__init__.py +0 -0
  106. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a0_qk_constants/agent_plan_schema.py +0 -0
  107. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a0_qk_constants/commandsmith_types.py +0 -0
  108. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a0_qk_constants/config_defaults.py +0 -0
  109. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a0_qk_constants/emergent_types.py +0 -0
  110. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a0_qk_constants/error_codes.py +0 -0
  111. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a0_qk_constants/forge_types.py +0 -0
  112. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a0_qk_constants/gen_language.py +0 -0
  113. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a0_qk_constants/lang_extensions.py +0 -0
  114. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a0_qk_constants/policy_schema.py +0 -0
  115. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a0_qk_constants/receipt_schema.py +0 -0
  116. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a0_qk_constants/roi_constants.py +0 -0
  117. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a0_qk_constants/semantic_types.py +0 -0
  118. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a0_qk_constants/sidecar_schema.py +0 -0
  119. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a0_qk_constants/synergy_types.py +0 -0
  120. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a0_qk_constants/tier_names.py +0 -0
  121. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/__init__.py +0 -0
  122. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/agent_context_pack.py +0 -0
  123. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/agent_memory.py +0 -0
  124. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/agent_summary.py +0 -0
  125. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/body_extractor.py +0 -0
  126. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/card_renderer.py +0 -0
  127. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/chat_context.py +0 -0
  128. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/cherry_pick.py +0 -0
  129. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/classify_tier.py +0 -0
  130. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/commandsmith_discover.py +0 -0
  131. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/commandsmith_render.py +0 -0
  132. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/compiler_feedback.py +0 -0
  133. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/compliance_checker.py +0 -0
  134. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/config_io.py +0 -0
  135. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/cs1_renderer.py +0 -0
  136. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/doc_synthesizer.py +0 -0
  137. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/emergent_compose.py +0 -0
  138. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/emergent_rank.py +0 -0
  139. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/emergent_signature_extract.py +0 -0
  140. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/emergent_synthesize.py +0 -0
  141. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/enforce_planner.py +0 -0
  142. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/error_hints.py +0 -0
  143. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/evolution_log.py +0 -0
  144. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/forge_auth.py +0 -0
  145. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/forge_feedback.py +0 -0
  146. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/generation_quality.py +0 -0
  147. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/import_repair.py +0 -0
  148. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/import_smoke.py +0 -0
  149. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/js_parser.py +0 -0
  150. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/lineage_chain.py +0 -0
  151. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/lineage_reader.py +0 -0
  152. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/local_signer.py +0 -0
  153. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/lsp_protocol.py +0 -0
  154. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/manifest_diff.py +0 -0
  155. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/patch_scorer.py +0 -0
  156. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/plan_adapter.py +0 -0
  157. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/policy_loader.py +0 -0
  158. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/preflight_change.py +0 -0
  159. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/progress_reporter.py +0 -0
  160. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/provider_detect.py +0 -0
  161. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/receipt_emitter.py +0 -0
  162. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/repo_explainer.py +0 -0
  163. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/roi_calculator.py +0 -0
  164. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/rollback_planner.py +0 -0
  165. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/sbom_emitter.py +0 -0
  166. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/scaffold_js.py +0 -0
  167. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/scaffold_pyproject.py +0 -0
  168. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/scaffold_starter.py +0 -0
  169. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/scout_walk.py +0 -0
  170. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/sidecar_parser.py +0 -0
  171. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/sidecar_validator.py +0 -0
  172. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/stub_detector.py +0 -0
  173. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/synergy_detect.py +0 -0
  174. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/synergy_render.py +0 -0
  175. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/synergy_surface_extract.py +0 -0
  176. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/test_selector.py +0 -0
  177. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/tier_init_rebuild.py +0 -0
  178. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/tool_composer.py +0 -0
  179. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/transcript_log.py +0 -0
  180. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/validation_commands.py +0 -0
  181. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a1_at_functions/wire_check.py +0 -0
  182. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a2_mo_composites/__init__.py +0 -0
  183. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a2_mo_composites/lineage_chain_store.py +0 -0
  184. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a2_mo_composites/manifest_store.py +0 -0
  185. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a2_mo_composites/plan_store.py +0 -0
  186. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a2_mo_composites/receipt_signer.py +0 -0
  187. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a3_og_features/__init__.py +0 -0
  188. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a3_og_features/commandsmith_feature.py +0 -0
  189. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a3_og_features/demo_packages/mixed_py_js/src/mixed_pkg/__init__.py +0 -0
  190. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a3_og_features/demo_packages/mixed_py_js/src/mixed_pkg/a0_qk_constants/__init__.py +0 -0
  191. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a3_og_features/demo_packages/mixed_py_js/src/mixed_pkg/a1_at_functions/__init__.py +0 -0
  192. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a3_og_features/demo_packages/mixed_py_js/tests/conftest.py +0 -0
  193. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a3_og_features/demo_packages/mixed_py_js/tests/test_mixed.py +0 -0
  194. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a3_og_features/demo_runner.py +0 -0
  195. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a3_og_features/emergent_feature.py +0 -0
  196. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a3_og_features/emergent_pipeline_integration.py +0 -0
  197. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a3_og_features/forge_enforce.py +0 -0
  198. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a3_og_features/forge_evolve.py +0 -0
  199. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a3_og_features/forge_loop.py +0 -0
  200. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a3_og_features/forge_pipeline.py +0 -0
  201. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a3_og_features/forge_plan_apply.py +0 -0
  202. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a3_og_features/lsp_server.py +0 -0
  203. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a3_og_features/mcp_server.py +0 -0
  204. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a3_og_features/setup_wizard.py +0 -0
  205. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a3_og_features/synergy_feature.py +0 -0
  206. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a4_sy_orchestration/__init__.py +0 -0
  207. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a4_sy_orchestration/cli.py +0 -0
  208. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a4_sy_orchestration/login_cmd.py +0 -0
  209. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/a4_sy_orchestration/whoami_cmd.py +0 -0
  210. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/commands/__init__.py +0 -0
  211. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/commands/_registry.py +0 -0
  212. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/commands/audit.py +0 -0
  213. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/commands/chat.py +0 -0
  214. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/commands/commandsmith.py +0 -0
  215. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/commands/config_cmd.py +0 -0
  216. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/commands/demo.py +0 -0
  217. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/commands/emergent.py +0 -0
  218. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/commands/emergent_then_synergy.py +0 -0
  219. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/commands/evolve.py +0 -0
  220. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/commands/evolve_then_iterate.py +0 -0
  221. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/commands/feature_then_emergent.py +0 -0
  222. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/commands/iterate.py +0 -0
  223. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/commands/synergy.py +0 -0
  224. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge/commands/synergy_then_emergent.py +0 -0
  225. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge.egg-info/dependency_links.txt +0 -0
  226. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge.egg-info/entry_points.txt +0 -0
  227. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge.egg-info/requires.txt +0 -0
  228. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/src/atomadic_forge.egg-info/top_level.txt +0 -0
  229. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_aaaa_nexus_client.py +0 -0
  230. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_agent_plan.py +0 -0
  231. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_agent_summary.py +0 -0
  232. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_audit_verb.py +0 -0
  233. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_badge_worker.py +0 -0
  234. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_body_extractor_repairs.py +0 -0
  235. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_card_renderer.py +0 -0
  236. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_certify_operational_axis.py +0 -0
  237. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_chat.py +0 -0
  238. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_classify_tier.py +0 -0
  239. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_cli_smoke.py +0 -0
  240. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_codex_5_complete.py +0 -0
  241. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_codex_6_enforce_polyglot.py +0 -0
  242. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_commandsmith.py +0 -0
  243. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_compiler_feedback.py +0 -0
  244. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_compliance_checker.py +0 -0
  245. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_config.py +0 -0
  246. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_copilots_copilot.py +0 -0
  247. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_cs1_renderer.py +0 -0
  248. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_demo.py +0 -0
  249. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_emergent_compose.py +0 -0
  250. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_emergent_signature_extract.py +0 -0
  251. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_error_codes.py +0 -0
  252. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_error_hints.py +0 -0
  253. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_evolve_js.py +0 -0
  254. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_forge_action.py +0 -0
  255. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_forge_enforce.py +0 -0
  256. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_generation_quality.py +0 -0
  257. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_ignore_and_docs.py +0 -0
  258. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_import_smoke.py +0 -0
  259. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_iterate_evolve.py +0 -0
  260. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_js_certify.py +0 -0
  261. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_js_parser.py +0 -0
  262. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_js_recon.py +0 -0
  263. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_js_wire.py +0 -0
  264. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_lineage_chain.py +0 -0
  265. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_local_signer.py +0 -0
  266. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_lsp_protocol.py +0 -0
  267. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_manifest_diff.py +0 -0
  268. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_mcp_protocol.py +0 -0
  269. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_ollama_client.py +0 -0
  270. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_pipeline.py +0 -0
  271. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_plan_apply.py +0 -0
  272. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_pre_audit_smoke.py +0 -0
  273. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_precommit_hooks.py +0 -0
  274. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_progress_reporter.py +0 -0
  275. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_receipt_emitter.py +0 -0
  276. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_receipt_schema.py +0 -0
  277. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_receipt_signer.py +0 -0
  278. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_roi_calculator.py +0 -0
  279. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_sbom_emitter.py +0 -0
  280. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_scaffold.py +0 -0
  281. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_sidecar.py +0 -0
  282. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_sidecar_validate.py +0 -0
  283. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_stagnation.py +0 -0
  284. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_stub_detector.py +0 -0
  285. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_synergy.py +0 -0
  286. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_test_runner.py +0 -0
  287. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_tier_init_rebuild.py +0 -0
  288. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_vscode_extension_manifest.py +0 -0
  289. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_whoami_cmd.py +0 -0
  290. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_wire_certify.py +0 -0
  291. {atomadic_forge-0.5.2 → atomadic_forge-0.6.0}/tests/test_wire_suggest_repairs.py +0 -0
@@ -1,5 +1,117 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0 - Frontier features: dedup, budget, memory, swarm hiring, Ling-2.6-1T
4
+
5
+ Major minor — five new capabilities cherry-picked from forge-deluxe-seed
6
+ plus a frontier free-tier LLM, plus three MCP bug fixes and agent-optimized
7
+ certify output.
8
+
9
+ ### Added — capabilities
10
+
11
+ - **`dedup_engine`** — orchestrates `intent_similarity` + `code_signature`
12
+ + `research_note_distiller` to catch duplicate research notes AND
13
+ duplicate code logic at the gate. The "never reinvent the wheel"
14
+ primitive, ported with 13 tests.
15
+
16
+ - **`cost_circuit_breaker`** — multi-tier (per-task / per-session /
17
+ per-day) USD + token budget with hard-kill, soft-warn-at-80%, and
18
+ no-progress stuck detection. Defaults match OpenHands
19
+ (MAX_ITERATIONS=100, $100/day). Closes the production-incident gap
20
+ ($47k loop, $30k loop) called out in cycle-14 SOTA research.
21
+
22
+ - **`hierarchical_memory`** — 4-tier MemGPT pattern (M0 working /
23
+ M1 core pinned / M2 episodic / M3 reflection) with Park-2023
24
+ recency × importance × relevance scoring. Pure stdlib sqlite3 —
25
+ air-gappable.
26
+
27
+ - **`agent_hire_protocol`** — 5-step swarm SOP (sealed-probe vetting
28
+ → trust gate → similarity check → contract → signed receipt) with
29
+ D_max=3 (ChatDev empirical limit) for safe multi-agent fanout.
30
+
31
+ - **`ling` / `--provider ling`** — wires Ling-2.6-1T (1T-param MoE,
32
+ 262K context, SOTA SWE-bench) via OpenRouter at the **free tier**.
33
+ Forge users now get a frontier model for iterate/evolve at zero
34
+ cost; OpenRouter default model also bumped from gemma-3-27b-it
35
+ to ling-2.6-1t.
36
+
37
+ ### Fixed
38
+
39
+ ### Fixed
40
+
41
+ - **P0 (critical)** `recon` MCP tool no longer overflows LLM context
42
+ windows. `symbols[]` (771 K chars on forge itself) is stripped from
43
+ the default response; pass `verbose: true` to get the full walk.
44
+ `symbol_count` remains so agents still see cardinality inline.
45
+
46
+ - **P1 (high)** `certify` behavioral score no longer reports `ran:
47
+ false, pass_ratio: 0.0` on repos using `xfailed`/`xpassed` pytest
48
+ status words. The `_parse_pytest_summary` parser now uses independent
49
+ per-metric regex patterns instead of a single monolithic regex that
50
+ choked on unknown status words between "passed" and "in Xs".
51
+ Atomadic-Lang's certify score recovers from 70 to 100.
52
+
53
+ - **P2 (high)** `auto_plan` verdict no longer reports `PASS` for repos
54
+ with score < 75 and no action cards. `not cards` alone was treated as
55
+ PASS regardless of score; now requires `score >= 75`. No-input plans
56
+ (score defaults to 0.0) correctly return `FAIL`.
57
+
58
+ ### Added — certify polish
59
+
60
+ - `certify` output now includes `health_summary` (score + verdict +
61
+ blockers + scan_duration_ms) — a single at-a-glance block for agent
62
+ loops that don't want to parse the full response.
63
+
64
+ - `certify` output now includes `axes` dict with per-axis `ok` bool and
65
+ `how_to_fix` string. When an axis fails, agents get an explicit action
66
+ ("Add README.md at the project root." / "Run forge wire src
67
+ --suggest-repairs -- N violation(s).") instead of just a flag.
68
+
69
+ - `certify` now reports `scan_duration_ms` at top level and inside
70
+ `health_summary` — agents doing performance budgeting can use this
71
+ for timeout planning.
72
+
73
+ - New recipe `bump_version` — checklist for patch/minor/major bumps:
74
+ edit pyproject.toml, update __version__, write CHANGELOG entry,
75
+ certify, commit, tag.
76
+
77
+ - New recipe `fix_test_detection` — debugging guide for when certify
78
+ reports `ran=false` or `pass_ratio=0` despite pytest passing locally:
79
+ traces xfailed parse failure, wrong-package import filter, and import
80
+ smoke failure paths.
81
+
82
+ ### Internal
83
+
84
+ - CI Ruff lint debt cleared: 50 errors → 0 across recent merges
85
+ (I001/F401/UP037/UP006/UP035 auto-fixed; UP038/B006/E701/E702/F841
86
+ manually). Lint now blocks regressions instead of being permanently red.
87
+
88
+ - Test count: 937 → 975 (+38) — every new capability shipped with
89
+ pinning tests; certify holds 100.0/100 across the lift.
90
+
91
+ - README provider matrix updated: Anthropic default → `claude-sonnet-4-6`,
92
+ OpenAI default → `gpt-4o-mini` (with override hints), `ling` row added.
93
+
94
+ - Live demo (`forge.atomadic.tech`) and invest links surfaced in README.
95
+
96
+ ---
97
+
98
+ ## 0.5.3 - Documentation metadata sync
99
+
100
+ Small follow-up to `0.5.2` so the published package description and
101
+ current-facing docs no longer advertise stale `0.3.x` version strings,
102
+ old test counts, or the pre-`0.5.2` MCP tool count.
103
+
104
+ ### Fixed
105
+
106
+ - README install and contributor snippets now show the current Forge
107
+ version family and `937 passed, 2 skipped`.
108
+ - Current ecosystem positioning now reflects the `0.5.3` PyPI line and
109
+ 23-tool MCP surface.
110
+ - First-run and VS Code extension docs now reference the current MCP
111
+ surface/version expectations.
112
+
113
+ ---
114
+
3
115
  ## 0.5.2 - Agent ergonomics and language-aware guidance
4
116
 
5
117
  Implements the first round of quality-of-life improvements from the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: atomadic-forge
3
- Version: 0.5.2
3
+ Version: 0.6.0
4
4
  Summary: Atomadic Forge — absorb, enforce, emerge. Polyglot (Python + JavaScript/TypeScript) architecture guardian for AI-generated code.
5
5
  Author: Atomadic
6
6
  License-Expression: BUSL-1.1
@@ -45,6 +45,8 @@ Dynamic: license-file
45
45
 
46
46
  > **Absorb. Enforce. Emerge.** The architecture substrate for AI-generated code — now polyglot (Python, JavaScript, TypeScript).
47
47
 
48
+ 🔗 **Try it live:** [forge.atomadic.tech](https://forge.atomadic.tech) — paste any GitHub repo and watch the analysis in real time.
49
+
48
50
  Forge is a monadic-architecture engine that does three things no existing
49
51
  tool combines:
50
52
 
@@ -185,7 +187,7 @@ Each tier is a layer of **verified building blocks**. Higher tiers never invent
185
187
 
186
188
  ```bash
187
189
  pip install atomadic-forge
188
- forge --version # atomadic-forge 0.3.2
190
+ forge --version # atomadic-forge 0.5.3
189
191
  forge doctor # environment check
190
192
  ```
191
193
 
@@ -198,7 +200,7 @@ generate).
198
200
  ```bash
199
201
  git clone https://github.com/atomadictech/atomadic-forge && cd atomadic-forge
200
202
  pip install -e ".[dev]"
201
- python -m pytest # 841 passing, 2 skipped
203
+ python -m pytest # 937 passed, 2 skipped
202
204
  ```
203
205
 
204
206
  ## AI Agent integration (MCP)
@@ -320,9 +322,10 @@ forge chat ask "hello" --provider stub --no-cwd-context --json
320
322
  |----------|------|---------|---------------|-------------|
321
323
  | `gemini` | **free tier** | `GEMINI_API_KEY` / `GOOGLE_API_KEY` | `gemini-2.5-flash` | Best free cloud option; override with `FORGE_GEMINI_MODEL` |
322
324
  | `nexus` / `aaaa-nexus` | paid | `AAAA_NEXUS_API_KEY` | (Nexus default) | AAAA-Nexus sovereign AI; most reliable for long runs |
323
- | `anthropic` | paid | `ANTHROPIC_API_KEY` | `claude-3-5-sonnet-latest` | Highest code quality |
324
- | `openai` | paid | `OPENAI_API_KEY` | `gpt-4o-mini` | Cheap GPT path |
325
- | `openrouter` | **free tier available** | `OPENROUTER_API_KEY` | `google/gemma-3-27b-it:free` | Access 200+ models; good fallback when Gemini quota exhausted; override with `FORGE_OPENROUTER_MODEL` |
325
+ | `anthropic` | paid | `ANTHROPIC_API_KEY` | `claude-sonnet-4-6` | Highest code quality (Claude 4.x; override with `claude-opus-4-7` for max reasoning or `claude-haiku-4-5-20251001` for speed) |
326
+ | `openai` | paid | `OPENAI_API_KEY` | `gpt-4o-mini` | Cheap GPT path; override to `gpt-4.1` or `gpt-4o` for higher quality |
327
+ | `openrouter` | **free tier available** | `OPENROUTER_API_KEY` | `inclusionai/ling-2.6-1t:free` | Access 200+ models; good fallback when Gemini quota exhausted; override with `FORGE_OPENROUTER_MODEL` |
328
+ | `ling` | **free** | `OPENROUTER_API_KEY` | `inclusionai/ling-2.6-1t:free` | Shortcut for Ling-2.6-1T (1T-param MoE, 262K ctx, SOTA SWE-bench) — frontier model at zero cost via OpenRouter |
326
329
  | `ollama` | free, local | `FORGE_OLLAMA=1` | `qwen2.5-coder:7b` | Offline; fully private |
327
330
  | `stub` | free, offline | n/a | n/a | Tests, CI, dry-runs |
328
331
 
@@ -449,7 +452,7 @@ Forge ships with named limits. No overpromise.
449
452
  | Product | What it is | Status |
450
453
  |---------|------------|--------|
451
454
  | **AAAA-Nexus** | Trust/safety/payments substrate for autonomous agents | Live at [atomadic.tech](https://atomadic.tech) |
452
- | **Atomadic Forge** | Absorb-and-emerge engine for developers (this repo) | **0.3.2** — on PyPI, 841 tests, 100/100, MCP server, desktop GUI |
455
+ | **Atomadic Forge** | Absorb-and-emerge engine for developers (this repo) | **0.5.3** — on PyPI, 937 tests, 100/100, 23-tool MCP server, desktop GUI |
453
456
  | **Atomadic Assistant** | Sovereign AI assistant with cognitive loop on Cloudflare | In development |
454
457
 
455
458
  ## License
@@ -477,7 +480,7 @@ Apache 2.0.
477
480
  **Forge itself is monadic.** Every source file belongs to one tier. The repo is a worked example:
478
481
 
479
482
  ```bash
480
- python -m pytest # 841 passing, 2 skipped
483
+ python -m pytest # 937 passed, 2 skipped
481
484
  forge doctor # Environment check
482
485
  forge wire src/atomadic_forge # Scan for violations (PASS)
483
486
  forge certify . --fail-under 100 # Score and gate the repo (100/100)
@@ -508,3 +511,7 @@ forge commandsmith smoke # Smoke-test all 36+ registered verbs
508
511
  - ✓ **Cloudflare badge worker** — live certify score in any README
509
512
  - ✗ Chain-of-custody notarization (future)
510
513
  - ✗ Rust / Go tier classification (roadmap)
514
+
515
+ ---
516
+
517
+ 💰 **Interested in investing?** [invest.atomadic.tech](https://invest.atomadic.tech) — learn about the Atomadic Technologies ecosystem.
@@ -13,6 +13,8 @@
13
13
 
14
14
  > **Absorb. Enforce. Emerge.** The architecture substrate for AI-generated code — now polyglot (Python, JavaScript, TypeScript).
15
15
 
16
+ 🔗 **Try it live:** [forge.atomadic.tech](https://forge.atomadic.tech) — paste any GitHub repo and watch the analysis in real time.
17
+
16
18
  Forge is a monadic-architecture engine that does three things no existing
17
19
  tool combines:
18
20
 
@@ -153,7 +155,7 @@ Each tier is a layer of **verified building blocks**. Higher tiers never invent
153
155
 
154
156
  ```bash
155
157
  pip install atomadic-forge
156
- forge --version # atomadic-forge 0.3.2
158
+ forge --version # atomadic-forge 0.5.3
157
159
  forge doctor # environment check
158
160
  ```
159
161
 
@@ -166,7 +168,7 @@ generate).
166
168
  ```bash
167
169
  git clone https://github.com/atomadictech/atomadic-forge && cd atomadic-forge
168
170
  pip install -e ".[dev]"
169
- python -m pytest # 841 passing, 2 skipped
171
+ python -m pytest # 937 passed, 2 skipped
170
172
  ```
171
173
 
172
174
  ## AI Agent integration (MCP)
@@ -288,9 +290,10 @@ forge chat ask "hello" --provider stub --no-cwd-context --json
288
290
  |----------|------|---------|---------------|-------------|
289
291
  | `gemini` | **free tier** | `GEMINI_API_KEY` / `GOOGLE_API_KEY` | `gemini-2.5-flash` | Best free cloud option; override with `FORGE_GEMINI_MODEL` |
290
292
  | `nexus` / `aaaa-nexus` | paid | `AAAA_NEXUS_API_KEY` | (Nexus default) | AAAA-Nexus sovereign AI; most reliable for long runs |
291
- | `anthropic` | paid | `ANTHROPIC_API_KEY` | `claude-3-5-sonnet-latest` | Highest code quality |
292
- | `openai` | paid | `OPENAI_API_KEY` | `gpt-4o-mini` | Cheap GPT path |
293
- | `openrouter` | **free tier available** | `OPENROUTER_API_KEY` | `google/gemma-3-27b-it:free` | Access 200+ models; good fallback when Gemini quota exhausted; override with `FORGE_OPENROUTER_MODEL` |
293
+ | `anthropic` | paid | `ANTHROPIC_API_KEY` | `claude-sonnet-4-6` | Highest code quality (Claude 4.x; override with `claude-opus-4-7` for max reasoning or `claude-haiku-4-5-20251001` for speed) |
294
+ | `openai` | paid | `OPENAI_API_KEY` | `gpt-4o-mini` | Cheap GPT path; override to `gpt-4.1` or `gpt-4o` for higher quality |
295
+ | `openrouter` | **free tier available** | `OPENROUTER_API_KEY` | `inclusionai/ling-2.6-1t:free` | Access 200+ models; good fallback when Gemini quota exhausted; override with `FORGE_OPENROUTER_MODEL` |
296
+ | `ling` | **free** | `OPENROUTER_API_KEY` | `inclusionai/ling-2.6-1t:free` | Shortcut for Ling-2.6-1T (1T-param MoE, 262K ctx, SOTA SWE-bench) — frontier model at zero cost via OpenRouter |
294
297
  | `ollama` | free, local | `FORGE_OLLAMA=1` | `qwen2.5-coder:7b` | Offline; fully private |
295
298
  | `stub` | free, offline | n/a | n/a | Tests, CI, dry-runs |
296
299
 
@@ -417,7 +420,7 @@ Forge ships with named limits. No overpromise.
417
420
  | Product | What it is | Status |
418
421
  |---------|------------|--------|
419
422
  | **AAAA-Nexus** | Trust/safety/payments substrate for autonomous agents | Live at [atomadic.tech](https://atomadic.tech) |
420
- | **Atomadic Forge** | Absorb-and-emerge engine for developers (this repo) | **0.3.2** — on PyPI, 841 tests, 100/100, MCP server, desktop GUI |
423
+ | **Atomadic Forge** | Absorb-and-emerge engine for developers (this repo) | **0.5.3** — on PyPI, 937 tests, 100/100, 23-tool MCP server, desktop GUI |
421
424
  | **Atomadic Assistant** | Sovereign AI assistant with cognitive loop on Cloudflare | In development |
422
425
 
423
426
  ## License
@@ -445,7 +448,7 @@ Apache 2.0.
445
448
  **Forge itself is monadic.** Every source file belongs to one tier. The repo is a worked example:
446
449
 
447
450
  ```bash
448
- python -m pytest # 841 passing, 2 skipped
451
+ python -m pytest # 937 passed, 2 skipped
449
452
  forge doctor # Environment check
450
453
  forge wire src/atomadic_forge # Scan for violations (PASS)
451
454
  forge certify . --fail-under 100 # Score and gate the repo (100/100)
@@ -476,3 +479,7 @@ forge commandsmith smoke # Smoke-test all 36+ registered verbs
476
479
  - ✓ **Cloudflare badge worker** — live certify score in any README
477
480
  - ✗ Chain-of-custody notarization (future)
478
481
  - ✗ Rust / Go tier classification (roadmap)
482
+
483
+ ---
484
+
485
+ 💰 **Interested in investing?** [invest.atomadic.tech](https://invest.atomadic.tech) — learn about the Atomadic Technologies ecosystem.
@@ -58,7 +58,7 @@ Or against the installed CLI:
58
58
 
59
59
  ```bash
60
60
  $ forge --version
61
- atomadic-forge 0.5.2
61
+ atomadic-forge 0.5.3
62
62
  ```
63
63
 
64
64
  The `--version` flag (and `-V`) is the canonical "Forge is wired in
@@ -368,7 +368,7 @@ Forge's normative answer; only the *attestation chain* is missing.
368
368
  | Symptom | Likely cause | Fix |
369
369
  |---|---|---|
370
370
  | Tools list returns 0 tools | MCP client didn't pass `--project .` | Check args in MCP config |
371
- | Tools list returns < 23 (e.g. 21 or 10) | Old `forge` install | `pip install -U atomadic-forge` (or `pip install -e .` from the repo). Pin `forge --version >= 0.5.2`. |
371
+ | Tools list returns < 23 (e.g. 21 or 10) | Old `forge` install | `pip install -U atomadic-forge` (or `pip install -e .` from the repo). Pin `forge --version >= 0.5.3`. |
372
372
  | `wire` says PASS but agent still hits import errors | Agent is running tests in a different working directory | `--project` should match the test cwd |
373
373
  | `certify` returns score 90 instead of 100 | Project has no `.github/workflows/` and no `CHANGELOG.md` (operational axis is 0) | Add either; both are 5pts |
374
374
  | Receipt's `signatures` is null | `AAAA_NEXUS_API_KEY` not set, or AAAA-Nexus endpoint not yet shipped | Soft-fail behavior — Receipt is still valid for local use |
@@ -1,6 +1,6 @@
1
1
  # Atomadic Forge — Command Reference
2
2
 
3
- All verbs available in the `forge` CLI as of 0.5.2.
3
+ All verbs available in the `forge` CLI as of 0.5.3.
4
4
 
5
5
  ## Pipeline / absorption
6
6
 
@@ -419,7 +419,7 @@ Prints the installed Forge version and exits 0. Hardened in v0.3.0
419
419
 
420
420
  ```bash
421
421
  $ forge --version
422
- atomadic-forge 0.5.2
422
+ atomadic-forge 0.5.3
423
423
  ```
424
424
 
425
425
  ### `.forge` sidecars — Lane D W8
@@ -185,7 +185,7 @@ You now know enough to use Forge productively. For specific topics:
185
185
  - [tutorials/](tutorials/) — quickstart tutorials and the JS / TS path.
186
186
  - **[AGENTS_GUIDE.md](AGENTS_GUIDE.md)** — using Forge from a coding
187
187
  agent (Cursor / Claude Code / Aider / Devin / Copilot / Codex) via
188
- the built-in MCP server. One config snippet, **21 tools**, 5
188
+ the built-in MCP server. One config snippet, **23 tools**, 5
189
189
  resources. The fastest way to put Forge in front of every PR your
190
190
  team's agents touch.
191
191
  - [MULTI_REPO.md](MULTI_REPO.md) — absorbing more than one repo at once.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "atomadic-forge"
7
- version = "0.5.2"
7
+ version = "0.6.0"
8
8
  description = "Atomadic Forge — absorb, enforce, emerge. Polyglot (Python + JavaScript/TypeScript) architecture guardian for AI-generated code."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -9,4 +9,4 @@ Public version surface only — every symbol lives in its tier package and
9
9
  is re-exported lazily by the CLI.
10
10
  """
11
11
 
12
- __version__ = "0.5.2"
12
+ __version__ = "0.6.0"
@@ -26,7 +26,6 @@ from __future__ import annotations
26
26
 
27
27
  from typing import TypedDict
28
28
 
29
-
30
29
  # ---- endpoints ----------------------------------------------------------
31
30
 
32
31
  DEFAULT_AUTH_ENDPOINT = "https://forge-auth.atomadic.tech/v1/forge/auth/verify"
@@ -279,7 +279,8 @@ def emit_agent_plan(
279
279
  score = float((certify_report or {}).get("score", 0.0))
280
280
  if wire_report and wire_report.get("verdict") == "PASS" and score >= 100:
281
281
  verdict = "PASS"
282
- elif not cards:
282
+ elif not cards and score >= 75:
283
+ # No action cards AND score is acceptable -- genuinely passing.
283
284
  verdict = "PASS"
284
285
  else:
285
286
  verdict = "FAIL"
@@ -285,6 +285,7 @@ def check_changelog(root: Path) -> tuple[bool, dict]:
285
285
 
286
286
  def certify(root: Path, *, project: str = "Atomadic project",
287
287
  package: str | None = None) -> dict:
288
+ _scan_start = time.perf_counter()
288
289
  docs_ok, docs_d = check_documentation(root)
289
290
  tests_ok, tests_d = check_tests_present(root)
290
291
  layout_ok, layout_d = check_tier_layout(root, package)
@@ -358,12 +359,14 @@ def certify(root: Path, *, project: str = "Atomadic project",
358
359
  "(or run `forge auto` to scaffold them).")
359
360
  if not wire_ok:
360
361
  issues.append(f"Upward-import violations: {wire_d['violation_count']}")
361
- recs.append("Run `forge wire` to inspect violations, then move imports down-tier or split modules.")
362
+ recs.append("Run `forge wire` to inspect violations, then move imports "
363
+ "down-tier or split modules.")
362
364
  if not no_stubs:
363
365
  issues.append(f"Stub bodies detected: {len(stub_findings)} "
364
366
  "function(s) with `pass`/NotImplementedError/TODO")
365
367
  for f in stub_findings[:5]:
366
- issues.append(f" · {f['file']}:{f['lineno']} {f['qualname']} ({f['kind']})")
368
+ issues.append(f" · {f['file']}:{f['lineno']} "
369
+ f"{f['qualname']} ({f['kind']})")
367
370
  recs.append("Replace stub bodies with real implementations before shipping.")
368
371
  if smoke is not None and not importable:
369
372
  issues.append(f"Package fails to import: {smoke['error_kind']} — "
@@ -392,7 +395,7 @@ def certify(root: Path, *, project: str = "Atomadic project",
392
395
  # docs / layout / wire — 10 each (30 max — structural axis)
393
396
  # tests-present — 5 (structural axis)
394
397
  # importable runtime — 25 (runtime axis)
395
- # tests-pass-ratio — 30 max (behavioural axis — rewards actual behaviour)
398
+ # tests-pass-ratio — 30 max (behavioural axis)
396
399
  # ci workflow — 5 (operational axis)
397
400
  # changelog/release notes — 5 (operational axis)
398
401
  # stub-body penalty — up to 40 deducted
@@ -410,6 +413,9 @@ def certify(root: Path, *, project: str = "Atomadic project",
410
413
  + (5 if changelog_ok else 0)
411
414
  )
412
415
  score = max(0.0, float(structural + runtime + behavioral + operational) - stub_pen)
416
+ scan_duration_ms = int((time.perf_counter() - _scan_start) * 1000)
417
+ blockers = len([i for i in issues if not i.startswith(" ·")])
418
+ verdict = "PASS" if score >= 75 and blockers == 0 else "FAIL"
413
419
  return {
414
420
  "schema_version": "atomadic-forge.certify/v1",
415
421
  "project": project,
@@ -424,6 +430,12 @@ def certify(root: Path, *, project: str = "Atomadic project",
424
430
  "ci_workflow_present": ci_ok,
425
431
  "changelog_present": changelog_ok,
426
432
  "score": score,
433
+ "health_summary": {
434
+ "score": score,
435
+ "verdict": verdict,
436
+ "blockers": blockers,
437
+ "scan_duration_ms": scan_duration_ms,
438
+ },
427
439
  "score_components": {
428
440
  "structural": structural,
429
441
  "runtime": runtime,
@@ -433,6 +445,64 @@ def certify(root: Path, *, project: str = "Atomadic project",
433
445
  },
434
446
  "issues": issues,
435
447
  "recommendations": recs,
448
+ "axes": {
449
+ "documentation": {
450
+ "ok": docs_ok, "score_weight": 10,
451
+ "how_to_fix": ("Add README.md or docs/*.md files."
452
+ if not docs_ok else None),
453
+ },
454
+ "tests_present": {
455
+ "ok": tests_ok, "score_weight": 5,
456
+ "how_to_fix": ("Create tests/test_*.py with at least one test."
457
+ if not tests_ok else None),
458
+ },
459
+ "tier_layout": {
460
+ "ok": layout_ok, "score_weight": 10,
461
+ "how_to_fix": (
462
+ "Add 3+ of a0_qk_constants/ a1_at_functions/ "
463
+ "a2_mo_composites/ a3_og_features/ a4_sy_orchestration/."
464
+ ) if not layout_ok else None,
465
+ },
466
+ "wire_clean": {
467
+ "ok": wire_ok, "score_weight": 10,
468
+ "how_to_fix": (
469
+ f"Fix {wire_d['violation_count']} upward import(s): "
470
+ "run forge wire --suggest-repairs."
471
+ ) if not wire_ok else None,
472
+ },
473
+ "no_stubs": {
474
+ "ok": no_stubs, "score_weight": 0,
475
+ "how_to_fix": (
476
+ f"Replace {len(stub_findings)} stub bodies "
477
+ "(pass/NotImplementedError/TODO) with real code."
478
+ ) if not no_stubs else None,
479
+ },
480
+ "importable": {
481
+ "ok": importable, "score_weight": 25,
482
+ "how_to_fix": (
483
+ f"Fix import error: {smoke['error_kind']} — "
484
+ f"{smoke['error_message']}"
485
+ if smoke else "Package not importable."
486
+ ) if not importable else None,
487
+ },
488
+ "tests_pass": {
489
+ "ok": test_pass_ratio == 1.0, "score_weight": 30,
490
+ "how_to_fix": (
491
+ f"Fix {test_run['failed']} failing test(s)." if test_run
492
+ else "Run pytest to diagnose."
493
+ ) if test_pass_ratio < 1.0 else None,
494
+ },
495
+ "ci_workflow": {
496
+ "ok": ci_ok, "score_weight": 5,
497
+ "how_to_fix": ("Add .github/workflows/ci.yml."
498
+ if not ci_ok else None),
499
+ },
500
+ "changelog": {
501
+ "ok": changelog_ok, "score_weight": 5,
502
+ "how_to_fix": ("Add CHANGELOG.md (200+ bytes) at project root."
503
+ if not changelog_ok else None),
504
+ },
505
+ },
436
506
  "detail": {"docs": docs_d, "tests": tests_d, "layout": layout_d,
437
507
  "wire": wire_d,
438
508
  "ci": ci_d,
@@ -0,0 +1,197 @@
1
+ """Tier a1 - deterministic semantic fingerprint for Python source.
2
+
3
+ Pure stateless. Given a Python source string, returns stable hashes
4
+ that survive renames, whitespace changes, and comment edits but
5
+ break on real logic changes. The atomic primitive that makes
6
+ "never reinvent the wheel" enforceable: two modules with identical
7
+ function-shape signatures are duplicate logic.
8
+
9
+ PREVENT pillar - block duplicate emits at the gate before they ship.
10
+
11
+ Returned shape::
12
+
13
+ ModuleSignature(
14
+ schema="atomadic-forge.code-signature/v1",
15
+ module_hash="<hex>", # full-module shape hash
16
+ functions=[FunctionSignature(...), ...],
17
+ classes=[ClassSignature(...), ...],
18
+ imports=("os", "ast", ...), # sorted top-level imports
19
+ )
20
+
21
+ Two ModuleSignatures with the same ``module_hash`` are byte-stable
22
+ duplicates of each other's logic, regardless of identifier names
23
+ or formatting.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import ast
29
+ import hashlib
30
+ from dataclasses import dataclass, field
31
+
32
+ SCHEMA: str = "atomadic-forge.code-signature/v1"
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class FunctionSignature:
37
+ name: str
38
+ arg_count: int
39
+ is_async: bool
40
+ body_hash: str # hash of normalized AST (no identifiers)
41
+ calls: tuple[str, ...] # sorted set of names this function calls
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class ClassSignature:
46
+ name: str
47
+ method_count: int
48
+ has_state: bool # __init__ assigns to self.X
49
+ body_hash: str
50
+
51
+
52
+ @dataclass(frozen=True)
53
+ class ModuleSignature:
54
+ schema: str = SCHEMA
55
+ module_hash: str = ""
56
+ functions: tuple[FunctionSignature, ...] = field(default_factory=tuple)
57
+ classes: tuple[ClassSignature, ...] = field(default_factory=tuple)
58
+ imports: tuple[str, ...] = field(default_factory=tuple)
59
+ parse_ok: bool = True
60
+
61
+
62
+ def _normalize_ast(node: ast.AST) -> str:
63
+ """Walk an AST node and emit a string of just the structural
64
+ shape - no identifier names, no string literals, no docstrings.
65
+ Two functions with identical control flow + call shape get
66
+ identical normalized output regardless of variable names."""
67
+ parts: list[str] = []
68
+ for sub in ast.walk(node):
69
+ cls = type(sub).__name__
70
+ # Capture structural detail without leaking identifiers.
71
+ if isinstance(sub, ast.Constant):
72
+ parts.append(f"C:{type(sub.value).__name__}")
73
+ elif isinstance(sub, ast.BinOp):
74
+ parts.append(f"B:{type(sub.op).__name__}")
75
+ elif isinstance(sub, ast.Compare):
76
+ ops = "/".join(type(o).__name__ for o in sub.ops)
77
+ parts.append(f"Cmp:{ops}")
78
+ elif isinstance(sub, ast.UnaryOp):
79
+ parts.append(f"U:{type(sub.op).__name__}")
80
+ elif isinstance(sub, ast.For | ast.While | ast.If | ast.Try
81
+ | ast.With | ast.Return | ast.Raise
82
+ | ast.Assign | ast.AugAssign | ast.AnnAssign
83
+ | ast.FunctionDef | ast.AsyncFunctionDef
84
+ | ast.ClassDef | ast.Lambda | ast.ListComp
85
+ | ast.DictComp | ast.SetComp | ast.GeneratorExp
86
+ | ast.Import | ast.ImportFrom | ast.Call
87
+ | ast.Attribute | ast.Subscript):
88
+ parts.append(cls)
89
+ return "|".join(parts)
90
+
91
+
92
+ def _hash(s: str) -> str:
93
+ return hashlib.sha256(s.encode("utf-8")).hexdigest()[:16]
94
+
95
+
96
+ def _called_names(func_node: ast.AST) -> tuple[str, ...]:
97
+ """Set of names called inside a function body. Used to detect
98
+ 'this module just wraps existing primitives' (good reuse) vs
99
+ 'this module reimplements logic locally' (bad)."""
100
+ out: set[str] = set()
101
+ for sub in ast.walk(func_node):
102
+ if isinstance(sub, ast.Call):
103
+ f = sub.func
104
+ if isinstance(f, ast.Name):
105
+ out.add(f.id)
106
+ elif isinstance(f, ast.Attribute):
107
+ out.add(f.attr)
108
+ return tuple(sorted(out))
109
+
110
+
111
+ def _has_self_assign(func_node: ast.FunctionDef) -> bool:
112
+ for stmt in ast.walk(func_node):
113
+ if isinstance(stmt, ast.Assign):
114
+ for tgt in stmt.targets:
115
+ if (isinstance(tgt, ast.Attribute)
116
+ and isinstance(tgt.value, ast.Name)
117
+ and tgt.value.id == "self"):
118
+ return True
119
+ return False
120
+
121
+
122
+ def _signature_function(node: ast.FunctionDef | ast.AsyncFunctionDef
123
+ ) -> FunctionSignature:
124
+ body_hash = _hash(_normalize_ast(node))
125
+ return FunctionSignature(
126
+ name=node.name,
127
+ arg_count=len(node.args.args) + len(node.args.kwonlyargs),
128
+ is_async=isinstance(node, ast.AsyncFunctionDef),
129
+ body_hash=body_hash,
130
+ calls=_called_names(node),
131
+ )
132
+
133
+
134
+ def _signature_class(node: ast.ClassDef) -> ClassSignature:
135
+ methods = [n for n in node.body
136
+ if isinstance(n, ast.FunctionDef | ast.AsyncFunctionDef)]
137
+ has_state = any(_has_self_assign(m) for m in methods
138
+ if isinstance(m, ast.FunctionDef)
139
+ and m.name == "__init__")
140
+ body_hash = _hash(_normalize_ast(node))
141
+ return ClassSignature(
142
+ name=node.name,
143
+ method_count=len(methods),
144
+ has_state=has_state,
145
+ body_hash=body_hash,
146
+ )
147
+
148
+
149
+ def _top_level_imports(tree: ast.Module) -> tuple[str, ...]:
150
+ out: set[str] = set()
151
+ for node in tree.body:
152
+ if isinstance(node, ast.Import):
153
+ out.update(a.name for a in node.names)
154
+ elif isinstance(node, ast.ImportFrom):
155
+ if node.module:
156
+ out.add(node.module)
157
+ return tuple(sorted(out))
158
+
159
+
160
+ def signature_of(source: str) -> ModuleSignature:
161
+ """Pure: source -> ModuleSignature with stable shape hashes."""
162
+ try:
163
+ tree = ast.parse(source)
164
+ except SyntaxError:
165
+ return ModuleSignature(parse_ok=False, module_hash=_hash(source))
166
+
167
+ funcs: list[FunctionSignature] = []
168
+ classes: list[ClassSignature] = []
169
+ for node in tree.body:
170
+ if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef):
171
+ funcs.append(_signature_function(node))
172
+ elif isinstance(node, ast.ClassDef):
173
+ classes.append(_signature_class(node))
174
+
175
+ imports = _top_level_imports(tree)
176
+ # Module hash is order-invariant over functions + classes;
177
+ # sort by body_hash so two files with same content in different
178
+ # order collide.
179
+ fn_hashes = sorted(f.body_hash for f in funcs)
180
+ cls_hashes = sorted(c.body_hash for c in classes)
181
+ blob = "|".join(fn_hashes + cls_hashes + list(imports))
182
+ return ModuleSignature(
183
+ module_hash=_hash(blob),
184
+ functions=tuple(funcs),
185
+ classes=tuple(classes),
186
+ imports=imports,
187
+ parse_ok=True,
188
+ )
189
+
190
+
191
+ def function_overlap(a: ModuleSignature, b: ModuleSignature
192
+ ) -> tuple[FunctionSignature, ...]:
193
+ """Functions present in both modules with identical body_hash.
194
+ Use to spot 'this new module reimplements N functions that
195
+ already exist' before accepting an emit."""
196
+ a_by_hash = {f.body_hash: f for f in a.functions}
197
+ return tuple(f for f in b.functions if f.body_hash in a_by_hash)