specweave 0.22.12 → 0.22.14

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 (188) hide show
  1. package/.claude-plugin/README.md +2 -2
  2. package/CLAUDE.md +269 -51
  3. package/README.md +33 -10
  4. package/dist/plugins/specweave-github/lib/ThreeLayerSyncManager.d.ts +1 -1
  5. package/dist/plugins/specweave-github/lib/ThreeLayerSyncManager.js +1 -1
  6. package/dist/plugins/specweave-github/lib/enhanced-github-sync.js +1 -1
  7. package/dist/plugins/specweave-github/lib/enhanced-github-sync.js.map +1 -1
  8. package/dist/plugins/specweave-github/lib/github-spec-content-sync.d.ts.map +1 -1
  9. package/dist/plugins/specweave-github/lib/github-spec-content-sync.js +4 -1
  10. package/dist/plugins/specweave-github/lib/github-spec-content-sync.js.map +1 -1
  11. package/dist/plugins/specweave-github/lib/github-spec-sync.d.ts +1 -1
  12. package/dist/plugins/specweave-github/lib/github-spec-sync.js +1 -1
  13. package/dist/plugins/specweave-github/lib/github-sync-bidirectional.d.ts +9 -0
  14. package/dist/plugins/specweave-github/lib/github-sync-bidirectional.d.ts.map +1 -1
  15. package/dist/plugins/specweave-github/lib/github-sync-bidirectional.js +10 -1
  16. package/dist/plugins/specweave-github/lib/github-sync-bidirectional.js.map +1 -1
  17. package/dist/plugins/specweave-github/lib/progress-comment-builder.js +2 -2
  18. package/dist/plugins/specweave-github/lib/progress-comment-builder.js.map +1 -1
  19. package/dist/plugins/specweave-github/lib/types.d.ts +1 -1
  20. package/dist/src/cli/commands/init.d.ts.map +1 -1
  21. package/dist/src/cli/commands/init.js +313 -1
  22. package/dist/src/cli/commands/init.js.map +1 -1
  23. package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
  24. package/dist/src/cli/helpers/issue-tracker/index.js +41 -24
  25. package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
  26. package/dist/src/config/import-config.d.ts +69 -0
  27. package/dist/src/config/import-config.d.ts.map +1 -0
  28. package/dist/src/config/import-config.js +136 -0
  29. package/dist/src/config/import-config.js.map +1 -0
  30. package/dist/src/config/types.d.ts +10 -10
  31. package/dist/src/core/living-docs/living-docs-sync.d.ts +2 -0
  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 +10 -1
  34. package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
  35. package/dist/src/core/living-docs/task-project-specific-generator.d.ts +2 -2
  36. package/dist/src/core/living-docs/task-project-specific-generator.js +2 -2
  37. package/dist/src/core/repo-structure/prompt-consolidator.d.ts +2 -2
  38. package/dist/src/core/repo-structure/prompt-consolidator.d.ts.map +1 -1
  39. package/dist/src/core/repo-structure/prompt-consolidator.js +3 -15
  40. package/dist/src/core/repo-structure/prompt-consolidator.js.map +1 -1
  41. package/dist/src/core/repo-structure/repo-structure-manager.d.ts +1 -1
  42. package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
  43. package/dist/src/core/repo-structure/repo-structure-manager.js +3 -6
  44. package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
  45. package/dist/src/core/spec-content-sync.d.ts +4 -1
  46. package/dist/src/core/spec-content-sync.d.ts.map +1 -1
  47. package/dist/src/core/spec-content-sync.js +139 -4
  48. package/dist/src/core/spec-content-sync.js.map +1 -1
  49. package/dist/src/core/spec-task-mapper.d.ts.map +1 -1
  50. package/dist/src/core/spec-task-mapper.js +9 -8
  51. package/dist/src/core/spec-task-mapper.js.map +1 -1
  52. package/dist/src/core/status-line-validator.d.ts +63 -0
  53. package/dist/src/core/status-line-validator.d.ts.map +1 -0
  54. package/dist/src/core/status-line-validator.js +253 -0
  55. package/dist/src/core/status-line-validator.js.map +1 -0
  56. package/dist/src/core/sync/bidirectional-engine.d.ts +10 -1
  57. package/dist/src/core/sync/bidirectional-engine.d.ts.map +1 -1
  58. package/dist/src/core/sync/bidirectional-engine.js +10 -1
  59. package/dist/src/core/sync/bidirectional-engine.js.map +1 -1
  60. package/dist/src/core/sync/profile-manager.d.ts.map +1 -1
  61. package/dist/src/core/sync/profile-manager.js +3 -0
  62. package/dist/src/core/sync/profile-manager.js.map +1 -1
  63. package/dist/src/core/sync/project-context.d.ts.map +1 -1
  64. package/dist/src/core/sync/project-context.js +3 -0
  65. package/dist/src/core/sync/project-context.js.map +1 -1
  66. package/dist/src/core/sync/status-sync-engine.d.ts +1 -1
  67. package/dist/src/core/sync/status-sync-engine.js +1 -1
  68. package/dist/src/core/types/origin-metadata.d.ts +153 -0
  69. package/dist/src/core/types/origin-metadata.d.ts.map +1 -0
  70. package/dist/src/core/types/origin-metadata.js +166 -0
  71. package/dist/src/core/types/origin-metadata.js.map +1 -0
  72. package/dist/src/core/types/sync-profile.d.ts +8 -2
  73. package/dist/src/core/types/sync-profile.d.ts.map +1 -1
  74. package/dist/src/core/types/sync-profile.js.map +1 -1
  75. package/dist/src/core/types/sync-settings.d.ts +73 -0
  76. package/dist/src/core/types/sync-settings.d.ts.map +1 -0
  77. package/dist/src/core/types/sync-settings.js +90 -0
  78. package/dist/src/core/types/sync-settings.js.map +1 -0
  79. package/dist/src/core/utils/permission-checker.d.ts +100 -0
  80. package/dist/src/core/utils/permission-checker.d.ts.map +1 -0
  81. package/dist/src/core/utils/permission-checker.js +166 -0
  82. package/dist/src/core/utils/permission-checker.js.map +1 -0
  83. package/dist/src/generators/spec/spec-parser.js +3 -3
  84. package/dist/src/generators/spec/spec-parser.js.map +1 -1
  85. package/dist/src/generators/spec/task-parser.js +4 -4
  86. package/dist/src/generators/spec/task-parser.js.map +1 -1
  87. package/dist/src/id-generators/task-id-generator.d.ts +96 -0
  88. package/dist/src/id-generators/task-id-generator.d.ts.map +1 -0
  89. package/dist/src/id-generators/task-id-generator.js +143 -0
  90. package/dist/src/id-generators/task-id-generator.js.map +1 -0
  91. package/dist/src/id-generators/us-id-generator.d.ts +96 -0
  92. package/dist/src/id-generators/us-id-generator.d.ts.map +1 -0
  93. package/dist/src/id-generators/us-id-generator.js +143 -0
  94. package/dist/src/id-generators/us-id-generator.js.map +1 -0
  95. package/dist/src/importers/ado-importer.d.ts +43 -0
  96. package/dist/src/importers/ado-importer.d.ts.map +1 -0
  97. package/dist/src/importers/ado-importer.js +234 -0
  98. package/dist/src/importers/ado-importer.js.map +1 -0
  99. package/dist/src/importers/external-importer.d.ts +96 -0
  100. package/dist/src/importers/external-importer.d.ts.map +1 -0
  101. package/dist/src/importers/external-importer.js +13 -0
  102. package/dist/src/importers/external-importer.js.map +1 -0
  103. package/dist/src/importers/github-importer.d.ts +37 -0
  104. package/dist/src/importers/github-importer.d.ts.map +1 -0
  105. package/dist/src/importers/github-importer.js +161 -0
  106. package/dist/src/importers/github-importer.js.map +1 -0
  107. package/dist/src/importers/import-coordinator.d.ts +90 -0
  108. package/dist/src/importers/import-coordinator.d.ts.map +1 -0
  109. package/dist/src/importers/import-coordinator.js +182 -0
  110. package/dist/src/importers/import-coordinator.js.map +1 -0
  111. package/dist/src/importers/item-converter.d.ts +91 -0
  112. package/dist/src/importers/item-converter.d.ts.map +1 -0
  113. package/dist/src/importers/item-converter.js +221 -0
  114. package/dist/src/importers/item-converter.js.map +1 -0
  115. package/dist/src/importers/jira-importer.d.ts +42 -0
  116. package/dist/src/importers/jira-importer.d.ts.map +1 -0
  117. package/dist/src/importers/jira-importer.js +221 -0
  118. package/dist/src/importers/jira-importer.js.map +1 -0
  119. package/dist/src/init/repo/types.d.ts +2 -2
  120. package/dist/src/integrations/jira/jira-mapper.d.ts +1 -1
  121. package/dist/src/integrations/jira/jira-mapper.js +1 -1
  122. package/dist/src/living-docs/fs-id-allocator.d.ts +149 -0
  123. package/dist/src/living-docs/fs-id-allocator.d.ts.map +1 -0
  124. package/dist/src/living-docs/fs-id-allocator.js +325 -0
  125. package/dist/src/living-docs/fs-id-allocator.js.map +1 -0
  126. package/dist/src/living-docs/id-registry.d.ts +124 -0
  127. package/dist/src/living-docs/id-registry.d.ts.map +1 -0
  128. package/dist/src/living-docs/id-registry.js +230 -0
  129. package/dist/src/living-docs/id-registry.js.map +1 -0
  130. package/dist/src/progress/us-progress-tracker.d.ts +68 -0
  131. package/dist/src/progress/us-progress-tracker.d.ts.map +1 -0
  132. package/dist/src/progress/us-progress-tracker.js +120 -0
  133. package/dist/src/progress/us-progress-tracker.js.map +1 -0
  134. package/package.json +2 -2
  135. package/plugins/specweave/.claude-plugin/plugin.json +16 -2
  136. package/plugins/specweave/agents/architect/AGENT.md +11 -2
  137. package/plugins/specweave/agents/test-aware-planner/AGENT.md +81 -25
  138. package/plugins/specweave/commands/specweave-import-docs.md +278 -88
  139. package/plugins/specweave/commands/specweave-progress.md +45 -97
  140. package/plugins/specweave/hooks/post-increment-completion.sh +168 -26
  141. package/plugins/specweave/hooks/post-increment-planning.sh +148 -4
  142. package/plugins/specweave/hooks/post-task-completion.sh +64 -4
  143. package/plugins/specweave/lib/hooks/sync-cache.js +294 -0
  144. package/plugins/specweave/lib/hooks/sync-living-docs.js +32 -1
  145. package/plugins/specweave/lib/hooks/sync-us-tasks.js +23 -13
  146. package/plugins/specweave-ado/.claude-plugin/plugin.json +1 -1
  147. package/plugins/specweave-ado/lib/conflict-resolver.ts +1 -1
  148. package/plugins/specweave-alternatives/.claude-plugin/plugin.json +1 -1
  149. package/plugins/specweave-backend/.claude-plugin/plugin.json +1 -1
  150. package/plugins/specweave-confluent/.claude-plugin/plugin.json +1 -1
  151. package/plugins/specweave-cost-optimizer/.claude-plugin/plugin.json +1 -1
  152. package/plugins/specweave-diagrams/.claude-plugin/plugin.json +1 -1
  153. package/plugins/specweave-docs/.claude-plugin/plugin.json +1 -1
  154. package/plugins/specweave-docs-preview/.claude-plugin/plugin.json +1 -1
  155. package/plugins/specweave-figma/.claude-plugin/plugin.json +1 -1
  156. package/plugins/specweave-frontend/.claude-plugin/plugin.json +1 -1
  157. package/plugins/specweave-github/.claude-plugin/plugin.json +1 -1
  158. package/plugins/specweave-github/hooks/post-task-completion.sh +37 -22
  159. package/plugins/specweave-github/lib/ThreeLayerSyncManager.ts +1 -1
  160. package/plugins/specweave-github/lib/enhanced-github-sync.js +1 -1
  161. package/plugins/specweave-github/lib/enhanced-github-sync.ts +1 -1
  162. package/plugins/specweave-github/lib/github-spec-content-sync.js +2 -1
  163. package/plugins/specweave-github/lib/github-spec-content-sync.ts +4 -1
  164. package/plugins/specweave-github/lib/github-spec-sync.js +1 -1
  165. package/plugins/specweave-github/lib/github-spec-sync.ts +1 -1
  166. package/plugins/specweave-github/lib/github-sync-bidirectional.js +1 -1
  167. package/plugins/specweave-github/lib/github-sync-bidirectional.ts +10 -1
  168. package/plugins/specweave-github/lib/progress-comment-builder.js +1 -1
  169. package/plugins/specweave-github/lib/progress-comment-builder.ts +2 -2
  170. package/plugins/specweave-github/lib/types.ts +1 -1
  171. package/plugins/specweave-infrastructure/.claude-plugin/plugin.json +1 -1
  172. package/plugins/specweave-jira/.claude-plugin/plugin.json +1 -1
  173. package/plugins/specweave-kafka/.claude-plugin/plugin.json +1 -1
  174. package/plugins/specweave-kafka-streams/.claude-plugin/plugin.json +1 -1
  175. package/plugins/specweave-kubernetes/.claude-plugin/plugin.json +1 -1
  176. package/plugins/specweave-ml/.claude-plugin/plugin.json +1 -1
  177. package/plugins/specweave-mobile/.claude-plugin/plugin.json +1 -1
  178. package/plugins/specweave-n8n/.claude-plugin/plugin.json +1 -1
  179. package/plugins/specweave-payments/.claude-plugin/plugin.json +1 -1
  180. package/plugins/specweave-release/.claude-plugin/plugin.json +1 -1
  181. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +252 -0
  182. package/plugins/specweave-testing/.claude-plugin/plugin.json +1 -1
  183. package/plugins/specweave-tooling/.claude-plugin/plugin.json +1 -1
  184. package/plugins/specweave-ui/.claude-plugin/plugin.json +1 -1
  185. package/src/templates/.env.example +5 -0
  186. package/src/templates/config-permissions-guide.md +413 -0
  187. package/src/templates/config.json.template +68 -0
  188. package/src/templates/tasks.md.template +180 -201
@@ -0,0 +1,294 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Sync Performance Cache Module
4
+ *
5
+ * Provides caching layer for living docs sync to meet <500ms target.
6
+ * Caches:
7
+ * - Parsed tasks.md content
8
+ * - File modification timestamps
9
+ * - US-Task mappings
10
+ *
11
+ * Part of increment 0047-us-task-linkage (T-012).
12
+ */
13
+
14
+ import fs from 'fs-extra';
15
+ import path from 'path';
16
+ import crypto from 'crypto';
17
+
18
+ /**
19
+ * In-memory cache with TTL
20
+ */
21
+ class SyncCache {
22
+ constructor() {
23
+ this.cache = new Map();
24
+ this.ttl = 60000; // 60 seconds default TTL
25
+ }
26
+
27
+ /**
28
+ * Get cached value
29
+ *
30
+ * @param {string} key - Cache key
31
+ * @returns {any|null} Cached value or null if expired/missing
32
+ */
33
+ get(key) {
34
+ const entry = this.cache.get(key);
35
+
36
+ if (!entry) {
37
+ return null;
38
+ }
39
+
40
+ // Check if expired
41
+ if (Date.now() > entry.expiry) {
42
+ this.cache.delete(key);
43
+ return null;
44
+ }
45
+
46
+ return entry.value;
47
+ }
48
+
49
+ /**
50
+ * Set cached value
51
+ *
52
+ * @param {string} key - Cache key
53
+ * @param {any} value - Value to cache
54
+ * @param {number} ttl - Time to live in milliseconds (optional)
55
+ */
56
+ set(key, value, ttl = this.ttl) {
57
+ this.cache.set(key, {
58
+ value,
59
+ expiry: Date.now() + ttl
60
+ });
61
+ }
62
+
63
+ /**
64
+ * Invalidate cache entry
65
+ *
66
+ * @param {string} key - Cache key
67
+ */
68
+ invalidate(key) {
69
+ this.cache.delete(key);
70
+ }
71
+
72
+ /**
73
+ * Clear all cache
74
+ */
75
+ clear() {
76
+ this.cache.clear();
77
+ }
78
+
79
+ /**
80
+ * Get cache size
81
+ *
82
+ * @returns {number} Number of cached entries
83
+ */
84
+ size() {
85
+ return this.cache.size;
86
+ }
87
+ }
88
+
89
+ // Global cache instance
90
+ const globalCache = new SyncCache();
91
+
92
+ /**
93
+ * Get file hash for change detection
94
+ *
95
+ * @param {string} filePath - Path to file
96
+ * @returns {string} SHA256 hash of file content
97
+ */
98
+ export function getFileHash(filePath) {
99
+ try {
100
+ const content = fs.readFileSync(filePath, 'utf-8');
101
+ return crypto.createHash('sha256').update(content).digest('hex');
102
+ } catch (error) {
103
+ return null;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Get cached parsed tasks.md
109
+ *
110
+ * @param {string} tasksPath - Path to tasks.md
111
+ * @param {Function} parser - Parser function to call if cache miss
112
+ * @returns {any} Parsed tasks (from cache or fresh)
113
+ */
114
+ export function getCachedTasks(tasksPath, parser) {
115
+ // Generate cache key from file path + hash
116
+ const fileHash = getFileHash(tasksPath);
117
+
118
+ if (!fileHash) {
119
+ // File doesn't exist, call parser directly
120
+ return parser(tasksPath);
121
+ }
122
+
123
+ const cacheKey = `tasks:${tasksPath}:${fileHash}`;
124
+
125
+ // Try to get from cache
126
+ const cached = globalCache.get(cacheKey);
127
+
128
+ if (cached) {
129
+ // Cache hit
130
+ return cached;
131
+ }
132
+
133
+ // Cache miss - parse and cache
134
+ const parsed = parser(tasksPath);
135
+ globalCache.set(cacheKey, parsed);
136
+
137
+ return parsed;
138
+ }
139
+
140
+ /**
141
+ * Get cached US file metadata
142
+ *
143
+ * @param {string} usFilePath - Path to US file
144
+ * @returns {object|null} Metadata object or null if file doesn't exist
145
+ */
146
+ export function getCachedUSMetadata(usFilePath) {
147
+ try {
148
+ const stats = fs.statSync(usFilePath);
149
+ const cacheKey = `us-metadata:${usFilePath}`;
150
+
151
+ const cached = globalCache.get(cacheKey);
152
+
153
+ if (cached && cached.mtime === stats.mtimeMs) {
154
+ // File hasn't changed, return cached metadata
155
+ return cached.metadata;
156
+ }
157
+
158
+ // File changed or not in cache - read and cache
159
+ const content = fs.readFileSync(usFilePath, 'utf-8');
160
+ const metadata = {
161
+ mtime: stats.mtimeMs,
162
+ metadata: {
163
+ path: usFilePath,
164
+ size: content.length,
165
+ tasksSectionExists: content.includes('## Tasks'),
166
+ acCount: (content.match(/- \[[x ]\] \*\*AC-US\d+-\d{2}\*\*/g) || []).length
167
+ }
168
+ };
169
+
170
+ globalCache.set(cacheKey, metadata);
171
+
172
+ return metadata.metadata;
173
+ } catch (error) {
174
+ return null;
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Batch file operations to reduce I/O
180
+ *
181
+ * @param {Array<{path: string, content: string}>} updates - Files to update
182
+ * @returns {Promise<void>}
183
+ */
184
+ export async function batchFileUpdates(updates) {
185
+ // Group updates by directory to optimize disk I/O
186
+ const updatesByDir = new Map();
187
+
188
+ updates.forEach(({ path: filePath, content }) => {
189
+ const dir = path.dirname(filePath);
190
+
191
+ if (!updatesByDir.has(dir)) {
192
+ updatesByDir.set(dir, []);
193
+ }
194
+
195
+ updatesByDir.get(dir).push({ path: filePath, content });
196
+ });
197
+
198
+ // Write files sequentially within same directory (better disk I/O)
199
+ for (const [dir, fileUpdates] of updatesByDir.entries()) {
200
+ // Ensure directory exists once per directory
201
+ await fs.ensureDir(dir);
202
+
203
+ // Write all files in this directory
204
+ await Promise.all(
205
+ fileUpdates.map(({ path: filePath, content }) =>
206
+ fs.writeFile(filePath, content, 'utf-8')
207
+ )
208
+ );
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Check if sync is needed for a US file
214
+ *
215
+ * @param {string} usFilePath - Path to US file
216
+ * @param {Array} tasks - Tasks for this US
217
+ * @param {string} tasksPath - Path to tasks.md
218
+ * @returns {boolean} True if sync is needed
219
+ */
220
+ export function needsSync(usFilePath, tasks, tasksPath) {
221
+ try {
222
+ // Check if US file exists
223
+ if (!fs.existsSync(usFilePath)) {
224
+ return false; // File doesn't exist, can't sync
225
+ }
226
+
227
+ // Get file modification times
228
+ const usStats = fs.statSync(usFilePath);
229
+ const tasksStats = fs.statSync(tasksPath);
230
+
231
+ // If tasks.md is newer than US file, sync is needed
232
+ if (tasksStats.mtimeMs > usStats.mtimeMs) {
233
+ return true;
234
+ }
235
+
236
+ // Check cache for last sync result
237
+ const cacheKey = `sync-result:${usFilePath}`;
238
+ const cached = globalCache.get(cacheKey);
239
+
240
+ if (cached) {
241
+ // Compare task list with cached
242
+ const currentTaskIds = tasks.map(t => t.id).sort().join(',');
243
+ const cachedTaskIds = cached.taskIds;
244
+
245
+ if (currentTaskIds === cachedTaskIds) {
246
+ // Task list unchanged, no sync needed
247
+ return false;
248
+ }
249
+ }
250
+
251
+ // Default: sync is needed
252
+ return true;
253
+ } catch (error) {
254
+ // If error checking, assume sync is needed
255
+ return true;
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Record sync result for incremental sync
261
+ *
262
+ * @param {string} usFilePath - Path to US file
263
+ * @param {Array} tasks - Tasks that were synced
264
+ */
265
+ export function recordSync(usFilePath, tasks) {
266
+ const cacheKey = `sync-result:${usFilePath}`;
267
+ const taskIds = tasks.map(t => t.id).sort().join(',');
268
+
269
+ globalCache.set(cacheKey, {
270
+ taskIds,
271
+ timestamp: Date.now()
272
+ });
273
+ }
274
+
275
+ /**
276
+ * Get cache statistics
277
+ *
278
+ * @returns {object} Cache stats
279
+ */
280
+ export function getCacheStats() {
281
+ return {
282
+ size: globalCache.size(),
283
+ entries: Array.from(globalCache.cache.keys())
284
+ };
285
+ }
286
+
287
+ /**
288
+ * Clear cache (for testing)
289
+ */
290
+ export function clearCache() {
291
+ globalCache.clear();
292
+ }
293
+
294
+ export default globalCache;
@@ -37,6 +37,23 @@ async function syncLivingDocs(incrementId) {
37
37
  return;
38
38
  }
39
39
  console.log(`\u{1F4C4} Changed/created ${changedDocs.length} file(s)`);
40
+
41
+ // ========================================================================
42
+ // CHECK PERMISSION: canUpdateExternalItems (v0.24.0 - Three-Permission Architecture)
43
+ // ========================================================================
44
+ // This permission controls whether SpecWeave can UPDATE externally-created items
45
+ // (full content: title, description, ACs, tasks, comments).
46
+ // If false, living docs sync happens locally but doesn't push to external tools.
47
+ const canUpdateExternal = config.sync?.settings?.canUpdateExternalItems ?? false;
48
+
49
+ if (!canUpdateExternal) {
50
+ console.log("\u2139\uFE0F GitHub sync skipped (canUpdateExternalItems = false)");
51
+ console.log(" Living docs updated locally only");
52
+ console.log(" To enable: Set sync.settings.canUpdateExternalItems = true in config.json");
53
+ console.log("\u2705 Living docs sync complete (local only)\n");
54
+ return;
55
+ }
56
+
40
57
  await syncToGitHub(incrementId, changedDocs);
41
58
  console.log("\u2705 Living docs sync complete\n");
42
59
  } catch (error) {
@@ -77,6 +94,18 @@ async function hierarchicalDistribution(incrementId) {
77
94
  console.log(" \u{1F4CA} Syncing increment to living docs structure...");
78
95
  const projectRoot = process.cwd();
79
96
 
97
+ // ========================================================================
98
+ // USE FEATURE ID FROM ENVIRONMENT (NEW in v0.23.0 - Increment 0047)
99
+ // ========================================================================
100
+ // If FEATURE_ID is provided via environment variable (extracted from spec.md),
101
+ // use it directly instead of auto-generating. This ensures correct traceability.
102
+ const explicitFeatureId = process.env.FEATURE_ID;
103
+ if (explicitFeatureId) {
104
+ console.log(` \u{1F4CE} Using explicit feature ID from spec.md: ${explicitFeatureId}`);
105
+ } else {
106
+ console.log(" \u{1F504} Feature ID will be auto-generated from increment number");
107
+ }
108
+
80
109
  // Create logger adapter for LivingDocsSync
81
110
  const logger = {
82
111
  log: (msg) => console.log(` ${msg}`),
@@ -87,7 +116,9 @@ async function hierarchicalDistribution(incrementId) {
87
116
  const sync = new LivingDocsSync(projectRoot, { logger });
88
117
  const result = await sync.syncIncrement(incrementId, {
89
118
  dryRun: false,
90
- force: false
119
+ force: false,
120
+ // Pass explicit feature ID if available (v0.23.0+)
121
+ explicitFeatureId: explicitFeatureId || undefined
91
122
  });
92
123
 
93
124
  if (!result.success) {
@@ -14,6 +14,7 @@ import fs from 'fs-extra';
14
14
  import path from 'path';
15
15
  import { parseTasksWithUSLinks, getAllTasks } from '../../../../dist/src/generators/spec/task-parser.js';
16
16
  import { glob } from 'glob';
17
+ import { getCachedTasks, needsSync, recordSync, batchFileUpdates } from './sync-cache.js';
17
18
 
18
19
  /**
19
20
  * Sync tasks from tasks.md to living docs User Story files
@@ -36,10 +37,10 @@ export async function syncUSTasksToLivingDocs(incrementId, projectRoot, featureI
36
37
  return { success: true, updatedFiles: [], errors: [] };
37
38
  }
38
39
 
39
- // Parse tasks with US linkage
40
+ // Parse tasks with US linkage (with caching for performance)
40
41
  let tasksByUS;
41
42
  try {
42
- tasksByUS = parseTasksWithUSLinks(tasksPath);
43
+ tasksByUS = getCachedTasks(tasksPath, parseTasksWithUSLinks);
43
44
  } catch (error) {
44
45
  console.error(` ❌ Failed to parse tasks.md:`, error.message);
45
46
  return { success: false, updatedFiles: [], errors: [error.message] };
@@ -59,6 +60,7 @@ export async function syncUSTasksToLivingDocs(incrementId, projectRoot, featureI
59
60
 
60
61
  const updatedFiles = [];
61
62
  const errors = [];
63
+ const filesToUpdate = []; // Batch file updates
62
64
 
63
65
  // For each User Story with tasks, update its living docs file
64
66
  for (const [usId, tasks] of Object.entries(tasksByUS)) {
@@ -73,12 +75,19 @@ export async function syncUSTasksToLivingDocs(incrementId, projectRoot, featureI
73
75
  continue;
74
76
  }
75
77
 
78
+ // Incremental sync: Check if sync is needed
79
+ if (!needsSync(usFilePath, tasks, tasksPath)) {
80
+ console.log(` ⏭️ ${usId} unchanged, skipping sync`);
81
+ continue;
82
+ }
83
+
76
84
  // Update US file with task list
77
- const updated = await updateUSFile(usFilePath, tasks, incrementId);
85
+ const result = await updateUSFile(usFilePath, tasks, incrementId);
78
86
 
79
- if (updated) {
80
- updatedFiles.push(usFilePath);
81
- console.log(` ✓ Updated ${usId} with ${tasks.length} tasks`);
87
+ if (result.updated) {
88
+ filesToUpdate.push({ path: usFilePath, content: result.content });
89
+ recordSync(usFilePath, tasks); // Cache sync result for next run
90
+ console.log(` ✓ Prepared update for ${usId} (${tasks.length} tasks)`);
82
91
  }
83
92
  } catch (error) {
84
93
  console.error(` ❌ Error updating ${usId}:`, error.message);
@@ -86,6 +95,12 @@ export async function syncUSTasksToLivingDocs(incrementId, projectRoot, featureI
86
95
  }
87
96
  }
88
97
 
98
+ // Batch write all file updates (reduce I/O)
99
+ if (filesToUpdate.length > 0) {
100
+ await batchFileUpdates(filesToUpdate);
101
+ updatedFiles.push(...filesToUpdate.map(f => f.path));
102
+ }
103
+
89
104
  if (updatedFiles.length > 0) {
90
105
  console.log(` ✅ Updated ${updatedFiles.length} User Story file(s)`);
91
106
  } else {
@@ -144,7 +159,7 @@ async function findUSFile(projectRoot, projectId, featureId, usId) {
144
159
  * @param {string} usFilePath - Path to US markdown file
145
160
  * @param {Array} tasks - Tasks linked to this US
146
161
  * @param {string} incrementId - Increment ID
147
- * @returns {Promise<boolean>} True if file was updated
162
+ * @returns {Promise<{updated: boolean, content: string}>} Update result
148
163
  */
149
164
  async function updateUSFile(usFilePath, tasks, incrementId) {
150
165
  let content = await fs.readFile(usFilePath, 'utf-8');
@@ -173,12 +188,7 @@ async function updateUSFile(usFilePath, tasks, incrementId) {
173
188
  updated = true;
174
189
  }
175
190
 
176
- // Write updated content
177
- if (updated) {
178
- await fs.writeFile(usFilePath, content, 'utf-8');
179
- }
180
-
181
- return updated;
191
+ return { updated, content };
182
192
  }
183
193
 
184
194
  /**
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specweave-ado",
3
- "version": "0.1.0",
3
+ "version": "0.22.14",
4
4
  "description": "Azure DevOps integration for SpecWeave - sync increments with ADO work items, track progress, and manage project workflows",
5
5
  "author": {
6
6
  "name": "SpecWeave Team",
@@ -402,7 +402,7 @@ export async function loadSpecMetadata(specPath: string): Promise<SpecMetadata>
402
402
  }
403
403
 
404
404
  /**
405
- * Perform bidirectional sync with conflict resolution
405
+ * Perform three-permission sync with conflict resolution
406
406
  */
407
407
  export async function performBidirectionalSync(
408
408
  specPath: string,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "specweave-alternatives",
3
3
  "description": "Compare SpecWeave with BMAD, spec-kit, openspec, and other spec-driven frameworks. Get gap analysis, feature comparison, and strategic recommendations to choose the best framework for your needs.",
4
- "version": "1.0.0",
4
+ "version": "0.22.14",
5
5
  "author": {
6
6
  "name": "SpecWeave Team",
7
7
  "url": "https://spec-weave.com"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "specweave-backend",
3
3
  "description": "Backend API development for Node.js, Python, and .NET stacks. Includes Express, NestJS, FastAPI, Django, Flask, ASP.NET Core, and Entity Framework Core. Focus on REST APIs, authentication, database operations, and background services.",
4
- "version": "1.0.0",
4
+ "version": "0.22.14",
5
5
  "author": {
6
6
  "name": "SpecWeave Team",
7
7
  "url": "https://spec-weave.com"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "specweave-confluent",
3
3
  "description": "Confluent Cloud integration for SpecWeave - Schema Registry, ksqlDB, Kafka Connect, Flink, stream processing, and enterprise Kafka features",
4
- "version": "1.0.0",
4
+ "version": "0.22.14",
5
5
  "author": {
6
6
  "name": "SpecWeave Team",
7
7
  "url": "https://spec-weave.com"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "specweave-cost-optimizer",
3
3
  "description": "Cloud cost optimization and platform comparison. Analyzes infrastructure requirements and recommends cheapest cloud platform (Hetzner, Vercel, AWS, Railway, Fly.io, DigitalOcean). Shows cost breakdown and savings calculations.",
4
- "version": "1.0.0",
4
+ "version": "0.22.14",
5
5
  "author": {
6
6
  "name": "SpecWeave Team",
7
7
  "url": "https://spec-weave.com"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "specweave-diagrams",
3
3
  "description": "Architecture diagram generation with Mermaid following C4 Model conventions. Creates C4 Context/Container/Component diagrams, sequence diagrams, ER diagrams, and deployment diagrams. SpecWeave-aware for HLD/LLD documentation.",
4
- "version": "1.0.0",
4
+ "version": "0.22.14",
5
5
  "author": {
6
6
  "name": "SpecWeave Team",
7
7
  "url": "https://spec-weave.com"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "specweave-docs",
3
3
  "description": "Documentation generation and spec-driven workflows. Includes Docusaurus site generation from SpecWeave structure, spec-driven brainstorming for feature ideation, and spec-driven debugging. Focus on living documentation and knowledge management.",
4
- "version": "1.0.0",
4
+ "version": "0.22.14",
5
5
  "author": {
6
6
  "name": "SpecWeave Team",
7
7
  "url": "https://spec-weave.com"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "specweave-docs-preview",
3
3
  "description": "Interactive documentation preview with Docusaurus. Launch local dev server to view living documentation in beautiful UI with hot reload, auto-generated sidebar, and Mermaid diagrams. Build static sites for deployment.",
4
- "version": "1.0.0",
4
+ "version": "0.22.14",
5
5
  "author": {
6
6
  "name": "SpecWeave Team",
7
7
  "url": "https://spec-weave.com"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "specweave-figma",
3
3
  "description": "Design-to-code workflow patterns for Figma (MCP-first). Requires Figma MCP server. Extracts design tokens, generates components (React/Angular/Vue/Svelte), scaffolds tests. Uses Atomic Design and TypeScript.",
4
- "version": "1.0.0",
4
+ "version": "0.22.14",
5
5
  "author": {
6
6
  "name": "SpecWeave Team",
7
7
  "url": "https://spec-weave.com"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "specweave-frontend",
3
3
  "description": "Frontend development for React, Vue, and Angular projects. Includes Next.js 14+ App Router support, design system architecture (Atomic Design), and UI component best practices. Focus on modern frontend patterns and performance.",
4
- "version": "1.0.0",
4
+ "version": "0.22.14",
5
5
  "author": {
6
6
  "name": "SpecWeave Team",
7
7
  "url": "https://spec-weave.com"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "specweave-github",
3
3
  "description": "GitHub Issues integration for SpecWeave increments. Bidirectional sync between SpecWeave increments and GitHub Issues. Automatically creates issues from increments, tracks progress, and closes issues on completion. Uses GitHub CLI (gh) for seamless integration.",
4
- "version": "1.0.0",
4
+ "version": "0.22.14",
5
5
  "author": {
6
6
  "name": "SpecWeave Team",
7
7
  "url": "https://spec-weave.com"
@@ -201,35 +201,50 @@ else
201
201
  fi
202
202
 
203
203
  # ============================================================================
204
- # EPIC GITHUB ISSUE SYNC (Update Epic issue with fresh task progress)
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
205
216
  # ============================================================================
206
217
 
207
- echo "[$(date)] [GitHub] 🔄 Checking for Epic GitHub issue update..." >> "$DEBUG_LOG" 2>/dev/null || true
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
208
220
 
209
- # Find active increment ID
210
- ACTIVE_INCREMENT=$(ls -t .specweave/increments/ | grep -v '^\.' | while read inc; do
211
- if [ -f ".specweave/increments/$inc/metadata.json" ]; then
212
- STATUS=$(grep -o '"status"[[:space:]]*:[[:space:]]*"[^"]*"' ".specweave/increments/$inc/metadata.json" 2>/dev/null | sed 's/.*"\([^"]*\)".*/\1/' || true)
213
- if [ "$STATUS" = "active" ]; then
214
- echo "$inc"
215
- break
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
216
242
  fi
217
- fi
218
- done | head -1)
219
-
220
- if [ -n "$ACTIVE_INCREMENT" ]; then
221
- echo "[$(date)] [GitHub] 🎯 Active increment: $ACTIVE_INCREMENT" >> "$DEBUG_LOG" 2>/dev/null || true
222
-
223
- # Run Epic sync script (silently, errors logged to debug log)
224
- if [ -f "$PROJECT_ROOT/scripts/update-epic-github-issue.sh" ]; then
225
- echo "[$(date)] [GitHub] 🚀 Updating Epic GitHub issue..." >> "$DEBUG_LOG" 2>/dev/null || true
226
- "$PROJECT_ROOT/scripts/update-epic-github-issue.sh" "$ACTIVE_INCREMENT" >> "$DEBUG_LOG" 2>&1 || true
227
- echo "[$(date)] [GitHub] ✅ Epic sync complete (see logs for details)" >> "$DEBUG_LOG" 2>/dev/null || true
228
243
  else
229
- echo "[$(date)] [GitHub] ⚠️ Epic sync script not found, skipping" >> "$DEBUG_LOG" 2>/dev/null || true
244
+ echo "[$(date)] [GitHub] ℹ️ No active increment found, skipping Epic sync" >> "$DEBUG_LOG" 2>/dev/null || true
230
245
  fi
231
246
  else
232
- echo "[$(date)] [GitHub] ℹ️ No active increment found, skipping Epic sync" >> "$DEBUG_LOG" 2>/dev/null || true
247
+ echo "[$(date)] [GitHub] ℹ️ Epic sync disabled (sync at User Story level only)" >> "$DEBUG_LOG" 2>/dev/null || true
233
248
  fi
234
249
 
235
250
  # ============================================================================
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Three-Layer Bidirectional Sync Manager
2
+ * Three-Layer Full Sync Manager (All Permissions Enabled)
3
3
  *
4
4
  * Handles synchronization across three layers:
5
5
  * - Layer 1: GitHub Issue (stakeholder UI)
@@ -108,7 +108,7 @@ async function syncSpecWithEnhancedContent(options) {
108
108
  };
109
109
  } else {
110
110
  const issue = await client.createEpicIssue(
111
- `[${baseSpec.identifier.compact}] ${baseSpec.title}`,
111
+ `[${baseSpec.project === "_features" ? baseSpec.identifier.display : baseSpec.identifier.compact}] ${baseSpec.title}`,
112
112
  description,
113
113
  void 0,
114
114
  allLabels
@@ -185,7 +185,7 @@ export async function syncSpecWithEnhancedContent(
185
185
  } else {
186
186
  // Create new issue with labels
187
187
  const issue = await client.createEpicIssue(
188
- `[${baseSpec.identifier.compact}] ${baseSpec.title}`,
188
+ `[${baseSpec.project === '_features' ? baseSpec.identifier.display : baseSpec.identifier.compact}] ${baseSpec.title}`,
189
189
  description,
190
190
  undefined,
191
191
  allLabels // Apply labels at creation
@@ -85,7 +85,8 @@ async function syncSpecContentToGitHub(options) {
85
85
  async function createGitHubIssue(client, spec, options) {
86
86
  const { specPath, dryRun, verbose } = options;
87
87
  try {
88
- const title = `[${spec.identifier.compact}] ${spec.title}`;
88
+ const titleId = spec.project === "_features" ? spec.identifier.display : spec.identifier.compact;
89
+ const title = `[${titleId}] ${spec.title}`;
89
90
  const body = buildExternalDescription(spec);
90
91
  if (verbose) {
91
92
  console.log(`