@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,204 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""license-author-consistency-grep: LICENSE author-identity presence check.
|
|
4
|
+
|
|
5
|
+
Reads the project's ``LICENSE`` file at the repository root and asserts it
|
|
6
|
+
carries a parseable ``Copyright (c) <name>`` author line. Under the D-007
|
|
7
|
+
NARROW verdict the per-file authorship banner is reduced to the single
|
|
8
|
+
``SPDX-License-Identifier: MIT`` line and no longer carries an author name, so
|
|
9
|
+
the historical banner-vs-LICENSE cross-check no longer applies. The root
|
|
10
|
+
``LICENSE`` remains the authoritative carrier of the copyright instrument, so
|
|
11
|
+
this validator now verifies that the LICENSE author identity is present and
|
|
12
|
+
parseable. It never invents author data; when the LICENSE is absent or carries
|
|
13
|
+
the canonical pending placeholder it reports the pending state and warns rather
|
|
14
|
+
than asserting a failure.
|
|
15
|
+
|
|
16
|
+
Verdict matrix:
|
|
17
|
+
pass — LICENSE carries a parseable ``Copyright (c) <name>`` line.
|
|
18
|
+
pass + warning — LICENSE absent or carries the canonical
|
|
19
|
+
``<USER-CONFIRM:license-pending>`` placeholder; the author
|
|
20
|
+
identity cannot yet be asserted.
|
|
21
|
+
fail — LICENSE is present and non-pending but carries no parseable
|
|
22
|
+
``Copyright (c) <name>`` author line.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import re
|
|
28
|
+
import sys
|
|
29
|
+
from dataclasses import dataclass
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from typing import Final
|
|
32
|
+
|
|
33
|
+
from apothem.conformity._grep_base import GrepResult, run_grep
|
|
34
|
+
|
|
35
|
+
GREP_NAME: Final[str] = "license-author-consistency-grep"
|
|
36
|
+
EXIT_PASS: Final[int] = 0
|
|
37
|
+
EXIT_FAIL: Final[int] = 2
|
|
38
|
+
STDIN_FLAG: Final[str] = "--stdin"
|
|
39
|
+
|
|
40
|
+
# Working-tree root anchor for the repo-root LICENSE check. In the repo
|
|
41
|
+
# checkout, parents[3] of ``src/apothem/conformity/<file>.py`` is the
|
|
42
|
+
# working-tree root where ``LICENSE`` lives. In the installed tree
|
|
43
|
+
# (``<install-root>/apothem/conformity/``) the anchor is a coarse
|
|
44
|
+
# filesystem ancestor that carries no project LICENSE: hook-mode
|
|
45
|
+
# dispatches degrade to a pass-through via the relative_to() guard in
|
|
46
|
+
# _is_in_scope(), and a direct invocation reports the pending state
|
|
47
|
+
# (pass + warning) rather than asserting a failure.
|
|
48
|
+
ECOSYSTEM_ROOT: Final[Path] = Path(__file__).resolve().parents[3]
|
|
49
|
+
LICENSE_RELATIVE: Final[Path] = Path("LICENSE")
|
|
50
|
+
LICENSE_PENDING_PLACEHOLDER: Final[str] = "<USER-CONFIRM:license-pending>"
|
|
51
|
+
|
|
52
|
+
# Match a copyright line with optional symbol, optional year tokens, and the
|
|
53
|
+
# author tail. Examples successfully matched:
|
|
54
|
+
# "Copyright (c) 2026 Ahmed G. Gad"
|
|
55
|
+
# "Copyright (C) Ahmed G. Gad"
|
|
56
|
+
COPYRIGHT_LINE_RE: Final[re.Pattern[str]] = re.compile(
|
|
57
|
+
r"Copyright\s*\(\s*[cC©]\s*\)\s*(?P<tail>.+?)\s*$",
|
|
58
|
+
re.MULTILINE,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Year-token pattern stripped before name comparison; matches single year,
|
|
62
|
+
# year ranges, comma-separated years, and any leading copyright symbols.
|
|
63
|
+
YEAR_TOKEN_RE: Final[re.Pattern[str]] = re.compile(
|
|
64
|
+
r"^\d{4}(?:\s*[-,]\s*\d{4})?(?:\s*,\s*\d{4})*\s+"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
RULE_AUTHOR_ABSENT: Final[str] = "LICENSE_AUTHOR_ABSENT"
|
|
68
|
+
RULE_LICENSE_PENDING: Final[str] = "LICENSE_PENDING"
|
|
69
|
+
|
|
70
|
+
SEVERITY_ERROR: Final[str] = "error"
|
|
71
|
+
SEVERITY_WARNING: Final[str] = "warning"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(frozen=True)
|
|
75
|
+
class Finding:
|
|
76
|
+
"""One diagnostic finding from the LICENSE author-presence check."""
|
|
77
|
+
|
|
78
|
+
line: int
|
|
79
|
+
match: str
|
|
80
|
+
context: str
|
|
81
|
+
rule: str
|
|
82
|
+
severity: str = SEVERITY_ERROR
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _normalise_author(tail: str) -> str:
|
|
86
|
+
"""Strip leading year tokens from a copyright tail."""
|
|
87
|
+
return YEAR_TOKEN_RE.sub("", tail).strip()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _extract_author(text: str) -> str | None:
|
|
91
|
+
"""Extract the normalized author name from the first copyright line."""
|
|
92
|
+
match = COPYRIGHT_LINE_RE.search(text)
|
|
93
|
+
if match is None:
|
|
94
|
+
return None
|
|
95
|
+
return _normalise_author(match.group("tail"))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _is_in_scope(path: Path) -> bool:
|
|
99
|
+
"""Return True when *path* is the LICENSE file."""
|
|
100
|
+
try:
|
|
101
|
+
rel = path.resolve().relative_to(ECOSYSTEM_ROOT)
|
|
102
|
+
except ValueError:
|
|
103
|
+
return False
|
|
104
|
+
return rel == LICENSE_RELATIVE
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def check(content: str, path: Path | None = None) -> GrepResult:
|
|
108
|
+
"""Validate that the LICENSE carries a parseable author identity.
|
|
109
|
+
|
|
110
|
+
Hook-mode pass-through. When *path* is set and is not the LICENSE
|
|
111
|
+
file, the validator returns ``passed=True`` immediately. The check
|
|
112
|
+
runs on in-scope dispatches and on CLI mode without a path argument.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
``GrepResult`` with ``passed=True`` when the LICENSE names an
|
|
116
|
+
author (or is pending, with an advisory finding), ``passed=False``
|
|
117
|
+
when the LICENSE is present and non-pending but names no author.
|
|
118
|
+
"""
|
|
119
|
+
# `content` is unused — interface parity with the orchestrator's
|
|
120
|
+
# _CheckCallable Protocol.
|
|
121
|
+
_ = content
|
|
122
|
+
if path is not None and not _is_in_scope(path):
|
|
123
|
+
return GrepResult(
|
|
124
|
+
grep=GREP_NAME,
|
|
125
|
+
path=str(path),
|
|
126
|
+
passed=True,
|
|
127
|
+
note="check skipped (scope not resolvable)",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
license_path = ECOSYSTEM_ROOT / LICENSE_RELATIVE
|
|
131
|
+
findings: list[Finding] = []
|
|
132
|
+
|
|
133
|
+
if not license_path.is_file():
|
|
134
|
+
findings.append(
|
|
135
|
+
Finding(
|
|
136
|
+
line=0,
|
|
137
|
+
match=str(LICENSE_RELATIVE.as_posix()),
|
|
138
|
+
context=(
|
|
139
|
+
f"LICENSE absent at {LICENSE_RELATIVE.as_posix()};"
|
|
140
|
+
f" pending the LICENSE selection"
|
|
141
|
+
),
|
|
142
|
+
rule=RULE_LICENSE_PENDING,
|
|
143
|
+
severity=SEVERITY_WARNING,
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
return GrepResult(
|
|
147
|
+
grep=GREP_NAME,
|
|
148
|
+
path=str(license_path),
|
|
149
|
+
passed=True,
|
|
150
|
+
findings=findings,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
license_text = license_path.read_text(encoding="utf-8")
|
|
154
|
+
if LICENSE_PENDING_PLACEHOLDER in license_text:
|
|
155
|
+
findings.append(
|
|
156
|
+
Finding(
|
|
157
|
+
line=0,
|
|
158
|
+
match=LICENSE_PENDING_PLACEHOLDER,
|
|
159
|
+
context=(
|
|
160
|
+
"LICENSE carries the canonical pending placeholder;"
|
|
161
|
+
" deferred until the LICENSE is finalized"
|
|
162
|
+
),
|
|
163
|
+
rule=RULE_LICENSE_PENDING,
|
|
164
|
+
severity=SEVERITY_WARNING,
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
return GrepResult(
|
|
168
|
+
grep=GREP_NAME,
|
|
169
|
+
path=str(license_path),
|
|
170
|
+
passed=True,
|
|
171
|
+
findings=findings,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
license_author = _extract_author(license_text)
|
|
175
|
+
if license_author is None or not license_author:
|
|
176
|
+
findings.append(
|
|
177
|
+
Finding(
|
|
178
|
+
line=0,
|
|
179
|
+
match=str(LICENSE_RELATIVE.as_posix()),
|
|
180
|
+
context=(
|
|
181
|
+
f"LICENSE at {LICENSE_RELATIVE.as_posix()} has no"
|
|
182
|
+
f" parseable Copyright (c) author line"
|
|
183
|
+
),
|
|
184
|
+
rule=RULE_AUTHOR_ABSENT,
|
|
185
|
+
severity=SEVERITY_ERROR,
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
return GrepResult(
|
|
189
|
+
grep=GREP_NAME,
|
|
190
|
+
path=str(license_path),
|
|
191
|
+
passed=False,
|
|
192
|
+
findings=findings,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
return GrepResult(
|
|
196
|
+
grep=GREP_NAME,
|
|
197
|
+
path=str(license_path),
|
|
198
|
+
passed=True,
|
|
199
|
+
findings=[],
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
if __name__ == "__main__":
|
|
204
|
+
sys.exit(run_grep(check, sys.argv))
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Validate Markdown internal links against the on-disk reference graph.
|
|
4
|
+
|
|
5
|
+
Why this enforcement exists. Spec section 2.4 ratifies that every
|
|
6
|
+
emitted Markdown link is resolvable at the artifact's emission time.
|
|
7
|
+
Stale internal links (pointing at moved or removed paths) are findings
|
|
8
|
+
per the visual-leverage rule's section 4 staleness clause and per the
|
|
9
|
+
ten-dimension check's dimension 5 (orphanism / staleness). The pre-
|
|
10
|
+
emission gate's mechanical row catches dead internal-path references
|
|
11
|
+
before they leave the operator's hands.
|
|
12
|
+
|
|
13
|
+
Detection strategy. The validator parses Markdown link syntax
|
|
14
|
+
``[text](path)`` and ``[text](path#anchor)`` from the input body and
|
|
15
|
+
checks each resolvable internal reference:
|
|
16
|
+
|
|
17
|
+
- **Relative links** (``../foo.md``, ``foo/bar.md``) resolve against the
|
|
18
|
+
source file's directory; a destination with no on-disk artifact is a
|
|
19
|
+
finding.
|
|
20
|
+
- **Site-route links** of the form ``/docs/<path>`` (English, served at
|
|
21
|
+
the site root) and ``/<locale>/docs/<path>`` (a non-default cohort
|
|
22
|
+
locale) resolve against the Fumadocs page set under
|
|
23
|
+
``site/content/docs/``. ``/docs/a/b`` maps to
|
|
24
|
+
``site/content/docs/a/b.mdx`` or the folder-index page
|
|
25
|
+
``site/content/docs/a/b/index.mdx``; the locale form maps under the
|
|
26
|
+
``site/content/docs/<locale>/`` subtree. A route backed by neither file
|
|
27
|
+
is a dead route — a user-facing 404 — and a finding, caught here rather
|
|
28
|
+
than only by the site build. The page root is discovered by walking the
|
|
29
|
+
source file's ancestors; when no ``site/content/docs`` ancestor exists
|
|
30
|
+
(a Markdown body outside the site repo) ``/docs/`` routes stay out of
|
|
31
|
+
scope.
|
|
32
|
+
- **Other root-absolute routes** (``/``, asset paths, well-known files)
|
|
33
|
+
and **external references** (HTTP / HTTPS, ``mailto:``) are counted but
|
|
34
|
+
not retrieved — their validity is the site build's or remote host's
|
|
35
|
+
concern, not this matcher's.
|
|
36
|
+
|
|
37
|
+
The Fumadocs routing contract this mapping mirrors is declared at
|
|
38
|
+
``site/lib/source.ts`` (``baseUrl: '/docs'``, ``parser: 'dir'``,
|
|
39
|
+
``hideLocale: 'default-locale'``) and the locale cohort at
|
|
40
|
+
``site/lib/i18n.ts`` (``ROUTED_NON_DEFAULT_LOCALES``).
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
from __future__ import annotations
|
|
44
|
+
|
|
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
|
+
# Markdown inline link shape: [text](destination). The destination may
|
|
53
|
+
# carry an optional fragment (#anchor) and may be wrapped in angle
|
|
54
|
+
# brackets per CommonMark. Reference-style links ([text][label]) are
|
|
55
|
+
# out of scope for the baseline pass; the host may extend coverage if
|
|
56
|
+
# reference-style usage emerges as a finding class.
|
|
57
|
+
LINK_RE: Final[re.Pattern[str]] = re.compile(
|
|
58
|
+
r"\[(?P<text>[^\]]*)\]\((?P<dest>[^)\s]+?)(?:\s+\"[^\"]*\")?\)"
|
|
59
|
+
)
|
|
60
|
+
EXTERNAL_PREFIXES: Final[tuple[str, ...]] = (
|
|
61
|
+
"http://",
|
|
62
|
+
"https://",
|
|
63
|
+
"mailto:",
|
|
64
|
+
"ftp://",
|
|
65
|
+
"ftps://",
|
|
66
|
+
)
|
|
67
|
+
GREP_NAME: Final[str] = "link-check"
|
|
68
|
+
RULE_ANCHOR: Final[str] = "rules/code-craft-markdown.md §6 Link Discipline"
|
|
69
|
+
EXIT_PASS: Final[int] = 0
|
|
70
|
+
EXIT_FAIL: Final[int] = 2
|
|
71
|
+
STDIN_FLAG: Final[str] = "--stdin"
|
|
72
|
+
|
|
73
|
+
# --- Fumadocs site-route resolution -----------------------------------------
|
|
74
|
+
#
|
|
75
|
+
# Root-absolute links of the form ``/docs/<path>`` (English, served at the
|
|
76
|
+
# site root) and ``/<locale>/docs/<path>`` (a non-default cohort locale) are
|
|
77
|
+
# real published routes whose validity the conformity gate can check without a
|
|
78
|
+
# site build: the Fumadocs ``loader`` (``site/lib/source.ts``) maps each route
|
|
79
|
+
# to an on-disk page under ``site/content/docs/``. Every OTHER root-absolute
|
|
80
|
+
# route (``/``, ``/img/...``, ``/.well-known/...``) carries no such mapping and
|
|
81
|
+
# stays out of scope.
|
|
82
|
+
|
|
83
|
+
# Repo-root-relative path components of the Fumadocs page content root.
|
|
84
|
+
DOCS_CONTENT_ROOT_SEGMENTS: Final[tuple[str, ...]] = ("site", "content", "docs")
|
|
85
|
+
# The route segment that mounts the docs tree (``baseUrl: '/docs'``).
|
|
86
|
+
DOCS_ROUTE_PREFIX: Final[str] = "docs"
|
|
87
|
+
# Page-source suffixes Fumadocs loads (``.mdx`` primary, ``.md`` accepted). A
|
|
88
|
+
# route resolves when any leaf-page or folder-index candidate, in any of these
|
|
89
|
+
# suffixes, exists on disk.
|
|
90
|
+
PAGE_SUFFIXES: Final[tuple[str, ...]] = (".mdx", ".md")
|
|
91
|
+
# Non-default routed-locale URL segments — the ``/<locale>/docs/...`` prefixes.
|
|
92
|
+
# Mirrors ``ROUTED_NON_DEFAULT_LOCALES`` at ``site/lib/i18n.ts`` (the single
|
|
93
|
+
# source of truth for the locale cohort); ``en`` is omitted because
|
|
94
|
+
# ``hideLocale: 'default-locale'`` serves English at the unprefixed root.
|
|
95
|
+
# Cohort amendments route through structured inquiry there, then mirror here.
|
|
96
|
+
LOCALE_ROUTE_SEGMENTS: Final[frozenset[str]] = frozenset(
|
|
97
|
+
{"es", "zh-cn", "pt-br", "fr", "de", "ja", "ko", "ru", "id", "hi", "ar"}
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass(frozen=True)
|
|
102
|
+
class Finding:
|
|
103
|
+
line: int
|
|
104
|
+
destination: str
|
|
105
|
+
detail: str
|
|
106
|
+
rule: str = RULE_ANCHOR
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass(frozen=True)
|
|
110
|
+
class GrepResult:
|
|
111
|
+
grep: str
|
|
112
|
+
path: str | None
|
|
113
|
+
passed: bool
|
|
114
|
+
internal_count: int
|
|
115
|
+
external_count: int
|
|
116
|
+
findings: list[Finding] = field(default_factory=list)
|
|
117
|
+
|
|
118
|
+
def to_json(self) -> str:
|
|
119
|
+
payload = {
|
|
120
|
+
"grep": self.grep,
|
|
121
|
+
"path": self.path,
|
|
122
|
+
"passed": self.passed,
|
|
123
|
+
"internal-count": self.internal_count,
|
|
124
|
+
"external-count": self.external_count,
|
|
125
|
+
"findings": [asdict(f) for f in self.findings],
|
|
126
|
+
}
|
|
127
|
+
return json.dumps(payload, indent=2)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _is_external(destination: str) -> bool:
|
|
131
|
+
return destination.startswith(EXTERNAL_PREFIXES)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _strip_fragment(destination: str) -> str:
|
|
135
|
+
return destination.split("#", 1)[0]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _resolve_target(
|
|
139
|
+
source: Path | None,
|
|
140
|
+
destination: str,
|
|
141
|
+
) -> Path | None:
|
|
142
|
+
"""Resolve a relative destination to an on-disk path."""
|
|
143
|
+
target = _strip_fragment(destination)
|
|
144
|
+
if not target:
|
|
145
|
+
# Anchor-only links (e.g., [foo](#section)) point inside the
|
|
146
|
+
# source file; treat as resolved by construction.
|
|
147
|
+
return source
|
|
148
|
+
if source is None:
|
|
149
|
+
return None
|
|
150
|
+
return (source.parent / target).resolve()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _find_docs_content_root(source: Path | None) -> Path | None:
|
|
154
|
+
"""Locate the ``site/content/docs`` page root above *source*, if any.
|
|
155
|
+
|
|
156
|
+
Walks *source*'s resolved ancestors and returns the first that contains
|
|
157
|
+
a ``site/content/docs`` directory. Returns None when *source* is None or
|
|
158
|
+
no such ancestor exists (e.g. a Markdown body checked outside the site
|
|
159
|
+
repo) — in which case ``/docs/`` routes stay out of scope, exactly as
|
|
160
|
+
before this resolution was added.
|
|
161
|
+
"""
|
|
162
|
+
if source is None:
|
|
163
|
+
return None
|
|
164
|
+
try:
|
|
165
|
+
resolved = source.expanduser().resolve()
|
|
166
|
+
except (OSError, RuntimeError):
|
|
167
|
+
return None
|
|
168
|
+
for ancestor in resolved.parents:
|
|
169
|
+
candidate = ancestor.joinpath(*DOCS_CONTENT_ROOT_SEGMENTS)
|
|
170
|
+
if candidate.is_dir():
|
|
171
|
+
return candidate
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _docs_route_targets(docs_root: Path, route: str) -> list[Path] | None:
|
|
176
|
+
"""Map a root-absolute ``/docs/...`` route to its on-disk page candidates.
|
|
177
|
+
|
|
178
|
+
*route* is the destination with its fragment already stripped and a
|
|
179
|
+
leading ``/``. Returns the candidate on-disk paths — the leaf page and
|
|
180
|
+
the folder-index page, across the accepted page suffixes — when *route*
|
|
181
|
+
is a ``/docs/<path>`` or ``/<locale>/docs/<path>`` site route; returns
|
|
182
|
+
None for any other root-absolute path (out of scope). The caller treats
|
|
183
|
+
a non-None list whose members all fail ``exists()`` as a dead route.
|
|
184
|
+
"""
|
|
185
|
+
# Drop a query string and any trailing slash; the live corpus carries
|
|
186
|
+
# neither, but tolerating them keeps a future ``/docs/a/`` or
|
|
187
|
+
# ``/docs/a?x=1`` from being mis-flagged.
|
|
188
|
+
path_part = route.split("?", 1)[0].rstrip("/")
|
|
189
|
+
segments = [segment for segment in path_part.split("/") if segment]
|
|
190
|
+
if segments and segments[0] == DOCS_ROUTE_PREFIX:
|
|
191
|
+
locale: str | None = None
|
|
192
|
+
rest = segments[1:]
|
|
193
|
+
elif (
|
|
194
|
+
len(segments) >= 2
|
|
195
|
+
and segments[0] in LOCALE_ROUTE_SEGMENTS
|
|
196
|
+
and segments[1] == DOCS_ROUTE_PREFIX
|
|
197
|
+
):
|
|
198
|
+
locale = segments[0]
|
|
199
|
+
rest = segments[2:]
|
|
200
|
+
else:
|
|
201
|
+
return None
|
|
202
|
+
base = docs_root if locale is None else docs_root / locale
|
|
203
|
+
if not rest:
|
|
204
|
+
# Bare ``/docs`` (or ``/<locale>/docs``): the section landing page.
|
|
205
|
+
return [base / f"index{suffix}" for suffix in PAGE_SUFFIXES]
|
|
206
|
+
leaf = base.joinpath(*rest)
|
|
207
|
+
candidates: list[Path] = []
|
|
208
|
+
for suffix in PAGE_SUFFIXES:
|
|
209
|
+
# The route as a page file: /docs/a/b -> site/content/docs/a/b.mdx.
|
|
210
|
+
candidates.append(leaf.parent / f"{leaf.name}{suffix}")
|
|
211
|
+
# The route as a section folder: -> site/content/docs/a/b/index.mdx.
|
|
212
|
+
candidates.append(leaf / f"index{suffix}")
|
|
213
|
+
return candidates
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _render_targets(docs_root: Path, targets: list[Path]) -> str:
|
|
217
|
+
"""Render candidate paths relative to the repo root for a finding message."""
|
|
218
|
+
# docs_root == <repo>/site/content/docs; parents[2] is the repo root.
|
|
219
|
+
try:
|
|
220
|
+
repo_root = docs_root.parents[len(DOCS_CONTENT_ROOT_SEGMENTS) - 1]
|
|
221
|
+
except IndexError:
|
|
222
|
+
repo_root = docs_root
|
|
223
|
+
rendered: list[str] = []
|
|
224
|
+
for target in targets:
|
|
225
|
+
try:
|
|
226
|
+
rendered.append(str(target.relative_to(repo_root)).replace("\\", "/"))
|
|
227
|
+
except ValueError:
|
|
228
|
+
rendered.append(str(target))
|
|
229
|
+
return " | ".join(rendered)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def check(
|
|
233
|
+
content: str,
|
|
234
|
+
path: Path | None = None,
|
|
235
|
+
) -> GrepResult:
|
|
236
|
+
"""Scan a Markdown body for internal-link reachability."""
|
|
237
|
+
findings: list[Finding] = []
|
|
238
|
+
internal = 0
|
|
239
|
+
external = 0
|
|
240
|
+
# The Fumadocs page root, discovered lazily on the first root-absolute
|
|
241
|
+
# link so the common path (no ``/docs/`` links) pays no filesystem cost.
|
|
242
|
+
docs_root: Path | None = None
|
|
243
|
+
docs_root_computed = False
|
|
244
|
+
|
|
245
|
+
for index, line in enumerate(content.splitlines(), start=1):
|
|
246
|
+
for match in LINK_RE.finditer(line):
|
|
247
|
+
destination = match.group("dest")
|
|
248
|
+
if _is_external(destination):
|
|
249
|
+
external += 1
|
|
250
|
+
continue
|
|
251
|
+
stripped = _strip_fragment(destination)
|
|
252
|
+
if stripped.startswith("/"):
|
|
253
|
+
if not docs_root_computed:
|
|
254
|
+
docs_root = _find_docs_content_root(path)
|
|
255
|
+
docs_root_computed = True
|
|
256
|
+
if docs_root is None:
|
|
257
|
+
# A Markdown body checked outside the site repo: the page
|
|
258
|
+
# set cannot be located, so ``/docs/`` routes are not
|
|
259
|
+
# checkable here. Counted like an external reference.
|
|
260
|
+
external += 1
|
|
261
|
+
continue
|
|
262
|
+
targets = _docs_route_targets(docs_root, stripped)
|
|
263
|
+
if targets is None:
|
|
264
|
+
# A non-``/docs/`` root-absolute route (site root, asset
|
|
265
|
+
# path, well-known file): route validity is the site
|
|
266
|
+
# build's concern, out of scope here.
|
|
267
|
+
external += 1
|
|
268
|
+
continue
|
|
269
|
+
internal += 1
|
|
270
|
+
if not any(target.exists() for target in targets):
|
|
271
|
+
findings.append(
|
|
272
|
+
Finding(
|
|
273
|
+
line=index,
|
|
274
|
+
destination=destination,
|
|
275
|
+
detail=(
|
|
276
|
+
"docs site route resolves to no page on disk "
|
|
277
|
+
f"(tried {_render_targets(docs_root, targets)})"
|
|
278
|
+
),
|
|
279
|
+
)
|
|
280
|
+
)
|
|
281
|
+
continue
|
|
282
|
+
internal += 1
|
|
283
|
+
resolved = _resolve_target(path, destination)
|
|
284
|
+
if resolved is None:
|
|
285
|
+
findings.append(
|
|
286
|
+
Finding(
|
|
287
|
+
line=index,
|
|
288
|
+
destination=destination,
|
|
289
|
+
detail="cannot resolve relative destination without source path",
|
|
290
|
+
)
|
|
291
|
+
)
|
|
292
|
+
continue
|
|
293
|
+
if not resolved.exists():
|
|
294
|
+
findings.append(
|
|
295
|
+
Finding(
|
|
296
|
+
line=index,
|
|
297
|
+
destination=destination,
|
|
298
|
+
detail=f"target does not exist on disk at {resolved}",
|
|
299
|
+
)
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
return GrepResult(
|
|
303
|
+
grep=GREP_NAME,
|
|
304
|
+
path=str(path) if path is not None else None,
|
|
305
|
+
passed=not findings,
|
|
306
|
+
internal_count=internal,
|
|
307
|
+
external_count=external,
|
|
308
|
+
findings=findings,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _read_input(argv: list[str]) -> tuple[str, Path | None]:
|
|
313
|
+
if len(argv) >= 2 and argv[1] != STDIN_FLAG:
|
|
314
|
+
path = Path(argv[1])
|
|
315
|
+
return path.read_text(encoding="utf-8"), path
|
|
316
|
+
return sys.stdin.read(), None
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _main(argv: list[str]) -> int:
|
|
320
|
+
content, path = _read_input(argv)
|
|
321
|
+
result = check(content, path)
|
|
322
|
+
print(result.to_json())
|
|
323
|
+
return EXIT_PASS if result.passed else EXIT_FAIL
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
if __name__ == "__main__":
|
|
327
|
+
sys.exit(_main(sys.argv))
|