claude-dev-env 2.8.0 → 2.10.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 +8 -2
- 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 +62 -562
- package/agents/code-quality-agent.md +10 -2
- package/agents/code-verifier.md +1 -1
- package/agents/test_agent_frontmatter.py +58 -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/CODE_RULES.md +4 -2
- package/docs/references/CLAUDE.md +3 -2
- package/docs/references/advisor-tool.md +44 -6
- package/docs/references/prose-style-enforcement.md +25 -0
- package/docs/references/team-advisor-skill.md +14 -8
- 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_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/code_rules_path_utils_constants.py +1 -0
- 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 +15 -0
- package/package.json +5 -2
- package/rules/CLAUDE.md +1 -0
- package/rules/code-standards.md +33 -7
- package/rules/durable-post-artifacts.md +2 -2
- package/rules/eli11-replies.md +7 -2
- 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 +13 -2
- 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 +3 -1
- 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/grok_run_ledger_constants.py +50 -0
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +104 -0
- package/scripts/dev_env_scripts_constants/verify_installable_package_constants.py +116 -0
- package/scripts/grok_patch_artifacts.py +123 -0
- package/scripts/grok_run_ledger.py +318 -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 +556 -9
- 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_grok_patch_artifacts.py +82 -0
- package/scripts/test_grok_run_ledger.py +116 -0
- package/scripts/test_resolve_worker_spawn.py +2 -2
- package/scripts/test_spawn_grok_batch.py +295 -0
- 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 +6 -2
- package/skills/_shared/CLAUDE.md +37 -4
- package/skills/_shared/advisor/CLAUDE.md +9 -0
- package/skills/_shared/advisor/advisor-protocol.md +5 -0
- package/skills/_shared/advisor/scripts/README.md +9 -0
- package/skills/_shared/end-of-run-gotcha-recommendations.md +156 -0
- package/skills/_shared/pr-loop/CLAUDE.md +18 -1
- package/skills/_shared/pr-loop/audit-contract.md +5 -0
- package/skills/_shared/pr-loop/audit-reply-template.md +5 -0
- package/skills/_shared/pr-loop/code-rules-gate.md +5 -0
- package/skills/_shared/pr-loop/fix-protocol.md +5 -0
- package/skills/_shared/pr-loop/gh-payloads.md +5 -0
- package/skills/_shared/pr-loop/post-audit-thread-contract.md +5 -0
- package/skills/_shared/pr-loop/precatch-rubric.md +5 -0
- package/skills/_shared/pr-loop/scripts/CLAUDE.md +9 -1
- package/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +29 -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/_shared/pr-loop/state-schema.md +5 -0
- package/skills/_shared/pr-loop/worker-spawn.md +5 -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 +29 -3
- 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/reference/runner-selection.md +40 -0
- 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/__init__.py +1 -0
- package/skills/e-code-review/scripts/e_code_review_scripts_constants/finding_pipeline_constants.py +49 -0
- package/skills/e-code-review/scripts/e_code_review_scripts_constants/grok_code_review_constants.py +55 -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 +230 -0
- 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 +289 -0
- package/skills/grok-spawn/SKILL.md +5 -0
- package/skills/orchestrator/SKILL.md +5 -0
- 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/task-build/reference/tool-routing.md +3 -0
- package/skills/team-advisor/SKILL.md +23 -44
- package/system-prompts/software-engineer.xml +17 -5
- package/commands/initialize.md +0 -90
- package/commands/stubcheck.md +0 -88
- package/skills/test_markdown_link_integrity.py +0 -107
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# output-styles
|
|
2
|
+
|
|
3
|
+
Output-style instruction files installed into `~/.claude/output-styles/` by `bin/install.mjs`. Each file instructs an agent or session to respond in a specific voice or format.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
No output-style instruction files ship in this directory. The active caveman behavior lives in `agents/caveman.md`.
|
|
8
|
+
|
|
9
|
+
## Format
|
|
10
|
+
|
|
11
|
+
Each file uses YAML frontmatter (`name`, `description`, optional `keep-coding-instructions`) followed by Markdown instructions. The `keep-coding-instructions: true` flag tells Claude Code to keep the session's coding rules even when this style is active.
|
|
12
|
+
|
|
13
|
+
## Adding a style
|
|
14
|
+
|
|
15
|
+
Create a `.md` file with frontmatter and behavioral instructions, then run `bin/install.mjs` to copy it to `~/.claude/output-styles/`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-dev-env",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.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/",
|
|
@@ -16,11 +16,14 @@
|
|
|
16
16
|
"docs/",
|
|
17
17
|
"commands/",
|
|
18
18
|
"agents/",
|
|
19
|
+
"output-styles/",
|
|
19
20
|
"skills/",
|
|
20
21
|
"hooks/",
|
|
21
22
|
"system-prompts/",
|
|
22
23
|
"scripts/",
|
|
23
24
|
"codex-capability-map.json",
|
|
25
|
+
"settings.json",
|
|
26
|
+
"installable-surfaces.manifest.json",
|
|
24
27
|
"_shared/",
|
|
25
28
|
"audit-rubrics/",
|
|
26
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 |
|
package/rules/code-standards.md
CHANGED
|
@@ -1,13 +1,39 @@
|
|
|
1
1
|
# Code Standards
|
|
2
2
|
|
|
3
|
-
> **
|
|
4
|
-
>
|
|
3
|
+
> **Canonical policy:** repository-root [`AGENTS.md`](../../../AGENTS.md) — the human and AI review contract for code quality.
|
|
4
|
+
> **Compact projection:** [`CODE_RULES.md`](../docs/CODE_RULES.md) — validated summary for generation load.
|
|
5
|
+
> **Production enforcement:** `hooks/blocking/code_rules_enforcer.py` — hand-maintained Write/Edit gates; each mechanical rule carries a synchronization test.
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
## Policy surface map
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
| Layer | Path | Role |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Canonical | `AGENTS.md` (repo root) | Full review criteria for PR agents; BugBot sync source |
|
|
12
|
+
| Projection | `docs/CODE_RULES.md` | Compact always-load reference; must not diverge from AGENTS |
|
|
13
|
+
| Enforcer | `hooks/blocking/code_rules_enforcer.py` | Hand-maintained blockers; not generated from the docs |
|
|
14
|
+
| Session rules | `rules/*.md` | Runtime session policy (questions, tasks, shell) |
|
|
9
15
|
|
|
10
|
-
|
|
11
|
-
- **Right-sized engineering** — CODE_RULES §7 is canonical: functions over classes, concrete over abstract, an abstraction added at the commit that introduces its second concrete implementation.
|
|
16
|
+
Load `AGENTS.md` when reviewing a PR or resolving a policy conflict. Load `CODE_RULES.md` when generating code under the compact checklist. Prefer linking these refs over restating rules.
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
Two standards live in the canonical policy in full (and in the projection by name):
|
|
19
|
+
|
|
20
|
+
- **TDD** — CODE_RULES §8 / AGENTS Tests: red, green, refactor; no production code before a failing test.
|
|
21
|
+
- **Right-sized engineering** — CODE_RULES §7 / AGENTS Design: functions over classes; concrete over abstract; add an abstraction at the commit that introduces its second concrete implementation.
|
|
22
|
+
|
|
23
|
+
BDD is the outer process and TDD is the inner loop: [`bdd.md`](bdd.md) discovers and formulates the behavior a feature needs, then each formulated behavior is built through the TDD cycle.
|
|
24
|
+
|
|
25
|
+
## Session policies (ref docs, not restated here)
|
|
26
|
+
|
|
27
|
+
| Concern | Rule file |
|
|
28
|
+
|---|---|
|
|
29
|
+
| Question routing | [`ask-user-question-required.md`](ask-user-question-required.md) |
|
|
30
|
+
| Task tracking / worker completion | [`workers-done-before-complete.md`](workers-done-before-complete.md) |
|
|
31
|
+
| Multi-step task list | skill `task-build` (see agents catalog) |
|
|
32
|
+
|
|
33
|
+
## Synchronization
|
|
34
|
+
|
|
35
|
+
Mechanical enforcer coverage is checked by `tests/test_agents_policy_parity.py` and the existing `hooks/blocking/test_code_rules_enforcer*.py` suite. BugBot projection drift is checked with:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
python .github/scripts/sync_ai_rules.py --check
|
|
39
|
+
```
|
|
@@ -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
|
|
|
@@ -20,12 +20,17 @@
|
|
|
20
20
|
|
|
21
21
|
## Rules
|
|
22
22
|
|
|
23
|
-
- **One command per block** — a command the user runs goes in its own `bash`-tagged fenced block, so the Run button appears. That tag gives the user a Run button; [`shell-invocation-policy`](shell-invocation
|
|
23
|
+
- **One command per block** — a command the user runs goes in its own `bash`-tagged fenced block, so the Run button appears. That tag gives the user a Run button; [`shell-invocation-policy`](shell-invocation.md) stays in charge of the agent's own Bash-tool calls, which run pwsh-only.
|
|
24
24
|
- **One line per status** — each background-work update gets a single line.
|
|
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."
|