@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,352 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Agnostic contexts surface — injectable, enable/disable-able prompt fragments.
|
|
4
|
+
|
|
5
|
+
A *context fragment* is dynamic injectable text the operator can toggle on or
|
|
6
|
+
off, with optional activation metadata describing when it applies. The shape is
|
|
7
|
+
neutral: it carries no tool-specific or vendor-specific identifier, so the same
|
|
8
|
+
fragment applies across any installation target.
|
|
9
|
+
|
|
10
|
+
This module defines the in-memory fragment value type, schema validation against
|
|
11
|
+
the packaged ``context-fragment.schema.json``, and a :class:`ContextStore` that
|
|
12
|
+
persists fragments deterministically under a :class:`~apothem.lib.data_home.DataHome`'s
|
|
13
|
+
``contexts`` directory.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
from collections.abc import Mapping
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Final
|
|
23
|
+
|
|
24
|
+
from jsonschema import Draft202012Validator
|
|
25
|
+
|
|
26
|
+
from apothem.lib.atomic_io import advisory_lock, write_bytes_atomically
|
|
27
|
+
from apothem.lib.data_home import DataHome
|
|
28
|
+
from apothem.schemas import context_fragment_schema_path
|
|
29
|
+
|
|
30
|
+
#: The deterministic on-disk filename holding the fragment array.
|
|
31
|
+
_FRAGMENTS_FILENAME: Final[str] = "fragments.json"
|
|
32
|
+
|
|
33
|
+
#: The advisory-lock filename guarding the fragment read-modify-write window.
|
|
34
|
+
_FRAGMENTS_LOCK_FILENAME: Final[str] = ".fragments.lock"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ContextError(ValueError):
|
|
38
|
+
"""Raised when a fragment fails validation or a store operation is invalid."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class Activation:
|
|
43
|
+
"""Metadata describing when a fragment activates.
|
|
44
|
+
|
|
45
|
+
Attributes:
|
|
46
|
+
scope: Activation breadth (``global`` | ``session`` | ``task``), or
|
|
47
|
+
``None`` when unspecified.
|
|
48
|
+
triggers: Phrases or signals that activate the fragment.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
scope: str | None = None
|
|
52
|
+
triggers: tuple[str, ...] = ()
|
|
53
|
+
|
|
54
|
+
def to_dict(self) -> dict[str, object]:
|
|
55
|
+
"""Serialize to a schema-conforming mapping, omitting empty optionals.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
A mapping carrying only the populated activation fields.
|
|
59
|
+
"""
|
|
60
|
+
data: dict[str, object] = {}
|
|
61
|
+
if self.scope is not None:
|
|
62
|
+
data["scope"] = self.scope
|
|
63
|
+
if self.triggers:
|
|
64
|
+
data["triggers"] = list(self.triggers)
|
|
65
|
+
return data
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def from_dict(cls, data: Mapping[str, object]) -> Activation:
|
|
69
|
+
"""Reconstruct an :class:`Activation` from a mapping.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
data: A mapping carrying ``scope`` and/or ``triggers``.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
The reconstructed activation.
|
|
76
|
+
"""
|
|
77
|
+
raw_scope = data.get("scope")
|
|
78
|
+
scope = raw_scope if isinstance(raw_scope, str) else None
|
|
79
|
+
raw_triggers = data.get("triggers")
|
|
80
|
+
triggers: tuple[str, ...] = (
|
|
81
|
+
tuple(str(item) for item in raw_triggers)
|
|
82
|
+
if isinstance(raw_triggers, list)
|
|
83
|
+
else ()
|
|
84
|
+
)
|
|
85
|
+
return cls(scope=scope, triggers=triggers)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass(frozen=True)
|
|
89
|
+
class ContextFragment:
|
|
90
|
+
"""One named, injectable prompt or context fragment.
|
|
91
|
+
|
|
92
|
+
Attributes:
|
|
93
|
+
id: Stable identifier, unique within a contexts store.
|
|
94
|
+
name: Human-readable fragment name.
|
|
95
|
+
body: The injectable prompt or context text.
|
|
96
|
+
enabled: Enable/disable switch; when ``False`` the fragment is retained
|
|
97
|
+
but never injected.
|
|
98
|
+
activation: Optional activation metadata, or ``None``.
|
|
99
|
+
tags: Discovery tags.
|
|
100
|
+
updated: Optional ISO 8601 timestamp of the last amendment.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
id: str
|
|
104
|
+
name: str
|
|
105
|
+
body: str
|
|
106
|
+
enabled: bool
|
|
107
|
+
activation: Activation | None = None
|
|
108
|
+
tags: tuple[str, ...] = ()
|
|
109
|
+
updated: str | None = None
|
|
110
|
+
|
|
111
|
+
def to_dict(self) -> dict[str, object]:
|
|
112
|
+
"""Serialize to a schema-conforming mapping, omitting empty optionals.
|
|
113
|
+
|
|
114
|
+
The required fields (``id``, ``name``, ``body``, ``enabled``) are always
|
|
115
|
+
present; optional fields are emitted only when populated so the output
|
|
116
|
+
validates under ``additionalProperties: false``.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
A mapping ready for JSON serialization and schema validation.
|
|
120
|
+
"""
|
|
121
|
+
data: dict[str, object] = {
|
|
122
|
+
"id": self.id,
|
|
123
|
+
"name": self.name,
|
|
124
|
+
"body": self.body,
|
|
125
|
+
"enabled": self.enabled,
|
|
126
|
+
}
|
|
127
|
+
if self.activation is not None:
|
|
128
|
+
activation = self.activation.to_dict()
|
|
129
|
+
if activation:
|
|
130
|
+
data["activation"] = activation
|
|
131
|
+
if self.tags:
|
|
132
|
+
data["tags"] = list(self.tags)
|
|
133
|
+
if self.updated is not None:
|
|
134
|
+
data["updated"] = self.updated
|
|
135
|
+
return data
|
|
136
|
+
|
|
137
|
+
@classmethod
|
|
138
|
+
def from_dict(cls, data: Mapping[str, object]) -> ContextFragment:
|
|
139
|
+
"""Reconstruct a :class:`ContextFragment` from a mapping.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
data: A mapping carrying the fragment fields.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
The reconstructed fragment.
|
|
146
|
+
|
|
147
|
+
Raises:
|
|
148
|
+
ContextError: When a required field is absent or carries the wrong
|
|
149
|
+
type.
|
|
150
|
+
"""
|
|
151
|
+
try:
|
|
152
|
+
identifier = data["id"]
|
|
153
|
+
name = data["name"]
|
|
154
|
+
body = data["body"]
|
|
155
|
+
enabled = data["enabled"]
|
|
156
|
+
except KeyError as exc:
|
|
157
|
+
raise ContextError(f"fragment is missing required field: {exc}") from exc
|
|
158
|
+
if not (
|
|
159
|
+
isinstance(identifier, str)
|
|
160
|
+
and isinstance(name, str)
|
|
161
|
+
and isinstance(body, str)
|
|
162
|
+
and isinstance(enabled, bool)
|
|
163
|
+
):
|
|
164
|
+
raise ContextError("fragment carries a field of the wrong type")
|
|
165
|
+
raw_activation = data.get("activation")
|
|
166
|
+
activation = (
|
|
167
|
+
Activation.from_dict(raw_activation)
|
|
168
|
+
if isinstance(raw_activation, Mapping)
|
|
169
|
+
else None
|
|
170
|
+
)
|
|
171
|
+
raw_tags = data.get("tags")
|
|
172
|
+
tags: tuple[str, ...] = (
|
|
173
|
+
tuple(str(item) for item in raw_tags) if isinstance(raw_tags, list) else ()
|
|
174
|
+
)
|
|
175
|
+
raw_updated = data.get("updated")
|
|
176
|
+
updated = raw_updated if isinstance(raw_updated, str) else None
|
|
177
|
+
return cls(
|
|
178
|
+
id=identifier,
|
|
179
|
+
name=name,
|
|
180
|
+
body=body,
|
|
181
|
+
enabled=enabled,
|
|
182
|
+
activation=activation,
|
|
183
|
+
tags=tags,
|
|
184
|
+
updated=updated,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _validator() -> Draft202012Validator:
|
|
189
|
+
"""Build a validator bound to the packaged context-fragment schema.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
A draft-2020-12 validator for a single fragment instance.
|
|
193
|
+
"""
|
|
194
|
+
schema = json.loads(context_fragment_schema_path().read_text(encoding="utf-8"))
|
|
195
|
+
return Draft202012Validator(schema)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def validate_fragment(data: Mapping[str, object]) -> None:
|
|
199
|
+
"""Validate a fragment mapping against the packaged schema.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
data: The candidate fragment mapping.
|
|
203
|
+
|
|
204
|
+
Raises:
|
|
205
|
+
ContextError: When the mapping violates the schema; the message lists
|
|
206
|
+
every validation error.
|
|
207
|
+
"""
|
|
208
|
+
errors = sorted(_validator().iter_errors(data), key=lambda error: error.path)
|
|
209
|
+
if errors:
|
|
210
|
+
joined = "; ".join(error.message for error in errors)
|
|
211
|
+
raise ContextError(f"invalid context fragment: {joined}")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@dataclass(frozen=True)
|
|
215
|
+
class ContextStore:
|
|
216
|
+
"""Deterministic persistence for fragments under a data home.
|
|
217
|
+
|
|
218
|
+
The store reads and writes ``fragments.json`` within the data home's
|
|
219
|
+
``contexts`` directory. The on-disk array is sorted by fragment id so the
|
|
220
|
+
serialization is byte-stable across runs.
|
|
221
|
+
|
|
222
|
+
Attributes:
|
|
223
|
+
data_home: The per-target data home whose ``contexts`` directory backs
|
|
224
|
+
this store.
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
data_home: DataHome
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def _lock_path(self) -> Path:
|
|
231
|
+
"""Return the advisory-lock path guarding the fragments RMW window."""
|
|
232
|
+
return self.data_home.contexts / _FRAGMENTS_LOCK_FILENAME
|
|
233
|
+
|
|
234
|
+
def _read_raw(self) -> list[dict[str, object]]:
|
|
235
|
+
"""Read the raw fragment mappings from disk.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
The stored mappings, or an empty list when the file is absent.
|
|
239
|
+
"""
|
|
240
|
+
path = self.data_home.contexts / _FRAGMENTS_FILENAME
|
|
241
|
+
if not path.is_file():
|
|
242
|
+
return []
|
|
243
|
+
loaded = json.loads(path.read_text(encoding="utf-8"))
|
|
244
|
+
if not isinstance(loaded, list):
|
|
245
|
+
raise ContextError(f"{path} does not hold a JSON array")
|
|
246
|
+
return [dict(entry) for entry in loaded]
|
|
247
|
+
|
|
248
|
+
def _write_raw(self, entries: list[dict[str, object]]) -> None:
|
|
249
|
+
"""Persist fragment mappings deterministically.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
entries: The fragment mappings to serialize.
|
|
253
|
+
"""
|
|
254
|
+
self.data_home.contexts.mkdir(parents=True, exist_ok=True)
|
|
255
|
+
ordered = sorted(entries, key=lambda entry: str(entry["id"]))
|
|
256
|
+
path = self.data_home.contexts / _FRAGMENTS_FILENAME
|
|
257
|
+
serialized = json.dumps(ordered, indent=2, sort_keys=True, ensure_ascii=False)
|
|
258
|
+
write_bytes_atomically(path, (serialized + "\n").encode("utf-8"))
|
|
259
|
+
|
|
260
|
+
def ensure_initialized(self) -> Path:
|
|
261
|
+
"""Create the canonical fragments file as an empty store when absent.
|
|
262
|
+
|
|
263
|
+
Materialization calls this so a freshly-installed target carries a
|
|
264
|
+
concrete, empty contexts artifact. The operation is idempotent and
|
|
265
|
+
non-destructive: an existing fragments file is left untouched, so
|
|
266
|
+
re-materializing a populated store never loses fragments.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
The path to the canonical fragments file.
|
|
270
|
+
"""
|
|
271
|
+
path = self.data_home.contexts / _FRAGMENTS_FILENAME
|
|
272
|
+
# Serialize the check-then-write under the same lock add()/set_enabled()
|
|
273
|
+
# hold, so a concurrent first add() cannot create the file with a
|
|
274
|
+
# fragment between the existence check and the empty-store write.
|
|
275
|
+
with advisory_lock(self._lock_path):
|
|
276
|
+
if not path.is_file():
|
|
277
|
+
self._write_raw([])
|
|
278
|
+
return path
|
|
279
|
+
|
|
280
|
+
def fragments(self) -> list[ContextFragment]:
|
|
281
|
+
"""Return every stored fragment, sorted by id.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
The fragments, or an empty list when the store is absent.
|
|
285
|
+
"""
|
|
286
|
+
return [ContextFragment.from_dict(entry) for entry in self._read_raw()]
|
|
287
|
+
|
|
288
|
+
def enabled_fragments(self) -> list[ContextFragment]:
|
|
289
|
+
"""Return only the fragments whose enable switch is on.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
The enabled fragments, sorted by id.
|
|
293
|
+
"""
|
|
294
|
+
return [fragment for fragment in self.fragments() if fragment.enabled]
|
|
295
|
+
|
|
296
|
+
def contains(self, fragment_id: str) -> bool:
|
|
297
|
+
"""Report whether a fragment with *fragment_id* is stored.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
fragment_id: The identifier to look up.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
``True`` when a fragment with that id exists.
|
|
304
|
+
"""
|
|
305
|
+
return any(entry["id"] == fragment_id for entry in self._read_raw())
|
|
306
|
+
|
|
307
|
+
def add(self, fragment: ContextFragment) -> None:
|
|
308
|
+
"""Validate and persist a fragment, replacing any same-id fragment.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
fragment: The fragment to store.
|
|
312
|
+
|
|
313
|
+
Raises:
|
|
314
|
+
ContextError: When the fragment fails schema validation.
|
|
315
|
+
"""
|
|
316
|
+
data = fragment.to_dict()
|
|
317
|
+
validate_fragment(data)
|
|
318
|
+
self.data_home.contexts.mkdir(parents=True, exist_ok=True)
|
|
319
|
+
# Serialize the RMW window so concurrent writers cannot lose a fragment.
|
|
320
|
+
with advisory_lock(self._lock_path):
|
|
321
|
+
entries = [e for e in self._read_raw() if e["id"] != fragment.id]
|
|
322
|
+
entries.append(data)
|
|
323
|
+
self._write_raw(entries)
|
|
324
|
+
|
|
325
|
+
def set_enabled(self, fragment_id: str, enabled: bool) -> None:
|
|
326
|
+
"""Flip a stored fragment's enable switch and persist.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
fragment_id: The identifier of the fragment to toggle.
|
|
330
|
+
enabled: The new enable state.
|
|
331
|
+
|
|
332
|
+
Raises:
|
|
333
|
+
ContextError: When no fragment with *fragment_id* is stored.
|
|
334
|
+
"""
|
|
335
|
+
self.data_home.contexts.mkdir(parents=True, exist_ok=True)
|
|
336
|
+
with advisory_lock(self._lock_path):
|
|
337
|
+
entries = self._read_raw()
|
|
338
|
+
for entry in entries:
|
|
339
|
+
if entry["id"] == fragment_id:
|
|
340
|
+
entry["enabled"] = enabled
|
|
341
|
+
self._write_raw(entries)
|
|
342
|
+
return
|
|
343
|
+
raise ContextError(f"no fragment with id {fragment_id!r}")
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
__all__ = [
|
|
347
|
+
"Activation",
|
|
348
|
+
"ContextError",
|
|
349
|
+
"ContextFragment",
|
|
350
|
+
"ContextStore",
|
|
351
|
+
"validate_fragment",
|
|
352
|
+
]
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Shared data-home resolution for the memory, contexts, and plans surfaces.
|
|
4
|
+
|
|
5
|
+
Every base directory receives ONE Apothem-owned working directory shared across
|
|
6
|
+
every installation target. The operator's durable memory records, injectable
|
|
7
|
+
context fragments, and captured learning signals live in that single home — a
|
|
8
|
+
write under one harness is visible to every other harness sharing the base. This
|
|
9
|
+
module derives the home deterministically from a base directory and the
|
|
10
|
+
working-directory name, so two harnesses installing into the same base resolve
|
|
11
|
+
to the same home rather than per-harness silos.
|
|
12
|
+
|
|
13
|
+
The data home is an Apothem-owned subtree (``<base>/.apothem/``) beneath the
|
|
14
|
+
base directory. Memory, contexts, learning, and plans each get a dedicated
|
|
15
|
+
child directory so the surfaces never collide.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import TYPE_CHECKING
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from apothem.lib.profile import WorkspaceConfig
|
|
26
|
+
|
|
27
|
+
#: The Apothem-owned subtree name placed under the base directory.
|
|
28
|
+
_APOTHEM_SUBTREE: str = ".apothem"
|
|
29
|
+
|
|
30
|
+
#: The plans child directory name beneath the shared working directory.
|
|
31
|
+
_PLANS_CHILD: str = "plans"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DataHomeError(ValueError):
|
|
35
|
+
"""Raised when a data home cannot be resolved from the given inputs."""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class DataHome:
|
|
40
|
+
"""The shared on-disk home for an Apothem base directory's data surfaces.
|
|
41
|
+
|
|
42
|
+
One home is shared across every installation target rooted at the same
|
|
43
|
+
base; the surfaces are never per-harness.
|
|
44
|
+
|
|
45
|
+
Attributes:
|
|
46
|
+
root: The Apothem-owned root directory for the shared data.
|
|
47
|
+
plans: The directory holding plan suites.
|
|
48
|
+
memory: The directory holding durable memory records.
|
|
49
|
+
contexts: The directory holding injectable context fragments.
|
|
50
|
+
learning: The directory holding captured learning signals.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
root: Path
|
|
54
|
+
plans: Path
|
|
55
|
+
memory: Path
|
|
56
|
+
contexts: Path
|
|
57
|
+
learning: Path
|
|
58
|
+
|
|
59
|
+
def ensure(self) -> DataHome:
|
|
60
|
+
"""Create the data-home directory tree if absent and return self.
|
|
61
|
+
|
|
62
|
+
Creating an existing tree is a no-op, so the call is idempotent and
|
|
63
|
+
never disturbs records already written under any of the surfaces. The
|
|
64
|
+
``plans`` child is included so the shared working directory carries its
|
|
65
|
+
plan-suite home alongside the data stores.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
This :class:`DataHome`, for call chaining.
|
|
69
|
+
"""
|
|
70
|
+
for directory in (
|
|
71
|
+
self.root,
|
|
72
|
+
self.plans,
|
|
73
|
+
self.memory,
|
|
74
|
+
self.contexts,
|
|
75
|
+
self.learning,
|
|
76
|
+
):
|
|
77
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
78
|
+
return self
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def resolve_shared_data_home(
|
|
82
|
+
*, base: Path, directory_name: str = ".apothem"
|
|
83
|
+
) -> DataHome:
|
|
84
|
+
"""Resolve the SHARED Apothem working directory beneath *base*.
|
|
85
|
+
|
|
86
|
+
All harnesses installing into the same *base* resolve to the SAME home:
|
|
87
|
+
memory, contexts, learning, and plans are shared, never per-harness. The
|
|
88
|
+
home is ``<base>/<directory_name>/`` with ``plans/``, ``memory/``,
|
|
89
|
+
``contexts/``, and ``learning/`` children.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
base: The base directory the working directory sits beneath — the
|
|
93
|
+
project root or the user home, as :func:`resolve_workspace_base`
|
|
94
|
+
selects from the profile's ``workspace.scope``.
|
|
95
|
+
directory_name: The shared working-directory name. Defaults to
|
|
96
|
+
``".apothem"`` and is overridable from the profile's
|
|
97
|
+
``workspace.directory_name``. Must be a single path component.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
A :class:`DataHome` with absolute child paths. The directories are
|
|
101
|
+
not created; call :meth:`DataHome.ensure` to materialize the tree.
|
|
102
|
+
|
|
103
|
+
Raises:
|
|
104
|
+
DataHomeError: When *directory_name* is empty, carries a path separator
|
|
105
|
+
or a drive specifier (``:``), or is a ``.`` / ``..`` component.
|
|
106
|
+
"""
|
|
107
|
+
name = _validate_directory_name(directory_name)
|
|
108
|
+
root = base / name
|
|
109
|
+
return DataHome(
|
|
110
|
+
root=root,
|
|
111
|
+
plans=root / _PLANS_CHILD,
|
|
112
|
+
memory=root / "memory",
|
|
113
|
+
contexts=root / "contexts",
|
|
114
|
+
learning=root / "learning",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _validate_directory_name(directory_name: str) -> str:
|
|
119
|
+
"""Return *directory_name* stripped, or raise if it is not one component.
|
|
120
|
+
|
|
121
|
+
The working-directory name must be exactly one path component so that
|
|
122
|
+
joining it onto a base never escapes the base. The guard rejects path
|
|
123
|
+
separators, a Windows drive specifier (``:``, which would reset pathlib to
|
|
124
|
+
the drive root), and the ``.`` / ``..`` traversal components.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
directory_name: The candidate working-directory name.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
The stripped, validated single-component name.
|
|
131
|
+
|
|
132
|
+
Raises:
|
|
133
|
+
DataHomeError: When *directory_name* is empty, carries a separator or a
|
|
134
|
+
drive specifier (``:``), or is a ``.`` / ``..`` component.
|
|
135
|
+
"""
|
|
136
|
+
name = directory_name.strip()
|
|
137
|
+
if not name:
|
|
138
|
+
raise DataHomeError("directory_name must be a non-empty string")
|
|
139
|
+
if "/" in name or "\\" in name or ":" in name or name in {".", ".."}:
|
|
140
|
+
raise DataHomeError(
|
|
141
|
+
f"directory_name must be a single path component, got {directory_name!r}"
|
|
142
|
+
)
|
|
143
|
+
return name
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def resolve_workspace_base(
|
|
147
|
+
*, workspace: WorkspaceConfig, project_root: Path, home: Path
|
|
148
|
+
) -> Path:
|
|
149
|
+
"""Resolve the base directory the shared working directory sits beneath.
|
|
150
|
+
|
|
151
|
+
The *workspace* scope selects which root the working directory is anchored
|
|
152
|
+
to: ``"project-local"`` (the default) roots it at *project_root*, while
|
|
153
|
+
``"user-home"`` roots it at *home*. The mapping is pure and deterministic;
|
|
154
|
+
no filesystem access occurs.
|
|
155
|
+
|
|
156
|
+
The default scope reproduces today's behavior (base equals the project
|
|
157
|
+
root), so a profile that omits ``workspace`` resolves the shared home under
|
|
158
|
+
the install root exactly as the pre-relocation layout did.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
workspace: The profile's workspace configuration carrying ``scope``.
|
|
162
|
+
project_root: The project root used when ``scope`` is project-local.
|
|
163
|
+
home: The user home directory used when ``scope`` is user-home.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
*project_root* for the project-local scope, *home* for the user-home
|
|
167
|
+
scope.
|
|
168
|
+
|
|
169
|
+
Raises:
|
|
170
|
+
DataHomeError: When ``workspace.scope`` is neither ``"project-local"``
|
|
171
|
+
nor ``"user-home"``.
|
|
172
|
+
"""
|
|
173
|
+
scope = workspace.scope
|
|
174
|
+
if scope == "project-local":
|
|
175
|
+
return project_root
|
|
176
|
+
if scope == "user-home":
|
|
177
|
+
return home
|
|
178
|
+
raise DataHomeError(
|
|
179
|
+
f"workspace.scope must be 'project-local' or 'user-home', got {scope!r}"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def resolve_install_data_home(
|
|
184
|
+
root: Path, *, profile: dict[str, object] | None
|
|
185
|
+
) -> DataHome:
|
|
186
|
+
"""Resolve the shared data home an install pass into *root* materializes.
|
|
187
|
+
|
|
188
|
+
The base is derived from the install *profile*'s ``workspace`` block:
|
|
189
|
+
project-local scope (the default) roots the shared home at *root*; user-home
|
|
190
|
+
scope roots it at the user home. The directory name (default ``.apothem``)
|
|
191
|
+
is the working-directory name. When *profile* is ``None`` (the
|
|
192
|
+
non-interactive path) or omits ``workspace``, the base is *root* and the name
|
|
193
|
+
is ``.apothem`` — behavior-preserving for the base while the harness segment
|
|
194
|
+
is collapsed.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
root: The install root (harness root or project root) the shared working
|
|
198
|
+
directory is anchored to under project-local scope.
|
|
199
|
+
profile: The install profile dict carrying the ``workspace`` block, or
|
|
200
|
+
``None`` for the non-interactive path.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
The shared :class:`DataHome` for the install pass. The directories are
|
|
204
|
+
not created; call :meth:`DataHome.ensure` to materialize the tree.
|
|
205
|
+
"""
|
|
206
|
+
# Local import avoids a module-load cycle: profile.py is heavier and is only
|
|
207
|
+
# needed to coerce the workspace block here.
|
|
208
|
+
from apothem.lib.profile import WorkspaceConfig, coerce_profile
|
|
209
|
+
|
|
210
|
+
workspace = (
|
|
211
|
+
WorkspaceConfig() if profile is None else coerce_profile(profile).workspace
|
|
212
|
+
)
|
|
213
|
+
base = resolve_workspace_base(
|
|
214
|
+
workspace=workspace, project_root=root, home=Path.home()
|
|
215
|
+
)
|
|
216
|
+
return resolve_shared_data_home(base=base, directory_name=workspace.directory_name)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def plans_root(*, base: Path, directory_name: str = ".apothem") -> Path:
|
|
220
|
+
"""Return the future shared plans home ``<base>/<directory_name>/plans``.
|
|
221
|
+
|
|
222
|
+
This is the target plans location once Apothem consolidates its working
|
|
223
|
+
state into a single shared working directory. The path is computed purely;
|
|
224
|
+
no filesystem access occurs and the directory is not created.
|
|
225
|
+
|
|
226
|
+
This helper is currently unconsumed — additive plumbing for a later
|
|
227
|
+
relocation of plans from ``<project-root>/.plans`` to the shared working
|
|
228
|
+
directory.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
base: The base directory the working directory sits beneath — the
|
|
232
|
+
project root or the user home, as :func:`resolve_workspace_base`
|
|
233
|
+
selects from the profile's ``workspace.scope``.
|
|
234
|
+
directory_name: The shared working-directory name. Defaults to
|
|
235
|
+
``".apothem"`` and is overridable from the profile's
|
|
236
|
+
``workspace.directory_name``. Must be a single path component.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
The path ``<base>/<directory_name>/plans``.
|
|
240
|
+
|
|
241
|
+
Raises:
|
|
242
|
+
DataHomeError: When *directory_name* is not a single path component.
|
|
243
|
+
"""
|
|
244
|
+
name = _validate_directory_name(directory_name)
|
|
245
|
+
return base / name / _PLANS_CHILD
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
__all__ = [
|
|
249
|
+
"DataHome",
|
|
250
|
+
"DataHomeError",
|
|
251
|
+
"plans_root",
|
|
252
|
+
"resolve_install_data_home",
|
|
253
|
+
"resolve_shared_data_home",
|
|
254
|
+
"resolve_workspace_base",
|
|
255
|
+
]
|