mindforge-cc 11.8.3 → 11.9.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 (435) hide show
  1. package/.agent/mindforge/ship.md +2 -2
  2. package/.agent/mindforge/update.md +1 -1
  3. package/.agent/mindforge/wf-catalog.md +5 -2
  4. package/.agent/mindforge/wf-orchestrate-review.md +35 -0
  5. package/.agent/mindforge/wf-verification-loop.md +45 -0
  6. package/.agent/mindforge/wf-workflow-optimizer.md +51 -0
  7. package/.agent/skills/article-illustrator/SKILL.md +7 -7
  8. package/.agent/skills/arxiv/SKILL.md +6 -6
  9. package/.agent/skills/code-wiki/SKILL.md +11 -11
  10. package/.agent/skills/concept-diagrams/SKILL.md +6 -6
  11. package/.agent/skills/concept-diagrams/examples/apartment-floor-plan-conversion.md +244 -0
  12. package/.agent/skills/concept-diagrams/examples/automated-password-reset-flow.md +276 -0
  13. package/.agent/skills/concept-diagrams/examples/autonomous-llm-research-agent-flow.md +240 -0
  14. package/.agent/skills/concept-diagrams/examples/banana-journey-tree-to-smoothie.md +161 -0
  15. package/.agent/skills/concept-diagrams/examples/commercial-aircraft-structure.md +209 -0
  16. package/.agent/skills/concept-diagrams/examples/cpu-ooo-microarchitecture.md +236 -0
  17. package/.agent/skills/concept-diagrams/examples/electricity-grid-flow.md +182 -0
  18. package/.agent/skills/concept-diagrams/examples/feature-film-production-pipeline.md +172 -0
  19. package/.agent/skills/concept-diagrams/examples/hospital-emergency-department-flow.md +165 -0
  20. package/.agent/skills/concept-diagrams/examples/ml-benchmark-grouped-bar-chart.md +114 -0
  21. package/.agent/skills/concept-diagrams/examples/place-order-uml-sequence.md +325 -0
  22. package/.agent/skills/concept-diagrams/examples/smart-city-infrastructure.md +173 -0
  23. package/.agent/skills/concept-diagrams/examples/smartphone-layer-anatomy.md +154 -0
  24. package/.agent/skills/concept-diagrams/examples/sn2-reaction-mechanism.md +247 -0
  25. package/.agent/skills/concept-diagrams/examples/wind-turbine-structure.md +338 -0
  26. package/.agent/skills/concept-diagrams/templates/template.html +174 -0
  27. package/.agent/skills/domain-intel/SKILL.md +7 -7
  28. package/.agent/skills/duckduckgo-search/SKILL.md +17 -17
  29. package/.agent/skills/github-auth/scripts/gh-env.sh +66 -0
  30. package/.agent/skills/github-code-review/SKILL.md +9 -9
  31. package/.agent/skills/github-pr-workflow/SKILL.md +3 -3
  32. package/.agent/skills/godmode/SKILL.md +8 -8
  33. package/.agent/skills/jupyter-live-kernel/SKILL.md +3 -3
  34. package/.agent/skills/kanban-orchestrator/SKILL.md +10 -10
  35. package/.agent/skills/kanban-worker/SKILL.md +6 -6
  36. package/.agent/skills/llm-wiki/SKILL.md +7 -7
  37. package/.agent/skills/mindforge-autonomous/SKILL.md +1 -1
  38. package/.agent/skills/mindforge-complete-milestone/SKILL.md +1 -1
  39. package/.agent/skills/mindforge-discuss-phase/SKILL.md +1 -1
  40. package/.agent/skills/mindforge-do/SKILL.md +1 -1
  41. package/.agent/skills/mindforge-execute-phase/SKILL.md +1 -1
  42. package/.agent/skills/mindforge-list-workspaces/SKILL.md +1 -1
  43. package/.agent/skills/mindforge-manager/SKILL.md +1 -1
  44. package/.agent/skills/mindforge-neural-orchestrator/SKILL.md +1 -1
  45. package/.agent/skills/mindforge-new-milestone/SKILL.md +4 -4
  46. package/.agent/skills/mindforge-new-project/SKILL.md +4 -4
  47. package/.agent/skills/mindforge-new-workspace/SKILL.md +1 -1
  48. package/.agent/skills/mindforge-note/SKILL.md +1 -1
  49. package/.agent/skills/mindforge-plan-phase/SKILL.md +1 -1
  50. package/.agent/skills/mindforge-profile-user/SKILL.md +1 -1
  51. package/.agent/skills/mindforge-remove-workspace/SKILL.md +1 -1
  52. package/.agent/skills/mindforge-research-phase/SKILL.md +1 -1
  53. package/.agent/skills/mindforge-ui-phase/SKILL.md +1 -1
  54. package/.agent/skills/mindforge-ui-review/SKILL.md +1 -1
  55. package/.agent/skills/mindforge-verify-work/SKILL.md +1 -1
  56. package/.agent/skills/mindforge-workspace-isolated/SKILL.md +1 -1
  57. package/.agent/skills/node-inspect-debugger/SKILL.md +1 -1
  58. package/.agent/skills/obsidian/SKILL.md +15 -15
  59. package/.agent/skills/osint-investigation/SKILL.md +3 -2
  60. package/.agent/skills/osint-investigation/references/sources/courtlistener.md +98 -0
  61. package/.agent/skills/osint-investigation/references/sources/gdelt.md +104 -0
  62. package/.agent/skills/osint-investigation/references/sources/icij-offshore.md +104 -0
  63. package/.agent/skills/osint-investigation/references/sources/nyc-acris.md +90 -0
  64. package/.agent/skills/osint-investigation/references/sources/ofac-sdn.md +92 -0
  65. package/.agent/skills/osint-investigation/references/sources/opencorporates.md +103 -0
  66. package/.agent/skills/osint-investigation/references/sources/sec-edgar.md +83 -0
  67. package/.agent/skills/osint-investigation/references/sources/senate-ld.md +89 -0
  68. package/.agent/skills/osint-investigation/references/sources/usaspending.md +97 -0
  69. package/.agent/skills/osint-investigation/references/sources/wayback.md +93 -0
  70. package/.agent/skills/osint-investigation/references/sources/wikipedia.md +107 -0
  71. package/.agent/skills/osint-investigation/scripts/_http.py +82 -0
  72. package/.agent/skills/osint-investigation/scripts/_normalize.py +67 -0
  73. package/.agent/skills/osint-investigation/scripts/build_findings.py +221 -0
  74. package/.agent/skills/osint-investigation/scripts/entity_resolution.py +228 -0
  75. package/.agent/skills/osint-investigation/scripts/fetch_courtlistener.py +149 -0
  76. package/.agent/skills/osint-investigation/scripts/fetch_gdelt.py +161 -0
  77. package/.agent/skills/osint-investigation/scripts/fetch_icij_offshore.py +234 -0
  78. package/.agent/skills/osint-investigation/scripts/fetch_nyc_acris.py +203 -0
  79. package/.agent/skills/osint-investigation/scripts/fetch_ofac_sdn.py +175 -0
  80. package/.agent/skills/osint-investigation/scripts/fetch_opencorporates.py +191 -0
  81. package/.agent/skills/osint-investigation/scripts/fetch_sec_edgar.py +184 -0
  82. package/.agent/skills/osint-investigation/scripts/fetch_senate_ld.py +146 -0
  83. package/.agent/skills/osint-investigation/scripts/fetch_usaspending.py +170 -0
  84. package/.agent/skills/osint-investigation/scripts/fetch_wayback.py +142 -0
  85. package/.agent/skills/osint-investigation/scripts/fetch_wikipedia.py +266 -0
  86. package/.agent/skills/osint-investigation/scripts/timing_analysis.py +252 -0
  87. package/.agent/skills/oss-forensics/SKILL.md +10 -9
  88. package/.agent/skills/oss-forensics/scripts/evidence-store.py +313 -0
  89. package/.agent/skills/parallel-cli/SKILL.md +2 -2
  90. package/.agent/skills/pinggy-tunnel/SKILL.md +3 -3
  91. package/.agent/skills/pixel-art/SKILL.md +2 -2
  92. package/.agent/skills/plan/SKILL.md +10 -10
  93. package/.agent/skills/requesting-code-review/SKILL.md +4 -6
  94. package/.agent/skills/research-paper-writing/SKILL.md +32 -32
  95. package/.agent/skills/rest-graphql-debug/SKILL.md +31 -32
  96. package/.agent/skills/s6-container-supervision/SKILL.md +1 -1
  97. package/.agent/skills/scrapling/SKILL.md +1 -1
  98. package/.agent/skills/sherlock/SKILL.md +1 -1
  99. package/.agent/skills/simplify-code/SKILL.md +5 -7
  100. package/.agent/skills/skill-authoring/SKILL.md +4 -4
  101. package/.agent/skills/spike/SKILL.md +11 -13
  102. package/.agent/skills/subagent-driven-development/SKILL.md +7 -11
  103. package/.agent/skills/systematic-debugging/SKILL.md +13 -14
  104. package/.agent/skills/test-driven-development/SKILL.md +6 -7
  105. package/.agent/skills/video-orchestrator/SKILL.md +1 -1
  106. package/.agent/skills/web-pentest/SKILL.md +7 -7
  107. package/.claude/commands/mindforge/ship.md +2 -2
  108. package/.claude/commands/mindforge/update.md +1 -1
  109. package/.claude/commands/mindforge/wf-catalog.md +5 -2
  110. package/.claude/commands/mindforge/wf-orchestrate-review.md +35 -0
  111. package/.claude/commands/mindforge/wf-verification-loop.md +45 -0
  112. package/.claude/commands/mindforge/wf-workflow-optimizer.md +51 -0
  113. package/.mindforge/config.json +3 -3
  114. package/.mindforge/dynamic-workflows/REGISTRY.md +4 -1
  115. package/.mindforge/dynamic-workflows/index.json +910 -840
  116. package/.mindforge/dynamic-workflows/scripts/accessibility-audit.js +88 -90
  117. package/.mindforge/dynamic-workflows/scripts/ai-model-eval.js +59 -61
  118. package/.mindforge/dynamic-workflows/scripts/api-contract-test.js +87 -85
  119. package/.mindforge/dynamic-workflows/scripts/api-migration.js +120 -122
  120. package/.mindforge/dynamic-workflows/scripts/architecture-modernization.js +84 -86
  121. package/.mindforge/dynamic-workflows/scripts/code-audit.js +75 -77
  122. package/.mindforge/dynamic-workflows/scripts/code-explainer.js +99 -101
  123. package/.mindforge/dynamic-workflows/scripts/competitive-analysis.js +62 -64
  124. package/.mindforge/dynamic-workflows/scripts/competitive-teardown.js +106 -108
  125. package/.mindforge/dynamic-workflows/scripts/cost-analysis.js +78 -80
  126. package/.mindforge/dynamic-workflows/scripts/data-pipeline-validate.js +47 -49
  127. package/.mindforge/dynamic-workflows/scripts/database-migration.js +85 -87
  128. package/.mindforge/dynamic-workflows/scripts/debug-detective.js +97 -99
  129. package/.mindforge/dynamic-workflows/scripts/dependency-health.js +81 -83
  130. package/.mindforge/dynamic-workflows/scripts/design-system-audit.js +84 -86
  131. package/.mindforge/dynamic-workflows/scripts/documentation-gen.js +65 -67
  132. package/.mindforge/dynamic-workflows/scripts/feature-planner.js +78 -80
  133. package/.mindforge/dynamic-workflows/scripts/incident-response.js +81 -83
  134. package/.mindforge/dynamic-workflows/scripts/multi-repo-sync.js +49 -51
  135. package/.mindforge/dynamic-workflows/scripts/mutation-testing.js +113 -115
  136. package/.mindforge/dynamic-workflows/scripts/onboard-codebase.js +79 -81
  137. package/.mindforge/dynamic-workflows/scripts/orchestrate-review.js +115 -0
  138. package/.mindforge/dynamic-workflows/scripts/perf-optimize.js +97 -99
  139. package/.mindforge/dynamic-workflows/scripts/pr-review.js +61 -63
  140. package/.mindforge/dynamic-workflows/scripts/refactor-plan.js +88 -90
  141. package/.mindforge/dynamic-workflows/scripts/release-prep.js +79 -81
  142. package/.mindforge/dynamic-workflows/scripts/security-hardening.js +117 -119
  143. package/.mindforge/dynamic-workflows/scripts/security-threat-model.js +119 -121
  144. package/.mindforge/dynamic-workflows/scripts/tdd-sprint.js +80 -82
  145. package/.mindforge/dynamic-workflows/scripts/tech-evaluation.js +50 -52
  146. package/.mindforge/dynamic-workflows/scripts/test-coverage-gap.js +68 -70
  147. package/.mindforge/dynamic-workflows/scripts/ux-heuristic-audit.js +89 -91
  148. package/.mindforge/dynamic-workflows/scripts/verification-loop.js +138 -0
  149. package/.mindforge/dynamic-workflows/scripts/workflow-optimizer.js +136 -0
  150. package/.mindforge/dynamic-workflows/scripts/writer-reviewer.js +63 -65
  151. package/.mindforge/memory/sync-manifest.json +1 -1
  152. package/.mindforge/personas/advisor-researcher.md +2 -2
  153. package/.mindforge/personas/api-load-tester.md +1 -1
  154. package/.mindforge/personas/authentication-architect.md +1 -1
  155. package/.mindforge/personas/backup-recovery-specialist.md +1 -1
  156. package/.mindforge/personas/build-optimizer.md +1 -1
  157. package/.mindforge/personas/caching-strategist.md +1 -1
  158. package/.mindforge/personas/chaos-engineer.md +1 -1
  159. package/.mindforge/personas/compliance-auditor.md +1 -1
  160. package/.mindforge/personas/config-management-expert.md +1 -1
  161. package/.mindforge/personas/coverage-specialist.md +1 -1
  162. package/.mindforge/personas/debug-specialist.md +2 -2
  163. package/.mindforge/personas/debugger.md +2 -2
  164. package/.mindforge/personas/dependency-auditor.md +1 -1
  165. package/.mindforge/personas/developer.md +1 -1
  166. package/.mindforge/personas/devops-engineer.md +1 -1
  167. package/.mindforge/personas/doc-auditor.md +1 -1
  168. package/.mindforge/personas/executor.md +1 -1
  169. package/.mindforge/personas/go-specialist.md +1 -1
  170. package/.mindforge/personas/incident-commander.md +1 -1
  171. package/.mindforge/personas/java-specialist.md +1 -1
  172. package/.mindforge/personas/kubernetes-debugger.md +1 -1
  173. package/.mindforge/personas/logging-architect.md +1 -1
  174. package/.mindforge/personas/mf-planner.md +1 -1
  175. package/.mindforge/personas/mf-researcher.md +1 -1
  176. package/.mindforge/personas/mf-reviewer.md +1 -1
  177. package/.mindforge/personas/monorepo-architect.md +2 -1
  178. package/.mindforge/personas/nyquist-auditor.md +1 -1
  179. package/.mindforge/personas/observability-engineer.md +1 -1
  180. package/.mindforge/personas/performance-optimizer.md +1 -1
  181. package/.mindforge/personas/phase-researcher.md +5 -5
  182. package/.mindforge/personas/planner.md +1 -1
  183. package/.mindforge/personas/project-researcher.md +2 -2
  184. package/.mindforge/personas/python-specialist.md +1 -1
  185. package/.mindforge/personas/qa-engineer.md +1 -1
  186. package/.mindforge/personas/release-manager.md +6 -4
  187. package/.mindforge/personas/research-agent.md +4 -4
  188. package/.mindforge/personas/roadmapper-extend.md +1 -1
  189. package/.mindforge/personas/rust-specialist.md +1 -1
  190. package/.mindforge/personas/security-reviewer.md +1 -1
  191. package/.mindforge/personas/tech-writer.md +1 -1
  192. package/.mindforge/personas/typescript-wizard.md +1 -1
  193. package/.mindforge/personas/ui-researcher.md +1 -1
  194. package/.mindforge/skills/accessibility/SKILL.md +2 -2
  195. package/.mindforge/skills/agent-architecture-audit/SKILL.md +11 -0
  196. package/.mindforge/skills/arxiv/SKILL.md +6 -6
  197. package/.mindforge/skills/arxiv/scripts/search_arxiv.py +114 -0
  198. package/.mindforge/skills/code-tour/SKILL.md +2 -2
  199. package/.mindforge/skills/code-wiki/SKILL.md +11 -11
  200. package/.mindforge/skills/code-wiki/templates/README.md +31 -0
  201. package/.mindforge/skills/code-wiki/templates/architecture.md +30 -0
  202. package/.mindforge/skills/code-wiki/templates/getting-started.md +47 -0
  203. package/.mindforge/skills/code-wiki/templates/module.md +38 -0
  204. package/.mindforge/skills/concept-diagrams/SKILL.md +6 -6
  205. package/.mindforge/skills/concept-diagrams/examples/apartment-floor-plan-conversion.md +244 -0
  206. package/.mindforge/skills/concept-diagrams/examples/automated-password-reset-flow.md +276 -0
  207. package/.mindforge/skills/concept-diagrams/examples/autonomous-llm-research-agent-flow.md +240 -0
  208. package/.mindforge/skills/concept-diagrams/examples/banana-journey-tree-to-smoothie.md +161 -0
  209. package/.mindforge/skills/concept-diagrams/examples/commercial-aircraft-structure.md +209 -0
  210. package/.mindforge/skills/concept-diagrams/examples/cpu-ooo-microarchitecture.md +236 -0
  211. package/.mindforge/skills/concept-diagrams/examples/electricity-grid-flow.md +182 -0
  212. package/.mindforge/skills/concept-diagrams/examples/feature-film-production-pipeline.md +172 -0
  213. package/.mindforge/skills/concept-diagrams/examples/hospital-emergency-department-flow.md +165 -0
  214. package/.mindforge/skills/concept-diagrams/examples/ml-benchmark-grouped-bar-chart.md +114 -0
  215. package/.mindforge/skills/concept-diagrams/examples/place-order-uml-sequence.md +325 -0
  216. package/.mindforge/skills/concept-diagrams/examples/smart-city-infrastructure.md +173 -0
  217. package/.mindforge/skills/concept-diagrams/examples/smartphone-layer-anatomy.md +154 -0
  218. package/.mindforge/skills/concept-diagrams/examples/sn2-reaction-mechanism.md +247 -0
  219. package/.mindforge/skills/concept-diagrams/examples/wind-turbine-structure.md +338 -0
  220. package/.mindforge/skills/concept-diagrams/references/dashboard-patterns.md +43 -0
  221. package/.mindforge/skills/concept-diagrams/references/infrastructure-patterns.md +144 -0
  222. package/.mindforge/skills/concept-diagrams/references/physical-shape-cookbook.md +42 -0
  223. package/.mindforge/skills/concept-diagrams/templates/template.html +174 -0
  224. package/.mindforge/skills/creative-ideation/references/full-prompt-library.md +110 -0
  225. package/.mindforge/skills/documentation/SKILL.md +4 -1
  226. package/.mindforge/skills/domain-intel/SKILL.md +7 -7
  227. package/.mindforge/skills/domain-intel/scripts/domain_intel.py +397 -0
  228. package/.mindforge/skills/duckduckgo-search/SKILL.md +17 -17
  229. package/.mindforge/skills/duckduckgo-search/scripts/duckduckgo.sh +28 -0
  230. package/.mindforge/skills/environment-management/SKILL.md +1 -1
  231. package/.mindforge/skills/github-code-review/SKILL.md +9 -9
  232. package/.mindforge/skills/github-code-review/references/review-output-template.md +74 -0
  233. package/.mindforge/skills/github-issues/templates/bug-report.md +35 -0
  234. package/.mindforge/skills/github-issues/templates/feature-request.md +31 -0
  235. package/.mindforge/skills/github-pr-workflow/SKILL.md +3 -3
  236. package/.mindforge/skills/github-pr-workflow/references/ci-troubleshooting.md +183 -0
  237. package/.mindforge/skills/github-pr-workflow/references/conventional-commits.md +71 -0
  238. package/.mindforge/skills/github-pr-workflow/templates/pr-body-bugfix.md +35 -0
  239. package/.mindforge/skills/github-pr-workflow/templates/pr-body-feature.md +33 -0
  240. package/.mindforge/skills/jupyter-live-kernel/SKILL.md +3 -3
  241. package/.mindforge/skills/kanban-orchestrator/SKILL.md +10 -10
  242. package/.mindforge/skills/kanban-worker/SKILL.md +6 -6
  243. package/.mindforge/skills/meme-generation/SKILL.md +1 -1
  244. package/.mindforge/skills/meme-generation/scripts/generate_meme.py +470 -0
  245. package/.mindforge/skills/meme-generation/scripts/templates.json +97 -0
  246. package/.mindforge/skills/obsidian/SKILL.md +15 -15
  247. package/.mindforge/skills/orch-pipeline/SKILL.md +2 -2
  248. package/.mindforge/skills/osint-investigation/SKILL.md +3 -2
  249. package/.mindforge/skills/osint-investigation/references/sources/courtlistener.md +98 -0
  250. package/.mindforge/skills/osint-investigation/references/sources/gdelt.md +104 -0
  251. package/.mindforge/skills/osint-investigation/references/sources/icij-offshore.md +104 -0
  252. package/.mindforge/skills/osint-investigation/references/sources/nyc-acris.md +90 -0
  253. package/.mindforge/skills/osint-investigation/references/sources/ofac-sdn.md +92 -0
  254. package/.mindforge/skills/osint-investigation/references/sources/opencorporates.md +103 -0
  255. package/.mindforge/skills/osint-investigation/references/sources/sec-edgar.md +83 -0
  256. package/.mindforge/skills/osint-investigation/references/sources/senate-ld.md +89 -0
  257. package/.mindforge/skills/osint-investigation/references/sources/usaspending.md +97 -0
  258. package/.mindforge/skills/osint-investigation/references/sources/wayback.md +93 -0
  259. package/.mindforge/skills/osint-investigation/references/sources/wikipedia.md +107 -0
  260. package/.mindforge/skills/osint-investigation/scripts/_http.py +82 -0
  261. package/.mindforge/skills/osint-investigation/scripts/_normalize.py +67 -0
  262. package/.mindforge/skills/osint-investigation/scripts/build_findings.py +221 -0
  263. package/.mindforge/skills/osint-investigation/scripts/entity_resolution.py +228 -0
  264. package/.mindforge/skills/osint-investigation/scripts/fetch_courtlistener.py +149 -0
  265. package/.mindforge/skills/osint-investigation/scripts/fetch_gdelt.py +161 -0
  266. package/.mindforge/skills/osint-investigation/scripts/fetch_icij_offshore.py +234 -0
  267. package/.mindforge/skills/osint-investigation/scripts/fetch_nyc_acris.py +203 -0
  268. package/.mindforge/skills/osint-investigation/scripts/fetch_ofac_sdn.py +175 -0
  269. package/.mindforge/skills/osint-investigation/scripts/fetch_opencorporates.py +191 -0
  270. package/.mindforge/skills/osint-investigation/scripts/fetch_sec_edgar.py +184 -0
  271. package/.mindforge/skills/osint-investigation/scripts/fetch_senate_ld.py +146 -0
  272. package/.mindforge/skills/osint-investigation/scripts/fetch_usaspending.py +170 -0
  273. package/.mindforge/skills/osint-investigation/scripts/fetch_wayback.py +142 -0
  274. package/.mindforge/skills/osint-investigation/scripts/fetch_wikipedia.py +266 -0
  275. package/.mindforge/skills/osint-investigation/scripts/timing_analysis.py +252 -0
  276. package/.mindforge/skills/osint-investigation/templates/source-template.md +59 -0
  277. package/.mindforge/skills/oss-forensics/SKILL.md +6 -6
  278. package/.mindforge/skills/oss-forensics/references/evidence-types.md +89 -0
  279. package/.mindforge/skills/oss-forensics/references/github-archive-guide.md +184 -0
  280. package/.mindforge/skills/oss-forensics/references/investigation-templates.md +131 -0
  281. package/.mindforge/skills/oss-forensics/references/recovery-techniques.md +164 -0
  282. package/.mindforge/skills/oss-forensics/scripts/evidence-store.py +313 -0
  283. package/.mindforge/skills/oss-forensics/templates/forensic-report.md +151 -0
  284. package/.mindforge/skills/oss-forensics/templates/malicious-package-report.md +43 -0
  285. package/.mindforge/skills/pixel-art/SKILL.md +2 -2
  286. package/.mindforge/skills/pixel-art/references/palettes.md +49 -0
  287. package/.mindforge/skills/plan/SKILL.md +10 -10
  288. package/.mindforge/skills/requesting-code-review/SKILL.md +4 -6
  289. package/.mindforge/skills/research-paper-writing/SKILL.md +32 -32
  290. package/.mindforge/skills/research-paper-writing/references/autoreason-methodology.md +394 -0
  291. package/.mindforge/skills/research-paper-writing/references/checklists.md +434 -0
  292. package/.mindforge/skills/research-paper-writing/references/citation-workflow.md +563 -0
  293. package/.mindforge/skills/research-paper-writing/references/experiment-patterns.md +728 -0
  294. package/.mindforge/skills/research-paper-writing/references/human-evaluation.md +476 -0
  295. package/.mindforge/skills/research-paper-writing/references/paper-types.md +481 -0
  296. package/.mindforge/skills/research-paper-writing/references/reviewer-guidelines.md +433 -0
  297. package/.mindforge/skills/research-paper-writing/references/sources.md +191 -0
  298. package/.mindforge/skills/research-paper-writing/references/writing-guide.md +474 -0
  299. package/.mindforge/skills/research-paper-writing/templates/README.md +251 -0
  300. package/.mindforge/skills/scrapling/SKILL.md +1 -1
  301. package/.mindforge/skills/sherlock/SKILL.md +1 -1
  302. package/.mindforge/skills/simplify-code/SKILL.md +5 -7
  303. package/.mindforge/skills/spike/SKILL.md +11 -13
  304. package/.mindforge/skills/subagent-driven-development/SKILL.md +7 -11
  305. package/.mindforge/skills/subagent-driven-development/references/context-budget-discipline.md +53 -0
  306. package/.mindforge/skills/subagent-driven-development/references/gates-taxonomy.md +93 -0
  307. package/.mindforge/skills/systematic-debugging/SKILL.md +13 -14
  308. package/.mindforge/skills/test-driven-development/SKILL.md +6 -7
  309. package/.mindforge/skills/testing-standards/SKILL.md +1 -1
  310. package/.mindforge/skills/web-pentest/SKILL.md +2 -2
  311. package/.mindforge/skills/web-pentest/references/bypass-techniques.md +133 -0
  312. package/.mindforge/skills/web-pentest/references/exploitation-techniques.md +204 -0
  313. package/.mindforge/skills/web-pentest/references/scope-enforcement.md +110 -0
  314. package/.mindforge/skills/web-pentest/references/vuln-taxonomy.md +81 -0
  315. package/.mindforge/skills/web-pentest/templates/authorization.md +69 -0
  316. package/.mindforge/skills/web-pentest/templates/pentest-report.md +178 -0
  317. package/.mindforge/skills/writing-skills/SKILL.md +4 -3
  318. package/CHANGELOG.md +23 -1821
  319. package/MINDFORGE.md +3 -3
  320. package/README.md +58 -510
  321. package/RELEASENOTES.md +87 -2
  322. package/SECURITY.md +5 -3
  323. package/bin/install.js +1 -1
  324. package/bin/models/bedrock-provider.js +192 -0
  325. package/bin/models/model-client.js +19 -0
  326. package/bin/updater/changelog-fetcher.js +95 -5
  327. package/bin/utils/readiness-gate.js +3 -3
  328. package/changelogs/README.md +109 -0
  329. package/changelogs/index.json +103 -0
  330. package/changelogs/v0.1.0.md +21 -0
  331. package/changelogs/v0.2.0.md +15 -0
  332. package/changelogs/v0.3.0.md +28 -0
  333. package/changelogs/v0.4.0.md +29 -0
  334. package/changelogs/v0.5.0.md +34 -0
  335. package/changelogs/v0.6.0.md +34 -0
  336. package/changelogs/v1.0.0.md +60 -0
  337. package/changelogs/v1.0.1.md +15 -0
  338. package/changelogs/v1.0.2.md +10 -0
  339. package/changelogs/v1.0.3.md +10 -0
  340. package/changelogs/v1.0.4.md +10 -0
  341. package/changelogs/v1.0.5.md +10 -0
  342. package/changelogs/v10.0.0.md +71 -0
  343. package/changelogs/v10.0.2.md +15 -0
  344. package/changelogs/v10.0.3.md +21 -0
  345. package/changelogs/v10.0.4.md +16 -0
  346. package/changelogs/v10.0.5.md +16 -0
  347. package/changelogs/v10.0.6.md +15 -0
  348. package/changelogs/v10.0.7.md +15 -0
  349. package/changelogs/v10.0.8.md +15 -0
  350. package/changelogs/v10.0.9.md +15 -0
  351. package/changelogs/v10.1.0.md +16 -0
  352. package/changelogs/v10.1.1.md +15 -0
  353. package/changelogs/v10.2.0.md +14 -0
  354. package/changelogs/v10.3.0.md +13 -0
  355. package/changelogs/v10.4.0.md +13 -0
  356. package/changelogs/v10.5.0.md +13 -0
  357. package/changelogs/v10.6.0.md +13 -0
  358. package/changelogs/v10.7.0.md +15 -0
  359. package/changelogs/v11.0.0.md +66 -0
  360. package/changelogs/v11.0.1.md +10 -0
  361. package/changelogs/v11.1.0.md +56 -0
  362. package/changelogs/v11.2.0.md +41 -0
  363. package/changelogs/v11.2.1.md +38 -0
  364. package/changelogs/v11.3.0.md +45 -0
  365. package/changelogs/v11.3.1.md +39 -0
  366. package/changelogs/v11.4.0.md +38 -0
  367. package/changelogs/v11.5.0.md +77 -0
  368. package/changelogs/v11.5.1.md +47 -0
  369. package/changelogs/v11.6.0.md +45 -0
  370. package/changelogs/v11.7.0.md +30 -0
  371. package/changelogs/v11.7.1.md +8 -0
  372. package/changelogs/v11.8.0.md +19 -0
  373. package/changelogs/v11.8.1.md +28 -0
  374. package/changelogs/v11.8.2.md +22 -0
  375. package/changelogs/v11.8.3.md +22 -0
  376. package/changelogs/v11.9.0.md +17 -0
  377. package/changelogs/v11.9.1.md +8 -0
  378. package/changelogs/v2.0.0-alpha.1.md +42 -0
  379. package/changelogs/v2.0.0-alpha.10.md +13 -0
  380. package/changelogs/v2.0.0-alpha.11.md +14 -0
  381. package/changelogs/v2.0.0-alpha.12.md +16 -0
  382. package/changelogs/v2.0.0-alpha.2.md +14 -0
  383. package/changelogs/v2.0.0-alpha.3.md +17 -0
  384. package/changelogs/v2.0.0-alpha.4.md +17 -0
  385. package/changelogs/v2.0.0-alpha.7.md +18 -0
  386. package/changelogs/v2.0.0-alpha.8.md +11 -0
  387. package/changelogs/v2.0.0-alpha.9.md +12 -0
  388. package/changelogs/v2.0.0.md +35 -0
  389. package/changelogs/v2.1.0.md +22 -0
  390. package/changelogs/v2.1.1.md +25 -0
  391. package/changelogs/v2.1.2.md +21 -0
  392. package/changelogs/v2.3.2.md +19 -0
  393. package/changelogs/v2.3.3.md +16 -0
  394. package/changelogs/v2.3.4.md +16 -0
  395. package/changelogs/v2.3.5.md +21 -0
  396. package/changelogs/v2.4.0.md +29 -0
  397. package/changelogs/v2.5.0.md +26 -0
  398. package/changelogs/v2.6.0.md +26 -0
  399. package/changelogs/v3.0.0.md +17 -0
  400. package/changelogs/v4.0.0.md +19 -0
  401. package/changelogs/v4.1.0.md +19 -0
  402. package/changelogs/v4.3.0.md +31 -0
  403. package/changelogs/v5.0.0-alpha.1.md +35 -0
  404. package/changelogs/v5.0.0-alpha.2.md +22 -0
  405. package/changelogs/v5.1.0.md +42 -0
  406. package/changelogs/v5.2.0.md +18 -0
  407. package/changelogs/v5.3.0.md +14 -0
  408. package/changelogs/v5.4.0.md +28 -0
  409. package/changelogs/v5.5.0.md +11 -0
  410. package/changelogs/v5.6.0.md +11 -0
  411. package/changelogs/v5.7.0.md +12 -0
  412. package/changelogs/v5.8.0.md +12 -0
  413. package/changelogs/v5.9.0.md +11 -0
  414. package/changelogs/v6.0.0-alpha.md +45 -0
  415. package/changelogs/v6.1.0-alpha.md +29 -0
  416. package/changelogs/v6.2.0-alpha.md +30 -0
  417. package/changelogs/v6.2.0.md +21 -0
  418. package/changelogs/v6.3.0.md +14 -0
  419. package/changelogs/v6.4.0.md +13 -0
  420. package/changelogs/v6.5.0.md +13 -0
  421. package/changelogs/v6.6.0.md +12 -0
  422. package/changelogs/v6.7.0.md +12 -0
  423. package/changelogs/v7.0.0.md +13 -0
  424. package/changelogs/v8.0.0.md +14 -0
  425. package/changelogs/v8.1.0.md +13 -0
  426. package/changelogs/v8.2.0.md +12 -0
  427. package/changelogs/v8.2.1.md +11 -0
  428. package/changelogs/v9.0.0.md +20 -0
  429. package/docs/commands-reference.md +72 -4
  430. package/docs/faq.md +38 -1
  431. package/docs/getting-started.md +52 -12
  432. package/docs/sdk-reference.md +36 -3
  433. package/docs/troubleshooting.md +59 -1
  434. package/docs/user-guide.md +7 -4
  435. package/package.json +4 -3
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env python3
2
+ """Search the GDELT 2.0 DOC API for news mentions.
3
+
4
+ GDELT monitors world news in 100+ languages and indexes the full text.
5
+ Free, anonymous, ~15-minute update frequency. Covers ~2015→present.
6
+
7
+ Useful for surfacing news mentions of a person, company, or topic across
8
+ international media — much wider net than Google News.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import csv
14
+ import sys
15
+ import time
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://api.gdeltproject.org/api/v2/doc/doc"
23
+
24
+ COLUMNS = [
25
+ "title",
26
+ "url",
27
+ "seen_date",
28
+ "domain",
29
+ "language",
30
+ "source_country",
31
+ "tone",
32
+ "social_image",
33
+ ]
34
+
35
+
36
+ def fetch(
37
+ query: str,
38
+ mode: str,
39
+ timespan: str | None,
40
+ start_datetime: str | None,
41
+ end_datetime: str | None,
42
+ source_country: str | None,
43
+ source_lang: str | None,
44
+ limit: int,
45
+ out_path: str,
46
+ ) -> int:
47
+ params: dict[str, str] = {
48
+ "query": query,
49
+ "mode": mode,
50
+ "format": "json",
51
+ "maxrecords": str(min(limit, 250)),
52
+ "sort": "datedesc",
53
+ }
54
+ if timespan:
55
+ params["timespan"] = timespan
56
+ if start_datetime:
57
+ params["startdatetime"] = start_datetime.replace("-", "").replace(":", "").replace(" ", "")
58
+ if end_datetime:
59
+ params["enddatetime"] = end_datetime.replace("-", "").replace(":", "").replace(" ", "")
60
+ if source_country:
61
+ params["sourcecountry"] = source_country
62
+ if source_lang:
63
+ params["sourcelang"] = source_lang
64
+
65
+ url = f"{BASE}?{urllib.parse.urlencode(params)}"
66
+ payload: dict | list = {}
67
+ for attempt in range(3):
68
+ try:
69
+ payload = get_json(url)
70
+ break
71
+ except RuntimeError as e:
72
+ # GDELT requires 1 request per 5 seconds; back off and retry.
73
+ if "429" in str(e) and attempt < 2:
74
+ print(
75
+ f"GDELT throttle hit; sleeping 6s before retry "
76
+ f"(attempt {attempt + 1}/3)",
77
+ file=sys.stderr,
78
+ )
79
+ time.sleep(6)
80
+ continue
81
+ print(f"GDELT error: {e}", file=sys.stderr)
82
+ payload = {}
83
+ break
84
+ except Exception as e: # noqa: BLE001
85
+ print(f"GDELT error: {e}", file=sys.stderr)
86
+ payload = {}
87
+ break
88
+
89
+ rows: list[dict[str, str]] = []
90
+ if isinstance(payload, dict):
91
+ articles = payload.get("articles", []) or []
92
+ for a in articles[:limit]:
93
+ seen = (a.get("seendate") or "")
94
+ # GDELT format: 20260319T083000Z → 2026-03-19 08:30:00Z
95
+ if len(seen) == 16 and "T" in seen:
96
+ seen = f"{seen[0:4]}-{seen[4:6]}-{seen[6:8]} {seen[9:11]}:{seen[11:13]}:{seen[13:15]}Z"
97
+ rows.append(
98
+ {
99
+ "title": (a.get("title") or "").replace("\n", " ").strip(),
100
+ "url": a.get("url") or "",
101
+ "seen_date": seen,
102
+ "domain": a.get("domain") or "",
103
+ "language": a.get("language") or "",
104
+ "source_country": a.get("sourcecountry") or "",
105
+ "tone": str(a.get("tone") or ""),
106
+ "social_image": a.get("socialimage") or "",
107
+ }
108
+ )
109
+
110
+ Path(out_path).parent.mkdir(parents=True, exist_ok=True)
111
+ with open(out_path, "w", newline="", encoding="utf-8") as fh:
112
+ w = csv.DictWriter(fh, fieldnames=COLUMNS)
113
+ w.writeheader()
114
+ w.writerows(rows)
115
+ if not rows:
116
+ print(
117
+ f"GDELT: 0 articles for query={query!r}. "
118
+ "GDELT indexes ~2015→present. Try widening the timespan or "
119
+ "checking the query syntax (https://blog.gdeltproject.org/gdelt-doc-2-0-api-debuts/).",
120
+ file=sys.stderr,
121
+ )
122
+ return len(rows)
123
+
124
+
125
+ def main() -> int:
126
+ p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
127
+ p.add_argument("--query", required=True, help='Search query (supports GDELT operators: quoted phrases, AND/OR/NOT, sourcecountry:, theme:)')
128
+ p.add_argument(
129
+ "--mode",
130
+ default="ArtList",
131
+ choices=["ArtList", "ImageCollage", "TimelineVol", "TimelineTone", "ToneChart"],
132
+ help="GDELT mode (default ArtList for article list)",
133
+ )
134
+ p.add_argument(
135
+ "--timespan",
136
+ help="Relative window: e.g. '1d', '1w', '1m', '3m', '1y' (overrides start/end)",
137
+ )
138
+ p.add_argument("--start", help="Absolute start YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS")
139
+ p.add_argument("--end", help="Absolute end YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS")
140
+ p.add_argument("--source-country", help="2-letter source country (e.g. US, UK)")
141
+ p.add_argument("--source-lang", help="Source language (e.g. English, Spanish)")
142
+ p.add_argument("--limit", type=int, default=100)
143
+ p.add_argument("--out", required=True)
144
+ a = p.parse_args()
145
+ n = fetch(
146
+ query=a.query,
147
+ mode=a.mode,
148
+ timespan=a.timespan,
149
+ start_datetime=a.start,
150
+ end_datetime=a.end,
151
+ source_country=a.source_country,
152
+ source_lang=a.source_lang,
153
+ limit=a.limit,
154
+ out_path=a.out,
155
+ )
156
+ print(f"Wrote {n} GDELT article rows to {a.out}")
157
+ return 0
158
+
159
+
160
+ if __name__ == "__main__":
161
+ raise SystemExit(main())
@@ -0,0 +1,234 @@
1
+ #!/usr/bin/env python3
2
+ """Search ICIJ Offshore Leaks via the bulk CSV database.
3
+
4
+ The old reconcile endpoint (https://offshoreleaks.icij.org/reconcile) returns
5
+ 404 — ICIJ has removed it. The remaining stable access path is the public
6
+ bulk download:
7
+
8
+ https://offshoreleaks-data.icij.org/offshoreleaks/csv/full-oldb.LATEST.zip
9
+
10
+ ~70 MB, ~6 CSVs inside (nodes-entities, nodes-officers, nodes-intermediaries,
11
+ nodes-addresses, relationships, ...). We cache it under
12
+ $HERMES_OSINT_CACHE/icij/ (default: ~/.cache/hermes-osint/icij/) and search
13
+ locally so the agent doesn't re-download for every query.
14
+
15
+ Output CSV columns match the original `fetch_icij_offshore.py` contract.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import argparse
20
+ import csv
21
+ import io
22
+ import os
23
+ import re
24
+ import sys
25
+ import time
26
+ import urllib.request
27
+ import zipfile
28
+ from pathlib import Path
29
+
30
+ BULK_URL = "https://offshoreleaks-data.icij.org/offshoreleaks/csv/full-oldb.LATEST.zip"
31
+
32
+ COLUMNS = [
33
+ "node_id",
34
+ "name",
35
+ "node_type",
36
+ "country_codes",
37
+ "countries",
38
+ "jurisdiction",
39
+ "incorporation_date",
40
+ "inactivation_date",
41
+ "source",
42
+ "entity_url",
43
+ "connections",
44
+ ]
45
+
46
+
47
+ def _cache_dir() -> Path:
48
+ base = os.environ.get("HERMES_OSINT_CACHE")
49
+ if base:
50
+ return Path(base) / "icij"
51
+ return Path.home() / ".cache" / "hermes-osint" / "icij"
52
+
53
+
54
+ def _download(dest: Path, force: bool = False) -> Path:
55
+ """Download (or reuse cached) ICIJ bulk ZIP."""
56
+ dest.mkdir(parents=True, exist_ok=True)
57
+ zip_path = dest / "full-oldb.zip"
58
+ if zip_path.exists() and not force:
59
+ # Re-check age: refetch if older than 30 days.
60
+ age_days = (time.time() - zip_path.stat().st_mtime) / 86400
61
+ if age_days < 30:
62
+ return zip_path
63
+ print(f"Downloading ICIJ bulk database (~70 MB) to {zip_path}", file=sys.stderr)
64
+ req = urllib.request.Request(
65
+ BULK_URL,
66
+ headers={"User-Agent": "hermes-agent osint-investigation skill"},
67
+ )
68
+ with urllib.request.urlopen(req, timeout=120) as resp: # noqa: S310
69
+ tmp = zip_path.with_suffix(".zip.tmp")
70
+ with open(tmp, "wb") as fh:
71
+ while True:
72
+ chunk = resp.read(1 << 16)
73
+ if not chunk:
74
+ break
75
+ fh.write(chunk)
76
+ tmp.replace(zip_path)
77
+ return zip_path
78
+
79
+
80
+ def _open_csv(zf: zipfile.ZipFile, name_pattern: str):
81
+ """Open the first CSV matching name_pattern (case-insensitive substring)."""
82
+ for info in zf.infolist():
83
+ if name_pattern.lower() in info.filename.lower() and info.filename.lower().endswith(".csv"):
84
+ return zf.open(info), info.filename
85
+ return None, None
86
+
87
+
88
+ def _match(needle_norm: str, hay: str) -> bool:
89
+ return needle_norm in (hay or "").upper()
90
+
91
+
92
+ def _normalize_query(s: str) -> str:
93
+ s = s.upper()
94
+ s = re.sub(r"[^\w\s]", " ", s)
95
+ s = re.sub(r"\s+", " ", s).strip()
96
+ return s
97
+
98
+
99
+ def fetch(
100
+ entity: str | None,
101
+ officer: str | None,
102
+ jurisdiction: str | None,
103
+ out_path: str,
104
+ cache_dir: Path,
105
+ force_refresh: bool = False,
106
+ limit: int = 500,
107
+ ) -> int:
108
+ zip_path = _download(cache_dir, force=force_refresh)
109
+ rows: list[dict[str, str]] = []
110
+ needles: list[tuple[str, str]] = [] # (kind, normalized needle)
111
+ if entity:
112
+ needles.append(("Entity", _normalize_query(entity)))
113
+ if officer:
114
+ needles.append(("Officer", _normalize_query(officer)))
115
+ jur_norm = _normalize_query(jurisdiction) if jurisdiction else None
116
+
117
+ targets = [
118
+ ("Entity", "nodes-entities"),
119
+ ("Officer", "nodes-officers"),
120
+ ("Intermediary", "nodes-intermediaries"),
121
+ ]
122
+
123
+ with zipfile.ZipFile(zip_path) as zf:
124
+ for node_type, csv_substring in targets:
125
+ relevant_needles = [n for (k, n) in needles if k in {node_type, "Entity", "Officer"}] or []
126
+ # Only scan a CSV if we have a needle that could plausibly match it,
127
+ # or if we have ONLY a jurisdiction filter.
128
+ applicable_needles = [n for (k, n) in needles if k == node_type]
129
+ if needles and not applicable_needles and not jur_norm:
130
+ continue
131
+ stream, fname = _open_csv(zf, csv_substring)
132
+ if not stream:
133
+ continue
134
+ with stream:
135
+ text = io.TextIOWrapper(stream, encoding="utf-8", errors="replace")
136
+ reader = csv.DictReader(text)
137
+ for row in reader:
138
+ name = (row.get("name") or "").strip()
139
+ if not name:
140
+ continue
141
+ name_u = name.upper()
142
+ matched = False
143
+ for n in applicable_needles or relevant_needles:
144
+ if _match(n, name_u):
145
+ matched = True
146
+ break
147
+ if not needles:
148
+ matched = True # jurisdiction-only sweep
149
+ if not matched:
150
+ continue
151
+ jur = (row.get("jurisdiction_description") or row.get("country_codes") or "").strip()
152
+ if jur_norm and jur_norm not in jur.upper() and jur_norm not in (row.get("countries") or "").upper():
153
+ continue
154
+ node_id = (row.get("node_id") or "").strip()
155
+ rows.append(
156
+ {
157
+ "node_id": node_id,
158
+ "name": name,
159
+ "node_type": node_type,
160
+ "country_codes": row.get("country_codes", "") or "",
161
+ "countries": row.get("countries", "") or "",
162
+ "jurisdiction": jur,
163
+ "incorporation_date": row.get("incorporation_date", "") or "",
164
+ "inactivation_date": row.get("inactivation_date", "") or "",
165
+ "source": row.get("sourceID", "") or row.get("source", "") or "",
166
+ "entity_url": (
167
+ f"https://offshoreleaks.icij.org/nodes/{node_id}" if node_id else ""
168
+ ),
169
+ "connections": "",
170
+ }
171
+ )
172
+ if len(rows) >= limit:
173
+ break
174
+ if len(rows) >= limit:
175
+ break
176
+
177
+ Path(out_path).parent.mkdir(parents=True, exist_ok=True)
178
+ with open(out_path, "w", newline="", encoding="utf-8") as fh:
179
+ w = csv.DictWriter(fh, fieldnames=COLUMNS)
180
+ w.writeheader()
181
+ w.writerows(rows)
182
+ if not rows:
183
+ bits = []
184
+ if entity:
185
+ bits.append(f"entity={entity!r}")
186
+ if officer:
187
+ bits.append(f"officer={officer!r}")
188
+ if jurisdiction:
189
+ bits.append(f"jurisdiction={jurisdiction!r}")
190
+ print(
191
+ f"ICIJ: 0 matches for {', '.join(bits)}. "
192
+ "The bulk database covers offshore leaks (Panama, Paradise, Pandora, "
193
+ "Bahamas, Offshore Leaks). Most private US individuals are NOT in it.",
194
+ file=sys.stderr,
195
+ )
196
+ return len(rows)
197
+
198
+
199
+ def main() -> int:
200
+ p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
201
+ p.add_argument("--entity", help="Search by entity name (substring, case-insensitive)")
202
+ p.add_argument("--officer", help="Search by officer / individual name (substring, case-insensitive)")
203
+ p.add_argument("--jurisdiction", help="Filter results by jurisdiction substring")
204
+ p.add_argument("--limit", type=int, default=500)
205
+ p.add_argument("--out", required=True)
206
+ p.add_argument(
207
+ "--cache-dir",
208
+ type=Path,
209
+ default=None,
210
+ help="Override cache directory (default: $HERMES_OSINT_CACHE/icij or ~/.cache/hermes-osint/icij)",
211
+ )
212
+ p.add_argument(
213
+ "--force-refresh",
214
+ action="store_true",
215
+ help="Re-download the bulk ZIP even if a recent cached copy exists.",
216
+ )
217
+ a = p.parse_args()
218
+ if not (a.entity or a.officer or a.jurisdiction):
219
+ p.error("must supply at least one of --entity / --officer / --jurisdiction")
220
+ n = fetch(
221
+ entity=a.entity,
222
+ officer=a.officer,
223
+ jurisdiction=a.jurisdiction,
224
+ out_path=a.out,
225
+ cache_dir=a.cache_dir or _cache_dir(),
226
+ force_refresh=a.force_refresh,
227
+ limit=a.limit,
228
+ )
229
+ print(f"Wrote {n} ICIJ Offshore Leaks rows to {a.out}")
230
+ return 0
231
+
232
+
233
+ if __name__ == "__main__":
234
+ raise SystemExit(main())
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env python3
2
+ """Search NYC property records via ACRIS (Automated City Register Information System).
3
+
4
+ Uses the city's Socrata-backed open data API. No auth required for read access.
5
+
6
+ Datasets:
7
+ bnx9-e6tj — Real Property Master (one row per recorded document)
8
+ 636b-3b5g — Real Property Parties (names — grantor, grantee, etc.)
9
+ 8h5j-fqxa — Real Property Legal (lot / property identifiers)
10
+ uqqa-hym2 — Real Property References
11
+
12
+ The Parties dataset has the names. We search by name and optionally join to
13
+ Master to get the doc type and date.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import csv
19
+ import sys
20
+ import urllib.parse
21
+ from pathlib import Path
22
+
23
+ sys.path.insert(0, str(Path(__file__).parent))
24
+ from _http import get_json # noqa: E402
25
+
26
+ PARTIES_URL = "https://data.cityofnewyork.us/resource/636b-3b5g.json"
27
+ MASTER_URL = "https://data.cityofnewyork.us/resource/bnx9-e6tj.json"
28
+
29
+ PARTY_TYPE = {
30
+ "1": "grantor (seller / mortgagor / debtor)",
31
+ "2": "grantee (buyer / mortgagee / creditor)",
32
+ "3": "other party",
33
+ }
34
+
35
+ BOROUGH = {
36
+ "1": "Manhattan",
37
+ "2": "Bronx",
38
+ "3": "Brooklyn",
39
+ "4": "Queens",
40
+ "5": "Staten Island",
41
+ }
42
+
43
+ COLUMNS = [
44
+ "document_id",
45
+ "name",
46
+ "party_type",
47
+ "party_role",
48
+ "address_1",
49
+ "address_2",
50
+ "city",
51
+ "state",
52
+ "zip",
53
+ "country",
54
+ "doc_type",
55
+ "doc_date",
56
+ "recorded_date",
57
+ "borough",
58
+ "amount",
59
+ "filing_url",
60
+ ]
61
+
62
+
63
+ def _filing_url(document_id: str) -> str:
64
+ if not document_id:
65
+ return ""
66
+ return (
67
+ f"https://a836-acris.nyc.gov/DS/DocumentSearch/DocumentImageView?doc_id={document_id}"
68
+ )
69
+
70
+
71
+ def fetch(
72
+ name: str | None,
73
+ address: str | None,
74
+ party_type: str | None,
75
+ limit: int,
76
+ out_path: str,
77
+ enrich: bool = True,
78
+ ) -> int:
79
+ if not (name or address):
80
+ raise SystemExit("must supply --name or --address")
81
+
82
+ where_clauses: list[str] = []
83
+ if name:
84
+ safe = name.upper().replace("'", "''")
85
+ where_clauses.append(f"upper(name) like '%{safe}%'")
86
+ if address:
87
+ safe_addr = address.upper().replace("'", "''")
88
+ where_clauses.append(f"upper(address_1) like '%{safe_addr}%'")
89
+ if party_type and party_type in {"1", "2", "3"}:
90
+ where_clauses.append(f"party_type='{party_type}'")
91
+
92
+ params = {
93
+ "$where": " AND ".join(where_clauses),
94
+ "$limit": str(limit),
95
+ }
96
+ url = f"{PARTIES_URL}?{urllib.parse.urlencode(params)}"
97
+ parties = get_json(url)
98
+ if not isinstance(parties, list):
99
+ raise SystemExit(f"Unexpected ACRIS response: {parties!r}")
100
+
101
+ # Enrich with master record (doc_type, dates, borough, amount).
102
+ doc_ids: list[str] = sorted({
103
+ d for d in (p.get("document_id") for p in parties) if d
104
+ })
105
+ masters: dict[str, dict] = {}
106
+ if enrich and doc_ids:
107
+ # Batch up to 100 doc_ids per request (Socrata IN-list is fine for this).
108
+ for i in range(0, len(doc_ids), 100):
109
+ chunk = doc_ids[i : i + 100]
110
+ id_list = ",".join(f"'{d}'" for d in chunk)
111
+ master_params = {
112
+ "$where": f"document_id in ({id_list})",
113
+ "$limit": "100",
114
+ }
115
+ url = f"{MASTER_URL}?{urllib.parse.urlencode(master_params)}"
116
+ try:
117
+ rows = get_json(url)
118
+ except Exception as e: # noqa: BLE001
119
+ print(f"ACRIS master lookup failed for chunk: {e}", file=sys.stderr)
120
+ continue
121
+ if isinstance(rows, list):
122
+ for r in rows:
123
+ did = r.get("document_id", "")
124
+ if did:
125
+ masters[did] = r
126
+
127
+ out_rows: list[dict[str, str]] = []
128
+ for p in parties:
129
+ did = p.get("document_id", "") or ""
130
+ m = masters.get(did, {})
131
+ out_rows.append(
132
+ {
133
+ "document_id": did,
134
+ "name": p.get("name", "") or "",
135
+ "party_type": p.get("party_type", "") or "",
136
+ "party_role": PARTY_TYPE.get(p.get("party_type", ""), ""),
137
+ "address_1": p.get("address_1", "") or "",
138
+ "address_2": p.get("address_2", "") or "",
139
+ "city": p.get("city", "") or "",
140
+ "state": p.get("state", "") or "",
141
+ "zip": p.get("zip", "") or "",
142
+ "country": p.get("country", "") or "",
143
+ "doc_type": m.get("doc_type", "") or "",
144
+ "doc_date": (m.get("document_date", "") or "")[:10],
145
+ "recorded_date": (m.get("recorded_datetime", "") or "")[:10],
146
+ "borough": BOROUGH.get(m.get("recorded_borough", ""), m.get("recorded_borough", "")),
147
+ "amount": m.get("document_amt", "") or "",
148
+ "filing_url": _filing_url(did),
149
+ }
150
+ )
151
+
152
+ Path(out_path).parent.mkdir(parents=True, exist_ok=True)
153
+ with open(out_path, "w", newline="", encoding="utf-8") as fh:
154
+ w = csv.DictWriter(fh, fieldnames=COLUMNS)
155
+ w.writeheader()
156
+ w.writerows(out_rows)
157
+
158
+ if not out_rows:
159
+ filters = []
160
+ if name:
161
+ filters.append(f"name={name!r}")
162
+ if address:
163
+ filters.append(f"address={address!r}")
164
+ print(
165
+ f"NYC ACRIS: 0 records for {', '.join(filters)}. "
166
+ "ACRIS covers ONLY NYC (5 boroughs). For property records elsewhere, "
167
+ "search the relevant county recorder directly.",
168
+ file=sys.stderr,
169
+ )
170
+ return len(out_rows)
171
+
172
+
173
+ def main() -> int:
174
+ p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
175
+ p.add_argument("--name", help="Party name substring (case-insensitive)")
176
+ p.add_argument("--address", help="Address line 1 substring")
177
+ p.add_argument(
178
+ "--party-type",
179
+ choices=["1", "2", "3"],
180
+ help="Filter party type: 1=grantor (seller/mortgagor), 2=grantee (buyer/mortgagee), 3=other",
181
+ )
182
+ p.add_argument("--limit", type=int, default=200)
183
+ p.add_argument(
184
+ "--no-enrich",
185
+ action="store_true",
186
+ help="Skip the master-document lookup that adds doc_type/date/amount",
187
+ )
188
+ p.add_argument("--out", required=True)
189
+ a = p.parse_args()
190
+ n = fetch(
191
+ name=a.name,
192
+ address=a.address,
193
+ party_type=a.party_type,
194
+ limit=a.limit,
195
+ out_path=a.out,
196
+ enrich=not a.no_enrich,
197
+ )
198
+ print(f"Wrote {n} NYC ACRIS rows to {a.out}")
199
+ return 0
200
+
201
+
202
+ if __name__ == "__main__":
203
+ raise SystemExit(main())