brainiac-cli 0.20.41__tar.gz → 0.20.43__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.
- {brainiac_cli-0.20.41/src/brainiac_cli.egg-info → brainiac_cli-0.20.43}/PKG-INFO +2 -2
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/pyproject.toml +5 -2
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_version.py +1 -1
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/doctor_checks.py +4 -1
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/doctor_context.py +2 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/doctor_desktop.py +203 -11
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/doctor_plugins.py +68 -12
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/folds/intake.py +4 -4
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/handlers/tables.py +7 -6
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/interview_detect.py +31 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/remediation.py +1 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/update_channels_2.py +28 -20
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/update_plugins.py +4 -3
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43/src/brainiac_cli.egg-info}/PKG-INFO +2 -2
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brainiac_cli.egg-info/requires.txt +1 -1
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/LICENSE +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/MANIFEST.in +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/README.md +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/setup.cfg +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/__init__.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/__main__.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/AGENTS.md +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/assets/graph-explorer-template.html +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/overlay/template/brand/brand-guide.md +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/overlay/template/keywords/glossary.md +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/overlay/template/people/roster.md +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/overlay/template/voice/voice-profile.md +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/routines/manifest.json +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/scripts/brain-brief-mac.plist +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/scripts/brain-brief.sh +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/scripts/brain-synthesis-mac.plist +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/scripts/brain-synthesis.sh +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/scripts/brain_synthesis_codex.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/scripts/brainiac-alerts.sh +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/scripts/brainiac-egress-guard.sh +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/scripts/install-brief-mac.sh +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/scripts/install-brief-windows.ps1 +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/scripts/register_tasks.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/scripts/vm-boundary-probe.sh +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/scripts/vm-selftest.sh +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/templates/company.md +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/templates/concept.md +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/templates/daily.md +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/templates/decision.md +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/templates/deliverable.md +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/templates/meeting.md +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/templates/person.md +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/templates/project.md +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_assets/templates/state-moc.md +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/_optional.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/alerts.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/alerts_render.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/alerts_staging.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/anchor.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/atomic_temp.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/attestation.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/audit.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/audit_chain.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/audit_drift.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/autolink.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/backup.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/brief.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/brief_render.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/capture.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/chunk.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/classification.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/__init__.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/common_parser.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/connect.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/connect_parser.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/eval.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/eval_parser.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/index_storage.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/index_storage_parser.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/ingest_storage.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/ingest_storage_parser.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/lifecycle.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/lifecycle_parser.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/maintenance.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/maintenance_parser.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/navigation.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/navigation_parser.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/owner_workflow.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/owner_workflow_parser.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/presentation.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/presentation_parser.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/retrieval.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/retrieval_parser.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/update.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/update_parser.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/versioning.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_cmds/versioning_parser.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_help.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_read_record.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cli_render.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/config.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/config_hostpaths.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/connect.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/context.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/conventions.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/conventions_scan.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/core/__init__.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/core/_audit.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/core/_briefing.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/core/_capture.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/core/_capture_staging.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/core/_durability.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/core/_health.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/core/_maintenance.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/core/_maintenance_state.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/core/_retrieval.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/core/_shared.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/core/_supersede_batch.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/core/_supersession.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/core/_supersession_journal.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cowork_leak_artifact.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cowork_leak_databases.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cowork_leak_notes.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cowork_leak_scan.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cowork_snapshot_guard.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/cowork_staging.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/dbretry.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/deliverables_absorb.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/deliverables_apply.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/deliverables_ledger.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/deliverables_materialize.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/deliverables_previous.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/deliverables_readme.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/deliverables_shelf.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/deliverables_sync.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/doctor.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/doctor_health.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/doctor_mount_leak.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/doctor_staging.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/doctor_vendor.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/doctor_vm.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/doctor_wiring.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/draft_drain.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/draft_update.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/egress.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/egress_terms.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/embed.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/embed_backends.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/embed_catalog.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/embed_onnx.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/embed_qwen.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/encryption.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/exceptions_cli.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/exceptions_page.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/exceptions_render.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/exceptions_tray.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/exceptions_verify.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/folds/__init__.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/folds/context.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/folds/daily.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/folds/deliverables.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/folds/golden.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/folds/graph.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/folds/invariants.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/folds/orchestrator.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/folds/organization.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/folds/preflight.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/folds/publish.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/folds/remediation.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/folds/remediation_bookkeeping.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/folds/reporting.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/folds/retention.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/folds/watchdogs.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/folds/weekly.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/frontmatter.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/golden_ops.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/golden_probe.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/golden_probe_cases.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/graph.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/graph_hygiene.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/graph_ops.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/graphify.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/graphify_edges.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/graphreport.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/graphreport_sections.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/graphreport_sections_2.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/guard_settings.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/healthreport.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/healthreport_sections.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/healthreport_unsigned.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/inbox.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index/__init__.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index/_connection.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index/_families.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index/_graph.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index/_identity.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index/_lifecycle.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index/_models.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index/_planning.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index/_records.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index/_retirement.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index/_schema.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index/_search.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index/_settings.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index/_shared.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index/_tools.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index/_zones.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index_stages/__init__.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index_stages/connection.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index_stages/diagnostics.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index_stages/duplicate_families.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index_stages/near_duplicates.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index_stages/rebuild.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index_stages/reranking.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index_stages/search.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index_stages/search_finalization.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index_stages/search_identity.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index_stages/search_ranking.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/index_stages/sync.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/__init__.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/deliverables.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/handlers/__init__.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/handlers/base.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/handlers/concealment.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/handlers/concealment_gate.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/handlers/concealment_html.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/handlers/concealment_ledger.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/handlers/concealment_office.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/handlers/concealment_pdf.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/handlers/docx.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/handlers/email.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/handlers/html.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/handlers/image.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/handlers/ole.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/handlers/pdf.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/handlers/pptx.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/handlers/text.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/handlers/xlsx.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/handlers/zip.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/pipeline.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/pipeline_duplicates.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/pipeline_files.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/pipeline_injection.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/pipeline_nested.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/pipeline_run.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/pipeline_stages.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/tierguard.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/tierguard_verdict.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/ingest/transcript.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/init.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/init_import.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/init_samples.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/init_seed.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/init_steps.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/injection_fold.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/injection_scan.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/interview.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/interview_apply.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/invariant_coverage.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/invariant_crosstier.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/invariant_deliverables.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/invariant_floors.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/invariant_ingest_guard.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/invariant_lane_health.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/invariant_reachability.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/invariant_shared.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/invariant_unsigned_notes.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/invariants.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/invariants_metrics.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/language.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/link_targets.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/lock.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/maintenance.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/maintenance_escalation.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/maintenance_folds.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/maintenance_folds_2.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/maintenance_folds_3.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/maintenance_folds_4.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/maintenance_golden.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/maintenance_graphfolds.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/maintenance_healthlog.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/maintenance_hotmd.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/maintenance_invariant_fields.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/maintenance_notify.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/maintenance_outcomes.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/maintenance_para.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/maintenance_recommendations.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/maintenance_retention.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/maintenance_sweep.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/maintenance_watchdog.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/mcp_adapter.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/mcp_capture_verbs.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/mcp_housekeeping_verbs.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/mcp_mediation.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/mcp_supersede_verbs.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/mcp_tools/__init__.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/mcp_tools/capture.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/mcp_tools/core_read.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/mcp_tools/housekeeping.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/mcp_tools/retrieval.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/mcp_tools/supersession.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/mcp_verbs.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/multihop.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/notes.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/originals_resolve.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/overlay.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/overlay_core.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/overlay_generated.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/overlay_keywords.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/overlay_rules.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/pathkey.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/progress.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/projection.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/provenance.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/provision.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/provision_mcp.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/provision_stage.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/provision_wire.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/querylog.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/querylog_digest.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/querylog_replay.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/querylog_status.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/read_log.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/reexec.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/remediation_answers.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/remediation_branches.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/remediation_converged.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/remediation_exceptions.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/remediation_exceptions_store.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/remediation_extract_retry.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/remediation_folds.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/remediation_questions.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/remediation_routing.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/remediation_schema.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/remediation_state.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/rerank.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/rerank_onnx.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/retrieval_ops.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/retro.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/session_hook.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/session_hook_settings.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/session_hook_spec.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/snapshot.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/status_ops.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/supersede_declared.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/sweepdirs.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/update.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/update_channels.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/update_cos_guard.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/update_model.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/update_ops.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/update_state.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/update_venv.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/vectors.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/versionlink.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/versionlink_stages.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/vm_ceiling.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/vmstaging.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brain/workspaces.py +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brainiac_cli.egg-info/SOURCES.txt +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brainiac_cli.egg-info/dependency_links.txt +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brainiac_cli.egg-info/entry_points.txt +0 -0
- {brainiac_cli-0.20.41 → brainiac_cli-0.20.43}/src/brainiac_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: brainiac-cli
|
|
3
|
-
Version: 0.20.
|
|
3
|
+
Version: 0.20.43
|
|
4
4
|
Summary: Brainiac — local any-LLM second-brain core engine + brain CLI (Markdown truth, derived SQLite index).
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Homepage, https://github.com/Autopsias/brainiac
|
|
@@ -60,7 +60,7 @@ Requires-Dist: numpy>=1.24; extra == "eval"
|
|
|
60
60
|
Provides-Extra: quant-tools
|
|
61
61
|
Requires-Dist: onnx>=1.14; extra == "quant-tools"
|
|
62
62
|
Provides-Extra: mcp
|
|
63
|
-
Requires-Dist: mcp<2,>=1.
|
|
63
|
+
Requires-Dist: mcp<2,>=1.30.0; extra == "mcp"
|
|
64
64
|
Provides-Extra: index
|
|
65
65
|
Requires-Dist: regex>=2023.0.0; extra == "index"
|
|
66
66
|
Dynamic: license-file
|
|
@@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta"
|
|
|
8
8
|
# check. The IMPORT package stays `brain` and the console script stays
|
|
9
9
|
# `brain` — only the distribution/project name changes.
|
|
10
10
|
name = "brainiac-cli"
|
|
11
|
-
version = "0.20.
|
|
11
|
+
version = "0.20.43"
|
|
12
12
|
description = "Brainiac — local any-LLM second-brain core engine + brain CLI (Markdown truth, derived SQLite index)."
|
|
13
13
|
readme = "README.md"
|
|
14
14
|
license = "Apache-2.0"
|
|
@@ -122,7 +122,10 @@ quant-tools = ["onnx>=1.14"]
|
|
|
122
122
|
# release pipeline's release-asset handshake gate, after it had already shipped).
|
|
123
123
|
# Upgrade path: port the adapter off FastMCP (mcp.server.lowlevel, or the
|
|
124
124
|
# separate `fastmcp` package) and lift the cap — a real migration, not a bump.
|
|
125
|
-
|
|
125
|
+
# >=1.30.0: CVE-2026-59951 (Streamable HTTP stateful sessions never reclaimed,
|
|
126
|
+
# unbounded memory) is fixed there. The FLOOR, not just the lock, for the same
|
|
127
|
+
# reason as pypdf above: a plain `pip install` reads this, not requirements.lock.
|
|
128
|
+
mcp = ["mcp>=1.30.0,<2"]
|
|
126
129
|
# Timeout-bounded regex engine (mrab-regex) used by BrainIndex.grep's --regex
|
|
127
130
|
# path (RET-04 hardening) as a ReDoS guard: regex.search(text, timeout=...)
|
|
128
131
|
# genuinely bounds catastrophic backtracking, unlike stdlib `re`. Absent ->
|
|
@@ -61,6 +61,7 @@ class DoctorChecks:
|
|
|
61
61
|
mcp_vault_paths: DoctorCheck
|
|
62
62
|
cos_deployed_skill: DoctorCheck
|
|
63
63
|
desktop_plugin_store: DoctorCheck
|
|
64
|
+
mcp_server_code: DoctorCheck
|
|
64
65
|
cowork_vault_dir: DoctorCheck
|
|
65
66
|
quarantine: DoctorCheck
|
|
66
67
|
ingest_capability: DoctorCheck
|
|
@@ -208,7 +209,9 @@ def _terminal_rows(
|
|
|
208
209
|
rows.extend([checks.mcpb_desktop_collision(context.app_support_dir),
|
|
209
210
|
*checks.mcp_vault_paths()])
|
|
210
211
|
rows.extend(row for row in [checks.cos_deployed_skill()] if row)
|
|
211
|
-
rows.extend(checks.desktop_plugin_store(context.app_support_dir, ssot
|
|
212
|
+
rows.extend(checks.desktop_plugin_store(context.app_support_dir, ssot,
|
|
213
|
+
registry_entries=context.registry_entries))
|
|
214
|
+
rows.extend(checks.mcp_server_code())
|
|
212
215
|
from .guard_settings import guard_settings_row as _guard_settings_row
|
|
213
216
|
from .guard_settings import session_state_row as _session_state_row
|
|
214
217
|
from .guard_settings import registry_pin_row as _registry_pin_row
|
|
@@ -131,6 +131,7 @@ def build_context_and_checks(
|
|
|
131
131
|
"""
|
|
132
132
|
from . import config as _config
|
|
133
133
|
from . import doctor as _doctor
|
|
134
|
+
from . import doctor_desktop as _desktop
|
|
134
135
|
from .doctor_checks import DoctorCheckContext, DoctorChecks
|
|
135
136
|
|
|
136
137
|
context = DoctorCheckContext(
|
|
@@ -175,6 +176,7 @@ def build_context_and_checks(
|
|
|
175
176
|
mcp_vault_paths=_doctor.check_mcp_vault_paths,
|
|
176
177
|
cos_deployed_skill=_doctor.check_cos_deployed_skill,
|
|
177
178
|
desktop_plugin_store=_doctor.check_desktop_plugin_store,
|
|
179
|
+
mcp_server_code=_desktop.check_mcp_server_code,
|
|
178
180
|
cowork_vault_dir=_doctor._cowork_vault_dir,
|
|
179
181
|
quarantine=_doctor.check_quarantine,
|
|
180
182
|
ingest_capability=_doctor.check_ingest_capability,
|
|
@@ -11,6 +11,7 @@ keeps working unchanged. Covered by ``tests/test_doctor_workspace.py``.
|
|
|
11
11
|
"""
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
|
+
import re
|
|
14
15
|
from pathlib import Path
|
|
15
16
|
from typing import Optional
|
|
16
17
|
|
|
@@ -124,8 +125,85 @@ def check_mcpb_desktop_collision(
|
|
|
124
125
|
# manual-required, NEVER gates the exit code — ADR-0005 Ruling 2/4).
|
|
125
126
|
# --------------------------------------------------------------------------
|
|
126
127
|
|
|
128
|
+
_STAMP = re.compile(rb"<!-- SKILL_VERSION:[^\n]*-->\n?")
|
|
129
|
+
|
|
130
|
+
# brainiac-manager's skills install/update the HOST and refuse to run in
|
|
131
|
+
# Cowork (brainiac-update is host-only by design), so its absence from the
|
|
132
|
+
# Cowork store is the expected state, not a step the owner owes.
|
|
133
|
+
_OPTIONAL_IN_COWORK = {"brainiac-manager"}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _skill_reference(registry_entries: Optional[list[dict]], ssot: str) -> dict[str, bytes]:
|
|
137
|
+
"""name -> current SKILL.md bytes (version stamp stripped), read from the
|
|
138
|
+
workspaces' staged ``.skill`` bundles at the SSOT version.
|
|
139
|
+
|
|
140
|
+
Cowork's marketplace is a cloud copy that can lag weeks behind GitHub and
|
|
141
|
+
re-label identical skill text with an old version (measured 2026-10-05:
|
|
142
|
+
0.20.39 installed, text identical to 0.20.41). Comparing TEXT against the
|
|
143
|
+
bundles `brain update` just staged is what tells the owner whether to act.
|
|
144
|
+
"""
|
|
145
|
+
import zipfile
|
|
146
|
+
|
|
147
|
+
from .vmstaging import _cowork_staging_root
|
|
148
|
+
|
|
149
|
+
ref: dict[str, bytes] = {}
|
|
150
|
+
for entry in registry_entries or []:
|
|
151
|
+
if entry.get("target") == "host":
|
|
152
|
+
continue
|
|
153
|
+
skills_dir = _cowork_staging_root(entry) / "skills"
|
|
154
|
+
for bundle in sorted(skills_dir.glob("*.skill")) if skills_dir.is_dir() else []:
|
|
155
|
+
if bundle.stem in ref:
|
|
156
|
+
continue
|
|
157
|
+
try:
|
|
158
|
+
with zipfile.ZipFile(bundle) as zf:
|
|
159
|
+
if zf.read(f"{bundle.stem}/VERSION").decode("utf-8").strip() != ssot:
|
|
160
|
+
continue
|
|
161
|
+
ref[bundle.stem] = _STAMP.sub(b"", zf.read(f"{bundle.stem}/SKILL.md"))
|
|
162
|
+
except (OSError, KeyError, UnicodeDecodeError, zipfile.BadZipFile):
|
|
163
|
+
continue
|
|
164
|
+
return ref
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _content_drift(plugin_dir: Path, reference: dict[str, bytes]) -> Optional[tuple[int, list[str]]]:
|
|
168
|
+
"""(skills compared, names whose text differs), or None when nothing in
|
|
169
|
+
the installed plugin has a reference to compare against."""
|
|
170
|
+
skills_root = plugin_dir / "skills"
|
|
171
|
+
if not skills_root.is_dir():
|
|
172
|
+
return None
|
|
173
|
+
names = sorted(p.name for p in skills_root.iterdir()
|
|
174
|
+
if p.name in reference and (p / "SKILL.md").is_file())
|
|
175
|
+
if not names:
|
|
176
|
+
return None
|
|
177
|
+
differs = [n for n in names
|
|
178
|
+
if _STAMP.sub(b"", (skills_root / n / "SKILL.md").read_bytes()) != reference[n]]
|
|
179
|
+
return len(names), differs
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _store_refresh_steps(pname: str, registry_entries: Optional[list[dict]]) -> str:
|
|
183
|
+
from .vmstaging import _cowork_staging_root
|
|
184
|
+
|
|
185
|
+
staged = next((str(_cowork_staging_root(e) / "skills") for e in registry_entries or []
|
|
186
|
+
if e.get("target") != "host"), "<workspace>/vault/.brain/skills")
|
|
187
|
+
return (f"in Cowork: Customize -> Plugins -> refresh the brainiac marketplace, then "
|
|
188
|
+
f"update {pname}; if it still offers older text, uninstall it and upload "
|
|
189
|
+
f"the .skill bundles from {staged}; re-run brain doctor on the host to confirm")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _newest_store_copy(sessions_root: Path, pname: str) -> list[tuple[float, Path]]:
|
|
193
|
+
candidates: list[tuple[float, Path]] = []
|
|
194
|
+
try:
|
|
195
|
+
for pjson in sessions_root.glob("*/*/rpm/plugin_*/.claude-plugin/plugin.json"):
|
|
196
|
+
data = _read_json(pjson)
|
|
197
|
+
if data and data.get("name") == pname:
|
|
198
|
+
candidates.append((pjson.stat().st_mtime, pjson))
|
|
199
|
+
except Exception:
|
|
200
|
+
return []
|
|
201
|
+
return sorted(candidates, key=lambda t: t[0], reverse=True)
|
|
202
|
+
|
|
203
|
+
|
|
127
204
|
def check_desktop_plugin_store(
|
|
128
205
|
app_support_dir: Path, ssot: str, plugin_dir_names: tuple[str, ...] = PLUGIN_NAMES,
|
|
206
|
+
registry_entries: Optional[list[dict]] = None,
|
|
129
207
|
) -> list[dict]:
|
|
130
208
|
"""Best-effort read of
|
|
131
209
|
``.../local-agent-mode-sessions/<uuid>/<uuid>/rpm/plugin_*/.claude-plugin/plugin.json``.
|
|
@@ -137,8 +215,14 @@ def check_desktop_plugin_store(
|
|
|
137
215
|
(not scriptable from here); if multiple candidates tie or none can be
|
|
138
216
|
confidently chosen it reports 'unknown (N candidate sessions)' rather than
|
|
139
217
|
inventing a version.
|
|
218
|
+
|
|
219
|
+
When the registered workspaces carry staged bundles at the SSOT version,
|
|
220
|
+
the verdict is the installed skill TEXT against them, not the label: a
|
|
221
|
+
match reads `current` (never gating either way, ADR-0005 Ruling 2/4).
|
|
222
|
+
Every row that needs the owner sets ``raw["action_needed"]``.
|
|
140
223
|
"""
|
|
141
224
|
sessions_root = app_support_dir / "local-agent-mode-sessions"
|
|
225
|
+
reference = _skill_reference(registry_entries, ssot)
|
|
142
226
|
rows = []
|
|
143
227
|
for pname in plugin_dir_names:
|
|
144
228
|
surface = f"Desktop/Cowork plugin store ({pname})"
|
|
@@ -148,27 +232,30 @@ def check_desktop_plugin_store(
|
|
|
148
232
|
"verify manually in the Cowork/Desktop client",
|
|
149
233
|
remediation="Open Cowork/Desktop -> Plugins -> check for update"))
|
|
150
234
|
continue
|
|
151
|
-
candidates
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
candidates = []
|
|
235
|
+
candidates = _newest_store_copy(sessions_root, pname)
|
|
236
|
+
if not candidates and pname in _OPTIONAL_IN_COWORK:
|
|
237
|
+
rows.append(_row(surface, UNMANAGED,
|
|
238
|
+
"not installed in Cowork — optional: its skills manage the "
|
|
239
|
+
"host install and refuse to run in Cowork; nothing to do",
|
|
240
|
+
raw={"action_needed": False}))
|
|
241
|
+
continue
|
|
159
242
|
if not candidates:
|
|
160
243
|
rows.append(_row(surface, MANUAL_REQUIRED,
|
|
161
244
|
"not found in any session dir — best-effort, "
|
|
162
245
|
"verify manually in the Cowork/Desktop client",
|
|
163
|
-
remediation=
|
|
246
|
+
remediation=_store_refresh_steps(pname, registry_entries),
|
|
247
|
+
raw={"action_needed": True}))
|
|
164
248
|
continue
|
|
165
|
-
candidates.sort(key=lambda t: t[0], reverse=True)
|
|
166
249
|
newest_mtime, newest_path = candidates[0]
|
|
167
250
|
data = _read_json(newest_path) or {}
|
|
168
251
|
version = data.get("version")
|
|
169
252
|
if version is None:
|
|
170
253
|
rows.append(_row(surface, UNKNOWN, f"unknown ({len(candidates)} candidate sessions, unparseable)"))
|
|
171
254
|
continue
|
|
255
|
+
drift = _content_drift(newest_path.parent.parent, reference)
|
|
256
|
+
if drift is not None:
|
|
257
|
+
rows.append(_content_row(surface, pname, str(version), ssot, drift, registry_entries))
|
|
258
|
+
continue
|
|
172
259
|
import datetime
|
|
173
260
|
|
|
174
261
|
mtime_str = datetime.datetime.fromtimestamp(newest_mtime).isoformat(timespec="seconds")
|
|
@@ -204,7 +291,112 @@ def check_desktop_plugin_store(
|
|
|
204
291
|
remediation = "looks current — no action needed"
|
|
205
292
|
rows.append(_row(surface, MANUAL_REQUIRED, detail, remediation=remediation,
|
|
206
293
|
raw={"version": version, "candidates": len(candidates),
|
|
207
|
-
"newest_mtime": mtime_str}))
|
|
294
|
+
"newest_mtime": mtime_str, "action_needed": skew != 0}))
|
|
295
|
+
return rows
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _content_row(surface: str, pname: str, version: str, ssot: str,
|
|
299
|
+
drift: tuple[int, list[str]], registry_entries: Optional[list[dict]]) -> dict:
|
|
300
|
+
checked, differs = drift
|
|
301
|
+
raw = {"version": version, "checked": checked, "differs": differs,
|
|
302
|
+
"action_needed": bool(differs)}
|
|
303
|
+
if not differs:
|
|
304
|
+
return _row(surface, CURRENT,
|
|
305
|
+
f"{checked} skill(s) match the {ssot} text (Cowork labels it {version}; "
|
|
306
|
+
"the label can lag, the text is what runs)", raw=raw)
|
|
307
|
+
return _row(surface, MANUAL_REQUIRED,
|
|
308
|
+
f"{len(differs)} of {checked} skill(s) differ from the {ssot} text "
|
|
309
|
+
f"(Cowork labels it {version}): {', '.join(differs)}",
|
|
310
|
+
remediation=_store_refresh_steps(pname, registry_entries), raw=raw)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
# --------------------------------------------------------------------------
|
|
314
|
+
# Claude Desktop's running brain-mcp servers. A Python process keeps the code
|
|
315
|
+
# it imported at start, so after `brain update` reinstalls the engine every
|
|
316
|
+
# server Desktop already started still serves the OLD engine until Desktop is
|
|
317
|
+
# quit and reopened. Measured 2026-10-04: 8 servers started 22:30, engine
|
|
318
|
+
# reinstalled 23:15, nothing reported it.
|
|
319
|
+
# --------------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
def _ps_rows() -> Optional[list[tuple[int, float, str]]]:
|
|
322
|
+
"""(pid, start epoch, command) for every process, or None where `ps` with
|
|
323
|
+
`lstart` is unavailable (Windows)."""
|
|
324
|
+
import datetime
|
|
325
|
+
import os
|
|
326
|
+
import subprocess
|
|
327
|
+
import sys
|
|
328
|
+
|
|
329
|
+
if sys.platform == "win32":
|
|
330
|
+
return None
|
|
331
|
+
try:
|
|
332
|
+
out = subprocess.run(["ps", "-axo", "pid=,lstart=,command="], capture_output=True,
|
|
333
|
+
text=True, timeout=15, env={**os.environ, "LC_ALL": "C"}).stdout
|
|
334
|
+
except (OSError, subprocess.SubprocessError):
|
|
335
|
+
return None
|
|
336
|
+
rows = []
|
|
337
|
+
for line in out.splitlines():
|
|
338
|
+
parts = line.split(None, 6)
|
|
339
|
+
if len(parts) < 7:
|
|
340
|
+
continue
|
|
341
|
+
try:
|
|
342
|
+
start = datetime.datetime.strptime(" ".join(parts[1:6]), "%a %b %d %H:%M:%S %Y")
|
|
343
|
+
rows.append((int(parts[0]), start.timestamp(), parts[6]))
|
|
344
|
+
except ValueError:
|
|
345
|
+
continue
|
|
346
|
+
return rows
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _engine_install(command: str) -> Optional[tuple[float, str]]:
|
|
350
|
+
"""(install time, version) of the engine a `<venv>/bin/brain-mcp` runs."""
|
|
351
|
+
for stamp in sorted(Path(command).parent.parent.glob("lib/python*/site-packages/brain/_version.py")):
|
|
352
|
+
match = re.search(r'__version__\s*=\s*"([^"]+)"', stamp.read_text(encoding="utf-8"))
|
|
353
|
+
return stamp.stat().st_mtime, (match.group(1) if match else "?")
|
|
354
|
+
return None
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def check_mcp_server_code(config_path: Optional[Path] = None,
|
|
358
|
+
ps_rows: Optional[list[tuple[int, float, str]]] = None) -> list[dict]:
|
|
359
|
+
"""One row per brain-mcp command Desktop launches: are its running
|
|
360
|
+
servers newer than the engine they would load today? Never gating — a
|
|
361
|
+
Desktop restart must not hold `brain update` or CI hostage."""
|
|
362
|
+
import datetime
|
|
363
|
+
|
|
364
|
+
from . import connect as _connect
|
|
365
|
+
|
|
366
|
+
data = _read_json(config_path or _connect.claude_desktop_config_path()) or {}
|
|
367
|
+
commands = sorted({str((e or {}).get("command")) for e in (data.get("mcpServers") or {}).values()
|
|
368
|
+
if "brain-mcp" in str((e or {}).get("command", ""))})
|
|
369
|
+
procs = _ps_rows() if ps_rows is None else ps_rows
|
|
370
|
+
rows = []
|
|
371
|
+
for command in commands:
|
|
372
|
+
surface = f"Claude Desktop Brainiac servers ({command})"
|
|
373
|
+
installed = _engine_install(command)
|
|
374
|
+
if procs is None or installed is None:
|
|
375
|
+
rows.append(_row(surface, NOT_DETECTABLE, "cannot read the running servers or the "
|
|
376
|
+
"engine they load on this platform", raw={"action_needed": False}))
|
|
377
|
+
continue
|
|
378
|
+
at, version = installed
|
|
379
|
+
when = datetime.datetime.fromtimestamp(at).strftime("%Y-%m-%d %H:%M")
|
|
380
|
+
# Desktop runs each server under its `disclaimer --pgroup --` helper,
|
|
381
|
+
# which carries the same command line: count the server, not the
|
|
382
|
+
# wrapper too (it read "16 processes" for 8 servers, 2026-10-11).
|
|
383
|
+
starts = [start for _pid, start, cmd in procs
|
|
384
|
+
if command in cmd and "/Helpers/disclaimer " not in cmd]
|
|
385
|
+
old = [s for s in starts if s < at]
|
|
386
|
+
if not starts:
|
|
387
|
+
rows.append(_row(surface, NOT_DETECTABLE,
|
|
388
|
+
f"none running — Claude Desktop will load {version} when it starts them",
|
|
389
|
+
raw={"action_needed": False}))
|
|
390
|
+
elif old:
|
|
391
|
+
rows.append(_row(surface, MANUAL_REQUIRED,
|
|
392
|
+
f"{len(old)} of {len(starts)} running server(s) started before the "
|
|
393
|
+
f"engine was installed ({version}, {when}) — they still serve the old code",
|
|
394
|
+
remediation="quit Claude Desktop completely (Cmd+Q) and reopen it",
|
|
395
|
+
raw={"action_needed": True, "version": version}))
|
|
396
|
+
else:
|
|
397
|
+
rows.append(_row(surface, CURRENT,
|
|
398
|
+
f"{len(starts)} running server(s), all started after the engine "
|
|
399
|
+
f"install ({version}, {when})", raw={"action_needed": False}))
|
|
208
400
|
return rows
|
|
209
401
|
|
|
210
402
|
|
|
@@ -237,6 +237,44 @@ def check_plugin_manifests(repo_root: Path, ssot: str) -> list[dict]:
|
|
|
237
237
|
# marketplace + installed_plugins.json are both local files, no network).
|
|
238
238
|
# --------------------------------------------------------------------------
|
|
239
239
|
|
|
240
|
+
def _plugin_copies(entries: list, folder_version: Optional[str] = None,
|
|
241
|
+
) -> tuple[list[dict], Optional[str], int]:
|
|
242
|
+
"""Each installed copy of one plugin as (scope, project_path, version),
|
|
243
|
+
plus the last unreadable installPath and the count of orphaned copies.
|
|
244
|
+
|
|
245
|
+
An orphaned copy is a project-scope entry whose project no longer exists
|
|
246
|
+
(a deleted worktree): Claude Code never loads it, and `brain update`
|
|
247
|
+
crashed running `claude plugin update` from the missing directory
|
|
248
|
+
(v0.20.42 deploy, 2026-10-11). It is not a copy to judge or update.
|
|
249
|
+
|
|
250
|
+
``folder_version`` is set when the marketplace is a local folder. Claude
|
|
251
|
+
Code then reads a project copy straight from that folder ("is read from
|
|
252
|
+
its folder: nothing to update") and never rewrites its registry record, so
|
|
253
|
+
the recorded cache path stays at an old version forever: judge the copy
|
|
254
|
+
by the folder it actually loads (false stale rows after 0.20.42)."""
|
|
255
|
+
copies, unreadable, orphaned = [], None, 0
|
|
256
|
+
for entry in entries:
|
|
257
|
+
if not isinstance(entry, dict):
|
|
258
|
+
unreadable = None
|
|
259
|
+
continue
|
|
260
|
+
project = entry.get("projectPath")
|
|
261
|
+
if entry.get("scope") in ("project", "local") and project and not Path(project).is_dir():
|
|
262
|
+
orphaned += 1
|
|
263
|
+
continue
|
|
264
|
+
if folder_version and entry.get("scope") in ("project", "local"):
|
|
265
|
+
copies.append({"scope": entry["scope"], "project_path": project,
|
|
266
|
+
"version": folder_version})
|
|
267
|
+
continue
|
|
268
|
+
install_path = entry.get("installPath")
|
|
269
|
+
pjson = _read_json(Path(install_path) / ".claude-plugin" / "plugin.json") if install_path else None
|
|
270
|
+
if not (pjson and pjson.get("version")):
|
|
271
|
+
unreadable = install_path
|
|
272
|
+
continue
|
|
273
|
+
copies.append({"scope": entry.get("scope") or "user",
|
|
274
|
+
"project_path": project, "version": pjson["version"]})
|
|
275
|
+
return copies, unreadable, orphaned
|
|
276
|
+
|
|
277
|
+
|
|
240
278
|
def check_installed_cli_plugins(
|
|
241
279
|
claude_home: Path, ssot: str, marketplace_name: str = "brainiac",
|
|
242
280
|
marketplace_dir: Optional[Path] = None,
|
|
@@ -246,6 +284,8 @@ def check_installed_cli_plugins(
|
|
|
246
284
|
# installLocation, NOT the hardcoded marketplaces/<name> dir — the caller
|
|
247
285
|
# passes the resolved dir; keep the hardcoded guess only as the fallback.
|
|
248
286
|
marketplace_dir = marketplace_dir or (claude_home / "plugins" / "marketplaces" / marketplace_name)
|
|
287
|
+
known = (_read_json(claude_home / "plugins" / "known_marketplaces.json") or {}).get(marketplace_name)
|
|
288
|
+
from_folder = ((known or {}).get("source") or {}).get("source") == "directory"
|
|
249
289
|
for pname in PLUGIN_NAMES:
|
|
250
290
|
surface = f"Installed CLI plugin ({pname})"
|
|
251
291
|
mkt_json = marketplace_dir / "plugins" / pname / ".claude-plugin" / "plugin.json"
|
|
@@ -258,11 +298,28 @@ def check_installed_cli_plugins(
|
|
|
258
298
|
mkt_version = mkt_data.get("version")
|
|
259
299
|
installed_json = claude_home / "plugins" / "installed_plugins.json"
|
|
260
300
|
installed_data = _read_json(installed_json) or {}
|
|
261
|
-
|
|
301
|
+
all_entries = installed_data.get("plugins") or {}
|
|
302
|
+
plugin_entries = all_entries.get(f"{pname}@{marketplace_name}")
|
|
303
|
+
elsewhere = sorted(k for k in all_entries if k.startswith(f"{pname}@") and all_entries[k])
|
|
304
|
+
if not plugin_entries and elsewhere:
|
|
305
|
+
# Installed from ANOTHER marketplace (e.g. a Desktop upload). That
|
|
306
|
+
# copy is not ours to manage: installing @<marketplace_name> beside
|
|
307
|
+
# it creates a same-name duplicate (measured 2026-10-04).
|
|
308
|
+
first = all_entries[elsewhere[0]]
|
|
309
|
+
first = first[0] if isinstance(first, list) else first
|
|
310
|
+
install_path = first.get("installPath") if isinstance(first, dict) else None
|
|
311
|
+
pjson = _read_json(Path(install_path) / ".claude-plugin" / "plugin.json") if install_path else None
|
|
312
|
+
version = (pjson or {}).get("version") or "unknown version"
|
|
313
|
+
rows.append(_row(surface, NOT_DETECTABLE,
|
|
314
|
+
f"installed as {elsewhere[0]} ({version}), not from {marketplace_name} "
|
|
315
|
+
f"(marketplace has {mkt_version}) — update it where it was installed",
|
|
316
|
+
raw={"installed_elsewhere": elsewhere, "marketplace": mkt_version}))
|
|
317
|
+
continue
|
|
262
318
|
if not plugin_entries:
|
|
263
319
|
rows.append(_row(surface, NOT_DETECTABLE,
|
|
264
320
|
f"not installed (marketplace has {mkt_version})",
|
|
265
|
-
remediation=f"/plugin install {pname}@{marketplace_name}"
|
|
321
|
+
remediation=f"/plugin install {pname}@{marketplace_name}",
|
|
322
|
+
raw={"marketplace": mkt_version}))
|
|
266
323
|
continue
|
|
267
324
|
# installed_plugins.json version field is a cache-dir label, not always
|
|
268
325
|
# semver (it can be a git sha for github-sourced plugins) — read the
|
|
@@ -272,16 +329,13 @@ def check_installed_cli_plugins(
|
|
|
272
329
|
# at its own version. Judge the oldest copy: reading only the first
|
|
273
330
|
# entry reported a stale copy current (or the reverse) by list order.
|
|
274
331
|
entries = plugin_entries if isinstance(plugin_entries, list) else [plugin_entries]
|
|
275
|
-
copies, unreadable =
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
copies.append({"scope": entry.get("scope") or "user",
|
|
283
|
-
"project_path": entry.get("projectPath"),
|
|
284
|
-
"version": pjson["version"]})
|
|
332
|
+
copies, unreadable, orphaned = _plugin_copies(entries, mkt_version if from_folder else None)
|
|
333
|
+
if orphaned and not copies and unreadable is None:
|
|
334
|
+
rows.append(_row(surface, NOT_DETECTABLE,
|
|
335
|
+
f"installed only for {orphaned} deleted project(s) "
|
|
336
|
+
f"(marketplace has {mkt_version})",
|
|
337
|
+
raw={"marketplace": mkt_version, "orphaned_copies": orphaned}))
|
|
338
|
+
continue
|
|
285
339
|
if unreadable is not None or not copies:
|
|
286
340
|
rows.append(_row(surface, UNKNOWN,
|
|
287
341
|
f"installed but version unreadable at {unreadable}"))
|
|
@@ -292,6 +346,8 @@ def check_installed_cli_plugins(
|
|
|
292
346
|
pick = min if stale_copies else max
|
|
293
347
|
installed_version = pick((c["version"] for c in copies), key=cmp_to_key(_compare))
|
|
294
348
|
where = "" if len(copies) == 1 else f" ({len(stale_copies)} of {len(copies)} copies behind)"
|
|
349
|
+
if orphaned:
|
|
350
|
+
where += f" ({orphaned} copy(ies) for deleted projects ignored)"
|
|
295
351
|
cmp_ = _compare(installed_version, mkt_version or "")
|
|
296
352
|
if cmp_ == 0:
|
|
297
353
|
rows.append(_row(surface, CURRENT, f"installed {installed_version} == marketplace {mkt_version}",
|
|
@@ -11,8 +11,7 @@ from .. import _optional, config, maintenance
|
|
|
11
11
|
# ADR 0013: both constants feed only the COS folds, which never run without COS.
|
|
12
12
|
if _optional.cos_available():
|
|
13
13
|
from ..cos.sheet_threads import BODY_NEVER_READABLE as _BODY_NEVER_READABLE
|
|
14
|
-
from ..cos._attachment_gate import
|
|
15
|
-
BODY_ERROR_NIGHTS as _BODY_ERROR_NIGHTS)
|
|
14
|
+
from ..cos._attachment_gate import BODY_ERROR_NIGHTS as _BODY_ERROR_NIGHTS
|
|
16
15
|
else:
|
|
17
16
|
_BODY_NEVER_READABLE = frozenset()
|
|
18
17
|
_BODY_ERROR_NIGHTS = 0
|
|
@@ -115,8 +114,9 @@ class IntakeFoldsMixin:
|
|
|
115
114
|
self._cos_night_stopped(run)
|
|
116
115
|
from ..cos.night_died import night_died_items # noqa: PLC0415
|
|
117
116
|
run.action_required.extend(night_died_items(self.vault, run.results))
|
|
118
|
-
from ..cos
|
|
119
|
-
run.action_required.extend(encrypted_decline_items(self.vault))
|
|
117
|
+
from ..cos import _ingest_claim, calendar_block # noqa: PLC0415
|
|
118
|
+
run.action_required.extend(_ingest_claim.encrypted_decline_items(self.vault))
|
|
119
|
+
run.action_required.extend(calendar_block.read_failed_items(self.vault))
|
|
120
120
|
|
|
121
121
|
def _cos_night_stopped(self, run: MaintenanceRun) -> None:
|
|
122
122
|
"""A NIGHT THAT STOPPED EARLY REACHES `brain alerts` (2026-09-27).
|
|
@@ -51,14 +51,15 @@ def rows_to_markdown(rows: list[list[object]], *, label: str = "table",
|
|
|
51
51
|
rows = [r for r in rows if any(_cell(c) for c in r)] # drop fully-blank rows
|
|
52
52
|
if not rows:
|
|
53
53
|
return ""
|
|
54
|
+
ragged = any(len(r) != len(rows[0]) for r in rows[1:])
|
|
55
|
+
# Drop fully-blank columns too: a sheet's used range can run thousands of
|
|
56
|
+
# empty columns wide, and every row then carries them as "| |" padding.
|
|
57
|
+
width = max(len(r) for r in rows)
|
|
58
|
+
cols = [i for i in range(width) if any(i < len(r) and _cell(r[i]) for r in rows)]
|
|
59
|
+
rows = [[r[i] for i in cols if i < len(r)] for r in rows]
|
|
54
60
|
header = [_cell(c) for c in rows[0]]
|
|
55
61
|
ncols = len(header)
|
|
56
|
-
unparsed = (
|
|
57
|
-
ncols == 0
|
|
58
|
-
or not any(header)
|
|
59
|
-
or len(rows) < 2
|
|
60
|
-
or any(len(r) != ncols for r in rows[1:])
|
|
61
|
-
)
|
|
62
|
+
unparsed = ncols == 0 or not any(header) or len(rows) < 2 or ragged
|
|
62
63
|
if unparsed:
|
|
63
64
|
raw = "\n".join(" | ".join(_cell(c) for c in r) for r in rows)
|
|
64
65
|
keep("chrome", "```table-unparsed")
|
|
@@ -233,6 +233,34 @@ def _link_targets(core: Any, query: str, sid: str,
|
|
|
233
233
|
for _, hid, title in ranked[:limit]]
|
|
234
234
|
|
|
235
235
|
|
|
236
|
+
_SERIES_DATE = re.compile(r"^\d{4}-\d{2}-\d{2}-")
|
|
237
|
+
_SERIES_NUMBER = re.compile(r"-\d+$")
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _series(sid: str) -> str:
|
|
241
|
+
"""`2026-09-14-safety-environment-week-33` -> `safety-environment-week-#`.
|
|
242
|
+
|
|
243
|
+
ONLY A TRAILING NUMBER, so `doc-48367-1-signed` stays its own series: a
|
|
244
|
+
numbered document is not a sibling of another numbered document."""
|
|
245
|
+
return _SERIES_NUMBER.sub("-#", _SERIES_DATE.sub("", sid))
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def noise_series(state: dict[str, Any], min_answers: int = 2) -> set[str]:
|
|
249
|
+
"""Numbered series the owner has answered `noise` on at least twice.
|
|
250
|
+
|
|
251
|
+
Sheet 2026-10-09: ten `noise` answers on weekly HSSEQ reports (weeks 24-32)
|
|
252
|
+
and the lane still asked weeks 33, 34 and 36, because each answer silenced
|
|
253
|
+
one source and a new week arrives every week. Two answers, not one, so a
|
|
254
|
+
single noise answer never hides a sibling the owner has not seen."""
|
|
255
|
+
counts: dict[str, int] = {}
|
|
256
|
+
for r in state["rows"]:
|
|
257
|
+
if (r.get("shape") == "orphan" and r.get("answer") == "noise"
|
|
258
|
+
and _SERIES_NUMBER.search(str((r.get("target") or {}).get("id") or ""))):
|
|
259
|
+
key = _series(str(r["target"]["id"]))
|
|
260
|
+
counts[key] = counts.get(key, 0) + 1
|
|
261
|
+
return {k for k, n in counts.items() if n >= min_answers}
|
|
262
|
+
|
|
263
|
+
|
|
236
264
|
def _orphan_candidates(core: Any, state: dict[str, Any], today: _dt.date,
|
|
237
265
|
per_night: int = 3, max_searches: int = 10):
|
|
238
266
|
"""Sources nothing cites, from the linking lane's own worklist, with the
|
|
@@ -249,12 +277,15 @@ def _orphan_candidates(core: Any, state: dict[str, Any], today: _dt.date,
|
|
|
249
277
|
except (OSError, ValueError):
|
|
250
278
|
return
|
|
251
279
|
yielded = searched = 0
|
|
280
|
+
silenced = noise_series(state)
|
|
252
281
|
for c in lane.get("candidates") or []:
|
|
253
282
|
if yielded >= per_night or searched >= max_searches:
|
|
254
283
|
return
|
|
255
284
|
sid = str(c.get("id") or "")
|
|
256
285
|
if not sid or blocked(state, question_key("orphan", [sid]), sid, today):
|
|
257
286
|
continue
|
|
287
|
+
if _series(sid) in silenced:
|
|
288
|
+
continue
|
|
258
289
|
searched += 1
|
|
259
290
|
title = str(c.get("title") or sid)
|
|
260
291
|
query = (title + " " + note_excerpt(core.vault, str(c.get("path") or ""),
|
|
@@ -369,6 +369,7 @@ _EXACT: dict[str, Remedy] = {
|
|
|
369
369
|
BANNER,
|
|
370
370
|
note="ADR 0013: this vault has COS folders but the installed engine "
|
|
371
371
|
"has no COS code, so the nightly cannot run; never silent"),
|
|
372
|
+
"cos-calendar-read-failed": Remedy(BANNER, note="next night retries; a good read clears it"),
|
|
372
373
|
"exceptions": Remedy(
|
|
373
374
|
BANNER, note="EXC-03: the unified 'N exception(s) — open <page>' "
|
|
374
375
|
"banner, read the SAME way on host and VM from the "
|
|
@@ -68,12 +68,11 @@ def _run_plugin_reinstall(
|
|
|
68
68
|
marketplace = raw.get("marketplace")
|
|
69
69
|
plugin_name = row["surface"].split("(", 1)[1].rstrip(")")
|
|
70
70
|
action = callbacks.decide_plugin_action(installed, marketplace)
|
|
71
|
-
if
|
|
72
|
-
applied = {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
71
|
+
if raw.get("installed_elsewhere"): # another marketplace's copy: never add a duplicate
|
|
72
|
+
applied = {"action": "skip", "ok": True,
|
|
73
|
+
"detail": f"installed as {', '.join(raw['installed_elsewhere'])} — not managed here"}
|
|
74
|
+
elif dry_run:
|
|
75
|
+
applied = {"action": action, "ok": True, "detail": f"[dry-run] would {action}"}
|
|
77
76
|
else:
|
|
78
77
|
applied = callbacks.apply_plugin_action(
|
|
79
78
|
action,
|
|
@@ -211,6 +210,28 @@ def _run_workspace_restage(
|
|
|
211
210
|
)
|
|
212
211
|
|
|
213
212
|
|
|
213
|
+
_DESKTOP_SURFACES = ("Desktop/Cowork plugin store", "Claude Desktop Brainiac servers")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _desktop_residual(after_doctor: dict[str, Any], compare: Any) -> list[str]:
|
|
217
|
+
"""One owner step per Desktop row that needs one. Each row states its own
|
|
218
|
+
verdict in ``raw["action_needed"]`` (the plugin store judges skill TEXT,
|
|
219
|
+
not Cowork's lagging label); a row without it falls back to the label."""
|
|
220
|
+
ssot = after_doctor.get("ssot_version") or "0.0.0"
|
|
221
|
+
steps = []
|
|
222
|
+
for row in after_doctor["rows"]:
|
|
223
|
+
if not row["surface"].startswith(_DESKTOP_SURFACES):
|
|
224
|
+
continue
|
|
225
|
+
raw = row.get("raw") or {}
|
|
226
|
+
need = raw.get("action_needed")
|
|
227
|
+
if need is None:
|
|
228
|
+
need = bool(raw.get("version")) and compare(str(raw["version"]), ssot) < 0
|
|
229
|
+
if need:
|
|
230
|
+
steps.append(f"{row['surface']} is STALE ({row['detail']}) -> "
|
|
231
|
+
f"{row.get('remediation') or 'see brain doctor'}")
|
|
232
|
+
return steps
|
|
233
|
+
|
|
234
|
+
|
|
214
235
|
def _finalize_update(
|
|
215
236
|
result: dict[str, Any],
|
|
216
237
|
before_table: dict[str, str],
|
|
@@ -219,20 +240,7 @@ def _finalize_update(
|
|
|
219
240
|
) -> None:
|
|
220
241
|
"""Build the final before/after table and completion note."""
|
|
221
242
|
result["steps"]["doctor_after"] = callbacks.render_human(after_doctor)
|
|
222
|
-
|
|
223
|
-
desktop_stale = any(
|
|
224
|
-
row["surface"].startswith("Desktop/Cowork plugin store")
|
|
225
|
-
and (row.get("raw") or {}).get("version")
|
|
226
|
-
and callbacks.compare(str((row.get("raw") or {})["version"]), ssot_after) < 0
|
|
227
|
-
for row in after_doctor["rows"]
|
|
228
|
-
)
|
|
229
|
-
if desktop_stale:
|
|
230
|
-
result["residual_human_steps"].append(
|
|
231
|
-
"Desktop/Cowork plugin store is STALE and has no external CLI: in a "
|
|
232
|
-
"Cowork session use /skill-creator to repackage + Save-and-Replace the "
|
|
233
|
-
"stale skill(s) — /brainiac-update is host-only and refuses in Cowork — "
|
|
234
|
-
"then re-run `brain doctor` on the host to confirm it took."
|
|
235
|
-
)
|
|
243
|
+
result["residual_human_steps"].extend(_desktop_residual(after_doctor, callbacks.compare))
|
|
236
244
|
|
|
237
245
|
table: list[dict[str, str]] = []
|
|
238
246
|
surfaces = sorted(set(before_table) | {
|