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,57 @@
|
|
|
1
|
+
You are the TESTING agent for increment [INCREMENT_ID].
|
|
2
|
+
|
|
3
|
+
MASTER SPEC (SOURCE OF TRUTH):
|
|
4
|
+
The feature is fully specified in [MASTER_INCREMENT_PATH]/spec.md.
|
|
5
|
+
This spec defines scope, user stories, and acceptance criteria.
|
|
6
|
+
Your tests MUST cover ALL ACs from the master spec.
|
|
7
|
+
Read the master spec BEFORE planning any work.
|
|
8
|
+
|
|
9
|
+
SKILLS TO INVOKE:
|
|
10
|
+
Skill({ skill: "testing:qa" })
|
|
11
|
+
Skill({ skill: "testing:e2e" }) // for E2E test suites
|
|
12
|
+
Skill({ skill: "testing:unit" }) // for unit test coverage
|
|
13
|
+
|
|
14
|
+
FILE OWNERSHIP (WRITE access):
|
|
15
|
+
tests/**
|
|
16
|
+
__tests__/**
|
|
17
|
+
src/**/*.test.ts
|
|
18
|
+
src/**/*.test.tsx
|
|
19
|
+
src/**/*.spec.ts
|
|
20
|
+
e2e/**
|
|
21
|
+
playwright.config.ts // if Playwright
|
|
22
|
+
cypress.config.ts // if Cypress
|
|
23
|
+
test-utils/**
|
|
24
|
+
fixtures/**
|
|
25
|
+
|
|
26
|
+
READ ACCESS: Any file in the repository
|
|
27
|
+
|
|
28
|
+
WORKFLOW:
|
|
29
|
+
1. Set working directory to your assigned repo: cd repositories/{ORG}/{repo-name}
|
|
30
|
+
2. If .specweave/ doesn't exist in your repo, run: specweave init
|
|
31
|
+
3. Create YOUR increment in YOUR repo: .specweave/increments/[ID]/
|
|
32
|
+
4. Read the MASTER SPEC at [MASTER_INCREMENT_PATH]/spec.md for scope and ACs
|
|
33
|
+
5. Wait for ALL other agents to produce initial code
|
|
34
|
+
6. Create plan files (plan.md, tasks.md) for your increment
|
|
35
|
+
7. Send plan to team-lead and WAIT for approval:
|
|
36
|
+
SendMessage({ type: "message", recipient: "team-lead",
|
|
37
|
+
content: "PLAN_READY: [increment path]. [summary of test strategy, coverage plan].",
|
|
38
|
+
summary: "Testing plan ready for review" })
|
|
39
|
+
8. WAIT for "PLAN_APPROVED" message. If "PLAN_REJECTED", revise and re-submit.
|
|
40
|
+
9. Write unit tests for new services/components
|
|
41
|
+
10. Write integration tests for API endpoints
|
|
42
|
+
11. Write E2E tests for user journeys
|
|
43
|
+
12. Execute tasks autonomously: prefer /sw:auto for autonomous execution
|
|
44
|
+
13. Run all tests (unit + integration + E2E): npm test && npx playwright test
|
|
45
|
+
14. Do NOT signal completion until all tests pass -- if tests fail, fix and repeat
|
|
46
|
+
15. Run quality gate: /sw:grill
|
|
47
|
+
16. After auto completes, attempt closure via /sw:done
|
|
48
|
+
17. Signal completion via SendMessage to team-lead
|
|
49
|
+
|
|
50
|
+
RULES:
|
|
51
|
+
- WRITE only to test files (listed above)
|
|
52
|
+
- READ any file for context
|
|
53
|
+
- Tests must cover all acceptance criteria from spec.md
|
|
54
|
+
- Follow existing test patterns and utilities
|
|
55
|
+
- E2E tests must include accessibility checks when applicable
|
|
56
|
+
- ALL repository operations MUST use `repositories/{ORG}/` directory structure
|
|
57
|
+
- Create .specweave/increments/ in YOUR assigned repo, NOT in the umbrella project root
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Generate tasks.md with embedded test plans in BDD format, one user story at a time to prevent crashes. Use for test-first task planning where each task includes Given/When/Then scenarios. Produces implementation tasks with inline test specifications.
|
|
3
|
+
context: fork
|
|
4
|
+
model: opus
|
|
3
5
|
---
|
|
4
6
|
|
|
5
7
|
# Test-Aware Planner Skill
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
# Dependencies:
|
|
11
11
|
# - Node.js for running sync scripts
|
|
12
12
|
# - jq for JSON parsing
|
|
13
|
-
# - metadata.json must have .ado.
|
|
13
|
+
# - metadata.json must have .external_sync.ado.workItemId field (fallback: .ado.item)
|
|
14
14
|
# - Azure DevOps PAT in .env
|
|
15
15
|
|
|
16
16
|
set +e # EMERGENCY FIX: Prevents Claude Code crashes
|
|
@@ -96,7 +96,7 @@ EOF
|
|
|
96
96
|
exit 0
|
|
97
97
|
fi
|
|
98
98
|
|
|
99
|
-
ADO_ITEM=$(jq -r '.ado.item // empty' "$METADATA_FILE" 2>/dev/null)
|
|
99
|
+
ADO_ITEM=$(jq -r '.external_sync.ado.workItemId // .ado.item // empty' "$METADATA_FILE" 2>/dev/null)
|
|
100
100
|
|
|
101
101
|
if [ -z "$ADO_ITEM" ]; then
|
|
102
102
|
echo "[$(date)] [ADO] ℹ️ No Azure DevOps work item linked to $CURRENT_INCREMENT, skipping sync" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
@@ -41,6 +41,18 @@ class AdoClientV2 {
|
|
|
41
41
|
// ==========================================================================
|
|
42
42
|
// Authentication & Setup
|
|
43
43
|
// ==========================================================================
|
|
44
|
+
/**
|
|
45
|
+
* Resolve PAT for an organization.
|
|
46
|
+
* Priority: AZURE_DEVOPS_PAT_{ORG_UPPER} > AZURE_DEVOPS_PAT > fallback
|
|
47
|
+
*/
|
|
48
|
+
static resolvePatForOrg(organization, fallbackPat) {
|
|
49
|
+
const orgKey = `AZURE_DEVOPS_PAT_${organization.toUpperCase().replace(/-/g, "_")}`;
|
|
50
|
+
const orgPat = process.env[orgKey];
|
|
51
|
+
if (orgPat) return orgPat;
|
|
52
|
+
const genericPat = process.env.AZURE_DEVOPS_PAT;
|
|
53
|
+
if (genericPat) return genericPat;
|
|
54
|
+
return fallbackPat || "";
|
|
55
|
+
}
|
|
44
56
|
/**
|
|
45
57
|
* Test connection and authentication
|
|
46
58
|
*/
|
|
@@ -49,11 +61,16 @@ class AdoClientV2 {
|
|
|
49
61
|
if (this.isMultiProject) {
|
|
50
62
|
await this.request("GET", `https://dev.azure.com/${this.organization}/_apis/projects?api-version=7.1`);
|
|
51
63
|
} else {
|
|
52
|
-
await this.request("GET",
|
|
64
|
+
await this.request("GET", `https://dev.azure.com/${this.organization}/_apis/projects/${this.project}?api-version=7.1`);
|
|
53
65
|
}
|
|
54
66
|
return { success: true };
|
|
55
67
|
} catch (error) {
|
|
56
|
-
|
|
68
|
+
const statusMatch = error.message?.match(/HTTP (\d+)/);
|
|
69
|
+
const status = statusMatch ? parseInt(statusMatch[1]) : 0;
|
|
70
|
+
let hint = "";
|
|
71
|
+
if (status === 401) hint = " (check your Personal Access Token)";
|
|
72
|
+
else if (status === 404) hint = " (check organization/project name)";
|
|
73
|
+
return { success: false, error: error.message + hint };
|
|
57
74
|
}
|
|
58
75
|
}
|
|
59
76
|
// ==========================================================================
|
|
@@ -207,25 +224,32 @@ class AdoClientV2 {
|
|
|
207
224
|
if (queryResult.workItems.length === 0) {
|
|
208
225
|
return [];
|
|
209
226
|
}
|
|
210
|
-
const
|
|
227
|
+
const allIds = queryResult.workItems.map((wi) => wi.id);
|
|
211
228
|
const batchUrl = this.isMultiProject ? `https://dev.azure.com/${this.organization}/_apis/wit/workitemsbatch?api-version=7.1` : `/_apis/wit/workitemsbatch?api-version=7.1`;
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
+
const batchFields = [
|
|
230
|
+
"System.Id",
|
|
231
|
+
"System.Title",
|
|
232
|
+
"System.Description",
|
|
233
|
+
"System.State",
|
|
234
|
+
"System.CreatedDate",
|
|
235
|
+
"System.ChangedDate",
|
|
236
|
+
"System.WorkItemType",
|
|
237
|
+
"System.Tags",
|
|
238
|
+
"System.AreaPath",
|
|
239
|
+
"System.IterationPath",
|
|
240
|
+
"System.TeamProject"
|
|
241
|
+
];
|
|
242
|
+
const PAGE_SIZE = 200;
|
|
243
|
+
const allWorkItems = [];
|
|
244
|
+
for (let i = 0; i < allIds.length; i += PAGE_SIZE) {
|
|
245
|
+
const pageIds = allIds.slice(i, i + PAGE_SIZE);
|
|
246
|
+
const workItems = await this.request("POST", batchUrl, {
|
|
247
|
+
ids: pageIds,
|
|
248
|
+
fields: batchFields
|
|
249
|
+
});
|
|
250
|
+
allWorkItems.push(...workItems.value || []);
|
|
251
|
+
}
|
|
252
|
+
return allWorkItems;
|
|
229
253
|
}
|
|
230
254
|
/**
|
|
231
255
|
* List work items within time range
|
|
@@ -280,7 +304,13 @@ class AdoClientV2 {
|
|
|
280
304
|
conditions.push(`[System.CreatedDate] >= '${since}'`);
|
|
281
305
|
conditions.push(`[System.CreatedDate] <= '${until}'`);
|
|
282
306
|
if (this.areaPaths && this.areaPaths.length > 0) {
|
|
283
|
-
const areaPathConditions = this.areaPaths.map((ap) =>
|
|
307
|
+
const areaPathConditions = this.areaPaths.map((ap) => {
|
|
308
|
+
const normalizedAp = ap.replace(/\//g, "\\");
|
|
309
|
+
if (normalizedAp === projectName || normalizedAp.startsWith(`${projectName}\\`)) {
|
|
310
|
+
return `[System.AreaPath] UNDER '${normalizedAp}'`;
|
|
311
|
+
}
|
|
312
|
+
return `[System.AreaPath] UNDER '${projectName}\\${normalizedAp}'`;
|
|
313
|
+
}).join(" OR ");
|
|
284
314
|
conditions.push(`(${areaPathConditions})`);
|
|
285
315
|
}
|
|
286
316
|
if (this.workItemTypes) {
|
|
@@ -137,6 +137,24 @@ export class AdoClientV2 {
|
|
|
137
137
|
// Authentication & Setup
|
|
138
138
|
// ==========================================================================
|
|
139
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Resolve PAT for an organization.
|
|
142
|
+
* Priority: AZURE_DEVOPS_PAT_{ORG_UPPER} > AZURE_DEVOPS_PAT > fallback
|
|
143
|
+
*/
|
|
144
|
+
static resolvePatForOrg(organization: string, fallbackPat?: string): string {
|
|
145
|
+
// Org-specific: AZURE_DEVOPS_PAT_CONTOSO (uppercased, hyphens → underscores)
|
|
146
|
+
const orgKey = `AZURE_DEVOPS_PAT_${organization.toUpperCase().replace(/-/g, '_')}`;
|
|
147
|
+
const orgPat = process.env[orgKey];
|
|
148
|
+
if (orgPat) return orgPat;
|
|
149
|
+
|
|
150
|
+
// Generic PAT
|
|
151
|
+
const genericPat = process.env.AZURE_DEVOPS_PAT;
|
|
152
|
+
if (genericPat) return genericPat;
|
|
153
|
+
|
|
154
|
+
// Fallback (e.g., profile-stored PAT)
|
|
155
|
+
return fallbackPat || '';
|
|
156
|
+
}
|
|
157
|
+
|
|
140
158
|
/**
|
|
141
159
|
* Test connection and authentication
|
|
142
160
|
*/
|
|
@@ -146,12 +164,17 @@ export class AdoClientV2 {
|
|
|
146
164
|
// Test org-level access
|
|
147
165
|
await this.request('GET', `https://dev.azure.com/${this.organization}/_apis/projects?api-version=7.1`);
|
|
148
166
|
} else {
|
|
149
|
-
// Test project-level access
|
|
150
|
-
await this.request('GET',
|
|
167
|
+
// Test project-level access using absolute URL to avoid double project path
|
|
168
|
+
await this.request('GET', `https://dev.azure.com/${this.organization}/_apis/projects/${this.project}?api-version=7.1`);
|
|
151
169
|
}
|
|
152
170
|
return { success: true };
|
|
153
171
|
} catch (error: any) {
|
|
154
|
-
|
|
172
|
+
const statusMatch = error.message?.match(/HTTP (\d+)/);
|
|
173
|
+
const status = statusMatch ? parseInt(statusMatch[1]) : 0;
|
|
174
|
+
let hint = '';
|
|
175
|
+
if (status === 401) hint = ' (check your Personal Access Token)';
|
|
176
|
+
else if (status === 404) hint = ' (check organization/project name)';
|
|
177
|
+
return { success: false, error: error.message + hint };
|
|
155
178
|
}
|
|
156
179
|
}
|
|
157
180
|
|
|
@@ -344,32 +367,41 @@ export class AdoClientV2 {
|
|
|
344
367
|
return [];
|
|
345
368
|
}
|
|
346
369
|
|
|
347
|
-
// Get full work item details (batch request)
|
|
348
|
-
const
|
|
370
|
+
// Get full work item details (batch request, paginated in chunks of 200)
|
|
371
|
+
const allIds = queryResult.workItems.map((wi) => wi.id);
|
|
349
372
|
|
|
350
373
|
// For multi-project, use org-level batch API
|
|
351
374
|
const batchUrl = this.isMultiProject
|
|
352
375
|
? `https://dev.azure.com/${this.organization}/_apis/wit/workitemsbatch?api-version=7.1`
|
|
353
376
|
: `/_apis/wit/workitemsbatch?api-version=7.1`;
|
|
354
377
|
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
'System.TeamProject',
|
|
369
|
-
],
|
|
370
|
-
});
|
|
378
|
+
const batchFields = [
|
|
379
|
+
'System.Id',
|
|
380
|
+
'System.Title',
|
|
381
|
+
'System.Description',
|
|
382
|
+
'System.State',
|
|
383
|
+
'System.CreatedDate',
|
|
384
|
+
'System.ChangedDate',
|
|
385
|
+
'System.WorkItemType',
|
|
386
|
+
'System.Tags',
|
|
387
|
+
'System.AreaPath',
|
|
388
|
+
'System.IterationPath',
|
|
389
|
+
'System.TeamProject',
|
|
390
|
+
];
|
|
371
391
|
|
|
372
|
-
|
|
392
|
+
const PAGE_SIZE = 200;
|
|
393
|
+
const allWorkItems: WorkItem[] = [];
|
|
394
|
+
|
|
395
|
+
for (let i = 0; i < allIds.length; i += PAGE_SIZE) {
|
|
396
|
+
const pageIds = allIds.slice(i, i + PAGE_SIZE);
|
|
397
|
+
const workItems = await this.request('POST', batchUrl, {
|
|
398
|
+
ids: pageIds,
|
|
399
|
+
fields: batchFields,
|
|
400
|
+
});
|
|
401
|
+
allWorkItems.push(...(workItems.value || []));
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return allWorkItems;
|
|
373
405
|
}
|
|
374
406
|
|
|
375
407
|
/**
|
|
@@ -457,7 +489,14 @@ export class AdoClientV2 {
|
|
|
457
489
|
// Area paths filter (if configured)
|
|
458
490
|
if (this.areaPaths && this.areaPaths.length > 0) {
|
|
459
491
|
const areaPathConditions = this.areaPaths
|
|
460
|
-
.map((ap: string) =>
|
|
492
|
+
.map((ap: string) => {
|
|
493
|
+
// Avoid double-prepending project name if path already starts with it
|
|
494
|
+
const normalizedAp = ap.replace(/\//g, '\\');
|
|
495
|
+
if (normalizedAp === projectName || normalizedAp.startsWith(`${projectName}\\`)) {
|
|
496
|
+
return `[System.AreaPath] UNDER '${normalizedAp}'`;
|
|
497
|
+
}
|
|
498
|
+
return `[System.AreaPath] UNDER '${projectName}\\${normalizedAp}'`;
|
|
499
|
+
})
|
|
461
500
|
.join(' OR ');
|
|
462
501
|
conditions.push(`(${areaPathConditions})`);
|
|
463
502
|
}
|
|
@@ -53,11 +53,11 @@ class AdoDuplicateDetector {
|
|
|
53
53
|
} catch (error) {
|
|
54
54
|
this.logger.log(`\u26A0\uFE0F Verification check failed: ${error.message}`);
|
|
55
55
|
return {
|
|
56
|
-
success:
|
|
57
|
-
// Assume success on error
|
|
56
|
+
success: false,
|
|
58
57
|
expectedCount,
|
|
59
|
-
actualCount:
|
|
60
|
-
duplicates: []
|
|
58
|
+
actualCount: -1,
|
|
59
|
+
duplicates: [],
|
|
60
|
+
error: `Verification failed: ${error.message}`
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -122,11 +122,12 @@ export class AdoDuplicateDetector {
|
|
|
122
122
|
} catch (error: any) {
|
|
123
123
|
this.logger.log(`⚠️ Verification check failed: ${error.message}`);
|
|
124
124
|
return {
|
|
125
|
-
success:
|
|
125
|
+
success: false,
|
|
126
126
|
expectedCount,
|
|
127
|
-
actualCount:
|
|
127
|
+
actualCount: -1,
|
|
128
128
|
duplicates: [],
|
|
129
|
-
|
|
129
|
+
error: `Verification failed: ${error.message}`,
|
|
130
|
+
} as VerificationResult & { error: string };
|
|
130
131
|
}
|
|
131
132
|
}
|
|
132
133
|
|
|
@@ -33,16 +33,15 @@ async function buildHierarchicalWIQL(organization, pat, containers) {
|
|
|
33
33
|
);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
+
if (container.filters) {
|
|
37
|
+
const containerFilterClauses = buildFilterClauses(container.filters);
|
|
38
|
+
if (containerFilterClauses.length > 0) {
|
|
39
|
+
parts.push(...containerFilterClauses);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
36
42
|
projectClauses.push(`(${parts.join(" AND ")})`);
|
|
37
43
|
}
|
|
38
44
|
let whereClause = projectClauses.join(" OR ");
|
|
39
|
-
const filters = containers[0]?.filters;
|
|
40
|
-
if (filters) {
|
|
41
|
-
const filterClauses = buildFilterClauses(filters);
|
|
42
|
-
if (filterClauses.length > 0) {
|
|
43
|
-
whereClause = `(${whereClause}) AND ${filterClauses.join(" AND ")}`;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
45
|
return `
|
|
47
46
|
SELECT [System.Id], [System.Title], [System.Description], [System.State],
|
|
48
47
|
[System.CreatedDate], [System.ChangedDate], [System.WorkItemType],
|
|
@@ -91,11 +90,18 @@ function addTimeRangeFilter(wiql, timeRange) {
|
|
|
91
90
|
}
|
|
92
91
|
const { since, until } = calculateTimeRange(timeRange);
|
|
93
92
|
const timeFilter = `[System.CreatedDate] >= '${since}' AND [System.CreatedDate] <= '${until}'`;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
const orderByRegex = /\bORDER\s+BY\b/i;
|
|
94
|
+
const match = wiql.match(orderByRegex);
|
|
95
|
+
if (match && match.index !== void 0) {
|
|
96
|
+
const before = wiql.substring(0, match.index).trimEnd();
|
|
97
|
+
const after = wiql.substring(match.index);
|
|
98
|
+
return `${before} AND ${timeFilter} ${after}`;
|
|
99
|
+
}
|
|
100
|
+
const whereRegex = /\bWHERE\b/i;
|
|
101
|
+
if (whereRegex.test(wiql)) {
|
|
97
102
|
return `${wiql} AND ${timeFilter}`;
|
|
98
103
|
}
|
|
104
|
+
return `${wiql} WHERE ${timeFilter}`;
|
|
99
105
|
}
|
|
100
106
|
function calculateTimeRange(timeRange) {
|
|
101
107
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -179,6 +185,10 @@ async function fetchWorkItemsFiltered(config, pat, timeRange) {
|
|
|
179
185
|
const baseWiql = await buildHierarchicalWIQL(organization, pat, containers);
|
|
180
186
|
const wiql = addTimeRangeFilter(baseWiql, timeRange);
|
|
181
187
|
console.log("\u{1F50D} Fetching work items (FILTERED strategy):", wiql);
|
|
188
|
+
const uniqueProjects = new Set(containers.map((c) => c.id));
|
|
189
|
+
if (uniqueProjects.size > 1) {
|
|
190
|
+
return executeQueryOrgLevel(organization, pat, wiql);
|
|
191
|
+
}
|
|
182
192
|
const project = containers[0].id;
|
|
183
193
|
return executeQuery(organization, project, pat, wiql);
|
|
184
194
|
}
|
|
@@ -208,7 +218,34 @@ async function executeQuery(organization, project, pat, wiql) {
|
|
|
208
218
|
});
|
|
209
219
|
return response.value || [];
|
|
210
220
|
}
|
|
211
|
-
function
|
|
221
|
+
async function executeQueryOrgLevel(organization, pat, wiql) {
|
|
222
|
+
const baseUrl = `https://dev.azure.com/${organization}`;
|
|
223
|
+
const queryUrl = `${baseUrl}/_apis/wit/wiql?api-version=7.1`;
|
|
224
|
+
const queryResult = await makeRequest(queryUrl, pat, "POST", { query: wiql });
|
|
225
|
+
if (!queryResult.workItems || queryResult.workItems.length === 0) {
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
const ids = queryResult.workItems.map((wi) => wi.id);
|
|
229
|
+
const batchUrl = `${baseUrl}/_apis/wit/workitemsbatch?api-version=7.1`;
|
|
230
|
+
const response = await makeRequest(batchUrl, pat, "POST", {
|
|
231
|
+
ids,
|
|
232
|
+
fields: [
|
|
233
|
+
"System.Id",
|
|
234
|
+
"System.Title",
|
|
235
|
+
"System.Description",
|
|
236
|
+
"System.State",
|
|
237
|
+
"System.CreatedDate",
|
|
238
|
+
"System.ChangedDate",
|
|
239
|
+
"System.WorkItemType",
|
|
240
|
+
"System.AreaPath",
|
|
241
|
+
"System.IterationPath",
|
|
242
|
+
"System.Tags"
|
|
243
|
+
]
|
|
244
|
+
});
|
|
245
|
+
return response.value || [];
|
|
246
|
+
}
|
|
247
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
248
|
+
function makeRequest(url, pat, method = "GET", body, timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
|
|
212
249
|
return new Promise((resolve, reject) => {
|
|
213
250
|
const { hostname, pathname, search } = new URL(url);
|
|
214
251
|
const authHeader = "Basic " + Buffer.from(`:${pat}`).toString("base64");
|
|
@@ -223,7 +260,8 @@ function makeRequest(url, pat, method = "GET", body) {
|
|
|
223
260
|
hostname,
|
|
224
261
|
path: pathname + search,
|
|
225
262
|
method,
|
|
226
|
-
headers
|
|
263
|
+
headers,
|
|
264
|
+
timeout: timeoutMs
|
|
227
265
|
};
|
|
228
266
|
const req = https.request(options, (res) => {
|
|
229
267
|
let data = "";
|
|
@@ -243,6 +281,10 @@ function makeRequest(url, pat, method = "GET", body) {
|
|
|
243
281
|
}
|
|
244
282
|
});
|
|
245
283
|
});
|
|
284
|
+
req.on("timeout", () => {
|
|
285
|
+
req.destroy();
|
|
286
|
+
reject(new Error(`ADO request timed out after ${timeoutMs}ms: ${method} ${url}`));
|
|
287
|
+
});
|
|
246
288
|
req.on("error", (error) => {
|
|
247
289
|
reject(error);
|
|
248
290
|
});
|
|
@@ -81,6 +81,14 @@ export async function buildHierarchicalWIQL(
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
// Per-container filters (each container applies its own filters)
|
|
85
|
+
if (container.filters) {
|
|
86
|
+
const containerFilterClauses = buildFilterClauses(container.filters);
|
|
87
|
+
if (containerFilterClauses.length > 0) {
|
|
88
|
+
parts.push(...containerFilterClauses);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
84
92
|
// Combine parts with AND
|
|
85
93
|
projectClauses.push(`(${parts.join(' AND ')})`);
|
|
86
94
|
}
|
|
@@ -88,15 +96,6 @@ export async function buildHierarchicalWIQL(
|
|
|
88
96
|
// Build WHERE clause with project clauses
|
|
89
97
|
let whereClause = projectClauses.join(' OR ');
|
|
90
98
|
|
|
91
|
-
// Add global filters (apply to all projects)
|
|
92
|
-
const filters = containers[0]?.filters;
|
|
93
|
-
if (filters) {
|
|
94
|
-
const filterClauses = buildFilterClauses(filters);
|
|
95
|
-
if (filterClauses.length > 0) {
|
|
96
|
-
whereClause = `(${whereClause}) AND ${filterClauses.join(' AND ')}`;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
99
|
// Build complete WIQL query
|
|
101
100
|
return `
|
|
102
101
|
SELECT [System.Id], [System.Title], [System.Description], [System.State],
|
|
@@ -185,15 +184,29 @@ function addTimeRangeFilter(wiql: string, timeRange: string): string {
|
|
|
185
184
|
|
|
186
185
|
const { since, until } = calculateTimeRange(timeRange as TimeRangePreset);
|
|
187
186
|
|
|
188
|
-
// Add time range to WHERE clause
|
|
189
187
|
const timeFilter = `[System.CreatedDate] >= '${since}' AND [System.CreatedDate] <= '${until}'`;
|
|
190
188
|
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
189
|
+
// Find the last ORDER BY outside of string literals (case-insensitive)
|
|
190
|
+
// Strategy: find ORDER BY keyword, ensuring it's not inside single quotes
|
|
191
|
+
const orderByRegex = /\bORDER\s+BY\b/i;
|
|
192
|
+
const match = wiql.match(orderByRegex);
|
|
193
|
+
|
|
194
|
+
if (match && match.index !== undefined) {
|
|
195
|
+
// Insert AND timeFilter before ORDER BY
|
|
196
|
+
const before = wiql.substring(0, match.index).trimEnd();
|
|
197
|
+
const after = wiql.substring(match.index);
|
|
198
|
+
return `${before} AND ${timeFilter} ${after}`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// No ORDER BY — check if there's a WHERE clause
|
|
202
|
+
const whereRegex = /\bWHERE\b/i;
|
|
203
|
+
if (whereRegex.test(wiql)) {
|
|
204
|
+
// Append to existing WHERE clause
|
|
195
205
|
return `${wiql} AND ${timeFilter}`;
|
|
196
206
|
}
|
|
207
|
+
|
|
208
|
+
// No WHERE clause at all — add one
|
|
209
|
+
return `${wiql} WHERE ${timeFilter}`;
|
|
197
210
|
}
|
|
198
211
|
|
|
199
212
|
/**
|
|
@@ -364,9 +377,15 @@ async function fetchWorkItemsFiltered(
|
|
|
364
377
|
|
|
365
378
|
console.log('🔍 Fetching work items (FILTERED strategy):', wiql);
|
|
366
379
|
|
|
367
|
-
//
|
|
368
|
-
const
|
|
380
|
+
// Detect multi-project: if containers span more than one project, use org-level endpoint
|
|
381
|
+
const uniqueProjects = new Set(containers.map(c => c.id));
|
|
382
|
+
if (uniqueProjects.size > 1) {
|
|
383
|
+
// Cross-project: use org-level WIQL endpoint
|
|
384
|
+
return executeQueryOrgLevel(organization, pat, wiql);
|
|
385
|
+
}
|
|
369
386
|
|
|
387
|
+
// Single-project: use project-scoped endpoint
|
|
388
|
+
const project = containers[0].id;
|
|
370
389
|
return executeQuery(organization, project, pat, wiql);
|
|
371
390
|
}
|
|
372
391
|
|
|
@@ -419,13 +438,58 @@ async function executeQuery(
|
|
|
419
438
|
}
|
|
420
439
|
|
|
421
440
|
/**
|
|
422
|
-
*
|
|
441
|
+
* Execute WIQL query at org level (cross-project)
|
|
442
|
+
*/
|
|
443
|
+
async function executeQueryOrgLevel(
|
|
444
|
+
organization: string,
|
|
445
|
+
pat: string,
|
|
446
|
+
wiql: string
|
|
447
|
+
): Promise<WorkItem[]> {
|
|
448
|
+
const baseUrl = `https://dev.azure.com/${organization}`;
|
|
449
|
+
|
|
450
|
+
// Execute query at org level
|
|
451
|
+
const queryUrl = `${baseUrl}/_apis/wit/wiql?api-version=7.1`;
|
|
452
|
+
const queryResult: any = await makeRequest(queryUrl, pat, 'POST', { query: wiql });
|
|
453
|
+
|
|
454
|
+
if (!queryResult.workItems || queryResult.workItems.length === 0) {
|
|
455
|
+
return [];
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Get full work item details (batch request at org level)
|
|
459
|
+
const ids = queryResult.workItems.map((wi: any) => wi.id);
|
|
460
|
+
const batchUrl = `${baseUrl}/_apis/wit/workitemsbatch?api-version=7.1`;
|
|
461
|
+
|
|
462
|
+
const response: any = await makeRequest(batchUrl, pat, 'POST', {
|
|
463
|
+
ids,
|
|
464
|
+
fields: [
|
|
465
|
+
'System.Id',
|
|
466
|
+
'System.Title',
|
|
467
|
+
'System.Description',
|
|
468
|
+
'System.State',
|
|
469
|
+
'System.CreatedDate',
|
|
470
|
+
'System.ChangedDate',
|
|
471
|
+
'System.WorkItemType',
|
|
472
|
+
'System.AreaPath',
|
|
473
|
+
'System.IterationPath',
|
|
474
|
+
'System.Tags',
|
|
475
|
+
],
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
return response.value || [];
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/** Default request timeout in ms (configurable via ado.requestTimeoutMs) */
|
|
482
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Make HTTPS request to ADO API with timeout
|
|
423
486
|
*/
|
|
424
487
|
function makeRequest(
|
|
425
488
|
url: string,
|
|
426
489
|
pat: string,
|
|
427
490
|
method: string = 'GET',
|
|
428
|
-
body?: any
|
|
491
|
+
body?: any,
|
|
492
|
+
timeoutMs: number = DEFAULT_REQUEST_TIMEOUT_MS
|
|
429
493
|
): Promise<any> {
|
|
430
494
|
return new Promise((resolve, reject) => {
|
|
431
495
|
const { hostname, pathname, search } = new URL(url);
|
|
@@ -446,6 +510,7 @@ function makeRequest(
|
|
|
446
510
|
path: pathname + search,
|
|
447
511
|
method,
|
|
448
512
|
headers,
|
|
513
|
+
timeout: timeoutMs,
|
|
449
514
|
};
|
|
450
515
|
|
|
451
516
|
const req = https.request(options, (res) => {
|
|
@@ -470,6 +535,11 @@ function makeRequest(
|
|
|
470
535
|
});
|
|
471
536
|
});
|
|
472
537
|
|
|
538
|
+
req.on('timeout', () => {
|
|
539
|
+
req.destroy();
|
|
540
|
+
reject(new Error(`ADO request timed out after ${timeoutMs}ms: ${method} ${url}`));
|
|
541
|
+
});
|
|
542
|
+
|
|
473
543
|
req.on('error', (error) => {
|
|
474
544
|
reject(error);
|
|
475
545
|
});
|
|
@@ -26,7 +26,7 @@ class AdoProfileResolver {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
const incrementProfile = await this.getIncrementProfile(incrementId);
|
|
29
|
-
const globalDefaultProfile = config.sync?.defaultProfile;
|
|
29
|
+
const globalDefaultProfile = config.sync?.activeProfile ?? config.sync?.defaultProfile;
|
|
30
30
|
const profileName = incrementProfile || globalDefaultProfile;
|
|
31
31
|
if (!profileName) {
|
|
32
32
|
return {
|
|
@@ -108,6 +108,7 @@ interface ConfigProfile {
|
|
|
108
108
|
* Sync config from config.json
|
|
109
109
|
*/
|
|
110
110
|
interface SyncConfig {
|
|
111
|
+
activeProfile?: string;
|
|
111
112
|
defaultProfile?: string;
|
|
112
113
|
profiles?: Record<string, ConfigProfile>;
|
|
113
114
|
}
|
|
@@ -158,7 +159,8 @@ export class AdoProfileResolver {
|
|
|
158
159
|
const incrementProfile = await this.getIncrementProfile(incrementId);
|
|
159
160
|
|
|
160
161
|
// Determine which profile to use
|
|
161
|
-
|
|
162
|
+
// Read both activeProfile (canonical) and defaultProfile (legacy) with fallback
|
|
163
|
+
const globalDefaultProfile = config.sync?.activeProfile ?? config.sync?.defaultProfile;
|
|
162
164
|
const profileName = incrementProfile || globalDefaultProfile;
|
|
163
165
|
|
|
164
166
|
if (!profileName) {
|