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.
- package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.js +6 -65
- package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts +6 -8
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js +117 -78
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js.map +1 -1
- package/dist/src/core/types/config.d.ts +0 -20
- package/dist/src/core/types/config.d.ts.map +1 -1
- package/dist/src/core/types/config.js +0 -7
- package/dist/src/core/types/config.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/hooks/hooks.json +0 -4
- package/plugins/specweave/hooks/lib/sync-spec-content.sh +2 -2
- package/plugins/specweave-ado/commands/specweave-ado-sync-spec.md +1 -1
- package/plugins/specweave-ado/lib/ado-spec-content-sync.js +5 -49
- package/plugins/specweave-ado/lib/ado-spec-content-sync.ts +6 -72
- package/plugins/specweave-github/commands/specweave-github-cleanup-duplicates.md +1 -1
- package/plugins/specweave-github/commands/specweave-github-sync-epic.md +1 -1
- package/plugins/specweave-github/commands/specweave-github-sync-spec.md +1 -1
- package/plugins/specweave-jira/commands/specweave-jira-sync-epic.md +1 -1
- package/plugins/specweave-jira/commands/specweave-jira-sync-spec.md +1 -1
- package/plugins/specweave-jira/lib/{enhanced-jira-sync.ts → enhanced-jira-sync.ts.disabled} +52 -26
- package/plugins/specweave-release/commands/specweave-release-platform.md +1 -1
- package/plugins/specweave-release/hooks/post-task-completion.sh +2 -2
- package/dist/src/core/deduplication/command-deduplicator.d.ts +0 -166
- package/dist/src/core/deduplication/command-deduplicator.d.ts.map +0 -1
- package/dist/src/core/deduplication/command-deduplicator.js +0 -254
- package/dist/src/core/deduplication/command-deduplicator.js.map +0 -1
- package/plugins/specweave/hooks/pre-command-deduplication.sh +0 -86
- package/plugins/specweave-jira/lib/enhanced-jira-sync.js +0 -134
|
@@ -4,9 +4,8 @@ import {
|
|
|
4
4
|
hasExternalLink,
|
|
5
5
|
updateSpecWithExternalLink
|
|
6
6
|
} from "../../../src/core/spec-content-sync.js";
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import * as fs from "fs/promises";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import fs from "fs/promises";
|
|
10
9
|
async function syncSpecContentToAdo(options) {
|
|
11
10
|
const { specPath, client, dryRun = false, verbose = false } = options;
|
|
12
11
|
try {
|
|
@@ -45,9 +44,8 @@ async function syncSpecContentToAdo(options) {
|
|
|
45
44
|
async function createAdoFeature(client, spec, options) {
|
|
46
45
|
const { specPath, dryRun, verbose } = options;
|
|
47
46
|
try {
|
|
48
|
-
const tasks = await getTaskMappings(specPath, spec.id);
|
|
49
47
|
const title = `[${spec.id.toUpperCase()}] ${spec.title}`;
|
|
50
|
-
const description = buildAdoDescription(spec
|
|
48
|
+
const description = buildAdoDescription(spec);
|
|
51
49
|
if (verbose) {
|
|
52
50
|
console.log(`
|
|
53
51
|
\u{1F4DD} Creating ADO feature:`);
|
|
@@ -127,9 +125,8 @@ async function updateAdoFeature(client, spec, workItemId, options) {
|
|
|
127
125
|
console.log(` - ${change}`);
|
|
128
126
|
}
|
|
129
127
|
}
|
|
130
|
-
const tasks = await getTaskMappings(specPath, spec.id);
|
|
131
128
|
const newTitle = `[${spec.id.toUpperCase()}] ${spec.title}`;
|
|
132
|
-
const newDescription = buildAdoDescription(spec
|
|
129
|
+
const newDescription = buildAdoDescription(spec);
|
|
133
130
|
if (dryRun) {
|
|
134
131
|
console.log("\n\u{1F50D} Dry run - would update feature:");
|
|
135
132
|
console.log(` Title: ${newTitle}`);
|
|
@@ -163,7 +160,7 @@ ${newDescription}`);
|
|
|
163
160
|
};
|
|
164
161
|
}
|
|
165
162
|
}
|
|
166
|
-
function buildAdoDescription(spec
|
|
163
|
+
function buildAdoDescription(spec) {
|
|
167
164
|
let html = "";
|
|
168
165
|
if (spec.description) {
|
|
169
166
|
html += `<p>${escapeHtml(spec.description)}</p>`;
|
|
@@ -182,52 +179,11 @@ function buildAdoDescription(spec, tasks) {
|
|
|
182
179
|
}
|
|
183
180
|
}
|
|
184
181
|
}
|
|
185
|
-
if (tasks && tasks.length > 0) {
|
|
186
|
-
html += "<h2>Implementation Tasks</h2>";
|
|
187
|
-
html += "<ul>";
|
|
188
|
-
for (const task of tasks) {
|
|
189
|
-
html += `<li><strong>${task.id}</strong>: ${escapeHtml(task.title)}`;
|
|
190
|
-
if (task.userStories && task.userStories.length > 0) {
|
|
191
|
-
html += ` (${task.userStories.join(", ")})`;
|
|
192
|
-
}
|
|
193
|
-
html += "</li>";
|
|
194
|
-
}
|
|
195
|
-
html += "</ul>";
|
|
196
|
-
}
|
|
197
182
|
if (spec.metadata.priority) {
|
|
198
183
|
html += `<p><strong>Priority:</strong> ${spec.metadata.priority}</p>`;
|
|
199
184
|
}
|
|
200
185
|
return html;
|
|
201
186
|
}
|
|
202
|
-
async function getTaskMappings(specPath, specId) {
|
|
203
|
-
try {
|
|
204
|
-
const rootDir = await findSpecWeaveRoot(specPath);
|
|
205
|
-
const mapper = new SpecIncrementMapper(rootDir);
|
|
206
|
-
const mapping = await mapper.mapSpecToIncrements(specId);
|
|
207
|
-
if (mapping.increments.length > 0) {
|
|
208
|
-
return mapping.increments[0].tasks;
|
|
209
|
-
}
|
|
210
|
-
return void 0;
|
|
211
|
-
} catch (error) {
|
|
212
|
-
return void 0;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
async function findSpecWeaveRoot(specPath) {
|
|
216
|
-
let currentDir = path.dirname(specPath);
|
|
217
|
-
while (true) {
|
|
218
|
-
const specweaveDir = path.join(currentDir, ".specweave");
|
|
219
|
-
try {
|
|
220
|
-
await fs.access(specweaveDir);
|
|
221
|
-
return currentDir;
|
|
222
|
-
} catch {
|
|
223
|
-
const parentDir = path.dirname(currentDir);
|
|
224
|
-
if (parentDir === currentDir) {
|
|
225
|
-
throw new Error(".specweave directory not found");
|
|
226
|
-
}
|
|
227
|
-
currentDir = parentDir;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
187
|
function escapeHtml(text) {
|
|
232
188
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
233
189
|
}
|
|
@@ -20,9 +20,8 @@ import {
|
|
|
20
20
|
ContentSyncResult,
|
|
21
21
|
} from '../../../src/core/spec-content-sync.js';
|
|
22
22
|
import { SyncProfile } from '../../../src/core/types/sync-profile.js';
|
|
23
|
-
import
|
|
24
|
-
import
|
|
25
|
-
import * as fs from 'fs/promises';
|
|
23
|
+
import path from 'path';
|
|
24
|
+
import fs from 'fs/promises';
|
|
26
25
|
|
|
27
26
|
export interface AdoContentSyncOptions {
|
|
28
27
|
specPath: string;
|
|
@@ -92,12 +91,9 @@ async function createAdoFeature(
|
|
|
92
91
|
const { specPath, dryRun, verbose } = options;
|
|
93
92
|
|
|
94
93
|
try {
|
|
95
|
-
// Get task mappings (if available)
|
|
96
|
-
const tasks = await getTaskMappings(specPath, spec.id);
|
|
97
|
-
|
|
98
94
|
// Build feature title and description
|
|
99
95
|
const title = `[${spec.id.toUpperCase()}] ${spec.title}`;
|
|
100
|
-
const description = buildAdoDescription(spec
|
|
96
|
+
const description = buildAdoDescription(spec);
|
|
101
97
|
|
|
102
98
|
if (verbose) {
|
|
103
99
|
console.log(`\n📝 Creating ADO feature:`);
|
|
@@ -200,12 +196,9 @@ async function updateAdoFeature(
|
|
|
200
196
|
}
|
|
201
197
|
}
|
|
202
198
|
|
|
203
|
-
// Get task mappings (if available)
|
|
204
|
-
const tasks = await getTaskMappings(specPath, spec.id);
|
|
205
|
-
|
|
206
199
|
// Build updated content
|
|
207
200
|
const newTitle = `[${spec.id.toUpperCase()}] ${spec.title}`;
|
|
208
|
-
const newDescription = buildAdoDescription(spec
|
|
201
|
+
const newDescription = buildAdoDescription(spec);
|
|
209
202
|
|
|
210
203
|
if (dryRun) {
|
|
211
204
|
console.log('\n🔍 Dry run - would update feature:');
|
|
@@ -249,10 +242,10 @@ async function updateAdoFeature(
|
|
|
249
242
|
}
|
|
250
243
|
|
|
251
244
|
/**
|
|
252
|
-
* Build ADO description from spec content
|
|
245
|
+
* Build ADO description from spec content
|
|
253
246
|
* ADO supports HTML in description
|
|
254
247
|
*/
|
|
255
|
-
function buildAdoDescription(spec: SpecContent
|
|
248
|
+
function buildAdoDescription(spec: SpecContent): string {
|
|
256
249
|
let html = '';
|
|
257
250
|
|
|
258
251
|
// Add spec description
|
|
@@ -278,20 +271,6 @@ function buildAdoDescription(spec: SpecContent, tasks?: TaskInfo[]): string {
|
|
|
278
271
|
}
|
|
279
272
|
}
|
|
280
273
|
|
|
281
|
-
// Add task mappings (if provided)
|
|
282
|
-
if (tasks && tasks.length > 0) {
|
|
283
|
-
html += '<h2>Implementation Tasks</h2>';
|
|
284
|
-
html += '<ul>';
|
|
285
|
-
for (const task of tasks) {
|
|
286
|
-
html += `<li><strong>${task.id}</strong>: ${escapeHtml(task.title)}`;
|
|
287
|
-
if (task.userStories && task.userStories.length > 0) {
|
|
288
|
-
html += ` (${task.userStories.join(', ')})`;
|
|
289
|
-
}
|
|
290
|
-
html += '</li>';
|
|
291
|
-
}
|
|
292
|
-
html += '</ul>';
|
|
293
|
-
}
|
|
294
|
-
|
|
295
274
|
// Add metadata
|
|
296
275
|
if (spec.metadata.priority) {
|
|
297
276
|
html += `<p><strong>Priority:</strong> ${spec.metadata.priority}</p>`;
|
|
@@ -300,51 +279,6 @@ function buildAdoDescription(spec: SpecContent, tasks?: TaskInfo[]): string {
|
|
|
300
279
|
return html;
|
|
301
280
|
}
|
|
302
281
|
|
|
303
|
-
/**
|
|
304
|
-
* Get task mappings for a spec (if available)
|
|
305
|
-
*/
|
|
306
|
-
async function getTaskMappings(specPath: string, specId: string): Promise<TaskInfo[] | undefined> {
|
|
307
|
-
try {
|
|
308
|
-
// Find SpecWeave root
|
|
309
|
-
const rootDir = await findSpecWeaveRoot(specPath);
|
|
310
|
-
|
|
311
|
-
// Use SpecIncrementMapper to get task mappings
|
|
312
|
-
const mapper = new SpecIncrementMapper(rootDir);
|
|
313
|
-
const mapping = await mapper.mapSpecToIncrements(specId);
|
|
314
|
-
|
|
315
|
-
if (mapping.increments.length > 0) {
|
|
316
|
-
// Return tasks from the first (most recent) increment
|
|
317
|
-
return mapping.increments[0].tasks;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
return undefined;
|
|
321
|
-
} catch (error) {
|
|
322
|
-
// If mapping fails, just return undefined (not critical)
|
|
323
|
-
return undefined;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Find SpecWeave root directory from spec path
|
|
329
|
-
*/
|
|
330
|
-
async function findSpecWeaveRoot(specPath: string): Promise<string> {
|
|
331
|
-
let currentDir = path.dirname(specPath);
|
|
332
|
-
|
|
333
|
-
while (true) {
|
|
334
|
-
const specweaveDir = path.join(currentDir, '.specweave');
|
|
335
|
-
try {
|
|
336
|
-
await fs.access(specweaveDir);
|
|
337
|
-
return currentDir;
|
|
338
|
-
} catch {
|
|
339
|
-
const parentDir = path.dirname(currentDir);
|
|
340
|
-
if (parentDir === currentDir) {
|
|
341
|
-
throw new Error('.specweave directory not found');
|
|
342
|
-
}
|
|
343
|
-
currentDir = parentDir;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
282
|
/**
|
|
349
283
|
* Escape HTML special characters
|
|
350
284
|
*/
|
|
@@ -2,16 +2,14 @@
|
|
|
2
2
|
* Enhanced JIRA Spec Content Sync
|
|
3
3
|
*
|
|
4
4
|
* Uses EnhancedContentBuilder and SpecIncrementMapper for rich epic descriptions.
|
|
5
|
-
*
|
|
6
|
-
* NOTE: This version focuses on enhanced content building.
|
|
7
|
-
* Actual JIRA API integration requires jira-spec-sync.ts
|
|
8
5
|
*/
|
|
9
6
|
|
|
7
|
+
import { JiraClientV2 } from './jira-client-v2.js';
|
|
10
8
|
import { EnhancedContentBuilder, EnhancedSpecContent } from '../../../src/core/sync/enhanced-content-builder.js';
|
|
11
9
|
import { SpecIncrementMapper, TaskInfo } from '../../../src/core/sync/spec-increment-mapper.js';
|
|
12
10
|
import { parseSpecContent } from '../../../src/core/spec-content-sync.js';
|
|
13
|
-
import
|
|
14
|
-
import
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import fs from 'fs/promises';
|
|
15
13
|
|
|
16
14
|
export interface EnhancedJiraSyncOptions {
|
|
17
15
|
specPath: string;
|
|
@@ -93,33 +91,55 @@ export async function syncSpecToJiraWithEnhancedContent(
|
|
|
93
91
|
};
|
|
94
92
|
}
|
|
95
93
|
|
|
96
|
-
// 5.
|
|
97
|
-
if (!
|
|
94
|
+
// 5. Create or update JIRA epic
|
|
95
|
+
if (!domain || !project) {
|
|
98
96
|
return {
|
|
99
97
|
success: false,
|
|
100
98
|
action: 'error',
|
|
101
|
-
error: 'JIRA domain/project not specified
|
|
99
|
+
error: 'JIRA domain/project not specified',
|
|
102
100
|
};
|
|
103
101
|
}
|
|
104
102
|
|
|
105
|
-
|
|
106
|
-
// Actual JIRA API integration is in jira-spec-sync.ts
|
|
107
|
-
const result: EnhancedJiraSyncResult = {
|
|
108
|
-
success: true,
|
|
109
|
-
action: dryRun ? 'no-change' : 'created', // Assume create if not dry run
|
|
110
|
-
tasksLinked: taskMapping?.tasks.length || 0
|
|
111
|
-
};
|
|
103
|
+
const client = new JiraClientV2({ domain, project });
|
|
112
104
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// For now, just simulate success
|
|
116
|
-
result.epicKey = `SPEC-001`; // Placeholder
|
|
117
|
-
result.epicUrl = `https://${domain}/browse/SPEC-001`;
|
|
105
|
+
// Check if epic already exists
|
|
106
|
+
const existingEpic = await findExistingEpic(client, baseSpec.identifier.compact);
|
|
118
107
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
108
|
+
let result: EnhancedJiraSyncResult;
|
|
109
|
+
|
|
110
|
+
if (existingEpic) {
|
|
111
|
+
// Update existing epic
|
|
112
|
+
await client.updateEpic(existingEpic.key, {
|
|
113
|
+
summary: `[${baseSpec.identifier.compact}] ${baseSpec.title}`,
|
|
114
|
+
description
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
result = {
|
|
118
|
+
success: true,
|
|
119
|
+
action: 'updated',
|
|
120
|
+
epicKey: existingEpic.key,
|
|
121
|
+
epicUrl: `https://${domain}/browse/${existingEpic.key}`,
|
|
122
|
+
tasksLinked: taskMapping?.tasks.length || 0
|
|
123
|
+
};
|
|
124
|
+
} else {
|
|
125
|
+
// Create new epic
|
|
126
|
+
const epic = await client.createEpic({
|
|
127
|
+
summary: `[${baseSpec.identifier.compact}] ${baseSpec.title}`,
|
|
128
|
+
description,
|
|
129
|
+
labels: ['spec', 'external-tool-sync']
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
result = {
|
|
133
|
+
success: true,
|
|
134
|
+
action: 'created',
|
|
135
|
+
epicKey: epic.key,
|
|
136
|
+
epicUrl: `https://${domain}/browse/${epic.key}`,
|
|
137
|
+
tasksLinked: taskMapping?.tasks.length || 0
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (verbose) {
|
|
142
|
+
console.log(`✅ ${result.action === 'created' ? 'Created' : 'Updated'} epic ${result.epicKey}`);
|
|
123
143
|
}
|
|
124
144
|
|
|
125
145
|
return result;
|
|
@@ -192,5 +212,11 @@ async function findArchitectureDocs(rootDir: string, specId: string): Promise<an
|
|
|
192
212
|
return docs;
|
|
193
213
|
}
|
|
194
214
|
|
|
195
|
-
|
|
196
|
-
|
|
215
|
+
async function findExistingEpic(client: JiraClientV2, specId: string): Promise<any | null> {
|
|
216
|
+
try {
|
|
217
|
+
const epics = await client.searchEpics(`summary ~ "[${specId}]"`);
|
|
218
|
+
return epics[0] || null;
|
|
219
|
+
} catch {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -13,7 +13,7 @@ SPECWEAVE_ROOT="${SPECWEAVE_ROOT:-$(pwd)}"
|
|
|
13
13
|
METRICS_DIR="${SPECWEAVE_ROOT}/.specweave/metrics"
|
|
14
14
|
HISTORY_FILE="${METRICS_DIR}/dora-history.jsonl"
|
|
15
15
|
DASHBOARD_FILE="${SPECWEAVE_ROOT}/.specweave/docs/internal/delivery/dora-dashboard.md"
|
|
16
|
-
DORA_CALCULATOR="${SPECWEAVE_ROOT}/dist/
|
|
16
|
+
DORA_CALCULATOR="${SPECWEAVE_ROOT}/dist/metrics/dora-calculator.js"
|
|
17
17
|
LATEST_FILE="${METRICS_DIR}/dora-latest.json"
|
|
18
18
|
|
|
19
19
|
# Logging
|
|
@@ -60,7 +60,7 @@ fi
|
|
|
60
60
|
|
|
61
61
|
# Step 3: Update living docs dashboard
|
|
62
62
|
log "📝 Updating DORA dashboard..."
|
|
63
|
-
DASHBOARD_GENERATOR="${SPECWEAVE_ROOT}/dist/
|
|
63
|
+
DASHBOARD_GENERATOR="${SPECWEAVE_ROOT}/dist/metrics/dashboard-generator.js"
|
|
64
64
|
|
|
65
65
|
if [[ -f "$DASHBOARD_GENERATOR" ]]; then
|
|
66
66
|
if node "$DASHBOARD_GENERATOR"; then
|
|
@@ -1,166 +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
|
-
/**
|
|
32
|
-
* Invocation record stored in cache
|
|
33
|
-
*/
|
|
34
|
-
export interface InvocationRecord {
|
|
35
|
-
/** Unique fingerprint of command + args */
|
|
36
|
-
fingerprint: string;
|
|
37
|
-
/** Command name (e.g., '/specweave:do') */
|
|
38
|
-
command: string;
|
|
39
|
-
/** Command arguments */
|
|
40
|
-
args: string[];
|
|
41
|
-
/** Timestamp when invoked (ms since epoch) */
|
|
42
|
-
timestamp: number;
|
|
43
|
-
/** Human-readable timestamp */
|
|
44
|
-
date: string;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Cache structure
|
|
48
|
-
*/
|
|
49
|
-
export interface InvocationCache {
|
|
50
|
-
/** List of invocation records */
|
|
51
|
-
invocations: InvocationRecord[];
|
|
52
|
-
/** Last cleanup timestamp */
|
|
53
|
-
lastCleanup: number;
|
|
54
|
-
/** Total invocations tracked */
|
|
55
|
-
totalInvocations: number;
|
|
56
|
-
/** Total duplicates blocked */
|
|
57
|
-
totalDuplicatesBlocked: number;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Configuration for deduplication
|
|
61
|
-
*/
|
|
62
|
-
export interface DeduplicationConfig {
|
|
63
|
-
/** Time window in milliseconds to check for duplicates (default: 1000ms) */
|
|
64
|
-
windowMs?: number;
|
|
65
|
-
/** Path to cache file (default: .specweave/state/command-invocations.json) */
|
|
66
|
-
cachePath?: string;
|
|
67
|
-
/** Maximum cache entries before cleanup (default: 1000) */
|
|
68
|
-
maxCacheSize?: number;
|
|
69
|
-
/** Enable debug logging (default: false) */
|
|
70
|
-
debug?: boolean;
|
|
71
|
-
/** Cleanup interval in milliseconds (default: 60000ms = 1 minute) */
|
|
72
|
-
cleanupIntervalMs?: number;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Global command deduplication system
|
|
76
|
-
*/
|
|
77
|
-
export declare class CommandDeduplicator {
|
|
78
|
-
private projectRoot;
|
|
79
|
-
private config;
|
|
80
|
-
private cache;
|
|
81
|
-
private lastCleanupCheck;
|
|
82
|
-
/**
|
|
83
|
-
* Create new deduplicator instance
|
|
84
|
-
*
|
|
85
|
-
* @param config - Configuration options
|
|
86
|
-
* @param projectRoot - Project root directory (default: process.cwd())
|
|
87
|
-
*/
|
|
88
|
-
constructor(config?: DeduplicationConfig, projectRoot?: string);
|
|
89
|
-
/**
|
|
90
|
-
* Check if command invocation is a duplicate
|
|
91
|
-
*
|
|
92
|
-
* @param command - Command name (e.g., '/specweave:do')
|
|
93
|
-
* @param args - Command arguments
|
|
94
|
-
* @returns true if duplicate detected, false otherwise
|
|
95
|
-
*/
|
|
96
|
-
checkDuplicate(command: string, args?: string[]): Promise<boolean>;
|
|
97
|
-
/**
|
|
98
|
-
* Record command invocation
|
|
99
|
-
*
|
|
100
|
-
* @param command - Command name
|
|
101
|
-
* @param args - Command arguments
|
|
102
|
-
*/
|
|
103
|
-
recordInvocation(command: string, args?: string[]): Promise<void>;
|
|
104
|
-
/**
|
|
105
|
-
* Create unique fingerprint for command + args
|
|
106
|
-
*
|
|
107
|
-
* @param command - Command name
|
|
108
|
-
* @param args - Command arguments
|
|
109
|
-
* @returns SHA256 hash of command + args
|
|
110
|
-
*/
|
|
111
|
-
private createFingerprint;
|
|
112
|
-
/**
|
|
113
|
-
* Load cache from disk
|
|
114
|
-
*
|
|
115
|
-
* @returns Invocation cache
|
|
116
|
-
*/
|
|
117
|
-
private loadCache;
|
|
118
|
-
/**
|
|
119
|
-
* Save cache to disk
|
|
120
|
-
*/
|
|
121
|
-
private saveCache;
|
|
122
|
-
/**
|
|
123
|
-
* Clean up old invocation records
|
|
124
|
-
*
|
|
125
|
-
* Removes records older than 10x the deduplication window to prevent cache bloat.
|
|
126
|
-
*/
|
|
127
|
-
private cleanup;
|
|
128
|
-
/**
|
|
129
|
-
* Get statistics about deduplication
|
|
130
|
-
*
|
|
131
|
-
* @returns Statistics object
|
|
132
|
-
*/
|
|
133
|
-
getStats(): {
|
|
134
|
-
totalInvocations: number;
|
|
135
|
-
totalDuplicatesBlocked: number;
|
|
136
|
-
currentCacheSize: number;
|
|
137
|
-
lastCleanup: string;
|
|
138
|
-
};
|
|
139
|
-
/**
|
|
140
|
-
* Clear all cached invocations (useful for testing)
|
|
141
|
-
*/
|
|
142
|
-
clear(): Promise<void>;
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Get global deduplicator instance
|
|
146
|
-
*
|
|
147
|
-
* @param config - Configuration (only used on first call)
|
|
148
|
-
* @returns Global deduplicator instance
|
|
149
|
-
*/
|
|
150
|
-
export declare function getGlobalDeduplicator(config?: DeduplicationConfig): CommandDeduplicator;
|
|
151
|
-
/**
|
|
152
|
-
* Convenience function: Check if command is duplicate
|
|
153
|
-
*
|
|
154
|
-
* @param command - Command name
|
|
155
|
-
* @param args - Command arguments
|
|
156
|
-
* @returns true if duplicate, false otherwise
|
|
157
|
-
*/
|
|
158
|
-
export declare function isDuplicate(command: string, args?: string[]): Promise<boolean>;
|
|
159
|
-
/**
|
|
160
|
-
* Convenience function: Record command invocation
|
|
161
|
-
*
|
|
162
|
-
* @param command - Command name
|
|
163
|
-
* @param args - Command arguments
|
|
164
|
-
*/
|
|
165
|
-
export declare function recordCommand(command: string, args?: string[]): Promise<void>;
|
|
166
|
-
//# sourceMappingURL=command-deduplicator.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"command-deduplicator.d.ts","sourceRoot":"","sources":["../../../../src/core/deduplication/command-deduplicator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAMH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAC;IAEpB,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAEhB,wBAAwB;IACxB,IAAI,EAAE,MAAM,EAAE,CAAC;IAEf,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAElB,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,iCAAiC;IACjC,WAAW,EAAE,gBAAgB,EAAE,CAAC;IAEhC,6BAA6B;IAC7B,WAAW,EAAE,MAAM,CAAC;IAEpB,gCAAgC;IAChC,gBAAgB,EAAE,MAAM,CAAC;IAEzB,+BAA+B;IAC/B,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,qEAAqE;IACrE,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;GAEG;AACH,qBAAa,mBAAmB;IAWgB,OAAO,CAAC,WAAW;IAVjE,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,KAAK,CAAkB;IAC/B,OAAO,CAAC,gBAAgB,CAAa;IAErC;;;;;OAKG;gBACS,MAAM,GAAE,mBAAwB,EAAU,WAAW,GAAE,MAAsB;IAYzF;;;;;;OAMG;IACU,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAiCnF;;;;;OAKG;IACU,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BlF;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAKzB;;;;OAIG;IACH,OAAO,CAAC,SAAS;IA0BjB;;OAEG;YACW,SAAS;IAavB;;;;OAIG;YACW,OAAO;IAsBrB;;;;OAIG;IACI,QAAQ,IAAI;QACjB,gBAAgB,EAAE,MAAM,CAAC;QACzB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,gBAAgB,EAAE,MAAM,CAAC;QACzB,WAAW,EAAE,MAAM,CAAC;KACrB;IASD;;OAEG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAapC;AAOD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,mBAAmB,GAAG,mBAAmB,CAKvF;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAExF;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvF"}
|