@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,318 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""always-on-budget-grep: token-budget conformity matcher for always-on rules.
|
|
4
|
+
|
|
5
|
+
Why this enforcement exists. Always-on rules load into every session's
|
|
6
|
+
context as standing directives; aggregate body length is a multiplier on
|
|
7
|
+
every turn's cost. The token-budget-discipline rule caps each always-on
|
|
8
|
+
body at MAX_SUBSTANTIVE_TOKENS substantive tokens, with a pre-emptive
|
|
9
|
+
warn-band at WARN_BAND_THRESHOLD nudging authors to plan a path-filtered
|
|
10
|
+
companion sub-rule before the hard ceiling triggers. Bodies above the
|
|
11
|
+
ceiling are decomposed into the demand-load companion-sub-rule pattern
|
|
12
|
+
ratified at rules/context-management.md and its companion
|
|
13
|
+
rules/context-management-scratch.md.
|
|
14
|
+
|
|
15
|
+
Substantive-token counter. The count is a whitespace-separated token
|
|
16
|
+
count over the rule body with three regions excluded:
|
|
17
|
+
|
|
18
|
+
1. YAML frontmatter — content between the opening `---` and the second
|
|
19
|
+
`---` delimiters at file head. Frontmatter declares the rule's
|
|
20
|
+
classification, not its directive content.
|
|
21
|
+
2. The trailing `## Bindings (§0.j five-direction)` section — bindings
|
|
22
|
+
are pointers to peer artifacts, not standing directives. They
|
|
23
|
+
consume context but do not raise body-content load.
|
|
24
|
+
3. Companion-sub-rule pointer lines — any line containing the literal
|
|
25
|
+
string `(Companion Sub-Rule Anchor)`. The pointer line names a
|
|
26
|
+
demand-load surface; the surface itself loads on path-filter match
|
|
27
|
+
and counts against its own (path-filtered) sibling rule, not the
|
|
28
|
+
parent always-on body.
|
|
29
|
+
|
|
30
|
+
Verdict matrix.
|
|
31
|
+
pass — the rule is not always-on (alwaysApply false OR pathFilter
|
|
32
|
+
non-empty), or the substantive-token count is at or below the
|
|
33
|
+
MAX_SUBSTANTIVE_TOKENS ceiling. WARN_BAND_THRESHOLD-MAX
|
|
34
|
+
counts produce an advisory in the finding payload but still
|
|
35
|
+
pass the gate.
|
|
36
|
+
FAIL — the substantive-token count exceeds MAX_SUBSTANTIVE_TOKENS;
|
|
37
|
+
the rule must be decomposed into a path-filtered companion
|
|
38
|
+
sub-rule with the parent body retaining only load-bearing
|
|
39
|
+
anchors plus the `(Companion Sub-Rule Anchor)` pointer.
|
|
40
|
+
|
|
41
|
+
Invocation. The matcher accepts either a single rule path on argv, a
|
|
42
|
+
directory (recurses over `*.md`), or `--stdin` reading the
|
|
43
|
+
rule body. When invoked with no path, defaults to scanning `rules/`
|
|
44
|
+
relative to the current working directory.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
from __future__ import annotations
|
|
48
|
+
|
|
49
|
+
import json
|
|
50
|
+
import sys
|
|
51
|
+
from dataclasses import asdict, dataclass, field
|
|
52
|
+
from pathlib import Path
|
|
53
|
+
from typing import Final
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
# Module identity
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
GREP_NAME: Final[str] = "always-on-budget-grep"
|
|
60
|
+
RULE_ANCHOR: Final[str] = "D1 token-budget-discipline §Always-on body budget"
|
|
61
|
+
EXIT_PASS: Final[int] = 0
|
|
62
|
+
EXIT_FAIL: Final[int] = 2
|
|
63
|
+
STDIN_FLAG: Final[str] = "--stdin"
|
|
64
|
+
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
# Budget constants
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
MAX_SUBSTANTIVE_TOKENS: Final[int] = 500
|
|
70
|
+
WARN_BAND_THRESHOLD: Final[int] = 450
|
|
71
|
+
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
# Region-exclusion markers
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
FRONTMATTER_DELIM: Final[str] = "---"
|
|
77
|
+
BINDINGS_HEADING_PREFIX: Final[str] = "## Bindings"
|
|
78
|
+
COMPANION_POINTER: Final[str] = "(Companion Sub-Rule Anchor)"
|
|
79
|
+
RULE_GLOB: Final[str] = "*.md"
|
|
80
|
+
DEFAULT_SCAN_DIR: Final[str] = "rules"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(frozen=True)
|
|
84
|
+
class Finding:
|
|
85
|
+
"""One per-rule budget verdict."""
|
|
86
|
+
|
|
87
|
+
path: str
|
|
88
|
+
always_on: bool
|
|
89
|
+
substantive_tokens: int
|
|
90
|
+
over_budget: bool
|
|
91
|
+
warn_band: bool
|
|
92
|
+
rule: str = RULE_ANCHOR
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass(frozen=True)
|
|
96
|
+
class GrepResult:
|
|
97
|
+
grep: str
|
|
98
|
+
passed: bool
|
|
99
|
+
findings: list[Finding] = field(default_factory=list)
|
|
100
|
+
|
|
101
|
+
def to_json(self) -> str:
|
|
102
|
+
payload = {
|
|
103
|
+
"grep": self.grep,
|
|
104
|
+
"passed": self.passed,
|
|
105
|
+
"max-substantive-tokens": MAX_SUBSTANTIVE_TOKENS,
|
|
106
|
+
"warn-band-threshold": WARN_BAND_THRESHOLD,
|
|
107
|
+
"findings": [asdict(f) for f in self.findings],
|
|
108
|
+
}
|
|
109
|
+
return json.dumps(payload, indent=2)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def parse_frontmatter(content: str) -> tuple[dict[str, str], str]:
|
|
113
|
+
"""Split frontmatter from body.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
content: full rule file content.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Tuple of (frontmatter mapping, body text). Frontmatter values are
|
|
120
|
+
stripped of surrounding quotes; missing keys yield an empty
|
|
121
|
+
mapping. When no frontmatter is present, returns ({}, content).
|
|
122
|
+
|
|
123
|
+
The parser tolerates a leading authorship-header banner (HTML comment
|
|
124
|
+
block on Markdown rules) preceding the frontmatter — it locates the
|
|
125
|
+
first `---` delimiter line that opens a YAML block whose immediate
|
|
126
|
+
content lines look like `key: value` pairs.
|
|
127
|
+
"""
|
|
128
|
+
lines = content.splitlines()
|
|
129
|
+
start_index = -1
|
|
130
|
+
for i, line in enumerate(lines):
|
|
131
|
+
if line.strip() == FRONTMATTER_DELIM:
|
|
132
|
+
start_index = i
|
|
133
|
+
break
|
|
134
|
+
if start_index == -1:
|
|
135
|
+
return {}, content
|
|
136
|
+
end_index = -1
|
|
137
|
+
for i in range(start_index + 1, len(lines)):
|
|
138
|
+
if lines[i].strip() == FRONTMATTER_DELIM:
|
|
139
|
+
end_index = i
|
|
140
|
+
break
|
|
141
|
+
if end_index == -1:
|
|
142
|
+
return {}, content
|
|
143
|
+
fm: dict[str, str] = {}
|
|
144
|
+
for raw in lines[start_index + 1 : end_index]:
|
|
145
|
+
if ":" not in raw:
|
|
146
|
+
continue
|
|
147
|
+
key, _, value = raw.partition(":")
|
|
148
|
+
fm[key.strip()] = value.strip().strip('"').strip("'")
|
|
149
|
+
body = "\n".join(lines[end_index + 1 :])
|
|
150
|
+
return fm, body
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def is_always_on(frontmatter: dict[str, str]) -> bool:
|
|
154
|
+
"""Classify a rule as always-on per its frontmatter.
|
|
155
|
+
|
|
156
|
+
A rule is always-on iff `alwaysApply` is the literal string `true`
|
|
157
|
+
AND `pathFilter` is empty (absent or whitespace-only). Path-filtered
|
|
158
|
+
rules demand-load on glob match and are exempt from the body budget.
|
|
159
|
+
"""
|
|
160
|
+
apply_value = frontmatter.get("alwaysApply", "").lower()
|
|
161
|
+
path_filter = frontmatter.get("pathFilter", "").strip()
|
|
162
|
+
return apply_value == "true" and path_filter == ""
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def strip_bindings_section(body: str) -> str:
|
|
166
|
+
"""Remove the trailing `## Bindings (§0.j five-direction)` section.
|
|
167
|
+
|
|
168
|
+
Bindings are reciprocal pointers to peer artifacts, not standing
|
|
169
|
+
directives; they are excluded from the substantive token count per
|
|
170
|
+
the rule's exclusion list.
|
|
171
|
+
"""
|
|
172
|
+
lines = body.splitlines()
|
|
173
|
+
cut = -1
|
|
174
|
+
for i, line in enumerate(lines):
|
|
175
|
+
if line.lstrip().startswith(BINDINGS_HEADING_PREFIX):
|
|
176
|
+
cut = i
|
|
177
|
+
break
|
|
178
|
+
if cut == -1:
|
|
179
|
+
return body
|
|
180
|
+
return "\n".join(lines[:cut])
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def strip_companion_pointers(body: str) -> str:
|
|
184
|
+
"""Remove every line containing the companion-sub-rule pointer.
|
|
185
|
+
|
|
186
|
+
The pointer line names a demand-load surface; the surface itself is
|
|
187
|
+
weighed against its own path-filtered sibling rule, not the parent
|
|
188
|
+
always-on body.
|
|
189
|
+
"""
|
|
190
|
+
return "\n".join(
|
|
191
|
+
line for line in body.splitlines() if COMPANION_POINTER not in line
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def count_substantive_tokens(body: str) -> int:
|
|
196
|
+
"""Count whitespace-separated tokens in the post-exclusion body.
|
|
197
|
+
|
|
198
|
+
The count is reproducible and platform-stable; markdown markers are
|
|
199
|
+
counted as tokens because they consume context just as words do.
|
|
200
|
+
"""
|
|
201
|
+
return len(body.split())
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def measure(content: str) -> tuple[bool, int]:
|
|
205
|
+
"""Measure the always-on classification and the substantive token count.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Tuple of (always_on, substantive_token_count). When the rule is
|
|
209
|
+
not always-on, the count is still reported for transparency but
|
|
210
|
+
the verdict is always pass.
|
|
211
|
+
"""
|
|
212
|
+
frontmatter, body = parse_frontmatter(content)
|
|
213
|
+
always_on = is_always_on(frontmatter)
|
|
214
|
+
body_no_bindings = strip_bindings_section(body)
|
|
215
|
+
body_no_pointers = strip_companion_pointers(body_no_bindings)
|
|
216
|
+
return always_on, count_substantive_tokens(body_no_pointers)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def check_file(path: Path) -> Finding:
|
|
220
|
+
"""Apply the matcher to a single rule file."""
|
|
221
|
+
content = path.read_text(encoding="utf-8")
|
|
222
|
+
always_on, tokens = measure(content)
|
|
223
|
+
over = always_on and tokens > MAX_SUBSTANTIVE_TOKENS
|
|
224
|
+
warn = always_on and (not over) and tokens >= WARN_BAND_THRESHOLD
|
|
225
|
+
return Finding(
|
|
226
|
+
path=str(path),
|
|
227
|
+
always_on=always_on,
|
|
228
|
+
substantive_tokens=tokens,
|
|
229
|
+
over_budget=over,
|
|
230
|
+
warn_band=warn,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def check(content: str, path: Path | None = None) -> GrepResult:
|
|
235
|
+
"""Per-Write dispatch entry consumed by `conformity/gate.py`.
|
|
236
|
+
|
|
237
|
+
Measures one rule body's substantive-token count. The orchestrator
|
|
238
|
+
invokes this on every Write/Edit; non-rule paths and non-always-on
|
|
239
|
+
rules pass without a finding.
|
|
240
|
+
|
|
241
|
+
Pre-conditions: `content` is the artifact about to be emitted; `path`
|
|
242
|
+
is the target file path or None.
|
|
243
|
+
Post-conditions: `result.passed` is True iff the artifact is not an
|
|
244
|
+
over-budget always-on rule body.
|
|
245
|
+
"""
|
|
246
|
+
path_str = str(path) if path is not None else "<stdin>"
|
|
247
|
+
if path is not None and not path_str.endswith(".md"):
|
|
248
|
+
return GrepResult(grep=GREP_NAME, passed=True, findings=[])
|
|
249
|
+
always_on, tokens = measure(content)
|
|
250
|
+
over = always_on and tokens > MAX_SUBSTANTIVE_TOKENS
|
|
251
|
+
warn = always_on and (not over) and tokens >= WARN_BAND_THRESHOLD
|
|
252
|
+
finding = Finding(
|
|
253
|
+
path=path_str,
|
|
254
|
+
always_on=always_on,
|
|
255
|
+
substantive_tokens=tokens,
|
|
256
|
+
over_budget=over,
|
|
257
|
+
warn_band=warn,
|
|
258
|
+
)
|
|
259
|
+
if not over:
|
|
260
|
+
return GrepResult(grep=GREP_NAME, passed=True, findings=[])
|
|
261
|
+
return GrepResult(grep=GREP_NAME, passed=False, findings=[finding])
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def discover_rule_files(target: Path) -> list[Path]:
|
|
265
|
+
"""Resolve a target into the rule files to inspect."""
|
|
266
|
+
if target.is_file():
|
|
267
|
+
return [target]
|
|
268
|
+
if target.is_dir():
|
|
269
|
+
return sorted(target.rglob(RULE_GLOB))
|
|
270
|
+
raise FileNotFoundError(f"target does not exist: {target}")
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _read_stdin_finding() -> Finding:
|
|
274
|
+
content = sys.stdin.read()
|
|
275
|
+
always_on, tokens = measure(content)
|
|
276
|
+
over = always_on and tokens > MAX_SUBSTANTIVE_TOKENS
|
|
277
|
+
warn = always_on and (not over) and tokens >= WARN_BAND_THRESHOLD
|
|
278
|
+
return Finding(
|
|
279
|
+
path="<stdin>",
|
|
280
|
+
always_on=always_on,
|
|
281
|
+
substantive_tokens=tokens,
|
|
282
|
+
over_budget=over,
|
|
283
|
+
warn_band=warn,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _resolve_target(argv: list[str]) -> Path:
|
|
288
|
+
if len(argv) >= 2:
|
|
289
|
+
return Path(argv[1])
|
|
290
|
+
return Path(DEFAULT_SCAN_DIR)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _main(argv: list[str]) -> int:
|
|
294
|
+
if len(argv) >= 2 and argv[1] == STDIN_FLAG:
|
|
295
|
+
finding = _read_stdin_finding()
|
|
296
|
+
result = GrepResult(
|
|
297
|
+
grep=GREP_NAME,
|
|
298
|
+
passed=not finding.over_budget,
|
|
299
|
+
findings=[finding],
|
|
300
|
+
)
|
|
301
|
+
print(result.to_json())
|
|
302
|
+
return EXIT_PASS if result.passed else EXIT_FAIL
|
|
303
|
+
|
|
304
|
+
target = _resolve_target(argv)
|
|
305
|
+
paths = discover_rule_files(target)
|
|
306
|
+
findings = [check_file(p) for p in paths]
|
|
307
|
+
failed = [f for f in findings if f.over_budget]
|
|
308
|
+
result = GrepResult(
|
|
309
|
+
grep=GREP_NAME,
|
|
310
|
+
passed=not failed,
|
|
311
|
+
findings=findings,
|
|
312
|
+
)
|
|
313
|
+
print(result.to_json())
|
|
314
|
+
return EXIT_PASS if result.passed else EXIT_FAIL
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
if __name__ == "__main__":
|
|
318
|
+
sys.exit(_main(sys.argv))
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Flag bare except clauses without typed exceptions or re-raise.
|
|
4
|
+
|
|
5
|
+
Why this enforcement exists. The code-craft rule M13.3 forbids bare
|
|
6
|
+
`except:` and broad `except Exception:` that swallow errors silently.
|
|
7
|
+
Caught exceptions are typed and either handled with rationale or
|
|
8
|
+
re-raised with context. The pre-emission gate's mechanical bar 13 (M13 code craft)
|
|
9
|
+
catches both bare-clause shapes and the broad-Exception form (without
|
|
10
|
+
re-raise) and surfaces each occurrence so the operator either narrows
|
|
11
|
+
the type or adds a `raise` so the failure propagates.
|
|
12
|
+
|
|
13
|
+
Detection strategy. The grep walks Python source for `except:` (no
|
|
14
|
+
type spec), `except Exception:` / `except BaseException:` (broad), and
|
|
15
|
+
the parenthesized-tuple form that contains a broad type
|
|
16
|
+
(`except (ValueError, Exception):`); within each clause's body it
|
|
17
|
+
inspects whether the body contains a `raise` statement. A clause that
|
|
18
|
+
catches broadly without re-raising is the canonical violation; bare
|
|
19
|
+
`except:` is always flagged.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import re
|
|
25
|
+
import sys
|
|
26
|
+
from dataclasses import dataclass
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Final
|
|
29
|
+
|
|
30
|
+
from apothem.conformity._grep_base import GrepResult, run_grep
|
|
31
|
+
|
|
32
|
+
# Bare or broad except shapes. Bare `except:` matches with no type
|
|
33
|
+
# specifier; broad forms catch the root exception types either directly
|
|
34
|
+
# (`except Exception:`) or inside a tuple (`except (ValueError, Exception):`).
|
|
35
|
+
# The match groups capture the form for the finding's report.
|
|
36
|
+
BARE_EXCEPT_RE: Final[re.Pattern[str]] = re.compile(
|
|
37
|
+
r"^(\s*)except\s*(?:"
|
|
38
|
+
r":" # bare `except:`
|
|
39
|
+
r"|(" # group 2: a broad spec including the trailing colon
|
|
40
|
+
r"(?:Exception|BaseException)(?:\s+as\s+\w+)?\s*:" # broad single type
|
|
41
|
+
r"|\([^)]*\b(?:Exception|BaseException)\b[^)]*\)(?:\s+as\s+\w+)?\s*:" # broad tuple
|
|
42
|
+
r"))"
|
|
43
|
+
)
|
|
44
|
+
RAISE_RE: Final[re.Pattern[str]] = re.compile(r"^\s+raise\b")
|
|
45
|
+
# A re-raise written inline after the `except ...:` colon — `except
|
|
46
|
+
# Exception: raise` or `except Exception: log(); raise`. Matched at the
|
|
47
|
+
# start of the inline body or immediately after a `;` statement separator,
|
|
48
|
+
# so an inline re-raise is recognized as faithfully as a subsequent-line one.
|
|
49
|
+
INLINE_RAISE_RE: Final[re.Pattern[str]] = re.compile(r"(?:^|;)\s*raise\b")
|
|
50
|
+
GREP_NAME: Final[str] = "bare-except-grep"
|
|
51
|
+
RULE_ANCHOR: Final[str] = "M13.3 error handling"
|
|
52
|
+
EXIT_PASS: Final[int] = 0
|
|
53
|
+
EXIT_FAIL: Final[int] = 2
|
|
54
|
+
STDIN_FLAG: Final[str] = "--stdin"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True)
|
|
58
|
+
class Finding:
|
|
59
|
+
line: int
|
|
60
|
+
form: str
|
|
61
|
+
detail: str
|
|
62
|
+
rule: str = RULE_ANCHOR
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _clause_has_raise(lines: list[str], clause_index: int, indent: int) -> bool:
|
|
66
|
+
"""Walk the clause body until dedent; return True iff any line is a raise."""
|
|
67
|
+
body_indent_min = indent + 1
|
|
68
|
+
for line in lines[clause_index + 1 :]:
|
|
69
|
+
if not line.strip():
|
|
70
|
+
continue
|
|
71
|
+
leading = len(line) - len(line.lstrip())
|
|
72
|
+
if leading < body_indent_min:
|
|
73
|
+
return False
|
|
74
|
+
if RAISE_RE.match(line):
|
|
75
|
+
return True
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def check(content: str, path: Path | None = None) -> GrepResult:
|
|
80
|
+
"""Scan content; return a structured result."""
|
|
81
|
+
findings: list[Finding] = []
|
|
82
|
+
lines = content.splitlines()
|
|
83
|
+
for index, line in enumerate(lines):
|
|
84
|
+
match = BARE_EXCEPT_RE.match(line)
|
|
85
|
+
if match is None:
|
|
86
|
+
continue
|
|
87
|
+
indent = len(match.group(1))
|
|
88
|
+
is_bare = match.group(2) is None
|
|
89
|
+
form = "bare except:" if is_bare else "broad except: " + match.group(2).strip()
|
|
90
|
+
# Bare except is always a finding. Broad except is a finding only
|
|
91
|
+
# when the clause body contains no `raise` — counting both a
|
|
92
|
+
# subsequent indented line and an inline re-raise on the `except`
|
|
93
|
+
# line itself (`except Exception: raise`), so a faithful inline
|
|
94
|
+
# re-raise is not flagged as a swallow.
|
|
95
|
+
inline_body = line[match.end() :]
|
|
96
|
+
has_raise = bool(INLINE_RAISE_RE.search(inline_body)) or _clause_has_raise(
|
|
97
|
+
lines, index, indent
|
|
98
|
+
)
|
|
99
|
+
if is_bare or not has_raise:
|
|
100
|
+
detail = (
|
|
101
|
+
"bare except swallows every exception class"
|
|
102
|
+
if is_bare
|
|
103
|
+
else "broad except without re-raise; narrow the type or add raise"
|
|
104
|
+
)
|
|
105
|
+
findings.append(Finding(line=index + 1, form=form, detail=detail))
|
|
106
|
+
return GrepResult(
|
|
107
|
+
grep=GREP_NAME,
|
|
108
|
+
path=str(path) if path is not None else None,
|
|
109
|
+
passed=not findings,
|
|
110
|
+
findings=findings,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
if __name__ == "__main__":
|
|
115
|
+
sys.exit(run_grep(check, sys.argv))
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Flag binding declarations using non-canonical arrow notation.
|
|
4
|
+
|
|
5
|
+
Why this enforcement exists. The bidirectional-binding rule M10 declares
|
|
6
|
+
five canonical arrows — `→` (Drives), `←` (Driven by), `↑` (Established by),
|
|
7
|
+
`↓` (rare downward; reserved), and `↔` (Cross-bound with) — that every
|
|
8
|
+
Bindings section uses verbatim. ASCII substitutes (`->`, `<-`, `<->`) and
|
|
9
|
+
emoji-arrow variants are notation drift; they break sibling-convergence and
|
|
10
|
+
the mechanical reciprocity walk that future cross-file matchers will perform.
|
|
11
|
+
This grep enforces the in-file shape of the Bindings section: heading is
|
|
12
|
+
present and well-named, every arrow is from the canonical set, no ASCII
|
|
13
|
+
arrows appear in the Bindings region.
|
|
14
|
+
|
|
15
|
+
Cross-file reciprocity check (full half-edge detection across the rule set)
|
|
16
|
+
is out of scope for this per-file grep — it requires a corpus walk that
|
|
17
|
+
the orchestrator at `gate.py` will assemble. The per-file
|
|
18
|
+
contribution here is the notation discipline within each Bindings section.
|
|
19
|
+
|
|
20
|
+
Code is excluded from the arrow scan: fenced code blocks and inline-code spans
|
|
21
|
+
inside a Bindings section legitimately quote ASCII arrows (a Python return
|
|
22
|
+
annotation, a shell pipe, a CLI usage snippet), which are quotations rather than
|
|
23
|
+
Bindings notation.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import re
|
|
29
|
+
import sys
|
|
30
|
+
from dataclasses import dataclass
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import Final
|
|
33
|
+
|
|
34
|
+
from apothem.conformity._grep_base import GrepResult, run_grep
|
|
35
|
+
|
|
36
|
+
# The canonical Bindings section heading per the §0.j five-direction notation.
|
|
37
|
+
# Rule files use `## Bindings (§0.j five-direction)`; some siblings drop the
|
|
38
|
+
# parenthetical. Both are accepted as valid section markers; the grep starts
|
|
39
|
+
# scanning at the first match and stops at the next H1/H2 heading.
|
|
40
|
+
BINDINGS_HEADING_RE: Final[re.Pattern[str]] = re.compile(
|
|
41
|
+
r"^##\s+Bindings(?:\s+\(§0\.j\s+five-direction\))?\s*$"
|
|
42
|
+
)
|
|
43
|
+
NEXT_HEADING_RE: Final[re.Pattern[str]] = re.compile(r"^##?\s+\S")
|
|
44
|
+
|
|
45
|
+
# ASCII substitute arrows that are notation drift. The rule's spec lists
|
|
46
|
+
# them explicitly as forbidden alternatives.
|
|
47
|
+
FORBIDDEN_ARROWS: Final[tuple[str, ...]] = (
|
|
48
|
+
"->",
|
|
49
|
+
"<-",
|
|
50
|
+
"<->",
|
|
51
|
+
"<=",
|
|
52
|
+
"=>",
|
|
53
|
+
)
|
|
54
|
+
FORBIDDEN_ARROW_RE: Final[re.Pattern[str]] = re.compile(
|
|
55
|
+
"|".join(re.escape(a) for a in FORBIDDEN_ARROWS)
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# The canonical arrow alphabet. Any character outside this set inside an arrow
|
|
59
|
+
# context is suspect, but we only flag the explicit ASCII substitutes since
|
|
60
|
+
# free-form prose may legitimately contain any Unicode character.
|
|
61
|
+
CANONICAL_ARROWS: Final[frozenset[str]] = frozenset({"→", "←", "↑", "↓", "↔"})
|
|
62
|
+
|
|
63
|
+
# Code spans inside the Bindings section legitimately quote ASCII arrows — a
|
|
64
|
+
# Python return annotation (`f() -> str`), a shell pipe, a CLI usage snippet —
|
|
65
|
+
# so fenced blocks and inline-code spans are excluded from the arrow scan.
|
|
66
|
+
_CODE_FENCE_RE: Final[re.Pattern[str]] = re.compile(r"^```")
|
|
67
|
+
_INLINE_CODE_RE: Final[re.Pattern[str]] = re.compile(r"`[^`]*`")
|
|
68
|
+
|
|
69
|
+
GREP_NAME: Final[str] = "binding-reciprocity-grep"
|
|
70
|
+
RULE_ANCHOR: Final[str] = "M10 bidirectional-binding §Notation"
|
|
71
|
+
EXIT_PASS: Final[int] = 0
|
|
72
|
+
EXIT_FAIL: Final[int] = 2
|
|
73
|
+
STDIN_FLAG: Final[str] = "--stdin"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass(frozen=True)
|
|
77
|
+
class Finding:
|
|
78
|
+
"""One notation-drift occurrence inside the Bindings section."""
|
|
79
|
+
|
|
80
|
+
line: int
|
|
81
|
+
match: str
|
|
82
|
+
context: str
|
|
83
|
+
rule: str = RULE_ANCHOR
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _bindings_region(lines: list[str]) -> tuple[int, int] | None:
|
|
87
|
+
"""Locate the Bindings section as a (start, end) line-range pair.
|
|
88
|
+
|
|
89
|
+
Returns a 1-indexed inclusive start line and exclusive end line, or
|
|
90
|
+
None if no Bindings section is present in the artifact.
|
|
91
|
+
"""
|
|
92
|
+
start: int | None = None
|
|
93
|
+
for i, line in enumerate(lines):
|
|
94
|
+
if start is None:
|
|
95
|
+
if BINDINGS_HEADING_RE.match(line):
|
|
96
|
+
start = i + 1 # First content line after the heading.
|
|
97
|
+
continue
|
|
98
|
+
# Past the heading; the section ends at the next heading.
|
|
99
|
+
if NEXT_HEADING_RE.match(line):
|
|
100
|
+
return start, i
|
|
101
|
+
if start is not None:
|
|
102
|
+
return start, len(lines)
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def check(content: str, path: Path | None = None) -> GrepResult:
|
|
107
|
+
"""Scan content; return a structured result.
|
|
108
|
+
|
|
109
|
+
Pre-conditions: `content` is the artifact body about to be emitted.
|
|
110
|
+
Post-conditions: `result.passed` is True when either the Bindings
|
|
111
|
+
section is absent (not every artifact has one) or the section uses
|
|
112
|
+
only canonical arrow notation.
|
|
113
|
+
"""
|
|
114
|
+
lines = content.splitlines()
|
|
115
|
+
region = _bindings_region(lines)
|
|
116
|
+
if region is None:
|
|
117
|
+
return GrepResult(
|
|
118
|
+
grep=GREP_NAME,
|
|
119
|
+
path=str(path) if path is not None else None,
|
|
120
|
+
passed=True,
|
|
121
|
+
)
|
|
122
|
+
findings: list[Finding] = []
|
|
123
|
+
start, end = region
|
|
124
|
+
inside_fence = False
|
|
125
|
+
for offset, line in enumerate(lines[start - 1 : end], start=start):
|
|
126
|
+
if _CODE_FENCE_RE.match(line):
|
|
127
|
+
inside_fence = not inside_fence
|
|
128
|
+
continue
|
|
129
|
+
if inside_fence:
|
|
130
|
+
continue
|
|
131
|
+
# Blank inline-code spans (same-length spaces preserve columns) so a
|
|
132
|
+
# quoted ASCII arrow inside backticks is not read as notation drift.
|
|
133
|
+
scanned = _INLINE_CODE_RE.sub(lambda m: " " * len(m.group(0)), line)
|
|
134
|
+
for match in FORBIDDEN_ARROW_RE.finditer(scanned):
|
|
135
|
+
findings.append(
|
|
136
|
+
Finding(
|
|
137
|
+
line=offset,
|
|
138
|
+
match=match.group(),
|
|
139
|
+
context=line.strip(),
|
|
140
|
+
)
|
|
141
|
+
)
|
|
142
|
+
return GrepResult(
|
|
143
|
+
grep=GREP_NAME,
|
|
144
|
+
path=str(path) if path is not None else None,
|
|
145
|
+
passed=not findings,
|
|
146
|
+
findings=findings,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
if __name__ == "__main__":
|
|
151
|
+
sys.exit(run_grep(check, sys.argv))
|