@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,261 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Coarse map of AI-conventions surfaces present in the working tree.
|
|
4
|
+
|
|
5
|
+
Why this scan exists. The ecosystem ships an AI-conventions surface
|
|
6
|
+
that spans multiple files, each authoritative within its own scope:
|
|
7
|
+
``AGENTS.md`` for the canonical project voice, ``CLAUDE.md`` for the
|
|
8
|
+
Claude Code mirror, ``.github/copilot-instructions.md`` for GitHub Copilot, optional surfaces
|
|
9
|
+
(``site/content/docs/architecture/agents.mdx``, ``.cursorrules``,
|
|
10
|
+
``.windsurfrules``) per the operator's opt-in. The detailed
|
|
11
|
+
section-presence map (``ai-surfaces.json``) is produced by a deeper
|
|
12
|
+
follow-up scan; this coarse pre-scan tells the deeper pass which
|
|
13
|
+
surfaces exist before it begins its content walk.
|
|
14
|
+
|
|
15
|
+
What this scan covers. Existence-and-presence checks for the
|
|
16
|
+
candidate surface paths plus the four mandatory-behavior blocks
|
|
17
|
+
that should appear inside ``AGENTS.md`` and mirror into ``CLAUDE.md``:
|
|
18
|
+
|
|
19
|
+
- ``AGENTS.md`` / ``CLAUDE.md`` mandatory blocks: `structured inquiry` discipline,
|
|
20
|
+
Plans Discipline, Authorship Header, Multi-Surface AI Conventions.
|
|
21
|
+
Each block's presence is detected by header / phrase heuristics.
|
|
22
|
+
- ``.github/copilot-instructions.md``: present / absent.
|
|
23
|
+
- Optional surfaces: presence flags only (no content scan).
|
|
24
|
+
|
|
25
|
+
What this scan reports. A single hit per detected absence or per
|
|
26
|
+
detected partial-presence. The output drives the deeper section-
|
|
27
|
+
presence pass and the optional-surface opt-in inquiry.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import argparse
|
|
33
|
+
import json
|
|
34
|
+
import sys
|
|
35
|
+
from dataclasses import asdict
|
|
36
|
+
from datetime import datetime, timezone
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
from typing import Final
|
|
39
|
+
|
|
40
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
41
|
+
|
|
42
|
+
from _scan_lib import (
|
|
43
|
+
SEVERITY_HIGH,
|
|
44
|
+
SEVERITY_LOW,
|
|
45
|
+
SEVERITY_MEDIUM,
|
|
46
|
+
Hit,
|
|
47
|
+
load_inventory,
|
|
48
|
+
read_text_safely,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Mandatory instruction-surface behavior blocks. Each is detected by a header
|
|
52
|
+
# phrase or canonical paragraph that signals the block's presence.
|
|
53
|
+
# Heuristic-driven; the deeper section-presence scan confirms
|
|
54
|
+
# structural conformance.
|
|
55
|
+
_MANDATORY_BLOCKS: Final[dict[str, list[str]]] = {
|
|
56
|
+
"structured-inquiry discipline": [
|
|
57
|
+
"structured inquiry",
|
|
58
|
+
"interactive-questions",
|
|
59
|
+
"structured-inquiry",
|
|
60
|
+
],
|
|
61
|
+
"plans-discipline": [
|
|
62
|
+
"plans-discipline",
|
|
63
|
+
"Plans Discipline",
|
|
64
|
+
".plans/ at",
|
|
65
|
+
"no-global-plans",
|
|
66
|
+
],
|
|
67
|
+
"authorship-header": [
|
|
68
|
+
"authorship-header",
|
|
69
|
+
"Authorship Header",
|
|
70
|
+
"authorship banner",
|
|
71
|
+
"five canonical lines",
|
|
72
|
+
],
|
|
73
|
+
"multi-surface-AI-conventions": [
|
|
74
|
+
"multi-surface",
|
|
75
|
+
"Multi-Surface",
|
|
76
|
+
"AI conventions",
|
|
77
|
+
"copilot-instructions",
|
|
78
|
+
],
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Optional opt-in AI-conventions surfaces. Each is a path probed for
|
|
82
|
+
# existence; absence is informational, not a finding. The operator
|
|
83
|
+
# ratifies which subset to materialize.
|
|
84
|
+
_OPTIONAL_SURFACES: Final[list[str]] = [
|
|
85
|
+
"site/content/docs/architecture/agents.mdx",
|
|
86
|
+
".cursorrules",
|
|
87
|
+
".windsurfrules",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
# The Copilot instructions surface is authored by the dedicated
|
|
91
|
+
# AI-conventions author pass.
|
|
92
|
+
_COPILOT_PATH: Final[str] = ".github/copilot-instructions.md"
|
|
93
|
+
|
|
94
|
+
# AGENTS.md is the canonical project voice surface; CLAUDE.md is the
|
|
95
|
+
# Claude Code mirror. Absence of either mandatory surface is fatal.
|
|
96
|
+
_AGENTS_PATH: Final[str] = "AGENTS.md"
|
|
97
|
+
_CLAUDE_PATH: Final[str] = "CLAUDE.md"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _scan_instruction_surface(path: str, content: str) -> list[Hit]:
|
|
101
|
+
"""Detect missing mandatory blocks inside an instruction surface."""
|
|
102
|
+
hits: list[Hit] = []
|
|
103
|
+
for block_name, signals in _MANDATORY_BLOCKS.items():
|
|
104
|
+
if not any(signal in content for signal in signals):
|
|
105
|
+
hits.append(
|
|
106
|
+
Hit(
|
|
107
|
+
file=path,
|
|
108
|
+
line=1,
|
|
109
|
+
signal=f"instruction-surface-missing-block: {block_name}",
|
|
110
|
+
severity=SEVERITY_HIGH,
|
|
111
|
+
remediation=(
|
|
112
|
+
f"Author the '{block_name}' block in {path} per"
|
|
113
|
+
" the spec's mandatory-behavior catalog; the"
|
|
114
|
+
" block carries the canonical project directive"
|
|
115
|
+
" for that surface."
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
return hits
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _scan_optional_surfaces(root: Path) -> list[Hit]:
|
|
123
|
+
"""Emit informational hits for opt-in surfaces present on disk."""
|
|
124
|
+
hits: list[Hit] = []
|
|
125
|
+
for path in _OPTIONAL_SURFACES:
|
|
126
|
+
if (root / path).exists():
|
|
127
|
+
hits.append(
|
|
128
|
+
Hit(
|
|
129
|
+
file=path,
|
|
130
|
+
line=0,
|
|
131
|
+
signal=f"optional-surface-present: {path}",
|
|
132
|
+
severity=SEVERITY_LOW,
|
|
133
|
+
remediation=(
|
|
134
|
+
"The deeper section-presence scan confirms"
|
|
135
|
+
" structure; the opt-in inquiry recovers the"
|
|
136
|
+
" operator's ratification for keep / refit /"
|
|
137
|
+
" remove."
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
return hits
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def main(argv: list[str] | None = None) -> int:
|
|
145
|
+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
146
|
+
parser.add_argument(
|
|
147
|
+
"--inventory",
|
|
148
|
+
type=Path,
|
|
149
|
+
default=Path(".audit/inventory.json"),
|
|
150
|
+
)
|
|
151
|
+
parser.add_argument("--root", type=Path, default=Path())
|
|
152
|
+
parser.add_argument(
|
|
153
|
+
"--output",
|
|
154
|
+
type=Path,
|
|
155
|
+
default=Path(".audit/drift-ai-surfaces-coarse.json"),
|
|
156
|
+
)
|
|
157
|
+
args = parser.parse_args(argv)
|
|
158
|
+
|
|
159
|
+
if not args.inventory.exists():
|
|
160
|
+
print(
|
|
161
|
+
f"error: inventory not found at {args.inventory}",
|
|
162
|
+
file=sys.stderr,
|
|
163
|
+
)
|
|
164
|
+
return 1
|
|
165
|
+
|
|
166
|
+
_, sha = load_inventory(args.inventory)
|
|
167
|
+
hits: list[Hit] = []
|
|
168
|
+
|
|
169
|
+
# Mandatory instruction-surface presence and block coverage.
|
|
170
|
+
agents_path = args.root / _AGENTS_PATH
|
|
171
|
+
claude_path = args.root / _CLAUDE_PATH
|
|
172
|
+
if not agents_path.exists():
|
|
173
|
+
hits.append(
|
|
174
|
+
Hit(
|
|
175
|
+
file=_AGENTS_PATH,
|
|
176
|
+
line=0,
|
|
177
|
+
signal="agents-md-absent",
|
|
178
|
+
severity=SEVERITY_HIGH,
|
|
179
|
+
remediation=(
|
|
180
|
+
"Author AGENTS.md at the repository root; this is the"
|
|
181
|
+
" canonical project instruction surface and its absence"
|
|
182
|
+
" disables downstream AI-conventions discipline."
|
|
183
|
+
),
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
else:
|
|
187
|
+
hits.extend(
|
|
188
|
+
_scan_instruction_surface(_AGENTS_PATH, read_text_safely(agents_path))
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
if not claude_path.exists():
|
|
192
|
+
hits.append(
|
|
193
|
+
Hit(
|
|
194
|
+
file=_CLAUDE_PATH,
|
|
195
|
+
line=0,
|
|
196
|
+
signal="claude-md-absent",
|
|
197
|
+
severity=SEVERITY_HIGH,
|
|
198
|
+
remediation=(
|
|
199
|
+
"Author CLAUDE.md as the Claude Code mirror of AGENTS.md;"
|
|
200
|
+
" its absence leaves that harness without the shared"
|
|
201
|
+
" instruction discipline."
|
|
202
|
+
),
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
else:
|
|
206
|
+
hits.extend(
|
|
207
|
+
_scan_instruction_surface(_CLAUDE_PATH, read_text_safely(claude_path))
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Copilot instructions presence.
|
|
211
|
+
copilot_path = args.root / _COPILOT_PATH
|
|
212
|
+
if not copilot_path.exists():
|
|
213
|
+
hits.append(
|
|
214
|
+
Hit(
|
|
215
|
+
file=_COPILOT_PATH,
|
|
216
|
+
line=0,
|
|
217
|
+
signal="copilot-instructions-absent",
|
|
218
|
+
severity=SEVERITY_MEDIUM,
|
|
219
|
+
remediation=(
|
|
220
|
+
"The AI-conventions author pass produces"
|
|
221
|
+
" .github/copilot-instructions.md per the canonical"
|
|
222
|
+
" nine-section structure. Until then the Copilot"
|
|
223
|
+
" surface defaults to its built-in heuristics without"
|
|
224
|
+
" the ecosystem's directive overlay."
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Optional opt-in surfaces.
|
|
230
|
+
hits.extend(_scan_optional_surfaces(args.root))
|
|
231
|
+
|
|
232
|
+
payload = {
|
|
233
|
+
"generated": datetime.now(timezone.utc).isoformat(),
|
|
234
|
+
"scanner": "scan_ai_surfaces_coarse",
|
|
235
|
+
"inventory-source-sha256": sha,
|
|
236
|
+
"agents-md-present": agents_path.exists(),
|
|
237
|
+
"claude-md-present": claude_path.exists(),
|
|
238
|
+
"copilot-instructions-present": copilot_path.exists(),
|
|
239
|
+
"optional-surfaces-present": [
|
|
240
|
+
p for p in _OPTIONAL_SURFACES if (args.root / p).exists()
|
|
241
|
+
],
|
|
242
|
+
"hit-count": len(hits),
|
|
243
|
+
"hits": [asdict(h) for h in hits],
|
|
244
|
+
}
|
|
245
|
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
246
|
+
args.output.write_text(
|
|
247
|
+
json.dumps(payload, indent=2) + "\n",
|
|
248
|
+
encoding="utf-8",
|
|
249
|
+
)
|
|
250
|
+
print(
|
|
251
|
+
f"scan_ai_surfaces_coarse: AGENTS.md={agents_path.exists()},"
|
|
252
|
+
f" CLAUDE.md={claude_path.exists()},"
|
|
253
|
+
f" copilot-instructions={copilot_path.exists()},"
|
|
254
|
+
f" optional={len(payload['optional-surfaces-present'])} of"
|
|
255
|
+
f" {len(_OPTIONAL_SURFACES)}; total hits={len(hits)}"
|
|
256
|
+
)
|
|
257
|
+
return 0
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
if __name__ == "__main__":
|
|
261
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Detect references to deprecated or renamed features.
|
|
4
|
+
|
|
5
|
+
Why this scan exists. Narrative artifacts (rules, agents, commands,
|
|
6
|
+
hook context, docs) accumulate references to vendor features as the
|
|
7
|
+
upstream surface evolves: model identifiers retire, tool names change,
|
|
8
|
+
flags are renamed, capabilities are deprecated. A reference to a
|
|
9
|
+
retired feature is functionally a broken pointer — it leads readers
|
|
10
|
+
toward behavior the runtime no longer supports. The scan walks every
|
|
11
|
+
narrative surface for tokens drawn from a curated deprecated list and
|
|
12
|
+
emits a finding per occurrence so the refit phases can replace them.
|
|
13
|
+
|
|
14
|
+
What this scan covers. Deprecated tokens live in
|
|
15
|
+
``src/apothem/audit/deprecated-tokens.txt`` (one token per line; comments
|
|
16
|
+
prefixed with ``#``; case-sensitive substring match). The list is
|
|
17
|
+
intentionally seeded empty: at first run there is no proof any specific
|
|
18
|
+
token is deprecated, and a populated allow-list with no evidence would
|
|
19
|
+
fabricate findings. Tokens are added when the operator confirms a
|
|
20
|
+
specific feature has retired; the file then becomes a living deprecation
|
|
21
|
+
ledger.
|
|
22
|
+
|
|
23
|
+
What this scan excludes. Source code (``*.py``, ``*.sh``, ``*.ps1``)
|
|
24
|
+
is also walked because deprecated identifiers can appear as string
|
|
25
|
+
literals in tooling scaffolds. Memory and plan-artifact files are
|
|
26
|
+
excluded per the shared narrative-surface filter.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import argparse
|
|
32
|
+
import sys
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from typing import Any
|
|
35
|
+
|
|
36
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
37
|
+
|
|
38
|
+
from _scan_lib import (
|
|
39
|
+
CONTENT_ROOT,
|
|
40
|
+
NARRATIVE_CLASSES,
|
|
41
|
+
SEVERITY_MEDIUM,
|
|
42
|
+
Hit,
|
|
43
|
+
WalkCallback,
|
|
44
|
+
emit_json,
|
|
45
|
+
load_inventory,
|
|
46
|
+
walk_narrative_surfaces,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _load_deprecated_tokens(token_file: Path) -> list[str]:
|
|
51
|
+
"""Parse the deprecated-token catalog.
|
|
52
|
+
|
|
53
|
+
Lines beginning with ``#`` or empty lines are comments. Returns an
|
|
54
|
+
empty list when the file is absent — the scan then emits zero hits,
|
|
55
|
+
which is correct (no deprecated tokens declared, no findings).
|
|
56
|
+
"""
|
|
57
|
+
if not token_file.exists():
|
|
58
|
+
return []
|
|
59
|
+
tokens: list[str] = []
|
|
60
|
+
for raw in token_file.read_text(encoding="utf-8").splitlines():
|
|
61
|
+
line = raw.strip()
|
|
62
|
+
if not line or line.startswith("#"):
|
|
63
|
+
continue
|
|
64
|
+
tokens.append(line)
|
|
65
|
+
return tokens
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _scan_file(tokens: list[str]) -> WalkCallback:
|
|
69
|
+
"""Build a callback that records every line containing any token."""
|
|
70
|
+
|
|
71
|
+
def _walk(path: Path, record: dict[str, Any], content: str) -> list[Hit]:
|
|
72
|
+
if not tokens:
|
|
73
|
+
return []
|
|
74
|
+
hits: list[Hit] = []
|
|
75
|
+
for lineno, line in enumerate(content.splitlines(), start=1):
|
|
76
|
+
for token in tokens:
|
|
77
|
+
if token in line:
|
|
78
|
+
hits.append(
|
|
79
|
+
Hit(
|
|
80
|
+
file=record["path"],
|
|
81
|
+
line=lineno,
|
|
82
|
+
signal=f"deprecated-feature-reference: {token}",
|
|
83
|
+
severity=SEVERITY_MEDIUM,
|
|
84
|
+
remediation=(
|
|
85
|
+
f"Replace '{token}' with the current vendor identifier;"
|
|
86
|
+
" consult the deprecated-tokens.txt catalog for the"
|
|
87
|
+
" replacement guidance."
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
return hits
|
|
92
|
+
|
|
93
|
+
return _walk
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def main(argv: list[str] | None = None) -> int:
|
|
97
|
+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
98
|
+
parser.add_argument(
|
|
99
|
+
"--inventory",
|
|
100
|
+
type=Path,
|
|
101
|
+
default=Path(".audit/inventory.json"),
|
|
102
|
+
help="Path to inventory.json (default: ./.audit/inventory.json)",
|
|
103
|
+
)
|
|
104
|
+
parser.add_argument(
|
|
105
|
+
"--root",
|
|
106
|
+
type=Path,
|
|
107
|
+
default=CONTENT_ROOT,
|
|
108
|
+
help="Content root the inventory paths resolve against "
|
|
109
|
+
"(default: the src/apothem package).",
|
|
110
|
+
)
|
|
111
|
+
parser.add_argument(
|
|
112
|
+
"--tokens",
|
|
113
|
+
type=Path,
|
|
114
|
+
default=Path("src/apothem/audit/deprecated-tokens.txt"),
|
|
115
|
+
help="Deprecated-token catalog (default: src/apothem/audit/deprecated-tokens.txt)",
|
|
116
|
+
)
|
|
117
|
+
parser.add_argument(
|
|
118
|
+
"--output",
|
|
119
|
+
type=Path,
|
|
120
|
+
default=Path(".audit/drift-feature-refs.json"),
|
|
121
|
+
help="Output JSON path (default: ./.audit/drift-feature-refs.json)",
|
|
122
|
+
)
|
|
123
|
+
args = parser.parse_args(argv)
|
|
124
|
+
|
|
125
|
+
if not args.inventory.exists():
|
|
126
|
+
print(f"error: inventory not found at {args.inventory}", file=sys.stderr)
|
|
127
|
+
return 1
|
|
128
|
+
|
|
129
|
+
records, sha = load_inventory(args.inventory)
|
|
130
|
+
tokens = _load_deprecated_tokens(args.tokens)
|
|
131
|
+
hits = walk_narrative_surfaces(records, args.root, _scan_file(tokens))
|
|
132
|
+
emit_json(args.output, "scan_drift_features", hits, sha)
|
|
133
|
+
narrative_count = sum(1 for r in records if r.get("class") in NARRATIVE_CLASSES)
|
|
134
|
+
print(
|
|
135
|
+
f"scan_drift_features: {len(hits)} hit(s) across "
|
|
136
|
+
f"{narrative_count} narrative files "
|
|
137
|
+
f"(deprecated-token catalog: {len(tokens)} entries)"
|
|
138
|
+
)
|
|
139
|
+
return 0
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
if __name__ == "__main__":
|
|
143
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Scan frontmatter consistency across artifact classes that carry it.
|
|
4
|
+
|
|
5
|
+
Why this scan exists. Agents, commands, skills, output-styles, and
|
|
6
|
+
several plan-artifact classes carry YAML frontmatter at the head of
|
|
7
|
+
each file. Per-class schemas have not yet been ratified at the time of
|
|
8
|
+
this scan, so the check is heuristic-driven: walk every artifact in a
|
|
9
|
+
class, sample the dominant key set and key order, and flag any sibling
|
|
10
|
+
that diverges. The scan also flags any TOML-style frontmatter
|
|
11
|
+
(``+++...+++``) because the discovered convention across every existing
|
|
12
|
+
file is YAML (``---...---``).
|
|
13
|
+
|
|
14
|
+
What this scan covers per class.
|
|
15
|
+
|
|
16
|
+
- **Required-key drift.** Keys present in at least 80% of the class's
|
|
17
|
+
files are treated as the de-facto required set; siblings missing any
|
|
18
|
+
of those keys produce a finding.
|
|
19
|
+
- **Key-order drift.** The dominant key sequence is the de-facto order;
|
|
20
|
+
siblings whose key sequence diverges produce a finding (rearranging a
|
|
21
|
+
frontmatter block is a near-zero-cost fix that compounds readability).
|
|
22
|
+
- **Format drift.** Any non-YAML frontmatter delimiter (``+++``,
|
|
23
|
+
``;;;``, JSON object as first content) is flagged irrespective of
|
|
24
|
+
class.
|
|
25
|
+
- **Empty required field.** A required key with an empty string /
|
|
26
|
+
``null`` value is flagged separately because the absence of value is
|
|
27
|
+
often more dangerous than the absence of key.
|
|
28
|
+
|
|
29
|
+
What this scan excludes. Memory and plan-artifact classes are excluded
|
|
30
|
+
from the dominant-pattern computation because they are not refit
|
|
31
|
+
targets at this phase. Files without any frontmatter that belong to a
|
|
32
|
+
class where frontmatter is universally present are flagged as
|
|
33
|
+
``frontmatter-absent``.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import argparse
|
|
39
|
+
import sys
|
|
40
|
+
from collections import Counter
|
|
41
|
+
from collections.abc import Iterable
|
|
42
|
+
from pathlib import Path
|
|
43
|
+
from typing import Any, Final
|
|
44
|
+
|
|
45
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
46
|
+
|
|
47
|
+
from _scan_lib import (
|
|
48
|
+
CONTENT_ROOT,
|
|
49
|
+
SEVERITY_MEDIUM,
|
|
50
|
+
Hit,
|
|
51
|
+
emit_json,
|
|
52
|
+
load_inventory,
|
|
53
|
+
read_text_safely,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Artifact classes that carry frontmatter as a convention. Settings
|
|
57
|
+
# files are JSON; scaffolding files vary; only the listed classes are
|
|
58
|
+
# walked for frontmatter consistency.
|
|
59
|
+
FRONTMATTER_CLASSES: Final[frozenset[str]] = frozenset(
|
|
60
|
+
{"agent", "command", "skill", "output-style"}
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Frontmatter requires presence in at least this fraction of a class's
|
|
64
|
+
# files to be treated as the de-facto required set.
|
|
65
|
+
REQUIRED_THRESHOLD: Final[float] = 0.8
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _extract_frontmatter(content: str) -> tuple[list[str], str | None]:
|
|
69
|
+
"""Return ``(ordered-keys, delimiter)`` for the document's
|
|
70
|
+
frontmatter, or ``([], None)`` if no frontmatter is present.
|
|
71
|
+
|
|
72
|
+
Recognizes YAML (``---``), TOML (``+++``), and a permissive
|
|
73
|
+
semicolon delimiter (``;;;``). Inside a YAML block, only top-level
|
|
74
|
+
keys (un-indented ``key:`` lines) are captured; nested keys are not
|
|
75
|
+
part of the frontmatter contract.
|
|
76
|
+
"""
|
|
77
|
+
lines = content.splitlines()
|
|
78
|
+
if not lines:
|
|
79
|
+
return [], None
|
|
80
|
+
first = lines[0].strip()
|
|
81
|
+
if first not in ("---", "+++", ";;;"):
|
|
82
|
+
return [], None
|
|
83
|
+
delimiter = first
|
|
84
|
+
keys: list[str] = []
|
|
85
|
+
for line in lines[1:]:
|
|
86
|
+
stripped = line.strip()
|
|
87
|
+
if stripped == delimiter:
|
|
88
|
+
return keys, delimiter
|
|
89
|
+
# Top-level YAML key: un-indented `key:` (no leading whitespace).
|
|
90
|
+
if line and not line[0].isspace() and ":" in line:
|
|
91
|
+
key = line.split(":", 1)[0].strip()
|
|
92
|
+
if key and not key.startswith("#"):
|
|
93
|
+
keys.append(key)
|
|
94
|
+
# Closing delimiter not found; treat as malformed frontmatter.
|
|
95
|
+
return keys, "malformed"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _empty_value_keys(content: str, keys: list[str]) -> list[str]:
|
|
99
|
+
"""Return keys whose value is empty / null / quoted-empty."""
|
|
100
|
+
if not keys:
|
|
101
|
+
return []
|
|
102
|
+
empty: list[str] = []
|
|
103
|
+
in_block = False
|
|
104
|
+
for line in content.splitlines():
|
|
105
|
+
stripped = line.strip()
|
|
106
|
+
if stripped == "---":
|
|
107
|
+
if in_block:
|
|
108
|
+
break
|
|
109
|
+
in_block = True
|
|
110
|
+
continue
|
|
111
|
+
if not in_block or line[:1].isspace() or ":" not in line:
|
|
112
|
+
continue
|
|
113
|
+
key, _, value = line.partition(":")
|
|
114
|
+
key = key.strip()
|
|
115
|
+
value = value.strip()
|
|
116
|
+
if key in keys and value in ("", "''", '""', "null", "~"):
|
|
117
|
+
empty.append(key)
|
|
118
|
+
return empty
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _gather_class_records(
|
|
122
|
+
records: Iterable[dict[str, Any]], cls: str
|
|
123
|
+
) -> list[dict[str, Any]]:
|
|
124
|
+
return [r for r in records if r.get("class") == cls]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _compute_class_baseline(
|
|
128
|
+
class_records: list[dict[str, Any]], root: Path
|
|
129
|
+
) -> tuple[set[str], list[str]]:
|
|
130
|
+
"""Return ``(required-keys, dominant-order)`` for a class."""
|
|
131
|
+
if not class_records:
|
|
132
|
+
return set(), []
|
|
133
|
+
key_presence: Counter[str] = Counter()
|
|
134
|
+
order_counter: Counter[tuple[str, ...]] = Counter()
|
|
135
|
+
n = 0
|
|
136
|
+
for record in class_records:
|
|
137
|
+
path = root / record["path"]
|
|
138
|
+
content = read_text_safely(path)
|
|
139
|
+
if not content:
|
|
140
|
+
continue
|
|
141
|
+
keys, delimiter = _extract_frontmatter(content)
|
|
142
|
+
if not keys or delimiter != "---":
|
|
143
|
+
continue
|
|
144
|
+
n += 1
|
|
145
|
+
for k in keys:
|
|
146
|
+
key_presence[k] += 1
|
|
147
|
+
order_counter[tuple(keys)] += 1
|
|
148
|
+
if n == 0:
|
|
149
|
+
return set(), []
|
|
150
|
+
threshold = max(1, int(REQUIRED_THRESHOLD * n))
|
|
151
|
+
required = {k for k, count in key_presence.items() if count >= threshold}
|
|
152
|
+
dominant_order = list(order_counter.most_common(1)[0][0]) if order_counter else []
|
|
153
|
+
return required, dominant_order
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _scan_class(cls: str, records: list[dict[str, Any]], root: Path) -> list[Hit]:
|
|
157
|
+
"""Walk a single class and emit hits for divergent siblings."""
|
|
158
|
+
required, dominant_order = _compute_class_baseline(records, root)
|
|
159
|
+
hits: list[Hit] = []
|
|
160
|
+
for record in records:
|
|
161
|
+
rel = record["path"]
|
|
162
|
+
path = root / rel
|
|
163
|
+
content = read_text_safely(path)
|
|
164
|
+
if not content:
|
|
165
|
+
continue
|
|
166
|
+
keys, delimiter = _extract_frontmatter(content)
|
|
167
|
+
hits.extend(
|
|
168
|
+
_diagnose_record(rel, content, keys, delimiter, required, dominant_order)
|
|
169
|
+
)
|
|
170
|
+
return hits
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _diagnose_record(
|
|
174
|
+
rel: str,
|
|
175
|
+
content: str,
|
|
176
|
+
keys: list[str],
|
|
177
|
+
delimiter: str | None,
|
|
178
|
+
required: set[str],
|
|
179
|
+
dominant_order: list[str],
|
|
180
|
+
) -> list[Hit]:
|
|
181
|
+
"""Return all findings for a single file's frontmatter state."""
|
|
182
|
+
hits: list[Hit] = []
|
|
183
|
+
if delimiter is None:
|
|
184
|
+
if required:
|
|
185
|
+
hits.append(
|
|
186
|
+
Hit(
|
|
187
|
+
file=rel,
|
|
188
|
+
line=1,
|
|
189
|
+
signal="frontmatter-absent",
|
|
190
|
+
severity=SEVERITY_MEDIUM,
|
|
191
|
+
remediation=(
|
|
192
|
+
"Add the class's required frontmatter keys at the"
|
|
193
|
+
" head of the file enclosed by '---' delimiters."
|
|
194
|
+
),
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
return hits
|
|
198
|
+
if delimiter == "+++" or delimiter == ";;;" or delimiter == "malformed":
|
|
199
|
+
hits.append(
|
|
200
|
+
Hit(
|
|
201
|
+
file=rel,
|
|
202
|
+
line=1,
|
|
203
|
+
signal=f"frontmatter-non-yaml-or-malformed: {delimiter}",
|
|
204
|
+
severity=SEVERITY_MEDIUM,
|
|
205
|
+
remediation=(
|
|
206
|
+
"Convert the frontmatter to the YAML form delimited"
|
|
207
|
+
" by '---'; non-YAML delimiters break tooling that"
|
|
208
|
+
" expects the convention."
|
|
209
|
+
),
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
missing = required - set(keys)
|
|
213
|
+
for key in sorted(missing):
|
|
214
|
+
hits.append(
|
|
215
|
+
Hit(
|
|
216
|
+
file=rel,
|
|
217
|
+
line=1,
|
|
218
|
+
signal=f"frontmatter-missing-required: {key}",
|
|
219
|
+
severity=SEVERITY_MEDIUM,
|
|
220
|
+
remediation=(
|
|
221
|
+
f"Add '{key}: <value>' to the frontmatter; the key is"
|
|
222
|
+
" present in at least 80% of the class's files."
|
|
223
|
+
),
|
|
224
|
+
)
|
|
225
|
+
)
|
|
226
|
+
if dominant_order and keys != dominant_order and not missing:
|
|
227
|
+
# Only flag order drift when the file has the full required set;
|
|
228
|
+
# missing-key cases dominate and ordering is secondary.
|
|
229
|
+
hits.append(
|
|
230
|
+
Hit(
|
|
231
|
+
file=rel,
|
|
232
|
+
line=1,
|
|
233
|
+
signal="frontmatter-key-order-drift",
|
|
234
|
+
severity=SEVERITY_MEDIUM,
|
|
235
|
+
remediation=(
|
|
236
|
+
"Reorder the frontmatter keys to match the class's"
|
|
237
|
+
f" dominant sequence: {dominant_order}."
|
|
238
|
+
),
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
for key in _empty_value_keys(content, list(required & set(keys))):
|
|
242
|
+
hits.append(
|
|
243
|
+
Hit(
|
|
244
|
+
file=rel,
|
|
245
|
+
line=1,
|
|
246
|
+
signal=f"frontmatter-empty-required-value: {key}",
|
|
247
|
+
severity=SEVERITY_MEDIUM,
|
|
248
|
+
remediation=(
|
|
249
|
+
f"Populate '{key}' with a non-empty value; the key is"
|
|
250
|
+
" required but its value is blank or null."
|
|
251
|
+
),
|
|
252
|
+
)
|
|
253
|
+
)
|
|
254
|
+
return hits
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def main(argv: list[str] | None = None) -> int:
|
|
258
|
+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
259
|
+
parser.add_argument(
|
|
260
|
+
"--inventory",
|
|
261
|
+
type=Path,
|
|
262
|
+
default=Path(".audit/inventory.json"),
|
|
263
|
+
)
|
|
264
|
+
parser.add_argument("--root", type=Path, default=CONTENT_ROOT)
|
|
265
|
+
parser.add_argument(
|
|
266
|
+
"--output",
|
|
267
|
+
type=Path,
|
|
268
|
+
default=Path(".audit/drift-frontmatter.json"),
|
|
269
|
+
)
|
|
270
|
+
args = parser.parse_args(argv)
|
|
271
|
+
|
|
272
|
+
if not args.inventory.exists():
|
|
273
|
+
print(
|
|
274
|
+
f"error: inventory not found at {args.inventory}",
|
|
275
|
+
file=sys.stderr,
|
|
276
|
+
)
|
|
277
|
+
return 1
|
|
278
|
+
|
|
279
|
+
records, sha = load_inventory(args.inventory)
|
|
280
|
+
hits: list[Hit] = []
|
|
281
|
+
per_class_counts: dict[str, int] = {}
|
|
282
|
+
for cls in sorted(FRONTMATTER_CLASSES):
|
|
283
|
+
cls_records = _gather_class_records(records, cls)
|
|
284
|
+
per_class_counts[cls] = len(cls_records)
|
|
285
|
+
hits.extend(_scan_class(cls, cls_records, args.root))
|
|
286
|
+
emit_json(args.output, "scan_frontmatter", hits, sha)
|
|
287
|
+
summary = ", ".join(f"{c}={n}" for c, n in per_class_counts.items())
|
|
288
|
+
print(f"scan_frontmatter: {len(hits)} hit(s) across classes [{summary}]")
|
|
289
|
+
return 0
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
if __name__ == "__main__":
|
|
293
|
+
raise SystemExit(main())
|