agileflow 2.96.3 → 2.96.4

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/CHANGELOG.md CHANGED
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.96.4] - 2026-02-06
11
+
12
+ ### Added
13
+ - Command deduplication to fix duplicate slash commands in autocomplete
14
+
10
15
  ## [2.96.3] - 2026-02-06
11
16
 
12
17
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agileflow",
3
- "version": "2.96.3",
3
+ "version": "2.96.4",
4
4
  "description": "AI-driven agile development system for Claude Code, Cursor, Windsurf, and more",
5
5
  "keywords": [
6
6
  "agile",
@@ -208,6 +208,69 @@ class BaseIdeSetup {
208
208
  }
209
209
  }
210
210
  }
211
+
212
+ // Remove duplicate command files that exist outside the agileflow/ subfolder
213
+ // These cause "command 2" entries in Claude Code's autocomplete
214
+ await this.removeDuplicateCommands(projectDir);
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Remove duplicate AgileFlow command files from the parent commands directory.
220
+ * When commands exist both in .claude/commands/agileflow/foo.md AND .claude/commands/foo.md,
221
+ * Claude Code shows them as "foo" and "foo 2" in autocomplete. This removes the loose copies.
222
+ * @param {string} projectDir - Project directory
223
+ */
224
+ async removeDuplicateCommands(projectDir) {
225
+ const commandsDir = path.join(projectDir, this.configDir, 'commands');
226
+ const agileflowDir = path.join(commandsDir, 'agileflow');
227
+
228
+ if (!(await fs.pathExists(commandsDir))) return;
229
+
230
+ // Get list of AgileFlow command names (files and directories)
231
+ const agileflowNames = new Set();
232
+ if (await fs.pathExists(agileflowDir)) {
233
+ const entries = await fs.readdir(agileflowDir, { withFileTypes: true });
234
+ for (const entry of entries) {
235
+ agileflowNames.add(entry.name);
236
+ }
237
+ }
238
+
239
+ // Also collect names from the source commands to catch pre-existing stale copies
240
+ const coreCommandsDir = path.join(projectDir, this.agileflowFolder, 'commands');
241
+ if (await fs.pathExists(coreCommandsDir)) {
242
+ const entries = await fs.readdir(coreCommandsDir, { withFileTypes: true });
243
+ for (const entry of entries) {
244
+ agileflowNames.add(entry.name);
245
+ }
246
+ }
247
+
248
+ if (agileflowNames.size === 0) return;
249
+
250
+ // Scan the parent commands directory for loose duplicates
251
+ let removedCount = 0;
252
+ const entries = await fs.readdir(commandsDir, { withFileTypes: true });
253
+
254
+ for (const entry of entries) {
255
+ // Skip the agileflow subfolder itself and non-agileflow items
256
+ if (entry.name === 'agileflow' || entry.name === 'AgileFlow') continue;
257
+
258
+ // Check if this file/directory matches an AgileFlow command name
259
+ if (agileflowNames.has(entry.name)) {
260
+ const duplicatePath = path.join(commandsDir, entry.name);
261
+ try {
262
+ await fs.remove(duplicatePath);
263
+ removedCount++;
264
+ } catch {
265
+ // Best effort - don't fail setup over cleanup
266
+ }
267
+ }
268
+ }
269
+
270
+ if (removedCount > 0) {
271
+ console.log(
272
+ chalk.dim(` Removed ${removedCount} duplicate command(s) from ${this.displayName}`)
273
+ );
211
274
  }
212
275
  }
213
276
 
@@ -35,6 +35,11 @@ class ClaudeCodeSetup extends BaseIdeSetup {
35
35
  const { ideDir, agileflowTargetDir } = result;
36
36
  const agentsSource = path.join(agileflowDir, 'agents');
37
37
 
38
+ // Claude Code specific: Check for duplicates in user-level ~/.claude/commands/
39
+ // Commands in both ~/.claude/commands/ and .claude/commands/agileflow/ cause
40
+ // "command 2" entries in autocomplete
41
+ await this.removeUserLevelDuplicates(agileflowDir);
42
+
38
43
  // Claude Code specific: Install agents as spawnable subagents (.claude/agents/agileflow/)
39
44
  // This allows Task tool to spawn them with subagent_type: "agileflow-ui"
40
45
  const spawnableAgentsDir = path.join(ideDir, 'agents', 'agileflow');
@@ -282,6 +287,71 @@ class ClaudeCodeSetup extends BaseIdeSetup {
282
287
  await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2));
283
288
  console.log(chalk.dim(` - SessionStart hooks: welcome, archive, context-loader`));
284
289
  }
290
+
291
+ /**
292
+ * Remove AgileFlow command duplicates from user-level ~/.claude/commands/
293
+ * When the same command exists in both ~/.claude/commands/ and
294
+ * <project>/.claude/commands/agileflow/, Claude Code shows duplicates.
295
+ * @param {string} agileflowDir - AgileFlow installation directory (to get command names)
296
+ */
297
+ async removeUserLevelDuplicates(agileflowDir) {
298
+ const os = require('os');
299
+ const userCommandsDir = path.join(os.homedir(), '.claude', 'commands');
300
+
301
+ if (!(await fs.pathExists(userCommandsDir))) return;
302
+
303
+ // Collect AgileFlow command names from source
304
+ const agileflowNames = new Set();
305
+ const commandsSource = path.join(agileflowDir, 'commands');
306
+ const agentsSource = path.join(agileflowDir, 'agents');
307
+
308
+ for (const sourceDir of [commandsSource, agentsSource]) {
309
+ if (await fs.pathExists(sourceDir)) {
310
+ const entries = await fs.readdir(sourceDir, { withFileTypes: true });
311
+ for (const entry of entries) {
312
+ agileflowNames.add(entry.name);
313
+ }
314
+ }
315
+ }
316
+
317
+ if (agileflowNames.size === 0) return;
318
+
319
+ // Check for agileflow-related items in user-level commands
320
+ let removedCount = 0;
321
+ const entries = await fs.readdir(userCommandsDir, { withFileTypes: true });
322
+
323
+ for (const entry of entries) {
324
+ // Only remove items that match AgileFlow command names
325
+ if (agileflowNames.has(entry.name)) {
326
+ const duplicatePath = path.join(userCommandsDir, entry.name);
327
+ try {
328
+ await fs.remove(duplicatePath);
329
+ removedCount++;
330
+ } catch {
331
+ // Best effort - don't fail setup over cleanup
332
+ }
333
+ }
334
+ }
335
+
336
+ // Also remove any agileflow/ subfolder from user-level commands
337
+ for (const folderName of ['agileflow', 'AgileFlow']) {
338
+ const userAgileflowDir = path.join(userCommandsDir, folderName);
339
+ if (await fs.pathExists(userAgileflowDir)) {
340
+ try {
341
+ await fs.remove(userAgileflowDir);
342
+ removedCount++;
343
+ } catch {
344
+ // Best effort
345
+ }
346
+ }
347
+ }
348
+
349
+ if (removedCount > 0) {
350
+ console.log(
351
+ chalk.dim(` - Removed ${removedCount} duplicate(s) from ~/.claude/commands/`)
352
+ );
353
+ }
354
+ }
285
355
  }
286
356
 
287
357
  module.exports = { ClaudeCodeSetup };