@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,607 @@
1
+ # SPDX-License-Identifier: MIT
2
+
3
+ """Build a node-edge graph of cross-artifact references in the ecosystem.
4
+
5
+ Why this graph exists. Agents call skills. Commands invoke agents and
6
+ reference skills. Hooks bind to harness events. Output-styles route work
7
+ to specific surfaces. The textual cross-references between these
8
+ artifacts form a directed graph; without an explicit map of that graph,
9
+ orphan artifacts (no inbound edges) and dangling references (edges to
10
+ non-existent targets) stay invisible until they break at runtime. This
11
+ tool emits ``capability-graph.json`` once; downstream graph analysis
12
+ (orphan detection, dangling-reference detection) reads it.
13
+
14
+ What the graph captures. Nodes correspond to addressable ecosystem
15
+ units, keyed by their content-root-relative path: agents (one per
16
+ ``agents/<name>.md``), commands (one per ``commands/*.md``), skills (one per
17
+ ``skills/*/``), hooks (one per hook script under ``hooks/``), output-styles
18
+ (one per ``output-styles/*``), statuslines, and the harness events
19
+ themselves (SessionStart,
20
+ PreToolUse, PostToolUse, UserPromptSubmit, Notification, Stop,
21
+ PreCompact, PostCompact). Edges record observed textual references:
22
+ ``invokes`` (agent / command body cites another agent or command),
23
+ ``references`` (any artifact mentions another by name in prose),
24
+ ``binds-to`` (hook artifact bound to a harness event in the engine hook
25
+ config), ``imports`` (Python module imports another).
26
+
27
+ Detection strategy. The tool first registers every node from the
28
+ inventory. It then scans the text of every node-bearing source file for
29
+ mentions of every other node's identifier. A textual match within an
30
+ artifact body emits an edge from the source artifact to the target
31
+ node. Hook bindings come from the ``hooks`` block of the engine
32
+ ``hooks/hooks.json`` config rather than text scanning.
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ import argparse
38
+ import json
39
+ import re
40
+ import sys
41
+ from dataclasses import dataclass, field
42
+ from datetime import datetime, timezone
43
+ from pathlib import Path
44
+ from typing import Final
45
+
46
+ # Edge relation taxonomy. Each edge carries exactly one relation value.
47
+ RELATION_INVOKES: Final[str] = "invokes"
48
+ RELATION_REFERENCES: Final[str] = "references"
49
+ RELATION_BINDS_TO: Final[str] = "binds-to"
50
+ RELATION_IMPORTS: Final[str] = "imports"
51
+
52
+ # Node-kind taxonomy mirrors the inventory's ecosystem classes for the
53
+ # subset that participates in the capability graph.
54
+ KIND_AGENT: Final[str] = "agent"
55
+ KIND_COMMAND: Final[str] = "command"
56
+ KIND_SKILL: Final[str] = "skill"
57
+ KIND_HOOK: Final[str] = "hook"
58
+ KIND_OUTPUT_STYLE: Final[str] = "output-style"
59
+ KIND_STATUSLINE: Final[str] = "statusline"
60
+ KIND_HARNESS_EVENT: Final[str] = "harness-event"
61
+ KIND_RULE: Final[str] = "rule"
62
+ KIND_MCP: Final[str] = "mcp"
63
+
64
+ # Canonical Claude Code hook events. Treated as virtual nodes so hook
65
+ # bindings have valid targets even though no source file represents the
66
+ # event itself.
67
+ HARNESS_EVENTS: Final[tuple[str, ...]] = (
68
+ "SessionStart",
69
+ "UserPromptSubmit",
70
+ "PreToolUse",
71
+ "PostToolUse",
72
+ "Notification",
73
+ "Stop",
74
+ "SubagentStop",
75
+ "PreCompact",
76
+ "PostCompact",
77
+ )
78
+
79
+ # Content-root-relative paths whose ``hooks`` block contributes binds-to
80
+ # edges. The committed ``hooks/hooks.json`` is the source-of-truth engine
81
+ # hook block the plugin tree and the per-harness settings templates render
82
+ # from; it lives inside the scanned content root, so its variable-prefixed
83
+ # script references resolve to bare hook node ids without crossing the root.
84
+ HOOK_CONFIG_RELPATHS: Final[tuple[str, ...]] = ("hooks/hooks.json",)
85
+
86
+ # Hook-artifact references inside a ``hooks.json`` command string carry a
87
+ # variable-prefixed absolute form (e.g.
88
+ # ``"${CLAUDE_PLUGIN_ROOT}/src/apothem/hooks/lib/bootstrap.sh"``). The capture
89
+ # group isolates the content-root-relative tail (``hooks/lib/bootstrap.sh``),
90
+ # which equals the hook node id the inventory assigns; the optional
91
+ # ``src/apothem/`` segment is consumed so the captured id never carries the
92
+ # content-root prefix. Message ``.md`` files count alongside the script
93
+ # extensions because the engine config binds them to context-emitting events.
94
+ _HOOK_REF_PATTERN: Final[re.Pattern[str]] = re.compile(
95
+ r"(?:src/apothem/)?(hooks/[\w./\-]+\.(?:ps1|sh|py|md))"
96
+ )
97
+
98
+ # Filenames inside src/apothem/skills/ that mark the entry point.
99
+ SKILL_ENTRY_FILENAME: Final[str] = "SKILL.md"
100
+
101
+ # Filename suffix that marks a persistent agent (flat <name>.md under src/apothem/agents/).
102
+ AGENT_ENTRY_SUFFIX: Final[str] = ".md"
103
+
104
+ # Maximum bytes read from a source file when scanning for references.
105
+ # The reference scan only inspects body content; large files (rare in
106
+ # this corpus) are truncated to keep the scan bounded.
107
+ SCAN_MAX_BYTES: Final[int] = 256_000
108
+
109
+ # Hook-script extensions. Shell and PowerShell stubs reference their siblings
110
+ # by bare filename, not by the content-root-relative node id: a POSIX stub
111
+ # sources ``. lib/find-python.sh``, a PowerShell stub joins
112
+ # ``hooks\lib\find-python.ps1`` with backslashes, and sibling comments name the
113
+ # counterpart as ``find-pwsh.ps1``. None of those carry the forward-slash node
114
+ # id the reference scan otherwise matches, so script-to-script references
115
+ # resolve by unique basename instead (see ``scan_text_references``).
116
+ SCRIPT_SUFFIXES: Final[tuple[str, ...]] = (".sh", ".ps1")
117
+
118
+ EXIT_OK: Final[int] = 0
119
+ EXIT_ERROR: Final[int] = 1
120
+
121
+
122
+ @dataclass(slots=True)
123
+ class Node:
124
+ """A capability-graph node — one unit with an addressable identifier."""
125
+
126
+ id: str
127
+ kind: str
128
+ path: str | None # None for harness-event virtual nodes.
129
+
130
+ def to_json(self) -> dict[str, object]:
131
+ return {"id": self.id, "kind": self.kind, "path": self.path}
132
+
133
+
134
+ @dataclass(slots=True)
135
+ class Edge:
136
+ """A directed reference from one node to another."""
137
+
138
+ source: str
139
+ target: str
140
+ relation: str
141
+ evidence_path: str # Source-side path where the reference was observed.
142
+
143
+ def to_json(self) -> dict[str, object]:
144
+ return {
145
+ "source": self.source,
146
+ "target": self.target,
147
+ "relation": self.relation,
148
+ "evidence-path": self.evidence_path,
149
+ }
150
+
151
+
152
+ @dataclass(slots=True)
153
+ class Graph:
154
+ """The full capability graph — nodes plus edges, deduplicated."""
155
+
156
+ nodes: list[Node] = field(default_factory=list)
157
+ edges: list[Edge] = field(default_factory=list)
158
+ edge_keys: set[tuple[str, str, str]] = field(default_factory=set)
159
+
160
+ def add_node(self, node: Node) -> None:
161
+ if not any(n.id == node.id and n.kind == node.kind for n in self.nodes):
162
+ self.nodes.append(node)
163
+
164
+ def add_edge(self, edge: Edge) -> None:
165
+ key = (edge.source, edge.target, edge.relation)
166
+ if key in self.edge_keys:
167
+ return
168
+ self.edge_keys.add(key)
169
+ self.edges.append(edge)
170
+
171
+
172
+ def load_inventory(path: Path) -> dict[str, object]:
173
+ """Read inventory.json and return its parsed payload."""
174
+ with path.open("r", encoding="utf-8") as handle:
175
+ payload: dict[str, object] = json.load(handle)
176
+ return payload
177
+
178
+
179
+ def derive_node_id(file_class: str, relative_path: str) -> str | None:
180
+ """Convert an inventory entry to its capability-graph node identifier.
181
+
182
+ Inventory paths are relative to the ecosystem content root (``src/apothem``
183
+ in this repo), so they carry bare top-level directory prefixes —
184
+ ``agents/<name>.md``, ``commands/<name>.md``, ``skills/<name>/SKILL.md``,
185
+ ``rules/<name>.md`` — matching the inventory's own ``classify_file``
186
+ keys. Agents, commands, output-styles, and rules drop that bare prefix and
187
+ the suffix; skills derive from the parent-directory name beneath
188
+ ``skills/``; hooks, statuslines, and MCP files use the relative path
189
+ verbatim.
190
+ """
191
+ if (
192
+ file_class == KIND_AGENT
193
+ and relative_path.startswith("agents/")
194
+ and relative_path.endswith(AGENT_ENTRY_SUFFIX)
195
+ ):
196
+ return relative_path.removeprefix("agents/").removesuffix(AGENT_ENTRY_SUFFIX)
197
+ if file_class == KIND_COMMAND and relative_path.endswith(".md"):
198
+ return relative_path.removeprefix("commands/").removesuffix(".md")
199
+ if file_class == KIND_SKILL and relative_path.endswith(SKILL_ENTRY_FILENAME):
200
+ # The parent directory of SKILL.md identifies the skill.
201
+ parts = relative_path.split("/")
202
+ if len(parts) >= 3 and parts[0] == "skills":
203
+ return parts[1]
204
+ return None
205
+ if file_class == KIND_HOOK:
206
+ return relative_path
207
+ if file_class == KIND_OUTPUT_STYLE:
208
+ return (
209
+ relative_path.removeprefix("output-styles/")
210
+ .removesuffix(".md")
211
+ .removesuffix(".json")
212
+ )
213
+ if file_class == KIND_STATUSLINE:
214
+ return relative_path
215
+ if file_class == "docs" and relative_path.startswith("rules/"):
216
+ return relative_path.removeprefix("rules/").removesuffix(".md")
217
+ if file_class == KIND_MCP:
218
+ return relative_path
219
+ return None
220
+
221
+
222
+ def register_nodes(inventory: dict[str, object], graph: Graph) -> None:
223
+ """Walk the inventory and populate ``graph.nodes`` for every addressable unit."""
224
+ files = inventory["files"]
225
+ if isinstance(files, list):
226
+ _register_file_nodes(files, graph)
227
+
228
+ # Register the canonical Claude Code hook events as virtual nodes so
229
+ # hook bindings can target them even though no source file represents
230
+ # an event itself.
231
+ for event in HARNESS_EVENTS:
232
+ graph.add_node(Node(id=event, kind=KIND_HARNESS_EVENT, path=None))
233
+
234
+
235
+ def _register_file_nodes(files: list[object], graph: Graph) -> None:
236
+ """Register one node per addressable inventory record in ``files``."""
237
+ for record in files:
238
+ if not isinstance(record, dict):
239
+ continue
240
+ cls = record.get("class")
241
+ path = record.get("path")
242
+ if not isinstance(cls, str) or not isinstance(path, str):
243
+ continue
244
+ identifier = derive_node_id(cls, path)
245
+ if identifier is None:
246
+ continue
247
+
248
+ kind_map = {
249
+ "agent": KIND_AGENT,
250
+ "command": KIND_COMMAND,
251
+ "skill": KIND_SKILL,
252
+ "hook": KIND_HOOK,
253
+ "output-style": KIND_OUTPUT_STYLE,
254
+ "statusline": KIND_STATUSLINE,
255
+ "mcp": KIND_MCP,
256
+ }
257
+ if cls in kind_map:
258
+ graph.add_node(Node(id=identifier, kind=kind_map[cls], path=path))
259
+ elif cls == "docs" and path.startswith("rules/"):
260
+ graph.add_node(Node(id=identifier, kind=KIND_RULE, path=path))
261
+
262
+
263
+ def read_text(absolute_path: Path) -> str:
264
+ """Read up to SCAN_MAX_BYTES of the file as UTF-8 text."""
265
+ try:
266
+ with absolute_path.open("rb") as handle:
267
+ data = handle.read(SCAN_MAX_BYTES)
268
+ return data.decode("utf-8", errors="replace")
269
+ except OSError:
270
+ return ""
271
+
272
+
273
+ def scan_text_references(graph: Graph, root: Path) -> None:
274
+ """For each text source, scan for mentions of every other node identifier.
275
+
276
+ Source nodes are agents, commands, skills, output-styles, rules, and
277
+ hook bodies — Markdown, Python, shell, and PowerShell. The body of each
278
+ source is read and matched against every other node's identifier as a
279
+ whole-word reference. Matches emit an ``invokes`` edge for agent / command
280
+ sources and a ``references`` edge for any other source kind.
281
+
282
+ Hook scripts additionally resolve their siblings by bare filename: a shell
283
+ or PowerShell stub names ``find-python.sh``, the backslash path
284
+ ``hooks\\lib\\find-python.ps1``, or the counterpart ``find-pwsh.ps1`` — never
285
+ the forward-slash node id — so script-source bodies are scanned for the
286
+ unique basenames of script nodes as well.
287
+ """
288
+ text_kinds = {
289
+ KIND_AGENT,
290
+ KIND_COMMAND,
291
+ KIND_SKILL,
292
+ KIND_OUTPUT_STYLE,
293
+ KIND_RULE,
294
+ KIND_HOOK,
295
+ }
296
+
297
+ non_event_nodes = [n for n in graph.nodes if n.kind != KIND_HARNESS_EVENT]
298
+ target_ids = sorted(
299
+ {n.id for n in non_event_nodes},
300
+ key=len,
301
+ reverse=True,
302
+ )
303
+ patterns: dict[str, re.Pattern[str]] = {
304
+ identifier: re.compile(rf"\b{re.escape(identifier)}\b")
305
+ for identifier in target_ids
306
+ }
307
+
308
+ # Script-sibling basename index: a shell / PowerShell stub references its
309
+ # siblings by filename, not by the content-root-relative node id. Map each
310
+ # script node's basename to its id, dropping basenames shared by more than
311
+ # one node so an ambiguous filename never mis-attributes an edge.
312
+ basename_ids: dict[str, list[str]] = {}
313
+ for node in non_event_nodes:
314
+ if node.path and node.path.endswith(SCRIPT_SUFFIXES):
315
+ basename_ids.setdefault(node.path.rsplit("/", 1)[-1], []).append(node.id)
316
+ script_targets: dict[str, str] = {
317
+ base: ids[0] for base, ids in basename_ids.items() if len(ids) == 1
318
+ }
319
+ script_patterns: dict[str, re.Pattern[str]] = {
320
+ base: re.compile(rf"\b{re.escape(base)}\b") for base in script_targets
321
+ }
322
+
323
+ for source in graph.nodes:
324
+ if source.kind not in text_kinds or source.path is None:
325
+ continue
326
+ if source.kind == KIND_HOOK and not source.path.endswith(
327
+ (".md", ".py", *SCRIPT_SUFFIXES)
328
+ ):
329
+ continue
330
+
331
+ absolute = root / source.path
332
+ body = read_text(absolute)
333
+ if not body:
334
+ continue
335
+
336
+ for target_id in target_ids:
337
+ if target_id == source.id:
338
+ continue
339
+ if patterns[target_id].search(body):
340
+ relation = (
341
+ RELATION_INVOKES
342
+ if source.kind in (KIND_AGENT, KIND_COMMAND)
343
+ else RELATION_REFERENCES
344
+ )
345
+ graph.add_edge(
346
+ Edge(
347
+ source=source.id,
348
+ target=target_id,
349
+ relation=relation,
350
+ evidence_path=source.path,
351
+ )
352
+ )
353
+
354
+ # Script siblings resolve their counterparts by bare filename, which
355
+ # the content-root-relative node-id scan above never matches.
356
+ if not source.path.endswith(SCRIPT_SUFFIXES):
357
+ continue
358
+ for base, pattern in script_patterns.items():
359
+ target_id = script_targets[base]
360
+ if target_id == source.id:
361
+ continue
362
+ if pattern.search(body):
363
+ graph.add_edge(
364
+ Edge(
365
+ source=source.id,
366
+ target=target_id,
367
+ relation=RELATION_REFERENCES,
368
+ evidence_path=source.path,
369
+ )
370
+ )
371
+
372
+
373
+ def scan_hook_bindings(graph: Graph, root: Path) -> None:
374
+ """Parse the engine hook config for event-name → hook-artifact bindings.
375
+
376
+ The committed ``hooks/hooks.json`` is the source-of-truth hook block the
377
+ plugin tree and the per-harness settings templates render from. Each
378
+ entry in its ``hooks`` block binds one or more hook artifacts — a
379
+ bootstrap stub plus, for context-emitting events, a message file — to a
380
+ harness event; every such pairing emits a ``binds-to`` edge from the hook
381
+ node to the event node. A single command may reference several artifacts,
382
+ so every distinct on-disk reference contributes its own edge.
383
+ """
384
+ for relpath in HOOK_CONFIG_RELPATHS:
385
+ path = root / relpath
386
+ if not path.is_file():
387
+ continue
388
+ try:
389
+ with path.open("r", encoding="utf-8") as handle:
390
+ config = json.load(handle)
391
+ except (OSError, json.JSONDecodeError):
392
+ continue
393
+ if not isinstance(config, dict):
394
+ continue
395
+
396
+ hooks_block = config.get("hooks", {})
397
+ if not isinstance(hooks_block, dict):
398
+ continue
399
+
400
+ for event_name, entries in hooks_block.items():
401
+ if event_name not in HARNESS_EVENTS:
402
+ continue
403
+ if not isinstance(entries, list):
404
+ continue
405
+ for entry in entries:
406
+ if not isinstance(entry, dict):
407
+ continue
408
+ hook_handlers = entry.get("hooks", [])
409
+ if not isinstance(hook_handlers, list):
410
+ continue
411
+ for handler in hook_handlers:
412
+ if not isinstance(handler, dict):
413
+ continue
414
+ command = handler.get("command", "")
415
+ if not isinstance(command, str):
416
+ continue
417
+ for node_id in _extract_hook_node_ids(command, root):
418
+ graph.add_edge(
419
+ Edge(
420
+ source=node_id,
421
+ target=event_name,
422
+ relation=RELATION_BINDS_TO,
423
+ evidence_path=relpath,
424
+ )
425
+ )
426
+
427
+
428
+ def _extract_hook_node_ids(command: str, root: Path) -> list[str]:
429
+ """Extract the content-root-relative hook node ids a command references.
430
+
431
+ A ``hooks.json`` ``command`` field is a shell invocation (PowerShell or
432
+ bash) that runs one or more hook artifacts — a bootstrap stub, and for
433
+ context-emitting events the per-event message file. Each is named by a
434
+ variable-prefixed path (e.g.
435
+ ``"${CLAUDE_PLUGIN_ROOT}/src/apothem/hooks/messages/stop.md"``); the
436
+ meaningful ``binds-to`` target is the content-root-relative tail
437
+ (``hooks/messages/stop.md``), which equals the inventory's hook node id.
438
+ Every distinct reference that resolves to a file on disk is returned in
439
+ first-seen order, so a single command binds every hook node it names.
440
+ """
441
+ node_ids: list[str] = []
442
+ seen: set[str] = set()
443
+ for match in _HOOK_REF_PATTERN.finditer(command):
444
+ node_id = match.group(1)
445
+ if node_id in seen:
446
+ continue
447
+ if not (root / node_id).is_file():
448
+ continue
449
+ seen.add(node_id)
450
+ node_ids.append(node_id)
451
+ return node_ids
452
+
453
+
454
+ def _python_import_names(relative_path: str, package_root: str) -> list[str]:
455
+ """Module names by which the Python file at ``relative_path`` is importable.
456
+
457
+ A file is importable under several names depending on which ancestor
458
+ directory is on ``sys.path`` at runtime. Returns the content-root-relative
459
+ dotted path (``hooks.lib.log``), the installed-package form
460
+ (``apothem.hooks.lib.log``), and — for a non-package module — the bare leaf
461
+ name (``log``). The hook bootstrap inserts both ``hooks/`` and ``hooks/lib/``
462
+ onto ``sys.path``, so the lib modules are imported by bare leaf name
463
+ (``from log import get_logger``); the bare-leaf candidate is what resolves
464
+ those edges. A package ``__init__.py`` is named for its directory, never the
465
+ literal ``__init__`` leaf, so it is never imported by leaf name.
466
+ """
467
+ parts = relative_path.removesuffix(".py").split("/")
468
+ is_package = parts[-1] == "__init__"
469
+ if is_package:
470
+ parts = parts[:-1]
471
+ if not parts:
472
+ return []
473
+ dotted = ".".join(parts)
474
+ names = [dotted, f"{package_root}.{dotted}"]
475
+ if not is_package:
476
+ names.append(parts[-1])
477
+ return names
478
+
479
+
480
+ def scan_python_imports(graph: Graph, root: Path) -> None:
481
+ """Trace import edges between Python source files in the graph.
482
+
483
+ Each Python node is indexed under every module name it answers to (see
484
+ ``_python_import_names``). An import statement whose module equals a known
485
+ name — or names a submodule of a known package — emits an ``imports`` edge
486
+ to that node. Bare-leaf indexing is what makes the hook lib modules resolve:
487
+ they are imported by bare name (``from log import …``) because the bootstrap
488
+ puts ``hooks/lib/`` on ``sys.path``, never by their content-root dotted path.
489
+ """
490
+ # Pair each Python node with its non-None path so the edge endpoints are
491
+ # typed ``str`` rather than ``str | None``.
492
+ py_paths: list[tuple[Node, str]] = [
493
+ (n, n.path)
494
+ for n in graph.nodes
495
+ if n.path is not None and n.path.endswith(".py")
496
+ ]
497
+ # Index every importable name to its file. First-seen wins on the rare
498
+ # basename collision; package ``__init__`` modules are excluded from bare-
499
+ # leaf indexing, so collisions are vanishingly unlikely in practice.
500
+ module_index: dict[str, str] = {}
501
+ for _node, path in py_paths:
502
+ for candidate in _python_import_names(path, root.name):
503
+ module_index.setdefault(candidate, path)
504
+
505
+ import_pattern = re.compile(
506
+ r"^\s*(?:from\s+([\w.]+)\s+import|import\s+([\w.]+))",
507
+ re.MULTILINE,
508
+ )
509
+
510
+ for _source, source_path in py_paths:
511
+ body = read_text(root / source_path)
512
+ if not body:
513
+ continue
514
+ for match in import_pattern.finditer(body):
515
+ module = match.group(1) or match.group(2)
516
+ if not module:
517
+ continue
518
+ for known_module, known_path in module_index.items():
519
+ if module == known_module or module.startswith(known_module + "."):
520
+ if known_path == source_path:
521
+ continue
522
+ graph.add_edge(
523
+ Edge(
524
+ source=source_path,
525
+ target=known_path,
526
+ relation=RELATION_IMPORTS,
527
+ evidence_path=source_path,
528
+ )
529
+ )
530
+
531
+
532
+ def emit_graph(graph: Graph, output: Path) -> None:
533
+ """Serialise the graph to ``output`` as pretty-printed JSON."""
534
+ payload = {
535
+ "generated-at": datetime.now(tz=timezone.utc).isoformat(),
536
+ "node-count": len(graph.nodes),
537
+ "edge-count": len(graph.edges),
538
+ "nodes": sorted(
539
+ (n.to_json() for n in graph.nodes),
540
+ key=lambda n: (n["kind"], n["id"]),
541
+ ),
542
+ "edges": sorted(
543
+ (e.to_json() for e in graph.edges),
544
+ key=lambda e: (e["source"], e["relation"], e["target"]),
545
+ ),
546
+ }
547
+ output.parent.mkdir(parents=True, exist_ok=True)
548
+ with output.open("w", encoding="utf-8") as handle:
549
+ json.dump(payload, handle, indent=2, sort_keys=False, ensure_ascii=False)
550
+ handle.write("\n")
551
+
552
+
553
+ def parse_arguments(argv: list[str]) -> argparse.Namespace:
554
+ """CLI surface — root, inventory, and output are required."""
555
+ parser = argparse.ArgumentParser(
556
+ prog="build_capability_graph",
557
+ description="Build a capability graph from the inventory.",
558
+ )
559
+ parser.add_argument(
560
+ "--root",
561
+ type=Path,
562
+ required=True,
563
+ help="Working-tree root containing the source artifacts.",
564
+ )
565
+ parser.add_argument(
566
+ "--inventory",
567
+ type=Path,
568
+ required=True,
569
+ help="Inventory JSON path produced by build_inventory.py.",
570
+ )
571
+ parser.add_argument(
572
+ "--output",
573
+ type=Path,
574
+ required=True,
575
+ help="Output JSON path (e.g., .audit/capability-graph.json).",
576
+ )
577
+ return parser.parse_args(argv)
578
+
579
+
580
+ def main(argv: list[str]) -> int:
581
+ """Entry point — returns the exit code."""
582
+ args = parse_arguments(argv)
583
+ root = args.root.resolve()
584
+ inventory_path = args.inventory.resolve()
585
+ output = args.output.resolve()
586
+
587
+ if not inventory_path.is_file():
588
+ print(f"error: inventory not found: {inventory_path}", file=sys.stderr)
589
+ return EXIT_ERROR
590
+
591
+ inventory = load_inventory(inventory_path)
592
+ graph = Graph()
593
+ register_nodes(inventory, graph)
594
+ scan_text_references(graph, root)
595
+ scan_hook_bindings(graph, root)
596
+ scan_python_imports(graph, root)
597
+ emit_graph(graph, output)
598
+
599
+ print(
600
+ f"capability-graph: {len(graph.nodes)} nodes; "
601
+ f"{len(graph.edges)} edges; output={output}"
602
+ )
603
+ return EXIT_OK
604
+
605
+
606
+ if __name__ == "__main__":
607
+ raise SystemExit(main(sys.argv[1:]))