claude-dev-env 2.9.0 → 2.11.0
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/CLAUDE.md +2 -2
- package/_shared/advisor/CLAUDE.md +3 -2
- package/_shared/advisor/advisor-protocol.md +74 -108
- package/_shared/advisor/reference/advisor-block.md +37 -0
- package/_shared/advisor/reference/cli-chain.md +45 -0
- package/_shared/advisor/reference/consult-format.md +41 -0
- package/_shared/advisor/reference/lifecycle.md +21 -0
- package/_shared/advisor/reference/sol-rung.md +31 -0
- package/_shared/advisor/reference/spawn-walk-log.md +31 -0
- package/_shared/advisor/reference/third-party-bind.md +30 -0
- package/_shared/advisor/reference/warm-up.md +33 -0
- package/_shared/advisor/scripts/codex_sol_advisor.py +449 -0
- package/_shared/advisor/scripts/config/advisor_scripts_constants/advisor_route_constants.py +21 -0
- package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +19 -17
- package/_shared/advisor/scripts/config/advisor_scripts_constants/sol_advisor_constants.py +28 -0
- package/_shared/advisor/scripts/model_tier_run_validator.py +32 -9
- package/_shared/advisor/scripts/tests/test_codex_sol_advisor.py +474 -0
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +79 -0
- package/_shared/advisor/scripts/tests/test_tier_model_ids.py +39 -17
- package/_shared/advisor/scripts/tier_model_ids.py +24 -0
- package/_shared/pr-loop/CLAUDE.md +1 -1
- package/_shared/pr-loop/audit-contract.md +17 -6
- package/_shared/pr-loop/audit-reply-template.md +4 -4
- package/_shared/pr-loop/code-rules-gate.md +3 -5
- package/_shared/pr-loop/fix-protocol.md +2 -3
- package/_shared/pr-loop/gh-payloads.md +1 -1
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -1
- package/_shared/pr-loop/scripts/README.md +1 -1
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_running.py +16 -1
- package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +11 -5
- package/_shared/pr-loop/scripts/preflight.py +9 -4
- package/_shared/pr-loop/scripts/reviews_disabled.py +50 -22
- package/_shared/pr-loop/scripts/tests/conftest.py +20 -0
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +6 -6
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +50 -6
- package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +1 -1
- package/_shared/pr-loop/state-schema.md +5 -14
- package/agents/CLAUDE.md +2 -2
- package/agents/clean-coder.md +58 -548
- package/agents/code-quality-agent.md +10 -2
- package/agents/code-verifier.md +1 -1
- package/agents/test_agent_frontmatter.py +32 -40
- package/audit-rubrics/CLAUDE.md +2 -1
- package/audit-rubrics/audit-categories.json +704 -0
- package/audit-rubrics/prompts/category-i-concurrency.md +1 -1
- package/bin/CLAUDE.md +16 -5
- package/bin/ever-shipped-skills.mjs +2 -0
- package/bin/install-plan.mjs +402 -0
- package/bin/install-transaction.mjs +455 -0
- package/bin/install.mjs +593 -147
- package/bin/install.plan.test.mjs +194 -0
- package/bin/install.profile-root.test.mjs +154 -0
- package/bin/install.profiles.test.mjs +253 -0
- package/bin/install.settings-defaults.test.mjs +200 -0
- package/bin/install.transaction.test.mjs +400 -0
- package/bin/install.uninstall-transaction.test.mjs +418 -0
- package/bin/merge_managed_permissions.mjs +130 -0
- package/bin/resolve-install-root.mjs +181 -0
- package/bin/select-install-targets.mjs +401 -0
- package/commands/CLAUDE.md +0 -2
- package/docs/references/CLAUDE.md +3 -1
- package/docs/references/advisor-tool.md +25 -7
- package/docs/references/prose-style-enforcement.md +25 -0
- package/docs/references/team-advisor-skill.md +3 -3
- package/docs/references/weak-executor-advisor.md +91 -0
- package/hooks/blocking/CLAUDE.md +6 -6
- package/hooks/blocking/_path_setup.py +9 -5
- package/hooks/blocking/code_rules_docstrings.py +124 -30
- package/hooks/blocking/code_rules_enforcer.py +161 -16
- package/hooks/blocking/code_rules_shared.py +40 -23
- package/hooks/blocking/config/CLAUDE.md +3 -5
- package/hooks/blocking/config/prose_style_enforcement_constants.py +38 -0
- package/hooks/blocking/config/test_prose_style_enforcement_constants.py +45 -0
- package/hooks/blocking/eli11_reply_enforcer.py +70 -113
- package/hooks/blocking/hedging_language_blocker.py +103 -20
- package/hooks/blocking/hook_prose_detector_consistency.py +6 -0
- package/hooks/blocking/intent_only_ending_blocker.py +6 -0
- package/hooks/blocking/plain_language_blocker.py +139 -20
- package/hooks/blocking/pre_tool_use_dispatcher.py +102 -20
- package/hooks/blocking/state_description_blocker.py +7 -1
- package/hooks/blocking/tdd_enforcer.py +8 -0
- package/hooks/blocking/test__path_setup.py +28 -0
- package/hooks/blocking/test_code_rules_enforcer_agent_home_tooling.py +99 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +232 -10
- package/hooks/blocking/test_code_rules_enforcer_ephemeral.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +41 -0
- package/hooks/blocking/test_code_rules_enforcer_string_magic.py +98 -0
- package/hooks/blocking/test_eli11_reply_enforcer.py +98 -165
- package/hooks/blocking/test_fable_spawn_gate.py +18 -11
- package/hooks/blocking/test_hedging_language_blocker.py +120 -1
- package/hooks/blocking/test_hook_prose_detector_consistency.py +28 -8
- package/hooks/blocking/test_intent_only_ending_blocker.py +27 -2
- package/hooks/blocking/test_package_inventory_stale_blocker.py +11 -4
- package/hooks/blocking/test_plain_language_blocker.py +129 -19
- package/hooks/blocking/test_plain_language_blocker_allowlist.py +70 -26
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +99 -26
- package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +87 -50
- package/hooks/blocking/test_state_description_blocker.py +45 -2
- package/hooks/blocking/test_stop_dispatcher.py +11 -7
- package/hooks/blocking/test_volatile_path_in_post_blocker.py +12 -12
- package/hooks/blocking/volatile_path_in_post_blocker.py +2 -2
- package/hooks/hooks.json +15 -0
- package/hooks/hooks_constants/CLAUDE.md +14 -3
- package/hooks/hooks_constants/ask_user_question_shape.py +281 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +2 -1
- package/hooks/hooks_constants/eli11_reply_enforcer_constants.py +5 -12
- package/hooks/hooks_constants/hedging_uncertainty_constants.py +42 -0
- package/hooks/hooks_constants/issue_tracker_session_starter_constants.py +23 -0
- package/hooks/hooks_constants/orchestrator_auto_starter_constants.py +23 -0
- package/hooks/hooks_constants/piped_pytest_blocker_constants.py +4 -1
- package/hooks/hooks_constants/plain_language_blocker_constants.py +4 -1
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +6 -0
- package/hooks/hooks_constants/project_paths_reader.py +31 -4
- package/hooks/hooks_constants/prose_matcher_precision_constants.py +40 -0
- package/hooks/hooks_constants/pytest_invocation.py +354 -0
- package/hooks/hooks_constants/session_start_injector.py +163 -0
- package/hooks/hooks_constants/session_start_injector_constants.py +46 -0
- package/hooks/hooks_constants/shell_command_pipeline.py +397 -0
- package/hooks/hooks_constants/shell_command_segments.py +5 -0
- package/hooks/hooks_constants/test_ask_user_question_shape.py +167 -0
- package/hooks/hooks_constants/test_project_paths_reader.py +29 -0
- package/hooks/hooks_constants/test_prose_metrics_parity.py +8 -0
- package/hooks/hooks_constants/test_pytest_invocation.py +130 -0
- package/hooks/hooks_constants/test_session_start_injector.py +168 -0
- package/hooks/hooks_constants/test_shell_command_pipeline.py +135 -0
- package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +1 -1
- package/hooks/hooks_constants/working_style_prompt_constants.py +30 -0
- package/hooks/observability/CLAUDE.md +2 -0
- package/hooks/observability/prose_matcher_advisory.py +237 -0
- package/hooks/observability/test_prose_matcher_advisory.py +143 -0
- package/hooks/session/CLAUDE.md +9 -1
- package/hooks/session/_path_setup.py +13 -0
- package/hooks/session/issue_tracker_session_starter.py +135 -0
- package/hooks/session/orchestrator_auto_starter.py +100 -0
- package/hooks/session/test__path_setup.py +28 -0
- package/hooks/session/test_issue_tracker_session_starter.py +104 -0
- package/hooks/session/test_orchestrator_auto_starter.py +99 -0
- package/hooks/session/test_working_style_prompt.py +47 -0
- package/hooks/session/untracked_repo_detector.py +1 -24
- package/hooks/session/working_style_prompt.py +36 -0
- package/hooks/validators/_path_setup.py +19 -0
- package/hooks/validators/run_all_validators.py +8 -13
- package/installable-surfaces.manifest.json +21 -0
- package/output-styles/CLAUDE.md +1 -3
- package/package.json +4 -2
- package/rules/CLAUDE.md +1 -0
- package/rules/durable-post-artifacts.md +2 -2
- package/rules/eli11-replies.md +6 -1
- package/rules/hedging-claims.md +4 -2
- package/rules/long-horizon-autonomy.md +3 -1
- package/rules/opus5-communication-contract.md +45 -0
- package/rules/plain-language.md +2 -2
- package/rules/research-mode.md +1 -1
- package/scripts/CLAUDE.md +11 -0
- package/scripts/Sync-RepoMain.ps1 +215 -0
- package/scripts/active_capability_references.py +218 -0
- package/scripts/ci/windows-installer-lifecycle.ps1 +78 -0
- package/scripts/claude_chain_runner.py +394 -6
- package/scripts/claude_chain_usage.py +1 -1
- package/scripts/codex_compat_materializer.py +105 -85
- package/scripts/dev_env_scripts_constants/CLAUDE.md +2 -0
- package/scripts/dev_env_scripts_constants/active_capability_constants.py +46 -0
- package/scripts/dev_env_scripts_constants/claude_chain_constants.py +74 -0
- package/scripts/dev_env_scripts_constants/verify_installable_package_constants.py +116 -0
- package/scripts/profile-isolation-launchers/config/mcp-bundles.json +25 -0
- package/scripts/profile-isolation-launchers/config/profile-isolation-constants.mjs +60 -0
- package/scripts/profile-isolation-launchers/config/profiles.manifest.json +54 -0
- package/scripts/profile-isolation-launchers/config/shared-allowlist.json +64 -0
- package/scripts/profile-isolation-launchers/launcher-runtime.mjs +180 -0
- package/scripts/profile-isolation-launchers/lib/profile-manifest.mjs +288 -0
- package/scripts/profile-isolation-launchers/mcp-bundles.mjs +275 -0
- package/scripts/profile-isolation-launchers/profile-isolation-contract.test.mjs +221 -0
- package/scripts/profile-isolation-launchers/tests/launcher-runtime.test.mjs +108 -0
- package/scripts/profile-isolation-launchers/tests/mcp-bundles.test.mjs +147 -0
- package/scripts/profile-isolation-launchers/tests/shortcut-contract.test.ps1 +102 -0
- package/scripts/profile-isolation-launchers/tests/version-compatibility.test.mjs +210 -0
- package/scripts/profile-isolation-launchers/version-compatibility.mjs +299 -0
- package/scripts/profile-isolation-launchers/windows/shortcut-inventory.ps1 +127 -0
- package/scripts/profile-isolation-launchers/windows/shortcut-manifest.json +51 -0
- package/scripts/profile-isolation-launchers/windows/shortcut-reconcile.ps1 +77 -0
- package/scripts/spawn_grok_batch.py +3 -0
- package/scripts/test_active_capability_references.py +108 -0
- package/scripts/test_claude_chain_runner.py +414 -82
- package/scripts/test_claude_chain_usage.py +12 -12
- package/scripts/test_resolve_worker_spawn.py +2 -2
- package/scripts/test_verify_installable_package.py +208 -0
- package/scripts/tests/test_codex_compat_materializer.py +33 -0
- package/scripts/verify_installable_package.py +612 -0
- package/settings.json +10 -0
- package/skills/CLAUDE.md +2 -0
- package/skills/_shared/advisor/CLAUDE.md +1 -1
- package/skills/_shared/advisor/scripts/README.md +2 -0
- package/skills/_shared/pr-loop/scripts/CLAUDE.md +1 -0
- package/skills/_shared/pr-loop/scripts/audit_category_schema.py +355 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +32 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +7 -19
- package/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +94 -0
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +21 -0
- package/skills/autoconverge/reference/convergence.md +2 -1
- package/skills/autoconverge/reference/stop-conditions.md +5 -3
- package/skills/beat-sheet/SKILL.md +54 -0
- package/skills/beat-sheet/reference/visual-beats.md +29 -0
- package/skills/bugteam/CONSTRAINTS.md +4 -4
- package/skills/bugteam/EXAMPLES.md +1 -1
- package/skills/bugteam/reference/README.md +1 -1
- package/skills/e-code-review/SKILL.md +26 -5
- package/skills/e-code-review/reference/effort-evaluation.md +35 -0
- package/skills/e-code-review/reference/medium.md +15 -4
- package/skills/e-code-review/scripts/config/e_code_review_effort_constants/__init__.py +41 -0
- package/skills/e-code-review/scripts/config/e_code_review_effort_constants/effort_constants.py +40 -0
- package/skills/e-code-review/scripts/e_code_review_scripts_constants/finding_pipeline_constants.py +49 -0
- package/skills/e-code-review/scripts/effort_defaults_evidence.json +186 -0
- package/skills/e-code-review/scripts/effort_evaluation.py +362 -0
- package/skills/e-code-review/scripts/finding_pipeline.py +140 -0
- package/skills/e-code-review/scripts/fixtures/demanding.json +26 -0
- package/skills/e-code-review/scripts/fixtures/easy.json +14 -0
- package/skills/e-code-review/scripts/fixtures/medium.json +20 -0
- package/skills/e-code-review/scripts/grok_code_review.py +16 -7
- package/skills/e-code-review/scripts/test_effort_evaluation.py +180 -0
- package/skills/e-code-review/scripts/test_finding_pipeline.py +197 -0
- package/skills/e-code-review/scripts/test_grok_code_review.py +77 -0
- package/skills/grokify/SKILL.md +1 -1
- package/skills/grokify/templates/handoff-template.md +2 -2
- package/skills/orchestrator/SKILL.md +5 -4
- package/skills/plan-to-pr/scripts/create_packet.py +4 -4
- package/skills/plan-to-pr/scripts/load_skill_constants.py +41 -0
- package/skills/plan-to-pr/scripts/validate_packet.py +4 -4
- package/skills/plan-to-pr/scripts/validate_protocol.py +4 -1
- package/skills/plan-to-pr/scripts/validate_run.py +4 -1
- package/skills/pr-converge/scripts/check_convergence.py +21 -19
- package/skills/pr-converge/scripts/check_convergence_availability.py +50 -7
- package/skills/pr-converge/scripts/conftest.py +35 -0
- package/skills/pr-converge/scripts/test_check_convergence_availability.py +65 -0
- package/skills/pr-converge/scripts/test_check_convergence_codex.py +11 -1
- package/skills/pr-converge/scripts/test_check_convergence_contract.py +9 -2
- package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
- package/skills/rebase/SKILL.md +15 -3
- package/skills/reviewer-gates/SKILL.md +2 -2
- package/skills/show/SKILL.md +51 -0
- package/skills/show/references/accessibility.md +7 -0
- package/skills/show/references/art.md +3 -0
- package/skills/show/references/charts.md +3 -0
- package/skills/show/references/core-design.md +14 -0
- package/skills/show/references/erds.md +3 -0
- package/skills/show/references/flowcharts.md +3 -0
- package/skills/show/references/host-and-html.md +3 -0
- package/skills/show/references/illustrative-diagrams.md +10 -0
- package/skills/show/references/interaction.md +3 -0
- package/skills/show/references/mockups.md +3 -0
- package/skills/show/references/quality-gates.md +7 -0
- package/skills/show/references/structural-diagrams.md +3 -0
- package/skills/show/references/subject-inventory.md +21 -0
- package/skills/show/references/svg-contract.md +22 -0
- package/skills/show/routing.yaml +30 -0
- package/skills/show/samples/pr1262-v2.svg +222 -0
- package/skills/show/scripts/README.md +6 -0
- package/skills/show/scripts/validate-artifact.py +91 -0
- package/skills/show/scripts/validate-package.py +18 -0
- package/skills/show/templates/html-widget.html +4 -0
- package/skills/show/templates/svg-base.svg +19 -0
- package/skills/show/tests/fixtures/css-var.svg +6 -0
- package/skills/show/tests/fixtures/dead-ref.svg +7 -0
- package/skills/show/tests/fixtures/filled-glyph.svg +8 -0
- package/skills/show/tests/fixtures/inherited-fill.svg +18 -0
- package/skills/show/tests/fixtures/invalid.svg +1 -0
- package/skills/show/tests/fixtures/large-canvas.svg +21 -0
- package/skills/show/tests/fixtures/unfilled-connector.svg +15 -0
- package/skills/show/tests/fixtures/valid.html +1 -0
- package/skills/show/tests/test_validate-artifact.py +74 -0
- package/skills/show/tests/test_validators.py +59 -0
- package/skills/show/workflows/create-visual.md +13 -0
- package/skills/show/workflows/review-visual.md +20 -0
- package/skills/split-pr/SKILL.md +85 -0
- package/skills/split-pr/reference/path-layers.md +16 -0
- package/skills/split-pr/reference/proposal-format.md +15 -0
- package/skills/split-pr/reference/split-further-loop.md +10 -0
- package/skills/split-pr/reference/splitting-principles.md +26 -0
- package/skills/split-pr/scripts/analyze_pr.py +279 -0
- package/skills/split-pr/scripts/categorize_files.py +106 -0
- package/skills/split-pr/scripts/config/__init__.py +1 -0
- package/skills/split-pr/scripts/config/dependency_constants.py +14 -0
- package/skills/split-pr/scripts/config/git_operations_constants.py +36 -0
- package/skills/split-pr/scripts/config/packing_constants.py +61 -0
- package/skills/split-pr/scripts/config/plan_constants.py +49 -0
- package/skills/split-pr/scripts/config/split_pr_constants.py +110 -0
- package/skills/split-pr/scripts/execute_split_slices.py +82 -0
- package/skills/split-pr/scripts/pack_files_into_slices.py +212 -0
- package/skills/split-pr/scripts/split_pr_dependency_graph.py +70 -0
- package/skills/split-pr/scripts/split_pr_git_operations.py +184 -0
- package/skills/split-pr/scripts/split_pr_layer_order.py +58 -0
- package/skills/split-pr/scripts/split_pr_paginate.py +119 -0
- package/skills/split-pr/scripts/split_pr_process_runner.py +52 -0
- package/skills/split-pr/scripts/split_pr_script_types.py +126 -0
- package/skills/split-pr/scripts/split_pr_title.py +41 -0
- package/skills/split-pr/scripts/test_analyze_pr.py +228 -0
- package/skills/split-pr/scripts/test_categorize_files.py +55 -0
- package/skills/split-pr/scripts/test_categorize_files_packing.py +59 -0
- package/skills/split-pr/scripts/test_execute_split_slices.py +99 -0
- package/skills/split-pr/scripts/test_split_pr_dependency_graph.py +47 -0
- package/skills/split-pr/scripts/test_split_pr_git_operations.py +125 -0
- package/skills/split-pr/scripts/test_split_pr_layer_order.py +36 -0
- package/skills/split-pr/scripts/test_split_pr_paginate.py +65 -0
- package/skills/split-pr/scripts/test_split_pr_script_types.py +73 -0
- package/skills/split-pr/scripts/test_split_pr_title.py +28 -0
- package/skills/split-pr/scripts/test_verify_dependency_graph.py +46 -0
- package/skills/split-pr/scripts/test_verify_plan.py +56 -0
- package/skills/split-pr/scripts/test_verify_plan_contract.py +50 -0
- package/skills/split-pr/scripts/test_verify_plan_path_normalization.py +45 -0
- package/skills/split-pr/scripts/verify_dependency_graph.py +111 -0
- package/skills/split-pr/scripts/verify_plan.py +139 -0
- package/skills/team-advisor/SKILL.md +7 -4
- package/skills/team-advisor/reference/advisor-docs-review.md +207 -0
- package/system-prompts/software-engineer.xml +11 -2
- package/commands/initialize.md +0 -90
- package/commands/stubcheck.md +0 -88
- package/output-styles/caveman-agent.md +0 -37
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Tests for the repository-gated issue-tracker SessionStart starter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
_HOOKS_ROOT = Path(__file__).resolve().parent.parent
|
|
14
|
+
if str(_HOOKS_ROOT) not in sys.path:
|
|
15
|
+
sys.path.insert(0, str(_HOOKS_ROOT))
|
|
16
|
+
|
|
17
|
+
from hooks_constants.issue_tracker_session_starter_constants import (
|
|
18
|
+
ISSUE_TRACKER_SESSION_START_DIRECTIVE,
|
|
19
|
+
ISSUE_TRACKER_SESSION_STARTER_ENABLED_ENV_VAR,
|
|
20
|
+
ISSUE_TRACKER_STARTER_TIMEOUT_MILLISECONDS,
|
|
21
|
+
)
|
|
22
|
+
from session.issue_tracker_session_starter import (
|
|
23
|
+
issue_tracker_session_starter_enabled_in_environment,
|
|
24
|
+
repository_is_registered,
|
|
25
|
+
run_issue_tracker_session_starter,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
STARTER_SCRIPT = Path(__file__).resolve().parent / "issue_tracker_session_starter.py"
|
|
29
|
+
DEFAULT_TIMEOUT = ISSUE_TRACKER_STARTER_TIMEOUT_MILLISECONDS
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_default_environment_is_disabled(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
33
|
+
monkeypatch.delenv(ISSUE_TRACKER_SESSION_STARTER_ENABLED_ENV_VAR, raising=False)
|
|
34
|
+
assert issue_tracker_session_starter_enabled_in_environment() is False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_enabled_env_values(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
38
|
+
monkeypatch.setenv(ISSUE_TRACKER_SESSION_STARTER_ENABLED_ENV_VAR, "1")
|
|
39
|
+
assert issue_tracker_session_starter_enabled_in_environment() is True
|
|
40
|
+
monkeypatch.setenv(ISSUE_TRACKER_SESSION_STARTER_ENABLED_ENV_VAR, "true")
|
|
41
|
+
assert issue_tracker_session_starter_enabled_in_environment() is True
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_disabled_or_ineligible_emits_nothing() -> None:
|
|
45
|
+
assert (
|
|
46
|
+
run_issue_tracker_session_starter(
|
|
47
|
+
{"source": "startup"}, False, True, DEFAULT_TIMEOUT
|
|
48
|
+
)
|
|
49
|
+
== {}
|
|
50
|
+
)
|
|
51
|
+
assert (
|
|
52
|
+
run_issue_tracker_session_starter(
|
|
53
|
+
{"source": "startup"}, True, False, DEFAULT_TIMEOUT
|
|
54
|
+
)
|
|
55
|
+
== {}
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_enabled_eligible_startup_emits_directive() -> None:
|
|
60
|
+
payload = run_issue_tracker_session_starter(
|
|
61
|
+
{"source": "startup"}, True, True, DEFAULT_TIMEOUT
|
|
62
|
+
)
|
|
63
|
+
assert payload["additionalContext"] == ISSUE_TRACKER_SESSION_START_DIRECTIVE
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_timeout_zero_emits_nothing() -> None:
|
|
67
|
+
assert (
|
|
68
|
+
run_issue_tracker_session_starter({"source": "startup"}, True, True, 0) == {}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_unknown_source_emits_nothing() -> None:
|
|
73
|
+
assert (
|
|
74
|
+
run_issue_tracker_session_starter({"source": "weird"}, True, True, DEFAULT_TIMEOUT)
|
|
75
|
+
== {}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_repository_is_registered_fail_closed_empty_registry() -> None:
|
|
80
|
+
assert repository_is_registered("/any/path", {}) is False # empty registry fails closed
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_repository_is_registered_matches_git_root(tmp_path: Path) -> None:
|
|
84
|
+
(tmp_path / ".git").mkdir()
|
|
85
|
+
child = tmp_path / "pkg"
|
|
86
|
+
child.mkdir()
|
|
87
|
+
registry = {"demo": str(tmp_path)}
|
|
88
|
+
assert repository_is_registered(str(child), registry) is True
|
|
89
|
+
assert repository_is_registered(str(tmp_path / "other"), {"demo": str(tmp_path / "nope")}) is False
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_main_disabled_prints_nothing() -> None:
|
|
93
|
+
environment_by_key = os.environ.copy()
|
|
94
|
+
environment_by_key.pop(ISSUE_TRACKER_SESSION_STARTER_ENABLED_ENV_VAR, None)
|
|
95
|
+
completed = subprocess.run(
|
|
96
|
+
[sys.executable, str(STARTER_SCRIPT)],
|
|
97
|
+
input=json.dumps({"source": "startup"}),
|
|
98
|
+
capture_output=True,
|
|
99
|
+
text=True,
|
|
100
|
+
check=False,
|
|
101
|
+
env=environment_by_key,
|
|
102
|
+
)
|
|
103
|
+
assert completed.returncode == 0
|
|
104
|
+
assert completed.stdout.strip() == ""
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Tests for the opt-in orchestrator SessionStart auto-starter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
_HOOKS_ROOT = Path(__file__).resolve().parent.parent
|
|
14
|
+
if str(_HOOKS_ROOT) not in sys.path:
|
|
15
|
+
sys.path.insert(0, str(_HOOKS_ROOT))
|
|
16
|
+
|
|
17
|
+
from hooks_constants.orchestrator_auto_starter_constants import (
|
|
18
|
+
ORCHESTRATOR_AUTO_STARTER_ENABLED_ENV_VAR,
|
|
19
|
+
ORCHESTRATOR_SESSION_START_DIRECTIVE,
|
|
20
|
+
ORCHESTRATOR_STARTER_TIMEOUT_MILLISECONDS,
|
|
21
|
+
)
|
|
22
|
+
from session.orchestrator_auto_starter import (
|
|
23
|
+
orchestrator_auto_starter_enabled_in_environment,
|
|
24
|
+
run_orchestrator_auto_starter,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
STARTER_SCRIPT = Path(__file__).resolve().parent / "orchestrator_auto_starter.py"
|
|
28
|
+
DEFAULT_TIMEOUT = ORCHESTRATOR_STARTER_TIMEOUT_MILLISECONDS
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_default_environment_is_disabled(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
32
|
+
monkeypatch.delenv(ORCHESTRATOR_AUTO_STARTER_ENABLED_ENV_VAR, raising=False)
|
|
33
|
+
assert orchestrator_auto_starter_enabled_in_environment() is False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_enabled_env_values(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
37
|
+
monkeypatch.setenv(ORCHESTRATOR_AUTO_STARTER_ENABLED_ENV_VAR, "1")
|
|
38
|
+
assert orchestrator_auto_starter_enabled_in_environment() is True
|
|
39
|
+
monkeypatch.setenv(ORCHESTRATOR_AUTO_STARTER_ENABLED_ENV_VAR, "true")
|
|
40
|
+
assert orchestrator_auto_starter_enabled_in_environment() is True
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_disabled_path_emits_empty_without_context() -> None:
|
|
44
|
+
payload = run_orchestrator_auto_starter(
|
|
45
|
+
{"source": "startup"}, False, DEFAULT_TIMEOUT
|
|
46
|
+
)
|
|
47
|
+
assert payload == {}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_enabled_startup_emits_orchestrator_directive() -> None:
|
|
51
|
+
payload = run_orchestrator_auto_starter(
|
|
52
|
+
{"source": "startup"}, True, DEFAULT_TIMEOUT
|
|
53
|
+
)
|
|
54
|
+
assert payload["additionalContext"] == ORCHESTRATOR_SESSION_START_DIRECTIVE
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@pytest.mark.parametrize("source", ("startup", "resume", "clear", "compact"))
|
|
58
|
+
def test_enabled_known_sources_emit_directive(source: str) -> None:
|
|
59
|
+
payload = run_orchestrator_auto_starter({"source": source}, True, DEFAULT_TIMEOUT)
|
|
60
|
+
assert ORCHESTRATOR_SESSION_START_DIRECTIVE in payload.get("additionalContext", "")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_enabled_unknown_source_emits_nothing() -> None:
|
|
64
|
+
payload = run_orchestrator_auto_starter({"source": "not-real"}, True, DEFAULT_TIMEOUT)
|
|
65
|
+
assert payload == {}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_timeout_budget_zero_emits_nothing() -> None:
|
|
69
|
+
payload = run_orchestrator_auto_starter({"source": "startup"}, True, 0)
|
|
70
|
+
assert payload == {}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_main_stdin_enabled_prints_json() -> None:
|
|
74
|
+
completed = subprocess.run(
|
|
75
|
+
[sys.executable, str(STARTER_SCRIPT)],
|
|
76
|
+
input=json.dumps({"source": "startup"}),
|
|
77
|
+
capture_output=True,
|
|
78
|
+
text=True,
|
|
79
|
+
check=False,
|
|
80
|
+
env={**os.environ, ORCHESTRATOR_AUTO_STARTER_ENABLED_ENV_VAR: "1"},
|
|
81
|
+
)
|
|
82
|
+
assert completed.returncode == 0
|
|
83
|
+
parsed = json.loads(completed.stdout)
|
|
84
|
+
assert parsed["additionalContext"] == ORCHESTRATOR_SESSION_START_DIRECTIVE
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_main_stdin_disabled_prints_nothing() -> None:
|
|
88
|
+
environment_by_key = os.environ.copy()
|
|
89
|
+
environment_by_key.pop(ORCHESTRATOR_AUTO_STARTER_ENABLED_ENV_VAR, None)
|
|
90
|
+
completed = subprocess.run(
|
|
91
|
+
[sys.executable, str(STARTER_SCRIPT)],
|
|
92
|
+
input=json.dumps({"source": "startup"}),
|
|
93
|
+
capture_output=True,
|
|
94
|
+
text=True,
|
|
95
|
+
check=False,
|
|
96
|
+
env=environment_by_key,
|
|
97
|
+
)
|
|
98
|
+
assert completed.returncode == 0
|
|
99
|
+
assert completed.stdout.strip() == ""
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Tests for working_style_prompt — SessionStart hook that injects working-style text."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from io import StringIO
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from unittest.mock import patch
|
|
8
|
+
|
|
9
|
+
_SESSION_DIR = Path(__file__).resolve().parent
|
|
10
|
+
_HOOKS_ROOT = _SESSION_DIR.parent
|
|
11
|
+
for each_sys_path_entry in (str(_SESSION_DIR), str(_HOOKS_ROOT)):
|
|
12
|
+
if each_sys_path_entry not in sys.path:
|
|
13
|
+
sys.path.insert(0, each_sys_path_entry)
|
|
14
|
+
|
|
15
|
+
import working_style_prompt as starter
|
|
16
|
+
|
|
17
|
+
from hooks_constants.working_style_prompt_constants import (
|
|
18
|
+
WORKING_STYLE_PROMPT,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _run_main() -> str:
|
|
23
|
+
"""Return stdout produced by running the hook's main()."""
|
|
24
|
+
captured_stdout = StringIO()
|
|
25
|
+
with patch("sys.stdout", captured_stdout):
|
|
26
|
+
starter.main()
|
|
27
|
+
return captured_stdout.getvalue()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestWorkingStylePrompt:
|
|
31
|
+
def test_main_emits_additional_context(self) -> None:
|
|
32
|
+
emitted = json.loads(_run_main())
|
|
33
|
+
assert "additionalContext" in emitted
|
|
34
|
+
|
|
35
|
+
def test_additional_context_matches_prompt_exactly(self) -> None:
|
|
36
|
+
emitted = json.loads(_run_main())
|
|
37
|
+
assert emitted["additionalContext"] == WORKING_STYLE_PROMPT
|
|
38
|
+
|
|
39
|
+
def test_emitted_prompt_carries_ledger_and_scope_lines(self) -> None:
|
|
40
|
+
emitted = json.loads(_run_main())
|
|
41
|
+
prompt_text = emitted["additionalContext"]
|
|
42
|
+
assert "scratch txt file you'll keep running as you go; ledger, if you will." in prompt_text
|
|
43
|
+
assert "Deliver what was asked, at the scope intended." in prompt_text
|
|
44
|
+
assert 'answer "what happened" or "what did you find,"' in prompt_text
|
|
45
|
+
|
|
46
|
+
def test_build_session_directive_returns_the_shared_constant(self) -> None:
|
|
47
|
+
assert starter.build_session_directive() == WORKING_STYLE_PROMPT
|
|
@@ -21,12 +21,11 @@ if _hooks_dir not in sys.path:
|
|
|
21
21
|
|
|
22
22
|
from hooks_constants.dynamic_stderr_handler import DynamicStderrHandler # noqa: E402
|
|
23
23
|
from hooks_constants.project_paths_reader import ( # noqa: E402
|
|
24
|
+
find_git_root,
|
|
24
25
|
load_registry,
|
|
25
26
|
registry_contains_path,
|
|
26
27
|
registry_file_path,
|
|
27
28
|
)
|
|
28
|
-
from hooks_constants.setup_project_paths_constants import GIT_DIRECTORY_SEGMENT_NAME # noqa: E402
|
|
29
|
-
|
|
30
29
|
|
|
31
30
|
_logger = logging.getLogger("untracked_repo_detector")
|
|
32
31
|
if not _logger.handlers:
|
|
@@ -42,28 +41,6 @@ def current_working_directory() -> str:
|
|
|
42
41
|
return os.getcwd()
|
|
43
42
|
|
|
44
43
|
|
|
45
|
-
def find_git_root(start_path: str) -> str | None:
|
|
46
|
-
"""Walk upward from start_path looking for a .git directory.
|
|
47
|
-
|
|
48
|
-
The walk is bounded by the user's home directory: once the candidate
|
|
49
|
-
reaches the home directory without finding ``.git``, the search stops.
|
|
50
|
-
This prevents a stray ``.git`` above the user's home (for example a
|
|
51
|
-
parent dotfiles repo) from being falsely reported as the session's repo.
|
|
52
|
-
|
|
53
|
-
Returns the absolute path of the repo root, or None if not found.
|
|
54
|
-
"""
|
|
55
|
-
home_directory = Path.home().resolve()
|
|
56
|
-
candidate = Path(start_path).resolve()
|
|
57
|
-
while True:
|
|
58
|
-
if (candidate / GIT_DIRECTORY_SEGMENT_NAME).exists():
|
|
59
|
-
return str(candidate)
|
|
60
|
-
if candidate == home_directory:
|
|
61
|
-
return None
|
|
62
|
-
parent = candidate.parent
|
|
63
|
-
if parent == candidate:
|
|
64
|
-
return None
|
|
65
|
-
candidate = parent
|
|
66
|
-
|
|
67
44
|
|
|
68
45
|
def _build_confirm_instruction(repo_root: str) -> str:
|
|
69
46
|
config_file_path = str(registry_file_path())
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""SessionStart hook — inject working-style guidance into the session.
|
|
3
|
+
|
|
4
|
+
At session start this hook emits an ``additionalContext`` block carrying a fixed
|
|
5
|
+
working-style prompt: keep a running ledger, write in plain English, narrate
|
|
6
|
+
before the first tool call, lead with the outcome, and stay at the asked scope.
|
|
7
|
+
The hook writes nothing and runs no tools itself.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
_hooks_dir = str(Path(__file__).resolve().parent.parent)
|
|
17
|
+
if _hooks_dir not in sys.path:
|
|
18
|
+
sys.path.insert(0, _hooks_dir)
|
|
19
|
+
|
|
20
|
+
from hooks_constants.working_style_prompt_constants import ( # noqa: E402
|
|
21
|
+
WORKING_STYLE_PROMPT,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def build_session_directive() -> str:
|
|
26
|
+
"""Return the working-style prompt emitted at session start."""
|
|
27
|
+
return WORKING_STYLE_PROMPT
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def main() -> None:
|
|
31
|
+
"""Emit the working-style prompt as SessionStart additionalContext."""
|
|
32
|
+
print(json.dumps({"additionalContext": build_session_directive()}))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
main()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Add the hooks directory to sys.path for cross-package validator imports.
|
|
2
|
+
|
|
3
|
+
Importing this module inserts the hooks directory (this file's parent's parent)
|
|
4
|
+
at the front of sys.path, so a validator module can import the ``blocking`` and
|
|
5
|
+
``hooks_constants`` packages while every import stays at module top.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
hooks_directory_on_path = str(Path(__file__).resolve().parent.parent)
|
|
12
|
+
"""The hooks directory this module places on ``sys.path``.
|
|
13
|
+
|
|
14
|
+
An importer binds this name so the bootstrap import reads as used rather than as
|
|
15
|
+
a stray side-effect import.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
if hooks_directory_on_path not in sys.path:
|
|
19
|
+
sys.path.insert(0, hooks_directory_on_path)
|
|
@@ -19,6 +19,7 @@ from datetime import datetime
|
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
from typing import Callable, Dict, List, Optional, Tuple
|
|
21
21
|
|
|
22
|
+
from ._path_setup import hooks_directory_on_path # noqa: F401
|
|
22
23
|
from .config.directory_exemption_constants import (
|
|
23
24
|
ALL_DIRECTORY_EXEMPTION_SEGMENT_NAMES,
|
|
24
25
|
ALL_DIRECTORY_EXEMPTION_SUBSTRING_PATTERNS,
|
|
@@ -29,23 +30,17 @@ from .mypy_integration import check_mypy_available, run_mypy_check
|
|
|
29
30
|
from .output_formatter import OutputFormatter, OutputMode, ValidatorResultDict
|
|
30
31
|
from .python_style_checks import fix_file
|
|
31
32
|
from .ruff_integration import check_ruff_available, run_ruff_check
|
|
32
|
-
|
|
33
|
+
from blocking.code_rules_shared import is_ephemeral_path
|
|
34
|
+
from hooks_constants.hook_block_logger import log_hook_block
|
|
35
|
+
from hooks_constants.multi_edit_reconstruction import (
|
|
36
|
+
apply_edits,
|
|
37
|
+
edits_for_tool,
|
|
38
|
+
)
|
|
33
39
|
|
|
34
40
|
VALIDATORS_DIR = Path(__file__).parent
|
|
35
41
|
hooks_dir = VALIDATORS_DIR.parent
|
|
36
42
|
package_name = VALIDATORS_DIR.name
|
|
37
43
|
|
|
38
|
-
_hooks_directory_on_path = str(hooks_dir.resolve())
|
|
39
|
-
if _hooks_directory_on_path not in sys.path:
|
|
40
|
-
sys.path.insert(0, _hooks_directory_on_path)
|
|
41
|
-
|
|
42
|
-
from blocking.code_rules_shared import is_ephemeral_path # noqa: E402
|
|
43
|
-
from hooks_constants.hook_block_logger import log_hook_block # noqa: E402
|
|
44
|
-
from hooks_constants.multi_edit_reconstruction import ( # noqa: E402
|
|
45
|
-
apply_edits,
|
|
46
|
-
edits_for_tool,
|
|
47
|
-
)
|
|
48
|
-
|
|
49
44
|
|
|
50
45
|
def _windows_non_unc_working_directory_string(
|
|
51
46
|
candidate_directory_strings: list[str | None],
|
|
@@ -824,7 +819,7 @@ def _is_absolute_path_under_system_temporary_directory(file_path: str) -> bool:
|
|
|
824
819
|
|
|
825
820
|
::
|
|
826
821
|
|
|
827
|
-
ok: "
|
|
822
|
+
ok: "<windows-temp-root>/pytest-of-x/test_foo0/a.py" -> True
|
|
828
823
|
ok: "/home/runner/work/_temp/pytest-basetemp/.../a.py" when RUNNER_TEMP
|
|
829
824
|
flag: "packages/demo/test_helpers/worker.py" -> False (relative)
|
|
830
825
|
flag: "C:/repo/pkg/test_helpers/worker.py" -> False (project)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"directories": [
|
|
3
|
+
"bin",
|
|
4
|
+
"rules",
|
|
5
|
+
"docs",
|
|
6
|
+
"commands",
|
|
7
|
+
"agents",
|
|
8
|
+
"output-styles",
|
|
9
|
+
"skills",
|
|
10
|
+
"hooks",
|
|
11
|
+
"system-prompts",
|
|
12
|
+
"scripts",
|
|
13
|
+
"_shared",
|
|
14
|
+
"audit-rubrics"
|
|
15
|
+
],
|
|
16
|
+
"root_files": [
|
|
17
|
+
"CLAUDE.md",
|
|
18
|
+
"codex-capability-map.json",
|
|
19
|
+
"installable-surfaces.manifest.json"
|
|
20
|
+
]
|
|
21
|
+
}
|
package/output-styles/CLAUDE.md
CHANGED
|
@@ -4,9 +4,7 @@ Output-style instruction files installed into `~/.claude/output-styles/` by `bin
|
|
|
4
4
|
|
|
5
5
|
## Files
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|---|---|---|
|
|
9
|
-
| `caveman-agent.md` | Caveman Agent | Terse fragments, lead with answer, smallest artifact that solves the stated problem, question premise before building |
|
|
7
|
+
No output-style instruction files ship in this directory. The active caveman behavior lives in `agents/caveman.md`.
|
|
10
8
|
|
|
11
9
|
## Format
|
|
12
10
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-dev-env",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"codex-compat": "bin/codex-compat.mjs"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"test": "node --test \"bin/*.test.mjs\" \"skills/**/*.test.mjs\""
|
|
11
|
+
"test": "node --test \"bin/*.test.mjs\" \"skills/**/*.test.mjs\" \"scripts/profile-isolation-launchers/**/*.test.mjs\" \"tests/fresh-session/**/*.test.mjs\""
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"bin/",
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
"system-prompts/",
|
|
23
23
|
"scripts/",
|
|
24
24
|
"codex-capability-map.json",
|
|
25
|
+
"settings.json",
|
|
26
|
+
"installable-surfaces.manifest.json",
|
|
25
27
|
"_shared/",
|
|
26
28
|
"audit-rubrics/",
|
|
27
29
|
"CLAUDE.md",
|
package/rules/CLAUDE.md
CHANGED
|
@@ -39,6 +39,7 @@ Rule files installed into `~/.claude/rules/` by `bin/install.mjs`. A rule withou
|
|
|
39
39
|
| `nas-ssh-invocation.md` | Reach the NAS through the paramiko-backed `nas_ssh_key.py` runner, which signs in-process; every ssh-family client reads the key through file permissions, refuses it, and stalls an unattended run on a password prompt |
|
|
40
40
|
| `no-cross-skill-duplicate-helpers.md` | Within one skill a duplicated helper is blocked; across two skill folders a small self-contained copy is a sanctioned isolation tradeoff that draws a non-blocking advisory naming the source skill |
|
|
41
41
|
| `orphan-css-class.md` | Every `class="..."` attribute in Python-generated markup has a matching selector in the `<style>` block |
|
|
42
|
+
| `opus5-communication-contract.md` | Visible concision, one-sentence first progress, important-change-only updates, outcome-first finals, thinking-disabled tool narration (`opus5-communication-contract-v1`) |
|
|
42
43
|
| `paired-test-coverage.md` | A public function omitted by a module's established paired test suite must get a behavioral test |
|
|
43
44
|
| `parallel-tools.md` | Make all independent tool calls in a single response |
|
|
44
45
|
| `plain-illustrative-docstrings.md` | Public docstring narrative reads plainly and shows behavior with a diagram block (a `::` example or a doctest), painting a concrete scene a general developer follows on first read; a run-on backstop hook, a prose-wall backstop hook, and Category O9 audit enforce it |
|
|
@@ -19,7 +19,7 @@ Handle the two kinds of content differently:
|
|
|
19
19
|
|
|
20
20
|
## Volatile paths that must not appear in a post body
|
|
21
21
|
|
|
22
|
-
- A job scratch directory (`.claude-
|
|
22
|
+
- A job scratch directory (`.claude-profile-a/jobs/`)
|
|
23
23
|
- A worktree (`.claude/worktrees/`)
|
|
24
24
|
- A system temp location (`AppData\Local\Temp`, `%TEMP%`, `$env:TEMP`, `/tmp/`)
|
|
25
25
|
- The job scratch environment variable (`$CLAUDE_JOB_DIR`)
|
|
@@ -28,7 +28,7 @@ Both slash directions count.
|
|
|
28
28
|
|
|
29
29
|
The worktree and job-scratch entries count as a path when either:
|
|
30
30
|
|
|
31
|
-
- A `/` or `\` sits right before them — a drive-letter path (`C:\Users\me\.claude\worktrees\wt\f.py`), a home path (`~/.claude/worktrees/wt`), or a POSIX absolute path (`/home/me/.claude-
|
|
31
|
+
- A `/` or `\` sits right before them — a drive-letter path (`C:\Users\me\.claude\worktrees\wt\f.py`), a home path (`~/.claude/worktrees/wt`), or a POSIX absolute path (`/home/me/.claude-profile-a/jobs/j/log.txt`).
|
|
32
32
|
- A path segment follows them — a relative path that names a child under that directory (`see .claude/worktrees/wt-1/notes.md`, a markdown link target, or `cd .claude/worktrees/wt-199`).
|
|
33
33
|
|
|
34
34
|
With neither anchor the text names the directory rather than something inside it, and it posts — a quoted config constant, a backticked directory name, or a placeholder form such as `.claude/worktrees/<name>`. Un-backticked prose that puts a word immediately after the marker reads the same as a relative path and is blocked; the placeholder form is the documented escape.
|
package/rules/eli11-replies.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
1. **Bold keyword first** — open every line and bullet with its load-bearing words in bold. Eyes land on the first words of a line, then fall down the left edge — the [F-pattern](https://www.nngroup.com/articles/f-shaped-pattern-reading-web-content-discovered/).
|
|
8
8
|
2. **One idea per line** — a second idea in the same line is invisible to a skimmer.
|
|
9
9
|
3. **Conclusion first** — the outcome goes in sentence one. Detail sits below it, or gets cut.
|
|
10
|
-
4. **Half the words** — write it, halve it, halve it again.
|
|
10
|
+
4. **Half the words** — write it, halve it, halve it again. Stay brief by default; a requested full report may run long.
|
|
11
11
|
|
|
12
12
|
**Measured gains** ([NN/g eyetracking](https://www.nngroup.com/articles/how-users-read-on-the-web/)): concise +58%, scannable layout +47%, both together +124%.
|
|
13
13
|
|
|
@@ -25,7 +25,12 @@
|
|
|
25
25
|
- **Cut findings first** — when a reply runs long, drop findings and keep the action steps.
|
|
26
26
|
- **Skim test** — reading only the bold words tells the whole story.
|
|
27
27
|
|
|
28
|
+
## Progress and finals
|
|
29
|
+
|
|
30
|
+
Mid-run and closing narration follow [`opus5-communication-contract.md`](opus5-communication-contract.md) (`opus5-communication-contract-v1`): first progress update is one sentence; later updates only for important discoveries or direction changes; the final starts with the outcome.
|
|
31
|
+
|
|
28
32
|
## Relationship to other rules
|
|
29
33
|
|
|
30
34
|
- **[`plain-language`](plain-language.md)** owns word choice; this rule owns reply length and shape.
|
|
35
|
+
- **[`opus5-communication-contract`](opus5-communication-contract.md)** owns progress updates, outcome-first finals, and thinking-disabled tool narration.
|
|
31
36
|
- **`AskUserQuestion`** carries every question to the user, in the same short style.
|
package/rules/hedging-claims.md
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
State a claim with the evidence that backs it, or name the claim unverified. A hedge word stands in for evidence you did not gather.
|
|
4
4
|
|
|
5
|
-
`hedging_language_blocker` (Stop hook, hosted by `stop_dispatcher`) blocks a response
|
|
5
|
+
`hedging_language_blocker` (Stop hook, hosted by `stop_dispatcher`) runs when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (see [`docs/references/prose-style-enforcement.md`](../docs/references/prose-style-enforcement.md)). It blocks a response that carries a bare hedge word — `likely`, `probably`, `presumably`, `perhaps`, `possibly`, `seemingly`, `apparently`, `arguably`, `supposedly`, `ostensibly`, `conceivably`, `plausibly`, `unlikely`, `probable`, and the phrases `might be`, `could be`, `seems to be`, `appears to be`, `in all likelihood`, `more likely than not`, `it's possible that` — **unless the same sentence** labels the claim with explicit uncertainty (`unverified`, `I don't know`, `I do not know`, `no source for this claim`, `without a source`).
|
|
6
6
|
|
|
7
|
-
Deleting the hedge word and keeping the claim does not clear the block. Gather the source, run the probe, or ask the user through `AskUserQuestion` — then re-output the whole revised response.
|
|
7
|
+
A label in another sentence does not clear a bare hedge. Deleting the hedge word and keeping the claim does not clear the block. Gather the source, run the probe, label the claim unverified in that sentence, or ask the user through `AskUserQuestion` — then re-output the whole revised response.
|
|
8
|
+
|
|
9
|
+
When enforcement is off, bare hedges still emit privacy-safe advisory candidates for precision measurement (OP-07B).
|
|
8
10
|
|
|
9
11
|
Sibling rules: [`research-mode.md`](research-mode.md) names what counts as a citation; [`verify-runtime-state.md`](verify-runtime-state.md) names the live probe a runtime verdict rests on.
|
|
@@ -30,9 +30,11 @@ On a long build, set a checkpoint cadence and hold to it. At each interval, chec
|
|
|
30
30
|
|
|
31
31
|
Before you report progress, check each claim against a tool result from this session. State only what the evidence backs; name anything unverified as unverified. If tests fail, say so with the output; if a step was skipped, say that.
|
|
32
32
|
|
|
33
|
+
Visible progress follows [`opus5-communication-contract.md`](opus5-communication-contract.md) (`opus5-communication-contract-v1`): the **first progress update is one sentence**; later updates mark only **important discoveries or a direction change**.
|
|
34
|
+
|
|
33
35
|
## Re-ground the final message
|
|
34
36
|
|
|
35
|
-
Terse shorthand between tool calls is fine — that is you thinking. The final message is for a reader who saw none of it. After a long or unwatched run, write it as a fresh briefing: the outcome in one sentence
|
|
37
|
+
Terse shorthand between tool calls is fine — that is you thinking. The final message is for a reader who saw none of it. After a long or unwatched run, write it as a fresh briefing: the **outcome in one sentence first**, then the one or two things you need from the reader, each explained as if new. Drop the working vocabulary, arrow chains, and stacked-hyphen compounds; give each file, commit, or flag its own plain clause. When short and clear pull apart, choose clear.
|
|
36
38
|
|
|
37
39
|
## Keep going on context
|
|
38
40
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Opus 5 communication contract
|
|
2
|
+
|
|
3
|
+
**Marker:** `opus5-communication-contract-v1`
|
|
4
|
+
|
|
5
|
+
Positive contract for visible replies. Complements [`eli11-replies.md`](eli11-replies.md) (shape) and [`plain-language.md`](plain-language.md) (word choice).
|
|
6
|
+
|
|
7
|
+
## Visible output
|
|
8
|
+
|
|
9
|
+
1. **Concise by default.** A simple answer stays brief. When the user asks for a full audit or deep dump, the reply may run long enough to cover the substance.
|
|
10
|
+
2. **First progress update is one sentence.** After the first tool batch on a multi-step task, one short status line is enough.
|
|
11
|
+
3. **Later updates only for important change.** Further mid-run narration marks only important discoveries or a direction change — not every tool call.
|
|
12
|
+
4. **Final starts with the outcome.** The closing message leads with what happened; artifacts scale to their substance.
|
|
13
|
+
5. **Material corrections only.** Narrate a correction only when it changes code, a conclusion, or a decision.
|
|
14
|
+
|
|
15
|
+
## Thinking-disabled tool narration
|
|
16
|
+
|
|
17
|
+
When thinking is disabled in the harness:
|
|
18
|
+
|
|
19
|
+
1. Speak one brief natural-language sentence before a tool call that needs user-visible context.
|
|
20
|
+
2. When no fitting tool exists, say so in plain language.
|
|
21
|
+
3. Never put internal-system XML in visible output.
|
|
22
|
+
|
|
23
|
+
## Positive examples
|
|
24
|
+
|
|
25
|
+
### Short answer
|
|
26
|
+
|
|
27
|
+
User: "What is the default branch?"
|
|
28
|
+
Reply: "**Default branch** is `main`."
|
|
29
|
+
|
|
30
|
+
### Requested full audit
|
|
31
|
+
|
|
32
|
+
User: "Audit every hook registration and list gaps."
|
|
33
|
+
Reply: Outcome first, then a structured audit long enough to cover each gap — ELI11 caps still guide skim shape, not a hard character ceiling when depth was asked.
|
|
34
|
+
|
|
35
|
+
### First progress update
|
|
36
|
+
|
|
37
|
+
After the first tool batch: "**Checked** `hooks.json` registration paths."
|
|
38
|
+
|
|
39
|
+
### Important discovery mid-run
|
|
40
|
+
|
|
41
|
+
"**Blocker:** the package suite shadows `config` — running as two sessions."
|
|
42
|
+
|
|
43
|
+
### Outcome-first final
|
|
44
|
+
|
|
45
|
+
"**Shipped** draft PR #123. CI green on head `abc1234`. Merge still needs your OK."
|
package/rules/plain-language.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
All prose a person reads (chat, `AskUserQuestion`, docs, PR/issue bodies, commits): everyday words, short active sentences, lead with the answer, define jargon on first use, and give only the detail the reader needs to act (progressive disclosure). Aim for first-pass readability by a non-specialist. Exact identifiers, file paths, and API names stay exact; code is out of scope.
|
|
4
4
|
|
|
5
|
-
The `plain_language_blocker` PreToolUse hook (AskUserQuestion + `.md` Write/Edit/MultiEdit)
|
|
5
|
+
The `plain_language_blocker` PreToolUse hook (AskUserQuestion + `.md` Write/Edit/MultiEdit) names an everyday swap for a heavy word when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (default off). That path is **allow with a systemMessage advisory** — it does not deny the write. AskUserQuestion lean-block structure stays always on and still denies chat detail. When the flag is off, heavy-word hits only emit privacy-safe advisory candidates (see [`docs/references/prose-style-enforcement.md`](../docs/references/prose-style-enforcement.md)). Code fences, inline code, blockquotes, URLs, and file paths are skipped.
|
|
6
6
|
|
|
7
|
-
[`eli11-replies`](eli11-replies.md) governs reply length and shape; this rule governs word choice.
|
|
7
|
+
[`eli11-replies`](eli11-replies.md) governs reply length and shape; [`opus5-communication-contract`](opus5-communication-contract.md) governs progress and finals; this rule governs word choice.
|
|
8
8
|
|
|
9
9
|
A project can keep its own domain words out of the check with a `.claude/plain-language-allow.json` file: a JSON array of terms. An exact, case-insensitive, whole-word match on any term passes. The hook reads this file only from inside the project tree, up to the repository root, so each project's allowlist stays with its own code.
|
package/rules/research-mode.md
CHANGED
|
@@ -30,4 +30,4 @@ Creative thinking, brainstorming, and novel ideas don't require citation. You ca
|
|
|
30
30
|
|
|
31
31
|
- [`explore-thoroughly.md`](explore-thoroughly.md) — grounds an implementation plan in the codebase, as this rule grounds a factual claim in a source.
|
|
32
32
|
- [`verify-runtime-state.md`](verify-runtime-state.md) — a runtime verdict needs a live probe from this session, not a citation.
|
|
33
|
-
- [`hedging-claims.md`](hedging-claims.md) — the Stop hook that catches an
|
|
33
|
+
- [`hedging-claims.md`](hedging-claims.md) — the Stop hook that catches a bare hedge word; an explicit uncertainty label in the same sentence is the allowed alternative to a citation.
|
package/scripts/CLAUDE.md
CHANGED
|
@@ -16,6 +16,8 @@ Utility scripts installed into `~/.claude/scripts/` by `bin/install.mjs`. Each s
|
|
|
16
16
|
| `sweep_empty_dirs.py` | Deletes empty directories older than a configurable age under a given root; runs once (`--once`) or in continuous-watch mode |
|
|
17
17
|
| `sync_to_cursor.py` | Entry point for syncing Claude rules to Cursor `.mdc` files; delegates to the `sync_to_cursor/` package |
|
|
18
18
|
| `resolve_worker_spawn.py` | Dispatches a worker role through grok then claude fallback tiers (preflight, headless grok, `claude_agent_required` handoff, optional claude headless); applies `require_timeout_within_bounds` before the preflight, so an out-of-bounds `--timeout-seconds` prints a `timeout_out_of_bounds` outcome and exits 3 on every tier; protocol: [`../_shared/pr-loop/worker-spawn.md`](../_shared/pr-loop/worker-spawn.md) |
|
|
19
|
+
| `active_capability_references.py` | Inventories committed skill, agent, and command names and scans package markdown for active slash or backticked capability references; fails ban-listed names outside inert historical/example fences |
|
|
20
|
+
| `verify_installable_package.py` | Verifies the published package: runs real `npm pack`, checks every surface in `installable-surfaces.manifest.json` appears in the tarball, requires each `hooks.json` `.py` command to be git-tracked, and smoke-compiles those scripts (`node --check` on `bin/install.mjs`) |
|
|
19
21
|
|
|
20
22
|
## PowerShell scripts
|
|
21
23
|
|
|
@@ -24,6 +26,7 @@ Utility scripts installed into `~/.claude/scripts/` by `bin/install.mjs`. Each s
|
|
|
24
26
|
| `Audit-ShellPolicy.ps1` | Audits Bash tool calls in session transcripts against the `pwsh`-only shell policy |
|
|
25
27
|
| `Migrate-ShellPolicy.ps1` | Applies automated fixes for common shell-policy violations found by the audit script |
|
|
26
28
|
| `Install-SweepEmptyDirs.ps1` | Registers `sweep_empty_dirs.py` as a scheduled task on Windows |
|
|
29
|
+
| `Sync-RepoMain.ps1` | Fast-forwards a main-tracking mirror (`-RepoPath`, required) to its remote main using `merge --ff-only`; refuses a different checkout or a diverged branch, logs each run to `-LogPath`, and exits non-zero so a scheduled-task history row surfaces the problem. `-StashDirty` stashes tracked changes right before the fast-forward under a dated `pre-sync stash` message and leaves the stash in the list |
|
|
27
30
|
| `check.ps1` | Runs the full code-quality check suite |
|
|
28
31
|
| `Show-Asset.ps1` | Opens files on screen, sizing each image window to the image's pixel dimensions (scaled to fit the screen); non-image files open in their default application |
|
|
29
32
|
| `Get-SessionAccount.ps1` | Reports which Claude account the current session is actually logged into by comparing `~/.claude.json`'s CLI login against a `CLAUDE_USER_DATA_DIR` desktop profile's `lastKnownAccountUuid`, recovering the desktop account's email from profile storage when the two accounts differ |
|
|
@@ -33,7 +36,9 @@ Utility scripts installed into `~/.claude/scripts/` by `bin/install.mjs`. Each s
|
|
|
33
36
|
|
|
34
37
|
| Entry | Description |
|
|
35
38
|
|---|---|
|
|
39
|
+
| `ci/` | CI-only adapters; `windows-installer-lifecycle.ps1` runs the Node installer 16-check driver under isolated HOME/USERPROFILE/GIT_CONFIG_GLOBAL and writes bounded evidence |
|
|
36
40
|
| `dev_env_scripts_constants/` | Named constants (`timing.py`, `grok_worker_constants.py`, …) for scripts in this directory, including worker-advisor placeholder launcher/model/effort, four verdict signals, correction cap, and advisor timeout |
|
|
41
|
+
| `profile-isolation-launchers/` | Profile-launcher contract plus MCP activation, Windows shortcut source semantics, and version preflight: profiles manifest, shared-path allowlist, profile resolver, `CLAUDE_CONFIG_DIR` authority, lean/full MCP bundle materialization into profile `mcp.json`, pure CLI/Desktop version-compatibility classifier consumed by launcher preflight, read-only shortcut inventory and preview reconcile under `windows/`. Live launcher execution, provenance pin, live shortcut mutation, and L1 deploy stay residual |
|
|
37
42
|
| `sync_to_cursor/` | Package that builds Cursor `.mdc` files from Claude rules and docs |
|
|
38
43
|
| `tests/` | pytest suite for the Python scripts and Pester (`*.Tests.ps1`) suite for the PowerShell scripts in this directory |
|
|
39
44
|
|
|
@@ -45,6 +50,12 @@ Python scripts (pytest):
|
|
|
45
50
|
python -m pytest packages/claude-dev-env/scripts/tests/
|
|
46
51
|
```
|
|
47
52
|
|
|
53
|
+
Profile-isolation launcher contract (node:test):
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
node --test packages/claude-dev-env/scripts/profile-isolation-launchers/**/*.test.mjs
|
|
57
|
+
```
|
|
58
|
+
|
|
48
59
|
PowerShell scripts (Pester 5+, `*.Tests.ps1`):
|
|
49
60
|
|
|
50
61
|
```powershell
|