@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,401 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Flag substantively-duplicated paragraphs across the governed corpus.
|
|
4
|
+
|
|
5
|
+
Why this enforcement exists. Spec section 3.1.e enumerates a five-class
|
|
6
|
+
redundancy taxonomy that erodes the demand-load discipline: (a)
|
|
7
|
+
parent-rule / companion sub-rule overlap, (b) cross-rule conceptual
|
|
8
|
+
duplication, (c) command / skill content overlap, (d) hook / rule
|
|
9
|
+
duplication, and (e) example duplication across docs. Each class
|
|
10
|
+
multiplies maintenance cost — a single edit must reach every duplicate
|
|
11
|
+
or the corpus drifts.
|
|
12
|
+
|
|
13
|
+
Detection strategy. The validator walks the four canonical authoring
|
|
14
|
+
trees (``rules/``, ``commands/``, ``skills/``, ``hooks/messages/``),
|
|
15
|
+
splits every Markdown file into paragraphs on blank-line boundaries,
|
|
16
|
+
normalises each paragraph (lowercase, collapse whitespace, strip
|
|
17
|
+
punctuation), tokenises into a set, and compares pairwise across files
|
|
18
|
+
using Jaccard similarity. Two paragraphs above ``--threshold`` (default
|
|
19
|
+
0.80) AND carrying at least 40 substantive tokens are reported as a
|
|
20
|
+
duplication finding with the participating file paths and a suggested
|
|
21
|
+
canonical home (the file whose path prefix sorts earliest, as a
|
|
22
|
+
deterministic-but-reviewable hint — the operator picks the real
|
|
23
|
+
canonical home).
|
|
24
|
+
|
|
25
|
+
Scope carve-outs. Lines opening with ``(Companion Sub-Rule Anchor)`` are
|
|
26
|
+
EXPLICIT delegation pointers and are excluded from comparison. Fenced
|
|
27
|
+
code blocks are excluded (sample inputs and command snippets quote
|
|
28
|
+
external material rather than carry the author's prescriptive prose).
|
|
29
|
+
YAML frontmatter is excluded. The ``## Bindings`` tail section is
|
|
30
|
+
excluded — reciprocal binding statements legitimately overlap by
|
|
31
|
+
construction.
|
|
32
|
+
|
|
33
|
+
Heuristic-status disclaimer. Jaccard on tokenised paragraphs is a
|
|
34
|
+
heuristic. False positives are expected on canonical templates (banner
|
|
35
|
+
lines, gate attestation skeletons, common preambles). The operator
|
|
36
|
+
triages each finding; the threshold flag exists so a tighter or looser
|
|
37
|
+
gate can be tuned per corpus state without code changes.
|
|
38
|
+
|
|
39
|
+
Exit semantics. Exits 0 when no findings; exits 2 on any finding.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
from __future__ import annotations
|
|
43
|
+
|
|
44
|
+
import argparse
|
|
45
|
+
import json
|
|
46
|
+
import re
|
|
47
|
+
import sys
|
|
48
|
+
from dataclasses import asdict, dataclass, field
|
|
49
|
+
from pathlib import Path
|
|
50
|
+
from typing import Final
|
|
51
|
+
|
|
52
|
+
GREP_NAME: Final[str] = "redundancy-grep"
|
|
53
|
+
RULE_ANCHOR: Final[str] = (
|
|
54
|
+
"rules/operational-mandates.md §CM-7 coherent-product non-redundancy"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
EXIT_PASS: Final[int] = 0
|
|
58
|
+
EXIT_FAIL: Final[int] = 2
|
|
59
|
+
|
|
60
|
+
# Canonical authoring trees the validator walks. The relative roots are
|
|
61
|
+
# resolved against the project root passed on the command line.
|
|
62
|
+
CORPUS_SUBTREES: Final[tuple[str, ...]] = (
|
|
63
|
+
"src/apothem/rules",
|
|
64
|
+
"src/apothem/commands",
|
|
65
|
+
"src/apothem/skills",
|
|
66
|
+
"src/apothem/hooks/messages",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Default Jaccard similarity threshold above which two paragraphs are
|
|
70
|
+
# reported as duplicates. Tuneable via --threshold.
|
|
71
|
+
DEFAULT_THRESHOLD: Final[float] = 0.80
|
|
72
|
+
|
|
73
|
+
# Minimum substantive token count for a paragraph to enter comparison.
|
|
74
|
+
# Shorter paragraphs (headings, one-liners, brief bullets) carry too
|
|
75
|
+
# little signal for Jaccard to discriminate genuine duplication from
|
|
76
|
+
# coincidental phrase overlap.
|
|
77
|
+
MIN_SUBSTANTIVE_TOKENS: Final[int] = 40
|
|
78
|
+
|
|
79
|
+
# Fenced code-block delimiter and frontmatter delimiter.
|
|
80
|
+
_FENCE_RE: Final[re.Pattern[str]] = re.compile(r"^```")
|
|
81
|
+
_FRONTMATTER_DELIM: Final[str] = "---"
|
|
82
|
+
|
|
83
|
+
# Companion-anchor pointer marker — excluded from comparison.
|
|
84
|
+
_COMPANION_ANCHOR_MARKER: Final[str] = "(Companion Sub-Rule Anchor)"
|
|
85
|
+
|
|
86
|
+
# Bindings tail section header. Everything from this header to EOF is
|
|
87
|
+
# excluded from the comparison surface.
|
|
88
|
+
_BINDINGS_HEADER_RE: Final[re.Pattern[str]] = re.compile(
|
|
89
|
+
r"^##+\s*Bindings\b", re.MULTILINE
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Markdown ATX heading recogniser. Captures the heading level (count of
|
|
93
|
+
# leading '#') and the title text.
|
|
94
|
+
_HEADING_RE: Final[re.Pattern[str]] = re.compile(r"^(#{1,6})\s+(.+?)\s*$")
|
|
95
|
+
|
|
96
|
+
# Canonical-scaffolding heading allow-list. Paragraphs whose enclosing
|
|
97
|
+
# heading (or any ancestor heading in the section hierarchy) contains any
|
|
98
|
+
# of these substrings as a heading title are excluded from comparison.
|
|
99
|
+
# The blocks under these headings are canonical-by-design — the same
|
|
100
|
+
# voice / contract / pipeline-position prose appears uniformly across
|
|
101
|
+
# every audit-fortress and plan-pipeline command and across every
|
|
102
|
+
# header-guard hook message, per `skills/ecosystem-audit/SKILL.md`
|
|
103
|
+
# Audit-Fortress Phase Skeleton and the operator-ratified canonical
|
|
104
|
+
# scaffolding pattern. Generic redundancy-grep flagging would force
|
|
105
|
+
# stripping the uniformity that the canonical scaffolding requires.
|
|
106
|
+
# Match is case-insensitive substring.
|
|
107
|
+
_SCAFFOLDING_HEADING_SUBSTRINGS: Final[tuple[str, ...]] = (
|
|
108
|
+
"pipeline contract",
|
|
109
|
+
"foundational stanzas",
|
|
110
|
+
"refusal & escalation",
|
|
111
|
+
"output surface",
|
|
112
|
+
"file-authoring contract",
|
|
113
|
+
"askuserquestion on ambiguity",
|
|
114
|
+
"inquiry cadence",
|
|
115
|
+
"audit-fortress",
|
|
116
|
+
"critical rules",
|
|
117
|
+
"mandates",
|
|
118
|
+
"findings report shape",
|
|
119
|
+
"axis-of-attention",
|
|
120
|
+
"axis attestation",
|
|
121
|
+
"borderline triage",
|
|
122
|
+
"severity triage",
|
|
123
|
+
"pre-emission gate",
|
|
124
|
+
"output discipline",
|
|
125
|
+
"inquiry triage",
|
|
126
|
+
"scope clarification",
|
|
127
|
+
# `## Resolution & Recovery` carries the canonical template/surface
|
|
128
|
+
# corruption-fallback contract that every pipeline suite-skill states in
|
|
129
|
+
# uniform voice (plan-suite cites TM-N/CP-N IDs, research-suite cites the
|
|
130
|
+
# R-mandate/lifecycle IDs) — same scaffolding class as "refusal &
|
|
131
|
+
# escalation"; per-pipeline specialisation lives in the prose, so the
|
|
132
|
+
# blocks stay self-contained rather than cross-referencing one another.
|
|
133
|
+
"resolution & recovery",
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Punctuation stripper for token normalisation.
|
|
137
|
+
_PUNCT_RE: Final[re.Pattern[str]] = re.compile(r"[^\w\s]+")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@dataclass(frozen=True)
|
|
141
|
+
class Finding:
|
|
142
|
+
issue: str
|
|
143
|
+
detail: str
|
|
144
|
+
files: list[str]
|
|
145
|
+
similarity: float
|
|
146
|
+
suggested_canonical_home: str
|
|
147
|
+
rule: str = RULE_ANCHOR
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@dataclass(frozen=True)
|
|
151
|
+
class GrepResult:
|
|
152
|
+
grep: str
|
|
153
|
+
root: str
|
|
154
|
+
threshold: float
|
|
155
|
+
paragraph_count: int
|
|
156
|
+
passed: bool
|
|
157
|
+
findings: list[Finding] = field(default_factory=list)
|
|
158
|
+
|
|
159
|
+
def to_json(self) -> str:
|
|
160
|
+
payload = {
|
|
161
|
+
"grep": self.grep,
|
|
162
|
+
"root": self.root,
|
|
163
|
+
"threshold": self.threshold,
|
|
164
|
+
"paragraph_count": self.paragraph_count,
|
|
165
|
+
"passed": self.passed,
|
|
166
|
+
"findings": [asdict(f) for f in self.findings],
|
|
167
|
+
}
|
|
168
|
+
return json.dumps(payload, indent=2)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _strip_frontmatter(text: str) -> str:
|
|
172
|
+
"""Remove leading YAML frontmatter, if present."""
|
|
173
|
+
lines = text.splitlines()
|
|
174
|
+
if not lines or lines[0].strip() != _FRONTMATTER_DELIM:
|
|
175
|
+
return text
|
|
176
|
+
for idx in range(1, len(lines)):
|
|
177
|
+
if lines[idx].strip() == _FRONTMATTER_DELIM:
|
|
178
|
+
return "\n".join(lines[idx + 1 :])
|
|
179
|
+
return text
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _strip_bindings_tail(text: str) -> str:
|
|
183
|
+
"""Trim everything from the first `## Bindings` header to EOF."""
|
|
184
|
+
match = _BINDINGS_HEADER_RE.search(text)
|
|
185
|
+
if match is None:
|
|
186
|
+
return text
|
|
187
|
+
return text[: match.start()]
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _strip_fenced_code(text: str) -> str:
|
|
191
|
+
"""Remove fenced code blocks; the surrounding prose remains."""
|
|
192
|
+
out: list[str] = []
|
|
193
|
+
in_fence = False
|
|
194
|
+
for line in text.splitlines():
|
|
195
|
+
if _FENCE_RE.match(line):
|
|
196
|
+
in_fence = not in_fence
|
|
197
|
+
continue
|
|
198
|
+
if not in_fence:
|
|
199
|
+
out.append(line)
|
|
200
|
+
return "\n".join(out)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _is_scaffolding_heading(title: str) -> bool:
|
|
204
|
+
"""Return True iff the heading title matches any canonical-scaffolding
|
|
205
|
+
allow-list substring (case-insensitive)."""
|
|
206
|
+
lowered = title.lower()
|
|
207
|
+
return any(sub in lowered for sub in _SCAFFOLDING_HEADING_SUBSTRINGS)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _split_paragraphs(text: str) -> list[str]:
|
|
211
|
+
"""Split on blank-line boundaries; drop companion-anchor pointers and
|
|
212
|
+
paragraphs whose enclosing heading hierarchy includes a
|
|
213
|
+
canonical-scaffolding allow-listed section.
|
|
214
|
+
|
|
215
|
+
The walker tracks the current heading stack: each ATX heading
|
|
216
|
+
establishes the title at its level and clears every deeper level.
|
|
217
|
+
A paragraph is excluded when any active heading in the stack matches
|
|
218
|
+
the scaffolding allow-list.
|
|
219
|
+
"""
|
|
220
|
+
out: list[str] = []
|
|
221
|
+
heading_stack: dict[int, str] = {}
|
|
222
|
+
current_block: list[str] = []
|
|
223
|
+
|
|
224
|
+
def flush_block() -> None:
|
|
225
|
+
if not current_block:
|
|
226
|
+
return
|
|
227
|
+
para = "\n".join(current_block).strip()
|
|
228
|
+
current_block.clear()
|
|
229
|
+
if not para:
|
|
230
|
+
return
|
|
231
|
+
if _COMPANION_ANCHOR_MARKER in para:
|
|
232
|
+
return
|
|
233
|
+
if any(_is_scaffolding_heading(title) for title in heading_stack.values()):
|
|
234
|
+
return
|
|
235
|
+
out.append(para)
|
|
236
|
+
|
|
237
|
+
for line in text.splitlines():
|
|
238
|
+
heading_match = _HEADING_RE.match(line)
|
|
239
|
+
if heading_match:
|
|
240
|
+
flush_block()
|
|
241
|
+
level = len(heading_match.group(1))
|
|
242
|
+
title = heading_match.group(2)
|
|
243
|
+
heading_stack[level] = title
|
|
244
|
+
for deeper in [k for k in heading_stack if k > level]:
|
|
245
|
+
del heading_stack[deeper]
|
|
246
|
+
continue
|
|
247
|
+
if not line.strip():
|
|
248
|
+
flush_block()
|
|
249
|
+
continue
|
|
250
|
+
current_block.append(line)
|
|
251
|
+
flush_block()
|
|
252
|
+
return out
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _tokenise(paragraph: str) -> frozenset[str]:
|
|
256
|
+
"""Lowercase, strip punctuation, split on whitespace; return token set."""
|
|
257
|
+
normalised = _PUNCT_RE.sub(" ", paragraph.lower())
|
|
258
|
+
return frozenset(tok for tok in normalised.split() if tok)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _jaccard(a: frozenset[str], b: frozenset[str]) -> float:
|
|
262
|
+
if not a or not b:
|
|
263
|
+
return 0.0
|
|
264
|
+
inter = len(a & b)
|
|
265
|
+
union = len(a | b)
|
|
266
|
+
return inter / union if union else 0.0
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _walk_corpus(root: Path) -> list[Path]:
|
|
270
|
+
"""Return every `.md` file under the four canonical subtrees."""
|
|
271
|
+
files: list[Path] = []
|
|
272
|
+
for sub in CORPUS_SUBTREES:
|
|
273
|
+
base = root / sub
|
|
274
|
+
if not base.exists():
|
|
275
|
+
continue
|
|
276
|
+
files.extend(sorted(base.rglob("*.md")))
|
|
277
|
+
return files
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _extract_paragraphs(path: Path) -> list[tuple[str, frozenset[str]]]:
|
|
281
|
+
"""Return (text, token-set) pairs for every comparable paragraph."""
|
|
282
|
+
try:
|
|
283
|
+
raw = path.read_text(encoding="utf-8")
|
|
284
|
+
except (OSError, UnicodeDecodeError):
|
|
285
|
+
return []
|
|
286
|
+
text = _strip_frontmatter(raw)
|
|
287
|
+
text = _strip_bindings_tail(text)
|
|
288
|
+
text = _strip_fenced_code(text)
|
|
289
|
+
out: list[tuple[str, frozenset[str]]] = []
|
|
290
|
+
for para in _split_paragraphs(text):
|
|
291
|
+
tokens = _tokenise(para)
|
|
292
|
+
if len(tokens) < MIN_SUBSTANTIVE_TOKENS:
|
|
293
|
+
continue
|
|
294
|
+
out.append((para, tokens))
|
|
295
|
+
return out
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _are_sibling_variants(path_a: Path, path_b: Path) -> bool:
|
|
299
|
+
"""Return True iff two files are canonical sibling variants.
|
|
300
|
+
|
|
301
|
+
Two files in the same directory whose stems differ in exactly one
|
|
302
|
+
kebab-token position (e.g., ``pretooluse-edit-header-guard`` vs.
|
|
303
|
+
``pretooluse-write-header-guard``) or where one stem is a strict
|
|
304
|
+
prefix of the other (parent / companion form,
|
|
305
|
+
e.g., ``sota-elevation`` vs. ``sota-elevation-exemplars``) are
|
|
306
|
+
canonical sibling variants. Their shared content IS the
|
|
307
|
+
design — the matcher excludes pairings between them.
|
|
308
|
+
"""
|
|
309
|
+
if path_a.parent != path_b.parent:
|
|
310
|
+
return False
|
|
311
|
+
tokens_a = path_a.stem.split("-")
|
|
312
|
+
tokens_b = path_b.stem.split("-")
|
|
313
|
+
if tokens_a == tokens_b:
|
|
314
|
+
return False
|
|
315
|
+
# Parent / companion: one is a strict prefix of the other.
|
|
316
|
+
shorter, longer = sorted([tokens_a, tokens_b], key=len)
|
|
317
|
+
if longer[: len(shorter)] == shorter:
|
|
318
|
+
return True
|
|
319
|
+
# Equal length: differ in exactly one position.
|
|
320
|
+
if len(tokens_a) == len(tokens_b):
|
|
321
|
+
differences = sum(1 for a, b in zip(tokens_a, tokens_b, strict=True) if a != b)
|
|
322
|
+
return differences == 1
|
|
323
|
+
return False
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def check_corpus(root: Path, threshold: float = DEFAULT_THRESHOLD) -> GrepResult:
|
|
327
|
+
"""Walk the corpus; report duplicated paragraphs above ``threshold``."""
|
|
328
|
+
files = _walk_corpus(root)
|
|
329
|
+
# Flatten to (path, paragraph_text, tokens) tuples.
|
|
330
|
+
entries: list[tuple[Path, str, frozenset[str]]] = []
|
|
331
|
+
for f in files:
|
|
332
|
+
for text, tokens in _extract_paragraphs(f):
|
|
333
|
+
entries.append((f, text, tokens))
|
|
334
|
+
findings: list[Finding] = []
|
|
335
|
+
seen_pairs: set[tuple[int, int]] = set()
|
|
336
|
+
for i in range(len(entries)):
|
|
337
|
+
path_i, text_i, tokens_i = entries[i]
|
|
338
|
+
for j in range(i + 1, len(entries)):
|
|
339
|
+
path_j, _text_j, tokens_j = entries[j]
|
|
340
|
+
if path_i == path_j:
|
|
341
|
+
continue
|
|
342
|
+
if _are_sibling_variants(path_i, path_j):
|
|
343
|
+
continue
|
|
344
|
+
sim = _jaccard(tokens_i, tokens_j)
|
|
345
|
+
if sim < threshold:
|
|
346
|
+
continue
|
|
347
|
+
key = (i, j)
|
|
348
|
+
if key in seen_pairs:
|
|
349
|
+
continue
|
|
350
|
+
seen_pairs.add(key)
|
|
351
|
+
files_pair = sorted({path_i.as_posix(), path_j.as_posix()})
|
|
352
|
+
canonical_home = files_pair[0]
|
|
353
|
+
preview = text_i.replace("\n", " ").strip()
|
|
354
|
+
if len(preview) > 160:
|
|
355
|
+
preview = preview[:157] + "..."
|
|
356
|
+
findings.append(
|
|
357
|
+
Finding(
|
|
358
|
+
issue="substantively-duplicated paragraph",
|
|
359
|
+
detail=(
|
|
360
|
+
f"paragraph with jaccard similarity {sim:.2f} "
|
|
361
|
+
f"appears across {len(files_pair)} files; "
|
|
362
|
+
f"preview: {preview!r}"
|
|
363
|
+
),
|
|
364
|
+
files=files_pair,
|
|
365
|
+
similarity=round(sim, 4),
|
|
366
|
+
suggested_canonical_home=canonical_home,
|
|
367
|
+
)
|
|
368
|
+
)
|
|
369
|
+
return GrepResult(
|
|
370
|
+
grep=GREP_NAME,
|
|
371
|
+
root=str(root),
|
|
372
|
+
threshold=threshold,
|
|
373
|
+
paragraph_count=len(entries),
|
|
374
|
+
passed=not findings,
|
|
375
|
+
findings=findings,
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _main(argv: list[str]) -> int:
|
|
380
|
+
parser = argparse.ArgumentParser(prog=GREP_NAME, description=__doc__)
|
|
381
|
+
parser.add_argument(
|
|
382
|
+
"root",
|
|
383
|
+
nargs="?",
|
|
384
|
+
default=".",
|
|
385
|
+
help="project root to walk (default: current directory)",
|
|
386
|
+
)
|
|
387
|
+
parser.add_argument(
|
|
388
|
+
"--threshold",
|
|
389
|
+
type=float,
|
|
390
|
+
default=DEFAULT_THRESHOLD,
|
|
391
|
+
help=f"jaccard similarity threshold (default: {DEFAULT_THRESHOLD})",
|
|
392
|
+
)
|
|
393
|
+
args = parser.parse_args(argv[1:])
|
|
394
|
+
root = Path(args.root).resolve()
|
|
395
|
+
result = check_corpus(root, threshold=args.threshold)
|
|
396
|
+
print(result.to_json())
|
|
397
|
+
return EXIT_PASS if result.passed else EXIT_FAIL
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
if __name__ == "__main__":
|
|
401
|
+
sys.exit(_main(sys.argv))
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Flag leaked reference-platform branding tokens on authored surfaces.
|
|
4
|
+
|
|
5
|
+
Why this enforcement exists. Apothem reimplements a peer platform's
|
|
6
|
+
feature-set in its own voice; the own-voice discipline requires that no
|
|
7
|
+
authored surface — rules, commands, skills, agents, documentation, brand
|
|
8
|
+
assets — leak the reference platform's slug or brand vocabulary. A
|
|
9
|
+
mechanical sweep proves zero in-scope hits so the product narrative reads
|
|
10
|
+
as its own work rather than as a paraphrase of the reference platform.
|
|
11
|
+
|
|
12
|
+
Scope. Corpus-level standalone validator. Walks the working tree under the
|
|
13
|
+
supplied root and inspects ONLY authored in-scope surfaces (the rules,
|
|
14
|
+
commands, skills, agents directories, the documentation tree, and the
|
|
15
|
+
brand-asset tree). The denylist data file, the conformity machinery, the
|
|
16
|
+
schemas directory, the test tree, and the plan-suite scratch are all
|
|
17
|
+
exempt — they are not the authored product narrative the discipline holds
|
|
18
|
+
to the zero-leak bar.
|
|
19
|
+
|
|
20
|
+
Detection. The forbidden tokens live in the package-relative denylist data
|
|
21
|
+
file, never in this module. Pure-alpha slugs match at word boundaries to
|
|
22
|
+
avoid false positives on larger words; punctuation-bearing tokens match as
|
|
23
|
+
escaped literals. All matching is case-insensitive.
|
|
24
|
+
|
|
25
|
+
Posture. The result is advisory: the sweep surfaces findings and never
|
|
26
|
+
silent-blocks by default, honoring apothem's agnostic gate posture. CI
|
|
27
|
+
opts into strict enforcement separately. Exits 0 when zero findings across
|
|
28
|
+
every in-scope file; exits 2 on any finding.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import json
|
|
34
|
+
import re
|
|
35
|
+
import sys
|
|
36
|
+
from dataclasses import asdict, dataclass, field
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
from typing import Final
|
|
39
|
+
|
|
40
|
+
GREP_NAME: Final[str] = "reference-token-grep"
|
|
41
|
+
RULE_ANCHOR: Final[str] = "rules/own-voice-reimplementation.md"
|
|
42
|
+
|
|
43
|
+
EXIT_PASS: Final[int] = 0
|
|
44
|
+
EXIT_FAIL: Final[int] = 2
|
|
45
|
+
|
|
46
|
+
# The denylist data file is package-relative: the conformity package ships
|
|
47
|
+
# beside its ``schemas/`` sibling in both supported layouts — the repo
|
|
48
|
+
# checkout (``src/apothem/{conformity,schemas}``) and the installed tree
|
|
49
|
+
# (``<install-root>/apothem/{conformity,schemas}``) — so one parent hop
|
|
50
|
+
# from this module reaches it in either shape. The data file holds the
|
|
51
|
+
# forbidden tokens so this module never names them.
|
|
52
|
+
_DENYLIST_PATH: Final[Path] = (
|
|
53
|
+
Path(__file__).resolve().parents[1] / "schemas" / "reference-token-denylist.txt"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Authored in-scope surface roots (relative to the supplied root). Only text
|
|
57
|
+
# files under these directories are scanned.
|
|
58
|
+
_IN_SCOPE_DIRS: Final[tuple[str, ...]] = (
|
|
59
|
+
"src/apothem/rules",
|
|
60
|
+
"src/apothem/commands",
|
|
61
|
+
"src/apothem/skills",
|
|
62
|
+
"src/apothem/agents",
|
|
63
|
+
"site/content/docs",
|
|
64
|
+
"assets",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Text-file suffixes scanned under the in-scope roots.
|
|
68
|
+
_TEXT_SUFFIXES: Final[frozenset[str]] = frozenset(
|
|
69
|
+
{".md", ".mdx", ".txt", ".svg", ".json", ".yaml", ".yml"}
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Exempt path fragments. A candidate whose POSIX-relative path contains any
|
|
73
|
+
# of these fragments is never scanned — the conformity machinery, the
|
|
74
|
+
# schemas data files (the denylist itself), the test tree, and the
|
|
75
|
+
# plan-suite scratch all legitimately reference or carry the tokens.
|
|
76
|
+
_EXEMPT_FRAGMENTS: Final[tuple[str, ...]] = (
|
|
77
|
+
".plans/",
|
|
78
|
+
"tests/",
|
|
79
|
+
"src/apothem/conformity/",
|
|
80
|
+
"src/apothem/schemas/",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass(frozen=True)
|
|
85
|
+
class Finding:
|
|
86
|
+
"""One leaked reference-platform token on an authored surface."""
|
|
87
|
+
|
|
88
|
+
surface: str
|
|
89
|
+
kind: str
|
|
90
|
+
detail: str
|
|
91
|
+
rule: str = RULE_ANCHOR
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(frozen=True)
|
|
95
|
+
class GrepResult:
|
|
96
|
+
"""Aggregated walk result for a single corpus sweep."""
|
|
97
|
+
|
|
98
|
+
grep: str
|
|
99
|
+
root: str
|
|
100
|
+
files_inspected: int
|
|
101
|
+
passed: bool
|
|
102
|
+
findings: list[Finding] = field(default_factory=list)
|
|
103
|
+
|
|
104
|
+
def to_json(self) -> str:
|
|
105
|
+
payload = {
|
|
106
|
+
"grep": self.grep,
|
|
107
|
+
"root": self.root,
|
|
108
|
+
"files_inspected": self.files_inspected,
|
|
109
|
+
"passed": self.passed,
|
|
110
|
+
"findings": [asdict(f) for f in self.findings],
|
|
111
|
+
"advisory": True,
|
|
112
|
+
}
|
|
113
|
+
return json.dumps(payload, indent=2)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _load_tokens() -> list[str]:
|
|
117
|
+
"""Parse the denylist data file into a list of forbidden tokens.
|
|
118
|
+
|
|
119
|
+
Pre-conditions: the package-relative denylist file exists.
|
|
120
|
+
Post-conditions: returns every non-empty, non-comment line, stripped.
|
|
121
|
+
"""
|
|
122
|
+
try:
|
|
123
|
+
raw = _DENYLIST_PATH.read_text(encoding="utf-8")
|
|
124
|
+
except OSError:
|
|
125
|
+
return []
|
|
126
|
+
tokens: list[str] = []
|
|
127
|
+
for line in raw.splitlines():
|
|
128
|
+
stripped = line.strip()
|
|
129
|
+
if not stripped or stripped.startswith("#"):
|
|
130
|
+
continue
|
|
131
|
+
tokens.append(stripped)
|
|
132
|
+
return tokens
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _compile_tokens(tokens: list[str]) -> re.Pattern[str] | None:
|
|
136
|
+
"""Compile the denylist into one case-insensitive alternation regex.
|
|
137
|
+
|
|
138
|
+
Pure-alphabetic tokens are anchored at word boundaries (``\\btoken\\b``)
|
|
139
|
+
so a larger word merely containing the substring is not flagged.
|
|
140
|
+
Punctuation-bearing tokens are escaped literals — word boundaries do not
|
|
141
|
+
apply cleanly across ``.`` / ``-`` characters, so the escaped literal is
|
|
142
|
+
matched as-is. Returns None when the denylist is empty.
|
|
143
|
+
"""
|
|
144
|
+
if not tokens:
|
|
145
|
+
return None
|
|
146
|
+
alternatives: list[str] = []
|
|
147
|
+
for token in tokens:
|
|
148
|
+
if token.isalpha():
|
|
149
|
+
alternatives.append(r"\b" + re.escape(token) + r"\b")
|
|
150
|
+
else:
|
|
151
|
+
alternatives.append(re.escape(token))
|
|
152
|
+
return re.compile(r"(?i)(?:" + "|".join(alternatives) + r")")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _is_in_scope(rel_posix: str) -> bool:
|
|
156
|
+
"""Return True iff a relative POSIX path is an authored in-scope surface."""
|
|
157
|
+
for fragment in _EXEMPT_FRAGMENTS:
|
|
158
|
+
if fragment in rel_posix or rel_posix.startswith(fragment.rstrip("/")):
|
|
159
|
+
return False
|
|
160
|
+
return any(rel_posix == d or rel_posix.startswith(d + "/") for d in _IN_SCOPE_DIRS)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _scan_file(path: Path, rel_posix: str, pattern: re.Pattern[str]) -> list[Finding]:
|
|
164
|
+
"""Scan one text file; return one Finding per token match."""
|
|
165
|
+
try:
|
|
166
|
+
content = path.read_text(encoding="utf-8")
|
|
167
|
+
except (OSError, UnicodeDecodeError):
|
|
168
|
+
return []
|
|
169
|
+
findings: list[Finding] = []
|
|
170
|
+
for line_index, line in enumerate(content.splitlines(), start=1):
|
|
171
|
+
for match in pattern.finditer(line):
|
|
172
|
+
findings.append(
|
|
173
|
+
Finding(
|
|
174
|
+
surface=rel_posix,
|
|
175
|
+
kind="reference-token",
|
|
176
|
+
detail=f"line {line_index}: '{match.group()}'",
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
return findings
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def check(root: Path) -> GrepResult:
|
|
183
|
+
"""Walk the corpus under ``root``; flag leaked reference-platform tokens.
|
|
184
|
+
|
|
185
|
+
Pre-conditions: ``root`` is the repository root (or an arbitrary subtree).
|
|
186
|
+
Post-conditions: ``result.passed`` is True iff every authored in-scope
|
|
187
|
+
text file contained zero denylisted tokens.
|
|
188
|
+
"""
|
|
189
|
+
tokens = _load_tokens()
|
|
190
|
+
pattern = _compile_tokens(tokens)
|
|
191
|
+
findings: list[Finding] = []
|
|
192
|
+
inspected = 0
|
|
193
|
+
root_resolved = root.resolve()
|
|
194
|
+
if pattern is not None:
|
|
195
|
+
for suffix in sorted(_TEXT_SUFFIXES):
|
|
196
|
+
for path in root.rglob(f"*{suffix}"):
|
|
197
|
+
if not path.is_file():
|
|
198
|
+
continue
|
|
199
|
+
try:
|
|
200
|
+
rel_posix = path.resolve().relative_to(root_resolved).as_posix()
|
|
201
|
+
except ValueError:
|
|
202
|
+
continue
|
|
203
|
+
if not _is_in_scope(rel_posix):
|
|
204
|
+
continue
|
|
205
|
+
inspected += 1
|
|
206
|
+
findings.extend(_scan_file(path, rel_posix, pattern))
|
|
207
|
+
return GrepResult(
|
|
208
|
+
grep=GREP_NAME,
|
|
209
|
+
root=str(root),
|
|
210
|
+
files_inspected=inspected,
|
|
211
|
+
passed=not findings,
|
|
212
|
+
findings=findings,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _read_input(argv: list[str]) -> Path:
|
|
217
|
+
if len(argv) >= 2:
|
|
218
|
+
return Path(argv[1])
|
|
219
|
+
return Path.cwd()
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _main(argv: list[str]) -> int:
|
|
223
|
+
root = _read_input(argv)
|
|
224
|
+
result = check(root)
|
|
225
|
+
print(result.to_json())
|
|
226
|
+
return EXIT_PASS if result.passed else EXIT_FAIL
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
if __name__ == "__main__":
|
|
230
|
+
sys.exit(_main(sys.argv))
|