specweave 1.0.2 → 1.0.6

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 (46) hide show
  1. package/CLAUDE.md +42 -0
  2. package/dist/src/cli/commands/init.d.ts.map +1 -1
  3. package/dist/src/cli/commands/init.js +6 -3
  4. package/dist/src/cli/commands/init.js.map +1 -1
  5. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.d.ts.map +1 -1
  6. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js +6 -2
  7. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js.map +1 -1
  8. package/dist/src/cli/helpers/issue-tracker/github.d.ts +11 -2
  9. package/dist/src/cli/helpers/issue-tracker/github.d.ts.map +1 -1
  10. package/dist/src/cli/helpers/issue-tracker/github.js +68 -2
  11. package/dist/src/cli/helpers/issue-tracker/github.js.map +1 -1
  12. package/dist/src/cli/helpers/issue-tracker/index.js +3 -3
  13. package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
  14. package/dist/src/cli/helpers/issue-tracker/types.d.ts +5 -0
  15. package/dist/src/cli/helpers/issue-tracker/types.d.ts.map +1 -1
  16. package/dist/src/cli/helpers/issue-tracker/types.js.map +1 -1
  17. package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
  18. package/dist/src/core/repo-structure/repo-structure-manager.js +5 -2
  19. package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
  20. package/package.json +1 -1
  21. package/plugins/specweave/hooks/docs-changed.sh.backup +79 -0
  22. package/plugins/specweave/hooks/human-input-required.sh.backup +75 -0
  23. package/plugins/specweave/hooks/post-first-increment.sh.backup +61 -0
  24. package/plugins/specweave/hooks/post-increment-change.sh.backup +98 -0
  25. package/plugins/specweave/hooks/post-increment-completion.sh.backup +231 -0
  26. package/plugins/specweave/hooks/post-increment-planning.sh.backup +1048 -0
  27. package/plugins/specweave/hooks/post-increment-status-change.sh.backup +147 -0
  28. package/plugins/specweave/hooks/post-spec-update.sh.backup +158 -0
  29. package/plugins/specweave/hooks/post-user-story-complete.sh.backup +179 -0
  30. package/plugins/specweave/hooks/pre-command-deduplication.sh.backup +83 -0
  31. package/plugins/specweave/hooks/pre-implementation.sh.backup +67 -0
  32. package/plugins/specweave/hooks/pre-task-completion.sh.backup +194 -0
  33. package/plugins/specweave/hooks/pre-tool-use.sh.backup +133 -0
  34. package/plugins/specweave/hooks/user-prompt-submit.sh.backup +386 -0
  35. package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +13 -0
  36. package/plugins/specweave/hooks/v2/guards/task-ac-sync-guard.sh +357 -0
  37. package/plugins/specweave-ado/hooks/post-living-docs-update.sh.backup +353 -0
  38. package/plugins/specweave-ado/hooks/post-task-completion.sh.backup +172 -0
  39. package/plugins/specweave-ado/lib/enhanced-ado-sync.js +170 -0
  40. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +1262 -0
  41. package/plugins/specweave-github/hooks/post-task-completion.sh.backup +258 -0
  42. package/plugins/specweave-github/lib/enhanced-github-sync.js +220 -0
  43. package/plugins/specweave-jira/hooks/post-task-completion.sh.backup +172 -0
  44. package/plugins/specweave-jira/lib/enhanced-jira-sync.js +134 -0
  45. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +1254 -0
  46. package/plugins/specweave-release/hooks/post-task-completion.sh.backup +110 -0
@@ -0,0 +1,258 @@
1
+ #!/bin/bash
2
+
3
+ # SpecWeave GitHub Sync Hook
4
+ # Runs after task completion to sync progress to GitHub Projects
5
+ #
6
+ # ARCHITECTURE (v0.19.0+): IMMUTABLE DESCRIPTIONS + PROGRESS COMMENTS
7
+ # - User Story files (.specweave/docs/internal/specs/) ↔ GitHub Issues
8
+ # - Issue descriptions created once (IMMUTABLE snapshot)
9
+ # - All updates via progress comments (audit trail)
10
+ #
11
+ # This hook is part of the specweave-github plugin and handles:
12
+ # - Finding which spec user stories the current work belongs to
13
+ # - Syncing progress via GitHub comments (NOT editing issue body)
14
+ # - Creating audit trail of all changes over time
15
+ # - Notifying stakeholders via GitHub notifications
16
+ #
17
+ # Dependencies:
18
+ # - Node.js and TypeScript CLI (dist/cli/commands/sync-spec-content.js)
19
+ # - GitHub CLI (gh) must be installed and authenticated
20
+ # - ProgressCommentBuilder (lib/progress-comment-builder.ts)
21
+
22
+ set -e
23
+
24
+ # ============================================================================
25
+ # PROJECT ROOT DETECTION
26
+ # ============================================================================
27
+
28
+ # Find project root by searching upward for .specweave/ directory
29
+ find_project_root() {
30
+ local dir="$1"
31
+ while [ "$dir" != "/" ]; do
32
+ if [ -d "$dir/.specweave" ]; then
33
+ echo "$dir"
34
+ return 0
35
+ fi
36
+ dir="$(dirname "$dir")"
37
+ done
38
+ # Fallback: try current directory
39
+ if [ -d "$(pwd)/.specweave" ]; then
40
+ pwd
41
+ else
42
+ echo "$(pwd)"
43
+ fi
44
+ }
45
+
46
+ PROJECT_ROOT="$(find_project_root "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")"
47
+ cd "$PROJECT_ROOT" 2>/dev/null || true
48
+
49
+ # ============================================================================
50
+ # CONFIGURATION
51
+ # ============================================================================
52
+
53
+ LOGS_DIR=".specweave/logs"
54
+ DEBUG_LOG="$LOGS_DIR/hooks-debug.log"
55
+
56
+ mkdir -p "$LOGS_DIR" 2>/dev/null || true
57
+
58
+ # ============================================================================
59
+ # PRECONDITIONS CHECK
60
+ # ============================================================================
61
+
62
+ echo "[$(date)] [GitHub] 🔗 GitHub sync hook fired" >> "$DEBUG_LOG" 2>/dev/null || true
63
+
64
+ # Check if Node.js is available
65
+ if ! command -v node &> /dev/null; then
66
+ echo "[$(date)] [GitHub] âš ī¸ Node.js not found, skipping GitHub sync" >> "$DEBUG_LOG" 2>/dev/null || true
67
+ cat <<EOF
68
+ {
69
+ "continue": true
70
+ }
71
+ EOF
72
+ exit 0
73
+ fi
74
+
75
+ # Check if github-spec-content-sync CLI exists
76
+ SYNC_CLI="$PROJECT_ROOT/dist/src/cli/commands/sync-spec-content.js"
77
+ if [ ! -f "$SYNC_CLI" ]; then
78
+ echo "[$(date)] [GitHub] âš ī¸ sync-spec-content CLI not found at $SYNC_CLI, skipping sync" >> "$DEBUG_LOG" 2>/dev/null || true
79
+ cat <<EOF
80
+ {
81
+ "continue": true
82
+ }
83
+ EOF
84
+ exit 0
85
+ fi
86
+
87
+ # Check for gh CLI
88
+ if ! command -v gh &> /dev/null; then
89
+ echo "[$(date)] [GitHub] âš ī¸ GitHub CLI (gh) not found, skipping sync" >> "$DEBUG_LOG" 2>/dev/null || true
90
+ cat <<EOF
91
+ {
92
+ "continue": true
93
+ }
94
+ EOF
95
+ exit 0
96
+ fi
97
+
98
+ # ============================================================================
99
+ # DETECT ALL SPECS (Multi-Spec Support)
100
+ # ============================================================================
101
+
102
+ # Strategy: Use multi-spec detector to find ALL specs referenced in current increment
103
+
104
+ # 1. Detect current increment (temporary context)
105
+ CURRENT_INCREMENT=$(ls -td .specweave/increments/*/ 2>/dev/null | xargs -n1 basename | grep -v "_backlog" | grep -v "_archive" | grep -v "_working" | head -1)
106
+
107
+ if [ -z "$CURRENT_INCREMENT" ]; then
108
+ echo "[$(date)] [GitHub] â„šī¸ No active increment, checking for spec changes..." >> "$DEBUG_LOG" 2>/dev/null || true
109
+ # Fall through to sync all changed specs
110
+ fi
111
+
112
+ # 2. Use TypeScript CLI to detect all specs
113
+ DETECT_CLI="$PROJECT_ROOT/dist/src/cli/commands/detect-specs.js"
114
+
115
+ if [ -f "$DETECT_CLI" ]; then
116
+ echo "[$(date)] [GitHub] 🔍 Detecting all specs in increment $CURRENT_INCREMENT..." >> "$DEBUG_LOG" 2>/dev/null || true
117
+
118
+ # Call detect-specs CLI and capture JSON output
119
+ DETECTION_RESULT=$(node "$DETECT_CLI" 2>> "$DEBUG_LOG" || echo "{}")
120
+
121
+ # Extract spec count
122
+ SPEC_COUNT=$(echo "$DETECTION_RESULT" | node -e "const fs=require('fs'); const data=JSON.parse(fs.readFileSync(0,'utf-8')); console.log(data.specs?.length || 0)")
123
+
124
+ echo "[$(date)] [GitHub] 📋 Detected $SPEC_COUNT spec(s)" >> "$DEBUG_LOG" 2>/dev/null || true
125
+
126
+ # Store detection result for later use
127
+ echo "$DETECTION_RESULT" > /tmp/specweave-detected-specs.json
128
+ else
129
+ echo "[$(date)] [GitHub] âš ī¸ detect-specs CLI not found at $DETECT_CLI, falling back to git diff" >> "$DEBUG_LOG" 2>/dev/null || true
130
+ SPEC_COUNT=0
131
+ fi
132
+
133
+ # ============================================================================
134
+ # SYNC ALL DETECTED SPECS TO GITHUB (Multi-Spec Support)
135
+ # ============================================================================
136
+
137
+ if [ -f /tmp/specweave-detected-specs.json ] && [ "$SPEC_COUNT" -gt 0 ]; then
138
+ # Multi-spec sync: Loop through all detected specs
139
+ echo "[$(date)] [GitHub] 🔄 Syncing $SPEC_COUNT spec(s) to GitHub..." >> "$DEBUG_LOG" 2>/dev/null || true
140
+
141
+ # Extract spec paths using Node.js
142
+ SPEC_PATHS=$(node -e "
143
+ const fs = require('fs');
144
+ const data = JSON.parse(fs.readFileSync('/tmp/specweave-detected-specs.json', 'utf-8'));
145
+ const syncable = data.specs.filter(s => s.syncEnabled && s.project !== '_parent');
146
+ syncable.forEach(s => console.log(s.path));
147
+ " 2>> "$DEBUG_LOG")
148
+
149
+ # Count syncable specs
150
+ SYNCABLE_COUNT=$(echo "$SPEC_PATHS" | grep -v '^$' | wc -l | tr -d ' ')
151
+
152
+ if [ "$SYNCABLE_COUNT" -gt 0 ]; then
153
+ echo "[$(date)] [GitHub] 📋 Syncing $SYNCABLE_COUNT syncable spec(s) (excluding _parent)" >> "$DEBUG_LOG" 2>/dev/null || true
154
+
155
+ # Sync each spec
156
+ echo "$SPEC_PATHS" | while read -r SPEC_FILE; do
157
+ if [ -n "$SPEC_FILE" ] && [ -f "$SPEC_FILE" ]; then
158
+ # Extract project and spec ID from path
159
+ SPEC_NAME=$(basename "$SPEC_FILE" .md)
160
+ PROJECT=$(basename "$(dirname "$SPEC_FILE")")
161
+
162
+ echo "[$(date)] [GitHub] 🔄 Syncing $PROJECT/$SPEC_NAME..." >> "$DEBUG_LOG" 2>/dev/null || true
163
+
164
+ (cd "$PROJECT_ROOT" && node "$SYNC_CLI" --spec "$SPEC_FILE" --provider github) 2>&1 | tee -a "$DEBUG_LOG" >/dev/null || {
165
+ echo "[$(date)] [GitHub] âš ī¸ Spec sync failed for $PROJECT/$SPEC_NAME (non-blocking)" >> "$DEBUG_LOG" 2>/dev/null || true
166
+ }
167
+
168
+ echo "[$(date)] [GitHub] ✅ Synced $PROJECT/$SPEC_NAME" >> "$DEBUG_LOG" 2>/dev/null || true
169
+ fi
170
+ done
171
+
172
+ echo "[$(date)] [GitHub] ✅ Multi-spec sync complete ($SYNCABLE_COUNT synced)" >> "$DEBUG_LOG" 2>/dev/null || true
173
+ else
174
+ echo "[$(date)] [GitHub] â„šī¸ No syncable specs (all specs are _parent or syncEnabled=false)" >> "$DEBUG_LOG" 2>/dev/null || true
175
+ fi
176
+
177
+ # Cleanup temp file
178
+ rm -f /tmp/specweave-detected-specs.json 2>/dev/null || true
179
+ else
180
+ # Fallback: Sync all modified specs (check git diff)
181
+ echo "[$(date)] [GitHub] 🔄 Checking for modified specs..." >> "$DEBUG_LOG" 2>/dev/null || true
182
+
183
+ MODIFIED_SPECS=$(git diff --name-only HEAD .specweave/docs/internal/specs/**/*.md 2>/dev/null || echo "")
184
+
185
+ if [ -n "$MODIFIED_SPECS" ]; then
186
+ echo "[$(date)] [GitHub] 📝 Found modified specs:" >> "$DEBUG_LOG" 2>/dev/null || true
187
+ echo "$MODIFIED_SPECS" >> "$DEBUG_LOG" 2>/dev/null || true
188
+
189
+ # Sync each modified spec
190
+ echo "$MODIFIED_SPECS" | while read -r SPEC_FILE; do
191
+ if [ -n "$SPEC_FILE" ] && [ -f "$SPEC_FILE" ]; then
192
+ echo "[$(date)] [GitHub] 🔄 Syncing $SPEC_FILE..." >> "$DEBUG_LOG" 2>/dev/null || true
193
+ (cd "$PROJECT_ROOT" && node "$SYNC_CLI" --spec "$SPEC_FILE" --provider github) 2>&1 | tee -a "$DEBUG_LOG" >/dev/null || true
194
+ fi
195
+ done
196
+
197
+ echo "[$(date)] [GitHub] ✅ Batch spec sync complete" >> "$DEBUG_LOG" 2>/dev/null || true
198
+ else
199
+ echo "[$(date)] [GitHub] â„šī¸ No modified specs found, skipping sync" >> "$DEBUG_LOG" 2>/dev/null || true
200
+ fi
201
+ fi
202
+
203
+ # ============================================================================
204
+ # EPIC GITHUB ISSUE SYNC (DEPRECATED v0.24.0+)
205
+ # ============================================================================
206
+ #
207
+ # âš ī¸ DEPRECATED: SpecWeave now syncs ONLY at User Story level.
208
+ #
209
+ # Feature/Epic-level issues are no longer updated.
210
+ # Use /specweave-github:sync instead to sync User Story issues.
211
+ #
212
+ # To re-enable (NOT recommended):
213
+ # export SPECWEAVE_ENABLE_EPIC_SYNC=true
214
+ #
215
+ # @see .specweave/increments/0047-us-task-linkage/reports/GITHUB-TITLE-FORMAT-FIX-PLAN.md
216
+ # ============================================================================
217
+
218
+ if [ "$SPECWEAVE_ENABLE_EPIC_SYNC" = "true" ]; then
219
+ echo "[$(date)] [GitHub] 🔄 Checking for Epic GitHub issue update (DEPRECATED)..." >> "$DEBUG_LOG" 2>/dev/null || true
220
+
221
+ # Find active increment ID
222
+ ACTIVE_INCREMENT=$(ls -t .specweave/increments/ | grep -v '^\.' | while read inc; do
223
+ if [ -f ".specweave/increments/$inc/metadata.json" ]; then
224
+ STATUS=$(grep -o '"status"[[:space:]]*:[[:space:]]*"[^"]*"' ".specweave/increments/$inc/metadata.json" 2>/dev/null | sed 's/.*"\([^"]*\)".*/\1/' || true)
225
+ if [ "$STATUS" = "active" ]; then
226
+ echo "$inc"
227
+ break
228
+ fi
229
+ fi
230
+ done | head -1)
231
+
232
+ if [ -n "$ACTIVE_INCREMENT" ]; then
233
+ echo "[$(date)] [GitHub] đŸŽ¯ Active increment: $ACTIVE_INCREMENT" >> "$DEBUG_LOG" 2>/dev/null || true
234
+
235
+ # Run Epic sync script (silently, errors logged to debug log)
236
+ if [ -f "$PROJECT_ROOT/scripts/update-epic-github-issue.sh" ]; then
237
+ echo "[$(date)] [GitHub] 🚀 Updating Epic GitHub issue (DEPRECATED)..." >> "$DEBUG_LOG" 2>/dev/null || true
238
+ "$PROJECT_ROOT/scripts/update-epic-github-issue.sh" "$ACTIVE_INCREMENT" >> "$DEBUG_LOG" 2>&1 || true
239
+ echo "[$(date)] [GitHub] âš ī¸ Epic sync is deprecated. Use /specweave-github:sync instead." >> "$DEBUG_LOG" 2>/dev/null || true
240
+ else
241
+ echo "[$(date)] [GitHub] âš ī¸ Epic sync script not found, skipping" >> "$DEBUG_LOG" 2>/dev/null || true
242
+ fi
243
+ else
244
+ echo "[$(date)] [GitHub] â„šī¸ No active increment found, skipping Epic sync" >> "$DEBUG_LOG" 2>/dev/null || true
245
+ fi
246
+ else
247
+ echo "[$(date)] [GitHub] â„šī¸ Epic sync disabled (sync at User Story level only)" >> "$DEBUG_LOG" 2>/dev/null || true
248
+ fi
249
+
250
+ # ============================================================================
251
+ # OUTPUT TO CLAUDE
252
+ # ============================================================================
253
+
254
+ cat <<EOF
255
+ {
256
+ "continue": true
257
+ }
258
+ EOF
@@ -0,0 +1,220 @@
1
+ import { GitHubClientV2 } from "./github-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 { LabelDetector } from "../../../src/core/sync/label-detector.js";
6
+ import path from "path";
7
+ import fs from "fs/promises";
8
+ async function syncSpecWithEnhancedContent(options) {
9
+ const { specPath, owner, repo, dryRun = false, verbose = false } = options;
10
+ try {
11
+ const baseSpec = await parseSpecContent(specPath);
12
+ if (!baseSpec) {
13
+ return {
14
+ success: false,
15
+ action: "error",
16
+ error: "Failed to parse spec content"
17
+ };
18
+ }
19
+ if (verbose) {
20
+ console.log(`\u{1F4C4} Parsed spec: ${baseSpec.identifier.compact}`);
21
+ }
22
+ const specId = baseSpec.identifier.full || baseSpec.identifier.compact;
23
+ const rootDir = await findSpecWeaveRoot(specPath);
24
+ const mapper = new SpecIncrementMapper(rootDir);
25
+ const mapping = await mapper.mapSpecToIncrements(specId);
26
+ if (verbose) {
27
+ console.log(`\u{1F517} Found ${mapping.increments.length} related increments`);
28
+ console.log(`\u{1F4CB} Mapped ${Object.keys(mapping.userStoryMappings).length} user stories to tasks`);
29
+ }
30
+ const taskMapping = buildTaskMapping(mapping.increments, owner, repo);
31
+ const architectureDocs = await findArchitectureDocs(rootDir, specId);
32
+ const sourceLinks = buildSourceLinks(mapping.increments[0]?.id, owner, repo);
33
+ const enhancedSpec = {
34
+ ...baseSpec,
35
+ summary: baseSpec.description,
36
+ taskMapping,
37
+ architectureDocs,
38
+ sourceLinks
39
+ };
40
+ const builder = new EnhancedContentBuilder();
41
+ const originalBuildExternal = builder.buildExternalDescription.bind(builder);
42
+ const description = (() => {
43
+ const sections = [];
44
+ sections.push(builder.buildSummarySection(enhancedSpec));
45
+ if (enhancedSpec.userStories && enhancedSpec.userStories.length > 0) {
46
+ sections.push(builder.buildUserStoriesSection(enhancedSpec.userStories));
47
+ }
48
+ if (enhancedSpec.taskMapping) {
49
+ sections.push(builder.buildTasksSection(enhancedSpec.taskMapping, {
50
+ showCheckboxes: true,
51
+ showProgressBar: true,
52
+ showCompletionStatus: true,
53
+ provider: "github"
54
+ }));
55
+ }
56
+ if (enhancedSpec.architectureDocs && enhancedSpec.architectureDocs.length > 0) {
57
+ sections.push(builder.buildArchitectureSection(enhancedSpec.architectureDocs));
58
+ }
59
+ if (enhancedSpec.sourceLinks) {
60
+ sections.push(builder.buildSourceLinksSection(enhancedSpec.sourceLinks));
61
+ }
62
+ return sections.filter((s) => s.length > 0).join("\n\n---\n\n");
63
+ })();
64
+ if (verbose) {
65
+ console.log(`\u{1F4DD} Generated description: ${description.length} characters`);
66
+ }
67
+ if (dryRun) {
68
+ console.log("\u{1F50D} DRY RUN - Would create/update issue with:");
69
+ console.log(` Title: ${baseSpec.title}`);
70
+ console.log(` Description length: ${description.length}`);
71
+ console.log(` Tasks linked: ${taskMapping?.tasks.length || 0}`);
72
+ return {
73
+ success: true,
74
+ action: "no-change",
75
+ tasksLinked: taskMapping?.tasks.length || 0
76
+ };
77
+ }
78
+ if (!owner || !repo) {
79
+ return {
80
+ success: false,
81
+ action: "error",
82
+ error: "GitHub owner/repo not specified"
83
+ };
84
+ }
85
+ const client = GitHubClientV2.fromRepo(owner, repo);
86
+ const labelDetector = new LabelDetector(void 0, false);
87
+ const detection = labelDetector.detectType(
88
+ await fs.readFile(specPath, "utf-8"),
89
+ mapping.increments[0]?.id
90
+ );
91
+ const githubLabels = labelDetector.getGitHubLabels(detection.type);
92
+ const allLabels = ["spec", ...githubLabels];
93
+ if (verbose) {
94
+ console.log(`\u{1F3F7}\uFE0F Detected type: ${detection.type} (${detection.confidence}% confidence)`);
95
+ console.log(` Labels: ${allLabels.join(", ")}`);
96
+ }
97
+ const existingIssue = await findExistingIssue(client, baseSpec.identifier.compact);
98
+ let result;
99
+ if (existingIssue) {
100
+ await client.updateIssueBody(existingIssue.number, description);
101
+ await client.addLabels(existingIssue.number, allLabels);
102
+ result = {
103
+ success: true,
104
+ action: "updated",
105
+ issueNumber: existingIssue.number,
106
+ issueUrl: existingIssue.html_url,
107
+ tasksLinked: taskMapping?.tasks.length || 0
108
+ };
109
+ } else {
110
+ const issue = await client.createEpicIssue(
111
+ `[${baseSpec.project === "_features" ? baseSpec.identifier.display : baseSpec.identifier.compact}] ${baseSpec.title}`,
112
+ description,
113
+ void 0,
114
+ allLabels
115
+ // Apply labels at creation
116
+ );
117
+ result = {
118
+ success: true,
119
+ action: "created",
120
+ issueNumber: issue.number,
121
+ issueUrl: issue.html_url,
122
+ tasksLinked: taskMapping?.tasks.length || 0
123
+ };
124
+ await mapper.updateSpecWithIncrementLinks(specId, mapping.increments[0]?.id);
125
+ }
126
+ if (verbose) {
127
+ console.log(`\u2705 ${result.action === "created" ? "Created" : "Updated"} issue #${result.issueNumber}`);
128
+ console.log(` URL: ${result.issueUrl}`);
129
+ console.log(` Tasks linked: ${result.tasksLinked}`);
130
+ }
131
+ return result;
132
+ } catch (error) {
133
+ return {
134
+ success: false,
135
+ action: "error",
136
+ error: error.message
137
+ };
138
+ }
139
+ }
140
+ async function findSpecWeaveRoot(specPath) {
141
+ let currentDir = path.dirname(specPath);
142
+ while (true) {
143
+ const specweaveDir = path.join(currentDir, ".specweave");
144
+ try {
145
+ await fs.access(specweaveDir);
146
+ return currentDir;
147
+ } catch {
148
+ const parentDir = path.dirname(currentDir);
149
+ if (parentDir === currentDir) {
150
+ throw new Error(".specweave directory not found");
151
+ }
152
+ currentDir = parentDir;
153
+ }
154
+ }
155
+ }
156
+ function buildTaskMapping(increments, owner, repo) {
157
+ if (increments.length === 0) return void 0;
158
+ const firstIncrement = increments[0];
159
+ const tasks = firstIncrement.tasks.map((task) => ({
160
+ id: task.id,
161
+ title: task.title,
162
+ userStories: task.userStories,
163
+ githubIssue: task.githubIssue
164
+ }));
165
+ return {
166
+ incrementId: firstIncrement.id,
167
+ tasks,
168
+ tasksUrl: `https://github.com/${owner}/${repo}/blob/develop/.specweave/increments/${firstIncrement.id}/tasks.md`
169
+ };
170
+ }
171
+ async function findArchitectureDocs(rootDir, specId) {
172
+ const docs = [];
173
+ const archDir = path.join(rootDir, ".specweave/docs/internal/architecture");
174
+ try {
175
+ const adrDir = path.join(archDir, "adr");
176
+ try {
177
+ const adrs = await fs.readdir(adrDir);
178
+ const relatedAdrs = adrs.filter((file) => file.includes(specId.replace("spec-", "")));
179
+ for (const adr of relatedAdrs) {
180
+ docs.push({
181
+ type: "adr",
182
+ path: path.join(adrDir, adr),
183
+ title: adr.replace(".md", "").replace(/-/g, " ")
184
+ });
185
+ }
186
+ } catch {
187
+ }
188
+ const hlds = await fs.readdir(archDir);
189
+ const relatedHlds = hlds.filter((file) => file.includes("hld") && file.includes(specId.replace("spec-", "")));
190
+ for (const hld of relatedHlds) {
191
+ docs.push({
192
+ type: "hld",
193
+ path: path.join(archDir, hld),
194
+ title: hld.replace(".md", "").replace(/-/g, " ")
195
+ });
196
+ }
197
+ } catch {
198
+ }
199
+ return docs;
200
+ }
201
+ function buildSourceLinks(incrementId, owner, repo) {
202
+ if (!incrementId) return void 0;
203
+ const baseUrl = `https://github.com/${owner}/${repo}/blob/develop/.specweave`;
204
+ return {
205
+ spec: `${baseUrl}/docs/internal/specs/default/spec-${incrementId.replace(/^\d+-/, "")}.md`,
206
+ plan: `${baseUrl}/increments/${incrementId}/plan.md`,
207
+ tasks: `${baseUrl}/increments/${incrementId}/tasks.md`
208
+ };
209
+ }
210
+ async function findExistingIssue(client, specId) {
211
+ try {
212
+ const issues = await client.listIssuesInTimeRange("ALL");
213
+ return issues.find((issue) => issue.title.includes(`[${specId}]`) && issue.labels?.some((l) => l.name === "spec")) || null;
214
+ } catch {
215
+ return null;
216
+ }
217
+ }
218
+ export {
219
+ syncSpecWithEnhancedContent
220
+ };
@@ -0,0 +1,172 @@
1
+ #!/bin/bash
2
+
3
+ # SpecWeave JIRA Sync Hook
4
+ # Runs after task completion to sync progress to JIRA Issues
5
+ #
6
+ # This hook is part of the specweave-jira plugin and handles:
7
+ # - Syncing task completion state to JIRA issues
8
+ # - Updating JIRA issue status based on increment progress
9
+ #
10
+ # Dependencies:
11
+ # - Node.js for running sync scripts
12
+ # - jq for JSON parsing
13
+ # - metadata.json must have .jira.issue field
14
+ # - JIRA API credentials in .env
15
+
16
+ set -e
17
+
18
+ # ============================================================================
19
+ # PROJECT ROOT DETECTION
20
+ # ============================================================================
21
+
22
+ # Find project root by searching upward for .specweave/ directory
23
+ find_project_root() {
24
+ local dir="$1"
25
+ while [ "$dir" != "/" ]; do
26
+ if [ -d "$dir/.specweave" ]; then
27
+ echo "$dir"
28
+ return 0
29
+ fi
30
+ dir="$(dirname "$dir")"
31
+ done
32
+ # Fallback: try current directory
33
+ if [ -d "$(pwd)/.specweave" ]; then
34
+ pwd
35
+ else
36
+ echo "$(pwd)"
37
+ fi
38
+ }
39
+
40
+ PROJECT_ROOT="$(find_project_root "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")"
41
+ cd "$PROJECT_ROOT" 2>/dev/null || true
42
+
43
+ # ============================================================================
44
+ # CONFIGURATION
45
+ # ============================================================================
46
+
47
+ LOGS_DIR=".specweave/logs"
48
+ DEBUG_LOG="$LOGS_DIR/hooks-debug.log"
49
+
50
+ mkdir -p "$LOGS_DIR" 2>/dev/null || true
51
+
52
+ # ============================================================================
53
+ # PRECONDITIONS CHECK
54
+ # ============================================================================
55
+
56
+ echo "[$(date)] [JIRA] 🔗 JIRA sync hook fired" >> "$DEBUG_LOG" 2>/dev/null || true
57
+
58
+ # Detect current increment
59
+ CURRENT_INCREMENT=$(ls -td .specweave/increments/*/ 2>/dev/null | xargs -n1 basename | grep -v "_backlog" | grep -v "_archive" | grep -v "_working" | head -1)
60
+
61
+ if [ -z "$CURRENT_INCREMENT" ]; then
62
+ echo "[$(date)] [JIRA] â„šī¸ No active increment, skipping JIRA sync" >> "$DEBUG_LOG" 2>/dev/null || true
63
+ cat <<EOF
64
+ {
65
+ "continue": true
66
+ }
67
+ EOF
68
+ exit 0
69
+ fi
70
+
71
+ # Check for metadata.json
72
+ METADATA_FILE=".specweave/increments/$CURRENT_INCREMENT/metadata.json"
73
+
74
+ if [ ! -f "$METADATA_FILE" ]; then
75
+ echo "[$(date)] [JIRA] â„šī¸ No metadata.json for $CURRENT_INCREMENT, skipping JIRA sync" >> "$DEBUG_LOG" 2>/dev/null || true
76
+ cat <<EOF
77
+ {
78
+ "continue": true
79
+ }
80
+ EOF
81
+ exit 0
82
+ fi
83
+
84
+ # Check for JIRA issue link
85
+ if ! command -v jq &> /dev/null; then
86
+ echo "[$(date)] [JIRA] âš ī¸ jq not found, skipping JIRA sync" >> "$DEBUG_LOG" 2>/dev/null || true
87
+ cat <<EOF
88
+ {
89
+ "continue": true
90
+ }
91
+ EOF
92
+ exit 0
93
+ fi
94
+
95
+ JIRA_ISSUE=$(jq -r '.jira.issue // empty' "$METADATA_FILE" 2>/dev/null)
96
+
97
+ if [ -z "$JIRA_ISSUE" ]; then
98
+ echo "[$(date)] [JIRA] â„šī¸ No JIRA issue linked to $CURRENT_INCREMENT, skipping sync" >> "$DEBUG_LOG" 2>/dev/null || true
99
+ cat <<EOF
100
+ {
101
+ "continue": true
102
+ }
103
+ EOF
104
+ exit 0
105
+ fi
106
+
107
+ # Check for Node.js
108
+ if ! command -v node &> /dev/null; then
109
+ echo "[$(date)] [JIRA] âš ī¸ Node.js not found, skipping JIRA sync" >> "$DEBUG_LOG" 2>/dev/null || true
110
+ cat <<EOF
111
+ {
112
+ "continue": true
113
+ }
114
+ EOF
115
+ exit 0
116
+ fi
117
+
118
+ # Check for JIRA sync script
119
+ if [ ! -f "dist/commands/jira-sync.js" ]; then
120
+ echo "[$(date)] [JIRA] âš ī¸ jira-sync.js not found, skipping JIRA sync" >> "$DEBUG_LOG" 2>/dev/null || true
121
+ cat <<EOF
122
+ {
123
+ "continue": true
124
+ }
125
+ EOF
126
+ exit 0
127
+ fi
128
+
129
+ # ============================================================================
130
+ # JIRA SYNC LOGIC
131
+ # ============================================================================
132
+
133
+ echo "[$(date)] [JIRA] 🔄 Syncing to JIRA issue $JIRA_ISSUE" >> "$DEBUG_LOG" 2>/dev/null || true
134
+
135
+ # Run JIRA sync command (non-blocking)
136
+ node dist/commands/jira-sync.js "$CURRENT_INCREMENT" 2>&1 | tee -a "$DEBUG_LOG" >/dev/null || {
137
+ echo "[$(date)] [JIRA] âš ī¸ Failed to sync to JIRA (non-blocking)" >> "$DEBUG_LOG" 2>/dev/null || true
138
+ }
139
+
140
+ echo "[$(date)] [JIRA] ✅ JIRA sync complete" >> "$DEBUG_LOG" 2>/dev/null || true
141
+
142
+ # ============================================================================
143
+ # SPEC COMMIT SYNC (NEW!)
144
+ # ============================================================================
145
+
146
+ echo "[$(date)] [JIRA] 🔗 Checking for spec commit sync..." >> "$DEBUG_LOG" 2>/dev/null || true
147
+
148
+ # Call TypeScript CLI to sync commits
149
+ if command -v node &> /dev/null && [ -f "$PROJECT_ROOT/dist/cli/commands/sync-spec-commits.js" ]; then
150
+ echo "[$(date)] [JIRA] 🚀 Running spec commit sync..." >> "$DEBUG_LOG" 2>/dev/null || true
151
+
152
+ node "$PROJECT_ROOT/dist/cli/commands/sync-spec-commits.js" \
153
+ --increment "$PROJECT_ROOT/.specweave/increments/$CURRENT_INCREMENT" \
154
+ --provider jira \
155
+ 2>&1 | tee -a "$DEBUG_LOG" >/dev/null || {
156
+ echo "[$(date)] [JIRA] âš ī¸ Spec commit sync failed (non-blocking)" >> "$DEBUG_LOG" 2>/dev/null || true
157
+ }
158
+
159
+ echo "[$(date)] [JIRA] ✅ Spec commit sync complete" >> "$DEBUG_LOG" 2>/dev/null || true
160
+ else
161
+ echo "[$(date)] [JIRA] â„šī¸ Spec commit sync not available (node or script not found)" >> "$DEBUG_LOG" 2>/dev/null || true
162
+ fi
163
+
164
+ # ============================================================================
165
+ # OUTPUT TO CLAUDE
166
+ # ============================================================================
167
+
168
+ cat <<EOF
169
+ {
170
+ "continue": true
171
+ }
172
+ EOF