@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,657 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
"""Walk an ecosystem root and produce a machine-readable inventory.
|
|
4
|
+
|
|
5
|
+
Why this tool exists. Subsequent audit work (drift detection, header
|
|
6
|
+
coverage, multi-surface conventions, validator authoring) needs a single
|
|
7
|
+
authoritative description of every file in the working tree: its path,
|
|
8
|
+
size, content hash, line count, ecosystem-class, and authorship-banner
|
|
9
|
+
status. Without that one source of truth, each consumer would re-walk
|
|
10
|
+
the tree with subtly different filters and produce drift between scans.
|
|
11
|
+
This tool emits ``inventory.json`` once; every downstream pass reads it.
|
|
12
|
+
|
|
13
|
+
What the inventory captures. Per file: ``path`` (relative to root),
|
|
14
|
+
``size`` (bytes), ``mtime`` (ISO 8601 UTC), ``sha256`` (hex digest),
|
|
15
|
+
``line-count`` (text files only — None for binaries), ``class`` (one of
|
|
16
|
+
the canonical thirteen classes below), ``header-status`` (
|
|
17
|
+
``present-canonical`` / ``present-malformed`` / ``absent`` /
|
|
18
|
+
``not-applicable``), and ``header-variant`` (the comment-syntax family
|
|
19
|
+
the file's authorship banner would use, when applicable).
|
|
20
|
+
|
|
21
|
+
What the inventory excludes. Version-control internals (``.git``),
|
|
22
|
+
Python bytecode caches (``__pycache__``), virtual environments
|
|
23
|
+
(``.venv`` / ``venv`` / ``env`` / ``.tox``), node modules, and the
|
|
24
|
+
audit-output directory itself (``.audit``). The exclusion list is
|
|
25
|
+
deliberate: these are derivative state, not source artifacts, and
|
|
26
|
+
including them would inflate aggregate counts without informing any
|
|
27
|
+
downstream consumer.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import argparse
|
|
33
|
+
import hashlib
|
|
34
|
+
import json
|
|
35
|
+
import sys
|
|
36
|
+
from collections.abc import Iterator
|
|
37
|
+
from dataclasses import dataclass, field
|
|
38
|
+
from datetime import datetime, timezone
|
|
39
|
+
from pathlib import Path
|
|
40
|
+
from typing import Final
|
|
41
|
+
|
|
42
|
+
# Thirteen canonical ecosystem classes. The classification matrix at the
|
|
43
|
+
# next audit phase refines these verdicts; this pass assigns one class per
|
|
44
|
+
# file via path-prefix lookup with extension-based fallbacks.
|
|
45
|
+
CLASS_MEMORY: Final[str] = "memory"
|
|
46
|
+
CLASS_AGENT: Final[str] = "agent"
|
|
47
|
+
CLASS_COMMAND: Final[str] = "command"
|
|
48
|
+
CLASS_SKILL: Final[str] = "skill"
|
|
49
|
+
CLASS_HOOK: Final[str] = "hook"
|
|
50
|
+
CLASS_OUTPUT_STYLE: Final[str] = "output-style"
|
|
51
|
+
CLASS_STATUSLINE: Final[str] = "statusline"
|
|
52
|
+
CLASS_SETTINGS: Final[str] = "settings"
|
|
53
|
+
CLASS_MCP: Final[str] = "mcp"
|
|
54
|
+
CLASS_DOCS: Final[str] = "docs"
|
|
55
|
+
CLASS_SCAFFOLDING: Final[str] = "scaffolding"
|
|
56
|
+
CLASS_PLAN_ARTIFACT: Final[str] = "plan-artifact"
|
|
57
|
+
CLASS_UNKNOWN: Final[str] = "unknown"
|
|
58
|
+
|
|
59
|
+
ALL_CLASSES: Final[tuple[str, ...]] = (
|
|
60
|
+
CLASS_MEMORY,
|
|
61
|
+
CLASS_AGENT,
|
|
62
|
+
CLASS_COMMAND,
|
|
63
|
+
CLASS_SKILL,
|
|
64
|
+
CLASS_HOOK,
|
|
65
|
+
CLASS_OUTPUT_STYLE,
|
|
66
|
+
CLASS_STATUSLINE,
|
|
67
|
+
CLASS_SETTINGS,
|
|
68
|
+
CLASS_MCP,
|
|
69
|
+
CLASS_DOCS,
|
|
70
|
+
CLASS_SCAFFOLDING,
|
|
71
|
+
CLASS_PLAN_ARTIFACT,
|
|
72
|
+
CLASS_UNKNOWN,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Header-status taxonomy. The four values are mutually exclusive per file.
|
|
76
|
+
HEADER_PRESENT_CANONICAL: Final[str] = "present-canonical"
|
|
77
|
+
HEADER_PRESENT_MALFORMED: Final[str] = "present-malformed"
|
|
78
|
+
HEADER_ABSENT: Final[str] = "absent"
|
|
79
|
+
HEADER_NOT_APPLICABLE: Final[str] = "not-applicable"
|
|
80
|
+
|
|
81
|
+
ALL_HEADER_STATUSES: Final[tuple[str, ...]] = (
|
|
82
|
+
HEADER_PRESENT_CANONICAL,
|
|
83
|
+
HEADER_PRESENT_MALFORMED,
|
|
84
|
+
HEADER_ABSENT,
|
|
85
|
+
HEADER_NOT_APPLICABLE,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Path-prefix to class mapping. The ordering is irrelevant — every key is
|
|
89
|
+
# matched against the file's relative-path top-level segment. Files outside
|
|
90
|
+
# any registered prefix fall through to extension-based classification.
|
|
91
|
+
PATH_PREFIX_CLASSES: Final[dict[str, str]] = {
|
|
92
|
+
"memory": CLASS_MEMORY,
|
|
93
|
+
"projects": CLASS_MEMORY,
|
|
94
|
+
"agents": CLASS_AGENT,
|
|
95
|
+
"commands": CLASS_COMMAND,
|
|
96
|
+
"skills": CLASS_SKILL,
|
|
97
|
+
"hooks": CLASS_HOOK,
|
|
98
|
+
"output-styles": CLASS_OUTPUT_STYLE,
|
|
99
|
+
"statusline": CLASS_STATUSLINE,
|
|
100
|
+
"statuslines": CLASS_STATUSLINE,
|
|
101
|
+
"settings": CLASS_SETTINGS,
|
|
102
|
+
"mcp": CLASS_MCP,
|
|
103
|
+
"docs": CLASS_DOCS,
|
|
104
|
+
"rules": CLASS_DOCS,
|
|
105
|
+
"examples": CLASS_DOCS,
|
|
106
|
+
"schemas": CLASS_SCAFFOLDING,
|
|
107
|
+
"scripts": CLASS_SCAFFOLDING,
|
|
108
|
+
"tests": CLASS_SCAFFOLDING,
|
|
109
|
+
"templates": CLASS_SCAFFOLDING,
|
|
110
|
+
"assets": CLASS_SCAFFOLDING,
|
|
111
|
+
".github": CLASS_SCAFFOLDING,
|
|
112
|
+
"packaging": CLASS_SCAFFOLDING,
|
|
113
|
+
".plans": CLASS_PLAN_ARTIFACT,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# Settings files at the working-tree root carry the settings class regardless
|
|
117
|
+
# of the absent ``settings/`` directory. The Claude-Code installable example
|
|
118
|
+
# (``settings.json``) lives under
|
|
119
|
+
# ``examples/harnesses/claude-code/native-install/`` and is classified via the
|
|
120
|
+
# ``examples`` path-prefix entry, not by root-filename.
|
|
121
|
+
ROOT_SETTINGS_FILES: Final[frozenset[str]] = frozenset(
|
|
122
|
+
{
|
|
123
|
+
".mcp.json",
|
|
124
|
+
".credentials.json",
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Files at the working-tree root that contribute to the scaffolding class.
|
|
129
|
+
# Covers the build-runner, lint configs, line-ending policy, install/update/
|
|
130
|
+
# uninstall scripts, version pin, secret-scan config, license header policy,
|
|
131
|
+
# and contributor mapping that any modern dotfiles repository carries at root.
|
|
132
|
+
ROOT_SCAFFOLDING_FILES: Final[frozenset[str]] = frozenset(
|
|
133
|
+
{
|
|
134
|
+
"Makefile",
|
|
135
|
+
"VERSION",
|
|
136
|
+
"pyproject.toml",
|
|
137
|
+
".gitignore",
|
|
138
|
+
".gitattributes",
|
|
139
|
+
".editorconfig",
|
|
140
|
+
".gitleaks.toml",
|
|
141
|
+
".gitmessage",
|
|
142
|
+
".licenserc.yaml",
|
|
143
|
+
".mailmap",
|
|
144
|
+
".markdownlint.json",
|
|
145
|
+
".markdownlint.jsonc",
|
|
146
|
+
".markdownlint-cli2.jsonc",
|
|
147
|
+
".prettierrc",
|
|
148
|
+
".prettierignore",
|
|
149
|
+
".shellcheckrc",
|
|
150
|
+
".pre-commit-config.yaml",
|
|
151
|
+
"scripts/installer/install.sh",
|
|
152
|
+
"scripts/installer/install.ps1",
|
|
153
|
+
"scripts/installer/update.sh",
|
|
154
|
+
"scripts/installer/update.ps1",
|
|
155
|
+
"scripts/installer/uninstall.sh",
|
|
156
|
+
"scripts/installer/uninstall.ps1",
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Documentation files at the working-tree root.
|
|
161
|
+
ROOT_DOCS_FILES: Final[frozenset[str]] = frozenset(
|
|
162
|
+
{
|
|
163
|
+
"CLAUDE.md",
|
|
164
|
+
"README.md",
|
|
165
|
+
"LICENSE",
|
|
166
|
+
"CHANGELOG.md",
|
|
167
|
+
"CONTRIBUTING.md",
|
|
168
|
+
"CODE_OF_CONDUCT.md",
|
|
169
|
+
"SECURITY.md",
|
|
170
|
+
"AUTHORS",
|
|
171
|
+
".mcp.json.notes.md",
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Directory names skipped during the walk. These are derivative state, not
|
|
176
|
+
# source artifacts. The set covers four distinct origins: version-control
|
|
177
|
+
# internals (``.git``), language-tooling caches (``__pycache__``,
|
|
178
|
+
# ``.pytest_cache``, ``.mypy_cache``, ``.ruff_cache``, ``node_modules``,
|
|
179
|
+
# ``.tox``), virtual environments (``.venv``, ``venv``, ``env``,
|
|
180
|
+
# ``apothem.egg-info``), and Claude Code harness-managed state
|
|
181
|
+
# (``file-history``, ``debug``, ``todos``, ``shell-snapshots``, ``ide``,
|
|
182
|
+
# ``sessions``, ``session-env``, ``runtime-data``, ``cache``, ``downloads``,
|
|
183
|
+
# ``backups``, ``statsig``, ``telemetry``, ``plugins``). The audit-output
|
|
184
|
+
# directory itself (``.audit``) is also skipped to keep self-reference out
|
|
185
|
+
# of the inventory.
|
|
186
|
+
SKIPPED_DIRS: Final[frozenset[str]] = frozenset(
|
|
187
|
+
{
|
|
188
|
+
# Version-control internals.
|
|
189
|
+
".git",
|
|
190
|
+
# Language-tooling caches.
|
|
191
|
+
"__pycache__",
|
|
192
|
+
".pytest_cache",
|
|
193
|
+
".mypy_cache",
|
|
194
|
+
".ruff_cache",
|
|
195
|
+
".tox",
|
|
196
|
+
"node_modules",
|
|
197
|
+
# Virtual environments and Python build artifacts.
|
|
198
|
+
".venv",
|
|
199
|
+
"venv",
|
|
200
|
+
"env",
|
|
201
|
+
"apothem.egg-info",
|
|
202
|
+
# Audit output (self-reference).
|
|
203
|
+
".audit",
|
|
204
|
+
# IDE workspace state.
|
|
205
|
+
".vscode",
|
|
206
|
+
".idea",
|
|
207
|
+
# Claude Code harness-managed state.
|
|
208
|
+
"file-history",
|
|
209
|
+
"debug",
|
|
210
|
+
"todos",
|
|
211
|
+
"shell-snapshots",
|
|
212
|
+
"ide",
|
|
213
|
+
"sessions",
|
|
214
|
+
"session-env",
|
|
215
|
+
"runtime-data",
|
|
216
|
+
"cache",
|
|
217
|
+
"downloads",
|
|
218
|
+
"backups",
|
|
219
|
+
"statsig",
|
|
220
|
+
"telemetry",
|
|
221
|
+
"plugins",
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# File extensions treated as binary. The header-status check skips these and
|
|
226
|
+
# the line-count returns None.
|
|
227
|
+
BINARY_EXTENSIONS: Final[frozenset[str]] = frozenset(
|
|
228
|
+
{
|
|
229
|
+
".png",
|
|
230
|
+
".jpg",
|
|
231
|
+
".jpeg",
|
|
232
|
+
".gif",
|
|
233
|
+
".ico",
|
|
234
|
+
".webp",
|
|
235
|
+
".pdf",
|
|
236
|
+
".zip",
|
|
237
|
+
".tar",
|
|
238
|
+
".gz",
|
|
239
|
+
".tgz",
|
|
240
|
+
".7z",
|
|
241
|
+
".rar",
|
|
242
|
+
".exe",
|
|
243
|
+
".dll",
|
|
244
|
+
".so",
|
|
245
|
+
".dylib",
|
|
246
|
+
".bin",
|
|
247
|
+
".pyc",
|
|
248
|
+
".pyo",
|
|
249
|
+
".woff",
|
|
250
|
+
".woff2",
|
|
251
|
+
".ttf",
|
|
252
|
+
".otf",
|
|
253
|
+
".eot",
|
|
254
|
+
".mp3",
|
|
255
|
+
".mp4",
|
|
256
|
+
".webm",
|
|
257
|
+
".mov",
|
|
258
|
+
".avi",
|
|
259
|
+
}
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# Lockfiles, generated assets, and vendored data are all not-applicable for
|
|
263
|
+
# the authorship banner. Adding a banner to JSON / lockfiles would break
|
|
264
|
+
# parsing; vendored content carries upstream attribution and must not be
|
|
265
|
+
# overwritten.
|
|
266
|
+
NOT_APPLICABLE_EXTENSIONS: Final[frozenset[str]] = frozenset(
|
|
267
|
+
{
|
|
268
|
+
".json",
|
|
269
|
+
".lock",
|
|
270
|
+
".min.js",
|
|
271
|
+
".min.css",
|
|
272
|
+
}
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# Filename patterns that mark a file as not-applicable for banner injection.
|
|
276
|
+
NOT_APPLICABLE_NAMES: Final[frozenset[str]] = frozenset(
|
|
277
|
+
{
|
|
278
|
+
"LICENSE",
|
|
279
|
+
"VERSION",
|
|
280
|
+
"go.sum",
|
|
281
|
+
"package-lock.json",
|
|
282
|
+
"yarn.lock",
|
|
283
|
+
"pnpm-lock.yaml",
|
|
284
|
+
"Pipfile.lock",
|
|
285
|
+
"poetry.lock",
|
|
286
|
+
"uv.lock",
|
|
287
|
+
}
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Header-variant family per filetype. The variants enumerate which comment
|
|
291
|
+
# syntax the canonical 5-line authorship banner uses on this filetype. The
|
|
292
|
+
# values are intent labels; the actual banner bytes live in the
|
|
293
|
+
# ``src/apothem/schemas/authorship-header.txt`` fixture authored at the next phase.
|
|
294
|
+
HEADER_VARIANT_HASH: Final[str] = "hash" # `# ...` line-comments
|
|
295
|
+
HEADER_VARIANT_DOUBLE_SLASH: Final[str] = "double-slash" # `// ...`
|
|
296
|
+
HEADER_VARIANT_HTML: Final[str] = "html" # `<!-- ... -->`
|
|
297
|
+
HEADER_VARIANT_C_BLOCK: Final[str] = "c-block" # `/* ... */`
|
|
298
|
+
HEADER_VARIANT_SEMICOLON: Final[str] = "semicolon" # `; ...`
|
|
299
|
+
HEADER_VARIANT_DOUBLE_DASH: Final[str] = "double-dash" # `-- ...`
|
|
300
|
+
HEADER_VARIANT_NONE: Final[str] = "none" # not-applicable
|
|
301
|
+
|
|
302
|
+
EXTENSION_VARIANT: Final[dict[str, str]] = {
|
|
303
|
+
".py": HEADER_VARIANT_HASH,
|
|
304
|
+
".sh": HEADER_VARIANT_HASH,
|
|
305
|
+
".bash": HEADER_VARIANT_HASH,
|
|
306
|
+
".zsh": HEADER_VARIANT_HASH,
|
|
307
|
+
".ps1": HEADER_VARIANT_HASH,
|
|
308
|
+
".psm1": HEADER_VARIANT_HASH,
|
|
309
|
+
".psd1": HEADER_VARIANT_HASH,
|
|
310
|
+
".yaml": HEADER_VARIANT_HASH,
|
|
311
|
+
".yml": HEADER_VARIANT_HASH,
|
|
312
|
+
".toml": HEADER_VARIANT_HASH,
|
|
313
|
+
".ini": HEADER_VARIANT_SEMICOLON,
|
|
314
|
+
".cfg": HEADER_VARIANT_HASH,
|
|
315
|
+
".conf": HEADER_VARIANT_HASH,
|
|
316
|
+
".md": HEADER_VARIANT_HTML,
|
|
317
|
+
".markdown": HEADER_VARIANT_HTML,
|
|
318
|
+
".html": HEADER_VARIANT_HTML,
|
|
319
|
+
".htm": HEADER_VARIANT_HTML,
|
|
320
|
+
".xml": HEADER_VARIANT_HTML,
|
|
321
|
+
".svg": HEADER_VARIANT_HTML,
|
|
322
|
+
".js": HEADER_VARIANT_DOUBLE_SLASH,
|
|
323
|
+
".ts": HEADER_VARIANT_DOUBLE_SLASH,
|
|
324
|
+
".jsx": HEADER_VARIANT_DOUBLE_SLASH,
|
|
325
|
+
".tsx": HEADER_VARIANT_DOUBLE_SLASH,
|
|
326
|
+
".java": HEADER_VARIANT_DOUBLE_SLASH,
|
|
327
|
+
".go": HEADER_VARIANT_DOUBLE_SLASH,
|
|
328
|
+
".rs": HEADER_VARIANT_DOUBLE_SLASH,
|
|
329
|
+
".swift": HEADER_VARIANT_DOUBLE_SLASH,
|
|
330
|
+
".kt": HEADER_VARIANT_DOUBLE_SLASH,
|
|
331
|
+
".c": HEADER_VARIANT_C_BLOCK,
|
|
332
|
+
".cpp": HEADER_VARIANT_C_BLOCK,
|
|
333
|
+
".h": HEADER_VARIANT_C_BLOCK,
|
|
334
|
+
".hpp": HEADER_VARIANT_C_BLOCK,
|
|
335
|
+
".css": HEADER_VARIANT_C_BLOCK,
|
|
336
|
+
".scss": HEADER_VARIANT_C_BLOCK,
|
|
337
|
+
".sql": HEADER_VARIANT_DOUBLE_DASH,
|
|
338
|
+
".lua": HEADER_VARIANT_DOUBLE_DASH,
|
|
339
|
+
".hs": HEADER_VARIANT_DOUBLE_DASH,
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
# Banner heuristic markers. The narrowed canonical header is the single
|
|
343
|
+
# ``SPDX-License-Identifier:`` line; its presence near the file head marks
|
|
344
|
+
# ``present-canonical``. The retired branded-banner author mark is retained
|
|
345
|
+
# as a legacy signal: a file still carrying it (but not the SPDX line) is a
|
|
346
|
+
# not-yet-narrowed header and counts ``present-malformed``. This stays a
|
|
347
|
+
# coarse sizing heuristic; the per-file authoritative verdict is recomputed
|
|
348
|
+
# downstream by the header-coverage scanner.
|
|
349
|
+
SPDX_PREFIX_TEXT: Final[str] = "SPDX-License-Identifier:"
|
|
350
|
+
LEGACY_AUTHOR_MARK: Final[str] = "Copyright (c) Ahmed G. Gad"
|
|
351
|
+
|
|
352
|
+
# Number of leading lines scanned for banner presence. Forty lines covers
|
|
353
|
+
# every shebang + interpreter-pragma + copyright-block prelude shape the
|
|
354
|
+
# canonical filetype variants emit.
|
|
355
|
+
BANNER_SCAN_LINE_BUDGET: Final[int] = 40
|
|
356
|
+
|
|
357
|
+
# Buffer size for SHA-256 streaming digest. Sized to balance syscall count
|
|
358
|
+
# against memory residency for the small-to-medium file sizes typical in
|
|
359
|
+
# this ecosystem.
|
|
360
|
+
SHA256_BUFFER_BYTES: Final[int] = 65_536
|
|
361
|
+
|
|
362
|
+
EXIT_OK: Final[int] = 0
|
|
363
|
+
EXIT_ERROR: Final[int] = 1
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
@dataclass(slots=True)
|
|
367
|
+
class FileRecord:
|
|
368
|
+
"""Per-file inventory row written to ``inventory.json``."""
|
|
369
|
+
|
|
370
|
+
path: str
|
|
371
|
+
size: int
|
|
372
|
+
mtime: str
|
|
373
|
+
sha256: str
|
|
374
|
+
line_count: int | None
|
|
375
|
+
file_class: str
|
|
376
|
+
header_status: str
|
|
377
|
+
header_variant: str
|
|
378
|
+
|
|
379
|
+
def to_json(self) -> dict[str, object]:
|
|
380
|
+
"""Render the record as the JSON-friendly dict the inventory emits."""
|
|
381
|
+
return {
|
|
382
|
+
"path": self.path,
|
|
383
|
+
"size": self.size,
|
|
384
|
+
"mtime": self.mtime,
|
|
385
|
+
"sha256": self.sha256,
|
|
386
|
+
"line-count": self.line_count,
|
|
387
|
+
"class": self.file_class,
|
|
388
|
+
"header-status": self.header_status,
|
|
389
|
+
"header-variant": self.header_variant,
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
@dataclass(slots=True)
|
|
394
|
+
class InventoryStats:
|
|
395
|
+
"""Aggregate counts emitted alongside the per-file array."""
|
|
396
|
+
|
|
397
|
+
total_files: int = 0
|
|
398
|
+
by_class: dict[str, int] = field(default_factory=dict)
|
|
399
|
+
by_header_status: dict[str, int] = field(default_factory=dict)
|
|
400
|
+
by_header_variant: dict[str, int] = field(default_factory=dict)
|
|
401
|
+
skipped_directories: list[str] = field(default_factory=list)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def classify_file(relative_path: Path) -> str:
|
|
405
|
+
"""Resolve the ecosystem-class for a file given its path relative to root.
|
|
406
|
+
|
|
407
|
+
Path-prefix lookup is the primary signal; root-level filename matches
|
|
408
|
+
cover the scaffolding / docs / settings cases at the working-tree root
|
|
409
|
+
where no enclosing directory carries a registered prefix.
|
|
410
|
+
"""
|
|
411
|
+
parts = relative_path.parts
|
|
412
|
+
if not parts:
|
|
413
|
+
return CLASS_UNKNOWN
|
|
414
|
+
|
|
415
|
+
if len(parts) == 1:
|
|
416
|
+
name = parts[0]
|
|
417
|
+
if name in ROOT_SETTINGS_FILES:
|
|
418
|
+
return CLASS_SETTINGS
|
|
419
|
+
if name in ROOT_SCAFFOLDING_FILES:
|
|
420
|
+
return CLASS_SCAFFOLDING
|
|
421
|
+
if name in ROOT_DOCS_FILES:
|
|
422
|
+
return CLASS_DOCS
|
|
423
|
+
return CLASS_UNKNOWN
|
|
424
|
+
|
|
425
|
+
top = parts[0]
|
|
426
|
+
if top in PATH_PREFIX_CLASSES:
|
|
427
|
+
return PATH_PREFIX_CLASSES[top]
|
|
428
|
+
|
|
429
|
+
return CLASS_UNKNOWN
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def header_variant_for(relative_path: Path) -> str:
|
|
433
|
+
"""Resolve the comment-syntax variant the canonical banner would use."""
|
|
434
|
+
name = relative_path.name
|
|
435
|
+
if name in NOT_APPLICABLE_NAMES:
|
|
436
|
+
return HEADER_VARIANT_NONE
|
|
437
|
+
|
|
438
|
+
suffix = relative_path.suffix.lower()
|
|
439
|
+
if suffix in NOT_APPLICABLE_EXTENSIONS:
|
|
440
|
+
return HEADER_VARIANT_NONE
|
|
441
|
+
if suffix in BINARY_EXTENSIONS:
|
|
442
|
+
return HEADER_VARIANT_NONE
|
|
443
|
+
|
|
444
|
+
return EXTENSION_VARIANT.get(suffix, HEADER_VARIANT_NONE)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def is_binary_file(relative_path: Path) -> bool:
|
|
448
|
+
"""Treat the file as binary based on its extension.
|
|
449
|
+
|
|
450
|
+
Conservative — when the suffix is not a known binary extension and the
|
|
451
|
+
file is otherwise text-shaped (carries a banner-eligible variant or
|
|
452
|
+
falls outside the binary set entirely), the file is read as text.
|
|
453
|
+
"""
|
|
454
|
+
return relative_path.suffix.lower() in BINARY_EXTENSIONS
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def compute_sha256(absolute_path: Path) -> str:
|
|
458
|
+
"""Stream the file through SHA-256 and return the lowercase hex digest."""
|
|
459
|
+
digest = hashlib.sha256()
|
|
460
|
+
with absolute_path.open("rb") as handle:
|
|
461
|
+
while chunk := handle.read(SHA256_BUFFER_BYTES):
|
|
462
|
+
digest.update(chunk)
|
|
463
|
+
return digest.hexdigest()
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def count_lines(absolute_path: Path) -> int | None:
|
|
467
|
+
"""Count newline-terminated lines; return None when the file is binary."""
|
|
468
|
+
try:
|
|
469
|
+
with absolute_path.open("rb") as handle:
|
|
470
|
+
count = 0
|
|
471
|
+
for _ in handle:
|
|
472
|
+
count += 1
|
|
473
|
+
return count
|
|
474
|
+
except OSError:
|
|
475
|
+
return None
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def scan_banner(absolute_path: Path, variant: str) -> str:
|
|
479
|
+
"""Inspect the file's leading lines for the canonical banner pattern.
|
|
480
|
+
|
|
481
|
+
The scan returns one of the four header-status values. Files whose
|
|
482
|
+
variant resolves to ``none`` (binary, lockfile, vendored, generated)
|
|
483
|
+
short-circuit to ``not-applicable``. Otherwise the leading scan-line
|
|
484
|
+
budget is read as text; the count of distinct banner marks observed
|
|
485
|
+
determines the verdict.
|
|
486
|
+
"""
|
|
487
|
+
if variant == HEADER_VARIANT_NONE:
|
|
488
|
+
return HEADER_NOT_APPLICABLE
|
|
489
|
+
|
|
490
|
+
try:
|
|
491
|
+
with absolute_path.open("r", encoding="utf-8", errors="replace") as handle:
|
|
492
|
+
head_lines: list[str] = []
|
|
493
|
+
for _, line in zip(range(BANNER_SCAN_LINE_BUDGET), handle, strict=False):
|
|
494
|
+
head_lines.append(line)
|
|
495
|
+
except OSError:
|
|
496
|
+
return HEADER_ABSENT
|
|
497
|
+
|
|
498
|
+
head_text = "".join(head_lines)
|
|
499
|
+
|
|
500
|
+
if SPDX_PREFIX_TEXT in head_text:
|
|
501
|
+
return HEADER_PRESENT_CANONICAL
|
|
502
|
+
if LEGACY_AUTHOR_MARK in head_text:
|
|
503
|
+
return HEADER_PRESENT_MALFORMED
|
|
504
|
+
return HEADER_ABSENT
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def walk_root(root: Path) -> tuple[list[FileRecord], list[str]]:
|
|
508
|
+
"""Walk the working tree and emit a record per file plus the skipped dirs."""
|
|
509
|
+
records: list[FileRecord] = []
|
|
510
|
+
skipped: list[str] = []
|
|
511
|
+
|
|
512
|
+
for current, dirnames, filenames in _ordered_walk(root):
|
|
513
|
+
original = list(dirnames)
|
|
514
|
+
dirnames[:] = [name for name in dirnames if name not in SKIPPED_DIRS]
|
|
515
|
+
for removed in original:
|
|
516
|
+
if removed in SKIPPED_DIRS:
|
|
517
|
+
rel = (current / removed).relative_to(root).as_posix()
|
|
518
|
+
skipped.append(rel)
|
|
519
|
+
|
|
520
|
+
for name in sorted(filenames):
|
|
521
|
+
absolute = current / name
|
|
522
|
+
if not absolute.is_file():
|
|
523
|
+
continue
|
|
524
|
+
try:
|
|
525
|
+
relative = absolute.relative_to(root)
|
|
526
|
+
except ValueError:
|
|
527
|
+
continue
|
|
528
|
+
|
|
529
|
+
try:
|
|
530
|
+
stat = absolute.stat()
|
|
531
|
+
except OSError:
|
|
532
|
+
continue
|
|
533
|
+
|
|
534
|
+
file_class = classify_file(relative)
|
|
535
|
+
variant = header_variant_for(relative)
|
|
536
|
+
try:
|
|
537
|
+
sha256 = compute_sha256(absolute)
|
|
538
|
+
except OSError:
|
|
539
|
+
continue
|
|
540
|
+
|
|
541
|
+
line_count = None if is_binary_file(relative) else count_lines(absolute)
|
|
542
|
+
header_status = scan_banner(absolute, variant)
|
|
543
|
+
mtime = datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc).isoformat()
|
|
544
|
+
|
|
545
|
+
records.append(
|
|
546
|
+
FileRecord(
|
|
547
|
+
path=relative.as_posix(),
|
|
548
|
+
size=stat.st_size,
|
|
549
|
+
mtime=mtime,
|
|
550
|
+
sha256=sha256,
|
|
551
|
+
line_count=line_count,
|
|
552
|
+
file_class=file_class,
|
|
553
|
+
header_status=header_status,
|
|
554
|
+
header_variant=variant,
|
|
555
|
+
)
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
return records, skipped
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def _ordered_walk(
|
|
562
|
+
root: Path,
|
|
563
|
+
) -> Iterator[tuple[Path, list[str], list[str]]]:
|
|
564
|
+
"""Deterministic os.walk-equivalent — sorts directory names in place."""
|
|
565
|
+
import os
|
|
566
|
+
|
|
567
|
+
for current, dirnames, filenames in os.walk(root):
|
|
568
|
+
dirnames.sort()
|
|
569
|
+
yield Path(current), dirnames, filenames
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def aggregate_stats(records: list[FileRecord], skipped: list[str]) -> InventoryStats:
|
|
573
|
+
"""Build the stats block emitted alongside the per-file array."""
|
|
574
|
+
stats = InventoryStats(skipped_directories=sorted(set(skipped)))
|
|
575
|
+
stats.total_files = len(records)
|
|
576
|
+
for cls in ALL_CLASSES:
|
|
577
|
+
stats.by_class[cls] = 0
|
|
578
|
+
for status in ALL_HEADER_STATUSES:
|
|
579
|
+
stats.by_header_status[status] = 0
|
|
580
|
+
for record in records:
|
|
581
|
+
stats.by_class[record.file_class] = stats.by_class.get(record.file_class, 0) + 1
|
|
582
|
+
stats.by_header_status[record.header_status] = (
|
|
583
|
+
stats.by_header_status.get(record.header_status, 0) + 1
|
|
584
|
+
)
|
|
585
|
+
stats.by_header_variant[record.header_variant] = (
|
|
586
|
+
stats.by_header_variant.get(record.header_variant, 0) + 1
|
|
587
|
+
)
|
|
588
|
+
return stats
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def emit_inventory(
|
|
592
|
+
root: Path, records: list[FileRecord], stats: InventoryStats, output: Path
|
|
593
|
+
) -> None:
|
|
594
|
+
"""Serialise the inventory to ``output`` as pretty-printed JSON."""
|
|
595
|
+
payload = {
|
|
596
|
+
"root": str(root),
|
|
597
|
+
"generated-at": datetime.now(tz=timezone.utc).isoformat(),
|
|
598
|
+
"total-files": stats.total_files,
|
|
599
|
+
"by-class": stats.by_class,
|
|
600
|
+
"by-header-status": stats.by_header_status,
|
|
601
|
+
"by-header-variant": stats.by_header_variant,
|
|
602
|
+
"skipped-directories": stats.skipped_directories,
|
|
603
|
+
"files": [record.to_json() for record in records],
|
|
604
|
+
}
|
|
605
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
606
|
+
with output.open("w", encoding="utf-8") as handle:
|
|
607
|
+
json.dump(payload, handle, indent=2, sort_keys=False, ensure_ascii=False)
|
|
608
|
+
handle.write("\n")
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def parse_arguments(argv: list[str]) -> argparse.Namespace:
|
|
612
|
+
"""CLI surface — root and output are required."""
|
|
613
|
+
parser = argparse.ArgumentParser(
|
|
614
|
+
prog="build_inventory",
|
|
615
|
+
description="Walk an ecosystem root and emit inventory.json.",
|
|
616
|
+
)
|
|
617
|
+
parser.add_argument(
|
|
618
|
+
"--root",
|
|
619
|
+
type=Path,
|
|
620
|
+
required=True,
|
|
621
|
+
help="Root directory to inventory (e.g., the working tree).",
|
|
622
|
+
)
|
|
623
|
+
parser.add_argument(
|
|
624
|
+
"--output",
|
|
625
|
+
type=Path,
|
|
626
|
+
required=True,
|
|
627
|
+
help="Output JSON path (e.g., .audit/inventory.json).",
|
|
628
|
+
)
|
|
629
|
+
return parser.parse_args(argv)
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
def main(argv: list[str]) -> int:
|
|
633
|
+
"""Entry point — returns the exit code."""
|
|
634
|
+
args = parse_arguments(argv)
|
|
635
|
+
root = args.root.resolve()
|
|
636
|
+
output = args.output.resolve()
|
|
637
|
+
|
|
638
|
+
if not root.is_dir():
|
|
639
|
+
print(f"error: root is not a directory: {root}", file=sys.stderr)
|
|
640
|
+
return EXIT_ERROR
|
|
641
|
+
|
|
642
|
+
records, skipped = walk_root(root)
|
|
643
|
+
stats = aggregate_stats(records, skipped)
|
|
644
|
+
emit_inventory(root, records, stats, output)
|
|
645
|
+
|
|
646
|
+
unknown_count = stats.by_class.get(CLASS_UNKNOWN, 0)
|
|
647
|
+
unknown_pct = (unknown_count / stats.total_files * 100) if stats.total_files else 0
|
|
648
|
+
print(
|
|
649
|
+
f"inventory: {stats.total_files} files; "
|
|
650
|
+
f"unknown={unknown_count} ({unknown_pct:.1f}%); "
|
|
651
|
+
f"output={output}"
|
|
652
|
+
)
|
|
653
|
+
return EXIT_OK
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
if __name__ == "__main__":
|
|
657
|
+
raise SystemExit(main(sys.argv[1:]))
|