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
package/rules/plain-language.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
All prose a person reads (chat, `AskUserQuestion`, docs, PR/issue bodies, commits): everyday words, short active sentences, lead with the answer, define jargon on first use, and give only the detail the reader needs to act (progressive disclosure). Aim for first-pass readability by a non-specialist. Exact identifiers, file paths, and API names stay exact; code is out of scope.
|
|
4
4
|
|
|
5
|
-
The `plain_language_blocker` PreToolUse hook (AskUserQuestion + `.md` Write/Edit/MultiEdit)
|
|
5
|
+
The `plain_language_blocker` PreToolUse hook (AskUserQuestion + `.md` Write/Edit/MultiEdit) names an everyday swap for a heavy word when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (default off). That path is **allow with a systemMessage advisory** — it does not deny the write. AskUserQuestion lean-block structure stays always on and still denies chat detail. When the flag is off, heavy-word hits only emit privacy-safe advisory candidates (see [`docs/references/prose-style-enforcement.md`](../docs/references/prose-style-enforcement.md)). Code fences, inline code, blockquotes, URLs, and file paths are skipped.
|
|
6
6
|
|
|
7
|
-
[`eli11-replies`](eli11-replies.md) governs reply length and shape; this rule governs word choice.
|
|
7
|
+
[`eli11-replies`](eli11-replies.md) governs reply length and shape; [`opus5-communication-contract`](opus5-communication-contract.md) governs progress and finals; this rule governs word choice.
|
|
8
8
|
|
|
9
9
|
A project can keep its own domain words out of the check with a `.claude/plain-language-allow.json` file: a JSON array of terms. An exact, case-insensitive, whole-word match on any term passes. The hook reads this file only from inside the project tree, up to the repository root, so each project's allowlist stays with its own code.
|
package/rules/research-mode.md
CHANGED
|
@@ -30,4 +30,4 @@ Creative thinking, brainstorming, and novel ideas don't require citation. You ca
|
|
|
30
30
|
|
|
31
31
|
- [`explore-thoroughly.md`](explore-thoroughly.md) — grounds an implementation plan in the codebase, as this rule grounds a factual claim in a source.
|
|
32
32
|
- [`verify-runtime-state.md`](verify-runtime-state.md) — a runtime verdict needs a live probe from this session, not a citation.
|
|
33
|
-
- [`hedging-claims.md`](hedging-claims.md) — the Stop hook that catches an
|
|
33
|
+
- [`hedging-claims.md`](hedging-claims.md) — the Stop hook that catches a bare hedge word; an explicit uncertainty label in the same sentence is the allowed alternative to a citation.
|
package/scripts/CLAUDE.md
CHANGED
|
@@ -12,10 +12,12 @@ Utility scripts installed into `~/.claude/scripts/` by `bin/install.mjs`. Each s
|
|
|
12
12
|
| `grok_headless_runner.py` | Runs one worker as headless `grok`: builds argv with no turn cap (the timeout is the only bound), mints a unique leader socket, captures streams, refuses a timeout that is missing, below `MIN_WORKER_TIMEOUT_SECONDS`, or above the `MAXIMUM_WORKER_TIMEOUT_SECONDS` (5400) ceiling, kills the whole process tree on timeout with grace and retries the kill-and-drain round once, classifies ok/usage_limit/auth_failure/timeout/kill_failed/error; exports `require_timeout_within_bounds` so a dispatcher can apply the same bounds without launching; imported by `spawn_grok_batch.py` and `resolve_worker_spawn.py` |
|
|
13
13
|
| `grok_worker_preflight.py` | Soft gate for the headless grok tier: binary on PATH, `grok models` auth, install manifest + role agents, opt-in cached live ping; non-zero exit is fallthrough, not failure |
|
|
14
14
|
| `setup_project_paths.py` | One-time bootstrap: discovers git repos via `es.exe` (Everything) and writes `~/.claude/project-paths.json`; never hardcodes scan roots |
|
|
15
|
-
| `spawn_grok_batch.py` | Launches a fleet of headless grok workers from a JSON batch spec: gates once through the preflight, refuses a spec whose `timeout_seconds` exceeds `MAXIMUM_WORKER_TIMEOUT_SECONDS` (5400) rather than clamping it, assembles each prompt from part files, staggers starts, runs each through `grok_headless_runner.py`, and emits one batch summary JSON |
|
|
15
|
+
| `spawn_grok_batch.py` | Launches a fleet of headless grok workers from a JSON batch spec: gates once through the preflight, refuses a spec whose `timeout_seconds` exceeds `MAXIMUM_WORKER_TIMEOUT_SECONDS` (5400) rather than clamping it, assembles each prompt from part files, optionally binds a unique worker advisor per role via the lead-supplied `advisor.launcher` (placeholder default in constants), injects `advisor_session_id`, requires the same session ENDORSE or bounded CORRECTION/PLAN then ENDORSE, classifies bind/verdict/timeout/missing-launcher failures as `advisor_blocked`, staggers starts, runs each through `grok_headless_runner.py`, and emits one batch summary JSON |
|
|
16
16
|
| `sweep_empty_dirs.py` | Deletes empty directories older than a configurable age under a given root; runs once (`--once`) or in continuous-watch mode |
|
|
17
17
|
| `sync_to_cursor.py` | Entry point for syncing Claude rules to Cursor `.mdc` files; delegates to the `sync_to_cursor/` package |
|
|
18
18
|
| `resolve_worker_spawn.py` | Dispatches a worker role through grok then claude fallback tiers (preflight, headless grok, `claude_agent_required` handoff, optional claude headless); applies `require_timeout_within_bounds` before the preflight, so an out-of-bounds `--timeout-seconds` prints a `timeout_out_of_bounds` outcome and exits 3 on every tier; protocol: [`../_shared/pr-loop/worker-spawn.md`](../_shared/pr-loop/worker-spawn.md) |
|
|
19
|
+
| `active_capability_references.py` | Inventories committed skill, agent, and command names and scans package markdown for active slash or backticked capability references; fails ban-listed names outside inert historical/example fences |
|
|
20
|
+
| `verify_installable_package.py` | Verifies the published package: runs real `npm pack`, checks every surface in `installable-surfaces.manifest.json` appears in the tarball, requires each `hooks.json` `.py` command to be git-tracked, and smoke-compiles those scripts (`node --check` on `bin/install.mjs`) |
|
|
19
21
|
|
|
20
22
|
## PowerShell scripts
|
|
21
23
|
|
|
@@ -24,6 +26,7 @@ Utility scripts installed into `~/.claude/scripts/` by `bin/install.mjs`. Each s
|
|
|
24
26
|
| `Audit-ShellPolicy.ps1` | Audits Bash tool calls in session transcripts against the `pwsh`-only shell policy |
|
|
25
27
|
| `Migrate-ShellPolicy.ps1` | Applies automated fixes for common shell-policy violations found by the audit script |
|
|
26
28
|
| `Install-SweepEmptyDirs.ps1` | Registers `sweep_empty_dirs.py` as a scheduled task on Windows |
|
|
29
|
+
| `Sync-RepoMain.ps1` | Fast-forwards a main-tracking mirror (`-RepoPath`, required) to its remote main using `merge --ff-only`; refuses a different checkout or a diverged branch, logs each run to `-LogPath`, and exits non-zero so a scheduled-task history row surfaces the problem. `-StashDirty` stashes tracked changes right before the fast-forward under a dated `pre-sync stash` message and leaves the stash in the list |
|
|
27
30
|
| `check.ps1` | Runs the full code-quality check suite |
|
|
28
31
|
| `Show-Asset.ps1` | Opens files on screen, sizing each image window to the image's pixel dimensions (scaled to fit the screen); non-image files open in their default application |
|
|
29
32
|
| `Get-SessionAccount.ps1` | Reports which Claude account the current session is actually logged into by comparing `~/.claude.json`'s CLI login against a `CLAUDE_USER_DATA_DIR` desktop profile's `lastKnownAccountUuid`, recovering the desktop account's email from profile storage when the two accounts differ |
|
|
@@ -33,7 +36,9 @@ Utility scripts installed into `~/.claude/scripts/` by `bin/install.mjs`. Each s
|
|
|
33
36
|
|
|
34
37
|
| Entry | Description |
|
|
35
38
|
|---|---|
|
|
36
|
-
| `
|
|
39
|
+
| `ci/` | CI-only adapters; `windows-installer-lifecycle.ps1` runs the Node installer 16-check driver under isolated HOME/USERPROFILE/GIT_CONFIG_GLOBAL and writes bounded evidence |
|
|
40
|
+
| `dev_env_scripts_constants/` | Named constants (`timing.py`, `grok_worker_constants.py`, …) for scripts in this directory, including worker-advisor placeholder launcher/model/effort, four verdict signals, correction cap, and advisor timeout |
|
|
41
|
+
| `profile-isolation-launchers/` | Profile-launcher contract plus MCP activation, Windows shortcut source semantics, and version preflight: profiles manifest, shared-path allowlist, profile resolver, `CLAUDE_CONFIG_DIR` authority, lean/full MCP bundle materialization into profile `mcp.json`, pure CLI/Desktop version-compatibility classifier consumed by launcher preflight, read-only shortcut inventory and preview reconcile under `windows/`. Live launcher execution, provenance pin, live shortcut mutation, and L1 deploy stay residual |
|
|
37
42
|
| `sync_to_cursor/` | Package that builds Cursor `.mdc` files from Claude rules and docs |
|
|
38
43
|
| `tests/` | pytest suite for the Python scripts and Pester (`*.Tests.ps1`) suite for the PowerShell scripts in this directory |
|
|
39
44
|
|
|
@@ -45,6 +50,12 @@ Python scripts (pytest):
|
|
|
45
50
|
python -m pytest packages/claude-dev-env/scripts/tests/
|
|
46
51
|
```
|
|
47
52
|
|
|
53
|
+
Profile-isolation launcher contract (node:test):
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
node --test packages/claude-dev-env/scripts/profile-isolation-launchers/**/*.test.mjs
|
|
57
|
+
```
|
|
58
|
+
|
|
48
59
|
PowerShell scripts (Pester 5+, `*.Tests.ps1`):
|
|
49
60
|
|
|
50
61
|
```powershell
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#Requires -Version 7.0
|
|
2
|
+
<#
|
|
3
|
+
.SYNOPSIS
|
|
4
|
+
Fast-forward a local main-tracking mirror to its remote main.
|
|
5
|
+
|
|
6
|
+
.DESCRIPTION
|
|
7
|
+
Runs against any working tree that is meant to stay on one branch and follow
|
|
8
|
+
a remote, such as a read-only mirror kept current by a scheduled task.
|
|
9
|
+
|
|
10
|
+
Safety rules:
|
|
11
|
+
- Only runs when the checked-out branch is the named branch
|
|
12
|
+
- Only runs when there are no tracked changes (untracked files are fine),
|
|
13
|
+
unless -StashDirty is passed
|
|
14
|
+
- Uses merge --ff-only only (never force, never hard reset, never clean)
|
|
15
|
+
- Logs and exits non-zero on skip, diverge, or failure so a Task Scheduler
|
|
16
|
+
history row can surface a problem instead of a silent no-op
|
|
17
|
+
|
|
18
|
+
Exit codes: 0 success, 1 git operation failed, 2 configuration or repo
|
|
19
|
+
problem, 3 skipped by a safety rule, 4 diverged from the remote.
|
|
20
|
+
|
|
21
|
+
.PARAMETER RepoPath
|
|
22
|
+
Absolute path to the git working tree. Required.
|
|
23
|
+
|
|
24
|
+
.PARAMETER Remote
|
|
25
|
+
Remote name whose main is the source of truth (default: origin).
|
|
26
|
+
|
|
27
|
+
.PARAMETER Branch
|
|
28
|
+
Local branch that must be checked out (default: main).
|
|
29
|
+
|
|
30
|
+
.PARAMETER StashDirty
|
|
31
|
+
Changes the tracked-changes branch from skip to stash. With the switch, a
|
|
32
|
+
tree carrying tracked modifications is stashed with the message
|
|
33
|
+
"pre-sync stash <yyyy-MM-dd>: uncommitted other-stream edits before ff to
|
|
34
|
+
<remote>/<branch>" right before the fast-forward, and the stash is left in
|
|
35
|
+
the stash list for a human to inspect. Untracked files are never stashed.
|
|
36
|
+
The stash runs only when the fast-forward is going to happen, so an
|
|
37
|
+
already-up-to-date or diverged run creates no stash entry. If the
|
|
38
|
+
fast-forward then fails, the stash is popped back so the edits do not go
|
|
39
|
+
missing; when that pop also fails, the log names the stash entry so the
|
|
40
|
+
operator can recover it by hand.
|
|
41
|
+
|
|
42
|
+
.PARAMETER LogPath
|
|
43
|
+
Absolute path to the run log (default: $HOME\.claude\logs\sync-repo-main.log).
|
|
44
|
+
#>
|
|
45
|
+
[CmdletBinding()]
|
|
46
|
+
param(
|
|
47
|
+
[Parameter(Mandatory)]
|
|
48
|
+
[string]$RepoPath,
|
|
49
|
+
[string]$Remote = 'origin',
|
|
50
|
+
[string]$Branch = 'main',
|
|
51
|
+
[switch]$StashDirty,
|
|
52
|
+
[string]$LogPath = (Join-Path $HOME '.claude\logs\sync-repo-main.log')
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
$ErrorActionPreference = 'Stop'
|
|
56
|
+
|
|
57
|
+
$logDirectory = Split-Path -Parent $LogPath
|
|
58
|
+
if (-not (Test-Path -LiteralPath $logDirectory)) {
|
|
59
|
+
New-Item -ItemType Directory -Force -Path $logDirectory | Out-Null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function Write-SyncLog {
|
|
63
|
+
param(
|
|
64
|
+
[Parameter(Mandatory)]
|
|
65
|
+
[string]$Level,
|
|
66
|
+
[Parameter(Mandatory)]
|
|
67
|
+
[string]$Message
|
|
68
|
+
)
|
|
69
|
+
$line = '{0} [{1}] {2}' -f (Get-Date -Format 'o'), $Level, $Message
|
|
70
|
+
Add-Content -LiteralPath $LogPath -Value $line -Encoding utf8
|
|
71
|
+
Write-Host $line
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function Get-GitExecutable {
|
|
75
|
+
$command = Get-Command git -ErrorAction SilentlyContinue
|
|
76
|
+
if ($null -eq $command) {
|
|
77
|
+
throw 'git executable not found on PATH'
|
|
78
|
+
}
|
|
79
|
+
return $command.Source
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function Invoke-Git {
|
|
83
|
+
param(
|
|
84
|
+
[Parameter(Mandatory)]
|
|
85
|
+
[string[]]$Arguments
|
|
86
|
+
)
|
|
87
|
+
$output = & $script:gitPath -C $RepoPath @Arguments 2>&1
|
|
88
|
+
$exitCode = $LASTEXITCODE
|
|
89
|
+
return [pscustomobject]@{
|
|
90
|
+
ExitCode = $exitCode
|
|
91
|
+
Output = ($output | Out-String).Trim()
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function Get-GitDecoration {
|
|
96
|
+
<#
|
|
97
|
+
Reads a value used only to decorate a log line. The sync has already
|
|
98
|
+
succeeded by the time these run, so a failure here returns a placeholder
|
|
99
|
+
rather than turning a completed sync into a failed run.
|
|
100
|
+
#>
|
|
101
|
+
param(
|
|
102
|
+
[Parameter(Mandatory)]
|
|
103
|
+
[string[]]$Arguments,
|
|
104
|
+
[Parameter(Mandatory)]
|
|
105
|
+
[string]$Placeholder
|
|
106
|
+
)
|
|
107
|
+
$result = Invoke-Git -Arguments $Arguments
|
|
108
|
+
if ($result.ExitCode -ne 0 -or -not $result.Output.Trim()) {
|
|
109
|
+
return $Placeholder
|
|
110
|
+
}
|
|
111
|
+
return $result.Output
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function Invoke-GitOrExit {
|
|
115
|
+
param(
|
|
116
|
+
[Parameter(Mandatory)]
|
|
117
|
+
[string[]]$Arguments,
|
|
118
|
+
[Parameter(Mandatory)]
|
|
119
|
+
[string]$FailureMessage,
|
|
120
|
+
[Parameter(Mandatory)]
|
|
121
|
+
[int]$FailureExitCode
|
|
122
|
+
)
|
|
123
|
+
$result = Invoke-Git -Arguments $Arguments
|
|
124
|
+
if ($result.ExitCode -ne 0) {
|
|
125
|
+
Write-SyncLog -Level 'ERROR' -Message "${FailureMessage}: $($result.Output)"
|
|
126
|
+
exit $FailureExitCode
|
|
127
|
+
}
|
|
128
|
+
return $result.Output
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
if (-not (Test-Path -LiteralPath $RepoPath)) {
|
|
133
|
+
Write-SyncLog -Level 'ERROR' -Message "repo path missing: $RepoPath"
|
|
134
|
+
exit 2
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
$script:gitPath = Get-GitExecutable
|
|
138
|
+
|
|
139
|
+
$currentBranch = Invoke-GitOrExit -Arguments @('branch', '--show-current') `
|
|
140
|
+
-FailureMessage 'cannot read current branch' -FailureExitCode 2
|
|
141
|
+
if ($currentBranch -ne $Branch) {
|
|
142
|
+
Write-SyncLog -Level 'SKIP' -Message "checkout is '$currentBranch', not '$Branch' (remote=$Remote)"
|
|
143
|
+
exit 3
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
$statusOutput = Invoke-GitOrExit -Arguments @('status', '--porcelain') `
|
|
147
|
+
-FailureMessage 'status failed' -FailureExitCode 2
|
|
148
|
+
$trackedDirtyLines = @($statusOutput -split "`r?`n" | Where-Object { $_.Trim() -and -not $_.StartsWith('??') })
|
|
149
|
+
if ($trackedDirtyLines.Count -gt 0 -and -not $StashDirty) {
|
|
150
|
+
Write-SyncLog -Level 'SKIP' -Message ("tracked changes present; refusing sync: " + ($trackedDirtyLines -join '; '))
|
|
151
|
+
exit 3
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
$null = Invoke-GitOrExit -Arguments @('fetch', $Remote, $Branch) `
|
|
155
|
+
-FailureMessage "fetch $Remote $Branch failed" -FailureExitCode 1
|
|
156
|
+
|
|
157
|
+
$tipOutput = Invoke-GitOrExit -Arguments @('rev-parse', $Branch, "$Remote/$Branch") `
|
|
158
|
+
-FailureMessage 'rev-parse of branch tips failed' -FailureExitCode 2
|
|
159
|
+
$tipLines = @($tipOutput -split "`r?`n" | Where-Object { $_.Trim() })
|
|
160
|
+
if ($tipLines.Count -ne 2) {
|
|
161
|
+
Write-SyncLog -Level 'ERROR' -Message "rev-parse returned $($tipLines.Count) tip(s), expected 2: $tipOutput"
|
|
162
|
+
exit 2
|
|
163
|
+
}
|
|
164
|
+
$localTip = $tipLines[0].Trim()
|
|
165
|
+
$remoteTip = $tipLines[1].Trim()
|
|
166
|
+
|
|
167
|
+
if ($localTip -eq $remoteTip) {
|
|
168
|
+
$remoteUrl = Get-GitDecoration -Arguments @('remote', 'get-url', $Remote) -Placeholder '<url unavailable>'
|
|
169
|
+
Write-SyncLog -Level 'OK' -Message "already up to date at $localTip ($Remote/$Branch $remoteUrl)"
|
|
170
|
+
exit 0
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
$ancestorResult = Invoke-Git -Arguments @('merge-base', '--is-ancestor', $Branch, "$Remote/$Branch")
|
|
174
|
+
if ($ancestorResult.ExitCode -ne 0) {
|
|
175
|
+
$countResult = Invoke-Git -Arguments @('rev-list', '--left-right', '--count', "${Branch}...${Remote}/${Branch}")
|
|
176
|
+
Write-SyncLog -Level 'ERROR' -Message "diverged from $Remote/$Branch (counts=$($countResult.Output)); fast-forward refused. local=$localTip remote=$remoteTip"
|
|
177
|
+
exit 4
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
$didStash = $false
|
|
181
|
+
if ($trackedDirtyLines.Count -gt 0) {
|
|
182
|
+
$stashMessage = 'pre-sync stash {0}: uncommitted other-stream edits before ff to {1}/{2}' -f (Get-Date -Format 'yyyy-MM-dd'), $Remote, $Branch
|
|
183
|
+
$stashResult = Invoke-Git -Arguments @('stash', 'push', '-m', $stashMessage)
|
|
184
|
+
if ($stashResult.ExitCode -ne 0) {
|
|
185
|
+
Write-SyncLog -Level 'ERROR' -Message "stash push failed; fast-forward abandoned: $($stashResult.Output)"
|
|
186
|
+
exit 1
|
|
187
|
+
}
|
|
188
|
+
$didStash = $true
|
|
189
|
+
Write-SyncLog -Level 'STASH' -Message ("stashed tracked changes as '" + $stashMessage + "' (left in stash list): " + ($trackedDirtyLines -join '; '))
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
$mergeResult = Invoke-Git -Arguments @('merge', '--ff-only', "$Remote/$Branch")
|
|
193
|
+
if ($mergeResult.ExitCode -ne 0) {
|
|
194
|
+
Write-SyncLog -Level 'ERROR' -Message "merge --ff-only failed: $($mergeResult.Output)"
|
|
195
|
+
if ($didStash) {
|
|
196
|
+
$popResult = Invoke-Git -Arguments @('stash', 'pop')
|
|
197
|
+
if ($popResult.ExitCode -eq 0) {
|
|
198
|
+
Write-SyncLog -Level 'OK' -Message "restored the stashed edits to the working tree after the failed fast-forward"
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
Write-SyncLog -Level 'ERROR' -Message ("stash pop failed after the failed fast-forward; the edits are still stashed as '" + $stashMessage + "' — recover them with git stash list and git stash pop: " + $popResult.Output)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
exit 1
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
$subject = Get-GitDecoration -Arguments @('log', '-1', '--format=%h %s') -Placeholder '<subject unavailable>'
|
|
208
|
+
$remoteUrl = Get-GitDecoration -Arguments @('remote', 'get-url', $Remote) -Placeholder '<url unavailable>'
|
|
209
|
+
Write-SyncLog -Level 'OK' -Message "$Branch $localTip -> $remoteTip | $subject | $Remote/$Branch $remoteUrl"
|
|
210
|
+
exit 0
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
Write-SyncLog -Level 'ERROR' -Message $_.Exception.Message
|
|
214
|
+
exit 1
|
|
215
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Resolve active skill, agent, command, and tool references in prompt surfaces.
|
|
3
|
+
|
|
4
|
+
Builds inventories from committed skill manifests, agent markdown files, and
|
|
5
|
+
commands. Scans active prompt text for slash and backticked capability names.
|
|
6
|
+
Fails when an active (non-inert) reference names a banned capability.
|
|
7
|
+
|
|
8
|
+
::
|
|
9
|
+
|
|
10
|
+
unresolved = unresolved_active_capabilities(package_root)
|
|
11
|
+
ok: unresolved == []
|
|
12
|
+
flag: active text mentions /qbug or `stub-detector`
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import re
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from dev_env_scripts_constants.active_capability_constants import (
|
|
22
|
+
ALL_BANNED_ACTIVE_CAPABILITY_NAMES,
|
|
23
|
+
ALL_INERT_FENCE_LANGUAGES,
|
|
24
|
+
BACKTICK_CAPABILITY_PATTERN,
|
|
25
|
+
BANNED_REASON_PREFIX,
|
|
26
|
+
FENCE_CLOSE_PATTERN,
|
|
27
|
+
FENCE_OPEN_PATTERN,
|
|
28
|
+
NEWLINE_JOIN_SEPARATOR,
|
|
29
|
+
PACKAGE_AGENTS_DIRECTORY,
|
|
30
|
+
PACKAGE_COMMANDS_DIRECTORY,
|
|
31
|
+
PACKAGE_SKILLS_DIRECTORY,
|
|
32
|
+
SKILL_MANIFEST_FILENAME,
|
|
33
|
+
SLASH_CAPABILITY_PATTERN,
|
|
34
|
+
UTF8_ENCODING,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class CapabilityInventory:
|
|
40
|
+
"""Committed capability names by kind."""
|
|
41
|
+
|
|
42
|
+
all_skill_names: frozenset[str]
|
|
43
|
+
all_agent_names: frozenset[str]
|
|
44
|
+
all_command_names: frozenset[str]
|
|
45
|
+
|
|
46
|
+
def all_known_names(self) -> frozenset[str]:
|
|
47
|
+
"""Return the union of skill, agent, and command names."""
|
|
48
|
+
return self.all_skill_names | self.all_agent_names | self.all_command_names
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class UnresolvedCapabilityReference:
|
|
53
|
+
"""One active reference that names a banned capability."""
|
|
54
|
+
|
|
55
|
+
file_path: str
|
|
56
|
+
line_number: int
|
|
57
|
+
capability_name: str
|
|
58
|
+
reason: str
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def build_capability_inventory(from_package_root: Path) -> CapabilityInventory:
|
|
62
|
+
"""Build inventories from committed skill/agent/command files.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
from_package_root: ``packages/claude-dev-env`` root.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Inventory of basenames present on disk.
|
|
69
|
+
"""
|
|
70
|
+
skills_root = from_package_root / PACKAGE_SKILLS_DIRECTORY
|
|
71
|
+
agents_root = from_package_root / PACKAGE_AGENTS_DIRECTORY
|
|
72
|
+
commands_root = from_package_root / PACKAGE_COMMANDS_DIRECTORY
|
|
73
|
+
all_skills: set[str] = set()
|
|
74
|
+
if skills_root.is_dir():
|
|
75
|
+
for each_skill_directory in skills_root.iterdir():
|
|
76
|
+
if each_skill_directory.is_dir() and (
|
|
77
|
+
each_skill_directory / SKILL_MANIFEST_FILENAME
|
|
78
|
+
).is_file():
|
|
79
|
+
all_skills.add(each_skill_directory.name)
|
|
80
|
+
all_agents: set[str] = set()
|
|
81
|
+
if agents_root.is_dir():
|
|
82
|
+
for each_agent_file in agents_root.glob("*.md"):
|
|
83
|
+
all_agents.add(each_agent_file.stem)
|
|
84
|
+
all_commands: set[str] = set()
|
|
85
|
+
if commands_root.is_dir():
|
|
86
|
+
for each_command_file in commands_root.glob("*.md"):
|
|
87
|
+
all_commands.add(each_command_file.stem)
|
|
88
|
+
return CapabilityInventory(
|
|
89
|
+
all_skill_names=frozenset(all_skills),
|
|
90
|
+
all_agent_names=frozenset(all_agents),
|
|
91
|
+
all_command_names=frozenset(all_commands),
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def strip_inert_fenced_blocks(markdown_text: str) -> str:
|
|
96
|
+
"""Remove fenced blocks tagged as historical/example content.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
markdown_text: Full markdown source.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Text with inert fenced blocks replaced by blank lines.
|
|
103
|
+
"""
|
|
104
|
+
fence_open_pattern = re.compile(FENCE_OPEN_PATTERN)
|
|
105
|
+
fence_close_pattern = re.compile(FENCE_CLOSE_PATTERN)
|
|
106
|
+
all_lines = markdown_text.splitlines()
|
|
107
|
+
all_kept: list[str] = []
|
|
108
|
+
is_inside_inert = False
|
|
109
|
+
for each_line in all_lines:
|
|
110
|
+
open_match = fence_open_pattern.match(each_line)
|
|
111
|
+
if open_match and not is_inside_inert:
|
|
112
|
+
language = (open_match.group(1) or "").lower()
|
|
113
|
+
if language in ALL_INERT_FENCE_LANGUAGES:
|
|
114
|
+
is_inside_inert = True
|
|
115
|
+
all_kept.append("")
|
|
116
|
+
continue
|
|
117
|
+
if is_inside_inert:
|
|
118
|
+
if fence_close_pattern.match(each_line):
|
|
119
|
+
is_inside_inert = False
|
|
120
|
+
all_kept.append("")
|
|
121
|
+
continue
|
|
122
|
+
all_kept.append(each_line)
|
|
123
|
+
return NEWLINE_JOIN_SEPARATOR.join(all_kept)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def extract_active_capability_names(markdown_text: str) -> list[tuple[int, str]]:
|
|
127
|
+
"""Return (1-based line, capability name) for active references.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
markdown_text: Prompt or skill markdown.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Ordered list of line number and capability name pairs.
|
|
134
|
+
"""
|
|
135
|
+
slash_pattern = re.compile(SLASH_CAPABILITY_PATTERN)
|
|
136
|
+
backtick_pattern = re.compile(BACKTICK_CAPABILITY_PATTERN)
|
|
137
|
+
active_text = strip_inert_fenced_blocks(markdown_text)
|
|
138
|
+
all_hits: list[tuple[int, str]] = []
|
|
139
|
+
for each_line_number, each_line in enumerate(active_text.splitlines(), start=1):
|
|
140
|
+
for each_match in slash_pattern.finditer(each_line):
|
|
141
|
+
all_hits.append((each_line_number, each_match.group(1)))
|
|
142
|
+
for each_match in backtick_pattern.finditer(each_line):
|
|
143
|
+
all_hits.append((each_line_number, each_match.group(1)))
|
|
144
|
+
return all_hits
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def classify_capability_reference(capability_name: str) -> str | None:
|
|
148
|
+
"""Return a failure reason when the capability is banned.
|
|
149
|
+
|
|
150
|
+
Extractors also match ordinary prose tokens, so unknown names pass.
|
|
151
|
+
Only the ban list fails a reference.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
capability_name: Extracted skill/command-like name.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Reason string, or None when the reference is allowed.
|
|
158
|
+
"""
|
|
159
|
+
if capability_name in ALL_BANNED_ACTIVE_CAPABILITY_NAMES:
|
|
160
|
+
return f"{BANNED_REASON_PREFIX}{capability_name}"
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def unresolved_active_capabilities(
|
|
165
|
+
from_package_root: Path,
|
|
166
|
+
*,
|
|
167
|
+
all_relative_markdown_paths: list[str] | None = None,
|
|
168
|
+
) -> list[UnresolvedCapabilityReference]:
|
|
169
|
+
"""Scan package markdown for banned active capability references.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
from_package_root: ``packages/claude-dev-env`` root.
|
|
173
|
+
all_relative_markdown_paths: Optional explicit relative paths to scan;
|
|
174
|
+
defaults to all skills, agents, and commands markdown.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Banned references with file, line, name, and reason.
|
|
178
|
+
"""
|
|
179
|
+
if all_relative_markdown_paths is None:
|
|
180
|
+
all_relative_markdown_paths = _default_markdown_paths(from_package_root)
|
|
181
|
+
all_unresolved: list[UnresolvedCapabilityReference] = []
|
|
182
|
+
for each_relative_path in all_relative_markdown_paths:
|
|
183
|
+
absolute_path = from_package_root / each_relative_path
|
|
184
|
+
if not absolute_path.is_file():
|
|
185
|
+
continue
|
|
186
|
+
markdown_text = absolute_path.read_text(encoding=UTF8_ENCODING)
|
|
187
|
+
for each_line_number, each_name in extract_active_capability_names(
|
|
188
|
+
markdown_text
|
|
189
|
+
):
|
|
190
|
+
reason = classify_capability_reference(each_name)
|
|
191
|
+
if reason is None:
|
|
192
|
+
continue
|
|
193
|
+
all_unresolved.append(
|
|
194
|
+
UnresolvedCapabilityReference(
|
|
195
|
+
file_path=each_relative_path.replace("\\", "/"),
|
|
196
|
+
line_number=each_line_number,
|
|
197
|
+
capability_name=each_name,
|
|
198
|
+
reason=reason,
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
return all_unresolved
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _default_markdown_paths(from_package_root: Path) -> list[str]:
|
|
205
|
+
all_paths: list[str] = []
|
|
206
|
+
for each_directory_name in (
|
|
207
|
+
PACKAGE_SKILLS_DIRECTORY,
|
|
208
|
+
PACKAGE_AGENTS_DIRECTORY,
|
|
209
|
+
PACKAGE_COMMANDS_DIRECTORY,
|
|
210
|
+
):
|
|
211
|
+
directory_path = from_package_root / each_directory_name
|
|
212
|
+
if not directory_path.is_dir():
|
|
213
|
+
continue
|
|
214
|
+
for each_markdown_file in directory_path.rglob("*.md"):
|
|
215
|
+
all_paths.append(
|
|
216
|
+
each_markdown_file.relative_to(from_package_root).as_posix()
|
|
217
|
+
)
|
|
218
|
+
return all_paths
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Windows CI adapter for the Node installer lifecycle driver.
|
|
2
|
+
#
|
|
3
|
+
# The driver already redirects HOME, USERPROFILE, and GIT_CONFIG_GLOBAL into a
|
|
4
|
+
# throwaway sandbox. This adapter records that isolation contract, runs the
|
|
5
|
+
# 16-check driver, and writes bounded evidence under -EvidenceRoot so a failed
|
|
6
|
+
# Windows runner leaves an actionable artifact.
|
|
7
|
+
#
|
|
8
|
+
# Usage (from packages/claude-dev-env):
|
|
9
|
+
# pwsh -NoProfile -File scripts/ci/windows-installer-lifecycle.ps1 -EvidenceRoot <dir>
|
|
10
|
+
|
|
11
|
+
[CmdletBinding()]
|
|
12
|
+
param(
|
|
13
|
+
[Parameter(Mandatory = $true)]
|
|
14
|
+
[string]$EvidenceRoot
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
$ErrorActionPreference = 'Stop'
|
|
18
|
+
|
|
19
|
+
$packageRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
|
|
20
|
+
$driverPath = Join-Path $packageRoot '.claude\skills\run-claude-dev-env\driver.mjs'
|
|
21
|
+
$allIsolationEnvNames = @(
|
|
22
|
+
'HOME',
|
|
23
|
+
'USERPROFILE',
|
|
24
|
+
'GIT_CONFIG_GLOBAL'
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
if (-not (Test-Path -LiteralPath $driverPath)) {
|
|
28
|
+
throw "Lifecycle driver missing at $driverPath"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
New-Item -ItemType Directory -Force -Path $EvidenceRoot | Out-Null
|
|
32
|
+
|
|
33
|
+
$preflightPath = Join-Path $EvidenceRoot 'preflight.json'
|
|
34
|
+
$driverLogPath = Join-Path $EvidenceRoot 'driver.log'
|
|
35
|
+
$summaryPath = Join-Path $EvidenceRoot 'summary.json'
|
|
36
|
+
|
|
37
|
+
$preflight = [ordered]@{
|
|
38
|
+
packageRoot = $packageRoot
|
|
39
|
+
driverPath = $driverPath
|
|
40
|
+
platform = [System.Environment]::OSVersion.VersionString
|
|
41
|
+
nodeVersion = (& node --version 2>&1 | Out-String).Trim()
|
|
42
|
+
pythonVersion = (& python --version 2>&1 | Out-String).Trim()
|
|
43
|
+
isolationContract = $allIsolationEnvNames
|
|
44
|
+
note = 'Driver owns isolation roots; adapter never points at the real user profile.'
|
|
45
|
+
}
|
|
46
|
+
$preflight | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $preflightPath -Encoding utf8
|
|
47
|
+
|
|
48
|
+
Write-Host "Evidence root: $EvidenceRoot"
|
|
49
|
+
Write-Host "Driver: $driverPath"
|
|
50
|
+
Write-Host ("Isolation contract: {0} (driver-owned sandbox)" -f ($allIsolationEnvNames -join ', '))
|
|
51
|
+
|
|
52
|
+
$driverOutput = & node $driverPath 2>&1
|
|
53
|
+
$driverExitCode = $LASTEXITCODE
|
|
54
|
+
$driverOutput | ForEach-Object { Write-Host $_ }
|
|
55
|
+
$driverOutputText = ($driverOutput | Out-String)
|
|
56
|
+
[System.IO.File]::WriteAllText($driverLogPath, $driverOutputText)
|
|
57
|
+
|
|
58
|
+
$passedCheckMatch = [regex]::Match($driverOutputText, '(\d+)/(\d+) checks passed')
|
|
59
|
+
$summary = [ordered]@{
|
|
60
|
+
exitCode = $driverExitCode
|
|
61
|
+
allChecksPassed = ($driverExitCode -eq 0 -and $driverOutputText -match 'ALL CHECKS PASSED')
|
|
62
|
+
checksLine = if ($passedCheckMatch.Success) { $passedCheckMatch.Value } else { 'unparsed' }
|
|
63
|
+
evidenceFiles = @(
|
|
64
|
+
'preflight.json',
|
|
65
|
+
'driver.log',
|
|
66
|
+
'summary.json'
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
$summary | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $summaryPath -Encoding utf8
|
|
70
|
+
|
|
71
|
+
if ($driverExitCode -ne 0) {
|
|
72
|
+
throw "Lifecycle driver exited $driverExitCode (see $driverLogPath)"
|
|
73
|
+
}
|
|
74
|
+
if ($driverOutputText -notmatch 'ALL CHECKS PASSED') {
|
|
75
|
+
throw "Lifecycle driver output missing ALL CHECKS PASSED (see $driverLogPath)"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
Write-Host "Windows installer lifecycle: OK"
|