about summary refs log tree commit diff stats
path: root/tree-sitter/dsk/dsk-cli/src/commands
diff options
context:
space:
mode:
Diffstat (limited to 'tree-sitter/dsk/dsk-cli/src/commands')
-rw-r--r--tree-sitter/dsk/dsk-cli/src/commands/build.ts429
-rw-r--r--tree-sitter/dsk/dsk-cli/src/commands/dev.ts105
-rw-r--r--tree-sitter/dsk/dsk-cli/src/commands/highlight.ts141
-rw-r--r--tree-sitter/dsk/dsk-cli/src/commands/new.ts485
-rw-r--r--tree-sitter/dsk/dsk-cli/src/commands/package.ts66
-rw-r--r--tree-sitter/dsk/dsk-cli/src/commands/self.ts71
-rw-r--r--tree-sitter/dsk/dsk-cli/src/commands/test.ts50
7 files changed, 1347 insertions, 0 deletions
diff --git a/tree-sitter/dsk/dsk-cli/src/commands/build.ts b/tree-sitter/dsk/dsk-cli/src/commands/build.ts
new file mode 100644
index 0000000..811788f
--- /dev/null
+++ b/tree-sitter/dsk/dsk-cli/src/commands/build.ts
@@ -0,0 +1,429 @@
+/**
+ * Build Command - Core Build Process
+ * 
+ * Compiles grammar.js into C static library and JavaScript package
+ */
+
+import { Command } from 'commander';
+import { execa } from 'execa';
+import chalk from 'chalk';
+import { existsSync, mkdirSync, writeFileSync, copyFileSync, readFileSync, cpSync } from 'fs';
+import { join, dirname, basename } from 'path';
+import { fileURLToPath } from 'url';
+
+/**
+ * Create the build command
+ */
+export function createBuildCommand(): Command {
+  const buildCommand = new Command('build');
+  
+  buildCommand
+    .description('Build the DSL parser and packages')
+    .option('-v, --verbose', 'Show detailed build output')
+    .option('--skip-c', 'Skip C library generation')
+    .option('--skip-js', 'Skip JavaScript package generation')
+    .action(async (options) => {
+      console.log(chalk.blue('๐Ÿ—๏ธ  Building DSL parser and packages...'));
+      console.log();
+      
+      try {
+        await runBuildProcess(options);
+        
+        console.log();
+        console.log(chalk.green('โœ… Build completed successfully!'));
+        console.log();
+        console.log(chalk.blue('Generated artifacts:'));
+        console.log(`  ${chalk.gray('โ€ข')} C library: generated/c/lib/`);
+        console.log(`  ${chalk.gray('โ€ข')} C headers: generated/c/include/`);
+        console.log(`  ${chalk.gray('โ€ข')} JS package: generated/js/`);
+        
+      } catch (error) {
+        console.error(chalk.red('โŒ Build failed:'), error instanceof Error ? error.message : error);
+        process.exit(1);
+      }
+    });
+
+  return buildCommand;
+}
+
+/**
+ * Run the complete build process
+ */
+async function runBuildProcess(options: { verbose?: boolean; skipC?: boolean; skipJs?: boolean }): Promise<void> {
+  // Verify we're in a DSL project directory
+  if (!existsSync('grammar.js')) {
+    throw new Error('No grammar.js found. Are you in a DSL project directory?');
+  }
+
+  // Ensure CommonJS semantics locally so tree-sitter can load grammar.js (uses module.exports)
+  ensureCommonJsPackageJson();
+
+  // Step 1: Generate parser with Tree-sitter
+  console.log(chalk.blue('1๏ธโƒฃ  Generating parser with Tree-sitter...'));
+  await generateParser(options.verbose);
+  console.log(chalk.green('   โœ… Parser generated'));
+
+  // Step 2: Build C library (unless skipped)
+  if (!options.skipC) {
+    console.log(chalk.blue('2๏ธโƒฃ  Building C static library...'));
+    await buildCLibrary(options.verbose);
+    console.log(chalk.green('   โœ… C library built'));
+  } else {
+    console.log(chalk.yellow('   โญ๏ธ  Skipping C library build'));
+  }
+
+  // Step 3: Build JavaScript package (unless skipped)
+  if (!options.skipJs) {
+    console.log(chalk.blue('3๏ธโƒฃ  Building JavaScript package...'));
+    await buildJavaScriptPackage(options.verbose);
+    console.log(chalk.green('   โœ… JavaScript package built'));
+  } else {
+    console.log(chalk.yellow('   โญ๏ธ  Skipping JavaScript package build'));
+  }
+}
+
+/**
+ * Step 1: Generate parser using tree-sitter generate
+ */
+async function generateParser(verbose?: boolean): Promise<void> {
+  try {
+    // Check if tree-sitter CLI is available
+    await checkTreeSitterAvailable();
+    
+    // Run tree-sitter generate
+    const result = await execa('tree-sitter', ['generate'], {
+      stdio: verbose ? 'inherit' : 'pipe'
+    });
+    
+    if (!verbose && result.stdout) {
+      console.log(chalk.gray(`   ${result.stdout.split('\n').slice(-2, -1)[0] || 'Generated successfully'}`));
+    }
+    
+  } catch (error: any) {
+    if (error.command) {
+      throw new Error(`Tree-sitter generation failed: ${error.message}`);
+    }
+    throw error;
+  }
+}
+
+/**
+ * Step 2: Build C static library
+ */
+async function buildCLibrary(verbose?: boolean): Promise<void> {
+  // Create output directories
+  const libDir = 'generated/c/lib';
+  const includeDir = 'generated/c/include';
+  
+  mkdirSync(libDir, { recursive: true });
+  mkdirSync(includeDir, { recursive: true });
+
+  // Get project name from grammar.js or directory
+  const projectName = getProjectName();
+  
+  // Compile parser.c to object file
+  const objectFile = join(libDir, `${projectName}.o`);
+  const libraryFile = join(libDir, `lib${projectName}.a`);
+  
+  try {
+    // Detect C compiler
+    const compiler = await detectCCompiler();
+    console.log(chalk.gray(`   Using compiler: ${compiler}`));
+    
+    // Compile to object file
+    const compileArgs = [
+      '-c',                    // Compile only, don't link
+      '-fPIC',                 // Position independent code
+      '-O2',                   // Optimize
+      'src/parser.c',          // Input file
+      '-o', objectFile         // Output file
+    ];
+    
+    await execa(compiler, compileArgs, {
+      stdio: verbose ? 'inherit' : 'pipe'
+    });
+    
+    // Create static library with ar
+    const arArgs = [
+      'rcs',                   // Create archive, insert files, write symbol table
+      libraryFile,             // Output library
+      objectFile               // Input object file
+    ];
+    
+    await execa('ar', arArgs, {
+      stdio: verbose ? 'inherit' : 'pipe'
+    });
+    
+    // Generate header file
+    await generateHeaderFile(projectName, includeDir);
+    
+    console.log(chalk.gray(`   Library: ${libraryFile}`));
+    console.log(chalk.gray(`   Header: ${join(includeDir, projectName + '.h')}`));
+    
+  } catch (error: any) {
+    throw new Error(`C library build failed: ${error.message}`);
+  }
+}
+
+/**
+ * Step 3: Build JavaScript package
+ */
+async function buildJavaScriptPackage(verbose?: boolean): Promise<void> {
+  const jsDir = 'generated/js';
+  
+  // Create JS package directory
+  mkdirSync(jsDir, { recursive: true });
+  
+  // Copy JS addon templates
+  await copyJSAddonTemplates(jsDir);
+  
+  // Copy src directory from tree-sitter generation
+  await copySourceFiles(jsDir);
+  
+  // Update binding.gyp based on available files
+  await updateBindingGyp(jsDir);
+  
+  // Update package.json with project name
+  await updateJSPackageJson(jsDir);
+  
+  // Detect runtime and install dependencies
+  const runtime = await detectRuntime();
+  console.log(chalk.gray(`   Using runtime: ${runtime}`));
+  
+  try {
+    // Install dependencies and build native addon
+    if (runtime === 'bun') {
+      await execa('bun', ['install'], {
+        cwd: jsDir,
+        stdio: verbose ? 'inherit' : 'pipe'
+      });
+    } else {
+      await execa('npm', ['install'], {
+        cwd: jsDir,
+        stdio: verbose ? 'inherit' : 'pipe'
+      });
+    }
+    
+    console.log(chalk.gray(`   Package: ${jsDir}/`));
+    
+  } catch (error: any) {
+    throw new Error(`JavaScript package build failed: ${error.message}`);
+  }
+}
+
+/**
+ * Check if tree-sitter CLI is available
+ */
+async function checkTreeSitterAvailable(): Promise<void> {
+  try {
+    await execa('tree-sitter', ['--version'], { stdio: 'pipe' });
+  } catch (error) {
+    throw new Error(
+      'tree-sitter CLI not found. Please install it:\n' +
+      '  npm install -g tree-sitter-cli\n' +
+      '  # or\n' +
+      '  brew install tree-sitter'
+    );
+  }
+}
+
+/**
+ * Detect available C compiler
+ */
+async function detectCCompiler(): Promise<string> {
+  const compilers = ['clang', 'gcc', 'cc'];
+  
+  for (const compiler of compilers) {
+    try {
+      await execa(compiler, ['--version'], { stdio: 'pipe' });
+      return compiler;
+    } catch {
+      // Try next compiler
+    }
+  }
+  
+  throw new Error(
+    'No C compiler found. Please install one:\n' +
+    '  macOS: xcode-select --install\n' +
+    '  Linux: sudo apt install build-essential (Ubuntu) or equivalent'
+  );
+}
+
+/**
+ * Detect runtime (bun vs npm)
+ */
+async function detectRuntime(): Promise<'bun' | 'npm'> {
+  try {
+    await execa('bun', ['--version'], { stdio: 'pipe' });
+    return 'bun';
+  } catch {
+    return 'npm';
+  }
+}
+
+/**
+ * Get project name from grammar.js or directory name
+ */
+function getProjectName(): string {
+  try {
+    const grammarContent = readFileSync('grammar.js', 'utf-8');
+    const nameMatch = grammarContent.match(/name:\s*['"]([^'"]+)['"]/);
+    if (nameMatch) {
+      return nameMatch[1];
+    }
+  } catch {
+    // Fall back to directory name
+  }
+  
+  return basename(process.cwd());
+}
+
+/**
+ * Ensure the current directory is treated as CommonJS for Node resolution,
+ * so that tree-sitter can load `grammar.js` (which uses module.exports).
+ * If no local package.json exists, create a minimal one with "type": "commonjs".
+ */
+function ensureCommonJsPackageJson(): void {
+  const packageJsonPath = 'package.json';
+  if (!existsSync(packageJsonPath)) {
+    const projectName = basename(process.cwd());
+    const minimal = {
+      name: `${projectName}-dsl`,
+      private: true,
+      type: 'commonjs',
+      description: `DSL project for ${projectName} generated by DSK`,
+      license: 'MIT'
+    } as const;
+    writeFileSync(packageJsonPath, JSON.stringify(minimal, null, 2));
+    console.log(chalk.gray('   Created local package.json with { "type": "commonjs" }'));
+  }
+}
+
+/**
+ * Generate C header file
+ */
+async function generateHeaderFile(projectName: string, includeDir: string): Promise<void> {
+  const headerContent = `#ifndef TREE_SITTER_${projectName.toUpperCase()}_H_
+#define TREE_SITTER_${projectName.toUpperCase()}_H_
+
+typedef struct TSLanguage TSLanguage;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+const TSLanguage *tree_sitter_${projectName}(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TREE_SITTER_${projectName.toUpperCase()}_H_
+`;
+
+  writeFileSync(join(includeDir, `${projectName}.h`), headerContent);
+}
+
+/**
+ * Copy JS addon template files
+ */
+async function copyJSAddonTemplates(jsDir: string): Promise<void> {
+  const __filename = fileURLToPath(import.meta.url);
+  const __dirname = dirname(__filename);
+  const templateDir = join(__dirname, '..', '..', 'templates', 'js-addon');
+  
+  if (!existsSync(templateDir)) {
+    throw new Error(`JS addon template directory not found: ${templateDir}`);
+  }
+  
+  // Copy all template files
+  const { processTemplate } = await import('../utils/template-processor.js');
+  const projectName = getProjectName();
+  
+  const templateContext = {
+    architecture: { 
+      name: projectName, 
+      paradigm: 'mixed' as const, 
+      purpose: 'DSL', 
+      dataPhilosophy: 'mixed' as const 
+    },
+    features: { controlFlow: [], dataStructures: [], functionTypes: [] },
+    syntax: {
+      comments: { type: 'line_comment', pattern: '//' },
+      identifiers: { pattern: '[a-zA-Z_][a-zA-Z0-9_]*', examples: ['identifier'] },
+      numbers: { pattern: '\\d+', examples: ['42'] },
+      strings: { pattern: '"[^"]*"', examples: ['"string"'] },
+      variables: { keyword: 'let', operator: '=', terminator: ';', example: 'let x = 42;' },
+      paradigmExamples: {}
+    }
+  };
+  
+  processTemplate(templateDir, jsDir, templateContext);
+}
+
+/**
+ * Copy source files from tree-sitter generation to JS package
+ */
+async function copySourceFiles(jsDir: string): Promise<void> {
+  const srcDir = 'src';
+  const targetSrcDir = join(jsDir, 'src');
+  
+  if (!existsSync(srcDir)) {
+    throw new Error('src/ directory not found. Run tree-sitter generate first.');
+  }
+  
+  // Copy the entire src directory
+  cpSync(srcDir, targetSrcDir, { recursive: true });
+  
+  console.log(chalk.gray(`   Copied src files to ${targetSrcDir}`));
+}
+
+/**
+ * Update binding.gyp based on available source files
+ */
+async function updateBindingGyp(jsDir: string): Promise<void> {
+  const bindingGypPath = join(jsDir, 'binding.gyp');
+  const scannerPath = join(jsDir, 'src', 'scanner.c');
+  
+  if (existsSync(bindingGypPath)) {
+    let bindingGyp = JSON.parse(readFileSync(bindingGypPath, 'utf-8'));
+    
+    // Add scanner.c if it exists
+    if (existsSync(scannerPath)) {
+      const target = bindingGyp.targets[0];
+      if (!target.sources.includes('src/scanner.c')) {
+        target.sources.push('src/scanner.c');
+        console.log(chalk.gray(`   Added scanner.c to build`));
+      }
+    } else {
+      console.log(chalk.gray(`   No scanner.c found, skipping`));
+    }
+    
+    writeFileSync(bindingGypPath, JSON.stringify(bindingGyp, null, 2));
+  }
+}
+
+/**
+ * Update package.json in JS package
+ */
+async function updateJSPackageJson(jsDir: string): Promise<void> {
+  const projectName = getProjectName();
+  const packageJsonPath = join(jsDir, 'package.json');
+  
+  if (existsSync(packageJsonPath)) {
+    let packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
+    
+    // Update name and description
+    packageJson.name = `tree-sitter-${projectName}`;
+    packageJson.description = `Tree-sitter parser for ${projectName}`;
+    
+    // Update keywords
+    if (packageJson.keywords && Array.isArray(packageJson.keywords)) {
+      packageJson.keywords = packageJson.keywords.map((keyword: string) => 
+        keyword === '__DSL_NAME__' ? projectName : keyword
+      );
+    }
+    
+    writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
+  }
+}
diff --git a/tree-sitter/dsk/dsk-cli/src/commands/dev.ts b/tree-sitter/dsk/dsk-cli/src/commands/dev.ts
new file mode 100644
index 0000000..92b2867
--- /dev/null
+++ b/tree-sitter/dsk/dsk-cli/src/commands/dev.ts
@@ -0,0 +1,105 @@
+/**
+ * Dev Command - Watch grammar.js and rebuild/test on change
+ */
+
+import { Command } from 'commander';
+import chokidar from 'chokidar';
+import chalk from 'chalk';
+import { existsSync } from 'fs';
+import { execa } from 'execa';
+
+/**
+ * Create the dev command
+ */
+export function createDevCommand(): Command {
+  const devCommand = new Command('dev');
+
+  devCommand
+    .description('Watch grammar.js and run build + test on changes')
+    .option('-v, --verbose', 'Show detailed build output')
+    .option('--quiet', 'Suppress non-error logs')
+    .option('--debounce <ms>', 'Debounce delay in milliseconds', '150')
+    .action(async (options) => {
+      if (!existsSync('grammar.js')) {
+        console.error(chalk.red('โŒ No grammar.js found. Are you in a DSL project directory?'));
+        process.exit(1);
+      }
+
+      const verbose: boolean = Boolean(options.verbose);
+      const quiet: boolean = Boolean(options.quiet);
+      const debounceMs: number = Number.parseInt(options.debounce, 10) || 150;
+
+      if (!quiet) {
+        console.log(chalk.blue('๐Ÿ‘€ Watching grammar.js for changes...'));
+      }
+
+      // Initial build + test
+      await runBuildAndTest({ verbose, quiet });
+
+      // Watcher with debounced rebuilds
+      const watcher = chokidar.watch('grammar.js', { ignoreInitial: true });
+      let isRunning = false;
+      let rerunRequested = false;
+      let debounceTimer: NodeJS.Timeout | null = null;
+
+      const runOnce = async () => {
+        if (isRunning) {
+          rerunRequested = true;
+          return;
+        }
+        isRunning = true;
+        if (!quiet) {
+          console.log(chalk.yellow('โ†ป Change detected. Rebuilding...'));
+        }
+        try {
+          await runBuildAndTest({ verbose, quiet });
+          if (!quiet) {
+            console.log(chalk.green('โœ… Rebuild and tests completed.'));
+          }
+        } catch (e) {
+          // Errors already printed by build/test
+        } finally {
+          isRunning = false;
+          if (rerunRequested) {
+            rerunRequested = false;
+            runOnce();
+          }
+        }
+      };
+
+      const debouncedTrigger = () => {
+        if (debounceTimer) clearTimeout(debounceTimer);
+        debounceTimer = setTimeout(runOnce, debounceMs);
+      };
+
+      watcher.on('change', debouncedTrigger);
+    });
+
+  return devCommand;
+}
+
+async function runBuildAndTest(opts: { verbose?: boolean; quiet?: boolean }): Promise<void> {
+  const { verbose, quiet } = opts;
+  if (!quiet) {
+    console.log(chalk.blue('๐Ÿ—๏ธ  Building...'));
+  }
+  try {
+    const buildArgs = ['build', ...(verbose ? ['--verbose'] : [])];
+    await execa('dsk', buildArgs, { stdio: 'inherit' });
+  } catch (error: any) {
+    console.error(chalk.red('โŒ Build failed. Fix errors and save again.'));
+    throw error;
+  }
+
+  if (!quiet) {
+    console.log(chalk.blue('๐Ÿงช Testing...'));
+  }
+  try {
+    await execa('dsk', ['test'], { stdio: 'inherit' });
+  } catch (error: any) {
+    console.error(chalk.red('โŒ Tests failed. Fix tests and save again.'));
+    throw error;
+  }
+}
+
+
diff --git a/tree-sitter/dsk/dsk-cli/src/commands/highlight.ts b/tree-sitter/dsk/dsk-cli/src/commands/highlight.ts
new file mode 100644
index 0000000..fd0d419
--- /dev/null
+++ b/tree-sitter/dsk/dsk-cli/src/commands/highlight.ts
@@ -0,0 +1,141 @@
+/**
+ * Highlight Command - Generate Tree-sitter highlights and editor scaffolds
+ */
+
+import { Command } from 'commander';
+import chalk from 'chalk';
+import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
+import { join } from 'path';
+
+/**
+ * Create the highlight command
+ */
+export function createHighlightCommand(): Command {
+  const highlightCommand = new Command('highlight');
+
+  highlightCommand
+    .description('Generate Tree-sitter highlights and editor scaffolds (tree-sitter, neovim, emacs, vscode)')
+    .action(async () => {
+      if (!existsSync('grammar.js')) {
+        console.error(chalk.red('โŒ No grammar.js found. Are you in a DSL project directory?'));
+        process.exit(1);
+      }
+
+      const languageName = getProjectName();
+
+      // Tree-sitter highlights
+      const tsOutDir = 'generated/editors/tree-sitter';
+      mkdirSync(tsOutDir, { recursive: true });
+      const highlights = `
+; Minimal highlights scaffold. Extend as needed.
+
+; Comments
+(comment) @comment
+
+; Strings
+(string) @string
+
+; Numbers
+(number) @number
+
+; Keywords (example)
+[("if") ("else") ("let") ("function")] @keyword
+`;
+      const tsFile = join(tsOutDir, 'highlights.scm');
+      writeFileSync(tsFile, highlights.trim() + '\n', 'utf-8');
+      console.log(chalk.green(`โœ… Generated ${tsFile}`));
+
+      // Neovim instructions
+      const nvimDir = 'generated/editors/neovim';
+      mkdirSync(nvimDir, { recursive: true });
+      const nvimMd = `# Neovim setup for ${languageName}
+
+1. Copy queries to your runtimepath: queries/${languageName}/highlights.scm
+2. Configure nvim-treesitter:
+
+\`\`\`lua
+require('nvim-treesitter.configs').setup({
+  ensure_installed = {},
+  highlight = { enable = true },
+})
+\`\`\`
+`;
+      writeFileSync(join(nvimDir, 'setup-instructions.md'), nvimMd, 'utf-8');
+      console.log(chalk.green(`โœ… Generated ${join(nvimDir, 'setup-instructions.md')}`));
+
+      // Emacs major mode (minimal)
+      const emacsDir = 'generated/editors/emacs';
+      mkdirSync(emacsDir, { recursive: true });
+      const emacsEl = `;;; ${languageName}-mode.el --- ${languageName} mode -*- lexical-binding: t; -*-
+
+(require 'treesit)
+
+(define-derived-mode ${languageName}-mode prog-mode "${languageName}"
+  "Major mode for ${languageName} using Tree-sitter."
+  (when (treesit-ready-p '${languageName})
+    (treesit-parser-create '${languageName})))
+
+(add-to-list 'auto-mode-alist '("\\.${languageName}$" . ${languageName}-mode))
+
+(provide '${languageName}-mode)
+`;
+      writeFileSync(join(emacsDir, `${languageName}-mode.el`), emacsEl, 'utf-8');
+      console.log(chalk.green(`โœ… Generated ${join(emacsDir, `${languageName}-mode.el`)}`));
+
+      // VS Code: TextMate grammar + language configuration (basic placeholders)
+      const vscodeSyntaxDir = 'generated/editors/vscode/syntaxes';
+      const vscodeDir = 'generated/editors/vscode';
+      mkdirSync(vscodeSyntaxDir, { recursive: true });
+      mkdirSync(vscodeDir, { recursive: true });
+      const scope = `source.${languageName}`;
+      const tmLanguage = {
+        name: languageName,
+        scopeName: scope,
+        patterns: [
+          { include: '#comment' },
+          { include: '#string' },
+          { include: '#number' },
+          { include: '#keyword' }
+        ],
+        repository: {
+          comment: { patterns: [{ name: 'comment.line.double-slash', match: '//.*$' }] },
+          string: { patterns: [{ name: 'string.quoted.double', begin: '"', end: '"' }] },
+          number: { patterns: [{ name: 'constant.numeric', match: '-?\\b[0-9]+(\\.[0-9]+)?\\b' }] },
+          keyword: { patterns: [{ name: 'keyword.control', match: '\\b(if|else|let|function)\\b' }] }
+        }
+      } as const;
+      writeFileSync(join(vscodeSyntaxDir, `${languageName}.tmLanguage.json`), JSON.stringify(tmLanguage, null, 2));
+      const langConfig = {
+        comments: { lineComment: '//' },
+        brackets: [["{","}"],["[","]"],["(",")"]],
+        autoClosingPairs: [
+          { open: '"', close: '"' },
+          { open: '{', close: '}' },
+          { open: '(', close: ')' },
+          { open: '[', close: ']' }
+        ],
+        surroundingPairs: [
+          { open: '"', close: '"' },
+          { open: '{', close: '}' },
+          { open: '(', close: ')' },
+          { open: '[', close: ']' }
+        ]
+      };
+      writeFileSync(join(vscodeDir, 'language-configuration.json'), JSON.stringify(langConfig, null, 2));
+      console.log(chalk.green(`โœ… Generated VS Code syntax and configuration`));
+    });
+
+  return highlightCommand;
+}
+
+function getProjectName(): string {
+  try {
+    const grammarContent = readFileSync('grammar.js', 'utf-8');
+    const nameMatch = grammarContent.match(/name:\s*['"]([^'"]+)['"]/);
+    if (nameMatch) return nameMatch[1];
+  } catch {}
+  const parts = process.cwd().split(/[\\/]/);
+  return parts[parts.length - 1] || 'dsl';
+}
+
+
diff --git a/tree-sitter/dsk/dsk-cli/src/commands/new.ts b/tree-sitter/dsk/dsk-cli/src/commands/new.ts
new file mode 100644
index 0000000..1829a21
--- /dev/null
+++ b/tree-sitter/dsk/dsk-cli/src/commands/new.ts
@@ -0,0 +1,485 @@
+/**
+ * New Command - Interactive Grammar Scaffolding
+ * 
+ * Creates a new DSL project with paradigm-aware grammar generation
+ */
+
+import { Command } from 'commander';
+import inquirer from 'inquirer';
+import chalk from 'chalk';
+import { existsSync, mkdirSync, writeFileSync } from 'fs';
+import { join, dirname } from 'path';
+import { fileURLToPath } from 'url';
+import { inferPattern, validatePattern, generateCustomPattern } from '../utils/inference.js';
+import { generateGrammar, generateGrammarFile } from '../utils/grammar-generator.js';
+import { processTemplate } from '../utils/template-processor.js';
+
+// Type definitions for our interactive flow
+export interface LanguageArchitecture {
+  name: string;
+  purpose: string;
+  paradigm: 'functional' | 'object-oriented' | 'procedural' | 'declarative' | 'mixed';
+  dataPhilosophy: 'immutable' | 'mutable' | 'mixed';
+}
+
+export interface LanguageFeatures {
+  controlFlow: string[];
+  dataStructures: string[];
+  functionTypes: string[];
+}
+
+export interface LanguageSyntax {
+  comments: { type: string; pattern: string };
+  identifiers: { pattern: string; examples: string[] };
+  numbers: { pattern: string; examples: string[] };
+  strings: { pattern: string; examples: string[] };
+  variables: { keyword: string; operator: string; terminator: string; example: string };
+  paradigmExamples: { [key: string]: string };
+}
+
+/**
+ * Create the new command
+ */
+export function createNewCommand(): Command {
+  const newCommand = new Command('new');
+  
+  newCommand
+    .description('Create a new DSL project')
+    .argument('<name>', 'Name of the DSL project')
+    .option('-i, --interactive', 'Use interactive grammar scaffolding')
+    .option('-t, --template <template>', 'Use a specific template (default: basic)')
+    .action(async (name: string, options) => {
+      console.log(chalk.blue('๐Ÿš€ Creating DSL project:'), chalk.bold(name));
+      
+      if (options.interactive) {
+        await runInteractiveFlow(name);
+      } else {
+        await createBasicProject(name, options.template || 'basic');
+      }
+    });
+
+  return newCommand;
+}
+
+/**
+ * Run the interactive grammar scaffolding flow
+ */
+async function runInteractiveFlow(projectName: string): Promise<void> {
+  console.log();
+  console.log(chalk.green('๐ŸŽฏ Welcome to the DSK Grammar Scaffolder!'));
+  console.log(chalk.gray('I\'ll ask you a few questions about your new language.'));
+  console.log(chalk.gray('Just provide examples, and I\'ll build a starter grammar for you.'));
+  console.log();
+
+  try {
+    // Phase A: Language Architecture & Paradigm
+    console.log(chalk.blue('๐Ÿ“‹ Phase A: Language Architecture & Paradigm'));
+    const architecture = await gatherArchitecture(projectName);
+    console.log(chalk.green('โœ”'), 'Architecture defined');
+
+    // Phase B: Core Language Features  
+    console.log();
+    console.log(chalk.blue('๐Ÿ”ง Phase B: Core Language Features'));
+    const features = await gatherFeatures(architecture);
+    console.log(chalk.green('โœ”'), 'Features defined');
+
+    // Phase C: Syntax & Tokens
+    console.log();
+    console.log(chalk.blue('๐Ÿ”ค Phase C: Syntax & Tokens'));
+    const syntax = await gatherSyntax(architecture, features);
+    console.log(chalk.green('โœ”'), 'Syntax defined');
+
+    // Generate the project
+    console.log();
+    console.log(chalk.blue('๐Ÿ—๏ธ  Generating project...'));
+    await generateProject(projectName, architecture, features, syntax);
+    
+    // Success message
+    console.log();
+    console.log(chalk.green('๐ŸŽ‰ All done!'));
+    console.log(`Your ${chalk.bold('grammar.js')} has been created with rules for:`);
+    console.log(`  ${chalk.gray('โ€ข')} Comments, Identifiers, Numbers, Strings`);
+    console.log(`  ${chalk.gray('โ€ข')} Variable Declarations`);
+    console.log(`  ${chalk.gray('โ€ข')} ${architecture.paradigm} language constructs`);
+    console.log();
+    console.log(chalk.yellow('To start editing and testing, run:'));
+    console.log(`  ${chalk.cyan('cd')} ${projectName}`);
+    console.log(`  ${chalk.cyan('dsk dev')}`);
+    console.log();
+
+  } catch (error) {
+    console.error(chalk.red('โŒ Error during interactive flow:'), error);
+    process.exit(1);
+  }
+}
+
+/**
+ * Phase A: Gather language architecture information
+ */
+async function gatherArchitecture(projectName: string): Promise<LanguageArchitecture> {
+  const purposeAnswer = await inquirer.prompt({
+    type: 'input',
+    name: 'purpose',
+    message: 'What is your language designed for? (e.g., configuration, scripting, domain modeling)',
+    default: 'General purpose scripting'
+  });
+
+  const paradigmAnswer = await inquirer.prompt({
+    type: 'list',
+    name: 'paradigm',
+    message: 'What programming style does your language follow?',
+    choices: [
+      { name: 'Functional (immutable data, functions as first-class)', value: 'functional' },
+      { name: 'Object-Oriented (classes, inheritance, methods)', value: 'object-oriented' },
+      { name: 'Procedural (step-by-step instructions, functions)', value: 'procedural' },
+      { name: 'Declarative (describe what, not how)', value: 'declarative' },
+      { name: 'Mixed (combination of above)', value: 'mixed' }
+    ],
+    default: 'procedural'
+  });
+
+  const dataAnswer = await inquirer.prompt({
+    type: 'list',
+    name: 'dataPhilosophy',
+    message: 'How does your language handle data?',
+    choices: [
+      { name: 'Immutable by default (functional style)', value: 'immutable' },
+      { name: 'Mutable variables (imperative style)', value: 'mutable' },
+      { name: 'Mixed approach', value: 'mixed' }
+    ],
+    default: 'mutable'
+  });
+
+  return {
+    name: projectName,
+    purpose: purposeAnswer.purpose,
+    paradigm: paradigmAnswer.paradigm,
+    dataPhilosophy: dataAnswer.dataPhilosophy
+  };
+}
+
+/**
+ * Phase B: Gather core language features
+ */
+async function gatherFeatures(architecture: LanguageArchitecture): Promise<LanguageFeatures> {
+  const controlFlowAnswer = await inquirer.prompt({
+    type: 'checkbox',
+    name: 'controlFlow',
+    message: 'What control structures does your language support?',
+    choices: [
+      { name: 'Conditionals (if/else)', value: 'conditionals', checked: true },
+      { name: 'Loops (for, while)', value: 'loops' },
+      { name: 'Pattern matching', value: 'pattern_matching' },
+      { name: 'Exception handling (try/catch)', value: 'exceptions' },
+      { name: 'Early returns/breaks', value: 'early_returns' }
+    ]
+  });
+
+  const dataStructuresAnswer = await inquirer.prompt({
+    type: 'checkbox', 
+    name: 'dataStructures',
+    message: 'What built-in data structures does your language have?',
+    choices: [
+      { name: 'Arrays/Lists: [1, 2, 3]', value: 'arrays', checked: true },
+      { name: 'Objects/Maps: {key: value}', value: 'objects' },
+      { name: 'Tuples: (a, b, c)', value: 'tuples' },
+      { name: 'Sets: {1, 2, 3}', value: 'sets' }
+    ]
+  });
+
+  const functionTypesAnswer = await inquirer.prompt({
+    type: 'checkbox',
+    name: 'functionTypes',
+    message: 'How are functions defined in your language?',
+    choices: [
+      { name: 'Named functions: function foo() { ... }', value: 'named', checked: true },
+      { name: 'Anonymous functions: (x) => x + 1', value: 'anonymous' },
+      { name: 'Methods on objects: obj.method()', value: 'methods' },
+      { name: 'First-class functions (can be passed around)', value: 'first_class' }
+    ]
+  });
+
+  return {
+    controlFlow: controlFlowAnswer.controlFlow,
+    dataStructures: dataStructuresAnswer.dataStructures,
+    functionTypes: functionTypesAnswer.functionTypes
+  };
+}
+
+/**
+ * Phase C: Gather syntax and token information
+ */
+async function gatherSyntax(architecture: LanguageArchitecture, features: LanguageFeatures): Promise<LanguageSyntax> {
+  const syntax: Partial<LanguageSyntax> = {};
+
+  // Comments
+  const commentAnswer = await inquirer.prompt({
+    type: 'input',
+    name: 'comment',
+    message: 'How do you write a single-line comment? (e.g., //, #, --, ;)',
+    default: '//'
+  });
+
+  const commentPrefix = String(commentAnswer.comment).trim().split(/\s+/)[0];
+  syntax.comments = {
+    type: 'line_comment',
+    pattern: commentPrefix
+  };
+
+  // Identifiers with inference
+  syntax.identifiers = await gatherTokenWithInference(
+    'identifier',
+    'Provide 3-5 examples of valid identifiers',
+    'Now provide 2-3 examples of invalid identifiers (optional)',
+    ['myVar', 'userName', '_private']
+  );
+
+  // Numbers with inference
+  syntax.numbers = await gatherTokenWithInference(
+    'number',
+    'Provide examples of numbers in your language',
+    'Provide examples of invalid numbers (optional)',
+    ['42', '3.14', '-17']
+  );
+
+  // Strings with inference (treat input as a single example, not space-split)
+  const stringValid = await inquirer.prompt({
+    type: 'input',
+    name: 'examples',
+    message: 'Provide an example of a string literal',
+    default: '"hello world"'
+  });
+  const stringInvalid = await inquirer.prompt({
+    type: 'input',
+    name: 'examples',
+    message: 'Provide examples of invalid strings (optional)',
+    default: ''
+  });
+  const stringValidExamples = stringValid.examples ? [String(stringValid.examples)] : ['"hello world"'];
+  const stringInvalidExamples = stringInvalid.examples ? [String(stringInvalid.examples)] : [];
+  const stringResult = inferPattern(stringValidExamples, stringInvalidExamples);
+  if (stringResult.pattern && stringResult.confidence > 0.7) {
+    syntax.strings = { pattern: stringResult.pattern.regex.source, examples: stringValidExamples };
+  } else {
+    syntax.strings = { pattern: '"[^"]*"', examples: stringValidExamples };
+  }
+
+  // Variable declarations
+  const varAnswer = await inquirer.prompt({
+    type: 'input',
+    name: 'example',
+    message: 'Show me how you declare a variable x with value 42 (helps identify keywords)',
+    default: 'let x = 42;'
+  });
+
+  const varParts = parseVariableDeclaration(varAnswer.example);
+  syntax.variables = varParts;
+
+  // Paradigm-specific examples
+  syntax.paradigmExamples = {};
+  
+  if (architecture.paradigm === 'object-oriented' || architecture.paradigm === 'mixed') {
+    const classAnswer = await inquirer.prompt({
+      type: 'input',
+      name: 'classExample',
+      message: 'Show me how you define a class with a method',
+      default: 'class Person { getName() { return this.name; } }'
+    });
+    syntax.paradigmExamples.class = classAnswer.classExample;
+  }
+
+  if (architecture.paradigm === 'functional' || architecture.paradigm === 'mixed') {
+    const funcAnswer = await inquirer.prompt({
+      type: 'input',
+      name: 'funcExample', 
+      message: 'Show me how you define and call a function',
+      default: 'function add(a, b) { return a + b; }'
+    });
+    syntax.paradigmExamples.function = funcAnswer.funcExample;
+  }
+
+  if (architecture.paradigm === 'declarative') {
+    const ruleAnswer = await inquirer.prompt({
+      type: 'input',
+      name: 'ruleExample',
+      message: 'Show me a typical declaration/rule in your language',
+      default: 'rule user_can_edit when user.role == "admin"'
+    });
+    syntax.paradigmExamples.rule = ruleAnswer.ruleExample;
+  }
+
+  return syntax as LanguageSyntax;
+}
+
+/**
+ * Gather token information with automatic pattern inference
+ */
+async function gatherTokenWithInference(
+  tokenType: string,
+  validPrompt: string,
+  invalidPrompt: string,
+  defaultExamples: string[]
+): Promise<{ pattern: string; examples: string[] }> {
+  
+  // Get valid examples
+  const validAnswer = await inquirer.prompt({
+    type: 'input',
+    name: 'examples',
+    message: `${validPrompt} (separate with spaces)`,
+    default: defaultExamples.join(' ')
+  });
+
+  const validExamples = validAnswer.examples.split(/\s+/).filter((ex: string) => ex.length > 0);
+
+  // Get invalid examples (optional)
+  const invalidAnswer = await inquirer.prompt({
+    type: 'input',
+    name: 'examples',
+    message: `${invalidPrompt} (separate with spaces, or press Enter to skip)`,
+    default: ''
+  });
+
+  const invalidExamples = invalidAnswer.examples 
+    ? invalidAnswer.examples.split(/\s+/).filter((ex: string) => ex.length > 0)
+    : [];
+
+  // Try inference
+  const result = inferPattern(validExamples, invalidExamples);
+  
+  if (result.pattern && result.confidence > 0.7) {
+    // Successful inference - confirm with user
+    const confirmAnswer = await inquirer.prompt({
+      type: 'confirm',
+      name: 'confirmed',
+      message: `I've inferred the pattern for ${tokenType} as: ${chalk.cyan(result.pattern.regex.source)}. Does this look correct?`,
+      default: true
+    });
+
+    if (confirmAnswer.confirmed) {
+      return {
+        pattern: result.pattern.regex.source,
+        examples: validExamples
+      };
+    }
+  }
+
+  // Inference failed or user rejected - offer alternatives
+  console.log(chalk.yellow(`I couldn't determine a reliable pattern from those examples.`));
+  
+  const fallbackAnswer = await inquirer.prompt({
+    type: 'list',
+    name: 'option',
+    message: 'How would you like to proceed?',
+    choices: [
+      { name: 'Provide a custom regular expression', value: 'custom_regex' },
+      { name: 'Generate a simple pattern from examples', value: 'simple_pattern' },
+      { name: 'Try different examples', value: 'retry' }
+    ]
+  });
+
+  if (fallbackAnswer.option === 'custom_regex') {
+    const regexAnswer = await inquirer.prompt({
+      type: 'input',
+      name: 'regex',
+      message: `Enter a regular expression for ${tokenType}:`,
+      validate: (input: string) => {
+        const validation = validatePattern(input, validExamples, invalidExamples);
+        return validation.isValid || validation.errors.join(', ');
+      }
+    });
+    
+    return {
+      pattern: regexAnswer.regex,
+      examples: validExamples
+    };
+  }
+
+  if (fallbackAnswer.option === 'simple_pattern') {
+    const simplePattern = generateCustomPattern(validExamples, invalidExamples);
+    return {
+      pattern: simplePattern,
+      examples: validExamples
+    };
+  }
+
+  // Retry with different examples
+  return await gatherTokenWithInference(tokenType, validPrompt, invalidPrompt, defaultExamples);
+}
+
+/**
+ * Parse variable declaration to extract components
+ */
+function parseVariableDeclaration(example: string): { keyword: string; operator: string; terminator: string; example: string } {
+  // Simple parsing - look for common patterns
+  const patterns = [
+    /^(\w+)\s+(\w+)\s*([=:])\s*[^;]*([;]?)/, // let x = 42;
+    /^(\w+)\s*([=:])\s*[^;]*([;]?)/, // x = 42;
+  ];
+
+  for (const pattern of patterns) {
+    const match = example.match(pattern);
+    if (match) {
+      return {
+        keyword: match[1] || '',
+        operator: match[3] || '=',
+        terminator: match[4] || ';',
+        example
+      };
+    }
+  }
+
+  // Fallback
+  return {
+    keyword: 'let',
+    operator: '=', 
+    terminator: ';',
+    example
+  };
+}
+
+/**
+ * Generate the project with collected information
+ */
+async function generateProject(
+  name: string,
+  architecture: LanguageArchitecture,
+  features: LanguageFeatures,
+  syntax: LanguageSyntax
+): Promise<void> {
+  // Create project directory
+  if (existsSync(name)) {
+    throw new Error(`Directory ${name} already exists`);
+  }
+
+  mkdirSync(name, { recursive: true });
+
+  // Generate grammar.js file
+  console.log(chalk.gray('๐Ÿ”ง Generating grammar.js...'));
+  const grammar = generateGrammar(architecture, features, syntax);
+  const grammarContent = generateGrammarFile(grammar);
+  writeFileSync(join(name, 'grammar.js'), grammarContent, 'utf-8');
+
+  // Process template files
+  console.log(chalk.gray('๐Ÿ“‹ Creating project structure...'));
+  const __filename = fileURLToPath(import.meta.url);
+  const __dirname = dirname(__filename);
+  const templateDir = join(__dirname, '..', '..', 'templates', 'default');
+  
+  const templateContext = {
+    architecture,
+    features,
+    syntax
+  };
+  
+  processTemplate(templateDir, name, templateContext);
+  
+  console.log(chalk.gray(`๐Ÿ“ Created project directory: ${name}/`));
+  console.log(chalk.green('โœจ Project generated successfully!'));
+}
+
+/**
+ * Create a basic project without interactive flow
+ */
+async function createBasicProject(name: string, template: string): Promise<void> {
+  console.log(chalk.yellow(`๐Ÿšง Basic project creation (template: ${template}) coming soon!`));
+}
diff --git a/tree-sitter/dsk/dsk-cli/src/commands/package.ts b/tree-sitter/dsk/dsk-cli/src/commands/package.ts
new file mode 100644
index 0000000..515c2ce
--- /dev/null
+++ b/tree-sitter/dsk/dsk-cli/src/commands/package.ts
@@ -0,0 +1,66 @@
+/**
+ * Package Command - Create distributable artifacts for C and JS outputs
+ */
+
+import { Command } from 'commander';
+import chalk from 'chalk';
+import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
+import { join } from 'path';
+import { execa } from 'execa';
+
+/**
+ * Create the package command
+ */
+export function createPackageCommand(): Command {
+  const pkgCommand = new Command('package');
+
+  pkgCommand
+    .description('Package C and JS artifacts into distributable archives')
+    .action(async () => {
+      if (!existsSync('grammar.js')) {
+        console.error(chalk.red('โŒ No grammar.js found. Are you in a DSL project directory?'));
+        process.exit(1);
+      }
+
+      console.log(chalk.blue('๐Ÿ“ฆ Packaging artifacts...'));
+
+      // Ensure build is up to date
+      await execa('dsk', ['build'], { stdio: 'inherit' });
+
+      const distDir = 'dist';
+      mkdirSync(distDir, { recursive: true });
+
+      // Zip C outputs (simple tar.gz using system tar to avoid extra deps)
+      const cDir = 'generated/c';
+      if (existsSync(cDir)) {
+        const cArchive = join(distDir, 'c-artifacts.tar.gz');
+        await execa('tar', ['-czf', cArchive, '-C', 'generated', 'c'], { stdio: 'inherit' });
+        console.log(chalk.green(`โœ… C artifacts: ${cArchive}`));
+      }
+
+      // Pack JS package
+      const jsDir = 'generated/js';
+      if (existsSync(jsDir)) {
+        // Prefer bun pack, fallback to npm pack
+        let pkgPath = '';
+        try {
+          const { stdout } = await execa('bun', ['pack'], { cwd: jsDir });
+          pkgPath = stdout.trim();
+        } catch {
+          const { stdout } = await execa('npm', ['pack'], { cwd: jsDir });
+          pkgPath = stdout.trim();
+        }
+        const fileName = pkgPath.split(/\s|\n/).pop() as string;
+        const srcPath = join(jsDir, fileName);
+        const destPath = join(distDir, fileName);
+        await execa('bash', ['-lc', `cp ${JSON.stringify(srcPath)} ${JSON.stringify(destPath)}`]);
+        console.log(chalk.green(`โœ… JS package: ${destPath}`));
+      }
+
+      console.log(chalk.blue('๐ŸŽ‰ Packaging complete.'));
+    });
+
+  return pkgCommand;
+}
+
+
diff --git a/tree-sitter/dsk/dsk-cli/src/commands/self.ts b/tree-sitter/dsk/dsk-cli/src/commands/self.ts
new file mode 100644
index 0000000..2b43fd6
--- /dev/null
+++ b/tree-sitter/dsk/dsk-cli/src/commands/self.ts
@@ -0,0 +1,71 @@
+/**
+ * Self Packaging Command - Build and package the dsk CLI for distribution
+ */
+
+import { Command } from 'commander';
+import chalk from 'chalk';
+import { execa } from 'execa';
+import { existsSync, mkdirSync, readFileSync } from 'fs';
+import { dirname, join } from 'path';
+import { fileURLToPath } from 'url';
+
+/**
+ * Create the self:package command
+ */
+export function createSelfPackageCommand(): Command {
+  const cmd = new Command('self:package');
+
+  cmd
+    .description('Build and package the dsk CLI into a .tgz for distribution')
+    .option('-v, --verbose', 'Show detailed build output')
+    .action(async (options) => {
+      const projectRoot = resolveProjectRoot();
+      if (!existsSync(join(projectRoot, 'package.json'))) {
+        console.error(chalk.red('โŒ Could not locate package.json for dsk-cli'));
+        process.exit(1);
+      }
+
+      const pkg = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf-8'));
+      console.log(chalk.blue(`๐Ÿ“ฆ Packaging ${pkg.name}@${pkg.version}`));
+
+      // 1) Build TypeScript โ†’ dist
+      console.log(chalk.blue('๐Ÿ—๏ธ  Building CLI...'));
+      await execa('bun', ['x', 'tsc'], { cwd: projectRoot, stdio: options.verbose ? 'inherit' : 'inherit' });
+
+      // 2) Pack npm tarball using bun pack (fallback to npm pack)
+      console.log(chalk.blue('๐Ÿงฐ Creating package tarball...'));
+      let tgzName = '';
+      try {
+        const { stdout } = await execa('bun', ['pack'], { cwd: projectRoot });
+        tgzName = stdout.trim().split(/\s|\n/).pop() || '';
+      } catch {
+        const { stdout } = await execa('npm', ['pack'], { cwd: projectRoot });
+        tgzName = stdout.trim().split(/\s|\n/).pop() || '';
+      }
+
+      if (!tgzName) {
+        console.error(chalk.red('โŒ Failed to determine generated package filename'));
+        process.exit(1);
+      }
+
+      const releaseDir = join(projectRoot, 'release');
+      mkdirSync(releaseDir, { recursive: true });
+      const src = join(projectRoot, tgzName);
+      const dest = join(releaseDir, tgzName);
+      await execa('bash', ['-lc', `mv -f ${JSON.stringify(src)} ${JSON.stringify(dest)}`]);
+
+      console.log(chalk.green(`โœ… Created ${dest}`));
+    });
+
+  return cmd;
+}
+
+function resolveProjectRoot(): string {
+  const __filename = fileURLToPath(import.meta.url);
+  const __dirname = dirname(__filename);
+  // When compiled, this file lives under dist/commands. Project root is two levels up.
+  const candidate = join(__dirname, '..', '..');
+  return candidate;
+}
+
+
diff --git a/tree-sitter/dsk/dsk-cli/src/commands/test.ts b/tree-sitter/dsk/dsk-cli/src/commands/test.ts
new file mode 100644
index 0000000..694acc7
--- /dev/null
+++ b/tree-sitter/dsk/dsk-cli/src/commands/test.ts
@@ -0,0 +1,50 @@
+/**
+ * Test Command - Wrapper around `tree-sitter test`
+ *
+ * Streams test output directly to the console.
+ */
+
+import { Command } from 'commander';
+import { execa } from 'execa';
+import chalk from 'chalk';
+import { existsSync } from 'fs';
+
+/**
+ * Create the test command
+ */
+export function createTestCommand(): Command {
+  const testCommand = new Command('test');
+
+  testCommand
+    .description('Run tree-sitter tests and stream output')
+    .allowExcessArguments(true)
+    .option('-u, --update', 'Update expected outputs (snapshots)')
+    .option('-f, --filter <regex>', 'Only run tests whose descriptions match the regex')
+    .option('--cwd <dir>', 'Run tests with a different working directory')
+    .option('-v, --verbose', 'Show verbose output (passes through to tree-sitter)')
+    .argument('[patterns...]', 'Optional test patterns (filenames or test names)')
+    .action(async (patterns: string[], options) => {
+      if (!existsSync('grammar.js')) {
+        console.error(chalk.red('โŒ No grammar.js found. Are you in a DSL project directory?'));
+        process.exit(1);
+      }
+
+      try {
+        console.log(chalk.blue('๐Ÿงช Running tests...'));
+        const args = ['test'];
+        if (options.update) args.push('-u');
+        if (options.filter) args.push('-f', String(options.filter));
+        if (options.verbose) args.push('--verbose');
+        args.push(...patterns);
+        await execa('tree-sitter', args, { stdio: 'inherit', cwd: options.cwd || process.cwd() });
+      } catch (error: any) {
+        const message = error?.message || error || 'Unknown error';
+        console.error(chalk.red('โŒ Tests failed:'), message);
+        process.exit(1);
+      }
+    });
+
+  return testCommand;
+}
+
+