@ngxtm/devkit 3.0.0 → 3.0.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/README.md CHANGED
@@ -1,236 +1,96 @@
1
- # Devkit Agent Assistant
1
+ # Devkit
2
2
 
3
- > Unified multi-agent system with auto-synced skills from multiple sources
3
+ > Unified multi-agent skill system for AI coding assistants
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@ngxtm/devkit.svg)](https://www.npmjs.com/package/@ngxtm/devkit)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
+ Supercharge your AI coding assistant with **414+ skills**, **38 agents**, and **57 commands**. Works with Claude Code, Cursor, GitHub Copilot, and Gemini.
9
+
8
10
  ## Features
9
11
 
10
- - **414+ Skills** - Merged from antigravity, agent-assistant, and claudekit
11
- - **38 Agents** - 20 from agent-assistant + 18 from claudekit
12
- - **57 Commands** - Full workflow commands from both sources
13
- - **9 Hooks** - Session management, privacy, context awareness (claudekit)
14
- - **6 Output Styles** - Coding levels from ELI5 to God mode (claudekit)
15
- - **10 Rules** - Coding standards for TypeScript, React, NestJS, etc.
16
- - **Matrix Skill Discovery** - Intelligent skill injection based on context
12
+ - **Smart Tech Detection** - Automatically detects your project stack and loads relevant skills
13
+ - **Per-Project Installation** - Install only what you need, where you need it
14
+ - **Context-Optimized** - Index-only mode reduces context usage by 99.95%
17
15
  - **Auto-Sync** - Daily updates from upstream sources via GitHub Actions
16
+ - **Multi-Tool Support** - Works with Claude, Cursor, Copilot, and Gemini
18
17
 
19
- ## Sources
20
-
21
- | Source | What's Included |
22
- |--------|-----------------|
23
- | [antigravity-awesome-skills](https://github.com/sickn33/antigravity-awesome-skills) | 256+ skills (primary, frequently updated) |
24
- | [agent-assistant](https://github.com/hainamchung/agent-assistant) | 310+ skills, Matrix system, 20 agents, 25 commands |
25
- | [claudekit](https://github.com/anthropics/claudekit) | 45 skills, 18 agents, 32 commands, hooks, output styles |
26
- | [skill-rule](https://github.com/ngxtm/skill-rule) | Coding standards for 10 frameworks |
27
-
28
- ## Installation
18
+ ## Quick Start
29
19
 
30
20
  ```bash
31
21
  npm install -g @ngxtm/devkit
32
22
  devkit install
33
23
  ```
34
24
 
35
- ### Install Modes
36
-
37
- | Mode | Command | Skills Size | Description |
38
- |------|---------|-------------|-------------|
39
- | **Index-only** (default) | `devkit install` | ~30KB | Only installs skills index, Claude loads skills on-demand |
40
- | Minimal | `devkit install --minimal` | ~2MB | Installs ~20 core skills |
41
- | Category | `devkit install -c=react` | varies | Installs specific categories |
42
- | Full | `devkit install --full` | ~59MB | Installs all 413+ skills (may cause context limit) |
43
-
44
- ```bash
45
- # Recommended: Index-only (default)
46
- devkit install # Best for avoiding context limits
47
- devkit install claude # Claude Code only
48
-
49
- # Alternative modes
50
- devkit install --minimal # ~20 core skills
51
- devkit install --category=react,ts # Specific categories
52
- devkit install --full # All skills (large)
53
- devkit install --interactive # Choose interactively
54
- ```
55
-
56
- ### How Index-Only Mode Works
57
-
58
- Instead of installing 3,500+ files (59MB), devkit installs:
59
- - `SKILLS_INDEX.md` - 30KB summary of all 411 skills
60
- - All commands (`/plan`, `/cook`, `/brainstorm`, etc.)
61
- - All agents (planner, debugger, reviewer, etc.)
62
- - Hooks, rules, output-styles
63
-
64
- When Claude needs a specific skill, it reads the index to find it, then loads the full skill on-demand. This reduces context usage by **99.95%**.
65
-
66
- ### Available Categories
25
+ ## Installation Modes
67
26
 
68
- ```bash
69
- devkit categories # Show all categories
70
- ```
27
+ | Mode | Command | Size | Description |
28
+ |------|---------|------|-------------|
29
+ | **Index-only** | `devkit install` | ~30KB | On-demand skill loading (recommended) |
30
+ | Minimal | `devkit install --minimal` | ~2MB | ~20 core skills |
31
+ | Category | `devkit install -c=react,ts` | varies | Specific categories only |
32
+ | Full | `devkit install --full` | ~59MB | All 414+ skills |
71
33
 
72
- | Category | Skills | Description |
73
- |----------|--------|-------------|
74
- | react | 9 | React, Next.js, Remix |
75
- | typescript | 4 | TypeScript patterns |
76
- | node | 6 | Node.js, NestJS, Express |
77
- | database | 5 | PostgreSQL, MongoDB, Redis |
78
- | devops | 7 | Docker, K8s, CI/CD, AWS |
79
- | testing | 6 | Jest, Playwright, Vitest |
80
- | security | 5 | OWASP, Auth, API security |
81
- | ai | 6 | AI agents, MCP, prompts |
82
- | mobile | 5 | React Native, Flutter |
83
- | frontend | 6 | CSS, Tailwind, a11y |
84
- | backend | 6 | APIs, microservices |
85
- | tools | 6 | Git, debugging, docs |
86
-
87
- ## Uninstallation
34
+ ## Commands
88
35
 
89
36
  ```bash
90
- # Remove from all tools
91
- devkit uninstall
92
-
93
- # Remove from specific tool
94
- devkit uninstall claude
95
- devkit uninstall cursor
37
+ # Planning & Building
38
+ /plan # Create implementation strategy
39
+ /cook # Build feature with full workflow
40
+ /code # Code with workflow
41
+
42
+ # Development
43
+ /fix # Fix bugs
44
+ /test # Run tests
45
+ /review # Code review
46
+ /scout # Explore codebase
47
+
48
+ # Setup
49
+ /bootstrap # Setup new project
50
+ /coding-level # Set output style (eli5 → god)
51
+ /kanban # Manage tasks
96
52
  ```
97
53
 
98
- ## What Gets Installed
54
+ ## Coding Levels
99
55
 
100
- ### For Claude Code (`~/.claude/`)
101
-
102
- ```
103
- ~/.claude/
104
- ├── skills/ # 414+ skills
105
- │ └── agent-assistant/ # Core framework
106
- │ ├── agents/ # 20 agents
107
- │ ├── commands/ # 25 commands
108
- │ ├── matrix-skills/ # Skill discovery system
109
- │ └── claudekit/ # Claudekit components
110
- │ ├── agents/ # 18 agents
111
- │ └── commands/ # 32 commands
112
- ├── rules/ # Coding standards
113
- ├── hooks/ # 9 hooks
114
- ├── output-styles/ # 6 coding levels
115
- ├── workflows/ # Workflow definitions
116
- └── settings.json # Claude settings
117
- ```
56
+ | Level | Command | Style |
57
+ |-------|---------|-------|
58
+ | 0 | `/coding-level 0` | ELI5 - Learning friendly |
59
+ | 1 | `/coding-level 1` | Junior - Detailed comments |
60
+ | 2 | `/coding-level 2` | Mid - Balanced |
61
+ | 3 | `/coding-level 3` | Senior - Concise |
62
+ | 4 | `/coding-level 4` | Lead - Architecture focus |
63
+ | 5 | `/coding-level 5` | God - Maximum efficiency |
118
64
 
119
- ### For Other Tools
120
-
121
- - **Cursor**: Skills, agents, commands, rules
122
- - **GitHub Copilot**: Skills, rules
123
- - **Gemini/Antigravity**: Skills, rules
124
-
125
- ## Quick Start Commands
126
-
127
- After installation, use these commands in your AI coding tool:
128
-
129
- ### From agent-assistant
130
- ```
131
- /cook - Build a feature with full workflow
132
- /plan - Plan implementation strategy
133
- /review - Code review
134
- /test - Run tests
135
- /fix - Fix bugs
136
- ```
65
+ ## Categories
137
66
 
138
- ### From claudekit
139
- ```
140
- /bootstrap - Setup new project
141
- /coding-level - Set output style (eli5/junior/mid/senior/lead/god)
142
- /code - Code with workflow
143
- /scout - Explore codebase
144
- /kanban - Manage tasks
67
+ ```bash
68
+ devkit categories # List all categories
145
69
  ```
146
70
 
147
- ## Output Styles (claudekit)
148
-
149
- Set your preferred coding level:
150
-
151
- | Level | Style | Best For |
152
- |-------|-------|----------|
153
- | `/coding-level 0` | ELI5 | Learning, explanations |
154
- | `/coding-level 1` | Junior | Detailed comments, safe patterns |
155
- | `/coding-level 2` | Mid | Balanced, best practices |
156
- | `/coding-level 3` | Senior | Concise, optimized |
157
- | `/coding-level 4` | Lead | Architecture focus |
158
- | `/coding-level 5` | God | Minimal, maximum efficiency |
159
-
160
- ## Hooks (Claude Code only)
161
-
162
- Claudekit hooks provide:
163
- - **session-init** - Initialize session context
164
- - **privacy-block** - Protect sensitive files
165
- - **scout-block** - Control exploration scope
166
- - **dev-rules-reminder** - Enforce coding standards
167
- - **usage-context-awareness** - Smart context management
168
- - And more...
169
-
170
- ## Auto-Sync
171
-
172
- Skills and rules are automatically updated via GitHub Actions:
173
-
174
- - **Skills**: Daily sync from antigravity + agent-assistant
175
- - **Rules**: Weekly sync from skill-rule
176
- - **Hooks/Claudekit**: Manual sync (local source)
177
-
178
- ## Configuration
71
+ | Category | Skills | Category | Skills |
72
+ |----------|--------|----------|--------|
73
+ | react | 9 | database | 5 |
74
+ | typescript | 4 | devops | 7 |
75
+ | node | 6 | testing | 6 |
76
+ | security | 5 | ai | 6 |
77
+ | mobile | 5 | frontend | 6 |
78
+ | backend | 6 | tools | 6 |
179
79
 
180
- Edit `SYNC_CONFIG.yaml` to customize sources:
181
-
182
- ```yaml
183
- skill_sources:
184
- - name: antigravity-awesome-skills
185
- repo: sickn33/antigravity-awesome-skills
186
- enabled: true
187
- priority: 1
188
-
189
- - name: agent-assistant
190
- repo: hainamchung/agent-assistant
191
- enabled: true
192
- priority: 2
193
-
194
- sync:
195
- merge_strategy: prefer-primary
196
- schedule: "0 0 * * *"
197
- ```
198
-
199
- ## Development
80
+ ## Uninstall
200
81
 
201
82
  ```bash
202
- # Clone the repo
203
- git clone https://github.com/YOUR_USERNAME/devkit-assistant.git
204
- cd devkit-assistant
205
-
206
- # Install dependencies
207
- npm install
208
-
209
- # Run initial sync (first time only)
210
- # Windows:
211
- initial-sync.bat
212
-
213
- # Linux/Mac:
214
- ./initial-sync.sh
215
-
216
- # Test install locally
217
- node cli/install.js
83
+ devkit uninstall # Remove from all tools
84
+ devkit uninstall claude # Remove from specific tool
218
85
  ```
219
86
 
220
87
  ## Contributing
221
88
 
222
89
  1. Fork the repository
223
- 2. Add your skills to `skills/your-skill-name/SKILL.md`
90
+ 2. Add skills to `skills/your-skill-name/SKILL.md`
224
91
  3. Run `python scripts/update_matrix.py`
225
92
  4. Submit a pull request
226
93
 
227
94
  ## License
228
95
 
229
- MIT License - see [LICENSE](LICENSE) for details.
230
-
231
- ## Credits
232
-
233
- - [antigravity-awesome-skills](https://github.com/sickn33/antigravity-awesome-skills) by sickn33
234
- - [agent-assistant](https://github.com/hainamchung/agent-assistant) by hainamchung
235
- - [claudekit](https://github.com/anthropics/claudekit) by Anthropic
236
- - [skill-rule](https://github.com/ngxtm/skill-rule) by ngxtm
96
+ MIT
@@ -505,6 +505,11 @@ test('getGitRoot from nested subdir returns correct root', () => {
505
505
  console.log('\n=== Symlinked directory tests ===\n');
506
506
 
507
507
  test('getGitRoot resolves through symlink to git repo', () => {
508
+ // Skip on Windows - symlinks require elevated privileges
509
+ if (process.platform === 'win32') {
510
+ console.log(' → Skipped: Symlinks require elevated privileges on Windows');
511
+ return;
512
+ }
508
513
  const realDir = path.join(os.tmpdir(), 'ck-test-real-' + Date.now());
509
514
  const linkDir = path.join(os.tmpdir(), 'ck-test-link-' + Date.now());
510
515
  fs.mkdirSync(realDir, { recursive: true });
@@ -529,6 +534,11 @@ test('getGitRoot resolves through symlink to git repo', () => {
529
534
  });
530
535
 
531
536
  test('getGitRoot with symlinked subdirectory', () => {
537
+ // Skip on Windows - symlinks require elevated privileges
538
+ if (process.platform === 'win32') {
539
+ console.log(' → Skipped: Symlinks require elevated privileges on Windows');
540
+ return;
541
+ }
532
542
  const realDir = path.join(os.tmpdir(), 'ck-test-real-sub-' + Date.now());
533
543
  const subDir = path.join(realDir, 'subdir');
534
544
  const linkToSub = path.join(os.tmpdir(), 'ck-test-link-sub-' + Date.now());
@@ -4,7 +4,7 @@
4
4
  /**
5
5
  * Integration Tests for Statusline Main Script
6
6
  * Tests the complete statusline.cjs with sample JSON input
7
- * Run: node .claude/hooks/lib/__tests__/statusline-integration.test.cjs
7
+ * Run: node hooks/lib/__tests__/statusline-integration.test.cjs
8
8
  */
9
9
 
10
10
  const fs = require('fs');
@@ -12,6 +12,12 @@ const path = require('path');
12
12
  const os = require('os');
13
13
  const { execSync } = require('child_process');
14
14
 
15
+ // Determine the correct statusline.cjs path (at project root)
16
+ const statuslinePath = path.resolve(__dirname, '..', '..', '..', 'statusline.cjs');
17
+
18
+ // Check if running on Windows
19
+ const isWindows = process.platform === 'win32';
20
+
15
21
  let passed = 0;
16
22
  let failed = 0;
17
23
  const failures = [];
@@ -47,6 +53,29 @@ function assertContains(actual, search, msg = '') {
47
53
  }
48
54
  }
49
55
 
56
+ /**
57
+ * Cross-platform helper to run statusline with JSON input
58
+ * Uses temp file approach to avoid shell quoting issues on Windows
59
+ */
60
+ function runStatusline(jsonInput, extraEnv = {}, options = {}) {
61
+ const tmpFile = path.join(os.tmpdir(), `statusline-test-${Date.now()}-${Math.random().toString(36).substring(7)}.json`);
62
+ try {
63
+ fs.writeFileSync(tmpFile, jsonInput);
64
+ const cmd = isWindows
65
+ ? `type "${tmpFile}" | node "${statuslinePath}"`
66
+ : `cat "${tmpFile}" | node "${statuslinePath}"`;
67
+ return execSync(cmd, {
68
+ encoding: 'utf8',
69
+ stdio: ['pipe', 'pipe', 'pipe'],
70
+ env: { ...process.env, ...extraEnv },
71
+ shell: true,
72
+ ...options
73
+ });
74
+ } finally {
75
+ try { fs.unlinkSync(tmpFile); } catch {}
76
+ }
77
+ }
78
+
50
79
  console.log('\n═══════════════════════════════════════════════════════');
51
80
  console.log('STATUSLINE INTEGRATION TESTS');
52
81
  console.log('═══════════════════════════════════════════════════════\n');
@@ -64,10 +93,7 @@ const minimalInput = JSON.stringify({
64
93
  });
65
94
 
66
95
  try {
67
- const result = execSync(`echo '${minimalInput.replace(/'/g, "'\\''")}' | node .claude/statusline.cjs`, {
68
- encoding: 'utf8',
69
- stdio: ['pipe', 'pipe', 'pipe']
70
- });
96
+ const result = runStatusline(minimalInput);
71
97
 
72
98
  test('Minimal input produces output', () => {
73
99
  assertTrue(result.length > 0, 'Should produce some output');
@@ -109,11 +135,7 @@ try {
109
135
  context_window: { context_window_size: 200000 }
110
136
  });
111
137
 
112
- const gitResult = execSync(`echo '${gitInput.replace(/'/g, "'\\''")}' | node .claude/statusline.cjs`, {
113
- encoding: 'utf8',
114
- cwd: tmpDir,
115
- stdio: ['pipe', 'pipe', 'pipe']
116
- });
138
+ const gitResult = runStatusline(gitInput);
117
139
 
118
140
  test('Git input processed without error', () => {
119
141
  assertTrue(gitResult.length > 0, 'Should produce output for git repo');
@@ -153,10 +175,7 @@ const contextInput = JSON.stringify({
153
175
  });
154
176
 
155
177
  try {
156
- const contextResult = execSync(`echo '${contextInput.replace(/'/g, "'\\''")}' | node .claude/statusline.cjs`, {
157
- encoding: 'utf8',
158
- stdio: ['pipe', 'pipe', 'pipe']
159
- });
178
+ const contextResult = runStatusline(contextInput);
160
179
 
161
180
  test('Context window data processed', () => {
162
181
  assertTrue(contextResult.length > 0, 'Should process context window data');
@@ -180,8 +199,6 @@ try {
180
199
 
181
200
  console.log('\nTEST 4: JSON with Cost Info\n');
182
201
 
183
- process.env.CLAUDE_BILLING_MODE = 'api';
184
-
185
202
  const costInput = JSON.stringify({
186
203
  model: { display_name: 'Claude' },
187
204
  workspace: { current_dir: '/home/user' },
@@ -194,11 +211,7 @@ const costInput = JSON.stringify({
194
211
  });
195
212
 
196
213
  try {
197
- const costResult = execSync(`echo '${costInput.replace(/'/g, "'\\''")}' | CLAUDE_BILLING_MODE=api node .claude/statusline.cjs`, {
198
- encoding: 'utf8',
199
- stdio: ['pipe', 'pipe', 'pipe'],
200
- env: { ...process.env, CLAUDE_BILLING_MODE: 'api' }
201
- });
214
+ const costResult = runStatusline(costInput, { CLAUDE_BILLING_MODE: 'api' });
202
215
 
203
216
  test('Cost info displayed in API mode', () => {
204
217
  assertTrue(costResult.length > 0, 'Should display cost info');
@@ -222,10 +235,7 @@ try {
222
235
  console.log('\nTEST 5: Invalid JSON Handling\n');
223
236
 
224
237
  try {
225
- const invalidResult = execSync(`echo 'not valid json' | node .claude/statusline.cjs`, {
226
- encoding: 'utf8',
227
- stdio: ['pipe', 'pipe', 'pipe']
228
- });
238
+ const invalidResult = runStatusline('not valid json');
229
239
 
230
240
  test('Invalid JSON produces fallback output', () => {
231
241
  assertTrue(invalidResult.length > 0, 'Should produce fallback output');
@@ -247,10 +257,7 @@ try {
247
257
  console.log('\nTEST 6: Empty Input Handling\n');
248
258
 
249
259
  try {
250
- const emptyResult = execSync(`echo '' | node .claude/statusline.cjs`, {
251
- encoding: 'utf8',
252
- stdio: ['pipe', 'pipe', 'pipe']
253
- });
260
+ const emptyResult = runStatusline('');
254
261
 
255
262
  test('Empty input handled', () => {
256
263
  // Should either error gracefully or produce fallback
@@ -281,10 +288,7 @@ const multilineInput = JSON.stringify({
281
288
  });
282
289
 
283
290
  try {
284
- const multilineResult = execSync(`echo '${multilineInput.replace(/'/g, "'\\''")}' | node .claude/statusline.cjs`, {
285
- encoding: 'utf8',
286
- stdio: ['pipe', 'pipe', 'pipe']
287
- });
291
+ const multilineResult = runStatusline(multilineInput);
288
292
 
289
293
  test('Multi-line output generates content', () => {
290
294
  assertTrue(multilineResult.length > 0, 'Should generate output');
@@ -322,10 +326,7 @@ const expandInput = JSON.stringify({
322
326
  });
323
327
 
324
328
  try {
325
- const expandResult = execSync(`echo '${expandInput.replace(/'/g, "'\\''")}' | node .claude/statusline.cjs`, {
326
- encoding: 'utf8',
327
- stdio: ['pipe', 'pipe', 'pipe']
328
- });
329
+ const expandResult = runStatusline(expandInput);
329
330
 
330
331
  test('Home directory expanded to tilde', () => {
331
332
  assertTrue(expandResult.includes('~') || expandResult.includes('projects'), 'Should expand or contain path');
@@ -352,11 +353,7 @@ const colorInput = JSON.stringify({
352
353
 
353
354
  try {
354
355
  // Test with NO_COLOR=1
355
- const noColorResult = execSync(`echo '${colorInput.replace(/'/g, "'\\''")}' | NO_COLOR=1 node .claude/statusline.cjs`, {
356
- encoding: 'utf8',
357
- stdio: ['pipe', 'pipe', 'pipe'],
358
- env: { ...process.env, NO_COLOR: '1' }
359
- });
356
+ const noColorResult = runStatusline(colorInput, { NO_COLOR: '1' });
360
357
 
361
358
  test('NO_COLOR=1 produces output', () => {
362
359
  assertTrue(noColorResult.length > 0, 'Should produce output with NO_COLOR=1');
@@ -390,11 +387,7 @@ const wideInput = JSON.stringify({
390
387
 
391
388
  let wideLines = 0;
392
389
  try {
393
- const wideResult = execSync(`echo '${wideInput.replace(/'/g, "'\\''")}' | COLUMNS=160 node .claude/statusline.cjs`, {
394
- encoding: 'utf8',
395
- stdio: ['pipe', 'pipe', 'pipe'],
396
- env: { ...process.env, COLUMNS: '160' }
397
- });
390
+ const wideResult = runStatusline(wideInput, { COLUMNS: '160' });
398
391
  wideLines = wideResult.trim().split('\n').length;
399
392
 
400
393
  test('Wide terminal (160 cols) produces output', () => {
@@ -425,11 +418,7 @@ const narrowInput = JSON.stringify({
425
418
  });
426
419
 
427
420
  try {
428
- const narrowResult = execSync(`echo '${narrowInput.replace(/'/g, "'\\''")}' | COLUMNS=80 node .claude/statusline.cjs`, {
429
- encoding: 'utf8',
430
- stdio: ['pipe', 'pipe', 'pipe'],
431
- env: { ...process.env, COLUMNS: '80' }
432
- });
421
+ const narrowResult = runStatusline(narrowInput, { COLUMNS: '80' });
433
422
  const narrowLines = narrowResult.trim().split('\n').length;
434
423
 
435
424
  test('Narrow terminal (80 cols) produces output', () => {
@@ -463,11 +452,7 @@ const longPathInput = JSON.stringify({
463
452
  });
464
453
 
465
454
  try {
466
- const longPathResult = execSync(`echo '${longPathInput.replace(/'/g, "'\\''")}' | COLUMNS=100 node .claude/statusline.cjs`, {
467
- encoding: 'utf8',
468
- stdio: ['pipe', 'pipe', 'pipe'],
469
- env: { ...process.env, COLUMNS: '100' }
470
- });
455
+ const longPathResult = runStatusline(longPathInput, { COLUMNS: '100' });
471
456
 
472
457
  test('Long path produces output without crash', () => {
473
458
  assertTrue(longPathResult.length > 0, 'Should produce output');
@@ -497,11 +482,7 @@ const longModelInput = JSON.stringify({
497
482
  });
498
483
 
499
484
  try {
500
- const longModelResult = execSync(`echo '${longModelInput.replace(/'/g, "'\\''")}' | COLUMNS=100 node .claude/statusline.cjs`, {
501
- encoding: 'utf8',
502
- stdio: ['pipe', 'pipe', 'pipe'],
503
- env: { ...process.env, COLUMNS: '100' }
504
- });
485
+ const longModelResult = runStatusline(longModelInput, { COLUMNS: '100' });
505
486
 
506
487
  test('Long model name produces output', () => {
507
488
  assertTrue(longModelResult.length > 0, 'Should produce output');
@@ -580,10 +561,7 @@ const agentTodoInput = JSON.stringify({
580
561
  });
581
562
 
582
563
  try {
583
- const agentTodoResult = execSync(`echo '${agentTodoInput.replace(/'/g, "'\\''")}' | node .claude/statusline.cjs`, {
584
- encoding: 'utf8',
585
- stdio: ['pipe', 'pipe', 'pipe']
586
- });
564
+ const agentTodoResult = runStatusline(agentTodoInput);
587
565
 
588
566
  test('Agent/Todo tracking produces output', () => {
589
567
  assertTrue(agentTodoResult.length > 0, 'Should produce output');
@@ -624,10 +602,7 @@ const zeroContextInput = JSON.stringify({
624
602
  });
625
603
 
626
604
  try {
627
- const zeroResult = execSync(`echo '${zeroContextInput.replace(/'/g, "'\\''")}' | node .claude/statusline.cjs`, {
628
- encoding: 'utf8',
629
- stdio: ['pipe', 'pipe', 'pipe']
630
- });
605
+ const zeroResult = runStatusline(zeroContextInput);
631
606
 
632
607
  test('Zero context produces output', () => {
633
608
  assertTrue(zeroResult.length > 0, 'Should produce output');
@@ -638,11 +613,7 @@ try {
638
613
 
639
614
  // Test with very small terminal
640
615
  try {
641
- const tinyResult = execSync(`echo '${wideInput.replace(/'/g, "'\\''")}' | COLUMNS=40 node .claude/statusline.cjs`, {
642
- encoding: 'utf8',
643
- stdio: ['pipe', 'pipe', 'pipe'],
644
- env: { ...process.env, COLUMNS: '40' }
645
- });
616
+ const tinyResult = runStatusline(wideInput, { COLUMNS: '40' });
646
617
 
647
618
  test('Very narrow terminal (40 cols) handles gracefully', () => {
648
619
  assertTrue(tinyResult.length > 0, 'Should produce output even at 40 cols');
@@ -138,9 +138,9 @@ const scenarios = [
138
138
  desc: 'Grep in src'
139
139
  },
140
140
  {
141
- input: { tool_name: 'Glob', tool_input: { pattern: '**/*.ts' } },
141
+ input: { tool_name: 'Glob', tool_input: { pattern: 'src/**/*.ts' } },
142
142
  expected: 'ALLOWED',
143
- desc: 'Glob all .ts files'
143
+ desc: 'Glob scoped .ts files in src'
144
144
  },
145
145
  {
146
146
  input: { tool_name: 'Bash', tool_input: { command: 'find packages -name "*.json" | head' } },
@@ -9,7 +9,7 @@ const { execSync } = require('child_process');
9
9
  const fs = require('fs');
10
10
  const path = require('path');
11
11
 
12
- const scriptPath = path.join(__dirname, '..', 'scout-block', 'scout-block.sh');
12
+ const scriptPath = path.join(__dirname, '..', 'scout-block.cjs');
13
13
  const ckignorePath = path.join(__dirname, '..', '..', '.ckignore');
14
14
  const ckignoreBackupPath = ckignorePath + '.backup';
15
15
 
@@ -23,7 +23,7 @@ if (fs.existsSync(ckignorePath)) {
23
23
  function runTest(name, input, expected) {
24
24
  try {
25
25
  const inputJson = JSON.stringify(input);
26
- execSync(`bash "${scriptPath}"`, {
26
+ execSync(`node "${scriptPath}"`, {
27
27
  input: inputJson,
28
28
  encoding: 'utf-8',
29
29
  stdio: ['pipe', 'pipe', 'pipe']
@@ -48,6 +48,11 @@ function restoreCkignore() {
48
48
  if (fs.existsSync(ckignoreBackupPath)) {
49
49
  fs.unlinkSync(ckignoreBackupPath);
50
50
  }
51
+ } else {
52
+ // If there was no original .ckignore, remove the test-created one
53
+ if (fs.existsSync(ckignorePath)) {
54
+ fs.unlinkSync(ckignorePath);
55
+ }
51
56
  }
52
57
  }
53
58
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngxtm/devkit",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "Per-project AI skills with smart tech detection - lightweight and context-optimized",
5
5
  "main": "cli/index.js",
6
6
  "bin": {
@@ -1,126 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Test script for modularization-hook.js (PostToolUse hook)
5
- * Tests if the hook correctly identifies files exceeding LOC threshold
6
- */
7
-
8
- const { execSync } = require('child_process');
9
- const path = require('path');
10
- const fs = require('fs');
11
- const os = require('os');
12
-
13
- // Create a temporary test file
14
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'modularization-test-'));
15
- const testFilePath = path.join(tempDir, 'test-file.js');
16
-
17
- // Create file with 250 lines (exceeds 200 LOC threshold)
18
- const longContent = Array(250).fill('// Test line').join('\n');
19
- fs.writeFileSync(testFilePath, longContent);
20
-
21
- // Create file with 50 lines (under threshold)
22
- const shortFilePath = path.join(tempDir, 'short-file.js');
23
- const shortContent = Array(50).fill('// Test line').join('\n');
24
- fs.writeFileSync(shortFilePath, shortContent);
25
-
26
- const testCases = [
27
- {
28
- name: 'Write tool with file exceeding LOC threshold',
29
- input: {
30
- tool_name: 'Write',
31
- tool_input: {
32
- file_path: testFilePath,
33
- content: longContent
34
- }
35
- },
36
- expectSuggestion: true
37
- },
38
- {
39
- name: 'Edit tool with file exceeding LOC threshold',
40
- input: {
41
- tool_name: 'Edit',
42
- tool_input: {
43
- file_path: testFilePath,
44
- old_string: '// Test line',
45
- new_string: '// Modified line'
46
- }
47
- },
48
- expectSuggestion: true
49
- },
50
- {
51
- name: 'Write tool with short file (under threshold)',
52
- input: {
53
- tool_name: 'Write',
54
- tool_input: {
55
- file_path: shortFilePath,
56
- content: shortContent
57
- }
58
- },
59
- expectSuggestion: false
60
- },
61
- {
62
- name: 'Write tool with non-existent file',
63
- input: {
64
- tool_name: 'Write',
65
- tool_input: {
66
- file_path: '/tmp/non-existent-file.js',
67
- content: 'test'
68
- }
69
- },
70
- expectSuggestion: false
71
- }
72
- ];
73
-
74
- console.log('Testing modularization-hook.js...\n');
75
-
76
- const scriptPath = path.join(__dirname, '..', 'modularization-hook.js');
77
- let passed = 0;
78
- let failed = 0;
79
-
80
- for (const test of testCases) {
81
- try {
82
- const input = JSON.stringify(test.input);
83
- const result = execSync(`node "${scriptPath}"`, {
84
- input,
85
- encoding: 'utf-8',
86
- stdio: ['pipe', 'pipe', 'pipe'],
87
- env: { ...process.env, MODULARIZATION_HOOK_DEBUG: 'false' }
88
- });
89
-
90
- let hasSuggestion = false;
91
- if (result && result.trim()) {
92
- try {
93
- const output = JSON.parse(result.trim());
94
- hasSuggestion = output.hookSpecificOutput?.additionalContext?.includes('LOC');
95
- } catch (e) {
96
- // Not valid JSON or no output
97
- }
98
- }
99
-
100
- const success = hasSuggestion === test.expectSuggestion;
101
-
102
- if (success) {
103
- console.log(`✓ ${test.name}: ${hasSuggestion ? 'suggestion shown' : 'no suggestion'}`);
104
- passed++;
105
- } else {
106
- console.log(`✗ ${test.name}: expected ${test.expectSuggestion ? 'suggestion' : 'no suggestion'}, got ${hasSuggestion ? 'suggestion' : 'no suggestion'}`);
107
- if (result) {
108
- console.log(` Output: ${result.trim()}`);
109
- }
110
- failed++;
111
- }
112
- } catch (error) {
113
- console.log(`✗ ${test.name}: error executing hook`);
114
- console.log(` Error: ${error.message}`);
115
- if (error.stderr) {
116
- console.log(` Stderr: ${error.stderr.toString()}`);
117
- }
118
- failed++;
119
- }
120
- }
121
-
122
- // Cleanup
123
- fs.rmSync(tempDir, { recursive: true, force: true });
124
-
125
- console.log(`\nResults: ${passed} passed, ${failed} failed`);
126
- process.exit(failed > 0 ? 1 : 0);