@latentforce/latentgraph 1.0.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.
@@ -0,0 +1,108 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import ignore from 'ignore';
4
+ const SHIFTIGNORE_FILE = '.shiftignore';
5
+ /**
6
+ * Load and parse a .shiftignore file from the project root.
7
+ * Returns an `ignore` instance that can test paths, or null if no .shiftignore exists.
8
+ *
9
+ * .shiftignore uses the same syntax as .gitignore:
10
+ * - Blank lines are ignored
11
+ * - Lines starting with # are comments
12
+ * - Standard glob patterns (*, **, ?)
13
+ * - Trailing / matches directories only
14
+ * - Leading / anchors to root
15
+ * - ! negates a pattern
16
+ */
17
+ const DEFAULT_SHIFTIGNORE = `# .shiftignore — files and directories to exclude from Shift indexing
18
+ # Uses the same syntax as .gitignore
19
+
20
+ # Dependencies
21
+ node_modules/
22
+ vendor/
23
+ bower_components/
24
+
25
+ # Build output
26
+ dist/
27
+ build/
28
+ out/
29
+ .next/
30
+
31
+ # Test & coverage
32
+ coverage/
33
+ .nyc_output/
34
+
35
+ # Environment & secrets
36
+ .env
37
+ .env.*
38
+ *.pem
39
+ *.key
40
+
41
+ # Logs
42
+ *.log
43
+ logs/
44
+
45
+ # OS files
46
+ .DS_Store
47
+ Thumbs.db
48
+
49
+ # IDE
50
+ .vscode/
51
+ .idea/
52
+ *.swp
53
+ *.swo
54
+
55
+ # Python
56
+ __pycache__/
57
+ *.pyc
58
+ venv/
59
+ .venv/
60
+
61
+ # Misc
62
+ *.min.js
63
+ *.min.css
64
+ *.map
65
+ `;
66
+ /**
67
+ * Create a default .shiftignore file in the project root if one doesn't exist.
68
+ * Returns true if a new file was created, false if it already exists.
69
+ */
70
+ export function scaffoldShiftIgnore(projectRoot) {
71
+ const ignorePath = path.join(projectRoot, SHIFTIGNORE_FILE);
72
+ if (fs.existsSync(ignorePath)) {
73
+ return false;
74
+ }
75
+ try {
76
+ fs.writeFileSync(ignorePath, DEFAULT_SHIFTIGNORE, 'utf-8');
77
+ return true;
78
+ }
79
+ catch (err) {
80
+ console.warn(`[ShiftIgnore] Warning: Could not create ${SHIFTIGNORE_FILE}: ${err.message}`);
81
+ return false;
82
+ }
83
+ }
84
+ export function loadShiftIgnore(projectRoot) {
85
+ const ignorePath = path.join(projectRoot, SHIFTIGNORE_FILE);
86
+ if (!fs.existsSync(ignorePath)) {
87
+ return null;
88
+ }
89
+ try {
90
+ const content = fs.readFileSync(ignorePath, 'utf-8');
91
+ const ig = ignore();
92
+ ig.add(content);
93
+ return ig;
94
+ }
95
+ catch (err) {
96
+ console.warn(`[ShiftIgnore] Warning: Could not read ${SHIFTIGNORE_FILE}: ${err.message}`);
97
+ return null;
98
+ }
99
+ }
100
+ /**
101
+ * Filter an array of file paths through the shiftIgnore rules.
102
+ * Returns only paths that are NOT ignored.
103
+ */
104
+ export function filterPaths(ig, paths) {
105
+ if (!ig)
106
+ return paths;
107
+ return paths.filter(p => !ig.ignores(p.replace(/\\/g, '/')));
108
+ }
@@ -0,0 +1,165 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ // Matching extension's file-tools.js exclude patterns
4
+ const DEFAULT_EXCLUDE_PATTERNS = [
5
+ '.git',
6
+ 'node_modules',
7
+ '__pycache__',
8
+ '.vscode',
9
+ 'dist',
10
+ 'build',
11
+ '.shift',
12
+ '.next',
13
+ '.cache',
14
+ 'coverage',
15
+ '.pytest_cache',
16
+ 'venv',
17
+ 'env',
18
+ '.env',
19
+ ];
20
+ /**
21
+ * Get project tree - matching extension's getProjectTree function
22
+ */
23
+ export function getProjectTree(workspaceRoot, options = {}) {
24
+ const { depth = 0, exclude_patterns = DEFAULT_EXCLUDE_PATTERNS, } = options;
25
+ let file_count = 0;
26
+ let dir_count = 0;
27
+ let total_size = 0;
28
+ const max_depth = depth === 0 ? Infinity : depth;
29
+ function scanDirectory(dirPath, currentDepth, relativePath) {
30
+ if (currentDepth >= max_depth) {
31
+ return [];
32
+ }
33
+ const items = [];
34
+ try {
35
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
36
+ for (const entry of entries) {
37
+ // Check if should be excluded by built-in patterns
38
+ if (exclude_patterns.some(pattern => entry.name.includes(pattern))) {
39
+ continue;
40
+ }
41
+ const itemPath = path.join(dirPath, entry.name);
42
+ const itemRelativePath = relativePath ? path.join(relativePath, entry.name) : entry.name;
43
+ if (entry.isDirectory()) {
44
+ dir_count++;
45
+ const children = scanDirectory(itemPath, currentDepth + 1, itemRelativePath);
46
+ items.push({
47
+ name: entry.name,
48
+ type: 'directory',
49
+ path: itemRelativePath.replace(/\\/g, '/'), // Normalize to forward slashes
50
+ depth: currentDepth,
51
+ children: children,
52
+ });
53
+ }
54
+ else if (entry.isFile()) {
55
+ file_count++;
56
+ try {
57
+ const stats = fs.statSync(itemPath);
58
+ total_size += stats.size;
59
+ const ext = path.extname(entry.name).toLowerCase();
60
+ items.push({
61
+ name: entry.name,
62
+ type: 'file',
63
+ path: itemRelativePath.replace(/\\/g, '/'), // Normalize to forward slashes
64
+ depth: currentDepth,
65
+ size: stats.size,
66
+ extension: ext,
67
+ modified: stats.mtime,
68
+ });
69
+ }
70
+ catch {
71
+ // Skip files we can't read
72
+ }
73
+ }
74
+ }
75
+ }
76
+ catch (err) {
77
+ console.error(`Error scanning ${dirPath}:`, err.message);
78
+ }
79
+ return items;
80
+ }
81
+ const tree = scanDirectory(workspaceRoot, 0, '');
82
+ const total_size_mb = (total_size / (1024 * 1024)).toFixed(2);
83
+ return {
84
+ status: 'success',
85
+ tree: tree,
86
+ file_count: file_count,
87
+ dir_count: dir_count,
88
+ total_size_bytes: total_size,
89
+ total_size_mb: total_size_mb,
90
+ scanned_from: workspaceRoot,
91
+ depth_limit: depth,
92
+ actual_max_depth: depth === 0 ? 'unlimited' : String(depth),
93
+ excluded_patterns: exclude_patterns,
94
+ };
95
+ }
96
+ /**
97
+ * Extract all file paths from tree structure
98
+ * Matching extension's extractAllFilePaths function
99
+ */
100
+ export function extractAllFilePaths(tree, basePath = '') {
101
+ let files = [];
102
+ for (const item of tree) {
103
+ const itemPath = basePath ? `${basePath}/${item.name}` : item.name;
104
+ if (item.type === 'file') {
105
+ files.push(itemPath);
106
+ }
107
+ else if (item.type === 'directory' && item.children) {
108
+ files = files.concat(extractAllFilePaths(item.children, itemPath));
109
+ }
110
+ }
111
+ return files;
112
+ }
113
+ /**
114
+ * Categorize files by type
115
+ * Matching extension's categorizeFiles function
116
+ */
117
+ /**
118
+ * Count total lines of code across all project files
119
+ */
120
+ export function countProjectLOC(rootPath, filePaths) {
121
+ let totalLOC = 0;
122
+ for (const filePath of filePaths) {
123
+ try {
124
+ const fullPath = path.join(rootPath, filePath);
125
+ const content = fs.readFileSync(fullPath, 'utf-8');
126
+ totalLOC += content.split('\n').length;
127
+ }
128
+ catch {
129
+ // Skip binary/unreadable files
130
+ }
131
+ }
132
+ return totalLOC;
133
+ }
134
+ export function categorizeFiles(tree, basePath = '') {
135
+ const categories = {
136
+ source_files: [],
137
+ config_files: [],
138
+ asset_files: [],
139
+ };
140
+ const sourceExts = ['.js', '.jsx', '.ts', '.tsx', '.py', '.java', '.cpp', '.cs', '.go', '.c', '.h'];
141
+ const configExts = ['.json', '.yaml', '.yml', '.toml', '.ini', '.config', '.xml'];
142
+ const assetExts = ['.png', '.jpg', '.jpeg', '.svg', '.gif', '.css', '.scss', '.less', '.sass'];
143
+ for (const item of tree) {
144
+ const itemPath = basePath ? `${basePath}/${item.name}` : item.name;
145
+ if (item.type === 'file') {
146
+ const ext = path.extname(item.name).toLowerCase();
147
+ if (sourceExts.includes(ext)) {
148
+ categories.source_files.push(itemPath);
149
+ }
150
+ else if (configExts.includes(ext)) {
151
+ categories.config_files.push(itemPath);
152
+ }
153
+ else if (assetExts.includes(ext)) {
154
+ categories.asset_files.push(itemPath);
155
+ }
156
+ }
157
+ else if (item.type === 'directory' && item.children) {
158
+ const subCategories = categorizeFiles(item.children, itemPath);
159
+ categories.source_files.push(...subCategories.source_files);
160
+ categories.config_files.push(...subCategories.config_files);
161
+ categories.asset_files.push(...subCategories.asset_files);
162
+ }
163
+ }
164
+ return categories;
165
+ }
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@latentforce/latentgraph",
3
+ "version": "1.0.0",
4
+ "description": "Shift CLI - AI-powered code intelligence with MCP support",
5
+ "type": "module",
6
+ "main": "./build/index.js",
7
+ "exports": {
8
+ ".": "./build/index.js"
9
+ },
10
+ "bin": {
11
+ "lgraph": "./build/index.js"
12
+ },
13
+ "files": [
14
+ "build"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "prepublishOnly": "npm run build",
19
+ "test": "echo \"No tests yet\""
20
+ },
21
+ "keywords": [
22
+ "mcp",
23
+ "model-context-protocol",
24
+ "cli",
25
+ "node",
26
+ "typescript"
27
+ ],
28
+ "author": "Latentforce",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": ""
33
+ },
34
+ "homepage": "",
35
+ "engines": {
36
+ "node": ">=18"
37
+ },
38
+ "dependencies": {
39
+ "@modelcontextprotocol/sdk": "^1.25.3",
40
+ "commander": "^12.0.0",
41
+ "ws": "^8.14.0",
42
+ "zod": "^3.25.76"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^25.0.9",
46
+ "@types/ws": "^8.5.10",
47
+ "typescript": "^5.9.3"
48
+ }
49
+ }