stigmergy 1.2.8 → 1.2.10

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 (48) hide show
  1. package/README.md +40 -6
  2. package/STIGMERGY.md +10 -0
  3. package/package.json +19 -5
  4. package/scripts/preuninstall.js +10 -0
  5. package/src/adapters/claude/install_claude_integration.js +21 -21
  6. package/src/adapters/codebuddy/install_codebuddy_integration.js +54 -51
  7. package/src/adapters/codex/install_codex_integration.js +27 -28
  8. package/src/adapters/gemini/install_gemini_integration.js +60 -60
  9. package/src/adapters/iflow/install_iflow_integration.js +72 -72
  10. package/src/adapters/qoder/install_qoder_integration.js +64 -64
  11. package/src/adapters/qwen/install_qwen_integration.js +7 -7
  12. package/src/cli/router.js +581 -175
  13. package/src/commands/skill-bridge.js +39 -0
  14. package/src/commands/skill-handler.js +150 -0
  15. package/src/commands/skill.js +127 -0
  16. package/src/core/cli_path_detector.js +573 -0
  17. package/src/core/cli_tools.js +72 -1
  18. package/src/core/coordination/nodejs/AdapterManager.js +29 -1
  19. package/src/core/directory_permission_manager.js +568 -0
  20. package/src/core/enhanced_cli_installer.js +609 -0
  21. package/src/core/installer.js +232 -88
  22. package/src/core/multilingual/language-pattern-manager.js +78 -50
  23. package/src/core/persistent_shell_configurator.js +468 -0
  24. package/src/core/skills/StigmergySkillManager.js +357 -0
  25. package/src/core/skills/__tests__/SkillInstaller.test.js +275 -0
  26. package/src/core/skills/__tests__/SkillParser.test.js +202 -0
  27. package/src/core/skills/__tests__/SkillReader.test.js +189 -0
  28. package/src/core/skills/cli-command-test.js +201 -0
  29. package/src/core/skills/comprehensive-e2e-test.js +473 -0
  30. package/src/core/skills/e2e-test.js +267 -0
  31. package/src/core/skills/embedded-openskills/SkillInstaller.js +438 -0
  32. package/src/core/skills/embedded-openskills/SkillParser.js +123 -0
  33. package/src/core/skills/embedded-openskills/SkillReader.js +143 -0
  34. package/src/core/skills/integration-test.js +248 -0
  35. package/src/core/skills/package.json +6 -0
  36. package/src/core/skills/regression-test.js +285 -0
  37. package/src/core/skills/run-all-tests.js +129 -0
  38. package/src/core/skills/sync-test.js +210 -0
  39. package/src/core/skills/test-runner.js +242 -0
  40. package/src/utils/helpers.js +3 -20
  41. package/src/auth.js +0 -173
  42. package/src/auth_command.js +0 -208
  43. package/src/calculator.js +0 -313
  44. package/src/core/enhanced_installer.js +0 -479
  45. package/src/core/enhanced_uninstaller.js +0 -638
  46. package/src/data_encryption.js +0 -143
  47. package/src/data_structures.js +0 -440
  48. package/src/deploy.js +0 -55
@@ -0,0 +1,123 @@
1
+ /**
2
+ * SkillParser - Parse SKILL.md files
3
+ *
4
+ * Adapted from: https://github.com/numman-ali/openskills
5
+ * Original License: Apache 2.0
6
+ * Modifications: Copyright Stigmergy Project
7
+ */
8
+
9
+ export class SkillParser {
10
+ /**
11
+ * Parse YAML frontmatter from skill content
12
+ * @param {string} content - Full SKILL.md content
13
+ * @returns {Object} Parsed metadata
14
+ */
15
+ parseMetadata(content) {
16
+ const match = content.match(/^---\n(.*?)\n---/s);
17
+ if (!match) {
18
+ return {};
19
+ }
20
+
21
+ const yamlContent = match[1];
22
+ const metadata = {};
23
+
24
+ const lines = yamlContent.split('\n');
25
+ let currentKey = null;
26
+ let currentArray = null;
27
+
28
+ for (const line of lines) {
29
+ // Handle array items
30
+ if (line.trim().startsWith('-') && currentKey) {
31
+ const value = line.trim().substring(1).trim();
32
+ if (!currentArray) {
33
+ currentArray = [];
34
+ metadata[currentKey] = currentArray;
35
+ }
36
+ currentArray.push(value);
37
+ continue;
38
+ }
39
+
40
+ // Handle key-value pairs
41
+ const colonIndex = line.indexOf(':');
42
+ if (colonIndex > 0) {
43
+ currentKey = line.substring(0, colonIndex).trim();
44
+ let value = line.substring(colonIndex + 1).trim();
45
+
46
+ // Reset array tracking
47
+ currentArray = null;
48
+
49
+ // Handle multiline values (>)
50
+ if (value === '>') {
51
+ continue; // Next lines will be the value
52
+ }
53
+
54
+ // Remove quotes
55
+ if ((value.startsWith('"') && value.endsWith('"')) ||
56
+ (value.startsWith("'") && value.endsWith("'"))) {
57
+ value = value.slice(1, -1);
58
+ }
59
+
60
+ if (value) {
61
+ metadata[currentKey] = value;
62
+ }
63
+ } else if (currentKey && line.trim()) {
64
+ // Multiline continuation
65
+ const existingValue = metadata[currentKey];
66
+ metadata[currentKey] = existingValue
67
+ ? `${existingValue} ${line.trim()}`
68
+ : line.trim();
69
+ }
70
+ }
71
+
72
+ return metadata;
73
+ }
74
+
75
+ /**
76
+ * Extract content after frontmatter
77
+ * @param {string} content - Full SKILL.md content
78
+ * @returns {string} Content without frontmatter
79
+ */
80
+ extractContent(content) {
81
+ const match = content.match(/^---\n.*?\n---\n(.*)$/s);
82
+ return match ? match[1].trim() : content;
83
+ }
84
+
85
+ /**
86
+ * Validate skill structure and content
87
+ * @param {string} content - Full SKILL.md content
88
+ * @returns {Object} Validation result with valid flag and errors array
89
+ */
90
+ validateSkill(content) {
91
+ const errors = [];
92
+ const metadata = this.parseMetadata(content);
93
+
94
+ // Required fields
95
+ if (!metadata.name) {
96
+ errors.push('Missing required field: name');
97
+ }
98
+
99
+ if (!metadata.description) {
100
+ errors.push('Missing required field: description');
101
+ }
102
+
103
+ // Name format validation
104
+ if (metadata.name && !/^[a-z0-9-]+$/.test(metadata.name)) {
105
+ errors.push('Skill name must use lowercase and hyphens only');
106
+ }
107
+
108
+ // Content length validation (approximate word count)
109
+ const mainContent = this.extractContent(content);
110
+ const wordCount = mainContent.split(/\s+/).length;
111
+
112
+ if (wordCount > 5000) {
113
+ errors.push('SKILL.md content exceeds 5000 words');
114
+ }
115
+
116
+ return {
117
+ valid: errors.length === 0,
118
+ errors: errors,
119
+ metadata: metadata,
120
+ wordCount: wordCount
121
+ };
122
+ }
123
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * SkillReader - Read and locate skills
3
+ *
4
+ * Adapted from: https://github.com/numman-ali/openskills
5
+ * Original License: Apache 2.0
6
+ * Modifications: Copyright Stigmergy Project
7
+ */
8
+
9
+ import fs from 'fs/promises';
10
+ import path from 'path';
11
+ import os from 'os';
12
+ import { SkillParser } from './SkillParser.js';
13
+
14
+ export class SkillReader {
15
+ /**
16
+ * @param {string[]} customSearchPaths - Optional custom search paths
17
+ */
18
+ constructor(customSearchPaths = null) {
19
+ this.parser = new SkillParser();
20
+
21
+ if (customSearchPaths) {
22
+ this.searchPaths = customSearchPaths;
23
+ } else {
24
+ // Default search paths (priority order)
25
+ this.searchPaths = [
26
+ path.join(process.cwd(), '.agent/skills'), // Project universal
27
+ path.join(os.homedir(), '.agent/skills'), // Global universal
28
+ path.join(process.cwd(), '.claude/skills'), // Project Claude
29
+ path.join(os.homedir(), '.claude/skills'), // Global Claude
30
+ path.join(os.homedir(), '.stigmergy/skills') // Stigmergy unified
31
+ ];
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Find skill in search paths
37
+ * @param {string} name - Skill name
38
+ * @returns {Promise<Object|null>} Skill info or null if not found
39
+ */
40
+ async findSkill(name) {
41
+ for (const basePath of this.searchPaths) {
42
+ const skillPath = path.join(basePath, name, 'SKILL.md');
43
+
44
+ try {
45
+ await fs.access(skillPath);
46
+ return {
47
+ name: name,
48
+ path: skillPath,
49
+ baseDir: path.dirname(skillPath)
50
+ };
51
+ } catch {
52
+ // Continue to next path
53
+ continue;
54
+ }
55
+ }
56
+
57
+ return null;
58
+ }
59
+
60
+ /**
61
+ * Read skill content
62
+ * @param {string} name - Skill name
63
+ * @returns {Promise<Object>} Skill data with name, baseDir, and content
64
+ */
65
+ async readSkill(name) {
66
+ const skill = await this.findSkill(name);
67
+
68
+ if (!skill) {
69
+ throw new Error(`Skill '${name}' not found in any search path`);
70
+ }
71
+
72
+ const content = await fs.readFile(skill.path, 'utf-8');
73
+
74
+ return {
75
+ name: skill.name,
76
+ baseDir: skill.baseDir,
77
+ content: content
78
+ };
79
+ }
80
+
81
+ /**
82
+ * List all available skills
83
+ * @returns {Promise<Array>} Array of skill info objects
84
+ */
85
+ async listSkills() {
86
+ const skills = [];
87
+ const seenNames = new Set();
88
+
89
+ for (const searchPath of this.searchPaths) {
90
+ try {
91
+ const entries = await fs.readdir(searchPath, { withFileTypes: true });
92
+
93
+ for (const entry of entries) {
94
+ if (!entry.isDirectory()) continue;
95
+ if (seenNames.has(entry.name)) continue; // Skip duplicates
96
+
97
+ const skillPath = path.join(searchPath, entry.name, 'SKILL.md');
98
+
99
+ try {
100
+ await fs.access(skillPath);
101
+ const content = await fs.readFile(skillPath, 'utf-8');
102
+ const metadata = this.parser.parseMetadata(content);
103
+
104
+ skills.push({
105
+ name: entry.name,
106
+ description: metadata.description || '',
107
+ location: this.determineLocation(searchPath),
108
+ path: skillPath
109
+ });
110
+
111
+ seenNames.add(entry.name);
112
+ } catch {
113
+ // Not a valid skill directory, skip
114
+ continue;
115
+ }
116
+ }
117
+ } catch {
118
+ // Search path doesn't exist, skip
119
+ continue;
120
+ }
121
+ }
122
+
123
+ return skills;
124
+ }
125
+
126
+ /**
127
+ * Determine skill location type from path
128
+ * @private
129
+ */
130
+ determineLocation(searchPath) {
131
+ if (searchPath.includes('.stigmergy')) {
132
+ return 'stigmergy';
133
+ } else if (searchPath.includes('.agent')) {
134
+ return 'universal';
135
+ } else if (searchPath.includes('.claude')) {
136
+ return 'claude';
137
+ } else if (searchPath.includes(process.cwd())) {
138
+ return 'project';
139
+ } else {
140
+ return 'global';
141
+ }
142
+ }
143
+ }
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Integration Test - End-to-end verification of Stigmergy Skills System
3
+ *
4
+ * TDD Verification:
5
+ * 1. Create test skills
6
+ * 2. Install skills
7
+ * 3. Read skills
8
+ * 4. List skills
9
+ * 5. Sync to AGENTS.md
10
+ * 6. Validate skills
11
+ * 7. Remove skills
12
+ */
13
+
14
+ import { StigmergySkillManager } from './StigmergySkillManager.js';
15
+ import fs from 'fs/promises';
16
+ import path from 'path';
17
+ import os from 'os';
18
+
19
+ class IntegrationTestRunner {
20
+ constructor() {
21
+ this.testDir = path.join(os.tmpdir(), `stigmergy-integration-test-${Date.now()}`);
22
+ this.manager = new StigmergySkillManager({
23
+ skillsDir: path.join(this.testDir, 'skills')
24
+ });
25
+ this.passed = 0;
26
+ this.failed = 0;
27
+ }
28
+
29
+ async test(name, fn) {
30
+ try {
31
+ await fn();
32
+ this.passed++;
33
+ console.log(`[OK] ${name}`);
34
+ } catch (err) {
35
+ this.failed++;
36
+ console.error(`[X] ${name}`);
37
+ console.error(` Error: ${err.message}`);
38
+ }
39
+ }
40
+
41
+ async setup() {
42
+ console.log('[INFO] Setting up test environment...\n');
43
+
44
+ // Create test skill repository structure
45
+ const repoDir = path.join(this.testDir, 'test-repo');
46
+ await fs.mkdir(repoDir, { recursive: true });
47
+
48
+ // Create test skill #1
49
+ const skill1Dir = path.join(repoDir, 'test-analyzer');
50
+ await fs.mkdir(skill1Dir, { recursive: true });
51
+ await fs.writeFile(
52
+ path.join(skill1Dir, 'SKILL.md'),
53
+ `---
54
+ name: test-analyzer
55
+ description: Test data analysis skill for integration testing
56
+ version: 1.0.0
57
+ ---
58
+
59
+ # Test Analyzer
60
+
61
+ ## Trigger Conditions
62
+ When user asks to analyze test data.
63
+
64
+ ## Core Workflow
65
+
66
+ ### Step 1: Data Loading
67
+ 1. Read input file
68
+ 2. Validate format
69
+
70
+ ### Step 2: Execute Analysis
71
+ Run analysis script:
72
+ \`\`\`bash
73
+ python scripts/analyze.py --input data.json
74
+ \`\`\`
75
+
76
+ ### Step 3: Generate Report
77
+ Output results to report.json
78
+ `
79
+ );
80
+
81
+ // Add scripts directory
82
+ await fs.mkdir(path.join(skill1Dir, 'scripts'));
83
+ await fs.writeFile(
84
+ path.join(skill1Dir, 'scripts', 'analyze.py'),
85
+ `#!/usr/bin/env python3
86
+ import json
87
+ import sys
88
+
89
+ data = json.load(sys.stdin)
90
+ print(json.dumps({"status": "analyzed", "count": len(data)}, indent=2))
91
+ `
92
+ );
93
+
94
+ // Create test skill #2
95
+ const skill2Dir = path.join(repoDir, 'test-formatter');
96
+ await fs.mkdir(skill2Dir);
97
+ await fs.writeFile(
98
+ path.join(skill2Dir, 'SKILL.md'),
99
+ `---
100
+ name: test-formatter
101
+ description: Test code formatting skill
102
+ ---
103
+
104
+ # Test Formatter
105
+
106
+ Format code according to standards.
107
+ `
108
+ );
109
+
110
+ console.log(`[OK] Test repository created at ${repoDir}\n`);
111
+ return repoDir;
112
+ }
113
+
114
+ async cleanup() {
115
+ console.log('\n[INFO] Cleaning up...');
116
+ await fs.rm(this.testDir, { recursive: true, force: true });
117
+ console.log('[OK] Cleanup complete');
118
+ }
119
+
120
+ summary() {
121
+ console.log(`\n${'='.repeat(60)}`);
122
+ console.log(`Integration Test Total: ${this.passed + this.failed} tests`);
123
+ console.log(`[OK] Passed: ${this.passed}`);
124
+ console.log(`[X] Failed: ${this.failed}`);
125
+ console.log('='.repeat(60));
126
+ return this.failed === 0;
127
+ }
128
+ }
129
+
130
+ async function runIntegrationTests() {
131
+ const runner = new IntegrationTestRunner();
132
+
133
+ try {
134
+ // Setup
135
+ const repoDir = await runner.setup();
136
+
137
+ console.log('[LIST] Running integration tests...\n');
138
+
139
+ // Test 1: Scan skills
140
+ await runner.test('Scan local skill repository', async () => {
141
+ const skills = await runner.manager.installer.scanSkills(repoDir);
142
+ assert(skills.length === 2, `Expected 2 skills, got ${skills.length}`);
143
+ assert(skills.some(s => s.name === 'test-analyzer'), 'Missing test-analyzer');
144
+ assert(skills.some(s => s.name === 'test-formatter'), 'Missing test-formatter');
145
+ });
146
+
147
+ // Test 2: Install skill manually
148
+ await runner.test('Install single skill', async () => {
149
+ const skills = await runner.manager.installer.scanSkills(repoDir);
150
+ const skill = skills.find(s => s.name === 'test-analyzer');
151
+ await runner.manager.installer.installSkill(skill);
152
+
153
+ // Verify installation
154
+ const installed = await fs.access(
155
+ path.join(runner.testDir, 'skills', 'test-analyzer', 'SKILL.md')
156
+ ).then(() => true).catch(() => false);
157
+ assert(installed, 'Skill not installed');
158
+ });
159
+
160
+ // Test 3: Read installed skill
161
+ await runner.test('Read installed skill', async () => {
162
+ const skill = await runner.manager.reader.readSkill('test-analyzer');
163
+ assert(skill.name === 'test-analyzer', `Expected name 'test-analyzer', got '${skill.name}'`);
164
+ assert(skill.content.includes('Test Analyzer'), 'Missing expected content');
165
+ assert(skill.baseDir.includes('test-analyzer'), 'Invalid base directory');
166
+ });
167
+
168
+ // Test 4: List all skills
169
+ await runner.test('List all installed skills', async () => {
170
+ const skills = await runner.manager.reader.listSkills();
171
+ assert(skills.length >= 1, 'No skills found');
172
+ assert(skills.some(s => s.name === 'test-analyzer'));
173
+ });
174
+
175
+ // Test 5: Validate skill format
176
+ await runner.test('Validate skill format', async () => {
177
+ const validation = await runner.manager.validate('test-analyzer');
178
+ assert(validation.valid === true, `Skill validation failed: ${validation.errors.join(', ')}`);
179
+ });
180
+
181
+ // Test 6: Sync to AGENTS.md
182
+ await runner.test('Sync skills to AGENTS.md', async () => {
183
+ // Create temporary AGENTS.md
184
+ const agentsMdPath = path.join(runner.testDir, 'AGENTS.md');
185
+ await fs.writeFile(agentsMdPath, '# Test AGENTS.md\n\n');
186
+
187
+ // Change working directory
188
+ const originalCwd = process.cwd();
189
+ process.chdir(runner.testDir);
190
+
191
+ try {
192
+ await runner.manager.sync();
193
+
194
+ // Verify AGENTS.md updated
195
+ const content = await fs.readFile(agentsMdPath, 'utf-8');
196
+ assert(content.includes('<available_skills>'), 'Missing skills section');
197
+ assert(content.includes('test-analyzer'), 'Missing test-analyzer in AGENTS.md');
198
+ } finally {
199
+ process.chdir(originalCwd);
200
+ }
201
+ });
202
+
203
+ // Test 7: Remove skill
204
+ await runner.test('Remove skill', async () => {
205
+ await runner.manager.installer.uninstallSkill('test-analyzer');
206
+
207
+ // Verify removal
208
+ const exists = await fs.access(
209
+ path.join(runner.testDir, 'skills', 'test-analyzer')
210
+ ).then(() => true).catch(() => false);
211
+ assert(!exists, 'Skill still exists after removal');
212
+ });
213
+
214
+ return runner.summary();
215
+ } catch (err) {
216
+ console.error(`\n[X] Integration test failed: ${err.message}`);
217
+ console.error(err.stack);
218
+ return false;
219
+ } finally {
220
+ await runner.cleanup();
221
+ }
222
+ }
223
+
224
+ function assert(condition, message) {
225
+ if (!condition) {
226
+ throw new Error(message || 'Assertion failed');
227
+ }
228
+ }
229
+
230
+ // Run integration tests
231
+ console.log('[SUCCESS] Stigmergy Skills Integration Test\n');
232
+ console.log('TDD-driven, embedded OpenSkills core\n');
233
+
234
+ runIntegrationTests()
235
+ .then(success => {
236
+ if (success) {
237
+ console.log('\n[SUCCESS] All integration tests passed!');
238
+ console.log('\nNext steps:');
239
+ console.log(' 1. Integrate into stigmergy CLI main command');
240
+ console.log(' 2. Test real GitHub repository installation');
241
+ console.log(' 3. Verify integration with all CLI tools');
242
+ }
243
+ process.exit(success ? 0 : 1);
244
+ })
245
+ .catch(err => {
246
+ console.error('[X] Test execution failed:', err);
247
+ process.exit(1);
248
+ });
@@ -0,0 +1,6 @@
1
+ {
2
+ "type": "module",
3
+ "name": "@stigmergy/skills-core",
4
+ "version": "1.0.0",
5
+ "private": true
6
+ }