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,170 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Fetch federal contracts/awards from USAspending.gov API v2.
|
|
3
|
+
|
|
4
|
+
No auth required. POST to /api/v2/search/spending_by_award/ with filters.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import csv
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
import time
|
|
13
|
+
import urllib.request
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
ENDPOINT = "https://api.usaspending.gov/api/v2/search/spending_by_award/"
|
|
17
|
+
COLUMNS = [
|
|
18
|
+
"award_id",
|
|
19
|
+
"recipient_name",
|
|
20
|
+
"recipient_uei",
|
|
21
|
+
"recipient_duns",
|
|
22
|
+
"recipient_parent_name",
|
|
23
|
+
"recipient_state",
|
|
24
|
+
"awarding_agency",
|
|
25
|
+
"awarding_sub_agency",
|
|
26
|
+
"award_type",
|
|
27
|
+
"award_amount",
|
|
28
|
+
"award_date",
|
|
29
|
+
"period_of_performance_start",
|
|
30
|
+
"period_of_performance_end",
|
|
31
|
+
"naics_code",
|
|
32
|
+
"psc_code",
|
|
33
|
+
"competition_extent",
|
|
34
|
+
"description",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
# USAspending result column "code" → human label mapping for output.
|
|
38
|
+
_FIELDS = [
|
|
39
|
+
"Award ID",
|
|
40
|
+
"Recipient Name",
|
|
41
|
+
"Recipient UEI",
|
|
42
|
+
"Recipient DUNS Number",
|
|
43
|
+
"Recipient Parent Name",
|
|
44
|
+
"Recipient State Code",
|
|
45
|
+
"Awarding Agency",
|
|
46
|
+
"Awarding Sub Agency",
|
|
47
|
+
"Award Type",
|
|
48
|
+
"Award Amount",
|
|
49
|
+
"Start Date",
|
|
50
|
+
"End Date",
|
|
51
|
+
"NAICS Code",
|
|
52
|
+
"PSC Code",
|
|
53
|
+
"Type of Set Aside",
|
|
54
|
+
"Description",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _post(body: dict) -> dict:
|
|
59
|
+
req = urllib.request.Request(
|
|
60
|
+
ENDPOINT,
|
|
61
|
+
data=json.dumps(body).encode("utf-8"),
|
|
62
|
+
headers={"Content-Type": "application/json", "User-Agent": "hermes-agent osint-investigation"},
|
|
63
|
+
method="POST",
|
|
64
|
+
)
|
|
65
|
+
with urllib.request.urlopen(req, timeout=60) as resp:
|
|
66
|
+
return json.loads(resp.read().decode("utf-8"))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def fetch(
|
|
70
|
+
recipient: str | None,
|
|
71
|
+
agency: str | None,
|
|
72
|
+
fy: int,
|
|
73
|
+
sole_source_only: bool,
|
|
74
|
+
out_path: str,
|
|
75
|
+
page_size: int = 100,
|
|
76
|
+
max_pages: int = 20,
|
|
77
|
+
) -> int:
|
|
78
|
+
filters: dict = {
|
|
79
|
+
"time_period": [{"start_date": f"{fy - 1}-10-01", "end_date": f"{fy}-09-30"}],
|
|
80
|
+
# Contracts only by default; adjust award_type_codes for grants/loans.
|
|
81
|
+
"award_type_codes": ["A", "B", "C", "D"],
|
|
82
|
+
}
|
|
83
|
+
if recipient:
|
|
84
|
+
filters["recipient_search_text"] = [recipient]
|
|
85
|
+
if agency:
|
|
86
|
+
filters["agencies"] = [{"type": "awarding", "tier": "toptier", "name": agency}]
|
|
87
|
+
|
|
88
|
+
rows: list[dict[str, str]] = []
|
|
89
|
+
page = 1
|
|
90
|
+
while page <= max_pages:
|
|
91
|
+
body = {
|
|
92
|
+
"filters": filters,
|
|
93
|
+
"fields": _FIELDS,
|
|
94
|
+
"page": page,
|
|
95
|
+
"limit": page_size,
|
|
96
|
+
"sort": "Award Amount",
|
|
97
|
+
"order": "desc",
|
|
98
|
+
}
|
|
99
|
+
try:
|
|
100
|
+
payload = _post(body)
|
|
101
|
+
except Exception as e: # noqa: BLE001
|
|
102
|
+
print(f"USAspending error on page {page}: {e}", file=sys.stderr)
|
|
103
|
+
break
|
|
104
|
+
results = payload.get("results", [])
|
|
105
|
+
if not results:
|
|
106
|
+
break
|
|
107
|
+
for r in results:
|
|
108
|
+
set_aside = r.get("Type of Set Aside", "") or ""
|
|
109
|
+
if sole_source_only and "sole" not in set_aside.lower():
|
|
110
|
+
continue
|
|
111
|
+
rows.append(
|
|
112
|
+
{
|
|
113
|
+
"award_id": r.get("Award ID", "") or "",
|
|
114
|
+
"recipient_name": r.get("Recipient Name", "") or "",
|
|
115
|
+
"recipient_uei": r.get("Recipient UEI", "") or "",
|
|
116
|
+
"recipient_duns": r.get("Recipient DUNS Number", "") or "",
|
|
117
|
+
"recipient_parent_name": r.get("Recipient Parent Name", "") or "",
|
|
118
|
+
"recipient_state": r.get("Recipient State Code", "") or "",
|
|
119
|
+
"awarding_agency": r.get("Awarding Agency", "") or "",
|
|
120
|
+
"awarding_sub_agency": r.get("Awarding Sub Agency", "") or "",
|
|
121
|
+
"award_type": r.get("Award Type", "") or "",
|
|
122
|
+
"award_amount": str(r.get("Award Amount", "") or ""),
|
|
123
|
+
"award_date": r.get("Start Date", "") or "",
|
|
124
|
+
"period_of_performance_start": r.get("Start Date", "") or "",
|
|
125
|
+
"period_of_performance_end": r.get("End Date", "") or "",
|
|
126
|
+
"naics_code": str(r.get("NAICS Code", "") or ""),
|
|
127
|
+
"psc_code": str(r.get("PSC Code", "") or ""),
|
|
128
|
+
"competition_extent": set_aside,
|
|
129
|
+
"description": r.get("Description", "") or "",
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
meta = payload.get("page_metadata", {})
|
|
133
|
+
if not meta.get("hasNext"):
|
|
134
|
+
break
|
|
135
|
+
page += 1
|
|
136
|
+
time.sleep(0.5)
|
|
137
|
+
|
|
138
|
+
Path(out_path).parent.mkdir(parents=True, exist_ok=True)
|
|
139
|
+
with open(out_path, "w", newline="", encoding="utf-8") as fh:
|
|
140
|
+
w = csv.DictWriter(fh, fieldnames=COLUMNS)
|
|
141
|
+
w.writeheader()
|
|
142
|
+
w.writerows(rows)
|
|
143
|
+
return len(rows)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def main() -> int:
|
|
147
|
+
p = argparse.ArgumentParser(description=__doc__)
|
|
148
|
+
p.add_argument("--recipient", help="Recipient name search")
|
|
149
|
+
p.add_argument("--agency", help="Awarding agency (top-tier)")
|
|
150
|
+
p.add_argument("--fy", type=int, default=2024, help="Federal fiscal year")
|
|
151
|
+
p.add_argument("--sole-source-only", action="store_true")
|
|
152
|
+
p.add_argument("--max-pages", type=int, default=20)
|
|
153
|
+
p.add_argument("--out", required=True)
|
|
154
|
+
a = p.parse_args()
|
|
155
|
+
if not (a.recipient or a.agency):
|
|
156
|
+
p.error("must supply at least one of --recipient / --agency")
|
|
157
|
+
n = fetch(
|
|
158
|
+
recipient=a.recipient,
|
|
159
|
+
agency=a.agency,
|
|
160
|
+
fy=a.fy,
|
|
161
|
+
sole_source_only=a.sole_source_only,
|
|
162
|
+
out_path=a.out,
|
|
163
|
+
max_pages=a.max_pages,
|
|
164
|
+
)
|
|
165
|
+
print(f"Wrote {n} USAspending rows to {a.out}")
|
|
166
|
+
return 0
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
if __name__ == "__main__":
|
|
170
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Search the Internet Archive Wayback Machine via the CDX server.
|
|
3
|
+
|
|
4
|
+
The CDX API indexes ~900B+ archived web pages. Anonymous read access,
|
|
5
|
+
no auth required. Useful for finding deleted / changed pages by URL,
|
|
6
|
+
domain, or substring match.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import csv
|
|
12
|
+
import sys
|
|
13
|
+
import urllib.parse
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
17
|
+
from _http import get_json # noqa: E402
|
|
18
|
+
|
|
19
|
+
BASE = "https://web.archive.org/cdx/search/cdx"
|
|
20
|
+
|
|
21
|
+
COLUMNS = [
|
|
22
|
+
"url",
|
|
23
|
+
"timestamp",
|
|
24
|
+
"wayback_url",
|
|
25
|
+
"mimetype",
|
|
26
|
+
"status",
|
|
27
|
+
"digest",
|
|
28
|
+
"length",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def fetch(
|
|
33
|
+
url_or_host: str,
|
|
34
|
+
match_type: str,
|
|
35
|
+
from_date: str | None,
|
|
36
|
+
to_date: str | None,
|
|
37
|
+
status: str | None,
|
|
38
|
+
mime: str | None,
|
|
39
|
+
collapse: str | None,
|
|
40
|
+
limit: int,
|
|
41
|
+
out_path: str,
|
|
42
|
+
) -> int:
|
|
43
|
+
params: dict[str, str] = {
|
|
44
|
+
"url": url_or_host,
|
|
45
|
+
"matchType": match_type,
|
|
46
|
+
"output": "json",
|
|
47
|
+
"limit": str(limit),
|
|
48
|
+
}
|
|
49
|
+
if from_date:
|
|
50
|
+
params["from"] = from_date.replace("-", "")
|
|
51
|
+
if to_date:
|
|
52
|
+
params["to"] = to_date.replace("-", "")
|
|
53
|
+
if status:
|
|
54
|
+
params["filter"] = f"statuscode:{status}"
|
|
55
|
+
if mime:
|
|
56
|
+
params.setdefault("filter", "")
|
|
57
|
+
# Multiple filters: CDX accepts repeated filter params via urlencode list
|
|
58
|
+
params["filter"] = f"mimetype:{mime}"
|
|
59
|
+
if collapse:
|
|
60
|
+
params["collapse"] = collapse
|
|
61
|
+
|
|
62
|
+
url = f"{BASE}?{urllib.parse.urlencode(params)}"
|
|
63
|
+
try:
|
|
64
|
+
payload = get_json(url)
|
|
65
|
+
except Exception as e: # noqa: BLE001
|
|
66
|
+
print(f"Wayback CDX error: {e}", file=sys.stderr)
|
|
67
|
+
payload = []
|
|
68
|
+
|
|
69
|
+
rows: list[dict[str, str]] = []
|
|
70
|
+
if isinstance(payload, list) and len(payload) > 1:
|
|
71
|
+
header = payload[0]
|
|
72
|
+
idx = {h: i for i, h in enumerate(header)}
|
|
73
|
+
for entry in payload[1:]:
|
|
74
|
+
ts = entry[idx["timestamp"]] if "timestamp" in idx else ""
|
|
75
|
+
orig = entry[idx["original"]] if "original" in idx else ""
|
|
76
|
+
rows.append(
|
|
77
|
+
{
|
|
78
|
+
"url": orig,
|
|
79
|
+
"timestamp": ts,
|
|
80
|
+
"wayback_url": f"https://web.archive.org/web/{ts}/{orig}" if ts and orig else "",
|
|
81
|
+
"mimetype": entry[idx["mimetype"]] if "mimetype" in idx else "",
|
|
82
|
+
"status": entry[idx["statuscode"]] if "statuscode" in idx else "",
|
|
83
|
+
"digest": entry[idx["digest"]] if "digest" in idx else "",
|
|
84
|
+
"length": entry[idx["length"]] if "length" in idx else "",
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
Path(out_path).parent.mkdir(parents=True, exist_ok=True)
|
|
89
|
+
with open(out_path, "w", newline="", encoding="utf-8") as fh:
|
|
90
|
+
w = csv.DictWriter(fh, fieldnames=COLUMNS)
|
|
91
|
+
w.writeheader()
|
|
92
|
+
w.writerows(rows)
|
|
93
|
+
if not rows:
|
|
94
|
+
print(
|
|
95
|
+
f"Wayback Machine: 0 captures for {url_or_host!r} matchType={match_type}.",
|
|
96
|
+
file=sys.stderr,
|
|
97
|
+
)
|
|
98
|
+
return len(rows)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def main() -> int:
|
|
102
|
+
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
103
|
+
p.add_argument("--url", required=True, help="URL or host to look up in the archive")
|
|
104
|
+
p.add_argument(
|
|
105
|
+
"--match",
|
|
106
|
+
default="exact",
|
|
107
|
+
choices=["exact", "prefix", "host", "domain"],
|
|
108
|
+
help=(
|
|
109
|
+
"exact: this URL only. "
|
|
110
|
+
"prefix: this URL's path-prefix. "
|
|
111
|
+
"host: any URL on this host. "
|
|
112
|
+
"domain: any URL on this domain or subdomains."
|
|
113
|
+
),
|
|
114
|
+
)
|
|
115
|
+
p.add_argument("--from-date", help="Earliest capture YYYY-MM-DD")
|
|
116
|
+
p.add_argument("--to-date", help="Latest capture YYYY-MM-DD")
|
|
117
|
+
p.add_argument("--status", help="HTTP status filter (e.g. 200)")
|
|
118
|
+
p.add_argument("--mime", help="MIME type filter (e.g. text/html)")
|
|
119
|
+
p.add_argument(
|
|
120
|
+
"--collapse",
|
|
121
|
+
help="Collapse adjacent identical entries (e.g. 'digest' for unique-content captures)",
|
|
122
|
+
)
|
|
123
|
+
p.add_argument("--limit", type=int, default=200)
|
|
124
|
+
p.add_argument("--out", required=True)
|
|
125
|
+
a = p.parse_args()
|
|
126
|
+
n = fetch(
|
|
127
|
+
url_or_host=a.url,
|
|
128
|
+
match_type=a.match,
|
|
129
|
+
from_date=a.from_date,
|
|
130
|
+
to_date=a.to_date,
|
|
131
|
+
status=a.status,
|
|
132
|
+
mime=a.mime,
|
|
133
|
+
collapse=a.collapse,
|
|
134
|
+
limit=a.limit,
|
|
135
|
+
out_path=a.out,
|
|
136
|
+
)
|
|
137
|
+
print(f"Wrote {n} Wayback capture rows to {a.out}")
|
|
138
|
+
return 0
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
if __name__ == "__main__":
|
|
142
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Search Wikipedia + Wikidata for an entity (person, company, place, concept).
|
|
3
|
+
|
|
4
|
+
Two free APIs:
|
|
5
|
+
- Wikipedia OpenSearch + REST summary endpoint for narrative bio
|
|
6
|
+
- Wikidata SPARQL endpoint for structured facts (birth, employer, awards, etc.)
|
|
7
|
+
|
|
8
|
+
Both are anonymous-access. Useful for resolving who-is-this-entity questions
|
|
9
|
+
and surfacing cross-references that other sources can join against.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import csv
|
|
15
|
+
import re
|
|
16
|
+
import sys
|
|
17
|
+
import urllib.parse
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
21
|
+
from _http import get_json # noqa: E402
|
|
22
|
+
|
|
23
|
+
WP_OPENSEARCH = "https://en.wikipedia.org/w/api.php"
|
|
24
|
+
WP_SUMMARY = "https://en.wikipedia.org/api/rest_v1/page/summary/"
|
|
25
|
+
WD_ACTION = "https://www.wikidata.org/w/api.php"
|
|
26
|
+
|
|
27
|
+
COLUMNS = [
|
|
28
|
+
"source",
|
|
29
|
+
"label",
|
|
30
|
+
"description",
|
|
31
|
+
"qid",
|
|
32
|
+
"wikipedia_title",
|
|
33
|
+
"wikipedia_url",
|
|
34
|
+
"wikidata_url",
|
|
35
|
+
"instance_of",
|
|
36
|
+
"country",
|
|
37
|
+
"occupation",
|
|
38
|
+
"employer",
|
|
39
|
+
"date_of_birth",
|
|
40
|
+
"place_of_birth",
|
|
41
|
+
"summary",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _wp_search(query: str, limit: int) -> list[dict]:
|
|
46
|
+
params = {
|
|
47
|
+
"action": "opensearch",
|
|
48
|
+
"search": query,
|
|
49
|
+
"limit": str(min(limit, 20)),
|
|
50
|
+
"format": "json",
|
|
51
|
+
}
|
|
52
|
+
url = f"{WP_OPENSEARCH}?{urllib.parse.urlencode(params)}"
|
|
53
|
+
data = get_json(url)
|
|
54
|
+
if not isinstance(data, list) or len(data) < 4:
|
|
55
|
+
return []
|
|
56
|
+
titles, descs, urls = data[1], data[2], data[3]
|
|
57
|
+
out = []
|
|
58
|
+
for i, title in enumerate(titles):
|
|
59
|
+
out.append(
|
|
60
|
+
{
|
|
61
|
+
"title": title,
|
|
62
|
+
"description": descs[i] if i < len(descs) else "",
|
|
63
|
+
"url": urls[i] if i < len(urls) else "",
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
return out
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _wp_summary(title: str) -> dict:
|
|
70
|
+
"""Pull the REST summary for a title — short bio, image, type."""
|
|
71
|
+
url = f"{WP_SUMMARY}{urllib.parse.quote(title.replace(' ', '_'))}"
|
|
72
|
+
try:
|
|
73
|
+
return get_json(url) # type: ignore[return-value]
|
|
74
|
+
except Exception as e: # noqa: BLE001
|
|
75
|
+
print(f"Wikipedia summary lookup for {title!r} failed: {e}", file=sys.stderr)
|
|
76
|
+
return {}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _wd_lookup_by_qid(qid: str) -> dict:
|
|
80
|
+
"""Pull common facts for a QID via Wikidata's Action API (no SPARQL).
|
|
81
|
+
|
|
82
|
+
The Action API is far more lenient on rate-limits than the SPARQL Query
|
|
83
|
+
Service. We get claims as QIDs and then resolve labels in one batch call.
|
|
84
|
+
"""
|
|
85
|
+
# Properties of interest. The Action API returns claims as QIDs or
|
|
86
|
+
# typed literals, so the slot mapping is local-only.
|
|
87
|
+
interesting = {
|
|
88
|
+
"P31": "instance_of",
|
|
89
|
+
"P17": "country", # for orgs / places
|
|
90
|
+
"P27": "country", # for individuals (country of citizenship)
|
|
91
|
+
"P106": "occupation",
|
|
92
|
+
"P108": "employer",
|
|
93
|
+
"P569": "date_of_birth",
|
|
94
|
+
"P19": "place_of_birth",
|
|
95
|
+
}
|
|
96
|
+
params = {
|
|
97
|
+
"action": "wbgetentities",
|
|
98
|
+
"ids": qid,
|
|
99
|
+
"props": "claims",
|
|
100
|
+
"format": "json",
|
|
101
|
+
}
|
|
102
|
+
url = f"{WD_ACTION}?{urllib.parse.urlencode(params)}"
|
|
103
|
+
try:
|
|
104
|
+
data = get_json(url)
|
|
105
|
+
except Exception as e: # noqa: BLE001
|
|
106
|
+
print(f"Wikidata wbgetentities for {qid} failed: {e}", file=sys.stderr)
|
|
107
|
+
return {}
|
|
108
|
+
if not isinstance(data, dict):
|
|
109
|
+
return {}
|
|
110
|
+
claims = (data.get("entities", {}).get(qid, {}) or {}).get("claims", {}) or {}
|
|
111
|
+
|
|
112
|
+
# Collect raw values (QIDs or literals) and remember which slot each
|
|
113
|
+
# came from. Date literals come back as ISO strings; QIDs need a label
|
|
114
|
+
# resolution pass.
|
|
115
|
+
qid_to_slots: dict[str, list[str]] = {}
|
|
116
|
+
facts: dict[str, list[str]] = {}
|
|
117
|
+
for prop_id, slot in interesting.items():
|
|
118
|
+
for claim in claims.get(prop_id, []) or []:
|
|
119
|
+
v = (claim.get("mainsnak", {}) or {}).get("datavalue", {}) or {}
|
|
120
|
+
vtype = v.get("type")
|
|
121
|
+
value = v.get("value")
|
|
122
|
+
if vtype == "wikibase-entityid" and isinstance(value, dict):
|
|
123
|
+
vqid = value.get("id", "")
|
|
124
|
+
if vqid:
|
|
125
|
+
qid_to_slots.setdefault(vqid, [])
|
|
126
|
+
if slot not in qid_to_slots[vqid]:
|
|
127
|
+
qid_to_slots[vqid].append(slot)
|
|
128
|
+
elif vtype == "time" and isinstance(value, dict):
|
|
129
|
+
raw = value.get("time", "") or ""
|
|
130
|
+
# +1955-10-28T00:00:00Z → 1955-10-28
|
|
131
|
+
m = re.search(r"[+-]?(\d{4})-(\d{2})-(\d{2})", raw)
|
|
132
|
+
if m:
|
|
133
|
+
facts.setdefault(slot, []).append(
|
|
134
|
+
f"{m.group(1)}-{m.group(2)}-{m.group(3)}"
|
|
135
|
+
)
|
|
136
|
+
elif vtype == "string":
|
|
137
|
+
facts.setdefault(slot, []).append(str(value))
|
|
138
|
+
|
|
139
|
+
# Resolve labels for all referenced QIDs in one batch (up to 50 at a time).
|
|
140
|
+
qids = list(qid_to_slots)
|
|
141
|
+
for i in range(0, len(qids), 50):
|
|
142
|
+
batch = qids[i : i + 50]
|
|
143
|
+
params = {
|
|
144
|
+
"action": "wbgetentities",
|
|
145
|
+
"ids": "|".join(batch),
|
|
146
|
+
"props": "labels",
|
|
147
|
+
"languages": "en",
|
|
148
|
+
"format": "json",
|
|
149
|
+
}
|
|
150
|
+
url = f"{WD_ACTION}?{urllib.parse.urlencode(params)}"
|
|
151
|
+
try:
|
|
152
|
+
data = get_json(url)
|
|
153
|
+
except Exception as e: # noqa: BLE001
|
|
154
|
+
print(f"Wikidata label batch failed: {e}", file=sys.stderr)
|
|
155
|
+
continue
|
|
156
|
+
if not isinstance(data, dict):
|
|
157
|
+
continue
|
|
158
|
+
ents = data.get("entities", {}) or {}
|
|
159
|
+
for vqid, ent in ents.items():
|
|
160
|
+
label = (ent.get("labels", {}).get("en", {}) or {}).get("value", "") or vqid
|
|
161
|
+
for slot in qid_to_slots.get(vqid, []):
|
|
162
|
+
facts.setdefault(slot, []).append(label)
|
|
163
|
+
|
|
164
|
+
# Deduplicate per slot, preserving order.
|
|
165
|
+
deduped: dict[str, list[str]] = {}
|
|
166
|
+
for slot, vals in facts.items():
|
|
167
|
+
seen = set()
|
|
168
|
+
out = []
|
|
169
|
+
for v in vals:
|
|
170
|
+
if v in seen:
|
|
171
|
+
continue
|
|
172
|
+
seen.add(v)
|
|
173
|
+
out.append(v)
|
|
174
|
+
deduped[slot] = out
|
|
175
|
+
return deduped
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _wd_qid_for_title(title: str) -> str:
|
|
179
|
+
"""Get the Wikidata QID associated with a Wikipedia article title."""
|
|
180
|
+
params = {
|
|
181
|
+
"action": "query",
|
|
182
|
+
"format": "json",
|
|
183
|
+
"prop": "pageprops",
|
|
184
|
+
"ppprop": "wikibase_item",
|
|
185
|
+
"titles": title,
|
|
186
|
+
"redirects": 1,
|
|
187
|
+
}
|
|
188
|
+
url = f"{WP_OPENSEARCH}?{urllib.parse.urlencode(params)}"
|
|
189
|
+
try:
|
|
190
|
+
data = get_json(url)
|
|
191
|
+
except Exception: # noqa: BLE001
|
|
192
|
+
return ""
|
|
193
|
+
if not isinstance(data, dict):
|
|
194
|
+
return ""
|
|
195
|
+
pages = data.get("query", {}).get("pages", {}) or {}
|
|
196
|
+
for page in pages.values():
|
|
197
|
+
qid = (page.get("pageprops") or {}).get("wikibase_item", "")
|
|
198
|
+
if qid:
|
|
199
|
+
return qid
|
|
200
|
+
return ""
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def fetch(query: str, limit: int, no_wikidata: bool, out_path: str) -> int:
|
|
204
|
+
hits = _wp_search(query, limit)
|
|
205
|
+
rows: list[dict[str, str]] = []
|
|
206
|
+
for hit in hits[:limit]:
|
|
207
|
+
title = hit.get("title", "")
|
|
208
|
+
if not title:
|
|
209
|
+
continue
|
|
210
|
+
summary = _wp_summary(title)
|
|
211
|
+
qid = _wd_qid_for_title(title) if not no_wikidata else ""
|
|
212
|
+
facts: dict = {}
|
|
213
|
+
if qid:
|
|
214
|
+
facts = _wd_lookup_by_qid(qid)
|
|
215
|
+
rows.append(
|
|
216
|
+
{
|
|
217
|
+
"source": "wikipedia+wikidata" if qid else "wikipedia",
|
|
218
|
+
"label": title,
|
|
219
|
+
"description": (summary.get("description") or hit.get("description") or "").strip(),
|
|
220
|
+
"qid": qid,
|
|
221
|
+
"wikipedia_title": title,
|
|
222
|
+
"wikipedia_url": hit.get("url", ""),
|
|
223
|
+
"wikidata_url": f"https://www.wikidata.org/wiki/{qid}" if qid else "",
|
|
224
|
+
"instance_of": "; ".join(facts.get("instance_of", [])),
|
|
225
|
+
"country": "; ".join(facts.get("country", [])),
|
|
226
|
+
"occupation": "; ".join(facts.get("occupation", [])),
|
|
227
|
+
"employer": "; ".join(facts.get("employer", [])),
|
|
228
|
+
"date_of_birth": "; ".join(facts.get("date_of_birth", []))[:10] if facts.get("date_of_birth") else "",
|
|
229
|
+
"place_of_birth": "; ".join(facts.get("place_of_birth", [])),
|
|
230
|
+
"summary": (summary.get("extract") or "").replace("\n", " ")[:1000],
|
|
231
|
+
}
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
Path(out_path).parent.mkdir(parents=True, exist_ok=True)
|
|
235
|
+
with open(out_path, "w", newline="", encoding="utf-8") as fh:
|
|
236
|
+
w = csv.DictWriter(fh, fieldnames=COLUMNS)
|
|
237
|
+
w.writeheader()
|
|
238
|
+
w.writerows(rows)
|
|
239
|
+
if not rows:
|
|
240
|
+
print(
|
|
241
|
+
f"Wikipedia: 0 articles for query={query!r}. "
|
|
242
|
+
"Private individuals not notable enough for a Wikipedia article "
|
|
243
|
+
"won't appear here (the bar is real).",
|
|
244
|
+
file=sys.stderr,
|
|
245
|
+
)
|
|
246
|
+
return len(rows)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def main() -> int:
|
|
250
|
+
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
251
|
+
p.add_argument("--query", required=True, help="Entity name (person, company, place, concept)")
|
|
252
|
+
p.add_argument("--limit", type=int, default=5)
|
|
253
|
+
p.add_argument(
|
|
254
|
+
"--no-wikidata",
|
|
255
|
+
action="store_true",
|
|
256
|
+
help="Skip the Wikidata SPARQL enrichment (faster, less detail)",
|
|
257
|
+
)
|
|
258
|
+
p.add_argument("--out", required=True)
|
|
259
|
+
a = p.parse_args()
|
|
260
|
+
n = fetch(query=a.query, limit=a.limit, no_wikidata=a.no_wikidata, out_path=a.out)
|
|
261
|
+
print(f"Wrote {n} Wikipedia/Wikidata rows to {a.out}")
|
|
262
|
+
return 0
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
if __name__ == "__main__":
|
|
266
|
+
raise SystemExit(main())
|