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,612 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Verify the npm package delivers every installable surface users need.
|
|
3
|
+
|
|
4
|
+
Runs real ``npm pack``, reads the generated tarball member list, and checks
|
|
5
|
+
each directory and root file from ``installable-surfaces.manifest.json``.
|
|
6
|
+
Also walks ``hooks/hooks.json`` for ``command`` fields, requires each ``.py``
|
|
7
|
+
script path to be tracked by ``git ls-files``, and smoke-compiles those scripts.
|
|
8
|
+
|
|
9
|
+
::
|
|
10
|
+
|
|
11
|
+
python verify_installable_package.py
|
|
12
|
+
-> exit 0 when the packed tarball holds every manifest surface
|
|
13
|
+
flag: missing package/hooks/ in the tarball fails the run
|
|
14
|
+
|
|
15
|
+
Call the public helpers from tests so the suite exercises shipped code rather
|
|
16
|
+
than reimplementing pack or path logic inside the test module.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import py_compile
|
|
23
|
+
import shutil
|
|
24
|
+
import subprocess
|
|
25
|
+
import sys
|
|
26
|
+
import tarfile
|
|
27
|
+
import tempfile
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from typing import Mapping
|
|
30
|
+
|
|
31
|
+
from dev_env_scripts_constants.verify_installable_package_constants import (
|
|
32
|
+
CLASS_CONTRADICTORY,
|
|
33
|
+
CLASS_PACKAGED,
|
|
34
|
+
CLASS_SOURCE_ONLY,
|
|
35
|
+
EXIT_CODE_FAILURE,
|
|
36
|
+
EXIT_CODE_SUCCESS,
|
|
37
|
+
GIT_BINARY_NAME,
|
|
38
|
+
GIT_LS_FILES_SUBCOMMAND,
|
|
39
|
+
HOOKS_DIRECTORY_NAME,
|
|
40
|
+
HOOKS_DIRECTORY_PREFIX,
|
|
41
|
+
HOOKS_JSON_RELATIVE_PATH,
|
|
42
|
+
INSTALL_ENTRYPOINT_RELATIVE_PATH,
|
|
43
|
+
INSTALL_ENTRYPOINT_SMOKE_FAILURE_HEADER,
|
|
44
|
+
JSON_COMMAND_KEY,
|
|
45
|
+
MANIFEST_DIRECTORIES_KEY,
|
|
46
|
+
MANIFEST_FILENAME,
|
|
47
|
+
MANIFEST_ROOT_FILES_KEY,
|
|
48
|
+
MISSING_MANIFEST_SURFACES_HEADER,
|
|
49
|
+
NEWLINE_JOIN_SEPARATOR,
|
|
50
|
+
NODE_BINARY_NAME,
|
|
51
|
+
NODE_CHECK_FLAG,
|
|
52
|
+
NPM_BINARY_NAME,
|
|
53
|
+
NPM_CMD_BINARY_NAME,
|
|
54
|
+
NPM_PACK_DESTINATION_FLAG,
|
|
55
|
+
NPM_PACK_FILENAME_KEY,
|
|
56
|
+
NPM_PACK_JSON_FLAG,
|
|
57
|
+
NPM_PACK_SUBCOMMAND,
|
|
58
|
+
PACKAGE_JSON_EXCLUDE_PREFIX,
|
|
59
|
+
PACKAGE_JSON_FILENAME,
|
|
60
|
+
PACKAGE_JSON_FILES_KEY,
|
|
61
|
+
PACKAGE_PATH_FROM_REPOSITORY,
|
|
62
|
+
PLUGIN_ROOT_TOKEN,
|
|
63
|
+
PYTHON_FILE_SUFFIX,
|
|
64
|
+
SMOKE_COMPILE_FAILURES_HEADER,
|
|
65
|
+
TARBALL_PACKAGE_PREFIX,
|
|
66
|
+
UNTRACKED_HOOK_SCRIPTS_HEADER,
|
|
67
|
+
UTF8_ENCODING,
|
|
68
|
+
VERIFICATION_PASSED_MESSAGE,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def package_root_path() -> Path:
|
|
73
|
+
"""Return the claude-dev-env package root that holds package.json."""
|
|
74
|
+
return Path(__file__).resolve().parent.parent
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def repository_root_path() -> Path:
|
|
78
|
+
"""Return the monorepo root two levels above the package root."""
|
|
79
|
+
return package_root_path().parent.parent
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def resolve_npm_executable() -> str:
|
|
83
|
+
"""Resolve the npm launcher path for the host platform.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Absolute path to ``npm.cmd`` on Windows or ``npm`` on POSIX.
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
FileNotFoundError: When neither launcher is on PATH.
|
|
90
|
+
"""
|
|
91
|
+
for each_binary_name in (NPM_CMD_BINARY_NAME, NPM_BINARY_NAME):
|
|
92
|
+
maybe_path = shutil.which(each_binary_name)
|
|
93
|
+
if maybe_path is not None:
|
|
94
|
+
return maybe_path
|
|
95
|
+
raise FileNotFoundError(
|
|
96
|
+
f"neither {NPM_CMD_BINARY_NAME!r} nor {NPM_BINARY_NAME!r} found on PATH"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def load_installable_surfaces_manifest(from_package_root: Path) -> Mapping[str, object]:
|
|
101
|
+
"""Load the committed installable-surfaces manifest.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
from_package_root: Package directory that holds the manifest file.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Parsed JSON object with directories and root_files lists.
|
|
108
|
+
"""
|
|
109
|
+
manifest_path = from_package_root / MANIFEST_FILENAME
|
|
110
|
+
return json.loads(manifest_path.read_text(encoding=UTF8_ENCODING))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _string_names_from_manifest_key(
|
|
114
|
+
all_manifest_entries: Mapping[str, object],
|
|
115
|
+
from_key: str,
|
|
116
|
+
) -> list[str]:
|
|
117
|
+
"""Return non-empty string entries for one manifest list key.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
all_manifest_entries: Parsed installable-surfaces manifest.
|
|
121
|
+
from_key: JSON key whose value is a list of surface names.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
String basenames from the list, or an empty list when the key is absent
|
|
125
|
+
or not a list.
|
|
126
|
+
"""
|
|
127
|
+
all_names = all_manifest_entries.get(from_key, [])
|
|
128
|
+
if not isinstance(all_names, list):
|
|
129
|
+
return []
|
|
130
|
+
return [
|
|
131
|
+
each_name
|
|
132
|
+
for each_name in all_names
|
|
133
|
+
if isinstance(each_name, str) and each_name
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def required_directory_names(all_manifest_entries: Mapping[str, object]) -> list[str]:
|
|
138
|
+
"""Return required top-level directory names from the manifest.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
all_manifest_entries: Parsed installable-surfaces manifest.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Directory basenames the packed package must deliver.
|
|
145
|
+
"""
|
|
146
|
+
return _string_names_from_manifest_key(
|
|
147
|
+
all_manifest_entries,
|
|
148
|
+
MANIFEST_DIRECTORIES_KEY,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def required_root_file_names(all_manifest_entries: Mapping[str, object]) -> list[str]:
|
|
153
|
+
"""Return required package-root file names from the manifest.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
all_manifest_entries: Parsed installable-surfaces manifest.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
File basenames the packed package must deliver at package root.
|
|
160
|
+
"""
|
|
161
|
+
return _string_names_from_manifest_key(
|
|
162
|
+
all_manifest_entries,
|
|
163
|
+
MANIFEST_ROOT_FILES_KEY,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def load_package_json_file_entries(from_package_root: Path) -> set[str]:
|
|
168
|
+
"""Load positive ``files`` entries from package.json without bang-excludes.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
from_package_root: Package directory that holds package.json.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Basename entries with trailing slashes stripped.
|
|
175
|
+
"""
|
|
176
|
+
package_json_path = from_package_root / PACKAGE_JSON_FILENAME
|
|
177
|
+
payload = json.loads(package_json_path.read_text(encoding=UTF8_ENCODING))
|
|
178
|
+
all_entries = payload.get(PACKAGE_JSON_FILES_KEY, [])
|
|
179
|
+
if not isinstance(all_entries, list):
|
|
180
|
+
return set()
|
|
181
|
+
return {
|
|
182
|
+
each_entry.rstrip("/")
|
|
183
|
+
for each_entry in all_entries
|
|
184
|
+
if isinstance(each_entry, str)
|
|
185
|
+
and not each_entry.startswith(PACKAGE_JSON_EXCLUDE_PREFIX)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def classify_surface(
|
|
190
|
+
directory_name: str,
|
|
191
|
+
all_packaged_names: set[str],
|
|
192
|
+
from_package_root: Path,
|
|
193
|
+
) -> str:
|
|
194
|
+
"""Classify one top-level surface against on-disk state and package.json.
|
|
195
|
+
|
|
196
|
+
::
|
|
197
|
+
|
|
198
|
+
classify_surface("hooks", {"hooks", "rules"}, package_root) -> packaged
|
|
199
|
+
classify_surface("scratch", {"hooks"}, package_root) -> source_only
|
|
200
|
+
flag: package.json lists missing_dir with no on-disk folder -> contradictory
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
directory_name: Top-level package directory basename.
|
|
204
|
+
all_packaged_names: Positive package.json files basenames.
|
|
205
|
+
from_package_root: Package directory used for the on-disk check.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
One of packaged, source_only, or contradictory.
|
|
209
|
+
"""
|
|
210
|
+
is_on_disk = (from_package_root / directory_name).is_dir()
|
|
211
|
+
is_packaged = directory_name in all_packaged_names
|
|
212
|
+
if is_on_disk and is_packaged:
|
|
213
|
+
return CLASS_PACKAGED
|
|
214
|
+
if is_on_disk and not is_packaged:
|
|
215
|
+
return CLASS_SOURCE_ONLY
|
|
216
|
+
if not is_on_disk and is_packaged:
|
|
217
|
+
return CLASS_CONTRADICTORY
|
|
218
|
+
return CLASS_SOURCE_ONLY
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def tarball_member_prefix_for_surface(surface_name: str) -> str:
|
|
222
|
+
"""Build the npm-pack member prefix for one surface path.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
surface_name: Directory or root-file path relative to the package root.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Prefix such as ``package/hooks`` used to match tarball members.
|
|
229
|
+
"""
|
|
230
|
+
return f"{TARBALL_PACKAGE_PREFIX}{surface_name}"
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def surface_appears_in_tarball_members(
|
|
234
|
+
surface_name: str,
|
|
235
|
+
all_tarball_members: frozenset[str],
|
|
236
|
+
) -> bool:
|
|
237
|
+
"""Report whether a surface path appears among packed tarball members.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
surface_name: Directory or root-file path relative to the package root.
|
|
241
|
+
all_tarball_members: Normalized member paths from the pack tarball.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
True when the exact member or a child path under the surface exists.
|
|
245
|
+
"""
|
|
246
|
+
member_prefix = tarball_member_prefix_for_surface(surface_name)
|
|
247
|
+
if member_prefix in all_tarball_members:
|
|
248
|
+
return True
|
|
249
|
+
child_prefix = f"{member_prefix}/"
|
|
250
|
+
return any(each_member.startswith(child_prefix) for each_member in all_tarball_members)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def list_npm_pack_tarball_members(from_tarball_path: Path) -> frozenset[str]:
|
|
254
|
+
"""List normalized member paths inside an npm pack tarball.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
from_tarball_path: Path to a ``.tgz`` produced by ``npm pack``.
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
Forward-slash member names as stored in the archive.
|
|
261
|
+
"""
|
|
262
|
+
with tarfile.open(from_tarball_path, mode="r:gz") as archive:
|
|
263
|
+
return frozenset(
|
|
264
|
+
each_member.name.replace("\\", "/") for each_member in archive.getmembers()
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _npm_pack_filename_from_json(stdout_text: str) -> str:
|
|
269
|
+
payload = json.loads(stdout_text)
|
|
270
|
+
if isinstance(payload, list) and payload:
|
|
271
|
+
first_record = payload[0]
|
|
272
|
+
if isinstance(first_record, dict):
|
|
273
|
+
filename = first_record.get(NPM_PACK_FILENAME_KEY)
|
|
274
|
+
if isinstance(filename, str) and filename:
|
|
275
|
+
return filename
|
|
276
|
+
if isinstance(payload, dict):
|
|
277
|
+
filename = payload.get(NPM_PACK_FILENAME_KEY)
|
|
278
|
+
if isinstance(filename, str) and filename:
|
|
279
|
+
return filename
|
|
280
|
+
raise RuntimeError(f"npm pack --json missing {NPM_PACK_FILENAME_KEY}: {stdout_text!r}")
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def run_npm_pack(into_directory: Path, from_package_root: Path) -> Path:
|
|
284
|
+
"""Run ``npm pack --json`` and return the written tarball path.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
into_directory: Directory that receives the generated ``.tgz``.
|
|
288
|
+
from_package_root: Package directory npm packs.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
Absolute path of the generated tarball.
|
|
292
|
+
"""
|
|
293
|
+
completed = subprocess.run(
|
|
294
|
+
[
|
|
295
|
+
resolve_npm_executable(),
|
|
296
|
+
NPM_PACK_SUBCOMMAND,
|
|
297
|
+
NPM_PACK_JSON_FLAG,
|
|
298
|
+
NPM_PACK_DESTINATION_FLAG,
|
|
299
|
+
str(into_directory),
|
|
300
|
+
],
|
|
301
|
+
cwd=str(from_package_root),
|
|
302
|
+
check=True,
|
|
303
|
+
capture_output=True,
|
|
304
|
+
text=True,
|
|
305
|
+
encoding=UTF8_ENCODING,
|
|
306
|
+
)
|
|
307
|
+
tarball_name = _npm_pack_filename_from_json(completed.stdout)
|
|
308
|
+
return into_directory / tarball_name
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def missing_manifest_surfaces_in_tarball(
|
|
312
|
+
all_manifest_entries: Mapping[str, object],
|
|
313
|
+
all_tarball_members: frozenset[str],
|
|
314
|
+
) -> list[str]:
|
|
315
|
+
"""List manifest surfaces absent from the packed tarball.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
all_manifest_entries: Parsed installable-surfaces manifest.
|
|
319
|
+
all_tarball_members: Member paths from ``npm pack``.
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
Surface names (directories and root files) missing from the tarball.
|
|
323
|
+
"""
|
|
324
|
+
all_required = required_directory_names(all_manifest_entries) + required_root_file_names(
|
|
325
|
+
all_manifest_entries
|
|
326
|
+
)
|
|
327
|
+
return [
|
|
328
|
+
each_surface
|
|
329
|
+
for each_surface in all_required
|
|
330
|
+
if not surface_appears_in_tarball_members(each_surface, all_tarball_members)
|
|
331
|
+
]
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def walk_json_command_fields(node: object) -> list[str]:
|
|
335
|
+
"""Collect every non-empty string under a ``command`` key in JSON.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
node: Arbitrary JSON-decoded object.
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
Command strings in depth-first encounter order.
|
|
342
|
+
"""
|
|
343
|
+
all_commands: list[str] = []
|
|
344
|
+
if isinstance(node, dict):
|
|
345
|
+
command = node.get(JSON_COMMAND_KEY)
|
|
346
|
+
if isinstance(command, str) and command.strip():
|
|
347
|
+
all_commands.append(command)
|
|
348
|
+
for each_child in node.values():
|
|
349
|
+
all_commands.extend(walk_json_command_fields(each_child))
|
|
350
|
+
return all_commands
|
|
351
|
+
if isinstance(node, list):
|
|
352
|
+
for each_child in node:
|
|
353
|
+
all_commands.extend(walk_json_command_fields(each_child))
|
|
354
|
+
return all_commands
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def extract_python_script_token(command: str) -> str | None:
|
|
358
|
+
"""Extract a ``.py`` path token from a hook command string.
|
|
359
|
+
|
|
360
|
+
Handles ``${CLAUDE_PLUGIN_ROOT}/hooks/...py`` shapes and absolute paths
|
|
361
|
+
that end with a Python script segment.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
command: Full hook command line from hooks.json.
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
The ``.py`` token, or None when the command has no script path.
|
|
368
|
+
"""
|
|
369
|
+
if PYTHON_FILE_SUFFIX not in command:
|
|
370
|
+
return None
|
|
371
|
+
for each_part in command.replace("\\", "/").split():
|
|
372
|
+
if each_part.endswith(PYTHON_FILE_SUFFIX):
|
|
373
|
+
return each_part
|
|
374
|
+
return None
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def package_relative_hook_script_path(script_token: str) -> str | None:
|
|
378
|
+
"""Normalize a hook script token to a package-relative ``hooks/...`` path.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
script_token: Path token extracted from a hook command.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
Package-relative path, or None when the token is not under hooks/.
|
|
385
|
+
"""
|
|
386
|
+
normalized = script_token.replace("\\", "/")
|
|
387
|
+
if normalized.startswith(PLUGIN_ROOT_TOKEN):
|
|
388
|
+
normalized = normalized[len(PLUGIN_ROOT_TOKEN) :]
|
|
389
|
+
package_marker = f"{PACKAGE_PATH_FROM_REPOSITORY}/"
|
|
390
|
+
if package_marker in normalized:
|
|
391
|
+
normalized = normalized.split(package_marker, 1)[1]
|
|
392
|
+
if normalized.startswith(HOOKS_DIRECTORY_PREFIX):
|
|
393
|
+
return normalized
|
|
394
|
+
return None
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def load_hook_python_script_paths(from_package_root: Path) -> list[str]:
|
|
398
|
+
"""Load package-relative ``.py`` paths referenced by hooks.json commands.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
from_package_root: Package directory that holds hooks/hooks.json.
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
Deduplicated package-relative script paths in first-seen order.
|
|
405
|
+
"""
|
|
406
|
+
hooks_json_path = from_package_root / HOOKS_JSON_RELATIVE_PATH
|
|
407
|
+
payload = json.loads(hooks_json_path.read_text(encoding=UTF8_ENCODING))
|
|
408
|
+
seen_paths: set[str] = set()
|
|
409
|
+
all_paths: list[str] = []
|
|
410
|
+
for each_command in walk_json_command_fields(payload):
|
|
411
|
+
script_token = extract_python_script_token(each_command)
|
|
412
|
+
if script_token is None:
|
|
413
|
+
continue
|
|
414
|
+
relative_path = package_relative_hook_script_path(script_token)
|
|
415
|
+
if relative_path is None or relative_path in seen_paths:
|
|
416
|
+
continue
|
|
417
|
+
seen_paths.add(relative_path)
|
|
418
|
+
all_paths.append(relative_path)
|
|
419
|
+
return all_paths
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def list_git_tracked_paths_under(
|
|
423
|
+
under_relative_path: str,
|
|
424
|
+
from_repository_root: Path,
|
|
425
|
+
) -> frozenset[str]:
|
|
426
|
+
"""List git-tracked paths under a repository-relative prefix.
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
under_relative_path: Repository-relative directory or file prefix.
|
|
430
|
+
from_repository_root: Repository root for ``git ls-files``.
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
Forward-slash repository-relative paths present in the index.
|
|
434
|
+
"""
|
|
435
|
+
completed = subprocess.run(
|
|
436
|
+
[GIT_BINARY_NAME, GIT_LS_FILES_SUBCOMMAND, "--", under_relative_path],
|
|
437
|
+
cwd=str(from_repository_root),
|
|
438
|
+
check=True,
|
|
439
|
+
capture_output=True,
|
|
440
|
+
text=True,
|
|
441
|
+
encoding=UTF8_ENCODING,
|
|
442
|
+
)
|
|
443
|
+
return frozenset(
|
|
444
|
+
each_line.replace("\\", "/")
|
|
445
|
+
for each_line in completed.stdout.splitlines()
|
|
446
|
+
if each_line.strip()
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def untracked_or_missing_hook_scripts(
|
|
451
|
+
all_package_relative_scripts: list[str],
|
|
452
|
+
from_package_root: Path,
|
|
453
|
+
from_repository_root: Path,
|
|
454
|
+
) -> list[str]:
|
|
455
|
+
"""Return hook scripts that are missing on disk or absent from the git index.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
all_package_relative_scripts: Paths relative to the package root.
|
|
459
|
+
from_package_root: Package directory for on-disk existence checks.
|
|
460
|
+
from_repository_root: Repository root for ``git ls-files``.
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
Package-relative script paths that fail the committed-file check.
|
|
464
|
+
"""
|
|
465
|
+
hooks_prefix = f"{PACKAGE_PATH_FROM_REPOSITORY}/{HOOKS_DIRECTORY_NAME}"
|
|
466
|
+
all_tracked = list_git_tracked_paths_under(
|
|
467
|
+
under_relative_path=hooks_prefix,
|
|
468
|
+
from_repository_root=from_repository_root,
|
|
469
|
+
)
|
|
470
|
+
missing_scripts: list[str] = []
|
|
471
|
+
for each_script in all_package_relative_scripts:
|
|
472
|
+
on_disk_path = from_package_root / each_script
|
|
473
|
+
repository_relative = f"{PACKAGE_PATH_FROM_REPOSITORY}/{each_script}".replace(
|
|
474
|
+
"\\", "/"
|
|
475
|
+
)
|
|
476
|
+
if not on_disk_path.is_file():
|
|
477
|
+
missing_scripts.append(each_script)
|
|
478
|
+
continue
|
|
479
|
+
if repository_relative not in all_tracked:
|
|
480
|
+
missing_scripts.append(each_script)
|
|
481
|
+
return missing_scripts
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def smoke_compile_python_scripts(
|
|
485
|
+
all_package_relative_scripts: list[str],
|
|
486
|
+
from_package_root: Path,
|
|
487
|
+
) -> list[str]:
|
|
488
|
+
"""Run ``py_compile`` on each package-relative script and collect failures.
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
all_package_relative_scripts: Paths relative to the package root.
|
|
492
|
+
from_package_root: Package directory that holds the scripts.
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
Package-relative paths that failed to compile, with error text.
|
|
496
|
+
"""
|
|
497
|
+
all_failures: list[str] = []
|
|
498
|
+
for each_script in all_package_relative_scripts:
|
|
499
|
+
script_path = from_package_root / each_script
|
|
500
|
+
try:
|
|
501
|
+
py_compile.compile(str(script_path), doraise=True)
|
|
502
|
+
except py_compile.PyCompileError as compile_error:
|
|
503
|
+
all_failures.append(f"{each_script}: {compile_error}")
|
|
504
|
+
return all_failures
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def smoke_check_install_entrypoint(from_package_root: Path) -> str | None:
|
|
508
|
+
"""Run ``node --check`` on the install entrypoint.
|
|
509
|
+
|
|
510
|
+
Args:
|
|
511
|
+
from_package_root: Package directory that holds bin/install.mjs.
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
Error text when the check fails, otherwise None.
|
|
515
|
+
"""
|
|
516
|
+
entrypoint_path = from_package_root / INSTALL_ENTRYPOINT_RELATIVE_PATH
|
|
517
|
+
completed = subprocess.run(
|
|
518
|
+
[NODE_BINARY_NAME, NODE_CHECK_FLAG, str(entrypoint_path)],
|
|
519
|
+
cwd=str(from_package_root),
|
|
520
|
+
capture_output=True,
|
|
521
|
+
text=True,
|
|
522
|
+
encoding=UTF8_ENCODING,
|
|
523
|
+
)
|
|
524
|
+
if completed.returncode == 0:
|
|
525
|
+
return None
|
|
526
|
+
return completed.stderr.strip() or completed.stdout.strip() or f"exit {completed.returncode}"
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def verify_packed_manifest_surfaces(from_package_root: Path) -> list[str]:
|
|
530
|
+
"""Pack the package and list manifest surfaces missing from the tarball.
|
|
531
|
+
|
|
532
|
+
Args:
|
|
533
|
+
from_package_root: Package directory to pack and verify.
|
|
534
|
+
|
|
535
|
+
Returns:
|
|
536
|
+
Missing surface names, empty when the tarball is complete.
|
|
537
|
+
"""
|
|
538
|
+
all_manifest_entries = load_installable_surfaces_manifest(
|
|
539
|
+
from_package_root=from_package_root
|
|
540
|
+
)
|
|
541
|
+
with tempfile.TemporaryDirectory() as temporary_directory:
|
|
542
|
+
tarball_path = run_npm_pack(
|
|
543
|
+
into_directory=Path(temporary_directory),
|
|
544
|
+
from_package_root=from_package_root,
|
|
545
|
+
)
|
|
546
|
+
all_members = list_npm_pack_tarball_members(from_tarball_path=tarball_path)
|
|
547
|
+
return missing_manifest_surfaces_in_tarball(all_manifest_entries, all_members)
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def run_all_installable_package_checks(
|
|
551
|
+
from_package_root: Path,
|
|
552
|
+
from_repository_root: Path,
|
|
553
|
+
) -> list[str]:
|
|
554
|
+
"""Run pack, git-index, and smoke checks; return human-readable failures.
|
|
555
|
+
|
|
556
|
+
Args:
|
|
557
|
+
from_package_root: Package directory under verification.
|
|
558
|
+
from_repository_root: Repository root for git index checks.
|
|
559
|
+
|
|
560
|
+
Returns:
|
|
561
|
+
Failure lines; empty when every check passes.
|
|
562
|
+
"""
|
|
563
|
+
all_failures: list[str] = []
|
|
564
|
+
missing_surfaces = verify_packed_manifest_surfaces(from_package_root=from_package_root)
|
|
565
|
+
if missing_surfaces:
|
|
566
|
+
all_failures.append(MISSING_MANIFEST_SURFACES_HEADER)
|
|
567
|
+
all_failures.extend(missing_surfaces)
|
|
568
|
+
|
|
569
|
+
all_hook_scripts = load_hook_python_script_paths(from_package_root=from_package_root)
|
|
570
|
+
missing_hooks = untracked_or_missing_hook_scripts(
|
|
571
|
+
all_package_relative_scripts=all_hook_scripts,
|
|
572
|
+
from_package_root=from_package_root,
|
|
573
|
+
from_repository_root=from_repository_root,
|
|
574
|
+
)
|
|
575
|
+
if missing_hooks:
|
|
576
|
+
all_failures.append(UNTRACKED_HOOK_SCRIPTS_HEADER)
|
|
577
|
+
all_failures.extend(missing_hooks)
|
|
578
|
+
|
|
579
|
+
compile_failures = smoke_compile_python_scripts(
|
|
580
|
+
all_package_relative_scripts=all_hook_scripts,
|
|
581
|
+
from_package_root=from_package_root,
|
|
582
|
+
)
|
|
583
|
+
if compile_failures:
|
|
584
|
+
all_failures.append(SMOKE_COMPILE_FAILURES_HEADER)
|
|
585
|
+
all_failures.extend(compile_failures)
|
|
586
|
+
|
|
587
|
+
entrypoint_failure = smoke_check_install_entrypoint(from_package_root=from_package_root)
|
|
588
|
+
if entrypoint_failure is not None:
|
|
589
|
+
all_failures.append(INSTALL_ENTRYPOINT_SMOKE_FAILURE_HEADER)
|
|
590
|
+
all_failures.append(entrypoint_failure)
|
|
591
|
+
return all_failures
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def main() -> int:
|
|
595
|
+
"""CLI entry: verify pack surfaces, hook scripts, and smoke checks.
|
|
596
|
+
|
|
597
|
+
Returns:
|
|
598
|
+
``0`` when every check passes, ``1`` when any check fails.
|
|
599
|
+
"""
|
|
600
|
+
all_failures = run_all_installable_package_checks(
|
|
601
|
+
from_package_root=package_root_path(),
|
|
602
|
+
from_repository_root=repository_root_path(),
|
|
603
|
+
)
|
|
604
|
+
if all_failures:
|
|
605
|
+
print(NEWLINE_JOIN_SEPARATOR.join(all_failures))
|
|
606
|
+
return EXIT_CODE_FAILURE
|
|
607
|
+
print(VERIFICATION_PASSED_MESSAGE)
|
|
608
|
+
return EXIT_CODE_SUCCESS
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
if __name__ == "__main__":
|
|
612
|
+
sys.exit(main())
|
package/settings.json
ADDED
package/skills/CLAUDE.md
CHANGED
|
@@ -17,7 +17,9 @@ Skills install to `~/.claude/skills/<skill-name>/` via `packages/claude-dev-env/
|
|
|
17
17
|
|
|
18
18
|
## Shared support code
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
**`skills/_shared/`** — skill-local PR-loop helpers plus `@` stubs that name
|
|
21
|
+
canonical homes under **`@~/.claude/_shared/`** (advisor protocol, PR-loop
|
|
22
|
+
contracts, runtime scripts). Map: `skills/_shared/CLAUDE.md`. End-of-run gotchas: `skills/_shared/end-of-run-gotcha-recommendations.md`.
|
|
21
23
|
|
|
22
24
|
## Skill groups
|
|
23
25
|
|
|
@@ -25,7 +27,7 @@ Skills install to `~/.claude/skills/<skill-name>/` via `packages/claude-dev-env/
|
|
|
25
27
|
- `anthropic-plan` — creates a source-grounded plan packet before any code changes
|
|
26
28
|
- `orchestrator` — turns the session into the orchestrator: it spawns executor subagents to do the code edits and test runs; hard decisions go to a shared advisor (Claude warm `session-advisor` via SendMessage; a third-party host: max-tier Claude via CLI Claude-chain)
|
|
27
29
|
- `orchestrator-refresh` — sub-skill fired by the `/orchestrator` loop to re-assert the host-matched shared-advisor discipline mid-run (Claude SendMessage; a third-party host's Claude CLI chain, no Agent-tool advisor spawn)
|
|
28
|
-
- `team-advisor` — binds one advisor at the strongest reachable tier (Claude warm agent; a third-party host: max-tier Claude via CLI Claude-chain, fail closed when unreachable)
|
|
30
|
+
- `team-advisor` — binds one advisor at the strongest reachable tier (Claude warm agent; a third-party host: max-tier Claude via CLI Claude-chain, fail closed when unreachable); consult cadence and weight live in `docs/references/advisor-tool.md`
|
|
29
31
|
- `grokify` — builds a paste-ready Grok Build handoff with a Claude advisor charter
|
|
30
32
|
- `grok-spawn` — orchestrator playbook for fleets of headless grok CLI workers (preflight, batch spec, `spawn_grok_batch.py`)
|
|
31
33
|
|
|
@@ -44,6 +46,8 @@ Skills install to `~/.claude/skills/<skill-name>/` via `packages/claude-dev-env/
|
|
|
44
46
|
- `recall` — retrieves facts from memory files
|
|
45
47
|
- `remember` — saves a decision, gotcha, or architectural choice to the Obsidian vault
|
|
46
48
|
- `everything-search` — file-system search via the Everything `es.exe` CLI
|
|
49
|
+
- `beat-sheet` — reformats a reply as short single-line beats
|
|
50
|
+
- `show` — creates and reviews inline visual explanations: diagrams, interactive widgets, mockups, charts, and illustrations
|
|
47
51
|
|
|
48
52
|
**Session and workflow management**
|
|
49
53
|
- `session-log` — logs a session report to the Obsidian vault
|
package/skills/_shared/CLAUDE.md
CHANGED
|
@@ -1,11 +1,44 @@
|
|
|
1
|
-
# _shared
|
|
1
|
+
# skills/_shared
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Map** for skill-install shared assets. Open a stub, then load the `@` target.
|
|
4
|
+
|
|
5
|
+
## Two homes
|
|
6
|
+
|
|
7
|
+
| Home | Path | Holds |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| **Skills shared** | `~/.claude/skills/_shared/` | Converge helpers, end-of-run gotcha ref, and `@` stubs |
|
|
10
|
+
| **Top-level shared** | `~/.claude/_shared/` | Advisor protocol, PR-loop contracts, runtime scripts |
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Reference docs (this tree)
|
|
14
|
+
|
|
15
|
+
| File | Role |
|
|
16
|
+
|---|---|
|
|
17
|
+
| **`end-of-run-gotcha-recommendations.md`** | End-of-run pasteable gotcha protocol for every skill |
|
|
4
18
|
|
|
5
19
|
## Subdirectories
|
|
6
20
|
|
|
7
21
|
| Directory | Role |
|
|
8
22
|
|---|---|
|
|
9
|
-
|
|
|
23
|
+
| **`advisor/`** | Stubs → `@~/.claude/_shared/advisor/` |
|
|
24
|
+
| **`pr-loop/`** | Local converge helpers + stubs → `@~/.claude/_shared/pr-loop/` |
|
|
25
|
+
|
|
26
|
+
## Canonical-path stubs
|
|
27
|
+
|
|
28
|
+
| Stub here | Load |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `advisor/advisor-protocol.md` | `@~/.claude/_shared/advisor/advisor-protocol.md` |
|
|
31
|
+
| `advisor/CLAUDE.md` | `@~/.claude/_shared/advisor/CLAUDE.md` |
|
|
32
|
+
| `advisor/scripts/README.md` | `@~/.claude/_shared/advisor/scripts/` |
|
|
33
|
+
| `pr-loop/audit-contract.md` | `@~/.claude/_shared/pr-loop/audit-contract.md` |
|
|
34
|
+
| `pr-loop/audit-reply-template.md` | `@~/.claude/_shared/pr-loop/audit-reply-template.md` |
|
|
35
|
+
| `pr-loop/code-rules-gate.md` | `@~/.claude/_shared/pr-loop/code-rules-gate.md` |
|
|
36
|
+
| `pr-loop/fix-protocol.md` | `@~/.claude/_shared/pr-loop/fix-protocol.md` |
|
|
37
|
+
| `pr-loop/gh-payloads.md` | `@~/.claude/_shared/pr-loop/gh-payloads.md` |
|
|
38
|
+
| `pr-loop/post-audit-thread-contract.md` | `@~/.claude/_shared/pr-loop/post-audit-thread-contract.md` |
|
|
39
|
+
| `pr-loop/precatch-rubric.md` | `@~/.claude/_shared/pr-loop/precatch-rubric.md` |
|
|
40
|
+
| `pr-loop/state-schema.md` | `@~/.claude/_shared/pr-loop/state-schema.md` |
|
|
41
|
+
| `pr-loop/worker-spawn.md` | `@~/.claude/_shared/pr-loop/worker-spawn.md` |
|
|
42
|
+
| `pr-loop/scripts/RUNTIME_SCRIPTS.md` | `@~/.claude/_shared/pr-loop/scripts/` |
|
|
10
43
|
|
|
11
|
-
|
|
44
|
+
Install via `packages/claude-dev-env/bin/install.mjs`.
|