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.
- package/.claude-plugin/README.md +2 -2
- package/CLAUDE.md +269 -51
- package/README.md +33 -10
- package/dist/plugins/specweave-github/lib/ThreeLayerSyncManager.d.ts +1 -1
- package/dist/plugins/specweave-github/lib/ThreeLayerSyncManager.js +1 -1
- package/dist/plugins/specweave-github/lib/enhanced-github-sync.js +1 -1
- package/dist/plugins/specweave-github/lib/enhanced-github-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.js +4 -1
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-spec-sync.d.ts +1 -1
- package/dist/plugins/specweave-github/lib/github-spec-sync.js +1 -1
- package/dist/plugins/specweave-github/lib/github-sync-bidirectional.d.ts +9 -0
- package/dist/plugins/specweave-github/lib/github-sync-bidirectional.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-sync-bidirectional.js +10 -1
- package/dist/plugins/specweave-github/lib/github-sync-bidirectional.js.map +1 -1
- package/dist/plugins/specweave-github/lib/progress-comment-builder.js +2 -2
- package/dist/plugins/specweave-github/lib/progress-comment-builder.js.map +1 -1
- package/dist/plugins/specweave-github/lib/types.d.ts +1 -1
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +313 -1
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/index.js +41 -24
- package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
- package/dist/src/config/import-config.d.ts +69 -0
- package/dist/src/config/import-config.d.ts.map +1 -0
- package/dist/src/config/import-config.js +136 -0
- package/dist/src/config/import-config.js.map +1 -0
- package/dist/src/config/types.d.ts +10 -10
- package/dist/src/core/living-docs/living-docs-sync.d.ts +2 -0
- package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.js +10 -1
- package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
- package/dist/src/core/living-docs/task-project-specific-generator.d.ts +2 -2
- package/dist/src/core/living-docs/task-project-specific-generator.js +2 -2
- package/dist/src/core/repo-structure/prompt-consolidator.d.ts +2 -2
- package/dist/src/core/repo-structure/prompt-consolidator.d.ts.map +1 -1
- package/dist/src/core/repo-structure/prompt-consolidator.js +3 -15
- package/dist/src/core/repo-structure/prompt-consolidator.js.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.js +3 -6
- package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
- package/dist/src/core/spec-content-sync.d.ts +4 -1
- package/dist/src/core/spec-content-sync.d.ts.map +1 -1
- package/dist/src/core/spec-content-sync.js +139 -4
- package/dist/src/core/spec-content-sync.js.map +1 -1
- package/dist/src/core/spec-task-mapper.d.ts.map +1 -1
- package/dist/src/core/spec-task-mapper.js +9 -8
- package/dist/src/core/spec-task-mapper.js.map +1 -1
- package/dist/src/core/status-line-validator.d.ts +63 -0
- package/dist/src/core/status-line-validator.d.ts.map +1 -0
- package/dist/src/core/status-line-validator.js +253 -0
- package/dist/src/core/status-line-validator.js.map +1 -0
- package/dist/src/core/sync/bidirectional-engine.d.ts +10 -1
- package/dist/src/core/sync/bidirectional-engine.d.ts.map +1 -1
- package/dist/src/core/sync/bidirectional-engine.js +10 -1
- package/dist/src/core/sync/bidirectional-engine.js.map +1 -1
- package/dist/src/core/sync/profile-manager.d.ts.map +1 -1
- package/dist/src/core/sync/profile-manager.js +3 -0
- package/dist/src/core/sync/profile-manager.js.map +1 -1
- package/dist/src/core/sync/project-context.d.ts.map +1 -1
- package/dist/src/core/sync/project-context.js +3 -0
- package/dist/src/core/sync/project-context.js.map +1 -1
- package/dist/src/core/sync/status-sync-engine.d.ts +1 -1
- package/dist/src/core/sync/status-sync-engine.js +1 -1
- package/dist/src/core/types/origin-metadata.d.ts +153 -0
- package/dist/src/core/types/origin-metadata.d.ts.map +1 -0
- package/dist/src/core/types/origin-metadata.js +166 -0
- package/dist/src/core/types/origin-metadata.js.map +1 -0
- package/dist/src/core/types/sync-profile.d.ts +8 -2
- package/dist/src/core/types/sync-profile.d.ts.map +1 -1
- package/dist/src/core/types/sync-profile.js.map +1 -1
- package/dist/src/core/types/sync-settings.d.ts +73 -0
- package/dist/src/core/types/sync-settings.d.ts.map +1 -0
- package/dist/src/core/types/sync-settings.js +90 -0
- package/dist/src/core/types/sync-settings.js.map +1 -0
- package/dist/src/core/utils/permission-checker.d.ts +100 -0
- package/dist/src/core/utils/permission-checker.d.ts.map +1 -0
- package/dist/src/core/utils/permission-checker.js +166 -0
- package/dist/src/core/utils/permission-checker.js.map +1 -0
- package/dist/src/generators/spec/spec-parser.js +3 -3
- package/dist/src/generators/spec/spec-parser.js.map +1 -1
- package/dist/src/generators/spec/task-parser.js +4 -4
- package/dist/src/generators/spec/task-parser.js.map +1 -1
- package/dist/src/id-generators/task-id-generator.d.ts +96 -0
- package/dist/src/id-generators/task-id-generator.d.ts.map +1 -0
- package/dist/src/id-generators/task-id-generator.js +143 -0
- package/dist/src/id-generators/task-id-generator.js.map +1 -0
- package/dist/src/id-generators/us-id-generator.d.ts +96 -0
- package/dist/src/id-generators/us-id-generator.d.ts.map +1 -0
- package/dist/src/id-generators/us-id-generator.js +143 -0
- package/dist/src/id-generators/us-id-generator.js.map +1 -0
- package/dist/src/importers/ado-importer.d.ts +43 -0
- package/dist/src/importers/ado-importer.d.ts.map +1 -0
- package/dist/src/importers/ado-importer.js +234 -0
- package/dist/src/importers/ado-importer.js.map +1 -0
- package/dist/src/importers/external-importer.d.ts +96 -0
- package/dist/src/importers/external-importer.d.ts.map +1 -0
- package/dist/src/importers/external-importer.js +13 -0
- package/dist/src/importers/external-importer.js.map +1 -0
- package/dist/src/importers/github-importer.d.ts +37 -0
- package/dist/src/importers/github-importer.d.ts.map +1 -0
- package/dist/src/importers/github-importer.js +161 -0
- package/dist/src/importers/github-importer.js.map +1 -0
- package/dist/src/importers/import-coordinator.d.ts +90 -0
- package/dist/src/importers/import-coordinator.d.ts.map +1 -0
- package/dist/src/importers/import-coordinator.js +182 -0
- package/dist/src/importers/import-coordinator.js.map +1 -0
- package/dist/src/importers/item-converter.d.ts +91 -0
- package/dist/src/importers/item-converter.d.ts.map +1 -0
- package/dist/src/importers/item-converter.js +221 -0
- package/dist/src/importers/item-converter.js.map +1 -0
- package/dist/src/importers/jira-importer.d.ts +42 -0
- package/dist/src/importers/jira-importer.d.ts.map +1 -0
- package/dist/src/importers/jira-importer.js +221 -0
- package/dist/src/importers/jira-importer.js.map +1 -0
- package/dist/src/init/repo/types.d.ts +2 -2
- package/dist/src/integrations/jira/jira-mapper.d.ts +1 -1
- package/dist/src/integrations/jira/jira-mapper.js +1 -1
- package/dist/src/living-docs/fs-id-allocator.d.ts +149 -0
- package/dist/src/living-docs/fs-id-allocator.d.ts.map +1 -0
- package/dist/src/living-docs/fs-id-allocator.js +325 -0
- package/dist/src/living-docs/fs-id-allocator.js.map +1 -0
- package/dist/src/living-docs/id-registry.d.ts +124 -0
- package/dist/src/living-docs/id-registry.d.ts.map +1 -0
- package/dist/src/living-docs/id-registry.js +230 -0
- package/dist/src/living-docs/id-registry.js.map +1 -0
- package/dist/src/progress/us-progress-tracker.d.ts +68 -0
- package/dist/src/progress/us-progress-tracker.d.ts.map +1 -0
- package/dist/src/progress/us-progress-tracker.js +120 -0
- package/dist/src/progress/us-progress-tracker.js.map +1 -0
- package/package.json +2 -2
- package/plugins/specweave/.claude-plugin/plugin.json +16 -2
- package/plugins/specweave/agents/architect/AGENT.md +11 -2
- package/plugins/specweave/agents/test-aware-planner/AGENT.md +81 -25
- package/plugins/specweave/commands/specweave-import-docs.md +278 -88
- package/plugins/specweave/commands/specweave-progress.md +45 -97
- package/plugins/specweave/hooks/post-increment-completion.sh +168 -26
- package/plugins/specweave/hooks/post-increment-planning.sh +148 -4
- package/plugins/specweave/hooks/post-task-completion.sh +64 -4
- package/plugins/specweave/lib/hooks/sync-cache.js +294 -0
- package/plugins/specweave/lib/hooks/sync-living-docs.js +32 -1
- package/plugins/specweave/lib/hooks/sync-us-tasks.js +23 -13
- package/plugins/specweave-ado/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-ado/lib/conflict-resolver.ts +1 -1
- package/plugins/specweave-alternatives/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-backend/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-confluent/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-cost-optimizer/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-diagrams/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-docs/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-docs-preview/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-figma/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-frontend/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-github/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-github/hooks/post-task-completion.sh +37 -22
- package/plugins/specweave-github/lib/ThreeLayerSyncManager.ts +1 -1
- package/plugins/specweave-github/lib/enhanced-github-sync.js +1 -1
- package/plugins/specweave-github/lib/enhanced-github-sync.ts +1 -1
- package/plugins/specweave-github/lib/github-spec-content-sync.js +2 -1
- package/plugins/specweave-github/lib/github-spec-content-sync.ts +4 -1
- package/plugins/specweave-github/lib/github-spec-sync.js +1 -1
- package/plugins/specweave-github/lib/github-spec-sync.ts +1 -1
- package/plugins/specweave-github/lib/github-sync-bidirectional.js +1 -1
- package/plugins/specweave-github/lib/github-sync-bidirectional.ts +10 -1
- package/plugins/specweave-github/lib/progress-comment-builder.js +1 -1
- package/plugins/specweave-github/lib/progress-comment-builder.ts +2 -2
- package/plugins/specweave-github/lib/types.ts +1 -1
- package/plugins/specweave-infrastructure/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-jira/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-kafka/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-kafka-streams/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-kubernetes/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-ml/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-mobile/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-n8n/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-payments/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-release/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +252 -0
- package/plugins/specweave-testing/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-tooling/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-ui/.claude-plugin/plugin.json +1 -1
- package/src/templates/.env.example +5 -0
- package/src/templates/config-permissions-guide.md +413 -0
- package/src/templates/config.json.template +68 -0
- 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 =
|
|
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
|
|
85
|
+
const result = await updateUSFile(usFilePath, tasks, incrementId);
|
|
78
86
|
|
|
79
|
-
if (updated) {
|
|
80
|
-
|
|
81
|
-
|
|
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>}
|
|
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
|
-
|
|
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
|
/**
|
|
@@ -402,7 +402,7 @@ export async function loadSpecMetadata(specPath: string): Promise<SpecMetadata>
|
|
|
402
402
|
}
|
|
403
403
|
|
|
404
404
|
/**
|
|
405
|
-
* Perform
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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]
|
|
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] ℹ️
|
|
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
|
# ============================================================================
|
|
@@ -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
|
|
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(`
|