Dev10x 0.76.0__tar.gz → 0.78.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.
- {dev10x-0.76.0/src/Dev10x.egg-info → dev10x-0.78.0}/PKG-INFO +1 -1
- {dev10x-0.76.0 → dev10x-0.78.0}/pyproject.toml +12 -2
- {dev10x-0.76.0 → dev10x-0.78.0/src/Dev10x.egg-info}/PKG-INFO +1 -1
- {dev10x-0.76.0 → dev10x-0.78.0}/src/Dev10x.egg-info/SOURCES.txt +16 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/commands/permission.py +129 -1
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/commands/skill.py +23 -33
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/__init__.py +4 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/dev10x_paths.py +15 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/friction_level.py +43 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/install_version.py +51 -0
- dev10x-0.78.0/src/dev10x/domain/sensitivity.py +298 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/session_rules.py +3 -17
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/hooks/edit_validator.py +35 -4
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/hooks/session.py +2 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/hooks/session_dispatch.py +39 -0
- dev10x-0.78.0/src/dev10x/mcp/_app.py +76 -0
- dev10x-0.78.0/src/dev10x/mcp/daemon.py +517 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/mcp/git_tools.py +60 -4
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/mcp/github_tools.py +26 -1
- dev10x-0.78.0/src/dev10x/mcp/knowledge_prompts.py +85 -0
- dev10x-0.78.0/src/dev10x/mcp/knowledge_resources.py +132 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/mcp/misc_tools.py +40 -10
- dev10x-0.78.0/src/dev10x/mcp/resource_watcher.py +413 -0
- dev10x-0.78.0/src/dev10x/mcp/roots_manager.py +277 -0
- dev10x-0.78.0/src/dev10x/mcp/roots_tools.py +41 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/mcp/server_cli.py +11 -2
- dev10x-0.78.0/src/dev10x/mcp/session_store.py +421 -0
- dev10x-0.78.0/src/dev10x/mcp/wiring.py +153 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/doctor/registry.py +1 -0
- dev10x-0.78.0/src/dev10x/skills/doctor/strategies/mcp_horizontal_duplicates.py +118 -0
- dev10x-0.78.0/src/dev10x/skills/harvest/__init__.py +7 -0
- dev10x-0.78.0/src/dev10x/skills/harvest/closed_prs.py +150 -0
- dev10x-0.78.0/src/dev10x/skills/harvest/review_threads.py +195 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/monitor/pr_notify.py +10 -29
- dev10x-0.78.0/src/dev10x/skills/notifications/slack_notify.py +312 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/permission/clean_project_files.py +16 -3
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/permission/doctor.py +137 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/permission/enumerate_mcp.py +218 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/subprocess_utils.py +92 -5
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/utilities/__init__.py +14 -1
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/validators/__init__.py +13 -0
- dev10x-0.78.0/src/dev10x/validators/sensitivity_target.py +122 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/validators/skill_redirect.py +18 -19
- dev10x-0.78.0/src/dev10x/validators/spec_drift.py +185 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/tests/test_subprocess_utils.py +181 -3
- dev10x-0.76.0/src/dev10x/mcp/_app.py +0 -11
- {dev10x-0.76.0 → dev10x-0.78.0}/LICENSE +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/README.md +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/setup.cfg +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/Dev10x.egg-info/dependency_links.txt +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/Dev10x.egg-info/entry_points.txt +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/Dev10x.egg-info/requires.txt +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/Dev10x.egg-info/top_level.txt +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/audit/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/audit/analyze.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/audit/log_reader.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/audit/permissions_model.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/cli.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/commands/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/commands/config.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/commands/github_app.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/commands/github_app_api.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/commands/hook.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/commands/init.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/commands/platform.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/commands/playbook.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/commands/validate.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/config/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/config/loader.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/db/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/audit_writer.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/claude_paths.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/common/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/common/allow_rule.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/common/branch_name.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/common/repository_ref.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/common/result.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/common/skill_name.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/common/ticket_id.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/config_loader.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/documents/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/documents/config_document.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/documents/plan.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/documents/session_state.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/documents/settings_document.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/documents/task.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/events/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/events/hook_input.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/file_locks.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/git_context.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/hook_telemetry.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/profile_tier.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/rules/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/rules/policy_rule.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/rules/rule_engine.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/rules/sql.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/rules/validation_rule.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/domain/session_document.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/git/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/github/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/github/app_auth.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/github/slack.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/hooks/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/hooks/audit_emit.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/hooks/permission_diagnostics.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/hooks/session_place.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/hooks/session_policy.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/hooks/skill.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/hooks/task_plan_sync.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/mcp/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/mcp/audit_tools.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/mcp/plan_tools.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/mcp/server_db.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/mcp/transport.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/monitor/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/permission/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/plan/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/plan/service.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/platform/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/platform/registry.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/release/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/runner/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/scope/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/scope/_matcher.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/scope/index_parser.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/scope/norms_renderer.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/scope/safeguards_renderer.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/session/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/session/queries.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skill_index/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skill_index/builder.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/audit/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/audit/analyze_actions.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/audit/analyze_permissions.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/audit/cli_friction.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/audit/extract_session.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/audit/instruction_budget.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/audit/privacy.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/common/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/common/jtbd.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/database/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/doctor/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/doctor/strategies/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/doctor/strategies/forbidden_token_priming.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/doctor/strategies/mcp_vs_script_drift.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/doctor/strategies/missing_linear_mcp_allow.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/doctor/strategy.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/evidence/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/git_fixup/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/git_fixup/find_fixup_target.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/monitor/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/monitor/ci_check_status.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/notifications/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/notifications/slack_review_request.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/orchestration/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/orchestration/batch_detection.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/orchestration/subagent_protocol.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/permission/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/permission/backup.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/permission/baseline-permissions.yaml +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/permission/cluster_paths.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/permission/config.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/permission/file_lock.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/permission/local_skill_approval.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/permission/merge_worktree_permissions.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/permission/update_paths.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/permission/uv_run_project.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/permission_investigator/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/permission_investigator/fixtures.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/permission_investigator/matrix.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/permission_investigator/report.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/playbook/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/playbook/compare.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/playbook/discovery.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/playbook/report.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/release/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/release/classifier.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/skills/release/collect_prs.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/spec/__init__.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/spec/drift_detector.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/validators/_quote_strip.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/validators/base.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/validators/bash_aggregation.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/validators/command-skill-map.yaml +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/validators/command_substitution.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/validators/commit_jtbd.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/validators/execution_safety.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/validators/mcp_prefix.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/validators/pipeline_allow.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/validators/pr_base.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/validators/prefix_friction.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/validators/redundant_fetch.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/validators/registry.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/validators/safe_expansion.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/validators/safe_subshell.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/src/dev10x/validators/sql_safety.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/tests/test_cli.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/tests/test_cli_lazy.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/tests/test_cwd_discipline_sweep.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/tests/test_cwd_enforcement.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/tests/test_integration.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/tests/test_no_module_scope_gitcontext.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/tests/test_script_loadability.py +0 -0
- {dev10x-0.76.0 → dev10x-0.78.0}/tests/test_shell_script_flags.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "Dev10x"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.78.0"
|
|
4
4
|
description = "Claude Code plugin providing reusable skills, hooks, and commands"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { file = "LICENSE" }
|
|
@@ -63,7 +63,7 @@ addopts = "--cov=src/dev10x --cov-report=term-missing"
|
|
|
63
63
|
source = ["src/dev10x"]
|
|
64
64
|
|
|
65
65
|
[tool.coverage.report]
|
|
66
|
-
fail_under =
|
|
66
|
+
fail_under = 75
|
|
67
67
|
show_missing = true
|
|
68
68
|
exclude_lines = ["if __name__", "pragma: no cover"]
|
|
69
69
|
|
|
@@ -74,5 +74,15 @@ src = ["src"]
|
|
|
74
74
|
[tool.ruff.lint]
|
|
75
75
|
select = ["E", "F", "I", "UP"]
|
|
76
76
|
|
|
77
|
+
[tool.pytest_benchmark]
|
|
78
|
+
# Store baselines in .benchmarks/ (gitignored per-run, cached by CI)
|
|
79
|
+
storage = "file"
|
|
80
|
+
# Fail on mean regression > 20% vs stored baseline
|
|
81
|
+
compare_fail = ["mean:20%"]
|
|
82
|
+
# Minimum rounds so outliers don't skew results
|
|
83
|
+
min_rounds = 5
|
|
84
|
+
timer = "perf_counter"
|
|
85
|
+
disable_gc = true
|
|
86
|
+
|
|
77
87
|
[tool.mypy]
|
|
78
88
|
mypy_path = "src"
|
|
@@ -39,6 +39,7 @@ src/dev10x/domain/git_context.py
|
|
|
39
39
|
src/dev10x/domain/hook_telemetry.py
|
|
40
40
|
src/dev10x/domain/install_version.py
|
|
41
41
|
src/dev10x/domain/profile_tier.py
|
|
42
|
+
src/dev10x/domain/sensitivity.py
|
|
42
43
|
src/dev10x/domain/session_document.py
|
|
43
44
|
src/dev10x/domain/session_rules.py
|
|
44
45
|
src/dev10x/domain/common/__init__.py
|
|
@@ -78,13 +79,21 @@ src/dev10x/hooks/task_plan_sync.py
|
|
|
78
79
|
src/dev10x/mcp/__init__.py
|
|
79
80
|
src/dev10x/mcp/_app.py
|
|
80
81
|
src/dev10x/mcp/audit_tools.py
|
|
82
|
+
src/dev10x/mcp/daemon.py
|
|
81
83
|
src/dev10x/mcp/git_tools.py
|
|
82
84
|
src/dev10x/mcp/github_tools.py
|
|
85
|
+
src/dev10x/mcp/knowledge_prompts.py
|
|
86
|
+
src/dev10x/mcp/knowledge_resources.py
|
|
83
87
|
src/dev10x/mcp/misc_tools.py
|
|
84
88
|
src/dev10x/mcp/plan_tools.py
|
|
89
|
+
src/dev10x/mcp/resource_watcher.py
|
|
90
|
+
src/dev10x/mcp/roots_manager.py
|
|
91
|
+
src/dev10x/mcp/roots_tools.py
|
|
85
92
|
src/dev10x/mcp/server_cli.py
|
|
86
93
|
src/dev10x/mcp/server_db.py
|
|
94
|
+
src/dev10x/mcp/session_store.py
|
|
87
95
|
src/dev10x/mcp/transport.py
|
|
96
|
+
src/dev10x/mcp/wiring.py
|
|
88
97
|
src/dev10x/monitor/__init__.py
|
|
89
98
|
src/dev10x/permission/__init__.py
|
|
90
99
|
src/dev10x/plan/__init__.py
|
|
@@ -118,15 +127,20 @@ src/dev10x/skills/doctor/registry.py
|
|
|
118
127
|
src/dev10x/skills/doctor/strategy.py
|
|
119
128
|
src/dev10x/skills/doctor/strategies/__init__.py
|
|
120
129
|
src/dev10x/skills/doctor/strategies/forbidden_token_priming.py
|
|
130
|
+
src/dev10x/skills/doctor/strategies/mcp_horizontal_duplicates.py
|
|
121
131
|
src/dev10x/skills/doctor/strategies/mcp_vs_script_drift.py
|
|
122
132
|
src/dev10x/skills/doctor/strategies/missing_linear_mcp_allow.py
|
|
123
133
|
src/dev10x/skills/evidence/__init__.py
|
|
124
134
|
src/dev10x/skills/git_fixup/__init__.py
|
|
125
135
|
src/dev10x/skills/git_fixup/find_fixup_target.py
|
|
136
|
+
src/dev10x/skills/harvest/__init__.py
|
|
137
|
+
src/dev10x/skills/harvest/closed_prs.py
|
|
138
|
+
src/dev10x/skills/harvest/review_threads.py
|
|
126
139
|
src/dev10x/skills/monitor/__init__.py
|
|
127
140
|
src/dev10x/skills/monitor/ci_check_status.py
|
|
128
141
|
src/dev10x/skills/monitor/pr_notify.py
|
|
129
142
|
src/dev10x/skills/notifications/__init__.py
|
|
143
|
+
src/dev10x/skills/notifications/slack_notify.py
|
|
130
144
|
src/dev10x/skills/notifications/slack_review_request.py
|
|
131
145
|
src/dev10x/skills/orchestration/__init__.py
|
|
132
146
|
src/dev10x/skills/orchestration/batch_detection.py
|
|
@@ -174,7 +188,9 @@ src/dev10x/validators/redundant_fetch.py
|
|
|
174
188
|
src/dev10x/validators/registry.py
|
|
175
189
|
src/dev10x/validators/safe_expansion.py
|
|
176
190
|
src/dev10x/validators/safe_subshell.py
|
|
191
|
+
src/dev10x/validators/sensitivity_target.py
|
|
177
192
|
src/dev10x/validators/skill_redirect.py
|
|
193
|
+
src/dev10x/validators/spec_drift.py
|
|
178
194
|
src/dev10x/validators/sql_safety.py
|
|
179
195
|
tests/test_cli.py
|
|
180
196
|
tests/test_cli_lazy.py
|
|
@@ -304,13 +304,49 @@ def init() -> None:
|
|
|
304
304
|
help="Print one line per changed file (count) instead of full per-file details",
|
|
305
305
|
)
|
|
306
306
|
@click.option("--restore", is_flag=True, help="Restore settings from most recent backups")
|
|
307
|
-
|
|
307
|
+
@click.option(
|
|
308
|
+
"--skip-global-dedup",
|
|
309
|
+
is_flag=True,
|
|
310
|
+
default=False,
|
|
311
|
+
help=(
|
|
312
|
+
"Do not remove project rules that are exact duplicates of global rules. "
|
|
313
|
+
"Retained for backward compatibility — global-dedup is now OFF by "
|
|
314
|
+
"default, so this flag is only meaningful alongside --aggressive."
|
|
315
|
+
),
|
|
316
|
+
)
|
|
317
|
+
@click.option(
|
|
318
|
+
"--aggressive",
|
|
319
|
+
is_flag=True,
|
|
320
|
+
default=False,
|
|
321
|
+
help=(
|
|
322
|
+
"Also remove project rules that are exact duplicates of global rules. "
|
|
323
|
+
"OFF by default (finding #47): global→project rule inheritance is NOT "
|
|
324
|
+
"guaranteed when a project has its own settings.local.json, so stripping "
|
|
325
|
+
"the local copy can reintroduce permission prompts. Only enable after "
|
|
326
|
+
"`dev10x permission investigate` confirms inheritance holds for this "
|
|
327
|
+
"environment. Recover a bad run with `dev10x permission clean --restore`."
|
|
328
|
+
),
|
|
329
|
+
)
|
|
330
|
+
def clean(
|
|
331
|
+
*,
|
|
332
|
+
dry_run: bool,
|
|
333
|
+
verbose: bool,
|
|
334
|
+
summary: bool,
|
|
335
|
+
restore: bool,
|
|
336
|
+
skip_global_dedup: bool,
|
|
337
|
+
aggressive: bool,
|
|
338
|
+
) -> None:
|
|
308
339
|
"""Clean redundant permissions from project settings files."""
|
|
309
340
|
from dev10x.skills.permission import clean_project_files as mod
|
|
310
341
|
|
|
311
342
|
if restore:
|
|
312
343
|
sys.exit(mod._restore(config_path=mod.find_config()))
|
|
313
344
|
|
|
345
|
+
# Global-dedup is opt-in (finding #47). It runs only under --aggressive,
|
|
346
|
+
# and --skip-global-dedup always wins so the safe behavior cannot be
|
|
347
|
+
# accidentally re-enabled.
|
|
348
|
+
skip_global_dedup = skip_global_dedup or not aggressive
|
|
349
|
+
|
|
314
350
|
config_path = mod.find_config()
|
|
315
351
|
click.echo(f"Config: {config_path}")
|
|
316
352
|
config = mod.load_config(config_path)
|
|
@@ -342,6 +378,7 @@ def clean(*, dry_run: bool, verbose: bool, summary: bool, restore: bool) -> None
|
|
|
342
378
|
total_kept = 0
|
|
343
379
|
files_changed = 0
|
|
344
380
|
total_secrets = 0
|
|
381
|
+
total_global_dedup = 0
|
|
345
382
|
|
|
346
383
|
for path in sorted(settings_files):
|
|
347
384
|
result, messages = mod.clean_file(
|
|
@@ -352,6 +389,7 @@ def clean(*, dry_run: bool, verbose: bool, summary: bool, restore: bool) -> None
|
|
|
352
389
|
cache_root=cache_root,
|
|
353
390
|
dry_run=dry_run,
|
|
354
391
|
verbose=verbose,
|
|
392
|
+
skip_global_dedup=skip_global_dedup,
|
|
355
393
|
)
|
|
356
394
|
if result is None:
|
|
357
395
|
click.echo(f"\n{path}")
|
|
@@ -376,6 +414,7 @@ def clean(*, dry_run: bool, verbose: bool, summary: bool, restore: bool) -> None
|
|
|
376
414
|
total_removed += result.total_removed
|
|
377
415
|
total_kept += len(result.kept)
|
|
378
416
|
total_secrets += len(result.leaked_secrets)
|
|
417
|
+
total_global_dedup += len(result.exact_duplicates)
|
|
379
418
|
if result.total_removed > 0:
|
|
380
419
|
files_changed += 1
|
|
381
420
|
else:
|
|
@@ -389,6 +428,14 @@ def clean(*, dry_run: bool, verbose: bool, summary: bool, restore: bool) -> None
|
|
|
389
428
|
click.echo(f"{verb} {total_removed} rules across {files_changed} files.")
|
|
390
429
|
click.echo(f"Kept {total_kept} rules total.")
|
|
391
430
|
|
|
431
|
+
if total_global_dedup > 0 and not skip_global_dedup:
|
|
432
|
+
click.echo(
|
|
433
|
+
f"\n⚠ WARNING: {total_global_dedup} rules removed as exact duplicates of global"
|
|
434
|
+
" rules (--aggressive). Global→project rule inheritance is NOT guaranteed when a"
|
|
435
|
+
" project has its own settings.local.json (finding #47). If new permission prompts"
|
|
436
|
+
" appear, recover with `dev10x permission clean --restore`."
|
|
437
|
+
)
|
|
438
|
+
|
|
392
439
|
if total_secrets > 0:
|
|
393
440
|
click.echo(
|
|
394
441
|
f"\n⚠ Found {total_secrets} rules containing leaked secrets."
|
|
@@ -951,6 +998,87 @@ def doctor_enable_group(*, group_name: str, dry_run: bool) -> None:
|
|
|
951
998
|
click.echo(f"\n{verb} {total_added} rules from group {group_name!r}.")
|
|
952
999
|
|
|
953
1000
|
|
|
1001
|
+
@doctor.command(name="anchor-worktree-roots")
|
|
1002
|
+
@click.option("--dry-run", is_flag=True, help="Show changes without modifying files")
|
|
1003
|
+
@click.option("--quiet", is_flag=True, help="Suppress per-file details")
|
|
1004
|
+
def doctor_anchor_worktree_roots(*, dry_run: bool, quiet: bool) -> None:
|
|
1005
|
+
"""Anchor .worktrees parents in additionalDirectories and flag relative skill-script rules.
|
|
1006
|
+
|
|
1007
|
+
Worktrees accumulate per-leaf ``additionalDirectories`` entries instead of
|
|
1008
|
+
anchoring the project-level ``.worktrees`` parent. This command:
|
|
1009
|
+
|
|
1010
|
+
\b
|
|
1011
|
+
1. Discovers every ``<project>/.worktrees`` parent beneath configured roots.
|
|
1012
|
+
2. Ensures each parent is present in ``additionalDirectories`` across all
|
|
1013
|
+
settings files — anchoring at the parent covers all sibling and future
|
|
1014
|
+
worktrees without re-prompting per leaf (GH-376).
|
|
1015
|
+
3. Flags bare-relative ``.claude/skills/.../scripts/`` allow rules that
|
|
1016
|
+
silently target different skill dirs per worktree CWD.
|
|
1017
|
+
"""
|
|
1018
|
+
from dev10x.skills.permission import doctor as mod
|
|
1019
|
+
from dev10x.skills.permission import update_paths as paths_mod
|
|
1020
|
+
|
|
1021
|
+
config_path = paths_mod.find_config()
|
|
1022
|
+
config = paths_mod.load_config(config_path)
|
|
1023
|
+
roots = config.get("roots", [])
|
|
1024
|
+
if not roots:
|
|
1025
|
+
click.echo("No roots configured. Run `dev10x permission init` first.")
|
|
1026
|
+
return
|
|
1027
|
+
|
|
1028
|
+
settings_files = paths_mod.find_settings_files(
|
|
1029
|
+
roots=roots,
|
|
1030
|
+
include_user=config.get("include_user_settings", True),
|
|
1031
|
+
)
|
|
1032
|
+
if not settings_files:
|
|
1033
|
+
click.echo("No settings files found.")
|
|
1034
|
+
return
|
|
1035
|
+
|
|
1036
|
+
if dry_run and not quiet:
|
|
1037
|
+
click.echo("(dry run — no files will be modified)\n")
|
|
1038
|
+
|
|
1039
|
+
worktrees_parents = mod.discover_worktrees_parents(roots)
|
|
1040
|
+
if not quiet:
|
|
1041
|
+
if worktrees_parents:
|
|
1042
|
+
click.echo(f"Discovered {len(worktrees_parents)} .worktrees parent(s):")
|
|
1043
|
+
for parent in worktrees_parents:
|
|
1044
|
+
click.echo(f" {parent}")
|
|
1045
|
+
else:
|
|
1046
|
+
click.echo("No .worktrees parents found beneath configured roots.")
|
|
1047
|
+
|
|
1048
|
+
anchor_result = mod.anchor_worktree_roots(
|
|
1049
|
+
settings_files,
|
|
1050
|
+
roots=roots,
|
|
1051
|
+
dry_run=dry_run,
|
|
1052
|
+
)
|
|
1053
|
+
|
|
1054
|
+
workspace_count = anchor_result.workspace_anchored
|
|
1055
|
+
skill_script_count = sum(1 for f in anchor_result.findings if f.scope == "skill-script")
|
|
1056
|
+
|
|
1057
|
+
for finding in anchor_result.findings:
|
|
1058
|
+
if finding.scope == "workspace" and not quiet:
|
|
1059
|
+
click.echo(f"\n{finding.settings_path}")
|
|
1060
|
+
click.echo(f" {finding.suggestion}")
|
|
1061
|
+
elif finding.scope == "skill-script":
|
|
1062
|
+
click.echo(f"\n{finding.settings_path}")
|
|
1063
|
+
click.echo(f" ! RELATIVE_SKILL_SCRIPT: {finding.rule}")
|
|
1064
|
+
if not quiet:
|
|
1065
|
+
click.echo(f" {finding.suggestion}")
|
|
1066
|
+
|
|
1067
|
+
if workspace_count == 0 and skill_script_count == 0:
|
|
1068
|
+
click.echo("\nAll settings files already have worktrees parents anchored.")
|
|
1069
|
+
else:
|
|
1070
|
+
if workspace_count > 0:
|
|
1071
|
+
verb = "Would anchor" if dry_run else "Anchored"
|
|
1072
|
+
click.echo(
|
|
1073
|
+
f"\n{verb} {workspace_count} worktrees parent path(s) in additionalDirectories."
|
|
1074
|
+
)
|
|
1075
|
+
if skill_script_count > 0:
|
|
1076
|
+
click.echo(
|
|
1077
|
+
f"\nFound {skill_script_count} relative skill-script rule(s) "
|
|
1078
|
+
f"— rewrite to absolute plugin-cache paths or Skill() invocations."
|
|
1079
|
+
)
|
|
1080
|
+
|
|
1081
|
+
|
|
954
1082
|
@permission.command(name="record-upgrade")
|
|
955
1083
|
@click.option(
|
|
956
1084
|
"--version",
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import subprocess
|
|
4
3
|
import sys
|
|
5
4
|
from pathlib import Path
|
|
6
5
|
|
|
@@ -23,14 +22,6 @@ def notify() -> None:
|
|
|
23
22
|
"""
|
|
24
23
|
|
|
25
24
|
|
|
26
|
-
def _plugin_root() -> Path:
|
|
27
|
-
"""Resolve the plugin root containing the skills/ directory.
|
|
28
|
-
|
|
29
|
-
`src/dev10x/commands/skill.py` -> parents[3] = plugin root.
|
|
30
|
-
"""
|
|
31
|
-
return Path(__file__).resolve().parents[3]
|
|
32
|
-
|
|
33
|
-
|
|
34
25
|
@notify.command(name="slack-review-prepare")
|
|
35
26
|
@click.option("--pr", type=int, required=True, help="PR number")
|
|
36
27
|
@click.option("--repo", required=True, help="GitHub repo (owner/name)")
|
|
@@ -69,37 +60,36 @@ def slack_send(
|
|
|
69
60
|
thread_ts: str | None,
|
|
70
61
|
workspace: str | None,
|
|
71
62
|
) -> None:
|
|
72
|
-
"""Send a Slack message via the
|
|
63
|
+
"""Send a Slack message via the importable slack_notify module (GH-442).
|
|
73
64
|
|
|
74
|
-
Delegates to `skills
|
|
75
|
-
|
|
76
|
-
|
|
65
|
+
Delegates to `dev10x.skills.notifications.slack_notify` so the command
|
|
66
|
+
works when dev10x is installed via ``uvx`` — where ``skills/`` data files
|
|
67
|
+
are not shipped as part of the wheel and cannot be reached by filesystem
|
|
68
|
+
traversal from the installed package location.
|
|
77
69
|
"""
|
|
78
70
|
if not message and not message_file:
|
|
79
71
|
raise click.UsageError("Provide --message or --message-file.")
|
|
80
72
|
|
|
81
|
-
|
|
82
|
-
if not slack_notify.exists():
|
|
83
|
-
click.echo(f"slack-notify.py not found at {slack_notify}", err=True)
|
|
84
|
-
sys.exit(1)
|
|
73
|
+
from dev10x.skills.notifications import slack_notify
|
|
85
74
|
|
|
86
|
-
cmd: list[str] = [str(slack_notify), "--channel", channel]
|
|
87
|
-
if message_file is not None:
|
|
88
|
-
cmd.extend(["--message-file", str(message_file)])
|
|
89
|
-
if message is not None:
|
|
90
|
-
cmd.extend(["--message", message])
|
|
91
|
-
if thread_ts is not None:
|
|
92
|
-
cmd.extend(["--thread-ts", thread_ts])
|
|
93
75
|
if workspace is not None:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
76
|
+
slack_notify.set_workspace(workspace)
|
|
77
|
+
|
|
78
|
+
msg: str
|
|
79
|
+
if message_file is not None:
|
|
80
|
+
msg = message_file.read_text()
|
|
81
|
+
else:
|
|
82
|
+
msg = message # type: ignore[assignment] # validated above
|
|
83
|
+
|
|
84
|
+
ts = slack_notify.send_slack_message(
|
|
85
|
+
channel=channel,
|
|
86
|
+
message=msg,
|
|
87
|
+
thread_ts=thread_ts,
|
|
88
|
+
)
|
|
89
|
+
if ts:
|
|
90
|
+
click.echo(f"✅ Slack message sent successfully! ts={ts}")
|
|
91
|
+
else:
|
|
92
|
+
sys.exit(1)
|
|
103
93
|
|
|
104
94
|
|
|
105
95
|
@skill.command(name="count-instructions")
|
|
@@ -8,6 +8,7 @@ from dev10x.domain.git_context import GitContext
|
|
|
8
8
|
from dev10x.domain.rules.rule_engine import RuleEngine, RuleMatch
|
|
9
9
|
from dev10x.domain.rules.sql import SqlStatement, is_read_only_sql
|
|
10
10
|
from dev10x.domain.rules.validation_rule import Compensation, Rule
|
|
11
|
+
from dev10x.domain.sensitivity import SensitivityClassifier, SensitivityLabel, SensitivityMatch
|
|
11
12
|
|
|
12
13
|
__all__ = [
|
|
13
14
|
"Compensation",
|
|
@@ -28,6 +29,9 @@ __all__ = [
|
|
|
28
29
|
"Rule",
|
|
29
30
|
"RuleEngine",
|
|
30
31
|
"RuleMatch",
|
|
32
|
+
"SensitivityClassifier",
|
|
33
|
+
"SensitivityLabel",
|
|
34
|
+
"SensitivityMatch",
|
|
31
35
|
"SqlStatement",
|
|
32
36
|
"is_read_only_sql",
|
|
33
37
|
]
|
|
@@ -164,6 +164,13 @@ class Dev10xConfigDir:
|
|
|
164
164
|
_legacy_settings_pr_merge_yaml,
|
|
165
165
|
)
|
|
166
166
|
|
|
167
|
+
@classmethod
|
|
168
|
+
def plugin_maintenance_prefs_yaml(cls) -> Path:
|
|
169
|
+
return _with_lazy_migration(
|
|
170
|
+
cls._resolve("plugin-maintenance-prefs.yaml"),
|
|
171
|
+
_legacy_plugin_maintenance_prefs_yaml,
|
|
172
|
+
)
|
|
173
|
+
|
|
167
174
|
|
|
168
175
|
def _with_lazy_migration(current: Path, legacy_provider: Callable[[], Path]) -> Path:
|
|
169
176
|
migrate_path(legacy=legacy_provider(), current=current)
|
|
@@ -185,6 +192,10 @@ _legacy_github_reviewers_config_yaml = ClaudeDir.github_reviewers_config_yaml
|
|
|
185
192
|
_legacy_settings_pr_merge_yaml = ClaudeDir.settings_pr_merge_yaml
|
|
186
193
|
|
|
187
194
|
|
|
195
|
+
def _legacy_plugin_maintenance_prefs_yaml() -> Path:
|
|
196
|
+
return ClaudeDir.memory_dev10x_dir() / "plugin-maintenance-prefs.yaml"
|
|
197
|
+
|
|
198
|
+
|
|
188
199
|
def _legacy_playbooks_dir() -> Path:
|
|
189
200
|
return ClaudeDir.memory_dev10x_dir() / "playbooks"
|
|
190
201
|
|
|
@@ -219,6 +230,10 @@ def _migration_pairs() -> list[tuple[Path, Path]]:
|
|
|
219
230
|
_legacy_settings_pr_merge_yaml(),
|
|
220
231
|
Dev10xConfigDir._resolve("settings-pr-merge.yaml"),
|
|
221
232
|
),
|
|
233
|
+
(
|
|
234
|
+
_legacy_plugin_maintenance_prefs_yaml(),
|
|
235
|
+
Dev10xConfigDir._resolve("plugin-maintenance-prefs.yaml"),
|
|
236
|
+
),
|
|
222
237
|
]
|
|
223
238
|
|
|
224
239
|
|
|
@@ -54,5 +54,48 @@ class FrictionLevel(StrEnum):
|
|
|
54
54
|
return member
|
|
55
55
|
return cls.default()
|
|
56
56
|
|
|
57
|
+
def pending_decisions_guidance(self) -> str:
|
|
58
|
+
"""Return the session-resume instruction for pending-decision state.
|
|
59
|
+
|
|
60
|
+
Called by ``DecisionGuidanceRule`` when the plan has one or more
|
|
61
|
+
unresolved decision tasks. Each member encodes its own pacing
|
|
62
|
+
rule, eliminating the if/elif dispatch from the rule body.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
A non-empty instruction string for every recognised member.
|
|
66
|
+
"""
|
|
67
|
+
if self is FrictionLevel.ADAPTIVE:
|
|
68
|
+
return (
|
|
69
|
+
"Session resumed with pending decisions. Friction level is adaptive — "
|
|
70
|
+
"auto-select recommended options for all queued decisions and continue "
|
|
71
|
+
"advancing through the task list without calling AskUserQuestion."
|
|
72
|
+
)
|
|
73
|
+
return (
|
|
74
|
+
"Session resumed with pending decisions. "
|
|
75
|
+
"Re-ask each pending decision using AskUserQuestion — "
|
|
76
|
+
"invoke Dev10x:ask before advancing."
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def fallback_guidance(self, *, fallback: str) -> str:
|
|
80
|
+
"""Return a friction-level-specific fallback clause for block messages.
|
|
81
|
+
|
|
82
|
+
In GUIDED mode the agent is shown a concrete fallback path (skill
|
|
83
|
+
guardrails to apply manually, or an MCP-unavailability escape).
|
|
84
|
+
In STRICT and ADAPTIVE modes the block is unadorned — the fallback
|
|
85
|
+
text is omitted so the message stays concise.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
fallback: The fallback snippet to surface in GUIDED mode.
|
|
89
|
+
May be an MCP-server fallback description, a manual-
|
|
90
|
+
guardrail list, or any other escape-hatch hint.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
The fallback clause (including a leading newline) when GUIDED,
|
|
94
|
+
or an empty string for other levels.
|
|
95
|
+
"""
|
|
96
|
+
if self is FrictionLevel.GUIDED:
|
|
97
|
+
return fallback
|
|
98
|
+
return ""
|
|
99
|
+
|
|
57
100
|
|
|
58
101
|
__all__ = ["FrictionLevel"]
|
|
@@ -85,6 +85,55 @@ def write_applied_version(
|
|
|
85
85
|
return path
|
|
86
86
|
|
|
87
87
|
|
|
88
|
+
def read_running_hook_version(*, plugin_root: Path | None = None) -> str | None:
|
|
89
|
+
"""Return the version that is *currently executing* inside this session.
|
|
90
|
+
|
|
91
|
+
``$CLAUDE_PLUGIN_ROOT`` is set by Claude Code to the plugin directory
|
|
92
|
+
that was resolved when the session started. Hooks loaded from that
|
|
93
|
+
directory run for the entire session lifetime — an on-disk upgrade
|
|
94
|
+
does not swap them out. Reading the version from this directory gives
|
|
95
|
+
us the *running* version, which may lag behind the latest installed
|
|
96
|
+
version when the user ran ``claude plugin update`` mid-session.
|
|
97
|
+
|
|
98
|
+
Returns ``None`` when the running root is unknown (e.g., local
|
|
99
|
+
``--plugin-dir`` dev runs that do not carry a versioned cache path).
|
|
100
|
+
"""
|
|
101
|
+
root = plugin_root or _default_plugin_root()
|
|
102
|
+
return read_plugin_version(plugin_root=root)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def read_latest_installed_version(
|
|
106
|
+
*,
|
|
107
|
+
cache_dir: Path | None = None,
|
|
108
|
+
publisher: str = "Dev10x-Guru",
|
|
109
|
+
plugin_slug: str = "dev10x-claude",
|
|
110
|
+
) -> str | None:
|
|
111
|
+
"""Return the highest-version directory under the plugin cache.
|
|
112
|
+
|
|
113
|
+
Scans ``~/.claude/plugins/cache/<publisher>/<plugin_slug>/`` for
|
|
114
|
+
semver-named subdirectories and returns the latest. Returns ``None``
|
|
115
|
+
when the directory is absent or empty (e.g., ``--plugin-dir`` installs
|
|
116
|
+
that bypass the cache).
|
|
117
|
+
"""
|
|
118
|
+
from dev10x.domain.claude_paths import ClaudeDir
|
|
119
|
+
|
|
120
|
+
root = cache_dir or (ClaudeDir.plugins_cache_dir() / publisher / plugin_slug)
|
|
121
|
+
if not root.is_dir():
|
|
122
|
+
return None
|
|
123
|
+
versions = sorted(
|
|
124
|
+
(d for d in root.iterdir() if d.is_dir()),
|
|
125
|
+
key=lambda p: _version_tuple(p.name),
|
|
126
|
+
)
|
|
127
|
+
return versions[-1].name if versions else None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _version_tuple(version: str) -> tuple[int, ...]:
|
|
131
|
+
try:
|
|
132
|
+
return tuple(int(x) for x in version.split("."))
|
|
133
|
+
except ValueError:
|
|
134
|
+
return (0,)
|
|
135
|
+
|
|
136
|
+
|
|
88
137
|
def record_upgrade(*, version: str | None = None) -> Result[dict[str, Any]]:
|
|
89
138
|
"""Record the currently-installed plugin version as applied.
|
|
90
139
|
|
|
@@ -169,7 +218,9 @@ __all__ = [
|
|
|
169
218
|
"InstallState",
|
|
170
219
|
"install_state",
|
|
171
220
|
"read_applied_version",
|
|
221
|
+
"read_latest_installed_version",
|
|
172
222
|
"read_plugin_version",
|
|
223
|
+
"read_running_hook_version",
|
|
173
224
|
"record_upgrade",
|
|
174
225
|
"write_applied_version",
|
|
175
226
|
]
|