bantamkit 0.33.0__tar.gz → 0.34.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 (220) hide show
  1. {bantamkit-0.33.0 → bantamkit-0.34.0}/PKG-INFO +66 -1
  2. {bantamkit-0.33.0 → bantamkit-0.34.0}/README.md +65 -0
  3. bantamkit-0.34.0/dist-0.33.0/bantamkit-0.33.0-py3-none-any.whl +0 -0
  4. bantamkit-0.34.0/dist-0.33.0/bantamkit-0.33.0.tar.gz +0 -0
  5. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/__init__.py +1 -1
  6. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/eventlog.py +23 -0
  7. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/hostinstall.py +6 -4
  8. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/memory/__init__.py +2 -0
  9. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/memory/store.py +67 -0
  10. bantamkit-0.34.0/tests/test_bantamkit_gitignore.py +236 -0
  11. {bantamkit-0.33.0 → bantamkit-0.34.0}/.gitignore +0 -0
  12. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/contracts/default.yaml +0 -0
  13. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/manifest.yaml +0 -0
  14. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/repo/HISTORY.md +0 -0
  15. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/repo/README.md +0 -0
  16. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/repo/docs/architecture.md +0 -0
  17. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/repo/docs/runbook.md +0 -0
  18. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/repo/issues/142-settlement-timeout.md +0 -0
  19. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/repo/patches/0009-retry-budget.patch +0 -0
  20. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/repo/src/ledger/__init__.py +0 -0
  21. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/repo/src/ledger/config.py +0 -0
  22. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/repo/src/ledger/errors.py +0 -0
  23. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/repo/src/ledger/posting.py +0 -0
  24. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/repo/src/ledger/registry.py +0 -0
  25. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/repo/src/ledger/report.py +0 -0
  26. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/repo/src/ledger/retry.py +0 -0
  27. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/repo/src/ledger/settle.py +0 -0
  28. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/repo/src/ledger/validate.py +0 -0
  29. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/repo/tests/test_posting.py +0 -0
  30. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/repo/tests/test_settle.py +0 -0
  31. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/tasks/dt-error-contract.yaml +0 -0
  32. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/tasks/dt-handler-map.yaml +0 -0
  33. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/tasks/dt-patch-before-after.yaml +0 -0
  34. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/tasks/dt-retry-attempts.yaml +0 -0
  35. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/tasks/dt-settlement-config.yaml +0 -0
  36. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/tasks/dt-symbol-home.yaml +0 -0
  37. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/tasks/dt-trace-blame.yaml +0 -0
  38. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/devteam/tasks/dt-unread-key.yaml +0 -0
  39. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/document/tasks/doc-large-in-137.yaml +0 -0
  40. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/document/tasks/doc-large-in-359.yaml +0 -0
  41. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/document/tasks/doc-large-in-372.yaml +0 -0
  42. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/document/tasks/doc-large-out-11764.yaml +0 -0
  43. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/document/tasks/doc-large-out-4137.yaml +0 -0
  44. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/document/tasks/doc-large-out-8022.yaml +0 -0
  45. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/document/tasks/doc-small-137.yaml +0 -0
  46. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/document/tasks/doc-small-261.yaml +0 -0
  47. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/document/tasks/doc-small-388.yaml +0 -0
  48. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/fixtures/.gitkeep +0 -0
  49. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/fixtures/catalog.json +0 -0
  50. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/perturbations/task-completion.yaml +0 -0
  51. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/.gitkeep +0 -0
  52. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/extract-contact.yaml +0 -0
  53. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/extract-invoice.yaml +0 -0
  54. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/extract-order.yaml +0 -0
  55. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/extract-schedule.yaml +0 -0
  56. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/extract-versions.yaml +0 -0
  57. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/nav-prod-port.yaml +0 -0
  58. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/nav-release-bundle.yaml +0 -0
  59. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/recall-audit-retention.yaml +0 -0
  60. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/recall-cache-ttl.yaml +0 -0
  61. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/recall-db-port.yaml +0 -0
  62. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/recall-deploy.yaml +0 -0
  63. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/recall-env-endpoint.yaml +0 -0
  64. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/recall-oncall-rotation.yaml +0 -0
  65. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/recall-oncall.yaml +0 -0
  66. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/recall-org-quota.yaml +0 -0
  67. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/recall-owner.yaml +0 -0
  68. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/shop-basket-total.yaml +0 -0
  69. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/shop-cheapest.yaml +0 -0
  70. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/shop-compare.yaml +0 -0
  71. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/shop-gadget-value.yaml +0 -0
  72. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/shop-stock-total.yaml +0 -0
  73. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/evals/tasks/shop-total.yaml +0 -0
  74. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/pricing/default.json +0 -0
  75. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/profiles/default.yaml +0 -0
  76. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/profiles/patient.yaml +0 -0
  77. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/rubrics/.gitkeep +0 -0
  78. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/rubrics/code-quality.yaml +0 -0
  79. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/rubrics/grounded-completion.yaml +0 -0
  80. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/rubrics/task-completion.yaml +0 -0
  81. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/schemas/shiftwork-checkpoint.json +0 -0
  82. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/skills/.gitkeep +0 -0
  83. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/skills/file-graph.md +0 -0
  84. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/skills/memory.md +0 -0
  85. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/tools/.gitkeep +0 -0
  86. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/tools/bantamkit_read.json +0 -0
  87. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/tools/bantamkit_status.json +0 -0
  88. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/tools/build_identity.json +0 -0
  89. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/tools/document_list.json +0 -0
  90. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/tools/document_read.json +0 -0
  91. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/tools/file_graph.json +0 -0
  92. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/tools/memory_compact.json +0 -0
  93. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/tools/memory_dream.json +0 -0
  94. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/tools/memory_recall.json +0 -0
  95. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/tools/memory_save.json +0 -0
  96. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/tools/repo_map.json +0 -0
  97. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/tools/shiftwork_clock_in.json +0 -0
  98. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/tools/shiftwork_clock_out.json +0 -0
  99. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/tools/shiftwork_status.json +0 -0
  100. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/tools/skill_audit.json +0 -0
  101. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/tools/token_ledger.json +0 -0
  102. {bantamkit-0.33.0 → bantamkit-0.34.0}/_assets/tools/validate_json.json +0 -0
  103. {bantamkit-0.33.0 → bantamkit-0.34.0}/hatch_build.py +0 -0
  104. {bantamkit-0.33.0 → bantamkit-0.34.0}/pyproject.toml +0 -0
  105. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/agent.py +0 -0
  106. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/assets.py +0 -0
  107. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/budget.py +0 -0
  108. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/client.py +0 -0
  109. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/contract.py +0 -0
  110. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/criticreplay.py +0 -0
  111. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/critique.py +0 -0
  112. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/docmanifest.py +0 -0
  113. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/docread.py +0 -0
  114. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/evalrun.py +0 -0
  115. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/filegraph.py +0 -0
  116. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/loopguard.py +0 -0
  117. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/mcpreport.py +0 -0
  118. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/mcpserver.py +0 -0
  119. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/memory/__main__.py +0 -0
  120. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/memory/component.py +0 -0
  121. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/memory/divergence.py +0 -0
  122. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/memory/dream.py +0 -0
  123. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/memory/layers.py +0 -0
  124. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/pdfread.py +0 -0
  125. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/pricing.py +0 -0
  126. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/profile.py +0 -0
  127. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/repomap.py +0 -0
  128. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/selfupdate.py +0 -0
  129. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/shiftwork.py +0 -0
  130. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/skillaudit.py +0 -0
  131. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/statusline.py +0 -0
  132. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/structured.py +0 -0
  133. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/textutil.py +0 -0
  134. {bantamkit-0.33.0 → bantamkit-0.34.0}/src/bantamkit/tokenledger.py +0 -0
  135. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/cli_exit_status_probe.py +0 -0
  136. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/conftest.py +0 -0
  137. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/data/docread/bad-crc.docx +0 -0
  138. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/data/docread/charref-4301-digits.html +0 -0
  139. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/data/docread/charset-table.json +0 -0
  140. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/data/docread/compression-method-9.docx +0 -0
  141. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/data/docread/corrupt-deflate.docx +0 -0
  142. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/data/docread/encrypted-member.docx +0 -0
  143. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/data/docread/encrypted-mimetype.odt +0 -0
  144. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/data/docread/eszett-cell-ref.xlsx +0 -0
  145. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/data/docread/internal-dtd-entity.docx +0 -0
  146. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/data/docread/rfc2231-charset.eml +0 -0
  147. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/data/docread/rfc822-nested-twice.eml +0 -0
  148. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/data/docread/unicode-digit-shared-string.xlsx +0 -0
  149. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/data/docread/x-uuencode.eml +0 -0
  150. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/data/f8404ab-perturbation-baseline.json +0 -0
  151. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/data/platform-assumption-baseline.json +0 -0
  152. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/data/served-tool-surface.json +0 -0
  153. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/docread_fixtures.py +0 -0
  154. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/perturbation_baseline_harness.py +0 -0
  155. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/rbp16_effect_probe.py +0 -0
  156. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/rbp18_payload_probe.py +0 -0
  157. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_adapter.py +0 -0
  158. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_agent.py +0 -0
  159. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_amendguard.py +0 -0
  160. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_bantamkit_read_tool.py +0 -0
  161. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_budget.py +0 -0
  162. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_build_identity.py +0 -0
  163. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_client.py +0 -0
  164. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_compaction_corpus_survey.py +0 -0
  165. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_conformance.py +0 -0
  166. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_conformance_harness_resilience.py +0 -0
  167. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_conformance_suite_table_gate.py +0 -0
  168. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_contract_fanout.py +0 -0
  169. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_criticreplay.py +0 -0
  170. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_critique.py +0 -0
  171. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_doc_commands_gate.py +0 -0
  172. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_docread.py +0 -0
  173. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_docread_ceilings.py +0 -0
  174. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_document_manifest_parity.py +0 -0
  175. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_document_setup.py +0 -0
  176. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_document_tasks.py +0 -0
  177. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_document_tools.py +0 -0
  178. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_encoding_gate.py +0 -0
  179. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_evalrun.py +0 -0
  180. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_eventlog.py +0 -0
  181. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_field_program_gates.py +0 -0
  182. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_field_programs.py +0 -0
  183. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_filegraph.py +0 -0
  184. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_hostinstall.py +0 -0
  185. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_install_shape.py +0 -0
  186. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_ladder_statistics.py +0 -0
  187. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_launcher_which.py +0 -0
  188. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_layers.py +0 -0
  189. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_loopguard.py +0 -0
  190. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_mcp_endpoint.py +0 -0
  191. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_mcpdrift.py +0 -0
  192. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_mcpreport.py +0 -0
  193. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_mcpserver.py +0 -0
  194. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_memory.py +0 -0
  195. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_memory_compact_tool.py +0 -0
  196. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_memory_component.py +0 -0
  197. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_memory_divergence.py +0 -0
  198. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_memory_dream.py +0 -0
  199. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_memory_layers.py +0 -0
  200. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_memory_store_tripwire.py +0 -0
  201. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_mutmatrix.py +0 -0
  202. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_newline_gate.py +0 -0
  203. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_packaging.py +0 -0
  204. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_pdfread.py +0 -0
  205. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_pinharness_ledger.py +0 -0
  206. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_platform_assumption_gate.py +0 -0
  207. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_pricing.py +0 -0
  208. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_repo_map_tool.py +0 -0
  209. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_repomap.py +0 -0
  210. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_selfupdate.py +0 -0
  211. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_served_tool_count_records.py +0 -0
  212. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_shiftwork.py +0 -0
  213. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_skillaudit.py +0 -0
  214. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_status_surface.py +0 -0
  215. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_statusline.py +0 -0
  216. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_structured.py +0 -0
  217. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_thread_exception_gate.py +0 -0
  218. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_tokenledger.py +0 -0
  219. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_tool_manifest.py +0 -0
  220. {bantamkit-0.33.0 → bantamkit-0.34.0}/tests/test_version_agreement.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: bantamkit
3
- Version: 0.33.0
3
+ Version: 0.34.0
4
4
  Summary: bantamweight tooling — harness primitives that lift small-model agents
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.11
@@ -50,6 +50,63 @@ The uv equivalent is `uvx --from "bantamkit[mcp]" bantamkit-mcp`. uv is not
50
50
  installed on the machine this README was measured on, so unlike every other
51
51
  command here that one is the documented form rather than a measured one.
52
52
 
53
+ > **AMENDED 2026-09-15 (job51) — `pipx run` launches online, and it is offline only while
54
+ > pipx's own cache lasts.** The first form above is fine to try the server. As the command a
55
+ > host runs on every launch, it has a cost. Measured with pipx 1.11.1 and `bantamkit 0.33.0`,
56
+ > with the network cut by pointing the proxy variables at a closed port:
57
+ >
58
+ > ```bash
59
+ > PIPX_HOME=<scratch> https_proxy=http://127.0.0.1:1 HTTPS_PROXY=http://127.0.0.1:1 \
60
+ > http_proxy=http://127.0.0.1:1 HTTP_PROXY=http://127.0.0.1:1 PIP_PROXY=http://127.0.0.1:1 \
61
+ > PIP_RETRIES=0 PIP_TIMEOUT=5 pipx run --spec "bantamkit[mcp]==0.33.0" bantamkit-mcp --assets-root
62
+ > ```
63
+ >
64
+ > On a `PIPX_HOME` that had never run it, this exited 1 in 1.33 s. After one run online into
65
+ > the same `PIPX_HOME`, the same command exited 0 in 0.47 s. So a `pipx run` host entry needs
66
+ > the package index on a new machine, after the cache is cleared, and whenever pipx decides
67
+ > its cached environment is stale. That last one is pipx's policy and was not measured here.
68
+ > For a host, install into an environment you keep, as in the next section.
69
+
70
+ ### Install once, run offline
71
+
72
+ Install into an environment you keep, then let `--install` record that environment's console
73
+ script:
74
+
75
+ ```bash
76
+ python -m venv <env>
77
+ <env>/bin/pip install "bantamkit[mcp]"
78
+ <env>/bin/bantamkit-mcp --install cursor # or claude, claude-desktop, copilot
79
+ ```
80
+
81
+ After that, no launch needs the network. `--install` writes the absolute path of the console
82
+ script you ran. Measured 2026-09-15 on macOS arm64 against `bantamkit 0.33.0`,
83
+ `<env>/bin/bantamkit-mcp --install cursor` wrote `"command": "<env>/bin/bantamkit-mcp", "args": []`.
84
+ That command answered `initialize` and `tools/list` with 12 tools under the PATH a GUI app
85
+ inherits on macOS, `/usr/bin:/bin:/usr/sbin:/sbin`. The Windows layout (`<env>\Scripts\`) was
86
+ not measured.
87
+
88
+ **For a machine with no network at all, carry a wheelhouse.** On a connected machine with the
89
+ **same operating system, CPU architecture and Python minor version** as the target:
90
+
91
+ ```bash
92
+ python -m pip download "bantamkit[mcp]==0.33.0" -d wheels
93
+ ```
94
+
95
+ With Python 3.12.13 on macOS arm64 that wrote 33 files. One of them is
96
+ `pydantic_core-2.46.5-cp312-cp312-macosx_11_0_arm64.whl`, which is built for CPython 3.12 on
97
+ arm64 macOS and nothing else. That is why the two machines must match. Copy `wheels/` across,
98
+ then on the target:
99
+
100
+ ```bash
101
+ python -m venv <env>
102
+ <env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.33.0"
103
+ <env>/bin/bantamkit-mcp --install cursor
104
+ ```
105
+
106
+ Measured in a fresh venv with `PIP_INDEX_URL=http://127.0.0.1:1/` (a closed port): the install
107
+ finished in 1.69 s, and the installed console script served the 12 tools as above. To move to
108
+ a newer version, repeat the download and the install with the new version number.
109
+
53
110
  ### Connect it to a host
54
111
 
55
112
  **One command, and it writes the entry for you:**
@@ -80,6 +137,14 @@ hand. Every host runs the same command; only the file and the key around it chan
80
137
  you installed with `pip` into an environment you keep, replace the `command`/`args` pair
81
138
  with the absolute path to the `bantamkit-mcp` console script in that environment.
82
139
 
140
+ **Prefer that absolute form over the `pipx run` lines below.** A `pipx run` entry needs the
141
+ package index whenever pipx's cache is cold (measured in the amendment under *Install and
142
+ run*). An entry that names a kept console script needs nothing at launch:
143
+
144
+ ```json
145
+ {"mcpServers": {"bantamkit": {"command": "/absolute/path/to/env/bin/bantamkit-mcp", "args": []}}}
146
+ ```
147
+
83
148
  **Claude Code** — one command, no file to edit. `-s user` makes it available in every
84
149
  project; drop it for this project only.
85
150
 
@@ -33,6 +33,63 @@ The uv equivalent is `uvx --from "bantamkit[mcp]" bantamkit-mcp`. uv is not
33
33
  installed on the machine this README was measured on, so unlike every other
34
34
  command here that one is the documented form rather than a measured one.
35
35
 
36
+ > **AMENDED 2026-09-15 (job51) — `pipx run` launches online, and it is offline only while
37
+ > pipx's own cache lasts.** The first form above is fine to try the server. As the command a
38
+ > host runs on every launch, it has a cost. Measured with pipx 1.11.1 and `bantamkit 0.33.0`,
39
+ > with the network cut by pointing the proxy variables at a closed port:
40
+ >
41
+ > ```bash
42
+ > PIPX_HOME=<scratch> https_proxy=http://127.0.0.1:1 HTTPS_PROXY=http://127.0.0.1:1 \
43
+ > http_proxy=http://127.0.0.1:1 HTTP_PROXY=http://127.0.0.1:1 PIP_PROXY=http://127.0.0.1:1 \
44
+ > PIP_RETRIES=0 PIP_TIMEOUT=5 pipx run --spec "bantamkit[mcp]==0.33.0" bantamkit-mcp --assets-root
45
+ > ```
46
+ >
47
+ > On a `PIPX_HOME` that had never run it, this exited 1 in 1.33 s. After one run online into
48
+ > the same `PIPX_HOME`, the same command exited 0 in 0.47 s. So a `pipx run` host entry needs
49
+ > the package index on a new machine, after the cache is cleared, and whenever pipx decides
50
+ > its cached environment is stale. That last one is pipx's policy and was not measured here.
51
+ > For a host, install into an environment you keep, as in the next section.
52
+
53
+ ### Install once, run offline
54
+
55
+ Install into an environment you keep, then let `--install` record that environment's console
56
+ script:
57
+
58
+ ```bash
59
+ python -m venv <env>
60
+ <env>/bin/pip install "bantamkit[mcp]"
61
+ <env>/bin/bantamkit-mcp --install cursor # or claude, claude-desktop, copilot
62
+ ```
63
+
64
+ After that, no launch needs the network. `--install` writes the absolute path of the console
65
+ script you ran. Measured 2026-09-15 on macOS arm64 against `bantamkit 0.33.0`,
66
+ `<env>/bin/bantamkit-mcp --install cursor` wrote `"command": "<env>/bin/bantamkit-mcp", "args": []`.
67
+ That command answered `initialize` and `tools/list` with 12 tools under the PATH a GUI app
68
+ inherits on macOS, `/usr/bin:/bin:/usr/sbin:/sbin`. The Windows layout (`<env>\Scripts\`) was
69
+ not measured.
70
+
71
+ **For a machine with no network at all, carry a wheelhouse.** On a connected machine with the
72
+ **same operating system, CPU architecture and Python minor version** as the target:
73
+
74
+ ```bash
75
+ python -m pip download "bantamkit[mcp]==0.33.0" -d wheels
76
+ ```
77
+
78
+ With Python 3.12.13 on macOS arm64 that wrote 33 files. One of them is
79
+ `pydantic_core-2.46.5-cp312-cp312-macosx_11_0_arm64.whl`, which is built for CPython 3.12 on
80
+ arm64 macOS and nothing else. That is why the two machines must match. Copy `wheels/` across,
81
+ then on the target:
82
+
83
+ ```bash
84
+ python -m venv <env>
85
+ <env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.33.0"
86
+ <env>/bin/bantamkit-mcp --install cursor
87
+ ```
88
+
89
+ Measured in a fresh venv with `PIP_INDEX_URL=http://127.0.0.1:1/` (a closed port): the install
90
+ finished in 1.69 s, and the installed console script served the 12 tools as above. To move to
91
+ a newer version, repeat the download and the install with the new version number.
92
+
36
93
  ### Connect it to a host
37
94
 
38
95
  **One command, and it writes the entry for you:**
@@ -63,6 +120,14 @@ hand. Every host runs the same command; only the file and the key around it chan
63
120
  you installed with `pip` into an environment you keep, replace the `command`/`args` pair
64
121
  with the absolute path to the `bantamkit-mcp` console script in that environment.
65
122
 
123
+ **Prefer that absolute form over the `pipx run` lines below.** A `pipx run` entry needs the
124
+ package index whenever pipx's cache is cold (measured in the amendment under *Install and
125
+ run*). An entry that names a kept console script needs nothing at launch:
126
+
127
+ ```json
128
+ {"mcpServers": {"bantamkit": {"command": "/absolute/path/to/env/bin/bantamkit-mcp", "args": []}}}
129
+ ```
130
+
66
131
  **Claude Code** — one command, no file to edit. `-s user` makes it available in every
67
132
  project; drop it for this project only.
68
133
 
@@ -29,4 +29,4 @@ from bantamkit.structured import StructuredOutputError, extract_json, structured
29
29
  # file as its dynamic version source, so the wheel's metadata and the string the MCP
30
30
  # server advertises are the same committed bytes, and neither is a function of when
31
31
  # someone last ran `pip`.
32
- __version__ = "0.33.0"
32
+ __version__ = "0.34.0"
@@ -69,6 +69,8 @@ from datetime import UTC, datetime, timedelta
69
69
  from pathlib import Path
70
70
  from typing import Any
71
71
 
72
+ from bantamkit.memory.store import ensure_bantamkit_gitignore
73
+
72
74
  #: Environment switch. Unset or `off`/`0`/`false`/`no`/empty -> disabled. `on`/`1`/
73
75
  #: `true`/`yes` -> the default file inside the memory store. Anything else is taken as
74
76
  #: the literal path of the log file.
@@ -276,7 +278,28 @@ class EventLog:
276
278
  intent, not a live path.
277
279
  """
278
280
  assert self.path is not None
281
+ # AUDIT FINDING (J51-1): this `mkdir(parents=True)` can bring a whole `.bantamkit`
282
+ # directory into existence on its own -- `BANTAMKIT_EVENT_LOG=on` with a project
283
+ # store that was never saved to reaches here first -- entirely bypassing
284
+ # `MemoryStore._ensure_dirs`, which is the only other place a `.bantamkit`
285
+ # directory gets created. Without this call a store built that way would never get
286
+ # its self-ignoring `.gitignore`. Cheap and idempotent: a no-op unless one of
287
+ # `self.path`'s ancestors is literally named `.bantamkit`.
288
+ #
289
+ # USER RULING #2 (J51-8a): the ignore file is written only when THIS call is what
290
+ # creates `.bantamkit`, so existence has to be checked BEFORE the mkdir below --
291
+ # after it, the directory unconditionally exists and the question is unanswerable.
292
+ bantamkit_dir = None
293
+ for parent in self.path.parents:
294
+ if parent.name == ".bantamkit":
295
+ bantamkit_dir = parent
296
+ break
297
+ bantamkit_dir_existed_before = bantamkit_dir is not None and bantamkit_dir.exists()
279
298
  self.path.parent.mkdir(parents=True, exist_ok=True)
299
+ if bantamkit_dir is not None:
300
+ ensure_bantamkit_gitignore(
301
+ bantamkit_dir, created=not bantamkit_dir_existed_before
302
+ )
280
303
  try:
281
304
  size = self.path.stat().st_size
282
305
  except FileNotFoundError:
@@ -15,10 +15,12 @@ convenience — a file the host owns and rewrites is a file we should not be mer
15
15
  hand.
16
16
 
17
17
  WHAT THIS WRITES IS WHAT IS RUNNING. The command recorded is this interpreter's own
18
- `bantamkit-mcp` console script, by absolute path. The Node distribution records
19
- `npx -y bantamkit-mcp` for the same reason: the thing installed should be the thing that
20
- answers, and neither side should send a host looking for the other's runtime. The two
21
- outputs therefore DIFFER by construction and that difference is ruled in `docs/porting.md`.
18
+ `bantamkit-mcp` console script, by absolute path. Since J51-4, the Node side records its
19
+ own absolute command the same way -- `<absolute node> <absolute dist/cli.js>` of the kept
20
+ install under `~/.bantamkit/mcp` -- for the same reason: the thing installed should be the
21
+ thing that answers, and neither side should send a host looking for the other's runtime.
22
+ The two outputs therefore DIFFER by construction (different interpreter, different path)
23
+ and that difference is ruled in `docs/porting.md`.
22
24
 
23
25
  WHY IT NEVER PROMPTS. An overwrite is exactly the moment a program wants to ask, and asking
24
26
  requires a terminal. Measured repeatedly on this machine: Claude Code's `!` channel has no
@@ -30,6 +30,7 @@ from bantamkit.memory.layers import (
30
30
  resolve_project_store,
31
31
  )
32
32
  from bantamkit.memory.store import (
33
+ BANTAMKIT_GITIGNORE_TEXT,
33
34
  DEFAULT_INDEX_BUDGET,
34
35
  INDEX_PRESSURE_PERCENT,
35
36
  RECALL_MIN_SCORE_RATIO,
@@ -40,5 +41,6 @@ from bantamkit.memory.store import (
40
41
  MemoryStore,
41
42
  MemoryValidationError,
42
43
  SaveResult,
44
+ ensure_bantamkit_gitignore,
43
45
  undegraded_index_ceiling,
44
46
  )
@@ -252,6 +252,67 @@ def _mtime_date(path: Path) -> str:
252
252
  return date.fromtimestamp(path.stat().st_mtime).isoformat()
253
253
 
254
254
 
255
+ # The exact bytes written into a `.bantamkit/.gitignore` the first time such a directory
256
+ # is brought into existence (user ruling 2026-09-15, `.shiftwork/notes-job51/P0-probes.md`).
257
+ # Both runtimes hold this text byte-for-byte -- a conformance case pins it against this
258
+ # literal -- so it is spelled once, here, rather than re-typed at each call site.
259
+ BANTAMKIT_GITIGNORE_TEXT = (
260
+ "# Created by bantamkit: this directory is local state. Delete this file to commit it.\n"
261
+ "*\n"
262
+ )
263
+
264
+
265
+ def ensure_bantamkit_gitignore(directory: Path, *, created: bool) -> None:
266
+ """Best-effort, idempotent: give `directory` a self-ignoring `.gitignore` if it is
267
+ literally named `.bantamkit` AND `created` says THIS CALLER'S OWN mkdir is what just
268
+ brought it into existence.
269
+
270
+ USER RULING #2 (2026-09-15, after J51-8 measured the fallout of the first rule): the
271
+ ignore file is written only when bantamkit itself creates the `.bantamkit` directory.
272
+ An existing `.bantamkit` -- made by an earlier bantamkit, by hand, or checked out from
273
+ git; with or without a `.gitignore` already in it -- is never given one. That is why
274
+ `created` is the caller's job, not this function's: only the caller knows whether
275
+ `.bantamkit` existed immediately before ITS mkdir, because by the time this function
276
+ runs the directory unconditionally exists either way. (Was: written whenever the
277
+ ignore file itself was absent, regardless of whether `.bantamkit` predated the call --
278
+ which meant deleting the file never stuck, and a store a team already commits would
279
+ silently start ignoring new fact files after an upgrade. `P0-probes.md` ruling 4.)
280
+
281
+ THE PROPERTY, not the mechanism: whenever a `.bantamkit` directory is brought into
282
+ existence -- by `MemoryStore._ensure_dirs`, or by anything else in this runtime that
283
+ creates one, such as `EventLog._append`'s own `mkdir` when the event log is on and
284
+ the project store was never saved to -- this is the one place that decides whether it
285
+ gets ignored. A second creator that skipped this call, or that got `created` wrong,
286
+ would leave a `.bantamkit` that git can see (or ignore one it should not), which is
287
+ the whole bug this closes.
288
+
289
+ NEVER REWRITES. A `.gitignore` that already exists -- whatever its bytes -- is left
290
+ exactly as it is, kept as a second guard even when `created` is `True`: an operator
291
+ who deleted it to commit the directory keeps that decision, and this never diffs its
292
+ own output against what is on disk.
293
+
294
+ NEVER RAISES. Failing to write the ignore file must never be why a fact does not get
295
+ saved -- the parent write (`save`, `compact`, an event record) still has to succeed
296
+ exactly as it does today. A missing ignore file is not worth a lost fact, so an
297
+ unwritable filesystem, a permissions error, or the directory disappearing under this
298
+ call are all swallowed the same way `EventLog.record` already swallows its own
299
+ `OSError`.
300
+
301
+ NEVER CREATES `directory` ITSELF. `write_text` fails into a missing parent exactly
302
+ like any other `OSError` here, so calling this before `directory` exists is a no-op,
303
+ not a way to bring `.bantamkit` into existence early.
304
+ """
305
+ if directory.name != ".bantamkit" or not created:
306
+ return
307
+ gitignore = directory / ".gitignore"
308
+ if gitignore.exists():
309
+ return
310
+ try:
311
+ gitignore.write_text(BANTAMKIT_GITIGNORE_TEXT, encoding="utf-8")
312
+ except OSError:
313
+ pass
314
+
315
+
255
316
  class MemoryStore:
256
317
  def __init__(
257
318
  self,
@@ -341,6 +402,11 @@ class MemoryStore:
341
402
  mkdir's internals. `self.root` is the path the caller named and both sides agree on
342
403
  it, which is what lets one conformance case pin this line byte for byte.
343
404
  """
405
+ # Checked BEFORE the mkdir, per user ruling #2: this is the one moment that can
406
+ # tell whether `.bantamkit` already existed. After the mkdir it unconditionally
407
+ # exists either way, so the answer has to be captured now or not at all.
408
+ bantamkit_dir = self.root.parent
409
+ bantamkit_dir_existed_before = bantamkit_dir.exists()
344
410
  try:
345
411
  (self.root / "facts").mkdir(parents=True, exist_ok=True)
346
412
  (self.root / "archive").mkdir(parents=True, exist_ok=True)
@@ -349,6 +415,7 @@ class MemoryStore:
349
415
  f"memory store could not be created: {self.root}; the directory is not"
350
416
  " there and this filesystem would not make it, so nothing was written"
351
417
  ) from e
418
+ ensure_bantamkit_gitignore(bantamkit_dir, created=not bantamkit_dir_existed_before)
352
419
 
353
420
  # ---- ops ----
354
421
 
@@ -0,0 +1,236 @@
1
+ """J51-1 / J51-8a: a `.bantamkit` directory ignores itself in git — but ONLY the one
2
+ bantamkit itself brings into existence.
3
+
4
+ User ruling 2026-09-15 (`.shiftwork/notes-job51/P0-probes.md`): `.bantamkit` stays out of
5
+ git by default via a self-ignoring `.bantamkit/.gitignore` (`*`), never by editing the
6
+ repository's own `.gitignore`. The property, from `.shiftwork/briefs-job51/J51-1.md`,
7
+ AS AMENDED by user ruling #2 (2026-09-15, `.shiftwork/briefs-job51/J51-8a.md`) after J51-8
8
+ measured that a deleted ignore file came back on the next save and that a store a team
9
+ already commits would silently start ignoring new fact files after an upgrade:
10
+
11
+ 1. The ignore file is written IF AND ONLY IF, in this call, the `.bantamkit` directory
12
+ did not exist immediately before the directories were made and exists afterwards.
13
+ "Did not exist" is decided by checking the `.bantamkit` directory itself BEFORE the
14
+ mkdir — never the `.gitignore` file, never `memory/`. (Was: written whenever the
15
+ store's directories are brought into existence and the ignore file itself is absent,
16
+ regardless of whether `.bantamkit` already existed — J51-8 measured this makes
17
+ deletion not stick.)
18
+ 2. A `.bantamkit` directory that already existed — made by an earlier bantamkit, by hand,
19
+ or checked out from git; with or without a `.gitignore` — is never given one. In
20
+ particular, an existing `.gitignore` is never rewritten (unchanged from J51-1).
21
+ 3. Writing it is best-effort: an unwritable `.gitignore` never fails the write that
22
+ triggered it.
23
+ 4. A store whose parent is not named `.bantamkit` gets no `.gitignore` anywhere.
24
+ 5. End-to-end: a fresh `git init` directory shows nothing under `.bantamkit` in
25
+ `git status --porcelain --untracked-files=all` after a `memory_save`.
26
+ 6. Consequence of 1: deleting the file after bantamkit created it, then saving again,
27
+ leaves it deleted — the file's own first line ("Delete this file to commit it") is
28
+ now true.
29
+
30
+ Also covers the bypass this audit found: `EventLog._append`'s own
31
+ `self.path.parent.mkdir(parents=True, exist_ok=True)` can bring a `.bantamkit` directory
32
+ into existence WITHOUT ever calling `MemoryStore._ensure_dirs` — e.g. when
33
+ `BANTAMKIT_EVENT_LOG=on` and the project store has never been saved to. That path must
34
+ carry the same gitignore-write (and the same "only if `.bantamkit` did not already
35
+ exist" gate), or a `.bantamkit` created that way is never ignored, or is ignored when it
36
+ should not be.
37
+ """
38
+
39
+ import shutil
40
+ import subprocess
41
+
42
+ import pytest
43
+
44
+ from bantamkit.eventlog import EventLog, default_path
45
+ from bantamkit.memory import Memory, MemoryStore
46
+ from bantamkit.memory.store import BANTAMKIT_GITIGNORE_TEXT, ensure_bantamkit_gitignore
47
+
48
+
49
+ def test_property_1_gitignore_written_when_store_root_parent_is_dot_bantamkit(tmp_path):
50
+ root = tmp_path / ".bantamkit" / "memory"
51
+ MemoryStore(root) # create=True by default; brings facts/ and archive/ into existence
52
+
53
+ gitignore = tmp_path / ".bantamkit" / ".gitignore"
54
+ assert gitignore.exists()
55
+ assert gitignore.read_bytes() == BANTAMKIT_GITIGNORE_TEXT.encode("utf-8")
56
+
57
+
58
+ def test_property_1_also_fires_on_save_not_only_construction(tmp_path):
59
+ root = tmp_path / ".bantamkit" / "memory"
60
+ store = MemoryStore(root, create=False)
61
+ gitignore = tmp_path / ".bantamkit" / ".gitignore"
62
+ assert not gitignore.exists()
63
+
64
+ store.save("project", "widget-cache", "one line", "body", ())
65
+
66
+ assert gitignore.exists()
67
+ assert gitignore.read_text(encoding="utf-8") == BANTAMKIT_GITIGNORE_TEXT
68
+
69
+
70
+ def test_property_2_an_existing_gitignore_is_never_rewritten(tmp_path):
71
+ root = tmp_path / ".bantamkit" / "memory"
72
+ bantamkit_dir = tmp_path / ".bantamkit"
73
+ bantamkit_dir.mkdir()
74
+ gitignore = bantamkit_dir / ".gitignore"
75
+ gitignore.write_text("operator's own content\n", encoding="utf-8")
76
+
77
+ store = MemoryStore(root)
78
+ store.save("project", "widget-cache", "one line", "body", ())
79
+ store.compact()
80
+
81
+ assert gitignore.read_text(encoding="utf-8") == "operator's own content\n"
82
+
83
+
84
+ def test_property_3_unwritable_gitignore_is_best_effort(tmp_path, monkeypatch):
85
+ root = tmp_path / ".bantamkit" / "memory"
86
+
87
+ from pathlib import Path
88
+
89
+ real_write_text = Path.write_text
90
+
91
+ def refuse(self, *args, **kwargs):
92
+ if self.name == ".gitignore":
93
+ raise OSError("simulated: read-only filesystem")
94
+ return real_write_text(self, *args, **kwargs)
95
+
96
+ monkeypatch.setattr(Path, "write_text", refuse)
97
+
98
+ store = MemoryStore(root) # must not raise
99
+ result = store.save("project", "widget-cache", "one line", "body", ()) # must not raise
100
+
101
+ assert result.status == "saved"
102
+ assert not (tmp_path / ".bantamkit" / ".gitignore").exists()
103
+
104
+
105
+ def test_property_4_store_not_under_dot_bantamkit_gets_no_gitignore(tmp_path):
106
+ root = tmp_path / "somewhere" / "mem"
107
+ MemoryStore(root)
108
+
109
+ assert not (tmp_path / "somewhere" / ".gitignore").exists()
110
+ assert not (tmp_path / ".gitignore").exists()
111
+
112
+
113
+ def test_ensure_bantamkit_gitignore_is_a_noop_off_a_non_bantamkit_directory(tmp_path):
114
+ """API change for J51-8a: `created` is now a required keyword — the caller must say
115
+ whether ITS OWN mkdir is what brought `directory` into existence. Passed `True` here
116
+ on purpose: the name check must refuse even when the caller claims a fresh creation.
117
+ """
118
+ directory = tmp_path / "not-bantamkit"
119
+ directory.mkdir()
120
+ ensure_bantamkit_gitignore(directory, created=True)
121
+ assert list(directory.iterdir()) == []
122
+
123
+
124
+ def test_property_1_amended_existing_bantamkit_without_gitignore_gets_none_on_save(tmp_path):
125
+ """J51-8a (a): a `.bantamkit` that already existed — with no `.gitignore` in it —
126
+ never gets one, even though the ignore file itself is absent. FAILS on the
127
+ pre-J51-8a source, which writes whenever the file is absent.
128
+ """
129
+ bantamkit_dir = tmp_path / ".bantamkit"
130
+ bantamkit_dir.mkdir()
131
+ gitignore = bantamkit_dir / ".gitignore"
132
+ assert not gitignore.exists()
133
+
134
+ root = bantamkit_dir / "memory"
135
+ store = MemoryStore(root)
136
+ store.save("project", "widget-cache", "one line", "body", ())
137
+
138
+ assert not gitignore.exists()
139
+
140
+
141
+ def test_property_1_amended_deleting_the_gitignore_after_creation_stays_deleted(tmp_path):
142
+ """J51-8a (b): bantamkit creates `.bantamkit` itself, so the ignore file is written —
143
+ delete it, save again, and it must stay deleted. FAILS on the pre-J51-8a source,
144
+ which rewrites it on the next save because the file is absent.
145
+ """
146
+ root = tmp_path / ".bantamkit" / "memory"
147
+ gitignore = tmp_path / ".bantamkit" / ".gitignore"
148
+
149
+ store = MemoryStore(root)
150
+ assert gitignore.exists()
151
+
152
+ gitignore.unlink()
153
+ store.save("project", "widget-cache", "one line", "body", ())
154
+
155
+ assert not gitignore.exists()
156
+
157
+
158
+ def test_eventlog_amended_existing_bantamkit_without_gitignore_gets_none(tmp_path):
159
+ """J51-8a (c), condition (a): the event-log creator follows the same rule — a
160
+ `.bantamkit` that already existed, with no `.gitignore`, is left alone.
161
+ """
162
+ bantamkit_dir = tmp_path / ".bantamkit"
163
+ bantamkit_dir.mkdir()
164
+ gitignore = bantamkit_dir / ".gitignore"
165
+ assert not gitignore.exists()
166
+
167
+ store_root = bantamkit_dir / "memory"
168
+ log = EventLog(default_path(store_root))
169
+ log.record("memory_save", "saved", {"budget": 1, "index_bytes": 1})
170
+
171
+ assert log.write_failed is False
172
+ assert not gitignore.exists()
173
+
174
+
175
+ def test_eventlog_amended_deleting_the_gitignore_after_creation_stays_deleted(tmp_path):
176
+ """J51-8a (c), condition (b): the event-log creator brought `.bantamkit` into
177
+ existence itself, so deleting the ignore file it wrote must stick on the next append.
178
+ """
179
+ store_root = tmp_path / ".bantamkit" / "memory"
180
+ gitignore = tmp_path / ".bantamkit" / ".gitignore"
181
+
182
+ log = EventLog(default_path(store_root))
183
+ log.record("memory_save", "saved", {"budget": 1, "index_bytes": 1})
184
+ assert gitignore.exists()
185
+
186
+ gitignore.unlink()
187
+ log.record("memory_save", "saved", {"budget": 1, "index_bytes": 1})
188
+
189
+ assert not gitignore.exists()
190
+
191
+
192
+ @pytest.mark.skipif(shutil.which("git") is None, reason="git is not on PATH")
193
+ def test_property_5_end_to_end_git_status_is_clean_under_dot_bantamkit(tmp_path):
194
+ subprocess.run(["git", "init", "-q"], cwd=tmp_path, check=True)
195
+ subprocess.run(
196
+ ["git", "config", "user.email", "test@example.com"], cwd=tmp_path, check=True
197
+ )
198
+ subprocess.run(["git", "config", "user.name", "test"], cwd=tmp_path, check=True)
199
+
200
+ memory = Memory(tmp_path / ".bantamkit" / "memory")
201
+ outcome = memory.save_outcome("project", "widget-cache", "one line", "body", ())
202
+ assert outcome.status == "saved"
203
+
204
+ result = subprocess.run(
205
+ ["git", "status", "--porcelain", "--untracked-files=all"],
206
+ cwd=tmp_path,
207
+ check=True,
208
+ capture_output=True,
209
+ encoding="utf-8",
210
+ )
211
+ bantamkit_lines = [line for line in result.stdout.splitlines() if ".bantamkit" in line]
212
+ assert bantamkit_lines == []
213
+
214
+
215
+ def test_eventlog_mkdir_bypass_still_gets_the_gitignore(tmp_path):
216
+ """The finding: `EventLog._append` mkdir's a `.bantamkit` tree on its own when the
217
+ project store was never saved to. That must not skip the gitignore write.
218
+ """
219
+ store_root = tmp_path / ".bantamkit" / "memory"
220
+ assert not (tmp_path / ".bantamkit").exists()
221
+
222
+ log = EventLog(default_path(store_root))
223
+ log.record("memory_save", "saved", {"budget": 1, "index_bytes": 1})
224
+
225
+ assert log.write_failed is False
226
+ gitignore = tmp_path / ".bantamkit" / ".gitignore"
227
+ assert gitignore.exists()
228
+ assert gitignore.read_text(encoding="utf-8") == BANTAMKIT_GITIGNORE_TEXT
229
+
230
+
231
+ def test_eventlog_path_unrelated_to_dot_bantamkit_writes_no_gitignore(tmp_path):
232
+ log = EventLog(tmp_path / "custom" / "log.jsonl")
233
+ log.record("memory_save", "saved", {"budget": 1, "index_bytes": 1})
234
+
235
+ assert log.write_failed is False
236
+ assert not list(tmp_path.glob("**/.gitignore"))
File without changes