brainiac-cli 0.20.48__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.48/src/brainiac_cli.egg-info → brainiac_cli-0.20.49}/PKG-INFO +4 -3
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/pyproject.toml +20 -8
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/AGENTS.md +1 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brain_synthesis_codex.py +16 -3
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_version.py +1 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/alerts.py +2 -2
- brainiac_cli-0.20.49/src/brain/atomic_temp.py +49 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/audit.py +7 -3
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/audit_drift.py +2 -5
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/autolink.py +12 -9
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/backup.py +13 -2
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/brief.py +21 -15
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/chunk.py +24 -19
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli.py +4 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/index_storage.py +59 -51
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/lifecycle.py +1 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/owner_workflow.py +17 -14
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_read_record.py +1 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/config.py +0 -7
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/context.py +2 -7
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/conventions_scan.py +20 -12
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_briefing.py +19 -10
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_capture.py +19 -17
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_health.py +1 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_maintenance_state.py +2 -5
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_shared.py +4 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_supersession_journal.py +16 -9
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/deliverables_materialize.py +11 -6
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/deliverables_sync.py +6 -3
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor.py +3 -3
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor_checks.py +1 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor_context.py +9 -2
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor_desktop.py +1 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor_health.py +19 -12
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor_plugins.py +5 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor_staging.py +3 -3
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor_vendor.py +17 -11
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor_vm.py +1 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor_wiring.py +59 -36
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/draft_drain.py +33 -2
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/draft_update.py +3 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/egress.py +0 -11
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/embed.py +4 -4
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/embed_catalog.py +1 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/embed_onnx.py +2 -2
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/embed_qwen.py +1 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/encryption.py +1 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/exceptions_page.py +3 -8
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/exceptions_verify.py +2 -4
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/context.py +3 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/deliverables.py +2 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/invariants.py +4 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/publish.py +3 -1
- {brainiac_cli-0.20.48 → 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.48 → brainiac_cli-0.20.49}/src/brain/golden_ops.py +59 -26
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/golden_probe_cases.py +15 -10
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/graph_ops.py +3 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/healthreport_sections.py +55 -40
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_tools.py +4 -3
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_zones.py +25 -17
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/connection.py +5 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/base.py +76 -31
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/concealment.py +0 -9
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/concealment_html.py +28 -19
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/concealment_pdf.py +15 -8
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/email.py +5 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/html.py +10 -2
- {brainiac_cli-0.20.48 → 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.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/pdf.py +45 -6
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/pptx.py +8 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/xlsx.py +19 -4
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/zip.py +12 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/pipeline_files.py +3 -7
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/pipeline_stages.py +16 -14
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/tierguard.py +10 -4
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/init_seed.py +0 -50
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/invariant_coverage.py +25 -15
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/invariant_deliverables.py +4 -4
- {brainiac_cli-0.20.48 → 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.48 → brainiac_cli-0.20.49}/src/brain/invariants.py +6 -26
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_folds.py +7 -7
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_folds_3.py +2 -5
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_folds_4.py +3 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_hotmd.py +18 -10
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_notify.py +73 -50
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_retention.py +38 -32
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/notes.py +45 -40
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/overlay.py +38 -27
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/overlay_generated.py +6 -7
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/provision.py +1 -3
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/querylog.py +4 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/querylog_digest.py +2 -2
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/querylog_status.py +19 -10
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/read_log.py +42 -33
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/reexec.py +1 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation.py +3 -7
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_exceptions_store.py +2 -4
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_extract_retry.py +15 -10
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_folds.py +5 -1
- brainiac_cli-0.20.49/src/brain/remediation_security.py +21 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_state.py +2 -4
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/rerank.py +3 -3
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/rerank_onnx.py +3 -3
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/session_hook_settings.py +2 -4
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/snapshot.py +5 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/update.py +10 -2
- {brainiac_cli-0.20.48 → 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.48 → brainiac_cli-0.20.49}/src/brain/update_ops.py +2 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/update_state.py +5 -4
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/update_venv.py +3 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/vectors.py +3 -3
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/vm_ceiling.py +2 -1
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/vmstaging.py +0 -61
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/workspaces.py +2 -4
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49/src/brainiac_cli.egg-info}/PKG-INFO +4 -3
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brainiac_cli.egg-info/SOURCES.txt +4 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brainiac_cli.egg-info/requires.txt +3 -2
- brainiac_cli-0.20.48/src/brain/atomic_temp.py +0 -6
- brainiac_cli-0.20.48/src/brain/invariant_shared.py +0 -4
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/LICENSE +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/MANIFEST.in +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/README.md +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/setup.cfg +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/__init__.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/__main__.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/assets/graph-explorer-template.html +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/overlay/template/brand/brand-guide.md +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/overlay/template/keywords/glossary.md +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/overlay/template/people/roster.md +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/overlay/template/voice/voice-profile.md +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/routines/manifest.json +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brain-brief-mac.plist +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brain-brief.sh +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brain-synthesis-mac.plist +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brain-synthesis.sh +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brainiac-alerts.sh +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/brainiac-egress-guard.sh +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/install-brief-mac.sh +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/install-brief-windows.ps1 +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/register_tasks.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/shorten_long_note_ids.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/vm-boundary-probe.sh +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/scripts/vm-selftest.sh +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/templates/company.md +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/templates/concept.md +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/templates/daily.md +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/templates/decision.md +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/templates/deliverable.md +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/templates/meeting.md +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/templates/person.md +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/templates/project.md +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_assets/templates/state-moc.md +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/_optional.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/alerts_render.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/alerts_staging.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/anchor.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/attestation.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/audit_chain.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/brief_render.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/capture.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/classification.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/__init__.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/common_parser.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/connect.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/connect_parser.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/eval.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/eval_parser.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/index_storage_parser.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/ingest_storage.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/ingest_storage_parser.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/lifecycle_parser.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/maintenance.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/maintenance_parser.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/navigation.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/navigation_parser.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/owner_workflow_parser.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/presentation.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/presentation_parser.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/retrieval.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/retrieval_parser.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/update.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/update_parser.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/versioning.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_cmds/versioning_parser.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_help.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cli_render.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/config_hostpaths.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/connect.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/conventions.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/__init__.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_audit.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_capture_staging.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_durability.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_maintenance.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_retrieval.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_supersede_batch.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/core/_supersession.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cowork_leak_artifact.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cowork_leak_databases.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cowork_leak_notes.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cowork_leak_scan.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cowork_snapshot_guard.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/cowork_staging.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/dbretry.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/deliverables_absorb.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/deliverables_apply.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/deliverables_ledger.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/deliverables_previous.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/deliverables_readme.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/deliverables_shelf.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/doctor_mount_leak.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/egress_terms.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/embed_backends.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/exceptions_cli.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/exceptions_render.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/exceptions_tray.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/__init__.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/daily.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/golden.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/graph.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/intake.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/orchestrator.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/organization.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/preflight.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/remediation.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/remediation_bookkeeping.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/reporting.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/retention.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/watchdogs.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/folds/weekly.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/golden_probe.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/graph.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/graph_hygiene.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/graphify.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/graphify_edges.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/graphreport.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/graphreport_sections.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/graphreport_sections_2.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/guard_settings.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/healthreport.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/healthreport_unsigned.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/inbox.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/__init__.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_connection.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_families.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_graph.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_identity.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_lifecycle.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_models.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_planning.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_records.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_retirement.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_schema.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_search.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_settings.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index/_shared.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/__init__.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/diagnostics.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/duplicate_families.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/near_duplicates.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/rebuild.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/reranking.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/search.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/search_finalization.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/search_identity.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/search_ranking.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/index_stages/sync.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/__init__.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/deliverables.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/__init__.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/concealment_gate.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/concealment_ledger.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/concealment_office.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/docx.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/ole.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/tables.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/handlers/text.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/pipeline.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/pipeline_duplicates.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/pipeline_injection.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/pipeline_nested.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/pipeline_run.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/tierguard_verdict.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/ingest/transcript.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/init.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/init_import.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/init_samples.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/init_steps.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/injection_fold.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/injection_scan.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/interview.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/interview_apply.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/interview_detect.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/invariant_crosstier.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/invariant_floors.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/invariant_ingest_guard.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/invariant_lane_health.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/invariant_unsigned_notes.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/invariants_metrics.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/language.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/link_targets.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/lock.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_escalation.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_folds_2.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_golden.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_graphfolds.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_healthlog.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_invariant_fields.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_outcomes.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_para.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_recommendations.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_sweep.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/maintenance_watchdog.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_adapter.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_capture_verbs.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_housekeeping_verbs.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_mediation.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_supersede_verbs.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_tools/__init__.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_tools/capture.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_tools/core_read.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_tools/housekeeping.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_tools/retrieval.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_tools/supersession.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/mcp_verbs.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/model_slot.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/multihop.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/originals_resolve.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/overlay_core.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/overlay_keywords.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/overlay_rules.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/pathkey.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/progress.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/projection.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/provenance.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/provision_mcp.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/provision_stage.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/provision_wire.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/querylog_replay.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_answers.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_branches.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_converged.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_exceptions.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_invariants.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_questions.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_routing.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/remediation_schema.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/retrieval_ops.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/retro.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/session_hook.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/session_hook_spec.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/status_ops.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/supersede_declared.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/sweepdirs.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/update_channels_2.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/update_cos_guard.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/update_model.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/update_plugins.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/versionlink.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brain/versionlink_stages.py +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brainiac_cli.egg-info/dependency_links.txt +0 -0
- {brainiac_cli-0.20.48 → brainiac_cli-0.20.49}/src/brainiac_cli.egg-info/entry_points.txt +0 -0
- {brainiac_cli-0.20.48 → 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
|
{brainiac_cli-0.20.48 → 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
|
|
|
@@ -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
|
|
|
@@ -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:
|
|
@@ -21,17 +21,28 @@ from __future__ import annotations
|
|
|
21
21
|
import hashlib
|
|
22
22
|
import io
|
|
23
23
|
import json
|
|
24
|
+
import os
|
|
24
25
|
import tarfile
|
|
25
26
|
import time
|
|
26
27
|
from dataclasses import asdict, dataclass
|
|
27
28
|
from pathlib import Path
|
|
28
29
|
from typing import Any
|
|
29
30
|
|
|
31
|
+
from . import config
|
|
30
32
|
from . import encryption as enc
|
|
31
33
|
|
|
32
34
|
EXCLUDE_DIRS = {".brain", ".git", "__pycache__", ".pytest_cache"}
|
|
33
35
|
|
|
34
36
|
|
|
37
|
+
|
|
38
|
+
def _write_owner_only(path: Path, data: bytes) -> None:
|
|
39
|
+
"""Create the archive 0600 from the first byte: a plaintext backup is the
|
|
40
|
+
whole vault, and the default umask left it readable by every local user."""
|
|
41
|
+
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
|
42
|
+
config.secure_file_permissions(path, 0o600) # O_CREAT ignores mode on an existing file
|
|
43
|
+
with os.fdopen(fd, "wb") as fh:
|
|
44
|
+
fh.write(data)
|
|
45
|
+
|
|
35
46
|
@dataclass
|
|
36
47
|
class BackupManifest:
|
|
37
48
|
schema_version: str
|
|
@@ -88,11 +99,11 @@ def create_backup(vault: Path, dest_dir: Path, *, name: str | None = None,
|
|
|
88
99
|
if encrypt:
|
|
89
100
|
token = enc.encrypt_bytes(plaintext, force=True) # force: off-device => encrypt
|
|
90
101
|
archive = dest_dir / f"{stamp}.tar.gz.enc"
|
|
91
|
-
archive
|
|
102
|
+
_write_owner_only(archive, token)
|
|
92
103
|
ct_bytes = len(token)
|
|
93
104
|
else:
|
|
94
105
|
archive = dest_dir / f"{stamp}.tar.gz"
|
|
95
|
-
archive
|
|
106
|
+
_write_owner_only(archive, plaintext)
|
|
96
107
|
ct_bytes = len(plaintext)
|
|
97
108
|
|
|
98
109
|
manifest = BackupManifest(
|
|
@@ -137,7 +137,26 @@ def format_brief(brief: dict[str, Any]) -> str:
|
|
|
137
137
|
elif brief.get("pending_before_drain", 0) == 0:
|
|
138
138
|
lines.append(" ✓ no pending captures")
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
lines.extend(_cos_liveness_lines(brief.get("cos_liveness") or {}))
|
|
141
|
+
|
|
142
|
+
snap = brief.get("snapshot_age")
|
|
143
|
+
if snap:
|
|
144
|
+
lines.append(f" snapshot age: {snap}")
|
|
145
|
+
|
|
146
|
+
if brief.get("recent"):
|
|
147
|
+
lines.append(" recent:")
|
|
148
|
+
for n in brief["recent"]:
|
|
149
|
+
lines.append(
|
|
150
|
+
f" {str(n.get('updated', ''))[:10]} {n.get('id', '')} "
|
|
151
|
+
f"({n.get('classification') or 'UNLABELLED'})"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
return "\n".join(lines)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _cos_liveness_lines(live: dict[str, Any]) -> list[str]:
|
|
158
|
+
"""COS liveness banner lines for the text brief."""
|
|
159
|
+
lines: list[str] = []
|
|
141
160
|
if live.get("alert"):
|
|
142
161
|
lines.append(f" ⚠ {live['alert_text']}")
|
|
143
162
|
elif live.get("pending_behind_backpressure"):
|
|
@@ -158,20 +177,7 @@ def format_brief(brief: dict[str, Any]) -> str:
|
|
|
158
177
|
# VALID run. Loud here for the same reason the line above is.
|
|
159
178
|
if live.get("quarantine_text"):
|
|
160
179
|
lines.append(f" ⚠ {live['quarantine_text']}")
|
|
161
|
-
|
|
162
|
-
snap = brief.get("snapshot_age")
|
|
163
|
-
if snap:
|
|
164
|
-
lines.append(f" snapshot age: {snap}")
|
|
165
|
-
|
|
166
|
-
if brief.get("recent"):
|
|
167
|
-
lines.append(" recent:")
|
|
168
|
-
for n in brief["recent"]:
|
|
169
|
-
lines.append(
|
|
170
|
-
f" {str(n.get('updated', ''))[:10]} {n.get('id', '')} "
|
|
171
|
-
f"({n.get('classification') or 'UNLABELLED'})"
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
return "\n".join(lines)
|
|
180
|
+
return lines
|
|
175
181
|
|
|
176
182
|
|
|
177
183
|
def _maintain_alert_lines(maintain_alert: dict[str, Any] | None) -> list[str]:
|
|
@@ -200,25 +200,8 @@ def _pack(heading: str, text: str, ordinal_start: int, lang_of) -> list[Chunk]:
|
|
|
200
200
|
return out
|
|
201
201
|
|
|
202
202
|
|
|
203
|
-
def
|
|
204
|
-
"""
|
|
205
|
-
|
|
206
|
-
Strategy: split into heading-scoped blocks, then greedily merge adjacent
|
|
207
|
-
blocks under the same heading up to TARGET_CHARS, splitting any single block
|
|
208
|
-
over MAX_CHARS. Each chunk's language is detected on its own text.
|
|
209
|
-
|
|
210
|
-
Dual-granularity (RET-03): a short note (whole body ≤ ``_whole_note_max()``)
|
|
211
|
-
is returned as ONE whole-note chunk — its sections are NOT split — so its
|
|
212
|
-
cross-lingual signal stays concentrated in a single vector. See
|
|
213
|
-
``WHOLE_NOTE_MAX_CHARS``.
|
|
214
|
-
"""
|
|
215
|
-
whole_max = _whole_note_max()
|
|
216
|
-
stripped = body.strip()
|
|
217
|
-
if whole_max > 0 and stripped and len(stripped) <= whole_max:
|
|
218
|
-
return [Chunk(0, "", stripped, lang_of(stripped))]
|
|
219
|
-
blocks = _split_blocks(body)
|
|
220
|
-
if not blocks:
|
|
221
|
-
return []
|
|
203
|
+
def _merge_blocks(blocks: list[tuple[str, str]], lang_of) -> list[Chunk]:
|
|
204
|
+
"""Greedily merge adjacent same-heading blocks up to TARGET_CHARS and pack."""
|
|
222
205
|
chunks: list[Chunk] = []
|
|
223
206
|
ordinal = 0
|
|
224
207
|
pending_heading = blocks[0][0]
|
|
@@ -247,3 +230,25 @@ def chunk_text(body: str, *, lang_of=detect_language) -> list[Chunk]:
|
|
|
247
230
|
if pending_text:
|
|
248
231
|
emit(pending_heading, pending_text)
|
|
249
232
|
return chunks
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def chunk_text(body: str, *, lang_of=detect_language) -> list[Chunk]:
|
|
236
|
+
"""Chunk a note body at section/block level with a soft size budget.
|
|
237
|
+
|
|
238
|
+
Strategy: split into heading-scoped blocks, then greedily merge adjacent
|
|
239
|
+
blocks under the same heading up to TARGET_CHARS, splitting any single block
|
|
240
|
+
over MAX_CHARS. Each chunk's language is detected on its own text.
|
|
241
|
+
|
|
242
|
+
Dual-granularity (RET-03): a short note (whole body ≤ ``_whole_note_max()``)
|
|
243
|
+
is returned as ONE whole-note chunk — its sections are NOT split — so its
|
|
244
|
+
cross-lingual signal stays concentrated in a single vector. See
|
|
245
|
+
``WHOLE_NOTE_MAX_CHARS``.
|
|
246
|
+
"""
|
|
247
|
+
whole_max = _whole_note_max()
|
|
248
|
+
stripped = body.strip()
|
|
249
|
+
if whole_max > 0 and stripped and len(stripped) <= whole_max:
|
|
250
|
+
return [Chunk(0, "", stripped, lang_of(stripped))]
|
|
251
|
+
blocks = _split_blocks(body)
|
|
252
|
+
if not blocks:
|
|
253
|
+
return []
|
|
254
|
+
return _merge_blocks(blocks, lang_of)
|
|
@@ -389,7 +389,10 @@ def _prepare_invocation(args: Any, config: Any) -> str:
|
|
|
389
389
|
# the session's own shell, so it can never raise a VM ceiling.
|
|
390
390
|
from . import vm_ceiling
|
|
391
391
|
|
|
392
|
-
ceiling,
|
|
392
|
+
# The ceiling of the vault this command OPENS, not $BRAIN_VAULT's (SEC-04).
|
|
393
|
+
explicit = getattr(args, "vault", None)
|
|
394
|
+
ceiling, _provenance = vm_ceiling.resolved_ceiling(
|
|
395
|
+
config.vault_root(explicit, allow_missing=True) if explicit else None)
|
|
393
396
|
args.max_tier = cls.clamp_to(str(args.max_tier), ceiling)
|
|
394
397
|
config.apply_role_embedder_policy(role)
|
|
395
398
|
return role
|