claude-skill-lord 2.1.0 → 2.1.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.
@@ -9,8 +9,8 @@
9
9
  {
10
10
  "name": "claude-skill-lord",
11
11
  "source": "./",
12
- "description": "Curated Claude Code plugin — 22 agents, 61 skills, 40+ commands with intelligent skill routing and UI/UX design intelligence",
13
- "version": "1.5.1",
12
+ "description": "Curated Claude Code plugin — 43 agents, 161 skills, 114 commands, 11 language rules",
13
+ "version": "2.1.0",
14
14
  "category": "workflow",
15
15
  "tags": ["agents", "skills", "commands", "hooks", "skill-routing", "quality-gate", "tdd", "code-review"],
16
16
  "strict": false
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "claude-skill-lord",
3
- "version": "2.0.6",
4
- "description": "Curated Claude Code plugin — 43 agents, 170 skills, 114 commands, 11 language rules",
3
+ "version": "2.1.0",
4
+ "description": "Curated Claude Code plugin — 43 agents, 161 skills, 114 commands, 11 language rules",
5
5
  "author": {
6
6
  "name": "Dong Anh",
7
7
  "email": "donganhvu20@gmail.com"
package/CLAUDE.md CHANGED
@@ -11,7 +11,7 @@ Curated Claude Code plugin with intelligent skill routing, structured agents, an
11
11
 
12
12
  **IMPORTANT:** Follow development rules strictly. Research & Reuse is mandatory FIRST step before implementation.
13
13
 
14
- ## Agents (44)
14
+ ## Agents (43)
15
15
 
16
16
  ### Core Agents
17
17
  | Agent | Role |
@@ -72,7 +72,7 @@ Curated Claude Code plugin with intelligent skill routing, structured agents, an
72
72
 
73
73
  All skills live in `./skills/<name>/SKILL.md` — flat structure, no tiers.
74
74
 
75
- ## Commands (115)
75
+ ## Commands (114)
76
76
 
77
77
  ### Core Commands
78
78
  | Command | Description |
@@ -85,7 +85,7 @@ All skills live in `./skills/<name>/SKILL.md` — flat structure, no tiers.
85
85
  | /tdd | Test-driven development workflow |
86
86
  | /debug | Deep issue analysis |
87
87
  | /code-review | Code review |
88
- | /route | Get skill recommendations for your task |
88
+ | /model-route | Get skill recommendations for your task |
89
89
  | /audit | Run quality checks |
90
90
  | /scout | Search codebase (variants: ext) |
91
91
  | /bootstrap | Initialize new projects (variants: auto, auto/fast) |
package/README.md CHANGED
@@ -139,7 +139,7 @@ The most common workflow — plan first, then implement, then validate:
139
139
 
140
140
  ```
141
141
  /scout src/ find all API endpoints
142
- /route I need to refactor the authentication module
142
+ /model-route I need to refactor the authentication module
143
143
  /review:codebase
144
144
  ```
145
145
 
@@ -14,7 +14,7 @@
14
14
  */
15
15
 
16
16
  const fs = require('fs');
17
- const { isGitRepo, getGitModifiedFiles, readFile, log } = require('../lib/utils');
17
+ const { isGitRepo, getGitModifiedFiles, readFile, log } = require('../../scripts/lib/utils');
18
18
 
19
19
  // Files where console.log is expected and should not trigger warnings
20
20
  const EXCLUDED_PATTERNS = [
@@ -27,19 +27,16 @@ fi
27
27
 
28
28
  send_discord_embed() {
29
29
  local title="$1" description="$2" color="$3" fields="$4"
30
- local payload=$(cat <<EOF
31
- {
32
- "embeds": [{
33
- "title": "$title",
34
- "description": "$description",
35
- "color": $color,
36
- "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)",
37
- "footer": {"text": "CSL • ${PROJECT_NAME}"},
38
- "fields": $fields
39
- }]
40
- }
41
- EOF
42
- )
30
+ # Use jq to safely construct JSON (prevents injection from unescaped variables)
31
+ local payload
32
+ payload=$(jq -n \
33
+ --arg title "$title" \
34
+ --arg desc "$description" \
35
+ --argjson color "$color" \
36
+ --arg ts "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" \
37
+ --arg footer "CSL • ${PROJECT_NAME}" \
38
+ --argjson fields "$fields" \
39
+ '{embeds: [{title: $title, description: $desc, color: $color, timestamp: $ts, footer: {text: $footer}, fields: $fields}]}')
43
40
  curl -s -X POST "$DISCORD_WEBHOOK_URL" -H "Content-Type: application/json" -d "$payload" > /dev/null 2>&1
44
41
  }
45
42
 
@@ -23,7 +23,7 @@ const path = require('path');
23
23
  // Shell metacharacters that cmd.exe interprets as command separators/operators
24
24
  const UNSAFE_PATH_CHARS = /[&|<>^%!]/;
25
25
 
26
- const { findProjectRoot, detectFormatter, resolveFormatterBin } = require('../lib/resolve-formatter');
26
+ const { findProjectRoot, detectFormatter, resolveFormatterBin } = require('../../scripts/lib/resolve-formatter');
27
27
 
28
28
  const MAX_STDIN = 1024 * 1024; // 1MB limit
29
29
 
@@ -18,7 +18,7 @@ const fs = require('fs');
18
18
  const path = require('path');
19
19
  const { spawnSync } = require('child_process');
20
20
 
21
- const { findProjectRoot, detectFormatter, resolveFormatterBin } = require('../lib/resolve-formatter');
21
+ const { findProjectRoot, detectFormatter, resolveFormatterBin } = require('../../scripts/lib/resolve-formatter');
22
22
 
23
23
  const MAX_STDIN = 1024 * 1024;
24
24
 
@@ -11,7 +11,7 @@
11
11
  const fs = require('fs');
12
12
  const path = require('path');
13
13
  const { spawnSync } = require('child_process');
14
- const { isHookEnabled } = require('../lib/hook-flags');
14
+ const { isHookEnabled } = require('../../scripts/lib/hook-flags');
15
15
 
16
16
  const MAX_STDIN = 1024 * 1024;
17
17
 
@@ -23,7 +23,7 @@ const {
23
23
  runCommand,
24
24
  stripAnsi,
25
25
  log
26
- } = require('../lib/utils');
26
+ } = require('../../scripts/lib/utils');
27
27
 
28
28
  const SUMMARY_START_MARKER = '<!-- SL:SUMMARY:START -->';
29
29
  const SUMMARY_END_MARKER = '<!-- SL:SUMMARY:END -->';
@@ -27,10 +27,15 @@ fi
27
27
 
28
28
  send_telegram() {
29
29
  local message="$1"
30
- local escaped=$(echo "$message" | jq -Rs .)
30
+ # Use jq to safely construct JSON payload (prevents injection from unescaped variables)
31
+ local payload
32
+ payload=$(jq -n \
33
+ --arg chat_id "$TELEGRAM_CHAT_ID" \
34
+ --arg text "$message" \
35
+ '{chat_id: $chat_id, text: $text, parse_mode: "Markdown", disable_web_page_preview: true}')
31
36
  curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
32
37
  -H "Content-Type: application/json" \
33
- -d "{\"chat_id\":\"${TELEGRAM_CHAT_ID}\",\"text\":${escaped},\"parse_mode\":\"Markdown\",\"disable_web_page_preview\":true}" > /dev/null
38
+ -d "$payload" > /dev/null
34
39
  }
35
40
 
36
41
  case "$HOOK_TYPE" in
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- The Telegram hook (`telegram_notify.sh`) automatically sends notifications when Claude Code sessions stop or subagents complete tasks. It provides detailed summaries including tool usage, files modified, and operation counts.
5
+ The Telegram hook (`telegram-notify.sh`) automatically sends notifications when Claude Code sessions stop or subagents complete tasks. It provides detailed summaries including tool usage, files modified, and operation counts.
6
6
 
7
7
  ## Features
8
8
 
@@ -181,13 +181,13 @@ Hooks are configured in `.claude/settings.local.json`:
181
181
  "Stop": [{
182
182
  "hooks": [{
183
183
  "type": "command",
184
- "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/telegram_notify.sh"
184
+ "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/telegram-notify.sh"
185
185
  }]
186
186
  }],
187
187
  "SubagentStop": [{
188
188
  "hooks": [{
189
189
  "type": "command",
190
- "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/telegram_notify.sh"
190
+ "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/telegram-notify.sh"
191
191
  }]
192
192
  }]
193
193
  }
@@ -203,7 +203,7 @@ Hooks are configured in `.claude/settings.local.json`:
203
203
  ### 5. Make Script Executable
204
204
 
205
205
  ```bash
206
- chmod +x .claude/hooks/telegram_notify.sh
206
+ chmod +x .claude/hooks/telegram-notify.sh
207
207
  ```
208
208
 
209
209
  ### 6. Verify Setup
@@ -220,7 +220,7 @@ echo '{
220
220
  {"tool": "Edit", "parameters": {"file_path": "test.ts"}},
221
221
  {"tool": "Bash", "parameters": {"command": "npm test"}}
222
222
  ]
223
- }' | ./.claude/hooks/telegram_notify.sh
223
+ }' | ./.claude/hooks/telegram-notify.sh
224
224
  ```
225
225
 
226
226
  **Expected output:**
@@ -492,13 +492,13 @@ jq --version
492
492
 
493
493
  3. **Verify script is executable:**
494
494
  ```bash
495
- ls -l .claude/hooks/telegram_notify.sh
495
+ ls -l .claude/hooks/telegram-notify.sh
496
496
  # Should show: -rwxr-xr-x
497
497
  ```
498
498
 
499
499
  4. **Make script executable if needed:**
500
500
  ```bash
501
- chmod +x .claude/hooks/telegram_notify.sh
501
+ chmod +x .claude/hooks/telegram-notify.sh
502
502
  ```
503
503
 
504
504
  5. **Test hook manually (see "Verify Setup" section)**
@@ -519,7 +519,7 @@ jq --version
519
519
  - Script uses `"parse_mode": "Markdown"`
520
520
 
521
521
  2. **Check message escaping in script:**
522
- - Edit `telegram_notify.sh`
522
+ - Edit `telegram-notify.sh`
523
523
  - Look for line: `local escaped_message=$(echo "$message" | jq -Rs .)`
524
524
  - This should properly escape for JSON
525
525
 
@@ -536,12 +536,12 @@ jq --version
536
536
 
537
537
  **Solution:**
538
538
  ```bash
539
- chmod +x .claude/hooks/telegram_notify.sh
539
+ chmod +x .claude/hooks/telegram-notify.sh
540
540
  ```
541
541
 
542
542
  **Verify:**
543
543
  ```bash
544
- ls -l .claude/hooks/telegram_notify.sh
544
+ ls -l .claude/hooks/telegram-notify.sh
545
545
  # Output should show: -rwxr-xr-x
546
546
  ```
547
547
 
@@ -561,7 +561,7 @@ TELEGRAM_CHAT_ID_ERROR=987654321 # Error notifications
561
561
 
562
562
  **Modified script logic:**
563
563
  ```bash
564
- # In telegram_notify.sh, add conditional chat ID selection
564
+ # In telegram-notify.sh, add conditional chat ID selection
565
565
  if [[ "$HOOK_TYPE" == "Stop" ]] && [[ $TOTAL_TOOLS -gt 20 ]]; then
566
566
  # Large operations go to success channel
567
567
  TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID_SUCCESS:-$TELEGRAM_CHAT_ID}"
@@ -572,7 +572,7 @@ fi
572
572
 
573
573
  Only send notifications for significant events:
574
574
 
575
- **Edit `telegram_notify.sh`:**
575
+ **Edit `telegram-notify.sh`:**
576
576
  ```bash
577
577
  # After line 65 (TOTAL_TOOLS calculation), add:
578
578
 
@@ -604,7 +604,7 @@ fi
604
604
 
605
605
  ### Custom Message Formatting
606
606
 
607
- Modify notification format in `telegram_notify.sh`:
607
+ Modify notification format in `telegram-notify.sh`:
608
608
 
609
609
  **Add Git branch info:**
610
610
  ```bash
@@ -650,7 +650,7 @@ Prevent notification spam:
650
650
 
651
651
  **Create rate limit file:**
652
652
  ```bash
653
- # Add to telegram_notify.sh, after line 55:
653
+ # Add to telegram-notify.sh, after line 55:
654
654
 
655
655
  RATE_LIMIT_FILE="/tmp/telegram_notify_last_sent"
656
656
  RATE_LIMIT_SECONDS=60
@@ -690,7 +690,7 @@ echo '{
690
690
  {"tool": "Bash", "parameters": {"command": "npm test"}},
691
691
  {"tool": "TodoWrite", "parameters": {}}
692
692
  ]
693
- }' | ./.claude/hooks/telegram_notify.sh
693
+ }' | ./.claude/hooks/telegram-notify.sh
694
694
  ```
695
695
 
696
696
  **SubagentStop event:**
@@ -700,7 +700,7 @@ echo '{
700
700
  "projectDir": "'"$(pwd)"'",
701
701
  "sessionId": "test-456",
702
702
  "subagentType": "planner"
703
- }' | ./.claude/hooks/telegram_notify.sh
703
+ }' | ./.claude/hooks/telegram-notify.sh
704
704
  ```
705
705
 
706
706
  ## Security Best Practices
@@ -748,7 +748,7 @@ echo '{
748
748
 
749
749
  ## Reference
750
750
 
751
- **Script Location:** `.claude/hooks/telegram_notify.sh`
751
+ **Script Location:** `.claude/hooks/telegram-notify.sh`
752
752
 
753
753
  **Configuration:** `.claude/config.json`
754
754
 
@@ -22,7 +22,7 @@
22
22
  {
23
23
  "id": "skills",
24
24
  "kind": "skills",
25
- "description": "All 170 skills — debugging, testing, frontend/backend, databases, security, UI/UX, patterns, languages, frameworks, and more",
25
+ "description": "All 161 skills — debugging, testing, frontend/backend, databases, security, UI/UX, patterns, languages, frameworks, and more",
26
26
  "paths": ["skills/"],
27
27
  "defaultInstall": true,
28
28
  "cost": "medium",
@@ -31,7 +31,7 @@
31
31
  {
32
32
  "id": "rules",
33
33
  "kind": "rules",
34
- "description": "Language-specific coding rules — 13 languages (TypeScript, Python, Go, Rust, Java, Kotlin, C++, C#, PHP, Perl, Swift) + common",
34
+ "description": "Language-specific coding rules — 11 languages (TypeScript, Python, Go, Rust, Java, Kotlin, C++, C#, PHP, Perl, Swift) + common",
35
35
  "paths": ["rules/"],
36
36
  "defaultInstall": true,
37
37
  "cost": "light",
@@ -69,7 +69,7 @@
69
69
  "id": "hooks-essential",
70
70
  "kind": "hooks",
71
71
  "description": "Essential hooks — security, formatting, type checking, notifications",
72
- "paths": ["hooks/"],
72
+ "paths": ["hooks/", "scripts/lib/"],
73
73
  "defaultInstall": true,
74
74
  "cost": "light",
75
75
  "stability": "stable"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-skill-lord",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "Curated Claude Code plugin — 43 agents, 161 skills, 114 commands, 11 language rules",
5
5
  "author": {
6
6
  "name": "Dong Anh",
@@ -9,7 +9,7 @@
9
9
  "license": "MIT",
10
10
  "repository": {
11
11
  "type": "git",
12
- "url": "https://github.com/donganhvuphp/Claude-Skills-Lord.git"
12
+ "url": "git+https://github.com/donganhvuphp/Claude-Skills-Lord.git"
13
13
  },
14
14
  "homepage": "https://github.com/donganhvuphp/Claude-Skills-Lord",
15
15
  "bugs": {
@@ -27,8 +27,8 @@
27
27
  "code-review"
28
28
  ],
29
29
  "bin": {
30
- "csl": "./scripts/sl.js",
31
- "skill-lord": "./scripts/sl.js"
30
+ "csl": "scripts/sl.js",
31
+ "skill-lord": "scripts/sl.js"
32
32
  },
33
33
  "files": [
34
34
  ".claude-plugin/",
@@ -54,7 +54,13 @@ if (!fs.existsSync(modulesPath)) {
54
54
  process.exit(1);
55
55
  }
56
56
 
57
- const modules = JSON.parse(fs.readFileSync(modulesPath, 'utf8'));
57
+ let modules;
58
+ try {
59
+ modules = JSON.parse(fs.readFileSync(modulesPath, 'utf8'));
60
+ } catch (err) {
61
+ console.error(`Error: Failed to parse install-modules.json: ${err.message}`);
62
+ process.exit(1);
63
+ }
58
64
 
59
65
  // Install all modules (optionally skip fonts)
60
66
  let allModules = modules.modules;
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Package Manager Detection
5
+ *
6
+ * Detects which package manager (npm, yarn, pnpm, bun) the project uses
7
+ * by checking lock files and environment variables.
8
+ *
9
+ * Used by: resolve-formatter.js, session hooks
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ /**
18
+ * Package manager configurations
19
+ */
20
+ const PM_CONFIGS = {
21
+ bun: {
22
+ name: 'bun',
23
+ lockFile: 'bun.lockb',
24
+ execCmd: 'bunx',
25
+ installCmd: 'bun install',
26
+ runCmd: 'bun run',
27
+ },
28
+ pnpm: {
29
+ name: 'pnpm',
30
+ lockFile: 'pnpm-lock.yaml',
31
+ execCmd: 'pnpm exec',
32
+ installCmd: 'pnpm install',
33
+ runCmd: 'pnpm run',
34
+ },
35
+ yarn: {
36
+ name: 'yarn',
37
+ lockFile: 'yarn.lock',
38
+ execCmd: 'yarn dlx',
39
+ installCmd: 'yarn install',
40
+ runCmd: 'yarn run',
41
+ },
42
+ npm: {
43
+ name: 'npm',
44
+ lockFile: 'package-lock.json',
45
+ execCmd: 'npx',
46
+ installCmd: 'npm install',
47
+ runCmd: 'npm run',
48
+ },
49
+ };
50
+
51
+ /**
52
+ * Detect which package manager a project uses.
53
+ *
54
+ * Priority:
55
+ * 1. CLAUDE_PACKAGE_MANAGER env variable
56
+ * 2. Lock file detection (bun > pnpm > yarn > npm)
57
+ * 3. Default to npm
58
+ *
59
+ * @param {{ projectDir?: string }} [options]
60
+ * @returns {{ name: string, config: object }}
61
+ */
62
+ function getPackageManager(options = {}) {
63
+ const projectDir = options.projectDir || process.cwd();
64
+
65
+ // 1. Environment variable override
66
+ const envPm = process.env.CLAUDE_PACKAGE_MANAGER;
67
+ if (envPm && PM_CONFIGS[envPm]) {
68
+ return { name: envPm, config: PM_CONFIGS[envPm] };
69
+ }
70
+
71
+ // 2. Lock file detection (ordered by specificity)
72
+ for (const [name, config] of Object.entries(PM_CONFIGS)) {
73
+ const lockPath = path.join(projectDir, config.lockFile);
74
+ if (fs.existsSync(lockPath)) {
75
+ return { name, config };
76
+ }
77
+ }
78
+
79
+ // 3. Default to npm
80
+ return { name: 'npm', config: PM_CONFIGS.npm };
81
+ }
82
+
83
+ module.exports = { getPackageManager, PM_CONFIGS };
@@ -6,7 +6,7 @@
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
8
  const os = require('os');
9
- const { execSync, spawnSync } = require('child_process');
9
+ const { execSync } = require('child_process');
10
10
 
11
11
  // Platform detection
12
12
  const isWindows = process.platform === 'win32';
@@ -41,13 +41,6 @@ function getLearnedSkillsDir() {
41
41
  return path.join(getClaudeDir(), 'skills', 'learned');
42
42
  }
43
43
 
44
- /**
45
- * Get the temp directory (cross-platform)
46
- */
47
- function getTempDir() {
48
- return os.tmpdir();
49
- }
50
-
51
44
  /**
52
45
  * Ensure a directory exists (create if not)
53
46
  * @param {string} dirPath - Directory path to create
@@ -119,20 +112,6 @@ function getSessionIdShort(fallback = 'default') {
119
112
  return getProjectName() || fallback;
120
113
  }
121
114
 
122
- /**
123
- * Get current datetime in YYYY-MM-DD HH:MM:SS format
124
- */
125
- function getDateTimeString() {
126
- const now = new Date();
127
- const year = now.getFullYear();
128
- const month = String(now.getMonth() + 1).padStart(2, '0');
129
- const day = String(now.getDate()).padStart(2, '0');
130
- const hours = String(now.getHours()).padStart(2, '0');
131
- const minutes = String(now.getMinutes()).padStart(2, '0');
132
- const seconds = String(now.getSeconds()).padStart(2, '0');
133
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
134
- }
135
-
136
115
  /**
137
116
  * Find files matching a pattern in a directory (cross-platform alternative to find)
138
117
  * @param {string} dir - Directory to search
@@ -296,38 +275,6 @@ function writeFile(filePath, content) {
296
275
  fs.writeFileSync(filePath, content, 'utf8');
297
276
  }
298
277
 
299
- /**
300
- * Append to a text file
301
- */
302
- function appendFile(filePath, content) {
303
- ensureDir(path.dirname(filePath));
304
- fs.appendFileSync(filePath, content, 'utf8');
305
- }
306
-
307
- /**
308
- * Check if a command exists in PATH
309
- * Uses execFileSync to prevent command injection
310
- */
311
- function commandExists(cmd) {
312
- // Validate command name - only allow alphanumeric, dash, underscore, dot
313
- if (!/^[a-zA-Z0-9_.-]+$/.test(cmd)) {
314
- return false;
315
- }
316
-
317
- try {
318
- if (isWindows) {
319
- // Use spawnSync to avoid shell interpolation
320
- const result = spawnSync('where', [cmd], { stdio: 'pipe' });
321
- return result.status === 0;
322
- } else {
323
- const result = spawnSync('which', [cmd], { stdio: 'pipe' });
324
- return result.status === 0;
325
- }
326
- } catch {
327
- return false;
328
- }
329
- }
330
-
331
278
  /**
332
279
  * Run a command and return output
333
280
  *
@@ -405,65 +352,6 @@ function getGitModifiedFiles(patterns = []) {
405
352
  return files;
406
353
  }
407
354
 
408
- /**
409
- * Replace text in a file (cross-platform sed alternative)
410
- * @param {string} filePath - Path to the file
411
- * @param {string|RegExp} search - Pattern to search for. String patterns replace
412
- * the FIRST occurrence only; use a RegExp with the `g` flag for global replacement.
413
- * @param {string} replace - Replacement string
414
- * @param {object} options - Options
415
- * @param {boolean} options.all - When true and search is a string, replaces ALL
416
- * occurrences (uses String.replaceAll). Ignored for RegExp patterns.
417
- * @returns {boolean} true if file was written, false on error
418
- */
419
- function replaceInFile(filePath, search, replace, options = {}) {
420
- const content = readFile(filePath);
421
- if (content === null) return false;
422
-
423
- try {
424
- let newContent;
425
- if (options.all && typeof search === 'string') {
426
- newContent = content.replaceAll(search, replace);
427
- } else {
428
- newContent = content.replace(search, replace);
429
- }
430
- writeFile(filePath, newContent);
431
- return true;
432
- } catch (err) {
433
- log(`[Utils] replaceInFile failed for ${filePath}: ${err.message}`);
434
- return false;
435
- }
436
- }
437
-
438
- /**
439
- * Count occurrences of a pattern in a file
440
- * @param {string} filePath - Path to the file
441
- * @param {string|RegExp} pattern - Pattern to count. Strings are treated as
442
- * global regex patterns. RegExp instances are used as-is but the global
443
- * flag is enforced to ensure correct counting.
444
- * @returns {number} Number of matches found
445
- */
446
- function countInFile(filePath, pattern) {
447
- const content = readFile(filePath);
448
- if (content === null) return 0;
449
-
450
- let regex;
451
- try {
452
- if (pattern instanceof RegExp) {
453
- // Always create new RegExp to avoid shared lastIndex state; ensure global flag
454
- regex = new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : pattern.flags + 'g');
455
- } else if (typeof pattern === 'string') {
456
- regex = new RegExp(pattern, 'g');
457
- } else {
458
- return 0;
459
- }
460
- } catch {
461
- return 0; // Invalid regex pattern
462
- }
463
- const matches = content.match(regex);
464
- return matches ? matches.length : 0;
465
- }
466
-
467
355
  /**
468
356
  * Strip all ANSI escape sequences from a string.
469
357
  *
@@ -482,39 +370,6 @@ function stripAnsi(str) {
482
370
  return str.replace(/\x1b(?:\[[0-9;?]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|\([A-Z]|[A-Z])/g, '');
483
371
  }
484
372
 
485
- /**
486
- * Search for pattern in file and return matching lines with line numbers
487
- */
488
- function grepFile(filePath, pattern) {
489
- const content = readFile(filePath);
490
- if (content === null) return [];
491
-
492
- let regex;
493
- try {
494
- if (pattern instanceof RegExp) {
495
- // Always create a new RegExp without the 'g' flag to prevent lastIndex
496
- // state issues when using .test() in a loop (g flag makes .test() stateful,
497
- // causing alternating match/miss on consecutive matching lines)
498
- const flags = pattern.flags.replace('g', '');
499
- regex = new RegExp(pattern.source, flags);
500
- } else {
501
- regex = new RegExp(pattern);
502
- }
503
- } catch {
504
- return []; // Invalid regex pattern
505
- }
506
- const lines = content.split('\n');
507
- const results = [];
508
-
509
- lines.forEach((line, index) => {
510
- if (regex.test(line)) {
511
- results.push({ lineNumber: index + 1, content: line });
512
- }
513
- });
514
-
515
- return results;
516
- }
517
-
518
373
  module.exports = {
519
374
  // Platform info
520
375
  isWindows,
@@ -522,17 +377,14 @@ module.exports = {
522
377
  isLinux,
523
378
 
524
379
  // Directories
525
- getHomeDir,
526
380
  getClaudeDir,
527
381
  getSessionsDir,
528
382
  getLearnedSkillsDir,
529
- getTempDir,
530
383
  ensureDir,
531
384
 
532
385
  // Date/Time
533
386
  getDateString,
534
387
  getTimeString,
535
- getDateTimeString,
536
388
 
537
389
  // Session/Project
538
390
  getSessionIdShort,
@@ -543,10 +395,6 @@ module.exports = {
543
395
  findFiles,
544
396
  readFile,
545
397
  writeFile,
546
- appendFile,
547
- replaceInFile,
548
- countInFile,
549
- grepFile,
550
398
 
551
399
  // String sanitisation
552
400
  stripAnsi,
@@ -557,7 +405,6 @@ module.exports = {
557
405
  output,
558
406
 
559
407
  // System
560
- commandExists,
561
408
  runCommand,
562
409
  isGitRepo,
563
410
  getGitModifiedFiles