specweave 1.0.350 → 1.0.352
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/specweave.js +9 -0
- package/dist/plugins/specweave-ado/lib/ado-client-v2.d.ts +5 -0
- package/dist/plugins/specweave-ado/lib/ado-client-v2.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-client-v2.js +61 -23
- package/dist/plugins/specweave-ado/lib/ado-client-v2.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.js +3 -2
- package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-profile-resolver.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-profile-resolver.js +2 -1
- package/dist/plugins/specweave-ado/lib/ado-profile-resolver.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-spec-sync.d.ts +1 -1
- package/dist/plugins/specweave-ado/lib/ado-spec-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-spec-sync.js +25 -9
- package/dist/plugins/specweave-ado/lib/ado-spec-sync.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/conflict-resolver.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/conflict-resolver.js +17 -1
- package/dist/plugins/specweave-ado/lib/conflict-resolver.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/per-us-sync.d.ts +3 -0
- package/dist/plugins/specweave-ado/lib/per-us-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/per-us-sync.js +14 -1
- package/dist/plugins/specweave-ado/lib/per-us-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.js +10 -7
- package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client.d.ts +1 -1
- package/dist/plugins/specweave-github/lib/github-client.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client.js +7 -5
- package/dist/plugins/specweave-github/lib/github-client.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-cross-repo-sync.js +13 -3
- package/dist/plugins/specweave-github/lib/github-cross-repo-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync-cli.d.ts +24 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync-cli.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync-cli.js +36 -20
- package/dist/plugins/specweave-github/lib/github-feature-sync-cli.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts +4 -2
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.js +38 -9
- package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-graphql-client.d.ts +1 -0
- package/dist/plugins/specweave-github/lib/github-graphql-client.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-graphql-client.js +32 -22
- package/dist/plugins/specweave-github/lib/github-graphql-client.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-spec-frontmatter-updater.js +144 -8
- package/dist/plugins/specweave-github/lib/github-spec-frontmatter-updater.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-spec-sync.d.ts +8 -1
- package/dist/plugins/specweave-github/lib/github-spec-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-spec-sync.js +94 -24
- package/dist/plugins/specweave-github/lib/github-spec-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-sync-orchestrator.d.ts +1 -0
- package/dist/plugins/specweave-github/lib/github-sync-orchestrator.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-sync-orchestrator.js +2 -1
- package/dist/plugins/specweave-github/lib/github-sync-orchestrator.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-us-auto-closer.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-us-auto-closer.js +25 -0
- package/dist/plugins/specweave-github/lib/github-us-auto-closer.js.map +1 -1
- package/dist/plugins/specweave-github/lib/per-us-sync.d.ts +3 -0
- package/dist/plugins/specweave-github/lib/per-us-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/per-us-sync.js +29 -9
- package/dist/plugins/specweave-github/lib/per-us-sync.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/content-format-adapter.d.ts +59 -0
- package/dist/plugins/specweave-jira/lib/content-format-adapter.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/content-format-adapter.js +159 -0
- package/dist/plugins/specweave-jira/lib/content-format-adapter.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-deployment-detector.d.ts +45 -0
- package/dist/plugins/specweave-jira/lib/jira-deployment-detector.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-deployment-detector.js +92 -0
- package/dist/plugins/specweave-jira/lib/jira-deployment-detector.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.js +13 -28
- package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts +2 -1
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.js +19 -7
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-field-discovery.d.ts +47 -0
- package/dist/plugins/specweave-jira/lib/jira-field-discovery.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-field-discovery.js +110 -0
- package/dist/plugins/specweave-jira/lib/jira-field-discovery.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-paginated-search.d.ts +26 -0
- package/dist/plugins/specweave-jira/lib/jira-paginated-search.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-paginated-search.js +77 -0
- package/dist/plugins/specweave-jira/lib/jira-paginated-search.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-spec-commit-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-spec-commit-sync.js +5 -3
- package/dist/plugins/specweave-jira/lib/jira-spec-commit-sync.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-spec-sync.d.ts +17 -2
- package/dist/plugins/specweave-jira/lib/jira-spec-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-spec-sync.js +103 -33
- package/dist/plugins/specweave-jira/lib/jira-spec-sync.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts +4 -0
- package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-status-sync.js +19 -6
- package/dist/plugins/specweave-jira/lib/jira-status-sync.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/metadata-paths.d.ts +29 -0
- package/dist/plugins/specweave-jira/lib/metadata-paths.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/metadata-paths.js +73 -0
- package/dist/plugins/specweave-jira/lib/metadata-paths.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/reorganization-detector.d.ts +15 -2
- package/dist/plugins/specweave-jira/lib/reorganization-detector.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/reorganization-detector.js +121 -33
- package/dist/plugins/specweave-jira/lib/reorganization-detector.js.map +1 -1
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +23 -18
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/sync-progress.d.ts +6 -0
- package/dist/src/cli/commands/sync-progress.d.ts.map +1 -1
- package/dist/src/cli/commands/sync-progress.js +37 -0
- package/dist/src/cli/commands/sync-progress.js.map +1 -1
- package/dist/src/cli/commands/sync-task.d.ts +16 -0
- package/dist/src/cli/commands/sync-task.d.ts.map +1 -0
- package/dist/src/cli/commands/sync-task.js +42 -0
- package/dist/src/cli/commands/sync-task.js.map +1 -0
- package/dist/src/cli/helpers/init/instruction-file-merger.js +3 -3
- package/dist/src/cli/helpers/init/instruction-file-merger.js.map +1 -1
- package/dist/src/core/hooks/LifecycleHookDispatcher.d.ts +9 -1
- package/dist/src/core/hooks/LifecycleHookDispatcher.d.ts.map +1 -1
- package/dist/src/core/hooks/LifecycleHookDispatcher.js +26 -8
- package/dist/src/core/hooks/LifecycleHookDispatcher.js.map +1 -1
- package/dist/src/core/increment/metadata-manager.d.ts +13 -0
- package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
- package/dist/src/core/increment/metadata-manager.js +144 -17
- package/dist/src/core/increment/metadata-manager.js.map +1 -1
- package/dist/src/core/increment/status-change-sync-trigger.d.ts +1 -1
- package/dist/src/core/increment/status-change-sync-trigger.d.ts.map +1 -1
- package/dist/src/core/increment/status-change-sync-trigger.js +2 -1
- package/dist/src/core/increment/status-change-sync-trigger.js.map +1 -1
- package/dist/src/core/increment/status-commands.d.ts.map +1 -1
- package/dist/src/core/increment/status-commands.js +33 -11
- package/dist/src/core/increment/status-commands.js.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.js +2 -1
- package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
- package/dist/src/locales/de/cli.json +252 -77
- package/dist/src/locales/en/cli.json +7 -0
- package/dist/src/locales/es/cli.json +245 -3
- package/dist/src/locales/fr/cli.json +259 -84
- package/dist/src/locales/ja/cli.json +253 -78
- package/dist/src/locales/ko/cli.json +253 -78
- package/dist/src/locales/pt/cli.json +252 -77
- package/dist/src/locales/ru/cli.json +17 -3
- package/dist/src/locales/zh/cli.json +258 -83
- package/dist/src/sync/ado-reconciler.d.ts.map +1 -1
- package/dist/src/sync/ado-reconciler.js +5 -1
- package/dist/src/sync/ado-reconciler.js.map +1 -1
- package/dist/src/sync/base-reconciler.d.ts.map +1 -1
- package/dist/src/sync/base-reconciler.js +6 -1
- package/dist/src/sync/base-reconciler.js.map +1 -1
- package/dist/src/sync/config.d.ts +4 -0
- package/dist/src/sync/config.d.ts.map +1 -1
- package/dist/src/sync/config.js +6 -4
- package/dist/src/sync/config.js.map +1 -1
- package/dist/src/sync/external-issue-auto-creator.d.ts +3 -0
- package/dist/src/sync/external-issue-auto-creator.d.ts.map +1 -1
- package/dist/src/sync/external-issue-auto-creator.js +53 -17
- package/dist/src/sync/external-issue-auto-creator.js.map +1 -1
- package/dist/src/sync/external-item-sync-service.d.ts +9 -0
- package/dist/src/sync/external-item-sync-service.d.ts.map +1 -1
- package/dist/src/sync/external-item-sync-service.js +210 -9
- package/dist/src/sync/external-item-sync-service.js.map +1 -1
- package/dist/src/sync/github-reconciler.d.ts +30 -0
- package/dist/src/sync/github-reconciler.d.ts.map +1 -1
- package/dist/src/sync/github-reconciler.js +242 -3
- package/dist/src/sync/github-reconciler.js.map +1 -1
- package/dist/src/sync/jira-reconciler.d.ts.map +1 -1
- package/dist/src/sync/jira-reconciler.js +5 -1
- package/dist/src/sync/jira-reconciler.js.map +1 -1
- package/dist/src/sync/provider-router.d.ts.map +1 -1
- package/dist/src/sync/provider-router.js +2 -1
- package/dist/src/sync/provider-router.js.map +1 -1
- package/dist/src/sync/providers/ado.d.ts +4 -0
- package/dist/src/sync/providers/ado.d.ts.map +1 -1
- package/dist/src/sync/providers/ado.js +36 -11
- package/dist/src/sync/providers/ado.js.map +1 -1
- package/dist/src/sync/providers/github.d.ts.map +1 -1
- package/dist/src/sync/providers/github.js +48 -35
- package/dist/src/sync/providers/github.js.map +1 -1
- package/dist/src/sync/providers/jira.d.ts.map +1 -1
- package/dist/src/sync/providers/jira.js +42 -26
- package/dist/src/sync/providers/jira.js.map +1 -1
- package/dist/src/sync/status-mapper.d.ts +3 -1
- package/dist/src/sync/status-mapper.d.ts.map +1 -1
- package/dist/src/sync/status-mapper.js +10 -2
- package/dist/src/sync/status-mapper.js.map +1 -1
- package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
- package/dist/src/sync/sync-coordinator.js +29 -19
- package/dist/src/sync/sync-coordinator.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/hooks/v2/guards/task-ac-sync-guard.sh +31 -0
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.d.ts +13 -0
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +144 -17
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
- package/plugins/specweave/lib/vendor/sync/github-reconciler.d.ts +30 -0
- package/plugins/specweave/lib/vendor/sync/github-reconciler.js +242 -3
- package/plugins/specweave/lib/vendor/sync/github-reconciler.js.map +1 -1
- package/plugins/specweave/skills/architect/SKILL.md +2 -0
- package/plugins/specweave/skills/grill/SKILL.md +2 -0
- package/plugins/specweave/skills/team-lead/SKILL.md +43 -320
- package/plugins/specweave/skills/team-lead/agents/backend.md +60 -0
- package/plugins/specweave/skills/team-lead/agents/database.md +51 -0
- package/plugins/specweave/skills/team-lead/agents/frontend.md +61 -0
- package/plugins/specweave/skills/team-lead/agents/security.md +52 -0
- package/plugins/specweave/skills/team-lead/agents/testing.md +57 -0
- package/plugins/specweave/skills/test-aware-planner/SKILL.md +2 -0
- package/plugins/specweave-ado/hooks/post-task-completion.sh +2 -2
- package/plugins/specweave-ado/lib/ado-client-v2.js +51 -21
- package/plugins/specweave-ado/lib/ado-client-v2.ts +62 -23
- package/plugins/specweave-ado/lib/ado-duplicate-detector.js +4 -4
- package/plugins/specweave-ado/lib/ado-duplicate-detector.ts +4 -3
- package/plugins/specweave-ado/lib/ado-hierarchical-sync.js +54 -12
- package/plugins/specweave-ado/lib/ado-hierarchical-sync.ts +88 -18
- package/plugins/specweave-ado/lib/ado-profile-resolver.js +1 -1
- package/plugins/specweave-ado/lib/ado-profile-resolver.ts +3 -1
- package/plugins/specweave-ado/lib/ado-spec-sync.js +22 -9
- package/plugins/specweave-ado/lib/ado-spec-sync.ts +27 -9
- package/plugins/specweave-ado/lib/conflict-resolver.js +17 -1
- package/plugins/specweave-ado/lib/conflict-resolver.ts +17 -1
- package/plugins/specweave-ado/lib/enhanced-ado-sync.js +11 -1
- package/plugins/specweave-ado/lib/per-us-sync.js +8 -1
- package/plugins/specweave-ado/lib/per-us-sync.ts +17 -2
- package/plugins/specweave-github/hooks/github-auto-create-handler.sh +28 -2
- package/plugins/specweave-github/hooks/post-task-completion.sh +6 -3
- package/plugins/specweave-github/lib/enhanced-github-sync.js +35 -6
- package/plugins/specweave-github/lib/github-board-resolver.js +4 -4
- package/plugins/specweave-github/lib/github-board-resolver.ts +4 -4
- package/plugins/specweave-github/lib/github-client-v2.js +6 -6
- package/plugins/specweave-github/lib/github-client-v2.ts +11 -7
- package/plugins/specweave-github/lib/github-client.js +5 -4
- package/plugins/specweave-github/lib/github-client.ts +7 -5
- package/plugins/specweave-github/lib/github-cross-repo-sync.js +17 -3
- package/plugins/specweave-github/lib/github-cross-repo-sync.ts +16 -3
- package/plugins/specweave-github/lib/github-feature-sync-cli.js +20 -11
- package/plugins/specweave-github/lib/github-feature-sync-cli.ts +42 -20
- package/plugins/specweave-github/lib/github-feature-sync.js +32 -8
- package/plugins/specweave-github/lib/github-feature-sync.ts +41 -9
- package/plugins/specweave-github/lib/github-graphql-client.js +29 -20
- package/plugins/specweave-github/lib/github-graphql-client.ts +34 -22
- package/plugins/specweave-github/lib/github-hierarchical-sync.js +2 -2
- package/plugins/specweave-github/lib/github-hierarchical-sync.ts +2 -2
- package/plugins/specweave-github/lib/github-multi-project-sync.js +23 -7
- package/plugins/specweave-github/lib/github-multi-project-sync.ts +26 -8
- package/plugins/specweave-github/lib/github-spec-frontmatter-updater.js +110 -5
- package/plugins/specweave-github/lib/github-spec-frontmatter-updater.ts +135 -9
- package/plugins/specweave-github/lib/github-spec-sync.js +85 -24
- package/plugins/specweave-github/lib/github-spec-sync.ts +100 -26
- package/plugins/specweave-github/lib/github-sync-orchestrator.js +2 -1
- package/plugins/specweave-github/lib/github-sync-orchestrator.ts +3 -1
- package/plugins/specweave-github/lib/github-us-auto-closer.js +25 -0
- package/plugins/specweave-github/lib/github-us-auto-closer.ts +43 -0
- package/plugins/specweave-github/lib/per-us-sync.js +26 -11
- package/plugins/specweave-github/lib/per-us-sync.ts +29 -11
- package/plugins/specweave-jira/hooks/post-task-completion.sh +2 -1
- package/plugins/specweave-jira/lib/content-format-adapter.js +116 -0
- package/plugins/specweave-jira/lib/content-format-adapter.ts +189 -0
- package/plugins/specweave-jira/lib/enhanced-jira-sync.js +21 -5
- package/plugins/specweave-jira/lib/jira-deployment-detector.js +63 -0
- package/plugins/specweave-jira/lib/jira-deployment-detector.ts +113 -0
- package/plugins/specweave-jira/lib/jira-duplicate-detector.js +12 -29
- package/plugins/specweave-jira/lib/jira-duplicate-detector.ts +13 -27
- package/plugins/specweave-jira/lib/jira-epic-sync.js +15 -5
- package/plugins/specweave-jira/lib/jira-epic-sync.ts +22 -7
- package/plugins/specweave-jira/lib/jira-field-discovery.js +76 -0
- package/plugins/specweave-jira/lib/jira-field-discovery.ts +139 -0
- package/plugins/specweave-jira/lib/jira-hierarchical-sync.js +10 -0
- package/plugins/specweave-jira/lib/jira-hierarchical-sync.ts +11 -0
- package/plugins/specweave-jira/lib/jira-multi-project-sync.js +19 -9
- package/plugins/specweave-jira/lib/jira-multi-project-sync.ts +25 -14
- package/plugins/specweave-jira/lib/jira-paginated-search.js +55 -0
- package/plugins/specweave-jira/lib/jira-paginated-search.ts +108 -0
- package/plugins/specweave-jira/lib/jira-spec-commit-sync.js +5 -3
- package/plugins/specweave-jira/lib/jira-spec-commit-sync.ts +5 -3
- package/plugins/specweave-jira/lib/jira-spec-sync.js +102 -31
- package/plugins/specweave-jira/lib/jira-spec-sync.ts +123 -45
- package/plugins/specweave-jira/lib/jira-status-sync.js +18 -5
- package/plugins/specweave-jira/lib/jira-status-sync.ts +21 -6
- package/plugins/specweave-jira/lib/metadata-paths.js +38 -0
- package/plugins/specweave-jira/lib/metadata-paths.ts +73 -0
- package/plugins/specweave-jira/lib/reorganization-detector.js +101 -23
- package/plugins/specweave-jira/lib/reorganization-detector.ts +125 -35
- package/plugins/specweave-jira/scripts/refresh-cache.js +1 -1
- package/plugins/specweave-jira/scripts/refresh-cache.ts +2 -2
- package/plugins/specweave-jira/skills/jira-resource-validator/SKILL.md +3 -5
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content Format Adapter
|
|
3
|
+
*
|
|
4
|
+
* Converts content between formats based on JIRA API version:
|
|
5
|
+
* - API v3 (Cloud): Atlassian Document Format (ADF)
|
|
6
|
+
* - API v2 (Server/DC): Wiki markup
|
|
7
|
+
*
|
|
8
|
+
* @module content-format-adapter
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { getApiVersion } from './jira-deployment-detector.js';
|
|
12
|
+
|
|
13
|
+
/** ADF document node */
|
|
14
|
+
export interface AdfDocument {
|
|
15
|
+
type: 'doc';
|
|
16
|
+
version: 1;
|
|
17
|
+
content: AdfNode[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface AdfNode {
|
|
21
|
+
type: string;
|
|
22
|
+
content?: AdfNode[];
|
|
23
|
+
text?: string;
|
|
24
|
+
attrs?: Record<string, any>;
|
|
25
|
+
marks?: Array<{ type: string; attrs?: Record<string, any> }>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Convert text/wiki markup to ADF document format.
|
|
30
|
+
*/
|
|
31
|
+
export function toADF(text: string): AdfDocument {
|
|
32
|
+
const lines = text.split('\n');
|
|
33
|
+
const content: AdfNode[] = [];
|
|
34
|
+
let currentParagraph: AdfNode[] = [];
|
|
35
|
+
|
|
36
|
+
const flushParagraph = () => {
|
|
37
|
+
if (currentParagraph.length > 0) {
|
|
38
|
+
content.push({ type: 'paragraph', content: [...currentParagraph] });
|
|
39
|
+
currentParagraph = [];
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
// Headings: h1. h2. h3. or # ## ###
|
|
45
|
+
const wikiHeadingMatch = line.match(/^h(\d)\.\s+(.+)$/);
|
|
46
|
+
const mdHeadingMatch = line.match(/^(#{1,6})\s+(.+)$/);
|
|
47
|
+
|
|
48
|
+
if (wikiHeadingMatch) {
|
|
49
|
+
flushParagraph();
|
|
50
|
+
const level = parseInt(wikiHeadingMatch[1]);
|
|
51
|
+
content.push({
|
|
52
|
+
type: 'heading',
|
|
53
|
+
attrs: { level },
|
|
54
|
+
content: [{ type: 'text', text: wikiHeadingMatch[2] }],
|
|
55
|
+
});
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (mdHeadingMatch) {
|
|
60
|
+
flushParagraph();
|
|
61
|
+
const level = mdHeadingMatch[1].length;
|
|
62
|
+
content.push({
|
|
63
|
+
type: 'heading',
|
|
64
|
+
attrs: { level },
|
|
65
|
+
content: [{ type: 'text', text: mdHeadingMatch[2] }],
|
|
66
|
+
});
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Horizontal rule: ---- or ---
|
|
71
|
+
if (/^-{3,}$/.test(line.trim())) {
|
|
72
|
+
flushParagraph();
|
|
73
|
+
content.push({ type: 'rule' });
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Bullet list item: * item or - item
|
|
78
|
+
const bulletMatch = line.match(/^\*\s+(.+)$/) || line.match(/^-\s+(.+)$/);
|
|
79
|
+
if (bulletMatch) {
|
|
80
|
+
flushParagraph();
|
|
81
|
+
const listItem: AdfNode = {
|
|
82
|
+
type: 'listItem',
|
|
83
|
+
content: [{ type: 'paragraph', content: [{ type: 'text', text: bulletMatch[1] }] }],
|
|
84
|
+
};
|
|
85
|
+
// Append to existing bulletList if the last node is one (consecutive items)
|
|
86
|
+
const lastNode = content[content.length - 1];
|
|
87
|
+
if (lastNode && lastNode.type === 'bulletList' && lastNode.content) {
|
|
88
|
+
lastNode.content.push(listItem);
|
|
89
|
+
} else {
|
|
90
|
+
content.push({ type: 'bulletList', content: [listItem] });
|
|
91
|
+
}
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Empty line = paragraph break
|
|
96
|
+
if (line.trim() === '') {
|
|
97
|
+
flushParagraph();
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Bold: *text* → strong mark
|
|
102
|
+
let textContent = line;
|
|
103
|
+
const textNodes: AdfNode[] = [];
|
|
104
|
+
|
|
105
|
+
// Simple inline formatting: *bold* and _italic_
|
|
106
|
+
const parts = textContent.split(/(\*[^*]+\*|_[^_]+_)/);
|
|
107
|
+
for (const part of parts) {
|
|
108
|
+
if (part.startsWith('*') && part.endsWith('*') && part.length > 2) {
|
|
109
|
+
textNodes.push({
|
|
110
|
+
type: 'text',
|
|
111
|
+
text: part.slice(1, -1),
|
|
112
|
+
marks: [{ type: 'strong' }],
|
|
113
|
+
});
|
|
114
|
+
} else if (part.startsWith('_') && part.endsWith('_') && part.length > 2) {
|
|
115
|
+
textNodes.push({
|
|
116
|
+
type: 'text',
|
|
117
|
+
text: part.slice(1, -1),
|
|
118
|
+
marks: [{ type: 'em' }],
|
|
119
|
+
});
|
|
120
|
+
} else if (part) {
|
|
121
|
+
textNodes.push({ type: 'text', text: part });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
currentParagraph.push(...textNodes);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
flushParagraph();
|
|
129
|
+
|
|
130
|
+
// Ensure at least one paragraph
|
|
131
|
+
if (content.length === 0) {
|
|
132
|
+
content.push({
|
|
133
|
+
type: 'paragraph',
|
|
134
|
+
content: [{ type: 'text', text: text || '' }],
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
type: 'doc',
|
|
140
|
+
version: 1,
|
|
141
|
+
content,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Convert content to wiki markup format (for JIRA Server/DC API v2).
|
|
147
|
+
* If already in wiki markup, returns as-is.
|
|
148
|
+
*/
|
|
149
|
+
export function toWikiMarkup(text: string): string {
|
|
150
|
+
// Already wiki markup — return as-is
|
|
151
|
+
return text;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Format content based on JIRA API version.
|
|
156
|
+
*
|
|
157
|
+
* @param text - Raw text/wiki markup content
|
|
158
|
+
* @param domain - JIRA domain (used to look up cached API version)
|
|
159
|
+
* @returns ADF document for v3/Cloud, wiki markup string for v2/Server
|
|
160
|
+
*/
|
|
161
|
+
export function formatContent(text: string, domain: string): AdfDocument | string {
|
|
162
|
+
const version = getApiVersion(domain);
|
|
163
|
+
if (version === '3') {
|
|
164
|
+
return toADF(text);
|
|
165
|
+
}
|
|
166
|
+
return toWikiMarkup(text);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Format a description field for JIRA issue create/update payload.
|
|
171
|
+
*
|
|
172
|
+
* @param text - Raw description text
|
|
173
|
+
* @param domain - JIRA domain
|
|
174
|
+
* @returns Properly formatted description for the API version
|
|
175
|
+
*/
|
|
176
|
+
export function toDescription(text: string, domain: string): AdfDocument | string {
|
|
177
|
+
return formatContent(text, domain);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Format a comment body for JIRA issue comment payload.
|
|
182
|
+
*
|
|
183
|
+
* @param text - Raw comment text
|
|
184
|
+
* @param domain - JIRA domain
|
|
185
|
+
* @returns Properly formatted comment body for the API version
|
|
186
|
+
*/
|
|
187
|
+
export function toCommentBody(text: string, domain: string): AdfDocument | string {
|
|
188
|
+
return formatContent(text, domain);
|
|
189
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EnhancedContentBuilder } from "../../../dist/src/core/sync/enhanced-content-builder.js";
|
|
2
2
|
import { SpecIncrementMapper } from "../../../dist/src/core/sync/spec-increment-mapper.js";
|
|
3
3
|
import { parseSpecContent } from "../../../dist/src/core/spec-content-sync.js";
|
|
4
|
+
import { readIssueKey } from "./metadata-paths.js";
|
|
4
5
|
import * as path from "path";
|
|
5
6
|
import * as fs from "fs/promises";
|
|
6
7
|
async function syncSpecToJiraWithEnhancedContent(options) {
|
|
@@ -61,11 +62,26 @@ async function syncSpecToJiraWithEnhancedContent(options) {
|
|
|
61
62
|
tasksLinked: taskMapping?.tasks.length || 0
|
|
62
63
|
};
|
|
63
64
|
if (domain && project && !dryRun) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
// Read real JIRA key from metadata if available
|
|
66
|
+
const rootDir = await findSpecWeaveRoot(specPath);
|
|
67
|
+
const metadataPath = path.join(rootDir, '.specweave', 'increments', specId, 'metadata.json');
|
|
68
|
+
let realKey = null;
|
|
69
|
+
try {
|
|
70
|
+
const meta = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
|
|
71
|
+
realKey = readIssueKey(meta);
|
|
72
|
+
} catch { /* no metadata */ }
|
|
73
|
+
|
|
74
|
+
if (realKey) {
|
|
75
|
+
result.epicKey = realKey;
|
|
76
|
+
result.epicUrl = `https://${domain}/browse/${realKey}`;
|
|
77
|
+
} else {
|
|
78
|
+
// No real JIRA key — return null instead of placeholder
|
|
79
|
+
result.epicKey = null;
|
|
80
|
+
result.epicUrl = null;
|
|
81
|
+
if (verbose) {
|
|
82
|
+
console.log(`\u26A0\uFE0F JIRA API integration not implemented in this file`);
|
|
83
|
+
console.log(` Use jira-spec-sync.ts for actual JIRA synchronization`);
|
|
84
|
+
}
|
|
69
85
|
}
|
|
70
86
|
}
|
|
71
87
|
return result;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
const deploymentCache = /* @__PURE__ */ new Map();
|
|
3
|
+
async function detectDeploymentType(domain, auth) {
|
|
4
|
+
const cached = deploymentCache.get(domain);
|
|
5
|
+
if (cached) return cached;
|
|
6
|
+
try {
|
|
7
|
+
const response = await axios.get(
|
|
8
|
+
`https://${domain}/rest/api/2/serverInfo`,
|
|
9
|
+
{
|
|
10
|
+
auth: {
|
|
11
|
+
username: auth.email,
|
|
12
|
+
password: auth.apiToken
|
|
13
|
+
},
|
|
14
|
+
headers: {
|
|
15
|
+
Accept: "application/json"
|
|
16
|
+
},
|
|
17
|
+
timeout: 1e4
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
const data = response.data;
|
|
21
|
+
const deploymentType = (data.deploymentType || "").toLowerCase();
|
|
22
|
+
const isCloud2 = deploymentType === "cloud" || domain.endsWith(".atlassian.net");
|
|
23
|
+
const info = {
|
|
24
|
+
type: isCloud2 ? "cloud" : "server",
|
|
25
|
+
apiVersion: isCloud2 ? "3" : "2",
|
|
26
|
+
baseUrl: `https://${domain}/rest/api/${isCloud2 ? "3" : "2"}`
|
|
27
|
+
};
|
|
28
|
+
deploymentCache.set(domain, info);
|
|
29
|
+
return info;
|
|
30
|
+
} catch {
|
|
31
|
+
const isCloud2 = domain.endsWith(".atlassian.net");
|
|
32
|
+
const info = {
|
|
33
|
+
type: isCloud2 ? "cloud" : "server",
|
|
34
|
+
apiVersion: isCloud2 ? "3" : "2",
|
|
35
|
+
baseUrl: `https://${domain}/rest/api/${isCloud2 ? "3" : "2"}`
|
|
36
|
+
};
|
|
37
|
+
deploymentCache.set(domain, info);
|
|
38
|
+
return info;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function getApiBaseUrl(domain) {
|
|
42
|
+
const cached = deploymentCache.get(domain);
|
|
43
|
+
if (cached) return cached.baseUrl;
|
|
44
|
+
return `https://${domain}/rest/api/3`;
|
|
45
|
+
}
|
|
46
|
+
function getApiVersion(domain) {
|
|
47
|
+
const cached = deploymentCache.get(domain);
|
|
48
|
+
return cached?.apiVersion ?? "3";
|
|
49
|
+
}
|
|
50
|
+
function isCloud(domain) {
|
|
51
|
+
const cached = deploymentCache.get(domain);
|
|
52
|
+
return cached ? cached.type === "cloud" : domain.endsWith(".atlassian.net");
|
|
53
|
+
}
|
|
54
|
+
function clearDeploymentCache() {
|
|
55
|
+
deploymentCache.clear();
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
clearDeploymentCache,
|
|
59
|
+
detectDeploymentType,
|
|
60
|
+
getApiBaseUrl,
|
|
61
|
+
getApiVersion,
|
|
62
|
+
isCloud
|
|
63
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JIRA Deployment Type Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects whether a JIRA instance is Cloud or Server/Data Center
|
|
5
|
+
* and returns the correct API version to use.
|
|
6
|
+
*
|
|
7
|
+
* - Cloud: /rest/api/3 (ADF content format)
|
|
8
|
+
* - Server/DC: /rest/api/2 (wiki markup content format)
|
|
9
|
+
*
|
|
10
|
+
* Results are cached per domain to avoid repeated lookups.
|
|
11
|
+
*
|
|
12
|
+
* @module jira-deployment-detector
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import axios from 'axios';
|
|
16
|
+
|
|
17
|
+
export interface DeploymentInfo {
|
|
18
|
+
type: 'cloud' | 'server';
|
|
19
|
+
apiVersion: '2' | '3';
|
|
20
|
+
baseUrl: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Cache: domain → deployment info */
|
|
24
|
+
const deploymentCache = new Map<string, DeploymentInfo>();
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Detect JIRA deployment type by calling /rest/api/2/serverInfo.
|
|
28
|
+
* This endpoint exists on both Cloud and Server/DC.
|
|
29
|
+
*
|
|
30
|
+
* Cloud instances return deploymentType: "Cloud".
|
|
31
|
+
* Server/DC instances return deploymentType: "Server" or "DataCenter".
|
|
32
|
+
*/
|
|
33
|
+
export async function detectDeploymentType(
|
|
34
|
+
domain: string,
|
|
35
|
+
auth: { email: string; apiToken: string }
|
|
36
|
+
): Promise<DeploymentInfo> {
|
|
37
|
+
// Check cache first
|
|
38
|
+
const cached = deploymentCache.get(domain);
|
|
39
|
+
if (cached) return cached;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const response = await axios.get(
|
|
43
|
+
`https://${domain}/rest/api/2/serverInfo`,
|
|
44
|
+
{
|
|
45
|
+
auth: {
|
|
46
|
+
username: auth.email,
|
|
47
|
+
password: auth.apiToken,
|
|
48
|
+
},
|
|
49
|
+
headers: {
|
|
50
|
+
Accept: 'application/json',
|
|
51
|
+
},
|
|
52
|
+
timeout: 10000,
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const data = response.data;
|
|
57
|
+
const deploymentType = (data.deploymentType || '').toLowerCase();
|
|
58
|
+
|
|
59
|
+
const isCloud = deploymentType === 'cloud' || domain.endsWith('.atlassian.net');
|
|
60
|
+
|
|
61
|
+
const info: DeploymentInfo = {
|
|
62
|
+
type: isCloud ? 'cloud' : 'server',
|
|
63
|
+
apiVersion: isCloud ? '3' : '2',
|
|
64
|
+
baseUrl: `https://${domain}/rest/api/${isCloud ? '3' : '2'}`,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
deploymentCache.set(domain, info);
|
|
68
|
+
return info;
|
|
69
|
+
} catch {
|
|
70
|
+
// Fallback: if .atlassian.net assume Cloud, otherwise Server
|
|
71
|
+
const isCloud = domain.endsWith('.atlassian.net');
|
|
72
|
+
const info: DeploymentInfo = {
|
|
73
|
+
type: isCloud ? 'cloud' : 'server',
|
|
74
|
+
apiVersion: isCloud ? '3' : '2',
|
|
75
|
+
baseUrl: `https://${domain}/rest/api/${isCloud ? '3' : '2'}`,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
deploymentCache.set(domain, info);
|
|
79
|
+
return info;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Build the full API base URL for a given JIRA domain.
|
|
85
|
+
* Uses cached deployment info if available.
|
|
86
|
+
*/
|
|
87
|
+
export function getApiBaseUrl(domain: string): string {
|
|
88
|
+
const cached = deploymentCache.get(domain);
|
|
89
|
+
if (cached) return cached.baseUrl;
|
|
90
|
+
// Default to v3 (Cloud) if not yet detected
|
|
91
|
+
return `https://${domain}/rest/api/3`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get the API version string for a domain.
|
|
96
|
+
*/
|
|
97
|
+
export function getApiVersion(domain: string): '2' | '3' {
|
|
98
|
+
const cached = deploymentCache.get(domain);
|
|
99
|
+
return cached?.apiVersion ?? '3';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check if a domain is Cloud deployment.
|
|
104
|
+
*/
|
|
105
|
+
export function isCloud(domain: string): boolean {
|
|
106
|
+
const cached = deploymentCache.get(domain);
|
|
107
|
+
return cached ? cached.type === 'cloud' : domain.endsWith('.atlassian.net');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Clear cache (for testing) */
|
|
111
|
+
export function clearDeploymentCache(): void {
|
|
112
|
+
deploymentCache.clear();
|
|
113
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { consoleLogger } from "../../../src/utils/logger.js";
|
|
2
|
+
import { getApiBaseUrl } from "./jira-deployment-detector.js";
|
|
3
|
+
import { toCommentBody } from "./content-format-adapter.js";
|
|
2
4
|
class JiraDuplicateDetector {
|
|
3
5
|
constructor(options = {}) {
|
|
4
6
|
this.domain = options.domain || process.env.JIRA_DOMAIN || "";
|
|
@@ -53,10 +55,9 @@ class JiraDuplicateDetector {
|
|
|
53
55
|
} catch (error) {
|
|
54
56
|
this.logger.log(`\u26A0\uFE0F Verification check failed: ${error.message}`);
|
|
55
57
|
return {
|
|
56
|
-
success:
|
|
57
|
-
// Assume success on error
|
|
58
|
+
success: false,
|
|
58
59
|
expectedCount,
|
|
59
|
-
actualCount:
|
|
60
|
+
actualCount: -1,
|
|
60
61
|
duplicates: []
|
|
61
62
|
};
|
|
62
63
|
}
|
|
@@ -178,7 +179,7 @@ class JiraDuplicateDetector {
|
|
|
178
179
|
throw new Error("JIRA credentials not configured");
|
|
179
180
|
}
|
|
180
181
|
const jql = encodeURIComponent(`summary ~ "${summaryPattern}" ORDER BY created ASC`);
|
|
181
|
-
const url =
|
|
182
|
+
const url = `${getApiBaseUrl(this.domain)}/search?jql=${jql}&fields=summary,status,created`;
|
|
182
183
|
const response = await fetch(url, {
|
|
183
184
|
headers: {
|
|
184
185
|
Authorization: `Basic ${this.auth}`,
|
|
@@ -209,7 +210,7 @@ class JiraDuplicateDetector {
|
|
|
209
210
|
if (!closeTransition) {
|
|
210
211
|
throw new Error(`No close transition found. Available: ${transitions.map((t) => t.name).join(", ")}`);
|
|
211
212
|
}
|
|
212
|
-
const url =
|
|
213
|
+
const url = `${getApiBaseUrl(this.domain)}/issue/${issueKey}/transitions`;
|
|
213
214
|
const response = await fetch(url, {
|
|
214
215
|
method: "POST",
|
|
215
216
|
headers: {
|
|
@@ -229,7 +230,7 @@ class JiraDuplicateDetector {
|
|
|
229
230
|
* Get available transitions for an issue
|
|
230
231
|
*/
|
|
231
232
|
async getTransitions(issueKey) {
|
|
232
|
-
const url =
|
|
233
|
+
const url = `${getApiBaseUrl(this.domain)}/issue/${issueKey}/transitions`;
|
|
233
234
|
const response = await fetch(url, {
|
|
234
235
|
headers: {
|
|
235
236
|
Authorization: `Basic ${this.auth}`,
|
|
@@ -246,38 +247,20 @@ class JiraDuplicateDetector {
|
|
|
246
247
|
* Add duplicate comment to issue
|
|
247
248
|
*/
|
|
248
249
|
async addComment(issueKey, originalKey) {
|
|
249
|
-
const url =
|
|
250
|
-
const
|
|
250
|
+
const url = `${getApiBaseUrl(this.domain)}/issue/${issueKey}/comment`;
|
|
251
|
+
const commentText = `h2. Duplicate of ${originalKey}
|
|
251
252
|
|
|
252
253
|
This issue was automatically closed by SpecWeave cleanup because it is a duplicate.
|
|
253
254
|
|
|
254
|
-
The original issue (${originalKey}) contains the same content and should be used for tracking instead
|
|
255
|
-
|
|
256
|
-
----
|
|
257
|
-
\u{1F916} Auto-closed by SpecWeave Duplicate Cleanup`;
|
|
255
|
+
The original issue (${originalKey}) contains the same content and should be used for tracking instead.`;
|
|
256
|
+
const body = toCommentBody(commentText, this.domain);
|
|
258
257
|
const response = await fetch(url, {
|
|
259
258
|
method: "POST",
|
|
260
259
|
headers: {
|
|
261
260
|
Authorization: `Basic ${this.auth}`,
|
|
262
261
|
"Content-Type": "application/json"
|
|
263
262
|
},
|
|
264
|
-
body: JSON.stringify({
|
|
265
|
-
body: {
|
|
266
|
-
type: "doc",
|
|
267
|
-
version: 1,
|
|
268
|
-
content: [
|
|
269
|
-
{
|
|
270
|
-
type: "paragraph",
|
|
271
|
-
content: [
|
|
272
|
-
{
|
|
273
|
-
type: "text",
|
|
274
|
-
text: comment
|
|
275
|
-
}
|
|
276
|
-
]
|
|
277
|
-
}
|
|
278
|
-
]
|
|
279
|
-
}
|
|
280
|
-
})
|
|
263
|
+
body: JSON.stringify({ body })
|
|
281
264
|
});
|
|
282
265
|
if (!response.ok) {
|
|
283
266
|
this.logger.log(` \u26A0\uFE0F Failed to add comment to ${issueKey}`);
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { Logger, consoleLogger } from '../../../src/utils/logger.js';
|
|
13
|
+
import { getApiBaseUrl } from './jira-deployment-detector.js';
|
|
14
|
+
import { toCommentBody } from './content-format-adapter.js';
|
|
13
15
|
|
|
14
16
|
export interface JiraIssue {
|
|
15
17
|
key: string;
|
|
@@ -122,9 +124,9 @@ export class JiraDuplicateDetector {
|
|
|
122
124
|
} catch (error: any) {
|
|
123
125
|
this.logger.log(`⚠️ Verification check failed: ${error.message}`);
|
|
124
126
|
return {
|
|
125
|
-
success:
|
|
127
|
+
success: false,
|
|
126
128
|
expectedCount,
|
|
127
|
-
actualCount:
|
|
129
|
+
actualCount: -1,
|
|
128
130
|
duplicates: [],
|
|
129
131
|
};
|
|
130
132
|
}
|
|
@@ -281,7 +283,7 @@ export class JiraDuplicateDetector {
|
|
|
281
283
|
}
|
|
282
284
|
|
|
283
285
|
const jql = encodeURIComponent(`summary ~ "${summaryPattern}" ORDER BY created ASC`);
|
|
284
|
-
const url =
|
|
286
|
+
const url = `${getApiBaseUrl(this.domain)}/search?jql=${jql}&fields=summary,status,created`;
|
|
285
287
|
|
|
286
288
|
const response = await fetch(url, {
|
|
287
289
|
headers: {
|
|
@@ -328,7 +330,7 @@ export class JiraDuplicateDetector {
|
|
|
328
330
|
throw new Error(`No close transition found. Available: ${transitions.map((t: any) => t.name).join(', ')}`);
|
|
329
331
|
}
|
|
330
332
|
|
|
331
|
-
const url =
|
|
333
|
+
const url = `${getApiBaseUrl(this.domain)}/issue/${issueKey}/transitions`;
|
|
332
334
|
|
|
333
335
|
const response = await fetch(url, {
|
|
334
336
|
method: 'POST',
|
|
@@ -351,7 +353,7 @@ export class JiraDuplicateDetector {
|
|
|
351
353
|
* Get available transitions for an issue
|
|
352
354
|
*/
|
|
353
355
|
private async getTransitions(issueKey: string): Promise<any[]> {
|
|
354
|
-
const url =
|
|
356
|
+
const url = `${getApiBaseUrl(this.domain)}/issue/${issueKey}/transitions`;
|
|
355
357
|
|
|
356
358
|
const response = await fetch(url, {
|
|
357
359
|
headers: {
|
|
@@ -372,16 +374,16 @@ export class JiraDuplicateDetector {
|
|
|
372
374
|
* Add duplicate comment to issue
|
|
373
375
|
*/
|
|
374
376
|
private async addComment(issueKey: string, originalKey: string): Promise<void> {
|
|
375
|
-
const url =
|
|
377
|
+
const url = `${getApiBaseUrl(this.domain)}/issue/${issueKey}/comment`;
|
|
376
378
|
|
|
377
|
-
const
|
|
379
|
+
const commentText = `h2. Duplicate of ${originalKey}
|
|
378
380
|
|
|
379
381
|
This issue was automatically closed by SpecWeave cleanup because it is a duplicate.
|
|
380
382
|
|
|
381
|
-
The original issue (${originalKey}) contains the same content and should be used for tracking instead
|
|
383
|
+
The original issue (${originalKey}) contains the same content and should be used for tracking instead.`;
|
|
382
384
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
+
// Use format adapter for correct format (ADF for Cloud, wiki for Server)
|
|
386
|
+
const body = toCommentBody(commentText, this.domain);
|
|
385
387
|
|
|
386
388
|
const response = await fetch(url, {
|
|
387
389
|
method: 'POST',
|
|
@@ -389,23 +391,7 @@ The original issue (${originalKey}) contains the same content and should be used
|
|
|
389
391
|
Authorization: `Basic ${this.auth}`,
|
|
390
392
|
'Content-Type': 'application/json',
|
|
391
393
|
},
|
|
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
|
-
}),
|
|
394
|
+
body: JSON.stringify({ body }),
|
|
409
395
|
});
|
|
410
396
|
|
|
411
397
|
if (!response.ok) {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import * as fs from "../../../src/utils/fs-native.js";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import * as yaml from "yaml";
|
|
4
|
+
import { toDescription } from "./content-format-adapter.js";
|
|
4
5
|
class JiraEpicSync {
|
|
5
|
-
constructor(client, specsDir, projectKey) {
|
|
6
|
+
constructor(client, specsDir, projectKey, domain) {
|
|
6
7
|
this.client = client;
|
|
7
8
|
this.specsDir = specsDir;
|
|
8
9
|
this.projectKey = projectKey;
|
|
10
|
+
this.domain = domain || client["credentials"]?.domain || "";
|
|
9
11
|
}
|
|
10
12
|
/**
|
|
11
13
|
* Sync Epic folder to JIRA (Epic + Stories)
|
|
@@ -92,8 +94,14 @@ class JiraEpicSync {
|
|
|
92
94
|
* Find Epic folder by ID (FS-001 or just 001)
|
|
93
95
|
*/
|
|
94
96
|
async findEpicFolder(epicId) {
|
|
95
|
-
const normalizedId = epicId.startsWith("FS-") ? epicId : `FS-${epicId.padStart(3, "0")}`;
|
|
96
97
|
const folders = await fs.readdir(this.specsDir);
|
|
98
|
+
for (const folder of folders) {
|
|
99
|
+
if (folder.startsWith(epicId)) {
|
|
100
|
+
return path.join(this.specsDir, folder);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const prefix = `${this.projectKey}-`;
|
|
104
|
+
const normalizedId = epicId.startsWith(prefix) ? epicId : `${prefix}${epicId.padStart(3, "0")}`;
|
|
97
105
|
for (const folder of folders) {
|
|
98
106
|
if (folder.startsWith(normalizedId)) {
|
|
99
107
|
return path.join(this.specsDir, folder);
|
|
@@ -142,12 +150,13 @@ class JiraEpicSync {
|
|
|
142
150
|
*/
|
|
143
151
|
async createEpic(epic) {
|
|
144
152
|
const summary = `[${epic.id}] ${epic.title}`;
|
|
145
|
-
const
|
|
153
|
+
const rawDescription = `Epic: ${epic.title}
|
|
146
154
|
|
|
147
155
|
Progress: ${epic.completed_increments}/${epic.total_increments} increments (${epic.progress})
|
|
148
156
|
|
|
149
157
|
Priority: ${epic.priority}
|
|
150
158
|
Status: ${epic.status}`;
|
|
159
|
+
const description = toDescription(rawDescription, this.domain);
|
|
151
160
|
const issueData = {
|
|
152
161
|
issueType: "Epic",
|
|
153
162
|
summary,
|
|
@@ -158,7 +167,7 @@ Status: ${epic.status}`;
|
|
|
158
167
|
const issue = await this.client.createIssue(issueData, this.projectKey);
|
|
159
168
|
return {
|
|
160
169
|
key: issue.key,
|
|
161
|
-
url: issue.self.replace(
|
|
170
|
+
url: issue.self.replace(/\/rest\/api\/\d+\/issue\//, "/browse/")
|
|
162
171
|
};
|
|
163
172
|
}
|
|
164
173
|
/**
|
|
@@ -166,12 +175,13 @@ Status: ${epic.status}`;
|
|
|
166
175
|
*/
|
|
167
176
|
async updateEpic(epicKey, epic) {
|
|
168
177
|
const summary = `[${epic.id}] ${epic.title}`;
|
|
169
|
-
const
|
|
178
|
+
const rawDescription = `Epic: ${epic.title}
|
|
170
179
|
|
|
171
180
|
Progress: ${epic.completed_increments}/${epic.total_increments} increments (${epic.progress})
|
|
172
181
|
|
|
173
182
|
Priority: ${epic.priority}
|
|
174
183
|
Status: ${epic.status}`;
|
|
184
|
+
const description = toDescription(rawDescription, this.domain);
|
|
175
185
|
await this.client.updateIssue({
|
|
176
186
|
key: epicKey,
|
|
177
187
|
summary,
|