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