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.
Files changed (101) hide show
  1. package/CLAUDE.md +133 -19
  2. package/dist/plugins/specweave-ado/lib/per-us-sync.d.ts +120 -0
  3. package/dist/plugins/specweave-ado/lib/per-us-sync.d.ts.map +1 -0
  4. package/dist/plugins/specweave-ado/lib/per-us-sync.js +276 -0
  5. package/dist/plugins/specweave-ado/lib/per-us-sync.js.map +1 -0
  6. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts +4 -1
  7. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
  8. package/dist/plugins/specweave-github/lib/github-client-v2.js +13 -3
  9. package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
  10. package/dist/plugins/specweave-github/lib/per-us-sync.d.ts +97 -0
  11. package/dist/plugins/specweave-github/lib/per-us-sync.d.ts.map +1 -0
  12. package/dist/plugins/specweave-github/lib/per-us-sync.js +274 -0
  13. package/dist/plugins/specweave-github/lib/per-us-sync.js.map +1 -0
  14. package/dist/plugins/specweave-jira/lib/per-us-sync.d.ts +113 -0
  15. package/dist/plugins/specweave-jira/lib/per-us-sync.d.ts.map +1 -0
  16. package/dist/plugins/specweave-jira/lib/per-us-sync.js +254 -0
  17. package/dist/plugins/specweave-jira/lib/per-us-sync.js.map +1 -0
  18. package/dist/src/cli/cleanup-zombies.js +8 -5
  19. package/dist/src/cli/cleanup-zombies.js.map +1 -1
  20. package/dist/src/config/types.d.ts +203 -1208
  21. package/dist/src/config/types.d.ts.map +1 -1
  22. package/dist/src/core/config/config-manager.d.ts.map +1 -1
  23. package/dist/src/core/config/config-manager.js +58 -0
  24. package/dist/src/core/config/config-manager.js.map +1 -1
  25. package/dist/src/core/config/types.d.ts +80 -0
  26. package/dist/src/core/config/types.d.ts.map +1 -1
  27. package/dist/src/core/config/types.js.map +1 -1
  28. package/dist/src/core/living-docs/cross-project-sync.d.ts +87 -15
  29. package/dist/src/core/living-docs/cross-project-sync.d.ts.map +1 -1
  30. package/dist/src/core/living-docs/cross-project-sync.js +147 -28
  31. package/dist/src/core/living-docs/cross-project-sync.js.map +1 -1
  32. package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
  33. package/dist/src/core/living-docs/living-docs-sync.js +26 -22
  34. package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
  35. package/dist/src/core/living-docs/types.d.ts +24 -3
  36. package/dist/src/core/living-docs/types.d.ts.map +1 -1
  37. package/dist/src/core/types/config.d.ts +79 -0
  38. package/dist/src/core/types/config.d.ts.map +1 -1
  39. package/dist/src/core/types/config.js.map +1 -1
  40. package/dist/src/importers/jira-importer.d.ts +10 -0
  41. package/dist/src/importers/jira-importer.d.ts.map +1 -1
  42. package/dist/src/importers/jira-importer.js +55 -5
  43. package/dist/src/importers/jira-importer.js.map +1 -1
  44. package/dist/src/init/architecture/types.d.ts +33 -140
  45. package/dist/src/init/architecture/types.d.ts.map +1 -1
  46. package/dist/src/init/compliance/types.d.ts +30 -27
  47. package/dist/src/init/compliance/types.d.ts.map +1 -1
  48. package/dist/src/init/repo/types.d.ts +11 -34
  49. package/dist/src/init/repo/types.d.ts.map +1 -1
  50. package/dist/src/init/research/src/config/types.d.ts +15 -82
  51. package/dist/src/init/research/src/config/types.d.ts.map +1 -1
  52. package/dist/src/init/research/types.d.ts +38 -93
  53. package/dist/src/init/research/types.d.ts.map +1 -1
  54. package/dist/src/init/team/types.d.ts +4 -42
  55. package/dist/src/init/team/types.d.ts.map +1 -1
  56. package/dist/src/sync/closure-metrics.d.ts +102 -0
  57. package/dist/src/sync/closure-metrics.d.ts.map +1 -0
  58. package/dist/src/sync/closure-metrics.js +267 -0
  59. package/dist/src/sync/closure-metrics.js.map +1 -0
  60. package/dist/src/sync/sync-coordinator.d.ts +49 -0
  61. package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
  62. package/dist/src/sync/sync-coordinator.js +399 -37
  63. package/dist/src/sync/sync-coordinator.js.map +1 -1
  64. package/dist/src/utils/notification-constants.d.ts +85 -0
  65. package/dist/src/utils/notification-constants.d.ts.map +1 -0
  66. package/dist/src/utils/notification-constants.js +129 -0
  67. package/dist/src/utils/notification-constants.js.map +1 -0
  68. package/dist/src/utils/platform-utils.d.ts +13 -3
  69. package/dist/src/utils/platform-utils.d.ts.map +1 -1
  70. package/dist/src/utils/platform-utils.js +17 -6
  71. package/dist/src/utils/platform-utils.js.map +1 -1
  72. package/dist/src/utils/project-resolver.d.ts +156 -0
  73. package/dist/src/utils/project-resolver.d.ts.map +1 -0
  74. package/dist/src/utils/project-resolver.js +587 -0
  75. package/dist/src/utils/project-resolver.js.map +1 -0
  76. package/package.json +1 -1
  77. package/plugins/specweave/commands/specweave-increment.md +46 -0
  78. package/plugins/specweave/commands/specweave-jobs.md +153 -8
  79. package/plugins/specweave/hooks/hooks.json +10 -0
  80. package/plugins/specweave/hooks/spec-project-validator.sh +24 -2
  81. package/plugins/specweave/hooks/universal/hook-wrapper.cmd +26 -26
  82. package/plugins/specweave/hooks/universal/session-start.cmd +16 -16
  83. package/plugins/specweave/hooks/universal/session-start.ps1 +16 -16
  84. package/plugins/specweave/hooks/user-prompt-submit.sh +105 -3
  85. package/plugins/specweave/hooks/v2/guards/per-us-project-validator.sh +281 -0
  86. package/plugins/specweave/hooks/v2/handlers/living-specs-handler.sh +29 -0
  87. package/plugins/specweave/scripts/session-watchdog.sh +278 -130
  88. package/plugins/specweave/skills/increment-planner/SKILL.md +48 -18
  89. package/plugins/specweave/skills/increment-planner/templates/spec-multi-project.md +27 -14
  90. package/plugins/specweave/skills/increment-planner/templates/spec-single-project.md +16 -5
  91. package/plugins/specweave/skills/spec-generator/SKILL.md +74 -15
  92. package/plugins/specweave-ado/lib/per-us-sync.js +247 -0
  93. package/plugins/specweave-ado/lib/per-us-sync.ts +410 -0
  94. package/plugins/specweave-github/lib/github-client-v2.js +10 -3
  95. package/plugins/specweave-github/lib/github-client-v2.ts +15 -3
  96. package/plugins/specweave-github/lib/per-us-sync.js +241 -0
  97. package/plugins/specweave-github/lib/per-us-sync.ts +375 -0
  98. package/plugins/specweave-jira/lib/per-us-sync.js +224 -0
  99. package/plugins/specweave-jira/lib/per-us-sync.ts +366 -0
  100. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +0 -738
  101. 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: Run "specweave context projects" to get valid project/board IDs
12
- # For 2-level structures: BOTH project AND board are REQUIRED
13
- project: {{PROJECT_ID}}
14
- board: {{BOARD_ID}}
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: {{PROJECT_FE_ID}}
18
+ - id: {{RESOLVED_PROJECT_FE}}
18
19
  prefix: FE
19
- - id: {{PROJECT_BE_ID}}
20
+ - id: {{RESOLVED_PROJECT_BE}}
20
21
  prefix: BE
21
- - id: {{PROJECT_SHARED_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
- <!-- Each US has its own **Project** and **Board** fields. User can modify per-US anytime. -->
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**: {{PROJECT_ID}}
39
- **Board**: {{BOARD_FE_ID}}
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**: {{PROJECT_ID}}
55
- **Board**: {{BOARD_BE_ID}}
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**: {{PROJECT_ID}}
71
- **Board**: {{BOARD_SHARED_ID}}
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: Run "specweave context projects" to get valid project ID
12
- project: {{PROJECT_ID}}
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
- <!-- Each US can have its own **Project** field. If omitted, uses default_project from frontmatter -->
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**: {{PROJECT_ID}}
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**: {{PROJECT_ID}}
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 ALL AVAILABLE CONTEXT TO RESOLVE PROJECT/BOARD:**
133
+ **🧠 USE CONTEXT API OUTPUT + LIVING DOCS TO RESOLVE PROJECT/BOARD:**
91
134
 
92
- Before generating spec.md, analyze:
93
- 1. **Living docs folders**: `ls .specweave/docs/internal/specs/` actual project IDs
94
- 2. **Recent increment patterns**: `grep "**Project**:" .specweave/increments/*/spec.md`
95
- 3. **Config projectMappings**: Exact project IDs from config
96
- 4. **Feature keywords**: Map to actual projects (not generic terms)
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
- Step 1: Check living docs → folders: frontend-app/, backend-api/, shared/
104
- Step 2: "frontend" keyword → matches "frontend-app" folder
105
- Step 3: Assign **Project**: frontend-app (NOT "frontend"!)
153
+ Mapping:
154
+ - "frontend" keyword → matches "frontend-app" (from context API)
155
+ - "login" spans frontend + backend
106
156
 
107
- If cross-cutting ("OAuth" = both frontend + backend):
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
- **NEVER:**
113
- - ❌ Use generic keywords as project names ("frontend", "backend")
114
- - ❌ Ask user when context provides the answer
115
- - Leave `{{PROJECT_ID}}` placeholders
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
+ };