bantamkit 0.38.0__tar.gz → 0.38.2__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 (318) hide show
  1. {bantamkit-0.38.0 → bantamkit-0.38.2}/PKG-INFO +3 -3
  2. {bantamkit-0.38.0 → bantamkit-0.38.2}/README.md +2 -2
  3. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/bantamkit_status.json +8 -2
  4. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/__init__.py +1 -1
  5. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/mcpserver.py +24 -7
  6. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/skillmine.py +153 -8
  7. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/storage/tiers.py +50 -8
  8. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/data/served-tool-surface.json +8 -2
  9. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_skillmine.py +130 -0
  10. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_status_surface.py +32 -0
  11. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_storage_tiers.py +70 -1
  12. {bantamkit-0.38.0 → bantamkit-0.38.2}/.gitignore +0 -0
  13. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/contracts/default.yaml +0 -0
  14. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/manifest.yaml +0 -0
  15. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/repo/HISTORY.md +0 -0
  16. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/repo/README.md +0 -0
  17. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/repo/docs/architecture.md +0 -0
  18. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/repo/docs/runbook.md +0 -0
  19. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/repo/issues/142-settlement-timeout.md +0 -0
  20. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/repo/patches/0009-retry-budget.patch +0 -0
  21. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/repo/src/ledger/__init__.py +0 -0
  22. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/repo/src/ledger/config.py +0 -0
  23. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/repo/src/ledger/errors.py +0 -0
  24. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/repo/src/ledger/posting.py +0 -0
  25. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/repo/src/ledger/registry.py +0 -0
  26. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/repo/src/ledger/report.py +0 -0
  27. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/repo/src/ledger/retry.py +0 -0
  28. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/repo/src/ledger/settle.py +0 -0
  29. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/repo/src/ledger/validate.py +0 -0
  30. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/repo/tests/test_posting.py +0 -0
  31. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/repo/tests/test_settle.py +0 -0
  32. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/tasks/dt-error-contract.yaml +0 -0
  33. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/tasks/dt-handler-map.yaml +0 -0
  34. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/tasks/dt-patch-before-after.yaml +0 -0
  35. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/tasks/dt-retry-attempts.yaml +0 -0
  36. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/tasks/dt-settlement-config.yaml +0 -0
  37. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/tasks/dt-symbol-home.yaml +0 -0
  38. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/tasks/dt-trace-blame.yaml +0 -0
  39. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/devteam/tasks/dt-unread-key.yaml +0 -0
  40. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/document/tasks/doc-large-in-137.yaml +0 -0
  41. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/document/tasks/doc-large-in-359.yaml +0 -0
  42. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/document/tasks/doc-large-in-372.yaml +0 -0
  43. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/document/tasks/doc-large-out-11764.yaml +0 -0
  44. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/document/tasks/doc-large-out-4137.yaml +0 -0
  45. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/document/tasks/doc-large-out-8022.yaml +0 -0
  46. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/document/tasks/doc-small-137.yaml +0 -0
  47. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/document/tasks/doc-small-261.yaml +0 -0
  48. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/document/tasks/doc-small-388.yaml +0 -0
  49. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/fixtures/.gitkeep +0 -0
  50. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/fixtures/catalog.json +0 -0
  51. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/perturbations/task-completion.yaml +0 -0
  52. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/.gitkeep +0 -0
  53. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/extract-contact.yaml +0 -0
  54. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/extract-invoice.yaml +0 -0
  55. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/extract-order.yaml +0 -0
  56. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/extract-schedule.yaml +0 -0
  57. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/extract-versions.yaml +0 -0
  58. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/nav-prod-port.yaml +0 -0
  59. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/nav-release-bundle.yaml +0 -0
  60. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/recall-audit-retention.yaml +0 -0
  61. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/recall-cache-ttl.yaml +0 -0
  62. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/recall-db-port.yaml +0 -0
  63. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/recall-deploy.yaml +0 -0
  64. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/recall-env-endpoint.yaml +0 -0
  65. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/recall-oncall-rotation.yaml +0 -0
  66. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/recall-oncall.yaml +0 -0
  67. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/recall-org-quota.yaml +0 -0
  68. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/recall-owner.yaml +0 -0
  69. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/shop-basket-total.yaml +0 -0
  70. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/shop-cheapest.yaml +0 -0
  71. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/shop-compare.yaml +0 -0
  72. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/shop-gadget-value.yaml +0 -0
  73. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/shop-stock-total.yaml +0 -0
  74. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/evals/tasks/shop-total.yaml +0 -0
  75. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/pricing/default.json +0 -0
  76. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/profiles/default.yaml +0 -0
  77. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/profiles/patient.yaml +0 -0
  78. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/rubrics/.gitkeep +0 -0
  79. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/rubrics/code-quality.yaml +0 -0
  80. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/rubrics/grounded-completion.yaml +0 -0
  81. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/rubrics/task-completion.yaml +0 -0
  82. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/schemas/shiftwork-checkpoint.json +0 -0
  83. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/skills/.gitkeep +0 -0
  84. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/skills/file-graph.md +0 -0
  85. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/skills/memory-queries.md +0 -0
  86. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/skills/memory.md +0 -0
  87. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/.gitkeep +0 -0
  88. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/bantamkit_read.json +0 -0
  89. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/build_identity.json +0 -0
  90. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/document_list.json +0 -0
  91. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/document_read.json +0 -0
  92. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/file_graph.json +0 -0
  93. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/memory_compact.json +0 -0
  94. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/memory_dream.json +0 -0
  95. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/memory_queries.json +0 -0
  96. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/memory_recall.json +0 -0
  97. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/memory_save.json +0 -0
  98. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/repo_map.json +0 -0
  99. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/shiftwork_clock_in.json +0 -0
  100. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/shiftwork_clock_out.json +0 -0
  101. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/shiftwork_draft.json +0 -0
  102. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/shiftwork_plan.json +0 -0
  103. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/shiftwork_status.json +0 -0
  104. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/skill_audit.json +0 -0
  105. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/token_ledger.json +0 -0
  106. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/validate_json.json +0 -0
  107. {bantamkit-0.38.0 → bantamkit-0.38.2}/_assets/tools/work_plan.json +0 -0
  108. {bantamkit-0.38.0 → bantamkit-0.38.2}/hatch_build.py +0 -0
  109. {bantamkit-0.38.0 → bantamkit-0.38.2}/pyproject.toml +0 -0
  110. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/agent.py +0 -0
  111. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/assets.py +0 -0
  112. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/budget.py +0 -0
  113. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/client.py +0 -0
  114. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/contract.py +0 -0
  115. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/criticreplay.py +0 -0
  116. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/critique.py +0 -0
  117. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/docmanifest.py +0 -0
  118. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/docread.py +0 -0
  119. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/evalrun.py +0 -0
  120. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/eventlog.py +0 -0
  121. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/experiment.py +0 -0
  122. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/filegraph.py +0 -0
  123. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/hookadapter.py +0 -0
  124. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/hostinstall.py +0 -0
  125. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/jobbudget.py +0 -0
  126. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/loopguard.py +0 -0
  127. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/mcpreport.py +0 -0
  128. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/mcpstdio.py +0 -0
  129. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/memory/__init__.py +0 -0
  130. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/memory/__main__.py +0 -0
  131. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/memory/component.py +0 -0
  132. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/memory/divergence.py +0 -0
  133. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/memory/dream.py +0 -0
  134. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/memory/layers.py +0 -0
  135. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/memory/native.py +0 -0
  136. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/memory/queries.py +0 -0
  137. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/memory/retrieval.py +0 -0
  138. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/memory/skills.py +0 -0
  139. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/memory/store.py +0 -0
  140. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/memory/usage.py +0 -0
  141. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/pdfread.py +0 -0
  142. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/pricing.py +0 -0
  143. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/profile.py +0 -0
  144. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/repomap.py +0 -0
  145. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/selfupdate.py +0 -0
  146. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/shiftwork.py +0 -0
  147. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/skillaudit.py +0 -0
  148. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/skilldraft.py +0 -0
  149. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/skillfile.py +0 -0
  150. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/skillhost.py +0 -0
  151. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/skillinstall.py +0 -0
  152. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/skillpromote.py +0 -0
  153. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/skillpropose.py +0 -0
  154. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/skillreport.py +0 -0
  155. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/statusline.py +0 -0
  156. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/storage/__init__.py +0 -0
  157. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/storage/coldindex.py +0 -0
  158. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/storage/hotindex.py +0 -0
  159. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/storage/lock.py +0 -0
  160. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/storage/logsegment.py +0 -0
  161. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/storage/retention.py +0 -0
  162. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/structured.py +0 -0
  163. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/textutil.py +0 -0
  164. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/tokenledger.py +0 -0
  165. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/touch/__init__.py +0 -0
  166. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/touch/bashturn.py +0 -0
  167. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/touch/context.py +0 -0
  168. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/touch/graph.py +0 -0
  169. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/touch/log.py +0 -0
  170. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/touch/recallfile.py +0 -0
  171. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/touch/refs.py +0 -0
  172. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/touch/repo.py +0 -0
  173. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/touch/summary.py +0 -0
  174. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/updatecheck.py +0 -0
  175. {bantamkit-0.38.0 → bantamkit-0.38.2}/src/bantamkit/workplan.py +0 -0
  176. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/cli_exit_status_probe.py +0 -0
  177. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/conftest.py +0 -0
  178. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/data/docread/bad-crc.docx +0 -0
  179. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/data/docread/charref-4301-digits.html +0 -0
  180. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/data/docread/charset-table.json +0 -0
  181. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/data/docread/compression-method-9.docx +0 -0
  182. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/data/docread/corrupt-deflate.docx +0 -0
  183. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/data/docread/encrypted-member.docx +0 -0
  184. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/data/docread/encrypted-mimetype.odt +0 -0
  185. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/data/docread/eszett-cell-ref.xlsx +0 -0
  186. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/data/docread/internal-dtd-entity.docx +0 -0
  187. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/data/docread/rfc2231-charset.eml +0 -0
  188. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/data/docread/rfc822-nested-twice.eml +0 -0
  189. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/data/docread/unicode-digit-shared-string.xlsx +0 -0
  190. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/data/docread/x-uuencode.eml +0 -0
  191. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/data/f8404ab-perturbation-baseline.json +0 -0
  192. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/data/platform-assumption-baseline.json +0 -0
  193. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/docread_fixtures.py +0 -0
  194. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/perturbation_baseline_harness.py +0 -0
  195. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/rbp16_effect_probe.py +0 -0
  196. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/rbp18_payload_probe.py +0 -0
  197. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_adapter.py +0 -0
  198. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_agent.py +0 -0
  199. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_agreements.py +0 -0
  200. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_amendguard.py +0 -0
  201. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_assets.py +0 -0
  202. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_bantamkit_gitignore.py +0 -0
  203. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_bantamkit_read_tool.py +0 -0
  204. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_budget.py +0 -0
  205. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_build_identity.py +0 -0
  206. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_cli_oneshot.py +0 -0
  207. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_client.py +0 -0
  208. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_compaction_corpus_survey.py +0 -0
  209. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_conformance.py +0 -0
  210. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_conformance_harness_resilience.py +0 -0
  211. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_conformance_suite_table_gate.py +0 -0
  212. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_contract_fanout.py +0 -0
  213. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_contract_patterns.py +0 -0
  214. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_contract_refs.py +0 -0
  215. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_criticreplay.py +0 -0
  216. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_critique.py +0 -0
  217. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_doc_commands_gate.py +0 -0
  218. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_docread.py +0 -0
  219. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_docread_ceilings.py +0 -0
  220. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_document_manifest_parity.py +0 -0
  221. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_document_setup.py +0 -0
  222. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_document_tasks.py +0 -0
  223. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_document_tools.py +0 -0
  224. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_encoding_gate.py +0 -0
  225. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_evalrun.py +0 -0
  226. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_eventlog.py +0 -0
  227. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_experimentation.py +0 -0
  228. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_field_program_gates.py +0 -0
  229. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_field_programs.py +0 -0
  230. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_filegraph.py +0 -0
  231. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_follow_ups.py +0 -0
  232. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_hookadapter.py +0 -0
  233. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_hostinstall.py +0 -0
  234. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_hostinstall_hooks.py +0 -0
  235. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_install_shape.py +0 -0
  236. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_investigation.py +0 -0
  237. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_jobbudget.py +0 -0
  238. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_ladder_statistics.py +0 -0
  239. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_launcher_which.py +0 -0
  240. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_layers.py +0 -0
  241. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_loopguard.py +0 -0
  242. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_mcp_endpoint.py +0 -0
  243. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_mcp_request_guard.py +0 -0
  244. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_mcpdrift.py +0 -0
  245. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_mcpreport.py +0 -0
  246. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_mcpserver.py +0 -0
  247. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_mcpstdio_frames.py +0 -0
  248. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_memory.py +0 -0
  249. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_memory_compact_tool.py +0 -0
  250. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_memory_component.py +0 -0
  251. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_memory_divergence.py +0 -0
  252. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_memory_dream.py +0 -0
  253. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_memory_layers.py +0 -0
  254. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_memory_lifecycle.py +0 -0
  255. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_memory_native.py +0 -0
  256. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_memory_queries.py +0 -0
  257. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_memory_retrieval.py +0 -0
  258. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_memory_skills.py +0 -0
  259. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_memory_store_tripwire.py +0 -0
  260. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_memory_strings.py +0 -0
  261. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_memory_usage.py +0 -0
  262. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_mutmatrix.py +0 -0
  263. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_nativeexport.py +0 -0
  264. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_never_block.py +0 -0
  265. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_newline_gate.py +0 -0
  266. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_packaging.py +0 -0
  267. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_pdfread.py +0 -0
  268. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_pinharness_ledger.py +0 -0
  269. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_platform_assumption_gate.py +0 -0
  270. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_pricing.py +0 -0
  271. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_readme_separation.py +0 -0
  272. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_repo_map_tool.py +0 -0
  273. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_repomap.py +0 -0
  274. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_reviews.py +0 -0
  275. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_sdk_wire_text.py +0 -0
  276. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_selfupdate.py +0 -0
  277. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_served_tool_count_records.py +0 -0
  278. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_shiftwork.py +0 -0
  279. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_shiftwork_draft.py +0 -0
  280. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_skillaudit.py +0 -0
  281. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_skillaudit_roots.py +0 -0
  282. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_skilldraft.py +0 -0
  283. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_skillfile.py +0 -0
  284. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_skillhost.py +0 -0
  285. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_skillimprove.py +0 -0
  286. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_skillimprove_signals.py +0 -0
  287. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_skillinstall.py +0 -0
  288. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_skillmine_daily.py +0 -0
  289. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_skillpromote.py +0 -0
  290. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_skillpropose.py +0 -0
  291. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_skillreport.py +0 -0
  292. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_statusline.py +0 -0
  293. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_storage_bar.py +0 -0
  294. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_storage_coldindex.py +0 -0
  295. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_storage_hotindex.py +0 -0
  296. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_storage_log.py +0 -0
  297. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_storage_log_consumers.py +0 -0
  298. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_storage_recall_index.py +0 -0
  299. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_storage_retention.py +0 -0
  300. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_structured.py +0 -0
  301. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_thread_exception_gate.py +0 -0
  302. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_tokenledger.py +0 -0
  303. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_tool_annotations.py +0 -0
  304. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_tool_manifest.py +0 -0
  305. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_touch_bashturn.py +0 -0
  306. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_touch_context.py +0 -0
  307. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_touch_graph.py +0 -0
  308. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_touch_log.py +0 -0
  309. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_touch_recall_file.py +0 -0
  310. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_touch_refs.py +0 -0
  311. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_touch_summary.py +0 -0
  312. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_touches.py +0 -0
  313. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_unit_agent_id.py +0 -0
  314. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_unit_results.py +0 -0
  315. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_unknown_arguments.py +0 -0
  316. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_updatecheck.py +0 -0
  317. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_version_agreement.py +0 -0
  318. {bantamkit-0.38.0 → bantamkit-0.38.2}/tests/test_workplan.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bantamkit
3
- Version: 0.38.0
3
+ Version: 0.38.2
4
4
  Summary: Persistent memory for coding agents: an MCP server for Claude Code, Claude Desktop, Cursor and VS Code Copilot, 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
@@ -144,9 +144,9 @@ CPU architecture and Python minor version** (some wheels, such as `pydantic_core
144
144
  one platform only), copy `wheels/` across, and install from it:
145
145
 
146
146
  ```bash
147
- python -m pip download "bantamkit[mcp]==0.38.0" -d wheels
147
+ python -m pip download "bantamkit[mcp]==0.38.2" -d wheels
148
148
  python -m venv <env>
149
- <env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.38.0"
149
+ <env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.38.2"
150
150
  <env>/bin/bantamkit-mcp --install cursor
151
151
  ```
152
152
 
@@ -113,9 +113,9 @@ CPU architecture and Python minor version** (some wheels, such as `pydantic_core
113
113
  one platform only), copy `wheels/` across, and install from it:
114
114
 
115
115
  ```bash
116
- python -m pip download "bantamkit[mcp]==0.38.0" -d wheels
116
+ python -m pip download "bantamkit[mcp]==0.38.2" -d wheels
117
117
  python -m venv <env>
118
- <env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.38.0"
118
+ <env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.38.2"
119
119
  <env>/bin/bantamkit-mcp --install cursor
120
120
  ```
121
121
 
@@ -1,12 +1,18 @@
1
1
  {
2
2
  "name": "bantamkit_status",
3
3
  "title": "bantamkit status",
4
- "description": "Is bantamkit working, and which bantamkit? Returns a short report a person can read in the transcript: `bantamkit Active 🟢` or `bantamkit Degraded 🟠` on the first line, then the version and `build_id` of the code answering, the surfaces it serves, the memory store it is bound to with its index size against its budget, whether the event log is on, and — when degraded — every condition in full with what to do about it. Call it when the user asks whether the server is alive or working, when a bantamkit tool answered oddly and you need to know whether the server is the reason, or when a reply arrived carrying a `bantamkit degraded` footer and you want the rest of it. Every fact is observed at call time from the server's own state; nothing is a heartbeat or a cached verdict. Takes no arguments, writes nothing, and never echoes another call's arguments. build_identity answers only the `build_id` fingerprint, as JSON, when all you need is to compare two installs; shiftwork_status reports a job, not the server.",
4
+ "description": "Is bantamkit working, and which bantamkit? Returns a short report a person can read in the transcript: `bantamkit Active 🟢` or `bantamkit Degraded 🟠` on the first line, then the version and `build_id` of the code answering, the surfaces it serves, the memory store it is bound to with its index size against its budget, whether the event log is on, and — when degraded — every condition in full with what to do about it. Call it when the user asks whether the server is alive or working, when a bantamkit tool answered oddly and you need to know whether the server is the reason, or when a reply arrived carrying a `bantamkit degraded` footer and you want the rest of it. Every fact is observed at call time from the server's own state; nothing is a heartbeat or a cached verdict. Pass `detail: true` to list every usage and touch ledger on its own line instead of one summary line per family. Writes nothing, and never echoes another call's arguments. build_identity answers only the `build_id` fingerprint, as JSON, when all you need is to compare two installs; shiftwork_status reports a job, not the server.",
5
5
  "surfaces": [
6
6
  "mcp"
7
7
  ],
8
8
  "parameters": {
9
- "properties": {},
9
+ "properties": {
10
+ "detail": {
11
+ "type": "boolean",
12
+ "default": false,
13
+ "description": "true lists every usage and touch ledger on its own line; the default folds each family into one summary line."
14
+ }
15
+ },
10
16
  "type": "object",
11
17
  "title": "bantamkit_statusArguments",
12
18
  "additionalProperties": false
@@ -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.38.0"
32
+ __version__ = "0.38.2"
@@ -667,6 +667,13 @@ def build_identity() -> dict[str, Any]:
667
667
  #: operator who read `bantamkit_status` in a footer must find it in their prompt menu
668
668
  #: without translating, and a model that read it in a prompt must find the tool.
669
669
  STATUS_NAME = "bantamkit_status"
670
+ #: job78: the one argument `bantamkit_status` takes. The advertised schema is the asset's
671
+ #: (`assets/tools/bantamkit_status.json`, which carries this same sentence); this is what
672
+ #: the signature enforces.
673
+ STATUS_DETAIL_DESCRIPTION = (
674
+ "true lists every usage and touch ledger on its own line; "
675
+ "the default folds each family into one summary line."
676
+ )
670
677
 
671
678
  #: What the prompt says after the report. The report is the payload; this is the one line
672
679
  #: that tells the model what the person wanted with it, and it asks for a RELAY rather
@@ -1004,6 +1011,8 @@ def status_report(
1004
1011
  prompts: int,
1005
1012
  templates: int,
1006
1013
  sampling: bool = False,
1014
+ *,
1015
+ detail: bool = False,
1007
1016
  ) -> str:
1008
1017
  """The whole answer `bantamkit_status` returns — prose, for a person, in a transcript.
1009
1018
 
@@ -1018,8 +1027,9 @@ def status_report(
1018
1027
  the question this tool exists to answer: two endpoints registered under one name is
1019
1028
  the situation RB-P84 filed, and a version string cannot tell them apart.
1020
1029
 
1021
- NO ARGUMENT VALUE CAN REACH THIS. It takes none, and every input above is server
1022
- state; `test_status_surface.py` asserts the absence positively with a sentinel.
1030
+ NO ARGUMENT VALUE CAN REACH THIS. Every input above is server state, except `detail`
1031
+ (job78), a bool that only chooses between the two forms of the `logs:` lines and is
1032
+ never printed; `test_status_surface.py` asserts the absence positively with a sentinel.
1023
1033
  """
1024
1034
  identity = build_identity()
1025
1035
  build = identity["build_id"]
@@ -1061,7 +1071,9 @@ def status_report(
1061
1071
  # #204 (job69 J69-7): the capability tiers, every hot index, every log and the last
1062
1072
  # retention run -- AFTER the update line, so every line above keeps its number. Read
1063
1073
  # here, on demand, and never on the per-call footer path.
1064
- *storage_tiers.status_lines(memory, Path.home(), storage_tiers.current_cwd()),
1074
+ *storage_tiers.status_lines(
1075
+ memory, Path.home(), storage_tiers.current_cwd(), detail=detail
1076
+ ),
1065
1077
  # #199 Task 6: read-repair and the usage ledger, AFTER the storage block for the same
1066
1078
  # reason the block sits after the update line -- every line above keeps its number.
1067
1079
  f"read-repaired: {memory.repaired} this process",
@@ -1325,9 +1337,10 @@ class _Tool(SDKTool if SDKTool is not None else object): # type: ignore[misc]
1325
1337
  side effect, also the 0.37.2 behaviour: the SDK logs a `ToolError` at INFO, not with
1326
1338
  `logger.exception`, so a crash prints no traceback to stderr under the default logging.
1327
1339
 
1328
- Adopting the SDK's crash-hiding policy -- dropping the suffix -- is a design change that
1329
- has to land in BOTH runtimes together with the conformance cases; it is a deliberate
1330
- follow-up to #268, not a bug in this class.
1340
+ Adopting the SDK's crash-hiding policy -- dropping the suffix -- was considered in #274
1341
+ and declined on 2026-11-05: keeping this override is the project's policy, written down
1342
+ in docs/porting.md ("A tool crash answers `Error executing tool <name>: <exc>` in both
1343
+ runtimes"). Reversing it is a change to BOTH runtimes and their conformance cases.
1331
1344
  """
1332
1345
 
1333
1346
  async def run(
@@ -1858,7 +1871,10 @@ def build_server(memory: Memory, log: EventLog | None = None) -> Any:
1858
1871
  )
1859
1872
  return _noted_dict(identity)
1860
1873
 
1861
- def bantamkit_status(ctx: Context) -> str:
1874
+ def bantamkit_status(
1875
+ ctx: Context,
1876
+ detail: Annotated[bool, Field(description=STATUS_DETAIL_DESCRIPTION)] = False,
1877
+ ) -> str:
1862
1878
  """The one surface every host renders, answering "is this thing working".
1863
1879
 
1864
1880
  NO FOOTER ON THIS ONE, and the omission is the design rather than an oversight:
@@ -1881,6 +1897,7 @@ def build_server(memory: Memory, log: EventLog | None = None) -> Any:
1881
1897
  SERVED_PROMPTS,
1882
1898
  SERVED_RESOURCE_TEMPLATES,
1883
1899
  _sampling_declared(ctx),
1900
+ detail=detail,
1884
1901
  )
1885
1902
 
1886
1903
  def bantamkit_read(
@@ -22,12 +22,19 @@ lacks a string `ts` or a known `status`) is skipped.
22
22
  - `repo` (R7): the realpath of the root's COMMON git dir — a linked worktree's `commondir`
23
23
  is followed — so the worktrees of one repository share one key; `""` outside a repo.
24
24
 
25
- The **signature** is `tokenize_v2` keywords of the prompt (a term in more than half of the
25
+ The **signature** is `tokenize_label` keywords of the prompt (a term in more than half of the
26
26
  source's pieces is dropped, then the `KEYWORDS_TOP` most frequent, ties in code-point
27
27
  order), the tool-name n-grams for `NGRAM_SIZES`, and the touched paths. **Similarity** is a
28
28
  weighted Jaccard over the three, each component whose union is empty left out of both sums,
29
29
  answered as an exact reduced fraction `(num, den)`; `meets` compares on integers only, so the
30
- two runtimes cannot disagree at the `SIM_MIN` boundary.
30
+ two runtimes cannot disagree at the `SIM_MIN` boundary. `tokenize_label` is the miner's own
31
+ tokenizer (#289), not recall's `tokenize_v2`: recall's code-point bigrams split Thai vowels and
32
+ tone marks (`ทำต่อ` became `ทํ ํา าต ต่ ่อ`) and let filler words such as `แล้ว` and `ทำ` decide
33
+ the clusters. It groups a base character with its combining marks and following vowels
34
+ (`ะ า ำ ๅ` and Lao `ະ າ ຳ`, which are Lo, not marks) and a leading vowel with the character
35
+ after it, drops `LABEL_STOPWORDS`, and forms bigrams of those units; spaced-script
36
+ words are kept whole exactly as `tokenize_v2` keeps them. `tokenize_v2` is unchanged, because
37
+ recall is measured on it.
31
38
 
32
39
  **Clusters** (Task 5): leader clustering per source, the X1 bars on successful members only,
33
40
  twice the bar for the installer, recurring steps, pitfalls from failed members, and the X3
@@ -47,7 +54,9 @@ list, or the interrupt marker; a last turn has none). The `MinerCache` keeps eac
47
54
  offset and open turn, so an unchanged file is not read and a grown one is read from where the
48
55
  last run stopped. Measured shapes (job73 C6 Step 0) added `bash-*`, `task-notification` and
49
56
  `local-command-caveat` to the cut list: a `!` command's output and a background task's result
50
- arrive as non-meta user records.
57
+ arrive as non-meta user records. #289 added `ide_opened_file`, `ide_selection`,
58
+ `ide_diagnostics` and `browser_instruction`: the IDE and the Chrome extension write their
59
+ notices into the user's record, and a prompt made only of them is empty and starts no piece.
51
60
 
52
61
  `runtime-ts/src/skillmine.ts` is the port;
53
62
  `tools/conformance/suites/skill-miner.mjs` compares the two.
@@ -61,6 +70,7 @@ import json
61
70
  import math
62
71
  import os
63
72
  import re
73
+ import unicodedata
64
74
  import zlib
65
75
  from collections import Counter
66
76
  from collections.abc import Iterable, Mapping
@@ -68,7 +78,7 @@ from dataclasses import dataclass, field
68
78
  from pathlib import Path
69
79
  from typing import Any
70
80
 
71
- from bantamkit.memory.retrieval import tokenize_v2
81
+ from bantamkit.memory.retrieval import _nospace, _wordchar
72
82
  from bantamkit.touch.log import AGENT_ID
73
83
 
74
84
  SOURCE_SHIFTWORK = "shiftwork"
@@ -408,13 +418,141 @@ def _read_bundle(p: str) -> dict[str, Any] | None:
408
418
  # ---------------------------------------------------------------------------- signature
409
419
 
410
420
 
421
+ #: #289: Thai filler words dropped from a label; each is matched as a unit sequence.
422
+ LABEL_STOPWORDS = (
423
+ "ครับ",
424
+ "ค่ะ",
425
+ "แล้ว",
426
+ "ต่อ",
427
+ "ทำ",
428
+ "ให้",
429
+ "ที่",
430
+ "และ",
431
+ "ได้",
432
+ "หน่อย",
433
+ "ด้วย",
434
+ "เลย",
435
+ "นี้",
436
+ "ไหม",
437
+ "อีก",
438
+ "ก่อน",
439
+ "เป็น",
440
+ )
441
+
442
+
443
+ def _is_leading_vowel(cp: int) -> bool:
444
+ return 0x0E40 <= cp <= 0x0E44 or 0x0EC0 <= cp <= 0x0EC4
445
+
446
+
447
+ #: Thai/Lao following vowels (SARA A, AA, AM, LAKKHANGYAO; Lao A, AA, AM): general category
448
+ #: Lo, yet they close the syllable before them, so a unit takes them the way it takes a mark
449
+ #: (#289 amendment A1).
450
+ _FOLLOWING_VOWELS = frozenset({0x0E30, 0x0E32, 0x0E33, 0x0E45, 0x0EB0, 0x0EB2, 0x0EB3})
451
+
452
+
453
+ def _label_fold(text: str) -> str:
454
+ """NFKC, lower-cased, SARA AM recomposed (NFKC splits it into NIKHAHIT + SARA AA)."""
455
+ s = unicodedata.normalize("NFKC", text).lower()
456
+ return s.replace("\u0e4d\u0e32", "\u0e33").replace("\u0ecd\u0eb2", "\u0eb3")
457
+
458
+
459
+ def _label_units(run: str) -> list[str]:
460
+ """A no-space run as units: a mark or a following vowel joins the unit before it, a
461
+ leading vowel the one after. Each unit is gathered as a list of parts and joined once, so
462
+ a long run of joining characters stays linear (`out[-1] += ch` copied the unit each time)."""
463
+ units: list[list[str]] = []
464
+ pend: list[str] = []
465
+ for ch in run:
466
+ joins = ord(ch) in _FOLLOWING_VOWELS or unicodedata.category(ch) in ("Mn", "Mc")
467
+ if joins and units and not pend:
468
+ units[-1].append(ch)
469
+ elif _is_leading_vowel(ord(ch)):
470
+ pend.append(ch)
471
+ else:
472
+ pend.append(ch)
473
+ units.append(pend)
474
+ pend = []
475
+ if pend:
476
+ units.append(pend)
477
+ return ["".join(u) for u in units]
478
+
479
+
480
+ _STOP_UNITS: tuple[tuple[str, ...], ...] = tuple(
481
+ sorted(
482
+ (tuple(_label_units(_label_fold(w))) for w in LABEL_STOPWORDS),
483
+ key=len,
484
+ reverse=True,
485
+ )
486
+ )
487
+
488
+
489
+ def _label_run(run: str, out: list[str]) -> None:
490
+ units = _label_units(run)
491
+ seg: list[str] = []
492
+
493
+ def emit() -> None:
494
+ if len(seg) == 1:
495
+ out.append(seg[0])
496
+ else:
497
+ out.extend(seg[i] + seg[i + 1] for i in range(len(seg) - 1))
498
+ seg.clear()
499
+
500
+ i = 0
501
+ while i < len(units):
502
+ for stop in _STOP_UNITS:
503
+ if tuple(units[i : i + len(stop)]) == stop:
504
+ emit()
505
+ i += len(stop)
506
+ break
507
+ else:
508
+ seg.append(units[i])
509
+ i += 1
510
+ emit()
511
+
512
+
513
+ def tokenize_label(text: str) -> list[str]:
514
+ """The miner's tokenizer (#289): `tokenize_v2`'s scan, but a no-space run is cut into
515
+ units (a base with its marks and following vowels, a leading vowel with what follows),
516
+ `LABEL_STOPWORDS` end a segment and emit nothing, and a segment emits its one unit or its
517
+ unit bigrams. A bigram of units can still span a syllable boundary, and a stopword's unit
518
+ sequence also matches inside a longer word (`ต่อย` -> `ย`): there is no segmenter."""
519
+ s = _label_fold(text)
520
+ out: list[str] = []
521
+ word: list[str] = []
522
+ run: list[str] = []
523
+
524
+ def flush_word() -> None:
525
+ if word:
526
+ out.append("".join(word))
527
+ word.clear()
528
+
529
+ def flush_run() -> None:
530
+ if run:
531
+ _label_run("".join(run), out)
532
+ run.clear()
533
+
534
+ for ch in s:
535
+ if _nospace(ord(ch)):
536
+ flush_word()
537
+ run.append(ch)
538
+ elif _wordchar(ch):
539
+ flush_run()
540
+ word.append(ch)
541
+ else:
542
+ flush_word()
543
+ flush_run()
544
+ flush_word()
545
+ flush_run()
546
+ return out
547
+
548
+
411
549
  def doc_freq(pieces: Iterable[Piece]) -> tuple[Counter[str], int]:
412
- """Document frequency of every `tokenize_v2` term over one source's pieces, and their count."""
550
+ """Document frequency of each `tokenize_label` term over one source's pieces, and the count."""
413
551
  df: Counter[str] = Counter()
414
552
  n = 0
415
553
  for p in pieces:
416
554
  n += 1
417
- df.update(set(tokenize_v2(p.prompt)))
555
+ df.update(set(tokenize_label(p.prompt)))
418
556
  return df, n
419
557
 
420
558
 
@@ -425,7 +563,7 @@ def ngrams(tools: Iterable[str]) -> frozenset[str]:
425
563
 
426
564
  def signature(piece: Piece, df: Mapping[str, int], n: int) -> Signature:
427
565
  num, den = KEYWORD_DF_MAX
428
- counts = Counter(t for t in tokenize_v2(piece.prompt) if df.get(t, 0) * den <= n * num)
566
+ counts = Counter(t for t in tokenize_label(piece.prompt) if df.get(t, 0) * den <= n * num)
429
567
  ranked = sorted(counts.items(), key=lambda kv: (-kv[1], kv[0]))
430
568
  return Signature(
431
569
  keywords=frozenset(t for t, _ in ranked[:KEYWORDS_TOP]),
@@ -842,7 +980,10 @@ CORRECTIONS = (
842
980
  INTERRUPT = "[Request interrupted by user"
843
981
  #: R3: spans the harness writes into a user record; none of them is the user's own words.
844
982
  #: `bash-*` and `task-notification` are measured additions (C6 Step 0): a `!` command's
845
- #: output and a background task's result arrive as non-meta user records.
983
+ #: output and a background task's result arrive as non-meta user records. The IDE and
984
+ #: browser tags are #289's, measured 2026-11-06 over the maintainer's 798 session pieces:
985
+ #: `ide_opened_file` was carried by 67, `browser_instruction` by 58, `ide_selection` by 20;
986
+ #: `ide_diagnostics` occurs 151 times in transcripts (0 in pieces) and is cut defensively.
846
987
  CUT_TAGS = (
847
988
  "system-reminder",
848
989
  "command-name",
@@ -855,6 +996,10 @@ CUT_TAGS = (
855
996
  "bash-stdout",
856
997
  "bash-stderr",
857
998
  "task-notification",
999
+ "ide_opened_file",
1000
+ "ide_selection",
1001
+ "ide_diagnostics",
1002
+ "browser_instruction",
858
1003
  )
859
1004
  #: R4: the tools whose input names a file the agent touched.
860
1005
  PATH_TOOLS = ("Read", "Edit", "MultiEdit", "Write", "NotebookEdit")
@@ -14,8 +14,10 @@ THE STATUS LINES, appended after the update line so every earlier line keeps its
14
14
  storage: core on; hooks on|off; host data on|off
15
15
  index facts <layer>: <n> doc(s), <bytes> bytes, persisted|in-memory (one per memory layer
16
16
  whose facts/ exists)
17
- logs: <name> <bytes> (<n> rotated, <n> skipped) (one per log; or
18
- `logs: none`)
17
+ logs: hook-log <bytes> (<n> rotated, <n> skipped) (when it exists)
18
+ logs: usage/* <n> file(s) <bytes> (<n> rotated, <n> skipped) (one per family that
19
+ logs: touch/* <n> file(s) <bytes> (<n> rotated, <n> skipped) has a counted log;
20
+ or `logs: none`)
19
21
  retention: [off; ]last ran <iso>|never; refused <n>
20
22
 
21
23
  `<layer>` is the layer's KIND (`project`, `extra`, `profile`), never a grant's name -- the rule
@@ -24,6 +26,15 @@ recall scores from an index built per call, and this is where that is reported.
24
26
  the index holds on disk (head plus every shard file present). A log's `bytes` is its live file
25
27
  plus its rotated segments; `skipped` is what `LogSegment.read` could not parse.
26
28
 
29
+ THE TWO FORMS OF THE `logs:` LINES (job78). By default every `usage/<id>` ledger is folded
30
+ into ONE `usage/*` line and every `touch/<id>` log into ONE `touch/*` line, in that order
31
+ after `hook-log`: `<n>` counts the family's logs, `file` when it is 1 and `files` otherwise,
32
+ and the bytes, rotated and skipped figures are the sums of the per-log numbers. A family
33
+ with no counted log prints no line. With `detail` (the `bantamkit_status` argument of that
34
+ name) every log gets its own line instead, `logs: <name> <bytes> (<n> rotated, <n>
35
+ skipped)` with `<name>` one of `hook-log`, `usage/<id>`, `touch/<id>` -- the only form
36
+ before job78, which on a long-lived home printed thousands of usage lines.
37
+
27
38
  `runtime-ts/src/storage/tiers.ts` is the port; `tools/conformance/suites/storage-tiers.mjs`
28
39
  compares the two over an MCP-only host profile (no hook, no native directory).
29
40
  """
@@ -150,8 +161,9 @@ def _logs(base: Path) -> list[tuple[str, Path]]:
150
161
  return found
151
162
 
152
163
 
153
- def log_lines(home: Path | str) -> list[str]:
154
- out: list[str] = []
164
+ def _log_counts(home: Path | str) -> list[tuple[str, int, int, int]]:
165
+ """`(name, held, rotated, skipped)` per counted log, in `_logs` order."""
166
+ out: list[tuple[str, int, int, int]] = []
155
167
  for name, path in _logs(Path(home) / ".bantamkit"):
156
168
  seg = LogSegment(path)
157
169
  segments = seg.segments()
@@ -159,7 +171,32 @@ def log_lines(home: Path | str) -> list[str]:
159
171
  continue
160
172
  held = _size(path) + sum(_size(s) for s in segments)
161
173
  seg.read()
162
- out.append(f"logs: {name} {held} ({len(segments)} rotated, {seg.skipped} skipped)")
174
+ out.append((name, held, len(segments), seg.skipped))
175
+ return out
176
+
177
+
178
+ def log_lines(home: Path | str, detail: bool = False) -> list[str]:
179
+ """The `logs:` lines: per family by default, per log with `detail` (module docstring)."""
180
+ counts = _log_counts(home)
181
+ if detail:
182
+ out = [f"logs: {n} {held} ({r} rotated, {s} skipped)" for n, held, r, s in counts]
183
+ return out or ["logs: none"]
184
+ out = [
185
+ f"logs: {n} {held} ({r} rotated, {s} skipped)"
186
+ for n, held, r, s in counts
187
+ if n == "hook-log"
188
+ ]
189
+ for family in ("usage", "touch"):
190
+ members = [c for c in counts if c[0].startswith(f"{family}/")]
191
+ if not members:
192
+ continue
193
+ held = sum(c[1] for c in members)
194
+ rotated = sum(c[2] for c in members)
195
+ skipped = sum(c[3] for c in members)
196
+ out.append(
197
+ f"logs: {family}/* {_plural(len(members), 'file')} {held} "
198
+ f"({rotated} rotated, {skipped} skipped)"
199
+ )
163
200
  return out or ["logs: none"]
164
201
 
165
202
 
@@ -196,12 +233,17 @@ def retention_line(home: Path | str) -> str:
196
233
  return f"retention: {prefix}last ran {ran}; refused {refused}"
197
234
 
198
235
 
199
- def status_lines(memory: Any, home: Path | str, cwd: Path | str | None) -> list[str]:
200
- """Every storage line of `bantamkit_status`, in order. Never raises."""
236
+ def status_lines(
237
+ memory: Any, home: Path | str, cwd: Path | str | None, *, detail: bool = False
238
+ ) -> list[str]:
239
+ """Every storage line of `bantamkit_status`, in order. Never raises.
240
+
241
+ `detail` is the tool's argument: one `logs:` line per log instead of per family.
242
+ """
201
243
  return [
202
244
  tier_line(tiers(home, cwd)),
203
245
  *index_lines(memory),
204
- *log_lines(home),
246
+ *log_lines(home, detail),
205
247
  touch_line(home),
206
248
  retention_line(home),
207
249
  ]
@@ -23,9 +23,15 @@
23
23
  "openWorldHint": false,
24
24
  "readOnlyHint": true
25
25
  },
26
- "description": "Is bantamkit working, and which bantamkit? Returns a short report a person can read in the transcript: `bantamkit Active 🟢` or `bantamkit Degraded 🟠` on the first line, then the version and `build_id` of the code answering, the surfaces it serves, the memory store it is bound to with its index size against its budget, whether the event log is on, and — when degraded — every condition in full with what to do about it. Call it when the user asks whether the server is alive or working, when a bantamkit tool answered oddly and you need to know whether the server is the reason, or when a reply arrived carrying a `bantamkit degraded` footer and you want the rest of it. Every fact is observed at call time from the server's own state; nothing is a heartbeat or a cached verdict. Takes no arguments, writes nothing, and never echoes another call's arguments. build_identity answers only the `build_id` fingerprint, as JSON, when all you need is to compare two installs; shiftwork_status reports a job, not the server.",
26
+ "description": "Is bantamkit working, and which bantamkit? Returns a short report a person can read in the transcript: `bantamkit Active 🟢` or `bantamkit Degraded 🟠` on the first line, then the version and `build_id` of the code answering, the surfaces it serves, the memory store it is bound to with its index size against its budget, whether the event log is on, and — when degraded — every condition in full with what to do about it. Call it when the user asks whether the server is alive or working, when a bantamkit tool answered oddly and you need to know whether the server is the reason, or when a reply arrived carrying a `bantamkit degraded` footer and you want the rest of it. Every fact is observed at call time from the server's own state; nothing is a heartbeat or a cached verdict. Pass `detail: true` to list every usage and touch ledger on its own line instead of one summary line per family. Writes nothing, and never echoes another call's arguments. build_identity answers only the `build_id` fingerprint, as JSON, when all you need is to compare two installs; shiftwork_status reports a job, not the server.",
27
27
  "inputSchema": {
28
- "properties": {},
28
+ "properties": {
29
+ "detail": {
30
+ "type": "boolean",
31
+ "default": false,
32
+ "description": "true lists every usage and touch ledger on its own line; the default folds each family into one summary line."
33
+ }
34
+ },
29
35
  "type": "object",
30
36
  "title": "bantamkit_statusArguments",
31
37
  "additionalProperties": false
@@ -1012,3 +1012,133 @@ def test_a_successful_commit_carries_its_subject_and_a_failed_one_none():
1012
1012
  del old["commits"] # a cache entry written before the field: read as no commits
1013
1013
  assert skillmine._piece_from(old).commits == ()
1014
1014
  assert skillmine._piece_from({**piece.as_dict(), "commits": [1]}) is None
1015
+
1016
+
1017
+ # ---------------------------------------------------------------------------- #289
1018
+
1019
+
1020
+ _IDE_NOTICE = (
1021
+ "<ide_opened_file>The user opened the file /x/a.py in the IDE. This may or may not be "
1022
+ "related to the current task.</ide_opened_file>"
1023
+ "<browser_instruction>Claude in Chrome browser automation is available.</browser_instruction>"
1024
+ )
1025
+
1026
+
1027
+ @pytest.mark.parametrize(
1028
+ ("text", "want"),
1029
+ [
1030
+ ("ทำต่อ", []),
1031
+ ("merge แล้ว tag ต่อ", ["merge", "tag"]),
1032
+ ("อนุมัติแล้ว", ["อนุ", "นุมั", "มัติ"]),
1033
+ ("กลับไปใช้ แผนเดิม", ["กลั", "ลับ", "บไป", "ไปใช้", "แผน", "นเดิ", "เดิม"]),
1034
+ ("เข้าใจแล้วครับ ทำต่อเลย", ["เข้าใจ"]),
1035
+ ("จำนวน", ["จำน", "นว", "วน"]),
1036
+ ("ทำงาน", ["งาน"]),
1037
+ ("ประชุม", ["ประ", "ระชุ", "ชุม"]),
1038
+ ("สร้างไฟล์ใหม่", ["สร้า", "ร้าง", "งไฟ", "ไฟล์", "ล์ให", "ใหม่"]),
1039
+ # Known limit (#289 A1): a stopword's unit sequence matches inside a longer word
1040
+ # ("ต่อ" in "ต่อย"); there is no dictionary segmenter, by ruling.
1041
+ ("ต่อย", ["ย"]),
1042
+ ],
1043
+ )
1044
+ def test_tokenize_label_vectors(text: str, want: list[str]) -> None:
1045
+ assert skillmine.tokenize_label(text) == want
1046
+
1047
+
1048
+ def test_tokenize_label_matches_tokenize_v2_on_ascii() -> None:
1049
+ from bantamkit.memory.retrieval import tokenize_v2
1050
+
1051
+ for text in ["Fix the parser in src/parse.py, then run 2 tests!", "merge PR #12 & tag v0.38"]:
1052
+ assert skillmine.tokenize_label(text) == tokenize_v2(text)
1053
+
1054
+
1055
+ _FOLLOWING_VOWELS = "\u0e30\u0e32\u0e33\u0e45\u0eb0\u0eb2\u0eb3"
1056
+
1057
+
1058
+ def test_tokenize_label_no_token_starts_with_a_mark_or_following_vowel() -> None:
1059
+ import unicodedata
1060
+
1061
+ texts = [
1062
+ "ทำต่อ",
1063
+ "merge แล้ว tag ต่อ",
1064
+ "อนุมัติแล้ว",
1065
+ "กลับไปใช้ แผนเดิม",
1066
+ "เข้าใจแล้วครับ ทำต่อเลย",
1067
+ "จำนวน",
1068
+ "ประชุม",
1069
+ "สร้างไฟล์ใหม่",
1070
+ "ສະບາຍດີ ຂອບໃຈຫຼາຍໆ",
1071
+ ]
1072
+ toks = [t for x in texts for t in skillmine.tokenize_label(x)]
1073
+ assert toks
1074
+ assert all(unicodedata.category(t[0]) not in ("Mn", "Mc") for t in toks)
1075
+ assert all(t[0] not in _FOLLOWING_VOWELS for t in toks)
1076
+
1077
+
1078
+ def test_label_units_attach_following_vowels() -> None:
1079
+ assert skillmine._label_units("ทำ") == ["ทำ"]
1080
+ assert skillmine._label_units("เข้าใจ") == ["เข้า", "ใจ"]
1081
+ assert skillmine._label_units("ประ") == ["ป", "ระ"]
1082
+ # A following vowel with no unit before it starts its own unit.
1083
+ assert skillmine._label_units("\u0e32ก") == ["\u0e32", "ก"]
1084
+ # One pending behind a leading vowel is not attached across it.
1085
+ assert skillmine._label_units("เาก") == ["เา", "ก"]
1086
+ assert skillmine.tokenize_label("เาก") == ["เาก"]
1087
+
1088
+
1089
+ def test_label_units_lakkhangyao_and_lao_sara_a() -> None:
1090
+ # U+0E45 LAKKHANGYAO joins the unit before it, like the mark U+0E35 after ษ.
1091
+ assert skillmine._label_units("ฤๅษี") == ["ฤๅ", "ษี"]
1092
+ assert skillmine.tokenize_label("ฤๅษี") == ["ฤๅษี"]
1093
+ # U+0EB0 Lao SARA A joins its base.
1094
+ assert skillmine._label_units("ຈະ") == ["ຈະ"]
1095
+ assert skillmine.tokenize_label("ຈະ") == ["ຈະ"]
1096
+
1097
+
1098
+ @pytest.mark.parametrize(
1099
+ "run",
1100
+ ["ก" + "\u0e48" * 10**6, "ก" + "\u0e32" * 10**6, "เ" + "\u0e48" * 10**6],
1101
+ ids=["mark", "following-vowel", "behind-leading-vowel"],
1102
+ )
1103
+ def test_label_units_linear_on_long_joining_runs(run: str) -> None:
1104
+ # `out[-1] += ch` copied the unit per joining character: ~13.7 s at 1M chars (26 s behind a
1105
+ # leading vowel). Linear it is ~0.3 s, so a 3 s bound fails the old code and not a slow box.
1106
+ import time
1107
+
1108
+ t0 = time.perf_counter()
1109
+ units = skillmine._label_units(run)
1110
+ assert time.perf_counter() - t0 < 3.0
1111
+ assert "".join(units) == run
1112
+
1113
+
1114
+ def test_tokenize_v2_unchanged_for_recall() -> None:
1115
+ from bantamkit.memory.retrieval import tokenize_v2
1116
+
1117
+ assert tokenize_v2("ทำต่อ") == ["ทํ", "ํา", "าต", "ต่", "่อ"]
1118
+
1119
+
1120
+ def test_signature_uses_tokenize_label() -> None:
1121
+ p = Piece(id="t", source="session", ts="", repo="", prompt="อนุมัติแล้ว ทำต่อ", ok=True)
1122
+ df, n = doc_freq([p])
1123
+ assert set(df) == {"อนุ", "นุมั", "มัติ"}
1124
+ assert signature(p, {}, 1).keywords == frozenset({"อนุ", "นุมั", "มัติ"})
1125
+
1126
+
1127
+ def test_clean_prompt_cuts_ide_and_browser_notices() -> None:
1128
+ assert clean_prompt(_IDE_NOTICE) == ""
1129
+ assert clean_prompt("<ide_selection>sel</ide_selection> fix it") == "fix it"
1130
+ assert clean_prompt("<ide_diagnostics>d</ide_diagnostics>") == ""
1131
+
1132
+
1133
+ def test_ide_notice_only_prompt_yields_no_piece_or_keyword(tmp_path: Path) -> None:
1134
+ home, repo = _home(tmp_path)
1135
+ t = _T(SID, str(repo))
1136
+ t.prompt(_ts(1), [{"type": "text", "text": _IDE_NOTICE}])
1137
+ t.call(_ts(2), "Read", {"file_path": str(repo / "src" / "parse.py")}, "t1")
1138
+ t.prompt(_ts(3), _IDE_NOTICE)
1139
+ t.call(_ts(4), "Read", {"file_path": str(repo / "src" / "parse.py")}, "t2")
1140
+ t.write(home / ".claude" / "projects" / "-work-repo" / f"{SID}.jsonl")
1141
+ pieces = _run(home)
1142
+ assert pieces == []
1143
+ for word in ("ide", "opened", "browser", "chrome"):
1144
+ assert word not in json.dumps(pieces)
@@ -480,6 +480,38 @@ def test_the_status_report_itself_never_carries_the_footer(tmp_path):
480
480
  assert report.endswith(".")
481
481
 
482
482
 
483
+ def test_the_detail_argument_chooses_the_form_of_the_logs_lines(tmp_path, record_home):
484
+ """job78: `detail` reaches the storage block through the tool, and only there.
485
+
486
+ Two usage ledgers: the default report folds them into one `usage/*` line, `detail: true`
487
+ prints one line each, and every other line of the report is the same in both.
488
+ """
489
+ usage = record_home / ".bantamkit" / "usage"
490
+ usage.mkdir()
491
+ (usage / "aa.jsonl").write_bytes(b'{"ts":"1"}\n')
492
+ (usage / "bb.jsonl").write_bytes(b'{"ts":"2"}\nbroken\n')
493
+ server = build_server(make_memory(tmp_path))
494
+
495
+ async def scenario():
496
+ async with Client(server) as c:
497
+ plain = (await c.call_tool(STATUS_NAME, {})).content[0].text
498
+ off = (await c.call_tool(STATUS_NAME, {"detail": False})).content[0].text
499
+ on = (await c.call_tool(STATUS_NAME, {"detail": True})).content[0].text
500
+ return plain, off, on
501
+
502
+ plain, off, on = run(scenario())
503
+ assert plain == off
504
+ summary = [ln for ln in plain.split("\n") if ln.startswith("logs: ")]
505
+ detailed = [ln for ln in on.split("\n") if ln.startswith("logs: ")]
506
+ assert summary == ["logs: usage/* 2 files 29 (0 rotated, 1 skipped)"]
507
+ assert detailed == [
508
+ "logs: usage/aa 11 (0 rotated, 0 skipped)",
509
+ "logs: usage/bb 18 (0 rotated, 1 skipped)",
510
+ ]
511
+ rest = [ln for ln in plain.split("\n") if not ln.startswith("logs: ")]
512
+ assert rest == [ln for ln in on.split("\n") if not ln.startswith("logs: ")]
513
+
514
+
483
515
  def test_no_argument_value_reaches_the_status_report_or_the_footer(tmp_path):
484
516
  """NON-VACUITY 3: the absence, asserted POSITIVELY, against a sentinel.
485
517