superlocalmemory 2.8.6 → 3.0.1
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.
- package/LICENSE +9 -1
- package/NOTICE +63 -0
- package/README.md +165 -480
- package/bin/slm +17 -449
- package/bin/slm-npm +62 -48
- package/conftest.py +5 -0
- package/docs/api-reference.md +284 -0
- package/docs/architecture.md +149 -0
- package/docs/auto-memory.md +150 -0
- package/docs/cli-reference.md +276 -0
- package/docs/compliance.md +191 -0
- package/docs/configuration.md +182 -0
- package/docs/getting-started.md +102 -0
- package/docs/ide-setup.md +261 -0
- package/docs/mcp-tools.md +220 -0
- package/docs/migration-from-v2.md +170 -0
- package/docs/profiles.md +173 -0
- package/docs/troubleshooting.md +310 -0
- package/{configs → ide/configs}/antigravity-mcp.json +3 -3
- package/ide/configs/chatgpt-desktop-mcp.json +16 -0
- package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
- package/{configs → ide/configs}/codex-mcp.toml +4 -4
- package/{configs → ide/configs}/continue-mcp.yaml +4 -3
- package/{configs → ide/configs}/continue-skills.yaml +6 -6
- package/ide/configs/cursor-mcp.json +15 -0
- package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
- package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
- package/{configs → ide/configs}/opencode-mcp.json +2 -2
- package/{configs → ide/configs}/perplexity-mcp.json +2 -2
- package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
- package/{configs → ide/configs}/windsurf-mcp.json +3 -3
- package/{configs → ide/configs}/zed-mcp.json +2 -2
- package/{hooks → ide/hooks}/context-hook.js +9 -20
- package/ide/hooks/memory-list-skill.js +70 -0
- package/ide/hooks/memory-profile-skill.js +101 -0
- package/ide/hooks/memory-recall-skill.js +62 -0
- package/ide/hooks/memory-remember-skill.js +68 -0
- package/ide/hooks/memory-reset-skill.js +160 -0
- package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
- package/ide/integrations/langchain/README.md +106 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
- package/ide/integrations/langchain/pyproject.toml +38 -0
- package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
- package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
- package/ide/integrations/langchain/tests/test_security.py +117 -0
- package/ide/integrations/llamaindex/README.md +81 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
- package/ide/integrations/llamaindex/pyproject.toml +43 -0
- package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
- package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
- package/ide/integrations/llamaindex/tests/test_security.py +241 -0
- package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
- package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
- package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
- package/package.json +13 -22
- package/pyproject.toml +85 -0
- package/scripts/build-dmg.sh +417 -0
- package/scripts/install-skills.ps1 +334 -0
- package/scripts/postinstall.js +2 -2
- package/scripts/start-dashboard.ps1 +52 -0
- package/scripts/start-dashboard.sh +41 -0
- package/scripts/sync-wiki.ps1 +127 -0
- package/scripts/sync-wiki.sh +82 -0
- package/scripts/test-dmg.sh +161 -0
- package/scripts/test-npm-package.ps1 +252 -0
- package/scripts/test-npm-package.sh +207 -0
- package/scripts/verify-install.ps1 +294 -0
- package/scripts/verify-install.sh +266 -0
- package/src/superlocalmemory/__init__.py +0 -0
- package/src/superlocalmemory/attribution/__init__.py +9 -0
- package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
- package/src/superlocalmemory/attribution/signer.py +153 -0
- package/src/superlocalmemory/attribution/watermark.py +189 -0
- package/src/superlocalmemory/cli/__init__.py +5 -0
- package/src/superlocalmemory/cli/commands.py +245 -0
- package/src/superlocalmemory/cli/main.py +89 -0
- package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
- package/src/superlocalmemory/cli/post_install.py +99 -0
- package/src/superlocalmemory/cli/setup_wizard.py +129 -0
- package/src/superlocalmemory/compliance/__init__.py +0 -0
- package/src/superlocalmemory/compliance/abac.py +204 -0
- package/src/superlocalmemory/compliance/audit.py +314 -0
- package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
- package/src/superlocalmemory/compliance/gdpr.py +294 -0
- package/src/superlocalmemory/compliance/lifecycle.py +158 -0
- package/src/superlocalmemory/compliance/retention.py +232 -0
- package/src/superlocalmemory/compliance/scheduler.py +148 -0
- package/src/superlocalmemory/core/__init__.py +0 -0
- package/src/superlocalmemory/core/config.py +391 -0
- package/src/superlocalmemory/core/embeddings.py +293 -0
- package/src/superlocalmemory/core/engine.py +701 -0
- package/src/superlocalmemory/core/hooks.py +65 -0
- package/src/superlocalmemory/core/maintenance.py +172 -0
- package/src/superlocalmemory/core/modes.py +140 -0
- package/src/superlocalmemory/core/profiles.py +234 -0
- package/src/superlocalmemory/core/registry.py +117 -0
- package/src/superlocalmemory/dynamics/__init__.py +0 -0
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
- package/src/superlocalmemory/encoding/__init__.py +0 -0
- package/src/superlocalmemory/encoding/consolidator.py +485 -0
- package/src/superlocalmemory/encoding/emotional.py +125 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
- package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
- package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
- package/src/superlocalmemory/encoding/foresight.py +91 -0
- package/src/superlocalmemory/encoding/graph_builder.py +302 -0
- package/src/superlocalmemory/encoding/observation_builder.py +160 -0
- package/src/superlocalmemory/encoding/scene_builder.py +183 -0
- package/src/superlocalmemory/encoding/signal_inference.py +90 -0
- package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
- package/src/superlocalmemory/encoding/type_router.py +235 -0
- package/src/superlocalmemory/hooks/__init__.py +3 -0
- package/src/superlocalmemory/hooks/auto_capture.py +111 -0
- package/src/superlocalmemory/hooks/auto_recall.py +93 -0
- package/src/superlocalmemory/hooks/ide_connector.py +204 -0
- package/src/superlocalmemory/hooks/rules_engine.py +99 -0
- package/src/superlocalmemory/infra/__init__.py +3 -0
- package/src/superlocalmemory/infra/auth_middleware.py +82 -0
- package/src/superlocalmemory/infra/backup.py +317 -0
- package/src/superlocalmemory/infra/cache_manager.py +267 -0
- package/src/superlocalmemory/infra/event_bus.py +381 -0
- package/src/superlocalmemory/infra/rate_limiter.py +135 -0
- package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
- package/src/superlocalmemory/learning/__init__.py +0 -0
- package/src/superlocalmemory/learning/adaptive.py +172 -0
- package/src/superlocalmemory/learning/behavioral.py +490 -0
- package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
- package/src/superlocalmemory/learning/bootstrap.py +298 -0
- package/src/superlocalmemory/learning/cross_project.py +399 -0
- package/src/superlocalmemory/learning/database.py +376 -0
- package/src/superlocalmemory/learning/engagement.py +323 -0
- package/src/superlocalmemory/learning/features.py +138 -0
- package/src/superlocalmemory/learning/feedback.py +316 -0
- package/src/superlocalmemory/learning/outcomes.py +255 -0
- package/src/superlocalmemory/learning/project_context.py +366 -0
- package/src/superlocalmemory/learning/ranker.py +155 -0
- package/src/superlocalmemory/learning/source_quality.py +303 -0
- package/src/superlocalmemory/learning/workflows.py +309 -0
- package/src/superlocalmemory/llm/__init__.py +0 -0
- package/src/superlocalmemory/llm/backbone.py +316 -0
- package/src/superlocalmemory/math/__init__.py +0 -0
- package/src/superlocalmemory/math/fisher.py +356 -0
- package/src/superlocalmemory/math/langevin.py +398 -0
- package/src/superlocalmemory/math/sheaf.py +257 -0
- package/src/superlocalmemory/mcp/__init__.py +0 -0
- package/src/superlocalmemory/mcp/resources.py +245 -0
- package/src/superlocalmemory/mcp/server.py +61 -0
- package/src/superlocalmemory/mcp/tools.py +18 -0
- package/src/superlocalmemory/mcp/tools_core.py +305 -0
- package/src/superlocalmemory/mcp/tools_v28.py +223 -0
- package/src/superlocalmemory/mcp/tools_v3.py +286 -0
- package/src/superlocalmemory/retrieval/__init__.py +0 -0
- package/src/superlocalmemory/retrieval/agentic.py +295 -0
- package/src/superlocalmemory/retrieval/ann_index.py +223 -0
- package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
- package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
- package/src/superlocalmemory/retrieval/engine.py +390 -0
- package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
- package/src/superlocalmemory/retrieval/fusion.py +78 -0
- package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
- package/src/superlocalmemory/retrieval/reranker.py +154 -0
- package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
- package/src/superlocalmemory/retrieval/strategy.py +96 -0
- package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
- package/src/superlocalmemory/server/__init__.py +1 -0
- package/src/superlocalmemory/server/api.py +248 -0
- package/src/superlocalmemory/server/routes/__init__.py +4 -0
- package/src/superlocalmemory/server/routes/agents.py +107 -0
- package/src/superlocalmemory/server/routes/backup.py +91 -0
- package/src/superlocalmemory/server/routes/behavioral.py +127 -0
- package/src/superlocalmemory/server/routes/compliance.py +160 -0
- package/src/superlocalmemory/server/routes/data_io.py +188 -0
- package/src/superlocalmemory/server/routes/events.py +183 -0
- package/src/superlocalmemory/server/routes/helpers.py +85 -0
- package/src/superlocalmemory/server/routes/learning.py +273 -0
- package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
- package/src/superlocalmemory/server/routes/memories.py +399 -0
- package/src/superlocalmemory/server/routes/profiles.py +219 -0
- package/src/superlocalmemory/server/routes/stats.py +346 -0
- package/src/superlocalmemory/server/routes/v3_api.py +365 -0
- package/src/superlocalmemory/server/routes/ws.py +82 -0
- package/src/superlocalmemory/server/security_middleware.py +57 -0
- package/src/superlocalmemory/server/ui.py +245 -0
- package/src/superlocalmemory/storage/__init__.py +0 -0
- package/src/superlocalmemory/storage/access_control.py +182 -0
- package/src/superlocalmemory/storage/database.py +594 -0
- package/src/superlocalmemory/storage/migrations.py +303 -0
- package/src/superlocalmemory/storage/models.py +406 -0
- package/src/superlocalmemory/storage/schema.py +726 -0
- package/src/superlocalmemory/storage/v2_migrator.py +317 -0
- package/src/superlocalmemory/trust/__init__.py +0 -0
- package/src/superlocalmemory/trust/gate.py +130 -0
- package/src/superlocalmemory/trust/provenance.py +124 -0
- package/src/superlocalmemory/trust/scorer.py +347 -0
- package/src/superlocalmemory/trust/signals.py +153 -0
- package/ui/index.html +278 -5
- package/ui/js/auto-settings.js +70 -0
- package/ui/js/dashboard.js +90 -0
- package/ui/js/fact-detail.js +92 -0
- package/ui/js/feedback.js +2 -2
- package/ui/js/ide-status.js +102 -0
- package/ui/js/math-health.js +98 -0
- package/ui/js/recall-lab.js +127 -0
- package/ui/js/settings.js +2 -2
- package/ui/js/trust-dashboard.js +73 -0
- package/api_server.py +0 -724
- package/bin/aider-smart +0 -72
- package/bin/superlocalmemoryv2-learning +0 -4
- package/bin/superlocalmemoryv2-list +0 -3
- package/bin/superlocalmemoryv2-patterns +0 -4
- package/bin/superlocalmemoryv2-profile +0 -3
- package/bin/superlocalmemoryv2-recall +0 -3
- package/bin/superlocalmemoryv2-remember +0 -3
- package/bin/superlocalmemoryv2-reset +0 -3
- package/bin/superlocalmemoryv2-status +0 -3
- package/configs/chatgpt-desktop-mcp.json +0 -16
- package/configs/cursor-mcp.json +0 -15
- package/hooks/memory-list-skill.js +0 -139
- package/hooks/memory-profile-skill.js +0 -273
- package/hooks/memory-recall-skill.js +0 -114
- package/hooks/memory-remember-skill.js +0 -127
- package/hooks/memory-reset-skill.js +0 -274
- package/mcp_server.py +0 -1808
- package/requirements-core.txt +0 -22
- package/requirements-learning.txt +0 -12
- package/requirements.txt +0 -12
- package/src/agent_registry.py +0 -411
- package/src/auth_middleware.py +0 -61
- package/src/auto_backup.py +0 -459
- package/src/behavioral/__init__.py +0 -49
- package/src/behavioral/behavioral_listener.py +0 -203
- package/src/behavioral/behavioral_patterns.py +0 -275
- package/src/behavioral/cross_project_transfer.py +0 -206
- package/src/behavioral/outcome_inference.py +0 -194
- package/src/behavioral/outcome_tracker.py +0 -193
- package/src/behavioral/tests/__init__.py +0 -4
- package/src/behavioral/tests/test_behavioral_integration.py +0 -108
- package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
- package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
- package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
- package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
- package/src/behavioral/tests/test_outcome_inference.py +0 -107
- package/src/behavioral/tests/test_outcome_tracker.py +0 -96
- package/src/cache_manager.py +0 -518
- package/src/compliance/__init__.py +0 -48
- package/src/compliance/abac_engine.py +0 -149
- package/src/compliance/abac_middleware.py +0 -116
- package/src/compliance/audit_db.py +0 -215
- package/src/compliance/audit_logger.py +0 -148
- package/src/compliance/retention_manager.py +0 -289
- package/src/compliance/retention_scheduler.py +0 -186
- package/src/compliance/tests/__init__.py +0 -4
- package/src/compliance/tests/test_abac_enforcement.py +0 -95
- package/src/compliance/tests/test_abac_engine.py +0 -124
- package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
- package/src/compliance/tests/test_audit_db.py +0 -123
- package/src/compliance/tests/test_audit_logger.py +0 -98
- package/src/compliance/tests/test_mcp_audit.py +0 -128
- package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
- package/src/compliance/tests/test_retention_manager.py +0 -131
- package/src/compliance/tests/test_retention_scheduler.py +0 -99
- package/src/compression/__init__.py +0 -25
- package/src/compression/cli.py +0 -150
- package/src/compression/cold_storage.py +0 -217
- package/src/compression/config.py +0 -72
- package/src/compression/orchestrator.py +0 -133
- package/src/compression/tier2_compressor.py +0 -228
- package/src/compression/tier3_compressor.py +0 -153
- package/src/compression/tier_classifier.py +0 -148
- package/src/db_connection_manager.py +0 -536
- package/src/embedding_engine.py +0 -63
- package/src/embeddings/__init__.py +0 -47
- package/src/embeddings/cache.py +0 -70
- package/src/embeddings/cli.py +0 -113
- package/src/embeddings/constants.py +0 -47
- package/src/embeddings/database.py +0 -91
- package/src/embeddings/engine.py +0 -247
- package/src/embeddings/model_loader.py +0 -145
- package/src/event_bus.py +0 -562
- package/src/graph/__init__.py +0 -36
- package/src/graph/build_helpers.py +0 -74
- package/src/graph/cli.py +0 -87
- package/src/graph/cluster_builder.py +0 -188
- package/src/graph/cluster_summary.py +0 -148
- package/src/graph/constants.py +0 -47
- package/src/graph/edge_builder.py +0 -162
- package/src/graph/entity_extractor.py +0 -95
- package/src/graph/graph_core.py +0 -226
- package/src/graph/graph_search.py +0 -231
- package/src/graph/hierarchical.py +0 -207
- package/src/graph/schema.py +0 -99
- package/src/graph_engine.py +0 -52
- package/src/hnsw_index.py +0 -628
- package/src/hybrid_search.py +0 -46
- package/src/learning/__init__.py +0 -217
- package/src/learning/adaptive_ranker.py +0 -682
- package/src/learning/bootstrap/__init__.py +0 -69
- package/src/learning/bootstrap/constants.py +0 -93
- package/src/learning/bootstrap/db_queries.py +0 -316
- package/src/learning/bootstrap/sampling.py +0 -82
- package/src/learning/bootstrap/text_utils.py +0 -71
- package/src/learning/cross_project_aggregator.py +0 -857
- package/src/learning/db/__init__.py +0 -40
- package/src/learning/db/constants.py +0 -44
- package/src/learning/db/schema.py +0 -279
- package/src/learning/engagement_tracker.py +0 -628
- package/src/learning/feature_extractor.py +0 -708
- package/src/learning/feedback_collector.py +0 -806
- package/src/learning/learning_db.py +0 -915
- package/src/learning/project_context_manager.py +0 -572
- package/src/learning/ranking/__init__.py +0 -33
- package/src/learning/ranking/constants.py +0 -84
- package/src/learning/ranking/helpers.py +0 -278
- package/src/learning/source_quality_scorer.py +0 -676
- package/src/learning/synthetic_bootstrap.py +0 -755
- package/src/learning/tests/test_adaptive_ranker.py +0 -325
- package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
- package/src/learning/tests/test_aggregator.py +0 -306
- package/src/learning/tests/test_auto_retrain_v28.py +0 -35
- package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
- package/src/learning/tests/test_feature_extractor_v28.py +0 -93
- package/src/learning/tests/test_feedback_collector.py +0 -294
- package/src/learning/tests/test_learning_db.py +0 -602
- package/src/learning/tests/test_learning_db_v28.py +0 -110
- package/src/learning/tests/test_learning_init_v28.py +0 -48
- package/src/learning/tests/test_outcome_signals.py +0 -48
- package/src/learning/tests/test_project_context.py +0 -292
- package/src/learning/tests/test_schema_migration.py +0 -319
- package/src/learning/tests/test_signal_inference.py +0 -397
- package/src/learning/tests/test_source_quality.py +0 -351
- package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
- package/src/learning/tests/test_workflow_miner.py +0 -318
- package/src/learning/workflow_pattern_miner.py +0 -655
- package/src/lifecycle/__init__.py +0 -54
- package/src/lifecycle/bounded_growth.py +0 -239
- package/src/lifecycle/compaction_engine.py +0 -226
- package/src/lifecycle/lifecycle_engine.py +0 -355
- package/src/lifecycle/lifecycle_evaluator.py +0 -257
- package/src/lifecycle/lifecycle_scheduler.py +0 -130
- package/src/lifecycle/retention_policy.py +0 -285
- package/src/lifecycle/tests/test_bounded_growth.py +0 -193
- package/src/lifecycle/tests/test_compaction.py +0 -179
- package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
- package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
- package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
- package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
- package/src/lifecycle/tests/test_mcp_compact.py +0 -149
- package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
- package/src/lifecycle/tests/test_retention_policy.py +0 -162
- package/src/mcp_tools_v28.py +0 -281
- package/src/memory/__init__.py +0 -36
- package/src/memory/cli.py +0 -205
- package/src/memory/constants.py +0 -39
- package/src/memory/helpers.py +0 -28
- package/src/memory/schema.py +0 -166
- package/src/memory-profiles.py +0 -595
- package/src/memory-reset.py +0 -491
- package/src/memory_compression.py +0 -989
- package/src/memory_store_v2.py +0 -1155
- package/src/migrate_v1_to_v2.py +0 -629
- package/src/pattern_learner.py +0 -34
- package/src/patterns/__init__.py +0 -24
- package/src/patterns/analyzers.py +0 -251
- package/src/patterns/learner.py +0 -271
- package/src/patterns/scoring.py +0 -171
- package/src/patterns/store.py +0 -225
- package/src/patterns/terminology.py +0 -140
- package/src/provenance_tracker.py +0 -312
- package/src/qualixar_attribution.py +0 -139
- package/src/qualixar_watermark.py +0 -78
- package/src/query_optimizer.py +0 -511
- package/src/rate_limiter.py +0 -83
- package/src/search/__init__.py +0 -20
- package/src/search/cli.py +0 -77
- package/src/search/constants.py +0 -26
- package/src/search/engine.py +0 -241
- package/src/search/fusion.py +0 -122
- package/src/search/index_loader.py +0 -114
- package/src/search/methods.py +0 -162
- package/src/search_engine_v2.py +0 -401
- package/src/setup_validator.py +0 -482
- package/src/subscription_manager.py +0 -391
- package/src/tree/__init__.py +0 -59
- package/src/tree/builder.py +0 -185
- package/src/tree/nodes.py +0 -202
- package/src/tree/queries.py +0 -257
- package/src/tree/schema.py +0 -80
- package/src/tree_manager.py +0 -19
- package/src/trust/__init__.py +0 -45
- package/src/trust/constants.py +0 -66
- package/src/trust/queries.py +0 -157
- package/src/trust/schema.py +0 -95
- package/src/trust/scorer.py +0 -299
- package/src/trust/signals.py +0 -95
- package/src/trust_scorer.py +0 -44
- package/ui/app.js +0 -1588
- package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
- package/ui/js/graph-cytoscape.js +0 -1168
- package/ui/js/graph-d3-backup.js +0 -32
- package/ui/js/graph.js +0 -32
- package/ui_server.py +0 -286
- /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
- /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
- /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
- /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
- /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
- /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
- /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
- /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
- /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
- /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
- /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
- /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
- /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
- /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
- /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
- /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
- /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
- /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
- /package/{completions → ide/completions}/slm.bash +0 -0
- /package/{completions → ide/completions}/slm.zsh +0 -0
- /package/{configs → ide/configs}/cody-commands.json +0 -0
- /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
- /package/{install.ps1 → scripts/install.ps1} +0 -0
- /package/{install.sh → scripts/install.sh} +0 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""Self-enforcing inter-layer parameter constraints.
|
|
6
|
+
|
|
7
|
+
The mathematical layers have parameters that are coupled through
|
|
8
|
+
a constraint derived from a private seed. If anyone modifies the
|
|
9
|
+
parameters without knowing the constraint, performance degrades.
|
|
10
|
+
|
|
11
|
+
Constraint: alpha * kappa = C * beta
|
|
12
|
+
Where:
|
|
13
|
+
alpha = metric scaling parameter
|
|
14
|
+
kappa = curvature parameter
|
|
15
|
+
beta = inverse temperature
|
|
16
|
+
C = derived from private HMAC key
|
|
17
|
+
|
|
18
|
+
If the constraint is violated:
|
|
19
|
+
- Similarity calibration drifts
|
|
20
|
+
- Hierarchical distance fidelity drops
|
|
21
|
+
- Energy landscape flattens
|
|
22
|
+
- Overall retrieval quality drops 15-20%
|
|
23
|
+
|
|
24
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
25
|
+
License: MIT
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import hashlib
|
|
31
|
+
import hmac
|
|
32
|
+
import os
|
|
33
|
+
|
|
34
|
+
import numpy as np
|
|
35
|
+
|
|
36
|
+
# Hopfield inverse temperature (legacy constant, kept for DNA compatibility)
|
|
37
|
+
HOPFIELD_INVERSE_TEMP = 4.0
|
|
38
|
+
|
|
39
|
+
# Default seed — production should use env var SLM_DNA_SEED
|
|
40
|
+
_DNA_SEED = "qualixar-slm-alpha-dna-v1"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class MathematicalDNA:
|
|
44
|
+
"""Self-enforcing parameter constraint system.
|
|
45
|
+
|
|
46
|
+
Derives a coupling constant from a private seed and provides
|
|
47
|
+
utilities to:
|
|
48
|
+
- Get a parameter set satisfying the constraint.
|
|
49
|
+
- Verify whether arbitrary parameters satisfy it.
|
|
50
|
+
- Compute an integrity score (1.0 = perfect).
|
|
51
|
+
- Embed / detect computation fingerprints at the precision level.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
seed: Override the DNA seed. If ``None``, falls back to
|
|
55
|
+
``SLM_DNA_SEED`` env var, then the built-in default.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(self, seed: str | None = None) -> None:
|
|
59
|
+
self._seed = seed or os.environ.get("SLM_DNA_SEED", _DNA_SEED)
|
|
60
|
+
self._constraint_C = self._derive_constraint()
|
|
61
|
+
|
|
62
|
+
# ── Constraint derivation ─────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
def _derive_constraint(self) -> float:
|
|
65
|
+
"""Derive the inter-layer coupling constant from seed.
|
|
66
|
+
|
|
67
|
+
Uses HMAC-SHA256 to produce a deterministic float in [0.5, 2.0].
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Coupling constant C.
|
|
71
|
+
"""
|
|
72
|
+
h = hmac.new(
|
|
73
|
+
self._seed.encode("utf-8"),
|
|
74
|
+
b"layer-coupling",
|
|
75
|
+
hashlib.sha256,
|
|
76
|
+
)
|
|
77
|
+
hash_int = int(h.hexdigest()[:8], 16)
|
|
78
|
+
return 0.5 + (hash_int / 0xFFFFFFFF) * 1.5
|
|
79
|
+
|
|
80
|
+
# ── Parameter generation ──────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
def get_coupled_parameters(self) -> dict[str, float | bool]:
|
|
83
|
+
"""Get a parameter set satisfying the constraint.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Dict with ``fisher_alpha``, ``poincare_kappa``,
|
|
87
|
+
``hopfield_beta``, ``constraint_C``, and
|
|
88
|
+
``constraint_satisfied`` (always ``True``).
|
|
89
|
+
"""
|
|
90
|
+
beta = HOPFIELD_INVERSE_TEMP
|
|
91
|
+
target_product = self._constraint_C * beta
|
|
92
|
+
|
|
93
|
+
kappa = abs(self._constraint_C)
|
|
94
|
+
alpha = target_product / kappa if kappa > 1e-10 else 1.0
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
"fisher_alpha": float(alpha),
|
|
98
|
+
"poincare_kappa": float(kappa),
|
|
99
|
+
"hopfield_beta": float(beta),
|
|
100
|
+
"constraint_C": float(self._constraint_C),
|
|
101
|
+
"constraint_satisfied": True,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# ── Constraint verification ───────────────────────────────────
|
|
105
|
+
|
|
106
|
+
def verify_constraint(
|
|
107
|
+
self,
|
|
108
|
+
alpha: float,
|
|
109
|
+
kappa: float,
|
|
110
|
+
beta: float,
|
|
111
|
+
tolerance: float = 0.01,
|
|
112
|
+
) -> bool:
|
|
113
|
+
"""Check if parameters satisfy alpha * kappa ≈ C * beta.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
alpha: Metric scaling parameter.
|
|
117
|
+
kappa: Curvature parameter.
|
|
118
|
+
beta: Inverse temperature.
|
|
119
|
+
tolerance: Relative tolerance for the check.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
``True`` if the constraint is satisfied.
|
|
123
|
+
"""
|
|
124
|
+
lhs = alpha * kappa
|
|
125
|
+
rhs = self._constraint_C * beta
|
|
126
|
+
denom = max(abs(rhs), 1e-10)
|
|
127
|
+
return abs(lhs - rhs) / denom < tolerance
|
|
128
|
+
|
|
129
|
+
def compute_integrity_score(
|
|
130
|
+
self,
|
|
131
|
+
alpha: float,
|
|
132
|
+
kappa: float,
|
|
133
|
+
beta: float,
|
|
134
|
+
) -> float:
|
|
135
|
+
"""Compute integrity score in [0, 1]. 1.0 = perfect.
|
|
136
|
+
|
|
137
|
+
The score multiplies retrieval quality — violation degrades
|
|
138
|
+
performance through a sigmoid penalty.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
alpha: Metric scaling parameter.
|
|
142
|
+
kappa: Curvature parameter.
|
|
143
|
+
beta: Inverse temperature.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Integrity score in [0.0, 1.0].
|
|
147
|
+
"""
|
|
148
|
+
lhs = alpha * kappa
|
|
149
|
+
rhs = self._constraint_C * beta
|
|
150
|
+
denom = max(abs(rhs), 1e-10)
|
|
151
|
+
deviation = abs(lhs - rhs) / denom
|
|
152
|
+
# Sigmoid degradation: small deviations tolerated
|
|
153
|
+
return float(1.0 / (1.0 + 10.0 * deviation ** 2))
|
|
154
|
+
|
|
155
|
+
# ── Fingerprinting ────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
def embed_fingerprint(self, value: float, memory_id: int) -> float:
|
|
158
|
+
"""Embed a computation fingerprint at the precision level.
|
|
159
|
+
|
|
160
|
+
Modifies the 12th decimal place to encode a signature.
|
|
161
|
+
The fingerprint is below the noise floor and does not affect
|
|
162
|
+
retrieval quality but can be forensically detected.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
value: Original floating point value.
|
|
166
|
+
memory_id: Unique memory identifier for per-memory signing.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Value with embedded fingerprint.
|
|
170
|
+
"""
|
|
171
|
+
sig = hmac.new(
|
|
172
|
+
self._seed.encode("utf-8"),
|
|
173
|
+
f"fingerprint:{memory_id}".encode("utf-8"),
|
|
174
|
+
hashlib.sha256,
|
|
175
|
+
).hexdigest()
|
|
176
|
+
# 8-digit fingerprint from hash, mapped to [0, 1)
|
|
177
|
+
fp = int(sig[:8], 16) / 0xFFFFFFFF
|
|
178
|
+
scale = 1e-10
|
|
179
|
+
return value + fp * scale
|
|
180
|
+
|
|
181
|
+
def detect_fingerprint(
|
|
182
|
+
self,
|
|
183
|
+
value: float,
|
|
184
|
+
memory_id: int,
|
|
185
|
+
original: float,
|
|
186
|
+
) -> bool:
|
|
187
|
+
"""Check if a value carries our fingerprint.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
value: The potentially fingerprinted value.
|
|
191
|
+
memory_id: Memory ID used during embedding.
|
|
192
|
+
original: The original pre-fingerprint value.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
``True`` if the fingerprint is detected.
|
|
196
|
+
"""
|
|
197
|
+
expected = self.embed_fingerprint(original, memory_id)
|
|
198
|
+
return abs(value - expected) < 1e-14
|
|
199
|
+
|
|
200
|
+
def generate_dna_hash(self, memory_id: int) -> str:
|
|
201
|
+
"""Generate a unique DNA hash for a memory.
|
|
202
|
+
|
|
203
|
+
This hash can be stored alongside the memory for provenance.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
memory_id: Memory identifier.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Hex digest string (64 chars).
|
|
210
|
+
"""
|
|
211
|
+
return hmac.new(
|
|
212
|
+
self._seed.encode("utf-8"),
|
|
213
|
+
f"dna:{memory_id}".encode("utf-8"),
|
|
214
|
+
hashlib.sha256,
|
|
215
|
+
).hexdigest()
|
|
216
|
+
|
|
217
|
+
def verify_dna_hash(self, memory_id: int, dna_hash: str) -> bool:
|
|
218
|
+
"""Verify that a DNA hash matches the expected value.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
memory_id: Memory identifier.
|
|
222
|
+
dna_hash: Hash to verify.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
``True`` if the hash is valid.
|
|
226
|
+
"""
|
|
227
|
+
expected = self.generate_dna_hash(memory_id)
|
|
228
|
+
return hmac.compare_digest(expected, dna_hash)
|
|
229
|
+
|
|
230
|
+
# ── Properties ────────────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def constraint_C(self) -> float:
|
|
234
|
+
"""The derived coupling constant."""
|
|
235
|
+
return self._constraint_C
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""Cryptographic signing for content attribution and tamper detection.
|
|
6
|
+
|
|
7
|
+
Provides HMAC-SHA256 signing so that every piece of content produced
|
|
8
|
+
by the system carries a verifiable proof of origin. Verification
|
|
9
|
+
detects any modification to the content after signing.
|
|
10
|
+
|
|
11
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
12
|
+
License: MIT
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import hashlib
|
|
18
|
+
import hmac
|
|
19
|
+
import os
|
|
20
|
+
from datetime import datetime, timezone
|
|
21
|
+
from typing import Dict
|
|
22
|
+
|
|
23
|
+
def _get_or_create_key() -> str:
|
|
24
|
+
"""Load key from env or generate a persistent random one."""
|
|
25
|
+
env_key = os.environ.get("SLM_SIGNER_KEY")
|
|
26
|
+
if env_key:
|
|
27
|
+
return env_key
|
|
28
|
+
key_path = os.path.expanduser("~/.superlocalmemory/.signer_key")
|
|
29
|
+
try:
|
|
30
|
+
with open(key_path) as f:
|
|
31
|
+
return f.read().strip()
|
|
32
|
+
except FileNotFoundError:
|
|
33
|
+
import secrets
|
|
34
|
+
key = secrets.token_hex(32)
|
|
35
|
+
os.makedirs(os.path.dirname(key_path), exist_ok=True)
|
|
36
|
+
with open(key_path, "w") as f:
|
|
37
|
+
f.write(key)
|
|
38
|
+
os.chmod(key_path, 0o600)
|
|
39
|
+
return key
|
|
40
|
+
|
|
41
|
+
_DEFAULT_KEY: str = _get_or_create_key()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class QualixarSigner:
|
|
45
|
+
"""Signs content with HMAC-SHA256 for tamper-proof attribution.
|
|
46
|
+
|
|
47
|
+
Typical usage::
|
|
48
|
+
|
|
49
|
+
signer = QualixarSigner()
|
|
50
|
+
attribution = signer.sign("some content")
|
|
51
|
+
assert signer.verify("some content", attribution) is True
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
secret_key: Shared secret used for HMAC computation.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
# ---- Branding constants (single source of truth) ----
|
|
58
|
+
_PLATFORM: str = "Qualixar"
|
|
59
|
+
_AUTHOR: str = "Varun Pratap Bhardwaj"
|
|
60
|
+
_AUTHOR_URL: str = "https://varunpratap.com"
|
|
61
|
+
_LICENSE: str = "MIT"
|
|
62
|
+
|
|
63
|
+
def __init__(self, secret_key: str = _DEFAULT_KEY) -> None:
|
|
64
|
+
if not secret_key:
|
|
65
|
+
raise ValueError("secret_key must be a non-empty string")
|
|
66
|
+
self._secret_key: bytes = secret_key.encode("utf-8")
|
|
67
|
+
|
|
68
|
+
# ------------------------------------------------------------------
|
|
69
|
+
# Public API
|
|
70
|
+
# ------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
def sign(self, content: str) -> Dict[str, str]:
|
|
73
|
+
"""Sign *content* and return attribution metadata.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
content: The text to sign.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
A dict containing:
|
|
80
|
+
|
|
81
|
+
- ``platform`` – always ``"Qualixar"``
|
|
82
|
+
- ``author`` – always ``"Varun Pratap Bhardwaj"``
|
|
83
|
+
- ``license`` – always ``"MIT"``
|
|
84
|
+
- ``content_hash`` – SHA-256 hex digest of *content*
|
|
85
|
+
- ``signature`` – HMAC-SHA256 hex digest (key-dependent)
|
|
86
|
+
- ``timestamp`` – ISO 8601 UTC timestamp
|
|
87
|
+
"""
|
|
88
|
+
content_bytes = content.encode("utf-8")
|
|
89
|
+
content_hash = hashlib.sha256(content_bytes).hexdigest()
|
|
90
|
+
signature = hmac.new(
|
|
91
|
+
self._secret_key, content_bytes, hashlib.sha256
|
|
92
|
+
).hexdigest()
|
|
93
|
+
timestamp = datetime.now(tz=timezone.utc).isoformat()
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
"platform": self._PLATFORM,
|
|
97
|
+
"author": self._AUTHOR,
|
|
98
|
+
"license": self._LICENSE,
|
|
99
|
+
"content_hash": content_hash,
|
|
100
|
+
"signature": signature,
|
|
101
|
+
"timestamp": timestamp,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
def verify(self, content: str, attribution: Dict[str, str]) -> bool:
|
|
105
|
+
"""Verify that *content* matches its attribution signature.
|
|
106
|
+
|
|
107
|
+
Checks both the SHA-256 content hash and the HMAC-SHA256
|
|
108
|
+
signature. Returns ``False`` if either has been tampered with.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
content: The text to verify.
|
|
112
|
+
attribution: The dict returned by :meth:`sign`.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
``True`` if valid, ``False`` if tampered or mismatched.
|
|
116
|
+
"""
|
|
117
|
+
content_bytes = content.encode("utf-8")
|
|
118
|
+
|
|
119
|
+
# 1. Verify content hash
|
|
120
|
+
expected_hash = hashlib.sha256(content_bytes).hexdigest()
|
|
121
|
+
if not hmac.compare_digest(
|
|
122
|
+
expected_hash, attribution.get("content_hash", "")
|
|
123
|
+
):
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
# 2. Verify HMAC signature
|
|
127
|
+
expected_sig = hmac.new(
|
|
128
|
+
self._secret_key, content_bytes, hashlib.sha256
|
|
129
|
+
).hexdigest()
|
|
130
|
+
if not hmac.compare_digest(
|
|
131
|
+
expected_sig, attribution.get("signature", "")
|
|
132
|
+
):
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
return True
|
|
136
|
+
|
|
137
|
+
@staticmethod
|
|
138
|
+
def get_attribution() -> Dict[str, str]:
|
|
139
|
+
"""Return basic (unsigned) attribution metadata.
|
|
140
|
+
|
|
141
|
+
This is a convenience method for embedding attribution in
|
|
142
|
+
contexts where full signing is not needed (e.g., file headers).
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
A dict with ``platform``, ``author``, ``author_url``, and
|
|
146
|
+
``license`` keys.
|
|
147
|
+
"""
|
|
148
|
+
return {
|
|
149
|
+
"platform": QualixarSigner._PLATFORM,
|
|
150
|
+
"author": QualixarSigner._AUTHOR,
|
|
151
|
+
"author_url": QualixarSigner._AUTHOR_URL,
|
|
152
|
+
"license": QualixarSigner._LICENSE,
|
|
153
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""Steganographic watermarking using zero-width Unicode characters.
|
|
6
|
+
|
|
7
|
+
Embeds an invisible binary payload (derived from a key string) into
|
|
8
|
+
visible text. The watermark is undetectable to end users but can be
|
|
9
|
+
extracted programmatically to prove provenance.
|
|
10
|
+
|
|
11
|
+
Encoding scheme
|
|
12
|
+
---------------
|
|
13
|
+
1. Convert the key to binary (each char -> 8-bit representation).
|
|
14
|
+
2. Map binary digits to zero-width characters:
|
|
15
|
+
|
|
16
|
+
- ``0`` -> U+200B (zero-width space)
|
|
17
|
+
- ``1`` -> U+200C (zero-width non-joiner)
|
|
18
|
+
|
|
19
|
+
3. Frame the bit sequence with markers:
|
|
20
|
+
|
|
21
|
+
- Start: U+FEFF (byte-order mark)
|
|
22
|
+
- End: U+200D (zero-width joiner)
|
|
23
|
+
|
|
24
|
+
4. Insert the entire encoded sequence immediately after the first
|
|
25
|
+
visible character of the host text.
|
|
26
|
+
|
|
27
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
28
|
+
License: MIT
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
from typing import Optional
|
|
34
|
+
|
|
35
|
+
# ---- Zero-width character constants ----
|
|
36
|
+
_BIT_ZERO: str = "\u200b" # Zero-width space
|
|
37
|
+
_BIT_ONE: str = "\u200c" # Zero-width non-joiner
|
|
38
|
+
_END_MARKER: str = "\u200d" # Zero-width joiner
|
|
39
|
+
_START_MARKER: str = "\ufeff" # Byte-order mark
|
|
40
|
+
|
|
41
|
+
# All zero-width characters used by this module (for stripping).
|
|
42
|
+
_ALL_ZW: set[str] = {_BIT_ZERO, _BIT_ONE, _END_MARKER, _START_MARKER}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class QualixarWatermark:
|
|
46
|
+
"""Embed and detect invisible watermarks in text.
|
|
47
|
+
|
|
48
|
+
Typical usage::
|
|
49
|
+
|
|
50
|
+
wm = QualixarWatermark()
|
|
51
|
+
watermarked = wm.embed("Hello world")
|
|
52
|
+
assert wm.detect(watermarked) is True
|
|
53
|
+
assert wm.extract(watermarked) == "qualixar"
|
|
54
|
+
assert wm.strip(watermarked) == "Hello world"
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
key: The string payload to embed. On extraction, this exact
|
|
58
|
+
string is recovered if the watermark is intact.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(self, key: str = "qualixar") -> None:
|
|
62
|
+
if not key:
|
|
63
|
+
raise ValueError("key must be a non-empty string")
|
|
64
|
+
self._key: str = key
|
|
65
|
+
self._encoded: str = self._encode_key(key)
|
|
66
|
+
|
|
67
|
+
# ------------------------------------------------------------------
|
|
68
|
+
# Public API
|
|
69
|
+
# ------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
def embed(self, text: str) -> str:
|
|
72
|
+
"""Embed an invisible watermark into *text*.
|
|
73
|
+
|
|
74
|
+
The returned string is visually identical to the original when
|
|
75
|
+
rendered — zero-width characters are invisible in all standard
|
|
76
|
+
fonts and terminals.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
text: The visible text to watermark.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
A new string containing the embedded watermark. If *text*
|
|
83
|
+
is empty, returns the empty string unchanged (nowhere to
|
|
84
|
+
insert the payload).
|
|
85
|
+
"""
|
|
86
|
+
if not text:
|
|
87
|
+
return text
|
|
88
|
+
|
|
89
|
+
# Insert the encoded payload after the first visible character.
|
|
90
|
+
return text[0] + self._encoded + text[1:]
|
|
91
|
+
|
|
92
|
+
def detect(self, text: str) -> bool:
|
|
93
|
+
"""Check whether *text* contains a valid watermark for this key.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
text: The text to inspect.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
``True`` if the watermark is present and matches the key,
|
|
100
|
+
``False`` otherwise.
|
|
101
|
+
"""
|
|
102
|
+
extracted = self.extract(text)
|
|
103
|
+
return extracted == self._key
|
|
104
|
+
|
|
105
|
+
def extract(self, text: str) -> Optional[str]:
|
|
106
|
+
"""Extract the watermark payload from *text*.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
text: The text to inspect.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
The decoded key string if a valid watermark is found,
|
|
113
|
+
``None`` if no watermark is present or it is malformed.
|
|
114
|
+
"""
|
|
115
|
+
start_idx = text.find(_START_MARKER)
|
|
116
|
+
if start_idx == -1:
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
end_idx = text.find(_END_MARKER, start_idx + 1)
|
|
120
|
+
if end_idx == -1:
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
payload = text[start_idx + 1 : end_idx]
|
|
124
|
+
return self._decode_payload(payload)
|
|
125
|
+
|
|
126
|
+
def strip(self, text: str) -> str:
|
|
127
|
+
"""Remove all zero-width characters, returning clean text.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
text: The potentially watermarked text.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
A copy of *text* with every zero-width character removed.
|
|
134
|
+
"""
|
|
135
|
+
return "".join(ch for ch in text if ch not in _ALL_ZW)
|
|
136
|
+
|
|
137
|
+
# ------------------------------------------------------------------
|
|
138
|
+
# Internal helpers
|
|
139
|
+
# ------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def _encode_key(key: str) -> str:
|
|
143
|
+
"""Convert *key* to a framed zero-width character sequence.
|
|
144
|
+
|
|
145
|
+
Each character of *key* is converted to 8 binary digits, each
|
|
146
|
+
digit is mapped to a zero-width char, and the whole thing is
|
|
147
|
+
wrapped with start/end markers.
|
|
148
|
+
"""
|
|
149
|
+
bits: list[str] = []
|
|
150
|
+
for char in key:
|
|
151
|
+
byte_val = ord(char)
|
|
152
|
+
for bit_pos in range(7, -1, -1):
|
|
153
|
+
if (byte_val >> bit_pos) & 1:
|
|
154
|
+
bits.append(_BIT_ONE)
|
|
155
|
+
else:
|
|
156
|
+
bits.append(_BIT_ZERO)
|
|
157
|
+
|
|
158
|
+
return _START_MARKER + "".join(bits) + _END_MARKER
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def _decode_payload(payload: str) -> Optional[str]:
|
|
162
|
+
"""Decode a zero-width bit sequence back to a string.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
payload: The raw zero-width characters between the start
|
|
166
|
+
and end markers (exclusive).
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
The decoded string, or ``None`` if the payload length is
|
|
170
|
+
not a multiple of 8 or contains unexpected characters.
|
|
171
|
+
"""
|
|
172
|
+
if len(payload) % 8 != 0:
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
chars: list[str] = []
|
|
176
|
+
for i in range(0, len(payload), 8):
|
|
177
|
+
byte_bits = payload[i : i + 8]
|
|
178
|
+
byte_val = 0
|
|
179
|
+
for bit_char in byte_bits:
|
|
180
|
+
byte_val <<= 1
|
|
181
|
+
if bit_char == _BIT_ONE:
|
|
182
|
+
byte_val |= 1
|
|
183
|
+
elif bit_char == _BIT_ZERO:
|
|
184
|
+
pass # bit stays 0
|
|
185
|
+
else:
|
|
186
|
+
return None # unexpected character in payload
|
|
187
|
+
chars.append(chr(byte_val))
|
|
188
|
+
|
|
189
|
+
return "".join(chars)
|