@sylphx/flow 1.1.1 → 1.3.0

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 (47) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/package.json +1 -1
  3. package/src/commands/flow-command.ts +28 -0
  4. package/src/commands/hook-command.ts +10 -230
  5. package/src/composables/index.ts +0 -1
  6. package/src/config/servers.ts +35 -78
  7. package/src/core/interfaces.ts +0 -33
  8. package/src/domains/index.ts +0 -2
  9. package/src/index.ts +0 -4
  10. package/src/services/mcp-service.ts +0 -16
  11. package/src/targets/claude-code.ts +3 -9
  12. package/src/targets/functional/claude-code-logic.ts +4 -22
  13. package/src/targets/opencode.ts +0 -6
  14. package/src/types/mcp.types.ts +29 -38
  15. package/src/types/target.types.ts +0 -2
  16. package/src/types.ts +0 -1
  17. package/src/utils/sync-utils.ts +106 -0
  18. package/src/commands/codebase-command.ts +0 -168
  19. package/src/commands/knowledge-command.ts +0 -161
  20. package/src/composables/useTargetConfig.ts +0 -45
  21. package/src/core/formatting/bytes.test.ts +0 -115
  22. package/src/core/validation/limit.test.ts +0 -155
  23. package/src/core/validation/query.test.ts +0 -44
  24. package/src/domains/codebase/index.ts +0 -5
  25. package/src/domains/codebase/tools.ts +0 -139
  26. package/src/domains/knowledge/index.ts +0 -10
  27. package/src/domains/knowledge/resources.ts +0 -537
  28. package/src/domains/knowledge/tools.ts +0 -174
  29. package/src/services/search/base-indexer.ts +0 -156
  30. package/src/services/search/codebase-indexer-types.ts +0 -38
  31. package/src/services/search/codebase-indexer.ts +0 -647
  32. package/src/services/search/embeddings-provider.ts +0 -455
  33. package/src/services/search/embeddings.ts +0 -316
  34. package/src/services/search/functional-indexer.ts +0 -323
  35. package/src/services/search/index.ts +0 -27
  36. package/src/services/search/indexer.ts +0 -380
  37. package/src/services/search/knowledge-indexer.ts +0 -422
  38. package/src/services/search/semantic-search.ts +0 -244
  39. package/src/services/search/tfidf.ts +0 -559
  40. package/src/services/search/unified-search-service.ts +0 -888
  41. package/src/services/storage/cache-storage.ts +0 -487
  42. package/src/services/storage/drizzle-storage.ts +0 -581
  43. package/src/services/storage/index.ts +0 -15
  44. package/src/services/storage/lancedb-vector-storage.ts +0 -494
  45. package/src/services/storage/memory-storage.ts +0 -268
  46. package/src/services/storage/separated-storage.ts +0 -467
  47. package/src/services/storage/vector-storage.ts +0 -13
@@ -1,115 +0,0 @@
1
- /**
2
- * Tests for Byte Formatting Utilities
3
- */
4
-
5
- import { describe, expect, it } from 'bun:test';
6
- import { formatBytes, formatFileSize } from './bytes.js';
7
-
8
- describe('formatBytes', () => {
9
- describe('default options (decimals: 2, longUnits)', () => {
10
- it('formats 0 bytes', () => {
11
- expect(formatBytes(0)).toBe('0 Bytes');
12
- });
13
-
14
- it('formats bytes', () => {
15
- expect(formatBytes(500)).toBe('500 Bytes');
16
- });
17
-
18
- it('formats kilobytes', () => {
19
- expect(formatBytes(1024)).toBe('1 KB');
20
- expect(formatBytes(1536)).toBe('1.5 KB');
21
- });
22
-
23
- it('formats megabytes', () => {
24
- expect(formatBytes(1048576)).toBe('1 MB');
25
- expect(formatBytes(1572864)).toBe('1.5 MB');
26
- });
27
-
28
- it('formats gigabytes', () => {
29
- expect(formatBytes(1073741824)).toBe('1 GB');
30
- expect(formatBytes(1610612736)).toBe('1.5 GB');
31
- });
32
-
33
- it('formats terabytes', () => {
34
- expect(formatBytes(1099511627776)).toBe('1 TB');
35
- });
36
-
37
- it('rounds to 2 decimal places', () => {
38
- expect(formatBytes(1587)).toBe('1.55 KB');
39
- expect(formatBytes(1638400)).toBe('1.56 MB');
40
- });
41
- });
42
-
43
- describe('with decimals option', () => {
44
- it('formats with 0 decimals', () => {
45
- expect(formatBytes(1536, { decimals: 0 })).toBe('2 KB');
46
- expect(formatBytes(1024, { decimals: 0 })).toBe('1 KB');
47
- });
48
-
49
- it('formats with 1 decimal', () => {
50
- expect(formatBytes(1536, { decimals: 1 })).toBe('1.5 KB');
51
- expect(formatBytes(1587, { decimals: 1 })).toBe('1.5 KB');
52
- });
53
-
54
- it('formats with 3 decimals', () => {
55
- expect(formatBytes(1587, { decimals: 3 })).toBe('1.55 KB'); // toFixed trims to 1.550, then trimmed
56
- });
57
- });
58
-
59
- describe('with shortUnits option', () => {
60
- it('uses short unit for 0 bytes', () => {
61
- expect(formatBytes(0, { shortUnits: true })).toBe('0 B');
62
- });
63
-
64
- it('uses short unit for bytes', () => {
65
- expect(formatBytes(500, { shortUnits: true })).toBe('500 B');
66
- });
67
-
68
- it('uses short units for kilobytes', () => {
69
- expect(formatBytes(1024, { shortUnits: true })).toBe('1 KB');
70
- });
71
-
72
- it('uses short units for megabytes', () => {
73
- expect(formatBytes(1048576, { shortUnits: true })).toBe('1 MB');
74
- });
75
- });
76
-
77
- describe('combined options', () => {
78
- it('uses short units with 1 decimal', () => {
79
- expect(formatBytes(1536, { decimals: 1, shortUnits: true })).toBe('1.5 KB');
80
- });
81
-
82
- it('uses short units with 0 decimals', () => {
83
- expect(formatBytes(1536, { decimals: 0, shortUnits: true })).toBe('2 KB');
84
- });
85
- });
86
-
87
- describe('edge cases', () => {
88
- it('handles 1 byte', () => {
89
- expect(formatBytes(1)).toBe('1 Bytes');
90
- });
91
-
92
- it('handles very large numbers', () => {
93
- const result = formatBytes(1099511627776 * 1024); // 1 PB
94
- expect(result).toContain('TB'); // Will show in TB since we only go up to TB
95
- });
96
-
97
- it('handles fractional KB', () => {
98
- expect(formatBytes(1500)).toBe('1.46 KB');
99
- });
100
- });
101
- });
102
-
103
- describe('formatFileSize', () => {
104
- it('formats with 1 decimal and short units', () => {
105
- expect(formatFileSize(0)).toBe('0 B');
106
- expect(formatFileSize(1024)).toBe('1 KB'); // toFixed(1) gives "1.0", trimmed to "1"
107
- expect(formatFileSize(1536)).toBe('1.5 KB');
108
- expect(formatFileSize(1048576)).toBe('1 MB'); // toFixed(1) gives "1.0", trimmed to "1"
109
- });
110
-
111
- it('is an alias for formatBytes with specific options', () => {
112
- const bytes = 1572864;
113
- expect(formatFileSize(bytes)).toBe(formatBytes(bytes, { decimals: 1, shortUnits: true }));
114
- });
115
- });
@@ -1,155 +0,0 @@
1
- /**
2
- * Tests for Limit Validation Utilities
3
- */
4
-
5
- import { describe, expect, it } from 'bun:test';
6
- import { validateLimit } from './limit.js';
7
-
8
- describe('validateLimit', () => {
9
- describe('with default parameters (defaultLimit=50, maxLimit=1000)', () => {
10
- it('returns default limit when undefined', () => {
11
- const result = validateLimit(undefined);
12
- expect(result._tag).toBe('Success');
13
- expect(result._tag === 'Success' && result.value).toBe(50);
14
- });
15
-
16
- it('accepts valid number', () => {
17
- const result = validateLimit(100);
18
- expect(result._tag).toBe('Success');
19
- expect(result._tag === 'Success' && result.value).toBe(100);
20
- });
21
-
22
- it('accepts valid string number', () => {
23
- const result = validateLimit('500');
24
- expect(result._tag).toBe('Success');
25
- expect(result._tag === 'Success' && result.value).toBe(500);
26
- });
27
-
28
- it('accepts limit at max', () => {
29
- const result = validateLimit(1000);
30
- expect(result._tag).toBe('Success');
31
- expect(result._tag === 'Success' && result.value).toBe(1000);
32
- });
33
-
34
- it('rejects limit above max', () => {
35
- const result = validateLimit(1001);
36
- expect(result._tag).toBe('Failure');
37
- expect(result._tag === 'Failure' && result.error.message).toBe('Limit cannot exceed 1000');
38
- });
39
-
40
- it('rejects zero', () => {
41
- const result = validateLimit(0);
42
- expect(result._tag).toBe('Failure');
43
- expect(result._tag === 'Failure' && result.error.message).toBe(
44
- 'Limit must be a positive number'
45
- );
46
- });
47
-
48
- it('rejects negative number', () => {
49
- const result = validateLimit(-10);
50
- expect(result._tag).toBe('Failure');
51
- expect(result._tag === 'Failure' && result.error.message).toBe(
52
- 'Limit must be a positive number'
53
- );
54
- });
55
-
56
- it('rejects invalid string', () => {
57
- const result = validateLimit('invalid');
58
- expect(result._tag).toBe('Failure');
59
- expect(result._tag === 'Failure' && result.error.message).toBe(
60
- 'Limit must be a positive number'
61
- );
62
- });
63
-
64
- it('rejects empty string', () => {
65
- const result = validateLimit('');
66
- expect(result._tag).toBe('Failure');
67
- expect(result._tag === 'Failure' && result.error.message).toBe(
68
- 'Limit must be a positive number'
69
- );
70
- });
71
- });
72
-
73
- describe('with custom default (defaultLimit=10)', () => {
74
- it('returns custom default when undefined', () => {
75
- const result = validateLimit(undefined, 10);
76
- expect(result._tag).toBe('Success');
77
- expect(result._tag === 'Success' && result.value).toBe(10);
78
- });
79
- });
80
-
81
- describe('with custom max (maxLimit=100)', () => {
82
- it('accepts limit at custom max', () => {
83
- const result = validateLimit(100, 10, 100);
84
- expect(result._tag).toBe('Success');
85
- expect(result._tag === 'Success' && result.value).toBe(100);
86
- });
87
-
88
- it('rejects limit above custom max', () => {
89
- const result = validateLimit(101, 10, 100);
90
- expect(result._tag).toBe('Failure');
91
- expect(result._tag === 'Failure' && result.error.message).toBe('Limit cannot exceed 100');
92
- });
93
- });
94
-
95
- describe('knowledge feature defaults (defaultLimit=10, maxLimit=100)', () => {
96
- it('returns 10 when undefined', () => {
97
- const result = validateLimit(undefined, 10, 100);
98
- expect(result._tag).toBe('Success');
99
- expect(result._tag === 'Success' && result.value).toBe(10);
100
- });
101
-
102
- it('accepts 50', () => {
103
- const result = validateLimit(50, 10, 100);
104
- expect(result._tag).toBe('Success');
105
- expect(result._tag === 'Success' && result.value).toBe(50);
106
- });
107
-
108
- it('rejects 101', () => {
109
- const result = validateLimit(101, 10, 100);
110
- expect(result._tag).toBe('Failure');
111
- expect(result._tag === 'Failure' && result.error.message).toBe('Limit cannot exceed 100');
112
- });
113
- });
114
-
115
- describe('memory feature defaults (defaultLimit=50, maxLimit=1000)', () => {
116
- it('returns 50 when undefined', () => {
117
- const result = validateLimit(undefined, 50, 1000);
118
- expect(result._tag).toBe('Success');
119
- expect(result._tag === 'Success' && result.value).toBe(50);
120
- });
121
-
122
- it('accepts 500', () => {
123
- const result = validateLimit(500, 50, 1000);
124
- expect(result._tag).toBe('Success');
125
- expect(result._tag === 'Success' && result.value).toBe(500);
126
- });
127
-
128
- it('rejects 1001', () => {
129
- const result = validateLimit(1001, 50, 1000);
130
- expect(result._tag).toBe('Failure');
131
- expect(result._tag === 'Failure' && result.error.message).toBe('Limit cannot exceed 1000');
132
- });
133
- });
134
-
135
- describe('edge cases', () => {
136
- it('accepts 1 (minimum valid)', () => {
137
- const result = validateLimit(1);
138
- expect(result._tag).toBe('Success');
139
- expect(result._tag === 'Success' && result.value).toBe(1);
140
- });
141
-
142
- it('handles string with whitespace', () => {
143
- const result = validateLimit(' 100 ');
144
- expect(result._tag).toBe('Success');
145
- expect(result._tag === 'Success' && result.value).toBe(100);
146
- });
147
-
148
- it('rejects decimal numbers', () => {
149
- const result = validateLimit('10.5');
150
- expect(result._tag).toBe('Success');
151
- // parseInt truncates to 10
152
- expect(result._tag === 'Success' && result.value).toBe(10);
153
- });
154
- });
155
- });
@@ -1,44 +0,0 @@
1
- /**
2
- * Tests for Query Validation Utilities
3
- */
4
-
5
- import { describe, expect, it } from 'bun:test';
6
- import { normalizeQuery } from './query.js';
7
-
8
- describe('normalizeQuery', () => {
9
- it('trims leading whitespace', () => {
10
- expect(normalizeQuery(' hello')).toBe('hello');
11
- });
12
-
13
- it('trims trailing whitespace', () => {
14
- expect(normalizeQuery('hello ')).toBe('hello');
15
- });
16
-
17
- it('trims both leading and trailing whitespace', () => {
18
- expect(normalizeQuery(' hello ')).toBe('hello');
19
- });
20
-
21
- it('preserves internal whitespace', () => {
22
- expect(normalizeQuery('hello world')).toBe('hello world');
23
- });
24
-
25
- it('handles empty string', () => {
26
- expect(normalizeQuery('')).toBe('');
27
- });
28
-
29
- it('handles string with only whitespace', () => {
30
- expect(normalizeQuery(' ')).toBe('');
31
- });
32
-
33
- it('handles tabs and newlines', () => {
34
- expect(normalizeQuery('\t\nhello\t\n')).toBe('hello');
35
- });
36
-
37
- it('handles already trimmed string', () => {
38
- expect(normalizeQuery('hello')).toBe('hello');
39
- });
40
-
41
- it('handles multi-line queries', () => {
42
- expect(normalizeQuery(' line1\nline2 ')).toBe('line1\nline2');
43
- });
44
- });
@@ -1,5 +0,0 @@
1
- /**
2
- * Codebase domain - Code analysis and search
3
- */
4
-
5
- export { registerCodebaseSearchTool, registerCodebaseTools } from './tools.js';
@@ -1,139 +0,0 @@
1
- /**
2
- * Codebase tools
3
- * All tools for working with project source code and files
4
- */
5
-
6
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
- import { z } from 'zod';
8
- import { getSearchService } from '../../services/search/unified-search-service.js';
9
-
10
- /**
11
- * Register codebase search tool
12
- */
13
- export function registerCodebaseSearchTool(server: McpServer): void {
14
- server.registerTool(
15
- 'codebase_search',
16
- {
17
- description: `Search project source files, documentation, and code. Use this to find implementations, functions, classes, or any code-related content.
18
-
19
- **IMPORTANT: Use this tool PROACTIVELY before starting work, not reactively when stuck.**
20
-
21
- This tool searches across all codebase files and returns the most relevant matches with content snippets.
22
-
23
- When to use this tool (BEFORE starting work):
24
- - **Before implementation**: Find existing patterns, similar functions, or reusable components
25
- - **Before refactoring**: Understand current implementation and dependencies
26
- - **Before adding features**: Check for existing similar functionality or conflicting code
27
- - **Before debugging**: Search for error messages, function names, or related code
28
- - **Before writing tests**: Find existing test patterns and test utilities
29
-
30
- The search includes:
31
- - Source code files (.ts, .js, .tsx, .jsx, etc.)
32
- - Configuration files (.json, .yaml, .toml, etc.)
33
- - Documentation files (.md, .txt, etc.)
34
- - Build and deployment files
35
-
36
- **Best Practice**: Search the codebase BEFORE writing new code to avoid duplication and follow existing patterns.`,
37
- inputSchema: {
38
- query: z
39
- .string()
40
- .describe('Search query - use natural language, function names, or technical terms'),
41
- limit: z
42
- .number()
43
- .default(10)
44
- .optional()
45
- .describe('Maximum number of results to return (default: 10)'),
46
- include_content: z
47
- .boolean()
48
- .default(true)
49
- .optional()
50
- .describe('Include file content snippets in results (default: true)'),
51
- file_extensions: z
52
- .array(z.string())
53
- .optional()
54
- .describe('Filter by file extensions (e.g., [".ts", ".tsx", ".js"])'),
55
- path_filter: z
56
- .string()
57
- .optional()
58
- .describe('Filter by path pattern (e.g., "src/components", "tests", "docs")'),
59
- exclude_paths: z
60
- .array(z.string())
61
- .optional()
62
- .describe(
63
- 'Exclude paths containing these patterns (e.g., ["node_modules", ".git", "dist"])'
64
- ),
65
- },
66
- },
67
- async ({
68
- query,
69
- limit = 10,
70
- include_content = true,
71
- file_extensions,
72
- path_filter,
73
- exclude_paths,
74
- }) => {
75
- try {
76
- // Use UnifiedSearchService - same logic as CLI
77
- const searchService = getSearchService();
78
- await searchService.initialize();
79
-
80
- // Check codebase status
81
- const status = await searchService.getStatus();
82
-
83
- // If indexing in progress, show progress
84
- if (status.codebase.isIndexing) {
85
- const progressBar =
86
- '█'.repeat(Math.floor(status.codebase.progress / 5)) +
87
- '░'.repeat(20 - Math.floor(status.codebase.progress / 5));
88
- return {
89
- content: [
90
- {
91
- type: 'text',
92
- text: `⏳ **Codebase Indexing In Progress**\n\nThe codebase is currently being indexed. Please wait...\n\n**Progress:** ${status.codebase.progress}%\n\`${progressBar}\`\n\n**Status:**\n- Files indexed: ${status.codebase.progress > 0 ? Math.floor((status.codebase.fileCount * status.codebase.progress) / 100) : 0}/${status.codebase.fileCount}\n${status.codebase.currentFile ? `- Current file: \`${status.codebase.currentFile}\`` : ''}\n\n**Estimated time:** ${status.codebase.progress > 0 ? 'Less than 1 minute' : 'Starting...'}\n\n💡 **Tip:** Try your search again in a few seconds.`,
93
- },
94
- ],
95
- };
96
- }
97
-
98
- if (!status.codebase.indexed) {
99
- return {
100
- content: [
101
- {
102
- type: 'text',
103
- text: `📭 **Codebase Not Indexed**\n\nThe codebase has not been indexed yet.\n\n**To fix:**\n- Run: \`sylphx codebase reindex\` from the command line\n- This will create a search index for all source files\n\n**Why this is needed:**\nThe first time you use codebase search, you need to build an index of all files. This only needs to be done once (or when files change significantly).`,
104
- },
105
- ],
106
- };
107
- }
108
-
109
- // Perform search using unified service
110
- const result = await searchService.searchCodebase(query, {
111
- limit,
112
- include_content,
113
- file_extensions,
114
- path_filter,
115
- exclude_paths,
116
- });
117
-
118
- // Return MCP-formatted results
119
- return searchService.formatResultsForMCP(result.results, query, result.totalIndexed);
120
- } catch (error) {
121
- return {
122
- content: [
123
- {
124
- type: 'text',
125
- text: `✗ Codebase search error: ${(error as Error).message}`,
126
- },
127
- ],
128
- };
129
- }
130
- }
131
- );
132
- }
133
-
134
- /**
135
- * Register all codebase tools
136
- */
137
- export function registerCodebaseTools(server: McpServer): void {
138
- registerCodebaseSearchTool(server);
139
- }
@@ -1,10 +0,0 @@
1
- /**
2
- * Knowledge domain - Documentation and guides
3
- */
4
-
5
- export { getKnowledgeContent } from './resources.js';
6
- export {
7
- registerKnowledgeGetTool,
8
- registerKnowledgeSearchTool,
9
- registerKnowledgeTools,
10
- } from './tools.js';