bantamkit 0.35.6__tar.gz → 0.35.8__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.6 → bantamkit-0.35.8}/PKG-INFO +4 -4
- {bantamkit-0.35.6 → bantamkit-0.35.8}/README.md +2 -2
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/tools/memory_compact.json +1 -2
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/tools/memory_recall.json +1 -3
- bantamkit-0.35.8/_assets/tools/shiftwork_clock_in.json +37 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/tools/shiftwork_clock_out.json +1 -1
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/tools/validate_json.json +1 -1
- bantamkit-0.35.8/_assets/tools/work_plan.json +55 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/__init__.py +1 -1
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/contract.py +393 -6
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/eventlog.py +24 -1
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/hookadapter.py +273 -50
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/hostinstall.py +148 -23
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/mcpreport.py +29 -4
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/mcpserver.py +87 -11
- bantamkit-0.35.8/src/bantamkit/mcpstdio.py +146 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/memory/__main__.py +17 -2
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/memory/component.py +81 -7
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/memory/layers.py +41 -1
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/memory/store.py +325 -38
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/selfupdate.py +212 -73
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/shiftwork.py +244 -42
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/workplan.py +67 -7
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/data/served-tool-surface.json +19 -16
- bantamkit-0.35.8/tests/test_contract_patterns.py +190 -0
- bantamkit-0.35.8/tests/test_contract_refs.py +155 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_eventlog.py +25 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_hookadapter.py +230 -2
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_hostinstall.py +106 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_hostinstall_hooks.py +113 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_mcpreport.py +42 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_mcpserver.py +15 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_memory.py +326 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_memory_compact_tool.py +8 -1
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_memory_component.py +12 -6
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_memory_layers.py +51 -0
- bantamkit-0.35.8/tests/test_memory_strings.py +372 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_nativeexport.py +10 -7
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_selfupdate.py +367 -33
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_shiftwork.py +420 -2
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_skillaudit.py +53 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_status_surface.py +16 -4
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_workplan.py +82 -0
- bantamkit-0.35.6/_assets/tools/shiftwork_clock_in.json +0 -37
- bantamkit-0.35.6/_assets/tools/work_plan.json +0 -49
- {bantamkit-0.35.6 → bantamkit-0.35.8}/.gitignore +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/contracts/default.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/manifest.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/repo/HISTORY.md +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/repo/README.md +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/repo/docs/architecture.md +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/repo/docs/runbook.md +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/repo/issues/142-settlement-timeout.md +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/repo/patches/0009-retry-budget.patch +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/repo/src/ledger/__init__.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/repo/src/ledger/config.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/repo/src/ledger/errors.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/repo/src/ledger/posting.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/repo/src/ledger/registry.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/repo/src/ledger/report.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/repo/src/ledger/retry.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/repo/src/ledger/settle.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/repo/src/ledger/validate.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/repo/tests/test_posting.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/repo/tests/test_settle.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/tasks/dt-error-contract.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/tasks/dt-handler-map.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/tasks/dt-patch-before-after.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/tasks/dt-retry-attempts.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/tasks/dt-settlement-config.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/tasks/dt-symbol-home.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/tasks/dt-trace-blame.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/devteam/tasks/dt-unread-key.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/document/tasks/doc-large-in-137.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/document/tasks/doc-large-in-359.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/document/tasks/doc-large-in-372.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/document/tasks/doc-large-out-11764.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/document/tasks/doc-large-out-4137.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/document/tasks/doc-large-out-8022.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/document/tasks/doc-small-137.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/document/tasks/doc-small-261.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/document/tasks/doc-small-388.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/fixtures/.gitkeep +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/fixtures/catalog.json +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/perturbations/task-completion.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/.gitkeep +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/extract-contact.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/extract-invoice.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/extract-order.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/extract-schedule.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/extract-versions.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/nav-prod-port.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/nav-release-bundle.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/recall-audit-retention.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/recall-cache-ttl.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/recall-db-port.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/recall-deploy.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/recall-env-endpoint.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/recall-oncall-rotation.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/recall-oncall.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/recall-org-quota.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/recall-owner.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/shop-basket-total.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/shop-cheapest.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/shop-compare.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/shop-gadget-value.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/shop-stock-total.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/evals/tasks/shop-total.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/pricing/default.json +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/profiles/default.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/profiles/patient.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/rubrics/.gitkeep +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/rubrics/code-quality.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/rubrics/grounded-completion.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/rubrics/task-completion.yaml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/schemas/shiftwork-checkpoint.json +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/skills/.gitkeep +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/skills/file-graph.md +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/skills/memory.md +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/tools/.gitkeep +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/tools/bantamkit_read.json +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/tools/bantamkit_status.json +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/tools/build_identity.json +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/tools/document_list.json +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/tools/document_read.json +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/tools/file_graph.json +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/tools/memory_dream.json +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/tools/memory_save.json +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/tools/repo_map.json +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/tools/shiftwork_plan.json +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/tools/shiftwork_status.json +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/tools/skill_audit.json +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/_assets/tools/token_ledger.json +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/hatch_build.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/pyproject.toml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/agent.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/assets.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/budget.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/client.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/criticreplay.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/critique.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/docmanifest.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/docread.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/evalrun.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/filegraph.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/loopguard.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/memory/__init__.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/memory/divergence.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/memory/dream.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/pdfread.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/pricing.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/profile.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/repomap.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/skillaudit.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/statusline.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/structured.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/textutil.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/tokenledger.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/src/bantamkit/updatecheck.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/cli_exit_status_probe.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/conftest.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/data/docread/bad-crc.docx +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/data/docread/charref-4301-digits.html +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/data/docread/charset-table.json +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/data/docread/compression-method-9.docx +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/data/docread/corrupt-deflate.docx +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/data/docread/encrypted-member.docx +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/data/docread/encrypted-mimetype.odt +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/data/docread/eszett-cell-ref.xlsx +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/data/docread/internal-dtd-entity.docx +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/data/docread/rfc2231-charset.eml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/data/docread/rfc822-nested-twice.eml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/data/docread/unicode-digit-shared-string.xlsx +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/data/docread/x-uuencode.eml +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/data/f8404ab-perturbation-baseline.json +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/data/platform-assumption-baseline.json +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/docread_fixtures.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/perturbation_baseline_harness.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/rbp16_effect_probe.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/rbp18_payload_probe.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_adapter.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_agent.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_amendguard.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_bantamkit_gitignore.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_bantamkit_read_tool.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_budget.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_build_identity.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_client.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_compaction_corpus_survey.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_conformance.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_conformance_harness_resilience.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_conformance_suite_table_gate.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_contract_fanout.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_criticreplay.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_critique.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_doc_commands_gate.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_docread.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_docread_ceilings.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_document_manifest_parity.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_document_setup.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_document_tasks.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_document_tools.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_encoding_gate.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_evalrun.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_field_program_gates.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_field_programs.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_filegraph.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_install_shape.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_ladder_statistics.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_launcher_which.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_layers.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_loopguard.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_mcp_endpoint.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_mcpdrift.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_memory_divergence.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_memory_dream.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_memory_store_tripwire.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_mutmatrix.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_newline_gate.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_packaging.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_pdfread.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_pinharness_ledger.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_platform_assumption_gate.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_pricing.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_readme_separation.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_repo_map_tool.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_repomap.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_served_tool_count_records.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_statusline.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_structured.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_thread_exception_gate.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_tokenledger.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_tool_manifest.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_updatecheck.py +0 -0
- {bantamkit-0.35.6 → bantamkit-0.35.8}/tests/test_version_agreement.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: bantamkit
|
|
3
|
-
Version: 0.35.
|
|
3
|
+
Version: 0.35.8
|
|
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
|
|
@@ -103,9 +103,9 @@ CPU architecture and Python minor version** (some wheels, such as `pydantic_core
|
|
|
103
103
|
one platform only), copy `wheels/` across, and install from it:
|
|
104
104
|
|
|
105
105
|
```bash
|
|
106
|
-
python -m pip download "bantamkit[mcp]==0.35.
|
|
106
|
+
python -m pip download "bantamkit[mcp]==0.35.8" -d wheels
|
|
107
107
|
python -m venv <env>
|
|
108
|
-
<env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.35.
|
|
108
|
+
<env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.35.8"
|
|
109
109
|
<env>/bin/bantamkit-mcp --install cursor
|
|
110
110
|
```
|
|
111
111
|
|
|
@@ -74,9 +74,9 @@ CPU architecture and Python minor version** (some wheels, such as `pydantic_core
|
|
|
74
74
|
one platform only), copy `wheels/` across, and install from it:
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
|
-
python -m pip download "bantamkit[mcp]==0.35.
|
|
77
|
+
python -m pip download "bantamkit[mcp]==0.35.8" -d wheels
|
|
78
78
|
python -m venv <env>
|
|
79
|
-
<env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.35.
|
|
79
|
+
<env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.35.8"
|
|
80
80
|
<env>/bin/bantamkit-mcp --install cursor
|
|
81
81
|
```
|
|
82
82
|
|
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
"properties": {
|
|
11
11
|
"reserve": {
|
|
12
12
|
"type": "integer",
|
|
13
|
-
"
|
|
14
|
-
"description": "Bytes of index headroom to free below the budget. Omit for the default: the size of the largest index line the store holds, capped at half the budget."
|
|
13
|
+
"description": "Bytes of index headroom to free below the budget: the target is `budget - reserve`, a negative value counts as 0, any value is capped at half the budget, and 0 compacts to the budget itself. Omit for the default: the room the degraded-index warning demands (the budget minus `(90 * budget - 1) // 100`) plus the size of the largest index line the store holds -- at the default 24000-byte budget with a 39-byte largest line that is 2401 + 39 = 2440, a 21560-byte target."
|
|
15
14
|
}
|
|
16
15
|
}
|
|
17
16
|
},
|
|
@@ -17,9 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"k": {
|
|
19
19
|
"type": "integer",
|
|
20
|
-
"
|
|
21
|
-
"maximum": 5,
|
|
22
|
-
"description": "Facts wanted. Raised to the store default of 3 when smaller; never lowered below it."
|
|
20
|
+
"description": "Facts wanted, clamped to 1..5. Raised to the store default of 3 when smaller; never lowered below it."
|
|
23
21
|
}
|
|
24
22
|
}
|
|
25
23
|
},
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shiftwork_clock_in",
|
|
3
|
+
"description": "Shift-work clock-in: schema-validate the checkpoint file and return the brief for the unit at plan.cursor — {unit, role, invariants, handoff, do_not, files} — to hand to the spawned agent verbatim. The unit briefed is the one at plan.cursor, or, when unit_id is given, that unit — either way it must appear in shiftwork_plan's `ready`, the batch the dependency graph says may run now: the cursor unit is held to the same ready test as unit_id. A unit that is not ready (a done or dropped cursor, a cursor whose dependencies have not run, a unit_id naming no unit) is refused with {\"result\": \"error\", \"reason\": \"unit <id> is not ready; ready is <a, b>\"}, and on both paths the refusals of the batch view itself (cycle, unknown dependency, unreadable checkpoint) pass through verbatim; a cursor naming no unit still escalates. Clock-in still never moves plan.cursor; clock_out does. Structured refusals, never exceptions: result=escalate when handoff.open_questions is non-empty, result=success when every unit is done or dropped, result=error when the checkpoint fails validation. Clock-in also WRITES: on result=brief, and only then, it appends one line {event: brief, ts, unit, role} to <checkpoint>.log.jsonl — best-effort: a log it cannot write costs the brief nothing (the brief still returns, no error, only the record is lost). clock_out reads those lines back to write `briefed` on every accounting line, so the ledger is no longer one line per clock-out; a brief line carries `event` and no `status`. See docs/shiftwork.md.",
|
|
4
|
+
"surfaces": [
|
|
5
|
+
"mcp"
|
|
6
|
+
],
|
|
7
|
+
"parameters": {
|
|
8
|
+
"properties": {
|
|
9
|
+
"checkpoint": {
|
|
10
|
+
"title": "Checkpoint",
|
|
11
|
+
"type": "string"
|
|
12
|
+
},
|
|
13
|
+
"unit_id": {
|
|
14
|
+
"anyOf": [
|
|
15
|
+
{
|
|
16
|
+
"type": "string"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"type": "null"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"default": null,
|
|
23
|
+
"title": "Unit Id"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"required": [
|
|
27
|
+
"checkpoint"
|
|
28
|
+
],
|
|
29
|
+
"type": "object",
|
|
30
|
+
"title": "shiftwork_clock_inArguments"
|
|
31
|
+
},
|
|
32
|
+
"output_schema": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"additionalProperties": true,
|
|
35
|
+
"title": "shiftwork_clock_inDictOutput"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"anyOf": [
|
|
88
88
|
{
|
|
89
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
|
|
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, except the five the runtime writes itself — unit, role, status, ts, event — which are refused (result=error) before anything is written; a self-reported briefed is overwritten, not refused. Lines written before this schema existed may spell these differently; the schema governs new writes only.",
|
|
91
91
|
"properties": {
|
|
92
92
|
"tokens": {
|
|
93
93
|
"type": "integer",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "validate_json",
|
|
3
|
-
"description": "Validate candidate output text against a JSON Schema. Returns {valid, feedback}; when invalid, feed the feedback back to the model and retry.",
|
|
3
|
+
"description": "Validate candidate output text against a JSON Schema. Returns {valid, feedback}; when invalid, feed the feedback back to the model and retry. The output may be any JSON value; otherwise the first fenced block, or the first object or array in the text, is used. $schema selects draft-04, draft-06, draft-07, 2019-09 or 2020-12 (default); $ref resolves only inside the one schema (\"#\", \"#/...\", \"#name\"), so a remote reference or an embedded $id is refused. pattern is Python re syntax, without inline flags, look-behind, backreferences, atomic or possessive groups, conditional groups or \\N{...}; a document's patterns get 500 ms in total.",
|
|
4
4
|
"surfaces": [
|
|
5
5
|
"mcp"
|
|
6
6
|
],
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "work_plan",
|
|
3
|
+
"description": "Work plan: turn a dependency graph into the batches that may run in parallel. Takes `nodes`, each `{id, depends_on, priority}` — `depends_on` absent or null means none, `priority` defaults to 0 — and answers `{\"result\": \"plan\", \"batches\", \"sequence\", \"width\"}`: batch k holds every node whose dependencies all appear in batches below k, `sequence` is those batches flattened in order, and `width` is the largest batch length — the widest fan-out, which is the number an orchestrator needs to decide whether it can afford the batch. Order inside a batch is priority descending, then the order the nodes were given; both halves are contract, because determinism inside a batch is what two implementations are held to. Empty `nodes` is an ANSWER, not a refusal: no batches, no sequence, width 0. Four refusals, checked in this order and one sentence each: `nodes[<i>].<field> must be ...` for a node whose `id` is not a string, whose `depends_on` is not a list of strings or whose `priority` is not an integer (named by position, never coerced), `duplicate node id <id>`, `node <id> depends on <dep>, which no node declares`, and `the graph has a cycle: <a> -> <b> -> <a>`. It reports what the graph permits and decides nothing about what may actually be dispatched together — `depends_on` encodes logical order, not file contention, so a batch this tool calls parallel may still hold work that writes one file. Pure computation over the nodes it was handed: it opens no file and takes no path. Never mutates.",
|
|
4
|
+
"surfaces": [
|
|
5
|
+
"mcp"
|
|
6
|
+
],
|
|
7
|
+
"parameters": {
|
|
8
|
+
"properties": {
|
|
9
|
+
"nodes": {
|
|
10
|
+
"title": "Nodes",
|
|
11
|
+
"type": "array",
|
|
12
|
+
"items": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"id": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "The node's identifier, unique across `nodes`"
|
|
18
|
+
},
|
|
19
|
+
"depends_on": {
|
|
20
|
+
"anyOf": [
|
|
21
|
+
{
|
|
22
|
+
"type": "array",
|
|
23
|
+
"items": {
|
|
24
|
+
"type": "string"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"type": "null"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"description": "Ids this node waits for; every one of them must be declared by some node. Optional: absent or null means none"
|
|
32
|
+
},
|
|
33
|
+
"priority": {
|
|
34
|
+
"type": "integer",
|
|
35
|
+
"description": "Tie-break inside a batch, descending. Optional; 0 when absent"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"required": [
|
|
39
|
+
"id"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"required": [
|
|
45
|
+
"nodes"
|
|
46
|
+
],
|
|
47
|
+
"type": "object",
|
|
48
|
+
"title": "work_planArguments"
|
|
49
|
+
},
|
|
50
|
+
"output_schema": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"additionalProperties": true,
|
|
53
|
+
"title": "work_planDictOutput"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -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.8"
|
|
@@ -2,11 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import builtins
|
|
6
|
+
import contextvars
|
|
7
|
+
import importlib
|
|
5
8
|
import json
|
|
9
|
+
import queue
|
|
6
10
|
import re
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
import threading
|
|
14
|
+
import time
|
|
15
|
+
from collections.abc import Iterator
|
|
16
|
+
from re import _constants as _sre
|
|
17
|
+
from re import _parser as _sre_parse
|
|
7
18
|
|
|
8
19
|
import jsonschema
|
|
9
20
|
import yaml
|
|
21
|
+
from jsonschema.exceptions import _WrappedReferencingError
|
|
22
|
+
from referencing.exceptions import NoSuchAnchor, PointerToNowhere
|
|
10
23
|
|
|
11
24
|
from bantamkit.assets import AssetNotFound, assets_root
|
|
12
25
|
from bantamkit.client import BantamError, Message
|
|
@@ -108,11 +121,17 @@ def validation_error_message(where: str, detail: str) -> str:
|
|
|
108
121
|
return load_contract()["validation_error"].format(where=where, detail=detail)
|
|
109
122
|
|
|
110
123
|
|
|
111
|
-
def
|
|
124
|
+
def _unfenced(text: str) -> str:
|
|
125
|
+
"""The text `extract_json` searches: stripped, and the first fenced block's body if any."""
|
|
112
126
|
text = text.strip()
|
|
113
127
|
fence = re.search(r"```(?:json)?\s*(.*?)```", text, re.DOTALL)
|
|
114
128
|
if fence:
|
|
115
129
|
text = fence.group(1).strip()
|
|
130
|
+
return text
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def extract_json(text: str) -> dict | list:
|
|
134
|
+
text = _unfenced(text)
|
|
116
135
|
start_brace = text.find("{")
|
|
117
136
|
start_bracket = text.find("[")
|
|
118
137
|
if start_brace == -1 and start_bracket == -1:
|
|
@@ -127,20 +146,388 @@ def extract_json(text: str) -> dict | list:
|
|
|
127
146
|
return obj
|
|
128
147
|
|
|
129
148
|
|
|
149
|
+
_REF_KEYWORDS = ("$ref", "$dynamicRef", "$recursiveRef")
|
|
150
|
+
# The keywords whose value holds subschemas, in ANY dialect — the walk the pre-scan takes.
|
|
151
|
+
# `runtime-ts/src/pyjsonschema.ts` (`subschemas`) is the same walk in the same order, which
|
|
152
|
+
# is what makes the two refusal sentences name the same place.
|
|
153
|
+
_WALK_SUBSCHEMA = frozenset(
|
|
154
|
+
{
|
|
155
|
+
"additionalItems",
|
|
156
|
+
"additionalProperties",
|
|
157
|
+
"contains",
|
|
158
|
+
"else",
|
|
159
|
+
"if",
|
|
160
|
+
"items",
|
|
161
|
+
"not",
|
|
162
|
+
"propertyNames",
|
|
163
|
+
"then",
|
|
164
|
+
"unevaluatedItems",
|
|
165
|
+
"unevaluatedProperties",
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
_WALK_MAP = frozenset(
|
|
169
|
+
{"$defs", "definitions", "dependencies", "dependentSchemas", "patternProperties", "properties"}
|
|
170
|
+
)
|
|
171
|
+
_WALK_LIST = frozenset({"allOf", "anyOf", "oneOf", "prefixItems"})
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _subschemas(schema: object, path: str = "") -> Iterator[tuple[str, dict]]:
|
|
175
|
+
"""Every subschema of `schema`, root first, in document order, with its path.
|
|
176
|
+
|
|
177
|
+
Structural, not blind: a PROPERTY named `$ref` is not a reference, and this walk never
|
|
178
|
+
mistakes it for one.
|
|
179
|
+
"""
|
|
180
|
+
if not isinstance(schema, dict):
|
|
181
|
+
return
|
|
182
|
+
yield path, schema
|
|
183
|
+
for k, v in schema.items():
|
|
184
|
+
at = k if path == "" else f"{path}/{k}"
|
|
185
|
+
if k == "items" and isinstance(v, list):
|
|
186
|
+
for i, x in enumerate(v):
|
|
187
|
+
yield from _subschemas(x, f"{at}/{i}")
|
|
188
|
+
elif k in _WALK_SUBSCHEMA:
|
|
189
|
+
yield from _subschemas(v, at)
|
|
190
|
+
elif k in _WALK_MAP and isinstance(v, dict):
|
|
191
|
+
for n, x in v.items():
|
|
192
|
+
yield from _subschemas(x, f"{at}/{n}")
|
|
193
|
+
elif k in _WALK_LIST and isinstance(v, list):
|
|
194
|
+
for i, x in enumerate(v):
|
|
195
|
+
yield from _subschemas(x, f"{at}/{i}")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _draft3_sentence(uri: str) -> str:
|
|
199
|
+
return (
|
|
200
|
+
f"the schema's $schema {json.dumps(uri)} names draft 3, which this validator does not "
|
|
201
|
+
"support (draft-04, draft-06, draft-07, 2019-09 and 2020-12 are)"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _names_draft3(uri: object) -> bool:
|
|
206
|
+
if not isinstance(uri, str):
|
|
207
|
+
return False
|
|
208
|
+
found = jsonschema.validators.validator_for({"$schema": uri}, default=None)
|
|
209
|
+
return found is jsonschema.Draft3Validator
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# ---- `pattern`: the dialect both runtimes share, and its clock (#131, #132) ----------------
|
|
213
|
+
|
|
214
|
+
# The opcodes `re` parses that the Node port cannot translate faithfully: look-behind
|
|
215
|
+
# (fixed-width here, any width in JS), backreferences (a JS backreference to a group that did
|
|
216
|
+
# not take part matches the empty string; here it fails), atomic groups, possessive
|
|
217
|
+
# quantifiers and conditional groups. Inline flags and `\N{...}` are found below.
|
|
218
|
+
_UNSUPPORTED_OPS = frozenset(
|
|
219
|
+
{_sre.ATOMIC_GROUP, _sre.POSSESSIVE_REPEAT, _sre.GROUPREF, _sre.GROUPREF_EXISTS}
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _names_a_character(pattern: str) -> bool:
|
|
224
|
+
"""`\\N{...}` anywhere: it parses to a plain LITERAL, so the tree cannot show it."""
|
|
225
|
+
i = 0
|
|
226
|
+
while i < len(pattern):
|
|
227
|
+
if pattern[i] == "\\":
|
|
228
|
+
if pattern[i + 1 : i + 2] == "N":
|
|
229
|
+
return True
|
|
230
|
+
i += 2
|
|
231
|
+
else:
|
|
232
|
+
i += 1
|
|
233
|
+
return False
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _tree_unsupported(items: object) -> bool:
|
|
237
|
+
for op, av in items: # type: ignore[attr-defined]
|
|
238
|
+
if op in _UNSUPPORTED_OPS:
|
|
239
|
+
return True
|
|
240
|
+
if op in (_sre.ASSERT, _sre.ASSERT_NOT):
|
|
241
|
+
direction, sub = av
|
|
242
|
+
if direction < 0 or _tree_unsupported(sub):
|
|
243
|
+
return True
|
|
244
|
+
elif op is _sre.SUBPATTERN:
|
|
245
|
+
_group, add_flags, del_flags, sub = av
|
|
246
|
+
if add_flags or del_flags or _tree_unsupported(sub):
|
|
247
|
+
return True
|
|
248
|
+
elif op in (_sre.MAX_REPEAT, _sre.MIN_REPEAT):
|
|
249
|
+
if _tree_unsupported(av[2]):
|
|
250
|
+
return True
|
|
251
|
+
elif op is _sre.BRANCH:
|
|
252
|
+
if any(_tree_unsupported(sub) for sub in av[1]):
|
|
253
|
+
return True
|
|
254
|
+
return False
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _pattern_unsupported(pattern: str) -> bool:
|
|
258
|
+
"""True when `re` parses `pattern` and it uses a construct outside the shared dialect.
|
|
259
|
+
|
|
260
|
+
A pattern `re` refuses is NOT this function's: it is left to `check_schema`, which raises
|
|
261
|
+
where it always raised. `runtime-ts/src/pyjsonschema.ts` (`translatePyPattern`) parses
|
|
262
|
+
with `re._parser`'s own grammar and marks the same constructs, which is what makes the
|
|
263
|
+
refusal one sentence on both runtimes rather than two verdicts.
|
|
264
|
+
"""
|
|
265
|
+
# `\\N{...}` first, whatever else the pattern holds: the port has no Unicode name table,
|
|
266
|
+
# so it cannot tell a defined name from an undefined one, and neither side tries to.
|
|
267
|
+
if _names_a_character(pattern):
|
|
268
|
+
return True
|
|
269
|
+
try:
|
|
270
|
+
parsed = _sre_parse.parse(pattern)
|
|
271
|
+
except (re.error, OverflowError, ValueError, RecursionError):
|
|
272
|
+
return False
|
|
273
|
+
# `(?u)` is the default for a str pattern; any other global flag is refused.
|
|
274
|
+
if parsed.state.flags & ~re.UNICODE:
|
|
275
|
+
return True
|
|
276
|
+
return _tree_unsupported(parsed.data)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _clip(text: str, limit: int = 200) -> str:
|
|
280
|
+
"""At most `limit` codepoints of `text`, then `…(N more)` (#153)."""
|
|
281
|
+
if len(text) <= limit:
|
|
282
|
+
return text
|
|
283
|
+
return f"{text[:limit]}…({len(text) - limit} more)"
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _unsupported_pattern_sentence(pattern: str) -> str:
|
|
287
|
+
return (
|
|
288
|
+
f"the pattern {_clip(repr(pattern))} is not supported: this validator refuses inline "
|
|
289
|
+
"flags, look-behind, backreferences, atomic groups, possessive quantifiers, "
|
|
290
|
+
"conditional groups and \\N{...} escapes"
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
PATTERN_BUDGET_MS = 500
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _pattern_budget_sentence(pattern: str) -> str:
|
|
298
|
+
return (
|
|
299
|
+
f"the pattern {_clip(repr(pattern))} did not finish within the {PATTERN_BUDGET_MS} ms "
|
|
300
|
+
"this validator allows a document's patterns; rewrite it so it cannot backtrack "
|
|
301
|
+
"catastrophically, as (a+)+$ does"
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class _PatternRefusal(Exception):
|
|
306
|
+
"""Raised out of `jsonschema` mid-validation; `schema_error` answers its sentence."""
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
# The search runs in a child interpreter because `re` holds the GIL for the whole of a
|
|
310
|
+
# search: a catastrophic pattern in a worker thread still stops the event loop that answers
|
|
311
|
+
# `ping` (measured: 0 ticks of the main thread during a 372 ms search). A child can be killed;
|
|
312
|
+
# a running `re.search` cannot be interrupted from another thread on any platform.
|
|
313
|
+
_CHILD_SOURCE = """
|
|
314
|
+
import json, re, sys
|
|
315
|
+
for line in sys.stdin.buffer:
|
|
316
|
+
pattern, text = json.loads(line)
|
|
317
|
+
try:
|
|
318
|
+
out = "1" if re.search(pattern, text) else "0"
|
|
319
|
+
except Exception as e:
|
|
320
|
+
out = json.dumps([type(e).__name__, str(e)])
|
|
321
|
+
sys.stdout.write(out + "\\n")
|
|
322
|
+
sys.stdout.flush()
|
|
323
|
+
"""
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class _PatternChild:
|
|
327
|
+
"""One long-lived `python -I -S` that answers `re.search`, restarted after a timeout."""
|
|
328
|
+
|
|
329
|
+
def __init__(self) -> None:
|
|
330
|
+
self._lock = threading.Lock()
|
|
331
|
+
self._proc: subprocess.Popen[bytes] | None = None
|
|
332
|
+
self._lines: queue.Queue[bytes | None] = queue.Queue()
|
|
333
|
+
|
|
334
|
+
def _start(self) -> subprocess.Popen[bytes]:
|
|
335
|
+
proc = subprocess.Popen(
|
|
336
|
+
[sys.executable, "-I", "-S", "-c", _CHILD_SOURCE],
|
|
337
|
+
stdin=subprocess.PIPE,
|
|
338
|
+
stdout=subprocess.PIPE,
|
|
339
|
+
stderr=subprocess.DEVNULL,
|
|
340
|
+
)
|
|
341
|
+
lines: queue.Queue[bytes | None] = queue.Queue()
|
|
342
|
+
|
|
343
|
+
def pump() -> None:
|
|
344
|
+
assert proc.stdout is not None
|
|
345
|
+
for line in proc.stdout:
|
|
346
|
+
lines.put(line)
|
|
347
|
+
lines.put(None)
|
|
348
|
+
|
|
349
|
+
threading.Thread(target=pump, daemon=True).start()
|
|
350
|
+
self._proc, self._lines = proc, lines
|
|
351
|
+
return proc
|
|
352
|
+
|
|
353
|
+
def _stop(self) -> None:
|
|
354
|
+
proc, self._proc = self._proc, None
|
|
355
|
+
if proc is not None:
|
|
356
|
+
proc.kill()
|
|
357
|
+
proc.wait()
|
|
358
|
+
|
|
359
|
+
def search(self, pattern: str, text: str, timeout: float) -> bool | None:
|
|
360
|
+
"""The search's truth, or None when it ran past `timeout` seconds (the child is killed)."""
|
|
361
|
+
with self._lock:
|
|
362
|
+
proc = self._proc if self._proc is not None and self._proc.poll() is None else None
|
|
363
|
+
if proc is None:
|
|
364
|
+
self._stop()
|
|
365
|
+
proc = self._start()
|
|
366
|
+
assert proc.stdin is not None
|
|
367
|
+
proc.stdin.write(json.dumps([pattern, text]).encode("ascii") + b"\n")
|
|
368
|
+
proc.stdin.flush()
|
|
369
|
+
try:
|
|
370
|
+
line = self._lines.get(timeout=timeout)
|
|
371
|
+
except queue.Empty:
|
|
372
|
+
self._stop()
|
|
373
|
+
return None
|
|
374
|
+
if line is None:
|
|
375
|
+
self._stop()
|
|
376
|
+
raise RuntimeError("the pattern child exited without answering")
|
|
377
|
+
answer = line.decode("ascii").strip()
|
|
378
|
+
if answer in ("0", "1"):
|
|
379
|
+
return answer == "1"
|
|
380
|
+
kind, message = json.loads(answer)
|
|
381
|
+
raise (re.error if kind == "error" else getattr(builtins, kind, RuntimeError))(message)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
_PATTERN_CHILD = _PatternChild()
|
|
385
|
+
_PATTERN_CLOCK: contextvars.ContextVar[list[float] | None] = contextvars.ContextVar(
|
|
386
|
+
"_PATTERN_CLOCK", default=None
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
class _BoundedRe:
|
|
391
|
+
"""`jsonschema`'s `re`, with `search` charged to the running validation's clock.
|
|
392
|
+
|
|
393
|
+
Outside `schema_error` (no clock) it is `re` itself. Installed on the three `jsonschema`
|
|
394
|
+
modules that call `re.search` on a schema's pattern, so every keyword that searches —
|
|
395
|
+
`pattern`, `patternProperties`, `additionalProperties`, `unevaluatedProperties` — is
|
|
396
|
+
bounded without re-implementing any of them.
|
|
397
|
+
"""
|
|
398
|
+
|
|
399
|
+
def __getattr__(self, name: str) -> object:
|
|
400
|
+
return getattr(re, name)
|
|
401
|
+
|
|
402
|
+
def search(self, pattern: object, string: object, flags: int = 0) -> object:
|
|
403
|
+
clock = _PATTERN_CLOCK.get()
|
|
404
|
+
if clock is None or flags or not isinstance(pattern, str) or not isinstance(string, str):
|
|
405
|
+
return re.search(pattern, string, flags) # type: ignore[arg-type]
|
|
406
|
+
remaining = PATTERN_BUDGET_MS / 1000 - clock[0]
|
|
407
|
+
if remaining <= 0:
|
|
408
|
+
raise _PatternRefusal(_pattern_budget_sentence(pattern))
|
|
409
|
+
started = time.monotonic()
|
|
410
|
+
try:
|
|
411
|
+
found = _PATTERN_CHILD.search(pattern, string, remaining)
|
|
412
|
+
finally:
|
|
413
|
+
clock[0] += time.monotonic() - started
|
|
414
|
+
if found is None:
|
|
415
|
+
raise _PatternRefusal(_pattern_budget_sentence(pattern))
|
|
416
|
+
return found
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
for _module in ("jsonschema._keywords", "jsonschema._utils", "jsonschema._legacy_keywords"):
|
|
420
|
+
importlib.import_module(_module).re = _BoundedRe() # type: ignore[attr-defined]
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _unsupported_schema(schema: dict) -> str | None:
|
|
424
|
+
"""The one sentence BOTH runtimes answer for a schema the validator will not resolve.
|
|
425
|
+
|
|
426
|
+
`jsonschema` resolves a `$ref` against a base URI and a registry; the Node port resolves
|
|
427
|
+
it inside the one resource a caller schema is (`pyjsonschema.ts`, module header). So
|
|
428
|
+
that the two never disagree, the reference narrows itself to the same rule BEFORE
|
|
429
|
+
`jsonschema.validate` runs, eagerly and in document order: draft 3 anywhere, a reference
|
|
430
|
+
that is not fragment-only, an embedded `$id` (`id` under draft 4) beside any reference.
|
|
431
|
+
Each is answered as `{valid: false, feedback}` rather than raised — a model handed the
|
|
432
|
+
sentence can act on it (#125). A pointer to nowhere or an unknown anchor is found
|
|
433
|
+
lazily, from the `referencing` exceptions, in `schema_error`.
|
|
434
|
+
"""
|
|
435
|
+
# `validator_for` on a non-string `$schema` raises (AttributeError) — both runtimes raise.
|
|
436
|
+
cls = jsonschema.validators.validator_for(schema)
|
|
437
|
+
if cls is jsonschema.Draft3Validator:
|
|
438
|
+
return _draft3_sentence(schema["$schema"])
|
|
439
|
+
id_key = "id" if cls is jsonschema.Draft4Validator else "$id"
|
|
440
|
+
refs: list[tuple[str, str]] = []
|
|
441
|
+
ids: list[str] = []
|
|
442
|
+
for path, sub in _subschemas(schema):
|
|
443
|
+
if path != "":
|
|
444
|
+
if _names_draft3(sub.get("$schema")):
|
|
445
|
+
return _draft3_sentence(sub["$schema"])
|
|
446
|
+
if isinstance(sub.get(id_key), str):
|
|
447
|
+
ids.append(path)
|
|
448
|
+
for k in _REF_KEYWORDS:
|
|
449
|
+
v = sub.get(k)
|
|
450
|
+
if isinstance(v, str):
|
|
451
|
+
refs.append((k, v))
|
|
452
|
+
for k, v in refs:
|
|
453
|
+
# `$recursiveRef`'s value is never read by the reference, so it is never judged.
|
|
454
|
+
if k != "$recursiveRef" and v != "" and not v.startswith("#"):
|
|
455
|
+
return (
|
|
456
|
+
f"the reference {json.dumps(v)} cannot be resolved: only references into this "
|
|
457
|
+
'schema ("#", "#/..." and "#name") are supported'
|
|
458
|
+
)
|
|
459
|
+
if refs and ids:
|
|
460
|
+
return (
|
|
461
|
+
f'the schema declares "{id_key}" at {ids[0]}; references are only resolved in a '
|
|
462
|
+
"schema with a single resource"
|
|
463
|
+
)
|
|
464
|
+
# #131: every `pattern` and `patternProperties` key, in document order.
|
|
465
|
+
for _path, sub in _subschemas(schema):
|
|
466
|
+
candidates = []
|
|
467
|
+
if isinstance(sub.get("pattern"), str):
|
|
468
|
+
candidates.append(sub["pattern"])
|
|
469
|
+
if isinstance(sub.get("patternProperties"), dict):
|
|
470
|
+
candidates.extend(sub["patternProperties"])
|
|
471
|
+
for candidate in candidates:
|
|
472
|
+
if isinstance(candidate, str) and _pattern_unsupported(candidate):
|
|
473
|
+
return _unsupported_pattern_sentence(candidate)
|
|
474
|
+
return None
|
|
475
|
+
|
|
476
|
+
|
|
130
477
|
def schema_error(output: str, schema: dict) -> str | None:
|
|
131
|
-
"""Return a pointed validation error for `output`, or None if it satisfies `schema`.
|
|
478
|
+
"""Return a pointed validation error for `output`, or None if it satisfies `schema`.
|
|
479
|
+
|
|
480
|
+
A schema the validator will not resolve (see `_unsupported_schema`) returns its one
|
|
481
|
+
sentence here too, so `validate_json` answers it instead of raising.
|
|
482
|
+
"""
|
|
132
483
|
try:
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
484
|
+
# #152: a whole-text JSON document of any type first — `"x"`, `1.0` and `null` are
|
|
485
|
+
# JSON texts — and only then the object/array search `extract_json` does.
|
|
486
|
+
data = json.loads(_unfenced(output))
|
|
487
|
+
except ValueError:
|
|
488
|
+
try:
|
|
489
|
+
data = extract_json(output)
|
|
490
|
+
except ValueError as e:
|
|
491
|
+
return parse_error_message(e)
|
|
492
|
+
if isinstance(schema, dict):
|
|
493
|
+
refusal = _unsupported_schema(schema)
|
|
494
|
+
if refusal is not None:
|
|
495
|
+
return refusal
|
|
496
|
+
token = _PATTERN_CLOCK.set([0.0])
|
|
136
497
|
try:
|
|
137
498
|
jsonschema.validate(data, schema)
|
|
499
|
+
except _PatternRefusal as e:
|
|
500
|
+
return str(e)
|
|
138
501
|
except jsonschema.ValidationError as e:
|
|
139
502
|
where = "/".join(str(p) for p in e.absolute_path) or "root"
|
|
140
|
-
return validation_error_message(where, e.message)
|
|
503
|
+
return validation_error_message(_clip(where), _clip_detail(e.message, e.instance))
|
|
504
|
+
except _WrappedReferencingError as e:
|
|
505
|
+
wrapped = e._wrapped
|
|
506
|
+
if isinstance(wrapped, PointerToNowhere):
|
|
507
|
+
ref = json.dumps("#" + wrapped.ref)
|
|
508
|
+
return f"the reference {ref} points at nothing in this schema"
|
|
509
|
+
if isinstance(wrapped, NoSuchAnchor):
|
|
510
|
+
anchor = json.dumps("#" + wrapped.anchor)
|
|
511
|
+
return f"the reference {anchor} names no anchor in this schema"
|
|
512
|
+
raise
|
|
513
|
+
finally:
|
|
514
|
+
_PATTERN_CLOCK.reset(token)
|
|
141
515
|
return None
|
|
142
516
|
|
|
143
517
|
|
|
518
|
+
def _clip_detail(message: str, instance: object) -> str:
|
|
519
|
+
"""#153: the offending value quoted at most 200 codepoints, the sentence at most 1000.
|
|
520
|
+
|
|
521
|
+
Every `repr` of the instance in the message is clipped first, so a `maxLength` failure on a
|
|
522
|
+
1 MiB string still says `is too long`; the whole sentence is then capped, which bounds the
|
|
523
|
+
values a message quotes that are PARTS of the instance (an unexpected property's name).
|
|
524
|
+
"""
|
|
525
|
+
quoted = repr(instance)
|
|
526
|
+
if len(quoted) > 200:
|
|
527
|
+
message = message.replace(quoted, _clip(quoted))
|
|
528
|
+
return _clip(message, 1000)
|
|
529
|
+
|
|
530
|
+
|
|
144
531
|
def render_evidence(messages: list[Message], budget: int = 4096) -> str:
|
|
145
532
|
"""Tool call/observation pairs from a run's transcript, as critic-readable lines."""
|
|
146
533
|
contract = load_contract()
|
|
@@ -196,10 +196,23 @@ class EventLog:
|
|
|
196
196
|
path: Path | None,
|
|
197
197
|
cap_bytes: int = CAP_BYTES,
|
|
198
198
|
clock: Callable[[], int] = _default_clock,
|
|
199
|
+
requires_dir: Path | None = None,
|
|
199
200
|
) -> None:
|
|
200
201
|
self.path = path
|
|
201
202
|
self.cap_bytes = cap_bytes
|
|
202
203
|
self._clock = clock
|
|
204
|
+
#: job65/#154: the directory that must already exist before a record is written.
|
|
205
|
+
#:
|
|
206
|
+
#: `from_env` sets it to the store root when the switch is `on` (the default file
|
|
207
|
+
#: lives INSIDE the store). WAS: the `mkdir` in `_append` brought a never-saved store
|
|
208
|
+
#: into existence -- `--store <absent>` plus one `work_plan` call left
|
|
209
|
+
#: `<store>/events/mcp.jsonl` behind, and the next layered session walking up through
|
|
210
|
+
#: it bound that directory as its store. The log does not create the store: while the
|
|
211
|
+
#: store is not there a record has nowhere to go and is DROPPED -- not redirected, and
|
|
212
|
+
#: not counted as `write_failed`, because nothing was offered to the filesystem. The
|
|
213
|
+
#: first save creates the store; records after it land. A literal path is the
|
|
214
|
+
#: operator's own choice and keeps `requires_dir` None.
|
|
215
|
+
self.requires_dir = requires_dir
|
|
203
216
|
#: Set the first time `record` swallows an `OSError`, and never cleared.
|
|
204
217
|
#:
|
|
205
218
|
#: FAILING TO LOG STILL NEVER FAILS THE TOOL — that contract is unchanged and
|
|
@@ -225,7 +238,13 @@ class EventLog:
|
|
|
225
238
|
clock: Callable[[], int] = _default_clock,
|
|
226
239
|
) -> EventLog:
|
|
227
240
|
source = os.environ if env is None else env
|
|
228
|
-
|
|
241
|
+
raw = source.get(EVENT_LOG_ENV)
|
|
242
|
+
in_store = raw is not None and raw.strip().lower() in _ON
|
|
243
|
+
return cls(
|
|
244
|
+
resolve_path(store_root, raw),
|
|
245
|
+
clock=clock,
|
|
246
|
+
requires_dir=Path(store_root) if in_store else None,
|
|
247
|
+
)
|
|
229
248
|
|
|
230
249
|
@property
|
|
231
250
|
def enabled(self) -> bool:
|
|
@@ -280,6 +299,10 @@ class EventLog:
|
|
|
280
299
|
intent, not a live path.
|
|
281
300
|
"""
|
|
282
301
|
assert self.path is not None
|
|
302
|
+
# job65/#154: `is_dir()` raises PermissionError for EACCES, which lands in `record`'s
|
|
303
|
+
# `except OSError` as a lost record -- the verdict `eventlog.ts` reaches too.
|
|
304
|
+
if self.requires_dir is not None and not self.requires_dir.is_dir():
|
|
305
|
+
return
|
|
283
306
|
# AUDIT FINDING (J51-1): this `mkdir(parents=True)` can bring a whole `.bantamkit`
|
|
284
307
|
# directory into existence on its own -- `BANTAMKIT_EVENT_LOG=on` with a project
|
|
285
308
|
# store that was never saved to reaches here first -- entirely bypassing
|