@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,297 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Verify CI declares a cross-platform OS x Python matrix.
|
|
4
|
+
|
|
5
|
+
Why this enforcement exists. The cross-platform discipline ratified at
|
|
6
|
+
the supply-chain SOTA-rigor contract requires the canonical CI workflow at
|
|
7
|
+
``.github/workflows/ci-matrix.yml`` to declare a strategy matrix
|
|
8
|
+
spanning three operating systems (``ubuntu-latest``, ``macos-latest``,
|
|
9
|
+
``windows-latest``) and four Python versions (``3.10``, ``3.11``,
|
|
10
|
+
``3.12``, ``3.13``), yielding twelve matrix cells. The validator parses
|
|
11
|
+
the workflow YAML, walks every job's ``strategy.matrix`` block, and
|
|
12
|
+
reports drift on five named classes: ``workflow-absent`` (informational
|
|
13
|
+
pass when the workflow has not yet been materialised),
|
|
14
|
+
``strategy-matrix-absent``, ``os-missing``, ``python-version-missing``,
|
|
15
|
+
and ``matrix-cell-count-mismatch``.
|
|
16
|
+
|
|
17
|
+
Detection strategy. The validator searches ``<root>/.github/workflows/``
|
|
18
|
+
for files matching ``*ci-matrix*.yml``. If no such file exists, the
|
|
19
|
+
result carries ``not-yet-materialised: true`` and exits 0
|
|
20
|
+
(informational tolerance — the workflow may be authored in a later
|
|
21
|
+
phase). When the workflow exists, the validator parses it with
|
|
22
|
+
``yaml.safe_load`` (falling back to a permissive string-scan if PyYAML
|
|
23
|
+
is absent), enumerates every job's ``strategy.matrix``, and verifies
|
|
24
|
+
the required OS and Python axes plus the expected twelve-cell product.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import json
|
|
30
|
+
import re
|
|
31
|
+
import sys
|
|
32
|
+
from dataclasses import asdict, dataclass, field
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from typing import Any, Final
|
|
35
|
+
|
|
36
|
+
REQUIRED_OS: Final[tuple[str, ...]] = (
|
|
37
|
+
"ubuntu-latest",
|
|
38
|
+
"macos-latest",
|
|
39
|
+
"windows-latest",
|
|
40
|
+
)
|
|
41
|
+
REQUIRED_PYTHON: Final[tuple[str, ...]] = ("3.10", "3.11", "3.12", "3.13")
|
|
42
|
+
EXPECTED_CELL_COUNT: Final[int] = len(REQUIRED_OS) * len(REQUIRED_PYTHON)
|
|
43
|
+
|
|
44
|
+
GREP_NAME: Final[str] = "cross-platform-matrix-grep"
|
|
45
|
+
RULE_ANCHOR: Final[str] = "cross-platform OS x Python CI matrix"
|
|
46
|
+
EXIT_PASS: Final[int] = 0
|
|
47
|
+
EXIT_FAIL: Final[int] = 2
|
|
48
|
+
|
|
49
|
+
WORKFLOW_DIR: Final[str] = ".github/workflows"
|
|
50
|
+
WORKFLOW_GLOB: Final[str] = "*ci-matrix*.yml"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True)
|
|
54
|
+
class Finding:
|
|
55
|
+
drift_class: str
|
|
56
|
+
detail: str
|
|
57
|
+
rule: str = RULE_ANCHOR
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True)
|
|
61
|
+
class GrepResult:
|
|
62
|
+
grep: str
|
|
63
|
+
root: str
|
|
64
|
+
passed: bool
|
|
65
|
+
workflow_path: str | None = None
|
|
66
|
+
not_yet_materialised: bool = False
|
|
67
|
+
findings: list[Finding] = field(default_factory=list)
|
|
68
|
+
|
|
69
|
+
def to_json(self) -> str:
|
|
70
|
+
payload: dict[str, Any] = {
|
|
71
|
+
"grep": self.grep,
|
|
72
|
+
"root": self.root,
|
|
73
|
+
"passed": self.passed,
|
|
74
|
+
"workflow_path": self.workflow_path,
|
|
75
|
+
"not-yet-materialised": self.not_yet_materialised,
|
|
76
|
+
"findings": [asdict(f) for f in self.findings],
|
|
77
|
+
}
|
|
78
|
+
return json.dumps(payload, indent=2)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _find_workflow(root: Path) -> Path | None:
|
|
82
|
+
"""Locate the canonical CI-matrix workflow, if present."""
|
|
83
|
+
workflows = root / WORKFLOW_DIR
|
|
84
|
+
if not workflows.is_dir():
|
|
85
|
+
return None
|
|
86
|
+
# Prefer the exact canonical name; fall back to the glob match.
|
|
87
|
+
canonical = workflows / "ci-matrix.yml"
|
|
88
|
+
if canonical.exists():
|
|
89
|
+
return canonical
|
|
90
|
+
candidates = sorted(workflows.glob(WORKFLOW_GLOB))
|
|
91
|
+
return candidates[0] if candidates else None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _parse_yaml(content: str) -> dict[str, Any] | None:
|
|
95
|
+
"""Parse YAML via PyYAML; return None when PyYAML is unavailable."""
|
|
96
|
+
try:
|
|
97
|
+
import yaml
|
|
98
|
+
except ImportError:
|
|
99
|
+
return None
|
|
100
|
+
try:
|
|
101
|
+
loaded = yaml.safe_load(content)
|
|
102
|
+
except yaml.YAMLError:
|
|
103
|
+
return None
|
|
104
|
+
return loaded if isinstance(loaded, dict) else None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _normalize_versions(values: list[Any]) -> list[str]:
|
|
108
|
+
"""Coerce matrix python-version entries to strings (YAML may yield floats)."""
|
|
109
|
+
out: list[str] = []
|
|
110
|
+
for v in values:
|
|
111
|
+
if isinstance(v, str):
|
|
112
|
+
out.append(v)
|
|
113
|
+
elif isinstance(v, (int, float)):
|
|
114
|
+
out.append(str(v))
|
|
115
|
+
return out
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _collect_matrices(workflow: dict[str, Any]) -> list[dict[str, Any]]:
|
|
119
|
+
"""Walk every job's strategy.matrix block."""
|
|
120
|
+
matrices: list[dict[str, Any]] = []
|
|
121
|
+
jobs = workflow.get("jobs")
|
|
122
|
+
if not isinstance(jobs, dict):
|
|
123
|
+
return matrices
|
|
124
|
+
for job in jobs.values():
|
|
125
|
+
if not isinstance(job, dict):
|
|
126
|
+
continue
|
|
127
|
+
strategy = job.get("strategy")
|
|
128
|
+
if not isinstance(strategy, dict):
|
|
129
|
+
continue
|
|
130
|
+
matrix = strategy.get("matrix")
|
|
131
|
+
if isinstance(matrix, dict):
|
|
132
|
+
matrices.append(matrix)
|
|
133
|
+
return matrices
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _check_parsed(workflow: dict[str, Any], findings: list[Finding]) -> None:
|
|
137
|
+
"""Verify the parsed workflow declares the canonical 3x4 matrix."""
|
|
138
|
+
matrices = _collect_matrices(workflow)
|
|
139
|
+
if not matrices:
|
|
140
|
+
findings.append(
|
|
141
|
+
Finding(
|
|
142
|
+
drift_class="strategy-matrix-absent",
|
|
143
|
+
detail="no job declares a strategy.matrix block",
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
return
|
|
147
|
+
# Union axes across every job's matrix so a multi-job split still passes.
|
|
148
|
+
all_os: set[str] = set()
|
|
149
|
+
all_py: set[str] = set()
|
|
150
|
+
for matrix in matrices:
|
|
151
|
+
raw_os = matrix.get("os")
|
|
152
|
+
if isinstance(raw_os, list):
|
|
153
|
+
all_os.update(s for s in raw_os if isinstance(s, str))
|
|
154
|
+
raw_py = (
|
|
155
|
+
matrix.get("python-version")
|
|
156
|
+
or matrix.get("python_version")
|
|
157
|
+
or matrix.get("python")
|
|
158
|
+
)
|
|
159
|
+
if isinstance(raw_py, list):
|
|
160
|
+
all_py.update(_normalize_versions(raw_py))
|
|
161
|
+
missing_os = [o for o in REQUIRED_OS if o not in all_os]
|
|
162
|
+
if missing_os:
|
|
163
|
+
findings.append(
|
|
164
|
+
Finding(
|
|
165
|
+
drift_class="os-missing",
|
|
166
|
+
detail=f"OS axis missing: {', '.join(missing_os)}",
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
missing_py = [p for p in REQUIRED_PYTHON if p not in all_py]
|
|
170
|
+
if missing_py:
|
|
171
|
+
findings.append(
|
|
172
|
+
Finding(
|
|
173
|
+
drift_class="python-version-missing",
|
|
174
|
+
detail=f"Python-version axis missing: {', '.join(missing_py)}",
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
# Cell-count check uses the intersection of required-and-present axes
|
|
178
|
+
# to avoid double-reporting when an axis is already flagged missing.
|
|
179
|
+
present_os = [o for o in REQUIRED_OS if o in all_os]
|
|
180
|
+
present_py = [p for p in REQUIRED_PYTHON if p in all_py]
|
|
181
|
+
actual_cells = len(present_os) * len(present_py)
|
|
182
|
+
if actual_cells != EXPECTED_CELL_COUNT:
|
|
183
|
+
findings.append(
|
|
184
|
+
Finding(
|
|
185
|
+
drift_class="matrix-cell-count-mismatch",
|
|
186
|
+
detail=(
|
|
187
|
+
f"expected {EXPECTED_CELL_COUNT} cells "
|
|
188
|
+
f"(3 OS x 4 Python); actual {actual_cells}"
|
|
189
|
+
),
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
_OS_LINE_RE: Final[re.Pattern[str]] = re.compile(
|
|
195
|
+
r"(ubuntu-latest|macos-latest|windows-latest)"
|
|
196
|
+
)
|
|
197
|
+
_PY_LINE_RE: Final[re.Pattern[str]] = re.compile(
|
|
198
|
+
r'["\']?(3\.10|3\.11|3\.12|3\.13)["\']?'
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _check_string_fallback(content: str, findings: list[Finding]) -> None:
|
|
203
|
+
"""Permissive string-scan when PyYAML is unavailable.
|
|
204
|
+
|
|
205
|
+
Looks for a ``strategy:`` block plus a ``matrix:`` token. Verifies
|
|
206
|
+
OS and Python version names appear somewhere in the document. This
|
|
207
|
+
fallback is intentionally lenient — it cannot distinguish per-job
|
|
208
|
+
matrices and will under-report drift compared with YAML parsing.
|
|
209
|
+
"""
|
|
210
|
+
if "strategy:" not in content or "matrix:" not in content:
|
|
211
|
+
findings.append(
|
|
212
|
+
Finding(
|
|
213
|
+
drift_class="strategy-matrix-absent",
|
|
214
|
+
detail="no `strategy:` / `matrix:` tokens found in workflow",
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
return
|
|
218
|
+
os_hits = {m.group(1) for m in _OS_LINE_RE.finditer(content)}
|
|
219
|
+
py_hits = {m.group(1) for m in _PY_LINE_RE.finditer(content)}
|
|
220
|
+
missing_os = [o for o in REQUIRED_OS if o not in os_hits]
|
|
221
|
+
if missing_os:
|
|
222
|
+
findings.append(
|
|
223
|
+
Finding(
|
|
224
|
+
drift_class="os-missing",
|
|
225
|
+
detail=f"OS axis missing: {', '.join(missing_os)}",
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
missing_py = [p for p in REQUIRED_PYTHON if p not in py_hits]
|
|
229
|
+
if missing_py:
|
|
230
|
+
findings.append(
|
|
231
|
+
Finding(
|
|
232
|
+
drift_class="python-version-missing",
|
|
233
|
+
detail=f"Python-version axis missing: {', '.join(missing_py)}",
|
|
234
|
+
)
|
|
235
|
+
)
|
|
236
|
+
present_os = [o for o in REQUIRED_OS if o in os_hits]
|
|
237
|
+
present_py = [p for p in REQUIRED_PYTHON if p in py_hits]
|
|
238
|
+
actual_cells = len(present_os) * len(present_py)
|
|
239
|
+
if actual_cells != EXPECTED_CELL_COUNT:
|
|
240
|
+
findings.append(
|
|
241
|
+
Finding(
|
|
242
|
+
drift_class="matrix-cell-count-mismatch",
|
|
243
|
+
detail=(
|
|
244
|
+
f"expected {EXPECTED_CELL_COUNT} cells "
|
|
245
|
+
f"(3 OS x 4 Python); actual {actual_cells}"
|
|
246
|
+
),
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def check(root: Path) -> GrepResult:
|
|
252
|
+
"""Verify the CI-matrix workflow declares the canonical 3x4 matrix."""
|
|
253
|
+
workflow = _find_workflow(root)
|
|
254
|
+
if workflow is None:
|
|
255
|
+
# Workflow-absent is informational tolerance: the canonical
|
|
256
|
+
# ci-matrix.yml may not yet be materialised. Pass cleanly with
|
|
257
|
+
# the informational field set so callers can distinguish.
|
|
258
|
+
return GrepResult(
|
|
259
|
+
grep=GREP_NAME,
|
|
260
|
+
root=str(root),
|
|
261
|
+
passed=True,
|
|
262
|
+
workflow_path=None,
|
|
263
|
+
not_yet_materialised=True,
|
|
264
|
+
findings=[],
|
|
265
|
+
)
|
|
266
|
+
content = workflow.read_text(encoding="utf-8")
|
|
267
|
+
findings: list[Finding] = []
|
|
268
|
+
parsed = _parse_yaml(content)
|
|
269
|
+
if parsed is not None:
|
|
270
|
+
_check_parsed(parsed, findings)
|
|
271
|
+
else:
|
|
272
|
+
_check_string_fallback(content, findings)
|
|
273
|
+
return GrepResult(
|
|
274
|
+
grep=GREP_NAME,
|
|
275
|
+
root=str(root),
|
|
276
|
+
passed=not findings,
|
|
277
|
+
workflow_path=str(workflow),
|
|
278
|
+
not_yet_materialised=False,
|
|
279
|
+
findings=findings,
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _read_input(argv: list[str]) -> Path:
|
|
284
|
+
if len(argv) >= 2:
|
|
285
|
+
return Path(argv[1])
|
|
286
|
+
return Path.cwd()
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _main(argv: list[str]) -> int:
|
|
290
|
+
root = _read_input(argv)
|
|
291
|
+
result = check(root)
|
|
292
|
+
print(result.to_json())
|
|
293
|
+
return EXIT_PASS if result.passed else EXIT_FAIL
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
if __name__ == "__main__":
|
|
297
|
+
sys.exit(_main(sys.argv))
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Prove every command and skill surface has a deterministic output shape.
|
|
4
|
+
|
|
5
|
+
Why this enforcement exists. apothem renders option sets and terminal
|
|
6
|
+
next-step blocks with a strictly expected output structure: identical
|
|
7
|
+
inputs produce identically-shaped output, the recommended marker sits in
|
|
8
|
+
a fixed position, and every terminal surface closes with a named next
|
|
9
|
+
step. This harness is the executable proof of that determinism contract
|
|
10
|
+
(`rules/determinism.md`): it reduces each surface to a canonical
|
|
11
|
+
structural signature, recomputes the signature across repeated reads, and
|
|
12
|
+
asserts the signature is byte-stable. A surface whose signature drifts
|
|
13
|
+
between runs carries undeclared non-determinism; a surface that fails the
|
|
14
|
+
minimal output-shape floor is structurally incomplete. Both are findings.
|
|
15
|
+
|
|
16
|
+
The signature is the deterministic structural fingerprint of a surface —
|
|
17
|
+
its SPDX-header presence, frontmatter key set, ordered H2 heading
|
|
18
|
+
sequence, recommended-marker count, fenced-code language multiset,
|
|
19
|
+
bindings-section presence, and terminal next-step form. The fingerprint
|
|
20
|
+
is sensitive to any shape change but immune to cosmetic prose edits, so
|
|
21
|
+
it captures "shape" rather than "content".
|
|
22
|
+
|
|
23
|
+
Division of labour. The per-dimension semantics of the recommended
|
|
24
|
+
marker and the next-step block are owned by `option_annotation_grep` and
|
|
25
|
+
`recommend_next_step_grep` respectively; this harness owns the *composite*
|
|
26
|
+
shape's stability and the minimal completeness floor that makes a
|
|
27
|
+
perturbed surface fail. The overlap on next-step presence is deliberate
|
|
28
|
+
defense-in-depth and extends coverage to skill surfaces.
|
|
29
|
+
|
|
30
|
+
Detection strategy. The validator walks `src/apothem/commands/*.md`
|
|
31
|
+
(excluding `README.md`) and `src/apothem/skills/*/SKILL.md`. For each
|
|
32
|
+
surface it computes the structural signature three times from fresh reads
|
|
33
|
+
and compares; inequality is a non-determinism finding. It then checks the
|
|
34
|
+
output-shape floor: an SPDX header, at least one H2 heading, and a
|
|
35
|
+
terminal next-step heading (`## Recommended Next Step` or `## Next
|
|
36
|
+
Steps`). A missing element is an incomplete-shape finding.
|
|
37
|
+
|
|
38
|
+
Exit semantics. Exits 0 when every surface passes; exits 2 on any
|
|
39
|
+
finding. The exit-2 convention matches the conformity-gate orchestrator's
|
|
40
|
+
EXIT_FAIL constant.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
from __future__ import annotations
|
|
44
|
+
|
|
45
|
+
import hashlib
|
|
46
|
+
import json
|
|
47
|
+
import re
|
|
48
|
+
import sys
|
|
49
|
+
from dataclasses import asdict, dataclass, field
|
|
50
|
+
from pathlib import Path
|
|
51
|
+
from typing import Final
|
|
52
|
+
|
|
53
|
+
GREP_NAME: Final[str] = "determinism-grep"
|
|
54
|
+
RULE_ANCHOR: Final[str] = "rules/determinism.md"
|
|
55
|
+
|
|
56
|
+
EXIT_PASS: Final[int] = 0
|
|
57
|
+
EXIT_FAIL: Final[int] = 2
|
|
58
|
+
|
|
59
|
+
# Surface roots relative to the repository root supplied at invocation.
|
|
60
|
+
COMMANDS_DIR: Final[str] = "src/apothem/commands"
|
|
61
|
+
SKILLS_DIR: Final[str] = "src/apothem/skills"
|
|
62
|
+
|
|
63
|
+
# Filenames excluded from the command sweep — folder-companion docs, not
|
|
64
|
+
# commands: the human-facing directory index and the agent-facing companion.
|
|
65
|
+
EXCLUDED_FILES: Final[frozenset[str]] = frozenset({"README.md", "AGENTS.md"})
|
|
66
|
+
|
|
67
|
+
# Number of independent signature recomputations per surface. Identical
|
|
68
|
+
# inputs must yield identical signatures across every recomputation.
|
|
69
|
+
RECOMPUTE_RUNS: Final[int] = 3
|
|
70
|
+
|
|
71
|
+
_SPDX_RE: Final[re.Pattern[str]] = re.compile(r"SPDX-License-Identifier:\s*MIT")
|
|
72
|
+
_H2_RE: Final[re.Pattern[str]] = re.compile(r"^##\s+(?P<text>\S.*?)\s*$")
|
|
73
|
+
_FRONTMATTER_KEY_RE: Final[re.Pattern[str]] = re.compile(r"^(?P<key>[A-Za-z0-9_-]+):")
|
|
74
|
+
_FENCE_RE: Final[re.Pattern[str]] = re.compile(r"^```(?P<lang>[A-Za-z0-9_+-]*)")
|
|
75
|
+
_RECOMMENDED_MARKER_RE: Final[re.Pattern[str]] = re.compile(r"\*\*Recommended\*\*")
|
|
76
|
+
_RECOMMENDED_POSTFIX_RE: Final[re.Pattern[str]] = re.compile(r"\(Recommended\)")
|
|
77
|
+
|
|
78
|
+
_NEXTSTEP_SINGULAR_RE: Final[re.Pattern[str]] = re.compile(
|
|
79
|
+
r"^##\s+Recommended\s+Next\s+Step\s*$"
|
|
80
|
+
)
|
|
81
|
+
_NEXTSTEP_MULTI_RE: Final[re.Pattern[str]] = re.compile(r"^##\s+Next\s+Steps\s*$")
|
|
82
|
+
_BINDINGS_RE: Final[re.Pattern[str]] = re.compile(r"^##\s+Bindings\b")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(frozen=True)
|
|
86
|
+
class Finding:
|
|
87
|
+
"""One surface that violates the determinism / output-shape contract."""
|
|
88
|
+
|
|
89
|
+
surface: str
|
|
90
|
+
kind: str
|
|
91
|
+
detail: str
|
|
92
|
+
rule: str = RULE_ANCHOR
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass(frozen=True)
|
|
96
|
+
class GrepResult:
|
|
97
|
+
"""Aggregated sweep result across every command and skill surface."""
|
|
98
|
+
|
|
99
|
+
grep: str
|
|
100
|
+
root: str
|
|
101
|
+
files_inspected: int
|
|
102
|
+
passed: bool
|
|
103
|
+
findings: list[Finding] = field(default_factory=list)
|
|
104
|
+
|
|
105
|
+
def to_json(self) -> str:
|
|
106
|
+
payload = {
|
|
107
|
+
"grep": self.grep,
|
|
108
|
+
"root": self.root,
|
|
109
|
+
"files_inspected": self.files_inspected,
|
|
110
|
+
"passed": self.passed,
|
|
111
|
+
"findings": [asdict(f) for f in self.findings],
|
|
112
|
+
}
|
|
113
|
+
return json.dumps(payload, indent=2)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _frontmatter_keys(lines: list[str]) -> list[str]:
|
|
117
|
+
"""Return the sorted key set of a leading YAML frontmatter block.
|
|
118
|
+
|
|
119
|
+
The block is delimited by `---` on the first non-empty line and the
|
|
120
|
+
next `---`. Surfaces without frontmatter return an empty list.
|
|
121
|
+
"""
|
|
122
|
+
start = next((i for i, ln in enumerate(lines) if ln.strip()), None)
|
|
123
|
+
if start is None or lines[start].strip() != "---":
|
|
124
|
+
return []
|
|
125
|
+
keys: list[str] = []
|
|
126
|
+
for line in lines[start + 1 :]:
|
|
127
|
+
if line.strip() == "---":
|
|
128
|
+
break
|
|
129
|
+
match = _FRONTMATTER_KEY_RE.match(line)
|
|
130
|
+
if match:
|
|
131
|
+
keys.append(match.group("key"))
|
|
132
|
+
return sorted(keys)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _fenced_languages(lines: list[str]) -> list[str]:
|
|
136
|
+
"""Return the sorted multiset of fenced-code language tags.
|
|
137
|
+
|
|
138
|
+
Only opening fences are counted: every second fence in a file is a
|
|
139
|
+
close. Untagged fences contribute an empty-string entry.
|
|
140
|
+
"""
|
|
141
|
+
langs: list[str] = []
|
|
142
|
+
in_fence = False
|
|
143
|
+
for line in lines:
|
|
144
|
+
match = _FENCE_RE.match(line)
|
|
145
|
+
if match is None:
|
|
146
|
+
continue
|
|
147
|
+
if not in_fence:
|
|
148
|
+
langs.append(match.group("lang"))
|
|
149
|
+
in_fence = not in_fence
|
|
150
|
+
return sorted(langs)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _terminal_nextstep_form(lines: list[str]) -> str:
|
|
154
|
+
"""Return the terminal next-step form: 'singular', 'multi', or 'absent'."""
|
|
155
|
+
for line in lines:
|
|
156
|
+
if _NEXTSTEP_SINGULAR_RE.match(line):
|
|
157
|
+
return "singular"
|
|
158
|
+
if _NEXTSTEP_MULTI_RE.match(line):
|
|
159
|
+
return "multi"
|
|
160
|
+
return "absent"
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _structural_shape(body: str) -> dict[str, object]:
|
|
164
|
+
"""Reduce a surface to its canonical, order-stable structural shape."""
|
|
165
|
+
lines = body.splitlines()
|
|
166
|
+
h2_sequence = [m.group("text") for ln in lines if (m := _H2_RE.match(ln))]
|
|
167
|
+
return {
|
|
168
|
+
"spdx": bool(_SPDX_RE.search(body)),
|
|
169
|
+
"frontmatter_keys": _frontmatter_keys(lines),
|
|
170
|
+
"h2_sequence": h2_sequence,
|
|
171
|
+
"recommended_marker_count": len(_RECOMMENDED_MARKER_RE.findall(body)),
|
|
172
|
+
"recommended_postfix_count": len(_RECOMMENDED_POSTFIX_RE.findall(body)),
|
|
173
|
+
"fenced_languages": _fenced_languages(lines),
|
|
174
|
+
"bindings_present": any(_BINDINGS_RE.match(ln) for ln in lines),
|
|
175
|
+
"nextstep_form": _terminal_nextstep_form(lines),
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _signature(body: str) -> str:
|
|
180
|
+
"""Compute the deterministic structural fingerprint of a surface."""
|
|
181
|
+
canonical = json.dumps(_structural_shape(body), sort_keys=True)
|
|
182
|
+
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _classify_surface(path: Path) -> list[Finding]:
|
|
186
|
+
"""Return findings for one surface — non-determinism + shape floor."""
|
|
187
|
+
surface = str(path)
|
|
188
|
+
findings: list[Finding] = []
|
|
189
|
+
|
|
190
|
+
signatures: list[str] = []
|
|
191
|
+
bodies: list[str] = []
|
|
192
|
+
for _ in range(RECOMPUTE_RUNS):
|
|
193
|
+
try:
|
|
194
|
+
body = path.read_text(encoding="utf-8")
|
|
195
|
+
except (OSError, UnicodeDecodeError) as exc:
|
|
196
|
+
return [
|
|
197
|
+
Finding(
|
|
198
|
+
surface=surface,
|
|
199
|
+
kind="unreadable",
|
|
200
|
+
detail=f"could not read surface: {exc}",
|
|
201
|
+
)
|
|
202
|
+
]
|
|
203
|
+
bodies.append(body)
|
|
204
|
+
signatures.append(_signature(body))
|
|
205
|
+
|
|
206
|
+
if len(set(signatures)) != 1:
|
|
207
|
+
findings.append(
|
|
208
|
+
Finding(
|
|
209
|
+
surface=surface,
|
|
210
|
+
kind="non-deterministic-signature",
|
|
211
|
+
detail=(
|
|
212
|
+
f"structural signature drifted across {RECOMPUTE_RUNS} reads "
|
|
213
|
+
f"({sorted(set(signatures))}); identical input must yield an "
|
|
214
|
+
"identically-shaped output or declare its non-determinism source"
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
shape = _structural_shape(bodies[0])
|
|
220
|
+
if not shape["spdx"]:
|
|
221
|
+
findings.append(
|
|
222
|
+
Finding(
|
|
223
|
+
surface=surface,
|
|
224
|
+
kind="incomplete-shape",
|
|
225
|
+
detail="no SPDX-License-Identifier header found",
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
if not shape["h2_sequence"]:
|
|
229
|
+
findings.append(
|
|
230
|
+
Finding(
|
|
231
|
+
surface=surface,
|
|
232
|
+
kind="incomplete-shape",
|
|
233
|
+
detail="no H2 section heading found; the surface has no structure",
|
|
234
|
+
)
|
|
235
|
+
)
|
|
236
|
+
if shape["nextstep_form"] == "absent":
|
|
237
|
+
findings.append(
|
|
238
|
+
Finding(
|
|
239
|
+
surface=surface,
|
|
240
|
+
kind="incomplete-shape",
|
|
241
|
+
detail=(
|
|
242
|
+
"no terminal `## Recommended Next Step` or `## Next Steps` "
|
|
243
|
+
"heading; every terminal surface closes with a named next step"
|
|
244
|
+
),
|
|
245
|
+
)
|
|
246
|
+
)
|
|
247
|
+
return findings
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _iter_surfaces(root: Path) -> list[Path]:
|
|
251
|
+
"""Return the sorted command + skill surface set under root."""
|
|
252
|
+
surfaces: list[Path] = []
|
|
253
|
+
commands_dir = root / COMMANDS_DIR
|
|
254
|
+
if commands_dir.is_dir():
|
|
255
|
+
surfaces.extend(
|
|
256
|
+
p for p in commands_dir.glob("*.md") if p.name not in EXCLUDED_FILES
|
|
257
|
+
)
|
|
258
|
+
skills_dir = root / SKILLS_DIR
|
|
259
|
+
if skills_dir.is_dir():
|
|
260
|
+
surfaces.extend(skills_dir.glob("*/SKILL.md"))
|
|
261
|
+
return sorted(surfaces)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def check(root: Path) -> GrepResult:
|
|
265
|
+
"""Sweep every command and skill surface for a deterministic shape."""
|
|
266
|
+
findings: list[Finding] = []
|
|
267
|
+
surfaces = _iter_surfaces(root)
|
|
268
|
+
missing: list[str] = []
|
|
269
|
+
if not (root / COMMANDS_DIR).is_dir():
|
|
270
|
+
missing.append(COMMANDS_DIR)
|
|
271
|
+
if not (root / SKILLS_DIR).is_dir():
|
|
272
|
+
missing.append(SKILLS_DIR)
|
|
273
|
+
for rel in missing:
|
|
274
|
+
findings.append(
|
|
275
|
+
Finding(
|
|
276
|
+
surface=rel,
|
|
277
|
+
kind="missing-surface-root",
|
|
278
|
+
detail=f"surface directory absent at canonical path {root / rel}",
|
|
279
|
+
)
|
|
280
|
+
)
|
|
281
|
+
for path in surfaces:
|
|
282
|
+
findings.extend(_classify_surface(path))
|
|
283
|
+
return GrepResult(
|
|
284
|
+
grep=GREP_NAME,
|
|
285
|
+
root=str(root),
|
|
286
|
+
files_inspected=len(surfaces),
|
|
287
|
+
passed=not findings,
|
|
288
|
+
findings=findings,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _read_input(argv: list[str]) -> Path:
|
|
293
|
+
if len(argv) >= 2:
|
|
294
|
+
return Path(argv[1])
|
|
295
|
+
return Path.cwd()
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _main(argv: list[str]) -> int:
|
|
299
|
+
root = _read_input(argv)
|
|
300
|
+
result = check(root)
|
|
301
|
+
print(result.to_json())
|
|
302
|
+
return EXIT_PASS if result.passed else EXIT_FAIL
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
if __name__ == "__main__":
|
|
306
|
+
sys.exit(_main(sys.argv))
|