@sk8metal/michi-cli 0.0.8 → 0.1.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/CHANGELOG.md +60 -0
- package/README.md +420 -49
- package/dist/scripts/__tests__/create-project.test.js +24 -28
- package/dist/scripts/__tests__/create-project.test.js.map +1 -1
- package/dist/scripts/__tests__/jira-transitions.test.d.ts +5 -0
- package/dist/scripts/__tests__/jira-transitions.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/jira-transitions.test.js +172 -0
- package/dist/scripts/__tests__/jira-transitions.test.js.map +1 -0
- package/dist/scripts/__tests__/multi-project-estimate.test.js +14 -15
- package/dist/scripts/__tests__/multi-project-estimate.test.js.map +1 -1
- package/dist/scripts/__tests__/setup-existing-project.test.js +146 -5
- package/dist/scripts/__tests__/setup-existing-project.test.js.map +1 -1
- package/dist/scripts/__tests__/setup-interactive.test.js +23 -17
- package/dist/scripts/__tests__/setup-interactive.test.js.map +1 -1
- package/dist/scripts/__tests__/spec-impl-workflow.test.d.ts +5 -0
- package/dist/scripts/__tests__/spec-impl-workflow.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/spec-impl-workflow.test.js +321 -0
- package/dist/scripts/__tests__/spec-impl-workflow.test.js.map +1 -0
- package/dist/scripts/__tests__/spec-loader.test.d.ts +5 -0
- package/dist/scripts/__tests__/spec-loader.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/spec-loader.test.js +153 -0
- package/dist/scripts/__tests__/spec-loader.test.js.map +1 -0
- package/dist/scripts/__tests__/validate-phase.test.js +26 -22
- package/dist/scripts/__tests__/validate-phase.test.js.map +1 -1
- package/dist/scripts/config/config-schema.d.ts +17 -0
- package/dist/scripts/config/config-schema.d.ts.map +1 -1
- package/dist/scripts/config/config-schema.js +55 -26
- package/dist/scripts/config/config-schema.js.map +1 -1
- package/dist/scripts/config-interactive.d.ts.map +1 -1
- package/dist/scripts/config-interactive.js +53 -38
- package/dist/scripts/config-interactive.js.map +1 -1
- package/dist/scripts/confluence-sync.d.ts.map +1 -1
- package/dist/scripts/confluence-sync.js +0 -11
- package/dist/scripts/confluence-sync.js.map +1 -1
- package/dist/scripts/constants/__tests__/environments.test.d.ts +2 -0
- package/dist/scripts/constants/__tests__/environments.test.d.ts.map +1 -0
- package/dist/scripts/constants/__tests__/environments.test.js +125 -0
- package/dist/scripts/constants/__tests__/environments.test.js.map +1 -0
- package/dist/scripts/constants/__tests__/languages.test.d.ts +2 -0
- package/dist/scripts/constants/__tests__/languages.test.d.ts.map +1 -0
- package/dist/scripts/constants/__tests__/languages.test.js +82 -0
- package/dist/scripts/constants/__tests__/languages.test.js.map +1 -0
- package/dist/scripts/constants/environments.d.ts +33 -0
- package/dist/scripts/constants/environments.d.ts.map +1 -0
- package/dist/scripts/constants/environments.js +64 -0
- package/dist/scripts/constants/environments.js.map +1 -0
- package/dist/scripts/constants/languages.d.ts +23 -0
- package/dist/scripts/constants/languages.d.ts.map +1 -0
- package/dist/scripts/constants/languages.js +53 -0
- package/dist/scripts/constants/languages.js.map +1 -0
- package/dist/scripts/constants/test-commands.d.ts +36 -0
- package/dist/scripts/constants/test-commands.d.ts.map +1 -0
- package/dist/scripts/constants/test-commands.js +70 -0
- package/dist/scripts/constants/test-commands.js.map +1 -0
- package/dist/scripts/create-project.d.ts +4 -0
- package/dist/scripts/create-project.d.ts.map +1 -1
- package/dist/scripts/create-project.js +51 -22
- package/dist/scripts/create-project.js.map +1 -1
- package/dist/scripts/jira-sync.d.ts +89 -3
- package/dist/scripts/jira-sync.d.ts.map +1 -1
- package/dist/scripts/jira-sync.js +366 -96
- package/dist/scripts/jira-sync.js.map +1 -1
- package/dist/scripts/markdown-to-confluence.js +1 -1
- package/dist/scripts/markdown-to-confluence.js.map +1 -1
- package/dist/scripts/phase-runner.d.ts +1 -1
- package/dist/scripts/phase-runner.d.ts.map +1 -1
- package/dist/scripts/phase-runner.js +809 -13
- package/dist/scripts/phase-runner.js.map +1 -1
- package/dist/scripts/pr-automation.d.ts.map +1 -1
- package/dist/scripts/pr-automation.js.map +1 -1
- package/dist/scripts/pre-flight-check.js +1 -1
- package/dist/scripts/pre-flight-check.js.map +1 -1
- package/dist/scripts/setup-existing-project.d.ts +3 -1
- package/dist/scripts/setup-existing-project.d.ts.map +1 -1
- package/dist/scripts/setup-existing-project.js +165 -78
- package/dist/scripts/setup-existing-project.js.map +1 -1
- package/dist/scripts/setup-interactive.js +3 -3
- package/dist/scripts/setup-interactive.js.map +1 -1
- package/dist/scripts/spec-impl-workflow.d.ts +94 -0
- package/dist/scripts/spec-impl-workflow.d.ts.map +1 -0
- package/dist/scripts/spec-impl-workflow.js +354 -0
- package/dist/scripts/spec-impl-workflow.js.map +1 -0
- package/dist/scripts/template/__tests__/renderer.test.d.ts +2 -0
- package/dist/scripts/template/__tests__/renderer.test.d.ts.map +1 -0
- package/dist/scripts/template/__tests__/renderer.test.js +165 -0
- package/dist/scripts/template/__tests__/renderer.test.js.map +1 -0
- package/dist/scripts/template/renderer.d.ts +70 -0
- package/dist/scripts/template/renderer.d.ts.map +1 -0
- package/dist/scripts/template/renderer.js +99 -0
- package/dist/scripts/template/renderer.js.map +1 -0
- package/dist/scripts/test-execution-generator.d.ts +52 -0
- package/dist/scripts/test-execution-generator.d.ts.map +1 -0
- package/dist/scripts/test-execution-generator.js +576 -0
- package/dist/scripts/test-execution-generator.js.map +1 -0
- package/dist/scripts/test-interactive.d.ts +10 -0
- package/dist/scripts/test-interactive.d.ts.map +1 -0
- package/dist/scripts/test-interactive.js +627 -0
- package/dist/scripts/test-interactive.js.map +1 -0
- package/dist/scripts/test-new-features.d.ts +5 -0
- package/dist/scripts/test-new-features.d.ts.map +1 -0
- package/dist/scripts/test-new-features.js +145 -0
- package/dist/scripts/test-new-features.js.map +1 -0
- package/dist/scripts/test-spec-generator.d.ts +29 -0
- package/dist/scripts/test-spec-generator.d.ts.map +1 -0
- package/dist/scripts/test-spec-generator.js +494 -0
- package/dist/scripts/test-spec-generator.js.map +1 -0
- package/dist/scripts/test-workflow-stages.d.ts +6 -0
- package/dist/scripts/test-workflow-stages.d.ts.map +1 -0
- package/dist/scripts/test-workflow-stages.js +43 -0
- package/dist/scripts/test-workflow-stages.js.map +1 -0
- package/dist/scripts/utils/__tests__/aidlc-parser.test.d.ts +5 -0
- package/dist/scripts/utils/__tests__/aidlc-parser.test.d.ts.map +1 -0
- package/dist/scripts/utils/__tests__/aidlc-parser.test.js +315 -0
- package/dist/scripts/utils/__tests__/aidlc-parser.test.js.map +1 -0
- package/dist/scripts/utils/__tests__/business-days.test.d.ts +5 -0
- package/dist/scripts/utils/__tests__/business-days.test.d.ts.map +1 -0
- package/dist/scripts/utils/__tests__/business-days.test.js +171 -0
- package/dist/scripts/utils/__tests__/business-days.test.js.map +1 -0
- package/dist/scripts/utils/__tests__/config-loader.test.js +1 -1
- package/dist/scripts/utils/__tests__/config-loader.test.js.map +1 -1
- package/dist/scripts/utils/__tests__/config-validator.test.js +164 -35
- package/dist/scripts/utils/__tests__/config-validator.test.js.map +1 -1
- package/dist/scripts/utils/__tests__/env-config.test.d.ts +5 -0
- package/dist/scripts/utils/__tests__/env-config.test.d.ts.map +1 -0
- package/dist/scripts/utils/__tests__/env-config.test.js +218 -0
- package/dist/scripts/utils/__tests__/env-config.test.js.map +1 -0
- package/dist/scripts/utils/__tests__/jira-issue-type-fetcher.test.d.ts +5 -0
- package/dist/scripts/utils/__tests__/jira-issue-type-fetcher.test.d.ts.map +1 -0
- package/dist/scripts/utils/__tests__/jira-issue-type-fetcher.test.js +202 -0
- package/dist/scripts/utils/__tests__/jira-issue-type-fetcher.test.js.map +1 -0
- package/dist/scripts/utils/__tests__/tasks-converter.test.d.ts +5 -0
- package/dist/scripts/utils/__tests__/tasks-converter.test.d.ts.map +1 -0
- package/dist/scripts/utils/__tests__/tasks-converter.test.js +500 -0
- package/dist/scripts/utils/__tests__/tasks-converter.test.js.map +1 -0
- package/dist/scripts/utils/__tests__/tasks-format-validator.test.d.ts +5 -0
- package/dist/scripts/utils/__tests__/tasks-format-validator.test.d.ts.map +1 -0
- package/dist/scripts/utils/__tests__/tasks-format-validator.test.js +314 -0
- package/dist/scripts/utils/__tests__/tasks-format-validator.test.js.map +1 -0
- package/dist/scripts/utils/__tests__/test-runner.test.d.ts +5 -0
- package/dist/scripts/utils/__tests__/test-runner.test.d.ts.map +1 -0
- package/dist/scripts/utils/__tests__/test-runner.test.js +64 -0
- package/dist/scripts/utils/__tests__/test-runner.test.js.map +1 -0
- package/dist/scripts/utils/aidlc-parser.d.ts +86 -0
- package/dist/scripts/utils/aidlc-parser.d.ts.map +1 -0
- package/dist/scripts/utils/aidlc-parser.js +208 -0
- package/dist/scripts/utils/aidlc-parser.js.map +1 -0
- package/dist/scripts/utils/business-days.d.ts +52 -0
- package/dist/scripts/utils/business-days.d.ts.map +1 -0
- package/dist/scripts/utils/business-days.js +98 -0
- package/dist/scripts/utils/business-days.js.map +1 -0
- package/dist/scripts/utils/ci-generator.d.ts +14 -0
- package/dist/scripts/utils/ci-generator.d.ts.map +1 -0
- package/dist/scripts/utils/ci-generator.js +61 -0
- package/dist/scripts/utils/ci-generator.js.map +1 -0
- package/dist/scripts/utils/config-loader.js +2 -2
- package/dist/scripts/utils/config-loader.js.map +1 -1
- package/dist/scripts/utils/config-validator.d.ts +7 -1
- package/dist/scripts/utils/config-validator.d.ts.map +1 -1
- package/dist/scripts/utils/config-validator.js +136 -23
- package/dist/scripts/utils/config-validator.js.map +1 -1
- package/dist/scripts/utils/confluence-approval.d.ts +46 -0
- package/dist/scripts/utils/confluence-approval.d.ts.map +1 -0
- package/dist/scripts/utils/confluence-approval.js +118 -0
- package/dist/scripts/utils/confluence-approval.js.map +1 -0
- package/dist/scripts/utils/confluence-hierarchy.d.ts.map +1 -1
- package/dist/scripts/utils/confluence-hierarchy.js +1 -1
- package/dist/scripts/utils/confluence-hierarchy.js.map +1 -1
- package/dist/scripts/utils/docker-generator.d.ts +9 -0
- package/dist/scripts/utils/docker-generator.d.ts.map +1 -0
- package/dist/scripts/utils/docker-generator.js +132 -0
- package/dist/scripts/utils/docker-generator.js.map +1 -0
- package/dist/scripts/utils/docker-requirement-detector.d.ts +15 -0
- package/dist/scripts/utils/docker-requirement-detector.d.ts.map +1 -0
- package/dist/scripts/utils/docker-requirement-detector.js +124 -0
- package/dist/scripts/utils/docker-requirement-detector.js.map +1 -0
- package/dist/scripts/utils/env-config.d.ts +54 -0
- package/dist/scripts/utils/env-config.d.ts.map +1 -0
- package/dist/scripts/utils/env-config.js +414 -0
- package/dist/scripts/utils/env-config.js.map +1 -0
- package/dist/scripts/utils/jira-issue-type-fetcher.d.ts +70 -0
- package/dist/scripts/utils/jira-issue-type-fetcher.d.ts.map +1 -0
- package/dist/scripts/utils/jira-issue-type-fetcher.js +147 -0
- package/dist/scripts/utils/jira-issue-type-fetcher.js.map +1 -0
- package/dist/scripts/utils/language-detector.d.ts +14 -0
- package/dist/scripts/utils/language-detector.d.ts.map +1 -0
- package/dist/scripts/utils/language-detector.js +119 -0
- package/dist/scripts/utils/language-detector.js.map +1 -0
- package/dist/scripts/utils/markdown-parser.d.ts +55 -0
- package/dist/scripts/utils/markdown-parser.d.ts.map +1 -0
- package/dist/scripts/utils/markdown-parser.js +289 -0
- package/dist/scripts/utils/markdown-parser.js.map +1 -0
- package/dist/scripts/utils/project-detector.d.ts +17 -0
- package/dist/scripts/utils/project-detector.d.ts.map +1 -0
- package/dist/scripts/utils/project-detector.js +166 -0
- package/dist/scripts/utils/project-detector.js.map +1 -0
- package/dist/scripts/utils/project-finder.js +2 -2
- package/dist/scripts/utils/project-finder.js.map +1 -1
- package/dist/scripts/utils/release-notes-generator.d.ts +56 -0
- package/dist/scripts/utils/release-notes-generator.d.ts.map +1 -0
- package/dist/scripts/utils/release-notes-generator.js +162 -0
- package/dist/scripts/utils/release-notes-generator.js.map +1 -0
- package/dist/scripts/utils/spec-loader.d.ts +79 -0
- package/dist/scripts/utils/spec-loader.d.ts.map +1 -0
- package/dist/scripts/utils/spec-loader.js +80 -0
- package/dist/scripts/utils/spec-loader.js.map +1 -0
- package/dist/scripts/utils/spec-updater.d.ts +7 -0
- package/dist/scripts/utils/spec-updater.d.ts.map +1 -1
- package/dist/scripts/utils/spec-updater.js.map +1 -1
- package/dist/scripts/utils/tasks-converter.d.ts +57 -0
- package/dist/scripts/utils/tasks-converter.d.ts.map +1 -0
- package/dist/scripts/utils/tasks-converter.js +322 -0
- package/dist/scripts/utils/tasks-converter.js.map +1 -0
- package/dist/scripts/utils/tasks-format-validator.d.ts +36 -0
- package/dist/scripts/utils/tasks-format-validator.d.ts.map +1 -0
- package/dist/scripts/utils/tasks-format-validator.js +158 -0
- package/dist/scripts/utils/tasks-format-validator.js.map +1 -0
- package/dist/scripts/utils/template-applier.d.ts +37 -0
- package/dist/scripts/utils/template-applier.d.ts.map +1 -0
- package/dist/scripts/utils/template-applier.js +129 -0
- package/dist/scripts/utils/template-applier.js.map +1 -0
- package/dist/scripts/utils/template-finder.d.ts +37 -0
- package/dist/scripts/utils/template-finder.d.ts.map +1 -0
- package/dist/scripts/utils/template-finder.js +63 -0
- package/dist/scripts/utils/template-finder.js.map +1 -0
- package/dist/scripts/utils/test-config-generator.d.ts +12 -0
- package/dist/scripts/utils/test-config-generator.d.ts.map +1 -0
- package/dist/scripts/utils/test-config-generator.js +185 -0
- package/dist/scripts/utils/test-config-generator.js.map +1 -0
- package/dist/scripts/utils/test-runner.d.ts +31 -0
- package/dist/scripts/utils/test-runner.d.ts.map +1 -0
- package/dist/scripts/utils/test-runner.js +103 -0
- package/dist/scripts/utils/test-runner.js.map +1 -0
- package/dist/scripts/validate-phase.d.ts +1 -1
- package/dist/scripts/validate-phase.d.ts.map +1 -1
- package/dist/scripts/validate-phase.js +153 -5
- package/dist/scripts/validate-phase.js.map +1 -1
- package/dist/scripts/workflow-orchestrator.d.ts +8 -0
- package/dist/scripts/workflow-orchestrator.d.ts.map +1 -1
- package/dist/scripts/workflow-orchestrator.js +108 -7
- package/dist/scripts/workflow-orchestrator.js.map +1 -1
- package/dist/src/__tests__/integration/internationalization.test.d.ts +8 -0
- package/dist/src/__tests__/integration/internationalization.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/internationalization.test.js +333 -0
- package/dist/src/__tests__/integration/internationalization.test.js.map +1 -0
- package/dist/src/__tests__/integration/setup/claude-agent.test.d.ts +5 -0
- package/dist/src/__tests__/integration/setup/claude-agent.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/setup/claude-agent.test.js +122 -0
- package/dist/src/__tests__/integration/setup/claude-agent.test.js.map +1 -0
- package/dist/src/__tests__/integration/setup/claude.test.d.ts +5 -0
- package/dist/src/__tests__/integration/setup/claude.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/setup/claude.test.js +111 -0
- package/dist/src/__tests__/integration/setup/claude.test.js.map +1 -0
- package/dist/src/__tests__/integration/setup/cursor.test.d.ts +5 -0
- package/dist/src/__tests__/integration/setup/cursor.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/setup/cursor.test.js +166 -0
- package/dist/src/__tests__/integration/setup/cursor.test.js.map +1 -0
- package/dist/src/__tests__/integration/setup/helpers/fs-assertions.d.ts +32 -0
- package/dist/src/__tests__/integration/setup/helpers/fs-assertions.d.ts.map +1 -0
- package/dist/src/__tests__/integration/setup/helpers/fs-assertions.js +72 -0
- package/dist/src/__tests__/integration/setup/helpers/fs-assertions.js.map +1 -0
- package/dist/src/__tests__/integration/setup/helpers/test-project.d.ts +38 -0
- package/dist/src/__tests__/integration/setup/helpers/test-project.d.ts.map +1 -0
- package/dist/src/__tests__/integration/setup/helpers/test-project.js +83 -0
- package/dist/src/__tests__/integration/setup/helpers/test-project.js.map +1 -0
- package/dist/src/__tests__/integration/setup/validation.test.d.ts +5 -0
- package/dist/src/__tests__/integration/setup/validation.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/setup/validation.test.js +301 -0
- package/dist/src/__tests__/integration/setup/validation.test.js.map +1 -0
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +228 -18
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/setup-existing.d.ts +25 -0
- package/dist/src/commands/setup-existing.d.ts.map +1 -0
- package/dist/src/commands/setup-existing.js +695 -0
- package/dist/src/commands/setup-existing.js.map +1 -0
- package/dist/vitest.config.d.ts.map +1 -1
- package/dist/vitest.config.js +4 -3
- package/dist/vitest.config.js.map +1 -1
- package/docs/README.md +3 -1
- package/docs/context.md +59 -0
- package/docs/design-issue-55.md +240 -0
- package/docs/design-issue-56.md +181 -0
- package/docs/michi-development/testing/manual-verification-flow.md +2242 -0
- package/docs/michi-development/testing/pre-publish-checklist.md +560 -0
- package/docs/plan.md +275 -0
- package/docs/user-guide/getting-started/github-token-setup.md +509 -0
- package/docs/{getting-started → user-guide/getting-started}/new-repository-setup.md +108 -28
- package/docs/{getting-started → user-guide/getting-started}/quick-start.md +73 -6
- package/docs/{getting-started → user-guide/getting-started}/setup.md +278 -3
- package/docs/{guides → user-guide/guides}/customization.md +3 -3
- package/docs/user-guide/guides/internationalization.md +540 -0
- package/docs/{guides → user-guide/guides}/multi-project.md +2 -2
- package/docs/{guides → user-guide/guides}/phase-automation.md +67 -9
- package/docs/user-guide/guides/workflow.md +582 -0
- package/docs/user-guide/hands-on/README.md +142 -0
- package/docs/user-guide/hands-on/claude-agent-setup.md +455 -0
- package/docs/user-guide/hands-on/claude-setup.md +398 -0
- package/docs/user-guide/hands-on/cursor-setup.md +352 -0
- package/docs/user-guide/hands-on/troubleshooting.md +964 -0
- package/docs/user-guide/hands-on/verification-checklist.md +438 -0
- package/docs/user-guide/hands-on/workflow-walkthrough.md +906 -0
- package/docs/user-guide/reference/config.md +564 -0
- package/docs/{reference → user-guide/reference}/quick-reference.md +75 -53
- package/docs/user-guide/release/ci-setup.md +541 -0
- package/docs/user-guide/release/release-flow.md +476 -0
- package/docs/user-guide/templates/test-specs/README.md +173 -0
- package/docs/user-guide/templates/test-specs/e2e-test-spec-template.md +547 -0
- package/docs/user-guide/templates/test-specs/integration-test-spec-template.md +435 -0
- package/docs/user-guide/templates/test-specs/performance-test-spec-template.md +454 -0
- package/docs/user-guide/templates/test-specs/security-test-spec-template.md +664 -0
- package/docs/user-guide/templates/test-specs/unit-test-spec-template.md +328 -0
- package/docs/user-guide/testing/integration-tests.md +312 -0
- package/docs/user-guide/testing/tdd-cycle.md +349 -0
- package/docs/user-guide/testing/test-execution-flow.md +396 -0
- package/docs/user-guide/testing/test-failure-handling.md +521 -0
- package/docs/user-guide/testing/test-planning-flow.md +181 -0
- package/docs/user-guide/testing-strategy.md +185 -0
- package/docs/verification-guide.md +518 -0
- package/package.json +13 -3
- package/scripts/__tests__/create-project.test.ts +67 -49
- package/scripts/__tests__/jira-transitions.test.ts +225 -0
- package/scripts/__tests__/multi-project-estimate.test.ts +36 -30
- package/scripts/__tests__/setup-existing-project.test.ts +171 -6
- package/scripts/__tests__/setup-interactive.test.ts +52 -46
- package/scripts/__tests__/spec-impl-workflow.test.ts +429 -0
- package/scripts/__tests__/spec-loader.test.ts +199 -0
- package/scripts/__tests__/validate-phase.test.ts +78 -54
- package/scripts/config/config-schema.ts +89 -50
- package/scripts/config-interactive.ts +191 -136
- package/scripts/confluence-sync.ts +0 -12
- package/scripts/constants/__tests__/environments.test.ts +146 -0
- package/scripts/constants/__tests__/languages.test.ts +100 -0
- package/scripts/constants/environments.ts +81 -0
- package/scripts/constants/languages.ts +70 -0
- package/scripts/constants/test-commands.ts +96 -0
- package/scripts/create-project.ts +52 -22
- package/scripts/jira-sync.ts +767 -232
- package/scripts/markdown-to-confluence.ts +1 -1
- package/scripts/phase-runner.ts +1056 -63
- package/scripts/pr-automation.ts +0 -1
- package/scripts/pre-flight-check.ts +1 -1
- package/scripts/pre-publish-check.sh +311 -0
- package/scripts/quick-verify.sh +115 -0
- package/scripts/setup-existing-project.ts +306 -143
- package/scripts/setup-interactive.ts +4 -4
- package/scripts/spec-impl-workflow.ts +505 -0
- package/scripts/template/__tests__/renderer.test.ts +206 -0
- package/scripts/template/renderer.ts +133 -0
- package/scripts/test-execution-generator.ts +695 -0
- package/scripts/test-interactive.ts +779 -0
- package/scripts/test-new-features.ts +168 -0
- package/scripts/test-npm-package.sh +345 -0
- package/scripts/test-spec-generator.ts +574 -0
- package/scripts/test-workflow-stages.ts +53 -0
- package/scripts/utils/__tests__/aidlc-parser.test.ts +349 -0
- package/scripts/utils/__tests__/business-days.test.ts +214 -0
- package/scripts/utils/__tests__/config-loader.test.ts +1 -1
- package/scripts/utils/__tests__/config-validator.test.ts +309 -88
- package/scripts/utils/__tests__/env-config.test.ts +259 -0
- package/scripts/utils/__tests__/jira-issue-type-fetcher.test.ts +272 -0
- package/scripts/utils/__tests__/tasks-converter.test.ts +582 -0
- package/scripts/utils/__tests__/tasks-format-validator.test.ts +338 -0
- package/scripts/utils/__tests__/test-runner.test.ts +77 -0
- package/scripts/utils/aidlc-parser.ts +289 -0
- package/scripts/utils/business-days.ts +115 -0
- package/scripts/utils/ci-generator.ts +84 -0
- package/scripts/utils/config-loader.ts +2 -2
- package/scripts/utils/config-validator.ts +304 -117
- package/scripts/utils/confluence-approval.ts +167 -0
- package/scripts/utils/confluence-hierarchy.ts +2 -4
- package/scripts/utils/docker-generator.ts +151 -0
- package/scripts/utils/docker-requirement-detector.ts +153 -0
- package/scripts/utils/env-config.ts +526 -0
- package/scripts/utils/jira-issue-type-fetcher.ts +199 -0
- package/scripts/utils/language-detector.ts +139 -0
- package/scripts/utils/markdown-parser.ts +376 -0
- package/scripts/utils/project-detector.ts +192 -0
- package/scripts/utils/project-finder.ts +2 -2
- package/scripts/utils/release-notes-generator.ts +210 -0
- package/scripts/utils/spec-loader.ts +125 -0
- package/scripts/utils/spec-updater.ts +8 -1
- package/scripts/utils/tasks-converter.ts +601 -0
- package/scripts/utils/tasks-format-validator.ts +193 -0
- package/scripts/utils/template-applier.ts +202 -0
- package/scripts/utils/template-finder.ts +75 -0
- package/scripts/utils/test-config-generator.ts +210 -0
- package/scripts/utils/test-runner.ts +133 -0
- package/scripts/validate-phase.ts +186 -9
- package/scripts/workflow-orchestrator.ts +130 -12
- package/templates/ci/github-actions/java.yml +54 -0
- package/templates/ci/github-actions/nodejs.yml +46 -0
- package/templates/ci/github-actions/php.yml +52 -0
- package/templates/ci/screwdriver/java.yaml +17 -0
- package/templates/ci/screwdriver/nodejs.yaml +17 -0
- package/templates/ci/screwdriver/php.yaml +20 -0
- package/templates/claude/commands/kiro/kiro-spec-impl.md +244 -0
- package/templates/claude/commands/kiro/kiro-spec-tasks.md +354 -0
- package/templates/claude/commands/michi/confluence-sync.md +38 -0
- package/templates/claude/commands/michi/project-switch.md +36 -0
- package/templates/claude/rules/atlassian-integration.md +35 -0
- package/templates/claude/rules/michi-core.md +54 -0
- package/templates/claude-agent/README.md +31 -0
- package/templates/claude-agent/agents/.gitkeep +0 -0
- package/templates/claude-agent/agents/designer.md +79 -0
- package/templates/claude-agent/agents/developer.md +68 -0
- package/templates/claude-agent/agents/manager-agent.md +59 -0
- package/templates/claude-agent/agents/tester.md +101 -0
- package/templates/claude-agent/commands/kiro/.gitkeep +0 -0
- package/templates/claude-agent/commands/kiro/kiro-spec-impl.md +244 -0
- package/templates/claude-agent/commands/kiro/kiro-spec-tasks.md +354 -0
- package/templates/cline/rules/atlassian-integration.md +36 -0
- package/templates/cline/rules/michi-core.md +56 -0
- package/templates/codex/AGENTS.override.md +277 -0
- package/templates/codex/prompts/confluence-sync.md +177 -0
- package/templates/codex/rules/README.md +210 -0
- package/templates/common/.kiro/project.json.template +21 -0
- package/templates/cursor/commands/kiro/kiro-spec-impl.md +244 -0
- package/templates/cursor/commands/kiro/kiro-spec-tasks.md +354 -0
- package/templates/cursor/commands/michi/confluence-sync.md +76 -0
- package/templates/cursor/commands/michi/project-switch.md +69 -0
- package/templates/cursor/rules/atlassian-mcp.mdc +188 -0
- package/templates/cursor/rules/github-ssot.mdc +151 -0
- package/templates/cursor/rules/multi-project.mdc +81 -0
- package/templates/gemini/commands/README.md +41 -0
- package/templates/gemini/rules/GEMINI.md +80 -0
- package/docs/guides/workflow.md +0 -342
- package/docs/reference/config.md +0 -545
- package/scripts/setup-env.sh +0 -52
- package/scripts/setup-existing.sh +0 -152
- /package/docs/{contributing → michi-development/contributing}/development.md +0 -0
- /package/docs/{contributing → michi-development/contributing}/release.md +0 -0
- /package/docs/{testing-strategy.md → michi-development/testing-strategy.md} +0 -0
- /package/docs/{reference → user-guide/reference}/tasks-template.md +0 -0
package/scripts/jira-sync.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* JIRA連携スクリプト
|
|
3
3
|
* tasks.md から JIRA Epic/Story/Subtask を自動作成
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* 【重要】Epic Link について:
|
|
6
6
|
* JIRA Cloud では Story を Epic に紐付けるには、Epic Link カスタムフィールド
|
|
7
7
|
* (通常 customfield_10014)を使用する必要があります。
|
|
8
|
-
*
|
|
8
|
+
*
|
|
9
9
|
* 現在の実装では parent フィールドを使用していますが、これは Subtask 専用です。
|
|
10
10
|
* Story 作成時に 400 エラーが発生する可能性があります。
|
|
11
|
-
*
|
|
11
|
+
*
|
|
12
12
|
* 対処方法:
|
|
13
13
|
* 1. JIRA 管理画面で Epic Link のカスタムフィールドIDを確認
|
|
14
14
|
* 2. 環境変数 JIRA_EPIC_LINK_FIELD に設定(例: customfield_10014)
|
|
15
15
|
* 3. または、Story 作成後に手動で Epic Link を設定
|
|
16
|
-
*
|
|
16
|
+
*
|
|
17
17
|
* 参考: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/#api-rest-api-3-issue-post
|
|
18
18
|
*/
|
|
19
19
|
|
|
@@ -24,8 +24,11 @@ import { config } from 'dotenv';
|
|
|
24
24
|
import { loadProjectMeta } from './utils/project-meta.js';
|
|
25
25
|
import { validateFeatureNameOrThrow } from './utils/feature-name-validator.js';
|
|
26
26
|
import { getConfig, getConfigPath } from './utils/config-loader.js';
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
27
|
+
import { validateForJiraSyncAsync } from './utils/config-validator.js';
|
|
28
|
+
import {
|
|
29
|
+
updateSpecJsonAfterJiraSync,
|
|
30
|
+
type SpecJson,
|
|
31
|
+
} from './utils/spec-updater.js';
|
|
29
32
|
|
|
30
33
|
config();
|
|
31
34
|
|
|
@@ -33,7 +36,7 @@ config();
|
|
|
33
36
|
* リクエスト間のスリープ処理(レートリミット対策)
|
|
34
37
|
*/
|
|
35
38
|
function sleep(ms: number): Promise<void> {
|
|
36
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
39
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
/**
|
|
@@ -44,6 +47,73 @@ function getRequestDelay(): number {
|
|
|
44
47
|
return parseInt(process.env.ATLASSIAN_REQUEST_DELAY || '500', 10);
|
|
45
48
|
}
|
|
46
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Atlassian Document Format (ADF) の型定義
|
|
52
|
+
*/
|
|
53
|
+
interface ADFNode {
|
|
54
|
+
type: string;
|
|
55
|
+
attrs?: Record<string, unknown>;
|
|
56
|
+
content?: ADFNode[];
|
|
57
|
+
text?: string;
|
|
58
|
+
marks?: Array<{ type: string; attrs?: Record<string, unknown> }>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface ADFDocument {
|
|
62
|
+
version: number;
|
|
63
|
+
type: 'doc';
|
|
64
|
+
content: ADFNode[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* JIRA Issue型定義(必要最小限)
|
|
69
|
+
*/
|
|
70
|
+
interface JIRAIssue {
|
|
71
|
+
id: string;
|
|
72
|
+
key: string;
|
|
73
|
+
self: string;
|
|
74
|
+
fields: {
|
|
75
|
+
summary: string;
|
|
76
|
+
issuetype?: { id: string; name: string };
|
|
77
|
+
status?: { name: string };
|
|
78
|
+
[key: string]: unknown;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* JIRA Issue作成/更新ペイロード型
|
|
84
|
+
*/
|
|
85
|
+
interface JIRAIssuePayload {
|
|
86
|
+
fields: {
|
|
87
|
+
project: { key: string };
|
|
88
|
+
summary: string;
|
|
89
|
+
description?: ADFDocument;
|
|
90
|
+
issuetype: { id: string };
|
|
91
|
+
labels?: string[];
|
|
92
|
+
parent?: { key: string };
|
|
93
|
+
[key: string]: unknown;
|
|
94
|
+
};
|
|
95
|
+
update?: Record<string, unknown>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* JIRA Issue作成レスポンス型
|
|
100
|
+
*/
|
|
101
|
+
interface JIRAIssueCreateResponse {
|
|
102
|
+
id: string;
|
|
103
|
+
key: string;
|
|
104
|
+
self: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* JIRA Issue Type型
|
|
109
|
+
*/
|
|
110
|
+
interface JIRAIssueType {
|
|
111
|
+
id: string;
|
|
112
|
+
name: string;
|
|
113
|
+
description?: string;
|
|
114
|
+
subtask: boolean;
|
|
115
|
+
}
|
|
116
|
+
|
|
47
117
|
/**
|
|
48
118
|
* Storyの詳細情報を抽出
|
|
49
119
|
*/
|
|
@@ -59,159 +129,181 @@ interface StoryDetails {
|
|
|
59
129
|
dueDate?: string;
|
|
60
130
|
}
|
|
61
131
|
|
|
62
|
-
function extractStoryDetails(
|
|
132
|
+
function extractStoryDetails(
|
|
133
|
+
tasksContent: string,
|
|
134
|
+
storyTitle: string,
|
|
135
|
+
): StoryDetails {
|
|
63
136
|
const details: StoryDetails = { title: storyTitle };
|
|
64
|
-
|
|
137
|
+
|
|
65
138
|
// Story セクションを抽出(ReDoS対策: [\s\S]*? → [^]*? に変更)
|
|
66
139
|
const escapedTitle = storyTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
67
|
-
const storyPattern = new RegExp(
|
|
140
|
+
const storyPattern = new RegExp(
|
|
141
|
+
`### Story [\\d.]+: ${escapedTitle}\\n([^]*?)(?=\\n### Story|\\n## Phase|$)`,
|
|
142
|
+
'i',
|
|
143
|
+
);
|
|
68
144
|
const storyMatch = tasksContent.match(storyPattern);
|
|
69
|
-
|
|
145
|
+
|
|
70
146
|
if (!storyMatch) return details;
|
|
71
|
-
|
|
147
|
+
|
|
72
148
|
const storySection = storyMatch[1];
|
|
73
|
-
|
|
149
|
+
|
|
74
150
|
// 優先度抽出
|
|
75
151
|
const priorityMatch = storySection.match(/\*\*優先度\*\*:\s*(.+)/);
|
|
76
152
|
if (priorityMatch) details.priority = priorityMatch[1].trim();
|
|
77
|
-
|
|
153
|
+
|
|
78
154
|
// 見積もり抽出
|
|
79
155
|
const estimateMatch = storySection.match(/\*\*見積もり\*\*:\s*(.+)/);
|
|
80
156
|
if (estimateMatch) details.estimate = estimateMatch[1].trim();
|
|
81
|
-
|
|
157
|
+
|
|
82
158
|
// 担当抽出
|
|
83
159
|
const assigneeMatch = storySection.match(/\*\*担当\*\*:\s*(.+)/);
|
|
84
160
|
if (assigneeMatch) details.assignee = assigneeMatch[1].trim();
|
|
85
|
-
|
|
161
|
+
|
|
86
162
|
// 期限抽出
|
|
87
|
-
const dueDateMatch = storySection.match(
|
|
163
|
+
const dueDateMatch = storySection.match(
|
|
164
|
+
/\*\*期限\*\*:\s*(\d{4}-\d{2}-\d{2})/,
|
|
165
|
+
);
|
|
88
166
|
if (dueDateMatch) details.dueDate = dueDateMatch[1];
|
|
89
|
-
|
|
167
|
+
|
|
90
168
|
// 説明抽出(改行あり・なし両方に対応)
|
|
91
|
-
const descriptionMatch = storySection.match(
|
|
169
|
+
const descriptionMatch = storySection.match(
|
|
170
|
+
/\*\*説明\*\*:\s*\n?(.+?)(?=\n\*\*|$)/s,
|
|
171
|
+
);
|
|
92
172
|
if (descriptionMatch) details.description = descriptionMatch[1].trim();
|
|
93
|
-
|
|
173
|
+
|
|
94
174
|
// 完了条件抽出
|
|
95
|
-
const criteriaMatch = storySection.match(
|
|
175
|
+
const criteriaMatch = storySection.match(
|
|
176
|
+
/\*\*完了条件\*\*:\s*\n((?:- \[.\].*\n?)+)/,
|
|
177
|
+
);
|
|
96
178
|
if (criteriaMatch) {
|
|
97
179
|
details.acceptanceCriteria = criteriaMatch[1]
|
|
98
180
|
.split('\n')
|
|
99
|
-
.filter(line => line.trim().startsWith('- ['))
|
|
100
|
-
.map(line => line.replace(/^- \[.\]\s*/, '').trim())
|
|
101
|
-
.filter(line => line.length > 0);
|
|
181
|
+
.filter((line) => line.trim().startsWith('- ['))
|
|
182
|
+
.map((line) => line.replace(/^- \[.\]\s*/, '').trim())
|
|
183
|
+
.filter((line) => line.length > 0);
|
|
102
184
|
}
|
|
103
|
-
|
|
185
|
+
|
|
104
186
|
// サブタスク抽出
|
|
105
|
-
const subtasksMatch = storySection.match(
|
|
187
|
+
const subtasksMatch = storySection.match(
|
|
188
|
+
/\*\*サブタスク\*\*:\s*\n((?:- \[.\].*\n?)+)/,
|
|
189
|
+
);
|
|
106
190
|
if (subtasksMatch) {
|
|
107
191
|
details.subtasks = subtasksMatch[1]
|
|
108
192
|
.split('\n')
|
|
109
|
-
.filter(line => line.trim().startsWith('- ['))
|
|
110
|
-
.map(line => line.replace(/^- \[.\]\s*/, '').trim())
|
|
111
|
-
.filter(line => line.length > 0);
|
|
193
|
+
.filter((line) => line.trim().startsWith('- ['))
|
|
194
|
+
.map((line) => line.replace(/^- \[.\]\s*/, '').trim())
|
|
195
|
+
.filter((line) => line.length > 0);
|
|
112
196
|
}
|
|
113
|
-
|
|
197
|
+
|
|
114
198
|
// 依存関係抽出
|
|
115
199
|
const dependenciesMatch = storySection.match(/\*\*依存関係\*\*:\s*(.+)/);
|
|
116
200
|
if (dependenciesMatch) details.dependencies = dependenciesMatch[1].trim();
|
|
117
|
-
|
|
201
|
+
|
|
118
202
|
return details;
|
|
119
203
|
}
|
|
120
204
|
|
|
121
205
|
/**
|
|
122
206
|
* リッチなADF形式を生成
|
|
123
207
|
*/
|
|
124
|
-
function createRichADF(
|
|
125
|
-
|
|
126
|
-
|
|
208
|
+
function createRichADF(
|
|
209
|
+
details: StoryDetails,
|
|
210
|
+
phaseLabel: string,
|
|
211
|
+
githubUrl: string,
|
|
212
|
+
): ADFDocument {
|
|
213
|
+
const content: ADFNode[] = [];
|
|
214
|
+
|
|
127
215
|
// 説明セクション
|
|
128
216
|
if (details.description) {
|
|
129
217
|
content.push({
|
|
130
218
|
type: 'heading',
|
|
131
219
|
attrs: { level: 2 },
|
|
132
|
-
content: [{ type: 'text', text: '説明' }]
|
|
220
|
+
content: [{ type: 'text', text: '説明' }],
|
|
133
221
|
});
|
|
134
222
|
content.push({
|
|
135
223
|
type: 'paragraph',
|
|
136
|
-
content: [{ type: 'text', text: details.description }]
|
|
224
|
+
content: [{ type: 'text', text: details.description }],
|
|
137
225
|
});
|
|
138
226
|
}
|
|
139
|
-
|
|
227
|
+
|
|
140
228
|
// メタデータセクション
|
|
141
229
|
const metadata: string[] = [];
|
|
142
230
|
if (details.priority) metadata.push(`優先度: ${details.priority}`);
|
|
143
231
|
if (details.estimate) metadata.push(`見積もり: ${details.estimate}`);
|
|
144
232
|
if (details.assignee) metadata.push(`担当: ${details.assignee}`);
|
|
145
233
|
if (details.dependencies) metadata.push(`依存関係: ${details.dependencies}`);
|
|
146
|
-
|
|
234
|
+
|
|
147
235
|
if (metadata.length > 0) {
|
|
148
236
|
content.push({
|
|
149
237
|
type: 'heading',
|
|
150
238
|
attrs: { level: 2 },
|
|
151
|
-
content: [{ type: 'text', text: 'メタデータ' }]
|
|
239
|
+
content: [{ type: 'text', text: 'メタデータ' }],
|
|
152
240
|
});
|
|
153
|
-
metadata.forEach(item => {
|
|
241
|
+
metadata.forEach((item) => {
|
|
154
242
|
content.push({
|
|
155
243
|
type: 'paragraph',
|
|
156
|
-
content: [{ type: 'text', text: item }]
|
|
244
|
+
content: [{ type: 'text', text: item }],
|
|
157
245
|
});
|
|
158
246
|
});
|
|
159
247
|
}
|
|
160
|
-
|
|
248
|
+
|
|
161
249
|
// 完了条件セクション
|
|
162
250
|
if (details.acceptanceCriteria && details.acceptanceCriteria.length > 0) {
|
|
163
251
|
content.push({
|
|
164
252
|
type: 'heading',
|
|
165
253
|
attrs: { level: 2 },
|
|
166
|
-
content: [{ type: 'text', text: '完了条件' }]
|
|
254
|
+
content: [{ type: 'text', text: '完了条件' }],
|
|
167
255
|
});
|
|
168
|
-
|
|
169
|
-
const listItems = details.acceptanceCriteria.map(criterion => ({
|
|
256
|
+
|
|
257
|
+
const listItems = details.acceptanceCriteria.map((criterion) => ({
|
|
170
258
|
type: 'listItem',
|
|
171
|
-
content: [
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
259
|
+
content: [
|
|
260
|
+
{
|
|
261
|
+
type: 'paragraph',
|
|
262
|
+
content: [{ type: 'text', text: criterion }],
|
|
263
|
+
},
|
|
264
|
+
],
|
|
175
265
|
}));
|
|
176
|
-
|
|
266
|
+
|
|
177
267
|
content.push({
|
|
178
268
|
type: 'bulletList',
|
|
179
|
-
content: listItems
|
|
269
|
+
content: listItems,
|
|
180
270
|
});
|
|
181
271
|
}
|
|
182
|
-
|
|
272
|
+
|
|
183
273
|
// サブタスクセクション
|
|
184
274
|
if (details.subtasks && details.subtasks.length > 0) {
|
|
185
275
|
content.push({
|
|
186
276
|
type: 'heading',
|
|
187
277
|
attrs: { level: 2 },
|
|
188
|
-
content: [{ type: 'text', text: 'サブタスク' }]
|
|
278
|
+
content: [{ type: 'text', text: 'サブタスク' }],
|
|
189
279
|
});
|
|
190
|
-
|
|
191
|
-
const listItems = details.subtasks.map(subtask => ({
|
|
280
|
+
|
|
281
|
+
const listItems = details.subtasks.map((subtask) => ({
|
|
192
282
|
type: 'listItem',
|
|
193
|
-
content: [
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
283
|
+
content: [
|
|
284
|
+
{
|
|
285
|
+
type: 'paragraph',
|
|
286
|
+
content: [{ type: 'text', text: subtask }],
|
|
287
|
+
},
|
|
288
|
+
],
|
|
197
289
|
}));
|
|
198
|
-
|
|
290
|
+
|
|
199
291
|
content.push({
|
|
200
292
|
type: 'bulletList',
|
|
201
|
-
content: listItems
|
|
293
|
+
content: listItems,
|
|
202
294
|
});
|
|
203
295
|
}
|
|
204
|
-
|
|
296
|
+
|
|
205
297
|
// フッター(Phase、GitHubリンク)
|
|
206
298
|
content.push({
|
|
207
|
-
type: 'rule'
|
|
299
|
+
type: 'rule',
|
|
208
300
|
});
|
|
209
301
|
content.push({
|
|
210
302
|
type: 'paragraph',
|
|
211
303
|
content: [
|
|
212
304
|
{ type: 'text', text: 'Phase: ', marks: [{ type: 'strong' }] },
|
|
213
|
-
{ type: 'text', text: phaseLabel }
|
|
214
|
-
]
|
|
305
|
+
{ type: 'text', text: phaseLabel },
|
|
306
|
+
],
|
|
215
307
|
});
|
|
216
308
|
content.push({
|
|
217
309
|
type: 'paragraph',
|
|
@@ -220,40 +312,42 @@ function createRichADF(details: StoryDetails, phaseLabel: string, githubUrl: str
|
|
|
220
312
|
{
|
|
221
313
|
type: 'text',
|
|
222
314
|
text: githubUrl,
|
|
223
|
-
marks: [
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
315
|
+
marks: [
|
|
316
|
+
{
|
|
317
|
+
type: 'link',
|
|
318
|
+
attrs: { href: githubUrl },
|
|
319
|
+
},
|
|
320
|
+
],
|
|
321
|
+
},
|
|
322
|
+
],
|
|
229
323
|
});
|
|
230
|
-
|
|
324
|
+
|
|
231
325
|
return {
|
|
232
326
|
type: 'doc',
|
|
233
327
|
version: 1,
|
|
234
|
-
content: content
|
|
328
|
+
content: content,
|
|
235
329
|
};
|
|
236
330
|
}
|
|
237
331
|
|
|
238
332
|
/**
|
|
239
333
|
* プレーンテキストをAtlassian Document Format(ADF)に変換
|
|
240
334
|
*/
|
|
241
|
-
function textToADF(text: string):
|
|
335
|
+
function textToADF(text: string): ADFDocument {
|
|
242
336
|
// 改行で分割して段落を作成
|
|
243
|
-
const paragraphs = text.split('\n').filter(line => line.trim().length > 0);
|
|
244
|
-
|
|
337
|
+
const paragraphs = text.split('\n').filter((line) => line.trim().length > 0);
|
|
338
|
+
|
|
245
339
|
return {
|
|
246
340
|
type: 'doc',
|
|
247
341
|
version: 1,
|
|
248
|
-
content: paragraphs.map(para => ({
|
|
342
|
+
content: paragraphs.map((para) => ({
|
|
249
343
|
type: 'paragraph',
|
|
250
344
|
content: [
|
|
251
345
|
{
|
|
252
346
|
type: 'text',
|
|
253
|
-
text: para.trim()
|
|
254
|
-
}
|
|
255
|
-
]
|
|
256
|
-
}))
|
|
347
|
+
text: para.trim(),
|
|
348
|
+
},
|
|
349
|
+
],
|
|
350
|
+
})),
|
|
257
351
|
};
|
|
258
352
|
}
|
|
259
353
|
|
|
@@ -267,11 +361,11 @@ function getJIRAConfig(): JIRAConfig {
|
|
|
267
361
|
const url = process.env.ATLASSIAN_URL;
|
|
268
362
|
const email = process.env.ATLASSIAN_EMAIL;
|
|
269
363
|
const apiToken = process.env.ATLASSIAN_API_TOKEN;
|
|
270
|
-
|
|
364
|
+
|
|
271
365
|
if (!url || !email || !apiToken) {
|
|
272
366
|
throw new Error('Missing JIRA credentials in .env');
|
|
273
367
|
}
|
|
274
|
-
|
|
368
|
+
|
|
275
369
|
return { url, email, apiToken };
|
|
276
370
|
}
|
|
277
371
|
|
|
@@ -279,122 +373,382 @@ class JIRAClient {
|
|
|
279
373
|
private baseUrl: string;
|
|
280
374
|
private auth: string;
|
|
281
375
|
private requestDelay: number;
|
|
282
|
-
|
|
376
|
+
|
|
283
377
|
constructor(config: JIRAConfig) {
|
|
284
378
|
this.baseUrl = `${config.url}/rest/api/3`;
|
|
285
|
-
this.auth = Buffer.from(`${config.email}:${config.apiToken}`).toString(
|
|
379
|
+
this.auth = Buffer.from(`${config.email}:${config.apiToken}`).toString(
|
|
380
|
+
'base64',
|
|
381
|
+
);
|
|
286
382
|
this.requestDelay = getRequestDelay();
|
|
287
383
|
}
|
|
288
|
-
|
|
384
|
+
|
|
289
385
|
/**
|
|
290
386
|
* JQL検索でIssueを検索
|
|
291
387
|
* @throws 検索エラー時は例外を再スロー(呼び出し元で処理)
|
|
292
388
|
*/
|
|
293
|
-
async searchIssues(jql: string): Promise<
|
|
389
|
+
async searchIssues(jql: string): Promise<JIRAIssue[]> {
|
|
294
390
|
// レートリミット対策: リクエスト前に待機
|
|
295
391
|
await sleep(this.requestDelay);
|
|
296
|
-
|
|
392
|
+
|
|
297
393
|
try {
|
|
394
|
+
// JIRA API v3の検索エンドポイントを使用
|
|
395
|
+
// GET /rest/api/3/search でJQL検索を実行(GETメソッドが推奨)
|
|
298
396
|
const response = await axios.get(`${this.baseUrl}/search`, {
|
|
299
|
-
params: {
|
|
397
|
+
params: {
|
|
398
|
+
jql,
|
|
399
|
+
maxResults: 100,
|
|
400
|
+
fields: 'summary,issuetype,status,key',
|
|
401
|
+
},
|
|
300
402
|
headers: {
|
|
301
|
-
|
|
302
|
-
'Content-Type': 'application/json'
|
|
303
|
-
}
|
|
403
|
+
Authorization: `Basic ${this.auth}`,
|
|
404
|
+
'Content-Type': 'application/json',
|
|
405
|
+
},
|
|
304
406
|
});
|
|
305
407
|
return response.data.issues || [];
|
|
306
408
|
} catch (error) {
|
|
307
|
-
|
|
409
|
+
// エラーハンドリング改善
|
|
410
|
+
if (axios.isAxiosError(error)) {
|
|
411
|
+
const status = error.response?.status;
|
|
412
|
+
const errorMessages = error.response?.data?.errorMessages || [];
|
|
413
|
+
const message = errorMessages.join(', ') || error.message;
|
|
414
|
+
|
|
415
|
+
console.error(`Error searching issues (HTTP ${status}): ${message}`);
|
|
416
|
+
|
|
417
|
+
if (status === 410) {
|
|
418
|
+
console.error(
|
|
419
|
+
'💡 Hint: The search API endpoint returned 410 (Gone).',
|
|
420
|
+
);
|
|
421
|
+
console.error(
|
|
422
|
+
' This may indicate the endpoint has been deprecated or disabled.',
|
|
423
|
+
);
|
|
424
|
+
console.error(
|
|
425
|
+
' Check JIRA instance configuration or try alternative search methods.',
|
|
426
|
+
);
|
|
427
|
+
} else if (status === 401) {
|
|
428
|
+
console.error(
|
|
429
|
+
'💡 Hint: Authentication failed. Check ATLASSIAN_API_TOKEN in .env',
|
|
430
|
+
);
|
|
431
|
+
} else if (status === 403) {
|
|
432
|
+
console.error(
|
|
433
|
+
'💡 Hint: Permission denied. Check API token permissions in JIRA.',
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
} else {
|
|
437
|
+
console.error(
|
|
438
|
+
'Error searching issues:',
|
|
439
|
+
error instanceof Error ? error.message : error,
|
|
440
|
+
);
|
|
441
|
+
}
|
|
308
442
|
throw error; // エラーを再スローして呼び出し元で処理
|
|
309
443
|
}
|
|
310
444
|
}
|
|
311
|
-
|
|
312
|
-
async createIssue(
|
|
445
|
+
|
|
446
|
+
async createIssue(
|
|
447
|
+
payload: JIRAIssuePayload,
|
|
448
|
+
): Promise<JIRAIssueCreateResponse> {
|
|
313
449
|
// レートリミット対策: リクエスト前に待機
|
|
314
450
|
await sleep(this.requestDelay);
|
|
315
|
-
|
|
316
|
-
const response = await axios.post(
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
451
|
+
|
|
452
|
+
const response = await axios.post<JIRAIssueCreateResponse>(
|
|
453
|
+
`${this.baseUrl}/issue`,
|
|
454
|
+
payload,
|
|
455
|
+
{
|
|
456
|
+
headers: {
|
|
457
|
+
Authorization: `Basic ${this.auth}`,
|
|
458
|
+
'Content-Type': 'application/json',
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
);
|
|
322
462
|
return response.data;
|
|
323
463
|
}
|
|
324
|
-
|
|
325
|
-
async updateIssue(
|
|
464
|
+
|
|
465
|
+
async updateIssue(
|
|
466
|
+
issueKey: string,
|
|
467
|
+
payload: Partial<JIRAIssuePayload>,
|
|
468
|
+
): Promise<void> {
|
|
326
469
|
// レートリミット対策: リクエスト前に待機
|
|
327
470
|
await sleep(this.requestDelay);
|
|
328
|
-
|
|
471
|
+
|
|
329
472
|
await axios.put(`${this.baseUrl}/issue/${issueKey}`, payload, {
|
|
330
473
|
headers: {
|
|
331
|
-
|
|
332
|
-
'Content-Type': 'application/json'
|
|
333
|
-
}
|
|
474
|
+
Authorization: `Basic ${this.auth}`,
|
|
475
|
+
'Content-Type': 'application/json',
|
|
476
|
+
},
|
|
334
477
|
});
|
|
335
478
|
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* JIRAチケットのステータスを変更(トランジション実行)
|
|
482
|
+
* @param issueKey JIRAチケットキー (例: "PROJ-123")
|
|
483
|
+
* @param transitionName 遷移先ステータス名 (例: "In Progress", "Ready for Review")
|
|
484
|
+
* @throws トランジションが見つからない場合はエラー
|
|
485
|
+
*/
|
|
486
|
+
async transitionIssue(
|
|
487
|
+
issueKey: string,
|
|
488
|
+
transitionName: string,
|
|
489
|
+
): Promise<void> {
|
|
490
|
+
// レートリミット対策: リクエスト前に待機
|
|
491
|
+
await sleep(this.requestDelay);
|
|
492
|
+
|
|
493
|
+
try {
|
|
494
|
+
// 1. 利用可能なトランジションを取得
|
|
495
|
+
const transitionsResponse = await axios.get(
|
|
496
|
+
`${this.baseUrl}/issue/${issueKey}/transitions`,
|
|
497
|
+
{
|
|
498
|
+
headers: {
|
|
499
|
+
Authorization: `Basic ${this.auth}`,
|
|
500
|
+
'Content-Type': 'application/json',
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
);
|
|
504
|
+
|
|
505
|
+
const transitions = transitionsResponse.data.transitions || [];
|
|
506
|
+
|
|
507
|
+
// 2. transitionNameに一致するトランジションIDを特定
|
|
508
|
+
// 名前の完全一致または部分一致で検索
|
|
509
|
+
const transition = transitions.find(
|
|
510
|
+
(t: { id: string; name: string }) =>
|
|
511
|
+
t.name.toLowerCase() === transitionName.toLowerCase() ||
|
|
512
|
+
t.name.toLowerCase().includes(transitionName.toLowerCase()),
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
if (!transition) {
|
|
516
|
+
const availableTransitions = transitions
|
|
517
|
+
.map((t: { name: string }) => t.name)
|
|
518
|
+
.join(', ');
|
|
519
|
+
throw new Error(
|
|
520
|
+
`Transition "${transitionName}" not found for issue ${issueKey}. ` +
|
|
521
|
+
`Available transitions: ${availableTransitions || 'none'}`,
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// レートリミット対策: リクエスト前に待機
|
|
526
|
+
await sleep(this.requestDelay);
|
|
527
|
+
|
|
528
|
+
// 3. トランジションを実行
|
|
529
|
+
await axios.post(
|
|
530
|
+
`${this.baseUrl}/issue/${issueKey}/transitions`,
|
|
531
|
+
{
|
|
532
|
+
transition: { id: transition.id },
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
headers: {
|
|
536
|
+
Authorization: `Basic ${this.auth}`,
|
|
537
|
+
'Content-Type': 'application/json',
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
console.log(
|
|
543
|
+
`✅ ${issueKey} のステータスを「${transition.name}」に変更しました`,
|
|
544
|
+
);
|
|
545
|
+
} catch (error) {
|
|
546
|
+
if (axios.isAxiosError(error)) {
|
|
547
|
+
const status = error.response?.status;
|
|
548
|
+
const errorMessages = error.response?.data?.errorMessages || [];
|
|
549
|
+
const message = errorMessages.join(', ') || error.message;
|
|
550
|
+
|
|
551
|
+
console.error(
|
|
552
|
+
`Error transitioning issue ${issueKey} (HTTP ${status}): ${message}`,
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
if (status === 404) {
|
|
556
|
+
console.error(
|
|
557
|
+
`💡 Hint: Issue ${issueKey} was not found. Check the issue key.`,
|
|
558
|
+
);
|
|
559
|
+
} else if (status === 400) {
|
|
560
|
+
console.error(
|
|
561
|
+
'💡 Hint: The transition may not be valid from the current status.',
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
throw error;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* JIRAチケットにコメントを追加
|
|
571
|
+
* @param issueKey JIRAチケットキー
|
|
572
|
+
* @param commentText コメント内容
|
|
573
|
+
*/
|
|
574
|
+
async addComment(issueKey: string, commentText: string): Promise<void> {
|
|
575
|
+
// レートリミット対策: リクエスト前に待機
|
|
576
|
+
await sleep(this.requestDelay);
|
|
577
|
+
|
|
578
|
+
try {
|
|
579
|
+
// Atlassian Document Format (ADF) でコメントを作成
|
|
580
|
+
const commentBody: ADFDocument = {
|
|
581
|
+
type: 'doc',
|
|
582
|
+
version: 1,
|
|
583
|
+
content: [
|
|
584
|
+
{
|
|
585
|
+
type: 'paragraph',
|
|
586
|
+
content: [
|
|
587
|
+
{
|
|
588
|
+
type: 'text',
|
|
589
|
+
text: commentText,
|
|
590
|
+
},
|
|
591
|
+
],
|
|
592
|
+
},
|
|
593
|
+
],
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
await axios.post(
|
|
597
|
+
`${this.baseUrl}/issue/${issueKey}/comment`,
|
|
598
|
+
{
|
|
599
|
+
body: commentBody,
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
headers: {
|
|
603
|
+
Authorization: `Basic ${this.auth}`,
|
|
604
|
+
'Content-Type': 'application/json',
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
);
|
|
608
|
+
|
|
609
|
+
console.log(`✅ ${issueKey} にコメントを追加しました`);
|
|
610
|
+
} catch (error) {
|
|
611
|
+
if (axios.isAxiosError(error)) {
|
|
612
|
+
const status = error.response?.status;
|
|
613
|
+
const errorMessages = error.response?.data?.errorMessages || [];
|
|
614
|
+
const message = errorMessages.join(', ') || error.message;
|
|
615
|
+
|
|
616
|
+
console.error(
|
|
617
|
+
`Error adding comment to ${issueKey} (HTTP ${status}): ${message}`,
|
|
618
|
+
);
|
|
619
|
+
|
|
620
|
+
if (status === 404) {
|
|
621
|
+
console.error(
|
|
622
|
+
`💡 Hint: Issue ${issueKey} was not found. Check the issue key.`,
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
throw error;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* プロジェクトのIssue Type IDを取得
|
|
632
|
+
* @param projectKey プロジェクトキー
|
|
633
|
+
* @param issueTypeName Issue Type名(例: "Epic", "Story")
|
|
634
|
+
* @returns Issue Type ID
|
|
635
|
+
*/
|
|
636
|
+
async getIssueTypeId(
|
|
637
|
+
projectKey: string,
|
|
638
|
+
issueTypeName: string,
|
|
639
|
+
): Promise<string | null> {
|
|
640
|
+
await sleep(this.requestDelay);
|
|
641
|
+
|
|
642
|
+
try {
|
|
643
|
+
const response = await axios.get(
|
|
644
|
+
`${this.baseUrl}/project/${projectKey}`,
|
|
645
|
+
{
|
|
646
|
+
headers: {
|
|
647
|
+
Authorization: `Basic ${this.auth}`,
|
|
648
|
+
'Content-Type': 'application/json',
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
const issueTypes = (response.data.issueTypes || []) as JIRAIssueType[];
|
|
654
|
+
const issueType = issueTypes.find(
|
|
655
|
+
(it: JIRAIssueType) =>
|
|
656
|
+
it.name.toLowerCase() === issueTypeName.toLowerCase() ||
|
|
657
|
+
it.name === issueTypeName,
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
return issueType ? issueType.id : null;
|
|
661
|
+
} catch (error) {
|
|
662
|
+
console.error(
|
|
663
|
+
`Error getting issue type ID for ${issueTypeName}:`,
|
|
664
|
+
error instanceof Error ? error.message : error,
|
|
665
|
+
);
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
336
669
|
}
|
|
337
670
|
|
|
338
671
|
async function syncTasksToJIRA(featureName: string): Promise<void> {
|
|
339
672
|
console.log(`Syncing tasks for feature: ${featureName}`);
|
|
340
|
-
|
|
673
|
+
|
|
341
674
|
// feature名のバリデーション(必須)
|
|
342
675
|
validateFeatureNameOrThrow(featureName);
|
|
343
|
-
|
|
344
|
-
//
|
|
345
|
-
const validation =
|
|
346
|
-
|
|
676
|
+
|
|
677
|
+
// 実行前の必須設定値チェック(非同期版:Issue Type IDの存在チェック付き)
|
|
678
|
+
const validation = await validateForJiraSyncAsync();
|
|
679
|
+
|
|
347
680
|
if (validation.info.length > 0) {
|
|
348
|
-
validation.info.forEach(msg => console.log(`ℹ️ ${msg}`));
|
|
681
|
+
validation.info.forEach((msg) => console.log(`ℹ️ ${msg}`));
|
|
349
682
|
}
|
|
350
|
-
|
|
683
|
+
|
|
351
684
|
if (validation.warnings.length > 0) {
|
|
352
685
|
console.warn('⚠️ Warnings:');
|
|
353
|
-
validation.warnings.forEach(warning => console.warn(` ${warning}`));
|
|
686
|
+
validation.warnings.forEach((warning) => console.warn(` ${warning}`));
|
|
354
687
|
}
|
|
355
|
-
|
|
688
|
+
|
|
356
689
|
if (validation.errors.length > 0) {
|
|
357
690
|
console.error('❌ Configuration errors:');
|
|
358
|
-
validation.errors.forEach(error => console.error(` ${error}`));
|
|
691
|
+
validation.errors.forEach((error) => console.error(` ${error}`));
|
|
359
692
|
const configPath = getConfigPath();
|
|
360
693
|
console.error(`\n設定ファイル: ${configPath}`);
|
|
361
|
-
throw new Error(
|
|
694
|
+
throw new Error(
|
|
695
|
+
'JIRA同期に必要な設定値が不足しています。上記のエラーを確認して設定を修正してください。',
|
|
696
|
+
);
|
|
362
697
|
}
|
|
363
|
-
|
|
364
|
-
console.log(
|
|
365
|
-
|
|
698
|
+
|
|
699
|
+
console.log(
|
|
700
|
+
`⏳ Request delay: ${getRequestDelay()}ms (set ATLASSIAN_REQUEST_DELAY to adjust)`,
|
|
701
|
+
);
|
|
702
|
+
|
|
366
703
|
// 設定からissue type IDを取得(検索と作成の両方で使用)
|
|
367
704
|
const appConfig = getConfig();
|
|
368
|
-
const
|
|
369
|
-
const
|
|
370
|
-
|
|
705
|
+
const projectMeta = loadProjectMeta();
|
|
706
|
+
const config = getJIRAConfig();
|
|
707
|
+
const client = new JIRAClient(config);
|
|
708
|
+
|
|
709
|
+
// StoryタイプのIDを動的に取得(日本語JIRAでは "ストーリー" という名前の場合がある)
|
|
710
|
+
let storyIssueTypeId: string | undefined =
|
|
711
|
+
appConfig.jira?.issueTypes?.story || process.env.JIRA_ISSUE_TYPE_STORY;
|
|
712
|
+
console.log(
|
|
713
|
+
`📋 Story Issue Type ID from config/env: ${storyIssueTypeId || 'not found'}`,
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
if (!storyIssueTypeId) {
|
|
717
|
+
console.log('🔍 Attempting to find Story issue type dynamically...');
|
|
718
|
+
const foundId =
|
|
719
|
+
(await client.getIssueTypeId(projectMeta.jiraProjectKey, 'Story')) ||
|
|
720
|
+
(await client.getIssueTypeId(projectMeta.jiraProjectKey, 'ストーリー'));
|
|
721
|
+
storyIssueTypeId = foundId ?? undefined;
|
|
722
|
+
console.log(
|
|
723
|
+
`📋 Story Issue Type ID from API: ${storyIssueTypeId || 'not found'}`,
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
|
|
371
727
|
if (!storyIssueTypeId) {
|
|
372
728
|
throw new Error(
|
|
373
|
-
'JIRA Story issue type ID is not configured. ' +
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
729
|
+
'JIRA Story issue type ID is not configured and could not be found in project. ' +
|
|
730
|
+
'Please set JIRA_ISSUE_TYPE_STORY environment variable or configure it in .michi/config.json. ' +
|
|
731
|
+
'You can find the issue type ID in JIRA UI (Settings > Issues > Issue types) or via REST API: ' +
|
|
732
|
+
'GET https://your-domain.atlassian.net/rest/api/3/project/{projectKey}',
|
|
377
733
|
);
|
|
378
734
|
}
|
|
379
|
-
|
|
380
|
-
|
|
735
|
+
|
|
736
|
+
console.log(`✅ Using Story Issue Type ID: ${storyIssueTypeId}`);
|
|
737
|
+
|
|
381
738
|
const tasksPath = resolve(`.kiro/specs/${featureName}/tasks.md`);
|
|
382
739
|
const tasksContent = readFileSync(tasksPath, 'utf-8');
|
|
383
|
-
|
|
384
|
-
const config = getJIRAConfig();
|
|
385
|
-
const client = new JIRAClient(config);
|
|
386
|
-
|
|
740
|
+
|
|
387
741
|
// spec.jsonを読み込んで既存のEpicキーを確認
|
|
388
742
|
const specPath = resolve(`.kiro/specs/${featureName}/spec.json`);
|
|
389
|
-
let spec:
|
|
743
|
+
let spec: SpecJson = {};
|
|
390
744
|
try {
|
|
391
|
-
spec = JSON.parse(readFileSync(specPath, 'utf-8'));
|
|
392
|
-
} catch
|
|
745
|
+
spec = JSON.parse(readFileSync(specPath, 'utf-8')) as SpecJson;
|
|
746
|
+
} catch {
|
|
393
747
|
console.error('spec.json not found or invalid');
|
|
394
748
|
}
|
|
395
|
-
|
|
396
|
-
let epic:
|
|
397
|
-
|
|
749
|
+
|
|
750
|
+
let epic: { key: string } | undefined;
|
|
751
|
+
|
|
398
752
|
// 既存のEpicをチェック
|
|
399
753
|
if (spec.jira?.epicKey) {
|
|
400
754
|
console.log(`Existing Epic found: ${spec.jira.epicKey}`);
|
|
@@ -404,135 +758,287 @@ async function syncTasksToJIRA(featureName: string): Promise<void> {
|
|
|
404
758
|
// Epic作成
|
|
405
759
|
console.log('Creating Epic...');
|
|
406
760
|
const epicSummary = `[${featureName}] ${projectMeta.projectName}`;
|
|
407
|
-
|
|
761
|
+
|
|
408
762
|
// 同じタイトルのEpicがすでに存在するかJQLで検索
|
|
409
763
|
const jql = `project = ${projectMeta.jiraProjectKey} AND issuetype = Epic AND summary ~ "${featureName}"`;
|
|
410
|
-
let existingEpics:
|
|
764
|
+
let existingEpics: JIRAIssue[] = [];
|
|
411
765
|
try {
|
|
412
766
|
existingEpics = await client.searchIssues(jql);
|
|
413
767
|
} catch (error) {
|
|
414
|
-
console.error(
|
|
415
|
-
|
|
416
|
-
|
|
768
|
+
console.error(
|
|
769
|
+
'❌ Failed to search existing Epics:',
|
|
770
|
+
error instanceof Error ? error.message : error,
|
|
771
|
+
);
|
|
772
|
+
console.error(
|
|
773
|
+
'⚠️ Cannot verify idempotency - proceeding with Epic creation',
|
|
774
|
+
);
|
|
775
|
+
console.error(
|
|
776
|
+
' If Epic already exists, manual cleanup may be required',
|
|
777
|
+
);
|
|
778
|
+
// 検索失敗時はフォールバック: 新規作成を試みる(重複リスクあり)
|
|
779
|
+
existingEpics = [];
|
|
417
780
|
}
|
|
418
|
-
|
|
781
|
+
|
|
419
782
|
if (existingEpics.length > 0) {
|
|
420
|
-
console.log(
|
|
783
|
+
console.log(
|
|
784
|
+
`Found existing Epic with similar title: ${existingEpics[0].key}`,
|
|
785
|
+
);
|
|
421
786
|
console.log('Using existing Epic instead of creating new one');
|
|
422
787
|
epic = existingEpics[0];
|
|
423
788
|
} else {
|
|
789
|
+
// EpicタイプのIDを取得(日本語JIRAでは "エピック" という名前の場合がある)
|
|
790
|
+
const epicTypeId =
|
|
791
|
+
(await client.getIssueTypeId(projectMeta.jiraProjectKey, 'Epic')) ||
|
|
792
|
+
(await client.getIssueTypeId(projectMeta.jiraProjectKey, 'エピック'));
|
|
793
|
+
|
|
794
|
+
if (!epicTypeId) {
|
|
795
|
+
throw new Error(
|
|
796
|
+
'Epic issue type not found in project. ' +
|
|
797
|
+
'Please ensure the project has Epic issue type enabled.',
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
|
|
424
801
|
const epicDescription = `機能: ${featureName}\nGitHub: ${projectMeta.repository}/tree/main/.kiro/specs/${featureName}`;
|
|
425
|
-
|
|
802
|
+
|
|
426
803
|
const epicPayload = {
|
|
427
804
|
fields: {
|
|
428
805
|
project: { key: projectMeta.jiraProjectKey },
|
|
429
806
|
summary: epicSummary,
|
|
430
|
-
description: textToADF(epicDescription),
|
|
431
|
-
issuetype: {
|
|
432
|
-
labels: projectMeta.confluenceLabels
|
|
433
|
-
}
|
|
807
|
+
description: textToADF(epicDescription), // ADF形式に変換
|
|
808
|
+
issuetype: { id: epicTypeId }, // IDを使用(nameではなく)
|
|
809
|
+
labels: projectMeta.confluenceLabels,
|
|
810
|
+
},
|
|
434
811
|
};
|
|
435
|
-
|
|
812
|
+
|
|
436
813
|
epic = await client.createIssue(epicPayload);
|
|
437
814
|
console.log(`✅ Epic created: ${epic.key}`);
|
|
438
815
|
}
|
|
439
816
|
}
|
|
440
|
-
|
|
817
|
+
|
|
818
|
+
// Epicが確実に設定されていることを確認
|
|
819
|
+
if (!epic) {
|
|
820
|
+
throw new Error('Epic creation or retrieval failed');
|
|
821
|
+
}
|
|
822
|
+
|
|
441
823
|
// 既存のStoryを検索(重複防止)
|
|
442
824
|
// ラベルで検索(summary検索では "Story: タイトル" 形式に一致しないため)
|
|
443
825
|
// issuetype検索にはIDを使用(名前は言語依存のため)
|
|
444
|
-
const
|
|
826
|
+
const storyJql = `project = ${projectMeta.jiraProjectKey} AND issuetype = ${storyIssueTypeId} AND labels = "${featureName}"`;
|
|
445
827
|
let existingStories: any[] = [];
|
|
446
828
|
try {
|
|
447
|
-
existingStories = await client.searchIssues(
|
|
829
|
+
existingStories = await client.searchIssues(storyJql);
|
|
448
830
|
} catch (error) {
|
|
449
|
-
console.error(
|
|
450
|
-
|
|
451
|
-
|
|
831
|
+
console.error(
|
|
832
|
+
'❌ Failed to search existing Stories:',
|
|
833
|
+
error instanceof Error ? error.message : error,
|
|
834
|
+
);
|
|
835
|
+
console.error(
|
|
836
|
+
'⚠️ Cannot verify idempotency - Story creation may result in duplicates',
|
|
837
|
+
);
|
|
838
|
+
console.error(
|
|
839
|
+
'⚠️ Continuing with story creation (duplicates may be created)...',
|
|
840
|
+
);
|
|
452
841
|
// 検索失敗時も処理を継続(既存ストーリーなしとして扱う)
|
|
453
842
|
existingStories = [];
|
|
454
843
|
}
|
|
455
|
-
|
|
456
|
-
const existingStorySummaries = new Set(
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
844
|
+
|
|
845
|
+
const existingStorySummaries = new Set(
|
|
846
|
+
existingStories
|
|
847
|
+
.filter((s: JIRAIssue) => s?.fields?.summary)
|
|
848
|
+
.map((s: JIRAIssue) => s.fields.summary),
|
|
849
|
+
);
|
|
850
|
+
const existingStoryKeys = new Set(
|
|
851
|
+
existingStories
|
|
852
|
+
.filter((s: JIRAIssue) => s?.key)
|
|
853
|
+
.map((s: JIRAIssue) => s.key),
|
|
854
|
+
);
|
|
855
|
+
|
|
856
|
+
console.log(
|
|
857
|
+
`Found ${existingStories.length} existing stories for this feature`,
|
|
858
|
+
);
|
|
859
|
+
|
|
461
860
|
// フェーズラベル検出用の正規表現
|
|
462
861
|
// Phase X: フェーズ名(ラベル)の形式を検出
|
|
463
|
-
|
|
464
|
-
|
|
862
|
+
// Phase番号: 数字(0, 1, 2...)、ドット付き数字(0.1, 0.2...)、英字(A, B)に対応
|
|
863
|
+
const phasePattern = /## Phase [\d.A-Z]+:\s*(.+?)(?:((.+?)))?/;
|
|
864
|
+
|
|
465
865
|
// Story作成(フェーズ検出付きパーサー)
|
|
466
866
|
const lines = tasksContent.split('\n');
|
|
467
867
|
let currentPhaseLabel = 'implementation'; // デフォルトは実装フェーズ
|
|
468
868
|
const createdStories: string[] = [];
|
|
469
|
-
|
|
869
|
+
|
|
470
870
|
for (let i = 0; i < lines.length; i++) {
|
|
471
871
|
const line = lines[i];
|
|
472
|
-
|
|
872
|
+
|
|
473
873
|
// フェーズ検出
|
|
474
874
|
const phaseMatch = line.match(phasePattern);
|
|
475
875
|
if (phaseMatch) {
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
876
|
+
const phaseTitle = phaseMatch[1]; // フェーズタイトル全体
|
|
877
|
+
const phaseName = phaseMatch[2] || phaseTitle; // 括弧内のラベル(例: Requirements)または全体
|
|
878
|
+
|
|
879
|
+
// Phase番号を抽出(例: "0.1", "2", "A")
|
|
880
|
+
const phaseNumberMatch = line.match(/## Phase ([\d.A-Z]+):/);
|
|
881
|
+
const phaseNumber = phaseNumberMatch ? phaseNumberMatch[1] : '';
|
|
882
|
+
|
|
883
|
+
// フェーズ番号またはフェーズ名からラベルを決定
|
|
884
|
+
// 新ワークフロー構造に対応
|
|
885
|
+
if (
|
|
886
|
+
phaseNumber === '0.0' ||
|
|
887
|
+
phaseName.includes('初期化') ||
|
|
888
|
+
phaseName.toLowerCase().includes('init')
|
|
889
|
+
) {
|
|
890
|
+
currentPhaseLabel = 'spec-init';
|
|
891
|
+
} else if (
|
|
892
|
+
phaseNumber === '0.1' ||
|
|
893
|
+
phaseName.includes('要件定義') ||
|
|
894
|
+
phaseName.toLowerCase().includes('requirements')
|
|
895
|
+
) {
|
|
480
896
|
currentPhaseLabel = 'requirements';
|
|
481
|
-
} else if (
|
|
897
|
+
} else if (
|
|
898
|
+
phaseNumber === '0.2' ||
|
|
899
|
+
phaseName.includes('設計') ||
|
|
900
|
+
phaseName.toLowerCase().includes('design')
|
|
901
|
+
) {
|
|
482
902
|
currentPhaseLabel = 'design';
|
|
483
|
-
} else if (
|
|
903
|
+
} else if (
|
|
904
|
+
phaseNumber === '0.3' ||
|
|
905
|
+
phaseName.includes('テストタイプ') ||
|
|
906
|
+
phaseName.toLowerCase().includes('test-type') ||
|
|
907
|
+
phaseName.toLowerCase().includes('test type')
|
|
908
|
+
) {
|
|
909
|
+
currentPhaseLabel = 'test-type-selection';
|
|
910
|
+
} else if (
|
|
911
|
+
phaseNumber === '0.4' ||
|
|
912
|
+
phaseName.includes('テスト仕様') ||
|
|
913
|
+
phaseName.toLowerCase().includes('test-spec') ||
|
|
914
|
+
phaseName.toLowerCase().includes('test spec')
|
|
915
|
+
) {
|
|
916
|
+
currentPhaseLabel = 'test-spec';
|
|
917
|
+
} else if (
|
|
918
|
+
phaseNumber === '0.5' ||
|
|
919
|
+
phaseName.includes('タスク分割') ||
|
|
920
|
+
phaseName.toLowerCase().includes('tasks') ||
|
|
921
|
+
phaseName.toLowerCase().includes('task breakdown')
|
|
922
|
+
) {
|
|
923
|
+
currentPhaseLabel = 'spec-tasks';
|
|
924
|
+
} else if (
|
|
925
|
+
phaseNumber === '0.6' ||
|
|
926
|
+
phaseName.includes('JIRA') ||
|
|
927
|
+
phaseName.toLowerCase().includes('jira')
|
|
928
|
+
) {
|
|
929
|
+
currentPhaseLabel = 'jira-sync';
|
|
930
|
+
} else if (
|
|
931
|
+
phaseNumber === '1' ||
|
|
932
|
+
phaseName.includes('環境構築') ||
|
|
933
|
+
phaseName.toLowerCase().includes('environment') ||
|
|
934
|
+
phaseName.toLowerCase().includes('setup')
|
|
935
|
+
) {
|
|
936
|
+
currentPhaseLabel = 'environment-setup';
|
|
937
|
+
} else if (
|
|
938
|
+
phaseNumber === '2' ||
|
|
939
|
+
phaseName.includes('実装') ||
|
|
940
|
+
phaseName.includes('TDD') ||
|
|
941
|
+
phaseName.toLowerCase().includes('implementation')
|
|
942
|
+
) {
|
|
484
943
|
currentPhaseLabel = 'implementation';
|
|
485
|
-
} else if (
|
|
486
|
-
|
|
487
|
-
|
|
944
|
+
} else if (
|
|
945
|
+
phaseNumber === 'A' ||
|
|
946
|
+
phaseNumber.toLowerCase() === 'a' ||
|
|
947
|
+
phaseName.includes('PR前') ||
|
|
948
|
+
phaseName.toLowerCase().includes('pr-test') ||
|
|
949
|
+
phaseName.toLowerCase().includes('pr test')
|
|
950
|
+
) {
|
|
951
|
+
currentPhaseLabel = 'phase-a';
|
|
952
|
+
} else if (
|
|
953
|
+
phaseNumber === '3' ||
|
|
954
|
+
phaseName.includes('追加QA') ||
|
|
955
|
+
phaseName.includes('QA') ||
|
|
956
|
+
phaseName.includes('試験') ||
|
|
957
|
+
phaseName.toLowerCase().includes('testing') ||
|
|
958
|
+
phaseName.toLowerCase().includes('additional qa')
|
|
959
|
+
) {
|
|
960
|
+
currentPhaseLabel = 'additional-qa';
|
|
961
|
+
} else if (
|
|
962
|
+
phaseNumber === 'B' ||
|
|
963
|
+
phaseNumber.toLowerCase() === 'b' ||
|
|
964
|
+
phaseName.includes('リリース準備テスト') ||
|
|
965
|
+
phaseName.toLowerCase().includes('release-test') ||
|
|
966
|
+
phaseName.toLowerCase().includes('release test')
|
|
967
|
+
) {
|
|
968
|
+
currentPhaseLabel = 'phase-b';
|
|
969
|
+
} else if (
|
|
970
|
+
phaseNumber === '4' ||
|
|
971
|
+
phaseName.includes('リリース準備') ||
|
|
972
|
+
phaseName.toLowerCase().includes('release-prep') ||
|
|
973
|
+
phaseName.toLowerCase().includes('release preparation')
|
|
974
|
+
) {
|
|
488
975
|
currentPhaseLabel = 'release-prep';
|
|
489
|
-
} else if (
|
|
976
|
+
} else if (
|
|
977
|
+
phaseNumber === '5' ||
|
|
978
|
+
(phaseName.includes('リリース') && !phaseName.includes('準備')) ||
|
|
979
|
+
(phaseName.toLowerCase().includes('release') &&
|
|
980
|
+
!phaseName.toLowerCase().includes('prep'))
|
|
981
|
+
) {
|
|
490
982
|
currentPhaseLabel = 'release';
|
|
491
983
|
}
|
|
492
|
-
|
|
493
|
-
console.log(
|
|
984
|
+
|
|
985
|
+
console.log(
|
|
986
|
+
`📌 Phase detected: ${phaseTitle} (number: ${phaseNumber}, label: ${currentPhaseLabel})`,
|
|
987
|
+
);
|
|
494
988
|
continue;
|
|
495
989
|
}
|
|
496
|
-
|
|
990
|
+
|
|
497
991
|
// Story検出
|
|
498
992
|
const storyMatch = line.match(/### Story [\d.]+: (.+)/);
|
|
499
993
|
if (!storyMatch) continue;
|
|
500
|
-
|
|
994
|
+
|
|
501
995
|
const storyTitle = storyMatch[1];
|
|
502
996
|
const storySummary = `Story: ${storyTitle}`;
|
|
503
|
-
|
|
997
|
+
|
|
504
998
|
// 既に同じタイトルのStoryが存在するかチェック
|
|
505
999
|
if (existingStorySummaries.has(storySummary)) {
|
|
506
1000
|
console.log(`Skipping Story (already exists): ${storyTitle}`);
|
|
507
|
-
const existing = existingStories.find(
|
|
1001
|
+
const existing = existingStories.find(
|
|
1002
|
+
(s: any) => s?.fields?.summary === storySummary,
|
|
1003
|
+
);
|
|
508
1004
|
if (existing) {
|
|
509
1005
|
createdStories.push(existing.key);
|
|
1006
|
+
existingStoryKeys.add(existing.key);
|
|
1007
|
+
} else {
|
|
1008
|
+
console.warn(
|
|
1009
|
+
`⚠️ Warning: Story "${storyTitle}" is in summary set but not found in existingStories array`,
|
|
1010
|
+
);
|
|
510
1011
|
}
|
|
511
1012
|
continue;
|
|
512
1013
|
}
|
|
513
|
-
|
|
1014
|
+
|
|
514
1015
|
console.log(`Creating Story: ${storyTitle} [${currentPhaseLabel}]`);
|
|
515
|
-
|
|
1016
|
+
|
|
516
1017
|
try {
|
|
517
1018
|
// Storyの詳細情報を抽出(新しい実装を使用)
|
|
518
1019
|
const storyDetails = extractStoryDetails(tasksContent, storyTitle);
|
|
519
|
-
|
|
1020
|
+
|
|
520
1021
|
// GitHubリンク
|
|
521
1022
|
const githubUrl = `${projectMeta.repository}/tree/main/.kiro/specs/${featureName}/tasks.md`;
|
|
522
|
-
|
|
1023
|
+
|
|
523
1024
|
// リッチなADF形式で説明文を生成
|
|
524
|
-
const richDescription = createRichADF(
|
|
525
|
-
|
|
1025
|
+
const richDescription = createRichADF(
|
|
1026
|
+
storyDetails,
|
|
1027
|
+
currentPhaseLabel,
|
|
1028
|
+
githubUrl,
|
|
1029
|
+
);
|
|
1030
|
+
|
|
526
1031
|
// 優先度のマッピング(デフォルト: Medium)
|
|
527
1032
|
const priorityMap: { [key: string]: string } = {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
1033
|
+
High: 'High',
|
|
1034
|
+
Medium: 'Medium',
|
|
1035
|
+
Low: 'Low',
|
|
531
1036
|
};
|
|
532
|
-
const priority =
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
1037
|
+
const priority =
|
|
1038
|
+
storyDetails.priority && priorityMap[storyDetails.priority]
|
|
1039
|
+
? priorityMap[storyDetails.priority]
|
|
1040
|
+
: 'Medium';
|
|
1041
|
+
|
|
536
1042
|
// 見積もり(Story Points)を取得
|
|
537
1043
|
let storyPoints: number | undefined;
|
|
538
1044
|
if (storyDetails.estimate) {
|
|
@@ -541,41 +1047,46 @@ async function syncTasksToJIRA(featureName: string): Promise<void> {
|
|
|
541
1047
|
storyPoints = parseInt(spMatch[1], 10);
|
|
542
1048
|
}
|
|
543
1049
|
}
|
|
544
|
-
|
|
1050
|
+
|
|
545
1051
|
// JIRAペイロードを作成(issue type IDは既に取得済み)
|
|
546
1052
|
const storyPayload: any = {
|
|
547
1053
|
fields: {
|
|
548
1054
|
project: { key: projectMeta.jiraProjectKey },
|
|
549
1055
|
summary: storySummary,
|
|
550
|
-
description: richDescription,
|
|
1056
|
+
description: richDescription, // リッチなADF形式
|
|
551
1057
|
issuetype: { id: storyIssueTypeId },
|
|
552
|
-
labels: [
|
|
553
|
-
|
|
554
|
-
|
|
1058
|
+
labels: [
|
|
1059
|
+
...projectMeta.confluenceLabels,
|
|
1060
|
+
featureName,
|
|
1061
|
+
currentPhaseLabel,
|
|
1062
|
+
],
|
|
1063
|
+
priority: { name: priority },
|
|
1064
|
+
},
|
|
555
1065
|
};
|
|
556
|
-
|
|
1066
|
+
|
|
557
1067
|
// 期限(Due Date)を設定
|
|
558
1068
|
if (storyDetails.dueDate) {
|
|
559
|
-
storyPayload.fields.duedate = storyDetails.dueDate;
|
|
1069
|
+
storyPayload.fields.duedate = storyDetails.dueDate; // YYYY-MM-DD形式
|
|
560
1070
|
}
|
|
561
|
-
|
|
1071
|
+
|
|
562
1072
|
// Story Pointsを設定(カスタムフィールド)
|
|
563
1073
|
// 注意: JIRAプロジェクトによってカスタムフィールドIDが異なる場合があります
|
|
564
1074
|
// 環境変数 JIRA_STORY_POINTS_FIELD で設定可能(例: customfield_10016)
|
|
565
1075
|
if (storyPoints !== undefined) {
|
|
566
|
-
const storyPointsField =
|
|
1076
|
+
const storyPointsField =
|
|
1077
|
+
process.env.JIRA_STORY_POINTS_FIELD || 'customfield_10016';
|
|
567
1078
|
storyPayload.fields[storyPointsField] = storyPoints;
|
|
568
1079
|
}
|
|
569
|
-
|
|
1080
|
+
|
|
570
1081
|
// 担当者を設定(アカウントIDが必要な場合があるため、オプション)
|
|
571
1082
|
// 注意: JIRAのアカウントIDが必要な場合があります
|
|
572
1083
|
// if (storyInfo?.assignee) {
|
|
573
1084
|
// storyPayload.fields.assignee = { name: storyInfo.assignee };
|
|
574
1085
|
// }
|
|
575
|
-
|
|
1086
|
+
|
|
576
1087
|
const story = await client.createIssue(storyPayload);
|
|
577
1088
|
console.log(` ✅ Story created: ${story.key} [${currentPhaseLabel}]`);
|
|
578
|
-
|
|
1089
|
+
|
|
579
1090
|
// 期限とStory Pointsの情報を表示
|
|
580
1091
|
if (storyDetails.dueDate) {
|
|
581
1092
|
console.log(` 期限: ${storyDetails.dueDate}`);
|
|
@@ -586,42 +1097,65 @@ async function syncTasksToJIRA(featureName: string): Promise<void> {
|
|
|
586
1097
|
if (storyPoints !== undefined) {
|
|
587
1098
|
console.log(` Story Points: ${storyPoints} SP`);
|
|
588
1099
|
}
|
|
589
|
-
|
|
1100
|
+
|
|
590
1101
|
createdStories.push(story.key);
|
|
591
|
-
|
|
1102
|
+
|
|
592
1103
|
// 進捗表示(大量作成時の見通し向上)
|
|
593
1104
|
if (createdStories.length % 5 === 0) {
|
|
594
|
-
console.log(
|
|
1105
|
+
console.log(
|
|
1106
|
+
` 📊 Progress: ${createdStories.length} stories created so far...`,
|
|
1107
|
+
);
|
|
595
1108
|
}
|
|
596
|
-
|
|
1109
|
+
|
|
597
1110
|
// Epic Linkは手動設定が必要(JIRA Cloudの制約)
|
|
598
1111
|
console.log(` ℹ️ Epic: ${epic.key} に手動でリンクしてください`);
|
|
599
1112
|
} catch (error: any) {
|
|
600
|
-
console.error(
|
|
601
|
-
|
|
1113
|
+
console.error(
|
|
1114
|
+
` ❌ Failed to create Story "${storyTitle}":`,
|
|
1115
|
+
error instanceof Error ? error.message : error,
|
|
1116
|
+
);
|
|
1117
|
+
|
|
602
1118
|
// JIRA APIエラーの詳細を表示
|
|
603
1119
|
if (error.response?.data) {
|
|
604
|
-
console.error(
|
|
605
|
-
|
|
1120
|
+
console.error(
|
|
1121
|
+
' 📋 JIRA API Error Details:',
|
|
1122
|
+
JSON.stringify(error.response.data, null, 2),
|
|
1123
|
+
);
|
|
1124
|
+
|
|
606
1125
|
// Story Pointsフィールドのエラーの場合、警告を表示
|
|
607
|
-
if (
|
|
1126
|
+
if (
|
|
1127
|
+
error.response.data.errors &&
|
|
1128
|
+
Object.keys(error.response.data.errors).some((key) =>
|
|
1129
|
+
key.includes('customfield'),
|
|
1130
|
+
)
|
|
1131
|
+
) {
|
|
608
1132
|
console.error(' ⚠️ Story Pointsフィールドの設定に失敗しました。');
|
|
609
|
-
console.error(
|
|
610
|
-
|
|
1133
|
+
console.error(
|
|
1134
|
+
' 💡 環境変数 JIRA_STORY_POINTS_FIELD を正しいカスタムフィールドIDに設定してください。',
|
|
1135
|
+
);
|
|
1136
|
+
console.error(
|
|
1137
|
+
' 💡 JIRA管理画面でStory PointsのカスタムフィールドIDを確認してください。',
|
|
1138
|
+
);
|
|
611
1139
|
}
|
|
612
1140
|
}
|
|
613
|
-
|
|
1141
|
+
|
|
614
1142
|
// エラーがあっても他のStoryの作成は継続
|
|
615
1143
|
}
|
|
616
1144
|
}
|
|
617
|
-
|
|
1145
|
+
|
|
618
1146
|
// 新規作成数と再利用数を正確に計算
|
|
619
|
-
const newStoryCount = createdStories.filter(
|
|
620
|
-
|
|
1147
|
+
const newStoryCount = createdStories.filter(
|
|
1148
|
+
(key) => !existingStoryKeys.has(key),
|
|
1149
|
+
).length;
|
|
1150
|
+
const reusedStoryCount = createdStories.filter((key) =>
|
|
1151
|
+
existingStoryKeys.has(key),
|
|
1152
|
+
).length;
|
|
621
1153
|
|
|
622
1154
|
console.log('\n✅ JIRA sync completed');
|
|
623
1155
|
console.log(` Epic: ${epic.key}`);
|
|
624
|
-
console.log(
|
|
1156
|
+
console.log(
|
|
1157
|
+
` Stories: ${createdStories.length} processed (${newStoryCount} new, ${reusedStoryCount} reused)`,
|
|
1158
|
+
);
|
|
625
1159
|
|
|
626
1160
|
// spec.json を更新
|
|
627
1161
|
const jiraBaseUrl = process.env.ATLASSIAN_URL || '';
|
|
@@ -630,10 +1164,12 @@ async function syncTasksToJIRA(featureName: string): Promise<void> {
|
|
|
630
1164
|
projectKey: projectMeta.jiraProjectKey,
|
|
631
1165
|
epicKey: epic.key,
|
|
632
1166
|
epicUrl: `${jiraBaseUrl}/browse/${epic.key}`,
|
|
633
|
-
storyKeys: createdStories
|
|
1167
|
+
storyKeys: createdStories,
|
|
634
1168
|
});
|
|
635
1169
|
} catch (error) {
|
|
636
|
-
console.warn(
|
|
1170
|
+
console.warn(
|
|
1171
|
+
`⚠️ Failed to update spec.json after JIRA sync: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1172
|
+
);
|
|
637
1173
|
// spec.json更新の失敗はスクリプト全体の失敗とはしない(JIRA同期は成功しているため)
|
|
638
1174
|
}
|
|
639
1175
|
}
|
|
@@ -641,12 +1177,12 @@ async function syncTasksToJIRA(featureName: string): Promise<void> {
|
|
|
641
1177
|
// CLI実行
|
|
642
1178
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
643
1179
|
const args = process.argv.slice(2);
|
|
644
|
-
|
|
1180
|
+
|
|
645
1181
|
if (args.length === 0) {
|
|
646
1182
|
console.error('Usage: npm run jira:sync <feature-name>');
|
|
647
1183
|
process.exit(1);
|
|
648
1184
|
}
|
|
649
|
-
|
|
1185
|
+
|
|
650
1186
|
syncTasksToJIRA(args[0])
|
|
651
1187
|
.then(() => process.exit(0))
|
|
652
1188
|
.catch((error) => {
|
|
@@ -656,4 +1192,3 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
656
1192
|
}
|
|
657
1193
|
|
|
658
1194
|
export { syncTasksToJIRA, JIRAClient };
|
|
659
|
-
|