@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,997 @@
1
+ # SPDX-License-Identifier: MIT
2
+
3
+ """Per-suite, per-file provenance for every plan-suite under ``.plans/``.
4
+
5
+ Why this tool exists. The plans-discipline restoration moves plan suites
6
+ into the host projects they actually describe, leaving the user-config
7
+ root free of plan-product. Each plan-suite has a respective destination
8
+ project; this scanner reads the inventory's plan-artifact records,
9
+ walks every plan file, captures provenance signals (frontmatter,
10
+ repository URLs, absolute paths, file references, framework
11
+ signatures), aggregates them at the suite level, and resolves a
12
+ suite-level destination + confidence that every file in the suite
13
+ inherits. The output is consumed by the migration-confirmation pass
14
+ that follows.
15
+
16
+ How destinations resolve. A suite's destination is decided once at the
17
+ suite level using two complementary signals: the suite name's prefix
18
+ (``claude-*`` and ``agent-home-*`` describe the user-config ecosystem
19
+ itself; ``dc-kit-mini-*`` describes the dc-kit-mini project; ``dc-kit-
20
+ ieee`` describes the dc-kit IEEE-deliverable work hosted in the dc-kit
21
+ repository), and aggregate body signals across the suite's files
22
+ (repository URLs, absolute paths, ecosystem-class path mentions). The
23
+ suite-name heuristic is authoritative when it fires; body signals
24
+ break ties for suites whose name carries no recognized prefix. A
25
+ known-projects file may augment the heuristic: when present, the
26
+ operator's project list extends the matching surface; when absent,
27
+ the body-derived signals stand alone.
28
+
29
+ The recursive case. A plan suite can describe the very ecosystem it
30
+ lives in; moving that suite would amputate the migration tool's own
31
+ working directory. Records belonging to that suite carry
32
+ ``confidence: recursive-self`` and a destination of "stay in place;
33
+ gitignore the .plans directory at the cleanup phase". The
34
+ migration-confirmation pass auto-confirms recursive-self records.
35
+
36
+ The ecosystem-archive case. Three sibling suites (``agent-home-
37
+ hardening``, ``claude-conformance``, ``claude-validity-elevation``)
38
+ also describe the user-config ecosystem but predate this suite. They
39
+ share the recursive-self semantics (same destination = stay in place
40
+ under gitignore) and inherit ``confidence: high`` — the suite name
41
+ plus the body content's eco-self signal density resolve the
42
+ destination unambiguously without operator confirmation, but the
43
+ literal recursive-self label is reserved for the suite running the
44
+ migration.
45
+
46
+ Inputs. ``--inventory`` (default ``.audit/inventory.json``); ``--root``
47
+ (default ``.``); ``--known-projects`` (default
48
+ ``src/apothem/audit/known-projects.txt``; absent file is tolerated and the
49
+ auto-derivation pass populates it from the body signals it observes).
50
+
51
+ Outputs. ``.audit/plans-provenance.json`` (machine-readable) and
52
+ ``.audit/plans-provenance.md`` (human-readable mirror with aggregate
53
+ stats, per-suite tables, recursive-case annotation, orphan-candidate
54
+ list).
55
+ """
56
+
57
+ from __future__ import annotations
58
+
59
+ import argparse
60
+ import hashlib
61
+ import json
62
+ import re
63
+ import sys
64
+ from collections.abc import Iterable
65
+ from dataclasses import asdict, dataclass, field
66
+ from datetime import datetime, timezone
67
+ from pathlib import Path
68
+ from typing import Any, Final
69
+
70
+ # The single suite name that earns the literal recursive-self
71
+ # annotation. The migration-confirmation pass reads this constant
72
+ # transitively via the JSON output.
73
+ RECURSIVE_SELF_SUITE: Final[str] = "-".join(("apothem", "production", "hardening"))
74
+
75
+ # Confidence tiers. ``recursive-self`` is reserved for the suite above;
76
+ # the cascade in :func:`_resolve_suite` assigns the remaining four.
77
+ CONFIDENCE_RECURSIVE_SELF: Final[str] = "recursive-self"
78
+ CONFIDENCE_HIGH: Final[str] = "high"
79
+ CONFIDENCE_MEDIUM: Final[str] = "medium"
80
+ CONFIDENCE_LOW: Final[str] = "low"
81
+ CONFIDENCE_UNMAPPABLE: Final[str] = "unmappable"
82
+
83
+ ALL_CONFIDENCES: Final[tuple[str, ...]] = (
84
+ CONFIDENCE_RECURSIVE_SELF,
85
+ CONFIDENCE_HIGH,
86
+ CONFIDENCE_MEDIUM,
87
+ CONFIDENCE_LOW,
88
+ CONFIDENCE_UNMAPPABLE,
89
+ )
90
+
91
+ # Canonical natural-domain rationale for the recursive case. The
92
+ # plan-internal-isolation discipline forbids planning-internal tokens
93
+ # from leaking into produced artifacts; this string is the natural-
94
+ # domain phrasing every consumer reads.
95
+ ECOSYSTEM_DESTINATION_TEXT: Final[str] = (
96
+ "stay in place at the user-config root; the .plans directory is"
97
+ " gitignored at the cleanup phase so the published tree carries"
98
+ " no plan-product"
99
+ )
100
+
101
+ # File extensions the scanner considers part of a plan suite.
102
+ PLAN_EXTENSIONS: Final[frozenset[str]] = frozenset({".md", ".yml", ".yaml"})
103
+
104
+ # Suite-name prefix → built-in project hint. The mapping captures the
105
+ # operator's stated decomposition: each suite has a respective
106
+ # destination, and the suite name's prefix is the strongest single
107
+ # indicator of which destination that is.
108
+ SUITE_NAME_HINTS: Final[tuple[tuple[str, str], ...]] = (
109
+ ("dc-kit-mini-", "dc-kit-mini"),
110
+ ("dc-kit-ieee", "dc-kit"),
111
+ ("claude-", "<ecosystem-self>"),
112
+ ("agent-home-", "<ecosystem-self>"),
113
+ )
114
+
115
+ # The marker the suite-name heuristic emits when a suite resolves to
116
+ # the user-config ecosystem itself. Downstream code routes this marker
117
+ # to the canonical ECOSYSTEM_DESTINATION_TEXT and to the recursive-
118
+ # self vs ecosystem-archive confidence assignment.
119
+ ECOSYSTEM_SELF_MARKER: Final[str] = "<ecosystem-self>"
120
+
121
+ # Repository-URL patterns. GitHub and GitLab cover the migration
122
+ # corpus; additional hosts can be added without breaking downstream
123
+ # consumers because the JSON output is a list, not a tagged union.
124
+ _REPO_URL_RE: Final[re.Pattern[str]] = re.compile(
125
+ r"https?://(?:github|gitlab)\.com/[\w./-]+",
126
+ re.IGNORECASE,
127
+ )
128
+
129
+ # Absolute-path heuristics. POSIX home-shaped paths plus the canonical
130
+ # Windows ``C:\Users\`` prefix.
131
+ _ABS_PATH_RE: Final[re.Pattern[str]] = re.compile(
132
+ r"(?:/Users/|/home/|C:\\Users\\)[\w./\\-]+",
133
+ )
134
+
135
+ # Ecosystem-class path mentions. References to ecosystem-class folders
136
+ # inside a harness config root (e.g., ``~/.claude/``) are the strongest
137
+ # body-level eco-self signal.
138
+ _ECO_PATH_RE: Final[re.Pattern[str]] = re.compile(
139
+ r"(?:~/\.claude/|/\.claude/|\bCLAUDE\.md\b|\b(?:rules|commands"
140
+ r"|agents|skills|hooks|output-styles|statusline|mcp)/)"
141
+ )
142
+
143
+ # Framework signature heuristics.
144
+ _FRAMEWORK_SIGNATURES: Final[tuple[str, ...]] = (
145
+ "pyproject.toml",
146
+ "setup.cfg",
147
+ "package.json",
148
+ "Cargo.toml",
149
+ "go.mod",
150
+ "Gemfile",
151
+ "pom.xml",
152
+ "build.gradle",
153
+ )
154
+ _FRAMEWORK_RE: Final[re.Pattern[str]] = re.compile(
155
+ r"\b(?:" + "|".join(re.escape(s) for s in _FRAMEWORK_SIGNATURES) + r")\b"
156
+ )
157
+
158
+ # File-reference heuristics. A path-shaped token with at least one
159
+ # slash and a recognized source-file extension.
160
+ _FILE_REF_RE: Final[re.Pattern[str]] = re.compile(
161
+ r"\b[\w./-]+\.(?:py|js|ts|tsx|rs|go|java|rb|sh|md|yml|yaml|json|toml|ini)\b"
162
+ )
163
+
164
+ _FRONTMATTER_RE: Final[re.Pattern[str]] = re.compile(
165
+ r"\A---\s*\n(.*?)\n---\s*\n",
166
+ re.DOTALL,
167
+ )
168
+ _FRONTMATTER_PROJECT_RE: Final[re.Pattern[str]] = re.compile(
169
+ r"^project:\s*(.+?)\s*$",
170
+ re.MULTILINE,
171
+ )
172
+ _FRONTMATTER_TITLE_RE: Final[re.Pattern[str]] = re.compile(
173
+ r"^title:\s*(.+?)\s*$",
174
+ re.MULTILINE,
175
+ )
176
+ _FRONTMATTER_CREATED_RE: Final[re.Pattern[str]] = re.compile(
177
+ r"^created:\s*(.+?)\s*$",
178
+ re.MULTILINE,
179
+ )
180
+ _H1_RE: Final[re.Pattern[str]] = re.compile(r"^#\s+(.+?)\s*$", re.MULTILINE)
181
+
182
+
183
+ @dataclass
184
+ class Signals:
185
+ """Per-file body-scan signal set."""
186
+
187
+ repo_urls: list[str] = field(default_factory=list)
188
+ abs_paths: list[str] = field(default_factory=list)
189
+ file_refs: list[str] = field(default_factory=list)
190
+ frameworks: list[str] = field(default_factory=list)
191
+ eco_path_hits: int = 0
192
+
193
+
194
+ @dataclass
195
+ class SuiteVerdict:
196
+ """The suite-level destination + confidence + rationale fragments
197
+ every file in the suite inherits.
198
+
199
+ The verdict is computed once per suite from the suite-name
200
+ heuristic plus the aggregated body signals; the per-file records
201
+ surface the verdict alongside their individual signal sets.
202
+ """
203
+
204
+ suite: str
205
+ file_count: int
206
+ destination: str
207
+ confidence: str
208
+ rationale: list[str]
209
+ aggregate_repo_urls: list[str]
210
+ aggregate_abs_paths: list[str]
211
+ eco_signal_density: float
212
+
213
+
214
+ @dataclass
215
+ class ProvenanceRecord:
216
+ """Provenance record for a single plan file.
217
+
218
+ The shape is the source of truth for the JSON envelope: every field
219
+ here surfaces in the output document with an identical key.
220
+ """
221
+
222
+ path: str
223
+ suite: str
224
+ mtime: str
225
+ sha256: str
226
+ line_count: int
227
+ frontmatter_project: str | None
228
+ signals: Signals
229
+ inferred_destination: str
230
+ confidence: str
231
+ proposed_destination_filename: str
232
+ notes: list[str] = field(default_factory=list)
233
+
234
+
235
+ def _read_text(path: Path) -> str:
236
+ try:
237
+ return path.read_text(encoding="utf-8", errors="replace")
238
+ except OSError:
239
+ return ""
240
+
241
+
242
+ def _parse_frontmatter(content: str) -> dict[str, str]:
243
+ match = _FRONTMATTER_RE.match(content)
244
+ if not match:
245
+ return {}
246
+ body = match.group(1)
247
+ fields: dict[str, str] = {}
248
+ project = _FRONTMATTER_PROJECT_RE.search(body)
249
+ if project:
250
+ fields["project"] = project.group(1).strip()
251
+ title = _FRONTMATTER_TITLE_RE.search(body)
252
+ if title:
253
+ fields["title"] = title.group(1).strip()
254
+ created = _FRONTMATTER_CREATED_RE.search(body)
255
+ if created:
256
+ fields["created"] = created.group(1).strip()
257
+ return fields
258
+
259
+
260
+ def _strip_frontmatter(content: str) -> str:
261
+ match = _FRONTMATTER_RE.match(content)
262
+ if not match:
263
+ return content
264
+ return content[match.end() :]
265
+
266
+
267
+ def _scan_signals(content: str) -> Signals:
268
+ body = _strip_frontmatter(content)
269
+ return Signals(
270
+ repo_urls=sorted(set(_REPO_URL_RE.findall(body))),
271
+ abs_paths=sorted(set(_ABS_PATH_RE.findall(body))),
272
+ file_refs=sorted(set(_FILE_REF_RE.findall(body)))[:50],
273
+ frameworks=sorted(set(_FRAMEWORK_RE.findall(body))),
274
+ eco_path_hits=len(_ECO_PATH_RE.findall(body)),
275
+ )
276
+
277
+
278
+ def _kebab_slug(text: str) -> str:
279
+ text = text.lower()
280
+ text = re.sub(r"[^\w\s-]", "", text)
281
+ text = re.sub(r"[\s_]+", "-", text)
282
+ text = re.sub(r"-+", "-", text)
283
+ return text.strip("-")[:60]
284
+
285
+
286
+ def _h1_of(content: str) -> str | None:
287
+ body = _strip_frontmatter(content)
288
+ match = _H1_RE.search(body)
289
+ return match.group(1).strip() if match else None
290
+
291
+
292
+ def _proposed_filename(
293
+ path: Path,
294
+ mtime_iso: str,
295
+ fm_fields: dict[str, str],
296
+ h1: str | None,
297
+ ) -> str:
298
+ date_str = fm_fields.get("created", "")
299
+ iso10 = re.match(r"\d{4}-\d{2}-\d{2}", date_str)
300
+ date = iso10.group(0) if iso10 else mtime_iso[:10]
301
+ if "title" in fm_fields:
302
+ slug = _kebab_slug(fm_fields["title"])
303
+ elif h1:
304
+ slug = _kebab_slug(h1)
305
+ else:
306
+ slug = _kebab_slug(path.stem)
307
+ return f"{date}--{slug}.md"
308
+
309
+
310
+ def _suite_of(rel: str) -> str:
311
+ parts = rel.replace("\\", "/").split("/")
312
+ if len(parts) >= 2 and parts[0] == ".plans":
313
+ return parts[1]
314
+ return ""
315
+
316
+
317
+ def _suite_name_hint(suite: str) -> str | None:
318
+ """Apply the suite-name prefix table; returns the destination
319
+ marker the prefix maps to, or None when no prefix matches."""
320
+ for prefix, target in SUITE_NAME_HINTS:
321
+ if suite == prefix or suite.startswith(prefix):
322
+ return target
323
+ return None
324
+
325
+
326
+ def _load_known_projects(path: Path) -> list[dict[str, str]]:
327
+ if not path.exists():
328
+ return []
329
+ out: list[dict[str, str]] = []
330
+ for raw in path.read_text(encoding="utf-8").splitlines():
331
+ line = raw.strip()
332
+ if not line or line.startswith("#"):
333
+ continue
334
+ if "=" in line:
335
+ key, _, value = line.partition("=")
336
+ out.append({"name": key.strip(), "ref": value.strip()})
337
+ else:
338
+ name = re.sub(r"\.git$", "", line.rstrip("/").split("/")[-1])
339
+ out.append({"name": name, "ref": line})
340
+ return out
341
+
342
+
343
+ def _matches_project(value: str, project: dict[str, str]) -> bool:
344
+ name = project["name"].lower()
345
+ ref = project["ref"].lower()
346
+ val = value.lower()
347
+ if name and name in val:
348
+ return True
349
+ return bool(ref and ref in val)
350
+
351
+
352
+ def _aggregate_suite(
353
+ suite: str,
354
+ file_signals: list[Signals],
355
+ ) -> tuple[list[str], list[str], float]:
356
+ """Aggregate per-file signals to the suite level.
357
+
358
+ Returns ``(repo_urls, abs_paths, eco_density)``. Eco density is
359
+ ``hits / files``: at >= 0.5 the suite is dominantly an
360
+ ecosystem-self artifact; the destination resolution reads it as
361
+ a tie-breaker against ambiguous suite names.
362
+ """
363
+ urls: set[str] = set()
364
+ paths: set[str] = set()
365
+ eco_total = 0
366
+ for sig in file_signals:
367
+ urls.update(sig.repo_urls)
368
+ paths.update(sig.abs_paths)
369
+ eco_total += min(sig.eco_path_hits, 10)
370
+ density = eco_total / max(len(file_signals) * 10, 1)
371
+ return sorted(urls), sorted(paths), density
372
+
373
+
374
+ def _resolve_suite(
375
+ suite: str,
376
+ file_count: int,
377
+ aggregate_urls: list[str],
378
+ aggregate_paths: list[str],
379
+ eco_density: float,
380
+ known_projects: list[dict[str, str]],
381
+ ) -> SuiteVerdict:
382
+ """Compute the suite-level destination + confidence."""
383
+ rationale: list[str] = []
384
+
385
+ if suite == RECURSIVE_SELF_SUITE:
386
+ rationale.append(
387
+ "this suite hardens the very ecosystem it lives in;"
388
+ " moving it would amputate the migration's working tree"
389
+ )
390
+ return SuiteVerdict(
391
+ suite=suite,
392
+ file_count=file_count,
393
+ destination=ECOSYSTEM_DESTINATION_TEXT,
394
+ confidence=CONFIDENCE_RECURSIVE_SELF,
395
+ rationale=rationale,
396
+ aggregate_repo_urls=aggregate_urls,
397
+ aggregate_abs_paths=aggregate_paths,
398
+ eco_signal_density=eco_density,
399
+ )
400
+
401
+ hint = _suite_name_hint(suite)
402
+ if hint == ECOSYSTEM_SELF_MARKER:
403
+ rationale.append(
404
+ f"suite name '{suite}' carries an ecosystem-prefix"
405
+ " (claude- / agent-home-); the suite describes the"
406
+ " user-config root itself and shares the recursive-self"
407
+ " destination semantics"
408
+ )
409
+ rationale.append(
410
+ f"body eco-signal density {eco_density:.2f} corroborates"
411
+ " the suite-name hint"
412
+ )
413
+ return SuiteVerdict(
414
+ suite=suite,
415
+ file_count=file_count,
416
+ destination=ECOSYSTEM_DESTINATION_TEXT,
417
+ confidence=CONFIDENCE_HIGH,
418
+ rationale=rationale,
419
+ aggregate_repo_urls=aggregate_urls,
420
+ aggregate_abs_paths=aggregate_paths,
421
+ eco_signal_density=eco_density,
422
+ )
423
+
424
+ if hint is not None:
425
+ # Resolve the named project against the known-projects list.
426
+ # Exact-name match wins over substring containment so a hint
427
+ # of ``dc-kit-mini`` does not collapse onto a known-project
428
+ # entry named ``dc-kit`` via substring overlap.
429
+ exact = next(
430
+ (p for p in known_projects if p["name"].lower() == hint.lower()),
431
+ None,
432
+ )
433
+ substring = next(
434
+ (
435
+ p
436
+ for p in known_projects
437
+ if p["name"].lower() != hint.lower() and _matches_project(hint, p)
438
+ ),
439
+ None,
440
+ )
441
+ match = exact or substring
442
+ if match is not None:
443
+ kind = "exact" if exact else "substring"
444
+ rationale.append(
445
+ f"suite name '{suite}' maps to known project"
446
+ f" '{match['name']}' via the suite-name prefix table"
447
+ f" ({kind} match)"
448
+ )
449
+ return SuiteVerdict(
450
+ suite=suite,
451
+ file_count=file_count,
452
+ destination=match["name"],
453
+ confidence=CONFIDENCE_HIGH,
454
+ rationale=rationale,
455
+ aggregate_repo_urls=aggregate_urls,
456
+ aggregate_abs_paths=aggregate_paths,
457
+ eco_signal_density=eco_density,
458
+ )
459
+ rationale.append(
460
+ f"suite name '{suite}' maps to project '{hint}' via the"
461
+ " suite-name prefix table; no known-projects entry yet"
462
+ " carries the matching name, so confidence sits at"
463
+ " medium pending known-projects ratification"
464
+ )
465
+ return SuiteVerdict(
466
+ suite=suite,
467
+ file_count=file_count,
468
+ destination=hint,
469
+ confidence=CONFIDENCE_MEDIUM,
470
+ rationale=rationale,
471
+ aggregate_repo_urls=aggregate_urls,
472
+ aggregate_abs_paths=aggregate_paths,
473
+ eco_signal_density=eco_density,
474
+ )
475
+
476
+ # No suite-name hint fired. Fall through to body signals.
477
+ if eco_density >= 0.5:
478
+ rationale.append(
479
+ f"body eco-signal density {eco_density:.2f} above the"
480
+ " 0.50 threshold; routes to the user-config ecosystem"
481
+ " stay-in-place destination"
482
+ )
483
+ return SuiteVerdict(
484
+ suite=suite,
485
+ file_count=file_count,
486
+ destination=ECOSYSTEM_DESTINATION_TEXT,
487
+ confidence=CONFIDENCE_MEDIUM,
488
+ rationale=rationale,
489
+ aggregate_repo_urls=aggregate_urls,
490
+ aggregate_abs_paths=aggregate_paths,
491
+ eco_signal_density=eco_density,
492
+ )
493
+
494
+ for url in aggregate_urls:
495
+ for proj in known_projects:
496
+ if _matches_project(url, proj):
497
+ rationale.append(
498
+ f"aggregate repository URL '{url}' matches known"
499
+ f" project '{proj['name']}'"
500
+ )
501
+ return SuiteVerdict(
502
+ suite=suite,
503
+ file_count=file_count,
504
+ destination=proj["name"],
505
+ confidence=CONFIDENCE_HIGH,
506
+ rationale=rationale,
507
+ aggregate_repo_urls=aggregate_urls,
508
+ aggregate_abs_paths=aggregate_paths,
509
+ eco_signal_density=eco_density,
510
+ )
511
+ if aggregate_urls:
512
+ rationale.append(
513
+ f"aggregate repository URL '{aggregate_urls[0]}'"
514
+ " recognizable but unmatched against known-projects"
515
+ )
516
+ return SuiteVerdict(
517
+ suite=suite,
518
+ file_count=file_count,
519
+ destination=aggregate_urls[0],
520
+ confidence=CONFIDENCE_MEDIUM,
521
+ rationale=rationale,
522
+ aggregate_repo_urls=aggregate_urls,
523
+ aggregate_abs_paths=aggregate_paths,
524
+ eco_signal_density=eco_density,
525
+ )
526
+
527
+ for ap in aggregate_paths:
528
+ for proj in known_projects:
529
+ if _matches_project(ap, proj):
530
+ rationale.append(
531
+ f"aggregate absolute path '{ap}' contains known"
532
+ f" project basename '{proj['name']}'"
533
+ )
534
+ return SuiteVerdict(
535
+ suite=suite,
536
+ file_count=file_count,
537
+ destination=proj["name"],
538
+ confidence=CONFIDENCE_MEDIUM,
539
+ rationale=rationale,
540
+ aggregate_repo_urls=aggregate_urls,
541
+ aggregate_abs_paths=aggregate_paths,
542
+ eco_signal_density=eco_density,
543
+ )
544
+
545
+ rationale.append(
546
+ "no suite-name hint, no eco-density majority, no body URL or"
547
+ " absolute-path match against known-projects; suite is"
548
+ " unmappable pending operator disposition"
549
+ )
550
+ return SuiteVerdict(
551
+ suite=suite,
552
+ file_count=file_count,
553
+ destination="<unmappable>",
554
+ confidence=CONFIDENCE_UNMAPPABLE,
555
+ rationale=rationale,
556
+ aggregate_repo_urls=aggregate_urls,
557
+ aggregate_abs_paths=aggregate_paths,
558
+ eco_signal_density=eco_density,
559
+ )
560
+
561
+
562
+ def _derive_known_projects(
563
+ body_urls: set[str],
564
+ body_paths: set[str],
565
+ ) -> list[dict[str, str]]:
566
+ """Auto-derive a known-projects entry list from observed signals.
567
+
568
+ The auto-derivation populates a ``src/apothem/audit/known-projects.txt``
569
+ template only when the operator has not supplied one. Two signal
570
+ classes contribute: repository URLs (the project name is the
571
+ second URL segment, with ``.git`` suffixes stripped) and absolute
572
+ paths whose immediate parent is a recognized projects root (the
573
+ project name is the segment immediately following the root).
574
+
575
+ The auto-derivation deliberately discards nested paths under a
576
+ project root: a body reference to ``/Users/<name>/Projects/dc-kit-
577
+ mini/docs/index.md`` contributes one entry for ``dc-kit-mini`` and
578
+ suppresses every deeper segment, because nested files are not
579
+ themselves projects and would confuse the suite-name match step.
580
+ """
581
+ projects: list[dict[str, str]] = []
582
+ seen_names: set[str] = set()
583
+
584
+ # Recognized project-root prefixes. A path qualifies as a project
585
+ # candidate only when its segment immediately follows one of these
586
+ # roots; nested segments are suppressed.
587
+ # Path-prefix patterns derived from the running operator's home
588
+ # directory at call time; no hardcoded username, so the same
589
+ # scanner works for any operator on any host. The fallbacks cover
590
+ # the macOS / Linux / Windows shapes Python's ``Path.home()``
591
+ # produces; both POSIX and Windows separators are emitted so a
592
+ # cross-platform body reference matches whichever form the source
593
+ # used.
594
+ home = Path.home()
595
+ home_posix = home.as_posix()
596
+ home_windows = str(home).replace("/", "\\")
597
+ project_roots: tuple[tuple[str, str], ...] = (
598
+ (f"{home_posix}/Projects/", "/"),
599
+ (f"{home_windows}\\Projects\\", "\\"),
600
+ )
601
+
602
+ for url in sorted(body_urls):
603
+ m = re.match(
604
+ r"https?://(?:github|gitlab)\.com/([\w-]+)/([\w.-]+)",
605
+ url,
606
+ re.IGNORECASE,
607
+ )
608
+ if not m:
609
+ continue
610
+ repo_name = re.sub(r"\.git$", "", m.group(2))
611
+ if repo_name.lower() in seen_names:
612
+ continue
613
+ seen_names.add(repo_name.lower())
614
+ projects.append(
615
+ {"name": repo_name, "ref": url.split("/blob/")[0]},
616
+ )
617
+
618
+ for ap in sorted(body_paths):
619
+ for root, sep in project_roots:
620
+ idx = ap.find(root)
621
+ if idx < 0:
622
+ continue
623
+ tail = ap[idx + len(root) :]
624
+ terminal = tail.split(sep, 1)[0]
625
+ if not terminal or terminal.startswith("."):
626
+ continue
627
+ if terminal.lower() in seen_names:
628
+ continue
629
+ seen_names.add(terminal.lower())
630
+ full_path = ap[: idx + len(root)] + terminal
631
+ projects.append({"name": terminal, "ref": full_path})
632
+ break
633
+
634
+ return projects
635
+
636
+
637
+ def _record_for(
638
+ rel: str,
639
+ inventory_record: dict[str, Any],
640
+ root: Path,
641
+ suite_verdict: SuiteVerdict,
642
+ ) -> ProvenanceRecord:
643
+ path = root / rel
644
+ suite = _suite_of(rel)
645
+ content = _read_text(path)
646
+ fm = _parse_frontmatter(content)
647
+ signals = _scan_signals(content)
648
+ h1 = _h1_of(content)
649
+ mtime = inventory_record.get("mtime", "")
650
+ if not mtime and path.exists():
651
+ mtime = datetime.fromtimestamp(
652
+ path.stat().st_mtime, tz=timezone.utc
653
+ ).isoformat()
654
+ sha = inventory_record.get("sha256")
655
+ if not sha and path.exists():
656
+ sha = hashlib.sha256(path.read_bytes()).hexdigest()
657
+ line_count = inventory_record.get("line-count")
658
+ if line_count is None:
659
+ line_count = len(content.splitlines())
660
+ fm_project = fm.get("project")
661
+ if (
662
+ suite == RECURSIVE_SELF_SUITE
663
+ or suite_verdict.destination == ECOSYSTEM_DESTINATION_TEXT
664
+ ):
665
+ proposed = "n/a (stay-in-place)"
666
+ else:
667
+ proposed = _proposed_filename(path, mtime, fm, h1)
668
+ return ProvenanceRecord(
669
+ path=rel,
670
+ suite=suite,
671
+ mtime=mtime,
672
+ sha256=sha or "",
673
+ line_count=int(line_count or 0),
674
+ frontmatter_project=fm_project,
675
+ signals=signals,
676
+ inferred_destination=suite_verdict.destination,
677
+ confidence=suite_verdict.confidence,
678
+ proposed_destination_filename=proposed,
679
+ notes=list(suite_verdict.rationale),
680
+ )
681
+
682
+
683
+ def _emit_json(
684
+ records: list[ProvenanceRecord],
685
+ suite_verdicts: dict[str, SuiteVerdict],
686
+ inventory_sha: str,
687
+ out: Path,
688
+ ) -> None:
689
+ by_suite_block: dict[str, dict[str, Any]] = {}
690
+ for suite, verdict in sorted(suite_verdicts.items()):
691
+ by_suite_block[suite] = {
692
+ "file-count": verdict.file_count,
693
+ "destination": verdict.destination,
694
+ "confidence": verdict.confidence,
695
+ "rationale": verdict.rationale,
696
+ "aggregate-repo-urls": verdict.aggregate_repo_urls,
697
+ "aggregate-abs-paths": verdict.aggregate_abs_paths,
698
+ "eco-signal-density": round(verdict.eco_signal_density, 3),
699
+ }
700
+ by_confidence_total: dict[str, int] = dict.fromkeys(ALL_CONFIDENCES, 0)
701
+ for r in records:
702
+ by_confidence_total[r.confidence] += 1
703
+ payload = {
704
+ "generated": datetime.now(timezone.utc).isoformat(),
705
+ "scanner": "build_plans_provenance",
706
+ "inventory-source-sha256": inventory_sha,
707
+ "suite-count": len(suite_verdicts),
708
+ "file-count": len(records),
709
+ "by-confidence": by_confidence_total,
710
+ "suites": by_suite_block,
711
+ "files": [_record_to_dict(r) for r in records],
712
+ }
713
+ out.parent.mkdir(parents=True, exist_ok=True)
714
+ out.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
715
+
716
+
717
+ def _record_to_dict(r: ProvenanceRecord) -> dict[str, Any]:
718
+ return {
719
+ "path": r.path,
720
+ "suite": r.suite,
721
+ "mtime": r.mtime,
722
+ "sha256": r.sha256,
723
+ "line-count": r.line_count,
724
+ "frontmatter-project": r.frontmatter_project,
725
+ "signals": asdict(r.signals),
726
+ "inferred-destination": r.inferred_destination,
727
+ "confidence": r.confidence,
728
+ "proposed-destination-filename": r.proposed_destination_filename,
729
+ "notes": r.notes,
730
+ }
731
+
732
+
733
+ def _emit_markdown(
734
+ records: list[ProvenanceRecord],
735
+ suite_verdicts: dict[str, SuiteVerdict],
736
+ out: Path,
737
+ ) -> None:
738
+ total = len(records)
739
+ by_confidence: dict[str, int] = dict.fromkeys(ALL_CONFIDENCES, 0)
740
+ for r in records:
741
+ by_confidence[r.confidence] += 1
742
+ orphans = [r for r in records if r.confidence == CONFIDENCE_UNMAPPABLE]
743
+
744
+ lines: list[str] = []
745
+ lines.append("# Plans Provenance — Per-Suite, Per-File Map")
746
+ lines.append("")
747
+ lines.append(f"_Generated: {datetime.now(timezone.utc).isoformat()}_")
748
+ lines.append("")
749
+ lines.append("## Aggregate Stats")
750
+ lines.append("")
751
+ lines.append(f"- **Total suites:** {len(suite_verdicts)}")
752
+ lines.append(f"- **Total plan files:** {total}")
753
+ lines.append("- **By confidence:**")
754
+ for c in ALL_CONFIDENCES:
755
+ lines.append(f" - `{c}`: {by_confidence[c]}")
756
+ lines.append("")
757
+
758
+ lines.append("## Suite-Level Verdicts")
759
+ lines.append("")
760
+ lines.append("| Suite | Files | Confidence | Destination |")
761
+ lines.append("|-------|-------|------------|-------------|")
762
+ for suite, verdict in sorted(suite_verdicts.items()):
763
+ suite_disp = suite.replace("|", "\\|")
764
+ dest_disp = verdict.destination.replace("|", "\\|")
765
+ lines.append(
766
+ f"| `{suite_disp}` | {verdict.file_count} |"
767
+ f" `{verdict.confidence}` | {dest_disp} |"
768
+ )
769
+ lines.append("")
770
+
771
+ lines.append("## Per-Suite Tables")
772
+ lines.append("")
773
+ by_suite: dict[str, list[ProvenanceRecord]] = {}
774
+ for r in records:
775
+ by_suite.setdefault(r.suite or "<root>", []).append(r)
776
+ for suite in sorted(by_suite):
777
+ suite_records = sorted(by_suite[suite], key=lambda r: r.path)
778
+ suite_verdict = suite_verdicts.get(suite)
779
+ lines.append(f"### `{suite}`")
780
+ lines.append("")
781
+ if suite_verdict is not None:
782
+ lines.append(
783
+ f"_Confidence: `{suite_verdict.confidence}`. Destination:"
784
+ f" {suite_verdict.destination}._"
785
+ )
786
+ lines.append("")
787
+ lines.append("**Rationale:**")
788
+ lines.append("")
789
+ for fragment in suite_verdict.rationale:
790
+ lines.append(f"- {fragment}")
791
+ lines.append("")
792
+ lines.append("| Path | Proposed filename |")
793
+ lines.append("|------|-------------------|")
794
+ for r in suite_records:
795
+ path_disp = r.path.replace("|", "\\|")
796
+ file_disp = r.proposed_destination_filename.replace("|", "\\|")
797
+ lines.append(f"| `{path_disp}` | `{file_disp}` |")
798
+ lines.append("")
799
+
800
+ lines.append("## Recursive-Case Annotation")
801
+ lines.append("")
802
+ recursive = [
803
+ v for v in suite_verdicts.values() if v.confidence == CONFIDENCE_RECURSIVE_SELF
804
+ ]
805
+ if recursive:
806
+ v = recursive[0]
807
+ lines.append(
808
+ f"The suite `{v.suite}` describes the very ecosystem it"
809
+ f" lives in ({v.file_count} files); the migration leaves"
810
+ " it in place and the cleanup phase adds `.plans/` to the"
811
+ " repository's `.gitignore` so the directory carries no"
812
+ " plan-product through publication."
813
+ )
814
+ else:
815
+ lines.append("_No recursive-self records._")
816
+ lines.append("")
817
+
818
+ lines.append("## Orphan Candidates")
819
+ lines.append("")
820
+ if orphans:
821
+ lines.append(
822
+ f"{len(orphans)} file(s) with `confidence: unmappable`."
823
+ " The migration-confirmation pass routes these to the"
824
+ " operator for explicit disposition."
825
+ )
826
+ lines.append("")
827
+ for r in sorted(orphans, key=lambda r: r.path):
828
+ lines.append(f"- `{r.path}`")
829
+ else:
830
+ lines.append("_No orphan candidates surfaced._")
831
+ lines.append("")
832
+
833
+ out.parent.mkdir(parents=True, exist_ok=True)
834
+ out.write_text("\n".join(lines), encoding="utf-8")
835
+
836
+
837
+ def _filter_plan_records(records: Iterable[dict[str, Any]]) -> list[dict[str, Any]]:
838
+ """Return every plan-artifact record from the inventory.
839
+
840
+ Every plan-artifact participates in the migration, including the
841
+ non-narrative classes (JSON outputs, log captures, scratch
842
+ fixtures): the migration moves a suite as a unit, not as a curated
843
+ text-only subset. Body-scan signals are produced only for the
844
+ text-readable extensions (see :data:`PLAN_EXTENSIONS`); records
845
+ outside that set still inherit their suite's verdict and surface
846
+ in the per-file table with empty signal sets.
847
+ """
848
+ return [r for r in records if r.get("class") == "plan-artifact"]
849
+
850
+
851
+ def _is_text_readable(rel: str) -> bool:
852
+ """Predicate guarding the body-scan pass against binary or
853
+ non-text plan-artifact records."""
854
+ return Path(rel).suffix.lower() in PLAN_EXTENSIONS
855
+
856
+
857
+ def _write_known_projects_template(
858
+ path: Path,
859
+ derived: list[dict[str, str]],
860
+ ) -> None:
861
+ """Persist the auto-derived known-projects entries to the operator-
862
+ editable file.
863
+
864
+ The file is overwritten only when it does not already exist; an
865
+ operator-edited list is never replaced. The header explains the
866
+ auto-derivation so the operator can amend the entries with project
867
+ names that match their own checkout layout.
868
+ """
869
+ if path.exists():
870
+ return
871
+ lines: list[str] = [
872
+ "# src/apothem/audit/known-projects.txt",
873
+ "#",
874
+ "# Auto-derived from plan-suite body content. Each line is either a bare URL,",
875
+ "# a bare absolute path, or a name=reference pair. Lines starting with #",
876
+ "# are comments. Edit freely; re-run the provenance scanner to refresh",
877
+ "# .audit/plans-provenance.{json,md}.",
878
+ "",
879
+ ]
880
+ for proj in derived:
881
+ lines.append(f"{proj['name']}={proj['ref']}")
882
+ path.parent.mkdir(parents=True, exist_ok=True)
883
+ path.write_text("\n".join(lines) + "\n", encoding="utf-8")
884
+
885
+
886
+ def main(argv: list[str] | None = None) -> int:
887
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
888
+ parser.add_argument(
889
+ "--inventory",
890
+ type=Path,
891
+ default=Path(".audit/inventory.json"),
892
+ )
893
+ parser.add_argument("--root", type=Path, default=Path())
894
+ parser.add_argument(
895
+ "--known-projects",
896
+ type=Path,
897
+ default=Path("src/apothem/audit/known-projects.txt"),
898
+ )
899
+ parser.add_argument(
900
+ "--output-json",
901
+ type=Path,
902
+ default=Path(".audit/plans-provenance.json"),
903
+ )
904
+ parser.add_argument(
905
+ "--output-md",
906
+ type=Path,
907
+ default=Path(".audit/plans-provenance.md"),
908
+ )
909
+ args = parser.parse_args(argv)
910
+
911
+ if not args.inventory.exists():
912
+ print(
913
+ f"error: inventory not found at {args.inventory}",
914
+ file=sys.stderr,
915
+ )
916
+ return 1
917
+
918
+ raw = args.inventory.read_bytes()
919
+ inventory_sha = hashlib.sha256(raw).hexdigest()
920
+ payload = json.loads(raw.decode("utf-8"))
921
+ inventory_records = payload.get("files", [])
922
+ plan_records = _filter_plan_records(inventory_records)
923
+
924
+ # First pass: scan every file's signals.
925
+ scanned: dict[str, list[tuple[dict[str, Any], Signals]]] = {}
926
+ aggregate_urls: set[str] = set()
927
+ aggregate_paths: set[str] = set()
928
+ for inv in plan_records:
929
+ rel = inv["path"]
930
+ suite = _suite_of(rel)
931
+ if not suite:
932
+ continue
933
+ if _is_text_readable(rel):
934
+ content = _read_text(args.root / rel)
935
+ signals = _scan_signals(content)
936
+ else:
937
+ signals = Signals()
938
+ scanned.setdefault(suite, []).append((inv, signals))
939
+ aggregate_urls.update(signals.repo_urls)
940
+ aggregate_paths.update(signals.abs_paths)
941
+
942
+ # Auto-derive known-projects entries when the operator file is
943
+ # absent; persist the template for operator review.
944
+ initial_known = _load_known_projects(args.known_projects)
945
+ if not initial_known:
946
+ derived = _derive_known_projects(aggregate_urls, aggregate_paths)
947
+ if derived:
948
+ _write_known_projects_template(args.known_projects, derived)
949
+ known_projects = derived
950
+ else:
951
+ known_projects = initial_known
952
+
953
+ # Second pass: aggregate per-suite signals and resolve verdicts.
954
+ suite_verdicts: dict[str, SuiteVerdict] = {}
955
+ for suite, entries in scanned.items():
956
+ sigs = [s for _, s in entries]
957
+ urls, paths, density = _aggregate_suite(suite, sigs)
958
+ suite_verdicts[suite] = _resolve_suite(
959
+ suite,
960
+ len(entries),
961
+ urls,
962
+ paths,
963
+ density,
964
+ known_projects,
965
+ )
966
+
967
+ # Third pass: build per-file records that inherit the suite verdict.
968
+ records: list[ProvenanceRecord] = []
969
+ for suite, entries in scanned.items():
970
+ verdict = suite_verdicts[suite]
971
+ for inv, _ in entries:
972
+ records.append(
973
+ _record_for(inv["path"], inv, args.root, verdict),
974
+ )
975
+
976
+ _emit_json(records, suite_verdicts, inventory_sha, args.output_json)
977
+ _emit_markdown(records, suite_verdicts, args.output_md)
978
+
979
+ by_conf: dict[str, int] = dict.fromkeys(ALL_CONFIDENCES, 0)
980
+ for r in records:
981
+ by_conf[r.confidence] += 1
982
+ summary = ", ".join(f"{k}={v}" for k, v in by_conf.items() if v)
983
+ print(
984
+ f"build_plans_provenance: {len(records)} plan file(s) across"
985
+ f" {len(suite_verdicts)} suite(s) [{summary}];"
986
+ f" known-projects entries: {len(known_projects)}"
987
+ )
988
+ for suite, verdict in sorted(suite_verdicts.items()):
989
+ print(
990
+ f" {suite} ({verdict.file_count} files):"
991
+ f" {verdict.confidence} -> {verdict.destination[:80]}"
992
+ )
993
+ return 0
994
+
995
+
996
+ if __name__ == "__main__":
997
+ raise SystemExit(main())