@latentforce/shift 1.0.14 → 1.0.16

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.
@@ -242,14 +242,20 @@ export async function initCommand(options = {}) {
242
242
  scan_timestamp: new Date().toISOString(),
243
243
  project_name: project.projectName,
244
244
  },
245
- scan_targets: scanTargets ?? [{ language: null, path: '' }],
245
+ scan_targets: scanTargets,
246
246
  total_loc: totalLOC,
247
247
  files: filesWithContent.length > 0 ? filesWithContent : undefined,
248
248
  };
249
249
  try {
250
250
  const response = await sendInitScan(apiKey, project.projectId, payload);
251
251
  console.log('[Init] ✓ Backend initialization completed');
252
- console.log(`[Init] Source files queued: ${response.source_files_count ?? response.files_read}`);
252
+ const count = response.source_files_count ?? response.files_read;
253
+ if (count != null) {
254
+ console.log(`[Init] Source files queued: ${count}`);
255
+ }
256
+ else {
257
+ console.log(`[Init] (backend did not return file count)`);
258
+ }
253
259
  // Update local config with agent info (matching extension)
254
260
  if (response.agents_created?.theme_planner) {
255
261
  const agentInfo = {
@@ -263,9 +269,6 @@ export async function initCommand(options = {}) {
263
269
  writeProjectConfig(projectConfig, projectRoot);
264
270
  console.log(`[Init] ✓ Agent info saved: ${agentInfo.agent_id}`);
265
271
  }
266
- if (response.files_read === 0 && response.files_failed > 0) {
267
- console.log(`\n[Init] ⚠️ No files were read (${response.files_failed} failed).`);
268
- }
269
272
  console.log('\n╔═══════════════════════════════════════════════╗');
270
273
  console.log('║ ✓ Project Initialization Complete ║');
271
274
  console.log('╚═══════════════════════════════════════════════╝');
@@ -22,6 +22,9 @@ export class ToolsExecutor {
22
22
  'get_repository_root': this.getRepositoryRoot.bind(this),
23
23
  'get_project_tree': this.getProjectTree.bind(this),
24
24
  'execute_command': this.executeCommand.bind(this),
25
+ 'run_bash': this.runBash.bind(this),
26
+ 'glob_search': this.globSearch.bind(this),
27
+ 'grep_search': this.grepSearch.bind(this),
25
28
  };
26
29
  }
27
30
  /**
@@ -285,6 +288,149 @@ export class ToolsExecutor {
285
288
  message: `No .git folder found after searching ${maxDepth} levels up`
286
289
  };
287
290
  }
291
+ /**
292
+ * Returns combined output matching tools.py's run_bash format.
293
+ */
294
+ async runBash(params) {
295
+ const { command } = params;
296
+ if (!command) {
297
+ return { status: 'error', error: 'command is required' };
298
+ }
299
+ console.log(`[ToolsExecutor] run_bash: ${command}`);
300
+ try {
301
+ const { stdout, stderr } = await execAsync(command, {
302
+ cwd: this.workspaceRoot,
303
+ timeout: 30000,
304
+ maxBuffer: 1024 * 1024,
305
+ });
306
+ let output = stdout;
307
+ if (stderr)
308
+ output += `\n[STDERR]\n${stderr}`;
309
+ return {
310
+ status: 'success',
311
+ output: output.trim() || '[No output]',
312
+ };
313
+ }
314
+ catch (error) {
315
+ let output = error.stdout || '';
316
+ if (error.stderr)
317
+ output += `\n[STDERR]\n${error.stderr}`;
318
+ output += `\n[EXIT CODE: ${error.code || 1}]`;
319
+ return {
320
+ status: 'error',
321
+ output: output.trim(),
322
+ error: error.message,
323
+ };
324
+ }
325
+ }
326
+ /**
327
+ * Find files matching a glob pattern
328
+ */
329
+ async globSearch(params) {
330
+ const { pattern } = params;
331
+ if (!pattern) {
332
+ return { status: 'error', error: 'pattern is required' };
333
+ }
334
+ console.log(`[ToolsExecutor] Glob search: ${pattern}`);
335
+ let basePath = '.';
336
+ let namePattern = pattern;
337
+ const lastSlash = pattern.lastIndexOf('/');
338
+ if (lastSlash !== -1) {
339
+ basePath = pattern.substring(0, lastSlash).replace(/\/?\*\*$/, '') || '.';
340
+ namePattern = pattern.substring(lastSlash + 1);
341
+ }
342
+ const searchDir = path.join(this.workspaceRoot, basePath);
343
+ const excludes = [
344
+ '-not', '-path', '*/node_modules/*',
345
+ '-not', '-path', '*/.git/*',
346
+ '-not', '-path', '*/__pycache__/*',
347
+ '-not', '-path', '*/dist/*',
348
+ '-not', '-path', '*/build/*',
349
+ '-not', '-path', '*/.venv/*',
350
+ ];
351
+ // Quote searchDir to handle workspace paths that contain spaces
352
+ const cmd = `find "${searchDir}" -name "${namePattern}" ${excludes.join(' ')}`;
353
+ try {
354
+ const { stdout } = await execAsync(cmd, { timeout: 30000 });
355
+ const matches = stdout
356
+ .trim()
357
+ .split('\n')
358
+ .filter(Boolean)
359
+ .map(f => path.relative(this.workspaceRoot, f))
360
+ .slice(0, 200);
361
+ return {
362
+ status: 'success',
363
+ pattern,
364
+ matches,
365
+ count: matches.length,
366
+ };
367
+ }
368
+ catch (error) {
369
+ return { status: 'error', error: error.message };
370
+ }
371
+ }
372
+ /**
373
+ * Search file contents with a regex pattern
374
+ */
375
+ async grepSearch(params) {
376
+ const { pattern, file_glob } = params;
377
+ if (!pattern) {
378
+ return { status: 'error', error: 'pattern is required' };
379
+ }
380
+ console.log(`[ToolsExecutor] Grep search: ${pattern}${file_glob ? ` (${file_glob})` : ''}`);
381
+ const baseArgs = [
382
+ 'grep', '-rn', '-E',
383
+ '--exclude-dir=node_modules',
384
+ '--exclude-dir=.git',
385
+ '--exclude-dir=__pycache__',
386
+ '--exclude-dir=dist',
387
+ '--exclude-dir=build',
388
+ '--exclude-dir=.venv',
389
+ '--binary-files=without-match',
390
+ ];
391
+ let searchPath = '.';
392
+ if (file_glob) {
393
+ if (file_glob.includes('/')) {
394
+ const match = file_glob.match(/^([^*]+?)\/(?:\*\*\/)?(.+)$/);
395
+ if (match) {
396
+ searchPath = match[1];
397
+ const namePat = match[2];
398
+ if (namePat && namePat !== '*')
399
+ baseArgs.push(`--include=${namePat}`);
400
+ }
401
+ else {
402
+ baseArgs.push(`--include=${file_glob}`);
403
+ }
404
+ }
405
+ else {
406
+ baseArgs.push(`--include=${file_glob}`);
407
+ }
408
+ }
409
+ const args = [...baseArgs, `'${pattern.replace(/'/g, `'\\''`)}'`, searchPath];
410
+ try {
411
+ const { stdout } = await execAsync(args.join(' '), {
412
+ cwd: this.workspaceRoot,
413
+ timeout: 30000,
414
+ maxBuffer: 1024 * 1024,
415
+ });
416
+ const lines = stdout.trim().split('\n').filter(Boolean);
417
+ const truncated = lines.length > 500;
418
+ const output = truncated ? lines.slice(0, 500).join('\n') + `\n\n... [truncated — ${lines.length} total matches, showing first 500]` : stdout.trim();
419
+ return {
420
+ status: 'success',
421
+ pattern,
422
+ file_glob: file_glob || null,
423
+ output,
424
+ match_count: lines.length,
425
+ };
426
+ }
427
+ catch (error) {
428
+ if (error.code === 1 && !error.stderr) {
429
+ return { status: 'success', pattern, file_glob: file_glob || null, output: '', match_count: 0 };
430
+ }
431
+ return { status: 'error', error: error.message };
432
+ }
433
+ }
288
434
  /**
289
435
  * Get project tree — delegates to shared tree-scanner utility
290
436
  */
@@ -110,6 +110,24 @@ function formatDependencies(data) {
110
110
  }
111
111
  }
112
112
  }
113
+ const implicitDeps = data.implicit_dependencies || [];
114
+ if (implicitDeps.length > 0) {
115
+ lines.push('');
116
+ lines.push('## Implicit / external dependencies');
117
+ lines.push('');
118
+ lines.push(`Implicit dependencies (**${implicitDeps.length}**):`);
119
+ lines.push('');
120
+ const implicitEdges = data.implicit_edge_summaries || {};
121
+ for (const dep of implicitDeps) {
122
+ const summary = implicitEdges[dep];
123
+ if (summary) {
124
+ lines.push(`- **${dep}**: ${summary}`);
125
+ }
126
+ else {
127
+ lines.push(`- **${dep}**`);
128
+ }
129
+ }
130
+ }
113
131
  return lines.join('\n');
114
132
  }
115
133
  /** Format blast_radius response as readable markdown */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@latentforce/shift",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "Shift CLI - AI-powered code intelligence with MCP support",
5
5
  "type": "module",
6
6
  "main": "./build/index.js",