@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,755 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Canonical shared-profile model, normalization, and diagnostics."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
from collections.abc import Callable, Iterable, Mapping
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Final
|
|
13
|
+
|
|
14
|
+
import yaml
|
|
15
|
+
from jsonschema import Draft202012Validator, FormatChecker
|
|
16
|
+
from jsonschema.exceptions import ValidationError
|
|
17
|
+
|
|
18
|
+
from apothem.lib.harness_registry import SUPPORTED_HARNESS_IDS
|
|
19
|
+
from apothem.schemas import profile_schema_path
|
|
20
|
+
|
|
21
|
+
DEFAULT_PROFILE_PATH = Path("~/.config/apothem/profile.yaml")
|
|
22
|
+
DEFAULT_ROLE = "senior software engineer"
|
|
23
|
+
DEFAULT_LANGUAGE = "python"
|
|
24
|
+
DEFAULT_STYLE = "concise"
|
|
25
|
+
DEFAULT_SERIOUSNESS = "PERSONAL_USE"
|
|
26
|
+
DEFAULT_WORKSPACE_DIRECTORY_NAME = ".apothem"
|
|
27
|
+
DEFAULT_WORKSPACE_SCOPE = "project-local"
|
|
28
|
+
|
|
29
|
+
SERIOUSNESS_LEVELS = (
|
|
30
|
+
"EXPLORING",
|
|
31
|
+
"PERSONAL_USE",
|
|
32
|
+
"SHARED",
|
|
33
|
+
"PUBLIC_LAUNCH",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Highest profile-schema version this engine understands. A version-less
|
|
37
|
+
# profile is treated as this version; a profile stamped higher is rejected
|
|
38
|
+
# with an upgrade-the-engine diagnostic before schema validation runs.
|
|
39
|
+
_CURRENT_SCHEMA_VERSION: Final[int] = 1
|
|
40
|
+
|
|
41
|
+
# MCP transports the schema and model both recognize; streamable-http is the
|
|
42
|
+
# modern replacement for sse. The schema↔code cross-check test pins these equal.
|
|
43
|
+
MCP_TRANSPORTS = ("stdio", "http", "sse", "streamable-http")
|
|
44
|
+
|
|
45
|
+
# The transport-conditional required field each server shape must carry. stdio
|
|
46
|
+
# servers launch a command; http-family servers carry a url. Mirrors the
|
|
47
|
+
# schema's if/then required-key blocks.
|
|
48
|
+
MCP_REQUIRED_FIELD_BY_TRANSPORT = {
|
|
49
|
+
"stdio": "command",
|
|
50
|
+
"http": "url",
|
|
51
|
+
"sse": "url",
|
|
52
|
+
"streamable-http": "url",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
_SENSITIVE_KEY_PARTS = (
|
|
56
|
+
"token",
|
|
57
|
+
"secret",
|
|
58
|
+
"password",
|
|
59
|
+
"credential",
|
|
60
|
+
"api_key",
|
|
61
|
+
"apikey",
|
|
62
|
+
"private_key",
|
|
63
|
+
"authorization",
|
|
64
|
+
"auth",
|
|
65
|
+
"header",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
_TOKEN_PATTERNS = (
|
|
69
|
+
re.compile(r"(?i)\b(?:bearer\s+)?sk-[A-Za-z0-9_-]{12,}\b"),
|
|
70
|
+
re.compile(r"\bgh[pousr]_[A-Za-z0-9_]{20,}\b"),
|
|
71
|
+
re.compile(r"\bxox[baprs]-[A-Za-z0-9-]{20,}\b"),
|
|
72
|
+
re.compile(r"\b[A-Za-z0-9_-]{32,}\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\b"),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass(frozen=True)
|
|
77
|
+
class IdentityProfile:
|
|
78
|
+
"""Operator identity fields carried by the shared profile."""
|
|
79
|
+
|
|
80
|
+
name: str
|
|
81
|
+
role: str = DEFAULT_ROLE
|
|
82
|
+
email: str | None = None
|
|
83
|
+
website: str | None = None
|
|
84
|
+
github: str | None = None
|
|
85
|
+
|
|
86
|
+
def to_dict(self) -> dict[str, str]:
|
|
87
|
+
"""Serialize to a dict, omitting any unset optional field
|
|
88
|
+
(``email`` / ``website`` / ``github``)."""
|
|
89
|
+
payload = {"name": self.name, "role": self.role}
|
|
90
|
+
if self.email:
|
|
91
|
+
payload["email"] = self.email
|
|
92
|
+
if self.website:
|
|
93
|
+
payload["website"] = self.website
|
|
94
|
+
if self.github:
|
|
95
|
+
payload["github"] = self.github
|
|
96
|
+
return payload
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass(frozen=True)
|
|
100
|
+
class PreferenceProfile:
|
|
101
|
+
"""Workflow and interaction defaults shared across harnesses."""
|
|
102
|
+
|
|
103
|
+
language: str = DEFAULT_LANGUAGE
|
|
104
|
+
style: str = DEFAULT_STYLE
|
|
105
|
+
formatter: str | None = None
|
|
106
|
+
test_framework: str | None = None
|
|
107
|
+
|
|
108
|
+
def to_dict(self) -> dict[str, str]:
|
|
109
|
+
"""Serialize to a dict, omitting any unset optional field
|
|
110
|
+
(``formatter`` / ``test_framework``)."""
|
|
111
|
+
payload = {"language": self.language, "style": self.style}
|
|
112
|
+
if self.formatter:
|
|
113
|
+
payload["formatter"] = self.formatter
|
|
114
|
+
if self.test_framework:
|
|
115
|
+
payload["test_framework"] = self.test_framework
|
|
116
|
+
return payload
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass(frozen=True)
|
|
120
|
+
class EnforcementFlags:
|
|
121
|
+
"""Opt-in toggles for shipped behaviors that ship default-off.
|
|
122
|
+
|
|
123
|
+
Every flag defaults ``False``: a clean install imposes no sprint
|
|
124
|
+
ceremony, no agent dispatch, no parallel-task expectation, no
|
|
125
|
+
continuous multi-step advancement, and no learning capture. Each
|
|
126
|
+
behavior's machinery stays preserved and invokable; setting a flag
|
|
127
|
+
``True`` opts the operator into that behavior explicitly, never the
|
|
128
|
+
reverse. Preferences carried as toggles live here; effort and model
|
|
129
|
+
selection are absent-by-default frontmatter resolved per request.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
sprints: bool = False
|
|
133
|
+
agent_teams: bool = False
|
|
134
|
+
multitasking: bool = False
|
|
135
|
+
continuous_execution: bool = False
|
|
136
|
+
learning_loop: bool = False
|
|
137
|
+
|
|
138
|
+
def to_dict(self) -> dict[str, bool]:
|
|
139
|
+
"""Serialize all five opt-in flags to a bool dict (none omitted)."""
|
|
140
|
+
return {
|
|
141
|
+
"sprints": self.sprints,
|
|
142
|
+
"agent_teams": self.agent_teams,
|
|
143
|
+
"multitasking": self.multitasking,
|
|
144
|
+
"continuous_execution": self.continuous_execution,
|
|
145
|
+
"learning_loop": self.learning_loop,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@dataclass(frozen=True)
|
|
150
|
+
class WorkspaceConfig:
|
|
151
|
+
"""Where Apothem keeps its shared, gitignored working directory.
|
|
152
|
+
|
|
153
|
+
``directory_name`` names the single working directory (default
|
|
154
|
+
``.apothem``); ``scope`` roots it project-local (``<project-root>/…``,
|
|
155
|
+
the default) or in the user home (``~/…``). The defaults reproduce
|
|
156
|
+
today's behavior exactly: a profile that omits ``workspace`` resolves
|
|
157
|
+
to a project-local ``.apothem``. The field is unconsumed by any
|
|
158
|
+
resolver today — additive plumbing for a later relocation.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
directory_name: str = ".apothem"
|
|
162
|
+
scope: str = "project-local" # "project-local" | "user-home"
|
|
163
|
+
|
|
164
|
+
def to_dict(self) -> dict[str, str]:
|
|
165
|
+
"""Serialize the workspace config to a string dict (none omitted)."""
|
|
166
|
+
return {"directory_name": self.directory_name, "scope": self.scope}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@dataclass(frozen=True)
|
|
170
|
+
class McpServer:
|
|
171
|
+
"""One MCP server entry from the shared profile's portability inventory.
|
|
172
|
+
|
|
173
|
+
stdio servers carry a ``command`` (and optional ``args``); http/sse/
|
|
174
|
+
streamable-http servers carry a ``url``. ``env`` is an optional string map
|
|
175
|
+
passed to the server process. The shape mirrors the schema's ``mcpServer``
|
|
176
|
+
``$def`` and its transport-conditional required keys.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
transport: str
|
|
180
|
+
command: str | None = None
|
|
181
|
+
args: tuple[str, ...] = ()
|
|
182
|
+
url: str | None = None
|
|
183
|
+
env: Mapping[str, str] | None = None
|
|
184
|
+
|
|
185
|
+
def to_dict(self) -> dict[str, Any]:
|
|
186
|
+
"""Serialize to a dict, omitting unset optional fields and emitting
|
|
187
|
+
``env`` with its keys sorted for deterministic output."""
|
|
188
|
+
payload: dict[str, Any] = {"transport": self.transport}
|
|
189
|
+
if self.command:
|
|
190
|
+
payload["command"] = self.command
|
|
191
|
+
if self.args:
|
|
192
|
+
payload["args"] = list(self.args)
|
|
193
|
+
if self.url:
|
|
194
|
+
payload["url"] = self.url
|
|
195
|
+
if self.env:
|
|
196
|
+
payload["env"] = {key: self.env[key] for key in sorted(self.env)}
|
|
197
|
+
return payload
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@dataclass(frozen=True)
|
|
201
|
+
class CanonicalProfile:
|
|
202
|
+
"""Normalized shared profile consumed by CLI and harness materializers."""
|
|
203
|
+
|
|
204
|
+
identity: IdentityProfile
|
|
205
|
+
preferences: PreferenceProfile
|
|
206
|
+
rules: tuple[str, ...] = ()
|
|
207
|
+
seriousness: str = DEFAULT_SERIOUSNESS
|
|
208
|
+
enforcement: EnforcementFlags = EnforcementFlags()
|
|
209
|
+
mcp_servers: Mapping[str, McpServer] | None = None
|
|
210
|
+
harnesses: Mapping[str, Mapping[str, Any]] | None = None
|
|
211
|
+
exclude_harnesses: tuple[str, ...] = ()
|
|
212
|
+
workspace: WorkspaceConfig = WorkspaceConfig()
|
|
213
|
+
|
|
214
|
+
def to_dict(self) -> dict[str, Any]:
|
|
215
|
+
"""Serialize the full profile to a nested dict, sorting MCP server
|
|
216
|
+
keys and stabilizing harness-override mappings for deterministic
|
|
217
|
+
output."""
|
|
218
|
+
payload: dict[str, Any] = {
|
|
219
|
+
"identity": self.identity.to_dict(),
|
|
220
|
+
"preferences": self.preferences.to_dict(),
|
|
221
|
+
"rules": list(self.rules),
|
|
222
|
+
"seriousness": self.seriousness,
|
|
223
|
+
"enforcement": self.enforcement.to_dict(),
|
|
224
|
+
"mcp_servers": {
|
|
225
|
+
name: (self.mcp_servers or {})[name].to_dict()
|
|
226
|
+
for name in sorted(self.mcp_servers or {})
|
|
227
|
+
},
|
|
228
|
+
"harnesses": {
|
|
229
|
+
harness: _stable_mapping(override)
|
|
230
|
+
for harness, override in (self.harnesses or {}).items()
|
|
231
|
+
},
|
|
232
|
+
"exclude_harnesses": list(self.exclude_harnesses),
|
|
233
|
+
"workspace": self.workspace.to_dict(),
|
|
234
|
+
}
|
|
235
|
+
return payload
|
|
236
|
+
|
|
237
|
+
def for_harness(self, harness_id: str) -> dict[str, Any]:
|
|
238
|
+
"""Return this profile with the named harness override applied."""
|
|
239
|
+
canonical_id = normalize_harness_id(harness_id)
|
|
240
|
+
if canonical_id not in SUPPORTED_HARNESS_IDS:
|
|
241
|
+
raise ValueError(f"Unsupported harness id: {harness_id}")
|
|
242
|
+
base = self.to_dict()
|
|
243
|
+
overrides = dict((self.harnesses or {}).get(canonical_id, {}))
|
|
244
|
+
merged = _deep_merge(base, overrides)
|
|
245
|
+
merged.pop("harnesses", None)
|
|
246
|
+
merged.pop("exclude_harnesses", None)
|
|
247
|
+
# workspace is project-global filesystem layout, not a per-harness
|
|
248
|
+
# surface — strip it from the per-harness projection (decision D-WS-2).
|
|
249
|
+
merged.pop("workspace", None)
|
|
250
|
+
return _stable_mapping(merged)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@dataclass(frozen=True)
|
|
254
|
+
class ProfileDiagnostic:
|
|
255
|
+
"""Actionable profile validation error suitable for plain or JSON output."""
|
|
256
|
+
|
|
257
|
+
code: str
|
|
258
|
+
message: str
|
|
259
|
+
profile_path: str
|
|
260
|
+
field: str
|
|
261
|
+
reason: str
|
|
262
|
+
fix: str
|
|
263
|
+
safe_value: Any | None = None
|
|
264
|
+
|
|
265
|
+
def to_dict(self) -> dict[str, Any]:
|
|
266
|
+
"""Serialize to a dict, appending an empty ``files_written`` list so
|
|
267
|
+
the payload matches the CLI lifecycle-envelope shape."""
|
|
268
|
+
return {
|
|
269
|
+
"code": self.code,
|
|
270
|
+
"message": self.message,
|
|
271
|
+
"profile_path": self.profile_path,
|
|
272
|
+
"field": self.field,
|
|
273
|
+
"reason": self.reason,
|
|
274
|
+
"fix": self.fix,
|
|
275
|
+
"safe_value": self.safe_value,
|
|
276
|
+
"files_written": [],
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
def format_plain(self) -> str:
|
|
280
|
+
"""Render the diagnostic as a human-readable plain-text block."""
|
|
281
|
+
safe_value = "none" if self.safe_value is None else repr(self.safe_value)
|
|
282
|
+
return (
|
|
283
|
+
f"{self.message}\n"
|
|
284
|
+
f"Profile: {self.profile_path}\n"
|
|
285
|
+
f"Field: {self.field}\n"
|
|
286
|
+
f"Reason: {self.reason}\n"
|
|
287
|
+
f"Fix: {self.fix}\n"
|
|
288
|
+
f"Safe value: {safe_value}\n"
|
|
289
|
+
"Files written: none."
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class ProfileValidationError(ValueError):
|
|
294
|
+
"""Raised when a profile cannot be parsed, validated, or normalized."""
|
|
295
|
+
|
|
296
|
+
def __init__(self, diagnostic: ProfileDiagnostic) -> None:
|
|
297
|
+
super().__init__(diagnostic.format_plain())
|
|
298
|
+
self.diagnostic = diagnostic
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def resolve_profile_path(profile: str | Path | None) -> Path:
|
|
302
|
+
"""Return an absolute shared-profile path without dereferencing symlinks."""
|
|
303
|
+
selected = Path(profile) if profile is not None else DEFAULT_PROFILE_PATH
|
|
304
|
+
# os.path.abspath normalizes to an absolute path WITHOUT resolving symlinks;
|
|
305
|
+
# Path.resolve would dereference them and break the no-symlink contract above.
|
|
306
|
+
return Path(os.path.abspath(selected.expanduser())) # noqa: PTH100
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def load_profile_file(profile_path: Path) -> CanonicalProfile:
|
|
310
|
+
"""Read, validate, and normalize a profile YAML file."""
|
|
311
|
+
resolved = resolve_profile_path(profile_path)
|
|
312
|
+
if not resolved.exists():
|
|
313
|
+
raise ProfileValidationError(
|
|
314
|
+
ProfileDiagnostic(
|
|
315
|
+
code="profile.not_found",
|
|
316
|
+
message="Apothem validation failed.",
|
|
317
|
+
profile_path=str(resolved),
|
|
318
|
+
field="profile",
|
|
319
|
+
reason="profile file does not exist",
|
|
320
|
+
fix="Run 'apothem profile init' or pass --profile PATH.",
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
try:
|
|
325
|
+
raw_text = resolved.read_text(encoding="utf-8")
|
|
326
|
+
except OSError as exc:
|
|
327
|
+
raise ProfileValidationError(
|
|
328
|
+
ProfileDiagnostic(
|
|
329
|
+
code="profile.read_failed",
|
|
330
|
+
message="Apothem validation failed.",
|
|
331
|
+
profile_path=str(resolved),
|
|
332
|
+
field="profile",
|
|
333
|
+
reason=str(exc),
|
|
334
|
+
fix="Check the profile path and file permissions.",
|
|
335
|
+
)
|
|
336
|
+
) from exc
|
|
337
|
+
|
|
338
|
+
try:
|
|
339
|
+
raw_profile = yaml.safe_load(raw_text)
|
|
340
|
+
except yaml.YAMLError as exc:
|
|
341
|
+
raise ProfileValidationError(
|
|
342
|
+
ProfileDiagnostic(
|
|
343
|
+
code="profile.yaml_invalid",
|
|
344
|
+
message="Apothem validation failed.",
|
|
345
|
+
profile_path=str(resolved),
|
|
346
|
+
field="profile",
|
|
347
|
+
reason=_one_line(str(exc)),
|
|
348
|
+
fix="Fix the YAML syntax before running the command again.",
|
|
349
|
+
)
|
|
350
|
+
) from exc
|
|
351
|
+
|
|
352
|
+
if raw_profile is None:
|
|
353
|
+
raw_profile = {}
|
|
354
|
+
if not isinstance(raw_profile, Mapping):
|
|
355
|
+
raise ProfileValidationError(
|
|
356
|
+
ProfileDiagnostic(
|
|
357
|
+
code="profile.type_invalid",
|
|
358
|
+
message="Apothem validation failed.",
|
|
359
|
+
profile_path=str(resolved),
|
|
360
|
+
field="profile",
|
|
361
|
+
reason="profile document must be a mapping",
|
|
362
|
+
fix="Use YAML key/value fields such as identity.name.",
|
|
363
|
+
safe_value=redact_value(raw_profile),
|
|
364
|
+
)
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
return validate_profile(raw_profile, profile_path=resolved)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
# Ordered profile-schema migration chain. Each entry maps a source version N to
|
|
371
|
+
# a callable that returns the profile upgraded to version N+1. The chain is a
|
|
372
|
+
# no-op while only v1 exists; a future v1->v2 migration registers as
|
|
373
|
+
# ``_MIGRATIONS[1] = _migrate_v1_to_v2`` and slots in without touching the
|
|
374
|
+
# driver loop in ``migrate_profile``.
|
|
375
|
+
_MIGRATIONS: dict[int, Callable[[Mapping[str, Any]], Mapping[str, Any]]] = {
|
|
376
|
+
# 1: _migrate_v1_to_v2, # registered when schema_version 2 ships.
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def migrate_profile(
|
|
381
|
+
profile: Mapping[str, Any], *, profile_path: str | Path = "<memory>"
|
|
382
|
+
) -> Mapping[str, Any]:
|
|
383
|
+
"""Forward-migrate *profile* to the current schema version.
|
|
384
|
+
|
|
385
|
+
A version-less profile is treated as ``_CURRENT_SCHEMA_VERSION``. A profile
|
|
386
|
+
stamped with a version greater than this engine supports is rejected with an
|
|
387
|
+
upgrade-the-engine diagnostic before any schema validation runs, so a
|
|
388
|
+
newer-version profile surfaces an actionable message rather than an opaque
|
|
389
|
+
``additionalProperties`` error. A non-integer or otherwise malformed
|
|
390
|
+
``schema_version`` is left for the jsonschema validator to reject. The v1
|
|
391
|
+
migration chain is a no-op: the mapping is returned unchanged.
|
|
392
|
+
"""
|
|
393
|
+
declared = profile.get("schema_version", _CURRENT_SCHEMA_VERSION)
|
|
394
|
+
# bool is an int subclass; treat a non-int (including bool) version as
|
|
395
|
+
# malformed and let the schema validator emit the precise diagnostic.
|
|
396
|
+
if isinstance(declared, int) and not isinstance(declared, bool):
|
|
397
|
+
if declared > _CURRENT_SCHEMA_VERSION:
|
|
398
|
+
raise ProfileValidationError(
|
|
399
|
+
ProfileDiagnostic(
|
|
400
|
+
code="profile.version_unsupported",
|
|
401
|
+
message="Apothem validation failed.",
|
|
402
|
+
profile_path=str(profile_path),
|
|
403
|
+
field="schema_version",
|
|
404
|
+
reason=(
|
|
405
|
+
f"profile declares schema_version {declared} but this "
|
|
406
|
+
f"apothem supports up to {_CURRENT_SCHEMA_VERSION}"
|
|
407
|
+
),
|
|
408
|
+
fix=(
|
|
409
|
+
"Upgrade apothem to a version that supports this "
|
|
410
|
+
"profile (the profile was written by a newer apothem)."
|
|
411
|
+
),
|
|
412
|
+
)
|
|
413
|
+
)
|
|
414
|
+
version = declared
|
|
415
|
+
while version < _CURRENT_SCHEMA_VERSION:
|
|
416
|
+
profile = _MIGRATIONS[version](profile)
|
|
417
|
+
version += 1
|
|
418
|
+
return profile
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def validate_profile(
|
|
422
|
+
profile: Mapping[str, Any], *, profile_path: str | Path = "<memory>"
|
|
423
|
+
) -> CanonicalProfile:
|
|
424
|
+
"""Validate *profile* against the packaged schema, then normalize it."""
|
|
425
|
+
profile = migrate_profile(profile, profile_path=profile_path)
|
|
426
|
+
schema = yaml.safe_load(profile_schema_path().read_text(encoding="utf-8"))
|
|
427
|
+
validator = Draft202012Validator(schema, format_checker=FormatChecker())
|
|
428
|
+
errors = sorted(
|
|
429
|
+
validator.iter_errors(profile), key=lambda err: list(err.absolute_path)
|
|
430
|
+
)
|
|
431
|
+
if errors:
|
|
432
|
+
raise ProfileValidationError(
|
|
433
|
+
_diagnostic_from_validation_error(errors[0], profile_path)
|
|
434
|
+
)
|
|
435
|
+
return coerce_profile(profile)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def coerce_profile(profile: Mapping[str, Any]) -> CanonicalProfile:
|
|
439
|
+
"""Apply profile defaults and deterministic normalization."""
|
|
440
|
+
identity = _mapping(profile.get("identity"))
|
|
441
|
+
preferences = _mapping(profile.get("preferences"))
|
|
442
|
+
enforcement = _mapping(profile.get("enforcement"))
|
|
443
|
+
workspace = _mapping(profile.get("workspace"))
|
|
444
|
+
harnesses = _mapping(profile.get("harnesses"))
|
|
445
|
+
|
|
446
|
+
normalized_harnesses: dict[str, Mapping[str, Any]] = {}
|
|
447
|
+
for harness_id in sorted(harnesses, key=_harness_sort_key):
|
|
448
|
+
value = harnesses[harness_id]
|
|
449
|
+
if isinstance(value, Mapping):
|
|
450
|
+
normalized_harnesses[normalize_harness_id(harness_id)] = _stable_mapping(
|
|
451
|
+
value
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
return CanonicalProfile(
|
|
455
|
+
identity=IdentityProfile(
|
|
456
|
+
name=_normalize_scalar(identity.get("name", "")),
|
|
457
|
+
role=_normalize_scalar(identity.get("role", DEFAULT_ROLE)) or DEFAULT_ROLE,
|
|
458
|
+
email=_optional_scalar(identity.get("email")),
|
|
459
|
+
website=_optional_scalar(identity.get("website")),
|
|
460
|
+
github=_optional_scalar(identity.get("github")),
|
|
461
|
+
),
|
|
462
|
+
preferences=PreferenceProfile(
|
|
463
|
+
language=normalize_language(preferences.get("language", DEFAULT_LANGUAGE)),
|
|
464
|
+
style=_normalize_scalar(preferences.get("style", DEFAULT_STYLE))
|
|
465
|
+
or DEFAULT_STYLE,
|
|
466
|
+
formatter=_optional_scalar(preferences.get("formatter")),
|
|
467
|
+
test_framework=_optional_scalar(preferences.get("test_framework")),
|
|
468
|
+
),
|
|
469
|
+
rules=tuple(_normalize_rules(profile.get("rules"))),
|
|
470
|
+
seriousness=_normalize_scalar(profile.get("seriousness", DEFAULT_SERIOUSNESS))
|
|
471
|
+
or DEFAULT_SERIOUSNESS,
|
|
472
|
+
enforcement=EnforcementFlags(
|
|
473
|
+
sprints=_normalize_flag(enforcement.get("sprints")),
|
|
474
|
+
agent_teams=_normalize_flag(enforcement.get("agent_teams")),
|
|
475
|
+
multitasking=_normalize_flag(enforcement.get("multitasking")),
|
|
476
|
+
continuous_execution=_normalize_flag(
|
|
477
|
+
enforcement.get("continuous_execution")
|
|
478
|
+
),
|
|
479
|
+
learning_loop=_normalize_flag(enforcement.get("learning_loop")),
|
|
480
|
+
),
|
|
481
|
+
mcp_servers=_normalize_mcp_servers(profile.get("mcp_servers")),
|
|
482
|
+
harnesses=normalized_harnesses,
|
|
483
|
+
exclude_harnesses=tuple(
|
|
484
|
+
sorted(
|
|
485
|
+
(
|
|
486
|
+
normalize_harness_id(item)
|
|
487
|
+
for item in _sequence(profile.get("exclude_harnesses"))
|
|
488
|
+
),
|
|
489
|
+
key=_harness_sort_key,
|
|
490
|
+
)
|
|
491
|
+
),
|
|
492
|
+
workspace=WorkspaceConfig(
|
|
493
|
+
directory_name=_normalize_scalar(workspace.get("directory_name"))
|
|
494
|
+
or DEFAULT_WORKSPACE_DIRECTORY_NAME,
|
|
495
|
+
scope=_normalize_scalar(workspace.get("scope")) or DEFAULT_WORKSPACE_SCOPE,
|
|
496
|
+
),
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def normalize_harness_id(value: object) -> str:
|
|
501
|
+
"""Return the canonical spelling for an already supported harness id."""
|
|
502
|
+
return _normalize_scalar(value).lower()
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def normalize_language(value: object) -> str:
|
|
506
|
+
"""Normalize the profile language preference to its semantic form."""
|
|
507
|
+
return _normalize_scalar(value).lower() or DEFAULT_LANGUAGE
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def redact_value(value: object, *, field_path: tuple[object, ...] = ()) -> object:
|
|
511
|
+
"""Return *value* with secret-like keys and token-shaped strings redacted."""
|
|
512
|
+
if _path_is_sensitive(field_path):
|
|
513
|
+
return "<redacted>"
|
|
514
|
+
if isinstance(value, str):
|
|
515
|
+
if any(pattern.search(value) for pattern in _TOKEN_PATTERNS):
|
|
516
|
+
return "<redacted>"
|
|
517
|
+
return value
|
|
518
|
+
if isinstance(value, Mapping):
|
|
519
|
+
redacted: dict[str, Any] = {}
|
|
520
|
+
for key, item in value.items():
|
|
521
|
+
redacted[str(key)] = redact_value(item, field_path=(*field_path, key))
|
|
522
|
+
return redacted
|
|
523
|
+
if isinstance(value, list):
|
|
524
|
+
return [
|
|
525
|
+
redact_value(item, field_path=(*field_path, index))
|
|
526
|
+
for index, item in enumerate(value)
|
|
527
|
+
]
|
|
528
|
+
return value
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _diagnostic_from_validation_error(
|
|
532
|
+
error: ValidationError, profile_path: str | Path
|
|
533
|
+
) -> ProfileDiagnostic:
|
|
534
|
+
field = _format_field_path(error.absolute_path)
|
|
535
|
+
return ProfileDiagnostic(
|
|
536
|
+
code=_error_code(error.validator),
|
|
537
|
+
message="Apothem validation failed.",
|
|
538
|
+
profile_path=str(profile_path),
|
|
539
|
+
field=field,
|
|
540
|
+
reason=error.message,
|
|
541
|
+
fix=_suggest_fix(error),
|
|
542
|
+
safe_value=redact_value(error.instance, field_path=tuple(error.absolute_path)),
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def _error_code(validator_name: object) -> str:
|
|
547
|
+
if not isinstance(validator_name, str):
|
|
548
|
+
return "profile.validation_failed"
|
|
549
|
+
return {
|
|
550
|
+
"additionalProperties": "profile.unknown_field",
|
|
551
|
+
"enum": "profile.invalid_choice",
|
|
552
|
+
"format": "profile.invalid_format",
|
|
553
|
+
"minLength": "profile.empty_value",
|
|
554
|
+
"required": "profile.required",
|
|
555
|
+
"type": "profile.invalid_type",
|
|
556
|
+
"uniqueItems": "profile.duplicate_value",
|
|
557
|
+
}.get(validator_name, "profile.validation_failed")
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def _suggest_fix(error: ValidationError) -> str:
|
|
561
|
+
if error.validator == "required":
|
|
562
|
+
return "Add the missing required field."
|
|
563
|
+
if error.validator == "additionalProperties":
|
|
564
|
+
return "Remove the unsupported field or correct its spelling."
|
|
565
|
+
if error.validator == "enum":
|
|
566
|
+
validator_value = error.validator_value
|
|
567
|
+
if not isinstance(validator_value, Iterable) or isinstance(
|
|
568
|
+
validator_value, str | bytes
|
|
569
|
+
):
|
|
570
|
+
return "Use one of the allowed values."
|
|
571
|
+
choices = ", ".join(repr(choice) for choice in validator_value)
|
|
572
|
+
return f"Use one of: {choices}."
|
|
573
|
+
if error.validator == "format":
|
|
574
|
+
return "Use a value that matches the required format."
|
|
575
|
+
if error.validator == "minLength":
|
|
576
|
+
return "Use a non-empty value."
|
|
577
|
+
if error.validator == "type":
|
|
578
|
+
return "Use a value with the expected type."
|
|
579
|
+
if error.validator == "uniqueItems":
|
|
580
|
+
return "Remove duplicate values."
|
|
581
|
+
return "Fix the profile field and run the command again."
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def _format_field_path(path: Iterable[object]) -> str:
|
|
585
|
+
parts = [str(part) for part in path]
|
|
586
|
+
return ".".join(parts) if parts else "profile"
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def _mapping(value: object) -> Mapping[str, Any]:
|
|
590
|
+
if isinstance(value, Mapping):
|
|
591
|
+
return value
|
|
592
|
+
return {}
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def _sequence(value: object) -> list[object]:
|
|
596
|
+
if isinstance(value, list):
|
|
597
|
+
return list(value)
|
|
598
|
+
if value is None:
|
|
599
|
+
return []
|
|
600
|
+
return [value]
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def _normalize_scalar(value: object) -> str:
|
|
604
|
+
if value is None:
|
|
605
|
+
return ""
|
|
606
|
+
return _normalize_newlines(str(value)).strip()
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def _optional_scalar(value: object) -> str | None:
|
|
610
|
+
normalized = _normalize_scalar(value)
|
|
611
|
+
return normalized or None
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def _normalize_flag(value: object) -> bool:
|
|
615
|
+
"""Coerce an opt-in enforcement flag to bool; absent reads as default-off."""
|
|
616
|
+
return bool(value)
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
def _normalize_newlines(value: str) -> str:
|
|
620
|
+
return value.replace("\r\n", "\n").replace("\r", "\n")
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def _normalize_rules(value: object) -> list[str]:
|
|
624
|
+
normalized: list[str] = []
|
|
625
|
+
for item in _sequence(value):
|
|
626
|
+
rule = _normalize_scalar(item)
|
|
627
|
+
if rule:
|
|
628
|
+
normalized.append(rule)
|
|
629
|
+
return normalized
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
def _normalize_mcp_servers(value: object) -> dict[str, McpServer]:
|
|
633
|
+
"""Normalize the profile MCP inventory into name-keyed McpServer values.
|
|
634
|
+
|
|
635
|
+
Servers are emitted in sorted name order for deterministic output. Each
|
|
636
|
+
server's transport is lower-cased; stdio ``args`` and ``env`` values are
|
|
637
|
+
coerced to strings. Non-mapping entries are skipped (the schema rejects
|
|
638
|
+
them before this runs on a validated profile).
|
|
639
|
+
"""
|
|
640
|
+
servers: dict[str, McpServer] = {}
|
|
641
|
+
mapping = _mapping(value)
|
|
642
|
+
for name in sorted(mapping):
|
|
643
|
+
spec = mapping[name]
|
|
644
|
+
if not isinstance(spec, Mapping):
|
|
645
|
+
continue
|
|
646
|
+
env_raw = _mapping(spec.get("env"))
|
|
647
|
+
env = {str(key): _normalize_scalar(item) for key, item in env_raw.items()}
|
|
648
|
+
servers[_normalize_scalar(name)] = McpServer(
|
|
649
|
+
transport=_normalize_scalar(spec.get("transport")).lower(),
|
|
650
|
+
command=_optional_scalar(spec.get("command")),
|
|
651
|
+
args=tuple(str(item) for item in _sequence(spec.get("args"))),
|
|
652
|
+
url=_optional_scalar(spec.get("url")),
|
|
653
|
+
env=env or None,
|
|
654
|
+
)
|
|
655
|
+
return servers
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def _union_lists(base: list[Any], override: list[Any]) -> list[Any]:
|
|
659
|
+
"""Return base ++ override with order preserved and duplicates removed."""
|
|
660
|
+
merged = list(base)
|
|
661
|
+
for item in override:
|
|
662
|
+
if item not in merged:
|
|
663
|
+
merged.append(item)
|
|
664
|
+
return merged
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
def _deep_merge(base: Mapping[str, Any], override: Mapping[str, Any]) -> dict[str, Any]:
|
|
668
|
+
"""Merge *override* onto *base* with the ratified per-harness semantics.
|
|
669
|
+
|
|
670
|
+
Mappings merge recursively; list-valued overrides (e.g. ``rules``) UNION
|
|
671
|
+
additively (base first, then new, deduplicated) — the ratified "refine"
|
|
672
|
+
semantics, not silent replace. The MCP inventory unions by server name with
|
|
673
|
+
each named server replaced wholesale, so an overridden server carries only
|
|
674
|
+
its own transport's keys (no stale-field pollution). Other scalars override.
|
|
675
|
+
"""
|
|
676
|
+
merged = dict(base)
|
|
677
|
+
for key, value in override.items():
|
|
678
|
+
existing = merged.get(key)
|
|
679
|
+
if (
|
|
680
|
+
key == "mcp_servers"
|
|
681
|
+
and isinstance(existing, Mapping)
|
|
682
|
+
and isinstance(value, Mapping)
|
|
683
|
+
):
|
|
684
|
+
merged[key] = {**existing, **value}
|
|
685
|
+
elif isinstance(existing, Mapping) and isinstance(value, Mapping):
|
|
686
|
+
merged[key] = _deep_merge(existing, value)
|
|
687
|
+
elif isinstance(existing, list) and isinstance(value, list):
|
|
688
|
+
merged[key] = _union_lists(existing, value)
|
|
689
|
+
else:
|
|
690
|
+
merged[key] = value
|
|
691
|
+
return merged
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
def _stable_mapping(value: Mapping[str, Any]) -> dict[str, Any]:
|
|
695
|
+
stable: dict[str, Any] = {}
|
|
696
|
+
for key in sorted(value):
|
|
697
|
+
item = value[key]
|
|
698
|
+
if isinstance(item, Mapping):
|
|
699
|
+
stable[str(key)] = _stable_mapping(item)
|
|
700
|
+
elif isinstance(item, list):
|
|
701
|
+
stable[str(key)] = [
|
|
702
|
+
_stable_mapping(child) if isinstance(child, Mapping) else child
|
|
703
|
+
for child in item
|
|
704
|
+
]
|
|
705
|
+
else:
|
|
706
|
+
stable[str(key)] = item
|
|
707
|
+
return stable
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
def _harness_sort_key(value: object) -> tuple[int, str]:
|
|
711
|
+
harness_id = normalize_harness_id(value)
|
|
712
|
+
try:
|
|
713
|
+
return (SUPPORTED_HARNESS_IDS.index(harness_id), harness_id)
|
|
714
|
+
except ValueError:
|
|
715
|
+
return (len(SUPPORTED_HARNESS_IDS), harness_id)
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
def _path_is_sensitive(path: tuple[object, ...]) -> bool:
|
|
719
|
+
joined = ".".join(str(part).lower() for part in path)
|
|
720
|
+
return any(part in joined for part in _SENSITIVE_KEY_PARTS)
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
def _one_line(value: str) -> str:
|
|
724
|
+
return " ".join(value.split())
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
__all__ = [
|
|
728
|
+
"DEFAULT_LANGUAGE",
|
|
729
|
+
"DEFAULT_PROFILE_PATH",
|
|
730
|
+
"DEFAULT_ROLE",
|
|
731
|
+
"DEFAULT_SERIOUSNESS",
|
|
732
|
+
"DEFAULT_STYLE",
|
|
733
|
+
"DEFAULT_WORKSPACE_DIRECTORY_NAME",
|
|
734
|
+
"DEFAULT_WORKSPACE_SCOPE",
|
|
735
|
+
"MCP_REQUIRED_FIELD_BY_TRANSPORT",
|
|
736
|
+
"MCP_TRANSPORTS",
|
|
737
|
+
"SERIOUSNESS_LEVELS",
|
|
738
|
+
"SUPPORTED_HARNESS_IDS",
|
|
739
|
+
"CanonicalProfile",
|
|
740
|
+
"EnforcementFlags",
|
|
741
|
+
"IdentityProfile",
|
|
742
|
+
"McpServer",
|
|
743
|
+
"PreferenceProfile",
|
|
744
|
+
"ProfileDiagnostic",
|
|
745
|
+
"ProfileValidationError",
|
|
746
|
+
"WorkspaceConfig",
|
|
747
|
+
"coerce_profile",
|
|
748
|
+
"load_profile_file",
|
|
749
|
+
"migrate_profile",
|
|
750
|
+
"normalize_harness_id",
|
|
751
|
+
"normalize_language",
|
|
752
|
+
"redact_value",
|
|
753
|
+
"resolve_profile_path",
|
|
754
|
+
"validate_profile",
|
|
755
|
+
]
|