bantamkit 0.37.1__tar.gz → 0.37.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bantamkit-0.37.1 → bantamkit-0.37.2}/PKG-INFO +3 -3
- {bantamkit-0.37.1 → bantamkit-0.37.2}/README.md +2 -2
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/__init__.py +1 -1
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/memory/component.py +24 -9
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/skillaudit.py +101 -20
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/skilldraft.py +4 -2
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/skillfile.py +64 -17
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/skillinstall.py +168 -12
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/skillpromote.py +62 -3
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/skillpropose.py +287 -1
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/skillreport.py +18 -3
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_skillaudit.py +43 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_skillfile.py +15 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_skillimprove_signals.py +62 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_skillpromote.py +155 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_skillreport.py +26 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/.gitignore +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/contracts/default.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/manifest.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/repo/HISTORY.md +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/repo/README.md +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/repo/docs/architecture.md +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/repo/docs/runbook.md +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/repo/issues/142-settlement-timeout.md +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/repo/patches/0009-retry-budget.patch +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/repo/src/ledger/__init__.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/repo/src/ledger/config.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/repo/src/ledger/errors.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/repo/src/ledger/posting.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/repo/src/ledger/registry.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/repo/src/ledger/report.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/repo/src/ledger/retry.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/repo/src/ledger/settle.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/repo/src/ledger/validate.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/repo/tests/test_posting.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/repo/tests/test_settle.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/tasks/dt-error-contract.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/tasks/dt-handler-map.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/tasks/dt-patch-before-after.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/tasks/dt-retry-attempts.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/tasks/dt-settlement-config.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/tasks/dt-symbol-home.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/tasks/dt-trace-blame.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/devteam/tasks/dt-unread-key.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/document/tasks/doc-large-in-137.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/document/tasks/doc-large-in-359.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/document/tasks/doc-large-in-372.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/document/tasks/doc-large-out-11764.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/document/tasks/doc-large-out-4137.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/document/tasks/doc-large-out-8022.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/document/tasks/doc-small-137.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/document/tasks/doc-small-261.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/document/tasks/doc-small-388.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/fixtures/.gitkeep +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/fixtures/catalog.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/perturbations/task-completion.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/.gitkeep +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/extract-contact.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/extract-invoice.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/extract-order.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/extract-schedule.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/extract-versions.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/nav-prod-port.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/nav-release-bundle.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/recall-audit-retention.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/recall-cache-ttl.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/recall-db-port.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/recall-deploy.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/recall-env-endpoint.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/recall-oncall-rotation.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/recall-oncall.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/recall-org-quota.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/recall-owner.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/shop-basket-total.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/shop-cheapest.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/shop-compare.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/shop-gadget-value.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/shop-stock-total.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/evals/tasks/shop-total.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/pricing/default.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/profiles/default.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/profiles/patient.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/rubrics/.gitkeep +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/rubrics/code-quality.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/rubrics/grounded-completion.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/rubrics/task-completion.yaml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/schemas/shiftwork-checkpoint.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/skills/.gitkeep +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/skills/file-graph.md +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/skills/memory-queries.md +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/skills/memory.md +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/tools/.gitkeep +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/tools/bantamkit_read.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/tools/bantamkit_status.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/tools/build_identity.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/tools/document_list.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/tools/document_read.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/tools/file_graph.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/tools/memory_compact.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/tools/memory_dream.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/tools/memory_queries.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/tools/memory_recall.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/tools/memory_save.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/tools/repo_map.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/tools/shiftwork_clock_in.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/tools/shiftwork_clock_out.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/tools/shiftwork_plan.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/tools/shiftwork_status.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/tools/skill_audit.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/tools/token_ledger.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/tools/validate_json.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/_assets/tools/work_plan.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/hatch_build.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/pyproject.toml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/agent.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/assets.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/budget.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/client.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/contract.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/criticreplay.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/critique.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/docmanifest.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/docread.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/evalrun.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/eventlog.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/filegraph.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/hookadapter.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/hostinstall.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/loopguard.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/mcpreport.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/mcpserver.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/mcpstdio.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/memory/__init__.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/memory/__main__.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/memory/divergence.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/memory/dream.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/memory/layers.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/memory/native.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/memory/queries.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/memory/retrieval.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/memory/skills.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/memory/store.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/memory/usage.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/pdfread.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/pricing.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/profile.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/repomap.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/selfupdate.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/shiftwork.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/skillhost.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/skillmine.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/statusline.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/storage/__init__.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/storage/coldindex.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/storage/hotindex.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/storage/lock.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/storage/logsegment.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/storage/retention.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/storage/tiers.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/structured.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/textutil.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/tokenledger.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/touch/__init__.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/touch/bashturn.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/touch/context.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/touch/graph.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/touch/log.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/touch/recallfile.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/touch/refs.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/touch/repo.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/touch/summary.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/updatecheck.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/src/bantamkit/workplan.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/cli_exit_status_probe.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/conftest.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/data/docread/bad-crc.docx +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/data/docread/charref-4301-digits.html +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/data/docread/charset-table.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/data/docread/compression-method-9.docx +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/data/docread/corrupt-deflate.docx +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/data/docread/encrypted-member.docx +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/data/docread/encrypted-mimetype.odt +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/data/docread/eszett-cell-ref.xlsx +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/data/docread/internal-dtd-entity.docx +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/data/docread/rfc2231-charset.eml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/data/docread/rfc822-nested-twice.eml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/data/docread/unicode-digit-shared-string.xlsx +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/data/docread/x-uuencode.eml +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/data/f8404ab-perturbation-baseline.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/data/platform-assumption-baseline.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/data/served-tool-surface.json +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/docread_fixtures.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/perturbation_baseline_harness.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/rbp16_effect_probe.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/rbp18_payload_probe.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_adapter.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_agent.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_amendguard.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_assets.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_bantamkit_gitignore.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_bantamkit_read_tool.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_budget.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_build_identity.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_cli_oneshot.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_client.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_compaction_corpus_survey.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_conformance.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_conformance_harness_resilience.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_conformance_suite_table_gate.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_contract_fanout.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_contract_patterns.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_contract_refs.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_criticreplay.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_critique.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_doc_commands_gate.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_docread.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_docread_ceilings.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_document_manifest_parity.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_document_setup.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_document_tasks.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_document_tools.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_encoding_gate.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_evalrun.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_eventlog.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_field_program_gates.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_field_programs.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_filegraph.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_hookadapter.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_hostinstall.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_hostinstall_hooks.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_install_shape.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_ladder_statistics.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_launcher_which.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_layers.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_loopguard.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_mcp_endpoint.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_mcp_request_guard.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_mcpdrift.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_mcpreport.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_mcpserver.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_mcpstdio_frames.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_memory.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_memory_compact_tool.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_memory_component.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_memory_divergence.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_memory_dream.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_memory_layers.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_memory_lifecycle.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_memory_native.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_memory_queries.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_memory_retrieval.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_memory_skills.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_memory_store_tripwire.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_memory_strings.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_memory_usage.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_mutmatrix.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_nativeexport.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_newline_gate.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_packaging.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_pdfread.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_pinharness_ledger.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_platform_assumption_gate.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_pricing.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_readme_separation.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_repo_map_tool.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_repomap.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_selfupdate.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_served_tool_count_records.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_shiftwork.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_skillaudit_roots.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_skilldraft.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_skillhost.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_skillimprove.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_skillinstall.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_skillmine.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_skillmine_daily.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_skillpropose.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_status_surface.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_statusline.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_storage_bar.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_storage_coldindex.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_storage_hotindex.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_storage_log.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_storage_log_consumers.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_storage_recall_index.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_storage_retention.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_storage_tiers.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_structured.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_thread_exception_gate.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_tokenledger.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_tool_annotations.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_tool_manifest.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_touch_bashturn.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_touch_context.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_touch_graph.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_touch_log.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_touch_recall_file.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_touch_refs.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_touch_summary.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_unknown_arguments.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_updatecheck.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_version_agreement.py +0 -0
- {bantamkit-0.37.1 → bantamkit-0.37.2}/tests/test_workplan.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bantamkit
|
|
3
|
-
Version: 0.37.
|
|
3
|
+
Version: 0.37.2
|
|
4
4
|
Summary: Persistent memory for coding agents: an MCP server for Claude Code, Claude Desktop, Cursor and VS Code Copilot, plus a Python library that lifts small-model agents. Install once, run offline.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Ink01101011/bantamkit
|
|
6
6
|
Project-URL: Repository, https://github.com/Ink01101011/bantamkit
|
|
@@ -144,9 +144,9 @@ CPU architecture and Python minor version** (some wheels, such as `pydantic_core
|
|
|
144
144
|
one platform only), copy `wheels/` across, and install from it:
|
|
145
145
|
|
|
146
146
|
```bash
|
|
147
|
-
python -m pip download "bantamkit[mcp]==0.37.
|
|
147
|
+
python -m pip download "bantamkit[mcp]==0.37.2" -d wheels
|
|
148
148
|
python -m venv <env>
|
|
149
|
-
<env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.37.
|
|
149
|
+
<env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.37.2"
|
|
150
150
|
<env>/bin/bantamkit-mcp --install cursor
|
|
151
151
|
```
|
|
152
152
|
|
|
@@ -113,9 +113,9 @@ CPU architecture and Python minor version** (some wheels, such as `pydantic_core
|
|
|
113
113
|
one platform only), copy `wheels/` across, and install from it:
|
|
114
114
|
|
|
115
115
|
```bash
|
|
116
|
-
python -m pip download "bantamkit[mcp]==0.37.
|
|
116
|
+
python -m pip download "bantamkit[mcp]==0.37.2" -d wheels
|
|
117
117
|
python -m venv <env>
|
|
118
|
-
<env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.37.
|
|
118
|
+
<env>/bin/pip install --no-index --find-links wheels "bantamkit[mcp]==0.37.2"
|
|
119
119
|
<env>/bin/bantamkit-mcp --install cursor
|
|
120
120
|
```
|
|
121
121
|
|
|
@@ -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.37.
|
|
32
|
+
__version__ = "0.37.2"
|
|
@@ -375,7 +375,8 @@ class Memory:
|
|
|
375
375
|
|
|
376
376
|
`only_present`: bound only when some root is a directory, so a machine with no skill
|
|
377
377
|
has exactly the layers it had before the layer existed. A short-lived hook sees a
|
|
378
|
-
skill the moment it is installed; a long-lived server retries on
|
|
378
|
+
skill the moment it is installed; a long-lived server retries on each public read
|
|
379
|
+
(`_bind_pending_skills`).
|
|
379
380
|
"""
|
|
380
381
|
if not roots:
|
|
381
382
|
return
|
|
@@ -384,6 +385,21 @@ class Memory:
|
|
|
384
385
|
return
|
|
385
386
|
self._layers.append((SKILL_LAYER, SkillStore(roots), False))
|
|
386
387
|
|
|
388
|
+
def _bind_pending_skills(self) -> None:
|
|
389
|
+
"""The `[skill]` layer `_add_skills` held back, bound once a root has appeared.
|
|
390
|
+
|
|
391
|
+
Called on entry to every public reader of `_layers`, never between two reads in one
|
|
392
|
+
call: a layer inserted mid-call would shift the indexes that call already holds.
|
|
393
|
+
"""
|
|
394
|
+
if self._pending_skills and any(r.is_dir() for r in self._pending_skills):
|
|
395
|
+
layer = (SKILL_LAYER, SkillStore(self._pending_skills), False)
|
|
396
|
+
position = next(
|
|
397
|
+
(i for i, (label, _, _) in enumerate(self._layers) if label == NATIVE_LAYER),
|
|
398
|
+
len(self._layers),
|
|
399
|
+
)
|
|
400
|
+
self._layers.insert(position, layer)
|
|
401
|
+
self._pending_skills = []
|
|
402
|
+
|
|
387
403
|
def _add_native(self, native: str | Path | None) -> None:
|
|
388
404
|
"""The host's auto-memory as the last, read-only layer, when a directory was resolved."""
|
|
389
405
|
if native is not None and str(native).strip():
|
|
@@ -673,14 +689,7 @@ class Memory:
|
|
|
673
689
|
"""
|
|
674
690
|
if not 0.0 <= min_ratio <= 1.0:
|
|
675
691
|
raise MemoryValidationError(MIN_RATIO_RANGE)
|
|
676
|
-
|
|
677
|
-
layer = (SKILL_LAYER, SkillStore(self._pending_skills), False)
|
|
678
|
-
position = next(
|
|
679
|
-
(i for i, (label, _, _) in enumerate(self._layers) if label == NATIVE_LAYER),
|
|
680
|
-
len(self._layers),
|
|
681
|
-
)
|
|
682
|
-
self._layers.insert(position, layer)
|
|
683
|
-
self._pending_skills = []
|
|
692
|
+
self._bind_pending_skills()
|
|
684
693
|
budget = self.k if k is None else max(k, self.k)
|
|
685
694
|
name = query.strip()
|
|
686
695
|
lookup: str | None = None
|
|
@@ -739,6 +748,7 @@ class Memory:
|
|
|
739
748
|
|
|
740
749
|
def layer_stores(self) -> list[tuple[str, Any, bool]]:
|
|
741
750
|
"""`(label, store, writable)` per layer, in precedence order (the touch graph's input)."""
|
|
751
|
+
self._bind_pending_skills()
|
|
742
752
|
return list(self._layers)
|
|
743
753
|
|
|
744
754
|
def recall_file(
|
|
@@ -758,6 +768,7 @@ class Memory:
|
|
|
758
768
|
`store.lookup(name, stamp=False)`, and a name no layer holds any more is passed over.
|
|
759
769
|
`status` is `answered` when a fact is shown, else `none`.
|
|
760
770
|
"""
|
|
771
|
+
self._bind_pending_skills()
|
|
761
772
|
rf = _recall_file_mod
|
|
762
773
|
budget = self.k if k is None else max(k, self.k)
|
|
763
774
|
ignored = "" if query is None else f"\n{rf.QUERY_IGNORED}"
|
|
@@ -1054,11 +1065,13 @@ class Memory:
|
|
|
1054
1065
|
|
|
1055
1066
|
def inject_candidates(self, prompt: str, head: str | None = None) -> list[Fact]:
|
|
1056
1067
|
"""What the `UserPromptSubmit` hook would inject for `prompt`, before its byte cap."""
|
|
1068
|
+
self._bind_pending_skills()
|
|
1057
1069
|
got = self._pooled(prompt, 3, RECALL_MIN_SCORE_RATIO, head=head)
|
|
1058
1070
|
return [fact for _, fact in got[0]]
|
|
1059
1071
|
|
|
1060
1072
|
def rank_all(self, prompt: str, depth: int = 50, head: str | None = None) -> list[Fact]:
|
|
1061
1073
|
"""The ranking past the gate, `depth` deep: what recall@3 is measured on."""
|
|
1074
|
+
self._bind_pending_skills()
|
|
1062
1075
|
got = self._pooled(prompt, depth, RECALL_MIN_SCORE_RATIO, head=head)
|
|
1063
1076
|
return [fact for _, fact in got[0]]
|
|
1064
1077
|
|
|
@@ -1110,6 +1123,7 @@ class Memory:
|
|
|
1110
1123
|
name order, each with its body's first `BODY_EXCERPT` code points, plus the
|
|
1111
1124
|
instructions for writing theirs. A name whose fact cannot be read is passed over and
|
|
1112
1125
|
is not counted in `remaining`."""
|
|
1126
|
+
self._bind_pending_skills()
|
|
1113
1127
|
missing = self._missing(self._named_facts())
|
|
1114
1128
|
remaining = len(missing)
|
|
1115
1129
|
facts: list[dict[str, str]] = []
|
|
@@ -1147,6 +1161,7 @@ class Memory:
|
|
|
1147
1161
|
"""`memory_queries {"facts": [...]}` (Q6): every item is checked before anything is
|
|
1148
1162
|
written, and the first bad one refuses the whole call (S5-S8). Then one write per
|
|
1149
1163
|
layer sidecar; one that cannot be written is S9. `queries: []` removes an entry."""
|
|
1164
|
+
self._bind_pending_skills()
|
|
1150
1165
|
labels = [label for label, _, _ in self._layers]
|
|
1151
1166
|
plan: dict[int, dict[str, tuple[list[str], str] | None]] = {}
|
|
1152
1167
|
for i, item in enumerate(facts):
|
|
@@ -328,6 +328,10 @@ class _Skill:
|
|
|
328
328
|
#: `BAD_*` when the frontmatter is malformed, `None` when it is not.
|
|
329
329
|
malformed: str | None = None
|
|
330
330
|
phrases: tuple[str, ...] = field(default_factory=tuple)
|
|
331
|
+
#: Reached through a symlinked directory (#245); `_drop_aliases` reads it.
|
|
332
|
+
linked: bool = False
|
|
333
|
+
#: The real path of the directory holding the file, compared and never reported.
|
|
334
|
+
real: str = ""
|
|
331
335
|
|
|
332
336
|
@property
|
|
333
337
|
def in_plugin(self) -> bool:
|
|
@@ -368,34 +372,77 @@ def _relative_parts(path: Path, root: Path) -> list[str]:
|
|
|
368
372
|
return list(path.relative_to(root).parts)
|
|
369
373
|
|
|
370
374
|
|
|
371
|
-
def _scan_tree(root: Path) -> tuple[list[Path], list[tuple[str, str, str]]]:
|
|
372
|
-
"""One walk,
|
|
375
|
+
def _scan_tree(root: Path) -> tuple[list[Path], list[tuple[str, str, str]], frozenset[Path]]:
|
|
376
|
+
"""One walk, three answers: every `SKILL.md` under `root`, every VERSION DIRECTORY, and
|
|
377
|
+
which of those files were reached THROUGH a symlinked directory.
|
|
373
378
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
therefore the omission `what` lists
|
|
377
|
-
|
|
379
|
+
The walk is depth-first with each directory's entries in code-point order, and `found` is
|
|
380
|
+
sorted by path parts after it, which is the same order. Sorting is what makes the scan
|
|
381
|
+
order — and therefore the omission `what` lists, the duplicate tie-break's "last wins" and
|
|
382
|
+
which of two links to one directory is walked — the same everywhere. A directory that will
|
|
383
|
+
not open is skipped in silence, as `os.walk(onerror=None)` did.
|
|
384
|
+
|
|
385
|
+
SYMLINKED DIRECTORIES ARE FOLLOWED (#245, #234 N4). A skill directory a person linked into
|
|
386
|
+
`~/.claude/skills` is loaded by the host every session, so a walk that stops at the link
|
|
387
|
+
under-reports the catalogue by every such skill. A link is NOT followed when its real path
|
|
388
|
+
is on the chain of real directories above it (a link back to an ancestor: the loop) or is
|
|
389
|
+
a directory an earlier link in this walk already entered (two links to one directory: the
|
|
390
|
+
second is not walked). A dangling link is not a directory and is passed over; only a
|
|
391
|
+
dangling link NAMED `SKILL.md` is listed, and it is then `unreadable`, as before. Each
|
|
392
|
+
file reached through a link is in the third answer, and `_drop_aliases` later drops it
|
|
393
|
+
when its real directory is already counted — so a skill is counted once per real
|
|
394
|
+
directory, however many names reach it.
|
|
378
395
|
|
|
379
396
|
THE SECOND ANSWER IS WHY THIS FUNCTION IS NOT JUST A FILE LIST. A version directory
|
|
380
397
|
declares itself by holding a `skills/` directory — `<marketplace>/<plugin>/<version>/
|
|
381
398
|
skills`, the first four segments of the path shape a skill has — and it declares itself
|
|
382
399
|
whether or not anything under it can be read. Resolving over the files instead makes an
|
|
383
400
|
EMPTY newer version invisible, so an older directory wins in silence; see
|
|
384
|
-
`_resolve_versions`. A directory reached
|
|
385
|
-
|
|
401
|
+
`_resolve_versions`. A directory reached through a symlink declares itself like any other,
|
|
402
|
+
because every skill under it carries that version and the resolution must know it.
|
|
386
403
|
"""
|
|
387
404
|
found: list[Path] = []
|
|
388
405
|
versions: list[tuple[str, str, str]] = []
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
406
|
+
linked: set[Path] = set()
|
|
407
|
+
entered: set[str] = set()
|
|
408
|
+
|
|
409
|
+
def visit(rel: list[str], chain: frozenset[str], via_link: bool) -> None:
|
|
410
|
+
here = root.joinpath(*rel)
|
|
411
|
+
try:
|
|
412
|
+
with os.scandir(here) as it:
|
|
413
|
+
entries = sorted(it, key=lambda e: e.name)
|
|
414
|
+
except OSError:
|
|
415
|
+
return
|
|
416
|
+
if len(rel) == VERSION_PATH_SEGMENTS and rel[3] == SKILLS_SEGMENT:
|
|
417
|
+
versions.append((rel[0], rel[1], rel[2]))
|
|
418
|
+
for entry in entries:
|
|
419
|
+
try:
|
|
420
|
+
directory = entry.is_dir()
|
|
421
|
+
except OSError:
|
|
422
|
+
directory = False
|
|
423
|
+
if not directory:
|
|
424
|
+
if entry.name == SKILL_FILE:
|
|
425
|
+
path = here / SKILL_FILE
|
|
426
|
+
found.append(path)
|
|
427
|
+
if via_link:
|
|
428
|
+
linked.add(path)
|
|
429
|
+
continue
|
|
430
|
+
real = os.path.realpath(here / entry.name)
|
|
431
|
+
if real in chain:
|
|
432
|
+
continue
|
|
433
|
+
# A Windows junction is a link to Node's `isSymbolicLink()`; it is one here too.
|
|
434
|
+
if entry.is_symlink() or getattr(entry, "is_junction", lambda: False)():
|
|
435
|
+
if real in entered:
|
|
436
|
+
continue
|
|
437
|
+
entered.add(real)
|
|
438
|
+
visit([*rel, entry.name], chain | {real}, True)
|
|
439
|
+
else:
|
|
440
|
+
visit([*rel, entry.name], chain | {real}, via_link)
|
|
441
|
+
|
|
442
|
+
visit([], frozenset({os.path.realpath(root)}), False)
|
|
396
443
|
found.sort(key=lambda p: p.parts)
|
|
397
444
|
versions.sort()
|
|
398
|
-
return found, versions
|
|
445
|
+
return found, versions, frozenset(linked)
|
|
399
446
|
|
|
400
447
|
|
|
401
448
|
def _walk(root: Path) -> list[Path]:
|
|
@@ -403,6 +450,38 @@ def _walk(root: Path) -> list[Path]:
|
|
|
403
450
|
return _scan_tree(root)[0]
|
|
404
451
|
|
|
405
452
|
|
|
453
|
+
def _reach(skill: _Skill, path: Path, linked: frozenset[Path]) -> _Skill:
|
|
454
|
+
"""Stamp how the walk reached `path`: through a link or not, and the real directory."""
|
|
455
|
+
skill.linked = path in linked
|
|
456
|
+
skill.real = os.path.realpath(path.parent)
|
|
457
|
+
return skill
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _drop_aliases(found: list[_Skill]) -> list[_Skill]:
|
|
461
|
+
"""One counted skill per REAL directory (#245): a skill reached through a symlink is
|
|
462
|
+
dropped when its real directory is already counted — by a skill reached without a link
|
|
463
|
+
anywhere in the scan, or by an earlier linked one in scan order.
|
|
464
|
+
|
|
465
|
+
It runs after the version resolution and before the name dedupe, over the skills still
|
|
466
|
+
counted, and it removes the alias from the list rather than recording an omission: one
|
|
467
|
+
directory on disk is one skill, not a duplicate of itself. A skill reached WITHOUT a link
|
|
468
|
+
is never dropped here, so two roots that overlap on disk behave exactly as before, and a
|
|
469
|
+
skill #206 promoted by symlink is still counted once, at its source, because bantamkit's
|
|
470
|
+
roots come before the host's. Running after the resolution keeps a version alias (a
|
|
471
|
+
linked version directory that wins) counted rather than dropped in favour of a loser.
|
|
472
|
+
"""
|
|
473
|
+
direct = {s.real for s in found if s.omitted is None and not s.linked}
|
|
474
|
+
seen: set[str] = set()
|
|
475
|
+
out: list[_Skill] = []
|
|
476
|
+
for skill in found:
|
|
477
|
+
if skill.omitted is None and skill.linked:
|
|
478
|
+
if skill.real in direct or skill.real in seen:
|
|
479
|
+
continue
|
|
480
|
+
seen.add(skill.real)
|
|
481
|
+
out.append(skill)
|
|
482
|
+
return out
|
|
483
|
+
|
|
484
|
+
|
|
406
485
|
def _scalar_close(value: str, quote: str) -> int:
|
|
407
486
|
"""Index of the quote that CLOSES a scalar opened at index 0, or `-1` when none does.
|
|
408
487
|
|
|
@@ -719,10 +798,11 @@ def _scan(
|
|
|
719
798
|
right from one that is right for two cancelling reasons.
|
|
720
799
|
"""
|
|
721
800
|
base = Path(root)
|
|
722
|
-
files, version_dirs = _scan_tree(base)
|
|
723
|
-
found = [_load(path, base) for path in files]
|
|
801
|
+
files, version_dirs, linked = _scan_tree(base)
|
|
802
|
+
found = [_reach(_load(path, base), path, linked) for path in files]
|
|
724
803
|
_apply_enabled(found, enabled)
|
|
725
804
|
_resolve_versions(found, version_dirs, versions)
|
|
805
|
+
found = _drop_aliases(found)
|
|
726
806
|
_apply_dedupe(found)
|
|
727
807
|
return found
|
|
728
808
|
|
|
@@ -814,15 +894,16 @@ def scan_roots(
|
|
|
814
894
|
version_dirs: list[tuple[str, str, str]] = []
|
|
815
895
|
for label, root in roots:
|
|
816
896
|
base = Path(root)
|
|
817
|
-
files, dirs = _scan_tree(base)
|
|
897
|
+
files, dirs, linked = _scan_tree(base)
|
|
818
898
|
for path in files:
|
|
819
|
-
skill = _load(path, base)
|
|
899
|
+
skill = _reach(_load(path, base), path, linked)
|
|
820
900
|
skill.relpath = f"{label}/{skill.relpath}"
|
|
821
901
|
found.append(skill)
|
|
822
902
|
version_dirs.extend(dirs)
|
|
823
903
|
version_dirs.sort()
|
|
824
904
|
_apply_enabled(found, enabled)
|
|
825
905
|
_resolve_versions(found, version_dirs, versions)
|
|
906
|
+
found = _drop_aliases(found)
|
|
826
907
|
_apply_dedupe(found)
|
|
827
908
|
return found
|
|
828
909
|
|
|
@@ -497,8 +497,10 @@ def draft(
|
|
|
497
497
|
home: str | Path,
|
|
498
498
|
cwd: str | None,
|
|
499
499
|
now: str,
|
|
500
|
+
own: str | Path | None = None,
|
|
500
501
|
) -> DraftOutcome:
|
|
501
|
-
"""Walk `chain` for one proposals record; log the outcome; never write a skill.
|
|
502
|
+
"""Walk `chain` for one proposals record; log the outcome; never write a skill. `own` is
|
|
503
|
+
passed to the gate (#247: a split's halves are gated with the skill being split as `own`)."""
|
|
502
504
|
b = bundle(cluster)
|
|
503
505
|
system, user = prompt(b)
|
|
504
506
|
unreachable: list[str] = []
|
|
@@ -518,7 +520,7 @@ def draft(
|
|
|
518
520
|
unreachable.append(d.label)
|
|
519
521
|
continue
|
|
520
522
|
text = unfence(answer)
|
|
521
|
-
g = gate(text, b, home, cwd)
|
|
523
|
+
g = gate(text, b, home, cwd, own=own)
|
|
522
524
|
outcome = DraftOutcome(d.label, g.ok, text if g.ok else None, g.reasons, unreachable)
|
|
523
525
|
break
|
|
524
526
|
if outcome is None:
|
|
@@ -306,8 +306,12 @@ def _fsync(fd: int) -> None:
|
|
|
306
306
|
os.fsync(fd)
|
|
307
307
|
|
|
308
308
|
|
|
309
|
+
class _Taken(SkillFileError):
|
|
310
|
+
"""A temp name already held by an entry this call did not make."""
|
|
311
|
+
|
|
312
|
+
|
|
309
313
|
def _taken(path: str) -> SkillFileError:
|
|
310
|
-
return
|
|
314
|
+
return _Taken(f"temp path {path} already exists; refusing to write through it")
|
|
311
315
|
|
|
312
316
|
|
|
313
317
|
def _write(path: str, data: bytes) -> None:
|
|
@@ -344,6 +348,26 @@ def _mkdir_temp(path: str) -> None:
|
|
|
344
348
|
raise _taken(path) from None
|
|
345
349
|
|
|
346
350
|
|
|
351
|
+
# How many temp names one write tries before it refuses. A taken name is passed over, never
|
|
352
|
+
# written through (#250): an orphan a killed writer left under a recycled pid must not block
|
|
353
|
+
# every later writer, so only a run of taken names this long is refused.
|
|
354
|
+
TEMP_TRIES = 8
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def _claim(target: Path | str, kind: str, make: Callable[[str], None]) -> str:
|
|
358
|
+
"""A temp name beside `target` that `make` created exclusively; the last refusal if none."""
|
|
359
|
+
for _ in range(TEMP_TRIES - 1):
|
|
360
|
+
path = _temp(target, kind)
|
|
361
|
+
try:
|
|
362
|
+
make(path)
|
|
363
|
+
except _Taken:
|
|
364
|
+
continue
|
|
365
|
+
return path
|
|
366
|
+
path = _temp(target, kind)
|
|
367
|
+
make(path)
|
|
368
|
+
return path
|
|
369
|
+
|
|
370
|
+
|
|
347
371
|
def _check_rel(rel: str) -> list[str]:
|
|
348
372
|
parts = rel.split("/")
|
|
349
373
|
if (
|
|
@@ -363,8 +387,7 @@ def _build(target: Path, files: dict[str, bytes]) -> str:
|
|
|
363
387
|
for rel in rels:
|
|
364
388
|
_check_rel(rel)
|
|
365
389
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
366
|
-
tmp =
|
|
367
|
-
_mkdir_temp(tmp)
|
|
390
|
+
tmp = _claim(target, "tmp", _mkdir_temp)
|
|
368
391
|
try:
|
|
369
392
|
for rel in rels:
|
|
370
393
|
path = os.path.join(tmp, *rel.split("/"))
|
|
@@ -393,6 +416,23 @@ def write_dir_atomic(target: Path | str, files: dict[str, bytes]) -> None:
|
|
|
393
416
|
raise
|
|
394
417
|
|
|
395
418
|
|
|
419
|
+
def rewrite_dir_atomic(target: Path | str, files: dict[str, bytes]) -> None:
|
|
420
|
+
"""The existing directory `target` replaced by `files` in one rename, no backup (#244).
|
|
421
|
+
|
|
422
|
+
The new tree is built in an exclusively claimed temp dir beside `target`, the old one is
|
|
423
|
+
set aside into another (#250), and on any failure the old directory is back in place.
|
|
424
|
+
"""
|
|
425
|
+
target = Path(target)
|
|
426
|
+
if not target.is_dir() or target.is_symlink():
|
|
427
|
+
raise SkillFileError(f"no skill at {target}")
|
|
428
|
+
tmp = _build(target, files)
|
|
429
|
+
try:
|
|
430
|
+
_swap_in(tmp, target)
|
|
431
|
+
except BaseException:
|
|
432
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
433
|
+
raise
|
|
434
|
+
|
|
435
|
+
|
|
396
436
|
def _copy_tree(src: Path, dst: str) -> None:
|
|
397
437
|
"""Directories and regular files into the existing `dst`, bytes as they are; a symlink is
|
|
398
438
|
not followed or copied."""
|
|
@@ -414,8 +454,7 @@ def _set_aside(target: Path) -> tuple[str, str]:
|
|
|
414
454
|
A rename onto the bare temp name would replace an empty dir planted there (POSIX allows
|
|
415
455
|
it), so the name is claimed by an exclusive mkdir and `target` goes inside it (#250).
|
|
416
456
|
"""
|
|
417
|
-
old =
|
|
418
|
-
_mkdir_temp(old)
|
|
457
|
+
old = _claim(target, "old", _mkdir_temp)
|
|
419
458
|
moved = os.path.join(old, target.name)
|
|
420
459
|
try:
|
|
421
460
|
_rename(str(target), moved)
|
|
@@ -462,8 +501,7 @@ def _new_backup(backup_root: Path | str, name: str, now: str, fill: Callable[[st
|
|
|
462
501
|
while os.path.lexists(final):
|
|
463
502
|
n += 1
|
|
464
503
|
final = home / f"{base}-{n}"
|
|
465
|
-
tmp =
|
|
466
|
-
_mkdir_temp(tmp) # outside the try: a taken name is not ours to remove
|
|
504
|
+
tmp = _claim(final, "tmp", _mkdir_temp) # outside the try: a taken name is not ours to remove
|
|
467
505
|
try:
|
|
468
506
|
fill(tmp)
|
|
469
507
|
_rename(tmp, str(final))
|
|
@@ -541,8 +579,7 @@ def undo(backup_root: Path | str, name: str, target: Path | str) -> str:
|
|
|
541
579
|
shutil.rmtree(backup, ignore_errors=True)
|
|
542
580
|
return f"removed {name}; it did not exist before {entry}"
|
|
543
581
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
544
|
-
tmp =
|
|
545
|
-
_mkdir_temp(tmp) # outside the try: a taken name is not ours to remove
|
|
582
|
+
tmp = _claim(target, "tmp", _mkdir_temp) # outside the try: a taken name is not ours to remove
|
|
546
583
|
try:
|
|
547
584
|
_copy_tree(backup, tmp)
|
|
548
585
|
_swap_in(tmp, target)
|
|
@@ -566,18 +603,28 @@ def history_add(skill_dir: Path | str, version: int) -> None:
|
|
|
566
603
|
_write_file_atomic(dst, data)
|
|
567
604
|
|
|
568
605
|
|
|
606
|
+
def _unlink_quietly(path: str) -> None:
|
|
607
|
+
try:
|
|
608
|
+
os.unlink(path)
|
|
609
|
+
except OSError:
|
|
610
|
+
pass
|
|
611
|
+
|
|
612
|
+
|
|
569
613
|
def _write_file_atomic(path: Path, data: bytes) -> None:
|
|
570
|
-
tmp
|
|
614
|
+
def make(tmp: str) -> None:
|
|
615
|
+
try:
|
|
616
|
+
_write(tmp, data)
|
|
617
|
+
except _Taken:
|
|
618
|
+
raise # what sits at a taken name is not ours to unlink
|
|
619
|
+
except BaseException:
|
|
620
|
+
_unlink_quietly(tmp)
|
|
621
|
+
raise
|
|
622
|
+
|
|
623
|
+
tmp = _claim(path, "tmp", make)
|
|
571
624
|
try:
|
|
572
|
-
_write(tmp, data)
|
|
573
625
|
_rename(tmp, str(path))
|
|
574
|
-
except SkillFileError:
|
|
575
|
-
raise # the temp name was taken: what sits there is not ours to unlink
|
|
576
626
|
except BaseException:
|
|
577
|
-
|
|
578
|
-
os.unlink(tmp)
|
|
579
|
-
except OSError:
|
|
580
|
-
pass
|
|
627
|
+
_unlink_quietly(tmp)
|
|
581
628
|
raise
|
|
582
629
|
|
|
583
630
|
|