@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,540 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Opt-in continuous-learning loop: capture, extraction, and promotion.
|
|
4
|
+
|
|
5
|
+
The learning loop ships default-off. Nothing is captured, scored, or promoted
|
|
6
|
+
unless the operator has explicitly opted in by setting
|
|
7
|
+
``enforcement.learning_loop: true`` in the shared profile. The loop has three
|
|
8
|
+
stages:
|
|
9
|
+
|
|
10
|
+
* **Capture** records one raw :class:`LearningSignal` per observation into an
|
|
11
|
+
append-friendly JSON Lines store under a data home's ``learning`` directory.
|
|
12
|
+
The :func:`capture` gate writes nothing when the opt-in flag is off.
|
|
13
|
+
* **Extraction** groups a set of signals and emits one :class:`LearningPattern`
|
|
14
|
+
per group, each carrying a deterministic confidence in the closed interval
|
|
15
|
+
``[0, 1]``.
|
|
16
|
+
* **Promotion** writes a catalog-skill ``SKILL.md`` for any pattern whose
|
|
17
|
+
confidence meets a caller-supplied threshold, validating the synthesized
|
|
18
|
+
frontmatter against the packaged skill schema before it lands on disk.
|
|
19
|
+
|
|
20
|
+
Signals are neutral: a signal carries no tool-specific or vendor-specific
|
|
21
|
+
identifier, so the same signal round-trips between any two installation
|
|
22
|
+
targets without loss.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import json
|
|
28
|
+
import re
|
|
29
|
+
from collections.abc import Mapping, Sequence
|
|
30
|
+
from dataclasses import dataclass, field
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import Final, cast
|
|
33
|
+
|
|
34
|
+
import yaml
|
|
35
|
+
from jsonschema import Draft202012Validator, FormatChecker
|
|
36
|
+
|
|
37
|
+
from apothem.lib.atomic_io import (
|
|
38
|
+
advisory_lock,
|
|
39
|
+
append_line_durably,
|
|
40
|
+
write_bytes_atomically,
|
|
41
|
+
)
|
|
42
|
+
from apothem.lib.data_home import DataHome
|
|
43
|
+
from apothem.lib.profile import CanonicalProfile
|
|
44
|
+
from apothem.schemas import learning_signal_schema_path, skill_schema_path
|
|
45
|
+
|
|
46
|
+
#: The canonical filename, under a data home's ``learning`` dir, holding the
|
|
47
|
+
#: append-friendly JSON Lines stream of captured signals.
|
|
48
|
+
_SIGNALS_FILENAME: Final[str] = "signals.jsonl"
|
|
49
|
+
|
|
50
|
+
#: The advisory-lock filename guarding the signal-append window.
|
|
51
|
+
_SIGNALS_LOCK_FILENAME: Final[str] = ".signals.lock"
|
|
52
|
+
|
|
53
|
+
#: The catalog-skill entry-point filename written under a promoted skill dir.
|
|
54
|
+
_SKILL_FILENAME: Final[str] = "SKILL.md"
|
|
55
|
+
|
|
56
|
+
#: The SemVer stamp every freshly promoted skill carries.
|
|
57
|
+
_PROMOTED_SKILL_VERSION: Final[str] = "0.1.0"
|
|
58
|
+
|
|
59
|
+
#: Support count at which confidence saturates to ``1.0``. A group with this
|
|
60
|
+
#: many or more supporting signals is treated as fully confident.
|
|
61
|
+
_CONFIDENCE_SATURATION_SUPPORT: Final[int] = 5
|
|
62
|
+
|
|
63
|
+
#: Pattern enforcing the kebab-case identifier shape the skill schema requires.
|
|
64
|
+
_KEBAB_CASE: Final[re.Pattern[str]] = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class LearningError(ValueError):
|
|
68
|
+
"""Raised when a learning artifact fails validation or synthesis."""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _signal_schema() -> dict[str, object]:
|
|
72
|
+
"""Load and parse the learning-signal JSON schema.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
The parsed schema document.
|
|
76
|
+
"""
|
|
77
|
+
raw = learning_signal_schema_path().read_text(encoding="utf-8")
|
|
78
|
+
return cast("dict[str, object]", json.loads(raw))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _skill_schema() -> dict[str, object]:
|
|
82
|
+
"""Load and parse the catalog-skill frontmatter JSON schema.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
The parsed schema document.
|
|
86
|
+
"""
|
|
87
|
+
raw = skill_schema_path().read_text(encoding="utf-8")
|
|
88
|
+
return cast("dict[str, object]", json.loads(raw))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def validate_signal(data: Mapping[str, object]) -> None:
|
|
92
|
+
"""Validate *data* against the learning-signal schema.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
data: The candidate signal mapping to validate.
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
LearningError: When *data* violates the schema. The message lists
|
|
99
|
+
every validation error discovered.
|
|
100
|
+
"""
|
|
101
|
+
validator = Draft202012Validator(_signal_schema(), format_checker=FormatChecker())
|
|
102
|
+
errors = sorted(validator.iter_errors(dict(data)), key=lambda error: error.path)
|
|
103
|
+
if errors:
|
|
104
|
+
details = "; ".join(error.message for error in errors)
|
|
105
|
+
raise LearningError(f"invalid learning signal: {details}")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass(frozen=True)
|
|
109
|
+
class LearningSignal:
|
|
110
|
+
"""One captured signal in the opt-in continuous-learning loop.
|
|
111
|
+
|
|
112
|
+
The signal shape mirrors the learning-signal schema and carries no
|
|
113
|
+
harness-specific or vendor-specific field, so it is portable across any
|
|
114
|
+
installation target.
|
|
115
|
+
|
|
116
|
+
Attributes:
|
|
117
|
+
id: Stable signal identifier, unique within a learning store.
|
|
118
|
+
kind: Signal taxonomy; one of ``correction``, ``confirmation``, or
|
|
119
|
+
``observation``.
|
|
120
|
+
summary: One-line statement of what was observed.
|
|
121
|
+
captured: ISO 8601 date-time the signal was captured.
|
|
122
|
+
tags: Tags used to group related signals during pattern extraction.
|
|
123
|
+
payload: Free-form structured detail accompanying the signal, if any.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
id: str
|
|
127
|
+
kind: str
|
|
128
|
+
summary: str
|
|
129
|
+
captured: str
|
|
130
|
+
tags: tuple[str, ...] = ()
|
|
131
|
+
payload: Mapping[str, object] | None = None
|
|
132
|
+
|
|
133
|
+
def to_dict(self) -> dict[str, object]:
|
|
134
|
+
"""Render the signal as a schema-conformant mapping.
|
|
135
|
+
|
|
136
|
+
Optional keys are omitted when unset (empty ``tags``, ``None``
|
|
137
|
+
``payload``) so the output validates under
|
|
138
|
+
``additionalProperties: false`` and round-trips cleanly.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
A JSON-serializable mapping of the signal's set fields.
|
|
142
|
+
"""
|
|
143
|
+
data: dict[str, object] = {
|
|
144
|
+
"id": self.id,
|
|
145
|
+
"kind": self.kind,
|
|
146
|
+
"summary": self.summary,
|
|
147
|
+
"captured": self.captured,
|
|
148
|
+
}
|
|
149
|
+
if self.tags:
|
|
150
|
+
data["tags"] = list(self.tags)
|
|
151
|
+
if self.payload is not None:
|
|
152
|
+
data["payload"] = dict(self.payload)
|
|
153
|
+
return data
|
|
154
|
+
|
|
155
|
+
@classmethod
|
|
156
|
+
def from_dict(cls, data: Mapping[str, object]) -> LearningSignal:
|
|
157
|
+
"""Reconstruct a signal from a schema-conformant mapping.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
data: A mapping carrying at least the required signal fields.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
The reconstructed :class:`LearningSignal`.
|
|
164
|
+
"""
|
|
165
|
+
raw_tags = data.get("tags", ())
|
|
166
|
+
tags: tuple[str, ...] = ()
|
|
167
|
+
if isinstance(raw_tags, Sequence) and not isinstance(raw_tags, (str, bytes)):
|
|
168
|
+
tags = tuple(str(tag) for tag in raw_tags)
|
|
169
|
+
|
|
170
|
+
raw_payload = data.get("payload")
|
|
171
|
+
payload: Mapping[str, object] | None = None
|
|
172
|
+
if isinstance(raw_payload, Mapping):
|
|
173
|
+
payload = {str(key): value for key, value in raw_payload.items()}
|
|
174
|
+
|
|
175
|
+
return cls(
|
|
176
|
+
id=str(data["id"]),
|
|
177
|
+
kind=str(data["kind"]),
|
|
178
|
+
summary=str(data["summary"]),
|
|
179
|
+
captured=str(data["captured"]),
|
|
180
|
+
tags=tags,
|
|
181
|
+
payload=payload,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _serialize_line(signal: LearningSignal) -> str:
|
|
186
|
+
"""Serialize one signal to a single canonical JSON Lines record (no newline).
|
|
187
|
+
|
|
188
|
+
The object is emitted with sorted keys and no embedded newline so each
|
|
189
|
+
on-disk line holds exactly one signal once the durable append adds the
|
|
190
|
+
record terminator.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
signal: The signal to serialize.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
The canonical line text, without a trailing newline.
|
|
197
|
+
"""
|
|
198
|
+
return json.dumps(signal.to_dict(), sort_keys=True, ensure_ascii=False)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class LearningStore:
|
|
202
|
+
"""An append-friendly JSON Lines store of captured learning signals.
|
|
203
|
+
|
|
204
|
+
The store persists to a single ``signals.jsonl`` file under a data home's
|
|
205
|
+
``learning`` directory. Each line is one canonically-serialized signal, so
|
|
206
|
+
a capture appends one line without rewriting the whole file.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
def __init__(self, data_home: DataHome) -> None:
|
|
210
|
+
"""Bind the store to *data_home*'s learning directory.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
data_home: The per-target data home whose ``learning`` directory
|
|
214
|
+
holds this store's JSON Lines signals file.
|
|
215
|
+
"""
|
|
216
|
+
self._learning_dir = data_home.learning
|
|
217
|
+
self._signals_path = data_home.learning / _SIGNALS_FILENAME
|
|
218
|
+
self._lock_path = data_home.learning / _SIGNALS_LOCK_FILENAME
|
|
219
|
+
|
|
220
|
+
def signals(self) -> list[LearningSignal]:
|
|
221
|
+
"""Load every captured signal from disk.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
The stored signals in capture order, or an empty list when the
|
|
225
|
+
file is absent.
|
|
226
|
+
|
|
227
|
+
Raises:
|
|
228
|
+
LearningError: When a line is not a JSON object.
|
|
229
|
+
"""
|
|
230
|
+
if not self._signals_path.exists():
|
|
231
|
+
return []
|
|
232
|
+
signals: list[LearningSignal] = []
|
|
233
|
+
for line in self._signals_path.read_text(encoding="utf-8").splitlines():
|
|
234
|
+
stripped = line.strip()
|
|
235
|
+
if not stripped:
|
|
236
|
+
continue
|
|
237
|
+
loaded = json.loads(stripped)
|
|
238
|
+
if not isinstance(loaded, Mapping):
|
|
239
|
+
raise LearningError("each learning signal line must be a JSON object")
|
|
240
|
+
signals.append(LearningSignal.from_dict(loaded))
|
|
241
|
+
return signals
|
|
242
|
+
|
|
243
|
+
def count(self) -> int:
|
|
244
|
+
"""Count the captured signals.
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
The number of signals on disk.
|
|
248
|
+
"""
|
|
249
|
+
return len(self.signals())
|
|
250
|
+
|
|
251
|
+
def ensure_initialized(self) -> Path:
|
|
252
|
+
"""Create the canonical signals file as an empty store when absent.
|
|
253
|
+
|
|
254
|
+
Materialization calls this so a freshly-installed target carries a
|
|
255
|
+
concrete, empty learning artifact alongside its memory and contexts
|
|
256
|
+
surfaces. The empty store is a zero-length JSON Lines file (no signals
|
|
257
|
+
captured), which :meth:`signals` reads back as an empty list. The
|
|
258
|
+
operation is idempotent and non-destructive: an existing signals file
|
|
259
|
+
is left untouched, so re-materializing a populated store never loses
|
|
260
|
+
captured signals.
|
|
261
|
+
|
|
262
|
+
Seeding the surface captures nothing; capture remains gated on the
|
|
263
|
+
operator's ``enforcement.learning_loop`` opt-in at :func:`capture`.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
The path to the canonical signals file.
|
|
267
|
+
"""
|
|
268
|
+
# Serialize the check-then-write under the same lock _append() holds, so
|
|
269
|
+
# a concurrent first capture cannot append a signal line between the
|
|
270
|
+
# existence check and the empty-store write that clobbers it.
|
|
271
|
+
with advisory_lock(self._lock_path):
|
|
272
|
+
if not self._signals_path.exists():
|
|
273
|
+
self._learning_dir.mkdir(parents=True, exist_ok=True)
|
|
274
|
+
write_bytes_atomically(self._signals_path, b"")
|
|
275
|
+
return self._signals_path
|
|
276
|
+
|
|
277
|
+
def _append(self, signal: LearningSignal) -> None:
|
|
278
|
+
"""Append *signal* as one JSON Lines record, creating the dir if absent.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
signal: The validated signal to persist.
|
|
282
|
+
"""
|
|
283
|
+
self._learning_dir.mkdir(parents=True, exist_ok=True)
|
|
284
|
+
# Hold the advisory lock around the durable O_APPEND so concurrent
|
|
285
|
+
# captures never tear a single JSONL record or interleave bytes.
|
|
286
|
+
with advisory_lock(self._lock_path):
|
|
287
|
+
append_line_durably(self._signals_path, _serialize_line(signal))
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def capture(
|
|
291
|
+
signal: LearningSignal,
|
|
292
|
+
*,
|
|
293
|
+
store: LearningStore,
|
|
294
|
+
profile: CanonicalProfile,
|
|
295
|
+
) -> LearningSignal | None:
|
|
296
|
+
"""Capture *signal* into *store* when the operator has opted in.
|
|
297
|
+
|
|
298
|
+
The capture is gated on ``profile.enforcement.learning_loop``. When the
|
|
299
|
+
flag is ``False`` (the default for a clean install), this writes nothing
|
|
300
|
+
and returns ``None``. When the flag is ``True``, the signal is validated
|
|
301
|
+
against the learning-signal schema and appended to the store.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
signal: The signal to capture.
|
|
305
|
+
store: The learning store the signal is appended to.
|
|
306
|
+
profile: The shared profile carrying the ``learning_loop`` opt-in flag.
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
The captured signal when the flag is on, else ``None``.
|
|
310
|
+
|
|
311
|
+
Raises:
|
|
312
|
+
LearningError: When the flag is on and *signal* fails validation.
|
|
313
|
+
"""
|
|
314
|
+
if not profile.enforcement.learning_loop:
|
|
315
|
+
return None
|
|
316
|
+
validate_signal(signal.to_dict())
|
|
317
|
+
store._append(signal)
|
|
318
|
+
return signal
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@dataclass(frozen=True)
|
|
322
|
+
class LearningPattern:
|
|
323
|
+
"""A scored pattern extracted across a group of related signals.
|
|
324
|
+
|
|
325
|
+
Attributes:
|
|
326
|
+
id: Stable kebab-case identifier derived from the group key.
|
|
327
|
+
summary: One-line statement describing the grouped observation.
|
|
328
|
+
confidence: Numeric confidence in the closed interval ``[0, 1]``.
|
|
329
|
+
support: Count of signals supporting the pattern.
|
|
330
|
+
signal_ids: Identifiers of the supporting signals, in capture order.
|
|
331
|
+
"""
|
|
332
|
+
|
|
333
|
+
id: str
|
|
334
|
+
summary: str
|
|
335
|
+
confidence: float
|
|
336
|
+
support: int
|
|
337
|
+
signal_ids: tuple[str, ...] = field(default_factory=tuple)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _score_confidence(support: int) -> float:
|
|
341
|
+
"""Score a pattern's confidence from its support count.
|
|
342
|
+
|
|
343
|
+
The score is ``support / _CONFIDENCE_SATURATION_SUPPORT``, saturating to
|
|
344
|
+
``1.0`` once support reaches the saturation threshold. The result is
|
|
345
|
+
clamped to the closed interval ``[0, 1]`` so a malformed (e.g. negative)
|
|
346
|
+
support count can never produce an out-of-range score.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
support: The count of signals supporting the pattern.
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
A confidence in ``[0, 1]``.
|
|
353
|
+
"""
|
|
354
|
+
raw = support / _CONFIDENCE_SATURATION_SUPPORT
|
|
355
|
+
return max(0.0, min(1.0, raw))
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _group_key(signal: LearningSignal) -> str:
|
|
359
|
+
"""Derive the grouping key for *signal*.
|
|
360
|
+
|
|
361
|
+
Signals carrying at least one tag group by their first tag; otherwise they
|
|
362
|
+
group by ``kind`` plus a whitespace-normalized, lowercased summary. The key
|
|
363
|
+
is rendered kebab-case so it can seed a schema-valid pattern identifier.
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
signal: The signal to derive a key for.
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
A kebab-case grouping key.
|
|
370
|
+
"""
|
|
371
|
+
if signal.tags:
|
|
372
|
+
basis = signal.tags[0]
|
|
373
|
+
else:
|
|
374
|
+
normalized_summary = " ".join(signal.summary.lower().split())
|
|
375
|
+
basis = f"{signal.kind} {normalized_summary}"
|
|
376
|
+
return _kebab_case(basis)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _kebab_case(value: str) -> str:
|
|
380
|
+
"""Render *value* as a schema-valid kebab-case identifier.
|
|
381
|
+
|
|
382
|
+
Non-alphanumeric runs collapse to single hyphens; the result is lowercased
|
|
383
|
+
and stripped of leading and trailing hyphens. An empty result falls back to
|
|
384
|
+
``pattern`` so a usable identifier is always produced.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
value: The arbitrary basis string.
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
A kebab-case identifier matching ``^[a-z0-9]+(-[a-z0-9]+)*$``.
|
|
391
|
+
"""
|
|
392
|
+
slug = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
|
|
393
|
+
return slug or "pattern"
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def extract_patterns(signals: Sequence[LearningSignal]) -> list[LearningPattern]:
|
|
397
|
+
"""Group *signals* and emit one confidence-scored pattern per group.
|
|
398
|
+
|
|
399
|
+
Signals group by :func:`_group_key` (first tag, else kind plus normalized
|
|
400
|
+
summary). Each group yields one :class:`LearningPattern`. Confidence is
|
|
401
|
+
scored by :func:`_score_confidence`, which saturates support to ``1.0`` and
|
|
402
|
+
clamps the result to ``[0, 1]``; every returned pattern therefore satisfies
|
|
403
|
+
``0.0 <= confidence <= 1.0``. Groups are emitted in sorted-key order for
|
|
404
|
+
determinism.
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
signals: The captured signals to extract patterns from.
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
One pattern per distinct group key, ordered by key.
|
|
411
|
+
"""
|
|
412
|
+
groups: dict[str, list[LearningSignal]] = {}
|
|
413
|
+
for signal in signals:
|
|
414
|
+
groups.setdefault(_group_key(signal), []).append(signal)
|
|
415
|
+
|
|
416
|
+
patterns: list[LearningPattern] = []
|
|
417
|
+
for key in sorted(groups):
|
|
418
|
+
members = groups[key]
|
|
419
|
+
support = len(members)
|
|
420
|
+
patterns.append(
|
|
421
|
+
LearningPattern(
|
|
422
|
+
id=key,
|
|
423
|
+
summary=members[0].summary,
|
|
424
|
+
confidence=_score_confidence(support),
|
|
425
|
+
support=support,
|
|
426
|
+
signal_ids=tuple(member.id for member in members),
|
|
427
|
+
)
|
|
428
|
+
)
|
|
429
|
+
return patterns
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def _skill_frontmatter(pattern: LearningPattern, *, today: str) -> dict[str, object]:
|
|
433
|
+
"""Synthesize catalog-skill frontmatter from *pattern*.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
pattern: The pattern being promoted.
|
|
437
|
+
today: ISO 8601 date stamped as the skill's ``updated`` field.
|
|
438
|
+
|
|
439
|
+
Returns:
|
|
440
|
+
A mapping carrying every required catalog-skill frontmatter key.
|
|
441
|
+
"""
|
|
442
|
+
return {
|
|
443
|
+
"name": pattern.id,
|
|
444
|
+
"description": (
|
|
445
|
+
f"Learned pattern: {pattern.summary} "
|
|
446
|
+
f"(confidence {pattern.confidence:.2f}, support {pattern.support})."
|
|
447
|
+
),
|
|
448
|
+
"version": _PROMOTED_SKILL_VERSION,
|
|
449
|
+
"updated": today,
|
|
450
|
+
"archetype": "learned-template",
|
|
451
|
+
"userInvocable": True,
|
|
452
|
+
"disable-model-invocation": True,
|
|
453
|
+
"allowed-tools": "*",
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def _validate_skill_frontmatter(frontmatter: Mapping[str, object]) -> None:
|
|
458
|
+
"""Validate synthesized skill frontmatter against the skill schema.
|
|
459
|
+
|
|
460
|
+
Args:
|
|
461
|
+
frontmatter: The candidate frontmatter mapping.
|
|
462
|
+
|
|
463
|
+
Raises:
|
|
464
|
+
LearningError: When the frontmatter violates the skill schema.
|
|
465
|
+
"""
|
|
466
|
+
validator = Draft202012Validator(_skill_schema(), format_checker=FormatChecker())
|
|
467
|
+
errors = sorted(
|
|
468
|
+
validator.iter_errors(dict(frontmatter)), key=lambda error: error.path
|
|
469
|
+
)
|
|
470
|
+
if errors:
|
|
471
|
+
details = "; ".join(error.message for error in errors)
|
|
472
|
+
raise LearningError(f"invalid promoted skill frontmatter: {details}")
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def promote(
|
|
476
|
+
pattern: LearningPattern,
|
|
477
|
+
*,
|
|
478
|
+
threshold: float,
|
|
479
|
+
skills_dir: Path,
|
|
480
|
+
today: str = "1970-01-01",
|
|
481
|
+
) -> Path | None:
|
|
482
|
+
"""Promote *pattern* to a catalog skill when it meets *threshold*.
|
|
483
|
+
|
|
484
|
+
When ``pattern.confidence >= threshold``, a catalog-skill ``SKILL.md`` is
|
|
485
|
+
written at ``<skills_dir>/<pattern.id>/SKILL.md`` with synthesized
|
|
486
|
+
frontmatter (validated against the skill schema before it lands) plus a
|
|
487
|
+
short Markdown body. When the pattern is below threshold, nothing is
|
|
488
|
+
written.
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
pattern: The scored pattern to consider for promotion.
|
|
492
|
+
threshold: The minimum confidence at which promotion proceeds.
|
|
493
|
+
skills_dir: The directory under which the promoted skill directory is
|
|
494
|
+
created.
|
|
495
|
+
today: ISO 8601 date stamped as the skill's ``last-reviewed`` field;
|
|
496
|
+
passed explicitly to keep promotion deterministic and testable.
|
|
497
|
+
|
|
498
|
+
Returns:
|
|
499
|
+
The path to the written ``SKILL.md`` when promotion proceeds, else
|
|
500
|
+
``None``.
|
|
501
|
+
|
|
502
|
+
Raises:
|
|
503
|
+
LearningError: When the pattern id is not kebab-case, or the
|
|
504
|
+
synthesized frontmatter fails skill-schema validation.
|
|
505
|
+
"""
|
|
506
|
+
if pattern.confidence < threshold:
|
|
507
|
+
return None
|
|
508
|
+
if not _KEBAB_CASE.match(pattern.id):
|
|
509
|
+
raise LearningError(
|
|
510
|
+
f"pattern id must be kebab-case to promote, got {pattern.id!r}"
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
frontmatter = _skill_frontmatter(pattern, today=today)
|
|
514
|
+
_validate_skill_frontmatter(frontmatter)
|
|
515
|
+
|
|
516
|
+
skill_dir = skills_dir / pattern.id
|
|
517
|
+
skill_dir.mkdir(parents=True, exist_ok=True)
|
|
518
|
+
skill_path = skill_dir / _SKILL_FILENAME
|
|
519
|
+
|
|
520
|
+
yaml_block = yaml.safe_dump(frontmatter, sort_keys=True, default_flow_style=False)
|
|
521
|
+
body = (
|
|
522
|
+
f"# {pattern.id}\n\n"
|
|
523
|
+
f"{pattern.summary}\n\n"
|
|
524
|
+
f"Promoted from {pattern.support} learning signal(s) at "
|
|
525
|
+
f"confidence {pattern.confidence:.2f}.\n"
|
|
526
|
+
)
|
|
527
|
+
write_bytes_atomically(skill_path, f"---\n{yaml_block}---\n\n{body}".encode())
|
|
528
|
+
return skill_path
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
__all__ = [
|
|
532
|
+
"LearningError",
|
|
533
|
+
"LearningPattern",
|
|
534
|
+
"LearningSignal",
|
|
535
|
+
"LearningStore",
|
|
536
|
+
"capture",
|
|
537
|
+
"extract_patterns",
|
|
538
|
+
"promote",
|
|
539
|
+
"validate_signal",
|
|
540
|
+
]
|