bantamkit 0.35.1__tar.gz → 0.35.3__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.
- {bantamkit-0.35.1 → bantamkit-0.35.3}/.gitignore +1 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/PKG-INFO +4 -4
- {bantamkit-0.35.1 → bantamkit-0.35.3}/README.md +2 -2
- bantamkit-0.35.3/_assets/tools/shiftwork_clock_out.json +150 -0
- bantamkit-0.35.3/_assets/tools/shiftwork_plan.json +25 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/pyproject.toml +6 -1
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/__init__.py +1 -1
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/mcpserver.py +32 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/memory/store.py +2 -2
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/selfupdate.py +89 -0
- bantamkit-0.35.3/src/bantamkit/updatecheck.py +340 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/data/served-tool-surface.json +65 -10
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_mcpserver.py +22 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_selfupdate.py +257 -1
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_status_surface.py +109 -1
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_tool_manifest.py +48 -1
- bantamkit-0.35.3/tests/test_updatecheck.py +541 -0
- bantamkit-0.35.1/_assets/tools/shiftwork_clock_out.json +0 -95
- bantamkit-0.35.1/_assets/tools/shiftwork_plan.json +0 -25
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/contracts/default.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/manifest.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/repo/HISTORY.md +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/repo/README.md +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/repo/docs/architecture.md +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/repo/docs/runbook.md +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/repo/issues/142-settlement-timeout.md +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/repo/patches/0009-retry-budget.patch +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/repo/src/ledger/__init__.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/repo/src/ledger/config.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/repo/src/ledger/errors.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/repo/src/ledger/posting.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/repo/src/ledger/registry.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/repo/src/ledger/report.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/repo/src/ledger/retry.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/repo/src/ledger/settle.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/repo/src/ledger/validate.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/repo/tests/test_posting.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/repo/tests/test_settle.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/tasks/dt-error-contract.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/tasks/dt-handler-map.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/tasks/dt-patch-before-after.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/tasks/dt-retry-attempts.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/tasks/dt-settlement-config.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/tasks/dt-symbol-home.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/tasks/dt-trace-blame.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/devteam/tasks/dt-unread-key.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/document/tasks/doc-large-in-137.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/document/tasks/doc-large-in-359.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/document/tasks/doc-large-in-372.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/document/tasks/doc-large-out-11764.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/document/tasks/doc-large-out-4137.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/document/tasks/doc-large-out-8022.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/document/tasks/doc-small-137.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/document/tasks/doc-small-261.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/document/tasks/doc-small-388.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/fixtures/.gitkeep +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/fixtures/catalog.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/perturbations/task-completion.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/.gitkeep +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/extract-contact.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/extract-invoice.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/extract-order.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/extract-schedule.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/extract-versions.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/nav-prod-port.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/nav-release-bundle.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/recall-audit-retention.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/recall-cache-ttl.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/recall-db-port.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/recall-deploy.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/recall-env-endpoint.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/recall-oncall-rotation.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/recall-oncall.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/recall-org-quota.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/recall-owner.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/shop-basket-total.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/shop-cheapest.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/shop-compare.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/shop-gadget-value.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/shop-stock-total.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/evals/tasks/shop-total.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/pricing/default.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/profiles/default.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/profiles/patient.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/rubrics/.gitkeep +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/rubrics/code-quality.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/rubrics/grounded-completion.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/rubrics/task-completion.yaml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/schemas/shiftwork-checkpoint.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/skills/.gitkeep +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/skills/file-graph.md +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/skills/memory.md +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/tools/.gitkeep +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/tools/bantamkit_read.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/tools/bantamkit_status.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/tools/build_identity.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/tools/document_list.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/tools/document_read.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/tools/file_graph.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/tools/memory_compact.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/tools/memory_dream.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/tools/memory_recall.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/tools/memory_save.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/tools/repo_map.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/tools/shiftwork_clock_in.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/tools/shiftwork_status.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/tools/skill_audit.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/tools/token_ledger.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/tools/validate_json.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/_assets/tools/work_plan.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/hatch_build.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/agent.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/assets.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/budget.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/client.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/contract.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/criticreplay.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/critique.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/docmanifest.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/docread.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/evalrun.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/eventlog.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/filegraph.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/hostinstall.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/loopguard.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/mcpreport.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/memory/__init__.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/memory/__main__.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/memory/component.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/memory/divergence.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/memory/dream.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/memory/layers.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/pdfread.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/pricing.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/profile.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/repomap.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/shiftwork.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/skillaudit.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/statusline.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/structured.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/textutil.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/tokenledger.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/src/bantamkit/workplan.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/cli_exit_status_probe.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/conftest.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/data/docread/bad-crc.docx +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/data/docread/charref-4301-digits.html +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/data/docread/charset-table.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/data/docread/compression-method-9.docx +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/data/docread/corrupt-deflate.docx +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/data/docread/encrypted-member.docx +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/data/docread/encrypted-mimetype.odt +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/data/docread/eszett-cell-ref.xlsx +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/data/docread/internal-dtd-entity.docx +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/data/docread/rfc2231-charset.eml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/data/docread/rfc822-nested-twice.eml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/data/docread/unicode-digit-shared-string.xlsx +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/data/docread/x-uuencode.eml +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/data/f8404ab-perturbation-baseline.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/data/platform-assumption-baseline.json +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/docread_fixtures.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/perturbation_baseline_harness.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/rbp16_effect_probe.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/rbp18_payload_probe.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_adapter.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_agent.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_amendguard.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_bantamkit_gitignore.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_bantamkit_read_tool.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_budget.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_build_identity.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_client.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_compaction_corpus_survey.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_conformance.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_conformance_harness_resilience.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_conformance_suite_table_gate.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_contract_fanout.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_criticreplay.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_critique.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_doc_commands_gate.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_docread.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_docread_ceilings.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_document_manifest_parity.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_document_setup.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_document_tasks.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_document_tools.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_encoding_gate.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_evalrun.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_eventlog.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_field_program_gates.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_field_programs.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_filegraph.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_hostinstall.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_install_shape.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_ladder_statistics.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_launcher_which.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_layers.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_loopguard.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_mcp_endpoint.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_mcpdrift.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_mcpreport.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_memory.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_memory_compact_tool.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_memory_component.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_memory_divergence.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_memory_dream.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_memory_layers.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_memory_store_tripwire.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_mutmatrix.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_newline_gate.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_packaging.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_pdfread.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_pinharness_ledger.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_platform_assumption_gate.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_pricing.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_repo_map_tool.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_repomap.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_served_tool_count_records.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_shiftwork.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_skillaudit.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_statusline.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_structured.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_thread_exception_gate.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_tokenledger.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_version_agreement.py +0 -0
- {bantamkit-0.35.1 → bantamkit-0.35.3}/tests/test_workplan.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bantamkit
|
|
3
|
-
Version: 0.35.
|
|
3
|
+
Version: 0.35.3
|
|
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
|
|
@@ -23,7 +23,7 @@ Requires-Dist: hatchling>=1.24; extra == 'dev'
|
|
|
23
23
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
24
24
|
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
25
25
|
Provides-Extra: mcp
|
|
26
|
-
Requires-Dist: mcp<
|
|
26
|
+
Requires-Dist: mcp<2.1,>=2.0; extra == 'mcp'
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
|
|
29
29
|
# bantamkit — memory MCP server for Claude Code, Cursor, VS Code Copilot and Claude Desktop (Python)
|
|
@@ -97,9 +97,9 @@ CPU architecture and Python minor version** (some wheels, such as `pydantic_core
|
|
|
97
97
|
one platform only), copy `wheels/` across, and install from it:
|
|
98
98
|
|
|
99
99
|
```bash
|
|
100
|
-
python -m pip download "bantamkit[mcp]==0.35.
|
|
100
|
+
python -m pip download "bantamkit[mcp]==0.35.3" -d wheels
|
|
101
101
|
python -m venv <env>
|
|
102
|
-
<env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.35.
|
|
102
|
+
<env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.35.3"
|
|
103
103
|
<env>/bin/bantamkit-mcp --install cursor
|
|
104
104
|
```
|
|
105
105
|
|
|
@@ -69,9 +69,9 @@ CPU architecture and Python minor version** (some wheels, such as `pydantic_core
|
|
|
69
69
|
one platform only), copy `wheels/` across, and install from it:
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
|
-
python -m pip download "bantamkit[mcp]==0.35.
|
|
72
|
+
python -m pip download "bantamkit[mcp]==0.35.3" -d wheels
|
|
73
73
|
python -m venv <env>
|
|
74
|
-
<env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.35.
|
|
74
|
+
<env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.35.3"
|
|
75
75
|
<env>/bin/bantamkit-mcp --install cursor
|
|
76
76
|
```
|
|
77
77
|
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shiftwork_clock_out",
|
|
3
|
+
"description": "Shift-work clock-out: record a finished unit — set its status, advance plan.cursor, merge handoff_patch, push history_entry onto the 5-entry ring — validating the whole mutated document against the checkpoint schema BEFORE an atomic write (a failure writes nothing and returns result=error). `unit_id` must be `plan.cursor`, the one unit clock_in briefs; any other id is refused (`unit X is not the cursor unit Y`). `handoff_patch` and `history_entry` are both required arguments: `handoff_patch` is shallow-merged into `handoff` and takes only its four keys (pass `{}` to leave it unchanged); `history_entry` needs `unit` and `outcome`; `status` is one of the five unit statuses — a value outside these shapes is refused by the schema check, nothing written. Every success appends one accounting line (unit, role, status, ts, briefed, plus your accounting fields, e.g. tokens/duration_ms/model) to <checkpoint>.log.jsonl, beside the brief lines clock_in writes (a brief line carries `event` and no `status`). `accounting` is optional: null, the default, logs a line with no cost figures at all; a non-null line must fit the accounting shape below. `briefed` is written by the runtime, never taken from you — a self-reported value is overwritten by the measured one: true when a brief was issued for this unit since its LAST clock-out (not ever), so a unit re-run inline after a blocked clock-out reads false. A brief is not a precondition: a unit that was never clocked in still clocks out, with briefed=false. If the checkpoint's job.roles names this unit's role, accounting.model must be one of that role's model identifiers, spelled exactly — a model that is not on the list, or none reported at all, is refused before anything is written; a role job.roles omits, and a checkpoint carrying no job.roles, are unconstrained.",
|
|
4
|
+
"surfaces": [
|
|
5
|
+
"mcp"
|
|
6
|
+
],
|
|
7
|
+
"parameters": {
|
|
8
|
+
"properties": {
|
|
9
|
+
"checkpoint": {
|
|
10
|
+
"title": "Checkpoint",
|
|
11
|
+
"type": "string"
|
|
12
|
+
},
|
|
13
|
+
"unit_id": {
|
|
14
|
+
"title": "Unit Id",
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "The unit being clocked out. Must equal plan.cursor — the single-pointer contract: clock_in briefs only the cursor unit and clock_out accepts only it."
|
|
17
|
+
},
|
|
18
|
+
"status": {
|
|
19
|
+
"title": "Status",
|
|
20
|
+
"type": "string",
|
|
21
|
+
"enum": [
|
|
22
|
+
"todo",
|
|
23
|
+
"in_progress",
|
|
24
|
+
"done",
|
|
25
|
+
"blocked",
|
|
26
|
+
"dropped"
|
|
27
|
+
],
|
|
28
|
+
"description": "The unit's new status; the same five values the checkpoint schema allows on plan.units[].status."
|
|
29
|
+
},
|
|
30
|
+
"handoff_patch": {
|
|
31
|
+
"title": "Handoff Patch",
|
|
32
|
+
"type": "object",
|
|
33
|
+
"additionalProperties": false,
|
|
34
|
+
"description": "Shallow-merged into `handoff`: only these four keys exist there, and a key outside them is refused by the schema check. Required; `{}` leaves `handoff` unchanged.",
|
|
35
|
+
"properties": {
|
|
36
|
+
"next_action": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"minLength": 1,
|
|
39
|
+
"description": "Imperative first move — zero re-derivation."
|
|
40
|
+
},
|
|
41
|
+
"open_questions": {
|
|
42
|
+
"type": "array",
|
|
43
|
+
"items": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"minLength": 1
|
|
46
|
+
},
|
|
47
|
+
"description": "The autonomy switch: empty = driver keeps cycling, non-empty = STOP and ask."
|
|
48
|
+
},
|
|
49
|
+
"do_not": {
|
|
50
|
+
"type": "array",
|
|
51
|
+
"items": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"minLength": 1
|
|
54
|
+
},
|
|
55
|
+
"description": "Negative space — near-mistakes past sessions made."
|
|
56
|
+
},
|
|
57
|
+
"notes": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"description": "Free-form prose for the next session, one string; the empty string clears it."
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"history_entry": {
|
|
64
|
+
"title": "History Entry",
|
|
65
|
+
"type": "object",
|
|
66
|
+
"required": [
|
|
67
|
+
"unit",
|
|
68
|
+
"outcome"
|
|
69
|
+
],
|
|
70
|
+
"additionalProperties": true,
|
|
71
|
+
"description": "Pushed onto the 5-entry `history` ring; `unit` and `outcome` are required, `notes` optional, other keys pass through.",
|
|
72
|
+
"properties": {
|
|
73
|
+
"unit": {
|
|
74
|
+
"type": "string",
|
|
75
|
+
"minLength": 1
|
|
76
|
+
},
|
|
77
|
+
"outcome": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"minLength": 1
|
|
80
|
+
},
|
|
81
|
+
"notes": {
|
|
82
|
+
"type": "string"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"accounting": {
|
|
87
|
+
"anyOf": [
|
|
88
|
+
{
|
|
89
|
+
"type": "object",
|
|
90
|
+
"description": "Per-unit cost. Written verbatim, beside ts/unit/role/status, as one line of <checkpoint>.log.jsonl - the ledger the N-sessions experiment reads. The keys named here have fixed meanings so a reader holding only the ledger knows what each number counts, in which unit, and which model produced it; any other key passes through unchanged (a refused key is friction, not safety). Lines written before this schema existed may spell these differently; the schema governs new writes only.",
|
|
91
|
+
"properties": {
|
|
92
|
+
"tokens": {
|
|
93
|
+
"type": "integer",
|
|
94
|
+
"minimum": 0,
|
|
95
|
+
"description": "Tokens the unit consumed as the harness's subagent counter reports them: every class it reports (input, output, cache creation) summed, EXCLUDING cache reads. In a non-null accounting line it is required — a line without it is refused before anything is written; only `accounting: null`, the default, is exempt, and that logs no cost at all. A rounded self-estimate is allowed only if `note` says it is one; a unit whose cost is unknown cannot be compared with any other."
|
|
96
|
+
},
|
|
97
|
+
"cache_read_tokens": {
|
|
98
|
+
"type": "integer",
|
|
99
|
+
"minimum": 0,
|
|
100
|
+
"description": "Tokens the unit's requests served from prompt cache, kept OUT of `tokens` because they dominate a real session's traffic and would swamp the work signal. Optional: omit it when the harness reports no such figure - a written 0 means the unit read nothing from cache."
|
|
101
|
+
},
|
|
102
|
+
"duration_ms": {
|
|
103
|
+
"type": "integer",
|
|
104
|
+
"minimum": 0,
|
|
105
|
+
"description": "Wall-clock from spawning the subagent to its final message, in whole milliseconds. In a non-null line it is required, and it is the only duration key with a defined meaning: older lines carry `duration`, `duration_min` or `duration_s`, which this schema neither reads nor renames."
|
|
106
|
+
},
|
|
107
|
+
"model": {
|
|
108
|
+
"type": "string",
|
|
109
|
+
"description": "The exact identifier of the model the subagent actually ran on (e.g. `claude-sonnet-5`) and nothing else - a caveat about how it was chosen belongs in `note`. Not required here: when the checkpoint's job.roles names this unit's role, clock_out already requires it and refuses a value off that role's list, spelled exactly; a checkpoint with no job.roles leaves it optional. This schema does not change that gate."
|
|
110
|
+
},
|
|
111
|
+
"tool_uses": {
|
|
112
|
+
"type": "integer",
|
|
113
|
+
"minimum": 0,
|
|
114
|
+
"description": "Tool calls the subagent made, as the harness counts them. Optional; it is the denominator that makes `tokens` comparable across units of different size (per-unit cost is linear in tool calls, not in unit length)."
|
|
115
|
+
},
|
|
116
|
+
"note": {
|
|
117
|
+
"type": "string",
|
|
118
|
+
"description": "Free text for what the fields above cannot say: that `tokens` is an estimate, that this is a retry or a second scope under the same unit id, what a harness counter excludes. Anything that is not a model identifier goes here, not in `model`."
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
"required": [
|
|
122
|
+
"tokens",
|
|
123
|
+
"duration_ms"
|
|
124
|
+
],
|
|
125
|
+
"additionalProperties": true
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"type": "null"
|
|
129
|
+
}
|
|
130
|
+
],
|
|
131
|
+
"default": null,
|
|
132
|
+
"title": "Accounting"
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
"required": [
|
|
136
|
+
"checkpoint",
|
|
137
|
+
"unit_id",
|
|
138
|
+
"status",
|
|
139
|
+
"handoff_patch",
|
|
140
|
+
"history_entry"
|
|
141
|
+
],
|
|
142
|
+
"type": "object",
|
|
143
|
+
"title": "shiftwork_clock_outArguments"
|
|
144
|
+
},
|
|
145
|
+
"output_schema": {
|
|
146
|
+
"type": "object",
|
|
147
|
+
"additionalProperties": true,
|
|
148
|
+
"title": "shiftwork_clock_outDictOutput"
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -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 whose dependencies are all satisfied, and `width` is the largest batch length, the widest fan-out. Only `cursor` can be clocked: shiftwork_clock_in briefs it and shiftwork_clock_out refuses every other id, so a `ready` member beyond the cursor is what the graph permits, not a unit the clock accepts yet. 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
|
+
}
|
|
@@ -69,7 +69,12 @@ dependencies = ["httpx>=0.27", "jsonschema>=4.21", "pyyaml>=6.0"]
|
|
|
69
69
|
# without this declaration the bar would silently skip in exactly the environment
|
|
70
70
|
# CI runs (`pip install -e "runtime-py[dev,mcp]"`). Declared here so it runs.
|
|
71
71
|
dev = ["pytest>=8.0", "ruff>=0.4", "hatchling>=1.24"]
|
|
72
|
-
|
|
72
|
+
# Upper bound measured, not guessed: 2.0.0 and 2.0.1 wrap a tool crash as
|
|
73
|
+
# `Error executing tool <name>: <exc>`, which runtime-ts/src/mcp/server.ts mirrors.
|
|
74
|
+
# 2.1.0 added an UnexpectedToolError branch that drops the `: <exc>` suffix, so the
|
|
75
|
+
# crash text stops reaching the client and `--suite workplan` goes red on the
|
|
76
|
+
# reference while the port still carries it. Raise this only together with the port.
|
|
77
|
+
mcp = ["mcp>=2.0,<2.1"]
|
|
73
78
|
|
|
74
79
|
[project.scripts]
|
|
75
80
|
bantamkit-mcp = "bantamkit.mcpserver:main"
|
|
@@ -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.35.
|
|
32
|
+
__version__ = "0.35.3"
|
|
@@ -30,6 +30,7 @@ from bantamkit import (
|
|
|
30
30
|
shiftwork,
|
|
31
31
|
skillaudit,
|
|
32
32
|
tokenledger,
|
|
33
|
+
updatecheck,
|
|
33
34
|
workplan,
|
|
34
35
|
)
|
|
35
36
|
from bantamkit.assets import AssetNotFound, assets_root, load_skill, load_tool_asset
|
|
@@ -965,6 +966,18 @@ def status_report(
|
|
|
965
966
|
+ ("unreadable" if size is None else str(size))
|
|
966
967
|
+ f" of {budget} bytes",
|
|
967
968
|
f"event log: {'on' if log.enabled else 'off'}",
|
|
969
|
+
# THE UPDATE LINE IS NOT A CONDITION, and that is the ruling of 2026-09-19 rather
|
|
970
|
+
# than an omission. A newer version existing is not a fault: the server is serving
|
|
971
|
+
# correctly, and `degraded_notice`'s docstring above is the operator's own reason —
|
|
972
|
+
# a footer on every result is noise, and noise trains a reader to skip it. So this
|
|
973
|
+
# never flips line 1, never enters `degraded_conditions`, and never rides another
|
|
974
|
+
# tool's reply. It also means the record is opened ONLY here, when someone asked for
|
|
975
|
+
# a report, and not on the per-call path whose cost `docs/status.md` documents.
|
|
976
|
+
#
|
|
977
|
+
# `updatecheck` opens one file and asks nobody anything — AS-7(3) is intact, and
|
|
978
|
+
# `test_selfupdate.py::test_only_the_update_flag_reaches_this_module_from_the_server`
|
|
979
|
+
# still holds, because the name this reaches is `updatecheck` and not `selfupdate`.
|
|
980
|
+
updatecheck.update_line(str(identity["version"])),
|
|
968
981
|
]
|
|
969
982
|
if conditions:
|
|
970
983
|
lines.append(f"{_plural(len(conditions), 'problem')}:")
|
|
@@ -1894,6 +1907,25 @@ def build_server(memory: Memory, log: EventLog | None = None) -> Any:
|
|
|
1894
1907
|
|
|
1895
1908
|
@server.resource("bantamkit://skills/{name}")
|
|
1896
1909
|
def skill_resource(name: str) -> str:
|
|
1910
|
+
# A skill that pairs with a tool is served on that tool's surfaces and no other. The
|
|
1911
|
+
# pairing is the one `filegraph.py` spells: `load_tool("file_graph")` beside
|
|
1912
|
+
# `load_skill("file-graph")` — the skill's name with `-` for `_`. `file-graph` is the
|
|
1913
|
+
# eval agent's system-prompt snippet telling it to call `file_graph`, a tool whose
|
|
1914
|
+
# asset claims only `agent`; handing that text to an MCP client sends it to a tool
|
|
1915
|
+
# `tools/list` does not carry and `tools/call` refuses as unknown (job60 row 46).
|
|
1916
|
+
# Same sentence and same error code on both runtimes; `tools/conformance/suites/
|
|
1917
|
+
# instructions.mjs` reads every skill on disk over `resources/read` and resolves the
|
|
1918
|
+
# tools it names against `tools/list`.
|
|
1919
|
+
paired = name.replace("-", "_")
|
|
1920
|
+
try:
|
|
1921
|
+
asset = load_tool_asset(paired)
|
|
1922
|
+
except AssetNotFound:
|
|
1923
|
+
asset = None
|
|
1924
|
+
if asset is not None and "mcp" not in asset["surfaces"]:
|
|
1925
|
+
raise ResourceError(
|
|
1926
|
+
f"skill asset {name} is not served here: it pairs with tool {paired}, "
|
|
1927
|
+
f"whose asset claims surfaces {asset['surfaces']}, not mcp"
|
|
1928
|
+
)
|
|
1897
1929
|
try:
|
|
1898
1930
|
return load_skill(name)
|
|
1899
1931
|
except AssetNotFound:
|
|
@@ -133,7 +133,7 @@ _FACTS_UNREADABLE = (
|
|
|
133
133
|
_ARCHIVE_UNREADABLE = (
|
|
134
134
|
"an archive that could not be listed is not an empty archive, and answering "
|
|
135
135
|
"'nothing is archived' here is what makes compaction look like deletion — the "
|
|
136
|
-
"facts
|
|
136
|
+
"facts that compaction moved are still on disk under this path"
|
|
137
137
|
)
|
|
138
138
|
# The same distinction one syscall down, for `restore`, which stats one named path
|
|
139
139
|
# instead of listing (see `archived()` for why). A refused stat is not an absent file,
|
|
@@ -1310,5 +1310,5 @@ class MemoryStore:
|
|
|
1310
1310
|
if size > self.index_budget:
|
|
1311
1311
|
raise MemoryBudgetExceeded(
|
|
1312
1312
|
f"memory index is {size} bytes, budget is {self.index_budget}: "
|
|
1313
|
-
f"
|
|
1313
|
+
f"compact the store or tersen descriptions"
|
|
1314
1314
|
)
|
|
@@ -55,11 +55,14 @@ a store or a transport exists, the same shape `--assets-root` and `--install` us
|
|
|
55
55
|
from __future__ import annotations
|
|
56
56
|
|
|
57
57
|
import json
|
|
58
|
+
import os
|
|
58
59
|
import shlex
|
|
59
60
|
import subprocess
|
|
60
61
|
import sys
|
|
62
|
+
import tempfile
|
|
61
63
|
from collections.abc import Callable
|
|
62
64
|
from dataclasses import dataclass
|
|
65
|
+
from datetime import UTC, datetime
|
|
63
66
|
|
|
64
67
|
from bantamkit import __version__
|
|
65
68
|
|
|
@@ -359,6 +362,83 @@ def compare_versions(installed: str, latest: str) -> int:
|
|
|
359
362
|
return 1 if left > right else -1
|
|
360
363
|
|
|
361
364
|
|
|
365
|
+
#: The `checked_at` stamp's shape, spelled rather than defaulted. `datetime.isoformat()`
|
|
366
|
+
#: renders `+00:00` and `Date.prototype.toISOString` renders `.000Z`, so a record written by
|
|
367
|
+
#: the two runtimes would differ in bytes neither reader cares about. Both sides render this
|
|
368
|
+
#: one shape instead, and `runtime-ts` strips its milliseconds to reach it.
|
|
369
|
+
STAMP = "%Y-%m-%dT%H:%M:%SZ"
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def record_update(latest: str, now: str | None = None) -> bool:
|
|
373
|
+
"""Put the version this flag just fetched into the record `bantamkit_status` reads.
|
|
374
|
+
|
|
375
|
+
NO SECOND NETWORK CALL AND NO NEW FAILURE MODE, which is the whole reason the writer is
|
|
376
|
+
here rather than anywhere a reader could reach. `update` already holds `latest` — it was
|
|
377
|
+
parsed out of the answer the operator's own `--update` asked for — so this writes what is
|
|
378
|
+
in hand. `updatecheck` reads that file and NEVER writes it; the split is structural and
|
|
379
|
+
both halves are gated (`test_updatecheck.py::test_the_source_writes_nothing_and_reaches_
|
|
380
|
+
no_network`), so a future reader cannot grow a write by accident.
|
|
381
|
+
|
|
382
|
+
`updatecheck` IS IMPORTED INSIDE THE BODY, and not for style. `updatecheck` imports
|
|
383
|
+
`PROGRAM` and `compare_versions` from THIS module at its top level, so a module-level
|
|
384
|
+
import here would be a cycle that fails at interpreter start. The function-local import
|
|
385
|
+
is the direction that works, and `_names_reached_by` in `test_selfupdate.py` sees it —
|
|
386
|
+
`bantamkit.updatecheck` is on that gate's allowlist by name.
|
|
387
|
+
|
|
388
|
+
IT CREATES NO DIRECTORY. `<homedir>/.bantamkit` is made by an install, and a `--update`
|
|
389
|
+
run on a machine that has never had one writes nothing rather than deciding where this
|
|
390
|
+
toolbox's home directory should be. A cwd-relative `.bantamkit` is a MEMORY STORE (J54-3)
|
|
391
|
+
and the path here comes from `updatecheck.record_path()`, which hangs off `_home()`.
|
|
392
|
+
|
|
393
|
+
THE OTHER RUNTIME'S KEY IS LEFT EXACTLY AS FOUND. npm and PyPI are two registries that
|
|
394
|
+
can disagree at one version number — job56 shipped a day where they did — so this fills
|
|
395
|
+
`updatecheck.KEY` and copies the rest of the record through untouched. An UNREADABLE
|
|
396
|
+
record is replaced rather than merged: there is nothing in it to preserve.
|
|
397
|
+
|
|
398
|
+
TEMP FILE AND RENAME, so a reader never sees half a record: `os.replace` is atomic on
|
|
399
|
+
POSIX and on Windows, and the temp file is made in the SAME directory so the rename is
|
|
400
|
+
never across a filesystem.
|
|
401
|
+
|
|
402
|
+
RETURNS whether it wrote, and RAISES FOR NOTHING. A failed write must not turn a
|
|
403
|
+
successful `--update` into a failure: the operator's install was updated either way, and
|
|
404
|
+
the worst case is a status line that still says `never checked`.
|
|
405
|
+
"""
|
|
406
|
+
# The MODULE is named in the import so the gate's allowlist can name it too: a
|
|
407
|
+
# `from bantamkit import updatecheck` records only `bantamkit`, which would let any
|
|
408
|
+
# sibling module in under the same allowance.
|
|
409
|
+
from bantamkit.updatecheck import KEY, SOURCE_RECORD, load_record, record_path
|
|
410
|
+
|
|
411
|
+
path = record_path()
|
|
412
|
+
directory = path.parent
|
|
413
|
+
if not directory.is_dir():
|
|
414
|
+
return False
|
|
415
|
+
source, existing = load_record(path)
|
|
416
|
+
payload: dict[str, object] = dict(existing) if source == SOURCE_RECORD and existing else {}
|
|
417
|
+
payload["checked_at"] = now or datetime.now(UTC).strftime(STAMP)
|
|
418
|
+
payload[KEY] = {"distribution": DISTRIBUTION, "latest": latest}
|
|
419
|
+
try:
|
|
420
|
+
body = json.dumps(payload, indent=2, ensure_ascii=False) + "\n"
|
|
421
|
+
except (TypeError, ValueError):
|
|
422
|
+
return False
|
|
423
|
+
try:
|
|
424
|
+
handle, temporary = tempfile.mkstemp(
|
|
425
|
+
dir=str(directory), prefix=".update-check-", suffix=".json"
|
|
426
|
+
)
|
|
427
|
+
except OSError:
|
|
428
|
+
return False
|
|
429
|
+
try:
|
|
430
|
+
with os.fdopen(handle, "w", encoding="utf-8", newline="\n") as stream:
|
|
431
|
+
stream.write(body)
|
|
432
|
+
os.replace(temporary, path)
|
|
433
|
+
except OSError:
|
|
434
|
+
try:
|
|
435
|
+
os.unlink(temporary)
|
|
436
|
+
except OSError:
|
|
437
|
+
pass
|
|
438
|
+
return False
|
|
439
|
+
return True
|
|
440
|
+
|
|
441
|
+
|
|
362
442
|
def update(
|
|
363
443
|
installed: str,
|
|
364
444
|
origin: Origin,
|
|
@@ -389,6 +469,15 @@ def update(
|
|
|
389
469
|
raise UpdateRefused(UNREACHABLE.format(reason=exc)) from None
|
|
390
470
|
|
|
391
471
|
latest = latest_from_index_payload(body)
|
|
472
|
+
|
|
473
|
+
# THE RECORD IS WRITTEN HERE AND NOT IN ONE OF THE ARMS BELOW, because what it records is
|
|
474
|
+
# "the index said X on this date" — which is true the moment the fetch returned, whatever
|
|
475
|
+
# this flag then decides to do about it. An operator whose shape has no route gets the
|
|
476
|
+
# refusal AND a `bantamkit_status` that now knows the number; one who is already current
|
|
477
|
+
# gets a line that says so with a date. The return value is deliberately dropped: a record
|
|
478
|
+
# that could not be written is not a reason to fail an update that worked.
|
|
479
|
+
record_update(latest)
|
|
480
|
+
|
|
392
481
|
header = COMPARISON.format(program=PROGRAM, installed=installed, latest=latest)
|
|
393
482
|
order = compare_versions(installed, latest)
|
|
394
483
|
if order == 0:
|