@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,284 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Verify version-bearing sites resolve dynamically, not as static literals.
|
|
4
|
+
|
|
5
|
+
Why this enforcement exists. Companion to ``dynamism_grep.py``. The
|
|
6
|
+
dynamism rule mandates that every version-bearing site in the project
|
|
7
|
+
derive its value from a single source of truth (package metadata via
|
|
8
|
+
``importlib.metadata``, the shields.io dynamic badge endpoints that
|
|
9
|
+
resolve against the npm registry and GitHub Releases, or the site
|
|
10
|
+
build's version-injection mechanism) rather than carry the version
|
|
11
|
+
as a hard-coded literal. Static literals drift the moment a release
|
|
12
|
+
ships: badge URLs lie about the current version, docs pages display
|
|
13
|
+
the version that was true at last edit, and the runtime ``__version__``
|
|
14
|
+
attribute returns a value that disagrees with the installed package.
|
|
15
|
+
This standalone validator walks three classes of site (README badges,
|
|
16
|
+
docs site config, runtime ``__version__``) and reports every static
|
|
17
|
+
resolution so the operator can convert it to a dynamic form.
|
|
18
|
+
|
|
19
|
+
Scope. Standalone corpus-level validator. Walks the project root from a
|
|
20
|
+
single ``main(root)`` entry point and exits 0 (PASS) or 2 (FAIL) with a
|
|
21
|
+
structured JSON report listing every static-version site. Invoked via
|
|
22
|
+
``python -m apothem.conformity.gate --all .`` or
|
|
23
|
+
``python -m apothem.conformity.gate --check static-version-grep <root>``.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import json
|
|
29
|
+
import re
|
|
30
|
+
import sys
|
|
31
|
+
from dataclasses import asdict, dataclass, field
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Final
|
|
34
|
+
|
|
35
|
+
GREP_NAME: Final[str] = "static-version-grep"
|
|
36
|
+
RULE_ANCHOR: Final[str] = "rules/dynamism.md §static-version-resolution"
|
|
37
|
+
EXIT_PASS: Final[int] = 0
|
|
38
|
+
EXIT_FAIL: Final[int] = 2
|
|
39
|
+
|
|
40
|
+
# README sites inspected for badge URLs. Shields.io dynamic badge paths
|
|
41
|
+
# include the project / package coordinate after a `/github/v/release/`,
|
|
42
|
+
# `/github/v/tag/`, `/npm/v/`, `/npm/dm/`, `/pypi/v/`, or `/pypi/dm/` segment;
|
|
43
|
+
# static badges instead embed a literal version number in the URL or use the
|
|
44
|
+
# `/badge/` static generator with a hard-coded value.
|
|
45
|
+
README_CANDIDATES: Final[tuple[str, ...]] = ("README.md", "README.rst", "README")
|
|
46
|
+
|
|
47
|
+
# Site config candidates. Each is inspected for a `version:` key bearing a
|
|
48
|
+
# string-literal value rather than a templated / dynamic reference.
|
|
49
|
+
SITE_CONFIG_CANDIDATES: Final[tuple[str, ...]] = (
|
|
50
|
+
"site/next.config.mjs",
|
|
51
|
+
"site/package.json",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Runtime version-bearing module. The canonical pattern is
|
|
55
|
+
# ``__version__ = importlib.metadata.version(__package__)`` (or an
|
|
56
|
+
# equivalent ``metadata.version("apothem")`` form). A literal string
|
|
57
|
+
# assignment is the failure mode this validator catches.
|
|
58
|
+
RUNTIME_VERSION_MODULE: Final[str] = "src/apothem/__init__.py"
|
|
59
|
+
|
|
60
|
+
# A semver-shaped literal version embedded in a badge URL or a config
|
|
61
|
+
# value. Captures forms like `v1.2.3`, `1.2.3`, `1.2.3-alpha.1`,
|
|
62
|
+
# `2.0.0+build.7`. Word boundaries prevent partial matches inside
|
|
63
|
+
# longer identifiers.
|
|
64
|
+
LITERAL_VERSION_RE: Final[re.Pattern[str]] = re.compile(
|
|
65
|
+
r"\bv?\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?\b"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Shields.io dynamic badge markers — any of these path fragments in a
|
|
69
|
+
# badge URL indicates the badge resolves dynamically against an upstream
|
|
70
|
+
# registry rather than carrying a literal version.
|
|
71
|
+
SHIELDS_DYNAMIC_MARKERS: Final[tuple[str, ...]] = (
|
|
72
|
+
"img.shields.io/github/v/release/",
|
|
73
|
+
"img.shields.io/github/v/tag/",
|
|
74
|
+
"img.shields.io/npm/v/",
|
|
75
|
+
"img.shields.io/npm/dm/",
|
|
76
|
+
"img.shields.io/pypi/v/",
|
|
77
|
+
"img.shields.io/pypi/dm/",
|
|
78
|
+
"img.shields.io/crates/v/",
|
|
79
|
+
"img.shields.io/gem/v/",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Markdown badge / image syntax. Captures the URL inside the
|
|
83
|
+
# `` form so each URL can be classified independently.
|
|
84
|
+
MARKDOWN_IMAGE_RE: Final[re.Pattern[str]] = re.compile(r"!\[[^\]]*\]\(([^)]+)\)")
|
|
85
|
+
|
|
86
|
+
# Git-ref query parameters (`?branch=`, `?tag=`, `?ref=`) on a badge URL carry a
|
|
87
|
+
# git ref that may itself be a version tag — e.g. a build-status badge pinned to
|
|
88
|
+
# `?branch=v1.2.3`. That ref is not the badge's *displayed* version, so a semver
|
|
89
|
+
# token appearing only inside one of these parameters must not be read as a
|
|
90
|
+
# static-version literal. The parameter values are stripped before the
|
|
91
|
+
# literal-version scan; a version embedded in the URL *path* (the static
|
|
92
|
+
# `/badge/<label>-<version>-<color>` generator) is untouched and still flagged.
|
|
93
|
+
REF_PARAM_RE: Final[re.Pattern[str]] = re.compile(r"[?&](?:branch|tag|ref)=[^&]*")
|
|
94
|
+
|
|
95
|
+
# A literal ``version:`` key in a YAML/Python config carrying a quoted
|
|
96
|
+
# string value (site-generator config style). Templated references — e.g.
|
|
97
|
+
# ``version: !ENV [VERSION]`` or ``version = metadata.version(...)`` —
|
|
98
|
+
# do not match.
|
|
99
|
+
YAML_LITERAL_VERSION_RE: Final[re.Pattern[str]] = re.compile(
|
|
100
|
+
r"""^\s*version\s*[:=]\s*["']([^"']+)["']""",
|
|
101
|
+
re.MULTILINE,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# A literal ``__version__ = "x.y.z"`` assignment. Captures the assigned
|
|
105
|
+
# string when it is a literal; the dynamic form
|
|
106
|
+
# ``__version__ = importlib.metadata.version(...)`` is not matched.
|
|
107
|
+
LITERAL_DUNDER_VERSION_RE: Final[re.Pattern[str]] = re.compile(
|
|
108
|
+
r"""^\s*__version__\s*=\s*["']([^"']+)["']""",
|
|
109
|
+
re.MULTILINE,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Dynamic ``__version__`` resolution markers. Presence of any of these
|
|
113
|
+
# fragments indicates the runtime version derives from package metadata
|
|
114
|
+
# rather than a literal.
|
|
115
|
+
DYNAMIC_VERSION_MARKERS: Final[tuple[str, ...]] = (
|
|
116
|
+
"importlib.metadata.version",
|
|
117
|
+
"importlib_metadata.version",
|
|
118
|
+
"metadata.version(",
|
|
119
|
+
"version(__package__",
|
|
120
|
+
'version("apothem"',
|
|
121
|
+
"version('apothem'",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass(frozen=True)
|
|
126
|
+
class Finding:
|
|
127
|
+
"""One static-version site discovered in the project."""
|
|
128
|
+
|
|
129
|
+
site_class: str
|
|
130
|
+
path: str
|
|
131
|
+
detail: str
|
|
132
|
+
rule: str = RULE_ANCHOR
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass(frozen=True)
|
|
136
|
+
class GrepResult:
|
|
137
|
+
grep: str
|
|
138
|
+
path: str | None
|
|
139
|
+
passed: bool
|
|
140
|
+
findings: list[Finding] = field(default_factory=list)
|
|
141
|
+
|
|
142
|
+
def to_json(self) -> str:
|
|
143
|
+
payload = {
|
|
144
|
+
"grep": self.grep,
|
|
145
|
+
"path": self.path,
|
|
146
|
+
"passed": self.passed,
|
|
147
|
+
"findings": [asdict(f) for f in self.findings],
|
|
148
|
+
}
|
|
149
|
+
return json.dumps(payload, indent=2)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _strip_ref_params(url: str) -> str:
|
|
153
|
+
"""Remove git-ref query-parameter values from a badge URL.
|
|
154
|
+
|
|
155
|
+
``?branch=v1.2.3`` / ``?tag=v1.2.3`` / ``?ref=v1.2.3`` carry a git ref, not
|
|
156
|
+
a displayed version; their values are dropped so the literal-version scan
|
|
157
|
+
does not misread a dynamic build-status badge pinned to a release branch.
|
|
158
|
+
A version embedded in the URL path is untouched.
|
|
159
|
+
"""
|
|
160
|
+
return REF_PARAM_RE.sub("", url)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _check_readme(root: Path, findings: list[Finding]) -> None:
|
|
164
|
+
"""Flag badge URLs in README that carry literal versions."""
|
|
165
|
+
for candidate in README_CANDIDATES:
|
|
166
|
+
readme = root / candidate
|
|
167
|
+
if not readme.is_file():
|
|
168
|
+
continue
|
|
169
|
+
try:
|
|
170
|
+
text = readme.read_text(encoding="utf-8")
|
|
171
|
+
except (OSError, UnicodeDecodeError):
|
|
172
|
+
continue
|
|
173
|
+
for match in MARKDOWN_IMAGE_RE.finditer(text):
|
|
174
|
+
url = match.group(1).strip()
|
|
175
|
+
if "shields.io" not in url and "badge" not in url.lower():
|
|
176
|
+
continue
|
|
177
|
+
is_dynamic = any(marker in url for marker in SHIELDS_DYNAMIC_MARKERS)
|
|
178
|
+
if is_dynamic:
|
|
179
|
+
continue
|
|
180
|
+
if LITERAL_VERSION_RE.search(_strip_ref_params(url)):
|
|
181
|
+
findings.append(
|
|
182
|
+
Finding(
|
|
183
|
+
site_class="readme-badge",
|
|
184
|
+
path=str(readme.relative_to(root)),
|
|
185
|
+
detail=(
|
|
186
|
+
f"badge URL carries a literal version: {url!r}; "
|
|
187
|
+
"convert to a shields.io dynamic endpoint "
|
|
188
|
+
"(e.g., img.shields.io/npm/v/%40ahmed-g-gad%2Fapothem "
|
|
189
|
+
"or img.shields.io/github/v/release/ahmed-g-gad/apothem)"
|
|
190
|
+
),
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _check_site_config(root: Path, findings: list[Finding]) -> None:
|
|
196
|
+
"""Flag site config files declaring a literal ``version:``."""
|
|
197
|
+
for candidate in SITE_CONFIG_CANDIDATES:
|
|
198
|
+
config = root / candidate
|
|
199
|
+
if not config.is_file():
|
|
200
|
+
continue
|
|
201
|
+
try:
|
|
202
|
+
text = config.read_text(encoding="utf-8")
|
|
203
|
+
except (OSError, UnicodeDecodeError):
|
|
204
|
+
continue
|
|
205
|
+
if any(marker in text for marker in DYNAMIC_VERSION_MARKERS):
|
|
206
|
+
# The config imports a dynamic version-resolution helper; a
|
|
207
|
+
# nearby literal `version:` key is almost certainly a fallback
|
|
208
|
+
# default rather than the canonical source, so do not flag.
|
|
209
|
+
continue
|
|
210
|
+
for match in YAML_LITERAL_VERSION_RE.finditer(text):
|
|
211
|
+
literal = match.group(1)
|
|
212
|
+
if not LITERAL_VERSION_RE.search(literal):
|
|
213
|
+
continue
|
|
214
|
+
findings.append(
|
|
215
|
+
Finding(
|
|
216
|
+
site_class="site-config",
|
|
217
|
+
path=str(config.relative_to(root)),
|
|
218
|
+
detail=(
|
|
219
|
+
f"site config declares a literal version: {literal!r}; "
|
|
220
|
+
'derive from importlib.metadata.version("apothem") '
|
|
221
|
+
"or an equivalent dynamic source"
|
|
222
|
+
),
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _check_runtime_version(root: Path, findings: list[Finding]) -> None:
|
|
228
|
+
"""Flag a literal ``__version__`` assignment in the runtime module."""
|
|
229
|
+
module = root / RUNTIME_VERSION_MODULE
|
|
230
|
+
if not module.is_file():
|
|
231
|
+
return
|
|
232
|
+
try:
|
|
233
|
+
text = module.read_text(encoding="utf-8")
|
|
234
|
+
except (OSError, UnicodeDecodeError):
|
|
235
|
+
return
|
|
236
|
+
if any(marker in text for marker in DYNAMIC_VERSION_MARKERS):
|
|
237
|
+
return
|
|
238
|
+
match = LITERAL_DUNDER_VERSION_RE.search(text)
|
|
239
|
+
if match is None:
|
|
240
|
+
return
|
|
241
|
+
findings.append(
|
|
242
|
+
Finding(
|
|
243
|
+
site_class="runtime-version",
|
|
244
|
+
path=str(module.relative_to(root)),
|
|
245
|
+
detail=(
|
|
246
|
+
f"__version__ is a literal string {match.group(1)!r}; "
|
|
247
|
+
"derive from importlib.metadata.version(__package__) so "
|
|
248
|
+
"the runtime value tracks the installed package metadata"
|
|
249
|
+
),
|
|
250
|
+
)
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def check(root: Path) -> GrepResult:
|
|
255
|
+
"""Walk the three version-bearing site classes; return a structured result.
|
|
256
|
+
|
|
257
|
+
Pre-conditions: ``root`` is the project root (the directory holding
|
|
258
|
+
``pyproject.toml`` or its closest ancestor).
|
|
259
|
+
Post-conditions: ``result.passed`` is True iff every inspected site
|
|
260
|
+
resolves dynamically (README badges hit shields.io dynamic endpoints,
|
|
261
|
+
docs config derives version from package metadata, runtime
|
|
262
|
+
``__version__`` calls ``importlib.metadata.version`` or equivalent).
|
|
263
|
+
"""
|
|
264
|
+
findings: list[Finding] = []
|
|
265
|
+
_check_readme(root, findings)
|
|
266
|
+
_check_site_config(root, findings)
|
|
267
|
+
_check_runtime_version(root, findings)
|
|
268
|
+
return GrepResult(
|
|
269
|
+
grep=GREP_NAME,
|
|
270
|
+
path=str(root),
|
|
271
|
+
passed=not findings,
|
|
272
|
+
findings=findings,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def main(root: Path) -> int:
|
|
277
|
+
result = check(root)
|
|
278
|
+
print(result.to_json())
|
|
279
|
+
return EXIT_PASS if result.passed else EXIT_FAIL
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
if __name__ == "__main__":
|
|
283
|
+
target = Path(sys.argv[1]) if len(sys.argv) >= 2 else Path.cwd()
|
|
284
|
+
sys.exit(main(target))
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Flag filler phrases, throat-clearing openers, and content-free qualifiers.
|
|
4
|
+
|
|
5
|
+
Why this enforcement exists. The clean-room generation rule §5 (Prose and
|
|
6
|
+
Documentation) and the token-efficiency rewrite rule require every sentence
|
|
7
|
+
to advance the artifact's purpose. Filler ("In this section, we will…"),
|
|
8
|
+
throat-clearing ("Before diving in…"), restatement openers, and
|
|
9
|
+
content-free qualifiers ("very", "quite", "rather", "fairly") consume
|
|
10
|
+
tokens without carrying semantic load. The mechanical matcher surfaces
|
|
11
|
+
each occurrence so the operator can either delete the phrase, replace it
|
|
12
|
+
with substantive content, or — for qualifiers — substitute a measured
|
|
13
|
+
form ("twice as fast" rather than "very fast").
|
|
14
|
+
|
|
15
|
+
Scope. Hits inside fenced code blocks (between triple-backtick fences),
|
|
16
|
+
inline-code spans (single-backtick delimited), quoted blocks (lines
|
|
17
|
+
starting `>`), and HTML comment regions are excluded. These typically carry
|
|
18
|
+
external material, sample inputs, preserved conversation, or — for inline
|
|
19
|
+
code — a forbidden phrase cited meta-linguistically (a doc that names the
|
|
20
|
+
filler vocabulary it forbids) rather than the artifact's own prescriptive
|
|
21
|
+
prose.
|
|
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
|
+
# Multi-word filler phrases and throat-clearing openers. Matched
|
|
35
|
+
# case-insensitively against the line text. These patterns are
|
|
36
|
+
# multi-word, so word-boundary anchors at the ends suffice.
|
|
37
|
+
FILLER_PHRASES: Final[tuple[str, ...]] = (
|
|
38
|
+
r"In this section, we will",
|
|
39
|
+
r"It is important to note",
|
|
40
|
+
r"As mentioned earlier",
|
|
41
|
+
r"Before diving in",
|
|
42
|
+
r"Without further ado",
|
|
43
|
+
r"Let's begin by",
|
|
44
|
+
r"Before we discuss",
|
|
45
|
+
r"To begin with",
|
|
46
|
+
r"First and foremost",
|
|
47
|
+
r"kind of",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Single-word content-free qualifiers that flag unconditionally. Separated
|
|
51
|
+
# from the multi-word phrases so each carries word-boundary anchors and
|
|
52
|
+
# matches only when standing alone (not as a substring of a larger word).
|
|
53
|
+
# "rather" is intentionally NOT here: it needs a context exclusion (see
|
|
54
|
+
# RATHER_QUALIFIER_RE) because the comparative "rather than" carries
|
|
55
|
+
# semantic load and is not a content-free qualifier.
|
|
56
|
+
QUALIFIER_WORDS: Final[tuple[str, ...]] = (
|
|
57
|
+
"very",
|
|
58
|
+
"quite",
|
|
59
|
+
"somewhat",
|
|
60
|
+
"fairly",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
FILLER_RE: Final[re.Pattern[str]] = re.compile(
|
|
64
|
+
r"(?i)(?:" + "|".join(FILLER_PHRASES) + r")"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
QUALIFIER_RE: Final[re.Pattern[str]] = re.compile(
|
|
68
|
+
r"(?i)\b(?:" + "|".join(QUALIFIER_WORDS) + r")\b"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# "rather" is a content-free qualifier as a bare intensifier ("rather slow",
|
|
72
|
+
# "rather large") but carries semantic load in the comparative construction
|
|
73
|
+
# "rather than" ("X rather than Y" expresses a deliberate contrast — the kind
|
|
74
|
+
# of measured form §5 endorses, not filler). The negative lookahead excludes
|
|
75
|
+
# the comparative form so legitimate comparative prose is not flagged; the
|
|
76
|
+
# bare-intensifier form still matches.
|
|
77
|
+
RATHER_QUALIFIER_RE: Final[re.Pattern[str]] = re.compile(r"(?i)\brather\b(?!\s+than\b)")
|
|
78
|
+
|
|
79
|
+
# Every qualifier regex contributes findings. The order is fixed so the
|
|
80
|
+
# matcher's output is deterministic across runs.
|
|
81
|
+
QUALIFIER_RES: Final[tuple[re.Pattern[str], ...]] = (
|
|
82
|
+
QUALIFIER_RE,
|
|
83
|
+
RATHER_QUALIFIER_RE,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
CODE_FENCE_RE: Final[re.Pattern[str]] = re.compile(r"^```")
|
|
87
|
+
# Inline-code span: a single-backtick-delimited run on one line. A filler
|
|
88
|
+
# phrase or qualifier quoted inside backticks is a meta-linguistic citation
|
|
89
|
+
# (a doc naming the vocabulary it forbids), not prose that uses it, so these
|
|
90
|
+
# spans are blanked before the scan. The column-preserving blank mirrors the
|
|
91
|
+
# inline-code exclusion in binding_reciprocity_grep.
|
|
92
|
+
INLINE_CODE_RE: Final[re.Pattern[str]] = re.compile(r"`[^`]*`")
|
|
93
|
+
QUOTE_LINE_RE: Final[re.Pattern[str]] = re.compile(r"^\s*>")
|
|
94
|
+
HTML_COMMENT_OPEN_RE: Final[re.Pattern[str]] = re.compile(r"<!--")
|
|
95
|
+
HTML_COMMENT_CLOSE_RE: Final[re.Pattern[str]] = re.compile(r"--!?>")
|
|
96
|
+
|
|
97
|
+
GREP_NAME: Final[str] = "token-efficiency-grep"
|
|
98
|
+
RULE_ANCHOR: Final[str] = "clean-room-generation §5 + token-efficiency-rewrite"
|
|
99
|
+
EXIT_PASS: Final[int] = 0
|
|
100
|
+
EXIT_FAIL: Final[int] = 2
|
|
101
|
+
STDIN_FLAG: Final[str] = "--stdin"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass(frozen=True)
|
|
105
|
+
class Finding:
|
|
106
|
+
"""One filler or qualifier occurrence outside an excluded region."""
|
|
107
|
+
|
|
108
|
+
line: int
|
|
109
|
+
match: str
|
|
110
|
+
context: str
|
|
111
|
+
rule: str = RULE_ANCHOR
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def check(content: str, path: Path | None = None) -> GrepResult:
|
|
115
|
+
"""Scan content; return a structured result.
|
|
116
|
+
|
|
117
|
+
Pre-conditions: `content` is the artifact body about to be emitted.
|
|
118
|
+
Post-conditions: `result.passed` is True iff zero filler phrases and
|
|
119
|
+
zero content-free qualifiers were found outside fenced code blocks,
|
|
120
|
+
inline-code spans, quoted lines, or HTML comment regions.
|
|
121
|
+
"""
|
|
122
|
+
findings: list[Finding] = []
|
|
123
|
+
inside_fence = False
|
|
124
|
+
inside_html_comment = False
|
|
125
|
+
for line_index, line in enumerate(content.splitlines(), start=1):
|
|
126
|
+
if CODE_FENCE_RE.match(line):
|
|
127
|
+
inside_fence = not inside_fence
|
|
128
|
+
continue
|
|
129
|
+
if inside_fence:
|
|
130
|
+
continue
|
|
131
|
+
# Track HTML comment span. A single-line comment opens and closes
|
|
132
|
+
# on the same line; a multi-line comment spans several lines.
|
|
133
|
+
working = line
|
|
134
|
+
if inside_html_comment:
|
|
135
|
+
close = HTML_COMMENT_CLOSE_RE.search(working)
|
|
136
|
+
if close is None:
|
|
137
|
+
continue
|
|
138
|
+
inside_html_comment = False
|
|
139
|
+
working = working[close.end() :]
|
|
140
|
+
# Strip inline comments and detect a trailing open.
|
|
141
|
+
while True:
|
|
142
|
+
open_match = HTML_COMMENT_OPEN_RE.search(working)
|
|
143
|
+
if open_match is None:
|
|
144
|
+
break
|
|
145
|
+
close_match = HTML_COMMENT_CLOSE_RE.search(working, open_match.end())
|
|
146
|
+
if close_match is None:
|
|
147
|
+
working = working[: open_match.start()]
|
|
148
|
+
inside_html_comment = True
|
|
149
|
+
break
|
|
150
|
+
working = working[: open_match.start()] + working[close_match.end() :]
|
|
151
|
+
# Blank inline-code spans (same-length spaces preserve columns) so a
|
|
152
|
+
# filler phrase or qualifier cited inside backticks — a doc that
|
|
153
|
+
# forbids the `kind of` phrasing — is read as a citation, not prose.
|
|
154
|
+
working = INLINE_CODE_RE.sub(lambda m: " " * len(m.group(0)), working)
|
|
155
|
+
if QUOTE_LINE_RE.match(working):
|
|
156
|
+
continue
|
|
157
|
+
if not working.strip():
|
|
158
|
+
continue
|
|
159
|
+
for match in FILLER_RE.finditer(working):
|
|
160
|
+
findings.append(
|
|
161
|
+
Finding(
|
|
162
|
+
line=line_index,
|
|
163
|
+
match=match.group(),
|
|
164
|
+
context=line.strip(),
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
for qualifier_re in QUALIFIER_RES:
|
|
168
|
+
for match in qualifier_re.finditer(working):
|
|
169
|
+
findings.append(
|
|
170
|
+
Finding(
|
|
171
|
+
line=line_index,
|
|
172
|
+
match=match.group(),
|
|
173
|
+
context=line.strip(),
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
return GrepResult(
|
|
177
|
+
grep=GREP_NAME,
|
|
178
|
+
path=str(path) if path is not None else None,
|
|
179
|
+
passed=not findings,
|
|
180
|
+
findings=findings,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
if __name__ == "__main__":
|
|
185
|
+
sys.exit(run_grep(check, sys.argv))
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Flag GitHub Actions workflows that pin actions to mutable refs.
|
|
4
|
+
|
|
5
|
+
Why this enforcement exists. The production-ready discipline M15 + the
|
|
6
|
+
supply-chain posture preservation clause require third-party action
|
|
7
|
+
references to be pinned to a 40-character commit SHA rather than a
|
|
8
|
+
floating tag. A `uses: actions/checkout@v4` reference can silently
|
|
9
|
+
re-point to a malicious release; a `uses: actions/checkout@<40-char-sha>`
|
|
10
|
+
reference cannot. The pre-emission gate's mechanical bar 15 (M15 supply-chain) catches the
|
|
11
|
+
mutable-ref shapes (`@main`, `@master`, `@latest`, `@v1`, `@v1.x.y`,
|
|
12
|
+
`@<branch>`) and surfaces each occurrence so the operator pins to a
|
|
13
|
+
SHA in the same change-set.
|
|
14
|
+
|
|
15
|
+
Detection strategy. The grep parses every line beginning with
|
|
16
|
+
`uses: <owner>/<action>@<ref>` (whitespace-tolerant) and inspects the
|
|
17
|
+
`<ref>`. A 40-character lowercase-hex SHA is the only conformant form;
|
|
18
|
+
anything else is flagged with the parsed action name and the offending
|
|
19
|
+
ref so the operator can rewrite to the SHA pin.
|
|
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
|
+
# `uses: <owner>/<action>(/<sub>)*@<ref>` shape per the GitHub Actions
|
|
33
|
+
# workflow YAML specification. The optional sub-path matches actions like
|
|
34
|
+
# `actions/cache/save@v3`. The capture groups isolate the action name and
|
|
35
|
+
# the ref so the finding can name both. A trailing `# ...` comment is
|
|
36
|
+
# tolerated so inline exemption markers (see `EXEMPTION_RE`) sit on the
|
|
37
|
+
# same line as the declaration without causing the regex to miss-match.
|
|
38
|
+
USES_LINE_RE: Final[re.Pattern[str]] = re.compile(
|
|
39
|
+
r"^\s*-?\s*uses:\s*([\w./-]+)@([\w./+-]+)\s*(?:#.*)?$"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Canonical SHA shape — exactly forty lowercase hex characters. No upper-
|
|
43
|
+
# case, no shorter prefixes (those are abbreviations, not pins).
|
|
44
|
+
COMMIT_SHA_RE: Final[re.Pattern[str]] = re.compile(r"^[0-9a-f]{40}$")
|
|
45
|
+
|
|
46
|
+
# Inline exemption marker. A `uses:` line carrying a trailing comment of
|
|
47
|
+
# the form `# action-pinning-exempt: <reason>` is exempt from the SHA-pin
|
|
48
|
+
# requirement. The reason is required (audit-trail discipline) — bare
|
|
49
|
+
# `# action-pinning-exempt` without a reason is not honored. Canonical
|
|
50
|
+
# use case: SLSA reusable-workflow references where the Sigstore policy
|
|
51
|
+
# forbids SHA-pinning and the `@<tag>` form is the trust anchor rather
|
|
52
|
+
# than a mutable ref.
|
|
53
|
+
EXEMPTION_RE: Final[re.Pattern[str]] = re.compile(r"#\s*action-pinning-exempt:\s*\S")
|
|
54
|
+
|
|
55
|
+
# Local-action references (path within the same repository) start with
|
|
56
|
+
# `./` and are out of scope — they're not third-party supply-chain risks.
|
|
57
|
+
LOCAL_ACTION_PREFIX: Final[str] = "./"
|
|
58
|
+
|
|
59
|
+
GREP_NAME: Final[str] = "unpinned-action-grep"
|
|
60
|
+
RULE_ANCHOR: Final[str] = "M15 production-ready §Supply-chain"
|
|
61
|
+
EXIT_PASS: Final[int] = 0
|
|
62
|
+
EXIT_FAIL: Final[int] = 2
|
|
63
|
+
STDIN_FLAG: Final[str] = "--stdin"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass(frozen=True)
|
|
67
|
+
class Finding:
|
|
68
|
+
"""One unpinned-action occurrence."""
|
|
69
|
+
|
|
70
|
+
line: int
|
|
71
|
+
action: str
|
|
72
|
+
ref: str
|
|
73
|
+
rule: str = RULE_ANCHOR
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def check(content: str, path: Path | None = None) -> GrepResult:
|
|
77
|
+
"""Scan content; return a structured result.
|
|
78
|
+
|
|
79
|
+
Pre-conditions: `content` is the YAML body of a workflow file about
|
|
80
|
+
to be emitted. The grep accepts content from any source — the
|
|
81
|
+
operator points it at a single workflow file or pipes a multi-file
|
|
82
|
+
bundle via stdin.
|
|
83
|
+
Post-conditions: `result.passed` is True iff every `uses: <action>@<ref>`
|
|
84
|
+
declaration's ref is a 40-character commit SHA, or the action is a
|
|
85
|
+
local in-repo reference exempt from supply-chain pinning.
|
|
86
|
+
"""
|
|
87
|
+
findings: list[Finding] = []
|
|
88
|
+
for line_index, line in enumerate(content.splitlines(), start=1):
|
|
89
|
+
match = USES_LINE_RE.match(line)
|
|
90
|
+
if match is None:
|
|
91
|
+
continue
|
|
92
|
+
action, ref = match.group(1), match.group(2)
|
|
93
|
+
if action.startswith(LOCAL_ACTION_PREFIX):
|
|
94
|
+
continue
|
|
95
|
+
if COMMIT_SHA_RE.match(ref) is not None:
|
|
96
|
+
continue
|
|
97
|
+
if EXEMPTION_RE.search(line) is not None:
|
|
98
|
+
continue
|
|
99
|
+
findings.append(
|
|
100
|
+
Finding(
|
|
101
|
+
line=line_index,
|
|
102
|
+
action=action,
|
|
103
|
+
ref=ref,
|
|
104
|
+
)
|
|
105
|
+
)
|
|
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,74 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Block emission of artifacts containing unresolved authority placeholders.
|
|
4
|
+
|
|
5
|
+
Why this enforcement exists. Required-category inquiries (identity, scope
|
|
6
|
+
direction, security, public-surface naming) emit a `<USER-CONFIRM:id=...>`
|
|
7
|
+
placeholder that the operator must resolve before the artifact ships. An
|
|
8
|
+
unfilled placeholder reaching emission means a required-data inquiry was
|
|
9
|
+
never closed; the artifact would silently bind to fabricated authority data
|
|
10
|
+
the moment it lands. The pre-emission gate's mechanical bar 5 (M5 authority) catches the
|
|
11
|
+
literal placeholder shape so the operator never has to remember.
|
|
12
|
+
|
|
13
|
+
Invocation. Two surfaces: as a callable for the orchestrator (`check(content,
|
|
14
|
+
path)`), or as a CLI tool (`python user-confirm-grep.py <file>` / `--stdin`).
|
|
15
|
+
Exit code 0 indicates clean; non-zero indicates a finding. Stdout carries a
|
|
16
|
+
JSON report regardless of outcome — the orchestrator reads it; humans read
|
|
17
|
+
it for triage.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import re
|
|
23
|
+
import sys
|
|
24
|
+
from dataclasses import dataclass
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Final
|
|
27
|
+
|
|
28
|
+
from apothem.conformity._grep_base import GrepResult, run_grep
|
|
29
|
+
|
|
30
|
+
# The canonical placeholder shape declared in the authority-inquiry rule §10.
|
|
31
|
+
# `<USER-CONFIRM:id=foo>`, `<USER-CONFIRM:kind=bar>`, and bare `<USER-CONFIRM>`
|
|
32
|
+
# all flag — the colon-id form is the documented contract; the bare form is
|
|
33
|
+
# rare but equally non-conformant.
|
|
34
|
+
PLACEHOLDER_RE: Final[re.Pattern[str]] = re.compile(r"<USER-CONFIRM(?::[^>]*)?>")
|
|
35
|
+
|
|
36
|
+
GREP_NAME: Final[str] = "user-confirm-grep"
|
|
37
|
+
RULE_ANCHOR: Final[str] = "M5 authority-inquiry §10"
|
|
38
|
+
EXIT_PASS: Final[int] = 0
|
|
39
|
+
EXIT_FAIL: Final[int] = 2
|
|
40
|
+
STDIN_FLAG: Final[str] = "--stdin"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class Finding:
|
|
45
|
+
"""One placeholder occurrence in the artifact body."""
|
|
46
|
+
|
|
47
|
+
line: int
|
|
48
|
+
match: str
|
|
49
|
+
rule: str = RULE_ANCHOR
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def check(content: str, path: Path | None = None) -> GrepResult:
|
|
53
|
+
"""Scan content; return a structured result.
|
|
54
|
+
|
|
55
|
+
Pre-conditions: `content` is the artifact body about to be emitted.
|
|
56
|
+
Post-conditions: `result.passed` is True iff zero placeholders were found.
|
|
57
|
+
Failure mode: malformed UTF-8 raises `UnicodeDecodeError` at the read site,
|
|
58
|
+
not here — this function operates on already-decoded text.
|
|
59
|
+
"""
|
|
60
|
+
findings: list[Finding] = []
|
|
61
|
+
for match in PLACEHOLDER_RE.finditer(content):
|
|
62
|
+
# Convert byte offset to a 1-indexed line number for human triage.
|
|
63
|
+
line = content.count("\n", 0, match.start()) + 1
|
|
64
|
+
findings.append(Finding(line=line, match=match.group()))
|
|
65
|
+
return GrepResult(
|
|
66
|
+
grep=GREP_NAME,
|
|
67
|
+
path=str(path) if path is not None else None,
|
|
68
|
+
passed=not findings,
|
|
69
|
+
findings=findings,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
sys.exit(run_grep(check, sys.argv))
|