@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.
Files changed (674) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/LICENSE +21 -0
  3. package/LICENSES/MIT.txt +18 -0
  4. package/LICENSES/PSF-2.0.txt +47 -0
  5. package/README.md +549 -0
  6. package/bin/README.md +37 -0
  7. package/bin/apothem.mjs +78 -0
  8. package/package.json +75 -0
  9. package/pyproject.toml +347 -0
  10. package/src/apothem/README.md +52 -0
  11. package/src/apothem/__init__.py +66 -0
  12. package/src/apothem/__main__.py +28 -0
  13. package/src/apothem/_vendor/.keep +0 -0
  14. package/src/apothem/_vendor/__init__.py +25 -0
  15. package/src/apothem/_vendor/attr/__init__.py +104 -0
  16. package/src/apothem/_vendor/attr/__init__.pyi +389 -0
  17. package/src/apothem/_vendor/attr/_cmp.py +160 -0
  18. package/src/apothem/_vendor/attr/_cmp.pyi +13 -0
  19. package/src/apothem/_vendor/attr/_compat.py +99 -0
  20. package/src/apothem/_vendor/attr/_config.py +31 -0
  21. package/src/apothem/_vendor/attr/_funcs.py +497 -0
  22. package/src/apothem/_vendor/attr/_make.py +3406 -0
  23. package/src/apothem/_vendor/attr/_next_gen.py +674 -0
  24. package/src/apothem/_vendor/attr/_typing_compat.pyi +15 -0
  25. package/src/apothem/_vendor/attr/_version_info.py +89 -0
  26. package/src/apothem/_vendor/attr/_version_info.pyi +9 -0
  27. package/src/apothem/_vendor/attr/converters.py +162 -0
  28. package/src/apothem/_vendor/attr/converters.pyi +19 -0
  29. package/src/apothem/_vendor/attr/exceptions.py +95 -0
  30. package/src/apothem/_vendor/attr/exceptions.pyi +17 -0
  31. package/src/apothem/_vendor/attr/filters.py +72 -0
  32. package/src/apothem/_vendor/attr/filters.pyi +6 -0
  33. package/src/apothem/_vendor/attr/py.typed +0 -0
  34. package/src/apothem/_vendor/attr/setters.py +79 -0
  35. package/src/apothem/_vendor/attr/setters.pyi +20 -0
  36. package/src/apothem/_vendor/attr/validators.py +750 -0
  37. package/src/apothem/_vendor/attr/validators.pyi +140 -0
  38. package/src/apothem/_vendor/attr.LICENSE +21 -0
  39. package/src/apothem/_vendor/attrs/__init__.py +72 -0
  40. package/src/apothem/_vendor/attrs/__init__.pyi +314 -0
  41. package/src/apothem/_vendor/attrs/converters.py +3 -0
  42. package/src/apothem/_vendor/attrs/exceptions.py +3 -0
  43. package/src/apothem/_vendor/attrs/filters.py +3 -0
  44. package/src/apothem/_vendor/attrs/py.typed +0 -0
  45. package/src/apothem/_vendor/attrs/setters.py +3 -0
  46. package/src/apothem/_vendor/attrs/validators.py +3 -0
  47. package/src/apothem/_vendor/attrs.LICENSE +21 -0
  48. package/src/apothem/_vendor/jsonschema/__init__.py +120 -0
  49. package/src/apothem/_vendor/jsonschema/__main__.py +6 -0
  50. package/src/apothem/_vendor/jsonschema/_format.py +546 -0
  51. package/src/apothem/_vendor/jsonschema/_keywords.py +449 -0
  52. package/src/apothem/_vendor/jsonschema/_legacy_keywords.py +449 -0
  53. package/src/apothem/_vendor/jsonschema/_types.py +204 -0
  54. package/src/apothem/_vendor/jsonschema/_typing.py +29 -0
  55. package/src/apothem/_vendor/jsonschema/_utils.py +355 -0
  56. package/src/apothem/_vendor/jsonschema/benchmarks/__init__.py +5 -0
  57. package/src/apothem/_vendor/jsonschema/benchmarks/const_vs_enum.py +30 -0
  58. package/src/apothem/_vendor/jsonschema/benchmarks/contains.py +28 -0
  59. package/src/apothem/_vendor/jsonschema/benchmarks/import_benchmark.py +31 -0
  60. package/src/apothem/_vendor/jsonschema/benchmarks/issue232/issue.json +2653 -0
  61. package/src/apothem/_vendor/jsonschema/benchmarks/issue232.py +25 -0
  62. package/src/apothem/_vendor/jsonschema/benchmarks/json_schema_test_suite.py +12 -0
  63. package/src/apothem/_vendor/jsonschema/benchmarks/nested_schemas.py +56 -0
  64. package/src/apothem/_vendor/jsonschema/benchmarks/subcomponents.py +42 -0
  65. package/src/apothem/_vendor/jsonschema/benchmarks/unused_registry.py +35 -0
  66. package/src/apothem/_vendor/jsonschema/benchmarks/useless_applicator_schemas.py +106 -0
  67. package/src/apothem/_vendor/jsonschema/benchmarks/useless_keywords.py +32 -0
  68. package/src/apothem/_vendor/jsonschema/benchmarks/validator_creation.py +14 -0
  69. package/src/apothem/_vendor/jsonschema/cli.py +292 -0
  70. package/src/apothem/_vendor/jsonschema/exceptions.py +490 -0
  71. package/src/apothem/_vendor/jsonschema/protocols.py +230 -0
  72. package/src/apothem/_vendor/jsonschema/validators.py +1410 -0
  73. package/src/apothem/_vendor/jsonschema.LICENSE +19 -0
  74. package/src/apothem/_vendor/jsonschema_specifications/__init__.py +12 -0
  75. package/src/apothem/_vendor/jsonschema_specifications/_core.py +38 -0
  76. package/src/apothem/_vendor/jsonschema_specifications/schemas/draft201909/metaschema.json +42 -0
  77. package/src/apothem/_vendor/jsonschema_specifications/schemas/draft201909/vocabularies/applicator +56 -0
  78. package/src/apothem/_vendor/jsonschema_specifications/schemas/draft201909/vocabularies/content +17 -0
  79. package/src/apothem/_vendor/jsonschema_specifications/schemas/draft201909/vocabularies/core +57 -0
  80. package/src/apothem/_vendor/jsonschema_specifications/schemas/draft201909/vocabularies/format +14 -0
  81. package/src/apothem/_vendor/jsonschema_specifications/schemas/draft201909/vocabularies/meta-data +37 -0
  82. package/src/apothem/_vendor/jsonschema_specifications/schemas/draft201909/vocabularies/validation +98 -0
  83. package/src/apothem/_vendor/jsonschema_specifications/schemas/draft202012/metaschema.json +58 -0
  84. package/src/apothem/_vendor/jsonschema_specifications/schemas/draft202012/vocabularies/applicator +48 -0
  85. package/src/apothem/_vendor/jsonschema_specifications/schemas/draft202012/vocabularies/content +17 -0
  86. package/src/apothem/_vendor/jsonschema_specifications/schemas/draft202012/vocabularies/core +51 -0
  87. package/src/apothem/_vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-annotation +14 -0
  88. package/src/apothem/_vendor/jsonschema_specifications/schemas/draft202012/vocabularies/format-assertion +14 -0
  89. package/src/apothem/_vendor/jsonschema_specifications/schemas/draft202012/vocabularies/meta-data +37 -0
  90. package/src/apothem/_vendor/jsonschema_specifications/schemas/draft202012/vocabularies/unevaluated +15 -0
  91. package/src/apothem/_vendor/jsonschema_specifications/schemas/draft202012/vocabularies/validation +98 -0
  92. package/src/apothem/_vendor/jsonschema_specifications/schemas/draft3/metaschema.json +172 -0
  93. package/src/apothem/_vendor/jsonschema_specifications/schemas/draft4/metaschema.json +149 -0
  94. package/src/apothem/_vendor/jsonschema_specifications/schemas/draft6/metaschema.json +153 -0
  95. package/src/apothem/_vendor/jsonschema_specifications/schemas/draft7/metaschema.json +166 -0
  96. package/src/apothem/_vendor/jsonschema_specifications.LICENSE +19 -0
  97. package/src/apothem/_vendor/referencing/__init__.py +7 -0
  98. package/src/apothem/_vendor/referencing/_attrs.py +31 -0
  99. package/src/apothem/_vendor/referencing/_attrs.pyi +21 -0
  100. package/src/apothem/_vendor/referencing/_core.py +739 -0
  101. package/src/apothem/_vendor/referencing/exceptions.py +165 -0
  102. package/src/apothem/_vendor/referencing/jsonschema.py +642 -0
  103. package/src/apothem/_vendor/referencing/py.typed +0 -0
  104. package/src/apothem/_vendor/referencing/retrieval.py +94 -0
  105. package/src/apothem/_vendor/referencing/typing.py +61 -0
  106. package/src/apothem/_vendor/referencing.LICENSE +19 -0
  107. package/src/apothem/_vendor/rpds/__init__.py +251 -0
  108. package/src/apothem/_vendor/typing_extensions.LICENSE +279 -0
  109. package/src/apothem/_vendor/typing_extensions.py +4317 -0
  110. package/src/apothem/_vendor/vendor.txt +22 -0
  111. package/src/apothem/_vendor/yaml/__init__.py +389 -0
  112. package/src/apothem/_vendor/yaml/composer.py +138 -0
  113. package/src/apothem/_vendor/yaml/constructor.py +748 -0
  114. package/src/apothem/_vendor/yaml/cyaml.py +100 -0
  115. package/src/apothem/_vendor/yaml/dumper.py +61 -0
  116. package/src/apothem/_vendor/yaml/emitter.py +1137 -0
  117. package/src/apothem/_vendor/yaml/error.py +74 -0
  118. package/src/apothem/_vendor/yaml/events.py +85 -0
  119. package/src/apothem/_vendor/yaml/loader.py +63 -0
  120. package/src/apothem/_vendor/yaml/nodes.py +48 -0
  121. package/src/apothem/_vendor/yaml/parser.py +588 -0
  122. package/src/apothem/_vendor/yaml/reader.py +185 -0
  123. package/src/apothem/_vendor/yaml/representer.py +388 -0
  124. package/src/apothem/_vendor/yaml/resolver.py +226 -0
  125. package/src/apothem/_vendor/yaml/scanner.py +1435 -0
  126. package/src/apothem/_vendor/yaml/serializer.py +110 -0
  127. package/src/apothem/_vendor/yaml/tokens.py +103 -0
  128. package/src/apothem/_vendor/yaml.LICENSE +20 -0
  129. package/src/apothem/agents/README.md +60 -0
  130. package/src/apothem/agents/codebase-explorer.md +91 -0
  131. package/src/apothem/agents/convention-auditor.md +93 -0
  132. package/src/apothem/agents/dependency-auditor.md +97 -0
  133. package/src/apothem/agents/fact-checker.md +84 -0
  134. package/src/apothem/agents/mcp-builder.md +86 -0
  135. package/src/apothem/agents/memory-auditor.md +93 -0
  136. package/src/apothem/agents/prompt-evaluator.md +87 -0
  137. package/src/apothem/agents/quality-gate.md +103 -0
  138. package/src/apothem/agents/refactor-surgeon.md +74 -0
  139. package/src/apothem/agents/research-scout.md +73 -0
  140. package/src/apothem/agents/security-scanner.md +83 -0
  141. package/src/apothem/agents/test-runner.md +84 -0
  142. package/src/apothem/audit/README.md +73 -0
  143. package/src/apothem/audit/_scan_lib.py +182 -0
  144. package/src/apothem/audit/analyze_graph.py +260 -0
  145. package/src/apothem/audit/build_capability_graph.py +607 -0
  146. package/src/apothem/audit/build_inventory.py +657 -0
  147. package/src/apothem/audit/build_plans_provenance.py +997 -0
  148. package/src/apothem/audit/check_links.py +389 -0
  149. package/src/apothem/audit/classify_artifacts.py +381 -0
  150. package/src/apothem/audit/deprecated-tokens.txt +10 -0
  151. package/src/apothem/audit/execute_plans_migration.py +491 -0
  152. package/src/apothem/audit/known-projects.txt +15 -0
  153. package/src/apothem/audit/render_capability_index.py +467 -0
  154. package/src/apothem/audit/render_inventory.py +405 -0
  155. package/src/apothem/audit/scan_ai_surfaces.py +1125 -0
  156. package/src/apothem/audit/scan_ai_surfaces_coarse.py +261 -0
  157. package/src/apothem/audit/scan_drift_features.py +143 -0
  158. package/src/apothem/audit/scan_frontmatter.py +293 -0
  159. package/src/apothem/audit/scan_header_coverage.py +1134 -0
  160. package/src/apothem/audit/scan_plan_leakage.py +540 -0
  161. package/src/apothem/audit/scan_plans_discipline.py +188 -0
  162. package/src/apothem/audit/scan_secrets_pii.py +245 -0
  163. package/src/apothem/audit/scan_stale_tokens.py +296 -0
  164. package/src/apothem/audit/synthesize_drift.py +205 -0
  165. package/src/apothem/benchmarks/README.md +33 -0
  166. package/src/apothem/benchmarks/__init__.py +3 -0
  167. package/src/apothem/benchmarks/bench_agents.py +63 -0
  168. package/src/apothem/benchmarks/bench_hooks.py +93 -0
  169. package/src/apothem/benchmarks/bench_install.py +58 -0
  170. package/src/apothem/benchmarks/bench_tests.py +93 -0
  171. package/src/apothem/benchmarks/bench_validate_ecosystem.py +84 -0
  172. package/src/apothem/cli/README.md +33 -0
  173. package/src/apothem/cli/__init__.py +229 -0
  174. package/src/apothem/cli/_cmd_completion.py +88 -0
  175. package/src/apothem/cli/_cmd_diff.py +181 -0
  176. package/src/apothem/cli/_cmd_doctor.py +143 -0
  177. package/src/apothem/cli/_cmd_harnesses.py +167 -0
  178. package/src/apothem/cli/_cmd_install.py +327 -0
  179. package/src/apothem/cli/_cmd_migrate_workspace.py +143 -0
  180. package/src/apothem/cli/_cmd_profile.py +341 -0
  181. package/src/apothem/cli/_cmd_status.py +180 -0
  182. package/src/apothem/cli/_cmd_uninstall.py +215 -0
  183. package/src/apothem/cli/_cmd_update.py +397 -0
  184. package/src/apothem/cli/_cmd_verify.py +194 -0
  185. package/src/apothem/cli/_common_flags.py +90 -0
  186. package/src/apothem/cli/_epilogs.py +296 -0
  187. package/src/apothem/cli/_helpers.py +857 -0
  188. package/src/apothem/cli/_json_formatter.py +21 -0
  189. package/src/apothem/cli/_materialize.py +376 -0
  190. package/src/apothem/cli/completions/apothem.bash +30 -0
  191. package/src/apothem/cli/completions/apothem.fish +19 -0
  192. package/src/apothem/cli/completions/apothem.ps1 +27 -0
  193. package/src/apothem/cli/completions/apothem.zsh +42 -0
  194. package/src/apothem/cli/reference_export.py +126 -0
  195. package/src/apothem/commands/README.md +125 -0
  196. package/src/apothem/commands/a11y-audit.md +203 -0
  197. package/src/apothem/commands/architecture-review.md +194 -0
  198. package/src/apothem/commands/audit.md +165 -0
  199. package/src/apothem/commands/code-audit.md +218 -0
  200. package/src/apothem/commands/code-review.md +193 -0
  201. package/src/apothem/commands/dependency-audit.md +209 -0
  202. package/src/apothem/commands/docs-review.md +199 -0
  203. package/src/apothem/commands/elevate.md +285 -0
  204. package/src/apothem/commands/eval.md +149 -0
  205. package/src/apothem/commands/fortress.md +172 -0
  206. package/src/apothem/commands/freshify.md +168 -0
  207. package/src/apothem/commands/github-deploy-fresh.md +178 -0
  208. package/src/apothem/commands/github-deploy-next.md +167 -0
  209. package/src/apothem/commands/perf-audit.md +198 -0
  210. package/src/apothem/commands/plan-amend.md +104 -0
  211. package/src/apothem/commands/plan-audit.md +127 -0
  212. package/src/apothem/commands/plan-design.md +257 -0
  213. package/src/apothem/commands/plan-execute.md +495 -0
  214. package/src/apothem/commands/plan-generate.md +351 -0
  215. package/src/apothem/commands/plan-review.md +555 -0
  216. package/src/apothem/commands/plan-spec.md +359 -0
  217. package/src/apothem/commands/plan-status.md +222 -0
  218. package/src/apothem/commands/plan.md +173 -0
  219. package/src/apothem/commands/projectify.md +142 -0
  220. package/src/apothem/commands/release-readiness.md +142 -0
  221. package/src/apothem/commands/research-analysis.md +241 -0
  222. package/src/apothem/commands/research-design.md +231 -0
  223. package/src/apothem/commands/research-disseminate.md +225 -0
  224. package/src/apothem/commands/research-experiment.md +232 -0
  225. package/src/apothem/commands/research-ideate.md +213 -0
  226. package/src/apothem/commands/research-paper.md +252 -0
  227. package/src/apothem/commands/research-proposal.md +220 -0
  228. package/src/apothem/commands/research-publish.md +255 -0
  229. package/src/apothem/commands/research-review.md +251 -0
  230. package/src/apothem/commands/research-sources.md +266 -0
  231. package/src/apothem/commands/research-spec.md +255 -0
  232. package/src/apothem/commands/research-synthesis.md +233 -0
  233. package/src/apothem/commands/research-theory.md +218 -0
  234. package/src/apothem/commands/research.md +181 -0
  235. package/src/apothem/commands/security-audit.md +196 -0
  236. package/src/apothem/commands/supply-chain-audit.md +192 -0
  237. package/src/apothem/commands/test-suite.md +146 -0
  238. package/src/apothem/commands/threat-model-audit.md +199 -0
  239. package/src/apothem/commands/ux-review.md +202 -0
  240. package/src/apothem/commands/workflow.md +162 -0
  241. package/src/apothem/conformity/README.md +173 -0
  242. package/src/apothem/conformity/__init__.py +1 -0
  243. package/src/apothem/conformity/_grep_base.py +93 -0
  244. package/src/apothem/conformity/agent_capability_grep.py +306 -0
  245. package/src/apothem/conformity/agents_md_coverage_grep.py +382 -0
  246. package/src/apothem/conformity/agnosticism_grep.py +311 -0
  247. package/src/apothem/conformity/always_on_budget_grep.py +318 -0
  248. package/src/apothem/conformity/bare_except_grep.py +115 -0
  249. package/src/apothem/conformity/binding_reciprocity_grep.py +151 -0
  250. package/src/apothem/conformity/brand_mark_grep.py +272 -0
  251. package/src/apothem/conformity/commented_out_code_grep.py +176 -0
  252. package/src/apothem/conformity/completion_claim_grep.py +169 -0
  253. package/src/apothem/conformity/conventional_commit_grep.py +319 -0
  254. package/src/apothem/conformity/copilot_instructions_presence_grep.py +324 -0
  255. package/src/apothem/conformity/cross_platform_matrix_grep.py +297 -0
  256. package/src/apothem/conformity/determinism_grep.py +306 -0
  257. package/src/apothem/conformity/diagram_staleness_grep.py +154 -0
  258. package/src/apothem/conformity/dynamism_grep.py +284 -0
  259. package/src/apothem/conformity/editorconfig_presence_grep.py +281 -0
  260. package/src/apothem/conformity/file_header_grep.py +502 -0
  261. package/src/apothem/conformity/freshness_token_grep.py +233 -0
  262. package/src/apothem/conformity/frontmatter_grep.py +274 -0
  263. package/src/apothem/conformity/frontmatter_value_grep.py +386 -0
  264. package/src/apothem/conformity/gate.py +1386 -0
  265. package/src/apothem/conformity/gitattributes_presence_grep.py +238 -0
  266. package/src/apothem/conformity/harden_runner_grep.py +320 -0
  267. package/src/apothem/conformity/hedging_grep.py +129 -0
  268. package/src/apothem/conformity/license_author_consistency_grep.py +204 -0
  269. package/src/apothem/conformity/link_check.py +327 -0
  270. package/src/apothem/conformity/magic_number_grep.py +182 -0
  271. package/src/apothem/conformity/multi_surface_coherence_grep.py +620 -0
  272. package/src/apothem/conformity/naming_grep.py +224 -0
  273. package/src/apothem/conformity/no_global_plans_grep.py +339 -0
  274. package/src/apothem/conformity/no_toplevel_docs_grep.py +120 -0
  275. package/src/apothem/conformity/oidc_trusted_publishing_grep.py +291 -0
  276. package/src/apothem/conformity/option_annotation_grep.py +352 -0
  277. package/src/apothem/conformity/orphan_output_grep.py +206 -0
  278. package/src/apothem/conformity/permissions_minimum_scope_grep.py +299 -0
  279. package/src/apothem/conformity/plain_language_grep.py +559 -0
  280. package/src/apothem/conformity/plan_next_step_consistency_grep.py +450 -0
  281. package/src/apothem/conformity/plan_suite_structure_grep.py +534 -0
  282. package/src/apothem/conformity/plans_discipline_language_grep.py +245 -0
  283. package/src/apothem/conformity/production_ready_pr_grep.py +200 -0
  284. package/src/apothem/conformity/recommend_next_step_grep.py +250 -0
  285. package/src/apothem/conformity/redundancy_grep.py +401 -0
  286. package/src/apothem/conformity/reference_token_grep.py +230 -0
  287. package/src/apothem/conformity/registry_capability_consistency_grep.py +368 -0
  288. package/src/apothem/conformity/secret_leak_grep.py +193 -0
  289. package/src/apothem/conformity/semver_stability_grep.py +358 -0
  290. package/src/apothem/conformity/smoke_install_grep.py +194 -0
  291. package/src/apothem/conformity/static_version_grep.py +284 -0
  292. package/src/apothem/conformity/token_efficiency_grep.py +185 -0
  293. package/src/apothem/conformity/unpinned_action_grep.py +115 -0
  294. package/src/apothem/conformity/user_confirm_grep.py +74 -0
  295. package/src/apothem/conformity/workflow_concurrency_grep.py +283 -0
  296. package/src/apothem/harnesses/README.md +63 -0
  297. package/src/apothem/harnesses/__init__.py +16 -0
  298. package/src/apothem/harnesses/_shared/README.md +36 -0
  299. package/src/apothem/harnesses/_shared/__init__.py +12 -0
  300. package/src/apothem/harnesses/_shared/install_driver.py +281 -0
  301. package/src/apothem/harnesses/_shared/install_driver_apply.py +612 -0
  302. package/src/apothem/harnesses/_shared/install_driver_backup.py +535 -0
  303. package/src/apothem/harnesses/_shared/install_driver_converters.py +310 -0
  304. package/src/apothem/harnesses/_shared/install_driver_lifecycle.py +495 -0
  305. package/src/apothem/harnesses/_shared/install_driver_materialize.py +675 -0
  306. package/src/apothem/harnesses/_shared/install_driver_merge.py +656 -0
  307. package/src/apothem/harnesses/_shared/install_driver_pathsafety.py +137 -0
  308. package/src/apothem/harnesses/_shared/install_driver_planvalidation.py +240 -0
  309. package/src/apothem/harnesses/_shared/install_driver_removal.py +366 -0
  310. package/src/apothem/harnesses/_shared/install_driver_treeops.py +248 -0
  311. package/src/apothem/harnesses/_shared/install_driver_types.py +330 -0
  312. package/src/apothem/harnesses/_shared/wrapper_factories.py +448 -0
  313. package/src/apothem/harnesses/antigravity/STANDARD-CONVENTION-PIN.md +91 -0
  314. package/src/apothem/harnesses/antigravity/__init__.py +70 -0
  315. package/src/apothem/harnesses/antigravity/capabilities.yml +40 -0
  316. package/src/apothem/harnesses/antigravity/install.py +63 -0
  317. package/src/apothem/harnesses/antigravity/templates/GEMINI.md +40 -0
  318. package/src/apothem/harnesses/antigravity/templates/plugin.json +5 -0
  319. package/src/apothem/harnesses/antigravity/uninstall.py +22 -0
  320. package/src/apothem/harnesses/antigravity/update.py +10 -0
  321. package/src/apothem/harnesses/antigravity/verify.py +11 -0
  322. package/src/apothem/harnesses/claude_code/STANDARD-CONVENTION-PIN.md +65 -0
  323. package/src/apothem/harnesses/claude_code/__init__.py +107 -0
  324. package/src/apothem/harnesses/claude_code/capabilities.yml +42 -0
  325. package/src/apothem/harnesses/claude_code/install.py +147 -0
  326. package/src/apothem/harnesses/claude_code/templates/settings.json +351 -0
  327. package/src/apothem/harnesses/claude_code/uninstall.py +23 -0
  328. package/src/apothem/harnesses/claude_code/update.py +10 -0
  329. package/src/apothem/harnesses/claude_code/verify.py +11 -0
  330. package/src/apothem/harnesses/codebuddy/STANDARD-CONVENTION-PIN.md +74 -0
  331. package/src/apothem/harnesses/codebuddy/__init__.py +49 -0
  332. package/src/apothem/harnesses/codebuddy/capabilities.yml +34 -0
  333. package/src/apothem/harnesses/codebuddy/install.py +40 -0
  334. package/src/apothem/harnesses/codebuddy/templates/apothem-rules.md +37 -0
  335. package/src/apothem/harnesses/codebuddy/uninstall.py +25 -0
  336. package/src/apothem/harnesses/codebuddy/update.py +10 -0
  337. package/src/apothem/harnesses/codebuddy/verify.py +11 -0
  338. package/src/apothem/harnesses/codex/STANDARD-CONVENTION-PIN.md +79 -0
  339. package/src/apothem/harnesses/codex/__init__.py +72 -0
  340. package/src/apothem/harnesses/codex/capabilities.yml +40 -0
  341. package/src/apothem/harnesses/codex/install.py +69 -0
  342. package/src/apothem/harnesses/codex/templates/AGENTS.md +40 -0
  343. package/src/apothem/harnesses/codex/templates/hooks.json +127 -0
  344. package/src/apothem/harnesses/codex/uninstall.py +23 -0
  345. package/src/apothem/harnesses/codex/update.py +10 -0
  346. package/src/apothem/harnesses/codex/verify.py +11 -0
  347. package/src/apothem/harnesses/cursor/STANDARD-CONVENTION-PIN.md +79 -0
  348. package/src/apothem/harnesses/cursor/__init__.py +48 -0
  349. package/src/apothem/harnesses/cursor/capabilities.yml +42 -0
  350. package/src/apothem/harnesses/cursor/install.py +38 -0
  351. package/src/apothem/harnesses/cursor/templates/apothem-rules.mdc +40 -0
  352. package/src/apothem/harnesses/cursor/uninstall.py +25 -0
  353. package/src/apothem/harnesses/cursor/update.py +10 -0
  354. package/src/apothem/harnesses/cursor/verify.py +11 -0
  355. package/src/apothem/harnesses/gemini_cli/STANDARD-CONVENTION-PIN.md +102 -0
  356. package/src/apothem/harnesses/gemini_cli/__init__.py +52 -0
  357. package/src/apothem/harnesses/gemini_cli/capabilities.yml +43 -0
  358. package/src/apothem/harnesses/gemini_cli/install.py +43 -0
  359. package/src/apothem/harnesses/gemini_cli/templates/GEMINI.md +38 -0
  360. package/src/apothem/harnesses/gemini_cli/uninstall.py +25 -0
  361. package/src/apothem/harnesses/gemini_cli/update.py +10 -0
  362. package/src/apothem/harnesses/gemini_cli/verify.py +11 -0
  363. package/src/apothem/harnesses/github_copilot/STANDARD-CONVENTION-PIN.md +84 -0
  364. package/src/apothem/harnesses/github_copilot/__init__.py +47 -0
  365. package/src/apothem/harnesses/github_copilot/capabilities.yml +42 -0
  366. package/src/apothem/harnesses/github_copilot/install.py +40 -0
  367. package/src/apothem/harnesses/github_copilot/templates/copilot-instructions.md +33 -0
  368. package/src/apothem/harnesses/github_copilot/uninstall.py +25 -0
  369. package/src/apothem/harnesses/github_copilot/update.py +10 -0
  370. package/src/apothem/harnesses/github_copilot/verify.py +11 -0
  371. package/src/apothem/harnesses/glm/STANDARD-CONVENTION-PIN.md +77 -0
  372. package/src/apothem/harnesses/glm/__init__.py +56 -0
  373. package/src/apothem/harnesses/glm/capabilities.yml +33 -0
  374. package/src/apothem/harnesses/glm/install.py +45 -0
  375. package/src/apothem/harnesses/glm/templates/glm.toml +58 -0
  376. package/src/apothem/harnesses/glm/uninstall.py +25 -0
  377. package/src/apothem/harnesses/glm/update.py +10 -0
  378. package/src/apothem/harnesses/glm/verify.py +11 -0
  379. package/src/apothem/harnesses/hermes/STANDARD-CONVENTION-PIN.md +57 -0
  380. package/src/apothem/harnesses/hermes/__init__.py +33 -0
  381. package/src/apothem/harnesses/hermes/capabilities.yml +36 -0
  382. package/src/apothem/harnesses/hermes/install.py +17 -0
  383. package/src/apothem/harnesses/hermes/materializer.py +35 -0
  384. package/src/apothem/harnesses/hermes/uninstall.py +33 -0
  385. package/src/apothem/harnesses/hermes/update.py +10 -0
  386. package/src/apothem/harnesses/hermes/verify.py +11 -0
  387. package/src/apothem/harnesses/kimi_code/STANDARD-CONVENTION-PIN.md +128 -0
  388. package/src/apothem/harnesses/kimi_code/__init__.py +59 -0
  389. package/src/apothem/harnesses/kimi_code/capabilities.yml +40 -0
  390. package/src/apothem/harnesses/kimi_code/install.py +42 -0
  391. package/src/apothem/harnesses/kimi_code/templates/AGENTS.md +43 -0
  392. package/src/apothem/harnesses/kimi_code/uninstall.py +27 -0
  393. package/src/apothem/harnesses/kimi_code/update.py +10 -0
  394. package/src/apothem/harnesses/kimi_code/verify.py +11 -0
  395. package/src/apothem/harnesses/kiro/STANDARD-CONVENTION-PIN.md +77 -0
  396. package/src/apothem/harnesses/kiro/__init__.py +49 -0
  397. package/src/apothem/harnesses/kiro/capabilities.yml +36 -0
  398. package/src/apothem/harnesses/kiro/install.py +39 -0
  399. package/src/apothem/harnesses/kiro/templates/apothem-rules.md +36 -0
  400. package/src/apothem/harnesses/kiro/uninstall.py +25 -0
  401. package/src/apothem/harnesses/kiro/update.py +10 -0
  402. package/src/apothem/harnesses/kiro/verify.py +11 -0
  403. package/src/apothem/harnesses/open_claw/STANDARD-CONVENTION-PIN.md +62 -0
  404. package/src/apothem/harnesses/open_claw/__init__.py +35 -0
  405. package/src/apothem/harnesses/open_claw/capabilities.yml +35 -0
  406. package/src/apothem/harnesses/open_claw/install.py +17 -0
  407. package/src/apothem/harnesses/open_claw/materializer.py +36 -0
  408. package/src/apothem/harnesses/open_claw/uninstall.py +32 -0
  409. package/src/apothem/harnesses/open_claw/update.py +10 -0
  410. package/src/apothem/harnesses/open_claw/verify.py +11 -0
  411. package/src/apothem/harnesses/opencode/STANDARD-CONVENTION-PIN.md +76 -0
  412. package/src/apothem/harnesses/opencode/__init__.py +35 -0
  413. package/src/apothem/harnesses/opencode/capabilities.yml +43 -0
  414. package/src/apothem/harnesses/opencode/install.py +17 -0
  415. package/src/apothem/harnesses/opencode/materializer.py +31 -0
  416. package/src/apothem/harnesses/opencode/uninstall.py +34 -0
  417. package/src/apothem/harnesses/opencode/update.py +10 -0
  418. package/src/apothem/harnesses/opencode/verify.py +11 -0
  419. package/src/apothem/harnesses/qwen_code/STANDARD-CONVENTION-PIN.md +87 -0
  420. package/src/apothem/harnesses/qwen_code/__init__.py +37 -0
  421. package/src/apothem/harnesses/qwen_code/capabilities.yml +43 -0
  422. package/src/apothem/harnesses/qwen_code/install.py +19 -0
  423. package/src/apothem/harnesses/qwen_code/materializer.py +174 -0
  424. package/src/apothem/harnesses/qwen_code/templates/QWEN.md +30 -0
  425. package/src/apothem/harnesses/qwen_code/uninstall.py +34 -0
  426. package/src/apothem/harnesses/qwen_code/update.py +10 -0
  427. package/src/apothem/harnesses/qwen_code/verify.py +11 -0
  428. package/src/apothem/harnesses/trae/STANDARD-CONVENTION-PIN.md +70 -0
  429. package/src/apothem/harnesses/trae/__init__.py +49 -0
  430. package/src/apothem/harnesses/trae/capabilities.yml +34 -0
  431. package/src/apothem/harnesses/trae/install.py +38 -0
  432. package/src/apothem/harnesses/trae/templates/apothem-rules.md +37 -0
  433. package/src/apothem/harnesses/trae/uninstall.py +25 -0
  434. package/src/apothem/harnesses/trae/update.py +10 -0
  435. package/src/apothem/harnesses/trae/verify.py +11 -0
  436. package/src/apothem/harnesses/windsurf/STANDARD-CONVENTION-PIN.md +91 -0
  437. package/src/apothem/harnesses/windsurf/__init__.py +52 -0
  438. package/src/apothem/harnesses/windsurf/capabilities.yml +40 -0
  439. package/src/apothem/harnesses/windsurf/install.py +41 -0
  440. package/src/apothem/harnesses/windsurf/templates/apothem-rules.md +37 -0
  441. package/src/apothem/harnesses/windsurf/uninstall.py +25 -0
  442. package/src/apothem/harnesses/windsurf/update.py +10 -0
  443. package/src/apothem/harnesses/windsurf/verify.py +11 -0
  444. package/src/apothem/harnesses/zed/STANDARD-CONVENTION-PIN.md +92 -0
  445. package/src/apothem/harnesses/zed/__init__.py +57 -0
  446. package/src/apothem/harnesses/zed/capabilities.yml +38 -0
  447. package/src/apothem/harnesses/zed/install.py +41 -0
  448. package/src/apothem/harnesses/zed/templates/apothem-rules.md +32 -0
  449. package/src/apothem/harnesses/zed/uninstall.py +28 -0
  450. package/src/apothem/harnesses/zed/update.py +10 -0
  451. package/src/apothem/harnesses/zed/verify.py +11 -0
  452. package/src/apothem/hooks/README.md +81 -0
  453. package/src/apothem/hooks/__init__.py +24 -0
  454. package/src/apothem/hooks/askuserquestion_validator.py +380 -0
  455. package/src/apothem/hooks/dispatch.py +296 -0
  456. package/src/apothem/hooks/emit_hook_context.py +444 -0
  457. package/src/apothem/hooks/hooks.json +318 -0
  458. package/src/apothem/hooks/lib/README.md +39 -0
  459. package/src/apothem/hooks/lib/__init__.py +18 -0
  460. package/src/apothem/hooks/lib/bootstrap.ps1 +129 -0
  461. package/src/apothem/hooks/lib/bootstrap.sh +103 -0
  462. package/src/apothem/hooks/lib/events.py +51 -0
  463. package/src/apothem/hooks/lib/find-pwsh.ps1 +78 -0
  464. package/src/apothem/hooks/lib/find-pwsh.sh +76 -0
  465. package/src/apothem/hooks/lib/find-python.ps1 +63 -0
  466. package/src/apothem/hooks/lib/find-python.sh +97 -0
  467. package/src/apothem/hooks/lib/log.py +43 -0
  468. package/src/apothem/hooks/lib/resolve_root.py +264 -0
  469. package/src/apothem/hooks/messages/postcompact.md +14 -0
  470. package/src/apothem/hooks/messages/posttooluse-proactive-compaction.md +46 -0
  471. package/src/apothem/hooks/messages/precompact.md +14 -0
  472. package/src/apothem/hooks/messages/pretooluse-askuserquestion-recommended.md +65 -0
  473. package/src/apothem/hooks/messages/pretooluse-bash-plan-guard.md +97 -0
  474. package/src/apothem/hooks/messages/pretooluse-bash.md +39 -0
  475. package/src/apothem/hooks/messages/pretooluse-conformity.md +70 -0
  476. package/src/apothem/hooks/messages/pretooluse-dependency-guard.md +21 -0
  477. package/src/apothem/hooks/messages/pretooluse-edit-header-guard.md +61 -0
  478. package/src/apothem/hooks/messages/pretooluse-edit.md +21 -0
  479. package/src/apothem/hooks/messages/pretooluse-eval-guard.md +39 -0
  480. package/src/apothem/hooks/messages/pretooluse-notebookedit.md +11 -0
  481. package/src/apothem/hooks/messages/pretooluse-write-header-guard.md +45 -0
  482. package/src/apothem/hooks/messages/pretooluse-write-plan-guard.md +72 -0
  483. package/src/apothem/hooks/messages/pretooluse-write.md +21 -0
  484. package/src/apothem/hooks/messages/sessionstart.md +15 -0
  485. package/src/apothem/hooks/messages/stop.md +27 -0
  486. package/src/apothem/hooks/proactive_compaction_tracker.py +327 -0
  487. package/src/apothem/hooks/session_start_bootstrap.py +472 -0
  488. package/src/apothem/lib/README.md +42 -0
  489. package/src/apothem/lib/__init__.py +13 -0
  490. package/src/apothem/lib/atomic_io.py +189 -0
  491. package/src/apothem/lib/auditor.py +687 -0
  492. package/src/apothem/lib/clean_slate.py +396 -0
  493. package/src/apothem/lib/contexts.py +352 -0
  494. package/src/apothem/lib/data_home.py +255 -0
  495. package/src/apothem/lib/frontmatter.py +101 -0
  496. package/src/apothem/lib/harness_materializer.py +213 -0
  497. package/src/apothem/lib/harness_protocol.py +59 -0
  498. package/src/apothem/lib/harness_registry.py +282 -0
  499. package/src/apothem/lib/harness_registry_data.py +843 -0
  500. package/src/apothem/lib/install_ledger.py +347 -0
  501. package/src/apothem/lib/learning.py +540 -0
  502. package/src/apothem/lib/memory.py +347 -0
  503. package/src/apothem/lib/parallel_sweep.py +234 -0
  504. package/src/apothem/lib/plan_tiers.py +200 -0
  505. package/src/apothem/lib/plugin_bootstrap.py +132 -0
  506. package/src/apothem/lib/plugin_tree.py +599 -0
  507. package/src/apothem/lib/profile.py +755 -0
  508. package/src/apothem/lib/profile_projection.py +198 -0
  509. package/src/apothem/lib/propagation-manifest.yaml +878 -0
  510. package/src/apothem/lib/propagation.py +220 -0
  511. package/src/apothem/lib/python_resolver.py +189 -0
  512. package/src/apothem/lib/reporter.py +62 -0
  513. package/src/apothem/lib/workspace_migration.py +323 -0
  514. package/src/apothem/output-styles/README.md +41 -0
  515. package/src/apothem/output-styles/concise-engineer.md +49 -0
  516. package/src/apothem/output-styles/default-architect.md +52 -0
  517. package/src/apothem/output-styles/default.md +113 -0
  518. package/src/apothem/output-styles/forensic-auditor.md +63 -0
  519. package/src/apothem/py.typed +0 -0
  520. package/src/apothem/rules/README.md +121 -0
  521. package/src/apothem/rules/agent-capability-discipline-matrix.md +89 -0
  522. package/src/apothem/rules/agent-capability-discipline.md +78 -0
  523. package/src/apothem/rules/agent-orchestration-patterns.md +144 -0
  524. package/src/apothem/rules/agent-orchestration.md +65 -0
  525. package/src/apothem/rules/agents-md-convention.md +86 -0
  526. package/src/apothem/rules/agile-sprints-elements.md +135 -0
  527. package/src/apothem/rules/agile-sprints.md +64 -0
  528. package/src/apothem/rules/agnostic-posture-checklist.md +47 -0
  529. package/src/apothem/rules/agnostic-posture.md +48 -0
  530. package/src/apothem/rules/authoritative-referencing-quotation.md +50 -0
  531. package/src/apothem/rules/authoritative-referencing.md +66 -0
  532. package/src/apothem/rules/authority-inquiry-categories.md +58 -0
  533. package/src/apothem/rules/authority-inquiry.md +54 -0
  534. package/src/apothem/rules/auto-memory-topic-files.md +86 -0
  535. package/src/apothem/rules/auto-memory.md +67 -0
  536. package/src/apothem/rules/bidirectional-binding.md +123 -0
  537. package/src/apothem/rules/canonical-layout-reporting-tiers.md +212 -0
  538. package/src/apothem/rules/canonical-layout.md +60 -0
  539. package/src/apothem/rules/clean-architecture-layers.md +186 -0
  540. package/src/apothem/rules/clean-room-generation-protocols.md +124 -0
  541. package/src/apothem/rules/clean-room-generation.md +59 -0
  542. package/src/apothem/rules/code-craft-conventions.md +101 -0
  543. package/src/apothem/rules/code-craft-markdown.md +138 -0
  544. package/src/apothem/rules/code-craft-python.md +154 -0
  545. package/src/apothem/rules/code-craft-shell.md +192 -0
  546. package/src/apothem/rules/cognitive-identity-techniques.md +180 -0
  547. package/src/apothem/rules/cognitive-identity.md +81 -0
  548. package/src/apothem/rules/context-management-budget.md +46 -0
  549. package/src/apothem/rules/context-management-protocol.md +161 -0
  550. package/src/apothem/rules/context-management-scratch.md +128 -0
  551. package/src/apothem/rules/context-management.md +85 -0
  552. package/src/apothem/rules/definitiveness-virtues.md +67 -0
  553. package/src/apothem/rules/definitiveness.md +58 -0
  554. package/src/apothem/rules/determinism.md +81 -0
  555. package/src/apothem/rules/disclosure-ledger-markers.md +58 -0
  556. package/src/apothem/rules/disclosure-ledger.md +52 -0
  557. package/src/apothem/rules/dynamism.md +38 -0
  558. package/src/apothem/rules/etc-extension.md +57 -0
  559. package/src/apothem/rules/expertise-posture-elements.md +68 -0
  560. package/src/apothem/rules/expertise-posture.md +54 -0
  561. package/src/apothem/rules/freshness-facade.md +64 -0
  562. package/src/apothem/rules/harness-adapter-shape-schemas.md +162 -0
  563. package/src/apothem/rules/harness-adapter-shape.md +42 -0
  564. package/src/apothem/rules/host-discovery-manifests.md +50 -0
  565. package/src/apothem/rules/host-discovery.md +56 -0
  566. package/src/apothem/rules/i18n-discipline-locale-cohorts.md +120 -0
  567. package/src/apothem/rules/i18n-discipline.md +70 -0
  568. package/src/apothem/rules/interactive-questions-canonical-shapes.md +590 -0
  569. package/src/apothem/rules/interactive-questions-detail.md +41 -0
  570. package/src/apothem/rules/interactive-questions-sweep-matchers.md +184 -0
  571. package/src/apothem/rules/interactive-questions.md +89 -0
  572. package/src/apothem/rules/large-file-generation.md +112 -0
  573. package/src/apothem/rules/large-file-reading.md +59 -0
  574. package/src/apothem/rules/living-docs.md +85 -0
  575. package/src/apothem/rules/multi-agent-workflow.md +57 -0
  576. package/src/apothem/rules/operational-mandates-expanded.md +78 -0
  577. package/src/apothem/rules/operational-mandates.md +88 -0
  578. package/src/apothem/rules/option-annotation-form.md +60 -0
  579. package/src/apothem/rules/option-annotation.md +45 -0
  580. package/src/apothem/rules/own-voice-reimplementation.md +86 -0
  581. package/src/apothem/rules/performance-discipline.md +91 -0
  582. package/src/apothem/rules/persistent-conventions-vigilance-checklist.md +54 -0
  583. package/src/apothem/rules/persistent-conventions-vigilance.md +61 -0
  584. package/src/apothem/rules/plain-language.md +56 -0
  585. package/src/apothem/rules/planning-techniques.md +130 -0
  586. package/src/apothem/rules/pre-emission-gate-bars.md +86 -0
  587. package/src/apothem/rules/pre-emission-gate.md +54 -0
  588. package/src/apothem/rules/production-ready-prs-surfaces.md +162 -0
  589. package/src/apothem/rules/production-ready-prs.md +83 -0
  590. package/src/apothem/rules/propagation.md +63 -0
  591. package/src/apothem/rules/recommend-next-step.md +106 -0
  592. package/src/apothem/rules/refactoring-discipline.md +76 -0
  593. package/src/apothem/rules/session-closure.md +44 -0
  594. package/src/apothem/rules/sota-elevation-exemplars.md +76 -0
  595. package/src/apothem/rules/sota-elevation.md +52 -0
  596. package/src/apothem/rules/source-accessibility.md +58 -0
  597. package/src/apothem/rules/surgical-manipulation.md +48 -0
  598. package/src/apothem/rules/systemic-participation-relations.md +108 -0
  599. package/src/apothem/rules/systemic-participation.md +70 -0
  600. package/src/apothem/rules/ten-dimension-check-dimensions.md +52 -0
  601. package/src/apothem/rules/ten-dimension-check.md +59 -0
  602. package/src/apothem/rules/token-budget-discipline.md +81 -0
  603. package/src/apothem/rules/token-efficiency-rewrite-protocol.md +79 -0
  604. package/src/apothem/rules/token-efficiency-rewrite.md +77 -0
  605. package/src/apothem/rules/tool-use-discipline.md +48 -0
  606. package/src/apothem/rules/visual-leverage.md +102 -0
  607. package/src/apothem/schemas/NOTICE.md +9 -0
  608. package/src/apothem/schemas/README.md +104 -0
  609. package/src/apothem/schemas/__init__.py +176 -0
  610. package/src/apothem/schemas/advisory-finding.schema.json +111 -0
  611. package/src/apothem/schemas/agent.schema.json +106 -0
  612. package/src/apothem/schemas/authorship-header.txt +1 -0
  613. package/src/apothem/schemas/cohort-manifest.yaml +248 -0
  614. package/src/apothem/schemas/cohort-metadata-vocabulary.yaml +168 -0
  615. package/src/apothem/schemas/cohort.schema.json +113 -0
  616. package/src/apothem/schemas/command.schema.json +68 -0
  617. package/src/apothem/schemas/compatibility-matrix.yaml +432 -0
  618. package/src/apothem/schemas/context-fragment.schema.json +64 -0
  619. package/src/apothem/schemas/freshness-token-denylist.txt +51 -0
  620. package/src/apothem/schemas/handoff-manifest.yaml +353 -0
  621. package/src/apothem/schemas/header-exceptions.txt +141 -0
  622. package/src/apothem/schemas/header-visibility.yaml +39 -0
  623. package/src/apothem/schemas/learning-signal.schema.json +46 -0
  624. package/src/apothem/schemas/memory-record.schema.json +61 -0
  625. package/src/apothem/schemas/output-style.schema.json +40 -0
  626. package/src/apothem/schemas/plan.schema.json +51 -0
  627. package/src/apothem/schemas/plugin.schema.json +83 -0
  628. package/src/apothem/schemas/profile.example.yaml +70 -0
  629. package/src/apothem/schemas/profile.minimal.yaml +6 -0
  630. package/src/apothem/schemas/profile.schema.json +396 -0
  631. package/src/apothem/schemas/reference-token-denylist.txt +25 -0
  632. package/src/apothem/schemas/skill.schema.json +75 -0
  633. package/src/apothem/skills/README.md +93 -0
  634. package/src/apothem/skills/dependency-upgrade/SKILL.md +105 -0
  635. package/src/apothem/skills/dev-toolkit/SKILL.md +120 -0
  636. package/src/apothem/skills/diagram-authoring/SKILL.md +113 -0
  637. package/src/apothem/skills/document-authoring/SKILL.md +118 -0
  638. package/src/apothem/skills/ecosystem-audit/SKILL.md +108 -0
  639. package/src/apothem/skills/ecosystem-audit/references/audit-fortress.md +85 -0
  640. package/src/apothem/skills/ecosystem-audit/references/procedure.md +162 -0
  641. package/src/apothem/skills/eval-harness/SKILL.md +88 -0
  642. package/src/apothem/skills/incident-runbook/SKILL.md +92 -0
  643. package/src/apothem/skills/multi-source-research/SKILL.md +90 -0
  644. package/src/apothem/skills/plan-suite/SKILL.md +118 -0
  645. package/src/apothem/skills/plan-suite/master_template.md +1324 -0
  646. package/src/apothem/skills/projectify/SKILL.md +117 -0
  647. package/src/apothem/skills/prompt-engineering/SKILL.md +122 -0
  648. package/src/apothem/skills/refactor-extract/SKILL.md +85 -0
  649. package/src/apothem/skills/research-suite/SKILL.md +170 -0
  650. package/src/apothem/skills/research-suite/references/directory-structure.md +47 -0
  651. package/src/apothem/skills/research-suite/references/lifecycle.md +67 -0
  652. package/src/apothem/skills/research-suite/references/principal-investigator-framework.md +37 -0
  653. package/src/apothem/skills/research-suite/references/rigor-mandates.md +30 -0
  654. package/src/apothem/skills/research-suite/research_template.md +476 -0
  655. package/src/apothem/skills/secret-rotation/SKILL.md +87 -0
  656. package/src/apothem/skills/source-synthesis/SKILL.md +92 -0
  657. package/src/apothem/skills/surgical-guard/SKILL.md +118 -0
  658. package/src/apothem/skills/test-authoring/SKILL.md +85 -0
  659. package/src/apothem/skills/vuln-triage/SKILL.md +91 -0
  660. package/src/apothem/skills/workflow/SKILL.md +139 -0
  661. package/src/apothem/statuslines/README.md +26 -0
  662. package/src/apothem/statuslines/__init__.py +20 -0
  663. package/src/apothem/statuslines/conformity.json +5 -0
  664. package/src/apothem/statuslines/render.py +334 -0
  665. package/src/apothem/statuslines/statusline.md +50 -0
  666. package/src/apothem/templates/README.md +43 -0
  667. package/src/apothem/templates/agents-md-template.md +80 -0
  668. package/src/apothem/templates/consideration-log.md +39 -0
  669. package/src/apothem/templates/expertise-gap-log.md +56 -0
  670. package/src/apothem/templates/master-index-template.md +93 -0
  671. package/src/apothem/templates/potency-map.md +53 -0
  672. package/src/apothem/templates/preservation-audit.md +60 -0
  673. package/src/apothem/templates/question-resolution-audit.md +52 -0
  674. package/src/apothem/templates/trace-matrix-template.md +77 -0
@@ -0,0 +1,687 @@
1
+ # SPDX-License-Identifier: MIT
2
+
3
+ """Unified conformance / security auditor — config scan, secret detection, conformance.
4
+
5
+ This module is the single auditor that subsumes the standalone conformity
6
+ validators and the machinable subset of the audit / review commands. It scans
7
+ a configuration file (or any text artifact), detects secret literals against a
8
+ closed, enumerated catalog, and applies rule-based conformance checks to the
9
+ parsed structure. Every result is a :class:`Finding`.
10
+
11
+ The auditor is **advisory by default**: :func:`audit` reports findings without
12
+ blocking, and the standalone CLI exits zero even with findings present unless
13
+ ``--strict`` (or the ``APOTHEM_AUDITOR_STRICT`` environment opt-in) is set. Every
14
+ finding carries a definitive ``next_step`` — the determinant move the operator
15
+ can take. A malformed config yields a structured finding, never a crash; an
16
+ internal auditor failure is itself reported as a ``category = error`` finding,
17
+ never swallowed.
18
+
19
+ The serialized run (:meth:`Findings.to_dict`) validates against the bundled
20
+ ``advisory-finding.schema.json`` contract, the same shape the hosted-layer
21
+ pull-request audit (:func:`pr_audit`) emits.
22
+
23
+ Standalone invocation (no package installation required; the plugin tree is
24
+ self-contained)::
25
+
26
+ python -m apothem.lib.auditor <path> [<path> ...] [--strict] [--json]
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import argparse
32
+ import json
33
+ import math
34
+ import os
35
+ import re
36
+ import sys
37
+ from collections.abc import Iterable, Mapping, Sequence
38
+ from dataclasses import dataclass, field
39
+ from pathlib import Path
40
+ from typing import Final, Literal, cast
41
+
42
+ from jsonschema import Draft202012Validator
43
+
44
+ from apothem.schemas import advisory_finding_schema_path
45
+
46
+ Category = Literal["config-scan", "secret", "conformance", "error"]
47
+ Severity = Literal["HIGH", "MEDIUM", "LOW"]
48
+
49
+ #: Opt-in environment variable that promotes the advisory default to strict.
50
+ STRICT_ENV: Final[str] = "APOTHEM_AUDITOR_STRICT"
51
+ _STRICT_TRUTHY: Final[frozenset[str]] = frozenset({"1", "true", "yes", "on"})
52
+
53
+ #: Config suffixes the structured scanner knows how to parse. Everything else is
54
+ #: still secret-scanned line-by-line as plain text.
55
+ _YAML_SUFFIXES: Final[frozenset[str]] = frozenset({".yaml", ".yml"})
56
+ _JSON_SUFFIXES: Final[frozenset[str]] = frozenset({".json"})
57
+ _TOML_SUFFIXES: Final[frozenset[str]] = frozenset({".toml"})
58
+
59
+ #: Substrings that mark a token as an obvious placeholder / example, not a
60
+ #: live secret, used to suppress the high-entropy heuristic's false positives.
61
+ _PLACEHOLDER_HINTS: Final[tuple[str, ...]] = (
62
+ "example",
63
+ "placeholder",
64
+ "dummy",
65
+ "redacted",
66
+ "your-",
67
+ "xxxx",
68
+ "changeme",
69
+ "<",
70
+ "...",
71
+ )
72
+
73
+ #: Owner-identity strings that legitimately appear in authorship banners and must
74
+ #: never be flagged as secrets.
75
+ _IDENTITY_ALLOWLIST: Final[tuple[str, ...]] = (
76
+ "ahmedgad.com",
77
+ "me@ahmedgad.com",
78
+ "github.com/ahmed-g-gad",
79
+ "ahmed-g-gad",
80
+ )
81
+
82
+ #: Universal-deny grant patterns: a conformance rule flags a config that grants
83
+ #: one of these in a permission / allow surface (the universal-deny floor).
84
+ _DENIED_GRANT_PATTERNS: Final[tuple[tuple[str, str], ...]] = (
85
+ (r"rm\s+-rf", "destructive recursive removal"),
86
+ (r"\bsudo\b", "privilege escalation"),
87
+ (r"git\s+push\s+--force", "history-rewriting force push"),
88
+ (r"\.env\b", "secret-file read access"),
89
+ (r"~/\.ssh", "ssh-credential read access"),
90
+ (r"\beval\b", "arbitrary code evaluation"),
91
+ )
92
+
93
+
94
+ class AuditorError(ValueError):
95
+ """Raised when the auditor's own output fails its schema contract."""
96
+
97
+
98
+ @dataclass(frozen=True)
99
+ class Location:
100
+ """Where a finding was found."""
101
+
102
+ path: str
103
+ line: int | None = None
104
+ column: int | None = None
105
+
106
+ def to_dict(self) -> dict[str, object]:
107
+ """Return the schema-shaped location mapping (omitting absent fields)."""
108
+ out: dict[str, object] = {"path": self.path}
109
+ if self.line is not None:
110
+ out["line"] = self.line
111
+ if self.column is not None:
112
+ out["column"] = self.column
113
+ return out
114
+
115
+
116
+ @dataclass(frozen=True)
117
+ class Finding:
118
+ """A single advisory finding, shared by all three auditor capabilities."""
119
+
120
+ id: str
121
+ category: Category
122
+ severity: Severity
123
+ location: Location
124
+ message: str
125
+ next_step: str
126
+
127
+ def to_dict(self) -> dict[str, object]:
128
+ """Return the schema-shaped finding mapping."""
129
+ return {
130
+ "id": self.id,
131
+ "category": self.category,
132
+ "severity": self.severity,
133
+ "location": self.location.to_dict(),
134
+ "message": self.message,
135
+ "next_step": self.next_step,
136
+ }
137
+
138
+ def _order_key(self) -> tuple[str, int, str, str]:
139
+ """Deterministic sort key: path, line, category, id."""
140
+ return (self.location.path, self.location.line or 0, self.category, self.id)
141
+
142
+
143
+ @dataclass(frozen=True)
144
+ class SecretPattern:
145
+ """One entry in the closed secret-pattern catalog."""
146
+
147
+ label: str
148
+ match_rule: str
149
+ severity: Severity = "HIGH"
150
+ #: When ``True`` the rule is the Shannon-entropy heuristic, not a literal regex
151
+ #: match; the detector applies the entropy gate and placeholder suppression.
152
+ entropy: bool = False
153
+ _compiled: re.Pattern[str] = field(init=False, repr=False, compare=False)
154
+
155
+ def __post_init__(self) -> None:
156
+ object.__setattr__(self, "_compiled", re.compile(self.match_rule))
157
+
158
+ def search(self, text: str) -> Iterable[re.Match[str]]:
159
+ """Yield every non-overlapping match of this pattern in *text*."""
160
+ return self._compiled.finditer(text)
161
+
162
+
163
+ #: The closed secret-pattern catalog. Each pattern carries a ``label`` and a
164
+ #: ``match_rule``; together they are the exhaustive set the auditor detects.
165
+ #: Excluded classes are attested in :data:`SECRET_PATTERNS_NA`.
166
+ SECRET_PATTERNS: Final[tuple[SecretPattern, ...]] = (
167
+ SecretPattern("aws-access-key-id", r"\bAKIA[0-9A-Z]{16}\b"),
168
+ SecretPattern("github-personal-token", r"\bghp_[A-Za-z0-9]{36}\b"),
169
+ SecretPattern("github-oauth-token", r"\bgho_[A-Za-z0-9]{36}\b"),
170
+ SecretPattern("github-app-token", r"\b(?:ghs|ghu|ghr)_[A-Za-z0-9]{36}\b"),
171
+ SecretPattern("openai-style-api-key", r"\bsk-[A-Za-z0-9]{32,}\b"),
172
+ SecretPattern("google-api-key", r"\bAIza[0-9A-Za-z_\-]{35}\b"),
173
+ SecretPattern("slack-token", r"\bxox[abprs]-[A-Za-z0-9-]{10,}\b"),
174
+ SecretPattern(
175
+ "jwt",
176
+ r"\beyJ[A-Za-z0-9_\-]{10,}\.eyJ[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}\b",
177
+ ),
178
+ SecretPattern(
179
+ "pem-private-key",
180
+ r"-----BEGIN (?:RSA |OPENSSH |EC |DSA |PGP )?PRIVATE KEY-----",
181
+ ),
182
+ SecretPattern(
183
+ "high-entropy-token",
184
+ r"[A-Za-z0-9_+/=\-]{40,}",
185
+ severity="LOW",
186
+ entropy=True,
187
+ ),
188
+ )
189
+
190
+ #: Secret classes the catalog explicitly does NOT cover (the N/A attestation). A
191
+ #: downstream catalog-assembly step (the repo-wide sync) consumes this list when
192
+ #: deciding whether to widen coverage.
193
+ SECRET_PATTERNS_NA: Final[tuple[str, ...]] = (
194
+ "oauth-client-secrets (no distinctive prefix)",
195
+ "database-connection-strings (user:pass@host)",
196
+ "azure / microsoft cloud credentials",
197
+ "gcp-service-account-json keys",
198
+ "stripe / twilio api keys",
199
+ "ssh host keys / known_hosts entries",
200
+ "pkcs#8 / sec1 private keys outside the PEM armor",
201
+ )
202
+
203
+ #: Minimum Shannon entropy (bits / char) for the high-entropy heuristic to fire.
204
+ _ENTROPY_THRESHOLD: Final[float] = 4.5
205
+
206
+
207
+ @dataclass(frozen=True)
208
+ class Findings:
209
+ """The result of an audit run — the schema-shaped output contract."""
210
+
211
+ findings: tuple[Finding, ...]
212
+ strict: bool
213
+
214
+ @property
215
+ def summary(self) -> dict[str, int]:
216
+ """Aggregate finding counts by severity."""
217
+ high = sum(1 for f in self.findings if f.severity == "HIGH")
218
+ medium = sum(1 for f in self.findings if f.severity == "MEDIUM")
219
+ low = sum(1 for f in self.findings if f.severity == "LOW")
220
+ return {"total": len(self.findings), "high": high, "medium": medium, "low": low}
221
+
222
+ @property
223
+ def present(self) -> bool:
224
+ """True when at least one finding was produced."""
225
+ return bool(self.findings)
226
+
227
+ def to_dict(self) -> dict[str, object]:
228
+ """Return the full schema-shaped run mapping (``advisory-finding`` schema)."""
229
+ return {
230
+ "findings": [f.to_dict() for f in self.findings],
231
+ "strict": self.strict,
232
+ "summary": self.summary,
233
+ }
234
+
235
+
236
+ @dataclass(frozen=True)
237
+ class PrAuditInput:
238
+ """The hosted-layer pull-request audit input (consumed by the Phase-B App).
239
+
240
+ The hosted layer hands the auditor the set of changed configuration paths
241
+ plus repository context (branch, base ref, repo identity); the auditor returns
242
+ the same :class:`Findings` shape the standalone CLI produces.
243
+ """
244
+
245
+ changed_paths: tuple[Path, ...]
246
+ repo_context: Mapping[str, object] = field(default_factory=dict)
247
+ strict: bool = False
248
+
249
+
250
+ def _schema() -> dict[str, object]:
251
+ """Load and parse the advisory-finding output schema."""
252
+ raw = advisory_finding_schema_path().read_text(encoding="utf-8")
253
+ return cast("dict[str, object]", json.loads(raw))
254
+
255
+
256
+ def validate_findings(data: Mapping[str, object]) -> None:
257
+ """Validate a serialized run against the advisory-finding schema.
258
+
259
+ Args:
260
+ data: A :meth:`Findings.to_dict` mapping.
261
+
262
+ Raises:
263
+ AuditorError: When *data* violates the schema; the message lists every
264
+ validation error discovered.
265
+ """
266
+ validator = Draft202012Validator(_schema())
267
+ errors = sorted(validator.iter_errors(dict(data)), key=lambda error: error.path)
268
+ if errors:
269
+ details = "; ".join(error.message for error in errors)
270
+ raise AuditorError(f"auditor output violates schema: {details}")
271
+
272
+
273
+ def resolve_strict(flag: bool) -> bool:
274
+ """Resolve the effective strict mode from the CLI flag and the env opt-in.
275
+
276
+ Strict is enabled when the ``--strict`` flag is passed OR the
277
+ ``APOTHEM_AUDITOR_STRICT`` environment variable is truthy. The shipped default
278
+ is advisory (``False``).
279
+ """
280
+ if flag:
281
+ return True
282
+ return os.environ.get(STRICT_ENV, "").strip().lower() in _STRICT_TRUTHY
283
+
284
+
285
+ def _shannon_entropy(token: str) -> float:
286
+ """Return the Shannon entropy (bits / char) of *token*."""
287
+ if not token:
288
+ return 0.0
289
+ counts: dict[str, int] = {}
290
+ for char in token:
291
+ counts[char] = counts.get(char, 0) + 1
292
+ length = len(token)
293
+ return -sum((c / length) * math.log2(c / length) for c in counts.values())
294
+
295
+
296
+ def _is_allowlisted(text: str) -> bool:
297
+ """True when *text* contains an owner-identity allowlist token."""
298
+ return any(token in text for token in _IDENTITY_ALLOWLIST)
299
+
300
+
301
+ def _looks_like_placeholder(token: str) -> bool:
302
+ """True when *token* is an obvious example / placeholder, not a live secret."""
303
+ lowered = token.lower()
304
+ return any(hint in lowered for hint in _PLACEHOLDER_HINTS)
305
+
306
+
307
+ # --- Capability 1: configuration-file scanning ------------------------------
308
+
309
+
310
+ def scan_config(path: Path) -> tuple[object | None, Finding | None]:
311
+ """Parse a harness configuration file into an inspectable structure.
312
+
313
+ Args:
314
+ path: The configuration file to read.
315
+
316
+ Returns:
317
+ A ``(structure, finding)`` pair. On success ``structure`` is the parsed
318
+ object and ``finding`` is ``None``. On a missing, unreadable, or malformed
319
+ file ``structure`` is ``None`` and ``finding`` is a structured
320
+ ``config-scan`` finding — never a raised exception.
321
+ """
322
+ if not path.is_file():
323
+ return None, Finding(
324
+ id="config-unreadable",
325
+ category="config-scan",
326
+ severity="MEDIUM",
327
+ location=Location(path=str(path)),
328
+ message=f"configuration path does not exist or is not a file: {path}",
329
+ next_step=f"create the file at {path} or correct the path passed to the auditor.",
330
+ )
331
+ try:
332
+ text = path.read_text(encoding="utf-8")
333
+ except (OSError, UnicodeDecodeError) as exc:
334
+ return None, Finding(
335
+ id="config-unreadable",
336
+ category="config-scan",
337
+ severity="MEDIUM",
338
+ location=Location(path=str(path)),
339
+ message=f"configuration file is not readable as UTF-8 text: {exc}",
340
+ next_step="verify the file is UTF-8 encoded and readable, then re-run the auditor.",
341
+ )
342
+
343
+ suffix = path.suffix.lower()
344
+ try:
345
+ structure = _parse_structure(text, suffix)
346
+ except _ParseError as exc:
347
+ return None, Finding(
348
+ id="config-malformed",
349
+ category="config-scan",
350
+ severity="MEDIUM",
351
+ location=Location(path=str(path), line=exc.line),
352
+ message=f"configuration file is malformed ({suffix or 'text'}): {exc}",
353
+ next_step="fix the reported syntax error; the auditor parses the file once it is well-formed.",
354
+ )
355
+ return structure, None
356
+
357
+
358
+ class _ParseError(Exception):
359
+ """Internal: a configuration file failed to parse."""
360
+
361
+ def __init__(self, message: str, line: int | None = None) -> None:
362
+ super().__init__(message)
363
+ self.line = line
364
+
365
+
366
+ def _parse_structure(text: str, suffix: str) -> object:
367
+ """Parse *text* by *suffix*; raise :class:`_ParseError` on malformed input."""
368
+ if suffix in _JSON_SUFFIXES:
369
+ try:
370
+ return json.loads(text)
371
+ except json.JSONDecodeError as exc:
372
+ raise _ParseError(str(exc), exc.lineno) from exc
373
+ if suffix in _YAML_SUFFIXES:
374
+ import yaml # vendored; deferred so non-YAML scans never import it
375
+
376
+ try:
377
+ return yaml.safe_load(text)
378
+ except yaml.YAMLError as exc:
379
+ raise _ParseError(str(exc)) from exc
380
+ if suffix in _TOML_SUFFIXES:
381
+ try:
382
+ # tomllib is stdlib only from 3.11; on 3.10 expose raw text.
383
+ # The dual-code ignore keeps both type-check contexts clean:
384
+ # import-not-found fires under --python-version=3.10 (typeshed
385
+ # gates tomllib to 3.11+), unused-ignore self-suppresses where
386
+ # the interpreter-version context resolves the stub.
387
+ import tomllib # type: ignore[import-not-found, unused-ignore]
388
+ except ModuleNotFoundError:
389
+ return {"_raw": text}
390
+
391
+ try:
392
+ return tomllib.loads(text)
393
+ except tomllib.TOMLDecodeError as exc:
394
+ raise _ParseError(str(exc)) from exc
395
+ # Unknown / plain-text config: expose the raw text for line-level scanning.
396
+ return {"_raw": text}
397
+
398
+
399
+ # --- Capability 2: secret-pattern detection ---------------------------------
400
+
401
+
402
+ def detect_secrets(path: Path, text: str) -> list[Finding]:
403
+ """Detect secret literals in *text* against the closed catalog.
404
+
405
+ Args:
406
+ path: The artifact the text came from (for finding locations).
407
+ text: The raw file content, scanned line by line.
408
+
409
+ Returns:
410
+ One finding per detected secret, deterministically ordered.
411
+ """
412
+ findings: list[Finding] = []
413
+ for lineno, line in enumerate(text.splitlines(), start=1):
414
+ if _is_allowlisted(line):
415
+ continue
416
+ for pattern in SECRET_PATTERNS:
417
+ for match in pattern.search(line):
418
+ token = match.group(0)
419
+ if pattern.entropy:
420
+ if _looks_like_placeholder(token):
421
+ continue
422
+ if _shannon_entropy(token) < _ENTROPY_THRESHOLD:
423
+ continue
424
+ # Skip tokens a named pattern already owns (dedupe).
425
+ if _matched_by_named_pattern(token):
426
+ continue
427
+ findings.append(
428
+ Finding(
429
+ id=f"secret-{pattern.label}",
430
+ category="secret",
431
+ severity=pattern.severity,
432
+ location=Location(
433
+ path=str(path), line=lineno, column=match.start() + 1
434
+ ),
435
+ message=f"possible {pattern.label} literal detected in {path.name}.",
436
+ next_step=(
437
+ "remove the literal; load the value from an environment variable "
438
+ "or secret manager and rotate the exposed credential."
439
+ ),
440
+ )
441
+ )
442
+ return findings
443
+
444
+
445
+ def _matched_by_named_pattern(token: str) -> bool:
446
+ """True when a non-entropy catalog pattern fully owns *token*."""
447
+ return any(
448
+ not p.entropy and p._compiled.fullmatch(token) is not None
449
+ for p in SECRET_PATTERNS
450
+ )
451
+
452
+
453
+ # --- Capability 3: rule-based conformance -----------------------------------
454
+
455
+
456
+ def run_conformance(path: Path, structure: object) -> list[Finding]:
457
+ """Apply conformance rules to a parsed configuration structure.
458
+
459
+ The rules walk the parsed structure shape-agnostically (no per-harness schema
460
+ is assumed), so the same engine audits a JSON, YAML, or TOML config alike. The
461
+ shipped rule is the universal-deny floor: a permission / allow surface must not
462
+ grant a denied operation.
463
+
464
+ Args:
465
+ path: The configuration file (for finding locations).
466
+ structure: The parsed structure from :func:`scan_config`.
467
+
468
+ Returns:
469
+ One finding per conformance violation, deterministically ordered.
470
+ """
471
+ findings: list[Finding] = []
472
+ for grant in _iter_string_values(structure):
473
+ for regex, label in _DENIED_GRANT_PATTERNS:
474
+ if re.search(regex, grant):
475
+ findings.append(
476
+ Finding(
477
+ id="denied-grant",
478
+ category="conformance",
479
+ severity="HIGH",
480
+ location=Location(path=str(path)),
481
+ message=f"configuration grants a denied operation ({label}): {grant!r}.",
482
+ next_step=(
483
+ f"remove the {label} grant; the universal-deny floor forbids it "
484
+ "regardless of the per-harness allow-list."
485
+ ),
486
+ )
487
+ )
488
+ break
489
+ return findings
490
+
491
+
492
+ def _iter_string_values(structure: object) -> Iterable[str]:
493
+ """Yield every string leaf in a nested mapping / sequence structure."""
494
+ if isinstance(structure, str):
495
+ yield structure
496
+ elif isinstance(structure, Mapping):
497
+ for value in structure.values():
498
+ yield from _iter_string_values(value)
499
+ elif isinstance(structure, (list, tuple)):
500
+ for item in structure:
501
+ yield from _iter_string_values(item)
502
+
503
+
504
+ # --- Top-level audit + integration contracts --------------------------------
505
+
506
+
507
+ def _audit_path(path: Path) -> list[Finding]:
508
+ """Run all three capabilities over a single path; never raise."""
509
+ findings: list[Finding] = []
510
+ try:
511
+ structure, scan_finding = scan_config(path)
512
+ if scan_finding is not None:
513
+ findings.append(scan_finding)
514
+ # Secret detection runs on raw text even when parsing failed.
515
+ if path.is_file():
516
+ try:
517
+ text = path.read_text(encoding="utf-8")
518
+ except (OSError, UnicodeDecodeError):
519
+ text = ""
520
+ findings.extend(detect_secrets(path, text))
521
+ if structure is not None:
522
+ findings.extend(run_conformance(path, structure))
523
+ except Exception as exc:
524
+ findings.append(
525
+ Finding(
526
+ id="auditor-internal-error",
527
+ category="error",
528
+ severity="MEDIUM",
529
+ location=Location(path=str(path)),
530
+ message=f"the auditor failed while scanning {path}: {exc}",
531
+ next_step="report this path to the auditor maintainer; the run continues for other paths.",
532
+ )
533
+ )
534
+ return findings
535
+
536
+
537
+ def audit(paths: Sequence[Path], *, strict: bool = False) -> Findings:
538
+ """Scan, detect, and conform over *paths*; return structured findings.
539
+
540
+ Args:
541
+ paths: Configuration files (or directories — every contained file is
542
+ scanned) to audit.
543
+ strict: When ``True`` the result's ``strict`` flag is set so a CLI caller
544
+ exits non-zero on findings-present. Advisory (``False``) is the default.
545
+
546
+ Returns:
547
+ A :class:`Findings` whose serialization validates against the
548
+ advisory-finding schema. The result is always returned — an internal error
549
+ on any path becomes a ``category = error`` finding, never an exception.
550
+ """
551
+ collected: list[Finding] = []
552
+ for path in _expand_paths(paths):
553
+ collected.extend(_audit_path(path))
554
+ ordered = tuple(sorted(collected, key=Finding._order_key))
555
+ return Findings(findings=ordered, strict=strict)
556
+
557
+
558
+ def pr_audit(changed: PrAuditInput) -> Findings:
559
+ """Audit the changed configuration paths of a pull request (hosted-layer contract).
560
+
561
+ This is the stable integration point the Phase-B hosted service consumes: it
562
+ receives the changed configuration paths plus repository context and returns
563
+ the same :class:`Findings` shape the standalone :func:`audit` produces. The
564
+ repository context is carried for the caller's reporting; the audit itself runs
565
+ over the changed paths.
566
+
567
+ Args:
568
+ changed: The changed configuration paths and repository context.
569
+
570
+ Returns:
571
+ Findings over the changed paths, in the advisory-finding shape.
572
+ """
573
+ return audit(changed.changed_paths, strict=changed.strict)
574
+
575
+
576
+ def _expand_paths(paths: Sequence[Path]) -> list[Path]:
577
+ """Expand directories to their contained files; keep files as-is, sorted."""
578
+ expanded: list[Path] = []
579
+ for path in paths:
580
+ if path.is_dir():
581
+ expanded.extend(sorted(p for p in path.rglob("*") if p.is_file()))
582
+ else:
583
+ expanded.append(path)
584
+ return expanded
585
+
586
+
587
+ def _exit_code(findings_present: bool, *, strict: bool) -> int:
588
+ """Map a verdict to an exit code: advisory exits 0; strict exits 1 on findings."""
589
+ if findings_present and strict:
590
+ return 1
591
+ return 0
592
+
593
+
594
+ def _build_parser() -> argparse.ArgumentParser:
595
+ """Build the standalone CLI argument parser."""
596
+ parser = argparse.ArgumentParser(
597
+ prog="python -m apothem.lib.auditor",
598
+ description="Advisory conformance / security auditor: config scan, secret detection, conformance.",
599
+ )
600
+ parser.add_argument(
601
+ "paths",
602
+ nargs="+",
603
+ type=Path,
604
+ help="configuration files or directories to audit",
605
+ )
606
+ parser.add_argument(
607
+ "--strict",
608
+ action="store_true",
609
+ help="exit non-zero when findings are present (default: advisory, always exit 0)",
610
+ )
611
+ parser.add_argument(
612
+ "--json",
613
+ action="store_true",
614
+ help="emit the findings as JSON (default: human-readable advisory summary)",
615
+ )
616
+ return parser
617
+
618
+
619
+ def _render_human(result: Findings) -> str:
620
+ """Render a human-readable advisory summary of the run."""
621
+ lines: list[str] = []
622
+ summary = result.summary
623
+ if not result.present:
624
+ return "auditor: 0 findings — configuration is clean."
625
+ lines.append(
626
+ f"auditor: {summary['total']} finding(s) "
627
+ f"(HIGH {summary['high']} | MEDIUM {summary['medium']} | LOW {summary['low']}) -- advisory."
628
+ )
629
+ for finding in result.findings:
630
+ loc = finding.location
631
+ where = loc.path + (f":{loc.line}" if loc.line is not None else "")
632
+ lines.append(
633
+ f" [{finding.severity}] {finding.category}/{finding.id} @ {where}"
634
+ )
635
+ lines.append(f" {finding.message}")
636
+ lines.append(f" next step: {finding.next_step}")
637
+ return "\n".join(lines)
638
+
639
+
640
+ def main(argv: Sequence[str] | None = None) -> int:
641
+ """Standalone CLI entry point (``python -m apothem.lib.auditor``).
642
+
643
+ Args:
644
+ argv: The argument vector (defaults to ``sys.argv[1:]``).
645
+
646
+ Returns:
647
+ ``0`` in advisory mode (the shipped default), even with findings present;
648
+ ``1`` when ``--strict`` (or the env opt-in) is set and findings are present.
649
+ """
650
+ parser = _build_parser()
651
+ args = parser.parse_args(argv)
652
+ strict = resolve_strict(bool(args.strict))
653
+ result = audit(cast("list[Path]", args.paths), strict=strict)
654
+ serialized = result.to_dict()
655
+ validate_findings(serialized)
656
+ if args.json:
657
+ sys.stdout.write(json.dumps(serialized, indent=2, sort_keys=True) + "\n")
658
+ else:
659
+ sys.stdout.write(_render_human(result) + "\n")
660
+ return _exit_code(result.present, strict=strict)
661
+
662
+
663
+ __all__ = [
664
+ "SECRET_PATTERNS",
665
+ "SECRET_PATTERNS_NA",
666
+ "STRICT_ENV",
667
+ "AuditorError",
668
+ "Category",
669
+ "Finding",
670
+ "Findings",
671
+ "Location",
672
+ "PrAuditInput",
673
+ "SecretPattern",
674
+ "Severity",
675
+ "audit",
676
+ "detect_secrets",
677
+ "main",
678
+ "pr_audit",
679
+ "resolve_strict",
680
+ "run_conformance",
681
+ "scan_config",
682
+ "validate_findings",
683
+ ]
684
+
685
+
686
+ if __name__ == "__main__": # pragma: no cover — exercised via subprocess in tests
687
+ raise SystemExit(main(sys.argv[1:]))