mindforge-cc 11.8.3 → 11.9.0

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 (351) hide show
  1. package/.agent/mindforge/wf-orchestrate-review.md +35 -0
  2. package/.agent/mindforge/wf-verification-loop.md +45 -0
  3. package/.agent/mindforge/wf-workflow-optimizer.md +51 -0
  4. package/.agent/skills/article-illustrator/SKILL.md +7 -7
  5. package/.agent/skills/arxiv/SKILL.md +6 -6
  6. package/.agent/skills/code-wiki/SKILL.md +11 -11
  7. package/.agent/skills/concept-diagrams/SKILL.md +6 -6
  8. package/.agent/skills/concept-diagrams/examples/apartment-floor-plan-conversion.md +244 -0
  9. package/.agent/skills/concept-diagrams/examples/automated-password-reset-flow.md +276 -0
  10. package/.agent/skills/concept-diagrams/examples/autonomous-llm-research-agent-flow.md +240 -0
  11. package/.agent/skills/concept-diagrams/examples/banana-journey-tree-to-smoothie.md +161 -0
  12. package/.agent/skills/concept-diagrams/examples/commercial-aircraft-structure.md +209 -0
  13. package/.agent/skills/concept-diagrams/examples/cpu-ooo-microarchitecture.md +236 -0
  14. package/.agent/skills/concept-diagrams/examples/electricity-grid-flow.md +182 -0
  15. package/.agent/skills/concept-diagrams/examples/feature-film-production-pipeline.md +172 -0
  16. package/.agent/skills/concept-diagrams/examples/hospital-emergency-department-flow.md +165 -0
  17. package/.agent/skills/concept-diagrams/examples/ml-benchmark-grouped-bar-chart.md +114 -0
  18. package/.agent/skills/concept-diagrams/examples/place-order-uml-sequence.md +325 -0
  19. package/.agent/skills/concept-diagrams/examples/smart-city-infrastructure.md +173 -0
  20. package/.agent/skills/concept-diagrams/examples/smartphone-layer-anatomy.md +154 -0
  21. package/.agent/skills/concept-diagrams/examples/sn2-reaction-mechanism.md +247 -0
  22. package/.agent/skills/concept-diagrams/examples/wind-turbine-structure.md +338 -0
  23. package/.agent/skills/concept-diagrams/templates/template.html +174 -0
  24. package/.agent/skills/domain-intel/SKILL.md +7 -7
  25. package/.agent/skills/duckduckgo-search/SKILL.md +17 -17
  26. package/.agent/skills/github-auth/scripts/gh-env.sh +66 -0
  27. package/.agent/skills/github-code-review/SKILL.md +9 -9
  28. package/.agent/skills/github-pr-workflow/SKILL.md +3 -3
  29. package/.agent/skills/godmode/SKILL.md +8 -8
  30. package/.agent/skills/jupyter-live-kernel/SKILL.md +3 -3
  31. package/.agent/skills/kanban-orchestrator/SKILL.md +10 -10
  32. package/.agent/skills/kanban-worker/SKILL.md +6 -6
  33. package/.agent/skills/llm-wiki/SKILL.md +7 -7
  34. package/.agent/skills/mindforge-autonomous/SKILL.md +1 -1
  35. package/.agent/skills/mindforge-complete-milestone/SKILL.md +1 -1
  36. package/.agent/skills/mindforge-discuss-phase/SKILL.md +1 -1
  37. package/.agent/skills/mindforge-do/SKILL.md +1 -1
  38. package/.agent/skills/mindforge-execute-phase/SKILL.md +1 -1
  39. package/.agent/skills/mindforge-list-workspaces/SKILL.md +1 -1
  40. package/.agent/skills/mindforge-manager/SKILL.md +1 -1
  41. package/.agent/skills/mindforge-neural-orchestrator/SKILL.md +1 -1
  42. package/.agent/skills/mindforge-new-milestone/SKILL.md +4 -4
  43. package/.agent/skills/mindforge-new-project/SKILL.md +4 -4
  44. package/.agent/skills/mindforge-new-workspace/SKILL.md +1 -1
  45. package/.agent/skills/mindforge-note/SKILL.md +1 -1
  46. package/.agent/skills/mindforge-plan-phase/SKILL.md +1 -1
  47. package/.agent/skills/mindforge-profile-user/SKILL.md +1 -1
  48. package/.agent/skills/mindforge-remove-workspace/SKILL.md +1 -1
  49. package/.agent/skills/mindforge-research-phase/SKILL.md +1 -1
  50. package/.agent/skills/mindforge-ui-phase/SKILL.md +1 -1
  51. package/.agent/skills/mindforge-ui-review/SKILL.md +1 -1
  52. package/.agent/skills/mindforge-verify-work/SKILL.md +1 -1
  53. package/.agent/skills/mindforge-workspace-isolated/SKILL.md +1 -1
  54. package/.agent/skills/node-inspect-debugger/SKILL.md +1 -1
  55. package/.agent/skills/obsidian/SKILL.md +15 -15
  56. package/.agent/skills/osint-investigation/SKILL.md +3 -2
  57. package/.agent/skills/osint-investigation/references/sources/courtlistener.md +98 -0
  58. package/.agent/skills/osint-investigation/references/sources/gdelt.md +104 -0
  59. package/.agent/skills/osint-investigation/references/sources/icij-offshore.md +104 -0
  60. package/.agent/skills/osint-investigation/references/sources/nyc-acris.md +90 -0
  61. package/.agent/skills/osint-investigation/references/sources/ofac-sdn.md +92 -0
  62. package/.agent/skills/osint-investigation/references/sources/opencorporates.md +103 -0
  63. package/.agent/skills/osint-investigation/references/sources/sec-edgar.md +83 -0
  64. package/.agent/skills/osint-investigation/references/sources/senate-ld.md +89 -0
  65. package/.agent/skills/osint-investigation/references/sources/usaspending.md +97 -0
  66. package/.agent/skills/osint-investigation/references/sources/wayback.md +93 -0
  67. package/.agent/skills/osint-investigation/references/sources/wikipedia.md +107 -0
  68. package/.agent/skills/osint-investigation/scripts/_http.py +82 -0
  69. package/.agent/skills/osint-investigation/scripts/_normalize.py +67 -0
  70. package/.agent/skills/osint-investigation/scripts/build_findings.py +221 -0
  71. package/.agent/skills/osint-investigation/scripts/entity_resolution.py +228 -0
  72. package/.agent/skills/osint-investigation/scripts/fetch_courtlistener.py +149 -0
  73. package/.agent/skills/osint-investigation/scripts/fetch_gdelt.py +161 -0
  74. package/.agent/skills/osint-investigation/scripts/fetch_icij_offshore.py +234 -0
  75. package/.agent/skills/osint-investigation/scripts/fetch_nyc_acris.py +203 -0
  76. package/.agent/skills/osint-investigation/scripts/fetch_ofac_sdn.py +175 -0
  77. package/.agent/skills/osint-investigation/scripts/fetch_opencorporates.py +191 -0
  78. package/.agent/skills/osint-investigation/scripts/fetch_sec_edgar.py +184 -0
  79. package/.agent/skills/osint-investigation/scripts/fetch_senate_ld.py +146 -0
  80. package/.agent/skills/osint-investigation/scripts/fetch_usaspending.py +170 -0
  81. package/.agent/skills/osint-investigation/scripts/fetch_wayback.py +142 -0
  82. package/.agent/skills/osint-investigation/scripts/fetch_wikipedia.py +266 -0
  83. package/.agent/skills/osint-investigation/scripts/timing_analysis.py +252 -0
  84. package/.agent/skills/oss-forensics/SKILL.md +10 -9
  85. package/.agent/skills/oss-forensics/scripts/evidence-store.py +313 -0
  86. package/.agent/skills/parallel-cli/SKILL.md +2 -2
  87. package/.agent/skills/pinggy-tunnel/SKILL.md +3 -3
  88. package/.agent/skills/pixel-art/SKILL.md +2 -2
  89. package/.agent/skills/plan/SKILL.md +10 -10
  90. package/.agent/skills/requesting-code-review/SKILL.md +4 -6
  91. package/.agent/skills/research-paper-writing/SKILL.md +32 -32
  92. package/.agent/skills/rest-graphql-debug/SKILL.md +31 -32
  93. package/.agent/skills/s6-container-supervision/SKILL.md +1 -1
  94. package/.agent/skills/scrapling/SKILL.md +1 -1
  95. package/.agent/skills/sherlock/SKILL.md +1 -1
  96. package/.agent/skills/simplify-code/SKILL.md +5 -7
  97. package/.agent/skills/skill-authoring/SKILL.md +4 -4
  98. package/.agent/skills/spike/SKILL.md +11 -13
  99. package/.agent/skills/subagent-driven-development/SKILL.md +7 -11
  100. package/.agent/skills/systematic-debugging/SKILL.md +13 -14
  101. package/.agent/skills/test-driven-development/SKILL.md +6 -7
  102. package/.agent/skills/video-orchestrator/SKILL.md +1 -1
  103. package/.agent/skills/web-pentest/SKILL.md +7 -7
  104. package/.mindforge/config.json +3 -3
  105. package/.mindforge/dynamic-workflows/REGISTRY.md +4 -1
  106. package/.mindforge/dynamic-workflows/index.json +910 -840
  107. package/.mindforge/dynamic-workflows/scripts/accessibility-audit.js +88 -90
  108. package/.mindforge/dynamic-workflows/scripts/ai-model-eval.js +59 -61
  109. package/.mindforge/dynamic-workflows/scripts/api-contract-test.js +87 -85
  110. package/.mindforge/dynamic-workflows/scripts/api-migration.js +120 -122
  111. package/.mindforge/dynamic-workflows/scripts/architecture-modernization.js +84 -86
  112. package/.mindforge/dynamic-workflows/scripts/code-audit.js +75 -77
  113. package/.mindforge/dynamic-workflows/scripts/code-explainer.js +99 -101
  114. package/.mindforge/dynamic-workflows/scripts/competitive-analysis.js +62 -64
  115. package/.mindforge/dynamic-workflows/scripts/competitive-teardown.js +106 -108
  116. package/.mindforge/dynamic-workflows/scripts/cost-analysis.js +78 -80
  117. package/.mindforge/dynamic-workflows/scripts/data-pipeline-validate.js +47 -49
  118. package/.mindforge/dynamic-workflows/scripts/database-migration.js +85 -87
  119. package/.mindforge/dynamic-workflows/scripts/debug-detective.js +97 -99
  120. package/.mindforge/dynamic-workflows/scripts/dependency-health.js +81 -83
  121. package/.mindforge/dynamic-workflows/scripts/design-system-audit.js +84 -86
  122. package/.mindforge/dynamic-workflows/scripts/documentation-gen.js +65 -67
  123. package/.mindforge/dynamic-workflows/scripts/feature-planner.js +78 -80
  124. package/.mindforge/dynamic-workflows/scripts/incident-response.js +81 -83
  125. package/.mindforge/dynamic-workflows/scripts/multi-repo-sync.js +49 -51
  126. package/.mindforge/dynamic-workflows/scripts/mutation-testing.js +113 -115
  127. package/.mindforge/dynamic-workflows/scripts/onboard-codebase.js +79 -81
  128. package/.mindforge/dynamic-workflows/scripts/orchestrate-review.js +115 -0
  129. package/.mindforge/dynamic-workflows/scripts/perf-optimize.js +97 -99
  130. package/.mindforge/dynamic-workflows/scripts/pr-review.js +61 -63
  131. package/.mindforge/dynamic-workflows/scripts/refactor-plan.js +88 -90
  132. package/.mindforge/dynamic-workflows/scripts/release-prep.js +79 -81
  133. package/.mindforge/dynamic-workflows/scripts/security-hardening.js +117 -119
  134. package/.mindforge/dynamic-workflows/scripts/security-threat-model.js +119 -121
  135. package/.mindforge/dynamic-workflows/scripts/tdd-sprint.js +80 -82
  136. package/.mindforge/dynamic-workflows/scripts/tech-evaluation.js +50 -52
  137. package/.mindforge/dynamic-workflows/scripts/test-coverage-gap.js +68 -70
  138. package/.mindforge/dynamic-workflows/scripts/ux-heuristic-audit.js +89 -91
  139. package/.mindforge/dynamic-workflows/scripts/verification-loop.js +138 -0
  140. package/.mindforge/dynamic-workflows/scripts/workflow-optimizer.js +136 -0
  141. package/.mindforge/dynamic-workflows/scripts/writer-reviewer.js +63 -65
  142. package/.mindforge/memory/sync-manifest.json +1 -1
  143. package/.mindforge/personas/advisor-researcher.md +2 -2
  144. package/.mindforge/personas/api-load-tester.md +1 -1
  145. package/.mindforge/personas/authentication-architect.md +1 -1
  146. package/.mindforge/personas/backup-recovery-specialist.md +1 -1
  147. package/.mindforge/personas/build-optimizer.md +1 -1
  148. package/.mindforge/personas/caching-strategist.md +1 -1
  149. package/.mindforge/personas/chaos-engineer.md +1 -1
  150. package/.mindforge/personas/compliance-auditor.md +1 -1
  151. package/.mindforge/personas/config-management-expert.md +1 -1
  152. package/.mindforge/personas/coverage-specialist.md +1 -1
  153. package/.mindforge/personas/debug-specialist.md +2 -2
  154. package/.mindforge/personas/debugger.md +2 -2
  155. package/.mindforge/personas/dependency-auditor.md +1 -1
  156. package/.mindforge/personas/developer.md +1 -1
  157. package/.mindforge/personas/devops-engineer.md +1 -1
  158. package/.mindforge/personas/executor.md +1 -1
  159. package/.mindforge/personas/go-specialist.md +1 -1
  160. package/.mindforge/personas/incident-commander.md +1 -1
  161. package/.mindforge/personas/java-specialist.md +1 -1
  162. package/.mindforge/personas/kubernetes-debugger.md +1 -1
  163. package/.mindforge/personas/logging-architect.md +1 -1
  164. package/.mindforge/personas/mf-planner.md +1 -1
  165. package/.mindforge/personas/mf-researcher.md +1 -1
  166. package/.mindforge/personas/mf-reviewer.md +1 -1
  167. package/.mindforge/personas/nyquist-auditor.md +1 -1
  168. package/.mindforge/personas/observability-engineer.md +1 -1
  169. package/.mindforge/personas/performance-optimizer.md +1 -1
  170. package/.mindforge/personas/phase-researcher.md +5 -5
  171. package/.mindforge/personas/planner.md +1 -1
  172. package/.mindforge/personas/project-researcher.md +2 -2
  173. package/.mindforge/personas/python-specialist.md +1 -1
  174. package/.mindforge/personas/qa-engineer.md +1 -1
  175. package/.mindforge/personas/release-manager.md +1 -1
  176. package/.mindforge/personas/research-agent.md +4 -4
  177. package/.mindforge/personas/roadmapper-extend.md +1 -1
  178. package/.mindforge/personas/rust-specialist.md +1 -1
  179. package/.mindforge/personas/security-reviewer.md +1 -1
  180. package/.mindforge/personas/tech-writer.md +1 -1
  181. package/.mindforge/personas/typescript-wizard.md +1 -1
  182. package/.mindforge/personas/ui-researcher.md +1 -1
  183. package/.mindforge/skills/accessibility/SKILL.md +2 -2
  184. package/.mindforge/skills/agent-architecture-audit/SKILL.md +11 -0
  185. package/.mindforge/skills/arxiv/SKILL.md +6 -6
  186. package/.mindforge/skills/arxiv/scripts/search_arxiv.py +114 -0
  187. package/.mindforge/skills/code-tour/SKILL.md +2 -2
  188. package/.mindforge/skills/code-wiki/SKILL.md +11 -11
  189. package/.mindforge/skills/code-wiki/templates/README.md +31 -0
  190. package/.mindforge/skills/code-wiki/templates/architecture.md +30 -0
  191. package/.mindforge/skills/code-wiki/templates/getting-started.md +47 -0
  192. package/.mindforge/skills/code-wiki/templates/module.md +38 -0
  193. package/.mindforge/skills/concept-diagrams/SKILL.md +6 -6
  194. package/.mindforge/skills/concept-diagrams/examples/apartment-floor-plan-conversion.md +244 -0
  195. package/.mindforge/skills/concept-diagrams/examples/automated-password-reset-flow.md +276 -0
  196. package/.mindforge/skills/concept-diagrams/examples/autonomous-llm-research-agent-flow.md +240 -0
  197. package/.mindforge/skills/concept-diagrams/examples/banana-journey-tree-to-smoothie.md +161 -0
  198. package/.mindforge/skills/concept-diagrams/examples/commercial-aircraft-structure.md +209 -0
  199. package/.mindforge/skills/concept-diagrams/examples/cpu-ooo-microarchitecture.md +236 -0
  200. package/.mindforge/skills/concept-diagrams/examples/electricity-grid-flow.md +182 -0
  201. package/.mindforge/skills/concept-diagrams/examples/feature-film-production-pipeline.md +172 -0
  202. package/.mindforge/skills/concept-diagrams/examples/hospital-emergency-department-flow.md +165 -0
  203. package/.mindforge/skills/concept-diagrams/examples/ml-benchmark-grouped-bar-chart.md +114 -0
  204. package/.mindforge/skills/concept-diagrams/examples/place-order-uml-sequence.md +325 -0
  205. package/.mindforge/skills/concept-diagrams/examples/smart-city-infrastructure.md +173 -0
  206. package/.mindforge/skills/concept-diagrams/examples/smartphone-layer-anatomy.md +154 -0
  207. package/.mindforge/skills/concept-diagrams/examples/sn2-reaction-mechanism.md +247 -0
  208. package/.mindforge/skills/concept-diagrams/examples/wind-turbine-structure.md +338 -0
  209. package/.mindforge/skills/concept-diagrams/references/dashboard-patterns.md +43 -0
  210. package/.mindforge/skills/concept-diagrams/references/infrastructure-patterns.md +144 -0
  211. package/.mindforge/skills/concept-diagrams/references/physical-shape-cookbook.md +42 -0
  212. package/.mindforge/skills/concept-diagrams/templates/template.html +174 -0
  213. package/.mindforge/skills/creative-ideation/references/full-prompt-library.md +110 -0
  214. package/.mindforge/skills/domain-intel/SKILL.md +7 -7
  215. package/.mindforge/skills/domain-intel/scripts/domain_intel.py +397 -0
  216. package/.mindforge/skills/duckduckgo-search/SKILL.md +17 -17
  217. package/.mindforge/skills/duckduckgo-search/scripts/duckduckgo.sh +28 -0
  218. package/.mindforge/skills/environment-management/SKILL.md +1 -1
  219. package/.mindforge/skills/github-code-review/SKILL.md +9 -9
  220. package/.mindforge/skills/github-code-review/references/review-output-template.md +74 -0
  221. package/.mindforge/skills/github-issues/templates/bug-report.md +35 -0
  222. package/.mindforge/skills/github-issues/templates/feature-request.md +31 -0
  223. package/.mindforge/skills/github-pr-workflow/SKILL.md +3 -3
  224. package/.mindforge/skills/github-pr-workflow/references/ci-troubleshooting.md +183 -0
  225. package/.mindforge/skills/github-pr-workflow/references/conventional-commits.md +71 -0
  226. package/.mindforge/skills/github-pr-workflow/templates/pr-body-bugfix.md +35 -0
  227. package/.mindforge/skills/github-pr-workflow/templates/pr-body-feature.md +33 -0
  228. package/.mindforge/skills/jupyter-live-kernel/SKILL.md +3 -3
  229. package/.mindforge/skills/kanban-orchestrator/SKILL.md +10 -10
  230. package/.mindforge/skills/kanban-worker/SKILL.md +6 -6
  231. package/.mindforge/skills/meme-generation/SKILL.md +1 -1
  232. package/.mindforge/skills/meme-generation/scripts/generate_meme.py +470 -0
  233. package/.mindforge/skills/meme-generation/scripts/templates.json +97 -0
  234. package/.mindforge/skills/obsidian/SKILL.md +15 -15
  235. package/.mindforge/skills/orch-pipeline/SKILL.md +2 -2
  236. package/.mindforge/skills/osint-investigation/SKILL.md +3 -2
  237. package/.mindforge/skills/osint-investigation/references/sources/courtlistener.md +98 -0
  238. package/.mindforge/skills/osint-investigation/references/sources/gdelt.md +104 -0
  239. package/.mindforge/skills/osint-investigation/references/sources/icij-offshore.md +104 -0
  240. package/.mindforge/skills/osint-investigation/references/sources/nyc-acris.md +90 -0
  241. package/.mindforge/skills/osint-investigation/references/sources/ofac-sdn.md +92 -0
  242. package/.mindforge/skills/osint-investigation/references/sources/opencorporates.md +103 -0
  243. package/.mindforge/skills/osint-investigation/references/sources/sec-edgar.md +83 -0
  244. package/.mindforge/skills/osint-investigation/references/sources/senate-ld.md +89 -0
  245. package/.mindforge/skills/osint-investigation/references/sources/usaspending.md +97 -0
  246. package/.mindforge/skills/osint-investigation/references/sources/wayback.md +93 -0
  247. package/.mindforge/skills/osint-investigation/references/sources/wikipedia.md +107 -0
  248. package/.mindforge/skills/osint-investigation/scripts/_http.py +82 -0
  249. package/.mindforge/skills/osint-investigation/scripts/_normalize.py +67 -0
  250. package/.mindforge/skills/osint-investigation/scripts/build_findings.py +221 -0
  251. package/.mindforge/skills/osint-investigation/scripts/entity_resolution.py +228 -0
  252. package/.mindforge/skills/osint-investigation/scripts/fetch_courtlistener.py +149 -0
  253. package/.mindforge/skills/osint-investigation/scripts/fetch_gdelt.py +161 -0
  254. package/.mindforge/skills/osint-investigation/scripts/fetch_icij_offshore.py +234 -0
  255. package/.mindforge/skills/osint-investigation/scripts/fetch_nyc_acris.py +203 -0
  256. package/.mindforge/skills/osint-investigation/scripts/fetch_ofac_sdn.py +175 -0
  257. package/.mindforge/skills/osint-investigation/scripts/fetch_opencorporates.py +191 -0
  258. package/.mindforge/skills/osint-investigation/scripts/fetch_sec_edgar.py +184 -0
  259. package/.mindforge/skills/osint-investigation/scripts/fetch_senate_ld.py +146 -0
  260. package/.mindforge/skills/osint-investigation/scripts/fetch_usaspending.py +170 -0
  261. package/.mindforge/skills/osint-investigation/scripts/fetch_wayback.py +142 -0
  262. package/.mindforge/skills/osint-investigation/scripts/fetch_wikipedia.py +266 -0
  263. package/.mindforge/skills/osint-investigation/scripts/timing_analysis.py +252 -0
  264. package/.mindforge/skills/osint-investigation/templates/source-template.md +59 -0
  265. package/.mindforge/skills/oss-forensics/SKILL.md +6 -6
  266. package/.mindforge/skills/oss-forensics/references/evidence-types.md +89 -0
  267. package/.mindforge/skills/oss-forensics/references/github-archive-guide.md +184 -0
  268. package/.mindforge/skills/oss-forensics/references/investigation-templates.md +131 -0
  269. package/.mindforge/skills/oss-forensics/references/recovery-techniques.md +164 -0
  270. package/.mindforge/skills/oss-forensics/scripts/evidence-store.py +313 -0
  271. package/.mindforge/skills/oss-forensics/templates/forensic-report.md +151 -0
  272. package/.mindforge/skills/oss-forensics/templates/malicious-package-report.md +43 -0
  273. package/.mindforge/skills/pixel-art/SKILL.md +2 -2
  274. package/.mindforge/skills/pixel-art/references/palettes.md +49 -0
  275. package/.mindforge/skills/plan/SKILL.md +10 -10
  276. package/.mindforge/skills/requesting-code-review/SKILL.md +4 -6
  277. package/.mindforge/skills/research-paper-writing/SKILL.md +32 -32
  278. package/.mindforge/skills/research-paper-writing/references/autoreason-methodology.md +394 -0
  279. package/.mindforge/skills/research-paper-writing/references/checklists.md +434 -0
  280. package/.mindforge/skills/research-paper-writing/references/citation-workflow.md +563 -0
  281. package/.mindforge/skills/research-paper-writing/references/experiment-patterns.md +728 -0
  282. package/.mindforge/skills/research-paper-writing/references/human-evaluation.md +476 -0
  283. package/.mindforge/skills/research-paper-writing/references/paper-types.md +481 -0
  284. package/.mindforge/skills/research-paper-writing/references/reviewer-guidelines.md +433 -0
  285. package/.mindforge/skills/research-paper-writing/references/sources.md +191 -0
  286. package/.mindforge/skills/research-paper-writing/references/writing-guide.md +474 -0
  287. package/.mindforge/skills/research-paper-writing/templates/README.md +251 -0
  288. package/.mindforge/skills/scrapling/SKILL.md +1 -1
  289. package/.mindforge/skills/sherlock/SKILL.md +1 -1
  290. package/.mindforge/skills/simplify-code/SKILL.md +5 -7
  291. package/.mindforge/skills/spike/SKILL.md +11 -13
  292. package/.mindforge/skills/subagent-driven-development/SKILL.md +7 -11
  293. package/.mindforge/skills/subagent-driven-development/references/context-budget-discipline.md +53 -0
  294. package/.mindforge/skills/subagent-driven-development/references/gates-taxonomy.md +93 -0
  295. package/.mindforge/skills/systematic-debugging/SKILL.md +13 -14
  296. package/.mindforge/skills/test-driven-development/SKILL.md +6 -7
  297. package/.mindforge/skills/testing-standards/SKILL.md +1 -1
  298. package/.mindforge/skills/web-pentest/SKILL.md +2 -2
  299. package/.mindforge/skills/web-pentest/references/bypass-techniques.md +133 -0
  300. package/.mindforge/skills/web-pentest/references/exploitation-techniques.md +204 -0
  301. package/.mindforge/skills/web-pentest/references/scope-enforcement.md +110 -0
  302. package/.mindforge/skills/web-pentest/references/vuln-taxonomy.md +81 -0
  303. package/.mindforge/skills/web-pentest/templates/authorization.md +69 -0
  304. package/.mindforge/skills/web-pentest/templates/pentest-report.md +178 -0
  305. package/.mindforge/skills/writing-skills/SKILL.md +4 -3
  306. package/CHANGELOG.md +15 -0
  307. package/MINDFORGE.md +3 -3
  308. package/README.md +9 -7
  309. package/RELEASENOTES.md +65 -2
  310. package/SECURITY.md +5 -3
  311. package/bin/models/bedrock-provider.js +192 -0
  312. package/bin/models/model-client.js +19 -0
  313. package/docs/commands-reference.md +66 -4
  314. package/docs/faq.md +38 -1
  315. package/docs/getting-started.md +27 -12
  316. package/docs/sdk-reference.md +36 -3
  317. package/docs/troubleshooting.md +59 -1
  318. package/docs/user-guide.md +7 -4
  319. package/package.json +3 -3
  320. package/.claude/commands/mindforge/skill-tdd.md +0 -53
  321. package/.claude/commands/mindforge/skills-index.md +0 -118
  322. package/.claude/commands/mindforge/systematic-debug.md +0 -60
  323. package/.claude/commands/mindforge/wf-accessibility-audit.md +0 -31
  324. package/.claude/commands/mindforge/wf-ai-model-eval.md +0 -31
  325. package/.claude/commands/mindforge/wf-api-migration.md +0 -31
  326. package/.claude/commands/mindforge/wf-architecture-modernization.md +0 -32
  327. package/.claude/commands/mindforge/wf-catalog.md +0 -59
  328. package/.claude/commands/mindforge/wf-code-audit.md +0 -31
  329. package/.claude/commands/mindforge/wf-code-explainer.md +0 -31
  330. package/.claude/commands/mindforge/wf-competitive-analysis.md +0 -31
  331. package/.claude/commands/mindforge/wf-competitive-teardown.md +0 -31
  332. package/.claude/commands/mindforge/wf-cost-analysis.md +0 -31
  333. package/.claude/commands/mindforge/wf-data-pipeline-validate.md +0 -31
  334. package/.claude/commands/mindforge/wf-database-migration.md +0 -31
  335. package/.claude/commands/mindforge/wf-debug-detective.md +0 -32
  336. package/.claude/commands/mindforge/wf-design-system-audit.md +0 -31
  337. package/.claude/commands/mindforge/wf-documentation-gen.md +0 -31
  338. package/.claude/commands/mindforge/wf-feature-planner.md +0 -31
  339. package/.claude/commands/mindforge/wf-incident-response.md +0 -31
  340. package/.claude/commands/mindforge/wf-multi-repo-sync.md +0 -31
  341. package/.claude/commands/mindforge/wf-mutation-testing.md +0 -31
  342. package/.claude/commands/mindforge/wf-onboard-codebase.md +0 -31
  343. package/.claude/commands/mindforge/wf-perf-optimize.md +0 -31
  344. package/.claude/commands/mindforge/wf-pr-review.md +0 -31
  345. package/.claude/commands/mindforge/wf-refactor-plan.md +0 -31
  346. package/.claude/commands/mindforge/wf-release-prep.md +0 -31
  347. package/.claude/commands/mindforge/wf-security-threat-model.md +0 -31
  348. package/.claude/commands/mindforge/wf-tdd-sprint.md +0 -31
  349. package/.claude/commands/mindforge/wf-tech-evaluation.md +0 -31
  350. package/.claude/commands/mindforge/wf-ux-heuristic-audit.md +0 -31
  351. package/.claude/commands/mindforge/wf-writer-reviewer.md +0 -30
@@ -0,0 +1,67 @@
1
+ """Shared entity-name normalization helpers (stdlib-only).
2
+
3
+ Used by entity_resolution.py and timing_analysis.py.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import re
8
+
9
+ # Legal suffixes / corporate boilerplate to strip during normalization.
10
+ _SUFFIX_TOKENS = {
11
+ "INC", "INCORPORATED", "LLC", "LLP", "LP", "LTD", "LIMITED",
12
+ "CORP", "CORPORATION", "CO", "COMPANY",
13
+ "GROUP", "GRP", "HOLDINGS", "HOLDING",
14
+ "PARTNERS", "ASSOCIATES",
15
+ "INTERNATIONAL", "INTL",
16
+ "ENTERPRISES", "ENTERPRISE",
17
+ "SERVICES", "SERVICE", "SVCS",
18
+ "SOLUTIONS", "MANAGEMENT", "MGMT", "CONSULTING",
19
+ "TECHNOLOGY", "TECHNOLOGIES", "TECH",
20
+ "INDUSTRIES", "INDUSTRY",
21
+ "AMERICA", "AMERICAN",
22
+ "USA", "US",
23
+ "PLLC", "PC",
24
+ "TRUST", "FOUNDATION",
25
+ }
26
+
27
+ _PUNCT_RE = re.compile(r"[^\w\s]")
28
+ _WS_RE = re.compile(r"\s+")
29
+
30
+
31
+ def normalize_name(name: str | None) -> str:
32
+ """Standard normalization: uppercase, strip suffixes, drop punctuation."""
33
+ if not name:
34
+ return ""
35
+ s = _PUNCT_RE.sub(" ", name.upper())
36
+ s = _WS_RE.sub(" ", s).strip()
37
+ tokens = [t for t in s.split() if t and t not in _SUFFIX_TOKENS]
38
+ return " ".join(tokens)
39
+
40
+
41
+ def normalize_aggressive(name: str | None) -> str:
42
+ """Aggressive normalization: sorted unique tokens (word-bag)."""
43
+ base = normalize_name(name)
44
+ if not base:
45
+ return ""
46
+ return " ".join(sorted(set(base.split())))
47
+
48
+
49
+ def name_tokens(name: str | None, min_len: int = 4) -> set[str]:
50
+ """Token set used for overlap matching."""
51
+ base = normalize_name(name)
52
+ if not base:
53
+ return set()
54
+ return {t for t in base.split() if len(t) >= min_len}
55
+
56
+
57
+ def token_overlap_ratio(left: str | None, right: str | None) -> tuple[float, int]:
58
+ """Return (jaccard-like ratio, shared token count) over min-len tokens."""
59
+ a = name_tokens(left)
60
+ b = name_tokens(right)
61
+ if not a or not b:
62
+ return 0.0, 0
63
+ shared = a & b
64
+ if not shared:
65
+ return 0.0, 0
66
+ union = a | b
67
+ return len(shared) / len(union), len(shared)
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env python3
2
+ """Build a structured findings.json with evidence chains (stdlib-only).
3
+
4
+ Aggregates cross_links.csv (entity_resolution output) and an optional
5
+ timing.json (timing_analysis output) into a single evidence-chain document.
6
+
7
+ Output structure:
8
+ {
9
+ "metadata": {...},
10
+ "findings": [
11
+ {
12
+ "id": "F0001",
13
+ "title": "...",
14
+ "severity": "HIGH|MEDIUM|LOW",
15
+ "confidence": "high|medium|low",
16
+ "summary": "...",
17
+ "evidence": [
18
+ {"source": "cross_links.csv", "row": 12, "fields": {...}},
19
+ ...
20
+ ],
21
+ "sources": ["cross_links.csv", "timing.json"]
22
+ }
23
+ ]
24
+ }
25
+
26
+ Every finding traces to specific source rows. No naked claims.
27
+ """
28
+ from __future__ import annotations
29
+
30
+ import argparse
31
+ import csv
32
+ import json
33
+ from collections import defaultdict
34
+ from pathlib import Path
35
+
36
+ CONFIDENCE_ORDER = {"high": 0, "medium": 1, "low": 2}
37
+ SEVERITY_ORDER = {"HIGH": 0, "MEDIUM": 1, "LOW": 2}
38
+
39
+
40
+ def _read_cross_links(path: str) -> list[dict[str, str]]:
41
+ with open(path, newline="", encoding="utf-8") as fh:
42
+ return list(csv.DictReader(fh))
43
+
44
+
45
+ def build_findings(
46
+ cross_links_path: str,
47
+ timing_path: str | None = None,
48
+ out_path: str = "findings.json",
49
+ bundled_threshold: int = 3,
50
+ ) -> dict:
51
+ findings: list[dict] = []
52
+ next_id = 1
53
+
54
+ # 1. Match-based findings, grouped by (left_normalized, right_normalized).
55
+ matches = _read_cross_links(cross_links_path)
56
+ grouped: dict[tuple[str, str], list[dict[str, str]]] = defaultdict(list)
57
+ for i, row in enumerate(matches):
58
+ row["__row__"] = str(i)
59
+ grouped[(row.get("left_normalized", ""), row.get("right_normalized", ""))].append(row)
60
+
61
+ for (left_norm, right_norm), rows in grouped.items():
62
+ if not left_norm or not right_norm:
63
+ continue
64
+ # Use the highest-confidence match for the finding's overall confidence.
65
+ best = min(rows, key=lambda r: CONFIDENCE_ORDER.get(r.get("confidence", "low"), 2))
66
+ finding_id = f"F{next_id:04d}"
67
+ next_id += 1
68
+ evidence = [
69
+ {
70
+ "source": "cross_links.csv",
71
+ "row": int(r["__row__"]),
72
+ "fields": {
73
+ "match_type": r.get("match_type", ""),
74
+ "confidence": r.get("confidence", ""),
75
+ "left_name": r.get("left_name", ""),
76
+ "right_name": r.get("right_name", ""),
77
+ "overlap_ratio": r.get("overlap_ratio", ""),
78
+ "shared_tokens": r.get("shared_tokens", ""),
79
+ },
80
+ }
81
+ for r in rows
82
+ ]
83
+ findings.append(
84
+ {
85
+ "id": finding_id,
86
+ "title": f"Entity match: {best.get('left_name', '')} ↔ {best.get('right_name', '')}",
87
+ "severity": "MEDIUM" if best.get("confidence") == "high" else "LOW",
88
+ "confidence": best.get("confidence", "low"),
89
+ "summary": (
90
+ f"{len(rows)} cross-link record(s) tie "
91
+ f"'{best.get('left_name', '')}' to "
92
+ f"'{best.get('right_name', '')}' "
93
+ f"(best tier: {best.get('match_type', '')})."
94
+ ),
95
+ "evidence": evidence,
96
+ "sources": ["cross_links.csv"],
97
+ }
98
+ )
99
+
100
+ # 2. Bundled-donations findings (if cross_links carries donor↔candidate pattern).
101
+ # Heuristic: many distinct left names sharing the same right name.
102
+ by_right: dict[str, set[str]] = defaultdict(set)
103
+ by_right_rows: dict[str, list[dict[str, str]]] = defaultdict(list)
104
+ for r in matches:
105
+ right = r.get("right_normalized", "")
106
+ left_raw = r.get("left_name", "").strip()
107
+ if right and left_raw:
108
+ by_right[right].add(left_raw)
109
+ by_right_rows[right].append(r)
110
+ for right_norm, lefts in by_right.items():
111
+ if len(lefts) < bundled_threshold:
112
+ continue
113
+ rows = by_right_rows[right_norm]
114
+ right_raw = rows[0].get("right_name", "")
115
+ findings.append(
116
+ {
117
+ "id": f"F{next_id:04d}",
118
+ "title": f"Bundled cross-links: {len(lefts)} distinct left entities ↔ '{right_raw}'",
119
+ "severity": "HIGH",
120
+ "confidence": "medium",
121
+ "summary": (
122
+ f"{len(lefts)} distinct left-side entities link to "
123
+ f"'{right_raw}'. Pattern suggests coordinated relationship "
124
+ f"(e.g. bundled donations, multi-vendor employer)."
125
+ ),
126
+ "evidence": [
127
+ {
128
+ "source": "cross_links.csv",
129
+ "row": int(r.get("__row__", "0")),
130
+ "fields": {
131
+ "left_name": r.get("left_name", ""),
132
+ "match_type": r.get("match_type", ""),
133
+ },
134
+ }
135
+ for r in rows
136
+ ],
137
+ "sources": ["cross_links.csv"],
138
+ }
139
+ )
140
+ next_id += 1
141
+
142
+ # 3. Timing-based findings.
143
+ if timing_path and Path(timing_path).exists():
144
+ timing = json.loads(Path(timing_path).read_text())
145
+ for r in timing.get("results", []):
146
+ if not r.get("significant"):
147
+ continue
148
+ findings.append(
149
+ {
150
+ "id": f"F{next_id:04d}",
151
+ "title": (
152
+ f"Donation timing significantly clusters near awards: "
153
+ f"{r['donor']} ↔ {r['recipient']}"
154
+ ),
155
+ "severity": "HIGH" if r["p_value"] < 0.01 else "MEDIUM",
156
+ "confidence": "medium",
157
+ "summary": (
158
+ f"Mean nearest-award distance {r['observed_mean_days']} days "
159
+ f"(null {r['null_mean_days']} days). p={r['p_value']}, "
160
+ f"effect size {r['effect_size_sd']} SD. "
161
+ f"{r['n_donations']} donations, {r['n_award_dates']} awards."
162
+ ),
163
+ "evidence": [
164
+ {
165
+ "source": "timing.json",
166
+ "row": None,
167
+ "fields": r,
168
+ }
169
+ ],
170
+ "sources": ["timing.json"],
171
+ }
172
+ )
173
+ next_id += 1
174
+
175
+ # Sort: severity → confidence → id.
176
+ findings.sort(
177
+ key=lambda f: (
178
+ SEVERITY_ORDER.get(f["severity"], 3),
179
+ CONFIDENCE_ORDER.get(f["confidence"], 3),
180
+ f["id"],
181
+ )
182
+ )
183
+
184
+ payload = {
185
+ "metadata": {
186
+ "n_findings": len(findings),
187
+ "cross_links_path": cross_links_path,
188
+ "timing_path": timing_path,
189
+ "bundled_threshold": bundled_threshold,
190
+ },
191
+ "findings": findings,
192
+ }
193
+ Path(out_path).write_text(json.dumps(payload, indent=2))
194
+ return payload
195
+
196
+
197
+ def main() -> int:
198
+ p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
199
+ p.add_argument("--cross-links", required=True)
200
+ p.add_argument("--timing", help="Optional timing.json from timing_analysis.py")
201
+ p.add_argument("--out", default="findings.json")
202
+ p.add_argument(
203
+ "--bundled-threshold",
204
+ type=int,
205
+ default=3,
206
+ help="Minimum distinct left entities to flag as bundled (default 3)",
207
+ )
208
+ a = p.parse_args()
209
+
210
+ payload = build_findings(
211
+ cross_links_path=a.cross_links,
212
+ timing_path=a.timing,
213
+ out_path=a.out,
214
+ bundled_threshold=a.bundled_threshold,
215
+ )
216
+ print(f"Wrote {payload['metadata']['n_findings']} findings to {a.out}")
217
+ return 0
218
+
219
+
220
+ if __name__ == "__main__":
221
+ raise SystemExit(main())
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env python3
2
+ """Cross-source entity resolution (stdlib-only).
3
+
4
+ Given two CSV files with name columns, find candidate matches using three
5
+ tiers of normalization:
6
+
7
+ 1. exact — normalized strings equal
8
+ 2. fuzzy — sorted-token (word-bag) match
9
+ 3. token_overlap — >=60% Jaccard overlap on >=4-char tokens, >=2 shared
10
+
11
+ Adapted from ShinMegamiBoson/OpenPlanter (MIT) but generalized: no Boston-
12
+ specific record types, no contribution-code filters, no fixed schemas.
13
+
14
+ Output CSV columns:
15
+ match_type, confidence, left_name, right_name,
16
+ left_normalized, right_normalized, left_row, right_row,
17
+ overlap_ratio, shared_tokens
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import argparse
22
+ import csv
23
+ import sys
24
+ from pathlib import Path
25
+
26
+ # Allow running directly or as a module.
27
+ sys.path.insert(0, str(Path(__file__).parent))
28
+ from _normalize import ( # noqa: E402
29
+ normalize_name,
30
+ normalize_aggressive,
31
+ token_overlap_ratio,
32
+ )
33
+
34
+ CONFIDENCE = {
35
+ "exact": "high",
36
+ "fuzzy": "medium",
37
+ "token_overlap": "low",
38
+ }
39
+
40
+
41
+ def _read_csv(path: str, name_col: str) -> list[dict[str, str]]:
42
+ rows = []
43
+ with open(path, newline="", encoding="utf-8") as fh:
44
+ reader = csv.DictReader(fh)
45
+ if name_col not in (reader.fieldnames or []):
46
+ raise SystemExit(
47
+ f"Column {name_col!r} not in {path}. "
48
+ f"Available: {reader.fieldnames}"
49
+ )
50
+ for i, row in enumerate(reader):
51
+ row["__row__"] = str(i)
52
+ rows.append(row)
53
+ return rows
54
+
55
+
56
+ def _build_index(rows: list[dict[str, str]], name_col: str):
57
+ """Index by exact-normalized and aggressive (sorted-token) form."""
58
+ exact: dict[str, list[dict[str, str]]] = {}
59
+ aggressive: dict[str, list[dict[str, str]]] = {}
60
+ for row in rows:
61
+ raw = row.get(name_col, "")
62
+ n = normalize_name(raw)
63
+ if n:
64
+ exact.setdefault(n, []).append(row)
65
+ a = normalize_aggressive(raw)
66
+ if a:
67
+ aggressive.setdefault(a, []).append(row)
68
+ return exact, aggressive
69
+
70
+
71
+ def _emit(
72
+ out_rows: list[dict[str, str]],
73
+ seen: set[tuple],
74
+ match_type: str,
75
+ left_row: dict[str, str],
76
+ right_row: dict[str, str],
77
+ left_col: str,
78
+ right_col: str,
79
+ ratio: float = 0.0,
80
+ shared: int = 0,
81
+ ):
82
+ left_raw = left_row.get(left_col, "")
83
+ right_raw = right_row.get(right_col, "")
84
+ key = (
85
+ left_row["__row__"],
86
+ right_row["__row__"],
87
+ match_type,
88
+ )
89
+ if key in seen:
90
+ return
91
+ seen.add(key)
92
+ out_rows.append(
93
+ {
94
+ "match_type": match_type,
95
+ "confidence": CONFIDENCE[match_type],
96
+ "left_name": left_raw,
97
+ "right_name": right_raw,
98
+ "left_normalized": normalize_name(left_raw),
99
+ "right_normalized": normalize_name(right_raw),
100
+ "left_row": left_row["__row__"],
101
+ "right_row": right_row["__row__"],
102
+ "overlap_ratio": f"{ratio:.3f}" if ratio else "",
103
+ "shared_tokens": str(shared) if shared else "",
104
+ }
105
+ )
106
+
107
+
108
+ def resolve(
109
+ left_path: str,
110
+ left_col: str,
111
+ right_path: str,
112
+ right_col: str,
113
+ out_path: str,
114
+ overlap_threshold: float = 0.60,
115
+ min_shared: int = 2,
116
+ skip_overlap: bool = False,
117
+ ) -> int:
118
+ left_rows = _read_csv(left_path, left_col)
119
+ right_rows = _read_csv(right_path, right_col)
120
+
121
+ right_exact, right_aggressive = _build_index(right_rows, right_col)
122
+
123
+ out_rows: list[dict[str, str]] = []
124
+ seen: set[tuple] = set()
125
+
126
+ # Pass 1+2: exact / fuzzy via index lookup.
127
+ for lrow in left_rows:
128
+ raw = lrow.get(left_col, "")
129
+ n = normalize_name(raw)
130
+ if not n:
131
+ continue
132
+ for rrow in right_exact.get(n, []):
133
+ _emit(out_rows, seen, "exact", lrow, rrow, left_col, right_col)
134
+ a = normalize_aggressive(raw)
135
+ if a:
136
+ for rrow in right_aggressive.get(a, []):
137
+ _emit(out_rows, seen, "fuzzy", lrow, rrow, left_col, right_col)
138
+
139
+ if not skip_overlap:
140
+ # Pass 3: token overlap (O(N*M) — expensive; allow opt-out).
141
+ for lrow in left_rows:
142
+ l_raw = lrow.get(left_col, "")
143
+ if not normalize_name(l_raw):
144
+ continue
145
+ for rrow in right_rows:
146
+ ratio, shared = token_overlap_ratio(
147
+ l_raw, rrow.get(right_col, "")
148
+ )
149
+ if ratio >= overlap_threshold and shared >= min_shared:
150
+ _emit(
151
+ out_rows,
152
+ seen,
153
+ "token_overlap",
154
+ lrow,
155
+ rrow,
156
+ left_col,
157
+ right_col,
158
+ ratio=ratio,
159
+ shared=shared,
160
+ )
161
+
162
+ fieldnames = [
163
+ "match_type",
164
+ "confidence",
165
+ "left_name",
166
+ "right_name",
167
+ "left_normalized",
168
+ "right_normalized",
169
+ "left_row",
170
+ "right_row",
171
+ "overlap_ratio",
172
+ "shared_tokens",
173
+ ]
174
+ with open(out_path, "w", newline="", encoding="utf-8") as fh:
175
+ writer = csv.DictWriter(fh, fieldnames=fieldnames)
176
+ writer.writeheader()
177
+ writer.writerows(out_rows)
178
+ return len(out_rows)
179
+
180
+
181
+ def main() -> int:
182
+ p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
183
+ p.add_argument("--left", required=True, help="Left CSV path")
184
+ p.add_argument(
185
+ "--left-name-col", required=True, help="Name column in left CSV"
186
+ )
187
+ p.add_argument("--right", required=True, help="Right CSV path")
188
+ p.add_argument(
189
+ "--right-name-col",
190
+ required=True,
191
+ help="Name column in right CSV",
192
+ )
193
+ p.add_argument("--out", required=True, help="Output CSV path")
194
+ p.add_argument(
195
+ "--overlap-threshold",
196
+ type=float,
197
+ default=0.60,
198
+ help="Jaccard overlap threshold for token_overlap tier (default 0.60)",
199
+ )
200
+ p.add_argument(
201
+ "--min-shared",
202
+ type=int,
203
+ default=2,
204
+ help="Minimum shared tokens for token_overlap tier (default 2)",
205
+ )
206
+ p.add_argument(
207
+ "--skip-overlap",
208
+ action="store_true",
209
+ help="Skip the O(N*M) token_overlap pass (much faster on large CSVs)",
210
+ )
211
+ args = p.parse_args()
212
+
213
+ count = resolve(
214
+ left_path=args.left,
215
+ left_col=args.left_name_col,
216
+ right_path=args.right,
217
+ right_col=args.right_name_col,
218
+ out_path=args.out,
219
+ overlap_threshold=args.overlap_threshold,
220
+ min_shared=args.min_shared,
221
+ skip_overlap=args.skip_overlap,
222
+ )
223
+ print(f"Wrote {count} match rows to {args.out}")
224
+ return 0
225
+
226
+
227
+ if __name__ == "__main__":
228
+ raise SystemExit(main())
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env python3
2
+ """Search court records via CourtListener (Free Law Project).
3
+
4
+ Covers ~10M federal and state court opinions, plus PACER docket data
5
+ where available. Public REST API v4 supports anonymous read access for
6
+ search; some endpoints require a token (free at courtlistener.com).
7
+
8
+ Set COURTLISTENER_TOKEN to authenticate (raises rate limits).
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import csv
14
+ import os
15
+ import sys
16
+ import urllib.parse
17
+ from pathlib import Path
18
+
19
+ sys.path.insert(0, str(Path(__file__).parent))
20
+ from _http import get_json # noqa: E402
21
+
22
+ BASE = "https://www.courtlistener.com/api/rest/v4/search/"
23
+
24
+ COLUMNS = [
25
+ "case_name",
26
+ "court",
27
+ "court_id",
28
+ "date_filed",
29
+ "docket_number",
30
+ "judge",
31
+ "citation",
32
+ "result_type",
33
+ "snippet",
34
+ "absolute_url",
35
+ ]
36
+
37
+ SEARCH_TYPES = {
38
+ "opinions": "o", # Court opinions
39
+ "dockets": "r", # PACER dockets (may require auth depending on coverage)
40
+ "oral": "oa", # Oral arguments
41
+ "people": "p", # Judges / people
42
+ "recap": "r", # Same as dockets in v4
43
+ }
44
+
45
+
46
+ def fetch(
47
+ query: str,
48
+ search_type: str,
49
+ court: str | None,
50
+ date_from: str | None,
51
+ date_to: str | None,
52
+ token: str | None,
53
+ limit: int,
54
+ out_path: str,
55
+ ) -> int:
56
+ type_code = SEARCH_TYPES.get(search_type, search_type)
57
+ params = {
58
+ "q": query,
59
+ "type": type_code,
60
+ }
61
+ if court:
62
+ params["court"] = court
63
+ if date_from:
64
+ params["filed_after"] = date_from
65
+ if date_to:
66
+ params["filed_before"] = date_to
67
+ headers = {"Authorization": f"Token {token}"} if token else None
68
+
69
+ rows: list[dict[str, str]] = []
70
+ next_url: str | None = f"{BASE}?{urllib.parse.urlencode(params)}"
71
+ while next_url and len(rows) < limit:
72
+ try:
73
+ payload = get_json(next_url, headers=headers)
74
+ except Exception as e: # noqa: BLE001
75
+ print(f"CourtListener error: {e}", file=sys.stderr)
76
+ break
77
+ if not isinstance(payload, dict):
78
+ break
79
+ results = payload.get("results", [])
80
+ for r in results:
81
+ if len(rows) >= limit:
82
+ break
83
+ rows.append(
84
+ {
85
+ "case_name": r.get("caseName", "") or r.get("case_name", "") or "",
86
+ "court": r.get("court", "") or "",
87
+ "court_id": r.get("court_id", "") or "",
88
+ "date_filed": (r.get("dateFiled", "") or r.get("date_filed", "") or "")[:10],
89
+ "docket_number": r.get("docketNumber", "") or r.get("docket_number", "") or "",
90
+ "judge": r.get("judge", "") or "",
91
+ "citation": "; ".join(r.get("citation", []) or []) if isinstance(r.get("citation"), list) else (r.get("citation") or ""),
92
+ "result_type": search_type,
93
+ "snippet": (r.get("snippet", "") or "").replace("\n", " ")[:500],
94
+ "absolute_url": (
95
+ f"https://www.courtlistener.com{r.get('absolute_url', '')}"
96
+ if r.get("absolute_url", "").startswith("/")
97
+ else r.get("absolute_url", "")
98
+ ),
99
+ }
100
+ )
101
+ next_url = payload.get("next")
102
+
103
+ Path(out_path).parent.mkdir(parents=True, exist_ok=True)
104
+ with open(out_path, "w", newline="", encoding="utf-8") as fh:
105
+ w = csv.DictWriter(fh, fieldnames=COLUMNS)
106
+ w.writeheader()
107
+ w.writerows(rows)
108
+ if not rows:
109
+ print(
110
+ f"CourtListener: 0 results for type={search_type!r} q={query!r}. "
111
+ "Most private individuals don't appear in published court records "
112
+ "unless they were party to a federal or state appellate case.",
113
+ file=sys.stderr,
114
+ )
115
+ return len(rows)
116
+
117
+
118
+ def main() -> int:
119
+ p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
120
+ p.add_argument("--query", required=True, help="Search query (party name, case name, keyword)")
121
+ p.add_argument(
122
+ "--type",
123
+ default="opinions",
124
+ choices=list(SEARCH_TYPES.keys()),
125
+ help="Search type (default: opinions)",
126
+ )
127
+ p.add_argument("--court", help="Court ID filter (e.g. 'nysd' = SDNY, 'scotus' = Supreme Court)")
128
+ p.add_argument("--date-from", help="Filed-after date YYYY-MM-DD")
129
+ p.add_argument("--date-to", help="Filed-before date YYYY-MM-DD")
130
+ p.add_argument("--token", default=os.environ.get("COURTLISTENER_TOKEN"))
131
+ p.add_argument("--limit", type=int, default=100)
132
+ p.add_argument("--out", required=True)
133
+ a = p.parse_args()
134
+ n = fetch(
135
+ query=a.query,
136
+ search_type=a.type,
137
+ court=a.court,
138
+ date_from=a.date_from,
139
+ date_to=a.date_to,
140
+ token=a.token,
141
+ limit=a.limit,
142
+ out_path=a.out,
143
+ )
144
+ print(f"Wrote {n} CourtListener rows to {a.out}")
145
+ return 0
146
+
147
+
148
+ if __name__ == "__main__":
149
+ raise SystemExit(main())