specweave 0.32.0 → 0.32.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +215 -2
- package/README.md +22 -0
- package/bin/specweave.js +52 -1
- package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.d.ts +100 -0
- package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.d.ts.map +1 -0
- package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.js +291 -0
- package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.d.ts +103 -0
- package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.js +310 -0
- package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-permission-gate.d.ts +126 -0
- package/dist/plugins/specweave-jira/lib/jira-permission-gate.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-permission-gate.js +207 -0
- package/dist/plugins/specweave-jira/lib/jira-permission-gate.js.map +1 -0
- package/dist/src/adapters/codex/README.md +1 -1
- package/dist/src/adapters/codex/adapter.js +1 -1
- package/dist/src/cli/commands/archive.d.ts +2 -0
- package/dist/src/cli/commands/archive.d.ts.map +1 -1
- package/dist/src/cli/commands/archive.js +33 -0
- package/dist/src/cli/commands/archive.js.map +1 -1
- package/dist/src/cli/commands/cache.d.ts +17 -0
- package/dist/src/cli/commands/cache.d.ts.map +1 -0
- package/dist/src/cli/commands/cache.js +126 -0
- package/dist/src/cli/commands/cache.js.map +1 -0
- package/dist/src/cli/commands/context.d.ts +92 -0
- package/dist/src/cli/commands/context.d.ts.map +1 -0
- package/dist/src/cli/commands/context.js +205 -0
- package/dist/src/cli/commands/context.js.map +1 -0
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +112 -70
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/plan/increment-detector.js +2 -2
- package/dist/src/cli/commands/plan/increment-detector.js.map +1 -1
- package/dist/src/cli/commands/sync-spec-commits.js +1 -1
- package/dist/src/cli/commands/sync-spec-commits.js.map +1 -1
- package/dist/src/cli/commands/sync-specs.js +2 -2
- package/dist/src/cli/commands/sync-specs.js.map +1 -1
- package/dist/src/cli/helpers/github/increment-profile-selector.js +1 -1
- package/dist/src/cli/helpers/github/increment-profile-selector.js.map +1 -1
- package/dist/src/cli/helpers/init/external-import.d.ts +3 -0
- package/dist/src/cli/helpers/init/external-import.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/external-import.js +17 -4
- package/dist/src/cli/helpers/init/external-import.js.map +1 -1
- package/dist/src/cli/helpers/init/index.d.ts +1 -0
- package/dist/src/cli/helpers/init/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/index.js +2 -0
- package/dist/src/cli/helpers/init/index.js.map +1 -1
- package/dist/src/cli/helpers/init/jira-ado-auto-detect.d.ts +70 -0
- package/dist/src/cli/helpers/init/jira-ado-auto-detect.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/jira-ado-auto-detect.js +214 -4
- package/dist/src/cli/helpers/init/jira-ado-auto-detect.js.map +1 -1
- package/dist/src/cli/helpers/init/living-docs-preflight.d.ts +4 -0
- package/dist/src/cli/helpers/init/living-docs-preflight.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/living-docs-preflight.js +34 -3
- package/dist/src/cli/helpers/init/living-docs-preflight.js.map +1 -1
- package/dist/src/cli/helpers/init/testing-config.d.ts +3 -0
- package/dist/src/cli/helpers/init/testing-config.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/testing-config.js +9 -2
- package/dist/src/cli/helpers/init/testing-config.js.map +1 -1
- package/dist/src/cli/helpers/init/translation-config.d.ts +3 -0
- package/dist/src/cli/helpers/init/translation-config.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/translation-config.js +21 -4
- package/dist/src/cli/helpers/init/translation-config.js.map +1 -1
- package/dist/src/cli/helpers/init/wizard-navigation.d.ts +45 -0
- package/dist/src/cli/helpers/init/wizard-navigation.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/wizard-navigation.js +97 -0
- package/dist/src/cli/helpers/init/wizard-navigation.js.map +1 -0
- package/dist/src/cli/workers/living-docs-worker.js +66 -1
- package/dist/src/cli/workers/living-docs-worker.js.map +1 -1
- package/dist/src/config/types.d.ts +203 -1208
- package/dist/src/config/types.d.ts.map +1 -1
- package/dist/src/core/discrepancy/increment-generator.d.ts.map +1 -1
- package/dist/src/core/discrepancy/increment-generator.js +5 -2
- package/dist/src/core/discrepancy/increment-generator.js.map +1 -1
- package/dist/src/core/increment/duplicate-detector.js +2 -2
- package/dist/src/core/increment/duplicate-detector.js.map +1 -1
- package/dist/src/core/increment/increment-archiver.d.ts +49 -4
- package/dist/src/core/increment/increment-archiver.d.ts.map +1 -1
- package/dist/src/core/increment/increment-archiver.js +123 -22
- package/dist/src/core/increment/increment-archiver.js.map +1 -1
- package/dist/src/core/increment/increment-status.js +2 -2
- package/dist/src/core/increment/increment-status.js.map +1 -1
- package/dist/src/core/increment/increment-utils.d.ts +150 -0
- package/dist/src/core/increment/increment-utils.d.ts.map +1 -1
- package/dist/src/core/increment/increment-utils.js +216 -4
- package/dist/src/core/increment/increment-utils.js.map +1 -1
- package/dist/src/core/increment/metadata-validator.js +1 -1
- package/dist/src/core/increment/metadata-validator.js.map +1 -1
- package/dist/src/core/living-docs/feature-archiver.d.ts +4 -0
- package/dist/src/core/living-docs/feature-archiver.d.ts.map +1 -1
- package/dist/src/core/living-docs/feature-archiver.js +32 -10
- package/dist/src/core/living-docs/feature-archiver.js.map +1 -1
- package/dist/src/core/living-docs/feature-id-manager.d.ts.map +1 -1
- package/dist/src/core/living-docs/feature-id-manager.js +8 -4
- package/dist/src/core/living-docs/feature-id-manager.js.map +1 -1
- package/dist/src/core/living-docs/governance/ecosystem-detector.d.ts +38 -0
- package/dist/src/core/living-docs/governance/ecosystem-detector.d.ts.map +1 -0
- package/dist/src/core/living-docs/governance/ecosystem-detector.js +325 -0
- package/dist/src/core/living-docs/governance/ecosystem-detector.js.map +1 -0
- package/dist/src/core/living-docs/governance/frontend-standards-parser.d.ts +74 -0
- package/dist/src/core/living-docs/governance/frontend-standards-parser.d.ts.map +1 -0
- package/dist/src/core/living-docs/governance/frontend-standards-parser.js +366 -0
- package/dist/src/core/living-docs/governance/frontend-standards-parser.js.map +1 -0
- package/dist/src/core/living-docs/governance/go-standards-parser.d.ts +64 -0
- package/dist/src/core/living-docs/governance/go-standards-parser.d.ts.map +1 -0
- package/dist/src/core/living-docs/governance/go-standards-parser.js +229 -0
- package/dist/src/core/living-docs/governance/go-standards-parser.js.map +1 -0
- package/dist/src/core/living-docs/governance/index.d.ts +50 -0
- package/dist/src/core/living-docs/governance/index.d.ts.map +1 -0
- package/dist/src/core/living-docs/governance/index.js +56 -0
- package/dist/src/core/living-docs/governance/index.js.map +1 -0
- package/dist/src/core/living-docs/governance/java-standards-parser.d.ts +89 -0
- package/dist/src/core/living-docs/governance/java-standards-parser.d.ts.map +1 -0
- package/dist/src/core/living-docs/governance/java-standards-parser.js +356 -0
- package/dist/src/core/living-docs/governance/java-standards-parser.js.map +1 -0
- package/dist/src/core/living-docs/governance/python-standards-parser.d.ts +83 -0
- package/dist/src/core/living-docs/governance/python-standards-parser.d.ts.map +1 -0
- package/dist/src/core/living-docs/governance/python-standards-parser.js +347 -0
- package/dist/src/core/living-docs/governance/python-standards-parser.js.map +1 -0
- package/dist/src/core/living-docs/governance/standards-generator.d.ts +38 -0
- package/dist/src/core/living-docs/governance/standards-generator.d.ts.map +1 -0
- package/dist/src/core/living-docs/governance/standards-generator.js +476 -0
- package/dist/src/core/living-docs/governance/standards-generator.js.map +1 -0
- package/dist/src/core/living-docs/hierarchy-mapper.js +3 -3
- package/dist/src/core/living-docs/hierarchy-mapper.js.map +1 -1
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.d.ts +18 -0
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js +299 -0
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.d.ts +15 -0
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.js +138 -0
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/file-sampler.d.ts +24 -0
- package/dist/src/core/living-docs/intelligent-analyzer/file-sampler.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/file-sampler.js +198 -0
- package/dist/src/core/living-docs/intelligent-analyzer/file-sampler.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/inconsistency-detector.d.ts +17 -0
- package/dist/src/core/living-docs/intelligent-analyzer/inconsistency-detector.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/inconsistency-detector.js +241 -0
- package/dist/src/core/living-docs/intelligent-analyzer/inconsistency-detector.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/index.d.ts +28 -0
- package/dist/src/core/living-docs/intelligent-analyzer/index.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/index.js +197 -0
- package/dist/src/core/living-docs/intelligent-analyzer/index.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.d.ts +22 -0
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js +482 -0
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/strategy-generator.d.ts +42 -0
- package/dist/src/core/living-docs/intelligent-analyzer/strategy-generator.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/strategy-generator.js +343 -0
- package/dist/src/core/living-docs/intelligent-analyzer/strategy-generator.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts +190 -0
- package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/types.js +7 -0
- package/dist/src/core/living-docs/intelligent-analyzer/types.js.map +1 -0
- package/dist/src/core/living-docs/living-docs-sync.d.ts +11 -3
- package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.js +53 -10
- package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
- package/dist/src/core/living-docs/module-analyzer.d.ts +22 -0
- package/dist/src/core/living-docs/module-analyzer.d.ts.map +1 -1
- package/dist/src/core/living-docs/module-analyzer.js +123 -19
- package/dist/src/core/living-docs/module-analyzer.js.map +1 -1
- package/dist/src/core/llm/provider-factory.js +2 -2
- package/dist/src/core/llm/provider-factory.js.map +1 -1
- package/dist/src/core/llm/providers/anthropic-provider.js +1 -1
- package/dist/src/core/llm/providers/bedrock-provider.d.ts.map +1 -1
- package/dist/src/core/llm/providers/bedrock-provider.js +8 -4
- package/dist/src/core/llm/providers/bedrock-provider.js.map +1 -1
- package/dist/src/core/sync/spec-increment-mapper.js +3 -3
- package/dist/src/core/sync/spec-increment-mapper.js.map +1 -1
- package/dist/src/importers/item-converter.d.ts +25 -0
- package/dist/src/importers/item-converter.d.ts.map +1 -1
- package/dist/src/importers/item-converter.js +135 -5
- package/dist/src/importers/item-converter.js.map +1 -1
- package/dist/src/importers/jira-importer.d.ts +14 -0
- package/dist/src/importers/jira-importer.d.ts.map +1 -1
- package/dist/src/importers/jira-importer.js +75 -0
- package/dist/src/importers/jira-importer.js.map +1 -1
- package/dist/src/init/architecture/types.d.ts +33 -140
- package/dist/src/init/architecture/types.d.ts.map +1 -1
- package/dist/src/init/compliance/types.d.ts +30 -27
- package/dist/src/init/compliance/types.d.ts.map +1 -1
- package/dist/src/init/repo/types.d.ts +11 -34
- package/dist/src/init/repo/types.d.ts.map +1 -1
- package/dist/src/init/research/src/config/types.d.ts +15 -82
- package/dist/src/init/research/src/config/types.d.ts.map +1 -1
- package/dist/src/init/research/types.d.ts +38 -93
- package/dist/src/init/research/types.d.ts.map +1 -1
- package/dist/src/init/team/types.d.ts +4 -42
- package/dist/src/init/team/types.d.ts.map +1 -1
- package/dist/src/integrations/jira/jira-token-provider.d.ts +93 -0
- package/dist/src/integrations/jira/jira-token-provider.d.ts.map +1 -0
- package/dist/src/integrations/jira/jira-token-provider.js +160 -0
- package/dist/src/integrations/jira/jira-token-provider.js.map +1 -0
- package/dist/src/sync/ado-reconciler.d.ts +92 -0
- package/dist/src/sync/ado-reconciler.d.ts.map +1 -0
- package/dist/src/sync/ado-reconciler.js +335 -0
- package/dist/src/sync/ado-reconciler.js.map +1 -0
- package/dist/src/sync/jira-reconciler.d.ts +106 -0
- package/dist/src/sync/jira-reconciler.d.ts.map +1 -0
- package/dist/src/sync/jira-reconciler.js +405 -0
- package/dist/src/sync/jira-reconciler.js.map +1 -0
- package/dist/src/types/dashboard-cache.d.ts +181 -0
- package/dist/src/types/dashboard-cache.d.ts.map +1 -0
- package/dist/src/types/dashboard-cache.js +65 -0
- package/dist/src/types/dashboard-cache.js.map +1 -0
- package/dist/src/types/model-selection.d.ts +6 -4
- package/dist/src/types/model-selection.d.ts.map +1 -1
- package/dist/src/types/model-selection.js +3 -1
- package/dist/src/types/model-selection.js.map +1 -1
- package/dist/src/utils/docs-validator.d.ts +131 -0
- package/dist/src/utils/docs-validator.d.ts.map +1 -0
- package/dist/src/utils/docs-validator.js +529 -0
- package/dist/src/utils/docs-validator.js.map +1 -0
- package/dist/src/utils/external-tool-drift-detector.d.ts +1 -1
- package/dist/src/utils/external-tool-drift-detector.d.ts.map +1 -1
- package/dist/src/utils/external-tool-drift-detector.js +5 -4
- package/dist/src/utils/external-tool-drift-detector.js.map +1 -1
- package/dist/src/utils/feature-id-collision.js +1 -1
- package/dist/src/utils/feature-id-collision.js.map +1 -1
- package/dist/src/utils/feature-id-derivation.d.ts +8 -3
- package/dist/src/utils/feature-id-derivation.d.ts.map +1 -1
- package/dist/src/utils/feature-id-derivation.js +14 -6
- package/dist/src/utils/feature-id-derivation.js.map +1 -1
- package/dist/src/utils/html-to-mdx.d.ts +1 -0
- package/dist/src/utils/html-to-mdx.d.ts.map +1 -1
- package/dist/src/utils/html-to-mdx.js +43 -5
- package/dist/src/utils/html-to-mdx.js.map +1 -1
- package/dist/src/utils/model-selection.d.ts +3 -4
- package/dist/src/utils/model-selection.d.ts.map +1 -1
- package/dist/src/utils/model-selection.js +3 -4
- package/dist/src/utils/model-selection.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/agents/code-standards-detective/AGENT.md +48 -0
- package/plugins/specweave/agents/pm/AGENT.md +10 -7
- package/plugins/specweave/commands/specweave-archive-features.md +5 -7
- package/plugins/specweave/commands/specweave-archive.md +2 -1
- package/plugins/specweave/commands/specweave-costs.md +4 -4
- package/plugins/specweave/commands/specweave-do.md +44 -10
- package/plugins/specweave/commands/specweave-done.md +109 -0
- package/plugins/specweave/commands/specweave-import-external.md +45 -18
- package/plugins/specweave/commands/specweave-increment.md +331 -33
- package/plugins/specweave/commands/specweave-jobs.md +2 -2
- package/plugins/specweave/commands/specweave-progress.md +4 -4
- package/plugins/specweave/commands/specweave-restore-feature.md +5 -4
- package/plugins/specweave/commands/specweave-sync-docs.md +1 -1
- package/plugins/specweave/commands/specweave-sync-specs.md +216 -322
- package/plugins/specweave/commands/specweave-validate-features.md +13 -8
- package/plugins/specweave/commands/specweave-validate.md +27 -1
- package/plugins/specweave/hooks/docs-changed.sh.backup +79 -0
- package/plugins/specweave/hooks/hooks.json +43 -4
- package/plugins/specweave/hooks/human-input-required.sh.backup +75 -0
- package/plugins/specweave/hooks/lib/common-setup.sh +375 -0
- package/plugins/specweave/hooks/lib/crash-prevention.sh +336 -0
- package/plugins/specweave/hooks/post-first-increment.sh.backup +61 -0
- package/plugins/specweave/hooks/post-increment-change.sh.backup +98 -0
- package/plugins/specweave/hooks/post-increment-completion.sh.backup +231 -0
- package/plugins/specweave/hooks/post-increment-planning.sh.backup +1048 -0
- package/plugins/specweave/hooks/post-increment-status-change.sh.backup +147 -0
- package/plugins/specweave/hooks/post-spec-update.sh.backup +158 -0
- package/plugins/specweave/hooks/post-task-completion.sh +4 -23
- package/plugins/specweave/hooks/post-user-story-complete.sh.backup +179 -0
- package/plugins/specweave/hooks/pre-command-deduplication.sh +1 -6
- package/plugins/specweave/hooks/pre-command-deduplication.sh.backup +83 -0
- package/plugins/specweave/hooks/pre-implementation.sh.backup +67 -0
- package/plugins/specweave/hooks/pre-task-completion.sh +8 -37
- package/plugins/specweave/hooks/pre-task-completion.sh.backup +194 -0
- package/plugins/specweave/hooks/pre-tool-use.sh +2 -11
- package/plugins/specweave/hooks/pre-tool-use.sh.backup +133 -0
- package/plugins/specweave/hooks/spec-project-validator.sh +80 -25
- package/plugins/specweave/hooks/universal/dispatcher.mjs +135 -42
- package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +183 -0
- package/plugins/specweave/hooks/user-prompt-submit.sh +140 -38
- package/plugins/specweave/hooks/user-prompt-submit.sh.backup +386 -0
- package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +12 -0
- package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +89 -0
- package/plugins/specweave/hooks/v2/guards/bash-file-guard.sh +211 -0
- package/plugins/specweave/hooks/v2/guards/bash-file-guard.test.sh +163 -0
- package/plugins/specweave/hooks/v2/guards/completion-guard.sh +26 -28
- package/plugins/specweave/hooks/v2/guards/features-folder-guard.sh +50 -0
- package/plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh +135 -0
- package/plugins/specweave/lib/vendor/core/increment/duplicate-detector.js +2 -2
- package/plugins/specweave/lib/vendor/core/increment/duplicate-detector.js.map +1 -1
- package/plugins/specweave/scripts/README.md +166 -0
- package/plugins/specweave/scripts/cleanup-state.sh +142 -0
- package/plugins/specweave/scripts/force-kill.sh +142 -0
- package/plugins/specweave/scripts/jobs.js +171 -0
- package/plugins/specweave/scripts/progress.js +170 -0
- package/plugins/specweave/scripts/read-costs.sh +132 -0
- package/plugins/specweave/scripts/read-jobs.sh +324 -0
- package/plugins/specweave/scripts/read-progress.sh +185 -0
- package/plugins/specweave/scripts/read-status.sh +146 -0
- package/plugins/specweave/scripts/read-workflow.sh +173 -0
- package/plugins/specweave/scripts/rebuild-dashboard-cache.sh +327 -0
- package/plugins/specweave/scripts/session-watchdog.sh +192 -0
- package/plugins/specweave/scripts/status.js +154 -0
- package/plugins/specweave/scripts/update-dashboard-cache.sh +281 -0
- package/plugins/specweave/skills/code-standards-analyzer/SKILL.md +58 -6
- package/plugins/specweave/skills/increment-planner/SKILL.md +388 -48
- package/plugins/specweave/skills/increment-planner/templates/spec-multi-project.md +17 -7
- package/plugins/specweave/skills/increment-planner/templates/spec-single-project.md +6 -1
- package/plugins/specweave/skills/increment-planner/templates/tasks-multi-project.md +1 -1
- package/plugins/specweave/skills/increment-planner/templates/tasks-single-project.md +1 -1
- package/plugins/specweave/skills/instant-status/SKILL.md +70 -0
- package/plugins/specweave-ado/commands/cleanup-duplicates.md +212 -0
- package/plugins/specweave-ado/commands/reconcile.md +120 -0
- package/plugins/specweave-ado/hooks/post-living-docs-update.sh.backup +353 -0
- package/plugins/specweave-ado/hooks/post-task-completion.sh.backup +172 -0
- package/plugins/specweave-ado/lib/ado-duplicate-detector.js +279 -0
- package/plugins/specweave-ado/lib/ado-duplicate-detector.ts +407 -0
- package/plugins/specweave-ado/lib/enhanced-ado-sync.js +170 -0
- package/plugins/specweave-docs/commands/build.md +32 -4
- package/plugins/specweave-docs/commands/preview.md +43 -1
- package/plugins/specweave-docs/commands/validate.md +250 -0
- package/plugins/specweave-github/agents/github-manager/AGENT.md +2 -2
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +1262 -0
- package/plugins/specweave-github/hooks/post-task-completion.sh.backup +258 -0
- package/plugins/specweave-github/lib/enhanced-github-sync.js +220 -0
- package/plugins/specweave-infrastructure/skills/hetzner-provisioner/README.md +1 -1
- package/plugins/specweave-jira/agents/jira-manager/AGENT.md +1 -1
- package/plugins/specweave-jira/agents/jira-multi-project-mapper/AGENT.md +530 -0
- package/plugins/specweave-jira/agents/jira-sync-judge/AGENT.md +438 -0
- package/plugins/specweave-jira/commands/cleanup-duplicates.md +219 -0
- package/plugins/specweave-jira/commands/close.md +297 -0
- package/plugins/specweave-jira/commands/create.md +198 -0
- package/plugins/specweave-jira/commands/reconcile.md +123 -0
- package/plugins/specweave-jira/commands/status.md +215 -0
- package/plugins/specweave-jira/hooks/post-task-completion.sh.backup +172 -0
- package/plugins/specweave-jira/lib/enhanced-jira-sync.js +134 -0
- package/plugins/specweave-jira/lib/jira-duplicate-detector.js +296 -0
- package/plugins/specweave-jira/lib/jira-duplicate-detector.ts +434 -0
- package/plugins/specweave-jira/lib/jira-permission-gate.js +160 -0
- package/plugins/specweave-jira/lib/jira-permission-gate.ts +276 -0
- package/plugins/specweave-jira/lib/jira-profile-resolver.js +222 -0
- package/plugins/specweave-jira/lib/jira-profile-resolver.ts +427 -0
- package/plugins/specweave-jira/reference/jira-specweave-mapping.md +16 -11
- package/plugins/specweave-release/commands/specweave-release-npm.md +140 -14
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +1254 -0
- package/plugins/specweave-release/hooks/post-task-completion.sh.backup +110 -0
- package/plugins/specweave/hooks/post-edit-spec.sh +0 -265
- package/plugins/specweave/hooks/post-write-spec.sh +0 -267
- package/plugins/specweave/hooks/pre-edit-spec.sh +0 -151
- package/plugins/specweave/hooks/pre-write-spec.sh +0 -151
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JIRA Duplicate Detector (v0.33.0)
|
|
3
|
+
*
|
|
4
|
+
* Implements 3-phase duplicate protection for JIRA issues:
|
|
5
|
+
* 1. Detection: Check before create
|
|
6
|
+
* 2. Verification: Count check after create
|
|
7
|
+
* 3. Reflection: Auto-close duplicates
|
|
8
|
+
*
|
|
9
|
+
* Mirrors the GitHub DuplicateDetector pattern for consistency.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Logger, consoleLogger } from '../../../src/utils/logger.js';
|
|
13
|
+
|
|
14
|
+
export interface JiraIssue {
|
|
15
|
+
key: string;
|
|
16
|
+
summary: string;
|
|
17
|
+
status: string;
|
|
18
|
+
created: string;
|
|
19
|
+
url?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface DuplicateGroup {
|
|
23
|
+
summary: string;
|
|
24
|
+
issues: JiraIssue[];
|
|
25
|
+
keepIssue: JiraIssue;
|
|
26
|
+
duplicates: JiraIssue[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface DetectionResult {
|
|
30
|
+
found: boolean;
|
|
31
|
+
existingIssue?: JiraIssue;
|
|
32
|
+
count: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface VerificationResult {
|
|
36
|
+
success: boolean;
|
|
37
|
+
expectedCount: number;
|
|
38
|
+
actualCount: number;
|
|
39
|
+
duplicates: JiraIssue[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface CleanupResult {
|
|
43
|
+
closedCount: number;
|
|
44
|
+
keptCount: number;
|
|
45
|
+
errors: string[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class JiraDuplicateDetector {
|
|
49
|
+
private domain: string;
|
|
50
|
+
private auth: string;
|
|
51
|
+
private logger: Logger;
|
|
52
|
+
|
|
53
|
+
constructor(options: {
|
|
54
|
+
domain?: string;
|
|
55
|
+
email?: string;
|
|
56
|
+
token?: string;
|
|
57
|
+
logger?: Logger;
|
|
58
|
+
} = {}) {
|
|
59
|
+
this.domain = options.domain || process.env.JIRA_DOMAIN || '';
|
|
60
|
+
const email = options.email || process.env.JIRA_EMAIL || '';
|
|
61
|
+
const token = options.token || process.env.JIRA_API_TOKEN || '';
|
|
62
|
+
this.auth = Buffer.from(`${email}:${token}`).toString('base64');
|
|
63
|
+
this.logger = options.logger || consoleLogger;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Phase 1: Check if issue exists before creating
|
|
68
|
+
*/
|
|
69
|
+
async checkBeforeCreate(
|
|
70
|
+
summaryPattern: string,
|
|
71
|
+
incrementId?: string
|
|
72
|
+
): Promise<DetectionResult> {
|
|
73
|
+
try {
|
|
74
|
+
const issues = await this.searchIssues(summaryPattern);
|
|
75
|
+
|
|
76
|
+
if (issues.length > 0) {
|
|
77
|
+
return {
|
|
78
|
+
found: true,
|
|
79
|
+
existingIssue: issues[0],
|
|
80
|
+
count: issues.length,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { found: false, count: 0 };
|
|
85
|
+
} catch (error: any) {
|
|
86
|
+
this.logger.log(`⚠️ Detection check failed: ${error.message}`);
|
|
87
|
+
// Graceful degradation - continue anyway
|
|
88
|
+
return { found: false, count: 0 };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Phase 2: Verify count after creation
|
|
94
|
+
*/
|
|
95
|
+
async verifyAfterCreate(
|
|
96
|
+
summaryPattern: string,
|
|
97
|
+
expectedCount: number = 1
|
|
98
|
+
): Promise<VerificationResult> {
|
|
99
|
+
try {
|
|
100
|
+
const issues = await this.searchIssues(summaryPattern);
|
|
101
|
+
|
|
102
|
+
if (issues.length > expectedCount) {
|
|
103
|
+
// Duplicates detected!
|
|
104
|
+
const sorted = issues.sort(
|
|
105
|
+
(a, b) => new Date(a.created).getTime() - new Date(b.created).getTime()
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
success: false,
|
|
110
|
+
expectedCount,
|
|
111
|
+
actualCount: issues.length,
|
|
112
|
+
duplicates: sorted.slice(expectedCount), // All issues after expected count
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
success: true,
|
|
118
|
+
expectedCount,
|
|
119
|
+
actualCount: issues.length,
|
|
120
|
+
duplicates: [],
|
|
121
|
+
};
|
|
122
|
+
} catch (error: any) {
|
|
123
|
+
this.logger.log(`⚠️ Verification check failed: ${error.message}`);
|
|
124
|
+
return {
|
|
125
|
+
success: true, // Assume success on error
|
|
126
|
+
expectedCount,
|
|
127
|
+
actualCount: expectedCount,
|
|
128
|
+
duplicates: [],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Phase 3: Auto-close duplicates
|
|
135
|
+
*/
|
|
136
|
+
async closeDuplicates(
|
|
137
|
+
duplicates: JiraIssue[],
|
|
138
|
+
keepIssueKey: string
|
|
139
|
+
): Promise<CleanupResult> {
|
|
140
|
+
const result: CleanupResult = {
|
|
141
|
+
closedCount: 0,
|
|
142
|
+
keptCount: 1,
|
|
143
|
+
errors: [],
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
for (const issue of duplicates) {
|
|
147
|
+
try {
|
|
148
|
+
await this.closeIssue(issue.key, keepIssueKey);
|
|
149
|
+
result.closedCount++;
|
|
150
|
+
this.logger.log(` ✅ Closed ${issue.key} (duplicate of ${keepIssueKey})`);
|
|
151
|
+
} catch (error: any) {
|
|
152
|
+
result.errors.push(`${issue.key}: ${error.message}`);
|
|
153
|
+
this.logger.log(` ❌ Failed to close ${issue.key}: ${error.message}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Full cleanup: Find and close all duplicates for a feature
|
|
162
|
+
*/
|
|
163
|
+
async cleanupFeatureDuplicates(
|
|
164
|
+
featureId: string,
|
|
165
|
+
dryRun: boolean = false
|
|
166
|
+
): Promise<{
|
|
167
|
+
groups: DuplicateGroup[];
|
|
168
|
+
totalIssues: number;
|
|
169
|
+
duplicateCount: number;
|
|
170
|
+
closedCount: number;
|
|
171
|
+
}> {
|
|
172
|
+
// 1. Search for all issues with feature ID
|
|
173
|
+
const searchPattern = `[${featureId}]`;
|
|
174
|
+
const issues = await this.searchIssues(searchPattern);
|
|
175
|
+
|
|
176
|
+
this.logger.log(`\n🔍 Scanning for duplicates in Feature ${featureId}...`);
|
|
177
|
+
this.logger.log(` Found ${issues.length} total issues`);
|
|
178
|
+
|
|
179
|
+
// 2. Group by summary
|
|
180
|
+
const groups = this.groupBySummary(issues);
|
|
181
|
+
const duplicateGroups = groups.filter(g => g.duplicates.length > 0);
|
|
182
|
+
|
|
183
|
+
if (duplicateGroups.length === 0) {
|
|
184
|
+
this.logger.log(` ✅ No duplicates found!`);
|
|
185
|
+
return {
|
|
186
|
+
groups: [],
|
|
187
|
+
totalIssues: issues.length,
|
|
188
|
+
duplicateCount: 0,
|
|
189
|
+
closedCount: 0,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
this.logger.log(` Detected ${duplicateGroups.length} duplicate groups:\n`);
|
|
194
|
+
|
|
195
|
+
// 3. Display groups
|
|
196
|
+
for (let i = 0; i < duplicateGroups.length; i++) {
|
|
197
|
+
const group = duplicateGroups[i];
|
|
198
|
+
this.logger.log(` 📋 Group ${i + 1}: "${group.summary.substring(0, 50)}..."`);
|
|
199
|
+
this.logger.log(` - ${group.keepIssue.key} (KEEP) - Created ${group.keepIssue.created.split('T')[0]}`);
|
|
200
|
+
for (const dup of group.duplicates) {
|
|
201
|
+
this.logger.log(` - ${dup.key} (CLOSE) - Created ${dup.created.split('T')[0]} - DUPLICATE`);
|
|
202
|
+
}
|
|
203
|
+
this.logger.log('');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const totalDuplicates = duplicateGroups.reduce((sum, g) => sum + g.duplicates.length, 0);
|
|
207
|
+
|
|
208
|
+
if (dryRun) {
|
|
209
|
+
this.logger.log(`\n✅ Dry run complete!`);
|
|
210
|
+
this.logger.log(` Total issues: ${issues.length}`);
|
|
211
|
+
this.logger.log(` Duplicate groups: ${duplicateGroups.length}`);
|
|
212
|
+
this.logger.log(` Issues to close: ${totalDuplicates}`);
|
|
213
|
+
this.logger.log(`\n⚠️ This was a DRY RUN - no changes made.`);
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
groups: duplicateGroups,
|
|
217
|
+
totalIssues: issues.length,
|
|
218
|
+
duplicateCount: totalDuplicates,
|
|
219
|
+
closedCount: 0,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// 4. Close duplicates
|
|
224
|
+
let closedCount = 0;
|
|
225
|
+
this.logger.log(`🗑️ Closing duplicates...`);
|
|
226
|
+
|
|
227
|
+
for (const group of duplicateGroups) {
|
|
228
|
+
const result = await this.closeDuplicates(group.duplicates, group.keepIssue.key);
|
|
229
|
+
closedCount += result.closedCount;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
this.logger.log(`\n✅ Cleanup complete!`);
|
|
233
|
+
this.logger.log(` Closed: ${closedCount} duplicates`);
|
|
234
|
+
this.logger.log(` Kept: ${duplicateGroups.length} original issues`);
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
groups: duplicateGroups,
|
|
238
|
+
totalIssues: issues.length,
|
|
239
|
+
duplicateCount: totalDuplicates,
|
|
240
|
+
closedCount,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Group issues by summary
|
|
246
|
+
*/
|
|
247
|
+
private groupBySummary(issues: JiraIssue[]): DuplicateGroup[] {
|
|
248
|
+
const summaryMap = new Map<string, JiraIssue[]>();
|
|
249
|
+
|
|
250
|
+
for (const issue of issues) {
|
|
251
|
+
const existing = summaryMap.get(issue.summary) || [];
|
|
252
|
+
existing.push(issue);
|
|
253
|
+
summaryMap.set(issue.summary, existing);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const groups: DuplicateGroup[] = [];
|
|
257
|
+
|
|
258
|
+
for (const [summary, groupIssues] of summaryMap) {
|
|
259
|
+
// Sort by created date (oldest first)
|
|
260
|
+
const sorted = groupIssues.sort(
|
|
261
|
+
(a, b) => new Date(a.created).getTime() - new Date(b.created).getTime()
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
groups.push({
|
|
265
|
+
summary,
|
|
266
|
+
issues: sorted,
|
|
267
|
+
keepIssue: sorted[0],
|
|
268
|
+
duplicates: sorted.slice(1),
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return groups;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Search for issues using JQL
|
|
277
|
+
*/
|
|
278
|
+
private async searchIssues(summaryPattern: string): Promise<JiraIssue[]> {
|
|
279
|
+
if (!this.domain || !this.auth) {
|
|
280
|
+
throw new Error('JIRA credentials not configured');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const jql = encodeURIComponent(`summary ~ "${summaryPattern}" ORDER BY created ASC`);
|
|
284
|
+
const url = `https://${this.domain}/rest/api/3/search?jql=${jql}&fields=summary,status,created`;
|
|
285
|
+
|
|
286
|
+
const response = await fetch(url, {
|
|
287
|
+
headers: {
|
|
288
|
+
Authorization: `Basic ${this.auth}`,
|
|
289
|
+
Accept: 'application/json',
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
if (!response.ok) {
|
|
294
|
+
throw new Error(`JQL search failed: ${response.status}`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const data = await response.json();
|
|
298
|
+
return (data.issues || []).map((issue: any) => ({
|
|
299
|
+
key: issue.key,
|
|
300
|
+
summary: issue.fields.summary,
|
|
301
|
+
status: issue.fields.status?.name,
|
|
302
|
+
created: issue.fields.created,
|
|
303
|
+
url: `https://${this.domain}/browse/${issue.key}`,
|
|
304
|
+
}));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Close an issue with duplicate comment
|
|
309
|
+
*/
|
|
310
|
+
private async closeIssue(issueKey: string, originalKey: string): Promise<void> {
|
|
311
|
+
// First, add comment
|
|
312
|
+
await this.addComment(issueKey, originalKey);
|
|
313
|
+
|
|
314
|
+
// Then, transition to closed
|
|
315
|
+
const transitions = await this.getTransitions(issueKey);
|
|
316
|
+
|
|
317
|
+
// Find "Won't Do" or "Done" transition
|
|
318
|
+
const closeTransition = transitions.find(
|
|
319
|
+
(t: any) =>
|
|
320
|
+
t.name === "Won't Do" ||
|
|
321
|
+
t.name === 'Done' ||
|
|
322
|
+
t.name === 'Closed' ||
|
|
323
|
+
t.to?.name === "Won't Do" ||
|
|
324
|
+
t.to?.name === 'Done'
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
if (!closeTransition) {
|
|
328
|
+
throw new Error(`No close transition found. Available: ${transitions.map((t: any) => t.name).join(', ')}`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const url = `https://${this.domain}/rest/api/3/issue/${issueKey}/transitions`;
|
|
332
|
+
|
|
333
|
+
const response = await fetch(url, {
|
|
334
|
+
method: 'POST',
|
|
335
|
+
headers: {
|
|
336
|
+
Authorization: `Basic ${this.auth}`,
|
|
337
|
+
'Content-Type': 'application/json',
|
|
338
|
+
},
|
|
339
|
+
body: JSON.stringify({
|
|
340
|
+
transition: { id: closeTransition.id },
|
|
341
|
+
}),
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
if (!response.ok) {
|
|
345
|
+
const error = await response.text();
|
|
346
|
+
throw new Error(`Failed to transition issue: ${response.status} - ${error}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Get available transitions for an issue
|
|
352
|
+
*/
|
|
353
|
+
private async getTransitions(issueKey: string): Promise<any[]> {
|
|
354
|
+
const url = `https://${this.domain}/rest/api/3/issue/${issueKey}/transitions`;
|
|
355
|
+
|
|
356
|
+
const response = await fetch(url, {
|
|
357
|
+
headers: {
|
|
358
|
+
Authorization: `Basic ${this.auth}`,
|
|
359
|
+
Accept: 'application/json',
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
if (!response.ok) {
|
|
364
|
+
throw new Error(`Failed to get transitions: ${response.status}`);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const data = await response.json();
|
|
368
|
+
return data.transitions || [];
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Add duplicate comment to issue
|
|
373
|
+
*/
|
|
374
|
+
private async addComment(issueKey: string, originalKey: string): Promise<void> {
|
|
375
|
+
const url = `https://${this.domain}/rest/api/3/issue/${issueKey}/comment`;
|
|
376
|
+
|
|
377
|
+
const comment = `h2. Duplicate of ${originalKey}
|
|
378
|
+
|
|
379
|
+
This issue was automatically closed by SpecWeave cleanup because it is a duplicate.
|
|
380
|
+
|
|
381
|
+
The original issue (${originalKey}) contains the same content and should be used for tracking instead.
|
|
382
|
+
|
|
383
|
+
----
|
|
384
|
+
🤖 Auto-closed by SpecWeave Duplicate Cleanup`;
|
|
385
|
+
|
|
386
|
+
const response = await fetch(url, {
|
|
387
|
+
method: 'POST',
|
|
388
|
+
headers: {
|
|
389
|
+
Authorization: `Basic ${this.auth}`,
|
|
390
|
+
'Content-Type': 'application/json',
|
|
391
|
+
},
|
|
392
|
+
body: JSON.stringify({
|
|
393
|
+
body: {
|
|
394
|
+
type: 'doc',
|
|
395
|
+
version: 1,
|
|
396
|
+
content: [
|
|
397
|
+
{
|
|
398
|
+
type: 'paragraph',
|
|
399
|
+
content: [
|
|
400
|
+
{
|
|
401
|
+
type: 'text',
|
|
402
|
+
text: comment,
|
|
403
|
+
},
|
|
404
|
+
],
|
|
405
|
+
},
|
|
406
|
+
],
|
|
407
|
+
},
|
|
408
|
+
}),
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
if (!response.ok) {
|
|
412
|
+
// Non-fatal, just log warning
|
|
413
|
+
this.logger.log(` ⚠️ Failed to add comment to ${issueKey}`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Convenience function for quick duplicate cleanup
|
|
420
|
+
*/
|
|
421
|
+
export async function cleanupJiraDuplicates(
|
|
422
|
+
featureId: string,
|
|
423
|
+
dryRun: boolean = false
|
|
424
|
+
): Promise<{
|
|
425
|
+
groups: DuplicateGroup[];
|
|
426
|
+
totalIssues: number;
|
|
427
|
+
duplicateCount: number;
|
|
428
|
+
closedCount: number;
|
|
429
|
+
}> {
|
|
430
|
+
const detector = new JiraDuplicateDetector();
|
|
431
|
+
return detector.cleanupFeatureDuplicates(featureId, dryRun);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export default JiraDuplicateDetector;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
const DEFAULT_SYNC_SETTINGS = {
|
|
4
|
+
canUpsertInternalItems: false,
|
|
5
|
+
canUpdateExternalItems: false,
|
|
6
|
+
canUpdateStatus: false
|
|
7
|
+
};
|
|
8
|
+
class JiraPermissionGate {
|
|
9
|
+
constructor(settings, configPath) {
|
|
10
|
+
this.settings = settings;
|
|
11
|
+
this.configPath = configPath;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Check if write operations (create/update issues) are allowed
|
|
15
|
+
*
|
|
16
|
+
* Requires: canUpdateExternalItems = true
|
|
17
|
+
*/
|
|
18
|
+
checkWritePermission() {
|
|
19
|
+
if (this.settings.canUpdateExternalItems) {
|
|
20
|
+
return {
|
|
21
|
+
allowed: true,
|
|
22
|
+
reason: "Write operations permitted (canUpdateExternalItems=true)"
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
allowed: false,
|
|
27
|
+
reason: "Permission denied: JIRA updates are disabled.",
|
|
28
|
+
suggestedAction: `Enable sync.settings.canUpdateExternalItems in ${this.configPath}`,
|
|
29
|
+
settingPath: "sync.settings.canUpdateExternalItems"
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check if status updates (transitions) are allowed
|
|
34
|
+
*
|
|
35
|
+
* Requires: canUpdateStatus = true
|
|
36
|
+
*/
|
|
37
|
+
checkStatusPermission() {
|
|
38
|
+
if (this.settings.canUpdateStatus) {
|
|
39
|
+
return {
|
|
40
|
+
allowed: true,
|
|
41
|
+
reason: "Status updates permitted (canUpdateStatus=true)"
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
allowed: false,
|
|
46
|
+
reason: "Permission denied: JIRA status transitions are disabled.",
|
|
47
|
+
suggestedAction: `Enable sync.settings.canUpdateStatus in ${this.configPath}`,
|
|
48
|
+
settingPath: "sync.settings.canUpdateStatus"
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Check if internal item creation is allowed
|
|
53
|
+
*
|
|
54
|
+
* Requires: canUpsertInternalItems = true
|
|
55
|
+
*/
|
|
56
|
+
checkCreateInternalPermission() {
|
|
57
|
+
if (this.settings.canUpsertInternalItems) {
|
|
58
|
+
return {
|
|
59
|
+
allowed: true,
|
|
60
|
+
reason: "Internal item creation permitted (canUpsertInternalItems=true)"
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
allowed: false,
|
|
65
|
+
reason: "Permission denied: Creating internal items is disabled.",
|
|
66
|
+
suggestedAction: `Enable sync.settings.canUpsertInternalItems in ${this.configPath}`,
|
|
67
|
+
settingPath: "sync.settings.canUpsertInternalItems"
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if close operation is allowed (requires both write AND status)
|
|
72
|
+
*
|
|
73
|
+
* Requires: canUpdateExternalItems = true AND canUpdateStatus = true
|
|
74
|
+
*/
|
|
75
|
+
checkClosePermission() {
|
|
76
|
+
const writeCheck = this.checkWritePermission();
|
|
77
|
+
const statusCheck = this.checkStatusPermission();
|
|
78
|
+
if (writeCheck.allowed && statusCheck.allowed) {
|
|
79
|
+
return {
|
|
80
|
+
allowed: true,
|
|
81
|
+
reason: "Close operations permitted (canUpdateExternalItems=true, canUpdateStatus=true)"
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const missingPermissions = [];
|
|
85
|
+
if (!writeCheck.allowed) {
|
|
86
|
+
missingPermissions.push("canUpdateExternalItems");
|
|
87
|
+
}
|
|
88
|
+
if (!statusCheck.allowed) {
|
|
89
|
+
missingPermissions.push("canUpdateStatus");
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
allowed: false,
|
|
93
|
+
reason: `Permission denied: Closing JIRA issues requires ${missingPermissions.join(" and ")}.`,
|
|
94
|
+
suggestedAction: `Enable ${missingPermissions.map((p) => `sync.settings.${p}`).join(" and ")} in ${this.configPath}`,
|
|
95
|
+
settingPath: missingPermissions.map((p) => `sync.settings.${p}`).join(", ")
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get current settings
|
|
100
|
+
*/
|
|
101
|
+
getSettings() {
|
|
102
|
+
return { ...this.settings };
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get human-readable permission summary
|
|
106
|
+
*/
|
|
107
|
+
getPermissionSummary() {
|
|
108
|
+
const parts = [];
|
|
109
|
+
if (this.settings.canUpdateExternalItems) {
|
|
110
|
+
parts.push("create/update JIRA issues");
|
|
111
|
+
}
|
|
112
|
+
if (this.settings.canUpdateStatus) {
|
|
113
|
+
parts.push("transition issue status");
|
|
114
|
+
}
|
|
115
|
+
if (this.settings.canUpsertInternalItems) {
|
|
116
|
+
parts.push("create internal items");
|
|
117
|
+
}
|
|
118
|
+
if (parts.length === 0) {
|
|
119
|
+
return "All JIRA write operations disabled (read-only mode)";
|
|
120
|
+
}
|
|
121
|
+
return `Allowed: ${parts.join(", ")}`;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function createJiraPermissionGate(projectRoot = process.cwd()) {
|
|
125
|
+
const configPath = path.join(projectRoot, ".specweave", "config.json");
|
|
126
|
+
try {
|
|
127
|
+
const content = await fs.readFile(configPath, "utf-8");
|
|
128
|
+
const config = JSON.parse(content);
|
|
129
|
+
const settings = {
|
|
130
|
+
canUpsertInternalItems: config?.sync?.settings?.canUpsertInternalItems ?? false,
|
|
131
|
+
canUpdateExternalItems: config?.sync?.settings?.canUpdateExternalItems ?? false,
|
|
132
|
+
canUpdateStatus: config?.sync?.settings?.canUpdateStatus ?? false
|
|
133
|
+
};
|
|
134
|
+
return new JiraPermissionGate(settings, configPath);
|
|
135
|
+
} catch {
|
|
136
|
+
return new JiraPermissionGate(DEFAULT_SYNC_SETTINGS, configPath);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function canWriteToJira(projectRoot = process.cwd()) {
|
|
140
|
+
const gate = await createJiraPermissionGate(projectRoot);
|
|
141
|
+
return gate.checkWritePermission();
|
|
142
|
+
}
|
|
143
|
+
async function canUpdateJiraStatus(projectRoot = process.cwd()) {
|
|
144
|
+
const gate = await createJiraPermissionGate(projectRoot);
|
|
145
|
+
return gate.checkStatusPermission();
|
|
146
|
+
}
|
|
147
|
+
async function canCloseJiraIssue(projectRoot = process.cwd()) {
|
|
148
|
+
const gate = await createJiraPermissionGate(projectRoot);
|
|
149
|
+
return gate.checkClosePermission();
|
|
150
|
+
}
|
|
151
|
+
var jira_permission_gate_default = JiraPermissionGate;
|
|
152
|
+
export {
|
|
153
|
+
DEFAULT_SYNC_SETTINGS,
|
|
154
|
+
JiraPermissionGate,
|
|
155
|
+
canCloseJiraIssue,
|
|
156
|
+
canUpdateJiraStatus,
|
|
157
|
+
canWriteToJira,
|
|
158
|
+
createJiraPermissionGate,
|
|
159
|
+
jira_permission_gate_default as default
|
|
160
|
+
};
|