specweave 0.22.2 → 0.22.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 (52) hide show
  1. package/CLAUDE.md +162 -13
  2. package/dist/src/cli/commands/repair-status-desync.d.ts +69 -0
  3. package/dist/src/cli/commands/repair-status-desync.d.ts.map +1 -0
  4. package/dist/src/cli/commands/repair-status-desync.js +221 -0
  5. package/dist/src/cli/commands/repair-status-desync.js.map +1 -0
  6. package/dist/src/cli/commands/validate-status-sync.d.ts +52 -0
  7. package/dist/src/cli/commands/validate-status-sync.d.ts.map +1 -0
  8. package/dist/src/cli/commands/validate-status-sync.js +176 -0
  9. package/dist/src/cli/commands/validate-status-sync.js.map +1 -0
  10. package/dist/src/cli/update-status-line.d.ts +16 -0
  11. package/dist/src/cli/update-status-line.d.ts.map +1 -0
  12. package/dist/src/cli/update-status-line.js +44 -0
  13. package/dist/src/cli/update-status-line.js.map +1 -0
  14. package/dist/src/core/increment/completion-validator.d.ts +56 -0
  15. package/dist/src/core/increment/completion-validator.d.ts.map +1 -0
  16. package/dist/src/core/increment/completion-validator.js +102 -0
  17. package/dist/src/core/increment/completion-validator.js.map +1 -0
  18. package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
  19. package/dist/src/core/increment/metadata-manager.js +10 -0
  20. package/dist/src/core/increment/metadata-manager.js.map +1 -1
  21. package/dist/src/core/increment/spec-frontmatter-updater.d.ts +78 -0
  22. package/dist/src/core/increment/spec-frontmatter-updater.d.ts.map +1 -0
  23. package/dist/src/core/increment/spec-frontmatter-updater.js +152 -0
  24. package/dist/src/core/increment/spec-frontmatter-updater.js.map +1 -0
  25. package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
  26. package/dist/src/core/living-docs/living-docs-sync.js +2 -1
  27. package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
  28. package/dist/src/core/status-line/status-line-manager.d.ts +3 -2
  29. package/dist/src/core/status-line/status-line-manager.d.ts.map +1 -1
  30. package/dist/src/core/status-line/status-line-manager.js +40 -17
  31. package/dist/src/core/status-line/status-line-manager.js.map +1 -1
  32. package/dist/src/core/status-line/status-line-updater.d.ts +67 -0
  33. package/dist/src/core/status-line/status-line-updater.d.ts.map +1 -0
  34. package/dist/src/core/status-line/status-line-updater.js +203 -0
  35. package/dist/src/core/status-line/status-line-updater.js.map +1 -0
  36. package/dist/src/core/status-line/types.d.ts +19 -5
  37. package/dist/src/core/status-line/types.d.ts.map +1 -1
  38. package/dist/src/core/status-line/types.js +3 -3
  39. package/dist/src/core/status-line/types.js.map +1 -1
  40. package/package.json +1 -1
  41. package/plugins/specweave/commands/specweave-done.md +60 -4
  42. package/plugins/specweave/commands/specweave-reopen.md +29 -2
  43. package/plugins/specweave/commands/specweave-sync-docs.md +71 -4
  44. package/plugins/specweave/commands/specweave-update-status.md +151 -0
  45. package/plugins/specweave/hooks/lib/update-status-line.sh +34 -6
  46. package/plugins/specweave/hooks/user-prompt-submit.sh +21 -0
  47. package/plugins/specweave/hooks/validate-increment-completion.sh +113 -0
  48. package/plugins/specweave-ado/lib/ado-multi-project-sync.js +0 -1
  49. package/plugins/specweave-github/commands/specweave-github-cleanup-duplicates.md +21 -0
  50. package/plugins/specweave-jira/lib/enhanced-jira-sync.js +3 -3
  51. package/plugins/specweave-ado/lib/enhanced-ado-sync.js +0 -170
  52. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +0 -3222
@@ -0,0 +1,151 @@
1
+ ---
2
+ name: specweave:update-status
3
+ description: Force-update status line cache with latest increment status
4
+ ---
5
+
6
+ # Update Status Line
7
+
8
+ **Purpose**: Force-refresh status line cache when it appears stale or after making changes.
9
+
10
+ **Use When**:
11
+ - Status line doesn't reflect recent changes
12
+ - After completing tasks (want to see updated progress immediately)
13
+ - After status transitions (active → completed)
14
+ - Before checking progress with `/specweave:progress`
15
+ - When status line shows outdated information
16
+
17
+ ---
18
+
19
+ ## How It Works
20
+
21
+ 1. **Scan All Increments**
22
+ - Reads all spec.md files in `.specweave/increments/`
23
+ - Identifies open increments (status = active/planning/in-progress)
24
+ - Extracts creation dates from YAML frontmatter
25
+
26
+ 2. **Select Current Increment**
27
+ - Sorts by creation date (oldest first)
28
+ - Takes first as current active increment
29
+ - Counts total open increments
30
+
31
+ 3. **Parse Task Progress**
32
+ - Reads tasks.md from current increment
33
+ - Uses TaskCounter for accurate counting (no overcounting)
34
+ - Calculates: completed, total, percentage
35
+
36
+ 4. **Update Cache**
37
+ - Writes `.specweave/state/status-line.json`
38
+ - Atomic write (temp file → rename)
39
+ - Runs SYNCHRONOUSLY (user waits ~50-100ms)
40
+
41
+ ---
42
+
43
+ ## Usage
44
+
45
+ ```bash
46
+ # Force-update status line (no arguments)
47
+ /specweave:update-status
48
+ ```
49
+
50
+ **Output**:
51
+ ```
52
+ ✓ Status line cache updated
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Example
58
+
59
+ **Before Update** (stale cache):
60
+ ```
61
+ Status Line: [0043] ████░░░░ 5/8 tasks (2 open)
62
+ ```
63
+
64
+ **After Update** (fresh cache):
65
+ ```
66
+ Status Line: [0043] ████████ 8/8 tasks (1 open) ✓
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Integration with Other Commands
72
+
73
+ This command is automatically called by:
74
+
75
+ 1. **`/specweave:progress`**
76
+ - Ensures status line is fresh before showing progress
77
+ - User always sees accurate completion percentages
78
+
79
+ 2. **`/specweave:done`**
80
+ - Refreshes status line before PM validation
81
+ - Ensures final progress is accurate
82
+
83
+ 3. **`/specweave:status`**
84
+ - Updates cache before showing increment list
85
+ - Shows current increment status
86
+
87
+ ---
88
+
89
+ ## Technical Details
90
+
91
+ **Performance**: ~50-100ms (synchronous execution)
92
+
93
+ **Cache Location**: `.specweave/state/status-line.json`
94
+
95
+ **Cache Format**:
96
+ ```json
97
+ {
98
+ "current": {
99
+ "id": "0043-spec-md-desync-fix",
100
+ "name": "0043-spec-md-desync-fix",
101
+ "completed": 8,
102
+ "total": 8,
103
+ "percentage": 100
104
+ },
105
+ "openCount": 1,
106
+ "lastUpdate": "2025-11-18T15:30:00Z"
107
+ }
108
+ ```
109
+
110
+ **Source of Truth**:
111
+ - Increment status: `spec.md` YAML frontmatter (NOT metadata.json)
112
+ - Task progress: `tasks.md` (TaskCounter for accuracy)
113
+
114
+ **Atomicity**: Uses temp file → rename pattern to prevent cache corruption
115
+
116
+ ---
117
+
118
+ ## Troubleshooting
119
+
120
+ **Status line still stale after update?**
121
+ 1. Check if `.specweave/state/status-line.json` was modified
122
+ 2. Verify spec.md has correct status in YAML frontmatter
123
+ 3. Check tasks.md has proper task format (## T-001, etc.)
124
+
125
+ **Error: "Failed to update status line"**
126
+ - Ensure `.specweave/increments/` exists
127
+ - Check file permissions on `.specweave/state/`
128
+ - Verify spec.md has valid YAML frontmatter
129
+
130
+ **Cache shows wrong increment?**
131
+ - Status line shows oldest active increment by default
132
+ - Complete older increments first
133
+ - Or check `created` date in spec.md frontmatter
134
+
135
+ ---
136
+
137
+ ## Implementation
138
+
139
+ **CLI**: `src/cli/update-status-line.ts`
140
+ **Core Logic**: `src/core/status-line/status-line-updater.ts`
141
+ **Hook (async)**: `plugins/specweave/hooks/lib/update-status-line.sh`
142
+
143
+ **Difference from Hook**:
144
+ - **Command**: SYNCHRONOUS (user waits, sees immediate result)
145
+ - **Hook**: ASYNCHRONOUS (background refresh, no blocking)
146
+
147
+ Both use same logic, different execution model.
148
+
149
+ ---
150
+
151
+ **This command ensures status line is ALWAYS fresh when you need it!** ✓
@@ -1,16 +1,17 @@
1
1
  #!/usr/bin/env bash
2
2
  #
3
- # update-status-line.sh (Simplified)
3
+ # update-status-line.sh (Enhanced with AC Metrics)
4
4
  #
5
5
  # Updates status line cache with current increment progress.
6
- # Shows: [increment-name] ████░░░░ X/Y tasks (Z open)
6
+ # Shows: [increment-name] ████░░░░ X/Y tasks | A/B ACs (Z open)
7
7
  #
8
8
  # Logic:
9
9
  # 1. Scan all spec.md for status=active/planning (SOURCE OF TRUTH!)
10
10
  # 2. Take first (oldest) as current increment
11
11
  # 3. Count all active/planning as openCount
12
- # 4. Parse current increment's tasks.md for progress
13
- # 5. Write to cache
12
+ # 4. Parse current increment's tasks.md for task progress
13
+ # 5. Parse current increment's spec.md for AC progress
14
+ # 6. Write to cache
14
15
  #
15
16
  # Performance: 50-100ms (runs async, user doesn't wait)
16
17
 
@@ -116,19 +117,44 @@ if [[ -f "$TASKS_FILE" ]]; then
116
117
  fi
117
118
  fi
118
119
 
120
+ # Step 4b: Parse spec.md for AC (Acceptance Criteria) progress
121
+ SPEC_FILE="$INCREMENTS_DIR/$CURRENT_INCREMENT/spec.md"
122
+ TOTAL_ACS=0
123
+ COMPLETED_ACS=0
124
+ OPEN_ACS=0
125
+
126
+ if [[ -f "$SPEC_FILE" ]]; then
127
+ # Count total ACs: both checked and unchecked
128
+ # Pattern: - [ ] **AC- OR - [x] **AC-
129
+ TOTAL_ACS=$(grep -cE '^- \[(x| )\] \*\*AC-' "$SPEC_FILE" 2>/dev/null || echo 0)
130
+ TOTAL_ACS=$(echo "$TOTAL_ACS" | tr -d '\n\r ' || echo 0)
131
+
132
+ # Count completed ACs (checked)
133
+ # Pattern: - [x] **AC-
134
+ COMPLETED_ACS=$(grep -cE '^- \[x\] \*\*AC-' "$SPEC_FILE" 2>/dev/null || echo 0)
135
+ COMPLETED_ACS=$(echo "$COMPLETED_ACS" | tr -d '\n\r ' || echo 0)
136
+
137
+ # Count open ACs (unchecked)
138
+ # Pattern: - [ ] **AC-
139
+ OPEN_ACS=$(grep -cE '^- \[ \] \*\*AC-' "$SPEC_FILE" 2>/dev/null || echo 0)
140
+ OPEN_ACS=$(echo "$OPEN_ACS" | tr -d '\n\r ' || echo 0)
141
+ fi
142
+
119
143
  # Step 5: Extract increment ID and name
120
144
  # Format: XXXX-name where XXXX is 4-digit prefix (brackets added by manager)
121
145
  INCREMENT_ID=$(echo "$CURRENT_INCREMENT" | grep -oE '^[0-9]{4}')
122
146
  INCREMENT_NAME_ONLY=$(echo "$CURRENT_INCREMENT" | sed 's/^[0-9]\{4\}-//')
123
147
  INCREMENT_NAME="$INCREMENT_ID-$INCREMENT_NAME_ONLY"
124
148
 
125
- # Step 6: Write cache
149
+ # Step 6: Write cache (now includes AC metrics)
126
150
  jq -n \
127
151
  --arg id "$CURRENT_INCREMENT" \
128
152
  --arg name "$INCREMENT_NAME" \
129
153
  --argjson completed "$COMPLETED_TASKS" \
130
154
  --argjson total "$TOTAL_TASKS" \
131
155
  --argjson percentage "$PERCENTAGE" \
156
+ --argjson acsCompleted "$COMPLETED_ACS" \
157
+ --argjson acsTotal "$TOTAL_ACS" \
132
158
  --argjson openCount "$OPEN_COUNT" \
133
159
  '{
134
160
  current: {
@@ -136,7 +162,9 @@ jq -n \
136
162
  name: $name,
137
163
  completed: $completed,
138
164
  total: $total,
139
- percentage: $percentage
165
+ percentage: $percentage,
166
+ acsCompleted: $acsCompleted,
167
+ acsTotal: $acsTotal
140
168
  },
141
169
  openCount: $openCount,
142
170
  lastUpdate: (now | strftime("%Y-%m-%dT%H:%M:%SZ"))
@@ -342,6 +342,27 @@ if echo "$PROMPT" | grep -qiE "(add|create|implement|build|develop)" && ! echo "
342
342
  fi
343
343
  fi
344
344
 
345
+ # ==============================================================================
346
+ # STATUS LINE REFRESH: Ensure cache is fresh before showing context
347
+ # ==============================================================================
348
+ # Performance: ~50-100ms (acceptable for UX)
349
+ # Frequency: Every user prompt (high coverage)
350
+ # Benefit: Catches ALL edge cases (manual edits, resume, direct changes)
351
+ #
352
+ # Why here? This hook runs on EVERY user prompt, ensuring status line
353
+ # is ALWAYS up-to-date before showing context to the user.
354
+ #
355
+ # Prevents desync scenarios:
356
+ # - Manual spec.md edits (status: planning → active)
357
+ # - /specweave:resume (status: paused → active)
358
+ # - Direct metadata changes (without hook triggers)
359
+ # - File system operations bypassing hooks
360
+ #
361
+ # Background execution: Runs async, doesn't block user prompt
362
+
363
+ HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
364
+ bash "$HOOK_DIR/lib/update-status-line.sh" 2>/dev/null || true
365
+
345
366
  # ==============================================================================
346
367
  # OUTPUT: Approve with context or no context
347
368
  # ==============================================================================
@@ -0,0 +1,113 @@
1
+ #!/bin/bash
2
+ #
3
+ # Pre-commit hook: Validate Increment Completion
4
+ #
5
+ # Prevents commits where increment status="completed" but:
6
+ # - Acceptance criteria are still open (- [ ] **AC-...)
7
+ # - Tasks are still pending (**Status**: [ ] pending)
8
+ #
9
+ # This prevents false completion and data integrity violations.
10
+ #
11
+ # Exit code 0: All completed increments are valid (allow commit)
12
+ # Exit code 1: Invalid completion detected (block commit)
13
+
14
+ # ANSI colors
15
+ RED='\033[0;31m'
16
+ YELLOW='\033[1;33m'
17
+ GREEN='\033[0;32m'
18
+ NC='\033[0m' # No Color
19
+
20
+ # Check if in SpecWeave project
21
+ if [ ! -d ".specweave/increments" ]; then
22
+ # Not a SpecWeave project, skip validation
23
+ exit 0
24
+ fi
25
+
26
+ # Track if any validation failures found
27
+ has_failures=false
28
+
29
+ # Check all increments
30
+ for increment_dir in .specweave/increments/*/; do
31
+ # Skip if not a directory
32
+ [ ! -d "$increment_dir" ] && continue
33
+
34
+ increment_id=$(basename "$increment_dir")
35
+ spec_file="$increment_dir/spec.md"
36
+ tasks_file="$increment_dir/tasks.md"
37
+ metadata_file="$increment_dir/metadata.json"
38
+
39
+ # Skip if required files don't exist
40
+ [ ! -f "$spec_file" ] && continue
41
+ [ ! -f "$tasks_file" ] && continue
42
+
43
+ # Get status from spec.md frontmatter
44
+ spec_status=$(grep -m1 "^status:" "$spec_file" 2>/dev/null | cut -d: -f2 | tr -d ' ')
45
+
46
+ # Get status from metadata.json (if exists)
47
+ metadata_status=""
48
+ if [ -f "$metadata_file" ]; then
49
+ metadata_status=$(grep -m1 '"status"' "$metadata_file" 2>/dev/null | cut -d'"' -f4)
50
+ fi
51
+
52
+ # Only validate if status is "completed"
53
+ if [ "$spec_status" = "completed" ] || [ "$metadata_status" = "completed" ]; then
54
+ # Count open acceptance criteria (- [ ] **AC-)
55
+ open_acs=$(grep -c "^- \[ \] \*\*AC-" "$spec_file" 2>/dev/null || echo 0)
56
+
57
+ # Count pending tasks (**Status**: [ ] pending)
58
+ pending_tasks=$(grep -ic "\*\*Status\*\*:\s*\[\s*\]\s*pending" "$tasks_file" 2>/dev/null || echo 0)
59
+
60
+ # Validate
61
+ if [ "$open_acs" -gt 0 ] || [ "$pending_tasks" -gt 0 ]; then
62
+ has_failures=true
63
+
64
+ echo -e "${RED}❌ COMMIT BLOCKED: Invalid completion detected${NC}"
65
+ echo -e "${YELLOW}Increment: $increment_id${NC}"
66
+ echo -e "Status: $spec_status (spec.md) / $metadata_status (metadata.json)"
67
+ echo ""
68
+
69
+ if [ "$open_acs" -gt 0 ]; then
70
+ echo -e "${RED} • $open_acs acceptance criteria still open${NC}"
71
+ fi
72
+
73
+ if [ "$pending_tasks" -gt 0 ]; then
74
+ echo -e "${RED} • $pending_tasks tasks still pending${NC}"
75
+ fi
76
+
77
+ echo ""
78
+ echo -e "${YELLOW}Fix options:${NC}"
79
+ echo " 1. Complete the open work before committing"
80
+ echo " 2. Change status to 'active' or 'paused' in both spec.md and metadata.json"
81
+ echo ""
82
+ echo -e "${YELLOW}To see details:${NC}"
83
+ echo " cat $spec_file | grep '- \[ \] \*\*AC-'"
84
+ echo " cat $tasks_file | grep -i 'Status.*pending'"
85
+ echo ""
86
+ fi
87
+ fi
88
+ done
89
+
90
+ # Exit with failure if any validation failures
91
+ if [ "$has_failures" = true ]; then
92
+ echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
93
+ echo -e "${RED}COMMIT BLOCKED: Cannot commit increments with invalid completion${NC}"
94
+ echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
95
+ echo ""
96
+ echo -e "${YELLOW}Why this matters:${NC}"
97
+ echo " • Prevents false completion (status='completed' with open work)"
98
+ echo " • Ensures data integrity (metadata matches reality)"
99
+ echo " • Stops misleading status line and GitHub sync"
100
+ echo ""
101
+ echo -e "${YELLOW}To fix:${NC}"
102
+ echo " 1. Complete all open ACs and pending tasks, OR"
103
+ echo " 2. Change status to 'active'/'paused' in spec.md and metadata.json"
104
+ echo ""
105
+ echo -e "${YELLOW}To bypass (NOT RECOMMENDED):${NC}"
106
+ echo " git commit --no-verify"
107
+ echo ""
108
+ exit 1
109
+ fi
110
+
111
+ # All validations passed
112
+ echo -e "${GREEN}✅ Completion validation passed${NC}"
113
+ exit 0
@@ -373,7 +373,6 @@ ${userStory.technicalContext}
373
373
  return mapping.task || "Task";
374
374
  case "Subtask":
375
375
  return mapping.task || "Task";
376
- // ADO doesn't have subtasks, use Task
377
376
  default:
378
377
  return "User Story";
379
378
  }
@@ -1,6 +1,27 @@
1
1
  ---
2
2
  name: specweave-github:cleanup-duplicates
3
3
  description: Clean up duplicate GitHub issues for an Epic. Finds issues with duplicate titles and closes all except the first created issue.
4
+ justification: |
5
+ CRITICAL INCIDENT RESPONSE TOOL - DO NOT DELETE!
6
+
7
+ Why This Command Exists:
8
+ - Prevention systems (deduplication, GitHub self-healing) work for single-process execution
9
+ - Multiple parallel Claude Code instances bypass all prevention (file-based cache, no distributed locking)
10
+ - GitHub API race conditions: Time gap between "check exists" and "create issue" allows duplicates
11
+ - Historical duplicates from pre-v0.14.1 users (before prevention was added)
12
+
13
+ Evidence of Need:
14
+ - 2025-11-13: 123 duplicate GitHub issues incident (cleaned to 29 unique)
15
+ - Parallel execution creates race conditions that prevention CANNOT solve
16
+ - Industry standard: Prevention + Detection + Cleanup (defense in depth)
17
+
18
+ When to Delete:
19
+ - ONLY if distributed locking implemented (Redis/file locks)
20
+ - AND parallel execution tested (100+ concurrent syncs with zero duplicates)
21
+ - AND zero duplicates for 6+ months in production
22
+ - AND all users migrated to prevention-enabled versions
23
+
24
+ See: .specweave/increments/0043-spec-md-desync-fix/reports/ULTRATHINK-CLEANUP-DUPLICATES-NECESSITY-2025-11-18.md
4
25
  ---
5
26
 
6
27
  # Clean Up Duplicate GitHub Issues
@@ -1,6 +1,6 @@
1
- import { EnhancedContentBuilder } from "../../../dist/src/core/sync/enhanced-content-builder.js";
2
- import { SpecIncrementMapper } from "../../../dist/src/core/sync/spec-increment-mapper.js";
3
- import { parseSpecContent } from "../../../dist/src/core/spec-content-sync.js";
1
+ import { EnhancedContentBuilder } from "../../../src/core/sync/enhanced-content-builder.js";
2
+ import { SpecIncrementMapper } from "../../../src/core/sync/spec-increment-mapper.js";
3
+ import { parseSpecContent } from "../../../src/core/spec-content-sync.js";
4
4
  import * as path from "path";
5
5
  import * as fs from "fs/promises";
6
6
  async function syncSpecToJiraWithEnhancedContent(options) {
@@ -1,170 +0,0 @@
1
- import { AdoClientV2 } from "./ado-client-v2.js";
2
- import { EnhancedContentBuilder } from "../../../src/core/sync/enhanced-content-builder.js";
3
- import { SpecIncrementMapper } from "../../../src/core/sync/spec-increment-mapper.js";
4
- import { parseSpecContent } from "../../../src/core/spec-content-sync.js";
5
- import path from "path";
6
- import fs from "fs/promises";
7
- async function syncSpecToAdoWithEnhancedContent(options) {
8
- const { specPath, organization, project, dryRun = false, verbose = false } = options;
9
- try {
10
- const baseSpec = await parseSpecContent(specPath);
11
- if (!baseSpec) {
12
- return {
13
- success: false,
14
- action: "error",
15
- error: "Failed to parse spec content"
16
- };
17
- }
18
- if (verbose) {
19
- console.log(`\u{1F4C4} Parsed spec: ${baseSpec.identifier.compact}`);
20
- }
21
- const specId = baseSpec.identifier.full || baseSpec.identifier.compact;
22
- const rootDir = await findSpecWeaveRoot(specPath);
23
- const mapper = new SpecIncrementMapper(rootDir);
24
- const mapping = await mapper.mapSpecToIncrements(specId);
25
- if (verbose) {
26
- console.log(`\u{1F517} Found ${mapping.increments.length} related increments`);
27
- }
28
- const taskMapping = buildTaskMapping(mapping.increments, organization, project);
29
- const architectureDocs = await findArchitectureDocs(rootDir, specId);
30
- const enhancedSpec = {
31
- ...baseSpec,
32
- summary: baseSpec.description,
33
- taskMapping,
34
- architectureDocs
35
- };
36
- const builder = new EnhancedContentBuilder();
37
- const description = builder.buildExternalDescription(enhancedSpec);
38
- if (verbose) {
39
- console.log(`\u{1F4DD} Generated description: ${description.length} characters`);
40
- }
41
- if (dryRun) {
42
- console.log("\u{1F50D} DRY RUN - Would create/update feature with:");
43
- console.log(` Title: ${baseSpec.title}`);
44
- console.log(` Description length: ${description.length}`);
45
- return {
46
- success: true,
47
- action: "no-change",
48
- tasksLinked: taskMapping?.tasks.length || 0
49
- };
50
- }
51
- if (!organization || !project) {
52
- return {
53
- success: false,
54
- action: "error",
55
- error: "Azure DevOps organization/project not specified"
56
- };
57
- }
58
- const profile = {
59
- provider: "ado",
60
- displayName: `${organization}/${project}`,
61
- config: {
62
- organization,
63
- project
64
- },
65
- timeRange: { default: "1M", max: "6M" }
66
- };
67
- const pat = process.env.AZURE_DEVOPS_PAT || "";
68
- const client = new AdoClientV2(profile, pat);
69
- const existingFeature = await findExistingFeature(client, baseSpec.identifier.compact);
70
- let result;
71
- if (existingFeature) {
72
- await client.updateWorkItem(existingFeature.id, {
73
- title: `[${baseSpec.identifier.compact}] ${baseSpec.title}`,
74
- description
75
- });
76
- result = {
77
- success: true,
78
- action: "updated",
79
- featureId: existingFeature.id,
80
- featureUrl: `https://dev.azure.com/${organization}/${project}/_workitems/edit/${existingFeature.id}`,
81
- tasksLinked: taskMapping?.tasks.length || 0
82
- };
83
- } else {
84
- const feature = await client.createEpic({
85
- title: `[${baseSpec.identifier.compact}] ${baseSpec.title}`,
86
- description,
87
- tags: ["spec", "external-tool-sync"]
88
- });
89
- result = {
90
- success: true,
91
- action: "created",
92
- featureId: feature.id,
93
- featureUrl: `https://dev.azure.com/${organization}/${project}/_workitems/edit/${feature.id}`,
94
- tasksLinked: taskMapping?.tasks.length || 0
95
- };
96
- }
97
- if (verbose) {
98
- console.log(`\u2705 ${result.action === "created" ? "Created" : "Updated"} feature #${result.featureId}`);
99
- }
100
- return result;
101
- } catch (error) {
102
- return {
103
- success: false,
104
- action: "error",
105
- error: error.message
106
- };
107
- }
108
- }
109
- async function findSpecWeaveRoot(specPath) {
110
- let currentDir = path.dirname(specPath);
111
- while (true) {
112
- const specweaveDir = path.join(currentDir, ".specweave");
113
- try {
114
- await fs.access(specweaveDir);
115
- return currentDir;
116
- } catch {
117
- const parentDir = path.dirname(currentDir);
118
- if (parentDir === currentDir) {
119
- throw new Error(".specweave directory not found");
120
- }
121
- currentDir = parentDir;
122
- }
123
- }
124
- }
125
- function buildTaskMapping(increments, organization, project) {
126
- if (increments.length === 0) return void 0;
127
- const firstIncrement = increments[0];
128
- const tasks = firstIncrement.tasks.map((task) => ({
129
- id: task.id,
130
- title: task.title,
131
- userStories: task.userStories
132
- }));
133
- return {
134
- incrementId: firstIncrement.id,
135
- tasks,
136
- tasksUrl: `https://dev.azure.com/${organization}/${project}/_git/repo?path=/.specweave/increments/${firstIncrement.id}/tasks.md`
137
- };
138
- }
139
- async function findArchitectureDocs(rootDir, specId) {
140
- const docs = [];
141
- const archDir = path.join(rootDir, ".specweave/docs/internal/architecture");
142
- try {
143
- const adrDir = path.join(archDir, "adr");
144
- try {
145
- const adrs = await fs.readdir(adrDir);
146
- const relatedAdrs = adrs.filter((file) => file.includes(specId.replace("spec-", "")));
147
- for (const adr of relatedAdrs) {
148
- docs.push({
149
- type: "adr",
150
- path: path.join(adrDir, adr),
151
- title: adr.replace(".md", "").replace(/-/g, " ")
152
- });
153
- }
154
- } catch {
155
- }
156
- } catch {
157
- }
158
- return docs;
159
- }
160
- async function findExistingFeature(client, specId) {
161
- try {
162
- const features = await client.queryWorkItems(`[System.Title] Contains '[${specId}]' AND [System.WorkItemType] = 'Feature'`);
163
- return features[0] || null;
164
- } catch {
165
- return null;
166
- }
167
- }
168
- export {
169
- syncSpecToAdoWithEnhancedContent
170
- };