bantamkit 0.34.2__tar.gz → 0.35.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 (222) hide show
  1. {bantamkit-0.34.2 → bantamkit-0.35.0}/PKG-INFO +16 -13
  2. {bantamkit-0.34.2 → bantamkit-0.35.0}/README.md +15 -12
  3. bantamkit-0.35.0/_assets/tools/shiftwork_plan.json +25 -0
  4. bantamkit-0.35.0/_assets/tools/work_plan.json +49 -0
  5. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/__init__.py +1 -1
  6. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/eventlog.py +12 -9
  7. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/mcpserver.py +21 -0
  8. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/memory/__main__.py +1 -1
  9. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/memory/store.py +50 -3
  10. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/shiftwork.py +68 -1
  11. bantamkit-0.35.0/src/bantamkit/workplan.py +157 -0
  12. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/data/served-tool-surface.json +69 -1
  13. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_bantamkit_gitignore.py +64 -0
  14. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_eventlog.py +1 -1
  15. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_layers.py +4 -0
  16. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_mcpserver.py +10 -3
  17. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_memory_compact_tool.py +3 -1
  18. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_served_tool_count_records.py +144 -18
  19. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_shiftwork.py +167 -0
  20. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_skillaudit.py +13 -3
  21. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_status_surface.py +1 -1
  22. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_tokenledger.py +2 -0
  23. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_tool_manifest.py +2 -0
  24. bantamkit-0.35.0/tests/test_workplan.py +341 -0
  25. {bantamkit-0.34.2 → bantamkit-0.35.0}/.gitignore +0 -0
  26. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/contracts/default.yaml +0 -0
  27. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/manifest.yaml +0 -0
  28. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/repo/HISTORY.md +0 -0
  29. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/repo/README.md +0 -0
  30. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/repo/docs/architecture.md +0 -0
  31. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/repo/docs/runbook.md +0 -0
  32. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/repo/issues/142-settlement-timeout.md +0 -0
  33. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/repo/patches/0009-retry-budget.patch +0 -0
  34. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/repo/src/ledger/__init__.py +0 -0
  35. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/repo/src/ledger/config.py +0 -0
  36. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/repo/src/ledger/errors.py +0 -0
  37. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/repo/src/ledger/posting.py +0 -0
  38. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/repo/src/ledger/registry.py +0 -0
  39. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/repo/src/ledger/report.py +0 -0
  40. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/repo/src/ledger/retry.py +0 -0
  41. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/repo/src/ledger/settle.py +0 -0
  42. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/repo/src/ledger/validate.py +0 -0
  43. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/repo/tests/test_posting.py +0 -0
  44. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/repo/tests/test_settle.py +0 -0
  45. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/tasks/dt-error-contract.yaml +0 -0
  46. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/tasks/dt-handler-map.yaml +0 -0
  47. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/tasks/dt-patch-before-after.yaml +0 -0
  48. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/tasks/dt-retry-attempts.yaml +0 -0
  49. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/tasks/dt-settlement-config.yaml +0 -0
  50. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/tasks/dt-symbol-home.yaml +0 -0
  51. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/tasks/dt-trace-blame.yaml +0 -0
  52. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/devteam/tasks/dt-unread-key.yaml +0 -0
  53. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/document/tasks/doc-large-in-137.yaml +0 -0
  54. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/document/tasks/doc-large-in-359.yaml +0 -0
  55. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/document/tasks/doc-large-in-372.yaml +0 -0
  56. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/document/tasks/doc-large-out-11764.yaml +0 -0
  57. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/document/tasks/doc-large-out-4137.yaml +0 -0
  58. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/document/tasks/doc-large-out-8022.yaml +0 -0
  59. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/document/tasks/doc-small-137.yaml +0 -0
  60. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/document/tasks/doc-small-261.yaml +0 -0
  61. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/document/tasks/doc-small-388.yaml +0 -0
  62. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/fixtures/.gitkeep +0 -0
  63. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/fixtures/catalog.json +0 -0
  64. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/perturbations/task-completion.yaml +0 -0
  65. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/.gitkeep +0 -0
  66. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/extract-contact.yaml +0 -0
  67. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/extract-invoice.yaml +0 -0
  68. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/extract-order.yaml +0 -0
  69. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/extract-schedule.yaml +0 -0
  70. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/extract-versions.yaml +0 -0
  71. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/nav-prod-port.yaml +0 -0
  72. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/nav-release-bundle.yaml +0 -0
  73. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/recall-audit-retention.yaml +0 -0
  74. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/recall-cache-ttl.yaml +0 -0
  75. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/recall-db-port.yaml +0 -0
  76. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/recall-deploy.yaml +0 -0
  77. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/recall-env-endpoint.yaml +0 -0
  78. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/recall-oncall-rotation.yaml +0 -0
  79. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/recall-oncall.yaml +0 -0
  80. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/recall-org-quota.yaml +0 -0
  81. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/recall-owner.yaml +0 -0
  82. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/shop-basket-total.yaml +0 -0
  83. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/shop-cheapest.yaml +0 -0
  84. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/shop-compare.yaml +0 -0
  85. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/shop-gadget-value.yaml +0 -0
  86. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/shop-stock-total.yaml +0 -0
  87. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/evals/tasks/shop-total.yaml +0 -0
  88. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/pricing/default.json +0 -0
  89. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/profiles/default.yaml +0 -0
  90. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/profiles/patient.yaml +0 -0
  91. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/rubrics/.gitkeep +0 -0
  92. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/rubrics/code-quality.yaml +0 -0
  93. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/rubrics/grounded-completion.yaml +0 -0
  94. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/rubrics/task-completion.yaml +0 -0
  95. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/schemas/shiftwork-checkpoint.json +0 -0
  96. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/skills/.gitkeep +0 -0
  97. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/skills/file-graph.md +0 -0
  98. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/skills/memory.md +0 -0
  99. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/tools/.gitkeep +0 -0
  100. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/tools/bantamkit_read.json +0 -0
  101. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/tools/bantamkit_status.json +0 -0
  102. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/tools/build_identity.json +0 -0
  103. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/tools/document_list.json +0 -0
  104. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/tools/document_read.json +0 -0
  105. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/tools/file_graph.json +0 -0
  106. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/tools/memory_compact.json +0 -0
  107. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/tools/memory_dream.json +0 -0
  108. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/tools/memory_recall.json +0 -0
  109. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/tools/memory_save.json +0 -0
  110. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/tools/repo_map.json +0 -0
  111. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/tools/shiftwork_clock_in.json +0 -0
  112. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/tools/shiftwork_clock_out.json +0 -0
  113. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/tools/shiftwork_status.json +0 -0
  114. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/tools/skill_audit.json +0 -0
  115. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/tools/token_ledger.json +0 -0
  116. {bantamkit-0.34.2 → bantamkit-0.35.0}/_assets/tools/validate_json.json +0 -0
  117. {bantamkit-0.34.2 → bantamkit-0.35.0}/hatch_build.py +0 -0
  118. {bantamkit-0.34.2 → bantamkit-0.35.0}/pyproject.toml +0 -0
  119. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/agent.py +0 -0
  120. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/assets.py +0 -0
  121. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/budget.py +0 -0
  122. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/client.py +0 -0
  123. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/contract.py +0 -0
  124. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/criticreplay.py +0 -0
  125. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/critique.py +0 -0
  126. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/docmanifest.py +0 -0
  127. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/docread.py +0 -0
  128. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/evalrun.py +0 -0
  129. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/filegraph.py +0 -0
  130. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/hostinstall.py +0 -0
  131. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/loopguard.py +0 -0
  132. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/mcpreport.py +0 -0
  133. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/memory/__init__.py +0 -0
  134. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/memory/component.py +0 -0
  135. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/memory/divergence.py +0 -0
  136. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/memory/dream.py +0 -0
  137. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/memory/layers.py +0 -0
  138. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/pdfread.py +0 -0
  139. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/pricing.py +0 -0
  140. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/profile.py +0 -0
  141. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/repomap.py +0 -0
  142. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/selfupdate.py +0 -0
  143. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/skillaudit.py +0 -0
  144. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/statusline.py +0 -0
  145. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/structured.py +0 -0
  146. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/textutil.py +0 -0
  147. {bantamkit-0.34.2 → bantamkit-0.35.0}/src/bantamkit/tokenledger.py +0 -0
  148. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/cli_exit_status_probe.py +0 -0
  149. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/conftest.py +0 -0
  150. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/data/docread/bad-crc.docx +0 -0
  151. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/data/docread/charref-4301-digits.html +0 -0
  152. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/data/docread/charset-table.json +0 -0
  153. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/data/docread/compression-method-9.docx +0 -0
  154. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/data/docread/corrupt-deflate.docx +0 -0
  155. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/data/docread/encrypted-member.docx +0 -0
  156. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/data/docread/encrypted-mimetype.odt +0 -0
  157. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/data/docread/eszett-cell-ref.xlsx +0 -0
  158. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/data/docread/internal-dtd-entity.docx +0 -0
  159. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/data/docread/rfc2231-charset.eml +0 -0
  160. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/data/docread/rfc822-nested-twice.eml +0 -0
  161. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/data/docread/unicode-digit-shared-string.xlsx +0 -0
  162. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/data/docread/x-uuencode.eml +0 -0
  163. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/data/f8404ab-perturbation-baseline.json +0 -0
  164. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/data/platform-assumption-baseline.json +0 -0
  165. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/docread_fixtures.py +0 -0
  166. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/perturbation_baseline_harness.py +0 -0
  167. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/rbp16_effect_probe.py +0 -0
  168. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/rbp18_payload_probe.py +0 -0
  169. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_adapter.py +0 -0
  170. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_agent.py +0 -0
  171. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_amendguard.py +0 -0
  172. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_bantamkit_read_tool.py +0 -0
  173. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_budget.py +0 -0
  174. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_build_identity.py +0 -0
  175. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_client.py +0 -0
  176. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_compaction_corpus_survey.py +0 -0
  177. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_conformance.py +0 -0
  178. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_conformance_harness_resilience.py +0 -0
  179. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_conformance_suite_table_gate.py +0 -0
  180. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_contract_fanout.py +0 -0
  181. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_criticreplay.py +0 -0
  182. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_critique.py +0 -0
  183. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_doc_commands_gate.py +0 -0
  184. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_docread.py +0 -0
  185. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_docread_ceilings.py +0 -0
  186. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_document_manifest_parity.py +0 -0
  187. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_document_setup.py +0 -0
  188. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_document_tasks.py +0 -0
  189. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_document_tools.py +0 -0
  190. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_encoding_gate.py +0 -0
  191. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_evalrun.py +0 -0
  192. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_field_program_gates.py +0 -0
  193. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_field_programs.py +0 -0
  194. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_filegraph.py +0 -0
  195. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_hostinstall.py +0 -0
  196. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_install_shape.py +0 -0
  197. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_ladder_statistics.py +0 -0
  198. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_launcher_which.py +0 -0
  199. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_loopguard.py +0 -0
  200. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_mcp_endpoint.py +0 -0
  201. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_mcpdrift.py +0 -0
  202. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_mcpreport.py +0 -0
  203. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_memory.py +0 -0
  204. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_memory_component.py +0 -0
  205. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_memory_divergence.py +0 -0
  206. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_memory_dream.py +0 -0
  207. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_memory_layers.py +0 -0
  208. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_memory_store_tripwire.py +0 -0
  209. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_mutmatrix.py +0 -0
  210. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_newline_gate.py +0 -0
  211. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_packaging.py +0 -0
  212. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_pdfread.py +0 -0
  213. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_pinharness_ledger.py +0 -0
  214. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_platform_assumption_gate.py +0 -0
  215. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_pricing.py +0 -0
  216. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_repo_map_tool.py +0 -0
  217. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_repomap.py +0 -0
  218. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_selfupdate.py +0 -0
  219. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_statusline.py +0 -0
  220. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_structured.py +0 -0
  221. {bantamkit-0.34.2 → bantamkit-0.35.0}/tests/test_thread_exception_gate.py +0 -0
  222. {bantamkit-0.34.2 → bantamkit-0.35.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.34.2
3
+ Version: 0.35.0
4
4
  Summary: Memory MCP server for Claude Code, Cursor, VS Code Copilot and Claude Desktop, plus a Python library that lifts small-model agents. Install once, run offline.
5
5
  Project-URL: Homepage, https://github.com/Ink01101011/bantamkit
6
6
  Project-URL: Repository, https://github.com/Ink01101011/bantamkit
@@ -55,7 +55,7 @@ easy.
55
55
  | [Configuration](#configuration) | Every flag and variable, with its default |
56
56
  | [Update](#update) | `--update`, pipx, uv, then restart |
57
57
  | [Troubleshooting](#troubleshooting) | Timeouts, refusals, the wrong store |
58
- | [What it serves](#what-it-serves) | 12 tools, one prompt, two resource templates |
58
+ | [What it serves](#what-it-serves) | 14 tools, one prompt, two resource templates |
59
59
  | [Requirements](#requirements) | Python and dependencies |
60
60
  | [The asset pack](#the-asset-pack) | `--assets-root`, `BANTAMKIT_ASSETS` |
61
61
  | [The operator CLI: `python -m bantamkit.memory`](#the-operator-cli-python--m-bantamkitmemory) | status, lint, compact, archived, archive, restore |
@@ -88,7 +88,8 @@ python -m venv <env>
88
88
  ```
89
89
 
90
90
  `--install` records the venv's console script by absolute path with `"args": []`, so no launch
91
- needs the network or your shell's PATH. Measured on macOS arm64, it served 12 tools under a GUI
91
+ needs the network or your shell's PATH. Measured on macOS arm64 (served-tools: dated — the
92
+ surface was twelve then), it served 12 tools under a GUI
92
93
  app's PATH, `/usr/bin:/bin:/usr/sbin:/sbin`; the Windows layout (`<env>\Scripts\`) was not.
93
94
 
94
95
  **No network on the target? Carry a wheelhouse.** Download it on a machine with the **same OS,
@@ -96,9 +97,9 @@ CPU architecture and Python minor version** (some wheels, such as `pydantic_core
96
97
  one platform only), copy `wheels/` across, and install from it:
97
98
 
98
99
  ```bash
99
- python -m pip download "bantamkit[mcp]==0.34.2" -d wheels
100
+ python -m pip download "bantamkit[mcp]==0.35.0" -d wheels
100
101
  python -m venv <env>
101
- <env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.34.2"
102
+ <env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.35.0"
102
103
  <env>/bin/bantamkit-mcp --install cursor
103
104
  ```
104
105
 
@@ -247,10 +248,10 @@ pipx upgrade bantamkit # pipx
247
248
  uv tool upgrade bantamkit # uv
248
249
  ```
249
250
 
250
- `--update` (`check the package index and update this install if it differs, then exit`) is the
251
- only network access here. An editable install, a local file or a source tree is refused with
252
- exit 1 and a sentence naming the manual route (for a clone, `git pull`). For a wheelhouse, repeat
253
- the download and install.
251
+ `--update` (`check the package index and update this install if it differs, then exit`) upgrades
252
+ an install from PyPI with this interpreter's pip; it is the only network access here. An editable
253
+ install, a local file or a source tree is refused with exit 1 and a sentence naming the manual
254
+ route (for a clone, `git pull`). For a wheelhouse, repeat the download and install.
254
255
 
255
256
  **Then restart the server in the host** (`/mcp` → reconnect in Claude Code; fully restart Claude
256
257
  Desktop). `bantamkit_status` prints the version **and the `build_id` of the code answering
@@ -272,7 +273,7 @@ you**; a new version with an old `build_id` means an old process.
272
273
 
273
274
  ## What it serves
274
275
 
275
- 12 tools, the `bantamkit_status` prompt and two resource templates
276
+ It serves 14 tools, the `bantamkit_status` prompt and two resource templates
276
277
  (`bantamkit://skills/{name}`, `bantamkit://rubrics/{name}`):
277
278
 
278
279
  | Tool | What it does |
@@ -286,9 +287,11 @@ you**; a new version with an old `build_id` means an old process.
286
287
  | `shiftwork_clock_in` | open a unit of work and get its brief |
287
288
  | `shiftwork_clock_out` | close a unit with status and accounting |
288
289
  | `shiftwork_status` | report the open cursor |
290
+ | `shiftwork_plan` | read-only: which units of a checkpoint its `depends_on` graph permits to run at once. Never moves the cursor |
291
+ | `work_plan` | turn any `{id, depends_on, priority}` graph into the batches that may run in parallel, plus the widest fan-out |
289
292
  | `token_ledger` | what a session cost, read off the host's transcripts |
290
293
  | `bantamkit_status` | report store health against its budget |
291
- | `build_identity` | report the fingerprint of the source on disk, not the executing code |
294
+ | `build_identity` | report the fingerprint of the source on disk, not the executing code — useful when a machine carries two installs under one name |
292
295
 
293
296
  The document reader `bantamkit_read` left the served tools in job50 (2026-09-12) and stays in the
294
297
  library as `bantamkit.docread`.
@@ -303,7 +306,7 @@ under the `[mcp]` extra.
303
306
  Contracts, schemas, eval tasks, rubrics and tool manifests ship inside the package.
304
307
  `bantamkit-mcp --assets-root` prints the resolved directory and its file count, and
305
308
  `BANTAMKIT_ASSETS` overrides it. A build that cannot find the pack fails instead of shipping
306
- without it.
309
+ without it — deliberately, because the silent version shipped once.
307
310
 
308
311
  ## The operator CLI: `python -m bantamkit.memory`
309
312
 
@@ -342,7 +345,7 @@ git clone https://github.com/Ink01101011/bantamkit
342
345
  cd bantamkit
343
346
  python -m venv .venv && .venv/bin/pip install -e "runtime-py[dev,mcp]"
344
347
  .venv/bin/python -m pytest runtime-py/tests -q
345
- .venv/bin/ruff check runtime-py
348
+ .venv/bin/ruff check runtime-py tools
346
349
  ```
347
350
 
348
351
  The cross-runtime gate needs Node:
@@ -27,7 +27,7 @@ easy.
27
27
  | [Configuration](#configuration) | Every flag and variable, with its default |
28
28
  | [Update](#update) | `--update`, pipx, uv, then restart |
29
29
  | [Troubleshooting](#troubleshooting) | Timeouts, refusals, the wrong store |
30
- | [What it serves](#what-it-serves) | 12 tools, one prompt, two resource templates |
30
+ | [What it serves](#what-it-serves) | 14 tools, one prompt, two resource templates |
31
31
  | [Requirements](#requirements) | Python and dependencies |
32
32
  | [The asset pack](#the-asset-pack) | `--assets-root`, `BANTAMKIT_ASSETS` |
33
33
  | [The operator CLI: `python -m bantamkit.memory`](#the-operator-cli-python--m-bantamkitmemory) | status, lint, compact, archived, archive, restore |
@@ -60,7 +60,8 @@ python -m venv <env>
60
60
  ```
61
61
 
62
62
  `--install` records the venv's console script by absolute path with `"args": []`, so no launch
63
- needs the network or your shell's PATH. Measured on macOS arm64, it served 12 tools under a GUI
63
+ needs the network or your shell's PATH. Measured on macOS arm64 (served-tools: dated — the
64
+ surface was twelve then), it served 12 tools under a GUI
64
65
  app's PATH, `/usr/bin:/bin:/usr/sbin:/sbin`; the Windows layout (`<env>\Scripts\`) was not.
65
66
 
66
67
  **No network on the target? Carry a wheelhouse.** Download it on a machine with the **same OS,
@@ -68,9 +69,9 @@ CPU architecture and Python minor version** (some wheels, such as `pydantic_core
68
69
  one platform only), copy `wheels/` across, and install from it:
69
70
 
70
71
  ```bash
71
- python -m pip download "bantamkit[mcp]==0.34.2" -d wheels
72
+ python -m pip download "bantamkit[mcp]==0.35.0" -d wheels
72
73
  python -m venv <env>
73
- <env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.34.2"
74
+ <env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.35.0"
74
75
  <env>/bin/bantamkit-mcp --install cursor
75
76
  ```
76
77
 
@@ -219,10 +220,10 @@ pipx upgrade bantamkit # pipx
219
220
  uv tool upgrade bantamkit # uv
220
221
  ```
221
222
 
222
- `--update` (`check the package index and update this install if it differs, then exit`) is the
223
- only network access here. An editable install, a local file or a source tree is refused with
224
- exit 1 and a sentence naming the manual route (for a clone, `git pull`). For a wheelhouse, repeat
225
- the download and install.
223
+ `--update` (`check the package index and update this install if it differs, then exit`) upgrades
224
+ an install from PyPI with this interpreter's pip; it is the only network access here. An editable
225
+ install, a local file or a source tree is refused with exit 1 and a sentence naming the manual
226
+ route (for a clone, `git pull`). For a wheelhouse, repeat the download and install.
226
227
 
227
228
  **Then restart the server in the host** (`/mcp` → reconnect in Claude Code; fully restart Claude
228
229
  Desktop). `bantamkit_status` prints the version **and the `build_id` of the code answering
@@ -244,7 +245,7 @@ you**; a new version with an old `build_id` means an old process.
244
245
 
245
246
  ## What it serves
246
247
 
247
- 12 tools, the `bantamkit_status` prompt and two resource templates
248
+ It serves 14 tools, the `bantamkit_status` prompt and two resource templates
248
249
  (`bantamkit://skills/{name}`, `bantamkit://rubrics/{name}`):
249
250
 
250
251
  | Tool | What it does |
@@ -258,9 +259,11 @@ you**; a new version with an old `build_id` means an old process.
258
259
  | `shiftwork_clock_in` | open a unit of work and get its brief |
259
260
  | `shiftwork_clock_out` | close a unit with status and accounting |
260
261
  | `shiftwork_status` | report the open cursor |
262
+ | `shiftwork_plan` | read-only: which units of a checkpoint its `depends_on` graph permits to run at once. Never moves the cursor |
263
+ | `work_plan` | turn any `{id, depends_on, priority}` graph into the batches that may run in parallel, plus the widest fan-out |
261
264
  | `token_ledger` | what a session cost, read off the host's transcripts |
262
265
  | `bantamkit_status` | report store health against its budget |
263
- | `build_identity` | report the fingerprint of the source on disk, not the executing code |
266
+ | `build_identity` | report the fingerprint of the source on disk, not the executing code — useful when a machine carries two installs under one name |
264
267
 
265
268
  The document reader `bantamkit_read` left the served tools in job50 (2026-09-12) and stays in the
266
269
  library as `bantamkit.docread`.
@@ -275,7 +278,7 @@ under the `[mcp]` extra.
275
278
  Contracts, schemas, eval tasks, rubrics and tool manifests ship inside the package.
276
279
  `bantamkit-mcp --assets-root` prints the resolved directory and its file count, and
277
280
  `BANTAMKIT_ASSETS` overrides it. A build that cannot find the pack fails instead of shipping
278
- without it.
281
+ without it — deliberately, because the silent version shipped once.
279
282
 
280
283
  ## The operator CLI: `python -m bantamkit.memory`
281
284
 
@@ -314,7 +317,7 @@ git clone https://github.com/Ink01101011/bantamkit
314
317
  cd bantamkit
315
318
  python -m venv .venv && .venv/bin/pip install -e "runtime-py[dev,mcp]"
316
319
  .venv/bin/python -m pytest runtime-py/tests -q
317
- .venv/bin/ruff check runtime-py
320
+ .venv/bin/ruff check runtime-py tools
318
321
  ```
319
322
 
320
323
  The cross-runtime gate needs Node:
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "shiftwork_plan",
3
+ "description": "Shift-work plan: read-only batch view of a checkpoint — which units its `depends_on` graph permits to run in parallel. Answers `{\"result\": \"plan\", \"batches\", \"ready\", \"sequence\", \"width\", \"cursor\"}`: `ready` is `batches[0]`, the units dispatchable right now, and `width` is the largest batch length, the widest fan-out. Units with status `done` or `dropped` are satisfied — dropped from the graph, and edges pointing at them treated as already resolved; `todo`, `in_progress` and `blocked` stay in it. Every unit has priority 0, because the checkpoint schema has no priority field, so order inside a batch is `plan.units` order. Same refusal sentences as `work_plan` for a duplicate id, an unknown dependency or a cycle, and the same refusals `shiftwork_status` gives for a checkpoint it cannot read. It reports what the dependency graph permits; it does not move the cursor, which is echoed unchanged so the single-pointer contract and the batch view can be read side by side. An orchestrator stays responsible for what it actually dispatches: `depends_on` encodes logical order, not file contention. Never mutates.",
4
+ "surfaces": [
5
+ "mcp"
6
+ ],
7
+ "parameters": {
8
+ "properties": {
9
+ "checkpoint": {
10
+ "title": "Checkpoint",
11
+ "type": "string"
12
+ }
13
+ },
14
+ "required": [
15
+ "checkpoint"
16
+ ],
17
+ "type": "object",
18
+ "title": "shiftwork_planArguments"
19
+ },
20
+ "output_schema": {
21
+ "type": "object",
22
+ "additionalProperties": true,
23
+ "title": "shiftwork_planDictOutput"
24
+ }
25
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "work_plan",
3
+ "description": "Work plan: turn a dependency graph into the batches that may run in parallel. Takes `nodes`, each `{id, depends_on, priority}` — `priority` defaults to 0 — and answers `{\"result\": \"plan\", \"batches\", \"sequence\", \"width\"}`: batch k holds every node whose dependencies all appear in batches below k, `sequence` is those batches flattened in order, and `width` is the largest batch length — the widest fan-out, which is the number an orchestrator needs to decide whether it can afford the batch. Order inside a batch is priority descending, then the order the nodes were given; both halves are contract, because determinism inside a batch is what two implementations are held to. Empty `nodes` is an ANSWER, not a refusal: no batches, no sequence, width 0. Three refusals, checked in this order and one sentence each: `duplicate node id <id>`, `node <id> depends on <dep>, which no node declares`, and `the graph has a cycle: <a> -> <b> -> <a>`. It reports what the graph permits and decides nothing about what may actually be dispatched together — `depends_on` encodes logical order, not file contention, so a batch this tool calls parallel may still hold work that writes one file. Pure computation over the nodes it was handed: it opens no file and takes no path. Never mutates.",
4
+ "surfaces": [
5
+ "mcp"
6
+ ],
7
+ "parameters": {
8
+ "properties": {
9
+ "nodes": {
10
+ "title": "Nodes",
11
+ "type": "array",
12
+ "items": {
13
+ "type": "object",
14
+ "properties": {
15
+ "id": {
16
+ "type": "string",
17
+ "description": "The node's identifier, unique across `nodes`"
18
+ },
19
+ "depends_on": {
20
+ "type": "array",
21
+ "items": {
22
+ "type": "string"
23
+ },
24
+ "description": "Ids this node waits for; every one of them must be declared by some node"
25
+ },
26
+ "priority": {
27
+ "type": "integer",
28
+ "description": "Tie-break inside a batch, descending. Optional; 0 when absent"
29
+ }
30
+ },
31
+ "required": [
32
+ "id",
33
+ "depends_on"
34
+ ]
35
+ }
36
+ }
37
+ },
38
+ "required": [
39
+ "nodes"
40
+ ],
41
+ "type": "object",
42
+ "title": "work_planArguments"
43
+ },
44
+ "output_schema": {
45
+ "type": "object",
46
+ "additionalProperties": true,
47
+ "title": "work_planDictOutput"
48
+ }
49
+ }
@@ -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.34.2"
32
+ __version__ = "0.35.0"
@@ -30,11 +30,13 @@ THREE HARD RULES, each with the failure it prevents:
30
30
  byte-compares both runtimes' streams; one stray write breaks the wire suite. Nothing
31
31
  in this module touches `sys.stderr` or `sys.stdout`.
32
32
  * **Metadata only.** Never a tool argument's value, never a memory body, never a
33
- validated output, never a query, never a document row. Eight of the twelve tools take
33
+ validated output, never a query, never a document row. Nine of the fourteen tools take
34
34
  an unbounded string (counted over `assets/tools/`: a `string` parameter with no `enum`
35
35
  or `maxLength`, or an array of such; `bantamkit_read` and `repo_map` were two more until
36
- job50 I5 retired them from the roster) and among those `skill_audit`'s `root`,
37
- `token_ledger`'s `root` and `prices` and the three shift-work tools' `checkpoint` are
36
+ job50 I5 retired them from the roster — and `work_plan` is NOT one of them: its `nodes`
37
+ is an array of objects, not an array of strings, so the rule does not reach the ids
38
+ inside it) and among those `skill_audit`'s `root`,
39
+ `token_ledger`'s `root` and `prices` and the four shift-work tools' `checkpoint` are
38
40
  absolute paths; each record carries counts and tokens from a closed set, never the
39
41
  path, never a part name, never a skill id, never a mapped file, never a session id,
40
42
  never a model name. The dormant `bantamkit_read` and `repo_map` handlers keep the same
@@ -69,7 +71,7 @@ from datetime import UTC, datetime, timedelta
69
71
  from pathlib import Path
70
72
  from typing import Any
71
73
 
72
- from bantamkit.memory.store import ensure_bantamkit_gitignore
74
+ from bantamkit.memory.store import bantamkit_dir_for, ensure_bantamkit_gitignore
73
75
 
74
76
  #: Environment switch. Unset or `off`/`0`/`false`/`no`/empty -> disabled. `on`/`1`/
75
77
  #: `true`/`yes` -> the default file inside the memory store. Anything else is taken as
@@ -289,11 +291,12 @@ class EventLog:
289
291
  # USER RULING #2 (J51-8a): the ignore file is written only when THIS call is what
290
292
  # creates `.bantamkit`, so existence has to be checked BEFORE the mkdir below --
291
293
  # 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
294
+ #
295
+ # J54-3: the walk moved into `bantamkit_dir_for` beside the writer it feeds, so the
296
+ # two creators in this runtime ask the same question in the same words rather than
297
+ # keeping a copy each. The answer here is unchanged -- a log file is never itself
298
+ # named `.bantamkit`, so self-or-ancestors and ancestors give the same directory.
299
+ bantamkit_dir = bantamkit_dir_for(self.path)
297
300
  bantamkit_dir_existed_before = bantamkit_dir is not None and bantamkit_dir.exists()
298
301
  self.path.parent.mkdir(parents=True, exist_ok=True)
299
302
  if bantamkit_dir is not None:
@@ -30,6 +30,7 @@ from bantamkit import (
30
30
  shiftwork,
31
31
  skillaudit,
32
32
  tokenledger,
33
+ workplan,
33
34
  )
34
35
  from bantamkit.assets import AssetNotFound, assets_root, load_skill, load_tool_asset
35
36
  from bantamkit.client import BantamError
@@ -1474,6 +1475,24 @@ def build_server(memory: Memory, log: EventLog | None = None) -> Any:
1474
1475
  _record_result(log, "shiftwork_status", lambda: shiftwork.status(checkpoint))
1475
1476
  )
1476
1477
 
1478
+ def work_plan(nodes: list[dict[str, Any]]) -> dict[str, Any]:
1479
+ # `result` is added HERE and not in `workplan.plan`, which is Layer 1 and answers
1480
+ # the computation (`batches`, `sequence`, `width`) rather than a wire shape. The
1481
+ # wire shape is the tool asset's, so the verdict key is put on at the seam that
1482
+ # serves it — the same division `plan_batches` uses one layer down. A refusal
1483
+ # already carries its own `result` and passes through untouched, because
1484
+ # `_record_result` reads that key to write the register's verdict to the log.
1485
+ def answered() -> dict[str, Any]:
1486
+ plan = workplan.plan(nodes)
1487
+ return plan if plan.get("result") == "error" else {"result": "plan", **plan}
1488
+
1489
+ return _noted_dict(_record_result(log, "work_plan", answered))
1490
+
1491
+ def shiftwork_plan(checkpoint: str) -> dict[str, Any]:
1492
+ return _noted_dict(
1493
+ _record_result(log, "shiftwork_plan", lambda: shiftwork.plan_batches(checkpoint))
1494
+ )
1495
+
1477
1496
  # A TOOL and not a resource or an `initialize` field, because the gap RB-P84 names is
1478
1497
  # an AGENT MID-CALL: the host reads `serverInfo` once at handshake and the
1479
1498
  # tool-calling model never sees it, and `resources/read` is a host-facing surface
@@ -1832,6 +1851,8 @@ def build_server(memory: Memory, log: EventLog | None = None) -> Any:
1832
1851
  _from_manifest(skill_audit, "skill_audit"),
1833
1852
  _from_manifest(memory_dream, "memory_dream"),
1834
1853
  _from_manifest(token_ledger, "token_ledger"),
1854
+ _from_manifest(work_plan, "work_plan"),
1855
+ _from_manifest(shiftwork_plan, "shiftwork_plan"),
1835
1856
  ]
1836
1857
 
1837
1858
  server = MCPServer(
@@ -2,7 +2,7 @@
2
2
 
3
3
  `docs/memory.md` states, as a design decision, that `lint`, `archived` and
4
4
  `restore` are **not** agent tools — "lifecycle is an operator decision, not a
5
- model decision" — and the twelve-tool surface holds to it; `compact` is the one
5
+ model decision" — and the fourteen-tool surface holds to it; `compact` is the one
6
6
  exception since job42 (`memory_compact`, the on-refusal path the model reaches
7
7
  itself). That position is only coherent if the operator can actually make the
8
8
  decision. Measured
@@ -286,6 +286,14 @@ def ensure_bantamkit_gitignore(directory: Path, *, created: bool) -> None:
286
286
  would leave a `.bantamkit` that git can see (or ignore one it should not), which is
287
287
  the whole bug this closes.
288
288
 
289
+ THE CREATORS, ENUMERATED (J54-3), because "anything else" is how one got missed: in
290
+ THIS runtime there are exactly two -- `MemoryStore._ensure_dirs` and `EventLog._append`
291
+ -- and both now ask `bantamkit_dir_for` which directory the decision is about, rather
292
+ than assuming the root's parent is it. `runtime-ts` has a THIRD, `hostinstall`'s
293
+ `thisCommand`, where npm creates `~/.bantamkit/mcp` for `--install`; there is no npm
294
+ install path in this runtime (the offline install is Node-only by user ruling), which
295
+ is why that creator has no counterpart here. `docs/porting.md` carries the row.
296
+
289
297
  NEVER REWRITES. A `.gitignore` that already exists -- whatever its bytes -- is left
290
298
  exactly as it is, kept as a second guard even when `created` is `True`: an operator
291
299
  who deleted it to commit the directory keeps that decision, and this never diffs its
@@ -313,6 +321,34 @@ def ensure_bantamkit_gitignore(directory: Path, *, created: bool) -> None:
313
321
  pass
314
322
 
315
323
 
324
+ def bantamkit_dir_for(path: Path) -> Path | None:
325
+ """The `.bantamkit` directory a `mkdir(parents=True)` of `path` would create or fill,
326
+ or `None` when there is none: `path` itself if it is named `.bantamkit`, otherwise its
327
+ nearest ancestor that is.
328
+
329
+ WHY A WALK AND NOT `path.parent` (J54-3). Every creator has to hand
330
+ `ensure_bantamkit_gitignore` the directory the decision is ABOUT, and `parent` is the
331
+ right answer for exactly one shape of path, `<x>/.bantamkit/memory`. A store rooted AT
332
+ the `.bantamkit` directory (`--store ~/.bantamkit`) or nested deeper under it
333
+ (`<x>/.bantamkit/memory/extra`) creates a `.bantamkit` whose gitignore decision was
334
+ taken about the wrong directory -- so no decision was taken at all, and git could see
335
+ the whole tree. MEASURED, not hypothetical: `~/.bantamkit` on the machine this was
336
+ found on holds an empty `facts/` and `archive/` beside `memory/`, left by a store once
337
+ rooted at it, and no `.gitignore`.
338
+
339
+ NEAREST AND NOT OUTERMOST, which is the choice `EventLog._append` already made and this
340
+ function now carries for both creators: under a `<x>/.bantamkit/y/.bantamkit/z` the
341
+ directory a reader would expect to be ignored is the one the store is actually in.
342
+
343
+ READ-ONLY. It looks at names, never at the filesystem, so it is equally valid before or
344
+ after the mkdir -- but `created` is answered by checking THIS path's existence BEFORE.
345
+ """
346
+ for candidate in (path, *path.parents):
347
+ if candidate.name == ".bantamkit":
348
+ return candidate
349
+ return None
350
+
351
+
316
352
  class MemoryStore:
317
353
  def __init__(
318
354
  self,
@@ -405,8 +441,16 @@ class MemoryStore:
405
441
  # Checked BEFORE the mkdir, per user ruling #2: this is the one moment that can
406
442
  # tell whether `.bantamkit` already existed. After the mkdir it unconditionally
407
443
  # 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()
444
+ #
445
+ # J54-3: `bantamkit_dir_for`, not `self.root.parent`. The mkdir below is
446
+ # `parents=True`, so it creates every missing component of `self.root` -- a root
447
+ # that IS the `.bantamkit` directory, or one nested deeper under it, brought a
448
+ # `.bantamkit` into existence while the decision was being taken about some other
449
+ # directory, which meant no decision at all.
450
+ bantamkit_dir = bantamkit_dir_for(self.root)
451
+ bantamkit_dir_existed_before = (
452
+ bantamkit_dir is not None and bantamkit_dir.exists()
453
+ )
410
454
  try:
411
455
  (self.root / "facts").mkdir(parents=True, exist_ok=True)
412
456
  (self.root / "archive").mkdir(parents=True, exist_ok=True)
@@ -415,7 +459,10 @@ class MemoryStore:
415
459
  f"memory store could not be created: {self.root}; the directory is not"
416
460
  " there and this filesystem would not make it, so nothing was written"
417
461
  ) from e
418
- ensure_bantamkit_gitignore(bantamkit_dir, created=not bantamkit_dir_existed_before)
462
+ if bantamkit_dir is not None:
463
+ ensure_bantamkit_gitignore(
464
+ bantamkit_dir, created=not bantamkit_dir_existed_before
465
+ )
419
466
 
420
467
  # ---- ops ----
421
468
 
@@ -67,7 +67,9 @@ a missing one.
67
67
 
68
68
  Cursor advance is v1-linear: it moves to the first non-terminal unit in plan
69
69
  order and ignores `depends_on` — non-linear plans need a planner unit to
70
- reorder `plan.units` first.
70
+ reorder `plan.units` first. `plan_batches` below READS `depends_on` and answers
71
+ the batch view, so the module no longer ignores the field; what still ignores it
72
+ is CURSOR ADVANCE, and the batch view is read-only and moves nothing.
71
73
 
72
74
  No lock: the MCP topology has one orchestrator by construction. The driver's
73
75
  O_EXCL lock guards cross-process races this shape does not have, and clock-out
@@ -84,6 +86,7 @@ from typing import Any
84
86
 
85
87
  import jsonschema
86
88
 
89
+ from bantamkit import workplan
87
90
  from bantamkit.assets import AssetNotFound, load_schema, load_tool_asset
88
91
  from bantamkit.contract import schema_error
89
92
 
@@ -550,3 +553,67 @@ def status(checkpoint: str) -> dict[str, Any]:
550
553
  "open_questions": len(document["handoff"]["open_questions"]),
551
554
  "last_history": history[-1] if history else None,
552
555
  }
556
+
557
+
558
+ def plan_batches(checkpoint: str) -> dict[str, Any]:
559
+ """Read-only batch view of a checkpoint. Never mutates.
560
+
561
+ The adapter over `workplan.plan`, and it holds no graph logic of its own: a loop over
562
+ `depends_on` here would be Layer 1's work done in Layer 5. Its whole content is the
563
+ three decisions below plus the shape it answers in.
564
+
565
+ **A `done` or `dropped` unit is SATISFIED**, which is two things and not one: it
566
+ leaves the graph, AND every edge pointing at it is treated as already resolved. Only
567
+ the first would be a defect rather than a simplification — `workplan.plan` refuses an
568
+ edge into an id no node declares, so dropping the unit while keeping the edge would
569
+ make every checkpoint with one finished unit unplannable, which is every checkpoint
570
+ after its first clock-out. `todo`, `in_progress` and `blocked` are all still work and
571
+ all stay in. The terminal pair is `TERMINAL_UNIT_STATUS`, the same constant the
572
+ driver's success test uses, so "finished" means one thing in this module.
573
+
574
+ **Every unit gets priority 0.** The checkpoint schema has no priority field and this
575
+ design does not add one, so the tie-break inside a batch falls through to the core's
576
+ insertion order — `plan.units` order, which is the order a reader of the checkpoint
577
+ already sees.
578
+
579
+ **The cursor is ECHOED, never written.** `clock_out` remains the only thing that
580
+ moves it and stays v1-linear; this tool reports what the graph PERMITS beside the
581
+ single pointer that says what the driver will actually do next, so an orchestrator
582
+ can read the two side by side and decide. Advisory, in one direction only.
583
+
584
+ Returns `{"result": "plan", "batches", "ready", "sequence", "width", "cursor"}` —
585
+ `ready` is `batches[0]`, or `[]` when the plan is all terminal, which is an ANSWER
586
+ and not a refusal. Refusals pass through verbatim in both directions: `_read_valid`'s
587
+ for a checkpoint that cannot be read or does not validate, and the core's own
588
+ duplicate-id / unknown-dependency / cycle sentences for a graph that cannot batch.
589
+ """
590
+ document, refusal = _read_valid(Path(checkpoint))
591
+ if refusal is not None:
592
+ return refusal
593
+ nodes = [
594
+ # `depends_on` is required by the schema, so `_read_valid` has already refused a
595
+ # unit without it and the default below cannot fire here. It is written anyway
596
+ # because the Node adapter reaches the same mapping and MUST default it too: a
597
+ # default on one side only is how two runtimes come to disagree about real data.
598
+ {"id": unit["id"], "depends_on": list(unit.get("depends_on") or []), "priority": 0}
599
+ for unit in document["plan"]["units"]
600
+ if unit["status"] not in TERMINAL_UNIT_STATUS
601
+ ]
602
+ satisfied = {
603
+ unit["id"] for unit in document["plan"]["units"] if unit["status"] in TERMINAL_UNIT_STATUS
604
+ }
605
+ for node in nodes:
606
+ node["depends_on"] = [dep for dep in node["depends_on"] if dep not in satisfied]
607
+
608
+ answer = workplan.plan(nodes)
609
+ if answer.get("result") == "error":
610
+ return answer
611
+ batches = answer["batches"]
612
+ return {
613
+ "result": "plan",
614
+ "batches": batches,
615
+ "ready": batches[0] if batches else [],
616
+ "sequence": answer["sequence"],
617
+ "width": answer["width"],
618
+ "cursor": document["plan"]["cursor"],
619
+ }