@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,224 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Validate filesystem paths against the canonical naming convention.
|
|
4
|
+
|
|
5
|
+
Why this enforcement exists. Spec section 4.1 ratifies kebab-case for
|
|
6
|
+
files and folders, with a closed set of canonical-uppercase exceptions
|
|
7
|
+
for ecosystem-wide singletons (CLAUDE.md, README.md, LICENSE, etc.) and
|
|
8
|
+
plan-suite singletons (MASTER-PLAN.md, PROGRESS.md, PHASE.md, SKILL.md,
|
|
9
|
+
VERSION). Numeric prefixes are admissible only inside ordered sequences
|
|
10
|
+
(phase folders `NN-topic/`, sub-phase folders `NNL-subtopic/`, migration
|
|
11
|
+
scripts where the host has ratified ordinal prefixes). The pre-emission
|
|
12
|
+
gate enforces the convention so namespace pollution cannot drift in
|
|
13
|
+
silently.
|
|
14
|
+
|
|
15
|
+
Detection strategy. Each input path is split into its file and parent
|
|
16
|
+
components; each component is matched against (a) the canonical-name
|
|
17
|
+
exception set, (b) the ordered-sequence prefix patterns, and (c) the
|
|
18
|
+
plain kebab-case shape. Components that match none of the three are
|
|
19
|
+
findings. Hidden directories (a leading dot) are exempt because dotfile
|
|
20
|
+
conventions are host-discovered, not ratified at user scope.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
import re
|
|
27
|
+
import sys
|
|
28
|
+
from dataclasses import asdict, dataclass, field
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Final
|
|
31
|
+
|
|
32
|
+
# Canonical-uppercase exception set per spec section 4.1. These names
|
|
33
|
+
# are recognized verbatim regardless of casing rules. The set is closed;
|
|
34
|
+
# new exceptions land via rule revision, never ad-hoc.
|
|
35
|
+
CANONICAL_NAMES: Final[frozenset[str]] = frozenset(
|
|
36
|
+
{
|
|
37
|
+
"CLAUDE.md",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE",
|
|
40
|
+
"LICENSE.md",
|
|
41
|
+
"LICENSE.txt",
|
|
42
|
+
"CHANGELOG.md",
|
|
43
|
+
"SECURITY.md",
|
|
44
|
+
"SUPPORT.md",
|
|
45
|
+
"CODE_OF_CONDUCT.md",
|
|
46
|
+
"CONTRIBUTING.md",
|
|
47
|
+
"NOTICE",
|
|
48
|
+
"NOTICE.md",
|
|
49
|
+
"MASTER-PLAN.md",
|
|
50
|
+
"PROGRESS.md",
|
|
51
|
+
"PHASE.md",
|
|
52
|
+
"SKILL.md",
|
|
53
|
+
"REPORT.md",
|
|
54
|
+
"PLAN-NOTES.md",
|
|
55
|
+
"PREAMBLE.md",
|
|
56
|
+
"COMPLETION.md",
|
|
57
|
+
"VERSION",
|
|
58
|
+
"MEMORY.md",
|
|
59
|
+
"site/content/docs/architecture/agents.mdx",
|
|
60
|
+
"Makefile",
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# kebab-case for the principal stem; an optional dotted suffix carries
|
|
65
|
+
# the host-natural extension list. The stem may carry digits but not
|
|
66
|
+
# uppercase letters, underscores, or spaces.
|
|
67
|
+
KEBAB_STEM_RE: Final[re.Pattern[str]] = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
|
|
68
|
+
|
|
69
|
+
# Phase-folder prefix `NN-topic` and sub-phase-folder prefix `NNL-topic`
|
|
70
|
+
# (digits + optional single uppercase letter) honor the ordering
|
|
71
|
+
# discipline at canonical-layout section 2.1. The suffix is kebab-case.
|
|
72
|
+
PHASE_PREFIX_RE: Final[re.Pattern[str]] = re.compile(
|
|
73
|
+
r"^[0-9]{2}[A-Z]?-[a-z0-9]+(?:-[a-z0-9]+)*$"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Migration-script prefix `NNNN_name` honors the host-ratified ordinal
|
|
77
|
+
# scheme common to migration tooling.
|
|
78
|
+
MIGRATION_PREFIX_RE: Final[re.Pattern[str]] = re.compile(
|
|
79
|
+
r"^[0-9]{2,5}_[a-z0-9]+(?:_[a-z0-9]+)*$"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# ADR filenames follow `NNNN-kebab-topic.md` per the ADR convention.
|
|
83
|
+
ADR_NAME_RE: Final[re.Pattern[str]] = re.compile(
|
|
84
|
+
r"^[0-9]{4}-[a-z0-9]+(?:-[a-z0-9]+)*\.md$"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
GREP_NAME: Final[str] = "naming-grep"
|
|
88
|
+
RULE_ANCHOR: Final[str] = "CLAUDE.md Coding Conventions (kebab-case naming)"
|
|
89
|
+
EXIT_PASS: Final[int] = 0
|
|
90
|
+
EXIT_FAIL: Final[int] = 2
|
|
91
|
+
STDIN_FLAG: Final[str] = "--stdin"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(frozen=True)
|
|
95
|
+
class Finding:
|
|
96
|
+
component: str
|
|
97
|
+
detail: str
|
|
98
|
+
rule: str = RULE_ANCHOR
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass(frozen=True)
|
|
102
|
+
class GrepResult:
|
|
103
|
+
grep: str
|
|
104
|
+
path: str | None
|
|
105
|
+
passed: bool
|
|
106
|
+
findings: list[Finding] = field(default_factory=list)
|
|
107
|
+
|
|
108
|
+
def to_json(self) -> str:
|
|
109
|
+
payload = {
|
|
110
|
+
"grep": self.grep,
|
|
111
|
+
"path": self.path,
|
|
112
|
+
"passed": self.passed,
|
|
113
|
+
"findings": [asdict(f) for f in self.findings],
|
|
114
|
+
}
|
|
115
|
+
return json.dumps(payload, indent=2)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _stem_and_extensions(name: str) -> tuple[str, str]:
|
|
119
|
+
"""Split a filename at the first dot; the suffix is the extension run."""
|
|
120
|
+
if "." not in name:
|
|
121
|
+
return name, ""
|
|
122
|
+
head, tail = name.split(".", 1)
|
|
123
|
+
return head, tail
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _is_canonical_name(name: str) -> bool:
|
|
127
|
+
return name in CANONICAL_NAMES
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _is_phase_prefix(name: str) -> bool:
|
|
131
|
+
return bool(PHASE_PREFIX_RE.match(name))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _is_migration_prefix(stem: str) -> bool:
|
|
135
|
+
return bool(MIGRATION_PREFIX_RE.match(stem))
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _is_adr_name(name: str) -> bool:
|
|
139
|
+
return bool(ADR_NAME_RE.match(name))
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _component_passes(name: str) -> tuple[bool, str]:
|
|
143
|
+
"""Return (passes, reason). reason explains the failure shape."""
|
|
144
|
+
if not name:
|
|
145
|
+
return True, ""
|
|
146
|
+
if name.startswith("."):
|
|
147
|
+
# Dotfiles and dot-dirs are host-discovered; do not enforce.
|
|
148
|
+
return True, ""
|
|
149
|
+
if _is_canonical_name(name):
|
|
150
|
+
return True, ""
|
|
151
|
+
# Phase-prefix shape (NN-topic / NNL-topic) is unambiguous; admit
|
|
152
|
+
# regardless of the directory-vs-file inference because a user-
|
|
153
|
+
# supplied path string carries no on-disk-stat hint.
|
|
154
|
+
if _is_phase_prefix(name):
|
|
155
|
+
return True, ""
|
|
156
|
+
if _is_adr_name(name):
|
|
157
|
+
return True, ""
|
|
158
|
+
stem, _ = _stem_and_extensions(name)
|
|
159
|
+
if _is_migration_prefix(stem):
|
|
160
|
+
return True, ""
|
|
161
|
+
if KEBAB_STEM_RE.match(stem):
|
|
162
|
+
return True, ""
|
|
163
|
+
return False, (
|
|
164
|
+
f"component {name!r} is neither kebab-case nor a canonical "
|
|
165
|
+
f"exception (spec section 4.1)"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def check(target: str, *, is_dir: bool | None = None) -> GrepResult:
|
|
170
|
+
"""Validate every path component against the naming convention.
|
|
171
|
+
|
|
172
|
+
``is_dir`` is retained for call-site signature compatibility; the
|
|
173
|
+
convention check is component-shape-only and does not branch on
|
|
174
|
+
whether the terminal component is a directory.
|
|
175
|
+
"""
|
|
176
|
+
del is_dir # accepted for signature compatibility; not consulted
|
|
177
|
+
findings: list[Finding] = []
|
|
178
|
+
parts = Path(target).parts
|
|
179
|
+
for component in parts:
|
|
180
|
+
# Drop drive anchors like 'C:', 'C:\\', or '/' on POSIX. The
|
|
181
|
+
# Windows form `Path('D:\\foo').parts[0]` returns the literal
|
|
182
|
+
# 'D:\\' (drive letter + colon + separator), not bare 'D:'.
|
|
183
|
+
if (
|
|
184
|
+
component.endswith(":")
|
|
185
|
+
or component in ("/", "\\")
|
|
186
|
+
or (len(component) >= 2 and component[1] == ":")
|
|
187
|
+
):
|
|
188
|
+
continue
|
|
189
|
+
passes, reason = _component_passes(component)
|
|
190
|
+
if not passes:
|
|
191
|
+
findings.append(Finding(component=component, detail=reason))
|
|
192
|
+
return GrepResult(
|
|
193
|
+
grep=GREP_NAME,
|
|
194
|
+
path=target,
|
|
195
|
+
passed=not findings,
|
|
196
|
+
findings=findings,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _read_input(argv: list[str]) -> str:
|
|
201
|
+
if len(argv) >= 2 and argv[1] != STDIN_FLAG:
|
|
202
|
+
return argv[1]
|
|
203
|
+
return sys.stdin.read().strip()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _main(argv: list[str]) -> int:
|
|
207
|
+
target = _read_input(argv)
|
|
208
|
+
# In the conformity gate's --all mode this validator is handed the
|
|
209
|
+
# absolute repository root. naming-grep validates the project's own
|
|
210
|
+
# artifact names, not the host filesystem ancestry above the repo
|
|
211
|
+
# root (`Users`, a home-directory name, etc.) — so an absolute
|
|
212
|
+
# directory input is reduced to its own basename. Relative inputs
|
|
213
|
+
# (the per-path invocation form) are validated component-by-component
|
|
214
|
+
# unchanged.
|
|
215
|
+
candidate = Path(target)
|
|
216
|
+
if candidate.is_absolute() and candidate.is_dir():
|
|
217
|
+
target = candidate.name
|
|
218
|
+
result = check(target)
|
|
219
|
+
print(result.to_json())
|
|
220
|
+
return EXIT_PASS if result.passed else EXIT_FAIL
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
if __name__ == "__main__":
|
|
224
|
+
sys.exit(_main(sys.argv))
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Walk the git index for tracked plans paths; fail when found.
|
|
4
|
+
|
|
5
|
+
Why this validator exists. The Plans Discipline at spec section 3
|
|
6
|
+
forbids planning artifacts at any global location and at the
|
|
7
|
+
ecosystem root. Plans live at the sole canonical
|
|
8
|
+
``<project-root>/.apothem/plans/`` tree and are gitignored — they
|
|
9
|
+
never enter a git history. A plans path (under ``.apothem/plans/`` or
|
|
10
|
+
a legacy ``.plans/``) appearing in ``git ls-files`` is direct evidence
|
|
11
|
+
that the discipline has lapsed: either the gitignore is missing the
|
|
12
|
+
pattern, the ``.gitignore`` has been bypassed via ``git add --force``,
|
|
13
|
+
or a nested project's plans directory has been tracked by mistake.
|
|
14
|
+
|
|
15
|
+
Why git-index, not filesystem. The recursion case at spec section 3
|
|
16
|
+
(the apothem source repository itself contains a plans tree while it is
|
|
17
|
+
being authored — and, for the Claude Code harness specifically, that
|
|
18
|
+
working tree lives under ``~/.claude/``) is resolved precisely because
|
|
19
|
+
the on-disk plans tree is gitignored. Walking the filesystem would flag
|
|
20
|
+
the in-flight suite as a violation; walking the git index returns clean
|
|
21
|
+
by construction. The validator's surface IS the git index — the
|
|
22
|
+
source-of-truth for "what ships with this repository."
|
|
23
|
+
|
|
24
|
+
Detection strategy. The validator runs ``git ls-files`` from the
|
|
25
|
+
root, filters the output for entries under any plans directory
|
|
26
|
+
(``.apothem/plans/`` or a legacy ``.plans/``, top-level or nested), and
|
|
27
|
+
excludes the documented anti-pattern references inside
|
|
28
|
+
``site/content/docs/reference/plans-discipline.mdx`` (the file is allow-
|
|
29
|
+
listed wholesale because the validator inspects path entries, not
|
|
30
|
+
file contents — and the file's path itself does not match the plans
|
|
31
|
+
pattern).
|
|
32
|
+
|
|
33
|
+
Filesystem stray-suite sweep (additive). The git-index check above is
|
|
34
|
+
blind to a plans directory that exists on disk but is untracked — a
|
|
35
|
+
stray suite materialized at a non-canonical location (a nested sub-tree,
|
|
36
|
+
a sibling tree, or a legacy ``<root>/.plans`` that has not yet been
|
|
37
|
+
upgraded) escapes ``git ls-files`` entirely because it is gitignored or
|
|
38
|
+
simply not yet added. The suite-locality invariant fixes exactly one
|
|
39
|
+
canonical plans tree per project: ``<root>/.apothem/plans/``. Any other
|
|
40
|
+
on-disk plans directory under the root — including a legacy
|
|
41
|
+
``<root>/.plans`` an operator upgrades via ``apothem migrate-workspace``
|
|
42
|
+
— is a stray suite and a Plans-Locality violation. The sweep walks the
|
|
43
|
+
filesystem (skipping the vendored / generated / VCS trees that carry
|
|
44
|
+
upstream conventions or machine state) and flags every plans directory
|
|
45
|
+
that is not the canonical ``<root>/.apothem/plans``. The two checks are
|
|
46
|
+
complementary: the git-index check catches a *tracked* plans path; the
|
|
47
|
+
filesystem sweep catches an *untracked stray* plans directory the index
|
|
48
|
+
check is blind to.
|
|
49
|
+
|
|
50
|
+
Exit semantics. Exits 0 when no findings; exits 2 on any finding.
|
|
51
|
+
The exit-2 convention matches the conformity-gate orchestrator's
|
|
52
|
+
EXIT_FAIL constant.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
from __future__ import annotations
|
|
56
|
+
|
|
57
|
+
import json
|
|
58
|
+
import re
|
|
59
|
+
import subprocess
|
|
60
|
+
import sys
|
|
61
|
+
from dataclasses import asdict, dataclass, field
|
|
62
|
+
from pathlib import Path
|
|
63
|
+
from typing import Final
|
|
64
|
+
|
|
65
|
+
GREP_NAME: Final[str] = "no-global-plans-grep"
|
|
66
|
+
RULE_ANCHOR: Final[str] = "CLAUDE.md Plans Discipline (no global plans)"
|
|
67
|
+
|
|
68
|
+
EXIT_PASS: Final[int] = 0
|
|
69
|
+
EXIT_FAIL: Final[int] = 2
|
|
70
|
+
|
|
71
|
+
# Pattern for any path containing a plans-suite directory segment. The sole
|
|
72
|
+
# canonical project-local plans location is `.apothem/plans/` (the shared
|
|
73
|
+
# Apothem working directory's plans child); the legacy `.plans/` layout is no
|
|
74
|
+
# longer canonical — operators upgrade an existing `.plans` tree via
|
|
75
|
+
# `apothem migrate-workspace`. Both layouts are gitignored, so a tracked path
|
|
76
|
+
# under EITHER is the same discipline lapse and is flagged for leak detection.
|
|
77
|
+
#
|
|
78
|
+
# Matches `.plans/foo`, `subdir/.plans/bar`, `.apothem/plans/foo`,
|
|
79
|
+
# `subdir/.apothem/plans/bar`, but does NOT match `plans-discipline.md` (no
|
|
80
|
+
# trailing slash), `my.plans/x.md` (no leading boundary), or a non-`plans`
|
|
81
|
+
# child of `.apothem/` such as `.apothem/memory/` (operator data, not a plan).
|
|
82
|
+
_PLANS_PATH_RE: Final[re.Pattern[str]] = re.compile(
|
|
83
|
+
r"(?:^|/)(?:\.plans|\.apothem/plans)/"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Directory names the filesystem stray-suite sweep does NOT descend into.
|
|
87
|
+
# Vendored / generated / VCS / cache trees carry upstream or machine state, not
|
|
88
|
+
# apothem-authored content; a `.plans/` directory inside any of them is not a
|
|
89
|
+
# stray apothem plan suite. Pruning them also keeps the walk fast.
|
|
90
|
+
_SWEEP_SKIP_DIRS: Final[frozenset[str]] = frozenset(
|
|
91
|
+
{
|
|
92
|
+
".git",
|
|
93
|
+
"_vendor",
|
|
94
|
+
"node_modules",
|
|
95
|
+
"dist",
|
|
96
|
+
"site/dist",
|
|
97
|
+
".mypy_cache",
|
|
98
|
+
".pytest_cache",
|
|
99
|
+
".ruff_cache",
|
|
100
|
+
".hypothesis",
|
|
101
|
+
"__pycache__",
|
|
102
|
+
".venv",
|
|
103
|
+
"venv",
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass(frozen=True)
|
|
109
|
+
class Finding:
|
|
110
|
+
"""One tracked-in-git path under a .plans/ directory."""
|
|
111
|
+
|
|
112
|
+
path: str
|
|
113
|
+
detail: str
|
|
114
|
+
rule: str = RULE_ANCHOR
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass(frozen=True)
|
|
118
|
+
class GrepResult:
|
|
119
|
+
"""Aggregated walk result for a single git-index + filesystem sweep."""
|
|
120
|
+
|
|
121
|
+
grep: str
|
|
122
|
+
root: str
|
|
123
|
+
tracked_count: int
|
|
124
|
+
passed: bool
|
|
125
|
+
stray_plans_count: int = 0
|
|
126
|
+
findings: list[Finding] = field(default_factory=list)
|
|
127
|
+
|
|
128
|
+
def to_json(self) -> str:
|
|
129
|
+
payload = {
|
|
130
|
+
"grep": self.grep,
|
|
131
|
+
"root": self.root,
|
|
132
|
+
"tracked_count": self.tracked_count,
|
|
133
|
+
"stray_plans_count": self.stray_plans_count,
|
|
134
|
+
"passed": self.passed,
|
|
135
|
+
"findings": [asdict(f) for f in self.findings],
|
|
136
|
+
}
|
|
137
|
+
return json.dumps(payload, indent=2)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _git_ls_files(root: Path) -> list[str]:
|
|
141
|
+
"""Return the git-tracked file list under root.
|
|
142
|
+
|
|
143
|
+
Raises ``RuntimeError`` when ``git`` is unavailable or root is not
|
|
144
|
+
a git repository — both are operator errors the caller surfaces.
|
|
145
|
+
"""
|
|
146
|
+
try:
|
|
147
|
+
completed = subprocess.run( # noqa: S603 — trusted invocation: literal argv against git
|
|
148
|
+
["git", "-C", str(root), "ls-files"], # noqa: S607 — PATH-resolved git is acceptable for a developer-tool validator; full-path resolution would require host-discovery and breaks portability across operator setups.
|
|
149
|
+
check=True,
|
|
150
|
+
capture_output=True,
|
|
151
|
+
text=True,
|
|
152
|
+
encoding="utf-8",
|
|
153
|
+
)
|
|
154
|
+
except FileNotFoundError as exc:
|
|
155
|
+
raise RuntimeError("git executable not found on PATH") from exc
|
|
156
|
+
except subprocess.CalledProcessError as exc:
|
|
157
|
+
stderr = (exc.stderr or "").strip()
|
|
158
|
+
raise RuntimeError(
|
|
159
|
+
f"git ls-files failed under {root!s}: {stderr or 'no stderr'}"
|
|
160
|
+
) from exc
|
|
161
|
+
return [line for line in completed.stdout.splitlines() if line]
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _sweep_stray_plans_dirs(
|
|
165
|
+
root: Path, tracked_plans_dirs: frozenset[str]
|
|
166
|
+
) -> list[Finding]:
|
|
167
|
+
"""Walk the filesystem under root; flag *untracked* stray plans dirs.
|
|
168
|
+
|
|
169
|
+
A stray suite is any on-disk plans directory that is not the sole canonical
|
|
170
|
+
project-local location ``<root>/.apothem/plans`` (the shared Apothem working
|
|
171
|
+
directory's plans child). Any ``.plans`` directory (including a legacy
|
|
172
|
+
``<root>/.plans`` — operators upgrade it via ``apothem migrate-workspace``),
|
|
173
|
+
or any ``.apothem/plans`` directory under a non-root ``.apothem`` (e.g. a
|
|
174
|
+
nested ``sub/.apothem/plans`` analogous to a global ``~/.apothem/plans``),
|
|
175
|
+
is a stray and a Plans-Locality violation.
|
|
176
|
+
|
|
177
|
+
The walk prunes the vendored / generated / VCS / cache trees in
|
|
178
|
+
``_SWEEP_SKIP_DIRS`` so a plans directory inside one of those (upstream or
|
|
179
|
+
machine state, never an apothem plan suite) is not flagged. It descends into
|
|
180
|
+
``.apothem`` directories (they hold operator memory/learning/contexts data
|
|
181
|
+
alongside plans) but never descends into a plans tree itself or into the
|
|
182
|
+
``.apothem`` data children — only the ``plans`` child of an ``.apothem``
|
|
183
|
+
directory is plans-relevant. The check is **additive and non-overlapping**
|
|
184
|
+
with the git-index check: a plans directory whose contents are already
|
|
185
|
+
git-tracked is reported there, so the sweep skips it via
|
|
186
|
+
``tracked_plans_dirs`` to avoid double-reporting. The sweep's unique value is
|
|
187
|
+
the *untracked* stray. Returns an empty list when the filesystem is
|
|
188
|
+
unreadable (the git-index check still carries the verdict).
|
|
189
|
+
"""
|
|
190
|
+
findings: list[Finding] = []
|
|
191
|
+
try:
|
|
192
|
+
resolved_root = root.resolve()
|
|
193
|
+
except (OSError, RuntimeError):
|
|
194
|
+
return findings
|
|
195
|
+
canonical_new = (resolved_root / ".apothem" / "plans").resolve()
|
|
196
|
+
canonical = {canonical_new}
|
|
197
|
+
|
|
198
|
+
def _record_stray(entry: Path, rel: str) -> None:
|
|
199
|
+
"""Append a stray finding for a plans directory unless it is canonical
|
|
200
|
+
or already covered by the git-index check."""
|
|
201
|
+
already_tracked = rel in tracked_plans_dirs
|
|
202
|
+
if entry.resolve() in canonical or already_tracked:
|
|
203
|
+
return
|
|
204
|
+
findings.append(
|
|
205
|
+
Finding(
|
|
206
|
+
path=f"{rel}/",
|
|
207
|
+
detail=(
|
|
208
|
+
"an untracked stray plans directory exists on disk outside "
|
|
209
|
+
"the sole canonical project-local <root>/.apothem/plans tree; "
|
|
210
|
+
"there is exactly one plans tree per project per the "
|
|
211
|
+
"suite-locality invariant — relocate the stray suite, or run "
|
|
212
|
+
"`apothem migrate-workspace` to upgrade a legacy <root>/.plans "
|
|
213
|
+
"tree"
|
|
214
|
+
),
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
stack: list[Path] = [resolved_root]
|
|
219
|
+
while stack:
|
|
220
|
+
current = stack.pop()
|
|
221
|
+
try:
|
|
222
|
+
entries = list(current.iterdir())
|
|
223
|
+
except (OSError, PermissionError):
|
|
224
|
+
continue
|
|
225
|
+
for entry in entries:
|
|
226
|
+
try:
|
|
227
|
+
is_dir = entry.is_dir()
|
|
228
|
+
except OSError:
|
|
229
|
+
continue
|
|
230
|
+
if not is_dir:
|
|
231
|
+
continue
|
|
232
|
+
name = entry.name
|
|
233
|
+
if name in _SWEEP_SKIP_DIRS:
|
|
234
|
+
continue
|
|
235
|
+
rel = entry.relative_to(resolved_root).as_posix()
|
|
236
|
+
if name == ".plans":
|
|
237
|
+
_record_stray(entry, rel)
|
|
238
|
+
# Do not descend into a .plans/ tree (it is suite content, not a
|
|
239
|
+
# place a nested .plans/ should appear).
|
|
240
|
+
continue
|
|
241
|
+
if name == ".apothem":
|
|
242
|
+
# An ``.apothem`` directory holds operator data
|
|
243
|
+
# (memory/learning/contexts) plus the plans child. Inspect only
|
|
244
|
+
# the ``plans`` child for stray-suite detection; descend no
|
|
245
|
+
# further into the data children.
|
|
246
|
+
plans_child = entry / "plans"
|
|
247
|
+
try:
|
|
248
|
+
if plans_child.is_dir():
|
|
249
|
+
_record_stray(plans_child, f"{rel}/plans")
|
|
250
|
+
except OSError:
|
|
251
|
+
pass
|
|
252
|
+
continue
|
|
253
|
+
stack.append(entry)
|
|
254
|
+
return findings
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _tracked_plans_dirs(tracked: list[str]) -> frozenset[str]:
|
|
258
|
+
"""Return the set of plans-segment directory paths the git index covers.
|
|
259
|
+
|
|
260
|
+
For each tracked path matching ``(.../)?(.plans|.apothem/plans)/...``,
|
|
261
|
+
extract the directory portion up to and including the plans segment (e.g.
|
|
262
|
+
``subdir/.plans/x.md`` -> ``subdir/.plans``; ``.plans/a.md`` -> ``.plans``;
|
|
263
|
+
``sub/.apothem/plans/x.md`` -> ``sub/.apothem/plans``;
|
|
264
|
+
``.apothem/plans/a.md`` -> ``.apothem/plans``). The sweep uses this set to
|
|
265
|
+
skip already-tracked plans directories so they are not double-counted across
|
|
266
|
+
the git-index check and the filesystem sweep.
|
|
267
|
+
"""
|
|
268
|
+
dirs: set[str] = set()
|
|
269
|
+
for path in tracked:
|
|
270
|
+
match = _PLANS_PATH_RE.search(path)
|
|
271
|
+
if not match:
|
|
272
|
+
continue
|
|
273
|
+
# The match spans the leading-boundary slash (when present) plus the
|
|
274
|
+
# plans segment and its trailing slash, e.g. "/.apothem/plans/" or
|
|
275
|
+
# "/.plans/". Trim the trailing slash to leave the directory path up to
|
|
276
|
+
# and including the plans segment.
|
|
277
|
+
end = match.end() - 1 # drop the trailing '/'
|
|
278
|
+
dir_path = path[:end]
|
|
279
|
+
# Strip a leading separator the boundary group may have consumed when the
|
|
280
|
+
# segment was not at the path start.
|
|
281
|
+
dirs.add(dir_path.lstrip("/") if match.start() != 0 else dir_path)
|
|
282
|
+
return frozenset(dirs)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def check(root: Path) -> GrepResult:
|
|
286
|
+
"""Walk the git index + filesystem under root; flag stray .plans/ paths.
|
|
287
|
+
|
|
288
|
+
Two complementary checks: (1) the git-index check flags any *tracked*
|
|
289
|
+
``.plans/`` or ``.apothem/plans/`` path (a planning artifact that has
|
|
290
|
+
entered git history); (2) the filesystem stray-suite sweep flags any
|
|
291
|
+
*untracked* on-disk plans directory that is not the sole canonical
|
|
292
|
+
``<root>/.apothem/plans`` (a stray suite the index check is blind to).
|
|
293
|
+
Either check's finding fails the validator.
|
|
294
|
+
"""
|
|
295
|
+
tracked = _git_ls_files(root)
|
|
296
|
+
findings: list[Finding] = []
|
|
297
|
+
for path in tracked:
|
|
298
|
+
if _PLANS_PATH_RE.search(path):
|
|
299
|
+
findings.append(
|
|
300
|
+
Finding(
|
|
301
|
+
path=path,
|
|
302
|
+
detail=(
|
|
303
|
+
"plans path is tracked in git; planning artifacts MUST "
|
|
304
|
+
"live in the gitignored project-local "
|
|
305
|
+
"<project-root>/.apothem/plans/ tree per spec section 3"
|
|
306
|
+
),
|
|
307
|
+
)
|
|
308
|
+
)
|
|
309
|
+
stray = _sweep_stray_plans_dirs(root, _tracked_plans_dirs(tracked))
|
|
310
|
+
findings.extend(stray)
|
|
311
|
+
return GrepResult(
|
|
312
|
+
grep=GREP_NAME,
|
|
313
|
+
root=str(root),
|
|
314
|
+
tracked_count=len(tracked),
|
|
315
|
+
stray_plans_count=len(stray),
|
|
316
|
+
passed=not findings,
|
|
317
|
+
findings=findings,
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _read_input(argv: list[str]) -> Path:
|
|
322
|
+
if len(argv) >= 2:
|
|
323
|
+
return Path(argv[1])
|
|
324
|
+
return Path.cwd()
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _main(argv: list[str]) -> int:
|
|
328
|
+
root = _read_input(argv)
|
|
329
|
+
try:
|
|
330
|
+
result = check(root)
|
|
331
|
+
except RuntimeError as exc:
|
|
332
|
+
sys.stderr.write(f"{GREP_NAME}: {exc}\n")
|
|
333
|
+
return EXIT_FAIL
|
|
334
|
+
print(result.to_json())
|
|
335
|
+
return EXIT_PASS if result.passed else EXIT_FAIL
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
if __name__ == "__main__":
|
|
339
|
+
sys.exit(_main(sys.argv))
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Fail when a top-level ``docs/`` directory exists at the repository root.
|
|
4
|
+
|
|
5
|
+
Why this validator exists. The documentation source of truth is the
|
|
6
|
+
Next.js + Fumadocs site under ``site/``; the rendered reference pages live
|
|
7
|
+
at ``site/content/docs/``. A bare ``docs/`` directory at the
|
|
8
|
+
repository root is a competing documentation source — a second place a
|
|
9
|
+
contributor (or a generator) might write or read docs from, splitting
|
|
10
|
+
the single-source-of-truth invariant. Once a root ``docs/`` exists,
|
|
11
|
+
links, build steps, and contributor habits accrete around it and the
|
|
12
|
+
two trees drift. The invariant is therefore structural and hard: there
|
|
13
|
+
is exactly one documentation tree (``site/``), and the repository root
|
|
14
|
+
never carries a ``docs/`` directory.
|
|
15
|
+
|
|
16
|
+
Scope of the violation. ONLY a directory named ``docs`` directly under
|
|
17
|
+
the repository root is forbidden. Nested ``docs/`` directories under
|
|
18
|
+
other trees (for example a vendored package's own ``docs/``), the
|
|
19
|
+
``site/`` tree, and ``src/`` are all fine — the validator inspects the
|
|
20
|
+
single root-level entry, not the whole tree.
|
|
21
|
+
|
|
22
|
+
Non-advisory. Unlike the advisory matchers, this is a hard structural
|
|
23
|
+
invariant: ``check()`` returns ``passed=False`` and the CLI exits 2 the
|
|
24
|
+
moment a root ``docs/`` directory is present, so a ``--strict`` CI step
|
|
25
|
+
blocks the change.
|
|
26
|
+
|
|
27
|
+
Exit semantics. Exits 0 when no findings; exits 2 on any finding,
|
|
28
|
+
matching the conformity-gate orchestrator's EXIT_FAIL constant.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import json
|
|
34
|
+
import sys
|
|
35
|
+
from dataclasses import asdict, dataclass, field
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
from typing import Final
|
|
38
|
+
|
|
39
|
+
GREP_NAME: Final[str] = "no-toplevel-docs-grep"
|
|
40
|
+
RULE_ANCHOR: Final[str] = "single-source-of-truth (docs live in site/, not root docs/)"
|
|
41
|
+
|
|
42
|
+
EXIT_PASS: Final[int] = 0
|
|
43
|
+
EXIT_FAIL: Final[int] = 2
|
|
44
|
+
|
|
45
|
+
# The forbidden directory name at the repository root.
|
|
46
|
+
_FORBIDDEN_DIR: Final[str] = "docs"
|
|
47
|
+
# The canonical documentation tree contributors must use instead.
|
|
48
|
+
_CANONICAL_DOCS_TREE: Final[str] = "site/content/docs/"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class Finding:
|
|
53
|
+
"""The single finding: a root-level ``docs/`` directory exists."""
|
|
54
|
+
|
|
55
|
+
path: str
|
|
56
|
+
detail: str
|
|
57
|
+
rule: str = RULE_ANCHOR
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True)
|
|
61
|
+
class GrepResult:
|
|
62
|
+
"""Aggregated result for a single root-level ``docs/`` probe."""
|
|
63
|
+
|
|
64
|
+
grep: str
|
|
65
|
+
root: str
|
|
66
|
+
passed: bool
|
|
67
|
+
advisory: bool = False
|
|
68
|
+
findings: list[Finding] = field(default_factory=list)
|
|
69
|
+
|
|
70
|
+
def to_json(self) -> str:
|
|
71
|
+
payload = {
|
|
72
|
+
"grep": self.grep,
|
|
73
|
+
"root": self.root,
|
|
74
|
+
"passed": self.passed,
|
|
75
|
+
"advisory": self.advisory,
|
|
76
|
+
"findings": [asdict(f) for f in self.findings],
|
|
77
|
+
}
|
|
78
|
+
return json.dumps(payload, indent=2)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def check(root: Path) -> GrepResult:
|
|
82
|
+
"""Flag a ``docs/`` directory living directly under *root*."""
|
|
83
|
+
candidate = root / _FORBIDDEN_DIR
|
|
84
|
+
findings: list[Finding] = []
|
|
85
|
+
if candidate.is_dir():
|
|
86
|
+
findings.append(
|
|
87
|
+
Finding(
|
|
88
|
+
path=f"{_FORBIDDEN_DIR}/",
|
|
89
|
+
detail=(
|
|
90
|
+
"a top-level docs/ directory is forbidden: the "
|
|
91
|
+
"documentation source of truth is site/ "
|
|
92
|
+
f"(rendered pages at {_CANONICAL_DOCS_TREE}); "
|
|
93
|
+
"remove the root docs/ tree and author documentation "
|
|
94
|
+
"under site/ instead"
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
return GrepResult(
|
|
99
|
+
grep=GREP_NAME,
|
|
100
|
+
root=str(root),
|
|
101
|
+
passed=not findings,
|
|
102
|
+
findings=findings,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _read_input(argv: list[str]) -> Path:
|
|
107
|
+
if len(argv) >= 2:
|
|
108
|
+
return Path(argv[1])
|
|
109
|
+
return Path.cwd()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _main(argv: list[str]) -> int:
|
|
113
|
+
root = _read_input(argv)
|
|
114
|
+
result = check(root)
|
|
115
|
+
print(result.to_json())
|
|
116
|
+
return EXIT_PASS if result.passed else EXIT_FAIL
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
if __name__ == "__main__":
|
|
120
|
+
sys.exit(_main(sys.argv))
|