@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,319 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Flag HEAD commit messages that drift from the Conventional-Commits grammar.
|
|
4
|
+
|
|
5
|
+
Why this enforcement exists. The apothem source repo
|
|
6
|
+
ratifies enterprise-grade Conventional-Commits discipline at the
|
|
7
|
+
project ``CLAUDE.md`` Coding Conventions block: every commit subject
|
|
8
|
+
follows ``<type>(<scope>): <subject>`` where type is drawn from the
|
|
9
|
+
closed set {feat, fix, chore, docs, refactor, test, perf, ci, build,
|
|
10
|
+
style, revert, release}, scope is optional kebab/comma-form, subject is <= 72
|
|
11
|
+
characters, written in imperative mood, and carries no trailing period.
|
|
12
|
+
Drift is silent in git history; the standalone validator reads the
|
|
13
|
+
HEAD commit message via ``git log -1 --pretty=%B`` and reports the
|
|
14
|
+
specific drift classes that fail.
|
|
15
|
+
|
|
16
|
+
Scope. Corpus-level standalone validator. Reads HEAD only. Merge commit
|
|
17
|
+
subjects (``Merge branch ...`` / ``Merge pull request ...``) pass
|
|
18
|
+
through — the merger does not author the merged content's subject form.
|
|
19
|
+
Shallow-clone or no-git contexts (no ``.git/``, empty repo) exit 0 with
|
|
20
|
+
an informational status so the validator never blocks a fresh clone or
|
|
21
|
+
a non-git working tree.
|
|
22
|
+
|
|
23
|
+
Exit semantics. Exits 0 when HEAD is conformant (or informationally
|
|
24
|
+
exempt). Exits 2 on any drift class. The exit-2 convention matches the
|
|
25
|
+
conformity-gate orchestrator's ``EXIT_FAIL`` constant.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import json
|
|
31
|
+
import re
|
|
32
|
+
import subprocess
|
|
33
|
+
import sys
|
|
34
|
+
from dataclasses import asdict, dataclass, field
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
from typing import Final
|
|
37
|
+
|
|
38
|
+
GREP_NAME: Final[str] = "conventional-commit-grep"
|
|
39
|
+
RULE_ANCHOR: Final[str] = "CLAUDE.md Coding Conventions — Conventional Commits"
|
|
40
|
+
|
|
41
|
+
EXIT_PASS: Final[int] = 0
|
|
42
|
+
EXIT_FAIL: Final[int] = 2
|
|
43
|
+
|
|
44
|
+
# Bound the gate-path git invocation so a hung `git log` cannot block a
|
|
45
|
+
# commit indefinitely; a timeout is treated as a transient git error.
|
|
46
|
+
GIT_TIMEOUT_SECONDS: Final[int] = 5
|
|
47
|
+
|
|
48
|
+
ALLOWED_TYPES: Final[frozenset[str]] = frozenset(
|
|
49
|
+
{
|
|
50
|
+
"feat",
|
|
51
|
+
"fix",
|
|
52
|
+
"chore",
|
|
53
|
+
"docs",
|
|
54
|
+
"refactor",
|
|
55
|
+
"test",
|
|
56
|
+
"perf",
|
|
57
|
+
"ci",
|
|
58
|
+
"build",
|
|
59
|
+
"style",
|
|
60
|
+
"revert",
|
|
61
|
+
"release",
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
MAX_SUBJECT_LEN: Final[int] = 72
|
|
66
|
+
|
|
67
|
+
# Full Conventional-Commits subject form. The scope is optional and
|
|
68
|
+
# constrained to kebab-case / comma-separated lowercase ASCII tokens.
|
|
69
|
+
CONVENTIONAL_RE: Final[re.Pattern[str]] = re.compile(
|
|
70
|
+
r"^(?P<type>[a-z]+)(?:\((?P<scope>[^)]+)\))?(?P<bang>!?): (?P<subject>.+)$"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Scope shape: lowercase letters, digits, hyphens, commas, forward
|
|
74
|
+
# slashes (e.g. ``harness/claude_code`` style). Uppercase or spaces
|
|
75
|
+
# trigger the scope-malformed drift class.
|
|
76
|
+
SCOPE_RE: Final[re.Pattern[str]] = re.compile(r"^[a-z0-9,\-/_]+$")
|
|
77
|
+
|
|
78
|
+
# Imperative-mood heuristic: forbid first verbs ending in -ed / -ing,
|
|
79
|
+
# and 3rd-person singular -s on common verbs. Heuristic only; intended
|
|
80
|
+
# to catch the most frequent drift forms (``added X``, ``adding X``,
|
|
81
|
+
# ``adds X``) without policing every English verb.
|
|
82
|
+
NON_IMPERATIVE_TAIL_RE: Final[re.Pattern[str]] = re.compile(
|
|
83
|
+
r"^(?P<verb>[a-z]+?)(?P<tail>ed|ing)$", re.IGNORECASE
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Common 3rd-person-singular verbs that surface as commit-message drift.
|
|
87
|
+
# Not exhaustive — intentionally narrow to avoid false positives on
|
|
88
|
+
# legitimate plural nouns acting as the first token (rare in imperative
|
|
89
|
+
# subjects but possible in chore/docs surfaces).
|
|
90
|
+
THIRD_PERSON_SINGULAR_VERBS: Final[frozenset[str]] = frozenset(
|
|
91
|
+
{
|
|
92
|
+
"adds",
|
|
93
|
+
"removes",
|
|
94
|
+
"updates",
|
|
95
|
+
"fixes",
|
|
96
|
+
"refactors",
|
|
97
|
+
"renames",
|
|
98
|
+
"moves",
|
|
99
|
+
"creates",
|
|
100
|
+
"deletes",
|
|
101
|
+
"introduces",
|
|
102
|
+
"implements",
|
|
103
|
+
"applies",
|
|
104
|
+
"changes",
|
|
105
|
+
"handles",
|
|
106
|
+
"wires",
|
|
107
|
+
"ensures",
|
|
108
|
+
"supports",
|
|
109
|
+
"enables",
|
|
110
|
+
"disables",
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
MERGE_PREFIXES: Final[tuple[str, ...]] = (
|
|
115
|
+
"Merge branch ",
|
|
116
|
+
"Merge pull request ",
|
|
117
|
+
"Merge remote-tracking branch ",
|
|
118
|
+
"Merge tag ",
|
|
119
|
+
# GitHub Actions checks out a synthetic `pull/N/merge` ref whose HEAD
|
|
120
|
+
# subject is `Merge <sha> into <sha>`; it is machine-authored, not a
|
|
121
|
+
# conventional commit. The generic "Merge " prefix covers this form (no
|
|
122
|
+
# conventional subject starts with a capitalized "Merge ").
|
|
123
|
+
"Merge ",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@dataclass(frozen=True)
|
|
128
|
+
class Drift:
|
|
129
|
+
"""One drift class detected on the HEAD commit subject."""
|
|
130
|
+
|
|
131
|
+
klass: str
|
|
132
|
+
detail: str
|
|
133
|
+
subject: str
|
|
134
|
+
rule: str = RULE_ANCHOR
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@dataclass(frozen=True)
|
|
138
|
+
class GrepResult:
|
|
139
|
+
grep: str
|
|
140
|
+
passed: bool
|
|
141
|
+
status: str
|
|
142
|
+
subject: str | None = None
|
|
143
|
+
drifts: list[Drift] = field(default_factory=list)
|
|
144
|
+
|
|
145
|
+
def to_json(self) -> str:
|
|
146
|
+
payload = {
|
|
147
|
+
"grep": self.grep,
|
|
148
|
+
"passed": self.passed,
|
|
149
|
+
"status": self.status,
|
|
150
|
+
"subject": self.subject,
|
|
151
|
+
"drifts": [asdict(d) for d in self.drifts],
|
|
152
|
+
}
|
|
153
|
+
return json.dumps(payload, indent=2)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _read_head_subject(root: Path) -> tuple[str | None, str]:
|
|
157
|
+
"""Return ``(subject, status)`` for the HEAD commit.
|
|
158
|
+
|
|
159
|
+
``status`` is one of ``ok`` (subject populated), ``not-a-git-repo``,
|
|
160
|
+
``no-commits-yet``, ``git-unavailable``. The subject is None for
|
|
161
|
+
every non-``ok`` status so the caller can short-circuit.
|
|
162
|
+
"""
|
|
163
|
+
if not (root / ".git").exists():
|
|
164
|
+
return None, "not-a-git-repo"
|
|
165
|
+
try:
|
|
166
|
+
completed = subprocess.run(
|
|
167
|
+
["git", "log", "-1", "--pretty=%B"], # noqa: S607 — literal git argv, trusted PATH lookup, no shell
|
|
168
|
+
cwd=str(root),
|
|
169
|
+
capture_output=True,
|
|
170
|
+
text=True,
|
|
171
|
+
check=False,
|
|
172
|
+
encoding="utf-8",
|
|
173
|
+
timeout=GIT_TIMEOUT_SECONDS,
|
|
174
|
+
)
|
|
175
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
176
|
+
return None, "git-unavailable"
|
|
177
|
+
if completed.returncode != 0:
|
|
178
|
+
# Empty repo or other transient git error — never block.
|
|
179
|
+
return None, "no-commits-yet"
|
|
180
|
+
body = completed.stdout or ""
|
|
181
|
+
first_line = body.splitlines()[0] if body.strip() else ""
|
|
182
|
+
if not first_line:
|
|
183
|
+
return None, "no-commits-yet"
|
|
184
|
+
return first_line, "ok"
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _is_merge_subject(subject: str) -> bool:
|
|
188
|
+
return any(subject.startswith(prefix) for prefix in MERGE_PREFIXES)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _classify_drift(subject: str) -> list[Drift]:
|
|
192
|
+
"""Return every drift class the subject exhibits (empty list = clean)."""
|
|
193
|
+
drifts: list[Drift] = []
|
|
194
|
+
match = CONVENTIONAL_RE.match(subject)
|
|
195
|
+
if match is None:
|
|
196
|
+
drifts.append(
|
|
197
|
+
Drift(
|
|
198
|
+
klass="missing-type",
|
|
199
|
+
detail="subject does not match '<type>(<scope>)?: <subject>'",
|
|
200
|
+
subject=subject,
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
# Without a parsed shape the remaining drift classes are not
|
|
204
|
+
# individually decidable; return the structural failure alone.
|
|
205
|
+
return drifts
|
|
206
|
+
|
|
207
|
+
type_token = match.group("type")
|
|
208
|
+
scope_token = match.group("scope")
|
|
209
|
+
body_subject = match.group("subject")
|
|
210
|
+
|
|
211
|
+
if type_token not in ALLOWED_TYPES:
|
|
212
|
+
drifts.append(
|
|
213
|
+
Drift(
|
|
214
|
+
klass="invalid-type",
|
|
215
|
+
detail=f"type '{type_token}' not in {sorted(ALLOWED_TYPES)}",
|
|
216
|
+
subject=subject,
|
|
217
|
+
)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
if scope_token is not None and not SCOPE_RE.match(scope_token):
|
|
221
|
+
drifts.append(
|
|
222
|
+
Drift(
|
|
223
|
+
klass="scope-malformed",
|
|
224
|
+
detail=(
|
|
225
|
+
f"scope '{scope_token}' contains characters outside [a-z0-9,-/_]"
|
|
226
|
+
),
|
|
227
|
+
subject=subject,
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
if len(subject) > MAX_SUBJECT_LEN:
|
|
232
|
+
drifts.append(
|
|
233
|
+
Drift(
|
|
234
|
+
klass="subject-too-long",
|
|
235
|
+
detail=f"subject length {len(subject)} > {MAX_SUBJECT_LEN}",
|
|
236
|
+
subject=subject,
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
if body_subject.endswith("."):
|
|
241
|
+
drifts.append(
|
|
242
|
+
Drift(
|
|
243
|
+
klass="subject-ends-with-period",
|
|
244
|
+
detail="subject body ends with '.' — strip the trailing period",
|
|
245
|
+
subject=subject,
|
|
246
|
+
)
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
first_word = body_subject.split(" ", 1)[0] if body_subject else ""
|
|
250
|
+
if first_word:
|
|
251
|
+
tail = NON_IMPERATIVE_TAIL_RE.match(first_word)
|
|
252
|
+
lower = first_word.lower()
|
|
253
|
+
if tail is not None:
|
|
254
|
+
drifts.append(
|
|
255
|
+
Drift(
|
|
256
|
+
klass="non-imperative-verb",
|
|
257
|
+
detail=(
|
|
258
|
+
f"first word '{first_word}' ends with "
|
|
259
|
+
f"'-{tail.group('tail')}' — use imperative mood"
|
|
260
|
+
),
|
|
261
|
+
subject=subject,
|
|
262
|
+
)
|
|
263
|
+
)
|
|
264
|
+
elif lower in THIRD_PERSON_SINGULAR_VERBS:
|
|
265
|
+
drifts.append(
|
|
266
|
+
Drift(
|
|
267
|
+
klass="non-imperative-verb",
|
|
268
|
+
detail=(
|
|
269
|
+
f"first word '{first_word}' is 3rd-person singular — "
|
|
270
|
+
"use imperative mood"
|
|
271
|
+
),
|
|
272
|
+
subject=subject,
|
|
273
|
+
)
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
return drifts
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def check_repo(root: Path) -> GrepResult:
|
|
280
|
+
"""Inspect ``root``'s HEAD commit; return a structured result.
|
|
281
|
+
|
|
282
|
+
Pre-conditions: ``root`` is the working-tree directory.
|
|
283
|
+
Post-conditions: ``result.passed`` is True iff HEAD is conformant
|
|
284
|
+
or the repo state is informationally exempt (no .git, no commits).
|
|
285
|
+
"""
|
|
286
|
+
subject, status = _read_head_subject(root)
|
|
287
|
+
if status != "ok" or subject is None:
|
|
288
|
+
return GrepResult(
|
|
289
|
+
grep=GREP_NAME,
|
|
290
|
+
passed=True,
|
|
291
|
+
status=status,
|
|
292
|
+
subject=None,
|
|
293
|
+
)
|
|
294
|
+
if _is_merge_subject(subject):
|
|
295
|
+
return GrepResult(
|
|
296
|
+
grep=GREP_NAME,
|
|
297
|
+
passed=True,
|
|
298
|
+
status="merge-commit",
|
|
299
|
+
subject=subject,
|
|
300
|
+
)
|
|
301
|
+
drifts = _classify_drift(subject)
|
|
302
|
+
return GrepResult(
|
|
303
|
+
grep=GREP_NAME,
|
|
304
|
+
passed=not drifts,
|
|
305
|
+
status="ok",
|
|
306
|
+
subject=subject,
|
|
307
|
+
drifts=drifts,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _main(argv: list[str]) -> int:
|
|
312
|
+
root = Path(argv[1]) if len(argv) >= 2 else Path.cwd()
|
|
313
|
+
result = check_repo(root)
|
|
314
|
+
print(result.to_json())
|
|
315
|
+
return EXIT_PASS if result.passed else EXIT_FAIL
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
if __name__ == "__main__":
|
|
319
|
+
sys.exit(_main(sys.argv))
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""copilot-instructions-presence-grep: GitHub Copilot surface presence check.
|
|
4
|
+
|
|
5
|
+
Asserts that ``.github/copilot-instructions.md`` exists at the GitHub-canonical
|
|
6
|
+
path, begins with a canonical Markdown banner (the single-line SPDX header or
|
|
7
|
+
the HTML-comment authorship block), and carries the nine canonical level-2
|
|
8
|
+
sections in the prescribed order with exact heading text. The check is invoked
|
|
9
|
+
at orchestrator dispatch time (the orchestrator passes ``content`` and
|
|
10
|
+
``path``); when ``path`` is absent the check reads
|
|
11
|
+
``.github/copilot-instructions.md`` from the ecosystem root directly. The
|
|
12
|
+
validator is non-mutating; it only reports. Outside a repo checkout (e.g. the
|
|
13
|
+
installed conformity tree), hook-mode dispatches degrade to a pass-through —
|
|
14
|
+
there is no working-tree Copilot surface to assert against.
|
|
15
|
+
|
|
16
|
+
Verdict matrix:
|
|
17
|
+
pass — file exists, banner is a canonical Markdown variant, all
|
|
18
|
+
nine canonical sections present in order with exact headings.
|
|
19
|
+
fail — any of: file absent, banner absent or not a Markdown variant,
|
|
20
|
+
any canonical section absent, sections out of order, or a
|
|
21
|
+
section's heading text differs from the canonical form.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import re
|
|
27
|
+
import sys
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Final
|
|
31
|
+
|
|
32
|
+
from apothem.conformity._grep_base import GrepResult, run_grep
|
|
33
|
+
|
|
34
|
+
GREP_NAME: Final[str] = "copilot-instructions-presence-grep"
|
|
35
|
+
EXIT_PASS: Final[int] = 0
|
|
36
|
+
EXIT_FAIL: Final[int] = 2
|
|
37
|
+
STDIN_FLAG: Final[str] = "--stdin"
|
|
38
|
+
|
|
39
|
+
# Working-tree root anchor for the repo-root Copilot surface. In the repo
|
|
40
|
+
# checkout, parents[3] of ``src/apothem/conformity/<file>.py`` is the
|
|
41
|
+
# working-tree root where ``.github/copilot-instructions.md`` lives. In
|
|
42
|
+
# the installed tree (``<install-root>/apothem/conformity/``) the anchor
|
|
43
|
+
# is a coarse filesystem ancestor with no Copilot surface beneath it:
|
|
44
|
+
# hook-mode dispatches degrade to a pass-through via the relative_to()
|
|
45
|
+
# guard in _is_target_path(), and the surface check itself only runs when
|
|
46
|
+
# directly invoked against the target.
|
|
47
|
+
ECOSYSTEM_ROOT: Final[Path] = Path(__file__).resolve().parents[3]
|
|
48
|
+
TARGET_RELATIVE: Final[Path] = Path(".github") / "copilot-instructions.md"
|
|
49
|
+
|
|
50
|
+
# The nine canonical level-2 sections, in prescribed order, with their exact
|
|
51
|
+
# heading text. The order matches the canonical Copilot-instructions spec
|
|
52
|
+
# and the file as shipped on disk.
|
|
53
|
+
CANONICAL_SECTIONS: Final[tuple[str, ...]] = (
|
|
54
|
+
"Project Context",
|
|
55
|
+
"Coding Conventions",
|
|
56
|
+
"File Headers",
|
|
57
|
+
"Plans Discipline",
|
|
58
|
+
"Structured Inquiry Behavior",
|
|
59
|
+
"Forbidden Patterns",
|
|
60
|
+
"Output Format",
|
|
61
|
+
"Review Checklist",
|
|
62
|
+
"Pointers",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# The canonical Markdown banner comes in two accepted forms: the single-line
|
|
66
|
+
# SPDX header (``<!-- SPDX-License-Identifier: MIT -->`` — the per-file
|
|
67
|
+
# authorship form ratified at the File Headers discipline) and the
|
|
68
|
+
# HTML-comment block whose body is the hash-form authorship banner (open
|
|
69
|
+
# delimiter on its own line, AUTHOR_MARK between, close delimiter within the
|
|
70
|
+
# scan budget).
|
|
71
|
+
AUTHOR_MARK: Final[str] = "Copyright (c) Ahmed G. Gad"
|
|
72
|
+
SPDX_MARK: Final[str] = "SPDX-License-Identifier:"
|
|
73
|
+
HTML_OPEN: Final[str] = "<!--"
|
|
74
|
+
HTML_CLOSE: Final[str] = "-->"
|
|
75
|
+
BANNER_SCAN_LINE_BUDGET: Final[int] = 20
|
|
76
|
+
|
|
77
|
+
H2_LINE_RE: Final[re.Pattern[str]] = re.compile(r"^##\s+(.+?)\s*$")
|
|
78
|
+
|
|
79
|
+
RULE_FILE_ABSENT: Final[str] = "COPILOT_FILE_ABSENT"
|
|
80
|
+
RULE_BANNER_ABSENT: Final[str] = "COPILOT_BANNER_ABSENT"
|
|
81
|
+
RULE_SECTION_ABSENT: Final[str] = "COPILOT_SECTION_ABSENT"
|
|
82
|
+
RULE_SECTION_OUT_OF_ORDER: Final[str] = "COPILOT_SECTION_OUT_OF_ORDER"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(frozen=True)
|
|
86
|
+
class Finding:
|
|
87
|
+
"""One diagnostic finding for the presence check."""
|
|
88
|
+
|
|
89
|
+
line: int
|
|
90
|
+
match: str
|
|
91
|
+
context: str
|
|
92
|
+
rule: str
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _anchor_relative(path: Path) -> Path | None:
|
|
96
|
+
"""Return *path* relative to the repo root or its matched hook scope, else None.
|
|
97
|
+
|
|
98
|
+
The apothem repo root is tried first; when the path is outside it, the
|
|
99
|
+
configured conformity scopes (hook-capable harness roots, via
|
|
100
|
+
``gate.scope_relative_path``) are tried. ``None`` means the path is under no
|
|
101
|
+
anchor — there is no scope to resolve the Copilot surface against.
|
|
102
|
+
"""
|
|
103
|
+
abs_path = path.resolve()
|
|
104
|
+
try:
|
|
105
|
+
return abs_path.relative_to(ECOSYSTEM_ROOT)
|
|
106
|
+
except ValueError:
|
|
107
|
+
pass
|
|
108
|
+
from apothem.conformity.gate import scope_relative_path
|
|
109
|
+
|
|
110
|
+
scoped = scope_relative_path(abs_path)
|
|
111
|
+
return scoped[1] if scoped is not None else None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _is_target_path(path: Path) -> bool:
|
|
115
|
+
"""Return True when *path* is the GitHub-canonical Copilot surface.
|
|
116
|
+
|
|
117
|
+
Scope-aware: the surface is recognised whether the path is under the apothem
|
|
118
|
+
repo root or under a hook scope (e.g. ``~/.claude/.github/copilot-instructions.md``).
|
|
119
|
+
"""
|
|
120
|
+
rel = _anchor_relative(path)
|
|
121
|
+
return rel is not None and rel == TARGET_RELATIVE
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _check_banner(content_lines: list[str]) -> Finding | None:
|
|
125
|
+
"""Verify the file opens with a canonical Markdown banner variant.
|
|
126
|
+
|
|
127
|
+
Two forms pass: the single-line SPDX header
|
|
128
|
+
(``<!-- SPDX-License-Identifier: MIT -->``) and the HTML-comment
|
|
129
|
+
block carrying the AUTHOR_MARK between its delimiters.
|
|
130
|
+
"""
|
|
131
|
+
if not content_lines:
|
|
132
|
+
return Finding(
|
|
133
|
+
line=1,
|
|
134
|
+
match="",
|
|
135
|
+
context="file is empty; expected canonical Markdown banner at line 1",
|
|
136
|
+
rule=RULE_BANNER_ABSENT,
|
|
137
|
+
)
|
|
138
|
+
first_line = content_lines[0].strip()
|
|
139
|
+
if (
|
|
140
|
+
first_line.startswith(HTML_OPEN)
|
|
141
|
+
and first_line.endswith(HTML_CLOSE)
|
|
142
|
+
and SPDX_MARK in first_line
|
|
143
|
+
):
|
|
144
|
+
# Single-line SPDX header form — canonical per the File Headers
|
|
145
|
+
# discipline; no block scan needed.
|
|
146
|
+
return None
|
|
147
|
+
if content_lines[0].strip() != HTML_OPEN:
|
|
148
|
+
return Finding(
|
|
149
|
+
line=1,
|
|
150
|
+
match=content_lines[0],
|
|
151
|
+
context=(
|
|
152
|
+
f"first line is not the canonical Markdown variant opener {HTML_OPEN!r}"
|
|
153
|
+
),
|
|
154
|
+
rule=RULE_BANNER_ABSENT,
|
|
155
|
+
)
|
|
156
|
+
scan_end = min(len(content_lines), BANNER_SCAN_LINE_BUDGET)
|
|
157
|
+
saw_author_mark = False
|
|
158
|
+
saw_close = False
|
|
159
|
+
for idx in range(1, scan_end):
|
|
160
|
+
line = content_lines[idx]
|
|
161
|
+
if AUTHOR_MARK in line:
|
|
162
|
+
saw_author_mark = True
|
|
163
|
+
if line.strip() == HTML_CLOSE:
|
|
164
|
+
saw_close = True
|
|
165
|
+
break
|
|
166
|
+
if not saw_author_mark:
|
|
167
|
+
return Finding(
|
|
168
|
+
line=1,
|
|
169
|
+
match=content_lines[0],
|
|
170
|
+
context=(
|
|
171
|
+
f"banner opener present but {AUTHOR_MARK!r} not found within"
|
|
172
|
+
f" the first {BANNER_SCAN_LINE_BUDGET} lines"
|
|
173
|
+
),
|
|
174
|
+
rule=RULE_BANNER_ABSENT,
|
|
175
|
+
)
|
|
176
|
+
if not saw_close:
|
|
177
|
+
return Finding(
|
|
178
|
+
line=1,
|
|
179
|
+
match=content_lines[0],
|
|
180
|
+
context=(
|
|
181
|
+
f"banner opener present but {HTML_CLOSE!r} not found within"
|
|
182
|
+
f" the first {BANNER_SCAN_LINE_BUDGET} lines"
|
|
183
|
+
),
|
|
184
|
+
rule=RULE_BANNER_ABSENT,
|
|
185
|
+
)
|
|
186
|
+
return None
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _extract_h2_sequence(content_lines: list[str]) -> list[tuple[int, str]]:
|
|
190
|
+
"""Return the (1-based line number, heading text) of every level-2 heading."""
|
|
191
|
+
headings: list[tuple[int, str]] = []
|
|
192
|
+
for idx, line in enumerate(content_lines, start=1):
|
|
193
|
+
match = H2_LINE_RE.match(line)
|
|
194
|
+
if match:
|
|
195
|
+
headings.append((idx, match.group(1)))
|
|
196
|
+
return headings
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _check_sections(content_lines: list[str]) -> list[Finding]:
|
|
200
|
+
"""Verify every canonical section is present in the prescribed order."""
|
|
201
|
+
headings = _extract_h2_sequence(content_lines)
|
|
202
|
+
findings: list[Finding] = []
|
|
203
|
+
|
|
204
|
+
# Pass 1 — every canonical section must be present.
|
|
205
|
+
seen_lines: dict[str, int] = {text: line for line, text in headings}
|
|
206
|
+
for section in CANONICAL_SECTIONS:
|
|
207
|
+
if section not in seen_lines:
|
|
208
|
+
findings.append(
|
|
209
|
+
Finding(
|
|
210
|
+
line=0,
|
|
211
|
+
match="",
|
|
212
|
+
context=f"canonical section {section!r} absent",
|
|
213
|
+
rule=RULE_SECTION_ABSENT,
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# Pass 2 — the canonical sections must appear in the prescribed order.
|
|
218
|
+
# Filter the heading sequence to canonical members and check the order.
|
|
219
|
+
canonical_seen = [
|
|
220
|
+
(line, text) for line, text in headings if text in CANONICAL_SECTIONS
|
|
221
|
+
]
|
|
222
|
+
expected_order = [t for _, t in canonical_seen]
|
|
223
|
+
canonical_present = [s for s in CANONICAL_SECTIONS if s in seen_lines]
|
|
224
|
+
if expected_order != canonical_present:
|
|
225
|
+
first_mismatch_line = canonical_seen[0][0] if canonical_seen else 0
|
|
226
|
+
findings.append(
|
|
227
|
+
Finding(
|
|
228
|
+
line=first_mismatch_line,
|
|
229
|
+
match=", ".join(expected_order),
|
|
230
|
+
context=(
|
|
231
|
+
f"canonical sections out of order; observed "
|
|
232
|
+
f"{expected_order!r} but expected the order "
|
|
233
|
+
f"{canonical_present!r}"
|
|
234
|
+
),
|
|
235
|
+
rule=RULE_SECTION_OUT_OF_ORDER,
|
|
236
|
+
)
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
return findings
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def check(content: str, path: Path | None = None) -> GrepResult:
|
|
243
|
+
"""Validate Copilot-surface presence and section structure.
|
|
244
|
+
|
|
245
|
+
Hook-mode pass-through. When *path* is set and does not name the
|
|
246
|
+
GitHub-canonical Copilot surface, the validator returns ``passed=True``
|
|
247
|
+
without reading anything else. The full structural check runs only on
|
|
248
|
+
direct dispatch against the target surface or on CLI mode without a
|
|
249
|
+
path argument (where the validator reads the on-disk surface).
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
content: In-flight body of the target surface (orchestrator
|
|
253
|
+
dispatch). When the orchestrator dispatches with no content
|
|
254
|
+
but the path matches the target, the on-disk body is read.
|
|
255
|
+
path: Absolute path of the file under dispatch; ``None`` in
|
|
256
|
+
``--stdin`` mode.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
``GrepResult`` with ``passed=True`` when the surface is
|
|
260
|
+
well-formed (or out-of-scope for this dispatch), ``passed=False``
|
|
261
|
+
with one ``Finding`` per defect otherwise.
|
|
262
|
+
"""
|
|
263
|
+
target_path = ECOSYSTEM_ROOT / TARGET_RELATIVE
|
|
264
|
+
|
|
265
|
+
if path is not None and not _is_target_path(path):
|
|
266
|
+
if _anchor_relative(path) is None:
|
|
267
|
+
# Out-of-anchor write: no repo root or hook scope resolves the
|
|
268
|
+
# Copilot surface — record a visible skip, not a silent pass.
|
|
269
|
+
return GrepResult(
|
|
270
|
+
grep=GREP_NAME,
|
|
271
|
+
path=str(path),
|
|
272
|
+
passed=True,
|
|
273
|
+
note="check skipped (scope not resolvable)",
|
|
274
|
+
)
|
|
275
|
+
# In-anchor but not the Copilot surface: legitimately not applicable.
|
|
276
|
+
return GrepResult(grep=GREP_NAME, path=str(path), passed=True)
|
|
277
|
+
|
|
278
|
+
if path is not None and _is_target_path(path):
|
|
279
|
+
if content:
|
|
280
|
+
body = content
|
|
281
|
+
elif target_path.is_file():
|
|
282
|
+
body = target_path.read_text(encoding="utf-8")
|
|
283
|
+
else:
|
|
284
|
+
body = ""
|
|
285
|
+
report_path = path
|
|
286
|
+
else:
|
|
287
|
+
if not target_path.is_file():
|
|
288
|
+
return GrepResult(
|
|
289
|
+
grep=GREP_NAME,
|
|
290
|
+
path=str(target_path),
|
|
291
|
+
passed=False,
|
|
292
|
+
findings=[
|
|
293
|
+
Finding(
|
|
294
|
+
line=0,
|
|
295
|
+
match="",
|
|
296
|
+
context=(
|
|
297
|
+
f"{TARGET_RELATIVE.as_posix()} absent at the"
|
|
298
|
+
f" GitHub-canonical path"
|
|
299
|
+
),
|
|
300
|
+
rule=RULE_FILE_ABSENT,
|
|
301
|
+
)
|
|
302
|
+
],
|
|
303
|
+
)
|
|
304
|
+
body = target_path.read_text(encoding="utf-8")
|
|
305
|
+
report_path = target_path
|
|
306
|
+
|
|
307
|
+
content_lines = body.split("\n")
|
|
308
|
+
|
|
309
|
+
findings: list[Finding] = []
|
|
310
|
+
banner_finding = _check_banner(content_lines)
|
|
311
|
+
if banner_finding is not None:
|
|
312
|
+
findings.append(banner_finding)
|
|
313
|
+
findings.extend(_check_sections(content_lines))
|
|
314
|
+
|
|
315
|
+
return GrepResult(
|
|
316
|
+
grep=GREP_NAME,
|
|
317
|
+
path=str(report_path),
|
|
318
|
+
passed=not findings,
|
|
319
|
+
findings=findings,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
if __name__ == "__main__":
|
|
324
|
+
sys.exit(run_grep(check, sys.argv))
|