@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,366 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Surgical removal of Apothem's contribution from operator-owned configs."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import contextlib
|
|
8
|
+
import json
|
|
9
|
+
from dataclasses import replace
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import yaml
|
|
13
|
+
|
|
14
|
+
from apothem.lib.harness_materializer import (
|
|
15
|
+
remove_managed_block,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from .install_driver_backup import _guarded_unlink, backup_existing, write_bytes_safely
|
|
19
|
+
from .install_driver_merge import _is_apothem_hook
|
|
20
|
+
from .install_driver_pathsafety import _validate_target_path
|
|
21
|
+
from .install_driver_types import (
|
|
22
|
+
_REMOVE_KEY,
|
|
23
|
+
MaterializationResult,
|
|
24
|
+
_path_text,
|
|
25
|
+
_result,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _remove_apothem_hook_handlers(existing_entries: object) -> object:
|
|
30
|
+
"""Strip Apothem hook handlers from one hooks event's matcher list.
|
|
31
|
+
|
|
32
|
+
The inverse of :func:`_merge_hooks` for a single event: walks the operator's
|
|
33
|
+
matcher entries, drops every handler :func:`_is_apothem_hook` recognizes, and
|
|
34
|
+
drops a matcher entry once its ``hooks`` list is emptied (an empty-hooks
|
|
35
|
+
matcher entry is meaningless — an Apothem matcher's sibling attributes such as
|
|
36
|
+
``sequential`` go with it). An operator-authored handler under any matcher
|
|
37
|
+
survives, keeping that matcher entry alive. Returns :data:`_REMOVE_KEY` when
|
|
38
|
+
no matcher entry survives, so the caller drops the ``hooks`` event entirely.
|
|
39
|
+
"""
|
|
40
|
+
if not isinstance(existing_entries, list):
|
|
41
|
+
return existing_entries
|
|
42
|
+
retained: list[object] = []
|
|
43
|
+
for entry in existing_entries:
|
|
44
|
+
if not isinstance(entry, dict):
|
|
45
|
+
retained.append(entry)
|
|
46
|
+
continue
|
|
47
|
+
handlers = entry.get("hooks")
|
|
48
|
+
if not isinstance(handlers, list):
|
|
49
|
+
retained.append(entry)
|
|
50
|
+
continue
|
|
51
|
+
kept_handlers = [
|
|
52
|
+
handler for handler in handlers if not _is_apothem_hook(handler)
|
|
53
|
+
]
|
|
54
|
+
if not kept_handlers:
|
|
55
|
+
# Every handler under this matcher was Apothem's: drop the entry.
|
|
56
|
+
continue
|
|
57
|
+
survivor = dict(entry)
|
|
58
|
+
survivor["hooks"] = kept_handlers
|
|
59
|
+
retained.append(survivor)
|
|
60
|
+
if not retained:
|
|
61
|
+
return _REMOVE_KEY
|
|
62
|
+
return retained
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _remove_apothem_hooks(
|
|
66
|
+
existing_hooks: object,
|
|
67
|
+
template_hooks: object,
|
|
68
|
+
) -> object:
|
|
69
|
+
"""Strip Apothem-managed handlers from an operator ``hooks`` object.
|
|
70
|
+
|
|
71
|
+
Each event the template contributed is filtered through
|
|
72
|
+
:func:`_remove_apothem_hook_handlers`; an event the operator added that the
|
|
73
|
+
template never wrote is preserved untouched. Returns :data:`_REMOVE_KEY` when
|
|
74
|
+
the resulting hooks object is empty, so the parent drops the key.
|
|
75
|
+
"""
|
|
76
|
+
if not isinstance(existing_hooks, dict):
|
|
77
|
+
return existing_hooks
|
|
78
|
+
template = template_hooks if isinstance(template_hooks, dict) else {}
|
|
79
|
+
result: dict[str, object] = {}
|
|
80
|
+
for event_name, existing_entries in existing_hooks.items():
|
|
81
|
+
if event_name not in template:
|
|
82
|
+
result[event_name] = existing_entries
|
|
83
|
+
continue
|
|
84
|
+
stripped = _remove_apothem_hook_handlers(existing_entries)
|
|
85
|
+
if stripped is _REMOVE_KEY:
|
|
86
|
+
continue
|
|
87
|
+
result[event_name] = stripped
|
|
88
|
+
if not result:
|
|
89
|
+
return _REMOVE_KEY
|
|
90
|
+
return result
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _remove_apothem_list_items(
|
|
94
|
+
existing: list[object], template: list[object]
|
|
95
|
+
) -> object:
|
|
96
|
+
"""Drop the items the template contributed from an operator list.
|
|
97
|
+
|
|
98
|
+
Removes one occurrence per template item (operator-added entries and
|
|
99
|
+
operator duplicates beyond the template's count survive). Returns
|
|
100
|
+
:data:`_REMOVE_KEY` only when the operator list is left empty *and* the
|
|
101
|
+
template list was non-empty — an operator who kept an Apothem list key but
|
|
102
|
+
emptied it of operator content carried only Apothem items, so the key goes.
|
|
103
|
+
"""
|
|
104
|
+
remaining = list(existing)
|
|
105
|
+
for item in template:
|
|
106
|
+
with contextlib.suppress(ValueError):
|
|
107
|
+
remaining.remove(item)
|
|
108
|
+
if not remaining and template:
|
|
109
|
+
return _REMOVE_KEY
|
|
110
|
+
return remaining
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _remove_apothem_values(existing: object, template: object) -> object:
|
|
114
|
+
"""Return *existing* with Apothem's *template* contribution removed.
|
|
115
|
+
|
|
116
|
+
The structural inverse of the install-side overlay merge. Recurses through
|
|
117
|
+
mappings: a key whose operator value equals the template value is removed
|
|
118
|
+
(it was purely Apothem's); a key the operator overrode to a different value
|
|
119
|
+
or added beyond the template is kept; nested mappings recurse; ``hooks`` is
|
|
120
|
+
handled by the dedicated handler-aware stripper; list-valued keys drop the
|
|
121
|
+
template-contributed items and keep operator-added ones. Returns
|
|
122
|
+
:data:`_REMOVE_KEY` when a mapping is emptied of all operator content so the
|
|
123
|
+
parent drops the key. Non-mapping *existing* is returned unchanged (the
|
|
124
|
+
caller decides container-level deletion).
|
|
125
|
+
"""
|
|
126
|
+
if not isinstance(existing, dict) or not isinstance(template, dict):
|
|
127
|
+
return existing
|
|
128
|
+
result: dict[str, object] = {}
|
|
129
|
+
for key, value in existing.items():
|
|
130
|
+
if key not in template:
|
|
131
|
+
# Operator-added key absent from the Apothem template: always kept.
|
|
132
|
+
result[key] = value
|
|
133
|
+
continue
|
|
134
|
+
template_value = template[key]
|
|
135
|
+
if key == "hooks" and isinstance(value, dict):
|
|
136
|
+
stripped_hooks = _remove_apothem_hooks(value, template_value)
|
|
137
|
+
if stripped_hooks is not _REMOVE_KEY:
|
|
138
|
+
result[key] = stripped_hooks
|
|
139
|
+
continue
|
|
140
|
+
if isinstance(value, dict) and isinstance(template_value, dict):
|
|
141
|
+
reduced = _remove_apothem_values(value, template_value)
|
|
142
|
+
if reduced is not _REMOVE_KEY:
|
|
143
|
+
result[key] = reduced
|
|
144
|
+
continue
|
|
145
|
+
if isinstance(value, list) and isinstance(template_value, list):
|
|
146
|
+
reduced_list = _remove_apothem_list_items(value, template_value)
|
|
147
|
+
if reduced_list is not _REMOVE_KEY:
|
|
148
|
+
result[key] = reduced_list
|
|
149
|
+
continue
|
|
150
|
+
if value == template_value:
|
|
151
|
+
# Purely Apothem's contribution: drop it.
|
|
152
|
+
continue
|
|
153
|
+
# Operator overrode the value: keep the operator's version.
|
|
154
|
+
result[key] = value
|
|
155
|
+
if not result:
|
|
156
|
+
return _REMOVE_KEY
|
|
157
|
+
return result
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _drop_owned_top_keys(
|
|
161
|
+
operator: dict[str, object], apothem_keys: frozenset[str]
|
|
162
|
+
) -> dict[str, object]:
|
|
163
|
+
"""Drop the *apothem_keys* top-level keys Apothem fully owns from *operator*.
|
|
164
|
+
|
|
165
|
+
Profile-derived materializer keys (hermes ``auxiliary``, qwen ``mcpServers``)
|
|
166
|
+
carry operator-data-shaped values the empty-profile template cannot
|
|
167
|
+
reproduce, so the structured value match never recognizes them. The adapter
|
|
168
|
+
declares the top-level namespaces it fully owns; those are removed wholesale
|
|
169
|
+
here, while every other operator key flows on to the value-level recursion.
|
|
170
|
+
"""
|
|
171
|
+
return {key: value for key, value in operator.items() if key not in apothem_keys}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _strip_apothem_json(
|
|
175
|
+
operator_text: str,
|
|
176
|
+
template_text: str,
|
|
177
|
+
*,
|
|
178
|
+
apothem_keys: frozenset[str] = frozenset(),
|
|
179
|
+
) -> str | None:
|
|
180
|
+
"""Return *operator_text* JSON with Apothem's *template_text* keys removed.
|
|
181
|
+
|
|
182
|
+
Parses both documents, removes Apothem's recursive contribution (plus any
|
|
183
|
+
*apothem_keys* top-level namespaces Apothem fully owns), and re-dumps the
|
|
184
|
+
operator remainder. Returns ``None`` when the remainder is an empty object
|
|
185
|
+
(the file became Apothem-only and the caller should delete it). Returns the
|
|
186
|
+
operator text unchanged when either side is unparseable JSON (never destroy
|
|
187
|
+
content the merge inverse cannot reason about).
|
|
188
|
+
"""
|
|
189
|
+
try:
|
|
190
|
+
operator = json.loads(operator_text)
|
|
191
|
+
template = json.loads(template_text)
|
|
192
|
+
except json.JSONDecodeError:
|
|
193
|
+
return operator_text
|
|
194
|
+
if isinstance(operator, dict) and apothem_keys:
|
|
195
|
+
operator = _drop_owned_top_keys(operator, apothem_keys)
|
|
196
|
+
reduced = _remove_apothem_values(operator, template)
|
|
197
|
+
if reduced is _REMOVE_KEY:
|
|
198
|
+
return None
|
|
199
|
+
if isinstance(reduced, dict) and not reduced:
|
|
200
|
+
return None
|
|
201
|
+
return json.dumps(reduced, indent=2, ensure_ascii=False) + "\n"
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _strip_apothem_yaml(
|
|
205
|
+
operator_text: str,
|
|
206
|
+
template_text: str,
|
|
207
|
+
*,
|
|
208
|
+
apothem_keys: frozenset[str] = frozenset(),
|
|
209
|
+
) -> str | None:
|
|
210
|
+
"""Return *operator_text* YAML with Apothem's *template_text* keys removed.
|
|
211
|
+
|
|
212
|
+
The YAML mirror of :func:`_strip_apothem_json`: parse both documents, remove
|
|
213
|
+
Apothem's recursive contribution (plus any *apothem_keys* top-level
|
|
214
|
+
namespaces Apothem fully owns), re-dump the operator remainder. Returns
|
|
215
|
+
``None`` when the remainder is empty (delete) and the operator text unchanged
|
|
216
|
+
when either side is unparseable (never destroy what the inverse cannot map).
|
|
217
|
+
PyYAML round-trips values, not comments, so a managed header comment is not
|
|
218
|
+
reproduced — operator value keys are what survive.
|
|
219
|
+
"""
|
|
220
|
+
try:
|
|
221
|
+
operator = yaml.safe_load(operator_text)
|
|
222
|
+
template = yaml.safe_load(template_text)
|
|
223
|
+
except yaml.YAMLError:
|
|
224
|
+
return operator_text
|
|
225
|
+
if operator is None:
|
|
226
|
+
# yaml.safe_load maps an empty OR a comment-only document to None.
|
|
227
|
+
# Apothem only ever contributes value keys, never bare comments, so a
|
|
228
|
+
# document that still carries text (operator comments) is pure operator
|
|
229
|
+
# content — preserve it. Delete only when the document is genuinely
|
|
230
|
+
# empty, mirroring _strip_apothem_json's "never destroy content the
|
|
231
|
+
# inverse cannot reason about" contract.
|
|
232
|
+
return None if not operator_text.strip() else operator_text
|
|
233
|
+
if not isinstance(operator, dict):
|
|
234
|
+
return operator_text
|
|
235
|
+
if apothem_keys:
|
|
236
|
+
operator = _drop_owned_top_keys(operator, apothem_keys)
|
|
237
|
+
template_dict = template if isinstance(template, dict) else {}
|
|
238
|
+
reduced = _remove_apothem_values(operator, template_dict)
|
|
239
|
+
if reduced is _REMOVE_KEY:
|
|
240
|
+
return None
|
|
241
|
+
if isinstance(reduced, dict) and not reduced:
|
|
242
|
+
return None
|
|
243
|
+
return yaml.safe_dump(reduced, sort_keys=False, allow_unicode=True)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _surgical_remove_from_target(
|
|
247
|
+
target: Path,
|
|
248
|
+
template_text: str,
|
|
249
|
+
*,
|
|
250
|
+
mode: str,
|
|
251
|
+
install_root: Path,
|
|
252
|
+
harness_name: str,
|
|
253
|
+
allowed_root: Path,
|
|
254
|
+
apothem_keys: frozenset[str] = frozenset(),
|
|
255
|
+
) -> MaterializationResult | None:
|
|
256
|
+
"""Surgically remove Apothem's contribution from one operator-owned target.
|
|
257
|
+
|
|
258
|
+
The reversal of the install-side operator-owned write. Backs the target up
|
|
259
|
+
under the Apothem backup root first, then:
|
|
260
|
+
|
|
261
|
+
- ``sentinel_merge`` anchors: strip the managed block via
|
|
262
|
+
:func:`remove_managed_block`; delete when the remainder is whitespace-only,
|
|
263
|
+
else atomic-rewrite the operator prose.
|
|
264
|
+
- ``.json`` / ``.yaml`` / ``.yml`` targets: remove only Apothem's keys/hooks
|
|
265
|
+
via the recursive structured stripper (operator-added/overridden keys
|
|
266
|
+
survive), plus any *apothem_keys* top-level namespaces Apothem fully owns;
|
|
267
|
+
delete when the remainder is empty, else atomic-rewrite.
|
|
268
|
+
- Any other suffix: delete when the file content equals the rendered template
|
|
269
|
+
(Apothem-owned, untouched); otherwise leave it in place (never destroy
|
|
270
|
+
unrecognized operator content) — the backup already captured it.
|
|
271
|
+
|
|
272
|
+
Returns ``None`` when *target* does not exist (nothing to remove). The
|
|
273
|
+
*template_text* is the rendered Apothem template (path tokens already
|
|
274
|
+
substituted) the install wrote, used to identify Apothem's contribution.
|
|
275
|
+
"""
|
|
276
|
+
if not target.exists() or not target.is_file():
|
|
277
|
+
return None
|
|
278
|
+
target_error = _validate_target_path(
|
|
279
|
+
target, allowed_root=allowed_root, operation="surgical_uninstall"
|
|
280
|
+
)
|
|
281
|
+
if target_error is not None:
|
|
282
|
+
return target_error
|
|
283
|
+
try:
|
|
284
|
+
existing = target.read_text(encoding="utf-8")
|
|
285
|
+
except OSError: # pragma: no cover - defensive
|
|
286
|
+
return None
|
|
287
|
+
backup = backup_existing(
|
|
288
|
+
target,
|
|
289
|
+
install_root=install_root,
|
|
290
|
+
harness_name=harness_name,
|
|
291
|
+
allowed_root=allowed_root,
|
|
292
|
+
)
|
|
293
|
+
suffix = target.suffix.lower()
|
|
294
|
+
remainder: str | None
|
|
295
|
+
if mode == "sentinel_merge":
|
|
296
|
+
stripped = remove_managed_block(existing)
|
|
297
|
+
remainder = None if not stripped.strip() else stripped
|
|
298
|
+
elif suffix == ".json":
|
|
299
|
+
remainder = _strip_apothem_json(
|
|
300
|
+
existing, template_text, apothem_keys=apothem_keys
|
|
301
|
+
)
|
|
302
|
+
elif suffix in {".yaml", ".yml"}:
|
|
303
|
+
remainder = _strip_apothem_yaml(
|
|
304
|
+
existing, template_text, apothem_keys=apothem_keys
|
|
305
|
+
)
|
|
306
|
+
else:
|
|
307
|
+
# Unrecognized operator content: only delete an exact template copy.
|
|
308
|
+
remainder = None if existing == template_text else existing
|
|
309
|
+
if remainder is None:
|
|
310
|
+
result = _guarded_unlink(
|
|
311
|
+
target, allowed_root=allowed_root, operation="surgical_uninstall"
|
|
312
|
+
)
|
|
313
|
+
elif remainder == existing:
|
|
314
|
+
result = _result(
|
|
315
|
+
"unchanged",
|
|
316
|
+
"surgical_uninstall",
|
|
317
|
+
target,
|
|
318
|
+
"no Apothem contribution to remove",
|
|
319
|
+
)
|
|
320
|
+
else:
|
|
321
|
+
result = write_bytes_safely(
|
|
322
|
+
target,
|
|
323
|
+
remainder.encode("utf-8"),
|
|
324
|
+
install_root=install_root,
|
|
325
|
+
harness_name=harness_name,
|
|
326
|
+
operation="surgical_uninstall",
|
|
327
|
+
allowed_root=allowed_root,
|
|
328
|
+
)
|
|
329
|
+
return replace(
|
|
330
|
+
result,
|
|
331
|
+
backup_path=_path_text(backup) if backup is not None else result.backup_path,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def surgically_remove_materialized_config(
|
|
336
|
+
target: Path,
|
|
337
|
+
template_text: str,
|
|
338
|
+
*,
|
|
339
|
+
install_root: Path,
|
|
340
|
+
harness_name: str,
|
|
341
|
+
allowed_root: Path | None = None,
|
|
342
|
+
apothem_keys: frozenset[str] = frozenset(),
|
|
343
|
+
) -> MaterializationResult | None:
|
|
344
|
+
"""Surgically remove Apothem's keys from an adapter-rendered native config.
|
|
345
|
+
|
|
346
|
+
The adapter-uninstall entry point for materializer configs that are not
|
|
347
|
+
manifest entries (hermes ``config.yaml``, open-claw / opencode / qwen-code
|
|
348
|
+
JSON). *template_text* is the config the adapter's materializer renders (from
|
|
349
|
+
an empty profile) used to recognize Apothem's structural contribution.
|
|
350
|
+
*apothem_keys* names the top-level namespaces Apothem fully owns whose values
|
|
351
|
+
are profile-derived (e.g. hermes ``auxiliary``, qwen ``mcpServers``) — these
|
|
352
|
+
carry operator-data-shaped values the empty-profile template cannot reproduce,
|
|
353
|
+
so they are stripped wholesale. Operator-added keys outside this set are
|
|
354
|
+
preserved and an Apothem-only file is deleted. Backs the target up before
|
|
355
|
+
mutation. Returns ``None`` when the target is absent. This is the surgical
|
|
356
|
+
replacement for the retired whole-file ``backup_file_to_sibling`` rename.
|
|
357
|
+
"""
|
|
358
|
+
return _surgical_remove_from_target(
|
|
359
|
+
target,
|
|
360
|
+
template_text,
|
|
361
|
+
mode="write_text",
|
|
362
|
+
install_root=install_root,
|
|
363
|
+
harness_name=harness_name,
|
|
364
|
+
allowed_root=allowed_root or install_root,
|
|
365
|
+
apothem_keys=apothem_keys,
|
|
366
|
+
)
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Directory tree replace / sweep / single-file-directory write primitives."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import contextlib
|
|
8
|
+
import os
|
|
9
|
+
import shutil
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from .install_driver_backup import _replace_path, backup_existing, write_bytes_safely
|
|
13
|
+
from .install_driver_pathsafety import _validate_target_path
|
|
14
|
+
from .install_driver_types import (
|
|
15
|
+
IgnoreFn,
|
|
16
|
+
MaterializationResult,
|
|
17
|
+
_handle_rm_error,
|
|
18
|
+
_result,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _iter_relative_files(root: Path, ignore: IgnoreFn | None) -> list[Path]:
|
|
23
|
+
"""Return relative file paths under *root* after applying ignore filters."""
|
|
24
|
+
relative_files: list[Path] = []
|
|
25
|
+
for directory, dirs, files in os.walk(root):
|
|
26
|
+
directory_path = Path(directory)
|
|
27
|
+
ignored = set(ignore(directory, dirs + files)) if ignore else set()
|
|
28
|
+
dirs[:] = sorted(name for name in dirs if name not in ignored)
|
|
29
|
+
for name in sorted(files):
|
|
30
|
+
if name not in ignored:
|
|
31
|
+
relative_files.append((directory_path / name).relative_to(root))
|
|
32
|
+
return relative_files
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _directory_contents_equal(src: Path, dst: Path, ignore: IgnoreFn) -> bool:
|
|
36
|
+
"""Return True when two directories have identical emitted file bytes.
|
|
37
|
+
|
|
38
|
+
The ignore filter applies to BOTH sides: the installed tree accumulates
|
|
39
|
+
interpreter artifacts (``__pycache__`` beside the hook scripts that run
|
|
40
|
+
in place at the harness root), and those generated entries must not
|
|
41
|
+
defeat the comparison — otherwise every update re-copies an unchanged
|
|
42
|
+
directory.
|
|
43
|
+
"""
|
|
44
|
+
if not src.is_dir() or not dst.is_dir():
|
|
45
|
+
return False
|
|
46
|
+
source_files = _iter_relative_files(src, ignore)
|
|
47
|
+
target_files = _iter_relative_files(dst, ignore)
|
|
48
|
+
if source_files != target_files:
|
|
49
|
+
return False
|
|
50
|
+
for relative in source_files:
|
|
51
|
+
if (src / relative).read_bytes() != (dst / relative).read_bytes():
|
|
52
|
+
return False
|
|
53
|
+
return True
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def replace_tree(
|
|
57
|
+
src: Path,
|
|
58
|
+
dst: Path,
|
|
59
|
+
ignore: IgnoreFn,
|
|
60
|
+
*,
|
|
61
|
+
install_root: Path | None = None,
|
|
62
|
+
harness_name: str = "manual",
|
|
63
|
+
allowed_root: Path | None = None,
|
|
64
|
+
) -> MaterializationResult:
|
|
65
|
+
"""Replace ``dst/`` with a fresh copy of ``src/``, eliminating stale files."""
|
|
66
|
+
if not src.is_dir():
|
|
67
|
+
return _result(
|
|
68
|
+
"skipped",
|
|
69
|
+
"replace_tree",
|
|
70
|
+
dst,
|
|
71
|
+
"source directory does not exist",
|
|
72
|
+
source=src,
|
|
73
|
+
)
|
|
74
|
+
root = install_root or dst.parent
|
|
75
|
+
target_error = _validate_target_path(
|
|
76
|
+
dst,
|
|
77
|
+
allowed_root=allowed_root or root,
|
|
78
|
+
operation="replace_tree",
|
|
79
|
+
)
|
|
80
|
+
if target_error is not None:
|
|
81
|
+
return target_error
|
|
82
|
+
if dst.exists() and _directory_contents_equal(src, dst, ignore):
|
|
83
|
+
return _result(
|
|
84
|
+
"unchanged",
|
|
85
|
+
"replace_tree",
|
|
86
|
+
dst,
|
|
87
|
+
"directory already matches",
|
|
88
|
+
source=src,
|
|
89
|
+
)
|
|
90
|
+
existed = dst.exists()
|
|
91
|
+
backup: Path | None = None
|
|
92
|
+
if existed:
|
|
93
|
+
backup = backup_existing(
|
|
94
|
+
dst,
|
|
95
|
+
install_root=root,
|
|
96
|
+
harness_name=harness_name,
|
|
97
|
+
allowed_root=allowed_root or root,
|
|
98
|
+
)
|
|
99
|
+
shutil.rmtree(dst, onerror=_handle_rm_error)
|
|
100
|
+
shutil.copytree(src, dst, ignore=ignore)
|
|
101
|
+
return _result(
|
|
102
|
+
"updated" if existed else "created",
|
|
103
|
+
"replace_tree",
|
|
104
|
+
dst,
|
|
105
|
+
"copied directory tree",
|
|
106
|
+
source=src,
|
|
107
|
+
backup_path=backup,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def sweep_stale(
|
|
112
|
+
stale_sweep: list[str],
|
|
113
|
+
root: Path,
|
|
114
|
+
*,
|
|
115
|
+
harness_name: str = "manual",
|
|
116
|
+
allowed_root: Path | None = None,
|
|
117
|
+
) -> list[MaterializationResult]:
|
|
118
|
+
"""Remove each stale top-level path under *root* from earlier layouts.
|
|
119
|
+
|
|
120
|
+
Directories are removed recursively via ``shutil.rmtree``; files are
|
|
121
|
+
removed via ``Path.unlink``. Missing paths are silently skipped so a
|
|
122
|
+
re-install is idempotent.
|
|
123
|
+
"""
|
|
124
|
+
results: list[MaterializationResult] = []
|
|
125
|
+
for legacy in stale_sweep:
|
|
126
|
+
stale = root / legacy
|
|
127
|
+
target_error = _validate_target_path(
|
|
128
|
+
stale,
|
|
129
|
+
allowed_root=allowed_root or root,
|
|
130
|
+
operation="sweep_stale",
|
|
131
|
+
)
|
|
132
|
+
if target_error is not None:
|
|
133
|
+
results.append(target_error)
|
|
134
|
+
continue
|
|
135
|
+
if stale.is_dir():
|
|
136
|
+
backup = backup_existing(
|
|
137
|
+
stale,
|
|
138
|
+
install_root=root,
|
|
139
|
+
harness_name=harness_name,
|
|
140
|
+
allowed_root=allowed_root or root,
|
|
141
|
+
)
|
|
142
|
+
shutil.rmtree(stale, onerror=_handle_rm_error)
|
|
143
|
+
results.append(
|
|
144
|
+
_result(
|
|
145
|
+
"updated",
|
|
146
|
+
"sweep_stale",
|
|
147
|
+
stale,
|
|
148
|
+
"removed stale directory",
|
|
149
|
+
backup_path=backup,
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
elif stale.is_file():
|
|
153
|
+
backup = backup_existing(
|
|
154
|
+
stale,
|
|
155
|
+
install_root=root,
|
|
156
|
+
harness_name=harness_name,
|
|
157
|
+
allowed_root=allowed_root or root,
|
|
158
|
+
)
|
|
159
|
+
with contextlib.suppress(OSError): # pragma: no cover
|
|
160
|
+
stale.unlink()
|
|
161
|
+
results.append(
|
|
162
|
+
_result(
|
|
163
|
+
"updated",
|
|
164
|
+
"sweep_stale",
|
|
165
|
+
stale,
|
|
166
|
+
"removed stale file",
|
|
167
|
+
backup_path=backup,
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
else:
|
|
171
|
+
results.append(
|
|
172
|
+
_result("unchanged", "sweep_stale", stale, "stale path absent")
|
|
173
|
+
)
|
|
174
|
+
return results
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _single_file_directory_matches(
|
|
178
|
+
directory: Path,
|
|
179
|
+
filename: str,
|
|
180
|
+
content: str,
|
|
181
|
+
) -> bool:
|
|
182
|
+
"""Return True when *directory* contains exactly one matching file."""
|
|
183
|
+
if not directory.is_dir():
|
|
184
|
+
return False
|
|
185
|
+
children = sorted(child.name for child in directory.iterdir())
|
|
186
|
+
return (
|
|
187
|
+
children == [filename]
|
|
188
|
+
and (directory / filename).read_text(encoding="utf-8") == content
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _write_single_file_directory(
|
|
193
|
+
directory: Path,
|
|
194
|
+
filename: str,
|
|
195
|
+
content: str,
|
|
196
|
+
*,
|
|
197
|
+
root: Path,
|
|
198
|
+
harness_name: str,
|
|
199
|
+
operation: str,
|
|
200
|
+
source: Path,
|
|
201
|
+
allowed_root: Path,
|
|
202
|
+
) -> MaterializationResult:
|
|
203
|
+
"""Replace or create a generated directory containing one text file."""
|
|
204
|
+
target_error = _validate_target_path(
|
|
205
|
+
directory,
|
|
206
|
+
allowed_root=allowed_root,
|
|
207
|
+
operation=operation,
|
|
208
|
+
)
|
|
209
|
+
if target_error is not None:
|
|
210
|
+
return target_error
|
|
211
|
+
if _single_file_directory_matches(directory, filename, content):
|
|
212
|
+
return _result(
|
|
213
|
+
"unchanged",
|
|
214
|
+
operation,
|
|
215
|
+
directory,
|
|
216
|
+
"generated directory already matches",
|
|
217
|
+
source=source,
|
|
218
|
+
)
|
|
219
|
+
removed = _replace_path(
|
|
220
|
+
directory,
|
|
221
|
+
install_root=root,
|
|
222
|
+
harness_name=harness_name,
|
|
223
|
+
allowed_root=allowed_root,
|
|
224
|
+
)
|
|
225
|
+
if removed is not None and removed.outcome == "error":
|
|
226
|
+
return removed
|
|
227
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
228
|
+
write_result = write_bytes_safely(
|
|
229
|
+
directory / filename,
|
|
230
|
+
content.encode("utf-8"),
|
|
231
|
+
install_root=root,
|
|
232
|
+
harness_name=harness_name,
|
|
233
|
+
operation=operation,
|
|
234
|
+
source=source,
|
|
235
|
+
allowed_root=allowed_root,
|
|
236
|
+
)
|
|
237
|
+
if write_result.outcome == "error":
|
|
238
|
+
return write_result
|
|
239
|
+
return _result(
|
|
240
|
+
"updated" if removed else "created",
|
|
241
|
+
operation,
|
|
242
|
+
directory,
|
|
243
|
+
"wrote generated directory",
|
|
244
|
+
source=source,
|
|
245
|
+
backup_path=Path(removed.backup_path)
|
|
246
|
+
if removed and removed.backup_path
|
|
247
|
+
else None,
|
|
248
|
+
)
|