specweave 0.17.19 → 0.18.1

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 (31) hide show
  1. package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.d.ts.map +1 -1
  2. package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.js +6 -65
  3. package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.js.map +1 -1
  4. package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts +6 -8
  5. package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts.map +1 -1
  6. package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js +117 -78
  7. package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js.map +1 -1
  8. package/dist/src/core/types/config.d.ts +0 -20
  9. package/dist/src/core/types/config.d.ts.map +1 -1
  10. package/dist/src/core/types/config.js +0 -7
  11. package/dist/src/core/types/config.js.map +1 -1
  12. package/package.json +1 -1
  13. package/plugins/specweave/hooks/hooks.json +0 -4
  14. package/plugins/specweave/hooks/lib/sync-spec-content.sh +2 -2
  15. package/plugins/specweave-ado/commands/specweave-ado-sync-spec.md +1 -1
  16. package/plugins/specweave-ado/lib/ado-spec-content-sync.js +5 -49
  17. package/plugins/specweave-ado/lib/ado-spec-content-sync.ts +6 -72
  18. package/plugins/specweave-github/commands/specweave-github-cleanup-duplicates.md +1 -1
  19. package/plugins/specweave-github/commands/specweave-github-sync-epic.md +1 -1
  20. package/plugins/specweave-github/commands/specweave-github-sync-spec.md +1 -1
  21. package/plugins/specweave-jira/commands/specweave-jira-sync-epic.md +1 -1
  22. package/plugins/specweave-jira/commands/specweave-jira-sync-spec.md +1 -1
  23. package/plugins/specweave-jira/lib/{enhanced-jira-sync.ts → enhanced-jira-sync.ts.disabled} +52 -26
  24. package/plugins/specweave-release/commands/specweave-release-platform.md +1 -1
  25. package/plugins/specweave-release/hooks/post-task-completion.sh +2 -2
  26. package/dist/src/core/deduplication/command-deduplicator.d.ts +0 -166
  27. package/dist/src/core/deduplication/command-deduplicator.d.ts.map +0 -1
  28. package/dist/src/core/deduplication/command-deduplicator.js +0 -254
  29. package/dist/src/core/deduplication/command-deduplicator.js.map +0 -1
  30. package/plugins/specweave/hooks/pre-command-deduplication.sh +0 -86
  31. package/plugins/specweave-jira/lib/enhanced-jira-sync.js +0 -134
@@ -1,254 +0,0 @@
1
- /**
2
- * Global Command Deduplication System
3
- *
4
- * Prevents ANY command/tool from being invoked twice within a configurable time window.
5
- * Tracks all SlashCommand, Write, Edit, and other tool invocations.
6
- *
7
- * Architecture:
8
- * - File-based cache: `.specweave/state/command-invocations.json`
9
- * - Hash-based deduplication: command + args → unique fingerprint
10
- * - Time-windowed checks: Configurable window (default: 1000ms)
11
- * - Automatic cleanup: Removes old entries to prevent bloat
12
- *
13
- * Usage:
14
- * ```typescript
15
- * import { CommandDeduplicator } from './command-deduplicator.js';
16
- *
17
- * const dedup = new CommandDeduplicator();
18
- * const isDuplicate = await dedup.checkDuplicate('/specweave:do', ['0031']);
19
- *
20
- * if (isDuplicate) {
21
- * console.log('⚠️ Duplicate invocation blocked!');
22
- * return;
23
- * }
24
- *
25
- * await dedup.recordInvocation('/specweave:do', ['0031']);
26
- * // ... execute command
27
- * ```
28
- *
29
- * @module core/deduplication
30
- */
31
- import * as fs from 'fs-extra';
32
- import * as path from 'path';
33
- import * as crypto from 'crypto';
34
- /**
35
- * Global command deduplication system
36
- */
37
- export class CommandDeduplicator {
38
- /**
39
- * Create new deduplicator instance
40
- *
41
- * @param config - Configuration options
42
- * @param projectRoot - Project root directory (default: process.cwd())
43
- */
44
- constructor(config = {}, projectRoot = process.cwd()) {
45
- this.projectRoot = projectRoot;
46
- this.lastCleanupCheck = 0;
47
- this.config = {
48
- windowMs: config.windowMs ?? 1000,
49
- cachePath: config.cachePath ?? path.join(projectRoot, '.specweave', 'state', 'command-invocations.json'),
50
- maxCacheSize: config.maxCacheSize ?? 1000,
51
- debug: config.debug ?? false,
52
- cleanupIntervalMs: config.cleanupIntervalMs ?? 60000
53
- };
54
- this.cache = this.loadCache();
55
- }
56
- /**
57
- * Check if command invocation is a duplicate
58
- *
59
- * @param command - Command name (e.g., '/specweave:do')
60
- * @param args - Command arguments
61
- * @returns true if duplicate detected, false otherwise
62
- */
63
- async checkDuplicate(command, args = []) {
64
- const fingerprint = this.createFingerprint(command, args);
65
- const now = Date.now();
66
- const windowStart = now - this.config.windowMs;
67
- // Check for recent invocations with same fingerprint
68
- const recentDuplicates = this.cache.invocations.filter(inv => inv.fingerprint === fingerprint &&
69
- inv.timestamp >= windowStart);
70
- if (recentDuplicates.length > 0) {
71
- const mostRecent = recentDuplicates[recentDuplicates.length - 1];
72
- const timeSince = now - mostRecent.timestamp;
73
- if (this.config.debug) {
74
- console.log(`[CommandDeduplicator] 🚫 DUPLICATE DETECTED!`);
75
- console.log(` Command: ${command}`);
76
- console.log(` Args: ${JSON.stringify(args)}`);
77
- console.log(` Time since last: ${timeSince}ms`);
78
- console.log(` Window: ${this.config.windowMs}ms`);
79
- }
80
- // Increment duplicate counter
81
- this.cache.totalDuplicatesBlocked++;
82
- await this.saveCache();
83
- return true;
84
- }
85
- return false;
86
- }
87
- /**
88
- * Record command invocation
89
- *
90
- * @param command - Command name
91
- * @param args - Command arguments
92
- */
93
- async recordInvocation(command, args = []) {
94
- const fingerprint = this.createFingerprint(command, args);
95
- const now = Date.now();
96
- const record = {
97
- fingerprint,
98
- command,
99
- args,
100
- timestamp: now,
101
- date: new Date(now).toISOString()
102
- };
103
- this.cache.invocations.push(record);
104
- this.cache.totalInvocations++;
105
- if (this.config.debug) {
106
- console.log(`[CommandDeduplicator] ✅ Recorded invocation: ${command} ${args.join(' ')}`);
107
- }
108
- // Trigger cleanup if needed
109
- if (now - this.lastCleanupCheck > this.config.cleanupIntervalMs) {
110
- await this.cleanup();
111
- this.lastCleanupCheck = now;
112
- }
113
- await this.saveCache();
114
- }
115
- /**
116
- * Create unique fingerprint for command + args
117
- *
118
- * @param command - Command name
119
- * @param args - Command arguments
120
- * @returns SHA256 hash of command + args
121
- */
122
- createFingerprint(command, args) {
123
- const data = JSON.stringify({ command, args });
124
- return crypto.createHash('sha256').update(data).digest('hex');
125
- }
126
- /**
127
- * Load cache from disk
128
- *
129
- * @returns Invocation cache
130
- */
131
- loadCache() {
132
- try {
133
- if (fs.existsSync(this.config.cachePath)) {
134
- const data = fs.readJsonSync(this.config.cachePath);
135
- if (this.config.debug) {
136
- console.log(`[CommandDeduplicator] 📂 Loaded cache: ${data.invocations.length} invocations`);
137
- }
138
- return data;
139
- }
140
- }
141
- catch (error) {
142
- if (this.config.debug) {
143
- console.log(`[CommandDeduplicator] ⚠️ Failed to load cache: ${error}`);
144
- }
145
- }
146
- // Return empty cache
147
- return {
148
- invocations: [],
149
- lastCleanup: Date.now(),
150
- totalInvocations: 0,
151
- totalDuplicatesBlocked: 0
152
- };
153
- }
154
- /**
155
- * Save cache to disk
156
- */
157
- async saveCache() {
158
- try {
159
- await fs.ensureDir(path.dirname(this.config.cachePath));
160
- await fs.writeJson(this.config.cachePath, this.cache, { spaces: 2 });
161
- if (this.config.debug) {
162
- console.log(`[CommandDeduplicator] 💾 Saved cache: ${this.cache.invocations.length} invocations`);
163
- }
164
- }
165
- catch (error) {
166
- console.error(`[CommandDeduplicator] ❌ Failed to save cache: ${error}`);
167
- }
168
- }
169
- /**
170
- * Clean up old invocation records
171
- *
172
- * Removes records older than 10x the deduplication window to prevent cache bloat.
173
- */
174
- async cleanup() {
175
- const now = Date.now();
176
- const cutoff = now - (this.config.windowMs * 10); // Keep 10x window
177
- const before = this.cache.invocations.length;
178
- this.cache.invocations = this.cache.invocations.filter(inv => inv.timestamp >= cutoff);
179
- const after = this.cache.invocations.length;
180
- // Also enforce max cache size
181
- if (this.cache.invocations.length > this.config.maxCacheSize) {
182
- this.cache.invocations = this.cache.invocations.slice(-this.config.maxCacheSize);
183
- }
184
- this.cache.lastCleanup = now;
185
- if (this.config.debug && before > after) {
186
- console.log(`[CommandDeduplicator] 🧹 Cleanup: Removed ${before - after} old records`);
187
- }
188
- await this.saveCache();
189
- }
190
- /**
191
- * Get statistics about deduplication
192
- *
193
- * @returns Statistics object
194
- */
195
- getStats() {
196
- return {
197
- totalInvocations: this.cache.totalInvocations,
198
- totalDuplicatesBlocked: this.cache.totalDuplicatesBlocked,
199
- currentCacheSize: this.cache.invocations.length,
200
- lastCleanup: new Date(this.cache.lastCleanup).toISOString()
201
- };
202
- }
203
- /**
204
- * Clear all cached invocations (useful for testing)
205
- */
206
- async clear() {
207
- this.cache = {
208
- invocations: [],
209
- lastCleanup: Date.now(),
210
- totalInvocations: 0,
211
- totalDuplicatesBlocked: 0
212
- };
213
- await this.saveCache();
214
- if (this.config.debug) {
215
- console.log(`[CommandDeduplicator] 🗑️ Cache cleared`);
216
- }
217
- }
218
- }
219
- /**
220
- * Global singleton instance (convenience)
221
- */
222
- let globalInstance = null;
223
- /**
224
- * Get global deduplicator instance
225
- *
226
- * @param config - Configuration (only used on first call)
227
- * @returns Global deduplicator instance
228
- */
229
- export function getGlobalDeduplicator(config) {
230
- if (!globalInstance) {
231
- globalInstance = new CommandDeduplicator(config);
232
- }
233
- return globalInstance;
234
- }
235
- /**
236
- * Convenience function: Check if command is duplicate
237
- *
238
- * @param command - Command name
239
- * @param args - Command arguments
240
- * @returns true if duplicate, false otherwise
241
- */
242
- export async function isDuplicate(command, args = []) {
243
- return await getGlobalDeduplicator().checkDuplicate(command, args);
244
- }
245
- /**
246
- * Convenience function: Record command invocation
247
- *
248
- * @param command - Command name
249
- * @param args - Command arguments
250
- */
251
- export async function recordCommand(command, args = []) {
252
- await getGlobalDeduplicator().recordInvocation(command, args);
253
- }
254
- //# sourceMappingURL=command-deduplicator.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"command-deduplicator.js","sourceRoot":"","sources":["../../../../src/core/deduplication/command-deduplicator.ts"],"names":[],"mappings":"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"}
@@ -1,86 +0,0 @@
1
- #!/bin/bash
2
-
3
- # SpecWeave Pre-Command Deduplication Hook
4
- # Fires BEFORE any command executes (UserPromptSubmit hook)
5
- # Purpose: Prevent duplicate command invocations within configurable time window
6
-
7
- set -euo pipefail
8
-
9
- # Read input JSON from stdin
10
- INPUT=$(cat)
11
-
12
- # Extract prompt from JSON
13
- PROMPT=$(echo "$INPUT" | node -e "
14
- const input = JSON.parse(require('fs').readFileSync(0, 'utf-8'));
15
- console.log(input.prompt || '');
16
- ")
17
-
18
- # ==============================================================================
19
- # DEDUPLICATION CHECK: Block duplicate commands within 1 second
20
- # ==============================================================================
21
-
22
- # Extract command name from prompt (if slash command)
23
- COMMAND=$(echo "$PROMPT" | grep -oE "^/[a-z0-9:-]+" | head -1 || echo "")
24
-
25
- if [[ -n "$COMMAND" ]]; then
26
- # Check deduplication using TypeScript module
27
- if command -v node >/dev/null 2>&1 && [[ -f "dist/src/core/deduplication/command-deduplicator.js" ]]; then
28
- # Run deduplication check
29
- DEDUP_RESULT=$(node -e "
30
- (async () => {
31
- try {
32
- const { CommandDeduplicator } = require('./dist/src/core/deduplication/command-deduplicator.js');
33
- const dedup = new CommandDeduplicator({ debug: false });
34
-
35
- // Parse command and args
36
- const fullCommand = '${COMMAND}';
37
- const args = '${PROMPT}'.replace(fullCommand, '').trim().split(/\\s+/).filter(Boolean);
38
-
39
- // Check for duplicate
40
- const isDuplicate = await dedup.checkDuplicate(fullCommand, args);
41
-
42
- if (isDuplicate) {
43
- const stats = dedup.getStats();
44
- console.log('DUPLICATE');
45
- console.log(JSON.stringify(stats));
46
- } else {
47
- // Record invocation
48
- await dedup.recordInvocation(fullCommand, args);
49
- console.log('OK');
50
- }
51
- } catch (e) {
52
- console.error('Error in deduplication:', e.message);
53
- console.log('OK'); // Fail-open (don't block on errors)
54
- }
55
- })();
56
- " 2>/dev/null || echo "OK")
57
-
58
- # Parse result
59
- STATUS=$(echo "$DEDUP_RESULT" | head -1)
60
-
61
- if [[ "$STATUS" == "DUPLICATE" ]]; then
62
- # Get stats
63
- STATS=$(echo "$DEDUP_RESULT" | tail -1)
64
-
65
- cat <<EOF
66
- {
67
- "decision": "block",
68
- "reason": "🚫 DUPLICATE COMMAND DETECTED\\n\\nCommand: \`$COMMAND\`\\nTime window: 1 second\\n\\nThis command was just executed! To prevent unintended duplicates, this invocation has been blocked.\\n\\n💡 If you meant to run this command again:\\n 1. Wait 1 second\\n 2. Run the command again\\n\\n📊 Deduplication Stats:\\n$STATS\\n\\n🔧 To adjust the time window, edit \`.specweave/config.json\`:\\n\`\`\`json\\n{\\n \\\"deduplication\\\": {\\n \\\"windowMs\\\": 2000 // Increase to 2 seconds\\n }\\n}\\n\`\`\`"
69
- }
70
- EOF
71
- exit 0
72
- fi
73
- fi
74
- fi
75
-
76
- # ==============================================================================
77
- # PASS THROUGH: No duplicate detected, proceed with command
78
- # ==============================================================================
79
-
80
- cat <<EOF
81
- {
82
- "decision": "approve"
83
- }
84
- EOF
85
-
86
- exit 0
@@ -1,134 +0,0 @@
1
- import { EnhancedContentBuilder } from "../../../src/core/sync/enhanced-content-builder.js";
2
- import { SpecIncrementMapper } from "../../../src/core/sync/spec-increment-mapper.js";
3
- import { parseSpecContent } from "../../../src/core/spec-content-sync.js";
4
- import * as path from "path";
5
- import * as fs from "fs/promises";
6
- async function syncSpecToJiraWithEnhancedContent(options) {
7
- const { specPath, domain, project, dryRun = false, verbose = false } = options;
8
- try {
9
- const baseSpec = await parseSpecContent(specPath);
10
- if (!baseSpec) {
11
- return {
12
- success: false,
13
- action: "error",
14
- error: "Failed to parse spec content"
15
- };
16
- }
17
- if (verbose) {
18
- console.log(`\u{1F4C4} Parsed spec: ${baseSpec.identifier.compact}`);
19
- }
20
- const specId = baseSpec.identifier.full || baseSpec.identifier.compact;
21
- const rootDir = await findSpecWeaveRoot(specPath);
22
- const mapper = new SpecIncrementMapper(rootDir);
23
- const mapping = await mapper.mapSpecToIncrements(specId);
24
- if (verbose) {
25
- console.log(`\u{1F517} Found ${mapping.increments.length} related increments`);
26
- }
27
- const taskMapping = buildTaskMapping(mapping.increments);
28
- const architectureDocs = await findArchitectureDocs(rootDir, specId);
29
- const enhancedSpec = {
30
- ...baseSpec,
31
- summary: baseSpec.description,
32
- taskMapping,
33
- architectureDocs
34
- };
35
- const builder = new EnhancedContentBuilder();
36
- const description = builder.buildExternalDescription(enhancedSpec);
37
- if (verbose) {
38
- console.log(`\u{1F4DD} Generated description: ${description.length} characters`);
39
- }
40
- if (dryRun) {
41
- console.log("\u{1F50D} DRY RUN - Would create/update epic with:");
42
- console.log(` Summary: ${baseSpec.title}`);
43
- console.log(` Description length: ${description.length}`);
44
- return {
45
- success: true,
46
- action: "no-change",
47
- tasksLinked: taskMapping?.tasks.length || 0
48
- };
49
- }
50
- if (!dryRun && (!domain || !project)) {
51
- return {
52
- success: false,
53
- action: "error",
54
- error: "JIRA domain/project not specified (required for actual sync)"
55
- };
56
- }
57
- const result = {
58
- success: true,
59
- action: dryRun ? "no-change" : "created",
60
- // Assume create if not dry run
61
- tasksLinked: taskMapping?.tasks.length || 0
62
- };
63
- if (domain && project && !dryRun) {
64
- result.epicKey = `SPEC-001`;
65
- result.epicUrl = `https://${domain}/browse/SPEC-001`;
66
- if (verbose) {
67
- console.log(`\u26A0\uFE0F JIRA API integration not implemented in this file`);
68
- console.log(` Use jira-spec-sync.ts for actual JIRA synchronization`);
69
- }
70
- }
71
- return result;
72
- } catch (error) {
73
- return {
74
- success: false,
75
- action: "error",
76
- error: error.message
77
- };
78
- }
79
- }
80
- async function findSpecWeaveRoot(specPath) {
81
- let currentDir = path.dirname(specPath);
82
- while (true) {
83
- const specweaveDir = path.join(currentDir, ".specweave");
84
- try {
85
- await fs.access(specweaveDir);
86
- return currentDir;
87
- } catch {
88
- const parentDir = path.dirname(currentDir);
89
- if (parentDir === currentDir) {
90
- throw new Error(".specweave directory not found");
91
- }
92
- currentDir = parentDir;
93
- }
94
- }
95
- }
96
- function buildTaskMapping(increments) {
97
- if (increments.length === 0) return void 0;
98
- const firstIncrement = increments[0];
99
- const tasks = firstIncrement.tasks.map((task) => ({
100
- id: task.id,
101
- title: task.title,
102
- userStories: task.userStories
103
- }));
104
- return {
105
- incrementId: firstIncrement.id,
106
- tasks,
107
- tasksUrl: `tasks.md`
108
- // JIRA doesn't support external links in same way
109
- };
110
- }
111
- async function findArchitectureDocs(rootDir, specId) {
112
- const docs = [];
113
- const archDir = path.join(rootDir, ".specweave/docs/internal/architecture");
114
- try {
115
- const adrDir = path.join(archDir, "adr");
116
- try {
117
- const adrs = await fs.readdir(adrDir);
118
- const relatedAdrs = adrs.filter((file) => file.includes(specId.replace("spec-", "")));
119
- for (const adr of relatedAdrs) {
120
- docs.push({
121
- type: "adr",
122
- path: path.join(adrDir, adr),
123
- title: adr.replace(".md", "").replace(/-/g, " ")
124
- });
125
- }
126
- } catch {
127
- }
128
- } catch {
129
- }
130
- return docs;
131
- }
132
- export {
133
- syncSpecToJiraWithEnhancedContent
134
- };