@trac3r/oh-my-god 2.2.11
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/CHANGELOG.md +188 -0
- package/INSTALL-VERIFICATION-INDEX.md +51 -0
- package/LICENSE +21 -0
- package/OMG-setup.sh +2549 -0
- package/QUICK-REFERENCE.md +58 -0
- package/README.md +207 -0
- package/agents/__init__.py +1 -0
- package/agents/__pycache__/model_roles.cpython-313.pyc +0 -0
- package/agents/_model_roles.yaml +26 -0
- package/agents/designer.md +67 -0
- package/agents/explore.md +60 -0
- package/agents/model_roles.py +196 -0
- package/agents/omg-api-builder.md +23 -0
- package/agents/omg-architect-mode.md +41 -0
- package/agents/omg-architect.md +13 -0
- package/agents/omg-backend-engineer.md +41 -0
- package/agents/omg-critic.md +16 -0
- package/agents/omg-database-engineer.md +41 -0
- package/agents/omg-escalation-router.md +17 -0
- package/agents/omg-executor.md +12 -0
- package/agents/omg-frontend-designer.md +41 -0
- package/agents/omg-implement-mode.md +49 -0
- package/agents/omg-infra-engineer.md +41 -0
- package/agents/omg-qa-tester.md +16 -0
- package/agents/omg-research-mode.md +41 -0
- package/agents/omg-security-auditor.md +41 -0
- package/agents/omg-testing-engineer.md +41 -0
- package/agents/plan.md +80 -0
- package/agents/quick_task.md +64 -0
- package/agents/reviewer.md +83 -0
- package/agents/task.md +71 -0
- package/bin/omg +41 -0
- package/commands/OMG:ai-commit.md +113 -0
- package/commands/OMG:api-twin.md +22 -0
- package/commands/OMG:arch.md +313 -0
- package/commands/OMG:browser.md +29 -0
- package/commands/OMG:ccg.md +22 -0
- package/commands/OMG:compat.md +57 -0
- package/commands/OMG:cost.md +181 -0
- package/commands/OMG:crazy.md +125 -0
- package/commands/OMG:create-agent.md +183 -0
- package/commands/OMG:deep-plan.md +18 -0
- package/commands/OMG:deps.md +248 -0
- package/commands/OMG:diagnose-plugins.md +33 -0
- package/commands/OMG:doctor.md +37 -0
- package/commands/OMG:domain-init.md +11 -0
- package/commands/OMG:escalate.md +52 -0
- package/commands/OMG:forge.md +103 -0
- package/commands/OMG:health-check.md +48 -0
- package/commands/OMG:init.md +134 -0
- package/commands/OMG:issue.md +56 -0
- package/commands/OMG:mode.md +44 -0
- package/commands/OMG:playwright.md +17 -0
- package/commands/OMG:preflight.md +26 -0
- package/commands/OMG:preset.md +49 -0
- package/commands/OMG:profile-review.md +58 -0
- package/commands/OMG:project-init.md +11 -0
- package/commands/OMG:ralph-start.md +43 -0
- package/commands/OMG:ralph-stop.md +23 -0
- package/commands/OMG:security-check.md +28 -0
- package/commands/OMG:session-branch.md +101 -0
- package/commands/OMG:session-fork.md +57 -0
- package/commands/OMG:session-merge.md +138 -0
- package/commands/OMG:setup.md +82 -0
- package/commands/OMG:ship.md +18 -0
- package/commands/OMG:stats.md +225 -0
- package/commands/OMG:teams.md +54 -0
- package/commands/OMG:theme.md +44 -0
- package/commands/OMG:validate.md +59 -0
- package/commands/__init__.py +1 -0
- package/docs/command-surface.md +55 -0
- package/docs/install/claude-code.md +53 -0
- package/docs/install/codex.md +45 -0
- package/docs/install/gemini.md +43 -0
- package/docs/install/github-action.md +81 -0
- package/docs/install/github-app-required-checks.md +107 -0
- package/docs/install/github-app.md +161 -0
- package/docs/install/kimi.md +43 -0
- package/docs/install/opencode.md +38 -0
- package/docs/proof.md +182 -0
- package/hooks/__init__.py +0 -0
- package/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_agent_registry.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_analytics.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_budget.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_common.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_compression_optimizer.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_cost_ledger.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_learnings.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_memory.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_post_write.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_protected_context.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_token_counter.cpython-313.pyc +0 -0
- package/hooks/__pycache__/branch_manager.cpython-313.pyc +0 -0
- package/hooks/__pycache__/budget_governor.cpython-313.pyc +0 -0
- package/hooks/__pycache__/circuit-breaker.cpython-313.pyc +0 -0
- package/hooks/__pycache__/compression_feedback.cpython-313.pyc +0 -0
- package/hooks/__pycache__/config-guard.cpython-313.pyc +0 -0
- package/hooks/__pycache__/context_pressure.cpython-313.pyc +0 -0
- package/hooks/__pycache__/credential_store.cpython-313.pyc +0 -0
- package/hooks/__pycache__/fetch-rate-limits.cpython-313.pyc +0 -0
- package/hooks/__pycache__/firewall.cpython-313.pyc +0 -0
- package/hooks/__pycache__/hashline-formatter-bridge.cpython-313.pyc +0 -0
- package/hooks/__pycache__/hashline-injector.cpython-313.pyc +0 -0
- package/hooks/__pycache__/hashline-validator.cpython-313.pyc +0 -0
- package/hooks/__pycache__/idle-detector.cpython-313.pyc +0 -0
- package/hooks/__pycache__/instructions-loaded.cpython-313.pyc +0 -0
- package/hooks/__pycache__/intentgate-keyword-detector.cpython-313.pyc +0 -0
- package/hooks/__pycache__/magic-keyword-router.cpython-313.pyc +0 -0
- package/hooks/__pycache__/policy_engine.cpython-313.pyc +0 -0
- package/hooks/__pycache__/post-tool-failure.cpython-313.pyc +0 -0
- package/hooks/__pycache__/post-write.cpython-313.pyc +0 -0
- package/hooks/__pycache__/post_write.cpython-313.pyc +0 -0
- package/hooks/__pycache__/pre-compact.cpython-313.pyc +0 -0
- package/hooks/__pycache__/pre-tool-inject.cpython-313.pyc +0 -0
- package/hooks/__pycache__/prompt-enhancer.cpython-313.pyc +0 -0
- package/hooks/__pycache__/quality-runner.cpython-313.pyc +0 -0
- package/hooks/__pycache__/query.cpython-313.pyc +0 -0
- package/hooks/__pycache__/secret-guard.cpython-313.pyc +0 -0
- package/hooks/__pycache__/secret_audit.cpython-313.pyc +0 -0
- package/hooks/__pycache__/security_validators.cpython-313.pyc +0 -0
- package/hooks/__pycache__/session-end-capture.cpython-313.pyc +0 -0
- package/hooks/__pycache__/session-start.cpython-313.pyc +0 -0
- package/hooks/__pycache__/setup_wizard.cpython-313.pyc +0 -0
- package/hooks/__pycache__/shadow_manager.cpython-313.pyc +0 -0
- package/hooks/__pycache__/state_migration.cpython-313.pyc +0 -0
- package/hooks/__pycache__/stop-gate.cpython-313.pyc +0 -0
- package/hooks/__pycache__/stop_dispatcher.cpython-313.pyc +0 -0
- package/hooks/__pycache__/tdd-gate.cpython-313.pyc +0 -0
- package/hooks/__pycache__/terms-guard.cpython-313.pyc +0 -0
- package/hooks/__pycache__/test-validator.cpython-313.pyc +0 -0
- package/hooks/__pycache__/test_generator_hook.cpython-313.pyc +0 -0
- package/hooks/__pycache__/todo-state-tracker.cpython-313.pyc +0 -0
- package/hooks/__pycache__/tool-ledger.cpython-313.pyc +0 -0
- package/hooks/__pycache__/trust_review.cpython-313.pyc +0 -0
- package/hooks/__pycache__/user-prompt-submit.cpython-313.pyc +0 -0
- package/hooks/_agent_registry.py +481 -0
- package/hooks/_analytics.py +291 -0
- package/hooks/_budget.py +31 -0
- package/hooks/_common.py +761 -0
- package/hooks/_compression_optimizer.py +119 -0
- package/hooks/_cost_ledger.py +176 -0
- package/hooks/_learnings.py +126 -0
- package/hooks/_memory.py +103 -0
- package/hooks/_post_write.py +46 -0
- package/hooks/_protected_context.py +150 -0
- package/hooks/_token_counter.py +221 -0
- package/hooks/branch_manager.py +255 -0
- package/hooks/budget_governor.py +326 -0
- package/hooks/circuit-breaker.py +270 -0
- package/hooks/compression_feedback.py +254 -0
- package/hooks/config-guard.py +193 -0
- package/hooks/context_pressure.py +119 -0
- package/hooks/credential_store.py +970 -0
- package/hooks/fetch-rate-limits.py +212 -0
- package/hooks/firewall.py +323 -0
- package/hooks/hashline-formatter-bridge.py +224 -0
- package/hooks/hashline-injector.py +273 -0
- package/hooks/hashline-validator.py +216 -0
- package/hooks/idle-detector.py +97 -0
- package/hooks/instructions-loaded.py +26 -0
- package/hooks/intentgate-keyword-detector.py +200 -0
- package/hooks/magic-keyword-router.py +195 -0
- package/hooks/policy_engine.py +767 -0
- package/hooks/post-tool-failure.py +19 -0
- package/hooks/post-write.py +233 -0
- package/hooks/pre-compact.py +470 -0
- package/hooks/pre-tool-inject.py +98 -0
- package/hooks/prompt-enhancer.py +879 -0
- package/hooks/quality-runner.py +191 -0
- package/hooks/query.py +512 -0
- package/hooks/secret-guard.py +120 -0
- package/hooks/secret_audit.py +144 -0
- package/hooks/security_validators.py +93 -0
- package/hooks/session-end-capture.py +505 -0
- package/hooks/session-start.py +261 -0
- package/hooks/setup_wizard.py +1101 -0
- package/hooks/shadow_manager.py +476 -0
- package/hooks/state_migration.py +228 -0
- package/hooks/stop-gate.py +7 -0
- package/hooks/stop_dispatcher.py +1259 -0
- package/hooks/tdd-gate.py +10 -0
- package/hooks/terms-guard.py +98 -0
- package/hooks/test-validator.py +462 -0
- package/hooks/test_generator_hook.py +123 -0
- package/hooks/todo-state-tracker.py +114 -0
- package/hooks/tool-ledger.py +165 -0
- package/hooks/trust_review.py +662 -0
- package/hooks/user-prompt-submit.py +12 -0
- package/hud/omg-hud.mjs +1571 -0
- package/lab/__init__.py +1 -0
- package/lab/__pycache__/__init__.cpython-313.pyc +0 -0
- package/lab/__pycache__/axolotl_adapter.cpython-313.pyc +0 -0
- package/lab/__pycache__/forge_runner.cpython-313.pyc +0 -0
- package/lab/__pycache__/gazebo_adapter.cpython-313.pyc +0 -0
- package/lab/__pycache__/isaac_gym_adapter.cpython-313.pyc +0 -0
- package/lab/__pycache__/mock_isaac_env.cpython-313.pyc +0 -0
- package/lab/__pycache__/pipeline.cpython-313.pyc +0 -0
- package/lab/__pycache__/policies.cpython-313.pyc +0 -0
- package/lab/__pycache__/pybullet_adapter.cpython-313.pyc +0 -0
- package/lab/axolotl_adapter.py +531 -0
- package/lab/forge_runner.py +103 -0
- package/lab/gazebo_adapter.py +168 -0
- package/lab/isaac_gym_adapter.py +190 -0
- package/lab/mock_isaac_env.py +47 -0
- package/lab/pipeline.py +712 -0
- package/lab/policies.py +52 -0
- package/lab/pybullet_adapter.py +192 -0
- package/package.json +61 -0
- package/plugins/README.md +78 -0
- package/plugins/__init__.py +1 -0
- package/plugins/__pycache__/__init__.cpython-313.pyc +0 -0
- package/plugins/advanced/commands/OMG-code-review.md +114 -0
- package/plugins/advanced/commands/OMG-deep-plan.md +266 -0
- package/plugins/advanced/commands/OMG-handoff.md +115 -0
- package/plugins/advanced/commands/OMG-learn.md +110 -0
- package/plugins/advanced/commands/OMG-maintainer.md +31 -0
- package/plugins/advanced/commands/OMG-ralph-start.md +43 -0
- package/plugins/advanced/commands/OMG-ralph-stop.md +23 -0
- package/plugins/advanced/commands/OMG-security-review.md +16 -0
- package/plugins/advanced/commands/OMG-sequential-thinking.md +20 -0
- package/plugins/advanced/commands/OMG-ship.md +46 -0
- package/plugins/advanced/commands/OMG:code-review.md +114 -0
- package/plugins/advanced/commands/OMG:deep-plan.md +266 -0
- package/plugins/advanced/commands/OMG:handoff.md +115 -0
- package/plugins/advanced/commands/OMG:learn.md +110 -0
- package/plugins/advanced/commands/OMG:maintainer.md +31 -0
- package/plugins/advanced/commands/OMG:ralph-start.md +43 -0
- package/plugins/advanced/commands/OMG:ralph-stop.md +23 -0
- package/plugins/advanced/commands/OMG:security-review.md +16 -0
- package/plugins/advanced/commands/OMG:sequential-thinking.md +20 -0
- package/plugins/advanced/commands/OMG:ship.md +46 -0
- package/plugins/advanced/plugin.json +104 -0
- package/plugins/core/plugin.json +204 -0
- package/plugins/dephealth/__init__.py +0 -0
- package/plugins/dephealth/__pycache__/__init__.cpython-313.pyc +0 -0
- package/plugins/dephealth/__pycache__/cve_scanner.cpython-313.pyc +0 -0
- package/plugins/dephealth/__pycache__/license_checker.cpython-313.pyc +0 -0
- package/plugins/dephealth/__pycache__/manifest_detector.cpython-313.pyc +0 -0
- package/plugins/dephealth/__pycache__/vuln_analyzer.cpython-313.pyc +0 -0
- package/plugins/dephealth/cve_scanner.py +279 -0
- package/plugins/dephealth/license_checker.py +135 -0
- package/plugins/dephealth/manifest_detector.py +423 -0
- package/plugins/dephealth/vuln_analyzer.py +176 -0
- package/plugins/testgen/__init__.py +0 -0
- package/plugins/testgen/__pycache__/__init__.cpython-313.pyc +0 -0
- package/plugins/testgen/__pycache__/codamosa_engine.cpython-313.pyc +0 -0
- package/plugins/testgen/__pycache__/edge_case_synthesizer.cpython-313.pyc +0 -0
- package/plugins/testgen/__pycache__/framework_detector.cpython-313.pyc +0 -0
- package/plugins/testgen/__pycache__/skeleton_generator.cpython-313.pyc +0 -0
- package/plugins/testgen/codamosa_engine.py +402 -0
- package/plugins/testgen/edge_case_synthesizer.py +184 -0
- package/plugins/testgen/framework_detector.py +271 -0
- package/plugins/testgen/skeleton_generator.py +219 -0
- package/plugins/viz/__init__.py +0 -0
- package/plugins/viz/__pycache__/__init__.cpython-313.pyc +0 -0
- package/plugins/viz/__pycache__/ast_parser.cpython-313.pyc +0 -0
- package/plugins/viz/__pycache__/diagram_generator.cpython-313.pyc +0 -0
- package/plugins/viz/__pycache__/graph_builder.cpython-313.pyc +0 -0
- package/plugins/viz/__pycache__/native_parsers.cpython-313.pyc +0 -0
- package/plugins/viz/__pycache__/regex_parser.cpython-313.pyc +0 -0
- package/plugins/viz/ast_parser.py +139 -0
- package/plugins/viz/diagram_generator.py +192 -0
- package/plugins/viz/graph_builder.py +444 -0
- package/plugins/viz/native_parsers.py +259 -0
- package/plugins/viz/regex_parser.py +112 -0
- package/pyproject.toml +143 -0
- package/registry/__init__.py +1 -0
- package/registry/__pycache__/__init__.cpython-313.pyc +0 -0
- package/registry/__pycache__/approval_artifact.cpython-313.pyc +0 -0
- package/registry/__pycache__/verify_artifact.cpython-313.pyc +0 -0
- package/registry/approval_artifact.py +236 -0
- package/registry/bundles/algorithms.yaml +45 -0
- package/registry/bundles/api-twin.yaml +48 -0
- package/registry/bundles/ast-pack.yaml +80 -0
- package/registry/bundles/claim-judge.yaml +49 -0
- package/registry/bundles/control-plane.yaml +192 -0
- package/registry/bundles/data-lineage.yaml +47 -0
- package/registry/bundles/delta-classifier.yaml +47 -0
- package/registry/bundles/eval-gate.yaml +47 -0
- package/registry/bundles/hash-edit.yaml +73 -0
- package/registry/bundles/health.yaml +45 -0
- package/registry/bundles/hook-governor.yaml +101 -0
- package/registry/bundles/incident-replay.yaml +47 -0
- package/registry/bundles/lsp-pack.yaml +80 -0
- package/registry/bundles/mcp-fabric.yaml +53 -0
- package/registry/bundles/plan-council.yaml +56 -0
- package/registry/bundles/preflight.yaml +48 -0
- package/registry/bundles/proof-gate.yaml +49 -0
- package/registry/bundles/remote-supervisor.yaml +49 -0
- package/registry/bundles/robotics.yaml +45 -0
- package/registry/bundles/secure-worktree-pipeline.yaml +69 -0
- package/registry/bundles/security-check.yaml +50 -0
- package/registry/bundles/terminal-lane.yaml +61 -0
- package/registry/bundles/test-intent-lock.yaml +49 -0
- package/registry/bundles/tracebank.yaml +47 -0
- package/registry/bundles/vision.yaml +45 -0
- package/registry/omg-capability.schema.json +378 -0
- package/registry/policy-packs/airgapped.lock.json +11 -0
- package/registry/policy-packs/airgapped.signature.json +10 -0
- package/registry/policy-packs/airgapped.yaml +16 -0
- package/registry/policy-packs/fintech.lock.json +11 -0
- package/registry/policy-packs/fintech.signature.json +10 -0
- package/registry/policy-packs/fintech.yaml +15 -0
- package/registry/policy-packs/locked-prod.lock.json +11 -0
- package/registry/policy-packs/locked-prod.signature.json +10 -0
- package/registry/policy-packs/locked-prod.yaml +18 -0
- package/registry/trusted_signers.json +44 -0
- package/registry/verify_artifact.py +493 -0
- package/runtime/__init__.py +36 -0
- package/runtime/__pycache__/__init__.cpython-313.pyc +0 -0
- package/runtime/__pycache__/adoption.cpython-313.pyc +0 -0
- package/runtime/__pycache__/agent_selector.cpython-313.pyc +0 -0
- package/runtime/__pycache__/api_twin.cpython-313.pyc +0 -0
- package/runtime/__pycache__/architecture_signal.cpython-313.pyc +0 -0
- package/runtime/__pycache__/artifact_parsers.cpython-313.pyc +0 -0
- package/runtime/__pycache__/asset_loader.cpython-313.pyc +0 -0
- package/runtime/__pycache__/background_verification.cpython-313.pyc +0 -0
- package/runtime/__pycache__/budget_envelopes.cpython-313.pyc +0 -0
- package/runtime/__pycache__/business_workflow.cpython-313.pyc +0 -0
- package/runtime/__pycache__/canonical_surface.cpython-313.pyc +0 -0
- package/runtime/__pycache__/canonical_taxonomy.cpython-313.pyc +0 -0
- package/runtime/__pycache__/claim_judge.cpython-313.pyc +0 -0
- package/runtime/__pycache__/cli_provider.cpython-313.pyc +0 -0
- package/runtime/__pycache__/compat.cpython-313.pyc +0 -0
- package/runtime/__pycache__/complexity_scorer.cpython-313.pyc +0 -0
- package/runtime/__pycache__/compliance_governor.cpython-313.pyc +0 -0
- package/runtime/__pycache__/config_transaction.cpython-313.pyc +0 -0
- package/runtime/__pycache__/context_compiler.cpython-313.pyc +0 -0
- package/runtime/__pycache__/context_engine.cpython-313.pyc +0 -0
- package/runtime/__pycache__/context_limits.cpython-313.pyc +0 -0
- package/runtime/__pycache__/contract_compiler.cpython-313.pyc +0 -0
- package/runtime/__pycache__/custom_agent_loader.cpython-313.pyc +0 -0
- package/runtime/__pycache__/data_lineage.cpython-313.pyc +0 -0
- package/runtime/__pycache__/defense_state.cpython-313.pyc +0 -0
- package/runtime/__pycache__/delta_classifier.cpython-313.pyc +0 -0
- package/runtime/__pycache__/dispatcher.cpython-313.pyc +0 -0
- package/runtime/__pycache__/doc_generator.cpython-313.pyc +0 -0
- package/runtime/__pycache__/domain_packs.cpython-313.pyc +0 -0
- package/runtime/__pycache__/ecosystem.cpython-313.pyc +0 -0
- package/runtime/__pycache__/equalizer.cpython-313.pyc +0 -0
- package/runtime/__pycache__/eval_gate.cpython-313.pyc +0 -0
- package/runtime/__pycache__/evidence_narrator.cpython-313.pyc +0 -0
- package/runtime/__pycache__/evidence_query.cpython-313.pyc +0 -0
- package/runtime/__pycache__/evidence_registry.cpython-313.pyc +0 -0
- package/runtime/__pycache__/evidence_requirements.cpython-313.pyc +0 -0
- package/runtime/__pycache__/exec_kernel.cpython-313.pyc +0 -0
- package/runtime/__pycache__/explainer_formatter.cpython-313.pyc +0 -0
- package/runtime/__pycache__/feature_registry.cpython-313.pyc +0 -0
- package/runtime/__pycache__/forge_agents.cpython-313.pyc +0 -0
- package/runtime/__pycache__/forge_contracts.cpython-313.pyc +0 -0
- package/runtime/__pycache__/forge_domains.cpython-313.pyc +0 -0
- package/runtime/__pycache__/forge_run_id.cpython-313.pyc +0 -0
- package/runtime/__pycache__/github_integration.cpython-313.pyc +0 -0
- package/runtime/__pycache__/github_review_bot.cpython-313.pyc +0 -0
- package/runtime/__pycache__/github_review_contract.cpython-313.pyc +0 -0
- package/runtime/__pycache__/github_review_formatter.cpython-313.pyc +0 -0
- package/runtime/__pycache__/guide_assert.cpython-313.pyc +0 -0
- package/runtime/__pycache__/hook_governor.cpython-313.pyc +0 -0
- package/runtime/__pycache__/host_parity.cpython-313.pyc +0 -0
- package/runtime/__pycache__/incident_replay.cpython-313.pyc +0 -0
- package/runtime/__pycache__/install_planner.cpython-313.pyc +0 -0
- package/runtime/__pycache__/interaction_journal.cpython-313.pyc +0 -0
- package/runtime/__pycache__/issue_surface.cpython-313.pyc +0 -0
- package/runtime/__pycache__/legacy_compat.cpython-313.pyc +0 -0
- package/runtime/__pycache__/mcp_config_writers.cpython-313.pyc +0 -0
- package/runtime/__pycache__/mcp_lifecycle.cpython-313.pyc +0 -0
- package/runtime/__pycache__/mcp_memory_server.cpython-313.pyc +0 -0
- package/runtime/__pycache__/memory_store.cpython-313.pyc +0 -0
- package/runtime/__pycache__/merge_writer.cpython-313.pyc +0 -0
- package/runtime/__pycache__/music_omr_testbed.cpython-313.pyc +0 -0
- package/runtime/__pycache__/mutation_gate.cpython-313.pyc +0 -0
- package/runtime/__pycache__/omc_compat.cpython-313.pyc +0 -0
- package/runtime/__pycache__/omg_browser_cli.cpython-313.pyc +0 -0
- package/runtime/__pycache__/omg_mcp_server.cpython-313.pyc +0 -0
- package/runtime/__pycache__/opus_plan.cpython-313.pyc +0 -0
- package/runtime/__pycache__/playwright_adapter.cpython-313.pyc +0 -0
- package/runtime/__pycache__/playwright_pack.cpython-313.pyc +0 -0
- package/runtime/__pycache__/plugin_diagnostics.cpython-313.pyc +0 -0
- package/runtime/__pycache__/plugin_interop.cpython-313.pyc +0 -0
- package/runtime/__pycache__/policy_pack_loader.cpython-313.pyc +0 -0
- package/runtime/__pycache__/preflight.cpython-313.pyc +0 -0
- package/runtime/__pycache__/profile_io.cpython-313.pyc +0 -0
- package/runtime/__pycache__/prompt_compiler.cpython-313.pyc +0 -0
- package/runtime/__pycache__/proof_chain.cpython-313.pyc +0 -0
- package/runtime/__pycache__/proof_gate.cpython-313.pyc +0 -0
- package/runtime/__pycache__/provider_parity_eval.cpython-313.pyc +0 -0
- package/runtime/__pycache__/release_artifact_audit.cpython-313.pyc +0 -0
- package/runtime/__pycache__/release_run_coordinator.cpython-313.pyc +0 -0
- package/runtime/__pycache__/release_surface_compiler.cpython-313.pyc +0 -0
- package/runtime/__pycache__/release_surface_registry.cpython-313.pyc +0 -0
- package/runtime/__pycache__/release_surfaces.cpython-313.pyc +0 -0
- package/runtime/__pycache__/remote_supervisor.cpython-313.pyc +0 -0
- package/runtime/__pycache__/repro_pack.cpython-313.pyc +0 -0
- package/runtime/__pycache__/rollback_manifest.cpython-313.pyc +0 -0
- package/runtime/__pycache__/router_critics.cpython-313.pyc +0 -0
- package/runtime/__pycache__/router_executor.cpython-313.pyc +0 -0
- package/runtime/__pycache__/router_selector.cpython-313.pyc +0 -0
- package/runtime/__pycache__/runtime_contracts.cpython-313.pyc +0 -0
- package/runtime/__pycache__/runtime_profile.cpython-313.pyc +0 -0
- package/runtime/__pycache__/security_check.cpython-313.pyc +0 -0
- package/runtime/__pycache__/session_health.cpython-313.pyc +0 -0
- package/runtime/__pycache__/skill_evolution.cpython-313.pyc +0 -0
- package/runtime/__pycache__/skill_registry.cpython-313.pyc +0 -0
- package/runtime/__pycache__/subagent_dispatcher.cpython-313.pyc +0 -0
- package/runtime/__pycache__/subscription_tiers.cpython-313.pyc +0 -0
- package/runtime/__pycache__/team_router.cpython-313.pyc +0 -0
- package/runtime/__pycache__/test_intent_lock.cpython-313-pytest-9.0.2.pyc +0 -0
- package/runtime/__pycache__/test_intent_lock.cpython-313.pyc +0 -0
- package/runtime/__pycache__/tmux_session_manager.cpython-313.pyc +0 -0
- package/runtime/__pycache__/tool_fabric.cpython-313.pyc +0 -0
- package/runtime/__pycache__/tool_plan_gate.cpython-313.pyc +0 -0
- package/runtime/__pycache__/tool_relevance.cpython-313.pyc +0 -0
- package/runtime/__pycache__/tracebank.cpython-313.pyc +0 -0
- package/runtime/__pycache__/untrusted_content.cpython-313.pyc +0 -0
- package/runtime/__pycache__/validate.cpython-313.pyc +0 -0
- package/runtime/__pycache__/verdict_schema.cpython-313.pyc +0 -0
- package/runtime/__pycache__/verification_controller.cpython-313.pyc +0 -0
- package/runtime/__pycache__/verification_loop.cpython-313.pyc +0 -0
- package/runtime/__pycache__/vision_artifacts.cpython-313.pyc +0 -0
- package/runtime/__pycache__/vision_cache.cpython-313.pyc +0 -0
- package/runtime/__pycache__/vision_jobs.cpython-313.pyc +0 -0
- package/runtime/__pycache__/worker_watchdog.cpython-313.pyc +0 -0
- package/runtime/adapters/__init__.py +13 -0
- package/runtime/adapters/__pycache__/__init__.cpython-313.pyc +0 -0
- package/runtime/adapters/__pycache__/claude.cpython-313.pyc +0 -0
- package/runtime/adapters/__pycache__/gpt.cpython-313.pyc +0 -0
- package/runtime/adapters/__pycache__/local.cpython-313.pyc +0 -0
- package/runtime/adapters/claude.py +63 -0
- package/runtime/adapters/gpt.py +56 -0
- package/runtime/adapters/local.py +56 -0
- package/runtime/adoption.py +280 -0
- package/runtime/api_twin.py +450 -0
- package/runtime/architecture_signal.py +226 -0
- package/runtime/artifact_parsers.py +161 -0
- package/runtime/asset_loader.py +62 -0
- package/runtime/background_verification.py +178 -0
- package/runtime/budget_envelopes.py +398 -0
- package/runtime/business_workflow.py +234 -0
- package/runtime/canonical_surface.py +53 -0
- package/runtime/canonical_taxonomy.py +27 -0
- package/runtime/claim_judge.py +648 -0
- package/runtime/cli_provider.py +105 -0
- package/runtime/compat.py +2222 -0
- package/runtime/complexity_scorer.py +148 -0
- package/runtime/compliance_governor.py +505 -0
- package/runtime/config_transaction.py +304 -0
- package/runtime/context_compiler.py +131 -0
- package/runtime/context_engine.py +708 -0
- package/runtime/context_limits.py +363 -0
- package/runtime/contract_compiler.py +3664 -0
- package/runtime/custom_agent_loader.py +366 -0
- package/runtime/data_lineage.py +244 -0
- package/runtime/defense_state.py +261 -0
- package/runtime/delta_classifier.py +231 -0
- package/runtime/dispatcher.py +47 -0
- package/runtime/doc_generator.py +319 -0
- package/runtime/domain_packs.py +75 -0
- package/runtime/ecosystem.py +371 -0
- package/runtime/equalizer.py +268 -0
- package/runtime/eval_gate.py +96 -0
- package/runtime/evidence_narrator.py +147 -0
- package/runtime/evidence_query.py +303 -0
- package/runtime/evidence_registry.py +16 -0
- package/runtime/evidence_requirements.py +157 -0
- package/runtime/exec_kernel.py +267 -0
- package/runtime/explainer_formatter.py +82 -0
- package/runtime/feature_registry.py +109 -0
- package/runtime/forge_agents.py +915 -0
- package/runtime/forge_contracts.py +519 -0
- package/runtime/forge_domains.py +68 -0
- package/runtime/forge_run_id.py +86 -0
- package/runtime/guide_assert.py +135 -0
- package/runtime/hook_governor.py +156 -0
- package/runtime/host_parity.py +373 -0
- package/runtime/incident_replay.py +310 -0
- package/runtime/install_planner.py +617 -0
- package/runtime/interaction_journal.py +566 -0
- package/runtime/issue_surface.py +472 -0
- package/runtime/legacy_compat.py +7 -0
- package/runtime/mcp_config_writers.py +360 -0
- package/runtime/mcp_lifecycle.py +175 -0
- package/runtime/mcp_memory_server.py +220 -0
- package/runtime/memory_parsers/__init__.py +0 -0
- package/runtime/memory_parsers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/runtime/memory_parsers/__pycache__/chatgpt_parser.cpython-313.pyc +0 -0
- package/runtime/memory_parsers/__pycache__/claude_import.cpython-313.pyc +0 -0
- package/runtime/memory_parsers/__pycache__/export.cpython-313.pyc +0 -0
- package/runtime/memory_parsers/__pycache__/gemini_import.cpython-313.pyc +0 -0
- package/runtime/memory_parsers/__pycache__/kimi_import.cpython-313.pyc +0 -0
- package/runtime/memory_parsers/chatgpt_parser.py +257 -0
- package/runtime/memory_parsers/claude_import.py +107 -0
- package/runtime/memory_parsers/export.py +97 -0
- package/runtime/memory_parsers/gemini_import.py +91 -0
- package/runtime/memory_parsers/kimi_import.py +91 -0
- package/runtime/memory_store.py +1182 -0
- package/runtime/merge_writer.py +445 -0
- package/runtime/music_omr_testbed.py +336 -0
- package/runtime/mutation_gate.py +320 -0
- package/runtime/omc_compat.py +7 -0
- package/runtime/omg_browser_cli.py +95 -0
- package/runtime/omg_compat_contract_snapshot.json +936 -0
- package/runtime/omg_contract_snapshot.json +936 -0
- package/runtime/omg_mcp_server.py +306 -0
- package/runtime/playwright_adapter.py +39 -0
- package/runtime/playwright_pack.py +253 -0
- package/runtime/plugin_diagnostics.py +308 -0
- package/runtime/plugin_interop.py +1060 -0
- package/runtime/policy_pack_loader.py +147 -0
- package/runtime/preflight.py +135 -0
- package/runtime/profile_io.py +328 -0
- package/runtime/proof_chain.py +472 -0
- package/runtime/proof_gate.py +442 -0
- package/runtime/provider_parity_eval.py +109 -0
- package/runtime/providers/__init__.py +0 -0
- package/runtime/providers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/runtime/providers/__pycache__/codex_provider.cpython-313.pyc +0 -0
- package/runtime/providers/__pycache__/gemini_provider.cpython-313.pyc +0 -0
- package/runtime/providers/__pycache__/kimi_provider.cpython-313.pyc +0 -0
- package/runtime/providers/__pycache__/opencode_provider.cpython-313.pyc +0 -0
- package/runtime/providers/codex_provider.py +129 -0
- package/runtime/providers/gemini_provider.py +143 -0
- package/runtime/providers/kimi_provider.py +167 -0
- package/runtime/providers/opencode_provider.py +99 -0
- package/runtime/release_artifact_audit.py +556 -0
- package/runtime/release_run_coordinator.py +574 -0
- package/runtime/release_surface_compiler.py +643 -0
- package/runtime/release_surface_registry.py +283 -0
- package/runtime/release_surfaces.py +320 -0
- package/runtime/remote_supervisor.py +79 -0
- package/runtime/repro_pack.py +398 -0
- package/runtime/rollback_manifest.py +143 -0
- package/runtime/router_critics.py +229 -0
- package/runtime/router_executor.py +142 -0
- package/runtime/router_selector.py +99 -0
- package/runtime/runtime_contracts.py +292 -0
- package/runtime/runtime_profile.py +133 -0
- package/runtime/security_check.py +1094 -0
- package/runtime/session_health.py +546 -0
- package/runtime/skill_evolution.py +221 -0
- package/runtime/skill_registry.py +53 -0
- package/runtime/subagent_dispatcher.py +604 -0
- package/runtime/subscription_tiers.py +258 -0
- package/runtime/team_router.py +1399 -0
- package/runtime/test_intent_lock.py +543 -0
- package/runtime/tmux_session_manager.py +172 -0
- package/runtime/tool_fabric.py +570 -0
- package/runtime/tool_plan_gate.py +460 -0
- package/runtime/tracebank.py +125 -0
- package/runtime/untrusted_content.py +360 -0
- package/runtime/validate.py +293 -0
- package/runtime/verdict_schema.py +198 -0
- package/runtime/verification_controller.py +235 -0
- package/runtime/verification_loop.py +73 -0
- package/runtime/vision_artifacts.py +31 -0
- package/runtime/vision_cache.py +38 -0
- package/runtime/vision_jobs.py +92 -0
- package/runtime/worker_watchdog.py +526 -0
- package/scripts/__pycache__/audit-published-artifact.cpython-313.pyc +0 -0
- package/scripts/__pycache__/check-doc-parity.cpython-313.pyc +0 -0
- package/scripts/__pycache__/check-omg-standalone-clean.cpython-313.pyc +0 -0
- package/scripts/__pycache__/github_review_helpers.cpython-313.pyc +0 -0
- package/scripts/__pycache__/omg.cpython-313.pyc +0 -0
- package/scripts/__pycache__/prepare-release-proof-fixtures.cpython-313.pyc +0 -0
- package/scripts/__pycache__/sync-release-identity.cpython-313.pyc +0 -0
- package/scripts/__pycache__/validate-release-identity.cpython-313.pyc +0 -0
- package/scripts/audit-published-artifact.py +59 -0
- package/scripts/check-omg-compat-contract-snapshot.py +137 -0
- package/scripts/check-omg-contract-snapshot.py +12 -0
- package/scripts/check-omg-public-ready.py +273 -0
- package/scripts/check-omg-standalone-clean.py +133 -0
- package/scripts/emit_host_parity.py +72 -0
- package/scripts/legacy_to_omg_migrate.py +29 -0
- package/scripts/migrate-legacy.py +464 -0
- package/scripts/omc_to_omg_migrate.py +12 -0
- package/scripts/omg.py +2962 -0
- package/scripts/pre-release-check.sh +38 -0
- package/scripts/prepare-release-proof-fixtures.py +602 -0
- package/scripts/print-canonical-version.py +80 -0
- package/scripts/settings-merge.py +289 -0
- package/scripts/sync-release-identity.py +481 -0
- package/scripts/validate-release-identity.py +632 -0
- package/scripts/verify-no-omc.sh +5 -0
- package/scripts/verify-standalone.sh +35 -0
- package/settings.json +751 -0
- package/tools/__init__.py +2 -0
- package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- package/tools/__pycache__/browser_consent.cpython-313.pyc +0 -0
- package/tools/__pycache__/browser_stealth.cpython-313.pyc +0 -0
- package/tools/__pycache__/browser_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/changelog_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/commit_splitter.cpython-313.pyc +0 -0
- package/tools/__pycache__/config_discovery.cpython-313.pyc +0 -0
- package/tools/__pycache__/config_merger.cpython-313.pyc +0 -0
- package/tools/__pycache__/dashboard_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/git_inspector.cpython-313.pyc +0 -0
- package/tools/__pycache__/lsp_client.cpython-313.pyc +0 -0
- package/tools/__pycache__/lsp_operations.cpython-313.pyc +0 -0
- package/tools/__pycache__/pr_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/python_repl.cpython-313.pyc +0 -0
- package/tools/__pycache__/python_sandbox.cpython-313.pyc +0 -0
- package/tools/__pycache__/session_snapshot.cpython-313.pyc +0 -0
- package/tools/__pycache__/ssh_manager.cpython-313.pyc +0 -0
- package/tools/__pycache__/theme_engine.cpython-313.pyc +0 -0
- package/tools/__pycache__/theme_selector.cpython-313.pyc +0 -0
- package/tools/__pycache__/web_search.cpython-313.pyc +0 -0
- package/tools/browser_consent.py +289 -0
- package/tools/browser_stealth.py +481 -0
- package/tools/browser_tool.py +448 -0
- package/tools/changelog_generator.py +347 -0
- package/tools/commit_splitter.py +749 -0
- package/tools/config_discovery.py +151 -0
- package/tools/config_merger.py +449 -0
- package/tools/dashboard_generator.py +300 -0
- package/tools/git_inspector.py +298 -0
- package/tools/lsp_client.py +275 -0
- package/tools/lsp_discovery.py +231 -0
- package/tools/lsp_operations.py +392 -0
- package/tools/pr_generator.py +404 -0
- package/tools/python_repl.py +712 -0
- package/tools/python_sandbox.py +768 -0
- package/tools/search_providers/__init__.py +77 -0
- package/tools/search_providers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/tools/search_providers/__pycache__/brave.cpython-313.pyc +0 -0
- package/tools/search_providers/__pycache__/exa.cpython-313.pyc +0 -0
- package/tools/search_providers/__pycache__/jina.cpython-313.pyc +0 -0
- package/tools/search_providers/__pycache__/perplexity.cpython-313.pyc +0 -0
- package/tools/search_providers/__pycache__/synthetic.cpython-313.pyc +0 -0
- package/tools/search_providers/brave.py +115 -0
- package/tools/search_providers/exa.py +116 -0
- package/tools/search_providers/jina.py +104 -0
- package/tools/search_providers/perplexity.py +139 -0
- package/tools/search_providers/synthetic.py +74 -0
- package/tools/session_snapshot.py +851 -0
- package/tools/ssh_manager.py +912 -0
- package/tools/theme_engine.py +296 -0
- package/tools/theme_selector.py +137 -0
- package/tools/web_search.py +675 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Hashline Formatter Bridge — reconciles sidecar hash cache after formatters run.
|
|
3
|
+
|
|
4
|
+
When a post-write formatter (e.g. prettier, ruff format) modifies a file,
|
|
5
|
+
the cached line hashes become stale. This bridge detects the change and
|
|
6
|
+
refreshes the sidecar cache so subsequent reads/edits use correct anchors.
|
|
7
|
+
|
|
8
|
+
Feature flag: OMG_HASHLINE_ENABLED (default: False)
|
|
9
|
+
"""
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
HOOKS_DIR = os.path.dirname(__file__)
|
|
15
|
+
if HOOKS_DIR not in sys.path:
|
|
16
|
+
sys.path.insert(0, HOOKS_DIR)
|
|
17
|
+
|
|
18
|
+
from _common import (
|
|
19
|
+
setup_crash_handler,
|
|
20
|
+
json_input,
|
|
21
|
+
get_feature_flag,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
setup_crash_handler("hashline-formatter-bridge")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# --- Feature Flag ---
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _is_enabled() -> bool:
|
|
31
|
+
"""Check if hashline features are enabled.
|
|
32
|
+
|
|
33
|
+
Resolution order: OMG_HASHLINE_ENABLED env var -> settings.json -> False
|
|
34
|
+
"""
|
|
35
|
+
env_val = os.environ.get("OMG_HASHLINE_ENABLED", "").lower()
|
|
36
|
+
if env_val in ("1", "true", "yes"):
|
|
37
|
+
return True
|
|
38
|
+
if env_val in ("0", "false", "no"):
|
|
39
|
+
return False
|
|
40
|
+
return get_feature_flag("HASHLINE", default=False)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# --- Lazy Imports from hashline-injector ---
|
|
44
|
+
|
|
45
|
+
_injector = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _get_injector():
|
|
49
|
+
"""Lazy-load hashline-injector module."""
|
|
50
|
+
global _injector
|
|
51
|
+
if _injector is None:
|
|
52
|
+
import importlib
|
|
53
|
+
_injector = importlib.import_module("hashline-injector")
|
|
54
|
+
return _injector
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# --- Core Functions ---
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def detect_formatter_change(file_path: str, original_content: str, formatted_content: str) -> bool:
|
|
61
|
+
"""Return True if the formatter changed the content.
|
|
62
|
+
|
|
63
|
+
Compares stripped versions of each line to ignore trivial
|
|
64
|
+
trailing-whitespace-only differences while still detecting
|
|
65
|
+
real structural changes.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
file_path: Path to the file (for context, not read).
|
|
69
|
+
original_content: Content before formatting.
|
|
70
|
+
formatted_content: Content after formatting.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
True if formatted_content differs from original_content.
|
|
74
|
+
"""
|
|
75
|
+
if original_content == formatted_content:
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
# Compare stripped lines to ignore trivial whitespace diffs
|
|
79
|
+
orig_lines = [l.rstrip() for l in original_content.split("\n")]
|
|
80
|
+
fmt_lines = [l.rstrip() for l in formatted_content.split("\n")]
|
|
81
|
+
return orig_lines != fmt_lines
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def refresh_cache_after_format(file_path: str, formatted_content: str) -> bool:
|
|
85
|
+
"""Recompute and save line hashes for newly formatted content.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
file_path: Path to the formatted file.
|
|
89
|
+
formatted_content: The file content after formatting.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
True on success (or when feature is disabled), False on error.
|
|
93
|
+
"""
|
|
94
|
+
if not _is_enabled():
|
|
95
|
+
return True
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
injector = _get_injector()
|
|
99
|
+
_line_hash_id = injector._line_hash_id
|
|
100
|
+
_cache_hashes = injector._cache_hashes
|
|
101
|
+
except Exception:
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
lines = formatted_content.split("\n")
|
|
106
|
+
line_hashes = {}
|
|
107
|
+
for i, line in enumerate(lines, start=1):
|
|
108
|
+
line_hashes[str(i)] = _line_hash_id(line)
|
|
109
|
+
|
|
110
|
+
_cache_hashes(file_path, line_hashes)
|
|
111
|
+
return True
|
|
112
|
+
except Exception:
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def reconcile_post_format(file_path: str) -> dict:
|
|
117
|
+
"""Reconcile hash cache with the current file on disk.
|
|
118
|
+
|
|
119
|
+
Reads the file, checks whether the cached mtime is stale
|
|
120
|
+
(indicating a formatter ran after the last cache write),
|
|
121
|
+
and refreshes the cache if needed.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
file_path: Path to the file to reconcile.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
dict with reconciliation result:
|
|
128
|
+
- ``{"skipped": True}`` when feature is disabled
|
|
129
|
+
- ``{"refreshed": True/False, "lines_updated": int, "file": str}``
|
|
130
|
+
"""
|
|
131
|
+
if not _is_enabled():
|
|
132
|
+
return {"skipped": True}
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
injector = _get_injector()
|
|
136
|
+
_get_cached_hashes = injector._get_cached_hashes
|
|
137
|
+
_line_hash_id = injector._line_hash_id
|
|
138
|
+
_cache_hashes = injector._cache_hashes
|
|
139
|
+
except Exception:
|
|
140
|
+
return {"refreshed": False, "lines_updated": 0, "file": file_path}
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
abs_path = os.path.abspath(file_path)
|
|
144
|
+
if not os.path.exists(abs_path):
|
|
145
|
+
return {"refreshed": False, "lines_updated": 0, "file": file_path}
|
|
146
|
+
|
|
147
|
+
# Read current content from disk
|
|
148
|
+
with open(abs_path, "r", encoding="utf-8", errors="replace") as f:
|
|
149
|
+
content = f.read()
|
|
150
|
+
|
|
151
|
+
# Check if cache exists — if _get_cached_hashes returns None,
|
|
152
|
+
# cache is either missing or mtime doesn't match (formatter ran).
|
|
153
|
+
cached = _get_cached_hashes(file_path)
|
|
154
|
+
if cached is not None:
|
|
155
|
+
# Cache is still valid (mtime matches) — no refresh needed
|
|
156
|
+
return {"refreshed": False, "lines_updated": 0, "file": file_path}
|
|
157
|
+
|
|
158
|
+
# Cache is stale or missing — refresh
|
|
159
|
+
lines = content.split("\n")
|
|
160
|
+
line_hashes = {}
|
|
161
|
+
for i, line in enumerate(lines, start=1):
|
|
162
|
+
line_hashes[str(i)] = _line_hash_id(line)
|
|
163
|
+
|
|
164
|
+
_cache_hashes(file_path, line_hashes)
|
|
165
|
+
return {"refreshed": True, "lines_updated": len(lines), "file": file_path}
|
|
166
|
+
except Exception:
|
|
167
|
+
return {"refreshed": False, "lines_updated": 0, "file": file_path}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# --- Write-tool names that trigger reconciliation ---
|
|
171
|
+
|
|
172
|
+
_WRITE_TOOLS = frozenset({
|
|
173
|
+
"Write", "Edit", "MultiEdit",
|
|
174
|
+
"mcp__filesystem__write_file",
|
|
175
|
+
"mcp__filesystem__edit_file",
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# --- Hook Entry Point ---
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def main():
|
|
183
|
+
"""PostToolUse hook stdin/stdout entry point.
|
|
184
|
+
|
|
185
|
+
Reads JSON from stdin::
|
|
186
|
+
|
|
187
|
+
{"tool_name": "Write", "tool_input": {"file_path": "..."}}
|
|
188
|
+
|
|
189
|
+
If tool_name is a write tool and OMG_HASHLINE_ENABLED is set,
|
|
190
|
+
runs ``reconcile_post_format`` to refresh the hash cache after
|
|
191
|
+
any post-write formatter may have modified the file.
|
|
192
|
+
|
|
193
|
+
Always exits 0 — never raises.
|
|
194
|
+
"""
|
|
195
|
+
if not _is_enabled():
|
|
196
|
+
sys.exit(0)
|
|
197
|
+
|
|
198
|
+
data = json_input()
|
|
199
|
+
if not isinstance(data, dict):
|
|
200
|
+
sys.exit(0)
|
|
201
|
+
|
|
202
|
+
tool_name = data.get("tool_name", "")
|
|
203
|
+
if tool_name not in _WRITE_TOOLS:
|
|
204
|
+
sys.exit(0)
|
|
205
|
+
|
|
206
|
+
tool_input = data.get("tool_input", {})
|
|
207
|
+
if not isinstance(tool_input, dict):
|
|
208
|
+
sys.exit(0)
|
|
209
|
+
|
|
210
|
+
file_path = tool_input.get("file_path", tool_input.get("filePath", ""))
|
|
211
|
+
if not file_path:
|
|
212
|
+
sys.exit(0)
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
result = reconcile_post_format(file_path)
|
|
216
|
+
json.dump(result, sys.stdout)
|
|
217
|
+
except Exception:
|
|
218
|
+
pass # Graceful degradation — never crash
|
|
219
|
+
|
|
220
|
+
sys.exit(0)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
if __name__ == "__main__":
|
|
224
|
+
main()
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Hashline Injector — injects content-hash anchors into file content on read.
|
|
3
|
+
|
|
4
|
+
Each line gets a tag: `{line_number}#{2-char-id}|{original line}`
|
|
5
|
+
where the 2-char ID is derived from SHA-256 of the line content mapped to
|
|
6
|
+
the charset ZPMQVRWSNKTXJBYH (16 chars, 4-bit nibbles of first hash byte).
|
|
7
|
+
|
|
8
|
+
Uses a sidecar cache at `.omg/state/hashline_cache.json` to avoid
|
|
9
|
+
regenerating hashes for unchanged files. Never modifies original files.
|
|
10
|
+
|
|
11
|
+
Feature flag: OMG_HASHLINE_ENABLED (default: False)
|
|
12
|
+
"""
|
|
13
|
+
import hashlib
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
import sys
|
|
18
|
+
import time
|
|
19
|
+
|
|
20
|
+
HOOKS_DIR = os.path.dirname(__file__)
|
|
21
|
+
if HOOKS_DIR not in sys.path:
|
|
22
|
+
sys.path.insert(0, HOOKS_DIR)
|
|
23
|
+
|
|
24
|
+
from _common import (
|
|
25
|
+
setup_crash_handler,
|
|
26
|
+
json_input,
|
|
27
|
+
get_feature_flag,
|
|
28
|
+
get_project_dir,
|
|
29
|
+
atomic_json_write,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
setup_crash_handler("hashline-injector")
|
|
33
|
+
|
|
34
|
+
# --- Constants ---
|
|
35
|
+
|
|
36
|
+
# 16-char charset for 4-bit nibble mapping
|
|
37
|
+
HASH_CHARSET = "ZPMQVRWSNKTXJBYH"
|
|
38
|
+
|
|
39
|
+
# Regex to strip hashline prefix: digits + # + 2 uppercase letters + |
|
|
40
|
+
_HASHLINE_RE = re.compile(r"^\d+#[A-Z]{2}\|")
|
|
41
|
+
|
|
42
|
+
# Sidecar cache path (relative to project dir)
|
|
43
|
+
_CACHE_REL_PATH = os.path.join(".omg", "state", "hashline_cache.json")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# --- Core Functions ---
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _line_hash_id(line: str) -> str:
|
|
50
|
+
"""Generate 2-char hash ID from line content.
|
|
51
|
+
|
|
52
|
+
Takes first byte of SHA-256 digest, splits into two 4-bit nibbles,
|
|
53
|
+
maps each nibble to HASH_CHARSET.
|
|
54
|
+
"""
|
|
55
|
+
digest = hashlib.sha256(line.encode("utf-8", errors="replace")).digest()
|
|
56
|
+
first_byte = digest[0]
|
|
57
|
+
high_nibble = (first_byte >> 4) & 0x0F
|
|
58
|
+
low_nibble = first_byte & 0x0F
|
|
59
|
+
return HASH_CHARSET[high_nibble] + HASH_CHARSET[low_nibble]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def inject_hashlines(content: str, file_path: str = None) -> str:
|
|
63
|
+
"""Add hash anchors to each line of content.
|
|
64
|
+
|
|
65
|
+
Format: `{line_num}#{hash_id}|{original_line}` (1-indexed)
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
content: File content string
|
|
69
|
+
file_path: Optional file path for caching. If provided and the file
|
|
70
|
+
exists, hashes are cached to `.omg/state/hashline_cache.json`.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Content with hash anchors prepended to each line.
|
|
74
|
+
Returns content unchanged if OMG_HASHLINE_ENABLED is False.
|
|
75
|
+
"""
|
|
76
|
+
if not _is_enabled():
|
|
77
|
+
return content
|
|
78
|
+
|
|
79
|
+
# Check cache first
|
|
80
|
+
if file_path:
|
|
81
|
+
cached = _get_cached_hashes(file_path)
|
|
82
|
+
if cached is not None:
|
|
83
|
+
return _apply_cached_hashes(content, cached)
|
|
84
|
+
|
|
85
|
+
lines = content.split("\n")
|
|
86
|
+
result = []
|
|
87
|
+
line_hashes = {}
|
|
88
|
+
|
|
89
|
+
for i, line in enumerate(lines, start=1):
|
|
90
|
+
hash_id = _line_hash_id(line)
|
|
91
|
+
line_hashes[str(i)] = hash_id
|
|
92
|
+
result.append(f"{i}#{hash_id}|{line}")
|
|
93
|
+
|
|
94
|
+
# Cache if file_path provided
|
|
95
|
+
if file_path:
|
|
96
|
+
_cache_hashes(file_path, line_hashes)
|
|
97
|
+
|
|
98
|
+
return "\n".join(result)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def strip_hashlines(content: str) -> str:
|
|
102
|
+
"""Remove hash anchors from content, restoring original text.
|
|
103
|
+
|
|
104
|
+
Strips `^\\d+#[A-Z]{2}\\|` prefix from each line.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
content: Content with hash anchors
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Original content without hash anchors.
|
|
111
|
+
"""
|
|
112
|
+
lines = content.split("\n")
|
|
113
|
+
result = []
|
|
114
|
+
for line in lines:
|
|
115
|
+
result.append(_HASHLINE_RE.sub("", line))
|
|
116
|
+
return "\n".join(result)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _apply_cached_hashes(content: str, line_hashes: dict) -> str:
|
|
120
|
+
"""Apply cached hash IDs to content lines."""
|
|
121
|
+
lines = content.split("\n")
|
|
122
|
+
result = []
|
|
123
|
+
for i, line in enumerate(lines, start=1):
|
|
124
|
+
hash_id = line_hashes.get(str(i))
|
|
125
|
+
if hash_id is None:
|
|
126
|
+
# Line count changed — cache is stale, regenerate
|
|
127
|
+
hash_id = _line_hash_id(line)
|
|
128
|
+
result.append(f"{i}#{hash_id}|{line}")
|
|
129
|
+
return "\n".join(result)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# --- Sidecar Cache ---
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _get_cache_path() -> str:
|
|
136
|
+
"""Get absolute path to hashline cache file."""
|
|
137
|
+
return os.path.join(get_project_dir(), _CACHE_REL_PATH)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _load_cache() -> dict:
|
|
141
|
+
"""Load the entire hashline cache from disk. Returns empty dict on failure."""
|
|
142
|
+
cache_path = _get_cache_path()
|
|
143
|
+
try:
|
|
144
|
+
if not os.path.exists(cache_path):
|
|
145
|
+
return {}
|
|
146
|
+
with open(cache_path, "r", encoding="utf-8") as f:
|
|
147
|
+
return json.load(f)
|
|
148
|
+
except Exception:
|
|
149
|
+
return {}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _get_cached_hashes(file_path: str):
|
|
153
|
+
"""Get cached line hashes for a file, if still valid.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
file_path: Path to the source file
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
dict mapping line number (str) -> hash_id, or None if not cached
|
|
160
|
+
or if the file's mtime has changed (cache invalidation).
|
|
161
|
+
"""
|
|
162
|
+
try:
|
|
163
|
+
abs_path = os.path.abspath(file_path)
|
|
164
|
+
if not os.path.exists(abs_path):
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
cache = _load_cache()
|
|
168
|
+
entry = cache.get(abs_path)
|
|
169
|
+
if entry is None:
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
# Check mtime for invalidation
|
|
173
|
+
current_mtime = os.path.getmtime(abs_path)
|
|
174
|
+
cached_mtime = entry.get("mtime", 0)
|
|
175
|
+
if abs(current_mtime - cached_mtime) > 0.001:
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
return entry.get("line_hashes")
|
|
179
|
+
except Exception:
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _cache_hashes(file_path: str, line_hashes: dict) -> None:
|
|
184
|
+
"""Save line hashes to sidecar cache with mtime for invalidation.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
file_path: Path to the source file
|
|
188
|
+
line_hashes: dict mapping line number (str) -> hash_id
|
|
189
|
+
"""
|
|
190
|
+
try:
|
|
191
|
+
abs_path = os.path.abspath(file_path)
|
|
192
|
+
cache = _load_cache()
|
|
193
|
+
|
|
194
|
+
mtime = 0.0
|
|
195
|
+
if os.path.exists(abs_path):
|
|
196
|
+
mtime = os.path.getmtime(abs_path)
|
|
197
|
+
|
|
198
|
+
cache[abs_path] = {
|
|
199
|
+
"mtime": mtime,
|
|
200
|
+
"line_hashes": line_hashes,
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
atomic_json_write(_get_cache_path(), cache)
|
|
204
|
+
except Exception:
|
|
205
|
+
pass # Never crash on cache write failure
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# --- Feature Flag ---
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _is_enabled() -> bool:
|
|
212
|
+
"""Check if hashline injection is enabled.
|
|
213
|
+
|
|
214
|
+
Resolution order: OMG_HASHLINE_ENABLED env var → settings.json → False
|
|
215
|
+
"""
|
|
216
|
+
# Fast path: check env var directly
|
|
217
|
+
env_val = os.environ.get("OMG_HASHLINE_ENABLED", "").lower()
|
|
218
|
+
if env_val in ("1", "true", "yes"):
|
|
219
|
+
return True
|
|
220
|
+
if env_val in ("0", "false", "no"):
|
|
221
|
+
return False
|
|
222
|
+
# Slow path: check settings.json via get_feature_flag
|
|
223
|
+
return get_feature_flag("HASHLINE", default=False)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# --- Hook Entry Point ---
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def main():
|
|
230
|
+
"""Hook stdin/stdout entry point for Claude Code PreToolUse hooks.
|
|
231
|
+
|
|
232
|
+
Reads JSON from stdin with tool_input containing file content.
|
|
233
|
+
If hashline injection is enabled and tool is a file read, injects
|
|
234
|
+
hash anchors into the content.
|
|
235
|
+
Writes modified tool input back to stdout.
|
|
236
|
+
Always exits 0 — never raises.
|
|
237
|
+
"""
|
|
238
|
+
if not _is_enabled():
|
|
239
|
+
sys.exit(0)
|
|
240
|
+
|
|
241
|
+
data = json_input()
|
|
242
|
+
if not isinstance(data, dict):
|
|
243
|
+
sys.exit(0)
|
|
244
|
+
|
|
245
|
+
# Only inject for file-read tools
|
|
246
|
+
tool_name = data.get("tool_name", "")
|
|
247
|
+
if tool_name not in ("Read", "mcp__filesystem__read_file",
|
|
248
|
+
"mcp__filesystem__read_text_file"):
|
|
249
|
+
sys.exit(0)
|
|
250
|
+
|
|
251
|
+
tool_input = data.get("tool_input", {})
|
|
252
|
+
if not isinstance(tool_input, dict):
|
|
253
|
+
sys.exit(0)
|
|
254
|
+
|
|
255
|
+
content = tool_input.get("content", "")
|
|
256
|
+
file_path = tool_input.get("file_path", tool_input.get("filePath", ""))
|
|
257
|
+
|
|
258
|
+
if not content:
|
|
259
|
+
sys.exit(0)
|
|
260
|
+
|
|
261
|
+
try:
|
|
262
|
+
injected = inject_hashlines(content, file_path or None)
|
|
263
|
+
tool_input["content"] = injected
|
|
264
|
+
data["tool_input"] = tool_input
|
|
265
|
+
json.dump(data, sys.stdout)
|
|
266
|
+
except Exception:
|
|
267
|
+
pass # Graceful degradation — never crash
|
|
268
|
+
|
|
269
|
+
sys.exit(0)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
if __name__ == "__main__":
|
|
273
|
+
main()
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Hashline Validator — validates edit targets against stored hash anchors.
|
|
3
|
+
|
|
4
|
+
Validates that ``line_ref`` (e.g. ``"11#VK"``) matches the cached hash for
|
|
5
|
+
that line before allowing an edit. Rejects mismatched edits with a clear
|
|
6
|
+
error dict. Updates cache after successful edits.
|
|
7
|
+
|
|
8
|
+
Feature flag: OMG_HASHLINE_ENABLED (default: False)
|
|
9
|
+
"""
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
HOOKS_DIR = os.path.dirname(__file__)
|
|
16
|
+
if HOOKS_DIR not in sys.path:
|
|
17
|
+
sys.path.insert(0, HOOKS_DIR)
|
|
18
|
+
|
|
19
|
+
from _common import (
|
|
20
|
+
setup_crash_handler,
|
|
21
|
+
json_input,
|
|
22
|
+
get_feature_flag,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
setup_crash_handler("hashline-validator")
|
|
26
|
+
|
|
27
|
+
# --- Constants ---
|
|
28
|
+
|
|
29
|
+
# Valid line_ref: digits + # + exactly 2 chars from HASH_CHARSET
|
|
30
|
+
_LINE_REF_RE = re.compile(r"^\d+#[ZPMQVRWSNKTXJBYH]{2}$")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# --- Feature Flag ---
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _is_enabled() -> bool:
|
|
37
|
+
"""Check if hashline validation is enabled.
|
|
38
|
+
|
|
39
|
+
Resolution order: OMG_HASHLINE_ENABLED env var → settings.json → False
|
|
40
|
+
"""
|
|
41
|
+
env_val = os.environ.get("OMG_HASHLINE_ENABLED", "").lower()
|
|
42
|
+
if env_val in ("1", "true", "yes"):
|
|
43
|
+
return True
|
|
44
|
+
if env_val in ("0", "false", "no"):
|
|
45
|
+
return False
|
|
46
|
+
return get_feature_flag("HASHLINE", default=False)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# --- Lazy Imports from hashline-injector ---
|
|
50
|
+
|
|
51
|
+
_injector = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _get_injector():
|
|
55
|
+
"""Lazy-load hashline-injector module."""
|
|
56
|
+
global _injector
|
|
57
|
+
if _injector is None:
|
|
58
|
+
import importlib
|
|
59
|
+
_injector = importlib.import_module("hashline-injector")
|
|
60
|
+
return _injector
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# --- Core Functions ---
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def validate_line_ref_format(line_ref: str) -> bool:
|
|
67
|
+
"""Return True if *line_ref* matches ``'{line_num}#{2-char hash_id}'``.
|
|
68
|
+
|
|
69
|
+
Hash ID characters must belong to the charset ``ZPMQVRWSNKTXJBYH``.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
line_ref: Line reference string (e.g. ``"11#VK"``)
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
True if format is valid, False otherwise.
|
|
76
|
+
"""
|
|
77
|
+
if not isinstance(line_ref, str):
|
|
78
|
+
return False
|
|
79
|
+
return bool(_LINE_REF_RE.match(line_ref))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def validate_edit(file_path: str, line_ref: str, expected_line: str) -> dict:
|
|
83
|
+
"""Validate a hash anchor before allowing an edit.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
file_path: Path to the file being edited.
|
|
87
|
+
line_ref: Line reference ``"{line_num}#{hash_id}"`` (e.g. ``"11#VK"``).
|
|
88
|
+
expected_line: The expected content of the line (context, not used
|
|
89
|
+
for hash matching itself).
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
dict with validation result — always contains ``"valid"`` key:
|
|
93
|
+
|
|
94
|
+
* Feature disabled → ``{"valid": True, "skipped": True}``
|
|
95
|
+
* No cache available → ``{"valid": True, "uncached": True}``
|
|
96
|
+
* Hash match → ``{"valid": True, "line": <int>}``
|
|
97
|
+
* Hash mismatch → ``{"valid": False, "error": "HASH_MISMATCH",
|
|
98
|
+
"line": <int>, "expected": <str>, "actual": <str>}``
|
|
99
|
+
* Bad format → ``{"valid": False, "error": "INVALID_LINE_REF",
|
|
100
|
+
"line_ref": <str>}``
|
|
101
|
+
"""
|
|
102
|
+
# Skip when disabled
|
|
103
|
+
if not _is_enabled():
|
|
104
|
+
return {"valid": True, "skipped": True}
|
|
105
|
+
|
|
106
|
+
# Validate format
|
|
107
|
+
if not validate_line_ref_format(line_ref):
|
|
108
|
+
return {"valid": False, "error": "INVALID_LINE_REF", "line_ref": line_ref}
|
|
109
|
+
|
|
110
|
+
# Parse line_ref
|
|
111
|
+
parts = line_ref.split("#")
|
|
112
|
+
line_num = int(parts[0])
|
|
113
|
+
hash_id = parts[1]
|
|
114
|
+
|
|
115
|
+
# Load cache via injector
|
|
116
|
+
try:
|
|
117
|
+
injector = _get_injector()
|
|
118
|
+
cached_hashes = injector._get_cached_hashes(file_path)
|
|
119
|
+
except Exception:
|
|
120
|
+
# Injector unavailable — cannot validate
|
|
121
|
+
return {"valid": True, "uncached": True}
|
|
122
|
+
|
|
123
|
+
if cached_hashes is None:
|
|
124
|
+
return {"valid": True, "uncached": True}
|
|
125
|
+
|
|
126
|
+
# Look up the line in the cache
|
|
127
|
+
stored_hash = cached_hashes.get(str(line_num))
|
|
128
|
+
if stored_hash is None:
|
|
129
|
+
# Line number not in cache (file may have grown since last cache)
|
|
130
|
+
return {"valid": True, "uncached": True}
|
|
131
|
+
|
|
132
|
+
# Compare
|
|
133
|
+
if stored_hash != hash_id:
|
|
134
|
+
return {
|
|
135
|
+
"valid": False,
|
|
136
|
+
"error": "HASH_MISMATCH",
|
|
137
|
+
"line": line_num,
|
|
138
|
+
"expected": hash_id,
|
|
139
|
+
"actual": stored_hash,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {"valid": True, "line": line_num}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def update_hashes_after_edit(file_path: str, new_content: str) -> bool:
|
|
146
|
+
"""Refresh the hash cache after a successful edit.
|
|
147
|
+
|
|
148
|
+
Re-generates line hashes for *new_content* and updates the sidecar
|
|
149
|
+
cache (``hashline_cache.json``) for *file_path*.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
file_path: Path to the edited file.
|
|
153
|
+
new_content: The file content after the edit.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
True on success (or when disabled), False on error.
|
|
157
|
+
"""
|
|
158
|
+
if not _is_enabled():
|
|
159
|
+
return True
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
injector = _get_injector()
|
|
163
|
+
_line_hash_id = injector._line_hash_id
|
|
164
|
+
_cache_hashes = injector._cache_hashes
|
|
165
|
+
except Exception:
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
lines = new_content.split("\n")
|
|
170
|
+
line_hashes = {}
|
|
171
|
+
for i, line in enumerate(lines, start=1):
|
|
172
|
+
line_hashes[str(i)] = _line_hash_id(line)
|
|
173
|
+
|
|
174
|
+
_cache_hashes(file_path, line_hashes)
|
|
175
|
+
return True
|
|
176
|
+
except Exception:
|
|
177
|
+
return False
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# --- Hook Entry Point ---
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def main():
|
|
184
|
+
"""Hook stdin/stdout entry point.
|
|
185
|
+
|
|
186
|
+
Reads JSON from stdin::
|
|
187
|
+
|
|
188
|
+
{"file_path": "...", "line_ref": "11#VK", "expected_line": "..."}
|
|
189
|
+
|
|
190
|
+
Calls :func:`validate_edit` and writes the result dict to stdout.
|
|
191
|
+
Always exits 0 — never raises.
|
|
192
|
+
"""
|
|
193
|
+
if not _is_enabled():
|
|
194
|
+
json.dump({"valid": True, "skipped": True}, sys.stdout)
|
|
195
|
+
sys.exit(0)
|
|
196
|
+
|
|
197
|
+
data = json_input()
|
|
198
|
+
if not isinstance(data, dict):
|
|
199
|
+
json.dump({"valid": False, "error": "INVALID_INPUT"}, sys.stdout)
|
|
200
|
+
sys.exit(0)
|
|
201
|
+
|
|
202
|
+
file_path = data.get("file_path", "")
|
|
203
|
+
line_ref = data.get("line_ref", "")
|
|
204
|
+
expected_line = data.get("expected_line", "")
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
result = validate_edit(file_path, line_ref, expected_line)
|
|
208
|
+
json.dump(result, sys.stdout)
|
|
209
|
+
except Exception:
|
|
210
|
+
json.dump({"valid": False, "error": "INTERNAL_ERROR"}, sys.stdout)
|
|
211
|
+
|
|
212
|
+
sys.exit(0)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
if __name__ == "__main__":
|
|
216
|
+
main()
|