bmad-method-quarkus 1.0.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/.claude-plugin/marketplace.json +53 -0
- package/AGENTS.md +12 -0
- package/LICENSE +30 -0
- package/README.md +94 -0
- package/README_CN.md +108 -0
- package/README_VN.md +109 -0
- package/bmad-modules.yaml +133 -0
- package/package.json +123 -0
- package/removals.txt +87 -0
- package/src/bmm-skills/agents/bmad-agent-analyst/SKILL.md +76 -0
- package/src/bmm-skills/agents/bmad-agent-analyst/customize.toml +105 -0
- package/src/bmm-skills/agents/bmad-agent-architect/SKILL.md +76 -0
- package/src/bmm-skills/agents/bmad-agent-architect/customize.toml +65 -0
- package/src/bmm-skills/agents/bmad-agent-dev/SKILL.md +76 -0
- package/src/bmm-skills/agents/bmad-agent-dev/customize.toml +83 -0
- package/src/bmm-skills/agents/bmad-agent-pm/SKILL.md +76 -0
- package/src/bmm-skills/agents/bmad-agent-pm/customize.toml +75 -0
- package/src/bmm-skills/agents/bmad-agent-ux-designer/SKILL.md +76 -0
- package/src/bmm-skills/agents/bmad-agent-ux-designer/customize.toml +60 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/.memlog.md +12 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/.memlog.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/SKILL.md +86 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/customize.toml +37 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/customize.toml:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/references/enrich-stories.md +19 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/references/enrich-stories.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/references/prompt-quality-canon.md +79 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/references/prompt-quality-canon.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/SKILL.md +80 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/assets/module-help.csv +9 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/assets/module-help.csv:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/assets/module.yaml +16 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/assets/module.yaml:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/scripts/cleanup-legacy.py +287 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/scripts/cleanup-legacy.py:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/scripts/merge-config.py +441 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/scripts/merge-config.py:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/scripts/merge-help-csv.py +246 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/scripts/merge-help-csv.py:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-error-handling-i18n/SKILL.md +181 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-error-handling-i18n/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-grpc-services/SKILL.md +141 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-grpc-services/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-hexagonal-core/SKILL.md +392 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-hexagonal-core/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-kafka-messaging/SKILL.md +148 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-kafka-messaging/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-observability-otel/SKILL.md +180 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-observability-otel/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-openapi-tmforum/SKILL.md +128 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-openapi-tmforum/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-sql-jdbc-agroal/SKILL.md +245 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-sql-jdbc-agroal/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/module-help.csv +19 -0
- package/src/bmm-skills/module.yaml +92 -0
- package/src/bmm-skills/plan/bmad-architecture/SKILL.md +85 -0
- package/src/bmm-skills/plan/bmad-architecture/assets/spine-template.md +79 -0
- package/src/bmm-skills/plan/bmad-architecture/customize.toml +102 -0
- package/src/bmm-skills/plan/bmad-architecture/references/headless.md +26 -0
- package/src/bmm-skills/plan/bmad-architecture/references/reviewer-gate.md +13 -0
- package/src/bmm-skills/plan/bmad-architecture/scripts/lint_spine.py +257 -0
- package/src/bmm-skills/plan/bmad-architecture/scripts/tests/test_lint_spine.py +270 -0
- package/src/bmm-skills/plan/bmad-create-epics-and-stories/SKILL.md +93 -0
- package/src/bmm-skills/plan/bmad-create-epics-and-stories/customize.toml +41 -0
- package/src/bmm-skills/plan/bmad-create-epics-and-stories/steps/step-01-validate-prerequisites.md +263 -0
- package/src/bmm-skills/plan/bmad-create-epics-and-stories/steps/step-02-design-epics.md +242 -0
- package/src/bmm-skills/plan/bmad-create-epics-and-stories/steps/step-03-create-stories.md +255 -0
- package/src/bmm-skills/plan/bmad-create-epics-and-stories/steps/step-04-final-validation.md +143 -0
- package/src/bmm-skills/plan/bmad-create-epics-and-stories/templates/epics-template.md +61 -0
- package/src/bmm-skills/plan/bmad-generate-project-context/SKILL.md +10 -0
- package/src/bmm-skills/plan/bmad-prd/SKILL.md +94 -0
- package/src/bmm-skills/plan/bmad-prd/assets/headless-schemas.md +76 -0
- package/src/bmm-skills/plan/bmad-prd/assets/prd-template.md +165 -0
- package/src/bmm-skills/plan/bmad-prd/assets/prd-validation-checklist.md +135 -0
- package/src/bmm-skills/plan/bmad-prd/assets/validation-report-template.html +325 -0
- package/src/bmm-skills/plan/bmad-prd/customize.toml +149 -0
- package/src/bmm-skills/plan/bmad-prd/references/headless.md +39 -0
- package/src/bmm-skills/plan/bmad-prd/references/validate.md +97 -0
- package/src/bmm-skills/plan/bmad-prfaq/SKILL.md +135 -0
- package/src/bmm-skills/plan/bmad-prfaq/agents/artifact-analyzer.md +60 -0
- package/src/bmm-skills/plan/bmad-prfaq/agents/web-researcher.md +49 -0
- package/src/bmm-skills/plan/bmad-prfaq/assets/prfaq-template.md +62 -0
- package/src/bmm-skills/plan/bmad-prfaq/bmad-manifest.json +16 -0
- package/src/bmm-skills/plan/bmad-prfaq/customize.toml +41 -0
- package/src/bmm-skills/plan/bmad-prfaq/references/customer-faq.md +55 -0
- package/src/bmm-skills/plan/bmad-prfaq/references/internal-faq.md +51 -0
- package/src/bmm-skills/plan/bmad-prfaq/references/press-release.md +60 -0
- package/src/bmm-skills/plan/bmad-prfaq/references/verdict.md +83 -0
- package/src/bmm-skills/plan/bmad-product-brief/SKILL.md +91 -0
- package/src/bmm-skills/plan/bmad-product-brief/assets/brief-template.md +41 -0
- package/src/bmm-skills/plan/bmad-product-brief/customize.toml +101 -0
- package/src/bmm-skills/plan/bmad-project-context/SKILL.md +110 -0
- package/src/bmm-skills/plan/bmad-project-context/customize.toml +24 -0
- package/src/bmm-skills/plan/bmad-project-context/references/best-practices.md +65 -0
- package/src/bmm-skills/plan/bmad-project-context/references/template.md +55 -0
- package/src/bmm-skills/plan/bmad-spec/SKILL.md +160 -0
- package/src/bmm-skills/plan/bmad-spec/assets/headless-schemas.md +33 -0
- package/src/bmm-skills/plan/bmad-spec/assets/spec-template.md +49 -0
- package/src/bmm-skills/plan/bmad-spec/assets/stories-schema.md +44 -0
- package/src/bmm-skills/plan/bmad-spec/customize.toml +52 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/SKILL.md +62 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/customize.toml +41 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/references/fix-sprint-status.md +30 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/references/generate-tracking.md +25 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/references/readiness-gate.md +20 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/references/status-view.md +14 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/references/validate.md +10 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/scripts/__pycache__/sprint_plan.cpython-311.pyc +0 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/scripts/sprint_plan.py +697 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/scripts/tests/__pycache__/test_sprint_plan.cpython-311-pytest-9.1.1.pyc +0 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/scripts/tests/test_sprint_plan.py +524 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/sprint-status-template.yaml +71 -0
- package/src/bmm-skills/plan/bmad-ux/SKILL.md +90 -0
- package/src/bmm-skills/plan/bmad-ux/assets/color-themes.md +9 -0
- package/src/bmm-skills/plan/bmad-ux/assets/design-directions.md +9 -0
- package/src/bmm-skills/plan/bmad-ux/assets/design-example-editorial.md +158 -0
- package/src/bmm-skills/plan/bmad-ux/assets/design-example-mobile.md +93 -0
- package/src/bmm-skills/plan/bmad-ux/assets/design-example-shadcn.md +109 -0
- package/src/bmm-skills/plan/bmad-ux/assets/excalidraw-wireframe.md +19 -0
- package/src/bmm-skills/plan/bmad-ux/assets/experience-example-mobile.md +112 -0
- package/src/bmm-skills/plan/bmad-ux/assets/experience-example-shadcn.md +133 -0
- package/src/bmm-skills/plan/bmad-ux/assets/headless-schemas.md +84 -0
- package/src/bmm-skills/plan/bmad-ux/assets/key-screens.md +29 -0
- package/src/bmm-skills/plan/bmad-ux/assets/validation-report-template.html +319 -0
- package/src/bmm-skills/plan/bmad-ux/customize.toml +102 -0
- package/src/bmm-skills/plan/bmad-ux/references/creative-tools.md +19 -0
- package/src/bmm-skills/plan/bmad-ux/references/design-md-spec.md +50 -0
- package/src/bmm-skills/plan/bmad-ux/references/headless.md +37 -0
- package/src/bmm-skills/plan/bmad-ux/references/validate.md +115 -0
- package/src/bmm-skills/ship/bmad-build/SKILL.md +13 -0
- package/src/bmm-skills/ship/bmad-build/compile-epic-context.md +62 -0
- package/src/bmm-skills/ship/bmad-build/customize.toml +164 -0
- package/src/bmm-skills/ship/bmad-build/references/deletion-check.md +14 -0
- package/src/bmm-skills/ship/bmad-build/review-prompts/edge-case-hunter.md +88 -0
- package/src/bmm-skills/ship/bmad-build/review-prompts/verification-gap.md +113 -0
- package/src/bmm-skills/ship/bmad-build/spec-template.md +89 -0
- package/src/bmm-skills/ship/bmad-build/step-01-clarify-and-route.md +105 -0
- package/src/bmm-skills/ship/bmad-build/step-02-plan.md +47 -0
- package/src/bmm-skills/ship/bmad-build/step-03-implement.md +49 -0
- package/src/bmm-skills/ship/bmad-build/step-04-review.md +55 -0
- package/src/bmm-skills/ship/bmad-build/step-05-present.md +75 -0
- package/src/bmm-skills/ship/bmad-build/step-oneshot.md +77 -0
- package/src/bmm-skills/ship/bmad-build/sync-sprint-status.md +19 -0
- package/src/bmm-skills/ship/bmad-build/workflow.md +84 -0
- package/src/bmm-skills/ship/bmad-build-auto/SKILL.md +13 -0
- package/src/bmm-skills/ship/bmad-build-auto/compile-epic-context.md +62 -0
- package/src/bmm-skills/ship/bmad-build-auto/customize.toml +121 -0
- package/src/bmm-skills/ship/bmad-build-auto/references/deletion-check.md +14 -0
- package/src/bmm-skills/ship/bmad-build-auto/review-prompts/edge-case-hunter.md +88 -0
- package/src/bmm-skills/ship/bmad-build-auto/review-prompts/verification-gap.md +113 -0
- package/src/bmm-skills/ship/bmad-build-auto/spec-template.md +99 -0
- package/src/bmm-skills/ship/bmad-build-auto/step-01-clarify-and-route.md +83 -0
- package/src/bmm-skills/ship/bmad-build-auto/step-02-plan.md +27 -0
- package/src/bmm-skills/ship/bmad-build-auto/step-03-implement.md +44 -0
- package/src/bmm-skills/ship/bmad-build-auto/step-04-review.md +96 -0
- package/src/bmm-skills/ship/bmad-build-auto/workflow.md +104 -0
- package/src/bmm-skills/ship/bmad-checkpoint-preview/SKILL.md +68 -0
- package/src/bmm-skills/ship/bmad-checkpoint-preview/customize.toml +41 -0
- package/src/bmm-skills/ship/bmad-checkpoint-preview/generate-trail.md +38 -0
- package/src/bmm-skills/ship/bmad-checkpoint-preview/step-01-orientation.md +105 -0
- package/src/bmm-skills/ship/bmad-checkpoint-preview/step-02-walkthrough.md +89 -0
- package/src/bmm-skills/ship/bmad-checkpoint-preview/step-03-detail-pass.md +106 -0
- package/src/bmm-skills/ship/bmad-checkpoint-preview/step-04-testing.md +74 -0
- package/src/bmm-skills/ship/bmad-checkpoint-preview/step-05-wrapup.md +30 -0
- package/src/bmm-skills/ship/bmad-code-review/SKILL.md +90 -0
- package/src/bmm-skills/ship/bmad-code-review/customize.toml +113 -0
- package/src/bmm-skills/ship/bmad-code-review/references/deletion-check.md +14 -0
- package/src/bmm-skills/ship/bmad-code-review/review-prompts/edge-case-hunter.md +88 -0
- package/src/bmm-skills/ship/bmad-code-review/review-prompts/verification-gap.md +113 -0
- package/src/bmm-skills/ship/bmad-code-review/steps/step-01-gather-context.md +87 -0
- package/src/bmm-skills/ship/bmad-code-review/steps/step-02-review.md +32 -0
- package/src/bmm-skills/ship/bmad-code-review/steps/step-03-triage.md +50 -0
- package/src/bmm-skills/ship/bmad-code-review/steps/step-04-present.md +132 -0
- package/src/bmm-skills/ship/bmad-correct-course/SKILL.md +300 -0
- package/src/bmm-skills/ship/bmad-correct-course/checklist.md +288 -0
- package/src/bmm-skills/ship/bmad-correct-course/customize.toml +41 -0
- package/src/bmm-skills/ship/bmad-qa-generate-e2e-tests/SKILL.md +176 -0
- package/src/bmm-skills/ship/bmad-qa-generate-e2e-tests/checklist.md +33 -0
- package/src/bmm-skills/ship/bmad-qa-generate-e2e-tests/customize.toml +41 -0
- package/src/bmm-skills/ship/bmad-retrospective/SKILL.md +94 -0
- package/src/bmm-skills/ship/bmad-retrospective/customize.toml +41 -0
- package/src/bmm-skills/ship/bmad-retrospective/references/acceptance-verdict.md +55 -0
- package/src/bmm-skills/ship/bmad-retrospective/references/aggregate-views.md +17 -0
- package/src/bmm-skills/ship/bmad-retrospective/references/evidence-gathering.md +30 -0
- package/src/bmm-skills/ship/bmad-retrospective/references/retro-document.md +84 -0
- package/src/bmm-skills/ship/bmad-retrospective/references/team-discussion.md +22 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/__pycache__/sprint_status.cpython-311.pyc +0 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/git_evidence.py +304 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/sprint_status.py +746 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/__pycache__/test_git_evidence.cpython-311-pytest-9.1.1.pyc +0 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/__pycache__/test_sprint_status.cpython-311-pytest-9.1.1.pyc +0 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/fixtures/sprint-status-template.yaml +71 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/test_git_evidence.py +750 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/test_sprint_status.py +1579 -0
- package/src/bmm-skills/v6-shims/README.md +28 -0
- package/src/bmm-skills/v6-shims/bmad-create-architecture/SKILL.md +30 -0
- package/src/bmm-skills/v6-shims/bmad-create-architecture/customize.toml +41 -0
- package/src/bmm-skills/v6-shims/bmad-create-prd/SKILL.md +30 -0
- package/src/bmm-skills/v6-shims/bmad-create-prd/customize.toml +41 -0
- package/src/bmm-skills/v6-shims/bmad-create-story/SKILL.md +434 -0
- package/src/bmm-skills/v6-shims/bmad-create-story/checklist.md +357 -0
- package/src/bmm-skills/v6-shims/bmad-create-story/customize.toml +41 -0
- package/src/bmm-skills/v6-shims/bmad-create-story/discover-inputs.md +88 -0
- package/src/bmm-skills/v6-shims/bmad-create-story/template.md +49 -0
- package/src/bmm-skills/v6-shims/bmad-dev-auto/SKILL.md +19 -0
- package/src/bmm-skills/v6-shims/bmad-dev-story/SKILL.md +502 -0
- package/src/bmm-skills/v6-shims/bmad-dev-story/checklist.md +80 -0
- package/src/bmm-skills/v6-shims/bmad-dev-story/customize.toml +44 -0
- package/src/bmm-skills/v6-shims/bmad-document-project/SKILL.md +14 -0
- package/src/bmm-skills/v6-shims/bmad-domain-research/SKILL.md +14 -0
- package/src/bmm-skills/v6-shims/bmad-edit-prd/SKILL.md +30 -0
- package/src/bmm-skills/v6-shims/bmad-edit-prd/customize.toml +42 -0
- package/src/bmm-skills/v6-shims/bmad-market-research/SKILL.md +14 -0
- package/src/bmm-skills/v6-shims/bmad-quick-dev/SKILL.md +19 -0
- package/src/bmm-skills/v6-shims/bmad-sprint-status/SKILL.md +26 -0
- package/src/bmm-skills/v6-shims/bmad-sprint-status/customize.toml +41 -0
- package/src/bmm-skills/v6-shims/bmad-technical-research/SKILL.md +14 -0
- package/src/bmm-skills/v6-shims/bmad-validate-prd/SKILL.md +30 -0
- package/src/bmm-skills/v6-shims/bmad-validate-prd/customize.toml +42 -0
- package/src/core-skills/bmad-advanced-elicitation/SKILL.md +65 -0
- package/src/core-skills/bmad-advanced-elicitation/assets/methods.csv +72 -0
- package/src/core-skills/bmad-advanced-elicitation/customize.toml +54 -0
- package/src/core-skills/bmad-advanced-elicitation/scripts/pick_methods.py +233 -0
- package/src/core-skills/bmad-advanced-elicitation/scripts/tests/test_pick_methods.py +228 -0
- package/src/core-skills/bmad-brainstorming/SKILL.md +80 -0
- package/src/core-skills/bmad-brainstorming/assets/brain-icons.json +166 -0
- package/src/core-skills/bmad-brainstorming/assets/brain-methods.csv +109 -0
- package/src/core-skills/bmad-brainstorming/assets/brain-selector.html +328 -0
- package/src/core-skills/bmad-brainstorming/customize.toml +84 -0
- package/src/core-skills/bmad-brainstorming/references/converge.md +24 -0
- package/src/core-skills/bmad-brainstorming/references/finalize.md +26 -0
- package/src/core-skills/bmad-brainstorming/references/headless.md +54 -0
- package/src/core-skills/bmad-brainstorming/references/in-chat-techniques.md +18 -0
- package/src/core-skills/bmad-brainstorming/references/mode-autonomous.md +10 -0
- package/src/core-skills/bmad-brainstorming/references/mode-facilitator.md +11 -0
- package/src/core-skills/bmad-brainstorming/references/mode-partner.md +16 -0
- package/src/core-skills/bmad-brainstorming/references/resume.md +5 -0
- package/src/core-skills/bmad-brainstorming/scripts/brain.py +770 -0
- package/src/core-skills/bmad-brainstorming/scripts/tests/test_brain.py +239 -0
- package/src/core-skills/bmad-customize/SKILL.md +111 -0
- package/src/core-skills/bmad-customize/scripts/list_customizable_skills.py +231 -0
- package/src/core-skills/bmad-customize/scripts/tests/test_list_customizable_skills.py +249 -0
- package/src/core-skills/bmad-deep-recon/SKILL.md +82 -0
- package/src/core-skills/bmad-deep-recon/assets/research.template.md +18 -0
- package/src/core-skills/bmad-deep-recon/customize.toml +212 -0
- package/src/core-skills/bmad-deep-recon/references/draft.md +8 -0
- package/src/core-skills/bmad-deep-recon/references/finalize.md +11 -0
- package/src/core-skills/bmad-deep-recon/references/html-briefing.md +16 -0
- package/src/core-skills/bmad-deep-recon/references/lifecycle.md +11 -0
- package/src/core-skills/bmad-deep-recon/references/process.md +10 -0
- package/src/core-skills/bmad-deep-recon/references/run.md +73 -0
- package/src/core-skills/bmad-deep-recon/references/selection.md +13 -0
- package/src/core-skills/bmad-deep-recon/references/synthesis.md +16 -0
- package/src/core-skills/bmad-deep-recon/references/verification.md +29 -0
- package/src/core-skills/bmad-deep-recon/scripts/recon_kit.py +322 -0
- package/src/core-skills/bmad-deep-recon/scripts/tests/test_recon_kit.py +144 -0
- package/src/core-skills/bmad-deep-recon/types/academic-lit.md +19 -0
- package/src/core-skills/bmad-deep-recon/types/competitive.md +19 -0
- package/src/core-skills/bmad-deep-recon/types/domain.md +19 -0
- package/src/core-skills/bmad-deep-recon/types/market.md +19 -0
- package/src/core-skills/bmad-deep-recon/types/technical.md +19 -0
- package/src/core-skills/bmad-deep-recon/types/user-voice.md +19 -0
- package/src/core-skills/bmad-forge-idea/SKILL.md +107 -0
- package/src/core-skills/bmad-forge-idea/customize.toml +42 -0
- package/src/core-skills/bmad-forge-idea/scripts/resolve_personas.py +275 -0
- package/src/core-skills/bmad-forge-idea/scripts/tests/test_resolve_personas.py +138 -0
- package/src/core-skills/bmad-help/SKILL.md +75 -0
- package/src/core-skills/bmad-party-mode/SKILL.md +60 -0
- package/src/core-skills/bmad-party-mode/customize.toml +211 -0
- package/src/core-skills/bmad-party-mode/references/create-party.md +70 -0
- package/src/core-skills/bmad-party-mode/references/mode-agent-team.md +13 -0
- package/src/core-skills/bmad-party-mode/references/mode-auto.md +13 -0
- package/src/core-skills/bmad-party-mode/references/mode-subagent.md +31 -0
- package/src/core-skills/bmad-party-mode/references/party-memory.md +51 -0
- package/src/core-skills/bmad-party-mode/scripts/resolve_party.py +282 -0
- package/src/core-skills/bmad-party-mode/scripts/tests/test_resolve_party.py +146 -0
- package/src/core-skills/bmad-review/SKILL.md +49 -0
- package/src/core-skills/bmad-review/customize.toml +141 -0
- package/src/core-skills/bmad-review/references/editorial-common.md +56 -0
- package/src/core-skills/bmad-review/references/lens-adversarial.md +19 -0
- package/src/core-skills/bmad-review/references/lens-edge-case-hunter.md +54 -0
- package/src/core-skills/bmad-review/references/lens-prose.md +7 -0
- package/src/core-skills/bmad-review/references/lens-structure.md +9 -0
- package/src/core-skills/bmad-review/references/lens-verification-gap.md +92 -0
- package/src/core-skills/bmad-review/references/structure-models.md +44 -0
- package/src/core-skills/bmad-review/scripts/tests/test_word_metrics.py +62 -0
- package/src/core-skills/bmad-review/scripts/word_metrics.py +102 -0
- package/src/core-skills/module-help.csv +10 -0
- package/src/core-skills/module.yaml +38 -0
- package/src/core-skills/v6-shims/README.md +25 -0
- package/src/core-skills/v6-shims/bmad-editorial-review/SKILL.md +6 -0
- package/src/core-skills/v6-shims/bmad-editorial-review/customize.toml +31 -0
- package/src/core-skills/v6-shims/bmad-editorial-review-prose/SKILL.md +6 -0
- package/src/core-skills/v6-shims/bmad-editorial-review-structure/SKILL.md +6 -0
- package/src/core-skills/v6-shims/bmad-review-adversarial-general/SKILL.md +6 -0
- package/src/core-skills/v6-shims/bmad-review-edge-case-hunter/SKILL.md +6 -0
- package/src/core-skills/v6-shims/bmad-review-verification-gap/SKILL.md +6 -0
- package/src/scripts/__pycache__/config_utils.cpython-311.pyc +0 -0
- package/src/scripts/config_utils.py +119 -0
- package/src/scripts/memlog.py +224 -0
- package/src/scripts/render_skill.py +401 -0
- package/src/scripts/resolve_config.py +74 -0
- package/src/scripts/resolve_customization.py +99 -0
- package/src/scripts/tests/__pycache__/test_config_utils.cpython-311.pyc +0 -0
- package/src/scripts/tests/__pycache__/test_resolve_config.cpython-311.pyc +0 -0
- package/src/scripts/tests/__pycache__/test_resolve_customization.cpython-311.pyc +0 -0
- package/src/scripts/tests/test_config_utils.py +85 -0
- package/src/scripts/tests/test_memlog.py +306 -0
- package/src/scripts/tests/test_resolve_config.py +89 -0
- package/src/scripts/tests/test_resolve_customization.py +77 -0
- package/tools/bundle-web-bundles.js +117 -0
- package/tools/format-workflow-md.js +263 -0
- package/tools/installer/README.md +60 -0
- package/tools/installer/bmad-cli.js +105 -0
- package/tools/installer/cli-utils.js +57 -0
- package/tools/installer/commands/install.js +149 -0
- package/tools/installer/commands/status.js +65 -0
- package/tools/installer/commands/uninstall.js +167 -0
- package/tools/installer/core/config.js +73 -0
- package/tools/installer/core/existing-install.js +121 -0
- package/tools/installer/core/install-paths.js +132 -0
- package/tools/installer/core/installer.js +1767 -0
- package/tools/installer/core/legacy-warnings.js +151 -0
- package/tools/installer/core/manifest-generator.js +859 -0
- package/tools/installer/core/manifest.js +433 -0
- package/tools/installer/core/uv-check.js +195 -0
- package/tools/installer/core/wsl-node-check.js +109 -0
- package/tools/installer/file-ops.js +204 -0
- package/tools/installer/fs-native.js +116 -0
- package/tools/installer/ide/_config-driven.js +972 -0
- package/tools/installer/ide/manager.js +324 -0
- package/tools/installer/ide/platform-codes.js +80 -0
- package/tools/installer/ide/platform-codes.yaml +343 -0
- package/tools/installer/ide/shared/installed-skills.js +50 -0
- package/tools/installer/ide/shared/path-utils.js +219 -0
- package/tools/installer/ide/shared/skill-manifest.js +57 -0
- package/tools/installer/install-messages.yaml +37 -0
- package/tools/installer/list-options.js +210 -0
- package/tools/installer/message-loader.js +83 -0
- package/tools/installer/modules/channel-plan.js +203 -0
- package/tools/installer/modules/channel-resolver.js +241 -0
- package/tools/installer/modules/custom-module-manager.js +924 -0
- package/tools/installer/modules/external-manager.js +671 -0
- package/tools/installer/modules/git-env.js +47 -0
- package/tools/installer/modules/module-help-schema.js +13 -0
- package/tools/installer/modules/official-modules.js +2257 -0
- package/tools/installer/modules/plugin-resolver.js +398 -0
- package/tools/installer/modules/version-resolver.js +336 -0
- package/tools/installer/project-root.js +224 -0
- package/tools/installer/prompts.js +791 -0
- package/tools/installer/set-overrides.js +330 -0
- package/tools/installer/ui.js +2167 -0
- package/tools/installer/yaml-format.js +245 -0
- package/tools/javascript-conventions.md +5 -0
- package/tools/migrate-custom-module-paths.js +124 -0
- package/tools/skill-validator.md +378 -0
- package/tools/validate-published-implementation-model.mjs +68 -0
- package/tools/validate-sidebar-order.js +388 -0
- package/tools/validate-skills.js +735 -0
- package/tools/validate-svg-changes.sh +356 -0
- package/web-bundles/README.md +46 -0
- package/web-bundles/brainstorming-coach/INSTRUCTIONS.md +86 -0
- package/web-bundles/brainstorming-coach/SKILL.md +83 -0
- package/web-bundles/brainstorming-coach/brain-methods.csv +62 -0
- package/web-bundles/bundles.json +139 -0
- package/web-bundles/market-and-industry-research/INSTRUCTIONS.md +88 -0
- package/web-bundles/market-and-industry-research/SKILL.md +59 -0
- package/web-bundles/prd-coach/INSTRUCTIONS.md +86 -0
- package/web-bundles/prd-coach/SKILL.md +101 -0
- package/web-bundles/prd-coach/prd-template.md +165 -0
- package/web-bundles/prd-coach/prd-validation-checklist.md +135 -0
- package/web-bundles/prfaq-coach/INSTRUCTIONS.md +86 -0
- package/web-bundles/prfaq-coach/SKILL.md +139 -0
- package/web-bundles/product-brief-coach/INSTRUCTIONS.md +86 -0
- package/web-bundles/product-brief-coach/SKILL.md +113 -0
- package/web-bundles/ux-coach/INSTRUCTIONS.md +92 -0
- package/web-bundles/ux-coach/SKILL.md +187 -0
- package/web-bundles/ux-coach/ux-validation.md +100 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Run
|
|
2
|
+
|
|
3
|
+
Native research, when chosen: resolve effort, hold the plan gate, then run the acquisition loop once per dimension of the approved plan, in plan order.
|
|
4
|
+
|
|
5
|
+
## Effort
|
|
6
|
+
|
|
7
|
+
Three knobs bundled in a **preset**; any knob pins individually, and **what the user says in the request beats both**.
|
|
8
|
+
|
|
9
|
+
| Preset (`{workflow.preset}`) | subagents | sources/round | depth |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| `quick` | low (2) | 5 | 1 |
|
|
12
|
+
| `standard` (default) | normal (3) | 8 | 2 |
|
|
13
|
+
| `deep` | high (6) | 12 | 3 |
|
|
14
|
+
|
|
15
|
+
- **subagents** — parallel assistants: `none` (0 — inline, sequential; also the no-subagent-harness fallback), `low` (2), `normal` (3), `high` (6, cap 10 — beyond the 3–5 sweet spot only for genuinely wide work).
|
|
16
|
+
- **max_sources_per_round** — distinct sources actually read per dimension per round (cap 25).
|
|
17
|
+
- **max_depth** — rounds per dimension: initial pass plus lead-following follow-ups (cap 5). A cap, not a quota — dimensions stop early on coverage or novelty exhaustion.
|
|
18
|
+
- **validation** (orthogonal to preset, default `normal`) — rigor rises `normal` < `high` < `max`; level semantics live in `references/verification.md`. Verification happens per dimension as material lands, never as an end-of-run rewrite pass.
|
|
19
|
+
|
|
20
|
+
`{workflow.subagent_models}` is an ordered model preference for assistants — first available wins; empty means harness default. Keep the lead on the strongest model; researchers at most one tier down; judgment work never on the smallest tier.
|
|
21
|
+
|
|
22
|
+
## The plan gate
|
|
23
|
+
|
|
24
|
+
The one hard stop, kept light: decision, type and pack-derived dimensions pruned to it, shape, the **decomposition topology** — *breadth-first* (independent sub-questions: assistants split the dimensions), *depth-first* (one question that needs several perspectives: assistants split by angle or methodology, not by dimension), or *straightforward* (a focused ask: one assistant, a handful of calls, no fan-out — never overinvest in a simple query) — knobs in force and where each came from, which search surfaces exist (harness web search; installed search-shaped MCP tools; `{workflow.external_sources}` — check, don't assume), whether to run the fan-out as a workflow when the harness offers orchestration and `{workflow.use_workflows}` allows, and an honest time estimate (a standard run is minutes; deep runs are tens of minutes and many times the tokens).
|
|
25
|
+
|
|
26
|
+
Present as a compact checklist, get approval, then: bind `{doc_workspace}` under `{workflow.research_output_path}` — expand the folder name with `uv run scripts/recon_kit.py slug "<topic>" --type <type> --pattern "{workflow.run_folder_pattern}"` so the same topic always resolves to the same folder — seed `research.md` from `{workflow.research_template}`, init the memlog (`uv run {project-root}/_bmad/scripts/memlog.py init --workspace {doc_workspace} --field topic="<topic>" --field type="<type>" --field decision="<decision>" --field preset="<preset>"`), log the approved plan as a `decision`, and tell the user the path.
|
|
27
|
+
|
|
28
|
+
Each dimension then runs in **rounds** — up to the resolved `max_depth` — and the report grows as material lands: the user watches the document build, not a spinner. Every digest is written to `{doc_workspace}/digests/` the moment it exists — one file per assistant per round (`<dimension>-r<round>-<n>.md`), the digest shape below, raw enough to re-derive from.
|
|
29
|
+
|
|
30
|
+
## Rounds and lead-following
|
|
31
|
+
|
|
32
|
+
Round 1 pursues the plan's questions **broad-first**: short, wide queries to map what exists, narrowing as the shape emerges — not long specific queries that return nothing. After each round, harvest the leads: new entities worth chasing, unexpected connections, contradictions between sources, and questions the round opened. Contradictions get priority. Promising leads become the next round's brief; note mid-course discoveries in the checkpoint so the user sees the turn happening.
|
|
33
|
+
|
|
34
|
+
A dimension stops before its round cap when either holds:
|
|
35
|
+
|
|
36
|
+
- **Coverage** — its plan questions are answered, with the critical claims confirmed per the resolved `validation` level.
|
|
37
|
+
- **Novelty exhaustion** — a full round surfaced no new load-bearing claim or lead.
|
|
38
|
+
|
|
39
|
+
Say which one ended it. Hitting the round cap with open questions is reported as an open question, never silently dropped.
|
|
40
|
+
|
|
41
|
+
**Stop-and-write valve.** If the run is dragging well past the plan gate's estimate — rounds queuing, budgets mostly spent — stop spawning, synthesize from the digests already on disk, and report the remainder as open questions with a route (a Deepen later, or a drafted prompt for the user's own tool). A shorter honest report beats a longer stale one.
|
|
42
|
+
|
|
43
|
+
## The fan-out
|
|
44
|
+
|
|
45
|
+
Fan out researcher assistants for the round — concurrency per the resolved `subagents` level, split by the plan's **topology**: breadth-first gives each assistant independent sub-questions; depth-first gives each a distinct perspective or methodology on the *same* question; straightforward is one assistant with a small budget — never fan out what one focused assistant answers. Each assistant runs behind the **research firewall**: it gets its brief and nothing else — no project files, no ambient context. The brief contains:
|
|
46
|
+
|
|
47
|
+
- the questions it owns, the decision they serve, and the topic
|
|
48
|
+
- its search surfaces (specialized tools first — installed search-shaped MCP tools, `{workflow.external_sources}` entries whose directive matches — then generic search), plus `{workflow.preferred_sources}` first / `{workflow.banned_sources}` never
|
|
49
|
+
- the pack's source craft and freshness bars, and the source-quality card below
|
|
50
|
+
- its budgets — sources (the round's share of `max_sources_per_round`) and tool calls, scaled to its task: under 5 for a simple lookup, ~5 medium, ~10 hard, 15 for genuinely multi-part, 20 never exceeded. Either budget spent → synthesize what it has
|
|
51
|
+
- the query craft: short queries (roughly five words or fewer) beat hyper-specific ones that return nothing; broaden when results are sparse, narrow when abundant; never repeat an identical query on the same tool; after every tool result, pause and evaluate — what did this add, what gap remains, what's the best next query — before firing again
|
|
52
|
+
- the epistemics rules verbatim, and the return contract: a digest, not raw results — findings as claims, each with `{claim, source, publisher, pub_date, accessed, confidence, class}`, plus leads worth chasing and what it looked for and could not find
|
|
53
|
+
|
|
54
|
+
**On each return, write the digest to `{doc_workspace}/digests/` before doing anything else with it.**
|
|
55
|
+
|
|
56
|
+
Spawn assistants on `{workflow.subagent_models}` when set (first available wins); otherwise the harness default — judgment work never drops to the smallest tier. When subagents are unavailable (or `subagents` is `none`), run the same rounds yourself, sequentially, under the same budgets and the same files-first discipline.
|
|
57
|
+
|
|
58
|
+
When workflow orchestration was approved at the plan gate, run the fan-out as a workflow: dimensions as parallel pipelines, assistants returning structured digests. The budgets, digest contract, firewall, and stopping rules apply unchanged — and however the acquisition parallelizes, digests land as files and the lead alone writes `research.md`, committing sections in plan order.
|
|
59
|
+
|
|
60
|
+
## Source quality
|
|
61
|
+
|
|
62
|
+
One card, applied by every assistant and the lead alike. Prefer **primary sources** — filings, regulator text, official documentation, original papers, a company's own reported numbers — over aggregators and secondary reporting. Red flags that downgrade confidence on sight: speculative language ("could", "may", projections in future tense presented as findings), marketing register, passive voice with unnamed sources, cherry-picked or unsourced numbers, and aggregators recycling a single upstream report (that's one publisher, however many domains echo it). Answer engines (Perplexity Sonar, Grok, and kin) are aggregators too, however good the synthesis: chase their citations and cite those, never the engine. Conflicts resolve by recency, consistency with adjacent established facts, and publisher quality — never by averaging.
|
|
63
|
+
|
|
64
|
+
## Synthesize the dimension
|
|
65
|
+
|
|
66
|
+
When a dimension's rounds are done:
|
|
67
|
+
|
|
68
|
+
1. Verify at landing per `references/verification.md` — at `normal` validation this is a spot-check of the dimension's load-bearing claims, not a sweep.
|
|
69
|
+
2. Write the dimension's section per the pack's skeleton from its digest files — findings woven into prose answering the dimension's questions, every load-bearing claim cited inline `[n]`, confidence flagged where below high, contradictions reported with both sides cited. Append to `research.md` and add its sources to the running source table.
|
|
70
|
+
3. Log one memlog line per source batch (`--type source`) and one per load-bearing claim worth tracking for refresh — `--type claim`, text in the machine-readable shape `ref=[n] status=<verified|unverified|disputed|overturned> class=<class> pub=<YYYY-MM> — <claim>` so `scripts/recon_kit.py tally` and `staleness` can read the ledger; a later status change is a fresh claim line with the same `ref=` (last status wins).
|
|
71
|
+
4. Checkpoint: one or two lines in chat — what the dimension found, anything surprising, anything unresolved. Keep moving unless the user speaks up; a mid-run scope change is logged as a `decision` and the plan adjusts. Headless: skip checkpoints entirely.
|
|
72
|
+
|
|
73
|
+
When all dimensions are done, proceed to `references/synthesis.md` for final assembly.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# The Select Shape
|
|
2
|
+
|
|
3
|
+
When the decision is **choose between candidates** — technologies, vendors, libraries, platforms, agencies, anything — this method layers over whichever research type fits the subject. The type pack still governs sources, craft, and freshness; this shape governs the flow and the verdict.
|
|
4
|
+
|
|
5
|
+
1. **Requirements frame.** What must the winner do, under what constraints — scale, compliance, budget, team skills, existing stack, exit-cost tolerance? Split hard gates from weighted preferences and set the weights. Sources: the project itself (brief, PRD, spine, `{workflow.persistent_facts}`, codebase) and the user — web research does not set requirements. **Agree the frame before any candidate research runs**; interactive runs confirm it even though the plan gate approved the dimension list.
|
|
6
|
+
2. **Candidate screen.** Establish the credible field — leaders, strong challengers, one wildcard — and cut anything failing a hard gate. Screen to 3–5 finalists; record the cuts and why. Screening sources ≤ 6 months old — this field moves.
|
|
7
|
+
3. **Evidence per criterion.** Score finalists against the frame using the type pack's dimensions and craft, verified against current versions/offerings. Cite every contested cell; where vendor claims and independent experience diverge, the divergence is a finding.
|
|
8
|
+
4. **Cost & lock-in.** Total cost over the product's horizon — license/subscription, hosting, operational load, learning curve — and the cost of leaving. Current pricing pages read directly (≤ 3 mo, always); pricing-change history — a vendor that repriced once will again; migration-away accounts for real exit costs.
|
|
9
|
+
5. **Verdict.** The weighted decision matrix — show the scoring, not just totals; a matrix the user can re-weight is worth more than a verdict they must trust. Then: the pick; the named runner-up and the conditions under which it wins instead; the strongest argument against the pick (from the red-team pass when it ran); the cheapest reversibility hedge (abstraction seam, pilot scope, exit test).
|
|
10
|
+
|
|
11
|
+
**Two-source classes (added to the type's own):** pricing figures; performance/scale numbers; any cell that decides between the top two finalists.
|
|
12
|
+
|
|
13
|
+
**Staleness:** a selection report older than two quarters should be refreshed before anyone acts on it — say so in the report.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Synthesis
|
|
2
|
+
|
|
3
|
+
The report answers the decision — whether the material came from a native Run or a processed import. **Succinct is the contract**: findings and verdicts, not essays; rationale lives in the memlog; a reader gets the decision-relevant truth in minutes. For Process mode this is the whole point — the summary is what downstream consumers read so nobody reprocesses the original, and sections with nothing behind them collapse to a line rather than pad.
|
|
4
|
+
|
|
5
|
+
Assemble `research.md` in this order, shaped by `{workflow.audience}` and written in `{document_output_language}`:
|
|
6
|
+
|
|
7
|
+
1. **Executive summary** — decision-first: what the evidence says to do, the two or three findings that drive that answer, and the biggest caveat. One page maximum, readable standalone. Written last, placed first.
|
|
8
|
+
2. **Dimension sections** — already written during the loop; now reconciled: consistent terminology, no duplicated ground, verification statuses and any corrections from the pass applied to the text.
|
|
9
|
+
3. **Cross-dimension insights** — what only the *combination* shows (e.g. the market is growing but the regulatory dimension caps the reachable segment; the technically superior option loses on ecosystem health). This section is the harness earning its keep — if there are no cross-dimension insights, say so rather than manufacture them.
|
|
10
|
+
4. **Contrary evidence** — when the red-team pass ran and found material; the strongest surviving counter-arguments, cited.
|
|
11
|
+
5. **Recommendations** — each bound to the decision and, where the project has them, to the downstream artifact that consumes it (per the pack's `Feeds` entries: brief section, PRD input, architecture constraint). Each recommendation names its confidence basis; a recommendation resting on low-confidence or disputed claims says so in the same sentence.
|
|
12
|
+
6. **Open questions** — what the research could not answer, and what it would take to answer each.
|
|
13
|
+
7. **Source appendix** — the numbered source table: `[n] | claim/finding it supports | publisher | pub date | accessed | confidence`, the publisher cell a markdown link to the source URL. Every inline `[n]` resolves here.
|
|
14
|
+
8. **Staleness map** — the claims that age fastest, computed not hand-derived: build the claims list (`claim`, `class`, `pub_date`) from the ledger, map the pack's freshness bars to months per class, and run `uv run scripts/recon_kit.py staleness <claims.json> --windows '<map>'` — render its re-check dates and close by noting the earliest. This is Refresh's work order.
|
|
15
|
+
|
|
16
|
+
Update the frontmatter (`status: complete`, `updated`, and the verified/unverified counts from `uv run scripts/recon_kit.py tally {doc_workspace}/.memlog.md` — never hand-counted), log a final `event` in the memlog, and proceed to `references/finalize.md`.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Verification
|
|
2
|
+
|
|
3
|
+
The trust layer — the same rules whatever produced the material (a native run's digests or a processed import). Verification happens **as material lands**, per dimension, in fresh-context verifier subagents reading digest files — never as an end-of-run rewrite pass over an hour of accumulated context. Late-pass rewrites degrade reports; landing-time checks improve them.
|
|
4
|
+
|
|
5
|
+
## The claims ledger
|
|
6
|
+
|
|
7
|
+
The memlog `claim` entries are the ledger: every claim a decision could rest on, with its class (each pack names its classes — quantitative sizes, pricing, versions/compatibility, regulatory assertions, …), source, publisher, publication date, and status. New claims enter `unverified`; on a Refresh or Deepen run, claims outside the run's scope keep their prior status from the memlog — only new and in-scope claims are (re)checked.
|
|
8
|
+
|
|
9
|
+
## Levels
|
|
10
|
+
|
|
11
|
+
Per the resolved `validation` level (request > knob > default `normal`):
|
|
12
|
+
|
|
13
|
+
- **normal** — spot-check the **load-bearing claims only**: the handful per dimension the recommendation actually rests on. One independent-source check each, at landing. Everything else ships with its single source cited and confidence marked honestly. Fast by design.
|
|
14
|
+
- **high** — cross-check every claim in the pack's *two-source classes*, and run the red-team pass on major conclusions regardless of `{workflow.red_team}`.
|
|
15
|
+
- **max** — cross-check every ledger claim, run the red-team pass below at full breadth (every major conclusion), and primary-source-priority ranking: where a primary source (filing, regulator text, official docs, original paper) exists, secondary reporting alone does not verify.
|
|
16
|
+
|
|
17
|
+
Verifier assistants run behind the research firewall on `{workflow.subagent_models}` when set; judgment work never drops to the smallest tier.
|
|
18
|
+
|
|
19
|
+
**Independent** means a different publisher with different underlying data or reporting — not a syndication, quote, or republication of the first source, and not the same vendor's marketing in two places. An imported report counts as one publisher regardless of how many sources it cites internally; two imports from different tools agreeing is genuine confirmation, and their disagreement is a finding.
|
|
20
|
+
|
|
21
|
+
Outcomes per claim: **verified** (independent source agrees within tolerance — for quantitative claims, same order of magnitude and direction), **disputed** (independent sources materially disagree — report both figures, both cited; never average), **unverified** (no independent check within budget — the claim stays, flagged, and joins the staleness map), or **overturned** (the weight of evidence contradicts it — corrected in the text, original noted). Every status change lands in the memlog as a fresh `claim` line with the same `ref=` and the new status — last status wins, which is how `scripts/recon_kit.py tally` reads the ledger. A verification outcome adjusts status and flags — it never licenses rewriting a finding's substance beyond what the new evidence says.
|
|
22
|
+
|
|
23
|
+
Confidence rendered in the report: **high** (verified, fresh, credible publishers), **medium** (single credible source, fresh), **low** (stale, weak publisher, or disputed) — plus the explicit `unverified` flag. Confidence is per-claim, never per-section.
|
|
24
|
+
|
|
25
|
+
## Red-team pass
|
|
26
|
+
|
|
27
|
+
The single adversarial mechanism — no other verifier duplicates it. Off by default (`{workflow.red_team}` = `"off"`; `"offer"` proposes it at the plan gate, `"on"` always runs; `high` validation includes it for major conclusions, `max` runs it at full breadth). When it runs: for each major conclusion, a **fresh-context** skeptic subagent — the conclusion and a search budget, no supporting evidence, no run context — hunts for disconfirming evidence: the bear case, failed attempts, contrary data, the strongest good-faith argument the conclusion is wrong.
|
|
28
|
+
|
|
29
|
+
What comes back is weighed, not appended: a conclusion that survives gets its strongest counter-argument acknowledged in the synthesis; one that doesn't is revised before the report states it. Material findings land in a **Contrary Evidence** section with full citation discipline. Zero findings after a real search is itself reportable — say what was searched for and not found.
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.10"
|
|
4
|
+
# ///
|
|
5
|
+
"""recon_kit — deterministic helpers for bmad-deep-recon.
|
|
6
|
+
|
|
7
|
+
The mechanical half of the research workflow: everything here is exact,
|
|
8
|
+
repeatable work the LLM should never re-derive by hand. All subcommands
|
|
9
|
+
print one JSON object to stdout; diagnostics go to stderr. Exit codes:
|
|
10
|
+
0 = pass, 1 = findings that need attention, 2 = usage/parse error.
|
|
11
|
+
|
|
12
|
+
Subcommands:
|
|
13
|
+
citations RESEARCH_MD
|
|
14
|
+
Cross-check inline [n] markers against the source-appendix table:
|
|
15
|
+
dangling markers (no appendix row) and orphaned rows (never cited).
|
|
16
|
+
tally MEMLOG_MD
|
|
17
|
+
Count memlog entries by type, and claim entries by status.
|
|
18
|
+
Claim lines carry `status=<word>` and optionally `ref=[n]`; for a
|
|
19
|
+
given ref the LAST status wins, so status changes are appends.
|
|
20
|
+
staleness CLAIMS_JSON --windows JSON [--today YYYY-MM-DD]
|
|
21
|
+
Given claims [{claim, class, pub_date}] and a months-per-class map
|
|
22
|
+
(e.g. '{"size/growth": 18, "pricing": 3}'), compute each claim's
|
|
23
|
+
re-check date, flag stale ones, and report the earliest re-check.
|
|
24
|
+
slug TOPIC --type TYPE [--pattern P] [--date YYYY-MM-DD]
|
|
25
|
+
Expand the run-folder pattern deterministically so the same topic
|
|
26
|
+
always lands in the same folder across draft -> process -> refresh.
|
|
27
|
+
escape-sources RESEARCH_MD
|
|
28
|
+
Emit the source-appendix table as HTML with every cell escaped and
|
|
29
|
+
only validated http(s) URLs turned into links, for the briefing.
|
|
30
|
+
"""
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import argparse
|
|
34
|
+
import calendar
|
|
35
|
+
import html
|
|
36
|
+
import json
|
|
37
|
+
import re
|
|
38
|
+
import sys
|
|
39
|
+
import unicodedata
|
|
40
|
+
from datetime import date, datetime
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
from urllib.parse import urlparse
|
|
43
|
+
|
|
44
|
+
MARKER_RE = re.compile(r"\[(\d+)\](?!\()") # [3] but not a [3](url) link
|
|
45
|
+
MD_LINK_RE = re.compile(r"\[([^\]]*)\]\((\S+?)\)")
|
|
46
|
+
BARE_URL_RE = re.compile(r"https?://[^\s|)\]]+")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def out(payload: dict, exit_code: int) -> int:
|
|
50
|
+
print(json.dumps(payload, indent=2, ensure_ascii=False, default=str))
|
|
51
|
+
return exit_code
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def read_text(path_arg: str) -> str:
|
|
55
|
+
if path_arg == "-":
|
|
56
|
+
return sys.stdin.read()
|
|
57
|
+
return Path(path_arg).read_text(encoding="utf-8")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def strip_fences(text: str) -> str:
|
|
61
|
+
"""Blank out fenced code blocks so their contents never count as markers or rows."""
|
|
62
|
+
lines, fenced = [], False
|
|
63
|
+
for ln in text.splitlines():
|
|
64
|
+
if ln.lstrip().startswith("```"):
|
|
65
|
+
fenced = not fenced
|
|
66
|
+
lines.append("")
|
|
67
|
+
continue
|
|
68
|
+
lines.append("" if fenced else ln)
|
|
69
|
+
return "\n".join(lines)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def table_cells(line: str) -> list[str]:
|
|
73
|
+
return [c.strip() for c in line.strip().strip("|").split("|")]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def appendix_rows(text: str) -> dict[int, list[str]]:
|
|
77
|
+
"""Source-appendix rows: markdown table rows whose first cell is a bare [n] / n."""
|
|
78
|
+
rows: dict[int, list[str]] = {}
|
|
79
|
+
for ln in text.splitlines():
|
|
80
|
+
stripped = ln.strip()
|
|
81
|
+
if not stripped.startswith("|"):
|
|
82
|
+
continue
|
|
83
|
+
cells = table_cells(stripped)
|
|
84
|
+
if not cells or len(cells) < 2:
|
|
85
|
+
continue
|
|
86
|
+
m = re.fullmatch(r"\[?(\d+)\]?", cells[0])
|
|
87
|
+
if m:
|
|
88
|
+
rows[int(m.group(1))] = cells
|
|
89
|
+
return rows
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# --- citations ---------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
def cmd_citations(args) -> int:
|
|
95
|
+
text = strip_fences(read_text(args.file))
|
|
96
|
+
rows = appendix_rows(text)
|
|
97
|
+
markers: set[int] = set()
|
|
98
|
+
for ln in text.splitlines():
|
|
99
|
+
stripped = ln.strip()
|
|
100
|
+
if stripped.startswith("|"):
|
|
101
|
+
cells = table_cells(stripped)
|
|
102
|
+
if cells and re.fullmatch(r"\[?(\d+)\]?", cells[0]):
|
|
103
|
+
continue # an appendix row is not a citation of itself
|
|
104
|
+
markers.update(int(n) for n in MARKER_RE.findall(ln))
|
|
105
|
+
dangling = sorted(markers - set(rows))
|
|
106
|
+
orphaned = sorted(set(rows) - markers)
|
|
107
|
+
ok = not dangling and not orphaned
|
|
108
|
+
return out({
|
|
109
|
+
"markers": sorted(markers),
|
|
110
|
+
"appendix_rows": sorted(rows),
|
|
111
|
+
"dangling_markers": dangling,
|
|
112
|
+
"orphaned_rows": orphaned,
|
|
113
|
+
"ok": ok,
|
|
114
|
+
}, 0 if ok else 1)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# --- tally -------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
ENTRY_RE = re.compile(r"^- (?:\(([\w-]+)(?: by [^)]*)?\)\s*)?(.*)$")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def cmd_tally(args) -> int:
|
|
123
|
+
text = read_text(args.file)
|
|
124
|
+
body = text.split("---", 2)[-1] if text.startswith("---") else text
|
|
125
|
+
by_type: dict[str, int] = {}
|
|
126
|
+
by_ref: dict[int, str] = {}
|
|
127
|
+
unref_status: dict[str, int] = {}
|
|
128
|
+
entries = 0
|
|
129
|
+
for ln in body.splitlines():
|
|
130
|
+
m = ENTRY_RE.match(ln)
|
|
131
|
+
if not m or not ln.startswith("- "):
|
|
132
|
+
continue
|
|
133
|
+
entries += 1
|
|
134
|
+
etype = m.group(1) or "note"
|
|
135
|
+
by_type[etype] = by_type.get(etype, 0) + 1
|
|
136
|
+
if etype == "claim":
|
|
137
|
+
status_m = re.search(r"status=([\w-]+)", m.group(2))
|
|
138
|
+
status = status_m.group(1) if status_m else "unknown"
|
|
139
|
+
ref_m = re.search(r"ref=\[?(\d+)\]?", m.group(2))
|
|
140
|
+
if ref_m:
|
|
141
|
+
by_ref[int(ref_m.group(1))] = status # last status wins per ref
|
|
142
|
+
else:
|
|
143
|
+
unref_status[status] = unref_status.get(status, 0) + 1
|
|
144
|
+
claims: dict[str, int] = dict(unref_status)
|
|
145
|
+
for status in by_ref.values():
|
|
146
|
+
claims[status] = claims.get(status, 0) + 1
|
|
147
|
+
return out({
|
|
148
|
+
"entries": entries,
|
|
149
|
+
"by_type": dict(sorted(by_type.items())),
|
|
150
|
+
"claims": dict(sorted(claims.items())),
|
|
151
|
+
"claims_total": sum(claims.values()),
|
|
152
|
+
}, 0)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# --- staleness ---------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
def parse_date(raw: str) -> date:
|
|
158
|
+
raw = raw.strip()
|
|
159
|
+
for fmt in ("%Y-%m-%d", "%Y-%m", "%Y"):
|
|
160
|
+
try:
|
|
161
|
+
return datetime.strptime(raw, fmt).date()
|
|
162
|
+
except ValueError:
|
|
163
|
+
continue
|
|
164
|
+
raise ValueError(f"unparseable date: {raw!r} (want YYYY[-MM[-DD]])")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def add_months(d: date, months: int) -> date:
|
|
168
|
+
total = d.month - 1 + months
|
|
169
|
+
year, month = d.year + total // 12, total % 12 + 1
|
|
170
|
+
return date(year, month, min(d.day, calendar.monthrange(year, month)[1]))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def cmd_staleness(args) -> int:
|
|
174
|
+
try:
|
|
175
|
+
payload = json.loads(read_text(args.file))
|
|
176
|
+
windows = {k.lower(): int(v) for k, v in json.loads(args.windows).items()}
|
|
177
|
+
today = parse_date(args.today) if args.today else date.today()
|
|
178
|
+
except (ValueError, json.JSONDecodeError) as e:
|
|
179
|
+
print(f"error: {e}", file=sys.stderr)
|
|
180
|
+
return 2
|
|
181
|
+
claims = payload["claims"] if isinstance(payload, dict) else payload
|
|
182
|
+
results, no_window, stale_count = [], set(), 0
|
|
183
|
+
earliest: date | None = None
|
|
184
|
+
for c in claims:
|
|
185
|
+
cls = str(c.get("class", "")).lower()
|
|
186
|
+
try:
|
|
187
|
+
pub = parse_date(str(c["pub_date"]))
|
|
188
|
+
except (KeyError, ValueError) as e:
|
|
189
|
+
print(f"error in claim {c!r}: {e}", file=sys.stderr)
|
|
190
|
+
return 2
|
|
191
|
+
months = windows.get(cls)
|
|
192
|
+
if months is None:
|
|
193
|
+
no_window.add(cls)
|
|
194
|
+
results.append({**c, "recheck": None, "stale": None})
|
|
195
|
+
continue
|
|
196
|
+
recheck = add_months(pub, months)
|
|
197
|
+
stale = recheck <= today
|
|
198
|
+
stale_count += stale
|
|
199
|
+
earliest = recheck if earliest is None or recheck < earliest else earliest
|
|
200
|
+
results.append({**c, "recheck": recheck.isoformat(), "stale": stale})
|
|
201
|
+
return out({
|
|
202
|
+
"today": today.isoformat(),
|
|
203
|
+
"claims": results,
|
|
204
|
+
"stale_count": stale_count,
|
|
205
|
+
"earliest_recheck": earliest.isoformat() if earliest else None,
|
|
206
|
+
"no_window_classes": sorted(no_window),
|
|
207
|
+
}, 1 if stale_count else 0)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# --- slug --------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
def slugify(text: str, max_len: int = 40) -> str:
|
|
213
|
+
text = unicodedata.normalize("NFKD", text).encode("ascii", "ignore").decode()
|
|
214
|
+
text = re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")
|
|
215
|
+
return re.sub(r"-{2,}", "-", text)[:max_len].rstrip("-")
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def cmd_slug(args) -> int:
|
|
219
|
+
slug = slugify(args.topic)
|
|
220
|
+
if not slug:
|
|
221
|
+
print("error: topic slugified to an empty string", file=sys.stderr)
|
|
222
|
+
return 2
|
|
223
|
+
folder = (args.pattern
|
|
224
|
+
.replace("{research_type}", args.type)
|
|
225
|
+
.replace("{topic_slug}", slug)
|
|
226
|
+
.replace("{date}", args.date or date.today().isoformat()))
|
|
227
|
+
return out({"topic_slug": slug, "folder": folder}, 0)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# --- escape-sources ----------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
def safe_url(raw: str) -> str | None:
|
|
233
|
+
parsed = urlparse(raw)
|
|
234
|
+
return raw if parsed.scheme in ("http", "https") and parsed.netloc else None
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def cell_html(cell: str, invalid: list[str]) -> str:
|
|
238
|
+
"""Escape a cell; a markdown link or bare URL becomes an <a> only when http(s)."""
|
|
239
|
+
link = MD_LINK_RE.search(cell)
|
|
240
|
+
if link:
|
|
241
|
+
url = safe_url(link.group(2))
|
|
242
|
+
label = html.escape(link.group(1) or link.group(2))
|
|
243
|
+
if url:
|
|
244
|
+
return html.escape(cell[:link.start()]) + \
|
|
245
|
+
f'<a href="{html.escape(url, quote=True)}" target="_blank" rel="noopener">{label}</a>' + \
|
|
246
|
+
html.escape(cell[link.end():])
|
|
247
|
+
invalid.append(link.group(2))
|
|
248
|
+
return html.escape(cell.replace(link.group(0), link.group(1) or link.group(2)))
|
|
249
|
+
bare = BARE_URL_RE.search(cell)
|
|
250
|
+
if bare:
|
|
251
|
+
url = safe_url(bare.group(0))
|
|
252
|
+
if url:
|
|
253
|
+
escaped = html.escape(url, quote=True)
|
|
254
|
+
return html.escape(cell[:bare.start()]) + \
|
|
255
|
+
f'<a href="{escaped}" target="_blank" rel="noopener">{escaped}</a>' + \
|
|
256
|
+
html.escape(cell[bare.end():])
|
|
257
|
+
invalid.append(bare.group(0))
|
|
258
|
+
return html.escape(cell)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def cmd_escape_sources(args) -> int:
|
|
262
|
+
text = strip_fences(read_text(args.file))
|
|
263
|
+
rows = appendix_rows(text)
|
|
264
|
+
if not rows:
|
|
265
|
+
print("error: no source-appendix table rows found", file=sys.stderr)
|
|
266
|
+
return 2
|
|
267
|
+
invalid: list[str] = []
|
|
268
|
+
body_rows = []
|
|
269
|
+
for n in sorted(rows):
|
|
270
|
+
cells = rows[n]
|
|
271
|
+
tds = "".join(f"<td>{cell_html(c, invalid)}</td>" for c in cells[1:])
|
|
272
|
+
body_rows.append(f'<tr id="src-{n}"><td>[{n}]</td>{tds}</tr>')
|
|
273
|
+
table = ('<table class="sources"><tbody>' + "".join(body_rows) + "</tbody></table>")
|
|
274
|
+
return out({"rows": len(rows), "invalid_urls": invalid, "html": table},
|
|
275
|
+
1 if invalid else 0)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# --- entry point -------------------------------------------------------------
|
|
279
|
+
|
|
280
|
+
def main(argv: list[str] | None = None) -> int:
|
|
281
|
+
p = argparse.ArgumentParser(description=__doc__,
|
|
282
|
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
283
|
+
sub = p.add_subparsers(dest="cmd", required=True)
|
|
284
|
+
|
|
285
|
+
pc = sub.add_parser("citations", help="cross-check [n] markers vs the source appendix")
|
|
286
|
+
pc.add_argument("file", help="path to research.md (or - for stdin)")
|
|
287
|
+
pc.set_defaults(func=cmd_citations)
|
|
288
|
+
|
|
289
|
+
pt = sub.add_parser("tally", help="count memlog entries by type and claims by status")
|
|
290
|
+
pt.add_argument("file", help="path to .memlog.md (or - for stdin)")
|
|
291
|
+
pt.set_defaults(func=cmd_tally)
|
|
292
|
+
|
|
293
|
+
ps = sub.add_parser("staleness", help="compute re-check dates from freshness windows")
|
|
294
|
+
ps.add_argument("file", help="claims JSON: [{claim, class, pub_date}] (or - for stdin)")
|
|
295
|
+
ps.add_argument("--windows", required=True,
|
|
296
|
+
help='JSON months-per-class map, e.g. \'{"pricing": 3}\'')
|
|
297
|
+
ps.add_argument("--today", help="override today's date (YYYY-MM-DD)")
|
|
298
|
+
ps.set_defaults(func=cmd_staleness)
|
|
299
|
+
|
|
300
|
+
pg = sub.add_parser("slug", help="expand the run-folder pattern deterministically")
|
|
301
|
+
pg.add_argument("topic", help="research topic text")
|
|
302
|
+
pg.add_argument("--type", required=True, help="research type code (e.g. market)")
|
|
303
|
+
pg.add_argument("--pattern", default="{research_type}-{topic_slug}-{date}",
|
|
304
|
+
help="folder pattern (default: {research_type}-{topic_slug}-{date})")
|
|
305
|
+
pg.add_argument("--date", help="override date (YYYY-MM-DD; default today)")
|
|
306
|
+
pg.set_defaults(func=cmd_slug)
|
|
307
|
+
|
|
308
|
+
pe = sub.add_parser("escape-sources",
|
|
309
|
+
help="source appendix as escaped HTML with validated links")
|
|
310
|
+
pe.add_argument("file", help="path to research.md (or - for stdin)")
|
|
311
|
+
pe.set_defaults(func=cmd_escape_sources)
|
|
312
|
+
|
|
313
|
+
args = p.parse_args(argv)
|
|
314
|
+
try:
|
|
315
|
+
return args.func(args)
|
|
316
|
+
except FileNotFoundError as e:
|
|
317
|
+
print(f"error: {e}", file=sys.stderr)
|
|
318
|
+
return 2
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
if __name__ == "__main__":
|
|
322
|
+
sys.exit(main())
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.10"
|
|
4
|
+
# ///
|
|
5
|
+
"""Tests for recon_kit.py."""
|
|
6
|
+
|
|
7
|
+
import io
|
|
8
|
+
import json
|
|
9
|
+
import sys
|
|
10
|
+
import unittest
|
|
11
|
+
from contextlib import redirect_stdout
|
|
12
|
+
from datetime import date
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
16
|
+
|
|
17
|
+
from recon_kit import add_months, main, parse_date, slugify
|
|
18
|
+
|
|
19
|
+
REPORT = """---
|
|
20
|
+
title: 'market research: widgets'
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# Report
|
|
24
|
+
|
|
25
|
+
The market is growing fast [1] and pricing clusters at $20 [2].
|
|
26
|
+
An uncited assertion sits here [4].
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
[9] inside a fence never counts
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Source appendix
|
|
33
|
+
|
|
34
|
+
| [n] | Supports | Publisher | Pub date | Accessed | Confidence |
|
|
35
|
+
| --- | --- | --- | --- | --- | --- |
|
|
36
|
+
| [1] | market growth | [Gartner](https://example.com/g) | 2026-01 | 2026-07-01 | high |
|
|
37
|
+
| [2] | pricing | [Acme](javascript:alert(1)) | 2026-05 | 2026-07-01 | medium |
|
|
38
|
+
| [3] | never cited | https://example.com/x | 2025-11 | 2026-07-01 | low |
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
MEMLOG = """---
|
|
42
|
+
topic: widgets
|
|
43
|
+
updated: 2026-07-22T10:00
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
- (decision) plan approved
|
|
47
|
+
- (source) round 1 batch: 4 sources
|
|
48
|
+
- (claim) ref=[1] status=verified class=size/growth pub=2026-01 — market growing 12% CAGR
|
|
49
|
+
- (claim) ref=[2] status=unverified class=pricing pub=2026-05 — pricing clusters at $20
|
|
50
|
+
- (claim) ref=[2] status=verified class=pricing pub=2026-05 — confirmed by second source
|
|
51
|
+
- (claim) status=unverified class=behavior pub=2025-03 — users churn at day 8
|
|
52
|
+
- (event) dimension 1 complete
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def run(argv):
|
|
57
|
+
buf = io.StringIO()
|
|
58
|
+
with redirect_stdout(buf):
|
|
59
|
+
code = main(argv)
|
|
60
|
+
return code, json.loads(buf.getvalue())
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class CitationsTest(unittest.TestCase):
|
|
64
|
+
def test_cross_check(self):
|
|
65
|
+
report = Path(__file__).parent / "_report.md"
|
|
66
|
+
report.write_text(REPORT, encoding="utf-8")
|
|
67
|
+
try:
|
|
68
|
+
code, result = run(["citations", str(report)])
|
|
69
|
+
finally:
|
|
70
|
+
report.unlink()
|
|
71
|
+
self.assertEqual(result["dangling_markers"], [4])
|
|
72
|
+
self.assertEqual(result["orphaned_rows"], [3])
|
|
73
|
+
self.assertNotIn(9, result["markers"]) # fenced content ignored
|
|
74
|
+
self.assertEqual(code, 1)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TallyTest(unittest.TestCase):
|
|
78
|
+
def test_last_status_wins_per_ref(self):
|
|
79
|
+
log = Path(__file__).parent / "_memlog.md"
|
|
80
|
+
log.write_text(MEMLOG, encoding="utf-8")
|
|
81
|
+
try:
|
|
82
|
+
code, result = run(["tally", str(log)])
|
|
83
|
+
finally:
|
|
84
|
+
log.unlink()
|
|
85
|
+
self.assertEqual(result["by_type"]["claim"], 4)
|
|
86
|
+
self.assertEqual(result["claims"], {"unverified": 1, "verified": 2})
|
|
87
|
+
self.assertEqual(result["claims_total"], 3) # ref=[2] counted once
|
|
88
|
+
self.assertEqual(code, 0)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class StalenessTest(unittest.TestCase):
|
|
92
|
+
def test_dates(self):
|
|
93
|
+
self.assertEqual(parse_date("2026-01"), date(2026, 1, 1))
|
|
94
|
+
self.assertEqual(add_months(date(2026, 1, 31), 1), date(2026, 2, 28))
|
|
95
|
+
|
|
96
|
+
def test_windows(self):
|
|
97
|
+
claims = json.dumps([
|
|
98
|
+
{"claim": "sizing", "class": "size/growth", "pub_date": "2024-06"},
|
|
99
|
+
{"claim": "pricing", "class": "pricing", "pub_date": "2026-06"},
|
|
100
|
+
{"claim": "odd", "class": "unmapped", "pub_date": "2026-06"},
|
|
101
|
+
])
|
|
102
|
+
f = Path(__file__).parent / "_claims.json"
|
|
103
|
+
f.write_text(claims, encoding="utf-8")
|
|
104
|
+
try:
|
|
105
|
+
code, result = run([
|
|
106
|
+
"staleness", str(f),
|
|
107
|
+
"--windows", '{"size/growth": 18, "pricing": 3}',
|
|
108
|
+
"--today", "2026-07-22",
|
|
109
|
+
])
|
|
110
|
+
finally:
|
|
111
|
+
f.unlink()
|
|
112
|
+
self.assertEqual(result["stale_count"], 1) # sizing recheck 2025-12 < today
|
|
113
|
+
self.assertEqual(result["earliest_recheck"], "2025-12-01")
|
|
114
|
+
self.assertEqual(result["no_window_classes"], ["unmapped"])
|
|
115
|
+
self.assertEqual(code, 1)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class SlugTest(unittest.TestCase):
|
|
119
|
+
def test_deterministic_folder(self):
|
|
120
|
+
self.assertEqual(slugify("Créme Brûlée: AI Tools!"), "creme-brulee-ai-tools")
|
|
121
|
+
code, result = run(["slug", "SMB Accounting SaaS", "--type", "market",
|
|
122
|
+
"--date", "2026-07-22"])
|
|
123
|
+
self.assertEqual(result["folder"], "market-smb-accounting-saas-2026-07-22")
|
|
124
|
+
self.assertEqual(code, 0)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class EscapeSourcesTest(unittest.TestCase):
|
|
128
|
+
def test_escaping_and_url_validation(self):
|
|
129
|
+
report = Path(__file__).parent / "_report.md"
|
|
130
|
+
report.write_text(REPORT, encoding="utf-8")
|
|
131
|
+
try:
|
|
132
|
+
code, result = run(["escape-sources", str(report)])
|
|
133
|
+
finally:
|
|
134
|
+
report.unlink()
|
|
135
|
+
self.assertEqual(result["rows"], 3)
|
|
136
|
+
self.assertTrue(any(u.startswith("javascript:") for u in result["invalid_urls"]))
|
|
137
|
+
self.assertNotIn("javascript:", result["html"]) # never linked
|
|
138
|
+
self.assertIn('href="https://example.com/g"', result["html"])
|
|
139
|
+
self.assertIn('id="src-1"', result["html"])
|
|
140
|
+
self.assertEqual(code, 1)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
unittest.main()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Academic Literature Pack
|
|
2
|
+
|
|
3
|
+
Serves: ground an approach in published research, scan the state of the art, run a defensible literature review, cite properly in technical writing.
|
|
4
|
+
|
|
5
|
+
**Dimensions (priority order — prune to the decision):**
|
|
6
|
+
|
|
7
|
+
1. The canon — seminal papers and the best recent surveys of the area
|
|
8
|
+
2. State of the art — current best results, benchmarks, and how they're measured
|
|
9
|
+
3. Methods & limitations — what the leading approaches assume and where they break
|
|
10
|
+
4. Open problems & live debates — what the field disagrees about right now
|
|
11
|
+
5. Who works on this — the labs and groups whose output to watch
|
|
12
|
+
|
|
13
|
+
**Craft (the non-obvious):** find one good survey before reading twenty abstracts; chase citations both directions — who they cite and who cites them (Semantic Scholar/Google Scholar); label preprint vs peer-reviewed on every citation — arXiv is not acceptance; take benchmark numbers from the original paper, never from a competitor's comparison table; check retraction and replication status on any load-bearing empirical claim; a result only ever shown by one lab is a lead, not a fact.
|
|
14
|
+
|
|
15
|
+
**Freshness:** state-of-the-art claims ≤ 12 mo (ML ≤ 6 mo) · seminal work has no freshness bar — but check whether it was since superseded.
|
|
16
|
+
|
|
17
|
+
**Two-source classes:** any empirical claim a conclusion rests on — independent replication or corroboration, not the same lab twice.
|
|
18
|
+
|
|
19
|
+
**Feeds (bmm):** technical and architecture bets · content and writing that cites · build-vs-adopt judgments on research-grade techniques.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Competitive Research Pack
|
|
2
|
+
|
|
3
|
+
Serves: position against *named* competitors, build battlecards, sharpen differentiation, anticipate their next move. (Surveying an unnamed field is the market type; this pack is for teardowns of specific players.)
|
|
4
|
+
|
|
5
|
+
**Dimensions (priority order — prune to the decision):**
|
|
6
|
+
|
|
7
|
+
1. Offer & feature teardown — what they actually ship, tried directly where possible
|
|
8
|
+
2. Pricing & packaging — models, tiers, what changed recently and which direction
|
|
9
|
+
3. Positioning & messaging — who they claim to serve, the story they tell, the gap between claim and product
|
|
10
|
+
4. Trajectory — funding, hiring, release cadence: where they're headed
|
|
11
|
+
5. Their customers' voice — what users of *their* product praise and complain about
|
|
12
|
+
|
|
13
|
+
**Craft (the non-obvious):** their changelog and release notes are roadmap truth; job postings reveal strategy six months early; their customers' 1–3★ reviews are your wedge; archived pricing pages (Wayback) show pricing direction, not just position; sales-facing comparison pages overclaim — verify capability claims against their docs; try the product yourself when a trial exists — an hour in-product beats ten reviews.
|
|
14
|
+
|
|
15
|
+
**Freshness:** pricing & features ≤ 3 mo · trajectory signals ≤ 6 mo · customer sentiment ≤ 12 mo.
|
|
16
|
+
|
|
17
|
+
**Two-source classes:** traction and market-share claims; any capability claim of theirs that your differentiation rests on.
|
|
18
|
+
|
|
19
|
+
**Feeds (bmm):** brief (alternatives) · PRD (differentiation) · GTM battlecards and positioning.
|