@ahmed-g-gad/apothem 0.1.1
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/CHANGELOG.md +60 -0
- package/LICENSE +21 -0
- package/LICENSES/MIT.txt +18 -0
- package/LICENSES/PSF-2.0.txt +47 -0
- package/README.md +549 -0
- package/bin/README.md +37 -0
- package/bin/apothem.mjs +78 -0
- package/package.json +75 -0
- package/pyproject.toml +347 -0
- package/src/apothem/README.md +52 -0
- package/src/apothem/__init__.py +66 -0
- package/src/apothem/__main__.py +28 -0
- package/src/apothem/_vendor/.keep +0 -0
- package/src/apothem/_vendor/__init__.py +25 -0
- package/src/apothem/_vendor/attr/__init__.py +104 -0
- package/src/apothem/_vendor/attr/__init__.pyi +389 -0
- package/src/apothem/_vendor/attr/_cmp.py +160 -0
- package/src/apothem/_vendor/attr/_cmp.pyi +13 -0
- package/src/apothem/_vendor/attr/_compat.py +99 -0
- package/src/apothem/_vendor/attr/_config.py +31 -0
- package/src/apothem/_vendor/attr/_funcs.py +497 -0
- package/src/apothem/_vendor/attr/_make.py +3406 -0
- package/src/apothem/_vendor/attr/_next_gen.py +674 -0
- package/src/apothem/_vendor/attr/_typing_compat.pyi +15 -0
- package/src/apothem/_vendor/attr/_version_info.py +89 -0
- package/src/apothem/_vendor/attr/_version_info.pyi +9 -0
- package/src/apothem/_vendor/attr/converters.py +162 -0
- package/src/apothem/_vendor/attr/converters.pyi +19 -0
- package/src/apothem/_vendor/attr/exceptions.py +95 -0
- package/src/apothem/_vendor/attr/exceptions.pyi +17 -0
- package/src/apothem/_vendor/attr/filters.py +72 -0
- package/src/apothem/_vendor/attr/filters.pyi +6 -0
- package/src/apothem/_vendor/attr/py.typed +0 -0
- package/src/apothem/_vendor/attr/setters.py +79 -0
- package/src/apothem/_vendor/attr/setters.pyi +20 -0
- package/src/apothem/_vendor/attr/validators.py +750 -0
- package/src/apothem/_vendor/attr/validators.pyi +140 -0
- package/src/apothem/_vendor/attr.LICENSE +21 -0
- package/src/apothem/_vendor/attrs/__init__.py +72 -0
- package/src/apothem/_vendor/attrs/__init__.pyi +314 -0
- package/src/apothem/_vendor/attrs/converters.py +3 -0
- package/src/apothem/_vendor/attrs/exceptions.py +3 -0
- package/src/apothem/_vendor/attrs/filters.py +3 -0
- package/src/apothem/_vendor/attrs/py.typed +0 -0
- package/src/apothem/_vendor/attrs/setters.py +3 -0
- package/src/apothem/_vendor/attrs/validators.py +3 -0
- package/src/apothem/_vendor/attrs.LICENSE +21 -0
- package/src/apothem/_vendor/jsonschema/__init__.py +120 -0
- package/src/apothem/_vendor/jsonschema/__main__.py +6 -0
- package/src/apothem/_vendor/jsonschema/_format.py +546 -0
- package/src/apothem/_vendor/jsonschema/_keywords.py +449 -0
- package/src/apothem/_vendor/jsonschema/_legacy_keywords.py +449 -0
- package/src/apothem/_vendor/jsonschema/_types.py +204 -0
- package/src/apothem/_vendor/jsonschema/_typing.py +29 -0
- package/src/apothem/_vendor/jsonschema/_utils.py +355 -0
- package/src/apothem/_vendor/jsonschema/benchmarks/__init__.py +5 -0
- package/src/apothem/_vendor/jsonschema/benchmarks/const_vs_enum.py +30 -0
- package/src/apothem/_vendor/jsonschema/benchmarks/contains.py +28 -0
- package/src/apothem/_vendor/jsonschema/benchmarks/import_benchmark.py +31 -0
- package/src/apothem/_vendor/jsonschema/benchmarks/issue232/issue.json +2653 -0
- package/src/apothem/_vendor/jsonschema/benchmarks/issue232.py +25 -0
- package/src/apothem/_vendor/jsonschema/benchmarks/json_schema_test_suite.py +12 -0
- package/src/apothem/_vendor/jsonschema/benchmarks/nested_schemas.py +56 -0
- package/src/apothem/_vendor/jsonschema/benchmarks/subcomponents.py +42 -0
- package/src/apothem/_vendor/jsonschema/benchmarks/unused_registry.py +35 -0
- package/src/apothem/_vendor/jsonschema/benchmarks/useless_applicator_schemas.py +106 -0
- package/src/apothem/_vendor/jsonschema/benchmarks/useless_keywords.py +32 -0
- package/src/apothem/_vendor/jsonschema/benchmarks/validator_creation.py +14 -0
- package/src/apothem/_vendor/jsonschema/cli.py +292 -0
- package/src/apothem/_vendor/jsonschema/exceptions.py +490 -0
- package/src/apothem/_vendor/jsonschema/protocols.py +230 -0
- package/src/apothem/_vendor/jsonschema/validators.py +1410 -0
- package/src/apothem/_vendor/jsonschema.LICENSE +19 -0
- package/src/apothem/_vendor/jsonschema_specifications/__init__.py +12 -0
- package/src/apothem/_vendor/jsonschema_specifications/_core.py +38 -0
- package/src/apothem/_vendor/jsonschema_specifications/schemas/draft201909/metaschema.json +42 -0
- package/src/apothem/_vendor/jsonschema_specifications/schemas/draft201909/vocabularies/applicator +56 -0
- package/src/apothem/_vendor/jsonschema_specifications/schemas/draft201909/vocabularies/content +17 -0
- package/src/apothem/_vendor/jsonschema_specifications/schemas/draft201909/vocabularies/core +57 -0
- package/src/apothem/_vendor/jsonschema_specifications/schemas/draft201909/vocabularies/format +14 -0
- package/src/apothem/_vendor/jsonschema_specifications/schemas/draft201909/vocabularies/meta-data +37 -0
- package/src/apothem/_vendor/jsonschema_specifications/schemas/draft201909/vocabularies/validation +98 -0
- package/src/apothem/_vendor/jsonschema_specifications/schemas/draft202012/metaschema.json +58 -0
- package/src/apothem/_vendor/jsonschema_specifications/schemas/draft202012/vocabularies/applicator +48 -0
- package/src/apothem/_vendor/jsonschema_specifications/schemas/draft202012/vocabularies/content +17 -0
- package/src/apothem/_vendor/jsonschema_specifications/schemas/draft202012/vocabularies/core +51 -0
- package/src/apothem/_vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-annotation +14 -0
- package/src/apothem/_vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-assertion +14 -0
- package/src/apothem/_vendor/jsonschema_specifications/schemas/draft202012/vocabularies/meta-data +37 -0
- package/src/apothem/_vendor/jsonschema_specifications/schemas/draft202012/vocabularies/unevaluated +15 -0
- package/src/apothem/_vendor/jsonschema_specifications/schemas/draft202012/vocabularies/validation +98 -0
- package/src/apothem/_vendor/jsonschema_specifications/schemas/draft3/metaschema.json +172 -0
- package/src/apothem/_vendor/jsonschema_specifications/schemas/draft4/metaschema.json +149 -0
- package/src/apothem/_vendor/jsonschema_specifications/schemas/draft6/metaschema.json +153 -0
- package/src/apothem/_vendor/jsonschema_specifications/schemas/draft7/metaschema.json +166 -0
- package/src/apothem/_vendor/jsonschema_specifications.LICENSE +19 -0
- package/src/apothem/_vendor/referencing/__init__.py +7 -0
- package/src/apothem/_vendor/referencing/_attrs.py +31 -0
- package/src/apothem/_vendor/referencing/_attrs.pyi +21 -0
- package/src/apothem/_vendor/referencing/_core.py +739 -0
- package/src/apothem/_vendor/referencing/exceptions.py +165 -0
- package/src/apothem/_vendor/referencing/jsonschema.py +642 -0
- package/src/apothem/_vendor/referencing/py.typed +0 -0
- package/src/apothem/_vendor/referencing/retrieval.py +94 -0
- package/src/apothem/_vendor/referencing/typing.py +61 -0
- package/src/apothem/_vendor/referencing.LICENSE +19 -0
- package/src/apothem/_vendor/rpds/__init__.py +251 -0
- package/src/apothem/_vendor/typing_extensions.LICENSE +279 -0
- package/src/apothem/_vendor/typing_extensions.py +4317 -0
- package/src/apothem/_vendor/vendor.txt +22 -0
- package/src/apothem/_vendor/yaml/__init__.py +389 -0
- package/src/apothem/_vendor/yaml/composer.py +138 -0
- package/src/apothem/_vendor/yaml/constructor.py +748 -0
- package/src/apothem/_vendor/yaml/cyaml.py +100 -0
- package/src/apothem/_vendor/yaml/dumper.py +61 -0
- package/src/apothem/_vendor/yaml/emitter.py +1137 -0
- package/src/apothem/_vendor/yaml/error.py +74 -0
- package/src/apothem/_vendor/yaml/events.py +85 -0
- package/src/apothem/_vendor/yaml/loader.py +63 -0
- package/src/apothem/_vendor/yaml/nodes.py +48 -0
- package/src/apothem/_vendor/yaml/parser.py +588 -0
- package/src/apothem/_vendor/yaml/reader.py +185 -0
- package/src/apothem/_vendor/yaml/representer.py +388 -0
- package/src/apothem/_vendor/yaml/resolver.py +226 -0
- package/src/apothem/_vendor/yaml/scanner.py +1435 -0
- package/src/apothem/_vendor/yaml/serializer.py +110 -0
- package/src/apothem/_vendor/yaml/tokens.py +103 -0
- package/src/apothem/_vendor/yaml.LICENSE +20 -0
- package/src/apothem/agents/README.md +60 -0
- package/src/apothem/agents/codebase-explorer.md +91 -0
- package/src/apothem/agents/convention-auditor.md +93 -0
- package/src/apothem/agents/dependency-auditor.md +97 -0
- package/src/apothem/agents/fact-checker.md +84 -0
- package/src/apothem/agents/mcp-builder.md +86 -0
- package/src/apothem/agents/memory-auditor.md +93 -0
- package/src/apothem/agents/prompt-evaluator.md +87 -0
- package/src/apothem/agents/quality-gate.md +103 -0
- package/src/apothem/agents/refactor-surgeon.md +74 -0
- package/src/apothem/agents/research-scout.md +73 -0
- package/src/apothem/agents/security-scanner.md +83 -0
- package/src/apothem/agents/test-runner.md +84 -0
- package/src/apothem/audit/README.md +73 -0
- package/src/apothem/audit/_scan_lib.py +182 -0
- package/src/apothem/audit/analyze_graph.py +260 -0
- package/src/apothem/audit/build_capability_graph.py +607 -0
- package/src/apothem/audit/build_inventory.py +657 -0
- package/src/apothem/audit/build_plans_provenance.py +997 -0
- package/src/apothem/audit/check_links.py +389 -0
- package/src/apothem/audit/classify_artifacts.py +381 -0
- package/src/apothem/audit/deprecated-tokens.txt +10 -0
- package/src/apothem/audit/execute_plans_migration.py +491 -0
- package/src/apothem/audit/known-projects.txt +15 -0
- package/src/apothem/audit/render_capability_index.py +467 -0
- package/src/apothem/audit/render_inventory.py +405 -0
- package/src/apothem/audit/scan_ai_surfaces.py +1125 -0
- package/src/apothem/audit/scan_ai_surfaces_coarse.py +261 -0
- package/src/apothem/audit/scan_drift_features.py +143 -0
- package/src/apothem/audit/scan_frontmatter.py +293 -0
- package/src/apothem/audit/scan_header_coverage.py +1134 -0
- package/src/apothem/audit/scan_plan_leakage.py +540 -0
- package/src/apothem/audit/scan_plans_discipline.py +188 -0
- package/src/apothem/audit/scan_secrets_pii.py +245 -0
- package/src/apothem/audit/scan_stale_tokens.py +296 -0
- package/src/apothem/audit/synthesize_drift.py +205 -0
- package/src/apothem/benchmarks/README.md +33 -0
- package/src/apothem/benchmarks/__init__.py +3 -0
- package/src/apothem/benchmarks/bench_agents.py +63 -0
- package/src/apothem/benchmarks/bench_hooks.py +93 -0
- package/src/apothem/benchmarks/bench_install.py +58 -0
- package/src/apothem/benchmarks/bench_tests.py +93 -0
- package/src/apothem/benchmarks/bench_validate_ecosystem.py +84 -0
- package/src/apothem/cli/README.md +33 -0
- package/src/apothem/cli/__init__.py +229 -0
- package/src/apothem/cli/_cmd_completion.py +88 -0
- package/src/apothem/cli/_cmd_diff.py +181 -0
- package/src/apothem/cli/_cmd_doctor.py +143 -0
- package/src/apothem/cli/_cmd_harnesses.py +167 -0
- package/src/apothem/cli/_cmd_install.py +327 -0
- package/src/apothem/cli/_cmd_migrate_workspace.py +143 -0
- package/src/apothem/cli/_cmd_profile.py +341 -0
- package/src/apothem/cli/_cmd_status.py +180 -0
- package/src/apothem/cli/_cmd_uninstall.py +215 -0
- package/src/apothem/cli/_cmd_update.py +397 -0
- package/src/apothem/cli/_cmd_verify.py +194 -0
- package/src/apothem/cli/_common_flags.py +90 -0
- package/src/apothem/cli/_epilogs.py +296 -0
- package/src/apothem/cli/_helpers.py +857 -0
- package/src/apothem/cli/_json_formatter.py +21 -0
- package/src/apothem/cli/_materialize.py +376 -0
- package/src/apothem/cli/completions/apothem.bash +30 -0
- package/src/apothem/cli/completions/apothem.fish +19 -0
- package/src/apothem/cli/completions/apothem.ps1 +27 -0
- package/src/apothem/cli/completions/apothem.zsh +42 -0
- package/src/apothem/cli/reference_export.py +126 -0
- package/src/apothem/commands/README.md +125 -0
- package/src/apothem/commands/a11y-audit.md +203 -0
- package/src/apothem/commands/architecture-review.md +194 -0
- package/src/apothem/commands/audit.md +165 -0
- package/src/apothem/commands/code-audit.md +218 -0
- package/src/apothem/commands/code-review.md +193 -0
- package/src/apothem/commands/dependency-audit.md +209 -0
- package/src/apothem/commands/docs-review.md +199 -0
- package/src/apothem/commands/elevate.md +285 -0
- package/src/apothem/commands/eval.md +149 -0
- package/src/apothem/commands/fortress.md +172 -0
- package/src/apothem/commands/freshify.md +168 -0
- package/src/apothem/commands/github-deploy-fresh.md +178 -0
- package/src/apothem/commands/github-deploy-next.md +167 -0
- package/src/apothem/commands/perf-audit.md +198 -0
- package/src/apothem/commands/plan-amend.md +104 -0
- package/src/apothem/commands/plan-audit.md +127 -0
- package/src/apothem/commands/plan-design.md +257 -0
- package/src/apothem/commands/plan-execute.md +495 -0
- package/src/apothem/commands/plan-generate.md +351 -0
- package/src/apothem/commands/plan-review.md +555 -0
- package/src/apothem/commands/plan-spec.md +359 -0
- package/src/apothem/commands/plan-status.md +222 -0
- package/src/apothem/commands/plan.md +173 -0
- package/src/apothem/commands/projectify.md +142 -0
- package/src/apothem/commands/release-readiness.md +142 -0
- package/src/apothem/commands/research-analysis.md +241 -0
- package/src/apothem/commands/research-design.md +231 -0
- package/src/apothem/commands/research-disseminate.md +225 -0
- package/src/apothem/commands/research-experiment.md +232 -0
- package/src/apothem/commands/research-ideate.md +213 -0
- package/src/apothem/commands/research-paper.md +252 -0
- package/src/apothem/commands/research-proposal.md +220 -0
- package/src/apothem/commands/research-publish.md +255 -0
- package/src/apothem/commands/research-review.md +251 -0
- package/src/apothem/commands/research-sources.md +266 -0
- package/src/apothem/commands/research-spec.md +255 -0
- package/src/apothem/commands/research-synthesis.md +233 -0
- package/src/apothem/commands/research-theory.md +218 -0
- package/src/apothem/commands/research.md +181 -0
- package/src/apothem/commands/security-audit.md +196 -0
- package/src/apothem/commands/supply-chain-audit.md +192 -0
- package/src/apothem/commands/test-suite.md +146 -0
- package/src/apothem/commands/threat-model-audit.md +199 -0
- package/src/apothem/commands/ux-review.md +202 -0
- package/src/apothem/commands/workflow.md +162 -0
- package/src/apothem/conformity/README.md +173 -0
- package/src/apothem/conformity/__init__.py +1 -0
- package/src/apothem/conformity/_grep_base.py +93 -0
- package/src/apothem/conformity/agent_capability_grep.py +306 -0
- package/src/apothem/conformity/agents_md_coverage_grep.py +382 -0
- package/src/apothem/conformity/agnosticism_grep.py +311 -0
- package/src/apothem/conformity/always_on_budget_grep.py +318 -0
- package/src/apothem/conformity/bare_except_grep.py +115 -0
- package/src/apothem/conformity/binding_reciprocity_grep.py +151 -0
- package/src/apothem/conformity/brand_mark_grep.py +272 -0
- package/src/apothem/conformity/commented_out_code_grep.py +176 -0
- package/src/apothem/conformity/completion_claim_grep.py +169 -0
- package/src/apothem/conformity/conventional_commit_grep.py +319 -0
- package/src/apothem/conformity/copilot_instructions_presence_grep.py +324 -0
- package/src/apothem/conformity/cross_platform_matrix_grep.py +297 -0
- package/src/apothem/conformity/determinism_grep.py +306 -0
- package/src/apothem/conformity/diagram_staleness_grep.py +154 -0
- package/src/apothem/conformity/dynamism_grep.py +284 -0
- package/src/apothem/conformity/editorconfig_presence_grep.py +281 -0
- package/src/apothem/conformity/file_header_grep.py +502 -0
- package/src/apothem/conformity/freshness_token_grep.py +233 -0
- package/src/apothem/conformity/frontmatter_grep.py +274 -0
- package/src/apothem/conformity/frontmatter_value_grep.py +386 -0
- package/src/apothem/conformity/gate.py +1386 -0
- package/src/apothem/conformity/gitattributes_presence_grep.py +238 -0
- package/src/apothem/conformity/harden_runner_grep.py +320 -0
- package/src/apothem/conformity/hedging_grep.py +129 -0
- package/src/apothem/conformity/license_author_consistency_grep.py +204 -0
- package/src/apothem/conformity/link_check.py +327 -0
- package/src/apothem/conformity/magic_number_grep.py +182 -0
- package/src/apothem/conformity/multi_surface_coherence_grep.py +620 -0
- package/src/apothem/conformity/naming_grep.py +224 -0
- package/src/apothem/conformity/no_global_plans_grep.py +339 -0
- package/src/apothem/conformity/no_toplevel_docs_grep.py +120 -0
- package/src/apothem/conformity/oidc_trusted_publishing_grep.py +291 -0
- package/src/apothem/conformity/option_annotation_grep.py +352 -0
- package/src/apothem/conformity/orphan_output_grep.py +206 -0
- package/src/apothem/conformity/permissions_minimum_scope_grep.py +299 -0
- package/src/apothem/conformity/plain_language_grep.py +559 -0
- package/src/apothem/conformity/plan_next_step_consistency_grep.py +450 -0
- package/src/apothem/conformity/plan_suite_structure_grep.py +534 -0
- package/src/apothem/conformity/plans_discipline_language_grep.py +245 -0
- package/src/apothem/conformity/production_ready_pr_grep.py +200 -0
- package/src/apothem/conformity/recommend_next_step_grep.py +250 -0
- package/src/apothem/conformity/redundancy_grep.py +401 -0
- package/src/apothem/conformity/reference_token_grep.py +230 -0
- package/src/apothem/conformity/registry_capability_consistency_grep.py +368 -0
- package/src/apothem/conformity/secret_leak_grep.py +193 -0
- package/src/apothem/conformity/semver_stability_grep.py +358 -0
- package/src/apothem/conformity/smoke_install_grep.py +194 -0
- package/src/apothem/conformity/static_version_grep.py +284 -0
- package/src/apothem/conformity/token_efficiency_grep.py +185 -0
- package/src/apothem/conformity/unpinned_action_grep.py +115 -0
- package/src/apothem/conformity/user_confirm_grep.py +74 -0
- package/src/apothem/conformity/workflow_concurrency_grep.py +283 -0
- package/src/apothem/harnesses/README.md +63 -0
- package/src/apothem/harnesses/__init__.py +16 -0
- package/src/apothem/harnesses/_shared/README.md +36 -0
- package/src/apothem/harnesses/_shared/__init__.py +12 -0
- package/src/apothem/harnesses/_shared/install_driver.py +281 -0
- package/src/apothem/harnesses/_shared/install_driver_apply.py +612 -0
- package/src/apothem/harnesses/_shared/install_driver_backup.py +535 -0
- package/src/apothem/harnesses/_shared/install_driver_converters.py +310 -0
- package/src/apothem/harnesses/_shared/install_driver_lifecycle.py +495 -0
- package/src/apothem/harnesses/_shared/install_driver_materialize.py +675 -0
- package/src/apothem/harnesses/_shared/install_driver_merge.py +656 -0
- package/src/apothem/harnesses/_shared/install_driver_pathsafety.py +137 -0
- package/src/apothem/harnesses/_shared/install_driver_planvalidation.py +240 -0
- package/src/apothem/harnesses/_shared/install_driver_removal.py +366 -0
- package/src/apothem/harnesses/_shared/install_driver_treeops.py +248 -0
- package/src/apothem/harnesses/_shared/install_driver_types.py +330 -0
- package/src/apothem/harnesses/_shared/wrapper_factories.py +448 -0
- package/src/apothem/harnesses/antigravity/STANDARD-CONVENTION-PIN.md +91 -0
- package/src/apothem/harnesses/antigravity/__init__.py +70 -0
- package/src/apothem/harnesses/antigravity/capabilities.yml +40 -0
- package/src/apothem/harnesses/antigravity/install.py +63 -0
- package/src/apothem/harnesses/antigravity/templates/GEMINI.md +40 -0
- package/src/apothem/harnesses/antigravity/templates/plugin.json +5 -0
- package/src/apothem/harnesses/antigravity/uninstall.py +22 -0
- package/src/apothem/harnesses/antigravity/update.py +10 -0
- package/src/apothem/harnesses/antigravity/verify.py +11 -0
- package/src/apothem/harnesses/claude_code/STANDARD-CONVENTION-PIN.md +65 -0
- package/src/apothem/harnesses/claude_code/__init__.py +107 -0
- package/src/apothem/harnesses/claude_code/capabilities.yml +42 -0
- package/src/apothem/harnesses/claude_code/install.py +147 -0
- package/src/apothem/harnesses/claude_code/templates/settings.json +351 -0
- package/src/apothem/harnesses/claude_code/uninstall.py +23 -0
- package/src/apothem/harnesses/claude_code/update.py +10 -0
- package/src/apothem/harnesses/claude_code/verify.py +11 -0
- package/src/apothem/harnesses/codebuddy/STANDARD-CONVENTION-PIN.md +74 -0
- package/src/apothem/harnesses/codebuddy/__init__.py +49 -0
- package/src/apothem/harnesses/codebuddy/capabilities.yml +34 -0
- package/src/apothem/harnesses/codebuddy/install.py +40 -0
- package/src/apothem/harnesses/codebuddy/templates/apothem-rules.md +37 -0
- package/src/apothem/harnesses/codebuddy/uninstall.py +25 -0
- package/src/apothem/harnesses/codebuddy/update.py +10 -0
- package/src/apothem/harnesses/codebuddy/verify.py +11 -0
- package/src/apothem/harnesses/codex/STANDARD-CONVENTION-PIN.md +79 -0
- package/src/apothem/harnesses/codex/__init__.py +72 -0
- package/src/apothem/harnesses/codex/capabilities.yml +40 -0
- package/src/apothem/harnesses/codex/install.py +69 -0
- package/src/apothem/harnesses/codex/templates/AGENTS.md +40 -0
- package/src/apothem/harnesses/codex/templates/hooks.json +127 -0
- package/src/apothem/harnesses/codex/uninstall.py +23 -0
- package/src/apothem/harnesses/codex/update.py +10 -0
- package/src/apothem/harnesses/codex/verify.py +11 -0
- package/src/apothem/harnesses/cursor/STANDARD-CONVENTION-PIN.md +79 -0
- package/src/apothem/harnesses/cursor/__init__.py +48 -0
- package/src/apothem/harnesses/cursor/capabilities.yml +42 -0
- package/src/apothem/harnesses/cursor/install.py +38 -0
- package/src/apothem/harnesses/cursor/templates/apothem-rules.mdc +40 -0
- package/src/apothem/harnesses/cursor/uninstall.py +25 -0
- package/src/apothem/harnesses/cursor/update.py +10 -0
- package/src/apothem/harnesses/cursor/verify.py +11 -0
- package/src/apothem/harnesses/gemini_cli/STANDARD-CONVENTION-PIN.md +102 -0
- package/src/apothem/harnesses/gemini_cli/__init__.py +52 -0
- package/src/apothem/harnesses/gemini_cli/capabilities.yml +43 -0
- package/src/apothem/harnesses/gemini_cli/install.py +43 -0
- package/src/apothem/harnesses/gemini_cli/templates/GEMINI.md +38 -0
- package/src/apothem/harnesses/gemini_cli/uninstall.py +25 -0
- package/src/apothem/harnesses/gemini_cli/update.py +10 -0
- package/src/apothem/harnesses/gemini_cli/verify.py +11 -0
- package/src/apothem/harnesses/github_copilot/STANDARD-CONVENTION-PIN.md +84 -0
- package/src/apothem/harnesses/github_copilot/__init__.py +47 -0
- package/src/apothem/harnesses/github_copilot/capabilities.yml +42 -0
- package/src/apothem/harnesses/github_copilot/install.py +40 -0
- package/src/apothem/harnesses/github_copilot/templates/copilot-instructions.md +33 -0
- package/src/apothem/harnesses/github_copilot/uninstall.py +25 -0
- package/src/apothem/harnesses/github_copilot/update.py +10 -0
- package/src/apothem/harnesses/github_copilot/verify.py +11 -0
- package/src/apothem/harnesses/glm/STANDARD-CONVENTION-PIN.md +77 -0
- package/src/apothem/harnesses/glm/__init__.py +56 -0
- package/src/apothem/harnesses/glm/capabilities.yml +33 -0
- package/src/apothem/harnesses/glm/install.py +45 -0
- package/src/apothem/harnesses/glm/templates/glm.toml +58 -0
- package/src/apothem/harnesses/glm/uninstall.py +25 -0
- package/src/apothem/harnesses/glm/update.py +10 -0
- package/src/apothem/harnesses/glm/verify.py +11 -0
- package/src/apothem/harnesses/hermes/STANDARD-CONVENTION-PIN.md +57 -0
- package/src/apothem/harnesses/hermes/__init__.py +33 -0
- package/src/apothem/harnesses/hermes/capabilities.yml +36 -0
- package/src/apothem/harnesses/hermes/install.py +17 -0
- package/src/apothem/harnesses/hermes/materializer.py +35 -0
- package/src/apothem/harnesses/hermes/uninstall.py +33 -0
- package/src/apothem/harnesses/hermes/update.py +10 -0
- package/src/apothem/harnesses/hermes/verify.py +11 -0
- package/src/apothem/harnesses/kimi_code/STANDARD-CONVENTION-PIN.md +128 -0
- package/src/apothem/harnesses/kimi_code/__init__.py +59 -0
- package/src/apothem/harnesses/kimi_code/capabilities.yml +40 -0
- package/src/apothem/harnesses/kimi_code/install.py +42 -0
- package/src/apothem/harnesses/kimi_code/templates/AGENTS.md +43 -0
- package/src/apothem/harnesses/kimi_code/uninstall.py +27 -0
- package/src/apothem/harnesses/kimi_code/update.py +10 -0
- package/src/apothem/harnesses/kimi_code/verify.py +11 -0
- package/src/apothem/harnesses/kiro/STANDARD-CONVENTION-PIN.md +77 -0
- package/src/apothem/harnesses/kiro/__init__.py +49 -0
- package/src/apothem/harnesses/kiro/capabilities.yml +36 -0
- package/src/apothem/harnesses/kiro/install.py +39 -0
- package/src/apothem/harnesses/kiro/templates/apothem-rules.md +36 -0
- package/src/apothem/harnesses/kiro/uninstall.py +25 -0
- package/src/apothem/harnesses/kiro/update.py +10 -0
- package/src/apothem/harnesses/kiro/verify.py +11 -0
- package/src/apothem/harnesses/open_claw/STANDARD-CONVENTION-PIN.md +62 -0
- package/src/apothem/harnesses/open_claw/__init__.py +35 -0
- package/src/apothem/harnesses/open_claw/capabilities.yml +35 -0
- package/src/apothem/harnesses/open_claw/install.py +17 -0
- package/src/apothem/harnesses/open_claw/materializer.py +36 -0
- package/src/apothem/harnesses/open_claw/uninstall.py +32 -0
- package/src/apothem/harnesses/open_claw/update.py +10 -0
- package/src/apothem/harnesses/open_claw/verify.py +11 -0
- package/src/apothem/harnesses/opencode/STANDARD-CONVENTION-PIN.md +76 -0
- package/src/apothem/harnesses/opencode/__init__.py +35 -0
- package/src/apothem/harnesses/opencode/capabilities.yml +43 -0
- package/src/apothem/harnesses/opencode/install.py +17 -0
- package/src/apothem/harnesses/opencode/materializer.py +31 -0
- package/src/apothem/harnesses/opencode/uninstall.py +34 -0
- package/src/apothem/harnesses/opencode/update.py +10 -0
- package/src/apothem/harnesses/opencode/verify.py +11 -0
- package/src/apothem/harnesses/qwen_code/STANDARD-CONVENTION-PIN.md +87 -0
- package/src/apothem/harnesses/qwen_code/__init__.py +37 -0
- package/src/apothem/harnesses/qwen_code/capabilities.yml +43 -0
- package/src/apothem/harnesses/qwen_code/install.py +19 -0
- package/src/apothem/harnesses/qwen_code/materializer.py +174 -0
- package/src/apothem/harnesses/qwen_code/templates/QWEN.md +30 -0
- package/src/apothem/harnesses/qwen_code/uninstall.py +34 -0
- package/src/apothem/harnesses/qwen_code/update.py +10 -0
- package/src/apothem/harnesses/qwen_code/verify.py +11 -0
- package/src/apothem/harnesses/trae/STANDARD-CONVENTION-PIN.md +70 -0
- package/src/apothem/harnesses/trae/__init__.py +49 -0
- package/src/apothem/harnesses/trae/capabilities.yml +34 -0
- package/src/apothem/harnesses/trae/install.py +38 -0
- package/src/apothem/harnesses/trae/templates/apothem-rules.md +37 -0
- package/src/apothem/harnesses/trae/uninstall.py +25 -0
- package/src/apothem/harnesses/trae/update.py +10 -0
- package/src/apothem/harnesses/trae/verify.py +11 -0
- package/src/apothem/harnesses/windsurf/STANDARD-CONVENTION-PIN.md +91 -0
- package/src/apothem/harnesses/windsurf/__init__.py +52 -0
- package/src/apothem/harnesses/windsurf/capabilities.yml +40 -0
- package/src/apothem/harnesses/windsurf/install.py +41 -0
- package/src/apothem/harnesses/windsurf/templates/apothem-rules.md +37 -0
- package/src/apothem/harnesses/windsurf/uninstall.py +25 -0
- package/src/apothem/harnesses/windsurf/update.py +10 -0
- package/src/apothem/harnesses/windsurf/verify.py +11 -0
- package/src/apothem/harnesses/zed/STANDARD-CONVENTION-PIN.md +92 -0
- package/src/apothem/harnesses/zed/__init__.py +57 -0
- package/src/apothem/harnesses/zed/capabilities.yml +38 -0
- package/src/apothem/harnesses/zed/install.py +41 -0
- package/src/apothem/harnesses/zed/templates/apothem-rules.md +32 -0
- package/src/apothem/harnesses/zed/uninstall.py +28 -0
- package/src/apothem/harnesses/zed/update.py +10 -0
- package/src/apothem/harnesses/zed/verify.py +11 -0
- package/src/apothem/hooks/README.md +81 -0
- package/src/apothem/hooks/__init__.py +24 -0
- package/src/apothem/hooks/askuserquestion_validator.py +380 -0
- package/src/apothem/hooks/dispatch.py +296 -0
- package/src/apothem/hooks/emit_hook_context.py +444 -0
- package/src/apothem/hooks/hooks.json +318 -0
- package/src/apothem/hooks/lib/README.md +39 -0
- package/src/apothem/hooks/lib/__init__.py +18 -0
- package/src/apothem/hooks/lib/bootstrap.ps1 +129 -0
- package/src/apothem/hooks/lib/bootstrap.sh +103 -0
- package/src/apothem/hooks/lib/events.py +51 -0
- package/src/apothem/hooks/lib/find-pwsh.ps1 +78 -0
- package/src/apothem/hooks/lib/find-pwsh.sh +76 -0
- package/src/apothem/hooks/lib/find-python.ps1 +63 -0
- package/src/apothem/hooks/lib/find-python.sh +97 -0
- package/src/apothem/hooks/lib/log.py +43 -0
- package/src/apothem/hooks/lib/resolve_root.py +264 -0
- package/src/apothem/hooks/messages/postcompact.md +14 -0
- package/src/apothem/hooks/messages/posttooluse-proactive-compaction.md +46 -0
- package/src/apothem/hooks/messages/precompact.md +14 -0
- package/src/apothem/hooks/messages/pretooluse-askuserquestion-recommended.md +65 -0
- package/src/apothem/hooks/messages/pretooluse-bash-plan-guard.md +97 -0
- package/src/apothem/hooks/messages/pretooluse-bash.md +39 -0
- package/src/apothem/hooks/messages/pretooluse-conformity.md +70 -0
- package/src/apothem/hooks/messages/pretooluse-dependency-guard.md +21 -0
- package/src/apothem/hooks/messages/pretooluse-edit-header-guard.md +61 -0
- package/src/apothem/hooks/messages/pretooluse-edit.md +21 -0
- package/src/apothem/hooks/messages/pretooluse-eval-guard.md +39 -0
- package/src/apothem/hooks/messages/pretooluse-notebookedit.md +11 -0
- package/src/apothem/hooks/messages/pretooluse-write-header-guard.md +45 -0
- package/src/apothem/hooks/messages/pretooluse-write-plan-guard.md +72 -0
- package/src/apothem/hooks/messages/pretooluse-write.md +21 -0
- package/src/apothem/hooks/messages/sessionstart.md +15 -0
- package/src/apothem/hooks/messages/stop.md +27 -0
- package/src/apothem/hooks/proactive_compaction_tracker.py +327 -0
- package/src/apothem/hooks/session_start_bootstrap.py +472 -0
- package/src/apothem/lib/README.md +42 -0
- package/src/apothem/lib/__init__.py +13 -0
- package/src/apothem/lib/atomic_io.py +189 -0
- package/src/apothem/lib/auditor.py +687 -0
- package/src/apothem/lib/clean_slate.py +396 -0
- package/src/apothem/lib/contexts.py +352 -0
- package/src/apothem/lib/data_home.py +255 -0
- package/src/apothem/lib/frontmatter.py +101 -0
- package/src/apothem/lib/harness_materializer.py +213 -0
- package/src/apothem/lib/harness_protocol.py +59 -0
- package/src/apothem/lib/harness_registry.py +282 -0
- package/src/apothem/lib/harness_registry_data.py +843 -0
- package/src/apothem/lib/install_ledger.py +347 -0
- package/src/apothem/lib/learning.py +540 -0
- package/src/apothem/lib/memory.py +347 -0
- package/src/apothem/lib/parallel_sweep.py +234 -0
- package/src/apothem/lib/plan_tiers.py +200 -0
- package/src/apothem/lib/plugin_bootstrap.py +132 -0
- package/src/apothem/lib/plugin_tree.py +599 -0
- package/src/apothem/lib/profile.py +755 -0
- package/src/apothem/lib/profile_projection.py +198 -0
- package/src/apothem/lib/propagation-manifest.yaml +878 -0
- package/src/apothem/lib/propagation.py +220 -0
- package/src/apothem/lib/python_resolver.py +189 -0
- package/src/apothem/lib/reporter.py +62 -0
- package/src/apothem/lib/workspace_migration.py +323 -0
- package/src/apothem/output-styles/README.md +41 -0
- package/src/apothem/output-styles/concise-engineer.md +49 -0
- package/src/apothem/output-styles/default-architect.md +52 -0
- package/src/apothem/output-styles/default.md +113 -0
- package/src/apothem/output-styles/forensic-auditor.md +63 -0
- package/src/apothem/py.typed +0 -0
- package/src/apothem/rules/README.md +121 -0
- package/src/apothem/rules/agent-capability-discipline-matrix.md +89 -0
- package/src/apothem/rules/agent-capability-discipline.md +78 -0
- package/src/apothem/rules/agent-orchestration-patterns.md +144 -0
- package/src/apothem/rules/agent-orchestration.md +65 -0
- package/src/apothem/rules/agents-md-convention.md +86 -0
- package/src/apothem/rules/agile-sprints-elements.md +135 -0
- package/src/apothem/rules/agile-sprints.md +64 -0
- package/src/apothem/rules/agnostic-posture-checklist.md +47 -0
- package/src/apothem/rules/agnostic-posture.md +48 -0
- package/src/apothem/rules/authoritative-referencing-quotation.md +50 -0
- package/src/apothem/rules/authoritative-referencing.md +66 -0
- package/src/apothem/rules/authority-inquiry-categories.md +58 -0
- package/src/apothem/rules/authority-inquiry.md +54 -0
- package/src/apothem/rules/auto-memory-topic-files.md +86 -0
- package/src/apothem/rules/auto-memory.md +67 -0
- package/src/apothem/rules/bidirectional-binding.md +123 -0
- package/src/apothem/rules/canonical-layout-reporting-tiers.md +212 -0
- package/src/apothem/rules/canonical-layout.md +60 -0
- package/src/apothem/rules/clean-architecture-layers.md +186 -0
- package/src/apothem/rules/clean-room-generation-protocols.md +124 -0
- package/src/apothem/rules/clean-room-generation.md +59 -0
- package/src/apothem/rules/code-craft-conventions.md +101 -0
- package/src/apothem/rules/code-craft-markdown.md +138 -0
- package/src/apothem/rules/code-craft-python.md +154 -0
- package/src/apothem/rules/code-craft-shell.md +192 -0
- package/src/apothem/rules/cognitive-identity-techniques.md +180 -0
- package/src/apothem/rules/cognitive-identity.md +81 -0
- package/src/apothem/rules/context-management-budget.md +46 -0
- package/src/apothem/rules/context-management-protocol.md +161 -0
- package/src/apothem/rules/context-management-scratch.md +128 -0
- package/src/apothem/rules/context-management.md +85 -0
- package/src/apothem/rules/definitiveness-virtues.md +67 -0
- package/src/apothem/rules/definitiveness.md +58 -0
- package/src/apothem/rules/determinism.md +81 -0
- package/src/apothem/rules/disclosure-ledger-markers.md +58 -0
- package/src/apothem/rules/disclosure-ledger.md +52 -0
- package/src/apothem/rules/dynamism.md +38 -0
- package/src/apothem/rules/etc-extension.md +57 -0
- package/src/apothem/rules/expertise-posture-elements.md +68 -0
- package/src/apothem/rules/expertise-posture.md +54 -0
- package/src/apothem/rules/freshness-facade.md +64 -0
- package/src/apothem/rules/harness-adapter-shape-schemas.md +162 -0
- package/src/apothem/rules/harness-adapter-shape.md +42 -0
- package/src/apothem/rules/host-discovery-manifests.md +50 -0
- package/src/apothem/rules/host-discovery.md +56 -0
- package/src/apothem/rules/i18n-discipline-locale-cohorts.md +120 -0
- package/src/apothem/rules/i18n-discipline.md +70 -0
- package/src/apothem/rules/interactive-questions-canonical-shapes.md +590 -0
- package/src/apothem/rules/interactive-questions-detail.md +41 -0
- package/src/apothem/rules/interactive-questions-sweep-matchers.md +184 -0
- package/src/apothem/rules/interactive-questions.md +89 -0
- package/src/apothem/rules/large-file-generation.md +112 -0
- package/src/apothem/rules/large-file-reading.md +59 -0
- package/src/apothem/rules/living-docs.md +85 -0
- package/src/apothem/rules/multi-agent-workflow.md +57 -0
- package/src/apothem/rules/operational-mandates-expanded.md +78 -0
- package/src/apothem/rules/operational-mandates.md +88 -0
- package/src/apothem/rules/option-annotation-form.md +60 -0
- package/src/apothem/rules/option-annotation.md +45 -0
- package/src/apothem/rules/own-voice-reimplementation.md +86 -0
- package/src/apothem/rules/performance-discipline.md +91 -0
- package/src/apothem/rules/persistent-conventions-vigilance-checklist.md +54 -0
- package/src/apothem/rules/persistent-conventions-vigilance.md +61 -0
- package/src/apothem/rules/plain-language.md +56 -0
- package/src/apothem/rules/planning-techniques.md +130 -0
- package/src/apothem/rules/pre-emission-gate-bars.md +86 -0
- package/src/apothem/rules/pre-emission-gate.md +54 -0
- package/src/apothem/rules/production-ready-prs-surfaces.md +162 -0
- package/src/apothem/rules/production-ready-prs.md +83 -0
- package/src/apothem/rules/propagation.md +63 -0
- package/src/apothem/rules/recommend-next-step.md +106 -0
- package/src/apothem/rules/refactoring-discipline.md +76 -0
- package/src/apothem/rules/session-closure.md +44 -0
- package/src/apothem/rules/sota-elevation-exemplars.md +76 -0
- package/src/apothem/rules/sota-elevation.md +52 -0
- package/src/apothem/rules/source-accessibility.md +58 -0
- package/src/apothem/rules/surgical-manipulation.md +48 -0
- package/src/apothem/rules/systemic-participation-relations.md +108 -0
- package/src/apothem/rules/systemic-participation.md +70 -0
- package/src/apothem/rules/ten-dimension-check-dimensions.md +52 -0
- package/src/apothem/rules/ten-dimension-check.md +59 -0
- package/src/apothem/rules/token-budget-discipline.md +81 -0
- package/src/apothem/rules/token-efficiency-rewrite-protocol.md +79 -0
- package/src/apothem/rules/token-efficiency-rewrite.md +77 -0
- package/src/apothem/rules/tool-use-discipline.md +48 -0
- package/src/apothem/rules/visual-leverage.md +102 -0
- package/src/apothem/schemas/NOTICE.md +9 -0
- package/src/apothem/schemas/README.md +104 -0
- package/src/apothem/schemas/__init__.py +176 -0
- package/src/apothem/schemas/advisory-finding.schema.json +111 -0
- package/src/apothem/schemas/agent.schema.json +106 -0
- package/src/apothem/schemas/authorship-header.txt +1 -0
- package/src/apothem/schemas/cohort-manifest.yaml +248 -0
- package/src/apothem/schemas/cohort-metadata-vocabulary.yaml +168 -0
- package/src/apothem/schemas/cohort.schema.json +113 -0
- package/src/apothem/schemas/command.schema.json +68 -0
- package/src/apothem/schemas/compatibility-matrix.yaml +432 -0
- package/src/apothem/schemas/context-fragment.schema.json +64 -0
- package/src/apothem/schemas/freshness-token-denylist.txt +51 -0
- package/src/apothem/schemas/handoff-manifest.yaml +353 -0
- package/src/apothem/schemas/header-exceptions.txt +141 -0
- package/src/apothem/schemas/header-visibility.yaml +39 -0
- package/src/apothem/schemas/learning-signal.schema.json +46 -0
- package/src/apothem/schemas/memory-record.schema.json +61 -0
- package/src/apothem/schemas/output-style.schema.json +40 -0
- package/src/apothem/schemas/plan.schema.json +51 -0
- package/src/apothem/schemas/plugin.schema.json +83 -0
- package/src/apothem/schemas/profile.example.yaml +70 -0
- package/src/apothem/schemas/profile.minimal.yaml +6 -0
- package/src/apothem/schemas/profile.schema.json +396 -0
- package/src/apothem/schemas/reference-token-denylist.txt +25 -0
- package/src/apothem/schemas/skill.schema.json +75 -0
- package/src/apothem/skills/README.md +93 -0
- package/src/apothem/skills/dependency-upgrade/SKILL.md +105 -0
- package/src/apothem/skills/dev-toolkit/SKILL.md +120 -0
- package/src/apothem/skills/diagram-authoring/SKILL.md +113 -0
- package/src/apothem/skills/document-authoring/SKILL.md +118 -0
- package/src/apothem/skills/ecosystem-audit/SKILL.md +108 -0
- package/src/apothem/skills/ecosystem-audit/references/audit-fortress.md +85 -0
- package/src/apothem/skills/ecosystem-audit/references/procedure.md +162 -0
- package/src/apothem/skills/eval-harness/SKILL.md +88 -0
- package/src/apothem/skills/incident-runbook/SKILL.md +92 -0
- package/src/apothem/skills/multi-source-research/SKILL.md +90 -0
- package/src/apothem/skills/plan-suite/SKILL.md +118 -0
- package/src/apothem/skills/plan-suite/master_template.md +1324 -0
- package/src/apothem/skills/projectify/SKILL.md +117 -0
- package/src/apothem/skills/prompt-engineering/SKILL.md +122 -0
- package/src/apothem/skills/refactor-extract/SKILL.md +85 -0
- package/src/apothem/skills/research-suite/SKILL.md +170 -0
- package/src/apothem/skills/research-suite/references/directory-structure.md +47 -0
- package/src/apothem/skills/research-suite/references/lifecycle.md +67 -0
- package/src/apothem/skills/research-suite/references/principal-investigator-framework.md +37 -0
- package/src/apothem/skills/research-suite/references/rigor-mandates.md +30 -0
- package/src/apothem/skills/research-suite/research_template.md +476 -0
- package/src/apothem/skills/secret-rotation/SKILL.md +87 -0
- package/src/apothem/skills/source-synthesis/SKILL.md +92 -0
- package/src/apothem/skills/surgical-guard/SKILL.md +118 -0
- package/src/apothem/skills/test-authoring/SKILL.md +85 -0
- package/src/apothem/skills/vuln-triage/SKILL.md +91 -0
- package/src/apothem/skills/workflow/SKILL.md +139 -0
- package/src/apothem/statuslines/README.md +26 -0
- package/src/apothem/statuslines/__init__.py +20 -0
- package/src/apothem/statuslines/conformity.json +5 -0
- package/src/apothem/statuslines/render.py +334 -0
- package/src/apothem/statuslines/statusline.md +50 -0
- package/src/apothem/templates/README.md +43 -0
- package/src/apothem/templates/agents-md-template.md +80 -0
- package/src/apothem/templates/consideration-log.md +39 -0
- package/src/apothem/templates/expertise-gap-log.md +56 -0
- package/src/apothem/templates/master-index-template.md +93 -0
- package/src/apothem/templates/potency-map.md +53 -0
- package/src/apothem/templates/preservation-audit.md +60 -0
- package/src/apothem/templates/question-resolution-audit.md +52 -0
- package/src/apothem/templates/trace-matrix-template.md +77 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
# Purpose: Locate a real PowerShell >= 7 on PATH for any PowerShell-side
|
|
4
|
+
# surface that needs to spawn a pwsh subprocess, rejecting Microsoft Store
|
|
5
|
+
# launcher shims (zero-byte stubs at AppData\Local\Microsoft\WindowsApps
|
|
6
|
+
# that fail at exec time with "Executable not found").
|
|
7
|
+
# Contract: dot-source then call Find-RealPwsh; returns the absolute
|
|
8
|
+
# path to a working pwsh interpreter, or $null when none found. Does
|
|
9
|
+
# not throw on probe failures — silently skips bad candidates so the
|
|
10
|
+
# caller's diagnostic envelope path runs.
|
|
11
|
+
# Sibling files in hooks/lib/: find-pwsh.sh (POSIX counterpart),
|
|
12
|
+
# find-python.ps1 (PowerShell counterpart for Python — paired-template
|
|
13
|
+
# peer), find-python.sh (POSIX counterpart of the Python locator).
|
|
14
|
+
|
|
15
|
+
Set-StrictMode -Version Latest
|
|
16
|
+
$ErrorActionPreference = 'Stop'
|
|
17
|
+
|
|
18
|
+
function Find-RealPwsh {
|
|
19
|
+
[CmdletBinding()]
|
|
20
|
+
[OutputType([string])]
|
|
21
|
+
param(
|
|
22
|
+
[int]$MinMajor = 7
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
$candidates = @('pwsh', 'pwsh-preview')
|
|
26
|
+
$fallbackPaths = @(
|
|
27
|
+
(Join-Path $env:ProgramFiles 'PowerShell\7\pwsh.exe'),
|
|
28
|
+
(Join-Path $env:ProgramFiles 'PowerShell\7-preview\pwsh.exe'),
|
|
29
|
+
(Join-Path ${env:ProgramFiles(x86)} 'PowerShell\7\pwsh.exe')
|
|
30
|
+
)
|
|
31
|
+
$probe = '$PSVersionTable.PSVersion.Major'
|
|
32
|
+
|
|
33
|
+
# Phase 1 — PATH probe via Get-Command, rejecting WindowsApps stubs.
|
|
34
|
+
foreach ($name in $candidates) {
|
|
35
|
+
$cmds = @(Get-Command $name -All -ErrorAction SilentlyContinue)
|
|
36
|
+
foreach ($cmd in $cmds) {
|
|
37
|
+
$src = $cmd.Source
|
|
38
|
+
if (-not $src) { continue }
|
|
39
|
+
if ($src -match 'WindowsApps') { continue }
|
|
40
|
+
$info = Get-Item -LiteralPath $src -ErrorAction SilentlyContinue
|
|
41
|
+
if (-not $info) { continue }
|
|
42
|
+
if ($info.Length -lt 1024) { continue }
|
|
43
|
+
|
|
44
|
+
$output = $null
|
|
45
|
+
try { $output = & $src -NoProfile -Command $probe 2>$null } catch { continue }
|
|
46
|
+
if ($LASTEXITCODE -ne 0) { continue }
|
|
47
|
+
if (-not $output) { continue }
|
|
48
|
+
$trimmed = ($output -join '').Trim()
|
|
49
|
+
$maj = 0
|
|
50
|
+
if (-not [int]::TryParse($trimmed, [ref]$maj)) { continue }
|
|
51
|
+
if ($maj -ge $MinMajor) {
|
|
52
|
+
return $src
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Phase 2 — canonical Windows install paths.
|
|
58
|
+
foreach ($candidate in $fallbackPaths) {
|
|
59
|
+
if (-not $candidate) { continue }
|
|
60
|
+
if (-not (Test-Path -LiteralPath $candidate)) { continue }
|
|
61
|
+
$info = Get-Item -LiteralPath $candidate -ErrorAction SilentlyContinue
|
|
62
|
+
if (-not $info) { continue }
|
|
63
|
+
if ($info.Length -lt 1024) { continue }
|
|
64
|
+
|
|
65
|
+
$output = $null
|
|
66
|
+
try { $output = & $candidate -NoProfile -Command $probe 2>$null } catch { continue }
|
|
67
|
+
if ($LASTEXITCODE -ne 0) { continue }
|
|
68
|
+
if (-not $output) { continue }
|
|
69
|
+
$trimmed = ($output -join '').Trim()
|
|
70
|
+
$maj = 0
|
|
71
|
+
if (-not [int]::TryParse($trimmed, [ref]$maj)) { continue }
|
|
72
|
+
if ($maj -ge $MinMajor) {
|
|
73
|
+
return $candidate
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return $null
|
|
78
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
# Purpose: Locate a real PowerShell >= 7 on PATH for any bash-side surface
|
|
5
|
+
# that needs to spawn a pwsh subprocess, rejecting Microsoft Store launcher
|
|
6
|
+
# shims (zero-byte stubs at AppData/Local/Microsoft/WindowsApps under
|
|
7
|
+
# WSL/Git-Bash mounts that fail at exec time with "Executable not found").
|
|
8
|
+
# Contract: dot-source then call find_real_pwsh; prints the absolute path
|
|
9
|
+
# on success and returns 0; returns 1 when no candidate satisfies the
|
|
10
|
+
# version floor. Does not throw on probe failures — silently skips bad
|
|
11
|
+
# candidates so the caller's diagnostic envelope path runs.
|
|
12
|
+
# Sibling files in hooks/lib/: find-pwsh.ps1 (PowerShell counterpart),
|
|
13
|
+
# find-python.sh (POSIX counterpart for Python — paired-template peer),
|
|
14
|
+
# find-python.ps1 (PowerShell counterpart of the Python locator).
|
|
15
|
+
#
|
|
16
|
+
# Usage (sourced): . lib/find-pwsh.sh && p="$(find_real_pwsh)" || exit 1
|
|
17
|
+
# Exit codes: prints absolute path on success; exits 1 if none found.
|
|
18
|
+
|
|
19
|
+
find_real_pwsh() {
|
|
20
|
+
local min_major="${1:-7}"
|
|
21
|
+
local candidates=(pwsh pwsh-preview)
|
|
22
|
+
local fallback_paths=(
|
|
23
|
+
/usr/local/bin/pwsh
|
|
24
|
+
/usr/bin/pwsh
|
|
25
|
+
/opt/microsoft/powershell/7/pwsh
|
|
26
|
+
/opt/microsoft/powershell/7-preview/pwsh
|
|
27
|
+
)
|
|
28
|
+
local name path size major
|
|
29
|
+
|
|
30
|
+
# Phase 1 — PATH probe via type -ap, rejecting WindowsApps stubs.
|
|
31
|
+
for name in "${candidates[@]}"; do
|
|
32
|
+
while IFS= read -r path; do
|
|
33
|
+
[[ -z "$path" ]] && continue
|
|
34
|
+
case "$path" in *WindowsApps*) continue ;; esac
|
|
35
|
+
if [[ ! -x "$path" ]]; then continue; fi
|
|
36
|
+
size=$(wc -c < "$path" 2>/dev/null || echo 0)
|
|
37
|
+
[[ "$size" -lt 1024 ]] && continue
|
|
38
|
+
local probe_output
|
|
39
|
+
# SC2016: the single-quoted expression is intentionally NOT
|
|
40
|
+
# bash-expanded — it's a PowerShell expression evaluated by pwsh.
|
|
41
|
+
# shellcheck disable=SC2016
|
|
42
|
+
probe_output=$("$path" -NoProfile -Command '$PSVersionTable.PSVersion.Major' 2>/dev/null) || continue
|
|
43
|
+
probe_output="${probe_output//$'\r'/}"
|
|
44
|
+
probe_output="${probe_output//[[:space:]]/}"
|
|
45
|
+
major="$probe_output"
|
|
46
|
+
[[ -z "$major" ]] && continue
|
|
47
|
+
[[ ! "$major" =~ ^[0-9]+$ ]] && continue
|
|
48
|
+
if (( major >= min_major )); then
|
|
49
|
+
printf '%s\n' "$path"
|
|
50
|
+
return 0
|
|
51
|
+
fi
|
|
52
|
+
done < <(type -ap "$name" 2>/dev/null)
|
|
53
|
+
done
|
|
54
|
+
|
|
55
|
+
# Phase 2 — canonical install paths (Linux / macOS).
|
|
56
|
+
for path in "${fallback_paths[@]}"; do
|
|
57
|
+
[[ -x "$path" ]] || continue
|
|
58
|
+
size=$(wc -c < "$path" 2>/dev/null || echo 0)
|
|
59
|
+
[[ "$size" -lt 1024 ]] && continue
|
|
60
|
+
local probe_output
|
|
61
|
+
# SC2016: same intentional PowerShell expression as Phase 1.
|
|
62
|
+
# shellcheck disable=SC2016
|
|
63
|
+
probe_output=$("$path" -NoProfile -Command '$PSVersionTable.PSVersion.Major' 2>/dev/null) || continue
|
|
64
|
+
probe_output="${probe_output//$'\r'/}"
|
|
65
|
+
probe_output="${probe_output//[[:space:]]/}"
|
|
66
|
+
major="$probe_output"
|
|
67
|
+
[[ -z "$major" ]] && continue
|
|
68
|
+
[[ ! "$major" =~ ^[0-9]+$ ]] && continue
|
|
69
|
+
if (( major >= min_major )); then
|
|
70
|
+
printf '%s\n' "$path"
|
|
71
|
+
return 0
|
|
72
|
+
fi
|
|
73
|
+
done
|
|
74
|
+
|
|
75
|
+
return 1
|
|
76
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
# Purpose: Locate a real CPython >= 3.10 on PATH for the PowerShell
|
|
4
|
+
# bootstrap stub, rejecting Microsoft Store launcher shims (zero-byte
|
|
5
|
+
# stubs at AppData\Local\Microsoft\WindowsApps).
|
|
6
|
+
# Contract: dot-source then call Find-RealPython; returns the absolute
|
|
7
|
+
# path to a working interpreter, or $null when none found. Does not
|
|
8
|
+
# throw on probe failures — silently skips bad candidates.
|
|
9
|
+
# Sibling files in hooks/lib/: find-python.sh (POSIX counterpart),
|
|
10
|
+
# bootstrap.ps1 (the script that dot-sources this locator),
|
|
11
|
+
# bootstrap.sh (POSIX counterpart of the bootstrap stub).
|
|
12
|
+
|
|
13
|
+
Set-StrictMode -Version Latest
|
|
14
|
+
$ErrorActionPreference = 'Stop'
|
|
15
|
+
|
|
16
|
+
function Find-RealPython {
|
|
17
|
+
[CmdletBinding()]
|
|
18
|
+
[OutputType([string])]
|
|
19
|
+
param(
|
|
20
|
+
[int]$MinMajor = 3,
|
|
21
|
+
[int]$MinMinor = 10
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
$candidates = @('python', 'python3', 'python3.14', 'python3.13', 'python3.12', 'python3.11', 'python3.10')
|
|
25
|
+
$probe = 'import sys; sys.stdout.write(str(sys.version_info.major) + chr(32) + str(sys.version_info.minor))'
|
|
26
|
+
|
|
27
|
+
foreach ($name in $candidates) {
|
|
28
|
+
# PATH resolution: Get-Command searches only the non-empty directories
|
|
29
|
+
# in $env:PATH and never the current directory implicitly, so an empty
|
|
30
|
+
# PATH segment (a doubled ';;' or a leading/trailing separator) yields
|
|
31
|
+
# no candidate here. The cwd-from-empty-segment footgun closed in the
|
|
32
|
+
# sibling find-python.sh / python_resolver.py PATH walks does not arise
|
|
33
|
+
# in this stub. Do NOT replace Get-Command with a manual PATH walk that
|
|
34
|
+
# maps empty segments to cwd: an explicit '.' entry on PATH is honored,
|
|
35
|
+
# an empty one is not. (Verified against Windows PowerShell.)
|
|
36
|
+
$cmds = @(Get-Command $name -All -ErrorAction SilentlyContinue)
|
|
37
|
+
foreach ($cmd in $cmds) {
|
|
38
|
+
$src = $cmd.Source
|
|
39
|
+
if (-not $src) { continue }
|
|
40
|
+
# Literal substring match (parity with the POSIX `${v#*WindowsApps}`
|
|
41
|
+
# prefix-strip in find-python.sh); -like avoids regex semantics.
|
|
42
|
+
if ($src -like '*WindowsApps*') { continue }
|
|
43
|
+
$info = Get-Item -LiteralPath $src -ErrorAction SilentlyContinue
|
|
44
|
+
if (-not $info) { continue }
|
|
45
|
+
if ($info.Length -lt 1024) { continue }
|
|
46
|
+
|
|
47
|
+
$output = $null
|
|
48
|
+
try { $output = & $src -c $probe 2>$null } catch { continue }
|
|
49
|
+
if ($LASTEXITCODE -ne 0) { continue }
|
|
50
|
+
if (-not $output) { continue }
|
|
51
|
+
$trimmed = ($output -join '').Trim()
|
|
52
|
+
$parts = $trimmed.Split(' ')
|
|
53
|
+
if ($parts.Count -lt 2) { continue }
|
|
54
|
+
$maj = 0; $min = 0
|
|
55
|
+
if (-not [int]::TryParse($parts[0], [ref]$maj)) { continue }
|
|
56
|
+
if (-not [int]::TryParse($parts[1], [ref]$min)) { continue }
|
|
57
|
+
if ($maj -gt $MinMajor -or ($maj -eq $MinMajor -and $min -ge $MinMinor)) {
|
|
58
|
+
return $src
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return $null
|
|
63
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
# Purpose: Locate a real CPython >= 3.10 on PATH for the POSIX bootstrap
|
|
5
|
+
# stub, rejecting Microsoft Store launcher shims (zero-byte stubs at
|
|
6
|
+
# AppData/Local/Microsoft/WindowsApps under WSL/Git-Bash mounts).
|
|
7
|
+
# Contract: dot-source then call find_real_python; prints the absolute
|
|
8
|
+
# path on success and returns 0; returns 1 when no candidate satisfies
|
|
9
|
+
# the version floor. Does not throw on probe failures — silently skips
|
|
10
|
+
# bad candidates so the dispatcher's diagnostic envelope path runs.
|
|
11
|
+
# POSIX sh only (no bashisms): runs under dash/ash as well as bash, so a
|
|
12
|
+
# `curl … | sh` installer that dot-sources this locator stays portable.
|
|
13
|
+
# Sibling files in hooks/lib/: find-python.ps1 (PowerShell counterpart),
|
|
14
|
+
# bootstrap.sh (the script that dot-sources this locator), bootstrap.ps1
|
|
15
|
+
# (PowerShell counterpart of the bootstrap stub).
|
|
16
|
+
#
|
|
17
|
+
# Usage (sourced): . lib/find-python.sh && py="$(find_real_python)" || exit 1
|
|
18
|
+
# Exit codes: prints absolute path on success; exits 1 if none found.
|
|
19
|
+
|
|
20
|
+
# Walk every PATH entry and print each executable file matching $1, in PATH
|
|
21
|
+
# order — the POSIX stand-in for bash's `type -ap`, which lists all matches
|
|
22
|
+
# rather than only the first (so a WindowsApps shim earlier on PATH does not
|
|
23
|
+
# mask a real interpreter found later).
|
|
24
|
+
_find_python_path_candidates() {
|
|
25
|
+
_fpc_name="$1"
|
|
26
|
+
_fpc_saved_ifs="$IFS"
|
|
27
|
+
IFS=:
|
|
28
|
+
for _fpc_dir in $PATH; do
|
|
29
|
+
IFS="$_fpc_saved_ifs"
|
|
30
|
+
# Skip an empty PATH segment (a doubled "::" or a leading/trailing
|
|
31
|
+
# delimiter) rather than resolving it to the current directory. POSIX
|
|
32
|
+
# maps an empty segment to cwd, but this locator's result is
|
|
33
|
+
# version-probed (executed) and wired into the hook command that runs
|
|
34
|
+
# on every tool use; discovering an attacker-planted cwd "python" there
|
|
35
|
+
# is the footgun this guard closes. A user who genuinely wants cwd
|
|
36
|
+
# searched adds an explicit "." entry — non-empty, still honored below.
|
|
37
|
+
if [ -z "$_fpc_dir" ]; then
|
|
38
|
+
IFS=:
|
|
39
|
+
continue
|
|
40
|
+
fi
|
|
41
|
+
_fpc_full="${_fpc_dir}/${_fpc_name}"
|
|
42
|
+
if [ -f "$_fpc_full" ] && [ -x "$_fpc_full" ]; then
|
|
43
|
+
printf '%s\n' "$_fpc_full"
|
|
44
|
+
fi
|
|
45
|
+
IFS=:
|
|
46
|
+
done
|
|
47
|
+
IFS="$_fpc_saved_ifs"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
find_real_python() {
|
|
51
|
+
_frp_min_major="${1:-3}"
|
|
52
|
+
_frp_min_minor="${2:-10}"
|
|
53
|
+
_frp_candidates="python python3 python3.14 python3.13 python3.12 python3.11 python3.10"
|
|
54
|
+
|
|
55
|
+
for _frp_name in $_frp_candidates; do
|
|
56
|
+
# Probe every match for this name once. The inner loop runs in a
|
|
57
|
+
# command-substitution subshell, so `break` (not `return`) emits the
|
|
58
|
+
# first qualifying interpreter; the parent reads it back.
|
|
59
|
+
_frp_winner=$(
|
|
60
|
+
_find_python_path_candidates "$_frp_name" | while IFS= read -r _frp_path; do
|
|
61
|
+
[ -z "$_frp_path" ] && continue
|
|
62
|
+
# Skip Microsoft Store launcher shims. A POSIX prefix-strip
|
|
63
|
+
# test, NOT `case`: this loop body runs inside `_frp_winner=$( … )`,
|
|
64
|
+
# and macOS `/bin/sh` (bash 3.2) cannot parse a `case … ;; esac`
|
|
65
|
+
# inside a command substitution. `dash -n` accepts it, which is
|
|
66
|
+
# why the POSIX-parse gate missed it; `sh install.sh` on macOS
|
|
67
|
+
# then fails at parse time. `${v#*WindowsApps}` differs from `$v`
|
|
68
|
+
# exactly when `$v` contains "WindowsApps".
|
|
69
|
+
if [ "${_frp_path#*WindowsApps}" != "$_frp_path" ]; then
|
|
70
|
+
continue
|
|
71
|
+
fi
|
|
72
|
+
[ -x "$_frp_path" ] || continue
|
|
73
|
+
_frp_size=$(wc -c < "$_frp_path" 2>/dev/null || echo 0)
|
|
74
|
+
[ "$_frp_size" -lt 1024 ] && continue
|
|
75
|
+
_frp_probe=$("$_frp_path" -c 'import sys; sys.stdout.write(str(sys.version_info.major) + " " + str(sys.version_info.minor))' 2>/dev/null) || continue
|
|
76
|
+
_frp_probe=$(printf '%s' "$_frp_probe" | tr -d '\r')
|
|
77
|
+
_frp_major=$(printf '%s\n' "$_frp_probe" | cut -d' ' -f1)
|
|
78
|
+
_frp_minor=$(printf '%s\n' "$_frp_probe" | cut -d' ' -f2)
|
|
79
|
+
{ [ -z "$_frp_major" ] || [ -z "$_frp_minor" ]; } && continue
|
|
80
|
+
# Reject empty or non-numeric version components without `case`
|
|
81
|
+
# (same bash-3.2 command-substitution limitation as above): strip
|
|
82
|
+
# every ASCII digit and require a non-empty value with nothing left.
|
|
83
|
+
{ [ -z "$_frp_major" ] || [ -n "$(printf '%s' "$_frp_major" | tr -d '0123456789')" ]; } && continue
|
|
84
|
+
{ [ -z "$_frp_minor" ] || [ -n "$(printf '%s' "$_frp_minor" | tr -d '0123456789')" ]; } && continue
|
|
85
|
+
if [ "$_frp_major" -gt "$_frp_min_major" ] || { [ "$_frp_major" -eq "$_frp_min_major" ] && [ "$_frp_minor" -ge "$_frp_min_minor" ]; }; then
|
|
86
|
+
printf '%s\n' "$_frp_path"
|
|
87
|
+
break
|
|
88
|
+
fi
|
|
89
|
+
done
|
|
90
|
+
)
|
|
91
|
+
if [ -n "$_frp_winner" ]; then
|
|
92
|
+
printf '%s\n' "$_frp_winner"
|
|
93
|
+
return 0
|
|
94
|
+
fi
|
|
95
|
+
done
|
|
96
|
+
return 1
|
|
97
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Shared logger factory for the apothem hook scripts.
|
|
4
|
+
|
|
5
|
+
Purpose: a single source of truth for how hook modules acquire a
|
|
6
|
+
``logging.Logger``. Both ``hooks/emit_hook_context.py`` and
|
|
7
|
+
``hooks/session_start_bootstrap.py`` previously instantiated their
|
|
8
|
+
loggers via ``logging.getLogger(<hard-coded-name>)``, which drifted
|
|
9
|
+
over time and made downstream handler configuration inconsistent.
|
|
10
|
+
|
|
11
|
+
Contract: ``get_logger(name)`` returns a ``logging.Logger`` whose
|
|
12
|
+
name is the caller's ``__name__``. The hook runtime configures
|
|
13
|
+
handlers itself; this factory does not attach handlers, set levels,
|
|
14
|
+
or alter the root logger — it only provides a uniform acquisition
|
|
15
|
+
point so future structured-logging changes land in one file.
|
|
16
|
+
|
|
17
|
+
Sibling files in hooks/lib/: events.py (event-name single source of
|
|
18
|
+
truth), resolve_root.py (project-root resolver), bootstrap.sh +
|
|
19
|
+
bootstrap.ps1 (the entry stubs that exec the Python hook scripts
|
|
20
|
+
this logger serves), find-python.sh + find-python.ps1 (interpreter
|
|
21
|
+
locators).
|
|
22
|
+
|
|
23
|
+
Note on module name: this file is `log.py` rather than `logging.py`
|
|
24
|
+
because ``hooks/lib/`` is inserted at ``sys.path[0]`` by the
|
|
25
|
+
dispatcher; a sibling named ``logging.py`` would shadow the stdlib
|
|
26
|
+
``logging`` module on first ``import logging`` and break every
|
|
27
|
+
caller.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import logging
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_logger(name: str) -> logging.Logger:
|
|
36
|
+
"""Return a logger named ``name``, typically the caller's ``__name__``.
|
|
37
|
+
|
|
38
|
+
The factory deliberately does no handler/level/formatter setup —
|
|
39
|
+
that is the hook runtime's responsibility. Centralizing the
|
|
40
|
+
acquisition point keeps the call sites uniform and makes future
|
|
41
|
+
structured-logging migrations a single-file change.
|
|
42
|
+
"""
|
|
43
|
+
return logging.getLogger(name)
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Project root resolution for the apothem ecosystem.
|
|
4
|
+
|
|
5
|
+
Resolves the absolute path of the apothem project root using a
|
|
6
|
+
four-level strategy: explicit environment variable, script-relative
|
|
7
|
+
ascent, current-working-directory walk, and a `$HOME/.claude` fallback
|
|
8
|
+
(the Claude Code harness's config root — one of the supported harnesses).
|
|
9
|
+
|
|
10
|
+
Two marker strategies are supported:
|
|
11
|
+
|
|
12
|
+
* ``Mode.HOOKS`` — the root must contain both ``hooks/`` and ``rules/``
|
|
13
|
+
subdirectories. Used by runtime hook scripts that need a populated
|
|
14
|
+
ecosystem.
|
|
15
|
+
* ``Mode.MARKER`` — the root must contain a ``CLAUDE.md`` file. Used by
|
|
16
|
+
scripts that may run before the full layout exists (ecosystem
|
|
17
|
+
scaffolding, bootstrap utilities).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
from collections.abc import Callable, Iterable, Mapping
|
|
24
|
+
from dataclasses import dataclass
|
|
25
|
+
from enum import Enum
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Final
|
|
28
|
+
|
|
29
|
+
#: The Apothem-owned shared working-directory name. The sole canonical
|
|
30
|
+
#: project-local plans home is ``<project-root>/.apothem/plans``. The legacy
|
|
31
|
+
#: ``<project-root>/.plans`` layout is no longer canonical; operators upgrade an
|
|
32
|
+
#: existing ``.plans`` tree via ``apothem migrate-workspace``.
|
|
33
|
+
_APOTHEM_SUBTREE: Final[str] = ".apothem"
|
|
34
|
+
_PLANS_CHILD: Final[str] = "plans"
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"Mode",
|
|
38
|
+
"ResolutionStrategy",
|
|
39
|
+
"canonical_plans_dir",
|
|
40
|
+
"default_content_root",
|
|
41
|
+
"is_canonical_project_plans_dir",
|
|
42
|
+
"resolve_project_root",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Mode(str, Enum):
|
|
47
|
+
"""Marker-set selector for root detection."""
|
|
48
|
+
|
|
49
|
+
HOOKS = "hooks"
|
|
50
|
+
MARKER = "marker"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
_HOOKS_MARKERS: Final[tuple[str, ...]] = ("hooks", "rules")
|
|
54
|
+
_FILE_MARKERS: Final[tuple[str, ...]] = ("CLAUDE.md",)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True)
|
|
58
|
+
class ResolutionStrategy:
|
|
59
|
+
"""Concrete resolution configuration derived from a `Mode`.
|
|
60
|
+
|
|
61
|
+
Attributes:
|
|
62
|
+
directory_markers: Subdirectories whose presence identifies a root.
|
|
63
|
+
file_markers: Files whose presence identifies a root.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
directory_markers: tuple[str, ...]
|
|
67
|
+
file_markers: tuple[str, ...]
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def for_mode(cls, mode: Mode) -> ResolutionStrategy:
|
|
71
|
+
"""Return the strategy corresponding to `mode`."""
|
|
72
|
+
if mode is Mode.HOOKS:
|
|
73
|
+
return cls(directory_markers=_HOOKS_MARKERS, file_markers=())
|
|
74
|
+
return cls(directory_markers=(), file_markers=_FILE_MARKERS)
|
|
75
|
+
|
|
76
|
+
def matches(self, candidate: Path) -> bool:
|
|
77
|
+
"""Return True when `candidate` contains all required markers."""
|
|
78
|
+
if not candidate.is_dir():
|
|
79
|
+
return False
|
|
80
|
+
if not all((candidate / name).is_dir() for name in self.directory_markers):
|
|
81
|
+
return False
|
|
82
|
+
return all((candidate / name).is_file() for name in self.file_markers)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _iter_ancestors(start: Path) -> Iterable[Path]:
|
|
86
|
+
"""Yield `start` and each parent up to the filesystem root."""
|
|
87
|
+
current = start.resolve()
|
|
88
|
+
yield current
|
|
89
|
+
yield from current.parents
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _from_env(strategy: ResolutionStrategy, env: Mapping[str, str]) -> Path | None:
|
|
93
|
+
"""Resolve via ``$CLAUDE_PROJECT_DIR`` or ``$LLM_PROJECT_DIR``, or ``None``.
|
|
94
|
+
|
|
95
|
+
The primary variable ``CLAUDE_PROJECT_DIR`` is consulted first; if it is
|
|
96
|
+
unset or does not satisfy the strategy, the vendor-neutral alias
|
|
97
|
+
``LLM_PROJECT_DIR`` is tried as secondary. A successor harness that only
|
|
98
|
+
sets ``LLM_PROJECT_DIR`` resolves without falling through to later steps.
|
|
99
|
+
"""
|
|
100
|
+
for var_name in ("CLAUDE_PROJECT_DIR", "LLM_PROJECT_DIR"):
|
|
101
|
+
env_dir = env.get(var_name)
|
|
102
|
+
if not env_dir:
|
|
103
|
+
continue
|
|
104
|
+
candidate = Path(env_dir)
|
|
105
|
+
if strategy.matches(candidate):
|
|
106
|
+
return candidate.resolve()
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _from_script(
|
|
111
|
+
strategy: ResolutionStrategy, script_path: Path | str | None
|
|
112
|
+
) -> Path | None:
|
|
113
|
+
"""Resolve via the parent of a script located in ``hooks/``."""
|
|
114
|
+
if script_path is None:
|
|
115
|
+
return None
|
|
116
|
+
script = Path(script_path).resolve()
|
|
117
|
+
if script.parent.name != "hooks":
|
|
118
|
+
return None
|
|
119
|
+
grand = script.parent.parent
|
|
120
|
+
return grand if strategy.matches(grand) else None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _from_walk(strategy: ResolutionStrategy, cwd: Path | str | None) -> Path | None:
|
|
124
|
+
"""Resolve by walking ancestors of `cwd`."""
|
|
125
|
+
start = Path(cwd) if cwd is not None else Path.cwd()
|
|
126
|
+
for ancestor in _iter_ancestors(start):
|
|
127
|
+
if strategy.matches(ancestor):
|
|
128
|
+
return ancestor
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _from_home(strategy: ResolutionStrategy, home: Path | str | None) -> Path | None:
|
|
133
|
+
"""Resolve via ``$HOME/.claude``."""
|
|
134
|
+
home_dir = Path(home) if home is not None else Path.home()
|
|
135
|
+
fallback = home_dir / ".claude"
|
|
136
|
+
return fallback.resolve() if strategy.matches(fallback) else None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def resolve_project_root(
|
|
140
|
+
mode: Mode = Mode.HOOKS,
|
|
141
|
+
*,
|
|
142
|
+
script_path: Path | str | None = None,
|
|
143
|
+
cwd: Path | str | None = None,
|
|
144
|
+
environ: Mapping[str, str] | None = None,
|
|
145
|
+
home: Path | str | None = None,
|
|
146
|
+
) -> Path | None:
|
|
147
|
+
"""Locate the apothem project root.
|
|
148
|
+
|
|
149
|
+
The resolution order is:
|
|
150
|
+
|
|
151
|
+
1. ``$CLAUDE_PROJECT_DIR`` if it satisfies the strategy.
|
|
152
|
+
2. ``$LLM_PROJECT_DIR`` (vendor-neutral alias) if it satisfies the
|
|
153
|
+
strategy.
|
|
154
|
+
3. ``script_path``'s grandparent, when `script_path`'s immediate
|
|
155
|
+
parent is named ``hooks``.
|
|
156
|
+
4. Walk from `cwd` upward; return the first ancestor that
|
|
157
|
+
satisfies the strategy.
|
|
158
|
+
5. ``$HOME/.claude`` if it satisfies the strategy.
|
|
159
|
+
|
|
160
|
+
Returns ``None`` when no candidate satisfies the chosen strategy.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
mode: Which marker set to require.
|
|
164
|
+
script_path: Absolute path of the calling script. When provided
|
|
165
|
+
and located in a ``hooks/`` directory, the parent is tested.
|
|
166
|
+
cwd: Starting directory for the upward walk. Defaults to
|
|
167
|
+
``Path.cwd()``.
|
|
168
|
+
environ: Environment mapping. Defaults to ``os.environ``.
|
|
169
|
+
home: Home directory. Defaults to ``Path.home()``.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Absolute `Path` of the resolved root, or `None` if unresolved.
|
|
173
|
+
"""
|
|
174
|
+
strategy = ResolutionStrategy.for_mode(mode)
|
|
175
|
+
env: Mapping[str, str] = os.environ if environ is None else environ
|
|
176
|
+
resolvers: tuple[Callable[[], Path | None], ...] = (
|
|
177
|
+
lambda: _from_env(strategy, env),
|
|
178
|
+
lambda: _from_script(strategy, script_path),
|
|
179
|
+
lambda: _from_walk(strategy, cwd),
|
|
180
|
+
lambda: _from_home(strategy, home),
|
|
181
|
+
)
|
|
182
|
+
for resolver in resolvers:
|
|
183
|
+
result = resolver()
|
|
184
|
+
if result is not None:
|
|
185
|
+
return result
|
|
186
|
+
return None
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def default_content_root(root: Path) -> Path:
|
|
190
|
+
"""Resolve the harness content root for a apothem project root.
|
|
191
|
+
|
|
192
|
+
In the apothem *source* repository the harness convention surface
|
|
193
|
+
(``hooks/``, ``rules/``, ``commands/``, ...) lives under the
|
|
194
|
+
``src/apothem/`` package directory; in an installed harness (for
|
|
195
|
+
example ``~/.claude/``) it sits flat at the project root. This
|
|
196
|
+
helper returns ``root / "src" / "apothem"`` when that path carries
|
|
197
|
+
the package marker (a ``hooks/`` subdirectory), otherwise ``root``
|
|
198
|
+
unchanged — so dev validators run bare from either layout without an
|
|
199
|
+
explicit ``--content-root`` flag.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
root: The resolved apothem project root.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
The directory under which the harness convention surface lives.
|
|
206
|
+
"""
|
|
207
|
+
src_layout = root / "src" / "apothem"
|
|
208
|
+
if (src_layout / "hooks").is_dir():
|
|
209
|
+
return src_layout
|
|
210
|
+
return root
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def canonical_plans_dir(project_root: Path) -> Path:
|
|
214
|
+
"""Return the canonical project-local plans directory for a root.
|
|
215
|
+
|
|
216
|
+
Apothem keeps its working state in a single shared
|
|
217
|
+
``<project-root>/.apothem`` directory; the sole canonical plans tree is
|
|
218
|
+
``<project-root>/.apothem/plans``. The legacy ``<project-root>/.plans``
|
|
219
|
+
layout is no longer canonical — operators upgrade an existing ``.plans``
|
|
220
|
+
tree via ``apothem migrate-workspace``.
|
|
221
|
+
|
|
222
|
+
The project-root resolution itself (git-ascent + CWD walk via
|
|
223
|
+
:func:`resolve_project_root`) is unchanged; this helper only names the
|
|
224
|
+
canonical plans location beneath an already-resolved root. The path is
|
|
225
|
+
computed purely; no filesystem access occurs and no directory is created.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
project_root: The resolved project root.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
``<project-root>/.apothem/plans`` — the sole canonical plans location.
|
|
232
|
+
"""
|
|
233
|
+
return project_root / _APOTHEM_SUBTREE / _PLANS_CHILD
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def is_canonical_project_plans_dir(candidate: Path, project_root: Path) -> bool:
|
|
237
|
+
"""Return True iff *candidate* is the canonical project-local plans directory.
|
|
238
|
+
|
|
239
|
+
The sole canonical project-local plans directory is
|
|
240
|
+
``<project-root>/.apothem/plans``. Any other plans-shaped directory — a
|
|
241
|
+
legacy ``<project-root>/.plans``, a stray ``.plans`` nested elsewhere, or an
|
|
242
|
+
``.apothem/plans`` under a different root such as a harness-config root or
|
|
243
|
+
the user home — is NOT canonical and remains subject to the global-plans
|
|
244
|
+
deny.
|
|
245
|
+
|
|
246
|
+
The comparison is by resolved path so a relative *candidate* and an absolute
|
|
247
|
+
*project_root* compare correctly.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
candidate: The plans-directory path under test.
|
|
251
|
+
project_root: The resolved project root the candidate must sit beneath.
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
True when *candidate* resolves to the canonical project-local plans
|
|
255
|
+
directory, False otherwise.
|
|
256
|
+
"""
|
|
257
|
+
try:
|
|
258
|
+
resolved_candidate = candidate.resolve()
|
|
259
|
+
except (OSError, RuntimeError):
|
|
260
|
+
return False
|
|
261
|
+
try:
|
|
262
|
+
return resolved_candidate == canonical_plans_dir(project_root).resolve()
|
|
263
|
+
except (OSError, RuntimeError):
|
|
264
|
+
return False
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: MIT -->
|
|
2
|
+
|
|
3
|
+
Post-compaction Blind Bootstrap (CM-24 §6.2). Restore working state from durable files; conversation context is now compressed.
|
|
4
|
+
|
|
5
|
+
Read sequence — execute IN ORDER:
|
|
6
|
+
|
|
7
|
+
1. MEMORY.md (project tier, then global) and every topic file referenced in the project index.
|
|
8
|
+
2. With an active plan suite: PROGRESS.md Resumption Contract — phase/task status, next action, convention anchors, blockers, watch items, critical-files manifest.
|
|
9
|
+
3. Each file in the critical-files manifest, in the listed order. Skip any path already covered by a Resumption Contract snapshot to avoid redundant context spend.
|
|
10
|
+
4. The active phase file (`phases/NN-topic/PHASE.md`) — scope, tasks, inputs, outputs, verification criteria.
|
|
11
|
+
5. Adopt the convention anchors verbatim from the Resumption Contract; do NOT re-derive them from the preamble.
|
|
12
|
+
6. Verify alignment. The user's most recent request must be fully addressable from the loaded state. If it references files, decisions, or context NOT in the loaded set, read those files BEFORE proceeding. If the request contradicts loaded state, surface the contradiction to the user — never silently override durable state.
|
|
13
|
+
|
|
14
|
+
No active suite: step 1, then re-read any files referenced in the user's most recent request, then step 6.
|