@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,535 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Backup capture, atomic write, restore, and install-ledger projection."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import contextlib
|
|
8
|
+
import hashlib
|
|
9
|
+
import shutil
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from apothem.harnesses._shared import install_driver
|
|
13
|
+
from apothem.lib import atomic_io, install_ledger
|
|
14
|
+
from apothem.lib.install_ledger import LedgerRecord, LedgerTarget
|
|
15
|
+
|
|
16
|
+
from .install_driver_pathsafety import (
|
|
17
|
+
_allowed_write_root,
|
|
18
|
+
_normalized,
|
|
19
|
+
_root_for,
|
|
20
|
+
_validate_target_path,
|
|
21
|
+
_within_allowed_root,
|
|
22
|
+
)
|
|
23
|
+
from .install_driver_types import (
|
|
24
|
+
MaterializationResult,
|
|
25
|
+
MaterializationRun,
|
|
26
|
+
_handle_rm_error,
|
|
27
|
+
_result,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _unique_path(path: Path) -> Path:
|
|
32
|
+
"""Return *path* or a suffixed sibling when the path already exists."""
|
|
33
|
+
if not path.exists():
|
|
34
|
+
return path
|
|
35
|
+
for index in range(1, 1000):
|
|
36
|
+
candidate = path.with_name(f"{path.name}.{index}")
|
|
37
|
+
if not candidate.exists():
|
|
38
|
+
return candidate
|
|
39
|
+
raise RuntimeError(f"cannot allocate backup path for {path}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _reserve_unique_backup(base: Path, *, is_dir: bool) -> Path:
|
|
43
|
+
"""Atomically reserve a unique backup destination, suffixing on collision.
|
|
44
|
+
|
|
45
|
+
Closes the check-then-act race that lets two concurrent install passes
|
|
46
|
+
resolve the same ``base`` within a single timestamp second (the granularity
|
|
47
|
+
of ``_timestamp_slug``). The first reserver gets ``base``; subsequent
|
|
48
|
+
reservers get ``base.1``, ``base.2`` ... . The reservation is the
|
|
49
|
+
filesystem object itself — an empty directory for tree backups, an
|
|
50
|
+
exclusively-created placeholder file for single-file backups — so the win
|
|
51
|
+
is atomic across processes rather than a non-atomic existence check that
|
|
52
|
+
races into ``FileExistsError`` (Windows ``WinError 183``).
|
|
53
|
+
|
|
54
|
+
The uncontended path returns ``base`` unchanged, preserving the documented
|
|
55
|
+
``~/.apothem/backups/<timestamp>/<harness>/<relative-path>`` layout for the
|
|
56
|
+
common case; the suffix appears only on a genuine same-second collision.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
base: The desired destination path under the backup root.
|
|
60
|
+
is_dir: ``True`` when *base* names a directory backup (reserved via
|
|
61
|
+
``mkdir``), ``False`` when it names a file backup (reserved via an
|
|
62
|
+
exclusive ``touch``).
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
The reserved destination path. Callers populate it: a directory backup
|
|
66
|
+
copies into it with ``dirs_exist_ok=True``; a file backup overwrites
|
|
67
|
+
the placeholder via ``copy2``.
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
RuntimeError: When 1000 suffixed candidates are all taken.
|
|
71
|
+
"""
|
|
72
|
+
base.parent.mkdir(parents=True, exist_ok=True)
|
|
73
|
+
for index in range(1000):
|
|
74
|
+
candidate = base if index == 0 else base.with_name(f"{base.name}.{index}")
|
|
75
|
+
try:
|
|
76
|
+
if is_dir:
|
|
77
|
+
candidate.mkdir()
|
|
78
|
+
else:
|
|
79
|
+
candidate.touch(exist_ok=False)
|
|
80
|
+
except FileExistsError:
|
|
81
|
+
continue
|
|
82
|
+
return candidate
|
|
83
|
+
raise RuntimeError(f"cannot allocate backup path for {base}")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _sibling_backup_path(target: Path) -> Path:
|
|
87
|
+
"""Return a timestamped sibling backup path for a file target."""
|
|
88
|
+
timestamp = install_driver._timestamp_slug()
|
|
89
|
+
return _unique_path(target.parent / f"{target.name}.{timestamp}.bak")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _replace_path(
|
|
93
|
+
target: Path,
|
|
94
|
+
*,
|
|
95
|
+
install_root: Path,
|
|
96
|
+
harness_name: str,
|
|
97
|
+
allowed_root: Path | None = None,
|
|
98
|
+
) -> MaterializationResult | None:
|
|
99
|
+
"""Backup and remove one existing target before writing its replacement."""
|
|
100
|
+
target_error = _validate_target_path(
|
|
101
|
+
target,
|
|
102
|
+
allowed_root=allowed_root or install_root,
|
|
103
|
+
operation="remove_existing",
|
|
104
|
+
)
|
|
105
|
+
if target_error is not None:
|
|
106
|
+
return target_error
|
|
107
|
+
if not target.exists():
|
|
108
|
+
return None
|
|
109
|
+
backup = backup_existing(
|
|
110
|
+
target,
|
|
111
|
+
install_root=install_root,
|
|
112
|
+
harness_name=harness_name,
|
|
113
|
+
allowed_root=allowed_root or install_root,
|
|
114
|
+
)
|
|
115
|
+
if target.is_dir():
|
|
116
|
+
shutil.rmtree(target, onerror=_handle_rm_error)
|
|
117
|
+
else:
|
|
118
|
+
with contextlib.suppress(OSError): # pragma: no cover
|
|
119
|
+
target.unlink()
|
|
120
|
+
return _result(
|
|
121
|
+
"updated",
|
|
122
|
+
"remove_existing",
|
|
123
|
+
target,
|
|
124
|
+
"removed existing target before replacement",
|
|
125
|
+
backup_path=backup,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def backup_file_to_sibling(target: Path) -> Path | None:
|
|
130
|
+
"""Rename *target* to a timestamped sibling backup when it is a file."""
|
|
131
|
+
if not target.is_file():
|
|
132
|
+
return None
|
|
133
|
+
backup = _sibling_backup_path(target)
|
|
134
|
+
target.rename(backup)
|
|
135
|
+
return backup
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _backup_relative_path(target: Path, boundary_root: Path) -> Path:
|
|
139
|
+
"""Return *target* relative to the allowed-write boundary root.
|
|
140
|
+
|
|
141
|
+
The boundary is ``_allowed_write_root`` (the home/parent for user-scope, the
|
|
142
|
+
project root for project-scope) — NOT the narrower harness install root. A
|
|
143
|
+
harness whose targets reach outside its own root (codex writes
|
|
144
|
+
``~/.config/apothem/…`` and ``~/.agents/skills/…`` alongside ``~/.codex/…``)
|
|
145
|
+
therefore keeps each target's full relative path under the backup tree, so two
|
|
146
|
+
distinct out-of-root files never collapse to one basename and
|
|
147
|
+
:func:`restore_backup` can rebuild the original absolute path from the same
|
|
148
|
+
boundary. The basename fallback only fires for a target genuinely outside the
|
|
149
|
+
boundary (a defect the write path already refuses).
|
|
150
|
+
"""
|
|
151
|
+
try:
|
|
152
|
+
return target.resolve().relative_to(boundary_root.resolve())
|
|
153
|
+
except ValueError:
|
|
154
|
+
return Path(target.name)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def backup_existing(
|
|
158
|
+
target: Path,
|
|
159
|
+
*,
|
|
160
|
+
install_root: Path,
|
|
161
|
+
harness_name: str,
|
|
162
|
+
allowed_root: Path | None = None,
|
|
163
|
+
) -> Path | None:
|
|
164
|
+
"""Copy an existing target into the Apothem backup root before mutation.
|
|
165
|
+
|
|
166
|
+
The backup is keyed by *target*'s path relative to *allowed_root* (the
|
|
167
|
+
allowed-write boundary; defaults to *install_root* for callers that share the
|
|
168
|
+
two), so the backup tree mirrors the boundary-relative layout that
|
|
169
|
+
:func:`restore_backup` reconstructs against.
|
|
170
|
+
"""
|
|
171
|
+
if not target.exists():
|
|
172
|
+
return None
|
|
173
|
+
rel = _backup_relative_path(target, allowed_root or install_root)
|
|
174
|
+
base = (
|
|
175
|
+
install_driver.BACKUP_ROOT
|
|
176
|
+
/ install_driver._timestamp_slug()
|
|
177
|
+
/ harness_name
|
|
178
|
+
/ rel
|
|
179
|
+
)
|
|
180
|
+
if target.is_dir():
|
|
181
|
+
backup = _reserve_unique_backup(base, is_dir=True)
|
|
182
|
+
shutil.copytree(target, backup, dirs_exist_ok=True)
|
|
183
|
+
else:
|
|
184
|
+
backup = _reserve_unique_backup(base, is_dir=False)
|
|
185
|
+
shutil.copy2(target, backup)
|
|
186
|
+
return backup
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _write_file_atomically(target: Path, data: bytes) -> None:
|
|
190
|
+
"""Write *data* through a sibling temp file and atomic replace.
|
|
191
|
+
|
|
192
|
+
Delegates to :func:`apothem.lib.atomic_io.write_bytes_atomically` so the
|
|
193
|
+
engine has a single atomic-write implementation shared with the state
|
|
194
|
+
stores and the append-only ledger.
|
|
195
|
+
"""
|
|
196
|
+
atomic_io.write_bytes_atomically(target, data)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def write_bytes_safely(
|
|
200
|
+
target: Path,
|
|
201
|
+
content: bytes,
|
|
202
|
+
*,
|
|
203
|
+
install_root: Path,
|
|
204
|
+
harness_name: str,
|
|
205
|
+
operation: str = "write_bytes",
|
|
206
|
+
source: Path | None = None,
|
|
207
|
+
allowed_root: Path | None = None,
|
|
208
|
+
) -> MaterializationResult:
|
|
209
|
+
"""Write bytes with no-op detection, backup-before-replace, and atomic swap."""
|
|
210
|
+
target_error = _validate_target_path(
|
|
211
|
+
target,
|
|
212
|
+
allowed_root=allowed_root or install_root,
|
|
213
|
+
operation=operation,
|
|
214
|
+
)
|
|
215
|
+
if target_error is not None:
|
|
216
|
+
return target_error
|
|
217
|
+
try:
|
|
218
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
219
|
+
except OSError as exc:
|
|
220
|
+
return _result(
|
|
221
|
+
"error",
|
|
222
|
+
operation,
|
|
223
|
+
target,
|
|
224
|
+
f"could not create target directory: {exc}",
|
|
225
|
+
source=source,
|
|
226
|
+
)
|
|
227
|
+
existed = target.exists()
|
|
228
|
+
backup: Path | None = None
|
|
229
|
+
if existed:
|
|
230
|
+
try:
|
|
231
|
+
if target.read_bytes() == content:
|
|
232
|
+
return _result(
|
|
233
|
+
"unchanged",
|
|
234
|
+
operation,
|
|
235
|
+
target,
|
|
236
|
+
"content already matches",
|
|
237
|
+
source=source,
|
|
238
|
+
)
|
|
239
|
+
except OSError:
|
|
240
|
+
# Best-effort read: backup and replacement still report any write error.
|
|
241
|
+
pass
|
|
242
|
+
try:
|
|
243
|
+
backup = backup_existing(
|
|
244
|
+
target,
|
|
245
|
+
install_root=install_root,
|
|
246
|
+
harness_name=harness_name,
|
|
247
|
+
allowed_root=allowed_root or install_root,
|
|
248
|
+
)
|
|
249
|
+
except OSError as exc:
|
|
250
|
+
return _result(
|
|
251
|
+
"error",
|
|
252
|
+
operation,
|
|
253
|
+
target,
|
|
254
|
+
f"could not back up existing target: {exc}",
|
|
255
|
+
source=source,
|
|
256
|
+
)
|
|
257
|
+
try:
|
|
258
|
+
install_driver._write_file_atomically(target, content)
|
|
259
|
+
except OSError as exc:
|
|
260
|
+
return _result(
|
|
261
|
+
"error",
|
|
262
|
+
operation,
|
|
263
|
+
target,
|
|
264
|
+
f"could not write target atomically: {exc}",
|
|
265
|
+
source=source,
|
|
266
|
+
backup_path=backup,
|
|
267
|
+
)
|
|
268
|
+
return _result(
|
|
269
|
+
"updated" if existed else "created",
|
|
270
|
+
operation,
|
|
271
|
+
target,
|
|
272
|
+
"wrote file",
|
|
273
|
+
source=source,
|
|
274
|
+
backup_path=backup,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _guarded_unlink(
|
|
279
|
+
target: Path,
|
|
280
|
+
*,
|
|
281
|
+
allowed_root: Path,
|
|
282
|
+
operation: str,
|
|
283
|
+
) -> MaterializationResult:
|
|
284
|
+
"""Delete *target* after confirming it stays inside *allowed_root*.
|
|
285
|
+
|
|
286
|
+
The deletion path for an Apothem-only file the surgical uninstall reduced to
|
|
287
|
+
empty. Guards the unlink with the same path-escape / symlink check the write
|
|
288
|
+
paths use, so an uninstall can never delete outside the materialization root.
|
|
289
|
+
"""
|
|
290
|
+
target_error = _validate_target_path(
|
|
291
|
+
target, allowed_root=allowed_root, operation=operation
|
|
292
|
+
)
|
|
293
|
+
if target_error is not None:
|
|
294
|
+
return target_error
|
|
295
|
+
with contextlib.suppress(OSError): # pragma: no cover - defensive
|
|
296
|
+
target.unlink()
|
|
297
|
+
return _result("updated", operation, target, "removed Apothem-only file")
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _install_lock_path(harness_name: str, root: Path) -> Path:
|
|
301
|
+
"""Return the per-(harness, root) advisory install-lock path.
|
|
302
|
+
|
|
303
|
+
Keyed by harness AND a digest of the resolved root so two installs into the
|
|
304
|
+
same root serialize behind one lock while installs into different roots run
|
|
305
|
+
concurrently. The lock lives under the (test-isolatable) ledger state root,
|
|
306
|
+
not inside the install tree.
|
|
307
|
+
"""
|
|
308
|
+
digest = hashlib.sha256(str(root.resolve()).encode("utf-8")).hexdigest()[:16]
|
|
309
|
+
return install_ledger.STATE_ROOT / harness_name / f"{digest}.install.lock"
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _compensating_rollback(
|
|
313
|
+
results: list[MaterializationResult], *, allowed_root: Path
|
|
314
|
+
) -> None:
|
|
315
|
+
"""Undo the on-disk writes recorded in *results* (newest first).
|
|
316
|
+
|
|
317
|
+
Reverses a partially-applied install pass: a target with a captured backup is
|
|
318
|
+
restored from it (the operator's prior bytes or tree); a target written fresh
|
|
319
|
+
(no backup) is removed (its pre-install state was absence). Targets outside
|
|
320
|
+
*allowed_root* are skipped defensively. Best-effort and exception-safe — it
|
|
321
|
+
runs from an ``except`` handler that re-raises the original failure, so it must
|
|
322
|
+
not raise itself.
|
|
323
|
+
"""
|
|
324
|
+
for result in reversed(results):
|
|
325
|
+
if result.outcome not in {"created", "updated"}:
|
|
326
|
+
continue
|
|
327
|
+
target = Path(result.path)
|
|
328
|
+
if not _within_allowed_root(_normalized(target), _normalized(allowed_root)):
|
|
329
|
+
continue
|
|
330
|
+
with contextlib.suppress(OSError):
|
|
331
|
+
if result.backup_path:
|
|
332
|
+
backup = Path(result.backup_path)
|
|
333
|
+
if backup.is_dir():
|
|
334
|
+
if target.exists():
|
|
335
|
+
shutil.rmtree(target, onerror=_handle_rm_error)
|
|
336
|
+
shutil.copytree(backup, target, dirs_exist_ok=True)
|
|
337
|
+
elif backup.is_file():
|
|
338
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
339
|
+
shutil.copy2(backup, target)
|
|
340
|
+
elif target.is_dir():
|
|
341
|
+
shutil.rmtree(target, onerror=_handle_rm_error)
|
|
342
|
+
elif target.exists():
|
|
343
|
+
target.unlink()
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
# Result outcomes that represent a materialized on-disk file worth recording in
|
|
347
|
+
# the install ledger (a created/updated/unchanged target). A clean install yields
|
|
348
|
+
# only created/updated, so this set equals the run's ``files_written`` there while
|
|
349
|
+
# also capturing the full managed set on an idempotent re-install.
|
|
350
|
+
_LEDGER_OUTCOMES: frozenset[str] = frozenset({"created", "updated", "unchanged"})
|
|
351
|
+
|
|
352
|
+
# Result operations that are NOT standalone installed ledger targets: the
|
|
353
|
+
# per-surface data-home files (reversed wholesale by the data-home cleanup on
|
|
354
|
+
# uninstall, not file-by-file), the advisory capability-projection note, and the
|
|
355
|
+
# stale-sweep pass (a removal that emits an ``unchanged`` result for every absent
|
|
356
|
+
# legacy path — recording those would log phantom targets that were never written).
|
|
357
|
+
_NON_LEDGER_OPERATIONS: frozenset[str] = frozenset(
|
|
358
|
+
{"data_surface", "capability_projection", "sweep_stale"}
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _ledger_targets(run: MaterializationRun) -> tuple[LedgerTarget, ...]:
|
|
363
|
+
"""Project a materialization run's written files to typed ledger targets.
|
|
364
|
+
|
|
365
|
+
One :class:`LedgerTarget` per created/updated/unchanged file result, carrying
|
|
366
|
+
the path, the install mode (the result ``operation`` — ``sentinel_merge`` /
|
|
367
|
+
``write_text`` / a tree mode), the ownership class, and the backup reference
|
|
368
|
+
captured during the write. Data-surface and advisory results are excluded;
|
|
369
|
+
duplicate paths collapse to the first occurrence.
|
|
370
|
+
"""
|
|
371
|
+
targets: list[LedgerTarget] = []
|
|
372
|
+
seen: set[str] = set()
|
|
373
|
+
for result in run.results:
|
|
374
|
+
if result.outcome not in _LEDGER_OUTCOMES:
|
|
375
|
+
continue
|
|
376
|
+
if result.operation in _NON_LEDGER_OPERATIONS:
|
|
377
|
+
continue
|
|
378
|
+
if result.path in seen:
|
|
379
|
+
continue
|
|
380
|
+
seen.add(result.path)
|
|
381
|
+
targets.append(
|
|
382
|
+
LedgerTarget(
|
|
383
|
+
path=result.path,
|
|
384
|
+
mode=result.operation,
|
|
385
|
+
ownership_class=result.detail.get("ownership_class", "operator-owned"),
|
|
386
|
+
backup_ref=result.backup_path,
|
|
387
|
+
)
|
|
388
|
+
)
|
|
389
|
+
return tuple(targets)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def record_install(run: MaterializationRun, *, root: Path) -> LedgerRecord | None:
|
|
393
|
+
"""Append an install record for *run* to the per-harness ledger; return it.
|
|
394
|
+
|
|
395
|
+
The record captures every file the pass wrote (including the materializer
|
|
396
|
+
adapters' native configs, which are part of the adapter's combined run) so
|
|
397
|
+
uninstall and rollback can reverse exactly what was installed rather than
|
|
398
|
+
re-deriving intent from the live manifest. A dry-run pass records nothing
|
|
399
|
+
(returns ``None``); a pass that wrote nothing still records an empty-target
|
|
400
|
+
install marker so the harness+root has a latest record.
|
|
401
|
+
"""
|
|
402
|
+
if run.dry_run:
|
|
403
|
+
return None
|
|
404
|
+
record = LedgerRecord.create(
|
|
405
|
+
harness=run.harness,
|
|
406
|
+
root=root,
|
|
407
|
+
kind="install",
|
|
408
|
+
targets=_ledger_targets(run),
|
|
409
|
+
)
|
|
410
|
+
install_ledger.append_record(record)
|
|
411
|
+
return record
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def finalize_install(run: MaterializationRun, *, root: Path) -> MaterializationRun:
|
|
415
|
+
"""Record *run* in the install ledger and return it unchanged.
|
|
416
|
+
|
|
417
|
+
The pass-through an adapter's ``install``/``update`` wraps around its final
|
|
418
|
+
:class:`MaterializationRun` so every supported install surface (the CLI, the
|
|
419
|
+
plugin shim, a direct adapter call) writes the ledger record uniformly at the
|
|
420
|
+
one layer that sees the complete run — manifest targets, the projected
|
|
421
|
+
instruction anchor, and any materializer-rendered native config alike.
|
|
422
|
+
"""
|
|
423
|
+
record_install(run, root=root)
|
|
424
|
+
return run
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def list_backup_timestamps(harness_name: str | None = None) -> list[str]:
|
|
428
|
+
"""Return available backup timestamps under the Apothem backup root.
|
|
429
|
+
|
|
430
|
+
Each timestamp is a ``~/.apothem/backups/<timestamp>/`` directory created
|
|
431
|
+
before a mutating install pass. When *harness_name* is given, only
|
|
432
|
+
timestamps carrying a backup set for that harness are returned. Results are
|
|
433
|
+
sorted oldest-to-newest (the timestamp slug sorts lexically by time).
|
|
434
|
+
"""
|
|
435
|
+
if not install_driver.BACKUP_ROOT.is_dir():
|
|
436
|
+
return []
|
|
437
|
+
stamps: list[str] = []
|
|
438
|
+
for ts_dir in sorted(install_driver.BACKUP_ROOT.iterdir()):
|
|
439
|
+
if not ts_dir.is_dir():
|
|
440
|
+
continue
|
|
441
|
+
if harness_name is None or (ts_dir / harness_name).is_dir():
|
|
442
|
+
stamps.append(ts_dir.name)
|
|
443
|
+
return stamps
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def restore_backup(
|
|
447
|
+
harness_name: str,
|
|
448
|
+
timestamp: str,
|
|
449
|
+
*,
|
|
450
|
+
harness_root: Path | None = None,
|
|
451
|
+
project_root: Path | None = None,
|
|
452
|
+
only_refs: frozenset[str] | None = None,
|
|
453
|
+
) -> list[MaterializationResult]:
|
|
454
|
+
"""Restore a harness's files from a ``~/.apothem/backups/<timestamp>`` set.
|
|
455
|
+
|
|
456
|
+
The backup tree at ``BACKUP_ROOT/<timestamp>/<harness_name>/`` mirrors the
|
|
457
|
+
layout captured before the matching install pass, keyed relative to the
|
|
458
|
+
allowed-write boundary (the home/parent for user-scope, the project root for
|
|
459
|
+
project-scope). Each backed-up file is copied back to
|
|
460
|
+
``<allowed_root>/<relative-path>``, recreating parents and backing up
|
|
461
|
+
whatever currently occupies the destination (the restore is itself
|
|
462
|
+
reversible). Because the key is boundary-relative, an out-of-root target
|
|
463
|
+
(codex ``~/.config/apothem/…`` or ``~/.agents/skills/…``) restores to its
|
|
464
|
+
true original path rather than collapsing under the harness root.
|
|
465
|
+
|
|
466
|
+
When *only_refs* is supplied (a set of resolved backup-file paths — the
|
|
467
|
+
``backup_ref`` values recorded by one install pass), restoration is scoped
|
|
468
|
+
to exactly those files. The timestamp slug is second-granular, so two
|
|
469
|
+
install passes in the same second share a ``<timestamp>/<harness>/`` set;
|
|
470
|
+
scoping to a single record's refs keeps a rollback from clobbering files a
|
|
471
|
+
sibling pass backed up under the shared timestamp. ``None`` restores the
|
|
472
|
+
whole set (the legacy contract).
|
|
473
|
+
|
|
474
|
+
Only file contents are restored; an intentionally-empty directory the
|
|
475
|
+
operator had created is not recreated (the backup walk keys on files). This
|
|
476
|
+
is a deliberate scope limit — config trees Apothem backs up are file sets,
|
|
477
|
+
not empty-directory structures.
|
|
478
|
+
|
|
479
|
+
Raises:
|
|
480
|
+
ValueError: When neither *harness_root* nor *project_root* is supplied.
|
|
481
|
+
"""
|
|
482
|
+
root = _root_for(harness_root, project_root)
|
|
483
|
+
allowed_root = _allowed_write_root(harness_root, project_root)
|
|
484
|
+
backup_set = install_driver.BACKUP_ROOT / timestamp / harness_name
|
|
485
|
+
if not backup_set.is_dir():
|
|
486
|
+
return [
|
|
487
|
+
_result(
|
|
488
|
+
"skipped",
|
|
489
|
+
"restore_backup",
|
|
490
|
+
backup_set,
|
|
491
|
+
"no backup set for this harness and timestamp",
|
|
492
|
+
)
|
|
493
|
+
]
|
|
494
|
+
results: list[MaterializationResult] = []
|
|
495
|
+
for backup_file in sorted(p for p in backup_set.rglob("*") if p.is_file()):
|
|
496
|
+
if only_refs is not None and str(backup_file.resolve()) not in only_refs:
|
|
497
|
+
# Scoped restore: this file belongs to a sibling pass that shared
|
|
498
|
+
# the second-granular timestamp slug, not the record being rolled
|
|
499
|
+
# back. Skip it so the rollback restores only its own targets.
|
|
500
|
+
continue
|
|
501
|
+
dest = allowed_root / backup_file.relative_to(backup_set)
|
|
502
|
+
try:
|
|
503
|
+
data = backup_file.read_bytes()
|
|
504
|
+
except OSError as exc: # pragma: no cover - defensive
|
|
505
|
+
results.append(
|
|
506
|
+
_result(
|
|
507
|
+
"error",
|
|
508
|
+
"restore_backup",
|
|
509
|
+
dest,
|
|
510
|
+
f"could not read backup file: {exc}",
|
|
511
|
+
source=backup_file,
|
|
512
|
+
)
|
|
513
|
+
)
|
|
514
|
+
continue
|
|
515
|
+
results.append(
|
|
516
|
+
write_bytes_safely(
|
|
517
|
+
dest,
|
|
518
|
+
data,
|
|
519
|
+
install_root=root,
|
|
520
|
+
harness_name=harness_name,
|
|
521
|
+
operation="restore_backup",
|
|
522
|
+
source=backup_file,
|
|
523
|
+
allowed_root=allowed_root,
|
|
524
|
+
)
|
|
525
|
+
)
|
|
526
|
+
if not results:
|
|
527
|
+
results.append(
|
|
528
|
+
_result(
|
|
529
|
+
"unchanged",
|
|
530
|
+
"restore_backup",
|
|
531
|
+
backup_set,
|
|
532
|
+
"backup set contained no files",
|
|
533
|
+
)
|
|
534
|
+
)
|
|
535
|
+
return results
|