mindforge-cc 11.8.2 → 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 (361) 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 -2
  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 +3 -3
  184. package/.mindforge/skills/agent-architecture-audit/SKILL.md +11 -0
  185. package/.mindforge/skills/agent-loops/SKILL.md +1 -1
  186. package/.mindforge/skills/arxiv/SKILL.md +6 -6
  187. package/.mindforge/skills/arxiv/scripts/search_arxiv.py +114 -0
  188. package/.mindforge/skills/code-tour/SKILL.md +2 -2
  189. package/.mindforge/skills/code-wiki/SKILL.md +11 -11
  190. package/.mindforge/skills/code-wiki/templates/README.md +31 -0
  191. package/.mindforge/skills/code-wiki/templates/architecture.md +30 -0
  192. package/.mindforge/skills/code-wiki/templates/getting-started.md +47 -0
  193. package/.mindforge/skills/code-wiki/templates/module.md +38 -0
  194. package/.mindforge/skills/concept-diagrams/SKILL.md +6 -6
  195. package/.mindforge/skills/concept-diagrams/examples/apartment-floor-plan-conversion.md +244 -0
  196. package/.mindforge/skills/concept-diagrams/examples/automated-password-reset-flow.md +276 -0
  197. package/.mindforge/skills/concept-diagrams/examples/autonomous-llm-research-agent-flow.md +240 -0
  198. package/.mindforge/skills/concept-diagrams/examples/banana-journey-tree-to-smoothie.md +161 -0
  199. package/.mindforge/skills/concept-diagrams/examples/commercial-aircraft-structure.md +209 -0
  200. package/.mindforge/skills/concept-diagrams/examples/cpu-ooo-microarchitecture.md +236 -0
  201. package/.mindforge/skills/concept-diagrams/examples/electricity-grid-flow.md +182 -0
  202. package/.mindforge/skills/concept-diagrams/examples/feature-film-production-pipeline.md +172 -0
  203. package/.mindforge/skills/concept-diagrams/examples/hospital-emergency-department-flow.md +165 -0
  204. package/.mindforge/skills/concept-diagrams/examples/ml-benchmark-grouped-bar-chart.md +114 -0
  205. package/.mindforge/skills/concept-diagrams/examples/place-order-uml-sequence.md +325 -0
  206. package/.mindforge/skills/concept-diagrams/examples/smart-city-infrastructure.md +173 -0
  207. package/.mindforge/skills/concept-diagrams/examples/smartphone-layer-anatomy.md +154 -0
  208. package/.mindforge/skills/concept-diagrams/examples/sn2-reaction-mechanism.md +247 -0
  209. package/.mindforge/skills/concept-diagrams/examples/wind-turbine-structure.md +338 -0
  210. package/.mindforge/skills/concept-diagrams/references/dashboard-patterns.md +43 -0
  211. package/.mindforge/skills/concept-diagrams/references/infrastructure-patterns.md +144 -0
  212. package/.mindforge/skills/concept-diagrams/references/physical-shape-cookbook.md +42 -0
  213. package/.mindforge/skills/concept-diagrams/templates/template.html +174 -0
  214. package/.mindforge/skills/creative-ideation/references/full-prompt-library.md +110 -0
  215. package/.mindforge/skills/documentation/SKILL.md +1 -1
  216. package/.mindforge/skills/domain-intel/SKILL.md +7 -7
  217. package/.mindforge/skills/domain-intel/scripts/domain_intel.py +397 -0
  218. package/.mindforge/skills/duckduckgo-search/SKILL.md +17 -17
  219. package/.mindforge/skills/duckduckgo-search/scripts/duckduckgo.sh +28 -0
  220. package/.mindforge/skills/environment-management/SKILL.md +1 -1
  221. package/.mindforge/skills/github-code-review/SKILL.md +9 -9
  222. package/.mindforge/skills/github-code-review/references/review-output-template.md +74 -0
  223. package/.mindforge/skills/github-issues/templates/bug-report.md +35 -0
  224. package/.mindforge/skills/github-issues/templates/feature-request.md +31 -0
  225. package/.mindforge/skills/github-pr-workflow/SKILL.md +3 -3
  226. package/.mindforge/skills/github-pr-workflow/references/ci-troubleshooting.md +183 -0
  227. package/.mindforge/skills/github-pr-workflow/references/conventional-commits.md +71 -0
  228. package/.mindforge/skills/github-pr-workflow/templates/pr-body-bugfix.md +35 -0
  229. package/.mindforge/skills/github-pr-workflow/templates/pr-body-feature.md +33 -0
  230. package/.mindforge/skills/incident-response/SKILL.md +1 -1
  231. package/.mindforge/skills/jupyter-live-kernel/SKILL.md +3 -3
  232. package/.mindforge/skills/kanban-orchestrator/SKILL.md +10 -10
  233. package/.mindforge/skills/kanban-worker/SKILL.md +6 -6
  234. package/.mindforge/skills/meme-generation/SKILL.md +1 -1
  235. package/.mindforge/skills/meme-generation/scripts/generate_meme.py +470 -0
  236. package/.mindforge/skills/meme-generation/scripts/templates.json +97 -0
  237. package/.mindforge/skills/obsidian/SKILL.md +15 -15
  238. package/.mindforge/skills/orch-pipeline/SKILL.md +2 -2
  239. package/.mindforge/skills/osint-investigation/SKILL.md +3 -2
  240. package/.mindforge/skills/osint-investigation/references/sources/courtlistener.md +98 -0
  241. package/.mindforge/skills/osint-investigation/references/sources/gdelt.md +104 -0
  242. package/.mindforge/skills/osint-investigation/references/sources/icij-offshore.md +104 -0
  243. package/.mindforge/skills/osint-investigation/references/sources/nyc-acris.md +90 -0
  244. package/.mindforge/skills/osint-investigation/references/sources/ofac-sdn.md +92 -0
  245. package/.mindforge/skills/osint-investigation/references/sources/opencorporates.md +103 -0
  246. package/.mindforge/skills/osint-investigation/references/sources/sec-edgar.md +83 -0
  247. package/.mindforge/skills/osint-investigation/references/sources/senate-ld.md +89 -0
  248. package/.mindforge/skills/osint-investigation/references/sources/usaspending.md +97 -0
  249. package/.mindforge/skills/osint-investigation/references/sources/wayback.md +93 -0
  250. package/.mindforge/skills/osint-investigation/references/sources/wikipedia.md +107 -0
  251. package/.mindforge/skills/osint-investigation/scripts/_http.py +82 -0
  252. package/.mindforge/skills/osint-investigation/scripts/_normalize.py +67 -0
  253. package/.mindforge/skills/osint-investigation/scripts/build_findings.py +221 -0
  254. package/.mindforge/skills/osint-investigation/scripts/entity_resolution.py +228 -0
  255. package/.mindforge/skills/osint-investigation/scripts/fetch_courtlistener.py +149 -0
  256. package/.mindforge/skills/osint-investigation/scripts/fetch_gdelt.py +161 -0
  257. package/.mindforge/skills/osint-investigation/scripts/fetch_icij_offshore.py +234 -0
  258. package/.mindforge/skills/osint-investigation/scripts/fetch_nyc_acris.py +203 -0
  259. package/.mindforge/skills/osint-investigation/scripts/fetch_ofac_sdn.py +175 -0
  260. package/.mindforge/skills/osint-investigation/scripts/fetch_opencorporates.py +191 -0
  261. package/.mindforge/skills/osint-investigation/scripts/fetch_sec_edgar.py +184 -0
  262. package/.mindforge/skills/osint-investigation/scripts/fetch_senate_ld.py +146 -0
  263. package/.mindforge/skills/osint-investigation/scripts/fetch_usaspending.py +170 -0
  264. package/.mindforge/skills/osint-investigation/scripts/fetch_wayback.py +142 -0
  265. package/.mindforge/skills/osint-investigation/scripts/fetch_wikipedia.py +266 -0
  266. package/.mindforge/skills/osint-investigation/scripts/timing_analysis.py +252 -0
  267. package/.mindforge/skills/osint-investigation/templates/source-template.md +59 -0
  268. package/.mindforge/skills/oss-forensics/SKILL.md +6 -6
  269. package/.mindforge/skills/oss-forensics/references/evidence-types.md +89 -0
  270. package/.mindforge/skills/oss-forensics/references/github-archive-guide.md +184 -0
  271. package/.mindforge/skills/oss-forensics/references/investigation-templates.md +131 -0
  272. package/.mindforge/skills/oss-forensics/references/recovery-techniques.md +164 -0
  273. package/.mindforge/skills/oss-forensics/scripts/evidence-store.py +313 -0
  274. package/.mindforge/skills/oss-forensics/templates/forensic-report.md +151 -0
  275. package/.mindforge/skills/oss-forensics/templates/malicious-package-report.md +43 -0
  276. package/.mindforge/skills/pixel-art/SKILL.md +2 -2
  277. package/.mindforge/skills/pixel-art/references/palettes.md +49 -0
  278. package/.mindforge/skills/plan/SKILL.md +10 -10
  279. package/.mindforge/skills/requesting-code-review/SKILL.md +4 -6
  280. package/.mindforge/skills/research-paper-writing/SKILL.md +32 -32
  281. package/.mindforge/skills/research-paper-writing/references/autoreason-methodology.md +394 -0
  282. package/.mindforge/skills/research-paper-writing/references/checklists.md +434 -0
  283. package/.mindforge/skills/research-paper-writing/references/citation-workflow.md +563 -0
  284. package/.mindforge/skills/research-paper-writing/references/experiment-patterns.md +728 -0
  285. package/.mindforge/skills/research-paper-writing/references/human-evaluation.md +476 -0
  286. package/.mindforge/skills/research-paper-writing/references/paper-types.md +481 -0
  287. package/.mindforge/skills/research-paper-writing/references/reviewer-guidelines.md +433 -0
  288. package/.mindforge/skills/research-paper-writing/references/sources.md +191 -0
  289. package/.mindforge/skills/research-paper-writing/references/writing-guide.md +474 -0
  290. package/.mindforge/skills/research-paper-writing/templates/README.md +251 -0
  291. package/.mindforge/skills/scrapling/SKILL.md +1 -1
  292. package/.mindforge/skills/security-review/SKILL.md +1 -1
  293. package/.mindforge/skills/sherlock/SKILL.md +1 -1
  294. package/.mindforge/skills/simplify-code/SKILL.md +5 -7
  295. package/.mindforge/skills/spike/SKILL.md +11 -13
  296. package/.mindforge/skills/subagent-driven-development/SKILL.md +7 -11
  297. package/.mindforge/skills/subagent-driven-development/references/context-budget-discipline.md +53 -0
  298. package/.mindforge/skills/subagent-driven-development/references/gates-taxonomy.md +93 -0
  299. package/.mindforge/skills/systematic-debugging/SKILL.md +13 -14
  300. package/.mindforge/skills/test-driven-development/SKILL.md +7 -8
  301. package/.mindforge/skills/testing-standards/SKILL.md +1 -1
  302. package/.mindforge/skills/web-pentest/SKILL.md +2 -2
  303. package/.mindforge/skills/web-pentest/references/bypass-techniques.md +133 -0
  304. package/.mindforge/skills/web-pentest/references/exploitation-techniques.md +204 -0
  305. package/.mindforge/skills/web-pentest/references/scope-enforcement.md +110 -0
  306. package/.mindforge/skills/web-pentest/references/vuln-taxonomy.md +81 -0
  307. package/.mindforge/skills/web-pentest/templates/authorization.md +69 -0
  308. package/.mindforge/skills/web-pentest/templates/pentest-report.md +178 -0
  309. package/.mindforge/skills/writing-skills/SKILL.md +4 -3
  310. package/CHANGELOG.md +35 -0
  311. package/MINDFORGE.md +3 -3
  312. package/README.md +9 -7
  313. package/RELEASENOTES.md +65 -2
  314. package/SECURITY.md +5 -3
  315. package/bin/autonomous/auto-runner.js +5 -2
  316. package/bin/engine/skill-loader.js +4 -0
  317. package/bin/governance/rbac.js +4 -0
  318. package/bin/memory/eis-client.js +1 -0
  319. package/bin/mindforge-cli.js +5 -0
  320. package/bin/models/bedrock-provider.js +192 -0
  321. package/bin/models/model-client.js +19 -0
  322. package/bin/spawn-agent.js +9 -2
  323. package/docs/commands-reference.md +68 -6
  324. package/docs/faq.md +38 -1
  325. package/docs/getting-started.md +27 -12
  326. package/docs/sdk-reference.md +36 -3
  327. package/docs/troubleshooting.md +59 -1
  328. package/docs/user-guide.md +7 -4
  329. package/package.json +3 -3
  330. package/.claude/commands/mindforge/skill-tdd.md +0 -53
  331. package/.claude/commands/mindforge/skills-index.md +0 -118
  332. package/.claude/commands/mindforge/systematic-debug.md +0 -60
  333. package/.claude/commands/mindforge/wf-accessibility-audit.md +0 -31
  334. package/.claude/commands/mindforge/wf-ai-model-eval.md +0 -31
  335. package/.claude/commands/mindforge/wf-api-migration.md +0 -31
  336. package/.claude/commands/mindforge/wf-architecture-modernization.md +0 -32
  337. package/.claude/commands/mindforge/wf-catalog.md +0 -59
  338. package/.claude/commands/mindforge/wf-code-audit.md +0 -31
  339. package/.claude/commands/mindforge/wf-code-explainer.md +0 -31
  340. package/.claude/commands/mindforge/wf-competitive-analysis.md +0 -31
  341. package/.claude/commands/mindforge/wf-competitive-teardown.md +0 -31
  342. package/.claude/commands/mindforge/wf-cost-analysis.md +0 -31
  343. package/.claude/commands/mindforge/wf-data-pipeline-validate.md +0 -31
  344. package/.claude/commands/mindforge/wf-database-migration.md +0 -31
  345. package/.claude/commands/mindforge/wf-debug-detective.md +0 -32
  346. package/.claude/commands/mindforge/wf-design-system-audit.md +0 -31
  347. package/.claude/commands/mindforge/wf-documentation-gen.md +0 -31
  348. package/.claude/commands/mindforge/wf-feature-planner.md +0 -31
  349. package/.claude/commands/mindforge/wf-incident-response.md +0 -31
  350. package/.claude/commands/mindforge/wf-multi-repo-sync.md +0 -31
  351. package/.claude/commands/mindforge/wf-mutation-testing.md +0 -31
  352. package/.claude/commands/mindforge/wf-onboard-codebase.md +0 -31
  353. package/.claude/commands/mindforge/wf-perf-optimize.md +0 -31
  354. package/.claude/commands/mindforge/wf-pr-review.md +0 -31
  355. package/.claude/commands/mindforge/wf-refactor-plan.md +0 -31
  356. package/.claude/commands/mindforge/wf-release-prep.md +0 -31
  357. package/.claude/commands/mindforge/wf-security-threat-model.md +0 -31
  358. package/.claude/commands/mindforge/wf-tdd-sprint.md +0 -31
  359. package/.claude/commands/mindforge/wf-tech-evaluation.md +0 -31
  360. package/.claude/commands/mindforge/wf-ux-heuristic-audit.md +0 -31
  361. package/.claude/commands/mindforge/wf-writer-reviewer.md +0 -30
@@ -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())