brainiac-cli 0.20.47__tar.gz → 0.20.49__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.47/src/brainiac_cli.egg-info → brainiac_cli-0.20.49}/PKG-INFO +4 -3
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/pyproject.toml +20 -8
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/AGENTS.md +12 -5
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brain-synthesis.sh +13 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brain_synthesis_codex.py +16 -3
- brainiac_cli-0.20.49/src/brain/_assets/scripts/shorten_long_note_ids.py +100 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_version.py +1 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/alerts.py +7 -7
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/alerts_staging.py +36 -0
- brainiac_cli-0.20.49/src/brain/atomic_temp.py +49 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/audit.py +7 -3
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/audit_drift.py +2 -5
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/autolink.py +12 -9
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/backup.py +13 -2
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/brief.py +21 -15
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/chunk.py +24 -19
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli.py +4 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/index_storage.py +59 -51
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/lifecycle.py +1 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/lifecycle_parser.py +1 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/owner_workflow.py +17 -14
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_read_record.py +1 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/config.py +0 -7
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/context.py +2 -7
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/conventions_scan.py +20 -12
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/core/_briefing.py +19 -10
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/core/_capture.py +19 -17
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/core/_health.py +1 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/core/_maintenance_state.py +2 -5
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/core/_shared.py +4 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/core/_supersession_journal.py +16 -9
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/deliverables_materialize.py +11 -6
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/deliverables_sync.py +6 -3
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/doctor.py +3 -3
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/doctor_checks.py +1 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/doctor_context.py +9 -2
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/doctor_desktop.py +1 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/doctor_health.py +19 -12
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/doctor_plugins.py +5 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/doctor_staging.py +3 -3
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/doctor_vendor.py +17 -11
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/doctor_vm.py +1 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/doctor_wiring.py +59 -36
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/draft_drain.py +33 -2
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/draft_update.py +3 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/egress.py +0 -11
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/embed.py +4 -4
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/embed_catalog.py +1 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/embed_onnx.py +2 -2
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/embed_qwen.py +1 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/encryption.py +1 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/exceptions_page.py +3 -8
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/exceptions_verify.py +2 -4
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/folds/context.py +3 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/folds/deliverables.py +2 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/folds/invariants.py +4 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/folds/publish.py +3 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/frontmatter.py +1 -1
- brainiac_cli-0.20.49/src/brain/golden_executor.py +107 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/golden_ops.py +59 -26
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/golden_probe_cases.py +15 -10
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/graph_ops.py +3 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/healthreport_sections.py +55 -40
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index/_tools.py +4 -3
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index/_zones.py +25 -17
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index_stages/connection.py +5 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/base.py +76 -31
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/concealment.py +32 -18
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/concealment_html.py +28 -19
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/concealment_office.py +3 -3
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/concealment_pdf.py +15 -8
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/email.py +5 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/html.py +10 -2
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/image.py +8 -1
- brainiac_cli-0.20.49/src/brain/ingest/handlers/ocr_retry.py +35 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/pdf.py +45 -6
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/pptx.py +8 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/xlsx.py +19 -4
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/zip.py +12 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/pipeline_files.py +3 -7
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/pipeline_stages.py +16 -14
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/tierguard.py +10 -4
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/init_seed.py +0 -50
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/injection_scan.py +32 -3
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/invariant_coverage.py +25 -15
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/invariant_deliverables.py +4 -4
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/invariant_reachability.py +12 -8
- brainiac_cli-0.20.49/src/brain/invariant_shared.py +32 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/invariants.py +6 -26
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/maintenance_folds.py +7 -7
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/maintenance_folds_3.py +2 -5
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/maintenance_folds_4.py +3 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/maintenance_hotmd.py +18 -10
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/maintenance_notify.py +73 -50
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/maintenance_retention.py +38 -32
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/mcp_housekeeping_verbs.py +1 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/notes.py +45 -40
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/overlay.py +38 -27
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/overlay_generated.py +6 -7
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/provision.py +1 -3
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/querylog.py +4 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/querylog_digest.py +2 -2
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/querylog_status.py +19 -10
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/read_log.py +42 -33
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/reexec.py +1 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/remediation.py +15 -90
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/remediation_exceptions_store.py +2 -4
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/remediation_extract_retry.py +15 -10
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/remediation_folds.py +5 -1
- brainiac_cli-0.20.49/src/brain/remediation_invariants.py +93 -0
- brainiac_cli-0.20.49/src/brain/remediation_security.py +21 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/remediation_state.py +2 -4
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/rerank.py +3 -3
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/rerank_onnx.py +3 -3
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/session_hook_settings.py +2 -4
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/snapshot.py +5 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/update.py +10 -2
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/update_channels.py +44 -36
- brainiac_cli-0.20.49/src/brain/update_engine_only.py +48 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/update_ops.py +2 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/update_state.py +5 -4
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/update_venv.py +3 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/vectors.py +3 -3
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/vm_ceiling.py +2 -1
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/vmstaging.py +0 -61
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/workspaces.py +2 -4
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49/src/brainiac_cli.egg-info}/PKG-INFO +4 -3
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brainiac_cli.egg-info/SOURCES.txt +6 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brainiac_cli.egg-info/requires.txt +3 -2
- brainiac_cli-0.20.47/src/brain/atomic_temp.py +0 -6
- brainiac_cli-0.20.47/src/brain/invariant_shared.py +0 -4
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/LICENSE +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/MANIFEST.in +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/README.md +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/setup.cfg +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/__init__.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/__main__.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/assets/graph-explorer-template.html +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/overlay/template/brand/brand-guide.md +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/overlay/template/keywords/glossary.md +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/overlay/template/people/roster.md +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/overlay/template/voice/voice-profile.md +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/routines/manifest.json +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brain-brief-mac.plist +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brain-brief.sh +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brain-synthesis-mac.plist +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brainiac-alerts.sh +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brainiac-egress-guard.sh +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/install-brief-mac.sh +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/install-brief-windows.ps1 +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/register_tasks.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/vm-boundary-probe.sh +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/vm-selftest.sh +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/templates/company.md +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/templates/concept.md +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/templates/daily.md +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/templates/decision.md +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/templates/deliverable.md +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/templates/meeting.md +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/templates/person.md +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/templates/project.md +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/templates/state-moc.md +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_optional.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/alerts_render.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/anchor.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/attestation.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/audit_chain.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/brief_render.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/capture.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/classification.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/__init__.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/common_parser.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/connect.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/connect_parser.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/eval.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/eval_parser.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/index_storage_parser.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/ingest_storage.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/ingest_storage_parser.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/maintenance.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/maintenance_parser.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/navigation.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/navigation_parser.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/owner_workflow_parser.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/presentation.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/presentation_parser.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/retrieval.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/retrieval_parser.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/update.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/update_parser.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/versioning.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_cmds/versioning_parser.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_help.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cli_render.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/config_hostpaths.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/connect.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/conventions.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/core/__init__.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/core/_audit.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/core/_capture_staging.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/core/_durability.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/core/_maintenance.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/core/_retrieval.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/core/_supersede_batch.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/core/_supersession.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cowork_leak_artifact.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cowork_leak_databases.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cowork_leak_notes.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cowork_leak_scan.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cowork_snapshot_guard.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/cowork_staging.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/dbretry.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/deliverables_absorb.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/deliverables_apply.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/deliverables_ledger.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/deliverables_previous.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/deliverables_readme.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/deliverables_shelf.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/doctor_mount_leak.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/egress_terms.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/embed_backends.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/exceptions_cli.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/exceptions_render.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/exceptions_tray.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/folds/__init__.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/folds/daily.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/folds/golden.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/folds/graph.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/folds/intake.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/folds/orchestrator.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/folds/organization.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/folds/preflight.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/folds/remediation.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/folds/remediation_bookkeeping.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/folds/reporting.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/folds/retention.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/folds/watchdogs.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/folds/weekly.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/golden_probe.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/graph.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/graph_hygiene.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/graphify.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/graphify_edges.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/graphreport.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/graphreport_sections.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/graphreport_sections_2.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/guard_settings.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/healthreport.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/healthreport_unsigned.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/inbox.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index/__init__.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index/_connection.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index/_families.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index/_graph.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index/_identity.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index/_lifecycle.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index/_models.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index/_planning.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index/_records.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index/_retirement.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index/_schema.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index/_search.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index/_settings.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index/_shared.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index_stages/__init__.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index_stages/diagnostics.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index_stages/duplicate_families.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index_stages/near_duplicates.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index_stages/rebuild.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index_stages/reranking.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index_stages/search.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index_stages/search_finalization.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index_stages/search_identity.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index_stages/search_ranking.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/index_stages/sync.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/__init__.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/deliverables.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/__init__.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/concealment_gate.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/concealment_ledger.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/docx.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/ole.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/tables.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/text.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/pipeline.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/pipeline_duplicates.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/pipeline_injection.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/pipeline_nested.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/pipeline_run.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/tierguard_verdict.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/ingest/transcript.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/init.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/init_import.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/init_samples.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/init_steps.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/injection_fold.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/interview.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/interview_apply.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/interview_detect.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/invariant_crosstier.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/invariant_floors.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/invariant_ingest_guard.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/invariant_lane_health.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/invariant_unsigned_notes.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/invariants_metrics.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/language.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/link_targets.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/lock.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/maintenance.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/maintenance_escalation.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/maintenance_folds_2.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/maintenance_golden.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/maintenance_graphfolds.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/maintenance_healthlog.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/maintenance_invariant_fields.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/maintenance_outcomes.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/maintenance_para.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/maintenance_recommendations.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/maintenance_sweep.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/maintenance_watchdog.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/mcp_adapter.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/mcp_capture_verbs.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/mcp_mediation.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/mcp_supersede_verbs.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/mcp_tools/__init__.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/mcp_tools/capture.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/mcp_tools/core_read.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/mcp_tools/housekeeping.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/mcp_tools/retrieval.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/mcp_tools/supersession.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/mcp_verbs.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/model_slot.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/multihop.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/originals_resolve.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/overlay_core.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/overlay_keywords.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/overlay_rules.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/pathkey.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/progress.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/projection.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/provenance.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/provision_mcp.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/provision_stage.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/provision_wire.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/querylog_replay.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/remediation_answers.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/remediation_branches.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/remediation_converged.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/remediation_exceptions.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/remediation_questions.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/remediation_routing.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/remediation_schema.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/retrieval_ops.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/retro.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/session_hook.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/session_hook_spec.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/status_ops.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/supersede_declared.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/sweepdirs.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/update_channels_2.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/update_cos_guard.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/update_model.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/update_plugins.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/versionlink.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/versionlink_stages.py +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brainiac_cli.egg-info/dependency_links.txt +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brainiac_cli.egg-info/entry_points.txt +0 -0
- {brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/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.49
|
|
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
|
|
@@ -19,7 +19,7 @@ Requires-Dist: cryptography>=49
|
|
|
19
19
|
Requires-Dist: PyYAML>=6
|
|
20
20
|
Requires-Dist: regex>=2023.0.0
|
|
21
21
|
Requires-Dist: keyring>=24; sys_platform == "win32"
|
|
22
|
-
Requires-Dist: pypdf>=6.21.
|
|
22
|
+
Requires-Dist: pypdf>=6.21.4
|
|
23
23
|
Requires-Dist: python-docx>=1.1
|
|
24
24
|
Requires-Dist: python-pptx>=0.6.23
|
|
25
25
|
Requires-Dist: openpyxl>=3.1
|
|
@@ -44,7 +44,7 @@ Requires-Dist: PyYAML>=6; extra == "corporate"
|
|
|
44
44
|
Provides-Extra: yaml
|
|
45
45
|
Requires-Dist: PyYAML>=6; extra == "yaml"
|
|
46
46
|
Provides-Extra: ingest
|
|
47
|
-
Requires-Dist: pypdf>=6.21.
|
|
47
|
+
Requires-Dist: pypdf>=6.21.4; extra == "ingest"
|
|
48
48
|
Requires-Dist: python-docx>=1.1; extra == "ingest"
|
|
49
49
|
Requires-Dist: python-pptx>=0.6.23; extra == "ingest"
|
|
50
50
|
Requires-Dist: openpyxl>=3.1; extra == "ingest"
|
|
@@ -63,6 +63,7 @@ Provides-Extra: quant-tools
|
|
|
63
63
|
Requires-Dist: onnx>=1.14; extra == "quant-tools"
|
|
64
64
|
Provides-Extra: mcp
|
|
65
65
|
Requires-Dist: mcp<2,>=1.30.0; extra == "mcp"
|
|
66
|
+
Requires-Dist: PyJWT>=2.15.0; extra == "mcp"
|
|
66
67
|
Provides-Extra: index
|
|
67
68
|
Requires-Dist: regex>=2023.0.0; extra == "index"
|
|
68
69
|
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.49"
|
|
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"
|
|
@@ -42,7 +42,7 @@ dependencies = [
|
|
|
42
42
|
# because requirements.lock is CI/audit-only -- a plain
|
|
43
43
|
# `pip install brainiac-cli` reads THIS constraint, and >=4.0 let the
|
|
44
44
|
# vulnerable 6.14.2 survive an upgrade to 0.20.5 on a real host.
|
|
45
|
-
"pypdf>=6.21.
|
|
45
|
+
"pypdf>=6.21.4",
|
|
46
46
|
"python-docx>=1.1",
|
|
47
47
|
"python-pptx>=0.6.23",
|
|
48
48
|
"openpyxl>=3.1",
|
|
@@ -97,7 +97,7 @@ yaml = ["PyYAML>=6"]
|
|
|
97
97
|
# message hints valid even though a default install already has these. Absent,
|
|
98
98
|
# `brain ingest` degrades per-handler (reports which extensions are
|
|
99
99
|
# unavailable) rather than crashing.
|
|
100
|
-
ingest = ["pypdf>=6.21.
|
|
100
|
+
ingest = ["pypdf>=6.21.4", "python-docx>=1.1", "python-pptx>=0.6.23", "openpyxl>=3.1", "Pillow>=13.0.0; python_version >= '3.11'", "Pillow>=12.3.0; python_version < '3.11'"]
|
|
101
101
|
# pytest-xdist/-timeout back the documented parallel suite run (AGENTS.md §8,
|
|
102
102
|
# "Running the test suite"): ~15 min sequential -> ~6 min at `-n 8`. Both are
|
|
103
103
|
# pytest-dev org, actively maintained. Declared here so a fresh checkout can
|
|
@@ -128,7 +128,9 @@ quant-tools = ["onnx>=1.14"]
|
|
|
128
128
|
# >=1.30.0: CVE-2026-59951 (Streamable HTTP stateful sessions never reclaimed,
|
|
129
129
|
# unbounded memory) is fixed there. The FLOOR, not just the lock, for the same
|
|
130
130
|
# reason as pypdf above: a plain `pip install` reads this, not requirements.lock.
|
|
131
|
-
|
|
131
|
+
# PyJWT>=2.15.0: the floor mcp pulls in, stated here so a plain install
|
|
132
|
+
# cannot resolve an older PyJWT (public PR #36).
|
|
133
|
+
mcp = ["mcp>=1.30.0,<2", "PyJWT>=2.15.0"]
|
|
132
134
|
# Timeout-bounded regex engine (mrab-regex) used by BrainIndex.grep's --regex
|
|
133
135
|
# path (RET-04 hardening) as a ReDoS guard: regex.search(text, timeout=...)
|
|
134
136
|
# genuinely bounds catastrophic backtracking, unlike stdlib `re`. Absent ->
|
|
@@ -183,7 +185,7 @@ known_first_party = ["brain", "workspace_registry", "framework_sync", "qwen3_emb
|
|
|
183
185
|
# package_module_name_map: the 4 real import-name mismatches (distribution
|
|
184
186
|
# name vs. the name actually imported).
|
|
185
187
|
package_module_name_map = { PyYAML = "yaml", "python-docx" = "docx", "python-pptx" = "pptx", Pillow = "PIL" }
|
|
186
|
-
per_rule_ignores = { DEP001 = ["llama_cpp", "lxml"], DEP002 = ["pytest", "pytest-xdist", "pytest-timeout", "ruff", "build", "onnx", "ranx"] }
|
|
188
|
+
per_rule_ignores = { DEP001 = ["llama_cpp", "lxml"], DEP002 = ["pytest", "pytest-xdist", "pytest-timeout", "ruff", "build", "onnx", "ranx", "PyJWT"] }
|
|
187
189
|
# DEP001 llama_cpp/lxml: both are optional accelerants guarded by
|
|
188
190
|
# try/except ImportError (src/brain/context.py, src/brain/ingest/handlers/html.py)
|
|
189
191
|
# — the code degrades gracefully absent either, same pattern as the existing
|
|
@@ -195,12 +197,15 @@ per_rule_ignores = { DEP001 = ["llama_cpp", "lxml"], DEP002 = ["pytest", "pytest
|
|
|
195
197
|
# src/brain package. ranx is a real [eval] runtime import, but its only
|
|
196
198
|
# import site is eval/, which extend_exclude drops above for the DEP001
|
|
197
199
|
# noise reason — excluding it flips ranx to a false DEP002.
|
|
200
|
+
# DEP002 PyJWT: a deliberate floor pin for mcp (see the mcp extra above,
|
|
201
|
+
# public PR #36); never imported by our own code.
|
|
198
202
|
|
|
199
203
|
[tool.claude-quality]
|
|
200
204
|
# Trees the size gates skip: frozen plan/archive/evidence dirs and the
|
|
201
205
|
# generated _assets mirror of tools/ + scripts/ (re-synced by
|
|
202
206
|
# tools/package_clients.py). Not authored source.
|
|
203
|
-
|
|
207
|
+
# eval/ holds frozen, one-off experiment scripts, not shipped source (owner choice 2026-11-03).
|
|
208
|
+
exclude = ["_plans", "_archive", "_evidence", "_assets", "eval"]
|
|
204
209
|
|
|
205
210
|
[tool.ruff]
|
|
206
211
|
# CORRECTNESS ONLY — this is not a style gate. The repo already has three
|
|
@@ -257,6 +262,9 @@ select = ["F", "E9", "E722"]
|
|
|
257
262
|
# exists to express that.
|
|
258
263
|
ignore = ["F403", "F405"]
|
|
259
264
|
|
|
265
|
+
[tool.ruff.lint.mccabe]
|
|
266
|
+
max-complexity = 12 # matches the complexity ratchet limit (tools/check_complexity.py, limit=12)
|
|
267
|
+
|
|
260
268
|
[tool.ruff.lint.per-file-ignores]
|
|
261
269
|
# F811 cannot see pytest. A module-level `@pytest.fixture def text()` and a test
|
|
262
270
|
# that takes `text` as a parameter is the framework's core idiom, and this rule
|
|
@@ -264,6 +272,10 @@ ignore = ["F403", "F405"]
|
|
|
264
272
|
# real F811s all live outside tests/.
|
|
265
273
|
"tests/**" = ["F811"]
|
|
266
274
|
|
|
275
|
+
# eval/ is out of the size and complexity gates (see [tool.claude-quality]); the
|
|
276
|
+
# correctness lint still runs on it.
|
|
277
|
+
"eval/**" = ["C901"]
|
|
278
|
+
|
|
267
279
|
|
|
268
280
|
[tool.pytest.ini_options]
|
|
269
281
|
# Scope collection to tests/ so pytest never descends into a local model venv
|
|
@@ -302,9 +314,9 @@ exclude = ["**/_plans", "**/_archive", "**/_evidence", "**/_assets"]
|
|
|
302
314
|
|
|
303
315
|
[tool.uv]
|
|
304
316
|
# Wait 7 days before resolving a newly published version (a new release can be
|
|
305
|
-
# malicious or broken). shortcut: pypdf 6.21.
|
|
317
|
+
# malicious or broken). shortcut: pypdf 6.21.4 (fixes 7 DoS advisories, 2026-10-31), filelock 4.2.4 and certifi
|
|
306
318
|
# 2026.10.23 are security fixes under 7 days old on 2026-10-24, so they are
|
|
307
|
-
# exempt;
|
|
319
|
+
# exempt; re-check the exemptions after 2026-11-08.
|
|
308
320
|
exclude-newer = "7 days"
|
|
309
321
|
exclude-newer-package = { pypdf = false, filelock = false, certifi = false }
|
|
310
322
|
# fastembed (the optional `embed` extra, not installed by default) caps
|
|
@@ -449,7 +449,7 @@ gates.
|
|
|
449
449
|
signal (`emitter_output`, 2026-08-20).** The paragraph above is right and
|
|
450
450
|
is not weakened: `proposal`, `report`, `analysis` and `review` stay OUT of
|
|
451
451
|
`OPERATIONAL_SOURCE_TYPES` because a human writes those about the
|
|
452
|
-
business. The gap it leaves is narrower — when
|
|
452
|
+
business. The gap it leaves is narrower — when a log-rotation script (not tracked here) emits
|
|
453
453
|
`type: proposal` once a day, type says "keep" and nothing else disagrees.
|
|
454
454
|
The reference vault carried **22** daily `Log rotation proposal` files
|
|
455
455
|
from one script, each counted as a source awaiting a note. So the second
|
|
@@ -1028,6 +1028,12 @@ rules, in order:
|
|
|
1028
1028
|
env-exported `$BRAIN_VM_MAX_EGRESS_TIER` is IGNORED (the session's own
|
|
1029
1029
|
shell can set it; VULN-3386). Raising a VM's ceiling is a host-operator
|
|
1030
1030
|
action (`brain vm-egress-tier <TIER>`, signed), never the model's own.
|
|
1031
|
+
That clamp binds the VM's own `brain --role vm` CLI only. A Cowork session
|
|
1032
|
+
that reads through the host broker (`brain-mcp`, the Desktop MCP entry)
|
|
1033
|
+
gets the BROKER's ceiling — the full vault unless `$BRAIN_MAX_EGRESS_TIER`
|
|
1034
|
+
narrows that entry — because the broker receives no caller role (owner
|
|
1035
|
+
ruling, `docs/security-acceptances.md` A-01; pentest VULN-3419). When
|
|
1036
|
+
asked for your cap, report the broker's, never the VM file's.
|
|
1031
1037
|
|
|
1032
1038
|
3. **Ask it in every language the vault holds — the VARIANT CONTRACT
|
|
1033
1039
|
(CON-01) — and paraphrase within your own.** The cross-language half is a
|
|
@@ -1850,8 +1856,9 @@ entry formats in `docs/session-memory.md`. Rules an agent needs at a glance:
|
|
|
1850
1856
|
count match the host's; there is nothing enforcing that a session actually
|
|
1851
1857
|
does it. When `N > 0`, do not tell the owner to go run `/brain-inbox` —
|
|
1852
1858
|
open the exceptions page yourself and walk them through what is open using
|
|
1853
|
-
`AskUserQuestion`, one at a time. The
|
|
1854
|
-
(`~/.brainiac/update-state.json`, `~/.brain/synthesis-state.json
|
|
1859
|
+
`AskUserQuestion`, one at a time. The three host-home sources
|
|
1860
|
+
(`~/.brainiac/update-state.json`, `~/.brain/synthesis-state.json`,
|
|
1861
|
+
`~/.brain/retrieval-watch-state.json`) stay out
|
|
1855
1862
|
of the VM's reach and are listed under `unreachable` rather than skipped.
|
|
1856
1863
|
Claude Code and Codex both fire this from a SessionStart hook; run it first
|
|
1857
1864
|
regardless of harness.
|
|
@@ -1928,8 +1935,8 @@ summary is the one authority for open owner questions on the VM leg now.
|
|
|
1928
1935
|
That is the whole point of the first bullet above — until 2026-08-14 this
|
|
1929
1936
|
logic lived only in a Claude Code hook, so a Cowork session worked for days
|
|
1930
1937
|
against a vault whose invariants had regressed with no surface that could
|
|
1931
|
-
tell it. Only the
|
|
1932
|
-
`~/.brain/synthesis-state.json`) are out of the VM's reach, and `alerts`
|
|
1938
|
+
tell it. Only the three HOST-HOME sources (`~/.brainiac/update-state.json`,
|
|
1939
|
+
`~/.brain/synthesis-state.json`, `~/.brain/retrieval-watch-state.json`) are out of the VM's reach, and `alerts`
|
|
1933
1940
|
reports those under `unreachable` rather than skipping them. The reads are
|
|
1934
1941
|
file-reads of counts and two findings files; the VM still never WRITES
|
|
1935
1942
|
anything here,
|
|
@@ -82,7 +82,9 @@ note and will be rejected; cite the source in the BODY as [[<bare-id>]], never
|
|
|
82
82
|
edge, so it inflates the metric while linking nothing); put the [[raw/<id>]]
|
|
83
83
|
form in the note's `source:` frontmatter as usual; set the note's
|
|
84
84
|
classification to at least the HIGHEST classification among the sources it
|
|
85
|
-
cites, read live off each source;
|
|
85
|
+
cites, read live off each source; give each new note a SHORT kebab-case
|
|
86
|
+
id of at most 80 characters, the same as its file name (a sentence-long id
|
|
87
|
+
is refused at signing and the note stays unsigned); and NEVER append these links into an
|
|
86
88
|
existing hand-curated note — always a new note. Work the list in order; in
|
|
87
89
|
your final summary state HOW MANY of the file's candidates you covered (the
|
|
88
90
|
file's own "consumed_today" field is the fold's count — quote yours beside
|
|
@@ -335,6 +337,16 @@ doctor: $DOCTOR_DIAG"
|
|
|
335
337
|
find "$VAULT/brain" -type f -name '*.md' \
|
|
336
338
|
! -name 'backlinks.md' ! -name 'catalog.md' \
|
|
337
339
|
-newer "$SIGN_MARKER" > "$SIGN_LIST" 2>/dev/null || true
|
|
340
|
+
# A sentence-long note name is refused by the drain's safe-id check
|
|
341
|
+
# (2026-10-25: 20 of 45 notes unsigned, rc=75). Give those a short id
|
|
342
|
+
# first; anything it cannot shorten safely is left for the drain to refuse.
|
|
343
|
+
SHORT_LIST="$(mktemp -t brain-synthesis-shortlist)"
|
|
344
|
+
if python3 "$(dirname "$0")/shorten_long_note_ids.py" "$SIGN_LIST" \
|
|
345
|
+
> "$SHORT_LIST" 2>>"$LOG"; then
|
|
346
|
+
mv -f -- "$SHORT_LIST" "$SIGN_LIST"
|
|
347
|
+
else
|
|
348
|
+
rm -f -- "$SHORT_LIST"
|
|
349
|
+
fi
|
|
338
350
|
SIGNED=0; SIGN_FAIL=0
|
|
339
351
|
# fd 3, not stdin: `brain write` reads the note body FROM STDIN, so a
|
|
340
352
|
# plain `while read` loop would feed it the file list instead.
|
{brainiac_cli-0.20.47 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brain_synthesis_codex.py
RENAMED
|
@@ -58,7 +58,11 @@ existing-note link suggestions, or missing watchdogs in action_required rather
|
|
|
58
58
|
than changing existing notes. This runner never edits or retires existing notes.
|
|
59
59
|
Do not create a duplicate of a supplied existing note. If there is no honest
|
|
60
60
|
new synthesis, return notes=[] and explain why in summary/action_required.
|
|
61
|
+
Maintenance status="ok" with a recent last_run is success; last_success and rc
|
|
62
|
+
are optional fields on many folds. Missing optional fields are not failures.
|
|
61
63
|
Note bodies must be 120–8000 characters; titles 1–200 characters.
|
|
64
|
+
Use each distinct set of source_ids for at most one note. Combine related
|
|
65
|
+
claims from the same source set rather than repeating that set in several notes.
|
|
62
66
|
|
|
63
67
|
UNTRUSTED DATA PACKET:
|
|
64
68
|
{packet}
|
|
@@ -113,8 +117,10 @@ def make_packet(args, core):
|
|
|
113
117
|
lane = link_lane_candidates(core.index.conn, args.vault, limit=100000)
|
|
114
118
|
gate = classification.ClassificationFilter(args.max_tier)
|
|
115
119
|
eligible = [x for x in lane["candidates"] if gate.allows(x.get("classification"))]
|
|
120
|
+
offset = getattr(args, "source_offset", 0) % max(len(eligible), 1)
|
|
121
|
+
ordered = eligible[offset:] + eligible[:offset]
|
|
116
122
|
sources, remaining = [], args.packet_chars
|
|
117
|
-
for row in
|
|
123
|
+
for row in ordered:
|
|
118
124
|
if len(sources) >= args.source_budget or remaining < 120:
|
|
119
125
|
break
|
|
120
126
|
note = get_gated(args, row["id"])
|
|
@@ -145,7 +151,7 @@ def make_packet(args, core):
|
|
|
145
151
|
except (OSError, ValueError):
|
|
146
152
|
state = {}
|
|
147
153
|
# Diagnostics expose cadence/status, not arbitrary strings from host stores.
|
|
148
|
-
diagnostics = {key: {k: value.get(k) for k in ("last_run", "last_success", "date", "rc")}
|
|
154
|
+
diagnostics = {key: {k: value.get(k) for k in ("last_run", "last_success", "date", "rc", "status", "failed", "consecutive_failures")}
|
|
149
155
|
for key, value in state.items() if isinstance(value, dict)}
|
|
150
156
|
return {"sources": sources, "knowledge_context": knowledge,
|
|
151
157
|
"diagnostics": diagnostics,
|
|
@@ -322,6 +328,8 @@ def parser():
|
|
|
322
328
|
p.add_argument("--model", default=None)
|
|
323
329
|
p.add_argument("--max-tier", choices=classification.TIERS, default="Internal")
|
|
324
330
|
p.add_argument("--source-budget", type=int, default=40)
|
|
331
|
+
p.add_argument("--source-offset", type=int, default=0,
|
|
332
|
+
help="Rotate the canonical eligible lane for an operator-run backfill; no exclusions")
|
|
325
333
|
p.add_argument("--note-budget", type=int, default=8)
|
|
326
334
|
p.add_argument("--packet-chars", type=int, default=120000)
|
|
327
335
|
p.add_argument("--timeout", type=int, default=1200)
|
|
@@ -337,7 +345,7 @@ def parser():
|
|
|
337
345
|
|
|
338
346
|
|
|
339
347
|
def validate_args(args):
|
|
340
|
-
if not (1 <= args.source_budget <= 40 and 1 <= args.note_budget <= 8
|
|
348
|
+
if not (0 <= args.source_offset <= 100000 and 1 <= args.source_budget <= 40 and 1 <= args.note_budget <= 8
|
|
341
349
|
and 1000 <= args.packet_chars <= 120000 and 30 <= args.timeout <= 1800):
|
|
342
350
|
raise SystemExit("Bounds refused")
|
|
343
351
|
for binary in (args.brain_bin, args.codex_bin):
|
|
@@ -441,6 +449,11 @@ def main():
|
|
|
441
449
|
# A dry-run/smoke test must never earn the production watchdog heartbeat.
|
|
442
450
|
if not args.smoke_test and not args.dry_run:
|
|
443
451
|
update_state(args, outcome)
|
|
452
|
+
# Numbers only: summary/action_required/error carry vault-derived text and
|
|
453
|
+
# stay in the owner-only run_dir, never in the launchd log this lands in.
|
|
454
|
+
print(json.dumps({"rc": outcome["rc"], "run_dir": outcome["run_dir"],
|
|
455
|
+
"created": len(outcome.get("created") or []),
|
|
456
|
+
**{k: outcome.get(k) for k in ("tokens", "duration_s", "est_cost_usd")}}))
|
|
444
457
|
return outcome["rc"]
|
|
445
458
|
|
|
446
459
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Give a too-long note a short id before the synthesis sign-drain signs it.
|
|
2
|
+
|
|
3
|
+
2026-10-25: the confined session named 20 of its 45 notes after a whole
|
|
4
|
+
sentence (up to 208 characters). `notes.safe_slug` refuses an id over
|
|
5
|
+
MAX_SLUG_BYTES, so all 20 stayed unsigned and the run failed rc=75.
|
|
6
|
+
|
|
7
|
+
Reads the drain's file list (one path per line), and for each note whose
|
|
8
|
+
file stem is over the limit AND equals its frontmatter `id:`, moves it to
|
|
9
|
+
`<first words>-<8 hex of the old id>.md` and rewrites that one `id:` line.
|
|
10
|
+
Anything else is left exactly as it was, so the drain still refuses it.
|
|
11
|
+
Then rewrites `[[old-id]]` links to `[[new-id]]` in every listed note: the
|
|
12
|
+
same session wrote the notes that link to these, and on 2026-10-26 a rename
|
|
13
|
+
without this left 18 links pointing at files that no longer existed.
|
|
14
|
+
Prints the list back with the new paths. Stdlib only: the script runs it
|
|
15
|
+
with the host's `python3`, outside the engine's venv.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import hashlib
|
|
20
|
+
import re
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
#: Same number as `brain.notes.MAX_SLUG_BYTES` (a test holds them equal).
|
|
25
|
+
MAX_SLUG_BYTES = 180
|
|
26
|
+
#: Short enough to read, long enough to keep the subject.
|
|
27
|
+
KEEP_BYTES = 80
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def short_id(old: str) -> str:
|
|
31
|
+
head = old.encode("utf-8")[:KEEP_BYTES].decode("utf-8", "ignore")
|
|
32
|
+
if "-" in head:
|
|
33
|
+
head = head.rsplit("-", 1)[0]
|
|
34
|
+
return f"{head.rstrip('-.')}-{hashlib.sha256(old.encode('utf-8')).hexdigest()[:8]}"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def shorten(path: Path) -> Path:
|
|
38
|
+
old = path.stem
|
|
39
|
+
if len(old.encode("utf-8")) <= MAX_SLUG_BYTES:
|
|
40
|
+
return path
|
|
41
|
+
lines = path.read_text(encoding="utf-8").split("\n")
|
|
42
|
+
if not lines or lines[0].strip() != "---":
|
|
43
|
+
return path
|
|
44
|
+
for i, line in enumerate(lines[1:], start=1):
|
|
45
|
+
if line.strip() == "---":
|
|
46
|
+
return path # no `id:` line: leave it to the drain
|
|
47
|
+
if line.startswith("id:"):
|
|
48
|
+
if line[3:].strip().strip("\"'") != old:
|
|
49
|
+
return path # the id is not the file name: leave it
|
|
50
|
+
new = short_id(old)
|
|
51
|
+
target = path.with_name(f"{new}.md")
|
|
52
|
+
if target.exists():
|
|
53
|
+
return path
|
|
54
|
+
lines[i] = f"id: {new}"
|
|
55
|
+
target.write_text("\n".join(lines), encoding="utf-8")
|
|
56
|
+
path.unlink()
|
|
57
|
+
return target
|
|
58
|
+
return path
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def relink(paths: list[str], renames: dict[str, str]) -> None:
|
|
62
|
+
"""Point `[[old]]`, `[[old|..]]` and `[[old#..]]` at the new id."""
|
|
63
|
+
if not renames:
|
|
64
|
+
return
|
|
65
|
+
pattern = re.compile(r"\[\[(" + "|".join(map(re.escape, renames)) + r")(?=[\]|#])")
|
|
66
|
+
for raw in paths:
|
|
67
|
+
path = Path(raw)
|
|
68
|
+
try:
|
|
69
|
+
text = path.read_text(encoding="utf-8")
|
|
70
|
+
except (OSError, UnicodeDecodeError):
|
|
71
|
+
continue
|
|
72
|
+
new = pattern.sub(lambda m: "[[" + renames[m.group(1)], text)
|
|
73
|
+
if new != text:
|
|
74
|
+
path.write_text(new, encoding="utf-8")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def main(argv: list[str]) -> int:
|
|
78
|
+
# "\n" only, as the shell's `read` splits: splitlines() also breaks on
|
|
79
|
+
# \r, \x1c, \u2028 ..., so a crafted file name could add a list entry.
|
|
80
|
+
with open(argv[1], encoding="utf-8", newline="") as fh: # no \r translation
|
|
81
|
+
entries = fh.read().split("\n")
|
|
82
|
+
out: list[str] = []
|
|
83
|
+
renames: dict[str, str] = {}
|
|
84
|
+
for raw in entries:
|
|
85
|
+
if raw:
|
|
86
|
+
try:
|
|
87
|
+
new = shorten(Path(raw))
|
|
88
|
+
except (OSError, UnicodeDecodeError):
|
|
89
|
+
new = Path(raw)
|
|
90
|
+
if str(new) != raw:
|
|
91
|
+
renames[Path(raw).stem] = new.stem
|
|
92
|
+
out.append(str(new))
|
|
93
|
+
relink(out, renames)
|
|
94
|
+
for line in out:
|
|
95
|
+
print(line)
|
|
96
|
+
return 0
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
if __name__ == "__main__":
|
|
100
|
+
raise SystemExit(main(sys.argv))
|
|
@@ -12,7 +12,7 @@ identical list.
|
|
|
12
12
|
|
|
13
13
|
Two design rules, both learned from earlier failures in this repo:
|
|
14
14
|
|
|
15
|
-
* **Silence must never be able to mean "could not look."**
|
|
15
|
+
* **Silence must never be able to mean "could not look."** Some of the
|
|
16
16
|
inputs live in the HOST home (``~/.brainiac``, ``~/.brain``) and the Cowork
|
|
17
17
|
VM cannot reach them. On ``role=vm`` they are reported in ``unreachable``
|
|
18
18
|
rather than quietly contributing nothing — the same posture ``doctor`` takes
|
|
@@ -42,7 +42,7 @@ from .alerts_staging import ( # noqa: F401 — re-exported for callers/tests
|
|
|
42
42
|
_untriaged,
|
|
43
43
|
_untriaged_key,
|
|
44
44
|
_unwrap_untriaged,
|
|
45
|
-
host_vaults,
|
|
45
|
+
host_vaults, retrieval_watch_alerts, RETRIEVAL_WATCH_UNREACHABLE,
|
|
46
46
|
staging_alerts,
|
|
47
47
|
)
|
|
48
48
|
|
|
@@ -79,7 +79,7 @@ SYNTHESIS_STALE_DAYS = 8
|
|
|
79
79
|
STUCK_DRAFT_HOURS = 48
|
|
80
80
|
|
|
81
81
|
# The feed's `findings[].key` vocabulary is closed (see maintenance_notify.py:
|
|
82
|
-
# "blocked", "synthesis-watchdog", "trend:<metric>", "invariant:<metric>",
|
|
82
|
+
# "blocked", "synthesis-watchdog", "trend:<metric>", "invariant:<metric>", # noqa: ERA001
|
|
83
83
|
# "branch-escalate:<branch>", "ingest_quarantine*", ...). The dir is on the
|
|
84
84
|
# VirtioFS mount the Cowork VM can write, so a key failing this shape must
|
|
85
85
|
# never reach the rendered alert text — it is the one thing standing between
|
|
@@ -91,7 +91,7 @@ def _read_json(path: Path) -> Any:
|
|
|
91
91
|
try:
|
|
92
92
|
with open(path, encoding="utf-8") as fh:
|
|
93
93
|
return json.load(fh)
|
|
94
|
-
except Exception:
|
|
94
|
+
except Exception: # fail-open: missing or corrupt feed file reads as no data
|
|
95
95
|
return None
|
|
96
96
|
|
|
97
97
|
|
|
@@ -443,7 +443,7 @@ def collect(
|
|
|
443
443
|
"""Every alert this role can see, plus what it could not look at.
|
|
444
444
|
|
|
445
445
|
``host`` sweeps the workspace registry; ``vm`` reads only its own vault and
|
|
446
|
-
reports the
|
|
446
|
+
reports the host-home sources as unreachable."""
|
|
447
447
|
home = home or Path.home()
|
|
448
448
|
today = today or datetime.date.today()
|
|
449
449
|
alerts: list[dict[str, str]] = []
|
|
@@ -454,7 +454,7 @@ def collect(
|
|
|
454
454
|
"auto-update state (~/.brainiac/update-state.json) — host-only, "
|
|
455
455
|
"run `brain alerts` on the host Mac",
|
|
456
456
|
"weekly synthesis task health (~/.brain/synthesis-state.json) — "
|
|
457
|
-
"host-only, run `brain alerts` on the host Mac",
|
|
457
|
+
"host-only, run `brain alerts` on the host Mac", RETRIEVAL_WATCH_UNREACHABLE,
|
|
458
458
|
]
|
|
459
459
|
vaults = [Path(vault)] if vault else []
|
|
460
460
|
if not vaults:
|
|
@@ -467,7 +467,7 @@ def collect(
|
|
|
467
467
|
else:
|
|
468
468
|
alerts += update_alerts(home, today)
|
|
469
469
|
alerts += staging_alerts(home)
|
|
470
|
-
alerts += synthesis_alerts(home, today)
|
|
470
|
+
alerts += synthesis_alerts(home, today) + retrieval_watch_alerts(home, today)
|
|
471
471
|
alerts += wiring_alerts()
|
|
472
472
|
vaults = host_vaults(home)
|
|
473
473
|
if not vaults and vault:
|
|
@@ -186,3 +186,39 @@ def host_vaults(home: Path) -> list[Path]:
|
|
|
186
186
|
if path not in seen:
|
|
187
187
|
seen.append(path)
|
|
188
188
|
return seen
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
#: The monthly retrieval watch fires on day 1; past this its silence is the news.
|
|
192
|
+
RETRIEVAL_WATCH_STALE_DAYS = 35
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def retrieval_watch_alerts(home: Path, today: Any) -> list[dict[str, str]]:
|
|
196
|
+
"""The monthly retrieval-quality watch (``~/.brain/retrieval-watch-state.json``,
|
|
197
|
+
written by ``scripts/brain-retrieval-watch.sh``). Until 2026-10-27 a FAIL
|
|
198
|
+
went only to ``hot.md``, a log nobody has to read, and it failed unread on
|
|
199
|
+
three months running against a baseline from a retired embedder."""
|
|
200
|
+
from .alerts import _alert, _date, _read_json
|
|
201
|
+
|
|
202
|
+
state = _read_json(home / ".brain" / "retrieval-watch-state.json")
|
|
203
|
+
if not isinstance(state, dict):
|
|
204
|
+
return [] # never installed on this host — nothing to watch
|
|
205
|
+
day = _date(state.get("at"))
|
|
206
|
+
if state.get("verdict") == "FAIL":
|
|
207
|
+
return [_alert(
|
|
208
|
+
"retrieval-watch:fail",
|
|
209
|
+
f"monthly retrieval check FAILED on {state.get('at')} — run /autoresearch, "
|
|
210
|
+
"or re-baseline if the engine or embedder changed")]
|
|
211
|
+
if state.get("verdict") == "ERROR":
|
|
212
|
+
return [_alert(
|
|
213
|
+
"retrieval-watch:error",
|
|
214
|
+
f"monthly retrieval check could not run on {state.get('at')} — "
|
|
215
|
+
"read ~/.brain/logs/launchd-retrievalwatch-stderr.log")]
|
|
216
|
+
if day is None or (today - day).days > RETRIEVAL_WATCH_STALE_DAYS:
|
|
217
|
+
return [_alert(
|
|
218
|
+
"retrieval-watch:stale",
|
|
219
|
+
f"monthly retrieval check has not run since {state.get('at') or 'never'}")]
|
|
220
|
+
return []
|
|
221
|
+
|
|
222
|
+
RETRIEVAL_WATCH_UNREACHABLE = (
|
|
223
|
+
"monthly retrieval check (~/.brain/retrieval-watch-state.json) — host-only, "
|
|
224
|
+
"run `brain alerts` on the host Mac")
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Durable-write temp-name helper and the one write-then-replace helper."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import errno
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from .lock import _atomic_temp_path
|
|
11
|
+
|
|
12
|
+
atomic_temp_path = _atomic_temp_path
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _fsync_dir(d: Path) -> None:
|
|
16
|
+
"""fsync a directory so a rename in it survives a crash; unsupported is fine."""
|
|
17
|
+
if os.name == "nt":
|
|
18
|
+
return # no directory descriptors on Windows
|
|
19
|
+
fd = os.open(d, os.O_RDONLY)
|
|
20
|
+
try:
|
|
21
|
+
os.fsync(fd)
|
|
22
|
+
except OSError as exc:
|
|
23
|
+
if exc.errno not in (errno.EINVAL, errno.ENOTSUP, errno.EBADF):
|
|
24
|
+
raise
|
|
25
|
+
finally:
|
|
26
|
+
os.close(fd)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def write_text_atomic(path: Path, text: str) -> None:
|
|
30
|
+
"""Write ``text`` to a sibling temp file, fsync, then ``os.replace`` it in.
|
|
31
|
+
|
|
32
|
+
The temp file is removed on any error, so the target keeps its old content.
|
|
33
|
+
"""
|
|
34
|
+
tmp = atomic_temp_path(path)
|
|
35
|
+
try:
|
|
36
|
+
with open(tmp, "w", encoding="utf-8") as fh:
|
|
37
|
+
fh.write(text)
|
|
38
|
+
fh.flush()
|
|
39
|
+
os.fsync(fh.fileno())
|
|
40
|
+
os.replace(tmp, path)
|
|
41
|
+
except BaseException:
|
|
42
|
+
tmp.unlink(missing_ok=True)
|
|
43
|
+
raise
|
|
44
|
+
_fsync_dir(path.parent) # after the replace: the target must not be unlinked on failure
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def write_json_atomic(path: Path, obj: Any) -> None:
|
|
48
|
+
"""``write_text_atomic`` of ``json.dumps(obj, indent=2)`` plus a newline."""
|
|
49
|
+
write_text_atomic(path, json.dumps(obj, indent=2) + "\n")
|
|
@@ -18,6 +18,7 @@ Resolution precedence (first hit wins, all yield identical PEM bytes):
|
|
|
18
18
|
"""
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
|
+
import logging
|
|
21
22
|
import base64 as base64
|
|
22
23
|
import contextlib
|
|
23
24
|
import hashlib
|
|
@@ -31,6 +32,8 @@ from datetime import datetime as datetime, timezone as timezone
|
|
|
31
32
|
from pathlib import Path
|
|
32
33
|
from typing import Iterator, Optional
|
|
33
34
|
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
34
37
|
JSONL_FORMAT = "brain-audit-chain-jsonl-v1"
|
|
35
38
|
NULL_PREV_HASH = "0" * 64
|
|
36
39
|
_REQUIRED_KEYS = frozenset({"format", "ts", "verb", "path", "reason", "prev_hash", "sig"})
|
|
@@ -137,7 +140,7 @@ def _pem_from_credential_manager(service: str, account: str) -> Optional[bytes]:
|
|
|
137
140
|
|
|
138
141
|
secret = keyring.get_password(service, account)
|
|
139
142
|
return secret.encode("utf-8") if secret else None
|
|
140
|
-
except Exception:
|
|
143
|
+
except Exception: # fail-open: keyring is optional; None means no key from this source
|
|
141
144
|
return None
|
|
142
145
|
|
|
143
146
|
|
|
@@ -334,7 +337,8 @@ def _exclusive_lock(log_path: Path) -> Iterator[None]:
|
|
|
334
337
|
|
|
335
338
|
msvcrt.locking(fd, msvcrt.LK_LOCK, 1)
|
|
336
339
|
locked_with = "msvcrt"
|
|
337
|
-
except Exception:
|
|
340
|
+
except Exception: # fail-open: best-effort lock; locked_with=None records that none was taken
|
|
341
|
+
logger.warning("audit lock not taken (msvcrt locking failed); writing unlocked", exc_info=True)
|
|
338
342
|
locked_with = None # best-effort fallback
|
|
339
343
|
yield
|
|
340
344
|
finally:
|
|
@@ -348,7 +352,7 @@ def _exclusive_lock(log_path: Path) -> Iterator[None]:
|
|
|
348
352
|
|
|
349
353
|
os.lseek(fd, 0, os.SEEK_SET)
|
|
350
354
|
msvcrt.locking(fd, msvcrt.LK_UNLCK, 1)
|
|
351
|
-
except Exception:
|
|
355
|
+
except Exception: # fail-open: unlock failure is harmless, the fd is closed next
|
|
352
356
|
pass
|
|
353
357
|
os.close(fd)
|
|
354
358
|
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
"""Host-private audit drift-disposition triage (INT-02)."""
|
|
2
2
|
from __future__ import annotations
|
|
3
|
+
from .atomic_temp import write_text_atomic
|
|
3
4
|
|
|
4
5
|
import json
|
|
5
|
-
import os
|
|
6
6
|
from datetime import date
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import TYPE_CHECKING
|
|
9
|
-
from .atomic_temp import atomic_temp_path
|
|
10
9
|
|
|
11
10
|
if TYPE_CHECKING: # annotation-only; a runtime import here would be a cycle
|
|
12
11
|
from .audit_chain import AuditChain
|
|
@@ -33,9 +32,7 @@ def _write_dispositions(path: Path, payload: dict) -> None:
|
|
|
33
32
|
turn every explained finding back into an unexplained one. `os.replace` is
|
|
34
33
|
atomic within a filesystem, so a reader sees the old file or the new one.
|
|
35
34
|
"""
|
|
36
|
-
|
|
37
|
-
tmp.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
|
38
|
-
os.replace(tmp, path)
|
|
35
|
+
write_text_atomic(path, json.dumps(payload, indent=2))
|
|
39
36
|
|
|
40
37
|
|
|
41
38
|
def drift_dispositions_path(vault: Path) -> Path:
|
|
@@ -45,16 +45,18 @@ def _fold(text: str) -> str:
|
|
|
45
45
|
return re.sub(r"[^a-z0-9]+", "-", folded).strip("-")
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
def _typed_notes(vault: Path, types: tuple[str, ...]) -> list[tuple[str, str]]:
|
|
49
|
-
"""[(note_id, folded-title-slug)] for every brain/ note of one of ``types``.
|
|
48
|
+
def _typed_notes(vault: Path, types: tuple[str, ...], notes=None) -> list[tuple[str, str]]:
|
|
49
|
+
"""[(note_id, folded-title-slug)] for every brain/ note of one of ``types``.
|
|
50
|
+
|
|
51
|
+
``notes`` is an already-scanned vault (one scan shared by both finders)."""
|
|
50
52
|
out: list[tuple[str, str]] = []
|
|
51
|
-
for note in scan_vault(vault):
|
|
53
|
+
for note in (scan_vault(vault) if notes is None else notes):
|
|
52
54
|
if note.zone == "brain" and note.type in types:
|
|
53
55
|
out.append((note.id, _fold(note.title)))
|
|
54
56
|
return out
|
|
55
57
|
|
|
56
58
|
|
|
57
|
-
def find_person_links(text: str, vault: Path) -> list[str]:
|
|
59
|
+
def find_person_links(text: str, vault: Path, notes=None) -> list[str]:
|
|
58
60
|
"""Full-name matches of existing ``type: person`` notes in ``text``.
|
|
59
61
|
|
|
60
62
|
A person note's title must fold to at least two hyphen-separated parts
|
|
@@ -63,7 +65,7 @@ def find_person_links(text: str, vault: Path) -> list[str]:
|
|
|
63
65
|
``text`` never links on its own."""
|
|
64
66
|
folded_text = _fold(text)
|
|
65
67
|
hits: list[str] = []
|
|
66
|
-
for note_id, name_slug in _typed_notes(vault, ("person",)):
|
|
68
|
+
for note_id, name_slug in _typed_notes(vault, ("person",), notes):
|
|
67
69
|
parts = [p for p in name_slug.split("-") if p]
|
|
68
70
|
if len(parts) < 2:
|
|
69
71
|
continue
|
|
@@ -72,7 +74,7 @@ def find_person_links(text: str, vault: Path) -> list[str]:
|
|
|
72
74
|
return hits
|
|
73
75
|
|
|
74
76
|
|
|
75
|
-
def find_project_link(text: str, vault: Path) -> list[str]:
|
|
77
|
+
def find_project_link(text: str, vault: Path, notes=None) -> list[str]:
|
|
76
78
|
"""Origin-context match against ``type: project``/``type: moc`` notes.
|
|
77
79
|
|
|
78
80
|
Returns a match only when exactly one candidate's folded title is a
|
|
@@ -80,7 +82,7 @@ def find_project_link(text: str, vault: Path) -> list[str]:
|
|
|
80
82
|
nothing."""
|
|
81
83
|
folded_text = _fold(text)
|
|
82
84
|
candidates = [
|
|
83
|
-
note_id for note_id, title_slug in _typed_notes(vault, ("project", "moc"))
|
|
85
|
+
note_id for note_id, title_slug in _typed_notes(vault, ("project", "moc"), notes)
|
|
84
86
|
if title_slug and title_slug in folded_text
|
|
85
87
|
]
|
|
86
88
|
return candidates if len(candidates) == 1 else []
|
|
@@ -109,8 +111,9 @@ def apply_autolinks(
|
|
|
109
111
|
return body, []
|
|
110
112
|
try:
|
|
111
113
|
search_text = "\n".join(t for t in (title, body) if t)
|
|
112
|
-
|
|
113
|
-
candidates
|
|
114
|
+
notes = list(scan_vault(vault)) # one scan per record; no cross-record cache
|
|
115
|
+
candidates = list(find_person_links(search_text, vault, notes))
|
|
116
|
+
candidates.extend(find_project_link("\n".join(t for t in (title, origin) if t), vault, notes))
|
|
114
117
|
existing = set(parse_wikilinks(body))
|
|
115
118
|
new_ids = [nid for nid in dict.fromkeys(candidates) if nid not in existing]
|
|
116
119
|
if not new_ids:
|