invar-tools 1.4.0__tar.gz → 1.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- invar_tools-1.5.0/.claude/hooks/PostToolUse.sh +3 -0
- invar_tools-1.5.0/.claude/hooks/PreToolUse.sh +3 -0
- invar_tools-1.5.0/.claude/hooks/Stop.sh +3 -0
- invar_tools-1.5.0/.claude/hooks/UserPromptSubmit.sh +3 -0
- invar_tools-1.5.0/.claude/hooks/invar.PostToolUse.sh +102 -0
- invar_tools-1.5.0/.claude/hooks/invar.PreToolUse.sh +74 -0
- invar_tools-1.5.0/.claude/hooks/invar.Stop.sh +23 -0
- invar_tools-1.5.0/.claude/hooks/invar.UserPromptSubmit.sh +386 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.claude/skills/develop/SKILL.md +37 -2
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.claude/skills/investigate/SKILL.md +1 -2
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.claude/skills/propose/SKILL.md +1 -2
- invar_tools-1.5.0/.claude/skills/review/SKILL.md +337 -0
- invar_tools-1.5.0/.invar/examples/functional.py +450 -0
- invar_tools-1.5.0/.invar/mcp-setup.md +83 -0
- invar_tools-1.5.0/.serena/cache/python/document_symbols.pkl +0 -0
- invar_tools-1.5.0/.serena/cache/python/raw_document_symbols.pkl +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/CLAUDE.md +1 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/PKG-INFO +12 -8
- {invar_tools-1.4.0 → invar_tools-1.5.0}/README.md +11 -7
- invar_tools-1.5.0/docs/guides/aider.md +475 -0
- invar_tools-1.5.0/docs/guides/cline.md +355 -0
- invar_tools-1.5.0/docs/guides/continue.md +466 -0
- invar_tools-1.5.0/docs/guides/cursor.md +442 -0
- invar_tools-1.5.0/docs/guides/multi-agent.md +226 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/index.html +7 -5
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-61-functional-pattern-guidance.md +3 -1
- invar_tools-1.5.0/docs/proposals/DX-61-implementation-design.md +1466 -0
- invar_tools-1.5.0/docs/proposals/DX-62-proactive-reference-reading.md +360 -0
- invar_tools-1.5.0/docs/proposals/DX-63-contracts-first-enforcement.md +511 -0
- invar_tools-1.5.0/docs/proposals/LX-02-agent-portability-analysis.md +445 -0
- invar_tools-1.5.0/docs/proposals/LX-03-multi-agent-support.md +422 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/index.md +25 -4
- {invar_tools-1.4.0 → invar_tools-1.5.0}/pyproject.toml +10 -2
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/formatter.py +6 -1
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/models.py +13 -0
- invar_tools-1.5.0/src/invar/core/patterns/__init__.py +53 -0
- invar_tools-1.5.0/src/invar/core/patterns/detector.py +249 -0
- invar_tools-1.5.0/src/invar/core/patterns/p0_exhaustive.py +207 -0
- invar_tools-1.5.0/src/invar/core/patterns/p0_literal.py +307 -0
- invar_tools-1.5.0/src/invar/core/patterns/p0_newtype.py +211 -0
- invar_tools-1.5.0/src/invar/core/patterns/p0_nonempty.py +307 -0
- invar_tools-1.5.0/src/invar/core/patterns/p0_validation.py +278 -0
- invar_tools-1.5.0/src/invar/core/patterns/registry.py +234 -0
- invar_tools-1.5.0/src/invar/core/patterns/types.py +167 -0
- invar_tools-1.5.0/src/invar/core/trivial_detection.py +189 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/mcp/server.py +4 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/commands/guard.py +65 -0
- invar_tools-1.5.0/src/invar/shell/contract_coverage.py +358 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/guard_output.py +5 -0
- invar_tools-1.5.0/src/invar/shell/pattern_integration.py +234 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/testing.py +13 -2
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/config/CLAUDE.md.jinja +1 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/skills/develop/SKILL.md.jinja +49 -0
- invar_tools-1.5.0/src/invar/templates/skills/review/SKILL.md.jinja +338 -0
- invar_tools-1.5.0/src/shell/__init__.py +0 -0
- invar_tools-1.5.0/tests/integration/__init__.py +0 -0
- invar_tools-1.4.0/.claude/skills/review/SKILL.md +0 -173
- invar_tools-1.4.0/src/invar/templates/skills/review/SKILL.md.jinja +0 -173
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.aider.conf.yml +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.claude/commands/audit.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.claude/commands/guard.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.cursorrules +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.github/workflows/ci.yml +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.github/workflows/publish.yml +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.gitignore +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.invar/archive/sessions-2025-12.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.invar/context.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.invar/examples/README.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.invar/examples/conftest.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.invar/examples/contracts.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.invar/examples/core_shell.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.invar/examples/workflow.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.invar/project-additions.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.invar/proposals/TEMPLATE.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.mcp.json +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.pre-commit-config.yaml +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.serena/.gitignore +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/.serena/project.yml +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/INVAR.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/LICENSE +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/LICENSE-GPL +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/NOTICE +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/.nojekyll +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/agents.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/design.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/diagrams.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/guide.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/history/decisions-2024.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/history/feedback/compliance-analysis.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/history/feedback/feedback-memo.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/history/feedback/index.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/history/index.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/history/original-vision.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/history/protocol-evolution.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/history/research/cruxeval-quick-validation.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/logo.svg +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-23-entry-point-detection.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-25-functional-patterns.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-29-pure-content-detection.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-37-coverage-integration.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-38-contract-quality-rules.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-39-workflow-efficiency.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-40-smart-tool-redirect-hook.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-41-automatic-review-orchestration.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-42-workflow-auto-routing.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-43-cross-platform-distribution.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-46-documentation-audit.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-48-code-structure-reorganization.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-49-protocol-distribution-unification.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-50-workflow-enforcement.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-51-workflow-phase-visibility.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-52-venv-dependency-injection.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-53-review-loop-effectiveness.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-54-agent-native-context-management.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-55-claude-init-conflict-resolution.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-56-template-sync-unification.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/DX-60-structured-rules-ssot.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/LX-01-multi-language-feasibility.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/2024-12-21-guard-enhancements.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/2024-12-21-language-inspired-enhancements.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/2024-12-21-test-first-enhancement.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/2025-12-21-dx-improvements.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/2025-12-23-dx-20-property-testing-enhancements.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/2025-12-23-dx-21-package-and-init.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/AGENT-IMPROVEMENTS.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-11-documentation-restructure.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-12-hypothesis-fallback.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-13-incremental-prove.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-14-expanded-prove-usage.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-16-agent-tool-enforcement.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-17-workflow-enforcement.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-22-verification-strategy.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-24-mechanism-documentation.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-26-guard-simplification.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-27-system-prompt-protocol.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-28-semantic-verification.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-30-visible-workflow.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-31-adversarial-reviewer.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-32-workflow-iteration.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-33-verification-blind-spots.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-34-review-cycle.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-35-workflow-phase-separation.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-36-documentation-restructuring.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-45-template-consistency.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-47-command-skill-naming.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-57-claude-code-hooks.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/proposals/completed/DX-58-document-structure-optimization.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/reference/architecture/index.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/reference/contracts/advanced.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/reference/contracts/completeness.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/reference/contracts/doctests.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/reference/contracts/index.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/reference/contracts/pre-post.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/reference/documentation.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/reference/index.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/reference/proposal-workflow.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/reference/rules/index.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/reference/rules/severity.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/reference/verification/crosshair-vs-hypothesis.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/reference/verification/index.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/reference/verification/smart-routing.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/reference/workflow/index.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/reference/workflow/session-start.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/reference/workflow/usbv.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/terminal-gif-guide.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/test-reports/DX-55-test-report.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/test-reports/DX-56-test-report.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/test-reports/DX-58-test-scenario.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/docs/vision.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/runtime/LICENSE +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/runtime/README.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/runtime/pyproject.toml +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/runtime/src/invar_runtime/__init__.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/runtime/src/invar_runtime/contracts.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/runtime/src/invar_runtime/decorators.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/runtime/src/invar_runtime/invariant.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/runtime/src/invar_runtime/relations.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/runtime/src/invar_runtime/resource.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/scripts/smart-guard.sh +0 -0
- {invar_tools-1.4.0/tests/integration → invar_tools-1.5.0/src/core}/__init__.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/__init__.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/__init__.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/contracts.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/entry_points.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/extraction.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/format_specs.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/format_strategies.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/hypothesis_strategies.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/inspect.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/lambda_helpers.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/must_use.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/parser.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/postcondition_scope.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/property_gen.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/purity.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/purity_heuristics.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/references.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/review_trigger.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/rule_meta.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/rules.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/shell_analysis.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/shell_architecture.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/strategies.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/suggestions.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/sync_helpers.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/tautology.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/template_parser.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/timeout_inference.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/utils.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/core/verification_routing.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/mcp/__init__.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/mcp/__main__.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/py.typed +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/__init__.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/claude_hooks.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/commands/__init__.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/commands/hooks.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/commands/init.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/commands/merge.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/commands/mutate.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/commands/perception.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/commands/sync_self.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/commands/template_sync.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/commands/test.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/commands/update.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/config.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/coverage.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/fs.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/git.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/guard_helpers.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/mcp_config.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/mutation.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/property_tests.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/prove/__init__.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/prove/accept.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/prove/cache.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/prove/crosshair.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/prove/hypothesis.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/subprocess_env.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/template_engine.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/shell/templates.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/CLAUDE.md.template +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/__init__.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/aider.conf.yml.template +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/commands/audit.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/commands/guard.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/config/context.md.jinja +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/config/pre-commit.yaml.jinja +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/context.md.template +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/cursorrules.template +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/examples/README.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/examples/conftest.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/examples/contracts.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/examples/core_shell.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/examples/workflow.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/hooks/PostToolUse.sh.jinja +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/hooks/PreToolUse.sh.jinja +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/hooks/Stop.sh.jinja +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/hooks/UserPromptSubmit.sh.jinja +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/hooks/__init__.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/manifest.toml +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/pre-commit-config.yaml.template +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/proposal.md.template +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/protocol/INVAR.md +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/skills/investigate/SKILL.md.jinja +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/src/invar/templates/skills/propose/SKILL.md.jinja +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/tests/__init__.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/tests/conftest.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/tests/integration/test_cli_flags.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/tests/integration/test_dx55_regression.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/tests/integration/test_dx56_sync.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/tests/test_subprocess_env.py +0 -0
- {invar_tools-1.4.0 → invar_tools-1.5.0}/uv.lock +0 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Invar PostToolUse Hook
|
|
3
|
+
# Protocol: v5.0 | Generated: 2025-12-29
|
|
4
|
+
# DX-57: Git-based change detection with fallback
|
|
5
|
+
|
|
6
|
+
TOOL_NAME="$1"
|
|
7
|
+
|
|
8
|
+
# Check if hooks are disabled
|
|
9
|
+
[[ -f ".claude/hooks/.invar_disabled" ]] && exit 0
|
|
10
|
+
|
|
11
|
+
# Use session-specific state directory
|
|
12
|
+
STATE_DIR="${CLAUDE_STATE_DIR:-/tmp/invar_hooks_$(id -u)}"
|
|
13
|
+
mkdir -p "$STATE_DIR" 2>/dev/null
|
|
14
|
+
|
|
15
|
+
CHANGES_FILE="$STATE_DIR/changes"
|
|
16
|
+
LAST_GUARD="$STATE_DIR/last_guard"
|
|
17
|
+
LAST_CHECK_MARKER="$STATE_DIR/last_check"
|
|
18
|
+
|
|
19
|
+
# ============================================
|
|
20
|
+
# Reset state on guard run (MCP or CLI)
|
|
21
|
+
# ============================================
|
|
22
|
+
# MCP: invar_guard tool call
|
|
23
|
+
if [[ "$TOOL_NAME" == "mcp__invar__invar_guard" ]]; then
|
|
24
|
+
date +%s > "$LAST_GUARD"
|
|
25
|
+
rm -f "$CHANGES_FILE"
|
|
26
|
+
touch "$LAST_CHECK_MARKER"
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# CLI: Bash command containing "invar guard"
|
|
31
|
+
if [[ "$TOOL_NAME" == "Bash" ]]; then
|
|
32
|
+
TOOL_INPUT="$2"
|
|
33
|
+
if echo "$TOOL_INPUT" | grep -qE '"command"[^}]*invar\s+guard'; then
|
|
34
|
+
date +%s > "$LAST_GUARD"
|
|
35
|
+
rm -f "$CHANGES_FILE"
|
|
36
|
+
touch "$LAST_CHECK_MARKER"
|
|
37
|
+
exit 0
|
|
38
|
+
fi
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# ============================================
|
|
42
|
+
# Detect changes (git with fallback)
|
|
43
|
+
# ============================================
|
|
44
|
+
is_git_repo() {
|
|
45
|
+
git rev-parse --git-dir >/dev/null 2>&1
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
detect_changes() {
|
|
49
|
+
if is_git_repo; then
|
|
50
|
+
# Primary: Git-based detection (includes staged + unstaged)
|
|
51
|
+
{ git diff --name-only -- '*.py' 2>/dev/null; git diff --cached --name-only -- '*.py' 2>/dev/null; } | sort -u
|
|
52
|
+
elif [[ -f "$LAST_CHECK_MARKER" ]]; then
|
|
53
|
+
# Fallback: Timestamp-based detection (approximate)
|
|
54
|
+
find . -name "*.py" -newer "$LAST_CHECK_MARKER" -type f 2>/dev/null | \
|
|
55
|
+
grep -v __pycache__ | grep -v '.venv' | head -20
|
|
56
|
+
fi
|
|
57
|
+
# Update marker for next check
|
|
58
|
+
touch "$LAST_CHECK_MARKER" 2>/dev/null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Track changes
|
|
62
|
+
CHANGED=$(detect_changes)
|
|
63
|
+
if [[ -n "$CHANGED" ]]; then
|
|
64
|
+
echo "$CHANGED" >> "$CHANGES_FILE"
|
|
65
|
+
sort -u "$CHANGES_FILE" -o "$CHANGES_FILE" 2>/dev/null
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# ============================================
|
|
69
|
+
# Smart trigger evaluation
|
|
70
|
+
# ============================================
|
|
71
|
+
CHANGE_COUNT=$(wc -l < "$CHANGES_FILE" 2>/dev/null | tr -d ' ' || echo 0)
|
|
72
|
+
LAST_TIME=$(cat "$LAST_GUARD" 2>/dev/null || echo 0)
|
|
73
|
+
NOW=$(date +%s)
|
|
74
|
+
ELAPSED=$((NOW - LAST_TIME))
|
|
75
|
+
|
|
76
|
+
SHOULD_REMIND=false
|
|
77
|
+
REASON=""
|
|
78
|
+
|
|
79
|
+
# Trigger 1: Accumulated changes (3+ files)
|
|
80
|
+
if [[ $CHANGE_COUNT -ge 3 ]]; then
|
|
81
|
+
SHOULD_REMIND=true
|
|
82
|
+
REASON="$CHANGE_COUNT files changed"
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Trigger 2: Core file changed (high priority)
|
|
86
|
+
if grep -qE "/core/|/contracts/" "$CHANGES_FILE" 2>/dev/null; then
|
|
87
|
+
SHOULD_REMIND=true
|
|
88
|
+
REASON="core/ files modified"
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# Trigger 3: Time threshold (>5 min with changes)
|
|
92
|
+
if [[ $ELAPSED -gt 300 && $CHANGE_COUNT -gt 0 ]]; then
|
|
93
|
+
SHOULD_REMIND=true
|
|
94
|
+
REASON=">5 min since last guard"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# Output reminder if triggered
|
|
98
|
+
if [[ "$SHOULD_REMIND" == "true" ]]; then
|
|
99
|
+
echo ""
|
|
100
|
+
echo "⚠️ Verification suggested: $REASON"
|
|
101
|
+
echo " Run: invar_guard --changed"
|
|
102
|
+
fi
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Invar PreToolUse Hook
|
|
3
|
+
# Protocol: v5.0 | Generated: 2025-12-29
|
|
4
|
+
# DX-57: Smart blocking with auto-escape for pytest/crosshair
|
|
5
|
+
|
|
6
|
+
TOOL_NAME="$1"
|
|
7
|
+
TOOL_INPUT="$2"
|
|
8
|
+
|
|
9
|
+
# Check if hooks are disabled
|
|
10
|
+
[[ -f ".claude/hooks/.invar_disabled" ]] && exit 0
|
|
11
|
+
|
|
12
|
+
# Only process Bash commands
|
|
13
|
+
[[ "$TOOL_NAME" != "Bash" ]] && exit 0
|
|
14
|
+
|
|
15
|
+
# Parse command from JSON input
|
|
16
|
+
# Primary: jq (accurate), Fallback: grep/sed (basic)
|
|
17
|
+
if command -v jq &>/dev/null; then
|
|
18
|
+
CMD=$(echo "$TOOL_INPUT" | jq -r '.command // empty' 2>/dev/null)
|
|
19
|
+
else
|
|
20
|
+
# Fallback: Extract command field using grep/sed (handles simple cases)
|
|
21
|
+
CMD=$(echo "$TOOL_INPUT" | grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"\(.*\)"/\1/')
|
|
22
|
+
fi
|
|
23
|
+
[[ -z "$CMD" ]] && exit 0
|
|
24
|
+
|
|
25
|
+
# ============================================
|
|
26
|
+
# pytest blocking with smart escape
|
|
27
|
+
# ============================================
|
|
28
|
+
if echo "$CMD" | grep -qE '\bpytest\b|python.*-m\s+pytest\b'; then
|
|
29
|
+
|
|
30
|
+
# Auto-escape 1: Debugging mode (--pdb, --debug, --tb=long)
|
|
31
|
+
if echo "$CMD" | grep -qE '\-\-pdb|\-\-debug|\-\-tb='; then
|
|
32
|
+
echo "⚠️ pytest debugging allowed. Run invar_guard after."
|
|
33
|
+
exit 0
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Auto-escape 2: External/vendor tests
|
|
37
|
+
if echo "$CMD" | grep -qE 'vendor/|third_party/|external/|node_modules/'; then
|
|
38
|
+
exit 0
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Auto-escape 3: Explicit coverage collection
|
|
42
|
+
if echo "$CMD" | grep -qE '\-\-cov'; then
|
|
43
|
+
echo "⚠️ pytest coverage allowed. Run invar_guard for contract verification."
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# Auto-escape 4: Environment variable override
|
|
48
|
+
if [[ "$INVAR_ALLOW_PYTEST" == "1" ]]; then
|
|
49
|
+
exit 0
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Default: Block with helpful message
|
|
53
|
+
echo "❌ Use invar_guard instead of pytest"
|
|
54
|
+
echo " invar_guard = static + doctests + CrossHair + Hypothesis"
|
|
55
|
+
echo ""
|
|
56
|
+
echo " Auto-allowed: pytest --pdb (debug), pytest --cov (coverage)"
|
|
57
|
+
echo " Manual escape: INVAR_ALLOW_PYTEST=1 pytest ..."
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# ============================================
|
|
62
|
+
# crosshair blocking (always redirect)
|
|
63
|
+
# ============================================
|
|
64
|
+
if echo "$CMD" | grep -qE '\bcrosshair\b'; then
|
|
65
|
+
if [[ "$INVAR_ALLOW_CROSSHAIR" == "1" ]]; then
|
|
66
|
+
exit 0
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
echo "❌ Use invar_guard (includes CrossHair by default)"
|
|
70
|
+
echo " Manual escape: INVAR_ALLOW_CROSSHAIR=1 crosshair ..."
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
exit 0
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Invar Stop Hook
|
|
3
|
+
# Protocol: v5.0 | Generated: 2025-12-29
|
|
4
|
+
# DX-57: Session cleanup and unverified changes warning
|
|
5
|
+
|
|
6
|
+
# Use session-specific state
|
|
7
|
+
STATE_DIR="${CLAUDE_STATE_DIR:-/tmp/invar_hooks_$(id -u)}"
|
|
8
|
+
CHANGES_FILE="$STATE_DIR/changes"
|
|
9
|
+
|
|
10
|
+
# Check for unverified changes
|
|
11
|
+
if [[ -f "$CHANGES_FILE" ]]; then
|
|
12
|
+
CHANGE_COUNT=$(wc -l < "$CHANGES_FILE" 2>/dev/null | tr -d ' ' || echo 0)
|
|
13
|
+
if [[ $CHANGE_COUNT -gt 0 ]]; then
|
|
14
|
+
echo ""
|
|
15
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
16
|
+
echo "⚠️ Invar: $CHANGE_COUNT Python files not verified"
|
|
17
|
+
echo " Run before commit: invar_guard --changed"
|
|
18
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
19
|
+
fi
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# Cleanup session state
|
|
23
|
+
rm -rf "$STATE_DIR" 2>/dev/null
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Invar UserPromptSubmit Hook
|
|
3
|
+
# Protocol: v5.0 | Generated: 2025-12-29
|
|
4
|
+
# DX-57: Protocol refresh with full INVAR.md injection
|
|
5
|
+
|
|
6
|
+
USER_MESSAGE="$1"
|
|
7
|
+
|
|
8
|
+
# Check if hooks are disabled
|
|
9
|
+
[[ -f ".claude/hooks/.invar_disabled" ]] && exit 0
|
|
10
|
+
|
|
11
|
+
# Use session-specific state
|
|
12
|
+
STATE_DIR="${CLAUDE_STATE_DIR:-/tmp/invar_hooks_$(id -u)}"
|
|
13
|
+
mkdir -p "$STATE_DIR" 2>/dev/null
|
|
14
|
+
|
|
15
|
+
# Session detection: Reset if state is stale (>4 hours)
|
|
16
|
+
SESSION_MARKER="$STATE_DIR/session_start"
|
|
17
|
+
MAX_AGE_SECONDS=14400 # 4 hours
|
|
18
|
+
|
|
19
|
+
reset_session() {
|
|
20
|
+
rm -f "$STATE_DIR/msg_count" "$STATE_DIR/changes" 2>/dev/null
|
|
21
|
+
date +%s > "$SESSION_MARKER"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if [[ -f "$SESSION_MARKER" ]]; then
|
|
25
|
+
MARKER_TIME=$(cat "$SESSION_MARKER" 2>/dev/null || echo 0)
|
|
26
|
+
NOW=$(date +%s)
|
|
27
|
+
AGE=$((NOW - MARKER_TIME))
|
|
28
|
+
if [[ $AGE -gt $MAX_AGE_SECONDS ]]; then
|
|
29
|
+
reset_session
|
|
30
|
+
fi
|
|
31
|
+
else
|
|
32
|
+
reset_session
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
COUNT_FILE="$STATE_DIR/msg_count"
|
|
36
|
+
COUNT=$(cat "$COUNT_FILE" 2>/dev/null || echo 0)
|
|
37
|
+
COUNT=$((COUNT + 1))
|
|
38
|
+
echo "$COUNT" > "$COUNT_FILE"
|
|
39
|
+
|
|
40
|
+
# ============================================
|
|
41
|
+
# Keyword triggers (independent of count)
|
|
42
|
+
# ============================================
|
|
43
|
+
|
|
44
|
+
# pytest intent → immediate correction
|
|
45
|
+
if echo "$USER_MESSAGE" | grep -qiE "run.*pytest|pytest.*test|用.*pytest"; then
|
|
46
|
+
echo "<system-reminder>Use invar_guard, not pytest.</system-reminder>"
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Implementation intent → workflow reminder (after warmup)
|
|
50
|
+
if [[ $COUNT -gt 3 ]]; then
|
|
51
|
+
if echo "$USER_MESSAGE" | grep -qiE "^implement|^fix|^add|^实现|^修复|^添加"; then
|
|
52
|
+
echo "<system-reminder>USBV: Specify contracts → Build → Validate</system-reminder>"
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# ============================================
|
|
57
|
+
# Progressive refresh based on message count
|
|
58
|
+
# ============================================
|
|
59
|
+
|
|
60
|
+
# Message 15: Lightweight checkpoint
|
|
61
|
+
if [[ $COUNT -eq 15 ]]; then
|
|
62
|
+
echo "<system-reminder>"
|
|
63
|
+
echo "Checkpoint: guard=verify, sig=contracts, USBV workflow."
|
|
64
|
+
echo "</system-reminder>"
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# Message 25+: Full INVAR.md injection every 10 messages
|
|
68
|
+
# SSOT: Inject entire protocol to ensure no content drift
|
|
69
|
+
if [[ $COUNT -ge 25 && $((COUNT % 10)) -eq 0 ]]; then
|
|
70
|
+
echo "<system-reminder>"
|
|
71
|
+
echo "=== Protocol Refresh (message $COUNT) ==="
|
|
72
|
+
echo ""
|
|
73
|
+
cat << 'INVAR_EOF'
|
|
74
|
+
<!--
|
|
75
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
76
|
+
│ INVAR-MANAGED FILE - DO NOT EDIT DIRECTLY │
|
|
77
|
+
│ │
|
|
78
|
+
│ This file is managed by Invar. Changes may be lost on │
|
|
79
|
+
│ `invar update`. Add project content to CLAUDE.md instead. │
|
|
80
|
+
└─────────────────────────────────────────────────────────────┘
|
|
81
|
+
|
|
82
|
+
License: CC-BY-4.0 (Creative Commons Attribution 4.0 International)
|
|
83
|
+
https://creativecommons.org/licenses/by/4.0/
|
|
84
|
+
|
|
85
|
+
You are free to share and adapt this document, provided you give
|
|
86
|
+
appropriate credit to the Invar project.
|
|
87
|
+
-->
|
|
88
|
+
# The Invar Protocol v5.0
|
|
89
|
+
|
|
90
|
+
> **"Trade structure for safety."**
|
|
91
|
+
|
|
92
|
+
## Six Laws
|
|
93
|
+
|
|
94
|
+
| Law | Principle |
|
|
95
|
+
|-----|-----------|
|
|
96
|
+
| 1. Separation | Core (pure logic) / Shell (I/O) physically separate |
|
|
97
|
+
| 2. Contract Complete | @pre/@post + doctests uniquely determine implementation |
|
|
98
|
+
| 3. Context Economy | map → sig → code (only read what's needed) |
|
|
99
|
+
| 4. Decompose First | Break into sub-functions before implementing |
|
|
100
|
+
| 5. Verify Reflectively | Fail → Reflect (why?) → Fix → Verify |
|
|
101
|
+
| 6. Integrate Fully | Local correct ≠ Global correct; verify all paths |
|
|
102
|
+
|
|
103
|
+
## Core/Shell Architecture
|
|
104
|
+
|
|
105
|
+
| Zone | Location | Requirements |
|
|
106
|
+
|------|----------|--------------|
|
|
107
|
+
| Core | `**/core/**` | @pre/@post, pure (no I/O), doctests |
|
|
108
|
+
| Shell | `**/shell/**` | `Result[T, E]` return type |
|
|
109
|
+
|
|
110
|
+
**Forbidden in Core:** `os`, `sys`, `subprocess`, `pathlib`, `open`, `requests`, `datetime.now`
|
|
111
|
+
|
|
112
|
+
### Decision Tree: Core vs Shell
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
Does this function...
|
|
116
|
+
│
|
|
117
|
+
├─ Read or write files? ──────────────────→ Shell
|
|
118
|
+
├─ Make network requests? ─────────────────→ Shell
|
|
119
|
+
├─ Access current time (datetime.now)? ────→ Shell OR inject as parameter
|
|
120
|
+
├─ Generate random values? ────────────────→ Shell OR inject as parameter
|
|
121
|
+
├─ Print to console? ──────────────────────→ Shell (return data, Shell logs)
|
|
122
|
+
├─ Access environment variables? ──────────→ Shell
|
|
123
|
+
│
|
|
124
|
+
└─ None of the above? ─────────────────────→ Core
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Pattern:** Inject impure values as parameters:
|
|
128
|
+
```python
|
|
129
|
+
# Core: receives 'now' as parameter (pure)
|
|
130
|
+
def is_expired(expiry: datetime, now: datetime) -> bool:
|
|
131
|
+
return now > expiry
|
|
132
|
+
|
|
133
|
+
# Shell calls with actual time
|
|
134
|
+
expired = is_expired(token.expiry, datetime.now())
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Core Example (Pure Logic)
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from deal import pre, post
|
|
141
|
+
|
|
142
|
+
@pre(lambda price, discount: price > 0 and 0 <= discount <= 1)
|
|
143
|
+
@post(lambda result: result >= 0)
|
|
144
|
+
def discounted_price(price: float, discount: float) -> float:
|
|
145
|
+
"""
|
|
146
|
+
>>> discounted_price(100, 0.2)
|
|
147
|
+
80.0
|
|
148
|
+
>>> discounted_price(100, 0) # Edge: no discount
|
|
149
|
+
100.0
|
|
150
|
+
"""
|
|
151
|
+
return price * (1 - discount)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Self-test:** Can someone else write the exact same function from just @pre/@post + doctests?
|
|
155
|
+
|
|
156
|
+
## Shell Example (I/O Operations)
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from pathlib import Path
|
|
160
|
+
from returns.result import Result, Success, Failure
|
|
161
|
+
|
|
162
|
+
def read_config(path: Path) -> Result[dict, str]:
|
|
163
|
+
"""Shell: handles I/O, returns Result for error handling."""
|
|
164
|
+
try:
|
|
165
|
+
import json
|
|
166
|
+
return Success(json.loads(path.read_text()))
|
|
167
|
+
except FileNotFoundError:
|
|
168
|
+
return Failure(f"File not found: {path}")
|
|
169
|
+
except json.JSONDecodeError as e:
|
|
170
|
+
return Failure(f"Invalid JSON: {e}")
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Pattern:** Shell reads file → passes content to Core → returns Result.
|
|
174
|
+
|
|
175
|
+
More examples: `.invar/examples/`
|
|
176
|
+
|
|
177
|
+
## Contract Rules
|
|
178
|
+
|
|
179
|
+
### Lambda Signature (Critical)
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
# WRONG: Lambda only takes first parameter
|
|
183
|
+
@pre(lambda x: x >= 0)
|
|
184
|
+
def calculate(x: int, y: int = 0): ...
|
|
185
|
+
|
|
186
|
+
# CORRECT: Lambda must include ALL parameters (even defaults)
|
|
187
|
+
@pre(lambda x, y=0: x >= 0)
|
|
188
|
+
def calculate(x: int, y: int = 0): ...
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Guard's `param_mismatch` rule catches this as ERROR.
|
|
192
|
+
|
|
193
|
+
### Meaningful Contracts
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
# Redundant - type hints already check this
|
|
197
|
+
@pre(lambda x: isinstance(x, int))
|
|
198
|
+
def calc(x: int): ...
|
|
199
|
+
|
|
200
|
+
# Meaningful - checks business logic
|
|
201
|
+
@pre(lambda x: x > 0)
|
|
202
|
+
def calc(x: int): ...
|
|
203
|
+
|
|
204
|
+
# Meaningful - checks relationship between params
|
|
205
|
+
@pre(lambda start, end: start < end)
|
|
206
|
+
def process_range(start: int, end: int): ...
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### @post Scope
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
# WRONG: @post cannot access function parameters
|
|
213
|
+
@post(lambda result: result > x) # 'x' not available!
|
|
214
|
+
def calc(x: int) -> int: ...
|
|
215
|
+
|
|
216
|
+
# CORRECT: @post can only use 'result'
|
|
217
|
+
@post(lambda result: result >= 0)
|
|
218
|
+
def calc(x: int) -> int: ...
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Check-In (Required)
|
|
222
|
+
|
|
223
|
+
Your first message MUST display:
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
✓ Check-In: [project] | [branch] | [clean/dirty]
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Actions:
|
|
230
|
+
1. Read `.invar/context.md` (Key Rules + Current State + Lessons Learned)
|
|
231
|
+
2. Show one-line status
|
|
232
|
+
|
|
233
|
+
**Do NOT execute guard or map at Check-In.**
|
|
234
|
+
Guard is for VALIDATE phase and Final only.
|
|
235
|
+
|
|
236
|
+
This is your sign-in. The user sees it immediately.
|
|
237
|
+
No visible check-in = Session not started.
|
|
238
|
+
|
|
239
|
+
## USBV Workflow (DX-32)
|
|
240
|
+
|
|
241
|
+
**U**nderstand → **S**pecify → **B**uild → **V**alidate
|
|
242
|
+
|
|
243
|
+
| Phase | Purpose | Activities |
|
|
244
|
+
|-------|---------|------------|
|
|
245
|
+
| UNDERSTAND | Know what and why | Intent, Inspect (invar sig/map), Constraints |
|
|
246
|
+
| SPECIFY | Define boundaries | @pre/@post, Design decomposition, Doctests |
|
|
247
|
+
| BUILD | Write code | Implement leaves, Compose |
|
|
248
|
+
| VALIDATE | Confirm correctness | invar guard, Review Gate, Reflect |
|
|
249
|
+
|
|
250
|
+
**Key:** Inspect before Contract. Depth varies naturally. Iterate when needed.
|
|
251
|
+
|
|
252
|
+
**Review Gate:** When Guard triggers `review_suggested` (escape hatches ≥3, security paths, low coverage), invoke `/review` before completion.
|
|
253
|
+
|
|
254
|
+
## Visible Workflow (DX-30)
|
|
255
|
+
|
|
256
|
+
For complex tasks (3+ functions), show 3 checkpoints in TodoList:
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
□ [UNDERSTAND] Task description, codebase context, constraints
|
|
260
|
+
□ [SPECIFY] Contracts (@pre/@post) and design decomposition
|
|
261
|
+
□ [VALIDATE] Guard results, Review Gate if triggered, integration status
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**BUILD is internal work** — not shown in TodoList.
|
|
265
|
+
|
|
266
|
+
**Show contracts before code.** Example:
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
[SPECIFY] calculate_discount:
|
|
270
|
+
@pre(lambda price, rate: price > 0 and 0 <= rate <= 1)
|
|
271
|
+
@post(lambda result: result >= 0)
|
|
272
|
+
def calculate_discount(price: float, rate: float) -> float: ...
|
|
273
|
+
|
|
274
|
+
[BUILD] Now coding...
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**When to use:** New features (3+ functions), architectural changes, Core modifications.
|
|
278
|
+
**Skip for:** Single-line fixes, documentation, trivial refactoring.
|
|
279
|
+
|
|
280
|
+
## Task Completion
|
|
281
|
+
|
|
282
|
+
A task is complete only when ALL conditions are met:
|
|
283
|
+
- Check-In displayed: `✓ Check-In: [project] | [branch] | [clean/dirty]`
|
|
284
|
+
- Intent explicitly stated
|
|
285
|
+
- Contract written before implementation
|
|
286
|
+
- Final displayed: `✓ Final: guard PASS | <errors>, <warnings>`
|
|
287
|
+
- User requirement satisfied
|
|
288
|
+
|
|
289
|
+
**Missing any = Task incomplete.**
|
|
290
|
+
|
|
291
|
+
## Markers
|
|
292
|
+
|
|
293
|
+
### Entry Points
|
|
294
|
+
|
|
295
|
+
Entry points are framework callbacks (`@app.route`, `@app.command`) at Shell boundary.
|
|
296
|
+
- **Exempt** from `Result[T, E]` — must match framework signature
|
|
297
|
+
- **Keep thin** (max 15 lines) — delegate to Shell functions that return Result
|
|
298
|
+
|
|
299
|
+
Auto-detected by decorators. For custom callbacks:
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
# @shell:entry
|
|
303
|
+
def on_custom_event(data: dict) -> dict:
|
|
304
|
+
result = handle_event(data)
|
|
305
|
+
return result.unwrap_or({"error": "failed"})
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Shell Complexity
|
|
309
|
+
|
|
310
|
+
When shell function complexity is justified:
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
# @shell_complexity: Subprocess with error classification
|
|
314
|
+
def run_external_tool(...): ...
|
|
315
|
+
|
|
316
|
+
# @shell_orchestration: Multi-step pipeline coordination
|
|
317
|
+
def process_batch(...): ...
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Architecture Escape Hatch
|
|
321
|
+
|
|
322
|
+
When rule violation has valid architectural justification:
|
|
323
|
+
|
|
324
|
+
```python
|
|
325
|
+
# @invar:allow shell_result: Framework callback signature fixed
|
|
326
|
+
def flask_handler(): ...
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**Valid rule names for @invar:allow:**
|
|
330
|
+
- `shell_result` — Shell function without Result return type
|
|
331
|
+
- `entry_point_too_thick` — Entry point exceeds 15 lines
|
|
332
|
+
- `forbidden_import` — I/O import in Core (rare, justify carefully)
|
|
333
|
+
|
|
334
|
+
Run `invar rules` for complete rule catalog with hints.
|
|
335
|
+
|
|
336
|
+
## Commands
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
invar guard # Full: static + doctests + CrossHair + Hypothesis
|
|
340
|
+
invar guard --static # Static only (quick debug, ~0.5s)
|
|
341
|
+
invar guard --changed # Modified files only
|
|
342
|
+
invar guard --coverage # Collect branch coverage
|
|
343
|
+
invar sig <file> # Show contracts + signatures
|
|
344
|
+
invar map --top 10 # Most-referenced symbols
|
|
345
|
+
invar rules # List all rules with detection/hints (JSON)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Configuration
|
|
349
|
+
|
|
350
|
+
```toml
|
|
351
|
+
# pyproject.toml or invar.toml
|
|
352
|
+
[tool.invar.guard]
|
|
353
|
+
core_paths = ["src/myapp/core"] # Default: ["src/core", "core"]
|
|
354
|
+
shell_paths = ["src/myapp/shell"] # Default: ["src/shell", "shell"]
|
|
355
|
+
max_file_lines = 500 # Default: 500 (warning at 80%)
|
|
356
|
+
max_function_lines = 50 # Default: 50
|
|
357
|
+
# Doctest lines are excluded from size calculations
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Troubleshooting
|
|
361
|
+
|
|
362
|
+
### Size Limits (Agent Quick Reference)
|
|
363
|
+
|
|
364
|
+
| Rule | Limit | Fix |
|
|
365
|
+
|------|-------|-----|
|
|
366
|
+
| `function_too_long` | **50 lines** | Extract helper: `_impl()` + main with docstring |
|
|
367
|
+
| `file_too_long` | **500 lines** | Split by responsibility |
|
|
368
|
+
| `entry_point_too_thick` | **15 lines** | Delegate to Shell functions |
|
|
369
|
+
|
|
370
|
+
*Doctest lines excluded from counts. Limits configurable in `pyproject.toml`.*
|
|
371
|
+
|
|
372
|
+
### Common Errors
|
|
373
|
+
|
|
374
|
+
| Symptom | Cause | Fix |
|
|
375
|
+
|---------|-------|-----|
|
|
376
|
+
| `param_mismatch` error | Lambda missing params | Include ALL params (even defaults) |
|
|
377
|
+
| `shell_result` error | Shell func no Result | Add Result[T,E] or @invar:allow |
|
|
378
|
+
| `is_failure()` not found | Wrong Result check | Use `isinstance(result, Failure)` |
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
*Protocol v5.0 — USBV workflow (DX-32) | [Examples](.invar/examples/)*
|
|
383
|
+
|
|
384
|
+
INVAR_EOF
|
|
385
|
+
echo "</system-reminder>"
|
|
386
|
+
fi
|
|
@@ -67,6 +67,42 @@ def calculate(x: int) -> int:
|
|
|
67
67
|
... # Implementation comes in BUILD
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
#### Function-Level Gates (DX-63)
|
|
71
|
+
|
|
72
|
+
When creating new modules, use **incremental development**:
|
|
73
|
+
|
|
74
|
+
1. Create ONE file
|
|
75
|
+
2. Write contracts for all functions (body = `...`)
|
|
76
|
+
3. Run `invar_guard(contracts_only=true)` to verify coverage
|
|
77
|
+
4. Implement functions
|
|
78
|
+
5. Run `invar_guard(changed=true)`
|
|
79
|
+
6. Proceed to next file
|
|
80
|
+
|
|
81
|
+
❌ Do NOT create multiple file skeletons at once
|
|
82
|
+
❌ Do NOT "structure first, fill later"
|
|
83
|
+
|
|
84
|
+
**TodoList Pattern: Interleaved SPECIFY/BUILD**
|
|
85
|
+
|
|
86
|
+
For each function:
|
|
87
|
+
```
|
|
88
|
+
□ [SPECIFY] Write contract for validate_input
|
|
89
|
+
□ [BUILD] Implement validate_input
|
|
90
|
+
□ [SPECIFY] Write contract for process_data
|
|
91
|
+
□ [BUILD] Implement process_data
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
NOT:
|
|
95
|
+
```
|
|
96
|
+
□ [SPECIFY] Write all contracts
|
|
97
|
+
□ [BUILD] Implement all functions
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Violation Self-Check** — Before writing ANY implementation code:
|
|
101
|
+
1. "Have I written the contract for THIS function?"
|
|
102
|
+
2. "Have I shown it in my response?"
|
|
103
|
+
3. "Have I run `invar_guard(contracts_only=true)`?"
|
|
104
|
+
If any NO → Stop. Write contract first.
|
|
105
|
+
|
|
70
106
|
### 3. BUILD
|
|
71
107
|
|
|
72
108
|
**For complex tasks:** Enter Plan Mode first, get user approval.
|
|
@@ -273,8 +309,7 @@ Agent:
|
|
|
273
309
|
|
|
274
310
|
✓ Final: guard PASS | 0 errors, 1 warning
|
|
275
311
|
```
|
|
276
|
-
<!--/invar:skill-->
|
|
277
|
-
<!--invar:extensions-->
|
|
312
|
+
<!--/invar:skill--><!--invar:extensions-->
|
|
278
313
|
<!-- ========================================================================
|
|
279
314
|
EXTENSIONS REGION - USER EDITABLE
|
|
280
315
|
Add project-specific extensions here. This section is preserved on update.
|
|
@@ -75,8 +75,7 @@ Before any workflow action:
|
|
|
75
75
|
|
|
76
76
|
**Next step?**
|
|
77
77
|
```
|
|
78
|
-
<!--/invar:skill-->
|
|
79
|
-
<!--invar:extensions-->
|
|
78
|
+
<!--/invar:skill--><!--invar:extensions-->
|
|
80
79
|
<!-- ========================================================================
|
|
81
80
|
EXTENSIONS REGION - USER EDITABLE
|
|
82
81
|
Add project-specific extensions here. This section is preserved on update.
|
|
@@ -86,8 +86,7 @@ Create `docs/proposals/DX-XX-[topic].md`:
|
|
|
86
86
|
| Chooses option | /develop to implement |
|
|
87
87
|
| Needs more info | /investigate for analysis |
|
|
88
88
|
| Approves proposal | Document created |
|
|
89
|
-
<!--/invar:skill-->
|
|
90
|
-
<!--invar:extensions-->
|
|
89
|
+
<!--/invar:skill--><!--invar:extensions-->
|
|
91
90
|
<!-- ========================================================================
|
|
92
91
|
EXTENSIONS REGION - USER EDITABLE
|
|
93
92
|
Add project-specific extensions here. This section is preserved on update.
|