@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,1125 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Detailed AI-conventions surface presence and coherence map.
|
|
4
|
+
|
|
5
|
+
Why this tool exists. The ecosystem ships its AI-assistant directive
|
|
6
|
+
prose across multiple surfaces — ``AGENTS.md`` (canonical project voice),
|
|
7
|
+
``CLAUDE.md`` (Claude Code mirror), ``.github/copilot-instructions.md``
|
|
8
|
+
(GitHub Copilot voice), and optional siblings
|
|
9
|
+
(``site/content/docs/architecture/agents.mdx``, ``.cursorrules``,
|
|
10
|
+
``.windsurfrules``) per the operator's opt-in. Each surface MUST carry
|
|
11
|
+
the same shared discipline claims (Plans Discipline, ambiguity
|
|
12
|
+
resolution, authorship header, naming, anti-patterns, modal hierarchy)
|
|
13
|
+
and SHOULD carry the canonical nine-section structure (Project Context,
|
|
14
|
+
Coding Conventions, File Headers, Plans Discipline, structured-inquiry Equivalent Behavior, Forbidden Patterns, Output Format, Review
|
|
15
|
+
Checklist, Pointers). The coarse pre-scan at the prior audit step
|
|
16
|
+
records bare presence; this deeper scan walks every present surface,
|
|
17
|
+
parses level-two headings, tests for the nine canonical sections by a
|
|
18
|
+
heading-and-body heuristic, computes the pairwise shared-section
|
|
19
|
+
coherence map across present surfaces, and emits a per-surface
|
|
20
|
+
authoring / refinement plan that the downstream Copilot-instructions
|
|
21
|
+
author, optional-surface generator, and instruction-surface refit consume.
|
|
22
|
+
|
|
23
|
+
What the tool captures. For every candidate surface in scope:
|
|
24
|
+
``path``, ``presence`` (``present`` / ``absent`` / ``partial``),
|
|
25
|
+
``sha256``, ``line-count``, parsed level-two headings, and a section-
|
|
26
|
+
presence map naming each canonical section's status (``present`` /
|
|
27
|
+
``absent`` / ``renamed-to:<heading-text>``). The coherence map carries
|
|
28
|
+
one entry per ordered pair of present surfaces; each entry records, for
|
|
29
|
+
every shared section (Plans Discipline, Structured Inquiry,
|
|
30
|
+
File Headers, Forbidden Patterns, Naming, Anti-Patterns, Modal
|
|
31
|
+
Hierarchy), one of four verdicts: ``coherent``, ``contradicts``,
|
|
32
|
+
``partial``, ``n/a``. The authoring / refinement plan is an array of
|
|
33
|
+
per-surface action items naming the missing-section template to
|
|
34
|
+
install, the present-but-drifted section delta to apply, or the
|
|
35
|
+
contradiction reconciliation path (default: ``AGENTS.md`` is the
|
|
36
|
+
canonical project voice; the divergent surface mirrors).
|
|
37
|
+
|
|
38
|
+
What the tool reports. ``ai-surfaces.json`` (machine-readable) and
|
|
39
|
+
``ai-surfaces.md`` (human-readable mirror) at ``.audit/``. The deeper
|
|
40
|
+
authoring passes consume the plan; the coherence-validator's fixture
|
|
41
|
+
template is informed by this map's structure.
|
|
42
|
+
|
|
43
|
+
Scope boundary. The tool ONLY reads the candidate surface files; it
|
|
44
|
+
NEVER writes to them. The semantic-equivalence test on shared sections
|
|
45
|
+
is heuristic (key-token overlap and claim-pattern matching); the
|
|
46
|
+
rigorous test lives at the multi-surface coherence validator and is
|
|
47
|
+
fixture-driven.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
from __future__ import annotations
|
|
51
|
+
|
|
52
|
+
import argparse
|
|
53
|
+
import hashlib
|
|
54
|
+
import json
|
|
55
|
+
import re
|
|
56
|
+
import sys
|
|
57
|
+
from dataclasses import asdict, dataclass
|
|
58
|
+
from datetime import datetime, timezone
|
|
59
|
+
from pathlib import Path
|
|
60
|
+
from typing import Final
|
|
61
|
+
|
|
62
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
63
|
+
|
|
64
|
+
from _scan_lib import load_inventory, read_text_safely
|
|
65
|
+
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
# Candidate surface catalog. Each entry names the canonical relative
|
|
68
|
+
# path, the surface's role in the ecosystem, and whether the surface is
|
|
69
|
+
# mandatory or opt-in. The order is the canonical reading order for
|
|
70
|
+
# every output table.
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
SURFACE_AGENTS_ROOT: Final[str] = "AGENTS.md"
|
|
73
|
+
SURFACE_CLAUDE_ROOT: Final[str] = "CLAUDE.md"
|
|
74
|
+
SURFACE_CLAUDE_NESTED: Final[str] = ".claude/CLAUDE.md"
|
|
75
|
+
SURFACE_COPILOT: Final[str] = ".github/copilot-instructions.md"
|
|
76
|
+
SURFACE_AGENTS_DOC: Final[str] = "site/content/docs/architecture/agents.mdx"
|
|
77
|
+
SURFACE_CURSOR: Final[str] = ".cursorrules"
|
|
78
|
+
SURFACE_WINDSURF: Final[str] = ".windsurfrules"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass(frozen=True)
|
|
82
|
+
class SurfaceDescriptor:
|
|
83
|
+
"""One candidate surface plus its mandatory / opt-in disposition."""
|
|
84
|
+
|
|
85
|
+
path: str
|
|
86
|
+
role: str
|
|
87
|
+
mandatory: bool
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
CANDIDATE_SURFACES: Final[tuple[SurfaceDescriptor, ...]] = (
|
|
91
|
+
SurfaceDescriptor(SURFACE_AGENTS_ROOT, "Canonical project instructions", True),
|
|
92
|
+
SurfaceDescriptor(SURFACE_CLAUDE_ROOT, "Claude Code mirror", True),
|
|
93
|
+
SurfaceDescriptor(SURFACE_CLAUDE_NESTED, "Claude (mirror layout)", False),
|
|
94
|
+
SurfaceDescriptor(SURFACE_COPILOT, "GitHub Copilot", True),
|
|
95
|
+
SurfaceDescriptor(SURFACE_AGENTS_DOC, "Multi-agent platforms", False),
|
|
96
|
+
SurfaceDescriptor(SURFACE_CURSOR, "Cursor", False),
|
|
97
|
+
SurfaceDescriptor(SURFACE_WINDSURF, "Windsurf", False),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# ---------------------------------------------------------------------------
|
|
102
|
+
# Canonical nine-section catalog. Each section carries:
|
|
103
|
+
# - ``slug`` — stable identifier used as the JSON key.
|
|
104
|
+
# - ``display`` — human-readable section title (matches the spec body).
|
|
105
|
+
# - ``heading_keywords`` — case-insensitive substrings any one of which,
|
|
106
|
+
# present in a level-two heading, qualifies the heading as the section.
|
|
107
|
+
# - ``body_signatures`` — substrings whose presence in the body of a
|
|
108
|
+
# heading signals the canonical content is there even when the
|
|
109
|
+
# heading text is renamed.
|
|
110
|
+
# - ``is_shared`` — whether the section participates in the cross-
|
|
111
|
+
# surface coherence contract.
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass(frozen=True)
|
|
116
|
+
class CanonicalSection:
|
|
117
|
+
slug: str
|
|
118
|
+
display: str
|
|
119
|
+
heading_keywords: tuple[str, ...]
|
|
120
|
+
body_signatures: tuple[str, ...]
|
|
121
|
+
is_shared: bool
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
CANONICAL_SECTIONS: Final[tuple[CanonicalSection, ...]] = (
|
|
125
|
+
CanonicalSection(
|
|
126
|
+
slug="project-context",
|
|
127
|
+
display="Project Context",
|
|
128
|
+
heading_keywords=(
|
|
129
|
+
"project context",
|
|
130
|
+
"context",
|
|
131
|
+
"about this",
|
|
132
|
+
"what this is",
|
|
133
|
+
"overview",
|
|
134
|
+
"introduction",
|
|
135
|
+
),
|
|
136
|
+
body_signatures=("repository", "ecosystem", "purpose", "scope"),
|
|
137
|
+
is_shared=False,
|
|
138
|
+
),
|
|
139
|
+
CanonicalSection(
|
|
140
|
+
slug="coding-conventions",
|
|
141
|
+
display="Coding Conventions",
|
|
142
|
+
heading_keywords=(
|
|
143
|
+
"coding conventions",
|
|
144
|
+
"conventions",
|
|
145
|
+
"code style",
|
|
146
|
+
"style",
|
|
147
|
+
"naming",
|
|
148
|
+
),
|
|
149
|
+
body_signatures=(
|
|
150
|
+
"kebab-case",
|
|
151
|
+
"snake_case",
|
|
152
|
+
"PascalCase",
|
|
153
|
+
"RFC 2119",
|
|
154
|
+
"MUST",
|
|
155
|
+
"modal",
|
|
156
|
+
),
|
|
157
|
+
is_shared=False,
|
|
158
|
+
),
|
|
159
|
+
CanonicalSection(
|
|
160
|
+
slug="file-headers",
|
|
161
|
+
display="File Headers",
|
|
162
|
+
heading_keywords=(
|
|
163
|
+
"file headers",
|
|
164
|
+
"file header",
|
|
165
|
+
"authorship header",
|
|
166
|
+
"authorship banner",
|
|
167
|
+
"header banner",
|
|
168
|
+
"banner",
|
|
169
|
+
),
|
|
170
|
+
body_signatures=(
|
|
171
|
+
"Copyright (c)",
|
|
172
|
+
"All rights reserved",
|
|
173
|
+
"inject-header",
|
|
174
|
+
"authorship-header",
|
|
175
|
+
"five canonical lines",
|
|
176
|
+
),
|
|
177
|
+
is_shared=True,
|
|
178
|
+
),
|
|
179
|
+
CanonicalSection(
|
|
180
|
+
slug="plans-discipline",
|
|
181
|
+
display="Plans Discipline",
|
|
182
|
+
heading_keywords=(
|
|
183
|
+
"plans discipline",
|
|
184
|
+
"plans-discipline",
|
|
185
|
+
"planning artifacts",
|
|
186
|
+
".plans/",
|
|
187
|
+
),
|
|
188
|
+
body_signatures=(
|
|
189
|
+
"<project-root>/.plans/",
|
|
190
|
+
"project-root/.plans",
|
|
191
|
+
"~/.claude/.plans/",
|
|
192
|
+
"no-global-plans",
|
|
193
|
+
".plans/ at the project root",
|
|
194
|
+
"ephemeral working memory",
|
|
195
|
+
),
|
|
196
|
+
is_shared=True,
|
|
197
|
+
),
|
|
198
|
+
CanonicalSection(
|
|
199
|
+
slug="Structured Inquiry",
|
|
200
|
+
display="Structured Inquiry Behavior",
|
|
201
|
+
heading_keywords=(
|
|
202
|
+
"structured inquiry",
|
|
203
|
+
"ask user",
|
|
204
|
+
"ambiguity",
|
|
205
|
+
"clarification",
|
|
206
|
+
"interactive question",
|
|
207
|
+
"structured inquiry",
|
|
208
|
+
"todo(clarify)",
|
|
209
|
+
),
|
|
210
|
+
body_signatures=(
|
|
211
|
+
"structured inquiry",
|
|
212
|
+
"TODO(clarify)",
|
|
213
|
+
"never invent",
|
|
214
|
+
"no silent",
|
|
215
|
+
"structured-inquiry",
|
|
216
|
+
"interactive-questions",
|
|
217
|
+
),
|
|
218
|
+
is_shared=True,
|
|
219
|
+
),
|
|
220
|
+
CanonicalSection(
|
|
221
|
+
slug="forbidden-patterns",
|
|
222
|
+
display="Forbidden Patterns",
|
|
223
|
+
heading_keywords=(
|
|
224
|
+
"forbidden patterns",
|
|
225
|
+
"forbidden",
|
|
226
|
+
"anti-patterns",
|
|
227
|
+
"anti patterns",
|
|
228
|
+
"do not",
|
|
229
|
+
"never do",
|
|
230
|
+
"prohibitions",
|
|
231
|
+
),
|
|
232
|
+
body_signatures=(
|
|
233
|
+
"no marketing",
|
|
234
|
+
"no hedging",
|
|
235
|
+
"console.log",
|
|
236
|
+
"absolute path",
|
|
237
|
+
"do not commit",
|
|
238
|
+
"never commit",
|
|
239
|
+
"MUST NOT",
|
|
240
|
+
),
|
|
241
|
+
is_shared=True,
|
|
242
|
+
),
|
|
243
|
+
CanonicalSection(
|
|
244
|
+
slug="output-format",
|
|
245
|
+
display="Output Format",
|
|
246
|
+
heading_keywords=(
|
|
247
|
+
"output format",
|
|
248
|
+
"output conventions",
|
|
249
|
+
"output discipline",
|
|
250
|
+
"response format",
|
|
251
|
+
"generation format",
|
|
252
|
+
),
|
|
253
|
+
body_signatures=(
|
|
254
|
+
"definitiveness",
|
|
255
|
+
"uniform sectioning",
|
|
256
|
+
"summary discipline",
|
|
257
|
+
"citations",
|
|
258
|
+
"typed",
|
|
259
|
+
"documented public surface",
|
|
260
|
+
),
|
|
261
|
+
is_shared=False,
|
|
262
|
+
),
|
|
263
|
+
CanonicalSection(
|
|
264
|
+
slug="review-checklist",
|
|
265
|
+
display="Review Checklist",
|
|
266
|
+
heading_keywords=(
|
|
267
|
+
"review checklist",
|
|
268
|
+
"checklist",
|
|
269
|
+
"before approving",
|
|
270
|
+
"review before",
|
|
271
|
+
"pr review",
|
|
272
|
+
"merge checklist",
|
|
273
|
+
),
|
|
274
|
+
body_signatures=(
|
|
275
|
+
"header present",
|
|
276
|
+
"naming compliant",
|
|
277
|
+
"no .plans/ writes",
|
|
278
|
+
"validators green",
|
|
279
|
+
"no contradictions",
|
|
280
|
+
),
|
|
281
|
+
is_shared=False,
|
|
282
|
+
),
|
|
283
|
+
CanonicalSection(
|
|
284
|
+
slug="pointers",
|
|
285
|
+
display="Pointers",
|
|
286
|
+
heading_keywords=(
|
|
287
|
+
"pointers",
|
|
288
|
+
"references",
|
|
289
|
+
"see also",
|
|
290
|
+
"links",
|
|
291
|
+
"further reading",
|
|
292
|
+
"registry",
|
|
293
|
+
"registries",
|
|
294
|
+
),
|
|
295
|
+
body_signatures=(
|
|
296
|
+
"site/content/docs/reference/plans-discipline",
|
|
297
|
+
"site/content/docs/reference/authorship-header",
|
|
298
|
+
"site/content/docs/reference/ai-conventions",
|
|
299
|
+
"CONTRIBUTING.md",
|
|
300
|
+
),
|
|
301
|
+
is_shared=False,
|
|
302
|
+
),
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
# Naming and Modal Hierarchy are sub-claims of Coding Conventions per
|
|
307
|
+
# the canonical nine-section list, but the coherence contract enumerates
|
|
308
|
+
# them as discrete shared sections. Each carries its own keyword and
|
|
309
|
+
# body signature so the cross-surface diff can attribute a contradiction
|
|
310
|
+
# to the right axis.
|
|
311
|
+
EXTRA_SHARED_SECTIONS: Final[tuple[CanonicalSection, ...]] = (
|
|
312
|
+
CanonicalSection(
|
|
313
|
+
slug="naming",
|
|
314
|
+
display="Naming",
|
|
315
|
+
heading_keywords=(
|
|
316
|
+
"naming",
|
|
317
|
+
"file naming",
|
|
318
|
+
"directory naming",
|
|
319
|
+
"kebab-case",
|
|
320
|
+
),
|
|
321
|
+
body_signatures=(
|
|
322
|
+
"kebab-case",
|
|
323
|
+
"snake_case",
|
|
324
|
+
"PascalCase",
|
|
325
|
+
"UPPER_SNAKE_CASE",
|
|
326
|
+
),
|
|
327
|
+
is_shared=True,
|
|
328
|
+
),
|
|
329
|
+
CanonicalSection(
|
|
330
|
+
slug="modal-hierarchy",
|
|
331
|
+
display="Modal Hierarchy",
|
|
332
|
+
heading_keywords=(
|
|
333
|
+
"modal hierarchy",
|
|
334
|
+
"modal",
|
|
335
|
+
"RFC 2119",
|
|
336
|
+
"RFC2119",
|
|
337
|
+
"MUST / SHOULD",
|
|
338
|
+
),
|
|
339
|
+
body_signatures=(
|
|
340
|
+
"RFC 2119",
|
|
341
|
+
"MUST",
|
|
342
|
+
"MUST NOT",
|
|
343
|
+
"SHOULD",
|
|
344
|
+
"SHOULD NOT",
|
|
345
|
+
"MAY",
|
|
346
|
+
),
|
|
347
|
+
is_shared=True,
|
|
348
|
+
),
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
ALL_SECTIONS_FOR_PRESENCE: Final[tuple[CanonicalSection, ...]] = CANONICAL_SECTIONS
|
|
352
|
+
|
|
353
|
+
SHARED_SECTIONS_FOR_COHERENCE: Final[tuple[CanonicalSection, ...]] = (
|
|
354
|
+
*(s for s in CANONICAL_SECTIONS if s.is_shared),
|
|
355
|
+
*EXTRA_SHARED_SECTIONS,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
# ---------------------------------------------------------------------------
|
|
360
|
+
# Coherence verdict taxonomy.
|
|
361
|
+
# ---------------------------------------------------------------------------
|
|
362
|
+
COHERENCE_COHERENT: Final[str] = "coherent"
|
|
363
|
+
COHERENCE_CONTRADICTS: Final[str] = "contradicts"
|
|
364
|
+
COHERENCE_PARTIAL: Final[str] = "partial"
|
|
365
|
+
COHERENCE_NOT_APPLICABLE: Final[str] = "n/a"
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
# Section-presence verdict prefixes.
|
|
369
|
+
PRESENCE_PRESENT: Final[str] = "present"
|
|
370
|
+
PRESENCE_ABSENT: Final[str] = "absent"
|
|
371
|
+
PRESENCE_RENAMED_PREFIX: Final[str] = "renamed-to:"
|
|
372
|
+
|
|
373
|
+
# Surface-presence (top-level) verdicts.
|
|
374
|
+
SURFACE_PRESENT: Final[str] = "present"
|
|
375
|
+
SURFACE_ABSENT: Final[str] = "absent"
|
|
376
|
+
SURFACE_PARTIAL: Final[str] = "partial"
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
# ---------------------------------------------------------------------------
|
|
380
|
+
# Heading parsing. We extract every level-two heading and the body block
|
|
381
|
+
# that follows up to the next level-two heading.
|
|
382
|
+
# ---------------------------------------------------------------------------
|
|
383
|
+
HEADING_RE: Final[re.Pattern[str]] = re.compile(
|
|
384
|
+
r"^(?P<hashes>#{1,6})\s+(?P<text>.+?)\s*$",
|
|
385
|
+
re.MULTILINE,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
@dataclass(frozen=True)
|
|
390
|
+
class HeadingBlock:
|
|
391
|
+
level: int
|
|
392
|
+
text: str
|
|
393
|
+
line_start: int
|
|
394
|
+
line_end: int
|
|
395
|
+
body: str
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def parse_headings(content: str) -> list[HeadingBlock]:
|
|
399
|
+
"""Return every heading paired with its body block.
|
|
400
|
+
|
|
401
|
+
A heading's body extends from the line after the heading up to the
|
|
402
|
+
line before the next heading at the same or shallower depth. We use
|
|
403
|
+
a flat slice and the parser keeps every heading level so the
|
|
404
|
+
section-presence heuristic can pick the most specific match.
|
|
405
|
+
"""
|
|
406
|
+
lines = content.splitlines()
|
|
407
|
+
matches: list[tuple[int, int, str]] = []
|
|
408
|
+
for index, line in enumerate(lines):
|
|
409
|
+
match = HEADING_RE.match(line)
|
|
410
|
+
if match is None:
|
|
411
|
+
continue
|
|
412
|
+
level = len(match.group("hashes"))
|
|
413
|
+
text = match.group("text").strip()
|
|
414
|
+
matches.append((index, level, text))
|
|
415
|
+
blocks: list[HeadingBlock] = []
|
|
416
|
+
for position, (line_index, level, text) in enumerate(matches):
|
|
417
|
+
start = line_index + 1
|
|
418
|
+
if position + 1 < len(matches):
|
|
419
|
+
next_line, _next_level, _ = matches[position + 1]
|
|
420
|
+
end = next_line
|
|
421
|
+
else:
|
|
422
|
+
end = len(lines)
|
|
423
|
+
body = "\n".join(lines[start:end])
|
|
424
|
+
blocks.append(
|
|
425
|
+
HeadingBlock(
|
|
426
|
+
level=level,
|
|
427
|
+
text=text,
|
|
428
|
+
line_start=line_index + 1,
|
|
429
|
+
line_end=end,
|
|
430
|
+
body=body,
|
|
431
|
+
)
|
|
432
|
+
)
|
|
433
|
+
return blocks
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def heading_text_matches(text: str, keywords: tuple[str, ...]) -> bool:
|
|
437
|
+
"""Case-insensitive substring test against any one keyword."""
|
|
438
|
+
lowered = text.lower()
|
|
439
|
+
return any(keyword.lower() in lowered for keyword in keywords)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def body_signature_count(body: str, signatures: tuple[str, ...]) -> int:
|
|
443
|
+
"""Count the distinct signature substrings present in a body block.
|
|
444
|
+
|
|
445
|
+
Case-sensitive — banner text and modal verbs are case-meaningful.
|
|
446
|
+
"""
|
|
447
|
+
return sum(1 for signature in signatures if signature in body)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def detect_section_presence(
|
|
451
|
+
section: CanonicalSection,
|
|
452
|
+
headings: list[HeadingBlock],
|
|
453
|
+
full_content: str,
|
|
454
|
+
) -> str:
|
|
455
|
+
"""Return ``present`` / ``absent`` / ``renamed-to:<text>``.
|
|
456
|
+
|
|
457
|
+
Order of preference:
|
|
458
|
+
1. A heading whose text matches the section's keywords AND whose
|
|
459
|
+
body carries at least one signature → ``present``.
|
|
460
|
+
2. A heading whose text matches the keywords but whose body lacks a
|
|
461
|
+
signature → still ``present`` (the heading is the canonical
|
|
462
|
+
claim; signature absence may indicate an under-fleshed section
|
|
463
|
+
but not a renamed section).
|
|
464
|
+
3. No heading-text match, but a heading whose body carries at least
|
|
465
|
+
one signature → ``renamed-to:<heading-text>``. The first such
|
|
466
|
+
heading by document order wins.
|
|
467
|
+
4. No heading-text match and no body-signature match anywhere →
|
|
468
|
+
``absent``.
|
|
469
|
+
"""
|
|
470
|
+
for heading in headings:
|
|
471
|
+
if heading_text_matches(heading.text, section.heading_keywords):
|
|
472
|
+
return PRESENCE_PRESENT
|
|
473
|
+
for heading in headings:
|
|
474
|
+
if body_signature_count(heading.body, section.body_signatures) > 0:
|
|
475
|
+
return f"{PRESENCE_RENAMED_PREFIX}{heading.text}"
|
|
476
|
+
if body_signature_count(full_content, section.body_signatures) > 0:
|
|
477
|
+
return f"{PRESENCE_RENAMED_PREFIX}<no-heading-found>"
|
|
478
|
+
return PRESENCE_ABSENT
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
# ---------------------------------------------------------------------------
|
|
482
|
+
# Per-surface scan.
|
|
483
|
+
# ---------------------------------------------------------------------------
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
@dataclass
|
|
487
|
+
class SurfaceScan:
|
|
488
|
+
descriptor: SurfaceDescriptor
|
|
489
|
+
presence: str
|
|
490
|
+
sha256: str | None
|
|
491
|
+
line_count: int
|
|
492
|
+
headings: list[HeadingBlock]
|
|
493
|
+
section_presence: dict[str, str]
|
|
494
|
+
raw_content: str
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def scan_surface(descriptor: SurfaceDescriptor, root: Path) -> SurfaceScan:
|
|
498
|
+
target = root / descriptor.path
|
|
499
|
+
if not target.exists() or not target.is_file():
|
|
500
|
+
return SurfaceScan(
|
|
501
|
+
descriptor=descriptor,
|
|
502
|
+
presence=SURFACE_ABSENT,
|
|
503
|
+
sha256=None,
|
|
504
|
+
line_count=0,
|
|
505
|
+
headings=[],
|
|
506
|
+
section_presence={
|
|
507
|
+
s.slug: PRESENCE_ABSENT for s in ALL_SECTIONS_FOR_PRESENCE
|
|
508
|
+
},
|
|
509
|
+
raw_content="",
|
|
510
|
+
)
|
|
511
|
+
content = read_text_safely(target)
|
|
512
|
+
if not content:
|
|
513
|
+
return SurfaceScan(
|
|
514
|
+
descriptor=descriptor,
|
|
515
|
+
presence=SURFACE_PARTIAL,
|
|
516
|
+
sha256=None,
|
|
517
|
+
line_count=0,
|
|
518
|
+
headings=[],
|
|
519
|
+
section_presence={
|
|
520
|
+
s.slug: PRESENCE_ABSENT for s in ALL_SECTIONS_FOR_PRESENCE
|
|
521
|
+
},
|
|
522
|
+
raw_content="",
|
|
523
|
+
)
|
|
524
|
+
raw = target.read_bytes()
|
|
525
|
+
sha = hashlib.sha256(raw).hexdigest()
|
|
526
|
+
headings = parse_headings(content)
|
|
527
|
+
line_count = len(content.splitlines())
|
|
528
|
+
presence_map: dict[str, str] = {}
|
|
529
|
+
for section in ALL_SECTIONS_FOR_PRESENCE:
|
|
530
|
+
presence_map[section.slug] = detect_section_presence(section, headings, content)
|
|
531
|
+
return SurfaceScan(
|
|
532
|
+
descriptor=descriptor,
|
|
533
|
+
presence=SURFACE_PRESENT,
|
|
534
|
+
sha256=sha,
|
|
535
|
+
line_count=line_count,
|
|
536
|
+
headings=headings,
|
|
537
|
+
section_presence=presence_map,
|
|
538
|
+
raw_content=content,
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
# ---------------------------------------------------------------------------
|
|
543
|
+
# Coherence map. For every ordered pair of present surfaces and every
|
|
544
|
+
# shared section we record one verdict.
|
|
545
|
+
# ---------------------------------------------------------------------------
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
@dataclass(frozen=True)
|
|
549
|
+
class CoherenceVerdict:
|
|
550
|
+
section_slug: str
|
|
551
|
+
section_display: str
|
|
552
|
+
verdict: str
|
|
553
|
+
rationale: str
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def section_present_in_scan(scan: SurfaceScan, slug: str) -> bool:
|
|
557
|
+
"""True when the section is present (either canonical or renamed)."""
|
|
558
|
+
status = scan.section_presence.get(slug, PRESENCE_ABSENT)
|
|
559
|
+
return status != PRESENCE_ABSENT
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def shared_claim_overlap(
|
|
563
|
+
section: CanonicalSection,
|
|
564
|
+
scan_a: SurfaceScan,
|
|
565
|
+
scan_b: SurfaceScan,
|
|
566
|
+
) -> tuple[int, int, int]:
|
|
567
|
+
"""Return ``(both, only_a, only_b)`` body-signature counts.
|
|
568
|
+
|
|
569
|
+
A shared signature appearing in both surfaces' bodies counts toward
|
|
570
|
+
``both``; a signature appearing in only one counts toward the
|
|
571
|
+
asymmetric bucket. The triple drives the verdict heuristic.
|
|
572
|
+
"""
|
|
573
|
+
both = 0
|
|
574
|
+
only_a = 0
|
|
575
|
+
only_b = 0
|
|
576
|
+
for signature in section.body_signatures:
|
|
577
|
+
in_a = signature in scan_a.raw_content
|
|
578
|
+
in_b = signature in scan_b.raw_content
|
|
579
|
+
if in_a and in_b:
|
|
580
|
+
both += 1
|
|
581
|
+
elif in_a:
|
|
582
|
+
only_a += 1
|
|
583
|
+
elif in_b:
|
|
584
|
+
only_b += 1
|
|
585
|
+
return both, only_a, only_b
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def coherence_verdict_for(
|
|
589
|
+
section: CanonicalSection,
|
|
590
|
+
scan_a: SurfaceScan,
|
|
591
|
+
scan_b: SurfaceScan,
|
|
592
|
+
) -> CoherenceVerdict:
|
|
593
|
+
if not section_present_in_scan(scan_a, section.slug) or not section_present_in_scan(
|
|
594
|
+
scan_b, section.slug
|
|
595
|
+
):
|
|
596
|
+
return CoherenceVerdict(
|
|
597
|
+
section_slug=section.slug,
|
|
598
|
+
section_display=section.display,
|
|
599
|
+
verdict=COHERENCE_NOT_APPLICABLE,
|
|
600
|
+
rationale=(
|
|
601
|
+
"At least one surface lacks the section; coherence is undefined "
|
|
602
|
+
"until both surfaces author the section."
|
|
603
|
+
),
|
|
604
|
+
)
|
|
605
|
+
both, only_a, only_b = shared_claim_overlap(section, scan_a, scan_b)
|
|
606
|
+
total_signatures = len(section.body_signatures)
|
|
607
|
+
if both == 0 and only_a == 0 and only_b == 0:
|
|
608
|
+
return CoherenceVerdict(
|
|
609
|
+
section_slug=section.slug,
|
|
610
|
+
section_display=section.display,
|
|
611
|
+
verdict=COHERENCE_PARTIAL,
|
|
612
|
+
rationale=(
|
|
613
|
+
"Both surfaces carry the section heading but neither body shows "
|
|
614
|
+
"the canonical claim signatures; the rigorous coherence test "
|
|
615
|
+
"lives at the multi-surface coherence validator."
|
|
616
|
+
),
|
|
617
|
+
)
|
|
618
|
+
if both > 0 and only_a == 0 and only_b == 0:
|
|
619
|
+
return CoherenceVerdict(
|
|
620
|
+
section_slug=section.slug,
|
|
621
|
+
section_display=section.display,
|
|
622
|
+
verdict=COHERENCE_COHERENT,
|
|
623
|
+
rationale=(
|
|
624
|
+
f"Both surfaces share {both} of {total_signatures} canonical "
|
|
625
|
+
"claim signatures; no asymmetric claim was detected."
|
|
626
|
+
),
|
|
627
|
+
)
|
|
628
|
+
if (only_a > 0 and only_b == 0) or (only_a == 0 and only_b > 0):
|
|
629
|
+
return CoherenceVerdict(
|
|
630
|
+
section_slug=section.slug,
|
|
631
|
+
section_display=section.display,
|
|
632
|
+
verdict=COHERENCE_PARTIAL,
|
|
633
|
+
rationale=(
|
|
634
|
+
f"Shared claims: {both}; asymmetric claims: {only_a + only_b}. "
|
|
635
|
+
"One surface carries claims absent from the other; reconcile "
|
|
636
|
+
"by mirroring the canonical project voice."
|
|
637
|
+
),
|
|
638
|
+
)
|
|
639
|
+
# Both surfaces carry asymmetric claims — the heuristic flags this
|
|
640
|
+
# as a candidate contradiction; the validator's fixture confirms.
|
|
641
|
+
return CoherenceVerdict(
|
|
642
|
+
section_slug=section.slug,
|
|
643
|
+
section_display=section.display,
|
|
644
|
+
verdict=COHERENCE_CONTRADICTS,
|
|
645
|
+
rationale=(
|
|
646
|
+
f"Each surface carries asymmetric claims (only-A={only_a}, "
|
|
647
|
+
f"only-B={only_b}, shared={both}); the heuristic flags a candidate "
|
|
648
|
+
"contradiction for the validator's fixture-driven confirmation."
|
|
649
|
+
),
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def build_coherence_map(scans: list[SurfaceScan]) -> dict[str, list[dict[str, str]]]:
|
|
654
|
+
"""Return the pairwise coherence map keyed by ``A__vs__B``."""
|
|
655
|
+
present_scans = [s for s in scans if s.presence == SURFACE_PRESENT]
|
|
656
|
+
coherence: dict[str, list[dict[str, str]]] = {}
|
|
657
|
+
for index_a, scan_a in enumerate(present_scans):
|
|
658
|
+
for scan_b in present_scans[index_a + 1 :]:
|
|
659
|
+
pair_key = f"{scan_a.descriptor.path}__vs__{scan_b.descriptor.path}"
|
|
660
|
+
verdicts: list[dict[str, str]] = []
|
|
661
|
+
for section in SHARED_SECTIONS_FOR_COHERENCE:
|
|
662
|
+
verdict = coherence_verdict_for(section, scan_a, scan_b)
|
|
663
|
+
verdicts.append(asdict(verdict))
|
|
664
|
+
coherence[pair_key] = verdicts
|
|
665
|
+
return coherence
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
# ---------------------------------------------------------------------------
|
|
669
|
+
# Authoring / refinement plan. One action item per surface; items name
|
|
670
|
+
# the missing-section template, the present-but-drifted delta, or the
|
|
671
|
+
# contradiction reconciliation path.
|
|
672
|
+
# ---------------------------------------------------------------------------
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
@dataclass(frozen=True)
|
|
676
|
+
class PlanAction:
|
|
677
|
+
surface: str
|
|
678
|
+
action: str
|
|
679
|
+
detail: str
|
|
680
|
+
target_phase_hint: str
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
def template_for_section(section: CanonicalSection) -> str:
|
|
684
|
+
"""Return a one-line template summary for the named section."""
|
|
685
|
+
return (
|
|
686
|
+
f"Author '{section.display}' section per the canonical nine-section "
|
|
687
|
+
f"structure; body covers: {', '.join(section.body_signatures[:3])}."
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
def build_authoring_plan(
|
|
692
|
+
scans: list[SurfaceScan],
|
|
693
|
+
coherence_map: dict[str, list[dict[str, str]]],
|
|
694
|
+
) -> list[PlanAction]:
|
|
695
|
+
actions: list[PlanAction] = []
|
|
696
|
+
for scan in scans:
|
|
697
|
+
if scan.presence == SURFACE_ABSENT:
|
|
698
|
+
if scan.descriptor.mandatory:
|
|
699
|
+
actions.append(
|
|
700
|
+
PlanAction(
|
|
701
|
+
surface=scan.descriptor.path,
|
|
702
|
+
action="author-from-template",
|
|
703
|
+
detail=(
|
|
704
|
+
"Mandatory surface absent; author the full nine-section "
|
|
705
|
+
"structure with the canonical authorship banner header. "
|
|
706
|
+
f"Audience: {scan.descriptor.role}."
|
|
707
|
+
),
|
|
708
|
+
target_phase_hint=(
|
|
709
|
+
"Copilot-instructions author pass / instruction-surface refit pass"
|
|
710
|
+
if scan.descriptor.path == SURFACE_COPILOT
|
|
711
|
+
else "instruction-surface refit pass"
|
|
712
|
+
),
|
|
713
|
+
)
|
|
714
|
+
)
|
|
715
|
+
else:
|
|
716
|
+
actions.append(
|
|
717
|
+
PlanAction(
|
|
718
|
+
surface=scan.descriptor.path,
|
|
719
|
+
action="defer-to-opt-in",
|
|
720
|
+
detail=(
|
|
721
|
+
"Optional surface absent; author only when the operator "
|
|
722
|
+
f"opts in via the multi-surface opt-in inquiry. Audience: "
|
|
723
|
+
f"{scan.descriptor.role}."
|
|
724
|
+
),
|
|
725
|
+
target_phase_hint="Optional-surface opt-in inquiry / generator",
|
|
726
|
+
)
|
|
727
|
+
)
|
|
728
|
+
continue
|
|
729
|
+
# Present surface — emit one action per absent / renamed section.
|
|
730
|
+
for section in ALL_SECTIONS_FOR_PRESENCE:
|
|
731
|
+
status = scan.section_presence[section.slug]
|
|
732
|
+
if status == PRESENCE_ABSENT:
|
|
733
|
+
actions.append(
|
|
734
|
+
PlanAction(
|
|
735
|
+
surface=scan.descriptor.path,
|
|
736
|
+
action="install-missing-section",
|
|
737
|
+
detail=template_for_section(section),
|
|
738
|
+
target_phase_hint=(
|
|
739
|
+
"instruction-surface refit pass"
|
|
740
|
+
if scan.descriptor.path
|
|
741
|
+
in (
|
|
742
|
+
SURFACE_AGENTS_ROOT,
|
|
743
|
+
SURFACE_CLAUDE_ROOT,
|
|
744
|
+
SURFACE_CLAUDE_NESTED,
|
|
745
|
+
)
|
|
746
|
+
else "Copilot-instructions author pass"
|
|
747
|
+
if scan.descriptor.path == SURFACE_COPILOT
|
|
748
|
+
else "Optional-surface generator"
|
|
749
|
+
),
|
|
750
|
+
)
|
|
751
|
+
)
|
|
752
|
+
elif status.startswith(PRESENCE_RENAMED_PREFIX):
|
|
753
|
+
renamed_to = status[len(PRESENCE_RENAMED_PREFIX) :]
|
|
754
|
+
actions.append(
|
|
755
|
+
PlanAction(
|
|
756
|
+
surface=scan.descriptor.path,
|
|
757
|
+
action="rename-or-mirror-section",
|
|
758
|
+
detail=(
|
|
759
|
+
f"Section '{section.display}' is present under heading "
|
|
760
|
+
f"'{renamed_to}'; either rename to the canonical heading "
|
|
761
|
+
"or add the canonical heading and migrate the body."
|
|
762
|
+
),
|
|
763
|
+
target_phase_hint="instruction-surface refit pass",
|
|
764
|
+
)
|
|
765
|
+
)
|
|
766
|
+
# Contradiction reconciliation actions per coherence map entry.
|
|
767
|
+
for pair_key, verdicts in coherence_map.items():
|
|
768
|
+
for verdict in verdicts:
|
|
769
|
+
if verdict["verdict"] == COHERENCE_CONTRADICTS:
|
|
770
|
+
surface_a, surface_b = pair_key.split("__vs__")
|
|
771
|
+
# The default reconciliation mirrors the canonical project
|
|
772
|
+
# voice into the divergent surface. AGENTS.md is canonical.
|
|
773
|
+
if surface_a == SURFACE_AGENTS_ROOT:
|
|
774
|
+
canonical, divergent = surface_a, surface_b
|
|
775
|
+
elif surface_b == SURFACE_AGENTS_ROOT:
|
|
776
|
+
canonical, divergent = surface_b, surface_a
|
|
777
|
+
elif surface_a == SURFACE_CLAUDE_ROOT:
|
|
778
|
+
canonical, divergent = surface_a, surface_b
|
|
779
|
+
elif surface_b == SURFACE_CLAUDE_ROOT:
|
|
780
|
+
canonical, divergent = surface_b, surface_a
|
|
781
|
+
else:
|
|
782
|
+
canonical, divergent = surface_a, surface_b
|
|
783
|
+
actions.append(
|
|
784
|
+
PlanAction(
|
|
785
|
+
surface=divergent,
|
|
786
|
+
action="reconcile-contradiction",
|
|
787
|
+
detail=(
|
|
788
|
+
f"Section '{verdict['section_display']}' contradicts the "
|
|
789
|
+
f"corresponding section in '{canonical}'. Default "
|
|
790
|
+
f"reconciliation: rewrite '{divergent}' to mirror "
|
|
791
|
+
f"'{canonical}' semantics in surface-appropriate framing."
|
|
792
|
+
),
|
|
793
|
+
target_phase_hint="Multi-surface coherence reconciliation",
|
|
794
|
+
)
|
|
795
|
+
)
|
|
796
|
+
return actions
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
# ---------------------------------------------------------------------------
|
|
800
|
+
# JSON envelope.
|
|
801
|
+
# ---------------------------------------------------------------------------
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
def serialise_scan(scan: SurfaceScan) -> dict:
|
|
805
|
+
return {
|
|
806
|
+
"path": scan.descriptor.path,
|
|
807
|
+
"role": scan.descriptor.role,
|
|
808
|
+
"mandatory": scan.descriptor.mandatory,
|
|
809
|
+
"presence": scan.presence,
|
|
810
|
+
"sha256": scan.sha256,
|
|
811
|
+
"line-count": scan.line_count,
|
|
812
|
+
"headings": [
|
|
813
|
+
{
|
|
814
|
+
"level": h.level,
|
|
815
|
+
"text": h.text,
|
|
816
|
+
"line-start": h.line_start,
|
|
817
|
+
"line-end": h.line_end,
|
|
818
|
+
}
|
|
819
|
+
for h in scan.headings
|
|
820
|
+
],
|
|
821
|
+
"section-presence": scan.section_presence,
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
def emit_json(
|
|
826
|
+
out_path: Path,
|
|
827
|
+
scans: list[SurfaceScan],
|
|
828
|
+
coherence_map: dict[str, list[dict[str, str]]],
|
|
829
|
+
plan: list[PlanAction],
|
|
830
|
+
inventory_sha: str,
|
|
831
|
+
coarse_sha: str | None,
|
|
832
|
+
) -> None:
|
|
833
|
+
payload = {
|
|
834
|
+
"generated": datetime.now(timezone.utc).isoformat(),
|
|
835
|
+
"scanner": "scan_ai_surfaces",
|
|
836
|
+
"inventory-source-sha256": inventory_sha,
|
|
837
|
+
"coarse-source-sha256": coarse_sha,
|
|
838
|
+
"candidate-surfaces": [
|
|
839
|
+
{
|
|
840
|
+
"path": d.path,
|
|
841
|
+
"role": d.role,
|
|
842
|
+
"mandatory": d.mandatory,
|
|
843
|
+
}
|
|
844
|
+
for d in CANDIDATE_SURFACES
|
|
845
|
+
],
|
|
846
|
+
"canonical-sections": [
|
|
847
|
+
{"slug": s.slug, "display": s.display, "is-shared": s.is_shared}
|
|
848
|
+
for s in ALL_SECTIONS_FOR_PRESENCE
|
|
849
|
+
],
|
|
850
|
+
"shared-sections-for-coherence": [
|
|
851
|
+
{"slug": s.slug, "display": s.display}
|
|
852
|
+
for s in SHARED_SECTIONS_FOR_COHERENCE
|
|
853
|
+
],
|
|
854
|
+
"surfaces": [serialise_scan(s) for s in scans],
|
|
855
|
+
"coherence-map": coherence_map,
|
|
856
|
+
"authoring-plan": [asdict(p) for p in plan],
|
|
857
|
+
"summary": {
|
|
858
|
+
"candidate-count": len(scans),
|
|
859
|
+
"present-count": sum(1 for s in scans if s.presence == SURFACE_PRESENT),
|
|
860
|
+
"absent-count": sum(1 for s in scans if s.presence == SURFACE_ABSENT),
|
|
861
|
+
"mandatory-absent": [
|
|
862
|
+
s.descriptor.path
|
|
863
|
+
for s in scans
|
|
864
|
+
if s.presence == SURFACE_ABSENT and s.descriptor.mandatory
|
|
865
|
+
],
|
|
866
|
+
"pair-count": len(coherence_map),
|
|
867
|
+
"plan-action-count": len(plan),
|
|
868
|
+
},
|
|
869
|
+
}
|
|
870
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
871
|
+
out_path.write_text(
|
|
872
|
+
json.dumps(payload, indent=2, sort_keys=False) + "\n",
|
|
873
|
+
encoding="utf-8",
|
|
874
|
+
)
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
# ---------------------------------------------------------------------------
|
|
878
|
+
# Markdown render.
|
|
879
|
+
# ---------------------------------------------------------------------------
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
def render_markdown(
|
|
883
|
+
scans: list[SurfaceScan],
|
|
884
|
+
coherence_map: dict[str, list[dict[str, str]]],
|
|
885
|
+
plan: list[PlanAction],
|
|
886
|
+
inventory_sha: str,
|
|
887
|
+
coarse_sha: str | None,
|
|
888
|
+
generated_at: str,
|
|
889
|
+
) -> str:
|
|
890
|
+
parts: list[str] = []
|
|
891
|
+
parts.append("# AI-Conventions Surfaces — Presence and Coherence Map\n")
|
|
892
|
+
parts.append("")
|
|
893
|
+
parts.append(
|
|
894
|
+
"> Per-surface presence detection plus pairwise shared-section "
|
|
895
|
+
"coherence verdicts plus authoring / refinement plan. Heuristic at "
|
|
896
|
+
"this layer; the rigorous fixture-driven test lives at the "
|
|
897
|
+
"multi-surface coherence validator.\n"
|
|
898
|
+
)
|
|
899
|
+
parts.append("")
|
|
900
|
+
parts.append(f"- **Generated:** `{generated_at}`")
|
|
901
|
+
parts.append(f"- **Inventory SHA-256:** `{inventory_sha}`")
|
|
902
|
+
if coarse_sha is not None:
|
|
903
|
+
parts.append(f"- **Coarse-scan SHA-256:** `{coarse_sha}`")
|
|
904
|
+
parts.append("")
|
|
905
|
+
# Surface presence table.
|
|
906
|
+
parts.append("## Surface Presence")
|
|
907
|
+
parts.append("")
|
|
908
|
+
parts.append("| Path | Role | Mandatory | Presence | SHA-256 | Lines |")
|
|
909
|
+
parts.append("|------|------|-----------|----------|---------|-------|")
|
|
910
|
+
for scan in scans:
|
|
911
|
+
sha_display = scan.sha256[:12] + "…" if scan.sha256 else "—"
|
|
912
|
+
parts.append(
|
|
913
|
+
f"| `{scan.descriptor.path}` | {scan.descriptor.role} | "
|
|
914
|
+
f"{'**MUST**' if scan.descriptor.mandatory else 'opt-in'} | "
|
|
915
|
+
f"{scan.presence} | `{sha_display}` | {scan.line_count} |"
|
|
916
|
+
)
|
|
917
|
+
parts.append("")
|
|
918
|
+
# Per-surface section-presence map.
|
|
919
|
+
parts.append("## Section Presence")
|
|
920
|
+
parts.append("")
|
|
921
|
+
section_headers = " | ".join(s.display for s in ALL_SECTIONS_FOR_PRESENCE)
|
|
922
|
+
section_separators = " | ".join("---" for _ in ALL_SECTIONS_FOR_PRESENCE)
|
|
923
|
+
parts.append(f"| Surface | {section_headers} |")
|
|
924
|
+
parts.append(f"|---------|{section_separators}|")
|
|
925
|
+
for scan in scans:
|
|
926
|
+
if scan.presence == SURFACE_ABSENT:
|
|
927
|
+
cells = " | ".join("—" for _ in ALL_SECTIONS_FOR_PRESENCE)
|
|
928
|
+
parts.append(f"| `{scan.descriptor.path}` | {cells} |")
|
|
929
|
+
continue
|
|
930
|
+
cells = []
|
|
931
|
+
for section in ALL_SECTIONS_FOR_PRESENCE:
|
|
932
|
+
status = scan.section_presence[section.slug]
|
|
933
|
+
if status == PRESENCE_PRESENT:
|
|
934
|
+
cells.append("present")
|
|
935
|
+
elif status == PRESENCE_ABSENT:
|
|
936
|
+
cells.append("**absent**")
|
|
937
|
+
else:
|
|
938
|
+
renamed_to = status[len(PRESENCE_RENAMED_PREFIX) :]
|
|
939
|
+
cells.append(f"renamed-to `{renamed_to[:24]}`")
|
|
940
|
+
cells_joined = " | ".join(cells)
|
|
941
|
+
parts.append(f"| `{scan.descriptor.path}` | {cells_joined} |")
|
|
942
|
+
parts.append("")
|
|
943
|
+
# Per-surface heading inventory (level-2 only, for present surfaces).
|
|
944
|
+
parts.append("## Per-Surface Heading Inventory (level-2)")
|
|
945
|
+
parts.append("")
|
|
946
|
+
for scan in scans:
|
|
947
|
+
if scan.presence != SURFACE_PRESENT:
|
|
948
|
+
continue
|
|
949
|
+
parts.append(f"### `{scan.descriptor.path}`")
|
|
950
|
+
parts.append("")
|
|
951
|
+
level_two = [h for h in scan.headings if h.level == 2]
|
|
952
|
+
if not level_two:
|
|
953
|
+
parts.append(
|
|
954
|
+
"_No level-two headings detected; the surface is either flat "
|
|
955
|
+
"prose or uses a different heading depth._\n"
|
|
956
|
+
)
|
|
957
|
+
parts.append("")
|
|
958
|
+
continue
|
|
959
|
+
parts.append("| # | Heading | Lines |")
|
|
960
|
+
parts.append("|---|---------|-------|")
|
|
961
|
+
for index, heading in enumerate(level_two, start=1):
|
|
962
|
+
parts.append(
|
|
963
|
+
f"| {index} | {heading.text} | "
|
|
964
|
+
f"{heading.line_start}-{heading.line_end} |"
|
|
965
|
+
)
|
|
966
|
+
parts.append("")
|
|
967
|
+
# Coherence map.
|
|
968
|
+
parts.append("## Coherence Map")
|
|
969
|
+
parts.append("")
|
|
970
|
+
if not coherence_map:
|
|
971
|
+
parts.append(
|
|
972
|
+
"_No pairs of present surfaces; the coherence map is empty by "
|
|
973
|
+
"construction. The map's structure is preserved for the post-"
|
|
974
|
+
"authoring re-run when at least two surfaces are present._\n"
|
|
975
|
+
)
|
|
976
|
+
parts.append("")
|
|
977
|
+
else:
|
|
978
|
+
for pair_key, verdicts in coherence_map.items():
|
|
979
|
+
surface_a, surface_b = pair_key.split("__vs__")
|
|
980
|
+
parts.append(f"### `{surface_a}` ↔ `{surface_b}`")
|
|
981
|
+
parts.append("")
|
|
982
|
+
parts.append("| Section | Verdict | Rationale |")
|
|
983
|
+
parts.append("|---------|---------|-----------|")
|
|
984
|
+
for verdict in verdicts:
|
|
985
|
+
rationale = verdict["rationale"].replace("|", "\\|")
|
|
986
|
+
parts.append(
|
|
987
|
+
f"| {verdict['section_display']} | "
|
|
988
|
+
f"{verdict['verdict']} | {rationale} |"
|
|
989
|
+
)
|
|
990
|
+
parts.append("")
|
|
991
|
+
# Authoring / refinement plan.
|
|
992
|
+
parts.append("## Authoring / Refinement Plan")
|
|
993
|
+
parts.append("")
|
|
994
|
+
if not plan:
|
|
995
|
+
parts.append(
|
|
996
|
+
"_No authoring / refinement actions emitted; every surface in "
|
|
997
|
+
"scope is fully canonical._\n"
|
|
998
|
+
)
|
|
999
|
+
parts.append("")
|
|
1000
|
+
else:
|
|
1001
|
+
parts.append("| # | Surface | Action | Target Phase Hint | Detail |")
|
|
1002
|
+
parts.append("|---|---------|--------|-------------------|--------|")
|
|
1003
|
+
for index, action in enumerate(plan, start=1):
|
|
1004
|
+
detail = action.detail.replace("|", "\\|")
|
|
1005
|
+
parts.append(
|
|
1006
|
+
f"| {index} | `{action.surface}` | {action.action} | "
|
|
1007
|
+
f"{action.target_phase_hint} | {detail} |"
|
|
1008
|
+
)
|
|
1009
|
+
parts.append("")
|
|
1010
|
+
# Reconciliation summary.
|
|
1011
|
+
contradictions = [
|
|
1012
|
+
verdict
|
|
1013
|
+
for verdicts in coherence_map.values()
|
|
1014
|
+
for verdict in verdicts
|
|
1015
|
+
if verdict["verdict"] == COHERENCE_CONTRADICTS
|
|
1016
|
+
]
|
|
1017
|
+
parts.append("## Reconciliation Summary")
|
|
1018
|
+
parts.append("")
|
|
1019
|
+
if not contradictions:
|
|
1020
|
+
parts.append(
|
|
1021
|
+
"_No candidate contradictions detected at this heuristic layer. "
|
|
1022
|
+
"The fixture-driven validator confirms the verdict at a later "
|
|
1023
|
+
"phase._\n"
|
|
1024
|
+
)
|
|
1025
|
+
parts.append("")
|
|
1026
|
+
else:
|
|
1027
|
+
parts.append(
|
|
1028
|
+
"Default reconciliation: `AGENTS.md` is the canonical project voice; "
|
|
1029
|
+
"any divergent surface is rewritten to mirror its semantics in "
|
|
1030
|
+
"surface-appropriate framing. Surface-specific overrides require "
|
|
1031
|
+
"an in-file `<!-- coherence-override: <claim-id> -->` marker "
|
|
1032
|
+
"pointing at an architectural decision record.\n"
|
|
1033
|
+
)
|
|
1034
|
+
parts.append("")
|
|
1035
|
+
parts.append("| Section | Surfaces | Default Reconciliation |")
|
|
1036
|
+
parts.append("|---------|----------|------------------------|")
|
|
1037
|
+
for verdict in contradictions:
|
|
1038
|
+
parts.append(
|
|
1039
|
+
f"| {verdict['section_display']} | (see plan) | "
|
|
1040
|
+
f"Rewrite divergent surface to mirror `AGENTS.md`. |"
|
|
1041
|
+
)
|
|
1042
|
+
parts.append("")
|
|
1043
|
+
return "\n".join(parts) + "\n"
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
# ---------------------------------------------------------------------------
|
|
1047
|
+
# Entry point.
|
|
1048
|
+
# ---------------------------------------------------------------------------
|
|
1049
|
+
|
|
1050
|
+
|
|
1051
|
+
def main(argv: list[str] | None = None) -> int:
|
|
1052
|
+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
1053
|
+
parser.add_argument(
|
|
1054
|
+
"--inventory",
|
|
1055
|
+
type=Path,
|
|
1056
|
+
default=Path(".audit/inventory.json"),
|
|
1057
|
+
help="Inventory snapshot whose SHA-256 anchors the scan output.",
|
|
1058
|
+
)
|
|
1059
|
+
parser.add_argument(
|
|
1060
|
+
"--coarse",
|
|
1061
|
+
type=Path,
|
|
1062
|
+
default=Path(".audit/drift-ai-surfaces-coarse.json"),
|
|
1063
|
+
help="Optional coarse pre-scan output; its SHA-256 is recorded.",
|
|
1064
|
+
)
|
|
1065
|
+
parser.add_argument("--root", type=Path, default=Path())
|
|
1066
|
+
parser.add_argument(
|
|
1067
|
+
"--out-json",
|
|
1068
|
+
type=Path,
|
|
1069
|
+
default=Path(".audit/ai-surfaces.json"),
|
|
1070
|
+
)
|
|
1071
|
+
parser.add_argument(
|
|
1072
|
+
"--out-md",
|
|
1073
|
+
type=Path,
|
|
1074
|
+
default=Path(".audit/ai-surfaces.md"),
|
|
1075
|
+
)
|
|
1076
|
+
args = parser.parse_args(argv)
|
|
1077
|
+
if not args.inventory.exists():
|
|
1078
|
+
print(
|
|
1079
|
+
f"error: inventory not found at {args.inventory}",
|
|
1080
|
+
file=sys.stderr,
|
|
1081
|
+
)
|
|
1082
|
+
return 1
|
|
1083
|
+
_, inventory_sha = load_inventory(args.inventory)
|
|
1084
|
+
coarse_sha: str | None = None
|
|
1085
|
+
if args.coarse.exists():
|
|
1086
|
+
coarse_sha = hashlib.sha256(args.coarse.read_bytes()).hexdigest()
|
|
1087
|
+
scans = [scan_surface(d, args.root) for d in CANDIDATE_SURFACES]
|
|
1088
|
+
coherence_map = build_coherence_map(scans)
|
|
1089
|
+
plan = build_authoring_plan(scans, coherence_map)
|
|
1090
|
+
generated_at = datetime.now(timezone.utc).isoformat()
|
|
1091
|
+
emit_json(args.out_json, scans, coherence_map, plan, inventory_sha, coarse_sha)
|
|
1092
|
+
args.out_md.parent.mkdir(parents=True, exist_ok=True)
|
|
1093
|
+
args.out_md.write_text(
|
|
1094
|
+
render_markdown(
|
|
1095
|
+
scans,
|
|
1096
|
+
coherence_map,
|
|
1097
|
+
plan,
|
|
1098
|
+
inventory_sha,
|
|
1099
|
+
coarse_sha,
|
|
1100
|
+
generated_at,
|
|
1101
|
+
),
|
|
1102
|
+
encoding="utf-8",
|
|
1103
|
+
)
|
|
1104
|
+
present_count = sum(1 for s in scans if s.presence == SURFACE_PRESENT)
|
|
1105
|
+
absent_count = sum(1 for s in scans if s.presence == SURFACE_ABSENT)
|
|
1106
|
+
mandatory_absent = [
|
|
1107
|
+
s.descriptor.path
|
|
1108
|
+
for s in scans
|
|
1109
|
+
if s.presence == SURFACE_ABSENT and s.descriptor.mandatory
|
|
1110
|
+
]
|
|
1111
|
+
print(
|
|
1112
|
+
f"scan_ai_surfaces: candidates={len(scans)}, present={present_count}, "
|
|
1113
|
+
f"absent={absent_count}, mandatory-absent={len(mandatory_absent)}, "
|
|
1114
|
+
f"pairs={len(coherence_map)}, plan-actions={len(plan)}"
|
|
1115
|
+
)
|
|
1116
|
+
if mandatory_absent:
|
|
1117
|
+
print(
|
|
1118
|
+
f" mandatory-absent: {', '.join(mandatory_absent)}",
|
|
1119
|
+
file=sys.stderr,
|
|
1120
|
+
)
|
|
1121
|
+
return 0
|
|
1122
|
+
|
|
1123
|
+
|
|
1124
|
+
if __name__ == "__main__":
|
|
1125
|
+
raise SystemExit(main())
|