ma-agents 3.12.3 → 3.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +67 -13
- package/docs/architecture.md +18 -0
- package/lib/bmad-cache/bmb/.claude-plugin/marketplace.json +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/hooks/commit-msg.sample +52 -2
- package/lib/bmad-cache/bmb/_git_preserved/hooks/fsmonitor-watchman.sample +2 -8
- package/lib/bmad-cache/bmb/_git_preserved/index +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-8f8b045fef5af6911495cf3b2a89f1ed75e120f7.idx +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-8f8b045fef5af6911495cf3b2a89f1ed75e120f7.pack +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-8f8b045fef5af6911495cf3b2a89f1ed75e120f7.rev +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/packed-refs +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/refs/heads/main +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/shallow +1 -1
- package/lib/bmad-cache/bmb/package-lock.json +2 -2
- package/lib/bmad-cache/bmb/package.json +1 -1
- package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/assets/module-help.csv +1 -1
- package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/scripts/merge-config.py +33 -0
- package/lib/bmad-cache/bmb/samples/bmad-agent-dream-weaver/scripts/merge-help-csv.py +28 -0
- package/lib/bmad-cache/bmb/samples/sample-module-setup/assets/module-help.csv +1 -1
- package/lib/bmad-cache/bmb/samples/sample-module-setup/scripts/cleanup-legacy.py +28 -0
- package/lib/bmad-cache/bmb/samples/sample-module-setup/scripts/merge-config.py +33 -0
- package/lib/bmad-cache/bmb/samples/sample-module-setup/scripts/merge-help-csv.py +28 -0
- package/lib/bmad-cache/bmb/skills/bmad-bmb-setup/assets/module-help.csv +1 -1
- package/lib/bmad-cache/bmb/skills/bmad-bmb-setup/scripts/cleanup-legacy.py +28 -0
- package/lib/bmad-cache/bmb/skills/bmad-bmb-setup/scripts/merge-config.py +33 -0
- package/lib/bmad-cache/bmb/skills/bmad-bmb-setup/scripts/merge-help-csv.py +28 -0
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/assets/Dockerfile +29 -0
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/docker_setup.py +115 -0
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/generate_report.py +184 -0
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/pty_runner.py +171 -0
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/run_evals.py +492 -0
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/run_triggers.py +366 -0
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/utils.py +260 -0
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/assets/setup-skill-template/assets/module-help.csv +1 -1
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/assets/setup-skill-template/scripts/cleanup-legacy.py +28 -0
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/assets/setup-skill-template/scripts/merge-config.py +33 -0
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/assets/setup-skill-template/scripts/merge-help-csv.py +28 -0
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/assets/standalone-module-template/merge-config.py +33 -0
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/assets/standalone-module-template/merge-help-csv.py +28 -0
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/tests/test-validate-module.py +74 -1
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/validate-module.py +24 -13
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/assets/sample-customize-product-brief.toml +48 -33
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/extract-report-json.py +287 -0
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/generate-html-report.py +57 -8
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/prepass-prompt-metrics.py +7 -7
- package/lib/bmad-cache/bmb/skills/module-help.csv +1 -1
- package/lib/bmad-cache/bmb/website/public/img/eval-test-types.png +0 -0
- package/lib/bmad-cache/cache-manifest.json +17 -18
- package/lib/bmad-cache/cis/_git_preserved/hooks/commit-msg.sample +52 -2
- package/lib/bmad-cache/cis/_git_preserved/hooks/fsmonitor-watchman.sample +2 -8
- package/lib/bmad-cache/cis/_git_preserved/index +0 -0
- package/lib/bmad-cache/cis/_git_preserved/objects/pack/pack-18c8290560a98bcb7bf0676e6cc9b2ac5ca2823e.idx +0 -0
- package/lib/bmad-cache/cis/_git_preserved/objects/pack/{pack-42ffc048f54e58ce94c6331bc6be97ebbb7936f2.pack → pack-18c8290560a98bcb7bf0676e6cc9b2ac5ca2823e.pack} +0 -0
- package/lib/bmad-cache/cis/_git_preserved/objects/pack/pack-18c8290560a98bcb7bf0676e6cc9b2ac5ca2823e.rev +0 -0
- package/lib/bmad-cache/cis/_git_preserved/packed-refs +1 -1
- package/lib/bmad-cache/cis/_git_preserved/refs/heads/main +1 -1
- package/lib/bmad-cache/cis/_git_preserved/refs/tags/v0.2.1 +1 -0
- package/lib/bmad-cache/cis/_git_preserved/shallow +1 -1
- package/lib/bmad-cache/cis/package-lock.json +2 -2
- package/lib/bmad-cache/cis/package.json +1 -1
- package/lib/bmad-cache/cis/src/module-help.csv +1 -1
- package/lib/bmad-cache/gds/.claude-plugin/marketplace.json +4 -7
- package/lib/bmad-cache/gds/README.md +3 -1
- package/lib/bmad-cache/gds/_git_preserved/hooks/commit-msg.sample +52 -2
- package/lib/bmad-cache/gds/_git_preserved/hooks/fsmonitor-watchman.sample +2 -8
- package/lib/bmad-cache/gds/_git_preserved/index +0 -0
- package/lib/bmad-cache/gds/_git_preserved/objects/pack/pack-dcb7c556d9bb6b6b70d2301e094eaac6d7300552.idx +0 -0
- package/lib/bmad-cache/gds/_git_preserved/objects/pack/{pack-9427a146a90c00bb542cba038874bf9671ba4dc0.pack → pack-dcb7c556d9bb6b6b70d2301e094eaac6d7300552.pack} +0 -0
- package/lib/bmad-cache/gds/_git_preserved/objects/pack/pack-dcb7c556d9bb6b6b70d2301e094eaac6d7300552.rev +0 -0
- package/lib/bmad-cache/gds/_git_preserved/packed-refs +1 -1
- package/lib/bmad-cache/gds/_git_preserved/refs/heads/main +1 -1
- package/lib/bmad-cache/gds/_git_preserved/shallow +1 -1
- package/lib/bmad-cache/gds/package.json +1 -1
- package/lib/bmad-cache/gds/src/agents/gds-agent-game-designer/customize.toml +5 -5
- package/lib/bmad-cache/gds/src/agents/gds-agent-game-dev/customize.toml +5 -5
- package/lib/bmad-cache/gds/src/agents/gds-agent-game-solo-dev/customize.toml +0 -5
- package/lib/bmad-cache/gds/src/module-help.csv +6 -12
- package/lib/bmad-cache/gds/src/module.yaml +1 -1
- package/lib/bmad-cache/gds/src/workflows/1-preproduction/gds-create-game-brief/customize.toml +97 -22
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-gdd/assets/validation-report-template.html +190 -0
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-gdd/customize.toml +99 -0
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-gdd/scripts/render-validation-html.py +290 -0
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-prd/assets/validation-report-template.html +190 -0
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-prd/customize.toml +84 -0
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-ux/assets/validation-report-template.html +319 -0
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-ux/customize.toml +101 -0
- package/lib/bmad-cache/gds/src/workflows/3-technical/gds-game-architecture/architecture-patterns.yaml +1 -0
- package/lib/bmad-cache/gds/src/workflows/3-technical/gds-game-architecture/decision-catalog.yaml +88 -0
- package/lib/bmad-cache/gds/src/workflows/3-technical/gds-game-architecture/engine-mcps.yaml +124 -2
- package/lib/bmad-cache/gds/src/workflows/4-production/gds-investigate/customize.toml +62 -0
- package/lib/bmad-cache/tea/.claude-plugin/marketplace.json +1 -1
- package/lib/bmad-cache/tea/.github/workflows/docs.yaml +3 -3
- package/lib/bmad-cache/tea/.github/workflows/quality.yaml +10 -10
- package/lib/bmad-cache/tea/AGENTS.md +31 -0
- package/lib/bmad-cache/tea/CHANGELOG.md +42 -1
- package/lib/bmad-cache/tea/README.md +8 -5
- package/lib/bmad-cache/tea/_git_preserved/hooks/commit-msg.sample +52 -2
- package/lib/bmad-cache/tea/_git_preserved/hooks/fsmonitor-watchman.sample +2 -8
- package/lib/bmad-cache/tea/_git_preserved/index +0 -0
- package/lib/bmad-cache/tea/_git_preserved/objects/pack/pack-9e4197e37df7763dd7a05c2965ee921dfd2eb617.idx +0 -0
- package/lib/bmad-cache/tea/_git_preserved/objects/pack/{pack-f0df537f2649464ff6c5aee241165eb9c8664227.pack → pack-9e4197e37df7763dd7a05c2965ee921dfd2eb617.pack} +0 -0
- package/lib/bmad-cache/tea/_git_preserved/objects/pack/pack-9e4197e37df7763dd7a05c2965ee921dfd2eb617.rev +0 -0
- package/lib/bmad-cache/tea/_git_preserved/packed-refs +1 -1
- package/lib/bmad-cache/tea/_git_preserved/refs/heads/main +1 -1
- package/lib/bmad-cache/tea/_git_preserved/refs/tags/v1.19.0 +1 -0
- package/lib/bmad-cache/tea/_git_preserved/shallow +1 -1
- package/lib/bmad-cache/tea/docs/explanation/engagement-models.md +15 -16
- package/lib/bmad-cache/tea/docs/explanation/knowledge-base-system.md +2 -0
- package/lib/bmad-cache/tea/docs/explanation/risk-based-testing.md +1 -1
- package/lib/bmad-cache/tea/docs/explanation/tea-overview.md +88 -52
- package/lib/bmad-cache/tea/docs/explanation/testing-as-engineering.md +13 -12
- package/lib/bmad-cache/tea/docs/glossary/index.md +2 -2
- package/lib/bmad-cache/tea/docs/how-to/brownfield/use-tea-for-enterprise.md +19 -18
- package/lib/bmad-cache/tea/docs/how-to/brownfield/use-tea-with-existing-tests.md +1 -1
- package/lib/bmad-cache/tea/docs/how-to/workflows/run-nfr-assess.md +32 -26
- package/lib/bmad-cache/tea/docs/how-to/workflows/run-test-design.md +20 -14
- package/lib/bmad-cache/tea/docs/how-to/workflows/run-trace.md +3 -3
- package/lib/bmad-cache/tea/docs/index.md +13 -11
- package/lib/bmad-cache/tea/docs/reference/commands.md +37 -13
- package/lib/bmad-cache/tea/docs/reference/knowledge-base.md +2 -2
- package/lib/bmad-cache/tea/package-lock.json +2 -2
- package/lib/bmad-cache/tea/package.json +1 -1
- package/lib/bmad-cache/tea/src/agents/bmad-tea/customize.toml +20 -15
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/confidence-gate.md +73 -0
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/knowledge/test-quality.md +1 -0
- package/lib/bmad-cache/tea/src/agents/bmad-tea/resources/tea-index.csv +2 -1
- package/lib/bmad-cache/tea/src/module-help.csv +2 -2
- package/lib/bmad-cache/tea/src/workflows/testarch/README.md +5 -4
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/data/role-paths.yaml +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/data/tea-resources-index.yaml +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-04-session-01.md +2 -2
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/steps-c/step-04-session-07.md +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-teach-me-testing/templates/certificate-template.md +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-atdd/resources/tea-index.csv +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-automate/resources/tea-index.csv +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-ci/resources/tea-index.csv +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-framework/resources/tea-index.csv +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/SKILL.md +3 -3
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/checklist.md +11 -11
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/instructions.md +4 -2
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/nfr-report-template.md +5 -5
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/resources/tea-index.csv +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-01-load-context.md +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-01b-resume.md +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-02-define-thresholds.md +14 -3
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-04-evaluate-and-score.md +7 -7
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-04a-subagent-security.md +4 -4
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-04b-subagent-performance.md +4 -4
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-04c-subagent-reliability.md +4 -4
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-04d-subagent-scalability.md +4 -4
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-04e-aggregate-nfr.md +4 -4
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/steps-c/step-05-generate-report.md +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/workflow-plan.md +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-nfr/workflow.yaml +3 -3
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/checklist.md +23 -3
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/resources/tea-index.csv +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-c/step-02-load-context.md +7 -0
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-c/step-03-risk-and-testability.md +16 -2
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-c/step-04-coverage-plan.md +20 -4
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/steps-c/step-05-generate-output.md +2 -0
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/test-design-architecture-template.md +17 -0
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/test-design-qa-template.md +15 -0
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-design/test-design-template.md +16 -0
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-test-review/resources/tea-index.csv +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/checklist.md +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/resources/tea-index.csv +1 -1
- package/lib/bmad-cache/tea/src/workflows/testarch/bmad-testarch-trace/trace-template.md +1 -1
- package/lib/bmad-cache/tea/test/test-installation-components.js +49 -0
- package/lib/bmad-cache/tea/website/astro.config.mjs +2 -2
- package/lib/bmad-cache/wds/README.md +1 -1
- package/lib/bmad-cache/wds/_git_preserved/hooks/commit-msg.sample +52 -2
- package/lib/bmad-cache/wds/_git_preserved/hooks/fsmonitor-watchman.sample +2 -8
- package/lib/bmad-cache/wds/_git_preserved/index +0 -0
- package/lib/bmad-cache/wds/_git_preserved/objects/pack/pack-656c3d8d5426e73043b6a7f45eedaab74e3c419e.idx +0 -0
- package/lib/bmad-cache/wds/_git_preserved/objects/pack/{pack-96877c1c09123cccb1f91c1412184b11d2b492ad.pack → pack-656c3d8d5426e73043b6a7f45eedaab74e3c419e.pack} +0 -0
- package/lib/bmad-cache/wds/_git_preserved/objects/pack/pack-656c3d8d5426e73043b6a7f45eedaab74e3c419e.rev +0 -0
- package/lib/bmad-cache/wds/_git_preserved/packed-refs +1 -1
- package/lib/bmad-cache/wds/_git_preserved/refs/heads/main +1 -1
- package/lib/bmad-cache/wds/_git_preserved/refs/tags/v0.4.3 +1 -0
- package/lib/bmad-cache/wds/_git_preserved/shallow +1 -1
- package/lib/bmad-cache/wds/eslint.config.mjs +1 -1
- package/lib/bmad-cache/wds/package.json +1 -1
- package/lib/bmad-cache/wds/src/agents/wds-agent-freya-ux/customize.toml +80 -0
- package/lib/bmad-cache/wds/src/agents/wds-agent-mimir-builder/customize.toml +52 -0
- package/lib/bmad-cache/wds/src/agents/wds-agent-saga-analyst/customize.toml +70 -0
- package/lib/bmad-cache/wds/src/module-help.csv +19 -19
- package/lib/bmad-cache/wds/src/module.yaml +28 -0
- package/lib/bmad-cache/wds/src/scripts/README.md +155 -0
- package/lib/bmad-cache/wds/src/scripts/wds-add-object.js +202 -0
- package/lib/bmad-cache/wds/src/scripts/wds-add-spacing.js +158 -0
- package/lib/bmad-cache/wds/src/scripts/wds-init-page.js +229 -0
- package/lib/bmad-cache/wds/src/scripts/wds-init-scenario.js +120 -0
- package/lib/bmad-cache/wds/src/scripts/wds-nav.js +201 -0
- package/lib/bmad-cache/wds/src/scripts/wds-validate.js +301 -0
- package/lib/bmad-cache/wds/src/workflows/wds-3-scenarios/workflow.xml +450 -0
- package/lib/bmad-cache/wds/src/workflows/wds-4-ux-design/workflow-specify.xml +387 -0
- package/lib/bmad-extension/.claude-plugin/marketplace.json.template +1 -1
- package/lib/bmad-extension-plugin/.claude-plugin/marketplace.json +2 -2
- package/lib/bmad.js +361 -18
- package/lib/installer.js +296 -26
- package/lib/mil498-templates/OCD.md +169 -169
- package/lib/mil498-templates/README.md +4 -4
- package/lib/mil498-templates/SDD.md +163 -163
- package/lib/mil498-templates/SDP.md +307 -307
- package/lib/mil498-templates/SRS.md +219 -219
- package/lib/mil498-templates/SSDD.md +154 -154
- package/lib/mil498-templates/SSS.md +225 -225
- package/lib/mil498-templates/STD.md +188 -188
- package/lib/profile.js +96 -5
- package/lib/templates/instruction-block-git.template.md +25 -0
- package/lib/templates/instruction-block-onprem.template.md +86 -86
- package/lib/templates/instruction-block-universal.template.md +29 -29
- package/package.json +5 -4
- package/scripts/build-bmad-cache.js +143 -42
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-6ecd9fc6445b1281449c5ec49a6c5794708e662e.idx +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-6ecd9fc6445b1281449c5ec49a6c5794708e662e.pack +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-6ecd9fc6445b1281449c5ec49a6c5794708e662e.rev +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/refs/remotes/origin/HEAD +0 -1
- package/lib/bmad-cache/bmb/_git_preserved/refs/tags/v1.7.0 +0 -1
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/generate-convert-report.py +0 -406
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/tests/test_generate_convert_report.py +0 -243
- package/lib/bmad-cache/cis/_git_preserved/objects/pack/pack-42ffc048f54e58ce94c6331bc6be97ebbb7936f2.idx +0 -0
- package/lib/bmad-cache/cis/_git_preserved/objects/pack/pack-42ffc048f54e58ce94c6331bc6be97ebbb7936f2.rev +0 -0
- package/lib/bmad-cache/cis/_git_preserved/refs/remotes/origin/HEAD +0 -1
- package/lib/bmad-cache/gds/_git_preserved/objects/pack/pack-9427a146a90c00bb542cba038874bf9671ba4dc0.idx +0 -0
- package/lib/bmad-cache/gds/_git_preserved/objects/pack/pack-9427a146a90c00bb542cba038874bf9671ba4dc0.rev +0 -0
- package/lib/bmad-cache/gds/_git_preserved/refs/remotes/origin/HEAD +0 -1
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-create-gdd/customize.toml +0 -41
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-create-prd/customize.toml +0 -41
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-create-prd/data/domain-complexity.csv +0 -15
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-create-prd/data/project-types.csv +0 -11
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-create-ux-design/customize.toml +0 -41
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-edit-gdd/customize.toml +0 -41
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-edit-prd/customize.toml +0 -41
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-validate-gdd/customize.toml +0 -41
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-validate-prd/customize.toml +0 -41
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-validate-prd/data/domain-complexity.csv +0 -15
- package/lib/bmad-cache/gds/src/workflows/2-design/gds-validate-prd/data/project-types.csv +0 -11
- package/lib/bmad-cache/tea/_git_preserved/objects/pack/pack-f0df537f2649464ff6c5aee241165eb9c8664227.idx +0 -0
- package/lib/bmad-cache/tea/_git_preserved/objects/pack/pack-f0df537f2649464ff6c5aee241165eb9c8664227.rev +0 -0
- package/lib/bmad-cache/tea/_git_preserved/refs/remotes/origin/HEAD +0 -1
- package/lib/bmad-cache/wds/_git_preserved/objects/pack/pack-96877c1c09123cccb1f91c1412184b11d2b492ad.idx +0 -0
- package/lib/bmad-cache/wds/_git_preserved/objects/pack/pack-96877c1c09123cccb1f91c1412184b11d2b492ad.rev +0 -0
- package/lib/bmad-cache/wds/_git_preserved/refs/remotes/origin/HEAD +0 -1
- package/lib/bmad-cache/wds/src/agents/wds-agent-freya-ux/bmad-skill-manifest.yaml +0 -12
- package/lib/bmad-cache/wds/src/agents/wds-agent-saga-analyst/bmad-skill-manifest.yaml +0 -12
- package/lib/bmad-cache/wds/src/workflows/wds-0-alignment-signoff/bmad-skill-manifest.yaml +0 -1
- package/lib/bmad-cache/wds/src/workflows/wds-0-project-setup/bmad-skill-manifest.yaml +0 -1
- package/lib/bmad-cache/wds/src/workflows/wds-1-project-brief/bmad-skill-manifest.yaml +0 -1
- package/lib/bmad-cache/wds/src/workflows/wds-2-trigger-mapping/bmad-skill-manifest.yaml +0 -1
- package/lib/bmad-cache/wds/src/workflows/wds-3-scenarios/bmad-skill-manifest.yaml +0 -1
- package/lib/bmad-cache/wds/src/workflows/wds-4-ux-design/bmad-skill-manifest.yaml +0 -1
- package/lib/bmad-cache/wds/src/workflows/wds-5-agentic-development/bmad-skill-manifest.yaml +0 -1
- package/lib/bmad-cache/wds/src/workflows/wds-6-asset-generation/bmad-skill-manifest.yaml +0 -1
- package/lib/bmad-cache/wds/src/workflows/wds-7-design-system/bmad-skill-manifest.yaml +0 -1
- package/lib/bmad-cache/wds/src/workflows/wds-8-product-evolution/bmad-skill-manifest.yaml +0 -1
- /package/lib/bmad-cache/gds/src/workflows/2-design/{gds-create-gdd → gds-gdd/assets}/game-types.csv +0 -0
- /package/lib/bmad-cache/gds/src/workflows/2-design/{gds-validate-gdd/data → gds-gdd/assets}/genre-complexity.csv +0 -0
|
@@ -139,9 +139,37 @@ def cleanup_legacy_csvs(
|
|
|
139
139
|
return deleted
|
|
140
140
|
|
|
141
141
|
|
|
142
|
+
def reject_unresolved_paths(named_paths: list[tuple[str, str]]) -> None:
|
|
143
|
+
"""Exit with a clear error if any path argument still contains the literal
|
|
144
|
+
``{project-root}`` token. That token is meaningful only inside config
|
|
145
|
+
values; filesystem path arguments must be resolved by the caller. Failing
|
|
146
|
+
loudly here prevents silently creating a junk ``{project-root}/`` directory.
|
|
147
|
+
"""
|
|
148
|
+
for name, value in named_paths:
|
|
149
|
+
if value and "{project-root}" in value:
|
|
150
|
+
print(
|
|
151
|
+
json.dumps(
|
|
152
|
+
{
|
|
153
|
+
"status": "error",
|
|
154
|
+
"error": (
|
|
155
|
+
f"Unresolved '{{project-root}}' token in {name} path: {value!r}. "
|
|
156
|
+
"Resolve '{project-root}' to the actual project root before running "
|
|
157
|
+
"this script — it is a filesystem path, not a config value."
|
|
158
|
+
),
|
|
159
|
+
},
|
|
160
|
+
indent=2,
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
sys.exit(1)
|
|
164
|
+
|
|
165
|
+
|
|
142
166
|
def main():
|
|
143
167
|
args = parse_args()
|
|
144
168
|
|
|
169
|
+
reject_unresolved_paths(
|
|
170
|
+
[("--target", args.target), ("--legacy-dir", args.legacy_dir)]
|
|
171
|
+
)
|
|
172
|
+
|
|
145
173
|
# Read source entries
|
|
146
174
|
source_header, source_rows = read_csv_rows(args.source)
|
|
147
175
|
if not source_rows:
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
FROM node:20-bookworm-slim
|
|
2
|
+
|
|
3
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
|
4
|
+
|
|
5
|
+
RUN apt-get update \
|
|
6
|
+
&& apt-get install -y --no-install-recommends \
|
|
7
|
+
git \
|
|
8
|
+
python3 \
|
|
9
|
+
python3-pip \
|
|
10
|
+
ca-certificates \
|
|
11
|
+
curl \
|
|
12
|
+
jq \
|
|
13
|
+
rsync \
|
|
14
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
15
|
+
|
|
16
|
+
RUN npm install -g @anthropic-ai/claude-code
|
|
17
|
+
|
|
18
|
+
RUN useradd -ms /bin/bash evaluator \
|
|
19
|
+
&& mkdir -p /workspace /project /output /home/evaluator/.claude \
|
|
20
|
+
&& chown -R evaluator:evaluator /workspace /output /home/evaluator
|
|
21
|
+
|
|
22
|
+
USER evaluator
|
|
23
|
+
WORKDIR /workspace
|
|
24
|
+
|
|
25
|
+
ENV HOME=/home/evaluator
|
|
26
|
+
ENV CLAUDE_CONFIG_DIR=/home/evaluator/.claude
|
|
27
|
+
ENV PATH=/home/evaluator/.local/bin:$PATH
|
|
28
|
+
|
|
29
|
+
CMD ["bash"]
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.9"
|
|
4
|
+
# ///
|
|
5
|
+
"""Detect Docker and build the bmad-eval-runner image when needed.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python3 docker_setup.py --check # exit 0 if image is ready, 1 otherwise
|
|
9
|
+
python3 docker_setup.py --build # build the image (no-op if present)
|
|
10
|
+
python3 docker_setup.py --rebuild # force rebuild
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import shutil
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
IMAGE_TAG = "bmad-eval-runner:latest"
|
|
24
|
+
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
25
|
+
DOCKERFILE = SCRIPT_DIR.parent / "assets" / "Dockerfile"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def docker_available() -> tuple[bool, str]:
|
|
29
|
+
if shutil.which("docker") is None:
|
|
30
|
+
return False, "docker CLI not found on PATH"
|
|
31
|
+
try:
|
|
32
|
+
result = subprocess.run(
|
|
33
|
+
["docker", "info"],
|
|
34
|
+
capture_output=True,
|
|
35
|
+
text=True,
|
|
36
|
+
timeout=5,
|
|
37
|
+
)
|
|
38
|
+
if result.returncode != 0:
|
|
39
|
+
return False, f"`docker info` failed: {result.stderr.strip().splitlines()[-1] if result.stderr.strip() else 'unknown'}"
|
|
40
|
+
return True, "ok"
|
|
41
|
+
except subprocess.TimeoutExpired:
|
|
42
|
+
return False, "`docker info` timed out"
|
|
43
|
+
except Exception as e:
|
|
44
|
+
return False, f"docker check error: {e}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def image_present(tag: str = IMAGE_TAG) -> bool:
|
|
48
|
+
try:
|
|
49
|
+
result = subprocess.run(
|
|
50
|
+
["docker", "image", "inspect", tag],
|
|
51
|
+
stdout=subprocess.DEVNULL,
|
|
52
|
+
stderr=subprocess.DEVNULL,
|
|
53
|
+
timeout=10,
|
|
54
|
+
)
|
|
55
|
+
return result.returncode == 0
|
|
56
|
+
except Exception:
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def build_image(tag: str = IMAGE_TAG, force: bool = False, verbose: bool = True) -> int:
|
|
61
|
+
if not DOCKERFILE.is_file():
|
|
62
|
+
print(f"Dockerfile missing at {DOCKERFILE}", file=sys.stderr)
|
|
63
|
+
return 2
|
|
64
|
+
|
|
65
|
+
cmd = ["docker", "build", "-t", tag, "-f", str(DOCKERFILE), str(DOCKERFILE.parent)]
|
|
66
|
+
if force:
|
|
67
|
+
cmd.insert(2, "--no-cache")
|
|
68
|
+
|
|
69
|
+
if verbose:
|
|
70
|
+
print(f"Building {tag} from {DOCKERFILE} ...", file=sys.stderr)
|
|
71
|
+
|
|
72
|
+
proc = subprocess.run(cmd, stdout=sys.stderr if verbose else subprocess.DEVNULL, stderr=sys.stderr)
|
|
73
|
+
return proc.returncode
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def main() -> int:
|
|
77
|
+
parser = argparse.ArgumentParser(description="Manage the bmad-eval-runner Docker image")
|
|
78
|
+
group = parser.add_mutually_exclusive_group(required=True)
|
|
79
|
+
group.add_argument("--check", action="store_true", help="Report status as JSON; exit 0 if image is ready")
|
|
80
|
+
group.add_argument("--build", action="store_true", help="Build the image (no-op if already present)")
|
|
81
|
+
group.add_argument("--rebuild", action="store_true", help="Force rebuild")
|
|
82
|
+
parser.add_argument("--quiet", action="store_true")
|
|
83
|
+
args = parser.parse_args()
|
|
84
|
+
|
|
85
|
+
available, reason = docker_available()
|
|
86
|
+
present = image_present() if available else False
|
|
87
|
+
|
|
88
|
+
if args.check:
|
|
89
|
+
print(json.dumps({
|
|
90
|
+
"docker_available": available,
|
|
91
|
+
"docker_reason": reason,
|
|
92
|
+
"image_present": present,
|
|
93
|
+
"image_tag": IMAGE_TAG,
|
|
94
|
+
}, indent=2))
|
|
95
|
+
return 0 if (available and present) else 1
|
|
96
|
+
|
|
97
|
+
if not available:
|
|
98
|
+
print(f"Docker is not available: {reason}", file=sys.stderr)
|
|
99
|
+
return 3
|
|
100
|
+
|
|
101
|
+
if args.rebuild:
|
|
102
|
+
return build_image(force=True, verbose=not args.quiet)
|
|
103
|
+
|
|
104
|
+
if args.build:
|
|
105
|
+
if present:
|
|
106
|
+
if not args.quiet:
|
|
107
|
+
print(f"{IMAGE_TAG} already present; skipping build (use --rebuild to force).", file=sys.stderr)
|
|
108
|
+
return 0
|
|
109
|
+
return build_image(force=False, verbose=not args.quiet)
|
|
110
|
+
|
|
111
|
+
return 0
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
if __name__ == "__main__":
|
|
115
|
+
sys.exit(main())
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.9"
|
|
4
|
+
# ///
|
|
5
|
+
"""Generate an aggregate HTML report for a run folder.
|
|
6
|
+
|
|
7
|
+
Reads run.json, execution-summary.json, each <eval-id>/grading.json (if present),
|
|
8
|
+
and triggers-result.json (if present), then renders a single-file HTML report.
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
python3 generate_report.py --run-dir PATH [-o report.html]
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import html as html_lib
|
|
18
|
+
import json
|
|
19
|
+
import sys
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def esc(s: object) -> str:
|
|
24
|
+
return html_lib.escape(str(s), quote=True)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def load(path: Path) -> dict | list | None:
|
|
28
|
+
if not path.is_file():
|
|
29
|
+
return None
|
|
30
|
+
try:
|
|
31
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
32
|
+
except json.JSONDecodeError:
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def render(run_dir: Path) -> str:
|
|
37
|
+
run_meta = load(run_dir / "run.json") or {}
|
|
38
|
+
exec_summary = load(run_dir / "execution-summary.json") or {}
|
|
39
|
+
triggers = load(run_dir / "triggers-result.json")
|
|
40
|
+
|
|
41
|
+
eval_blocks: list[str] = []
|
|
42
|
+
grading_total = 0
|
|
43
|
+
grading_passed = 0
|
|
44
|
+
|
|
45
|
+
for res in exec_summary.get("results", []):
|
|
46
|
+
eval_id = str(res.get("eval_id", "?"))
|
|
47
|
+
eval_dir = run_dir / eval_id
|
|
48
|
+
grading = load(eval_dir / "grading.json")
|
|
49
|
+
metrics = res.get("metrics") or load(eval_dir / "metrics.json") or {}
|
|
50
|
+
rc = res.get("return_code")
|
|
51
|
+
|
|
52
|
+
rows: list[str] = []
|
|
53
|
+
if grading:
|
|
54
|
+
for exp in grading.get("expectations", []):
|
|
55
|
+
passed = bool(exp.get("passed"))
|
|
56
|
+
grading_total += 1
|
|
57
|
+
if passed:
|
|
58
|
+
grading_passed += 1
|
|
59
|
+
rows.append(
|
|
60
|
+
f'<tr class="{ "pass" if passed else "fail" }">'
|
|
61
|
+
f'<td>{ "✔" if passed else "✘" }</td>'
|
|
62
|
+
f'<td>{esc(exp.get("text", ""))}</td>'
|
|
63
|
+
f'<td>{esc(exp.get("evidence", ""))}</td></tr>'
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
feedback = (grading or {}).get("eval_feedback") or {}
|
|
67
|
+
feedback_html = ""
|
|
68
|
+
if feedback:
|
|
69
|
+
sugg = feedback.get("suggestions") or []
|
|
70
|
+
sugg_html = "".join(
|
|
71
|
+
f"<li><strong>{esc(s.get('assertion','(general)'))}</strong>: {esc(s.get('reason',''))}</li>"
|
|
72
|
+
for s in sugg
|
|
73
|
+
)
|
|
74
|
+
overall = esc(feedback.get("overall", ""))
|
|
75
|
+
feedback_html = (
|
|
76
|
+
f'<details class="feedback"><summary>Grader feedback on the evals</summary>'
|
|
77
|
+
f'<p>{overall}</p>'
|
|
78
|
+
f'{"<ul>" + sugg_html + "</ul>" if sugg_html else ""}'
|
|
79
|
+
f'</details>'
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
artifacts_listing = ""
|
|
83
|
+
artifacts_dir = eval_dir / "artifacts"
|
|
84
|
+
if artifacts_dir.is_dir():
|
|
85
|
+
files = sorted(p for p in artifacts_dir.rglob("*") if p.is_file())
|
|
86
|
+
if files:
|
|
87
|
+
artifacts_listing = "<ul>" + "".join(
|
|
88
|
+
f'<li><code>{esc(p.relative_to(eval_dir))}</code> '
|
|
89
|
+
f'<span class="muted">({p.stat().st_size}b)</span></li>'
|
|
90
|
+
for p in files
|
|
91
|
+
) + "</ul>"
|
|
92
|
+
|
|
93
|
+
tool_calls = metrics.get("tool_calls", {})
|
|
94
|
+
tool_summary = ", ".join(f"{k}={v}" for k, v in sorted(tool_calls.items())) or "—"
|
|
95
|
+
|
|
96
|
+
eval_blocks.append(f"""
|
|
97
|
+
<section class="eval">
|
|
98
|
+
<h3>Eval {esc(eval_id)} <span class="muted">rc={esc(rc)} · {esc(metrics.get('elapsed_s', '?'))}s</span></h3>
|
|
99
|
+
<p class="muted">Tool calls: {esc(tool_summary)} · output {esc(metrics.get('output_chars', 0))}b · transcript {esc(metrics.get('transcript_chars', 0))}b</p>
|
|
100
|
+
{ '<table><thead><tr><th></th><th>Expectation</th><th>Evidence</th></tr></thead><tbody>' + ''.join(rows) + '</tbody></table>' if rows else '<p class="muted">No grading.json yet.</p>' }
|
|
101
|
+
{feedback_html}
|
|
102
|
+
<details><summary>Artifacts</summary>{artifacts_listing or '<p class="muted">No artifacts captured.</p>'}</details>
|
|
103
|
+
</section>
|
|
104
|
+
""")
|
|
105
|
+
|
|
106
|
+
triggers_html = ""
|
|
107
|
+
if triggers:
|
|
108
|
+
rows = []
|
|
109
|
+
for r in triggers.get("results", []):
|
|
110
|
+
rows.append(
|
|
111
|
+
f'<tr class="{ "pass" if r["pass"] else "fail" }">'
|
|
112
|
+
f'<td>{ "✔" if r["pass"] else "✘" }</td>'
|
|
113
|
+
f'<td>{esc(r["query"])}</td>'
|
|
114
|
+
f'<td>{esc(r["should_trigger"])}</td>'
|
|
115
|
+
f'<td>{r["triggers"]}/{r["runs"]} ({r["trigger_rate"]:.2f})</td></tr>'
|
|
116
|
+
)
|
|
117
|
+
s = triggers.get("summary", {})
|
|
118
|
+
triggers_html = f"""
|
|
119
|
+
<section class="triggers">
|
|
120
|
+
<h2>Trigger Evals — {s.get('passed',0)}/{s.get('total',0)} pass</h2>
|
|
121
|
+
<table><thead><tr><th></th><th>Query</th><th>Should fire</th><th>Rate</th></tr></thead>
|
|
122
|
+
<tbody>{''.join(rows)}</tbody></table>
|
|
123
|
+
</section>
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
artifact_summary = ""
|
|
127
|
+
if exec_summary:
|
|
128
|
+
artifact_summary = (
|
|
129
|
+
f"<p>Executed {exec_summary.get('executed', 0)} / {exec_summary.get('total', 0)} "
|
|
130
|
+
f"evals · {exec_summary.get('exec_failures', 0)} execution failures · "
|
|
131
|
+
f"grader: {grading_passed}/{grading_total} expectations passed</p>"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return f"""<!doctype html>
|
|
135
|
+
<html><head><meta charset="utf-8"><title>Eval Run — {esc(run_meta.get('skill_name','?'))}</title>
|
|
136
|
+
<style>
|
|
137
|
+
body {{ font: 14px/1.5 system-ui, sans-serif; max-width: 1080px; margin: 2em auto; color: #222; padding: 0 1em; }}
|
|
138
|
+
h1, h2, h3 {{ font-weight: 600; }}
|
|
139
|
+
h1 {{ font-size: 1.6em; margin-bottom: 0.2em; }}
|
|
140
|
+
.meta {{ color: #666; margin-bottom: 1.5em; }}
|
|
141
|
+
.muted {{ color: #888; font-weight: normal; }}
|
|
142
|
+
section.eval {{ border: 1px solid #ddd; border-radius: 6px; padding: 1em 1.2em; margin: 1em 0; background: #fafafa; }}
|
|
143
|
+
table {{ width: 100%; border-collapse: collapse; margin: 0.5em 0; font-size: 13px; }}
|
|
144
|
+
th, td {{ text-align: left; padding: 6px 8px; border-bottom: 1px solid #eee; vertical-align: top; }}
|
|
145
|
+
tr.pass td:first-child {{ color: #2c8a3a; font-weight: 700; }}
|
|
146
|
+
tr.fail td:first-child {{ color: #b3261e; font-weight: 700; }}
|
|
147
|
+
tr.fail {{ background: #fdf3f2; }}
|
|
148
|
+
details.feedback {{ margin-top: 0.6em; padding: 0.4em 0.7em; background: #fff8e1; border-radius: 4px; }}
|
|
149
|
+
details summary {{ cursor: pointer; font-weight: 600; }}
|
|
150
|
+
code {{ background: #eee; padding: 1px 4px; border-radius: 3px; }}
|
|
151
|
+
</style></head>
|
|
152
|
+
<body>
|
|
153
|
+
<h1>{esc(run_meta.get('skill_name','?'))} — eval run</h1>
|
|
154
|
+
<div class="meta">
|
|
155
|
+
Run id: <code>{esc(run_meta.get('run_id','?'))}</code> ·
|
|
156
|
+
isolation: {esc(run_meta.get('isolation','?'))} ·
|
|
157
|
+
started: {esc(run_meta.get('started_at','?'))}
|
|
158
|
+
</div>
|
|
159
|
+
{artifact_summary}
|
|
160
|
+
{''.join(eval_blocks)}
|
|
161
|
+
{triggers_html}
|
|
162
|
+
</body></html>
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def main() -> int:
|
|
167
|
+
parser = argparse.ArgumentParser(description="Generate HTML report for an eval run folder")
|
|
168
|
+
parser.add_argument("--run-dir", required=True, type=Path)
|
|
169
|
+
parser.add_argument("-o", "--output", type=Path, default=None)
|
|
170
|
+
args = parser.parse_args()
|
|
171
|
+
|
|
172
|
+
run_dir = args.run_dir.resolve()
|
|
173
|
+
if not run_dir.is_dir():
|
|
174
|
+
print(f"run-dir not found: {run_dir}", file=sys.stderr)
|
|
175
|
+
return 2
|
|
176
|
+
|
|
177
|
+
out = args.output or (run_dir / "report.html")
|
|
178
|
+
out.write_text(render(run_dir), encoding="utf-8")
|
|
179
|
+
print(str(out))
|
|
180
|
+
return 0
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
if __name__ == "__main__":
|
|
184
|
+
sys.exit(main())
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.9"
|
|
4
|
+
# ///
|
|
5
|
+
"""Run claude interactively via PTY so the Skill tool is available.
|
|
6
|
+
|
|
7
|
+
In `claude -p` (print mode) the Skill tool is never offered — Claude handles
|
|
8
|
+
everything inline. Running `claude` in interactive mode activates the Skill
|
|
9
|
+
tool so dependency skills installed in .claude/skills/ can be properly invoked.
|
|
10
|
+
|
|
11
|
+
The PTY tricks claude into thinking it has a terminal (interactive mode) while
|
|
12
|
+
we capture its stream-json output programmatically.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
python3 pty_runner.py --prompt-file /path/to/prompt.txt \\
|
|
16
|
+
--output /path/to/transcript.jsonl \\
|
|
17
|
+
[--timeout 600]
|
|
18
|
+
python3 pty_runner.py --prompt "Run headless. ..." --output transcript.jsonl
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
import pty
|
|
27
|
+
import re
|
|
28
|
+
import select
|
|
29
|
+
import subprocess
|
|
30
|
+
import sys
|
|
31
|
+
import time
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
|
|
34
|
+
ANSI_RE = re.compile(r"\x1b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])|\r")
|
|
35
|
+
|
|
36
|
+
# How long to wait for claude to initialize before sending the prompt.
|
|
37
|
+
# Claude loads skill registry, checks credentials, etc. on startup.
|
|
38
|
+
INIT_WAIT_S = 5.0
|
|
39
|
+
|
|
40
|
+
# How long to wait after the stream-json 'result' event before killing claude.
|
|
41
|
+
# Trailing tool-result output sometimes follows the result event.
|
|
42
|
+
POST_RESULT_S = 4.0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _strip_ansi(text: str) -> str:
|
|
46
|
+
return ANSI_RE.sub("", text)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def run_interactive(prompt: str, output: Path, timeout: int = 600) -> None:
|
|
50
|
+
"""Spawn claude interactively via PTY, send one prompt, capture transcript."""
|
|
51
|
+
master, slave = pty.openpty()
|
|
52
|
+
|
|
53
|
+
proc = subprocess.Popen(
|
|
54
|
+
[
|
|
55
|
+
"claude",
|
|
56
|
+
"--output-format", "stream-json",
|
|
57
|
+
"--verbose",
|
|
58
|
+
"--dangerously-skip-permissions",
|
|
59
|
+
],
|
|
60
|
+
stdin=slave,
|
|
61
|
+
stdout=slave,
|
|
62
|
+
stderr=slave,
|
|
63
|
+
close_fds=True,
|
|
64
|
+
)
|
|
65
|
+
os.close(slave)
|
|
66
|
+
|
|
67
|
+
json_lines: list[str] = []
|
|
68
|
+
buf = b""
|
|
69
|
+
prompt_sent = False
|
|
70
|
+
done_at: float | None = None
|
|
71
|
+
start = time.time()
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
while True:
|
|
75
|
+
elapsed = time.time() - start
|
|
76
|
+
if elapsed > timeout:
|
|
77
|
+
print(f"[pty_runner] timeout after {elapsed:.0f}s", file=sys.stderr)
|
|
78
|
+
break
|
|
79
|
+
if done_at is not None and (time.time() - done_at) > POST_RESULT_S:
|
|
80
|
+
break
|
|
81
|
+
|
|
82
|
+
# Short select so we stay responsive but don't spin.
|
|
83
|
+
r, _, _ = select.select([master], [], [], 0.3)
|
|
84
|
+
|
|
85
|
+
if r:
|
|
86
|
+
try:
|
|
87
|
+
chunk = os.read(master, 8192)
|
|
88
|
+
except OSError:
|
|
89
|
+
break # PTY closed — claude exited
|
|
90
|
+
buf += chunk
|
|
91
|
+
|
|
92
|
+
# Process all complete lines in buffer.
|
|
93
|
+
while b"\n" in buf:
|
|
94
|
+
raw, buf = buf.split(b"\n", 1)
|
|
95
|
+
line = _strip_ansi(raw.decode("utf-8", errors="replace")).strip()
|
|
96
|
+
if not line.startswith("{"):
|
|
97
|
+
continue
|
|
98
|
+
json_lines.append(line)
|
|
99
|
+
try:
|
|
100
|
+
obj = json.loads(line)
|
|
101
|
+
# 'result' marks end of a claude turn.
|
|
102
|
+
if obj.get("type") == "result" and done_at is None:
|
|
103
|
+
done_at = time.time()
|
|
104
|
+
print(
|
|
105
|
+
f"[pty_runner] result event at t={time.time()-start:.1f}s "
|
|
106
|
+
f"({len(json_lines)} lines so far)",
|
|
107
|
+
file=sys.stderr,
|
|
108
|
+
)
|
|
109
|
+
except json.JSONDecodeError:
|
|
110
|
+
pass
|
|
111
|
+
else:
|
|
112
|
+
# Silence window — send prompt once claude has had time to init.
|
|
113
|
+
if not prompt_sent and (time.time() - start) >= INIT_WAIT_S:
|
|
114
|
+
os.write(master, (prompt + "\n").encode())
|
|
115
|
+
prompt_sent = True
|
|
116
|
+
print(
|
|
117
|
+
f"[pty_runner] prompt sent at t={time.time()-start:.1f}s",
|
|
118
|
+
file=sys.stderr,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
finally:
|
|
122
|
+
# Politely ask claude to exit, then hard-kill if needed.
|
|
123
|
+
try:
|
|
124
|
+
os.write(master, b"exit\n")
|
|
125
|
+
time.sleep(0.3)
|
|
126
|
+
except OSError:
|
|
127
|
+
pass
|
|
128
|
+
try:
|
|
129
|
+
proc.terminate()
|
|
130
|
+
proc.wait(timeout=5)
|
|
131
|
+
except Exception:
|
|
132
|
+
try:
|
|
133
|
+
proc.kill()
|
|
134
|
+
except Exception:
|
|
135
|
+
pass
|
|
136
|
+
try:
|
|
137
|
+
os.close(master)
|
|
138
|
+
except OSError:
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
142
|
+
content = "\n".join(json_lines) + ("\n" if json_lines else "")
|
|
143
|
+
output.write_text(content, encoding="utf-8")
|
|
144
|
+
print(
|
|
145
|
+
f"[pty_runner] wrote {len(json_lines)} transcript lines → {output}",
|
|
146
|
+
file=sys.stderr,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def main() -> int:
|
|
151
|
+
p = argparse.ArgumentParser(
|
|
152
|
+
description="Run claude interactively via PTY and capture stream-json transcript"
|
|
153
|
+
)
|
|
154
|
+
grp = p.add_mutually_exclusive_group(required=True)
|
|
155
|
+
grp.add_argument("--prompt", help="Prompt text")
|
|
156
|
+
grp.add_argument("--prompt-file", type=Path, help="File containing the prompt")
|
|
157
|
+
p.add_argument("--output", type=Path, required=True, help="Output .jsonl transcript file")
|
|
158
|
+
p.add_argument("--timeout", type=int, default=600, help="Hard timeout in seconds")
|
|
159
|
+
args = p.parse_args()
|
|
160
|
+
|
|
161
|
+
prompt = (
|
|
162
|
+
args.prompt_file.read_text(encoding="utf-8").strip()
|
|
163
|
+
if args.prompt_file
|
|
164
|
+
else args.prompt
|
|
165
|
+
)
|
|
166
|
+
run_interactive(prompt, args.output, args.timeout)
|
|
167
|
+
return 0
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
if __name__ == "__main__":
|
|
171
|
+
sys.exit(main())
|