specweave 0.32.10 → 0.33.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/CLAUDE.md +162 -1
  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/add-child-pid.d.ts +11 -0
  19. package/dist/src/cli/add-child-pid.d.ts.map +1 -0
  20. package/dist/src/cli/add-child-pid.js +42 -0
  21. package/dist/src/cli/add-child-pid.js.map +1 -0
  22. package/dist/src/cli/add-child-process.d.ts +15 -0
  23. package/dist/src/cli/add-child-process.d.ts.map +1 -0
  24. package/dist/src/cli/add-child-process.js +40 -0
  25. package/dist/src/cli/add-child-process.js.map +1 -0
  26. package/dist/src/cli/check-watchdog.d.ts +15 -0
  27. package/dist/src/cli/check-watchdog.d.ts.map +1 -0
  28. package/dist/src/cli/check-watchdog.js +47 -0
  29. package/dist/src/cli/check-watchdog.js.map +1 -0
  30. package/dist/src/cli/cleanup-zombies.d.ts +14 -0
  31. package/dist/src/cli/cleanup-zombies.d.ts.map +1 -0
  32. package/dist/src/cli/cleanup-zombies.js +268 -0
  33. package/dist/src/cli/cleanup-zombies.js.map +1 -0
  34. package/dist/src/cli/find-session-by-pid.d.ts +14 -0
  35. package/dist/src/cli/find-session-by-pid.d.ts.map +1 -0
  36. package/dist/src/cli/find-session-by-pid.js +45 -0
  37. package/dist/src/cli/find-session-by-pid.js.map +1 -0
  38. package/dist/src/cli/get-stale-sessions.d.ts +17 -0
  39. package/dist/src/cli/get-stale-sessions.d.ts.map +1 -0
  40. package/dist/src/cli/get-stale-sessions.js +36 -0
  41. package/dist/src/cli/get-stale-sessions.js.map +1 -0
  42. package/dist/src/cli/register-session.d.ts +16 -0
  43. package/dist/src/cli/register-session.d.ts.map +1 -0
  44. package/dist/src/cli/register-session.js +48 -0
  45. package/dist/src/cli/register-session.js.map +1 -0
  46. package/dist/src/cli/remove-session.d.ts +11 -0
  47. package/dist/src/cli/remove-session.d.ts.map +1 -0
  48. package/dist/src/cli/remove-session.js +36 -0
  49. package/dist/src/cli/remove-session.js.map +1 -0
  50. package/dist/src/cli/update-heartbeat.d.ts +11 -0
  51. package/dist/src/cli/update-heartbeat.d.ts.map +1 -0
  52. package/dist/src/cli/update-heartbeat.js +36 -0
  53. package/dist/src/cli/update-heartbeat.js.map +1 -0
  54. package/dist/src/config/types.d.ts +1208 -203
  55. package/dist/src/config/types.d.ts.map +1 -1
  56. package/dist/src/core/background/job-manager.d.ts +16 -0
  57. package/dist/src/core/background/job-manager.d.ts.map +1 -1
  58. package/dist/src/core/background/job-manager.js +110 -15
  59. package/dist/src/core/background/job-manager.js.map +1 -1
  60. package/dist/src/core/config/config-manager.d.ts.map +1 -1
  61. package/dist/src/core/config/config-manager.js +58 -0
  62. package/dist/src/core/config/config-manager.js.map +1 -1
  63. package/dist/src/core/config/types.d.ts +80 -0
  64. package/dist/src/core/config/types.d.ts.map +1 -1
  65. package/dist/src/core/config/types.js.map +1 -1
  66. package/dist/src/core/increment/increment-utils.d.ts +26 -1
  67. package/dist/src/core/increment/increment-utils.d.ts.map +1 -1
  68. package/dist/src/core/increment/increment-utils.js +66 -4
  69. package/dist/src/core/increment/increment-utils.js.map +1 -1
  70. package/dist/src/core/increment/status-change-sync-trigger.d.ts +3 -1
  71. package/dist/src/core/increment/status-change-sync-trigger.d.ts.map +1 -1
  72. package/dist/src/core/increment/status-change-sync-trigger.js +5 -2
  73. package/dist/src/core/increment/status-change-sync-trigger.js.map +1 -1
  74. package/dist/src/core/living-docs/cross-project-sync.d.ts +87 -15
  75. package/dist/src/core/living-docs/cross-project-sync.d.ts.map +1 -1
  76. package/dist/src/core/living-docs/cross-project-sync.js +147 -28
  77. package/dist/src/core/living-docs/cross-project-sync.js.map +1 -1
  78. package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.d.ts.map +1 -1
  79. package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js +48 -12
  80. package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js.map +1 -1
  81. package/dist/src/core/living-docs/intelligent-analyzer/cache-manager.d.ts +70 -0
  82. package/dist/src/core/living-docs/intelligent-analyzer/cache-manager.d.ts.map +1 -0
  83. package/dist/src/core/living-docs/intelligent-analyzer/cache-manager.js +188 -0
  84. package/dist/src/core/living-docs/intelligent-analyzer/cache-manager.js.map +1 -0
  85. package/dist/src/core/living-docs/intelligent-analyzer/dashboard-generator.d.ts +33 -0
  86. package/dist/src/core/living-docs/intelligent-analyzer/dashboard-generator.d.ts.map +1 -0
  87. package/dist/src/core/living-docs/intelligent-analyzer/dashboard-generator.js +290 -0
  88. package/dist/src/core/living-docs/intelligent-analyzer/dashboard-generator.js.map +1 -0
  89. package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.d.ts.map +1 -1
  90. package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.js +114 -11
  91. package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.js.map +1 -1
  92. package/dist/src/core/living-docs/intelligent-analyzer/graph-visualizer.d.ts +23 -0
  93. package/dist/src/core/living-docs/intelligent-analyzer/graph-visualizer.d.ts.map +1 -0
  94. package/dist/src/core/living-docs/intelligent-analyzer/graph-visualizer.js +283 -0
  95. package/dist/src/core/living-docs/intelligent-analyzer/graph-visualizer.js.map +1 -0
  96. package/dist/src/core/living-docs/intelligent-analyzer/mermaid-generator.d.ts +44 -0
  97. package/dist/src/core/living-docs/intelligent-analyzer/mermaid-generator.d.ts.map +1 -0
  98. package/dist/src/core/living-docs/intelligent-analyzer/mermaid-generator.js +61 -0
  99. package/dist/src/core/living-docs/intelligent-analyzer/mermaid-generator.js.map +1 -0
  100. package/dist/src/core/living-docs/intelligent-analyzer/orchestrator.d.ts +126 -0
  101. package/dist/src/core/living-docs/intelligent-analyzer/orchestrator.d.ts.map +1 -0
  102. package/dist/src/core/living-docs/intelligent-analyzer/orchestrator.js +378 -0
  103. package/dist/src/core/living-docs/intelligent-analyzer/orchestrator.js.map +1 -0
  104. package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.d.ts.map +1 -1
  105. package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js +57 -0
  106. package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js.map +1 -1
  107. package/dist/src/core/living-docs/intelligent-analyzer/pattern-analyzer.d.ts +82 -0
  108. package/dist/src/core/living-docs/intelligent-analyzer/pattern-analyzer.d.ts.map +1 -0
  109. package/dist/src/core/living-docs/intelligent-analyzer/pattern-analyzer.js +430 -0
  110. package/dist/src/core/living-docs/intelligent-analyzer/pattern-analyzer.js.map +1 -0
  111. package/dist/src/core/living-docs/intelligent-analyzer/repo-scanner.d.ts +84 -0
  112. package/dist/src/core/living-docs/intelligent-analyzer/repo-scanner.d.ts.map +1 -0
  113. package/dist/src/core/living-docs/intelligent-analyzer/repo-scanner.js +387 -0
  114. package/dist/src/core/living-docs/intelligent-analyzer/repo-scanner.js.map +1 -0
  115. package/dist/src/core/living-docs/intelligent-analyzer/report-writer.d.ts +61 -0
  116. package/dist/src/core/living-docs/intelligent-analyzer/report-writer.d.ts.map +1 -0
  117. package/dist/src/core/living-docs/intelligent-analyzer/report-writer.js +174 -0
  118. package/dist/src/core/living-docs/intelligent-analyzer/report-writer.js.map +1 -0
  119. package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts +1 -1
  120. package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts.map +1 -1
  121. package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
  122. package/dist/src/core/living-docs/living-docs-sync.js +26 -22
  123. package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
  124. package/dist/src/core/living-docs/module-analyzer.d.ts +3 -0
  125. package/dist/src/core/living-docs/module-analyzer.d.ts.map +1 -1
  126. package/dist/src/core/living-docs/module-analyzer.js +40 -1
  127. package/dist/src/core/living-docs/module-analyzer.js.map +1 -1
  128. package/dist/src/core/living-docs/types.d.ts +24 -3
  129. package/dist/src/core/living-docs/types.d.ts.map +1 -1
  130. package/dist/src/core/qa/qa-runner.js +1 -1
  131. package/dist/src/core/qa/qa-runner.js.map +1 -1
  132. package/dist/src/core/scheduler/session-sync-executor.js +1 -1
  133. package/dist/src/core/scheduler/session-sync-executor.js.map +1 -1
  134. package/dist/src/core/status-line/status-line-updater.d.ts +1 -1
  135. package/dist/src/core/status-line/status-line-updater.d.ts.map +1 -1
  136. package/dist/src/core/status-line/status-line-updater.js +4 -3
  137. package/dist/src/core/status-line/status-line-updater.js.map +1 -1
  138. package/dist/src/core/types/config.d.ts +79 -0
  139. package/dist/src/core/types/config.d.ts.map +1 -1
  140. package/dist/src/core/types/config.js.map +1 -1
  141. package/dist/src/importers/jira-importer.d.ts.map +1 -1
  142. package/dist/src/importers/jira-importer.js +18 -9
  143. package/dist/src/importers/jira-importer.js.map +1 -1
  144. package/dist/src/init/architecture/types.d.ts +140 -33
  145. package/dist/src/init/architecture/types.d.ts.map +1 -1
  146. package/dist/src/init/compliance/types.d.ts +27 -30
  147. package/dist/src/init/compliance/types.d.ts.map +1 -1
  148. package/dist/src/init/repo/types.d.ts +34 -11
  149. package/dist/src/init/repo/types.d.ts.map +1 -1
  150. package/dist/src/init/research/src/config/types.d.ts +82 -15
  151. package/dist/src/init/research/src/config/types.d.ts.map +1 -1
  152. package/dist/src/init/research/types.d.ts +93 -38
  153. package/dist/src/init/research/types.d.ts.map +1 -1
  154. package/dist/src/init/team/types.d.ts +42 -4
  155. package/dist/src/init/team/types.d.ts.map +1 -1
  156. package/dist/src/sync/ado-reconciler.js +1 -1
  157. package/dist/src/sync/ado-reconciler.js.map +1 -1
  158. package/dist/src/sync/github-reconciler.js +1 -1
  159. package/dist/src/sync/github-reconciler.js.map +1 -1
  160. package/dist/src/sync/jira-reconciler.js +1 -1
  161. package/dist/src/sync/jira-reconciler.js.map +1 -1
  162. package/dist/src/sync/sync-coordinator.d.ts +20 -0
  163. package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
  164. package/dist/src/sync/sync-coordinator.js +258 -33
  165. package/dist/src/sync/sync-coordinator.js.map +1 -1
  166. package/dist/src/types/session.d.ts +65 -0
  167. package/dist/src/types/session.d.ts.map +1 -0
  168. package/dist/src/types/session.js +8 -0
  169. package/dist/src/types/session.js.map +1 -0
  170. package/dist/src/utils/lock-manager.d.ts +48 -0
  171. package/dist/src/utils/lock-manager.d.ts.map +1 -0
  172. package/dist/src/utils/lock-manager.js +195 -0
  173. package/dist/src/utils/lock-manager.js.map +1 -0
  174. package/dist/src/utils/notification-manager.d.ts +45 -0
  175. package/dist/src/utils/notification-manager.d.ts.map +1 -0
  176. package/dist/src/utils/notification-manager.js +130 -0
  177. package/dist/src/utils/notification-manager.js.map +1 -0
  178. package/dist/src/utils/platform-utils.d.ts +136 -0
  179. package/dist/src/utils/platform-utils.d.ts.map +1 -0
  180. package/dist/src/utils/platform-utils.js +366 -0
  181. package/dist/src/utils/platform-utils.js.map +1 -0
  182. package/dist/src/utils/project-resolver.d.ts +156 -0
  183. package/dist/src/utils/project-resolver.d.ts.map +1 -0
  184. package/dist/src/utils/project-resolver.js +587 -0
  185. package/dist/src/utils/project-resolver.js.map +1 -0
  186. package/dist/src/utils/session-registry.d.ts +142 -0
  187. package/dist/src/utils/session-registry.d.ts.map +1 -0
  188. package/dist/src/utils/session-registry.js +480 -0
  189. package/dist/src/utils/session-registry.js.map +1 -0
  190. package/package.json +5 -2
  191. package/plugins/specweave/commands/specweave-living-docs.md +42 -0
  192. package/plugins/specweave/hooks/hooks.json +20 -0
  193. package/plugins/specweave/hooks/lib/update-active-increment.sh +2 -2
  194. package/plugins/specweave/hooks/lib/update-status-line.sh +1 -1
  195. package/plugins/specweave/hooks/post-increment-status-change.sh +3 -3
  196. package/plugins/specweave/hooks/post-metadata-change.sh +1 -1
  197. package/plugins/specweave/hooks/universal/hook-wrapper.cmd +26 -26
  198. package/plugins/specweave/hooks/universal/session-start.cmd +16 -16
  199. package/plugins/specweave/hooks/universal/session-start.ps1 +16 -16
  200. package/plugins/specweave/hooks/user-prompt-submit.sh +107 -5
  201. package/plugins/specweave/hooks/v2/guards/increment-root-guard.sh +61 -0
  202. package/plugins/specweave/hooks/v2/guards/per-us-project-validator.sh +281 -0
  203. package/plugins/specweave/hooks/v2/handlers/living-specs-handler.sh +29 -0
  204. package/plugins/specweave/hooks/v2/session-end.sh +69 -0
  205. package/plugins/specweave/hooks/v2/session-start.sh +81 -0
  206. package/plugins/specweave/lib/vendor/sync/github-reconciler.js +1 -1
  207. package/plugins/specweave/lib/vendor/sync/github-reconciler.js.map +1 -1
  208. package/plugins/specweave/scripts/heartbeat.sh +110 -0
  209. package/plugins/specweave/scripts/progress.js +34 -4
  210. package/plugins/specweave/scripts/read-jobs.sh +1 -1
  211. package/plugins/specweave/scripts/read-progress.sh +50 -5
  212. package/plugins/specweave/scripts/read-workflow.sh +1 -1
  213. package/plugins/specweave/scripts/session-watchdog.sh +65 -0
  214. package/plugins/specweave/scripts/status.js +28 -11
  215. package/plugins/specweave-ado/lib/per-us-sync.js +247 -0
  216. package/plugins/specweave-ado/lib/per-us-sync.ts +410 -0
  217. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +738 -0
  218. package/plugins/specweave-github/lib/github-client-v2.js +10 -3
  219. package/plugins/specweave-github/lib/github-client-v2.ts +15 -3
  220. package/plugins/specweave-github/lib/per-us-sync.js +241 -0
  221. package/plugins/specweave-github/lib/per-us-sync.ts +375 -0
  222. package/plugins/specweave-jira/lib/per-us-sync.js +224 -0
  223. package/plugins/specweave-jira/lib/per-us-sync.ts +366 -0
  224. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +1107 -0
@@ -118,11 +118,24 @@ if (specificId) {
118
118
 
119
119
  // Show all active increments
120
120
  const increments = incrementFolders.map(parseIncrement);
121
- const active = increments.filter(i => ['active', 'planning', 'in-progress'].includes(i.status));
121
+ const readyForReview = increments.filter(i => i.status === 'ready_for_review');
122
+ const active = increments.filter(i => ['active', 'planning', 'backlog'].includes(i.status));
122
123
  const paused = increments.filter(i => i.status === 'paused');
123
124
 
124
125
  console.log('\n📊 Increment Progress\n');
125
126
 
127
+ // Show ready_for_review FIRST (needs attention!)
128
+ if (readyForReview.length > 0) {
129
+ console.log(`👀 Ready for Review (${readyForReview.length}):`);
130
+ for (const inc of readyForReview) {
131
+ const bar = createProgressBar(inc.percentage, 15);
132
+ console.log(` ${inc.id}`);
133
+ console.log(` ${bar} ${inc.completedTasks}/${inc.totalTasks} (${inc.percentage}%)`);
134
+ console.log(` → /specweave:done ${inc.id}`);
135
+ }
136
+ console.log('');
137
+ }
138
+
126
139
  if (active.length > 0) {
127
140
  console.log(`🔄 Active (${active.length}):`);
128
141
  for (const inc of active) {
@@ -141,16 +154,31 @@ if (paused.length > 0) {
141
154
  console.log('');
142
155
  }
143
156
 
144
- if (active.length === 0 && paused.length === 0) {
157
+ // Summary section
158
+ if (readyForReview.length > 0 || active.length > 0 || paused.length > 0) {
159
+ console.log('─'.repeat(40));
160
+ const parts = [];
161
+ if (readyForReview.length > 0) parts.push(`${readyForReview.length} ready for review`);
162
+ if (active.length > 0) parts.push(`${active.length} active`);
163
+ if (paused.length > 0) parts.push(`${paused.length} paused`);
164
+ console.log(`Summary: ${parts.join(', ')}`);
165
+ console.log('');
166
+
167
+ if (readyForReview.length > 0) {
168
+ console.log('💡 Run /specweave:done <id> to close reviewed increments');
169
+ } else {
170
+ console.log('💡 For details: /specweave:progress <incrementId>');
171
+ }
172
+ } else {
145
173
  console.log('No active increments.');
146
174
  const completed = increments.filter(i => i.status === 'completed');
147
175
  if (completed.length > 0) {
148
176
  console.log(`${completed.length} completed increment(s).`);
149
177
  }
178
+ console.log('');
179
+ console.log('💡 Run /specweave:increment to start new work');
150
180
  }
151
181
 
152
- console.log('💡 For details: /specweave:progress <incrementId>');
153
-
154
182
  // Helpers
155
183
  function createProgressBar(pct, width = 20) {
156
184
  const filled = Math.round((pct / 100) * width);
@@ -162,6 +190,8 @@ function formatStatus(status) {
162
190
  const icons = {
163
191
  'active': '🔄 active',
164
192
  'planning': '📝 planning',
193
+ 'backlog': '📋 backlog',
194
+ 'ready_for_review': '👀 ready for review',
165
195
  'paused': '⏸️ paused',
166
196
  'completed': '✅ completed',
167
197
  'abandoned': '❌ abandoned'
@@ -110,7 +110,7 @@ if [[ -f "$CACHE_FILE" ]] && jq -e '.' "$CACHE_FILE" >/dev/null 2>&1; then
110
110
  # Show active increments
111
111
  ACTIVE=$(jq -r '
112
112
  .increments | to_entries[] |
113
- select(.value.status == "active" or .value.status == "planning" or .value.status == "in-progress") |
113
+ select(.value.status == "active" or .value.status == "planning" or .value.status == "backlog" or .value.status == "ready_for_review") |
114
114
  "\(.key)|\(.value.status)|\(.value.tasks.completed)|\(.value.tasks.total)"
115
115
  ' "$CACHE_FILE" 2>/dev/null)
116
116
 
@@ -126,10 +126,36 @@ echo ""
126
126
  echo "📊 Increment Progress"
127
127
  echo ""
128
128
 
129
- # Get active increments
129
+ # Get ready_for_review increments FIRST (needs attention!)
130
+ READY_FOR_REVIEW=$(jq -r '
131
+ .increments | to_entries[] |
132
+ select(.value.status == "ready_for_review") |
133
+ "\(.key)|\(.value.tasks.completed)|\(.value.tasks.total)"
134
+ ' "$CACHE_FILE" 2>/dev/null)
135
+
136
+ REVIEW_COUNT=0
137
+ if [[ -n "$READY_FOR_REVIEW" ]]; then
138
+ echo "👀 Ready for Review:"
139
+ while IFS='|' read -r id completed total; do
140
+ [[ -z "$id" ]] && continue
141
+ REVIEW_COUNT=$((REVIEW_COUNT + 1))
142
+ if [[ "$total" -gt 0 ]]; then
143
+ pct=$((completed * 100 / total))
144
+ else
145
+ pct=0
146
+ fi
147
+ bar=$(progress_bar "$pct" 15)
148
+ echo " $id"
149
+ echo " $bar $completed/$total ($pct%)"
150
+ echo " → /specweave:done $id"
151
+ done <<< "$READY_FOR_REVIEW"
152
+ echo ""
153
+ fi
154
+
155
+ # Get active increments (excluding ready_for_review)
130
156
  ACTIVE=$(jq -r '
131
157
  .increments | to_entries[] |
132
- select(.value.status == "active" or .value.status == "planning" or .value.status == "in-progress") |
158
+ select(.value.status == "active" or .value.status == "planning" or .value.status == "backlog") |
133
159
  "\(.key)|\(.value.tasks.completed)|\(.value.tasks.total)"
134
160
  ' "$CACHE_FILE" 2>/dev/null)
135
161
 
@@ -173,8 +199,20 @@ if [[ -n "$PAUSED" ]]; then
173
199
  echo ""
174
200
  fi
175
201
 
176
- # If no active/paused
177
- if [[ "$ACTIVE_COUNT" -eq 0 ]] && [[ -z "$PAUSED" ]]; then
202
+ # Summary section
203
+ if [[ "$REVIEW_COUNT" -gt 0 ]] || [[ "$ACTIVE_COUNT" -gt 0 ]] || [[ -n "$PAUSED" ]]; then
204
+ # Has work - show summary
205
+ echo "────────────────────────────────────────"
206
+ SUMMARY_PARTS=()
207
+ [[ "$REVIEW_COUNT" -gt 0 ]] && SUMMARY_PARTS+=("$REVIEW_COUNT ready for review")
208
+ [[ "$ACTIVE_COUNT" -gt 0 ]] && SUMMARY_PARTS+=("$ACTIVE_COUNT active")
209
+ PAUSED_COUNT=0
210
+ [[ -n "$PAUSED" ]] && PAUSED_COUNT=$(echo "$PAUSED" | grep -c '|' || echo 0)
211
+ [[ "$PAUSED_COUNT" -gt 0 ]] && SUMMARY_PARTS+=("$PAUSED_COUNT paused")
212
+
213
+ IFS=', '; echo "Summary: ${SUMMARY_PARTS[*]}"
214
+ else
215
+ # No work in progress
178
216
  echo "No active increments."
179
217
  COMPLETED_COUNT=$(jq '.summary.completed // 0' "$CACHE_FILE")
180
218
  if [[ "$COMPLETED_COUNT" -gt 0 ]]; then
@@ -182,4 +220,11 @@ if [[ "$ACTIVE_COUNT" -eq 0 ]] && [[ -z "$PAUSED" ]]; then
182
220
  fi
183
221
  fi
184
222
 
185
- echo "💡 For details: /specweave:progress <incrementId>"
223
+ echo ""
224
+ if [[ "$REVIEW_COUNT" -gt 0 ]]; then
225
+ echo "💡 Run /specweave:done <id> to close reviewed increments"
226
+ elif [[ "$ACTIVE_COUNT" -eq 0 ]]; then
227
+ echo "💡 Run /specweave:increment to start new work"
228
+ else
229
+ echo "💡 For details: /specweave:progress <incrementId>"
230
+ fi
@@ -64,7 +64,7 @@ else
64
64
  # Find first active increment
65
65
  ACTIVE_ID=$(jq -r '
66
66
  .increments | to_entries[] |
67
- select(.value.status == "active" or .value.status == "in-progress") |
67
+ select(.value.status == "active" or .value.status == "planning" or .value.status == "backlog" or .value.status == "ready_for_review") |
68
68
  .key
69
69
  ' "$CACHE_FILE" 2>/dev/null | head -1)
70
70
  fi
@@ -12,6 +12,9 @@ SPECWEAVE_ROOT="${SPECWEAVE_ROOT:-.specweave}"
12
12
  SIGNAL_FILE="${SPECWEAVE_ROOT}/state/.session-stuck"
13
13
  HEARTBEAT_FILE="${SPECWEAVE_ROOT}/state/.heartbeat"
14
14
  DAEMON_MODE=false
15
+ PROJECT_ROOT="${PWD}"
16
+ SESSION_ID="watchdog-$$-$(date +%s)"
17
+ COORDINATION_THRESHOLD=30 # Heartbeat threshold for active watchdog detection
15
18
 
16
19
  # Colors
17
20
  RED='\033[0;31m'
@@ -176,15 +179,77 @@ check_session_health() {
176
179
  fi
177
180
  }
178
181
 
182
+ # Coordination Functions
183
+ check_active_watchdog() {
184
+ # Check if another watchdog is running via session registry
185
+ node "${PROJECT_ROOT}/dist/src/cli/check-watchdog.js" 2>/dev/null || echo ""
186
+ }
187
+
188
+ register_watchdog() {
189
+ # Register this watchdog in session registry
190
+ node "${PROJECT_ROOT}/dist/src/cli/register-session.js" "$SESSION_ID" $$ "watchdog" 2>&1 | \
191
+ grep -v "^$" | head -3
192
+ }
193
+
194
+ update_watchdog_heartbeat() {
195
+ # Update watchdog heartbeat
196
+ node "${PROJECT_ROOT}/dist/src/cli/update-heartbeat.js" "$SESSION_ID" 2>/dev/null || true
197
+ }
198
+
199
+ cleanup_watchdog() {
200
+ # Remove watchdog from registry on exit
201
+ node "${PROJECT_ROOT}/dist/src/cli/remove-session.js" "$SESSION_ID" 2>&1 | \
202
+ grep -v "^$" | head -3
203
+ log "Watchdog cleanup complete"
204
+ }
205
+
206
+ run_cleanup_service() {
207
+ # Run zombie process cleanup
208
+ node "${PROJECT_ROOT}/dist/src/cli/cleanup-zombies.js" 60 2>&1 | \
209
+ grep -v "^$" | head -10 || true
210
+ }
211
+
179
212
  # Main execution
180
213
  if [[ "$DAEMON_MODE" == "true" ]]; then
214
+ # Coordination check before starting daemon
215
+ active_watchdog=$(check_active_watchdog)
216
+
217
+ if [[ -n "$active_watchdog" ]]; then
218
+ log "${YELLOW}Watchdog already active (PID: $active_watchdog)${NC}"
219
+ log "Exiting to avoid duplicate watchdogs"
220
+ exit 0
221
+ fi
222
+
223
+ # Register as watchdog
224
+ register_watchdog
225
+
226
+ # Trap signals for graceful shutdown
227
+ trap cleanup_watchdog SIGTERM SIGINT EXIT
228
+
181
229
  log "Starting session watchdog daemon (interval: ${CHECK_INTERVAL}s, threshold: ${STUCK_THRESHOLD_SECONDS}s)"
230
+ log "Watchdog session: $SESSION_ID (PID: $$)"
182
231
  log "Press Ctrl+C to stop"
183
232
 
184
233
  while true; do
234
+ # Update own heartbeat
235
+ update_watchdog_heartbeat
236
+
237
+ # Check session health
185
238
  check_session_health || true
239
+
240
+ # Run cleanup service
241
+ run_cleanup_service
242
+
243
+ # Check if parent process still exists (if we have a parent session)
244
+ if ! kill -0 $PPID 2>/dev/null; then
245
+ log "${YELLOW}Parent process died, exiting watchdog${NC}"
246
+ break
247
+ fi
248
+
186
249
  sleep "$CHECK_INTERVAL"
187
250
  done
251
+
252
+ cleanup_watchdog
188
253
  else
189
254
  log "Running single health check..."
190
255
  check_session_health
@@ -86,7 +86,7 @@ for (const folder of incrementFolders) {
86
86
  // Display
87
87
  console.log('\n📋 SpecWeave Status Overview\n');
88
88
 
89
- const statusOrder = ['active', 'planning', 'in-progress', 'ready_for_review', 'paused', 'backlog', 'completed', 'abandoned'];
89
+ const statusOrder = ['ready_for_review', 'active', 'planning', 'in-progress', 'paused', 'backlog', 'completed', 'abandoned'];
90
90
  const statusIcons = {
91
91
  'active': '🔄',
92
92
  'planning': '📝',
@@ -105,12 +105,16 @@ for (const status of statusOrder) {
105
105
  if (items && items.length > 0) {
106
106
  hasOutput = true;
107
107
  const icon = statusIcons[status] || '•';
108
- console.log(`${icon} ${status.replace('_', ' ')} (${items.length}):`);
109
-
108
+ console.log(`${icon} ${status.replace(/_/g, ' ')} (${items.length}):`);
109
+
110
110
  // Show first 5, summarize rest
111
111
  const toShow = items.slice(0, 5);
112
112
  for (const item of toShow) {
113
113
  console.log(` ${item}`);
114
+ // Add action hint for ready_for_review
115
+ if (status === 'ready_for_review') {
116
+ console.log(` → /specweave:done ${item}`);
117
+ }
114
118
  }
115
119
  if (items.length > 5) {
116
120
  console.log(` ... and ${items.length - 5} more`);
@@ -140,15 +144,28 @@ if (!hasOutput) {
140
144
 
141
145
  // Summary line
142
146
  const total = incrementFolders.length;
143
- const activeCount = (byStatus['active']?.length || 0) +
144
- (byStatus['planning']?.length || 0) +
145
- (byStatus['in-progress']?.length || 0);
147
+ const reviewCount = byStatus['ready_for_review']?.length || 0;
148
+ const activeCount = (byStatus['active']?.length || 0) +
149
+ (byStatus['planning']?.length || 0) +
150
+ (byStatus['backlog']?.length || 0);
146
151
  const completedCount = byStatus['completed']?.length || 0;
147
152
 
148
153
  console.log('─'.repeat(40));
149
- console.log(`Total: ${total} increment(s) | Active: ${activeCount} | Completed: ${completedCount} | Archived: ${archivedCount}`);
154
+ const summaryParts = [`Total: ${total}`];
155
+ if (reviewCount > 0) summaryParts.push(`Review: ${reviewCount}`);
156
+ summaryParts.push(`Active: ${activeCount}`);
157
+ summaryParts.push(`Completed: ${completedCount}`);
158
+ if (archivedCount > 0) summaryParts.push(`Archived: ${archivedCount}`);
159
+ console.log(summaryParts.join(' | '));
150
160
  console.log('');
151
- console.log('💡 Commands:');
152
- console.log(' /specweave:progress Show task progress');
153
- console.log(' /specweave:do Execute current tasks');
154
- console.log(' /specweave:done <id> Close increment');
161
+
162
+ // Context-aware command hints
163
+ if (reviewCount > 0) {
164
+ console.log('💡 Next step: /specweave:done <id> to close reviewed increment(s)');
165
+ } else if (activeCount > 0) {
166
+ console.log('💡 Commands:');
167
+ console.log(' /specweave:progress Show task progress');
168
+ console.log(' /specweave:do Execute current tasks');
169
+ } else {
170
+ console.log('💡 Run /specweave:increment to start new work');
171
+ }
@@ -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
+ };