@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,101 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""YAML frontmatter field probing and value extraction for ecosystem files.
|
|
4
|
+
|
|
5
|
+
Provides three layers of inspection:
|
|
6
|
+
|
|
7
|
+
* :func:`extract_frontmatter` — return the raw YAML block.
|
|
8
|
+
* :func:`field_value` — return the value of a single declared field as a string,
|
|
9
|
+
or ``None`` when the field is absent.
|
|
10
|
+
* :func:`has_field` / :func:`has_all_fields` — boolean presence checks.
|
|
11
|
+
|
|
12
|
+
The probe is intentionally regex-based rather than a full YAML parser so that
|
|
13
|
+
the validator surface has zero third-party dependencies and remains
|
|
14
|
+
import-time cheap. It supports the small subset of YAML actually used in
|
|
15
|
+
ecosystem frontmatter: scalar values (quoted or unquoted) on a single line.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import re
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Final
|
|
23
|
+
|
|
24
|
+
_FRONTMATTER_BLOCK: Final[re.Pattern[str]] = re.compile(
|
|
25
|
+
r"\A---\s*\n(.*?)\n---", re.DOTALL
|
|
26
|
+
)
|
|
27
|
+
_LEADING_HTML_COMMENT: Final[re.Pattern[str]] = re.compile(
|
|
28
|
+
r"\A\s*<!--.*?-->\s*", re.DOTALL
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def extract_frontmatter(path: Path) -> str | None:
|
|
33
|
+
"""Return the raw YAML block at the top of ``path``, or ``None``.
|
|
34
|
+
|
|
35
|
+
A leading HTML-comment block (the canonical authorship banner injected
|
|
36
|
+
ecosystem-wide above the YAML frontmatter on every Markdown file) is
|
|
37
|
+
skipped before matching the frontmatter delimiter so the probe sees
|
|
38
|
+
the YAML block at the correct anchor.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
path: File to read.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
The block content between the opening and closing ``---`` lines, or
|
|
45
|
+
``None`` when the file is unreadable or has no frontmatter.
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
text = path.read_text(encoding="utf-8", errors="replace")
|
|
49
|
+
except OSError:
|
|
50
|
+
return None
|
|
51
|
+
text = _LEADING_HTML_COMMENT.sub("", text, count=1)
|
|
52
|
+
match = _FRONTMATTER_BLOCK.match(text)
|
|
53
|
+
return match.group(1) if match else None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def field_value(path: Path, field_name: str) -> str | None:
|
|
57
|
+
"""Return the unquoted scalar value of ``field_name`` from ``path``.
|
|
58
|
+
|
|
59
|
+
Recognizes single-line ``key: "value"`` and ``key: value`` declarations.
|
|
60
|
+
Multi-line YAML constructs (block scalars, lists, mappings) return the
|
|
61
|
+
raw matched suffix and should be treated as opaque by callers.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
path: File to read.
|
|
65
|
+
field_name: YAML key to look up.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Stripped, dequoted value, or ``None`` when the field is absent.
|
|
69
|
+
"""
|
|
70
|
+
block = extract_frontmatter(path)
|
|
71
|
+
if block is None:
|
|
72
|
+
return None
|
|
73
|
+
pattern = re.compile(
|
|
74
|
+
rf"^{re.escape(field_name)}\s*:\s*(.*?)\s*$",
|
|
75
|
+
re.MULTILINE,
|
|
76
|
+
)
|
|
77
|
+
match = pattern.search(block)
|
|
78
|
+
if match is None:
|
|
79
|
+
return None
|
|
80
|
+
raw = match.group(1).strip()
|
|
81
|
+
if (raw.startswith('"') and raw.endswith('"')) or (
|
|
82
|
+
raw.startswith("'") and raw.endswith("'")
|
|
83
|
+
):
|
|
84
|
+
# Dequote by stripping the outer quotes only. Backslash escape
|
|
85
|
+
# sequences inside a double-quoted scalar (``"a \"b\""``) are NOT
|
|
86
|
+
# decoded — the inner text is returned verbatim. Apothem's own
|
|
87
|
+
# frontmatter values are plain prose with no escaped quotes, so this
|
|
88
|
+
# is sufficient in practice; a value needing escape decoding must be
|
|
89
|
+
# parsed with a full YAML loader by the caller.
|
|
90
|
+
return raw[1:-1]
|
|
91
|
+
return raw
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def has_field(path: Path, field_name: str) -> bool:
|
|
95
|
+
"""Return True when frontmatter in ``path`` declares ``field_name:``."""
|
|
96
|
+
return field_value(path, field_name) is not None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def has_all_fields(path: Path, field_names: list[str]) -> bool:
|
|
100
|
+
"""Return True when every field in ``field_names`` is declared."""
|
|
101
|
+
return all(has_field(path, name) for name in field_names)
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Shared building blocks for per-harness materializers.
|
|
4
|
+
|
|
5
|
+
These helpers cover profile-to-Markdown field extraction and the legacy
|
|
6
|
+
managed-YAML body shape retained for compatibility tests. Current harness
|
|
7
|
+
materializers that target strict vendor schemas should emit only documented
|
|
8
|
+
keys instead of using the generic managed body.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from collections.abc import Mapping
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from typing import Any, Final
|
|
16
|
+
|
|
17
|
+
import yaml
|
|
18
|
+
|
|
19
|
+
from apothem.lib.profile import DEFAULT_LANGUAGE, DEFAULT_STYLE, coerce_profile
|
|
20
|
+
|
|
21
|
+
# Canonical sentinel pair delimiting the Apothem-managed block inside an
|
|
22
|
+
# operator-owned Markdown instruction anchor (AGENTS.md, GEMINI.md, QWEN.md,
|
|
23
|
+
# copilot-instructions.md, and the project-scope rule files). The pair is the
|
|
24
|
+
# stable, documented merge boundary each harness dossier references: Apothem
|
|
25
|
+
# owns only the bytes between the sentinels; operator prose outside is
|
|
26
|
+
# preserved verbatim. Changing these strings would orphan every previously
|
|
27
|
+
# installed managed block, so they are a frozen contract.
|
|
28
|
+
APOTHEM_BLOCK_BEGIN: Final[str] = "<!-- BEGIN APOTHEM MANAGED BLOCK -->"
|
|
29
|
+
APOTHEM_BLOCK_END: Final[str] = "<!-- END APOTHEM MANAGED BLOCK -->"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def wrap_managed_block(body: str) -> str:
|
|
33
|
+
"""Return *body* wrapped in the canonical Apothem managed-block sentinels.
|
|
34
|
+
|
|
35
|
+
The wrapped form is the unit Apothem writes into an operator-owned
|
|
36
|
+
Markdown anchor. The body is stripped of surrounding blank lines so the
|
|
37
|
+
block is byte-stable across re-installs (idempotency depends on the
|
|
38
|
+
wrapped form being identical for identical bodies).
|
|
39
|
+
"""
|
|
40
|
+
return f"{APOTHEM_BLOCK_BEGIN}\n{body.strip()}\n{APOTHEM_BLOCK_END}\n"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def extract_managed_block(text: str) -> str | None:
|
|
44
|
+
"""Return the managed-block region found in *text*, or ``None``.
|
|
45
|
+
|
|
46
|
+
A well-formed block (begin sentinel followed by an end sentinel) returns
|
|
47
|
+
both sentinels and the body between them, exactly as in *text*. An *orphan*
|
|
48
|
+
begin sentinel with no following end sentinel (a truncated or
|
|
49
|
+
operator-mangled block) returns the degenerate region from the begin
|
|
50
|
+
sentinel to end-of-text, so :func:`merge_managed_block` REPLACES it in place
|
|
51
|
+
rather than appending a second block. Appending on an orphan would leave a
|
|
52
|
+
two-begin file whose next merge swallows the operator text trapped between
|
|
53
|
+
the orphan begin and the appended block — so the orphan is reclaimed on the
|
|
54
|
+
first merge instead (operator prose *before* the orphan begin is always
|
|
55
|
+
preserved; the caller writes a backup of the prior file). ``None`` only when
|
|
56
|
+
no begin sentinel is present at all.
|
|
57
|
+
"""
|
|
58
|
+
begin = text.find(APOTHEM_BLOCK_BEGIN)
|
|
59
|
+
if begin == -1:
|
|
60
|
+
return None
|
|
61
|
+
end = text.find(APOTHEM_BLOCK_END, begin + len(APOTHEM_BLOCK_BEGIN))
|
|
62
|
+
if end == -1:
|
|
63
|
+
return text[begin:]
|
|
64
|
+
return text[begin : end + len(APOTHEM_BLOCK_END)]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def merge_managed_block(existing: str, body: str) -> str:
|
|
68
|
+
"""Merge an Apothem managed block carrying *body* into *existing* text.
|
|
69
|
+
|
|
70
|
+
Three cases, all preserving operator prose:
|
|
71
|
+
|
|
72
|
+
- *existing* is empty — return the wrapped block alone.
|
|
73
|
+
- *existing* already carries a managed block — replace that block in
|
|
74
|
+
place, leaving operator prose before and after it untouched.
|
|
75
|
+
- *existing* carries no managed block — append the wrapped block after
|
|
76
|
+
the operator's content, separated by one blank line.
|
|
77
|
+
|
|
78
|
+
The merge is idempotent: re-merging the same *body* into text that
|
|
79
|
+
already carries the resulting block returns byte-identical output.
|
|
80
|
+
"""
|
|
81
|
+
wrapped = wrap_managed_block(body)
|
|
82
|
+
if not existing.strip():
|
|
83
|
+
return wrapped
|
|
84
|
+
current_block = extract_managed_block(existing)
|
|
85
|
+
if current_block is not None:
|
|
86
|
+
return existing.replace(current_block, wrapped.rstrip("\n"), 1)
|
|
87
|
+
separator = "" if existing.endswith("\n") else "\n"
|
|
88
|
+
return f"{existing}{separator}\n{wrapped}"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def remove_managed_block(existing: str) -> str:
|
|
92
|
+
"""Return *existing* with the Apothem managed block surgically removed.
|
|
93
|
+
|
|
94
|
+
The inverse of :func:`merge_managed_block`: strips the managed block (and the
|
|
95
|
+
blank-line separator the merge inserted around it) while preserving operator
|
|
96
|
+
prose before and after it. When the remainder is whitespace-only — i.e. the
|
|
97
|
+
anchor was Apothem-only — returns the empty string so the caller can delete
|
|
98
|
+
the now-empty file rather than leaving a stub. Returns *existing* unchanged
|
|
99
|
+
when it carries no managed block (including a degenerate orphan-begin block,
|
|
100
|
+
which :func:`extract_managed_block` reclaims).
|
|
101
|
+
|
|
102
|
+
Round-trips byte-for-byte for the canonical case: an operator prose block
|
|
103
|
+
ending in a single newline, merged-then-removed, returns the original.
|
|
104
|
+
"""
|
|
105
|
+
block = extract_managed_block(existing)
|
|
106
|
+
if block is None:
|
|
107
|
+
return existing
|
|
108
|
+
index = existing.find(block)
|
|
109
|
+
before = existing[:index].rstrip("\n")
|
|
110
|
+
after = existing[index + len(block) :].lstrip("\n")
|
|
111
|
+
if before and after:
|
|
112
|
+
remainder = f"{before}\n\n{after}"
|
|
113
|
+
elif before:
|
|
114
|
+
remainder = f"{before}\n"
|
|
115
|
+
else:
|
|
116
|
+
remainder = after
|
|
117
|
+
return "" if not remainder.strip() else remainder
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@dataclass(frozen=True)
|
|
121
|
+
class MarkdownProfileFields:
|
|
122
|
+
"""The profile fields the Markdown-output adapters render into their body.
|
|
123
|
+
|
|
124
|
+
Carries the full identity (name/role/email/website/github), both
|
|
125
|
+
preference scalars (language/style), the seriousness band, the rules
|
|
126
|
+
list (+ its pre-rendered block), and the opted-in enforcement flags. The
|
|
127
|
+
optional fields default to absent so an empty profile renders cleanly; the
|
|
128
|
+
projection seam omits any field with no value.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
name: str
|
|
132
|
+
role: str
|
|
133
|
+
language: str
|
|
134
|
+
rules: list[str]
|
|
135
|
+
seriousness: str
|
|
136
|
+
extra_rules_block: str
|
|
137
|
+
style: str = DEFAULT_STYLE
|
|
138
|
+
email: str | None = None
|
|
139
|
+
website: str | None = None
|
|
140
|
+
github: str | None = None
|
|
141
|
+
enforcement: Mapping[str, bool] = field(default_factory=dict)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def extract_markdown_fields(profile: dict[str, Any]) -> MarkdownProfileFields:
|
|
145
|
+
"""Extract the canonical Markdown-output field set from *profile*.
|
|
146
|
+
|
|
147
|
+
The defaults are delegated to the canonical profile model so Markdown
|
|
148
|
+
helpers and schema-backed profile loading cannot drift. Accepts either a
|
|
149
|
+
raw profile dict or a ``CanonicalProfile.for_harness`` projection.
|
|
150
|
+
"""
|
|
151
|
+
canonical = coerce_profile(profile)
|
|
152
|
+
normalized = canonical.to_dict()
|
|
153
|
+
identity = normalized["identity"]
|
|
154
|
+
preferences = normalized["preferences"]
|
|
155
|
+
rules: list[str] = normalized["rules"]
|
|
156
|
+
return MarkdownProfileFields(
|
|
157
|
+
name=identity["name"],
|
|
158
|
+
role=identity["role"],
|
|
159
|
+
language=preferences["language"],
|
|
160
|
+
rules=rules,
|
|
161
|
+
seriousness=normalized["seriousness"],
|
|
162
|
+
extra_rules_block=_render_extra_rules_block(rules),
|
|
163
|
+
style=preferences["style"],
|
|
164
|
+
email=identity.get("email"),
|
|
165
|
+
website=identity.get("website"),
|
|
166
|
+
github=identity.get("github"),
|
|
167
|
+
enforcement=dict(normalized["enforcement"]),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _render_extra_rules_block(rules: list[str]) -> str:
|
|
172
|
+
"""Format the optional ``## Custom Rules`` Markdown block.
|
|
173
|
+
|
|
174
|
+
Empty when *rules* is empty; otherwise a two-newline-separated
|
|
175
|
+
section header followed by a bullet list of rules.
|
|
176
|
+
"""
|
|
177
|
+
if not rules:
|
|
178
|
+
return ""
|
|
179
|
+
body = "\n".join(f"- {r}" for r in rules)
|
|
180
|
+
return f"\n\n## Custom Rules\n\n{body}"
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def build_yaml_managed_config(profile: dict[str, Any], header: str) -> str:
|
|
184
|
+
"""Render the canonical ``apothem_managed`` YAML config body.
|
|
185
|
+
|
|
186
|
+
Retained for compatibility with earlier generic materializers. The body
|
|
187
|
+
carries the apothem-managed sentinel, the full identity + preferences
|
|
188
|
+
sub-dicts (with their per-key defaults), the seriousness band, and an
|
|
189
|
+
optional rules list.
|
|
190
|
+
"""
|
|
191
|
+
normalized = coerce_profile(profile).to_dict()
|
|
192
|
+
identity = normalized["identity"]
|
|
193
|
+
preferences = normalized["preferences"]
|
|
194
|
+
rules: list[str] = normalized["rules"]
|
|
195
|
+
|
|
196
|
+
config: dict[str, Any] = {
|
|
197
|
+
"apothem_managed": True,
|
|
198
|
+
"identity": {
|
|
199
|
+
"name": identity.get("name", ""),
|
|
200
|
+
"role": identity.get("role", ""),
|
|
201
|
+
"email": identity.get("email", ""),
|
|
202
|
+
},
|
|
203
|
+
"preferences": {
|
|
204
|
+
"language": preferences.get("language", DEFAULT_LANGUAGE),
|
|
205
|
+
"style": preferences.get("style", DEFAULT_STYLE),
|
|
206
|
+
},
|
|
207
|
+
"apothem_support": "apothem",
|
|
208
|
+
"seriousness": normalized["seriousness"],
|
|
209
|
+
}
|
|
210
|
+
if rules:
|
|
211
|
+
config["rules"] = rules
|
|
212
|
+
|
|
213
|
+
return header + yaml.safe_dump(config, default_flow_style=False, allow_unicode=True)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Apothem harness adapter protocol — the foundation contract.
|
|
4
|
+
|
|
5
|
+
Defines :class:`HarnessAdapter`, the structural contract every concrete
|
|
6
|
+
adapter must satisfy. It lives in the foundation ``lib`` layer so the harness
|
|
7
|
+
registry can reference the contract without the foundation importing its
|
|
8
|
+
consumers; :mod:`apothem.harnesses` re-exports it as the public surface
|
|
9
|
+
(importable as ``apothem.harnesses.HarnessAdapter``), and the adapters that
|
|
10
|
+
satisfy it are declared in :mod:`apothem.lib.harness_registry`.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, Protocol, runtime_checkable
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@runtime_checkable
|
|
20
|
+
class HarnessAdapter(Protocol):
|
|
21
|
+
"""Protocol every apothem harness adapter must satisfy.
|
|
22
|
+
|
|
23
|
+
Adapters translate a shared :class:`dict` profile into a
|
|
24
|
+
harness-native configuration on disk. The lifecycle contract is small:
|
|
25
|
+
an adapter owns one primary ``output_path`` anchor, implements the
|
|
26
|
+
install/update/uninstall/verify methods, and keeps the richer identity,
|
|
27
|
+
target, docs, capability, and package-data obligations in the central
|
|
28
|
+
registry.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def name(self) -> str:
|
|
33
|
+
"""Canonical kebab-case harness identifier (e.g. ``'claude-code'``)."""
|
|
34
|
+
...
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def output_path(self) -> Path:
|
|
38
|
+
"""Absolute path to the target configuration file."""
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
def install(self, profile: dict[str, Any]) -> object:
|
|
42
|
+
"""Materialize the harness configuration from the shared profile dict."""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
def update(self, profile: dict[str, Any]) -> object:
|
|
46
|
+
"""Re-materialize the harness configuration from the updated profile."""
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
def uninstall(self) -> None:
|
|
50
|
+
"""Remove the harness configuration file if present."""
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
def is_installed(self) -> bool:
|
|
54
|
+
"""Return ``True`` if the harness configuration file exists on disk."""
|
|
55
|
+
...
|
|
56
|
+
|
|
57
|
+
def verify(self) -> bool:
|
|
58
|
+
"""Return ``True`` if the installed harness configuration is valid."""
|
|
59
|
+
...
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Central registry for supported Apothem harness adapters.
|
|
4
|
+
|
|
5
|
+
The declarative ``HARNESS_REGISTRY`` data table lives in the sibling
|
|
6
|
+
``harness_registry_data`` module; this module imports it and carries the
|
|
7
|
+
resolution / discovery logic plus the derived lookup indexes. It re-exports the
|
|
8
|
+
full public surface (data + types + functions) so
|
|
9
|
+
``from apothem.lib.harness_registry import X`` is unchanged for every consumer.
|
|
10
|
+
This module is authoritative at runtime.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import importlib
|
|
16
|
+
import importlib.util
|
|
17
|
+
import sys
|
|
18
|
+
from collections.abc import Mapping
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from types import ModuleType
|
|
21
|
+
|
|
22
|
+
from apothem.lib.harness_protocol import HarnessAdapter
|
|
23
|
+
from apothem.lib.harness_registry_data import (
|
|
24
|
+
HARNESS_REGISTRY as HARNESS_REGISTRY,
|
|
25
|
+
)
|
|
26
|
+
from apothem.lib.harness_registry_data import (
|
|
27
|
+
REQUIRED_CAPABILITIES as REQUIRED_CAPABILITIES,
|
|
28
|
+
)
|
|
29
|
+
from apothem.lib.harness_registry_data import (
|
|
30
|
+
SUPPORTED_HARNESS_COUNT as SUPPORTED_HARNESS_COUNT,
|
|
31
|
+
)
|
|
32
|
+
from apothem.lib.harness_registry_data import (
|
|
33
|
+
CapabilityStatus as CapabilityStatus,
|
|
34
|
+
)
|
|
35
|
+
from apothem.lib.harness_registry_data import (
|
|
36
|
+
HarnessRegistryEntry as HarnessRegistryEntry,
|
|
37
|
+
)
|
|
38
|
+
from apothem.lib.harness_registry_data import (
|
|
39
|
+
HarnessScope as HarnessScope,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
SUPPORTED_HARNESS_IDS: tuple[str, ...] = tuple(
|
|
43
|
+
entry.public_id for entry in HARNESS_REGISTRY
|
|
44
|
+
)
|
|
45
|
+
SUPPORTED_PACKAGE_KEYS: tuple[str, ...] = tuple(
|
|
46
|
+
entry.package_key for entry in HARNESS_REGISTRY
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
_BY_PUBLIC_ID: Mapping[str, HarnessRegistryEntry] = {
|
|
50
|
+
entry.public_id: entry for entry in HARNESS_REGISTRY
|
|
51
|
+
}
|
|
52
|
+
_BY_PACKAGE_KEY: Mapping[str, HarnessRegistryEntry] = {
|
|
53
|
+
entry.package_key: entry for entry in HARNESS_REGISTRY
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if len(HARNESS_REGISTRY) != SUPPORTED_HARNESS_COUNT: # pragma: no cover
|
|
57
|
+
raise RuntimeError(
|
|
58
|
+
"the supported harness registry must contain exactly "
|
|
59
|
+
f"{SUPPORTED_HARNESS_COUNT} entries"
|
|
60
|
+
)
|
|
61
|
+
if len(set(SUPPORTED_HARNESS_IDS)) != SUPPORTED_HARNESS_COUNT: # pragma: no cover
|
|
62
|
+
raise RuntimeError("duplicate public harness ids in registry")
|
|
63
|
+
if len(set(SUPPORTED_PACKAGE_KEYS)) != SUPPORTED_HARNESS_COUNT: # pragma: no cover
|
|
64
|
+
raise RuntimeError("duplicate harness package keys in registry")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def normalize_harness_reference(value: object) -> str:
|
|
68
|
+
"""Normalize a public id or package key reference for registry lookup."""
|
|
69
|
+
return str(value).strip().lower()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def iter_harness_entries() -> tuple[HarnessRegistryEntry, ...]:
|
|
73
|
+
"""Return registry entries in deterministic public-id order."""
|
|
74
|
+
return HARNESS_REGISTRY
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_harness_entry(value: object) -> HarnessRegistryEntry:
|
|
78
|
+
"""Return the registry entry addressed by public id or package key."""
|
|
79
|
+
key = normalize_harness_reference(value)
|
|
80
|
+
if key in _BY_PUBLIC_ID:
|
|
81
|
+
return _BY_PUBLIC_ID[key]
|
|
82
|
+
if key in _BY_PACKAGE_KEY:
|
|
83
|
+
return _BY_PACKAGE_KEY[key]
|
|
84
|
+
raise KeyError(key)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def public_id_for_package_key(package_key: object) -> str:
|
|
88
|
+
"""Return the public harness id for a Python package key."""
|
|
89
|
+
return get_harness_entry(package_key).public_id
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def package_key_for_public_id(public_id: object) -> str:
|
|
93
|
+
"""Return the Python package key for a public harness id."""
|
|
94
|
+
return get_harness_entry(public_id).package_key
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def load_adapter_class(entry: HarnessRegistryEntry) -> type[object]:
|
|
98
|
+
"""Import and return the adapter class declared by a registry entry."""
|
|
99
|
+
module = importlib.import_module(entry.adapter_module)
|
|
100
|
+
adapter_class = getattr(module, entry.adapter_class_name)
|
|
101
|
+
if not isinstance(adapter_class, type):
|
|
102
|
+
raise TypeError(f"{entry.entry_point} did not resolve to a class")
|
|
103
|
+
return adapter_class
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class AdapterDiscoveryError(RuntimeError):
|
|
107
|
+
"""Raised when a harness sub-package fails convention-based adapter resolution.
|
|
108
|
+
|
|
109
|
+
The error message names the offending directory so a silently-dropped
|
|
110
|
+
adapter never becomes an invisible coverage regression.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# Directory names under ``harnesses/`` that hold shared helpers rather than a
|
|
115
|
+
# concrete adapter sub-package. They are skipped during discovery alongside
|
|
116
|
+
# private (underscore / dot prefixed) and cache directories.
|
|
117
|
+
_DISCOVERY_HELPER_DIRS: frozenset[str] = frozenset({"_shared", "templates"})
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _adapter_canonical_name(package_key: str) -> str:
|
|
121
|
+
"""Return the canonical kebab-case name for a harness package directory.
|
|
122
|
+
|
|
123
|
+
Reuses the static registry's ``package_key`` -> ``public_id`` mapping for
|
|
124
|
+
fidelity (e.g. ``claude_code`` -> ``claude-code``). Falls back to a direct
|
|
125
|
+
underscore-to-hyphen transform when the directory is not yet registered,
|
|
126
|
+
so a newly-added adapter is still discoverable before the static registry
|
|
127
|
+
is updated.
|
|
128
|
+
"""
|
|
129
|
+
entry = _BY_PACKAGE_KEY.get(package_key)
|
|
130
|
+
if entry is not None:
|
|
131
|
+
return entry.public_id
|
|
132
|
+
return package_key.replace("_", "-")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _resolve_adapter_class(package_key: str, module: object) -> type[object]:
|
|
136
|
+
"""Find the single ``*Adapter`` class exposed by an imported harness module.
|
|
137
|
+
|
|
138
|
+
The class name is resolved by convention rather than computed from the
|
|
139
|
+
directory name: the snake-to-Pascal transform is not reliable (for
|
|
140
|
+
instance ``github_copilot`` exposes ``GitHubCopilotAdapter`` with an
|
|
141
|
+
internal capital ``H``). Instead every attribute whose name ends with
|
|
142
|
+
``Adapter`` and is a class is collected; exactly one must be present.
|
|
143
|
+
"""
|
|
144
|
+
candidates: list[type[object]] = []
|
|
145
|
+
for attr_name in dir(module):
|
|
146
|
+
if not attr_name.endswith("Adapter"):
|
|
147
|
+
continue
|
|
148
|
+
attr = getattr(module, attr_name)
|
|
149
|
+
if isinstance(attr, type) and attr is not HarnessAdapter:
|
|
150
|
+
candidates.append(attr)
|
|
151
|
+
|
|
152
|
+
# De-duplicate while preserving identity (an adapter may be re-exported
|
|
153
|
+
# under more than one attribute name pointing at the same class object).
|
|
154
|
+
unique = list(dict.fromkeys(candidates))
|
|
155
|
+
if not unique:
|
|
156
|
+
raise AdapterDiscoveryError(
|
|
157
|
+
f"harnesses/{package_key}: no '*Adapter' class found in the package module"
|
|
158
|
+
)
|
|
159
|
+
if len(unique) > 1:
|
|
160
|
+
names = ", ".join(sorted(cls.__name__ for cls in unique))
|
|
161
|
+
raise AdapterDiscoveryError(
|
|
162
|
+
f"harnesses/{package_key}: expected exactly one '*Adapter' class, "
|
|
163
|
+
f"found {len(unique)} ({names})"
|
|
164
|
+
)
|
|
165
|
+
return unique[0]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _is_installed_package_root(root: Path) -> bool:
|
|
169
|
+
"""Return ``True`` when ``root`` is the installed ``apothem`` package dir."""
|
|
170
|
+
import apothem
|
|
171
|
+
|
|
172
|
+
apothem_file = getattr(apothem, "__file__", None)
|
|
173
|
+
if apothem_file is None: # pragma: no cover - namespace package edge
|
|
174
|
+
return False
|
|
175
|
+
return Path(apothem_file).resolve().parent == root.resolve()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _import_harness_module(root: Path, name: str, package_dir: Path) -> ModuleType:
|
|
179
|
+
"""Import the harness sub-package ``name`` found under ``root / harnesses``.
|
|
180
|
+
|
|
181
|
+
When ``root`` is the installed ``apothem`` package directory the robust
|
|
182
|
+
namespace import (``apothem.harnesses.<name>``) is used — this honors
|
|
183
|
+
editable installs and namespace-package layouts. Otherwise the module is
|
|
184
|
+
loaded directly from its ``__init__.py`` on disk, so an arbitrary ``root``
|
|
185
|
+
(for instance a test fixture tree) is enumerated against the actual files
|
|
186
|
+
it contains rather than the installed package.
|
|
187
|
+
"""
|
|
188
|
+
if _is_installed_package_root(root):
|
|
189
|
+
return importlib.import_module(f"apothem.harnesses.{name}")
|
|
190
|
+
|
|
191
|
+
fq_name = f"_apothem_discovered_harness_{name}"
|
|
192
|
+
spec = importlib.util.spec_from_file_location(fq_name, package_dir / "__init__.py")
|
|
193
|
+
if spec is None or spec.loader is None: # pragma: no cover - defensive
|
|
194
|
+
raise AdapterDiscoveryError(
|
|
195
|
+
f"harnesses/{name}: package could not be loaded from {package_dir}"
|
|
196
|
+
)
|
|
197
|
+
module = importlib.util.module_from_spec(spec)
|
|
198
|
+
sys.modules[fq_name] = module
|
|
199
|
+
spec.loader.exec_module(module)
|
|
200
|
+
return module
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def discover_adapters(root: Path) -> dict[str, type[object]]:
|
|
204
|
+
"""Discover harness adapters by filesystem convention under ``root``.
|
|
205
|
+
|
|
206
|
+
Conformance utility, NOT the runtime resolver. The authoritative
|
|
207
|
+
adapter source at runtime is the static :data:`HARNESS_REGISTRY`; this
|
|
208
|
+
discovery exists so a parity test can assert the on-disk adapter set
|
|
209
|
+
equals the registry, catching a convention-correct but unregistered
|
|
210
|
+
adapter as a coverage regression. No CLI / runtime path calls this.
|
|
211
|
+
|
|
212
|
+
Scans the immediate subdirectories of ``root / "harnesses"``, importing
|
|
213
|
+
each package and resolving its ``*Adapter`` class by convention. The
|
|
214
|
+
returned mapping is keyed by the adapter's canonical kebab-case name and
|
|
215
|
+
ordered deterministically (sorted by key).
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
root: The ``apothem`` package directory whose ``harnesses/``
|
|
219
|
+
subdirectory holds the per-harness adapter sub-packages.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
A ``dict`` mapping canonical harness name (e.g. ``'claude-code'``)
|
|
223
|
+
to the resolved adapter class, in sorted key order.
|
|
224
|
+
|
|
225
|
+
Raises:
|
|
226
|
+
AdapterDiscoveryError: When a candidate sub-package exposes zero or
|
|
227
|
+
more than one ``*Adapter`` class. The message names the directory.
|
|
228
|
+
"""
|
|
229
|
+
harnesses_dir = root / "harnesses"
|
|
230
|
+
discovered: dict[str, type[object]] = {}
|
|
231
|
+
for child in sorted(harnesses_dir.iterdir()):
|
|
232
|
+
if not child.is_dir():
|
|
233
|
+
continue
|
|
234
|
+
name = child.name
|
|
235
|
+
if name.startswith(("_", ".")) or name in _DISCOVERY_HELPER_DIRS:
|
|
236
|
+
continue
|
|
237
|
+
if not (child / "__init__.py").is_file():
|
|
238
|
+
continue
|
|
239
|
+
module = _import_harness_module(root, name, child)
|
|
240
|
+
adapter_class = _resolve_adapter_class(name, module)
|
|
241
|
+
discovered[_adapter_canonical_name(name)] = adapter_class
|
|
242
|
+
return dict(sorted(discovered.items()))
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def discovered_adapter_map() -> dict[str, type[object]]:
|
|
246
|
+
"""Discover adapters under the installed ``apothem`` package directory.
|
|
247
|
+
|
|
248
|
+
Convenience wrapper over :func:`discover_adapters` that defaults ``root``
|
|
249
|
+
to the directory of the installed ``apothem`` package, so callers need
|
|
250
|
+
not compute it themselves. Like :func:`discover_adapters`, this is a
|
|
251
|
+
conformance/parity utility (registry-vs-filesystem agreement), not the
|
|
252
|
+
runtime adapter resolver — the static :data:`HARNESS_REGISTRY` is
|
|
253
|
+
authoritative at runtime.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
A ``dict`` mapping canonical harness name to adapter class, sorted.
|
|
257
|
+
"""
|
|
258
|
+
import apothem
|
|
259
|
+
|
|
260
|
+
package_root = Path(apothem.__file__).resolve().parent
|
|
261
|
+
return discover_adapters(package_root)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
__all__ = [
|
|
265
|
+
"HARNESS_REGISTRY",
|
|
266
|
+
"REQUIRED_CAPABILITIES",
|
|
267
|
+
"SUPPORTED_HARNESS_COUNT",
|
|
268
|
+
"SUPPORTED_HARNESS_IDS",
|
|
269
|
+
"SUPPORTED_PACKAGE_KEYS",
|
|
270
|
+
"AdapterDiscoveryError",
|
|
271
|
+
"CapabilityStatus",
|
|
272
|
+
"HarnessRegistryEntry",
|
|
273
|
+
"HarnessScope",
|
|
274
|
+
"discover_adapters",
|
|
275
|
+
"discovered_adapter_map",
|
|
276
|
+
"get_harness_entry",
|
|
277
|
+
"iter_harness_entries",
|
|
278
|
+
"load_adapter_class",
|
|
279
|
+
"normalize_harness_reference",
|
|
280
|
+
"package_key_for_public_id",
|
|
281
|
+
"public_id_for_package_key",
|
|
282
|
+
]
|