specweave 0.33.2 → 0.33.4
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 +133 -19
- package/dist/plugins/specweave-ado/lib/per-us-sync.d.ts +120 -0
- package/dist/plugins/specweave-ado/lib/per-us-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-ado/lib/per-us-sync.js +276 -0
- package/dist/plugins/specweave-ado/lib/per-us-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.d.ts +4 -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 +13 -3
- package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
- package/dist/plugins/specweave-github/lib/per-us-sync.d.ts +97 -0
- package/dist/plugins/specweave-github/lib/per-us-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/per-us-sync.js +274 -0
- package/dist/plugins/specweave-github/lib/per-us-sync.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/per-us-sync.d.ts +113 -0
- package/dist/plugins/specweave-jira/lib/per-us-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/per-us-sync.js +254 -0
- package/dist/plugins/specweave-jira/lib/per-us-sync.js.map +1 -0
- package/dist/src/cli/cleanup-zombies.js +8 -5
- package/dist/src/cli/cleanup-zombies.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/config/config-manager.d.ts.map +1 -1
- package/dist/src/core/config/config-manager.js +58 -0
- package/dist/src/core/config/config-manager.js.map +1 -1
- package/dist/src/core/config/types.d.ts +80 -0
- package/dist/src/core/config/types.d.ts.map +1 -1
- package/dist/src/core/config/types.js.map +1 -1
- package/dist/src/core/living-docs/cross-project-sync.d.ts +87 -15
- package/dist/src/core/living-docs/cross-project-sync.d.ts.map +1 -1
- package/dist/src/core/living-docs/cross-project-sync.js +147 -28
- package/dist/src/core/living-docs/cross-project-sync.js.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.js +26 -22
- package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
- package/dist/src/core/living-docs/types.d.ts +24 -3
- package/dist/src/core/living-docs/types.d.ts.map +1 -1
- package/dist/src/core/types/config.d.ts +79 -0
- package/dist/src/core/types/config.d.ts.map +1 -1
- package/dist/src/core/types/config.js.map +1 -1
- package/dist/src/importers/jira-importer.d.ts +10 -0
- package/dist/src/importers/jira-importer.d.ts.map +1 -1
- package/dist/src/importers/jira-importer.js +55 -5
- 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/sync/closure-metrics.d.ts +102 -0
- package/dist/src/sync/closure-metrics.d.ts.map +1 -0
- package/dist/src/sync/closure-metrics.js +267 -0
- package/dist/src/sync/closure-metrics.js.map +1 -0
- package/dist/src/sync/sync-coordinator.d.ts +49 -0
- package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
- package/dist/src/sync/sync-coordinator.js +399 -37
- package/dist/src/sync/sync-coordinator.js.map +1 -1
- package/dist/src/utils/notification-constants.d.ts +85 -0
- package/dist/src/utils/notification-constants.d.ts.map +1 -0
- package/dist/src/utils/notification-constants.js +129 -0
- package/dist/src/utils/notification-constants.js.map +1 -0
- package/dist/src/utils/platform-utils.d.ts +13 -3
- package/dist/src/utils/platform-utils.d.ts.map +1 -1
- package/dist/src/utils/platform-utils.js +17 -6
- package/dist/src/utils/platform-utils.js.map +1 -1
- package/dist/src/utils/project-resolver.d.ts +156 -0
- package/dist/src/utils/project-resolver.d.ts.map +1 -0
- package/dist/src/utils/project-resolver.js +587 -0
- package/dist/src/utils/project-resolver.js.map +1 -0
- package/package.json +1 -1
- package/plugins/specweave/commands/specweave-increment.md +46 -0
- package/plugins/specweave/commands/specweave-jobs.md +153 -8
- package/plugins/specweave/hooks/hooks.json +10 -0
- package/plugins/specweave/hooks/spec-project-validator.sh +24 -2
- package/plugins/specweave/hooks/universal/hook-wrapper.cmd +26 -26
- package/plugins/specweave/hooks/universal/session-start.cmd +16 -16
- package/plugins/specweave/hooks/universal/session-start.ps1 +16 -16
- package/plugins/specweave/hooks/user-prompt-submit.sh +105 -3
- package/plugins/specweave/hooks/v2/guards/per-us-project-validator.sh +281 -0
- package/plugins/specweave/hooks/v2/handlers/living-specs-handler.sh +29 -0
- package/plugins/specweave/scripts/session-watchdog.sh +278 -130
- package/plugins/specweave/skills/increment-planner/SKILL.md +48 -18
- package/plugins/specweave/skills/increment-planner/templates/spec-multi-project.md +27 -14
- package/plugins/specweave/skills/increment-planner/templates/spec-single-project.md +16 -5
- package/plugins/specweave/skills/spec-generator/SKILL.md +74 -15
- package/plugins/specweave-ado/lib/per-us-sync.js +247 -0
- package/plugins/specweave-ado/lib/per-us-sync.ts +410 -0
- package/plugins/specweave-github/lib/github-client-v2.js +10 -3
- package/plugins/specweave-github/lib/github-client-v2.ts +15 -3
- package/plugins/specweave-github/lib/per-us-sync.js +241 -0
- package/plugins/specweave-github/lib/per-us-sync.ts +375 -0
- package/plugins/specweave-jira/lib/per-us-sync.js +224 -0
- package/plugins/specweave-jira/lib/per-us-sync.ts +366 -0
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +0 -738
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +0 -1107
|
@@ -8,17 +8,18 @@ created: {{DATE}}
|
|
|
8
8
|
structure: user-stories
|
|
9
9
|
test_mode: {{TEST_MODE}}
|
|
10
10
|
coverage_target: {{COVERAGE_TARGET}}
|
|
11
|
-
# MANDATORY:
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
# MANDATORY: Must be RESOLVED values from "specweave context projects" output
|
|
12
|
+
# ⛔ NEVER use {{PROJECT_ID}} or {{BOARD_ID}} placeholders!
|
|
13
|
+
# For 2-level structures: BOTH project AND board are REQUIRED per US
|
|
14
|
+
project: {{RESOLVED_PROJECT}}
|
|
15
|
+
board: {{RESOLVED_BOARD}}
|
|
15
16
|
multi_project: true
|
|
16
17
|
projects:
|
|
17
|
-
- id: {{
|
|
18
|
+
- id: {{RESOLVED_PROJECT_FE}}
|
|
18
19
|
prefix: FE
|
|
19
|
-
- id: {{
|
|
20
|
+
- id: {{RESOLVED_PROJECT_BE}}
|
|
20
21
|
prefix: BE
|
|
21
|
-
- id: {{
|
|
22
|
+
- id: {{RESOLVED_PROJECT_SHARED}}
|
|
22
23
|
prefix: SHARED
|
|
23
24
|
---
|
|
24
25
|
|
|
@@ -30,13 +31,25 @@ projects:
|
|
|
30
31
|
|
|
31
32
|
## User Stories
|
|
32
33
|
|
|
33
|
-
<!--
|
|
34
|
+
<!--
|
|
35
|
+
⚠️ MANDATORY RESOLUTION (v0.34.0+):
|
|
36
|
+
1. Run: specweave context projects
|
|
37
|
+
2. Parse JSON output:
|
|
38
|
+
- level 1: projects[].id gives valid project IDs
|
|
39
|
+
- level 2: projects[].id + boardsByProject[project][].id gives project AND board IDs
|
|
40
|
+
3. Replace ALL placeholders with actual IDs from step 2
|
|
41
|
+
4. Each US MUST have **Project**: (and **Board**: for 2-level) with RESOLVED values
|
|
42
|
+
|
|
43
|
+
❌ FORBIDDEN: Using {{PROJECT_ID}}, {{BOARD_ID}} placeholders
|
|
44
|
+
❌ FORBIDDEN: Inventing project/board names
|
|
45
|
+
✅ REQUIRED: Use ONLY IDs from "specweave context projects" output
|
|
46
|
+
-->
|
|
34
47
|
|
|
35
48
|
### Frontend Stories
|
|
36
49
|
|
|
37
50
|
#### US-FE-001: [Story Title] (P1)
|
|
38
|
-
**Project**: {{
|
|
39
|
-
**Board**: {{
|
|
51
|
+
**Project**: {{RESOLVED_PROJECT}}
|
|
52
|
+
**Board**: {{RESOLVED_BOARD_FE}}
|
|
40
53
|
|
|
41
54
|
**As a** [user type]
|
|
42
55
|
**I want** [goal]
|
|
@@ -51,8 +64,8 @@ projects:
|
|
|
51
64
|
### Backend Stories
|
|
52
65
|
|
|
53
66
|
#### US-BE-001: [Story Title] (P1)
|
|
54
|
-
**Project**: {{
|
|
55
|
-
**Board**: {{
|
|
67
|
+
**Project**: {{RESOLVED_PROJECT}}
|
|
68
|
+
**Board**: {{RESOLVED_BOARD_BE}}
|
|
56
69
|
|
|
57
70
|
**As a** [system/frontend application]
|
|
58
71
|
**I want** [API endpoint/service goal]
|
|
@@ -67,8 +80,8 @@ projects:
|
|
|
67
80
|
### Shared Library Stories
|
|
68
81
|
|
|
69
82
|
#### US-SHARED-001: [Story Title] (P1)
|
|
70
|
-
**Project**: {{
|
|
71
|
-
**Board**: {{
|
|
83
|
+
**Project**: {{RESOLVED_PROJECT}}
|
|
84
|
+
**Board**: {{RESOLVED_BOARD_SHARED}}
|
|
72
85
|
|
|
73
86
|
**As a** developer in FE or BE repos
|
|
74
87
|
**I want** [shared types/utilities/validators]
|
|
@@ -8,8 +8,9 @@ created: {{DATE}}
|
|
|
8
8
|
structure: user-stories
|
|
9
9
|
test_mode: {{TEST_MODE}}
|
|
10
10
|
coverage_target: {{COVERAGE_TARGET}}
|
|
11
|
-
# MANDATORY:
|
|
12
|
-
|
|
11
|
+
# MANDATORY: Must be a RESOLVED value from "specweave context projects" output
|
|
12
|
+
# ⛔ NEVER use {{PROJECT_ID}} placeholder - always resolve BEFORE creating spec.md!
|
|
13
|
+
project: {{RESOLVED_PROJECT}}
|
|
13
14
|
---
|
|
14
15
|
|
|
15
16
|
# Feature: {{FEATURE_TITLE}}
|
|
@@ -20,10 +21,20 @@ project: {{PROJECT_ID}}
|
|
|
20
21
|
|
|
21
22
|
## User Stories
|
|
22
23
|
|
|
23
|
-
<!--
|
|
24
|
+
<!--
|
|
25
|
+
⚠️ MANDATORY RESOLUTION (v0.34.0+):
|
|
26
|
+
1. Run: specweave context projects
|
|
27
|
+
2. Parse JSON: projects[].id gives valid project IDs
|
|
28
|
+
3. Replace {{RESOLVED_PROJECT}} with actual ID from step 2
|
|
29
|
+
4. Each US MUST have **Project**: field with RESOLVED value
|
|
30
|
+
|
|
31
|
+
❌ FORBIDDEN: Using {{PROJECT_ID}} placeholder
|
|
32
|
+
❌ FORBIDDEN: Inventing project names
|
|
33
|
+
✅ REQUIRED: Use ONLY IDs from "specweave context projects" output
|
|
34
|
+
-->
|
|
24
35
|
|
|
25
36
|
### US-001: [Story Title] (P1)
|
|
26
|
-
**Project**: {{
|
|
37
|
+
**Project**: {{RESOLVED_PROJECT}}
|
|
27
38
|
|
|
28
39
|
**As a** [user type]
|
|
29
40
|
**I want** [goal]
|
|
@@ -34,7 +45,7 @@ project: {{PROJECT_ID}}
|
|
|
34
45
|
- [ ] **AC-US1-02**: [Another criterion]
|
|
35
46
|
|
|
36
47
|
### US-002: [Story Title] (P2)
|
|
37
|
-
**Project**: {{
|
|
48
|
+
**Project**: {{RESOLVED_PROJECT}}
|
|
38
49
|
|
|
39
50
|
[Repeat structure - change Project per US if spanning multiple projects]
|
|
40
51
|
|
|
@@ -85,34 +85,93 @@ The LLM MUST resolve these from context - see RULE 0 in increment-planner.
|
|
|
85
85
|
|
|
86
86
|
---
|
|
87
87
|
|
|
88
|
+
### MANDATORY STEP 0: Get Project Context FIRST (v0.34.0+ BLOCKING!)
|
|
89
|
+
|
|
90
|
+
**⛔ YOU CANNOT GENERATE spec.md UNTIL YOU COMPLETE THIS STEP!**
|
|
91
|
+
|
|
92
|
+
**This step is BLOCKING - do not proceed until you have actual project/board IDs.**
|
|
93
|
+
|
|
94
|
+
**1. Run the context API command:**
|
|
95
|
+
```bash
|
|
96
|
+
specweave context projects
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**2. Parse the JSON output:**
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"level": 1,
|
|
103
|
+
"projects": [{"id": "frontend-app", "name": "Frontend App"}],
|
|
104
|
+
"detectionReason": "multiProject configuration"
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
For 2-level:
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"level": 2,
|
|
111
|
+
"projects": [{"id": "acme-corp", "name": "ACME Corp"}],
|
|
112
|
+
"boardsByProject": {
|
|
113
|
+
"acme-corp": [
|
|
114
|
+
{"id": "digital-ops", "name": "Digital Operations"},
|
|
115
|
+
{"id": "mobile-team", "name": "Mobile Team"}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**3. STORE the actual IDs for use in spec.md:**
|
|
122
|
+
```
|
|
123
|
+
RESOLVED_PROJECT = "frontend-app" // from projects[].id
|
|
124
|
+
RESOLVED_BOARD = "digital-ops" // from boardsByProject (2-level only)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**4. Now generate spec.md using RESOLVED values (NEVER placeholders!)**
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
88
131
|
### Per-US Project Resolution (v0.33.0+ MANDATORY)
|
|
89
132
|
|
|
90
|
-
**🧠 USE
|
|
133
|
+
**🧠 USE CONTEXT API OUTPUT + LIVING DOCS TO RESOLVE PROJECT/BOARD:**
|
|
91
134
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
135
|
+
After running `specweave context projects`, you have the valid project/board IDs.
|
|
136
|
+
Now map each user story to the correct project:
|
|
137
|
+
|
|
138
|
+
**Resolution Flow:**
|
|
139
|
+
```
|
|
140
|
+
1. Get valid projects from context API: ["frontend-app", "backend-api", "shared"]
|
|
141
|
+
2. Analyze feature description for keywords
|
|
142
|
+
3. Map keywords to ACTUAL project IDs (from step 1, NOT generic terms!)
|
|
143
|
+
4. Assign each US to its project
|
|
144
|
+
```
|
|
97
145
|
|
|
98
146
|
**Resolution Example:**
|
|
99
147
|
```
|
|
148
|
+
Context API returned: projects = ["frontend-app", "backend-api", "shared"]
|
|
149
|
+
|
|
100
150
|
Feature: "Add OAuth login to React frontend"
|
|
101
|
-
Detected: "React", "frontend", "login"
|
|
151
|
+
Detected keywords: "React", "frontend", "login"
|
|
102
152
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
153
|
+
Mapping:
|
|
154
|
+
- "frontend" keyword → matches "frontend-app" (from context API)
|
|
155
|
+
- "login" spans frontend + backend
|
|
106
156
|
|
|
107
|
-
|
|
157
|
+
Result:
|
|
108
158
|
US-001 (Login UI) → **Project**: frontend-app
|
|
109
159
|
US-002 (Auth API) → **Project**: backend-api
|
|
110
160
|
```
|
|
111
161
|
|
|
112
|
-
**
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
162
|
+
**VALIDATION RULES:**
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
✅ REQUIRED: Run "specweave context projects" BEFORE generating spec.md
|
|
166
|
+
✅ REQUIRED: Use ONLY project IDs from the API response
|
|
167
|
+
✅ REQUIRED: Each US has explicit **Project**: field with resolved value
|
|
168
|
+
✅ REQUIRED: For 2-level, each US has explicit **Board**: field with resolved value
|
|
169
|
+
|
|
170
|
+
❌ FORBIDDEN: Generating spec.md without running context API first
|
|
171
|
+
❌ FORBIDDEN: Using {{PROJECT_ID}} or {{BOARD_ID}} placeholders
|
|
172
|
+
❌ FORBIDDEN: Using generic keywords as project names ("frontend" vs "frontend-app")
|
|
173
|
+
❌ FORBIDDEN: Inventing project names not in the API response
|
|
174
|
+
```
|
|
116
175
|
|
|
117
176
|
## Success Metrics
|
|
118
177
|
[How we'll measure success]
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { consoleLogger } from "../../../src/utils/logger.js";
|
|
2
|
+
class PerUSAdoSync {
|
|
3
|
+
constructor(adoClient, projectMappings, options = {}) {
|
|
4
|
+
this.adoClient = adoClient;
|
|
5
|
+
this.projectMappings = projectMappings;
|
|
6
|
+
this.logger = options.logger ?? consoleLogger;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Sync all user stories to their respective ADO projects
|
|
10
|
+
*
|
|
11
|
+
* @param userStories - User stories with explicit project/board fields
|
|
12
|
+
* @param featureId - Feature ID (e.g., "FS-137")
|
|
13
|
+
* @param options - Sync options
|
|
14
|
+
*/
|
|
15
|
+
async syncUserStories(userStories, featureId, options = {}) {
|
|
16
|
+
const synced = [];
|
|
17
|
+
const failed = [];
|
|
18
|
+
const externalRefs = {};
|
|
19
|
+
const groups = this.groupByProject(userStories, options.defaultProject);
|
|
20
|
+
this.logger.log(`\u{1F4E1} Per-US ADO Sync: ${userStories.length} USs across ${groups.size} projects`);
|
|
21
|
+
for (const [projectId, stories] of groups) {
|
|
22
|
+
const mapping = this.projectMappings[projectId]?.ado;
|
|
23
|
+
if (!mapping) {
|
|
24
|
+
this.logger.warn(` \u26A0\uFE0F No ADO mapping for project "${projectId}" - skipping ${stories.length} USs`);
|
|
25
|
+
for (const story of stories) {
|
|
26
|
+
failed.push({
|
|
27
|
+
usId: story.id,
|
|
28
|
+
projectId,
|
|
29
|
+
adoProject: "N/A",
|
|
30
|
+
areaPath: "",
|
|
31
|
+
workItemId: 0,
|
|
32
|
+
url: "",
|
|
33
|
+
action: "skipped",
|
|
34
|
+
error: `No ADO mapping for project "${projectId}"`
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
for (const story of stories) {
|
|
40
|
+
try {
|
|
41
|
+
const result = await this.syncUserStory(story, mapping, featureId, options);
|
|
42
|
+
synced.push({
|
|
43
|
+
...result,
|
|
44
|
+
projectId
|
|
45
|
+
});
|
|
46
|
+
if (!options.dryRun && result.action !== "skipped") {
|
|
47
|
+
externalRefs[story.id] = {
|
|
48
|
+
ado: {
|
|
49
|
+
provider: "ado",
|
|
50
|
+
issueNumber: result.workItemId,
|
|
51
|
+
url: result.url,
|
|
52
|
+
targetProject: projectId,
|
|
53
|
+
lastSynced: (/* @__PURE__ */ new Date()).toISOString()
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
failed.push({
|
|
59
|
+
usId: story.id,
|
|
60
|
+
projectId,
|
|
61
|
+
adoProject: mapping.project,
|
|
62
|
+
areaPath: mapping.areaPath || "",
|
|
63
|
+
workItemId: 0,
|
|
64
|
+
url: "",
|
|
65
|
+
action: "skipped",
|
|
66
|
+
error: error instanceof Error ? error.message : String(error)
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const created = synced.filter((r) => r.action === "created").length;
|
|
72
|
+
const updated = synced.filter((r) => r.action === "updated").length;
|
|
73
|
+
const skipped = synced.filter((r) => r.action === "skipped").length;
|
|
74
|
+
return {
|
|
75
|
+
success: failed.length === 0,
|
|
76
|
+
synced,
|
|
77
|
+
failed,
|
|
78
|
+
externalRefs,
|
|
79
|
+
summary: {
|
|
80
|
+
total: userStories.length,
|
|
81
|
+
created,
|
|
82
|
+
updated,
|
|
83
|
+
skipped,
|
|
84
|
+
failed: failed.length
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Sync a single user story to ADO
|
|
90
|
+
*
|
|
91
|
+
* For 2-level structures:
|
|
92
|
+
* - **Project**: maps to ADO project
|
|
93
|
+
* - **Board**: maps to area path under the project
|
|
94
|
+
*/
|
|
95
|
+
async syncUserStory(story, mapping, featureId, options) {
|
|
96
|
+
const title = `[${featureId}][${story.id}] ${story.title}`;
|
|
97
|
+
const description = this.buildWorkItemDescription(story, featureId);
|
|
98
|
+
let areaPath = mapping.areaPath || mapping.project;
|
|
99
|
+
if (story.board) {
|
|
100
|
+
areaPath = `${mapping.project}\\${story.board}`;
|
|
101
|
+
}
|
|
102
|
+
if (options.dryRun) {
|
|
103
|
+
this.logger.log(` \u{1F50D} [DRY-RUN] Would sync ${story.id} to ${mapping.project} (area: ${areaPath})`);
|
|
104
|
+
return {
|
|
105
|
+
usId: story.id,
|
|
106
|
+
projectId: story.project || "unknown",
|
|
107
|
+
adoProject: mapping.project,
|
|
108
|
+
areaPath,
|
|
109
|
+
workItemId: 0,
|
|
110
|
+
url: "",
|
|
111
|
+
action: "skipped"
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const existingItem = await this.findExistingWorkItem(mapping.project, story.id);
|
|
115
|
+
if (existingItem) {
|
|
116
|
+
await this.adoClient.updateWorkItem(
|
|
117
|
+
mapping.project,
|
|
118
|
+
existingItem.id,
|
|
119
|
+
title,
|
|
120
|
+
description,
|
|
121
|
+
areaPath
|
|
122
|
+
);
|
|
123
|
+
this.logger.log(` \u{1F504} Updated ${story.id} \u2192 ${mapping.project}/${existingItem.id}`);
|
|
124
|
+
return {
|
|
125
|
+
usId: story.id,
|
|
126
|
+
projectId: story.project || "unknown",
|
|
127
|
+
adoProject: mapping.project,
|
|
128
|
+
areaPath,
|
|
129
|
+
workItemId: existingItem.id,
|
|
130
|
+
url: this.adoClient.getWorkItemUrl(mapping.project, existingItem.id),
|
|
131
|
+
action: "updated"
|
|
132
|
+
};
|
|
133
|
+
} else {
|
|
134
|
+
const newItem = await this.adoClient.createWorkItem(
|
|
135
|
+
mapping.project,
|
|
136
|
+
"User Story",
|
|
137
|
+
title,
|
|
138
|
+
description,
|
|
139
|
+
areaPath
|
|
140
|
+
);
|
|
141
|
+
this.logger.log(` \u2705 Created ${story.id} \u2192 ${mapping.project}/${newItem.id}`);
|
|
142
|
+
return {
|
|
143
|
+
usId: story.id,
|
|
144
|
+
projectId: story.project || "unknown",
|
|
145
|
+
adoProject: mapping.project,
|
|
146
|
+
areaPath,
|
|
147
|
+
workItemId: newItem.id,
|
|
148
|
+
url: newItem.url,
|
|
149
|
+
action: "created"
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Find existing work item by US ID in title
|
|
155
|
+
*/
|
|
156
|
+
async findExistingWorkItem(project, usId) {
|
|
157
|
+
try {
|
|
158
|
+
const query = `[System.Title] Contains '[${usId}]'`;
|
|
159
|
+
const results = await this.adoClient.searchWorkItems(project, query);
|
|
160
|
+
return results.length > 0 ? { id: results[0].id } : null;
|
|
161
|
+
} catch {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Build work item description from user story
|
|
167
|
+
*/
|
|
168
|
+
buildWorkItemDescription(story, featureId) {
|
|
169
|
+
const lines = [];
|
|
170
|
+
lines.push(`<h1>${story.title}</h1>`);
|
|
171
|
+
lines.push("");
|
|
172
|
+
if (story.description) {
|
|
173
|
+
lines.push(`<p>${story.description}</p>`);
|
|
174
|
+
lines.push("");
|
|
175
|
+
}
|
|
176
|
+
if (story.acceptanceCriteria && story.acceptanceCriteria.length > 0) {
|
|
177
|
+
lines.push("<h2>Acceptance Criteria</h2>");
|
|
178
|
+
lines.push("<ul>");
|
|
179
|
+
for (const ac of story.acceptanceCriteria) {
|
|
180
|
+
lines.push(` <li>${ac}</li>`);
|
|
181
|
+
}
|
|
182
|
+
lines.push("</ul>");
|
|
183
|
+
lines.push("");
|
|
184
|
+
}
|
|
185
|
+
lines.push("<hr/>");
|
|
186
|
+
lines.push("");
|
|
187
|
+
lines.push(`<p><strong>Feature</strong>: ${featureId}</p>`);
|
|
188
|
+
lines.push(`<p><strong>User Story</strong>: ${story.id}</p>`);
|
|
189
|
+
if (story.project) {
|
|
190
|
+
lines.push(`<p><strong>Project</strong>: ${story.project}</p>`);
|
|
191
|
+
}
|
|
192
|
+
if (story.board) {
|
|
193
|
+
lines.push(`<p><strong>Board</strong>: ${story.board}</p>`);
|
|
194
|
+
}
|
|
195
|
+
lines.push("");
|
|
196
|
+
lines.push("<p><em>Auto-generated by SpecWeave</em></p>");
|
|
197
|
+
return lines.join("\n");
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Group user stories by their explicit project field
|
|
201
|
+
*/
|
|
202
|
+
groupByProject(userStories, defaultProject) {
|
|
203
|
+
const groups = /* @__PURE__ */ new Map();
|
|
204
|
+
for (const story of userStories) {
|
|
205
|
+
const project = story.project || defaultProject || "default";
|
|
206
|
+
if (!groups.has(project)) {
|
|
207
|
+
groups.set(project, []);
|
|
208
|
+
}
|
|
209
|
+
groups.get(project).push(story);
|
|
210
|
+
}
|
|
211
|
+
return groups;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function formatPerUSSyncResults(result) {
|
|
215
|
+
const lines = [];
|
|
216
|
+
lines.push("");
|
|
217
|
+
lines.push("\u{1F4CA} Per-US ADO Sync Results");
|
|
218
|
+
lines.push("");
|
|
219
|
+
const byProject = /* @__PURE__ */ new Map();
|
|
220
|
+
for (const r of [...result.synced, ...result.failed]) {
|
|
221
|
+
const existing = byProject.get(r.projectId) || [];
|
|
222
|
+
existing.push(r);
|
|
223
|
+
byProject.set(r.projectId, existing);
|
|
224
|
+
}
|
|
225
|
+
for (const [projectId, results] of byProject) {
|
|
226
|
+
const adoProject = results[0]?.adoProject || "N/A";
|
|
227
|
+
const areaPath = results[0]?.areaPath || "";
|
|
228
|
+
lines.push(`**${projectId}** (\u2192 ${adoProject}${areaPath ? ` [${areaPath}]` : ""}):`);
|
|
229
|
+
for (const r of results) {
|
|
230
|
+
const icon = r.action === "created" ? "\u2705" : r.action === "updated" ? "\u{1F504}" : r.error ? "\u274C" : "\u23ED\uFE0F";
|
|
231
|
+
if (r.workItemId > 0) {
|
|
232
|
+
lines.push(` ${icon} ${r.usId} \u2192 ${r.adoProject}/${r.workItemId}`);
|
|
233
|
+
} else if (r.error) {
|
|
234
|
+
lines.push(` ${icon} ${r.usId}: ${r.error}`);
|
|
235
|
+
} else {
|
|
236
|
+
lines.push(` ${icon} ${r.usId} (${r.action})`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
lines.push("");
|
|
240
|
+
}
|
|
241
|
+
lines.push(`\u{1F4C8} Summary: ${result.summary.created} created, ${result.summary.updated} updated, ${result.summary.failed} failed`);
|
|
242
|
+
return lines.join("\n");
|
|
243
|
+
}
|
|
244
|
+
export {
|
|
245
|
+
PerUSAdoSync,
|
|
246
|
+
formatPerUSSyncResults
|
|
247
|
+
};
|