[FEATURE]: Formatter enhanced file extension matching #2273

Open
opened 2026-02-16 17:34:56 -05:00 by yindo · 6 comments
Owner

Originally created by @zutritt on GitHub (Oct 24, 2025).

Originally assigned to: @rekram1-node on GitHub.

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

I think it would be beneficial to modify the formatter logic to support pattern matching or compare against config defined extension rather than extracting it from the file path.

For ex. problematic extensions: .php + .blade.php

Currently the workaround for this is creating single rule:

"formatter": {
    "php": {
        "extensions": [".php"],
        "command": [
            "./.opencode/format.sh",
            "$FILE"
        ],
    },
},

and adding a script to handle the differentiation:

#!/bin/bash

file=$1

if [[ $file == *.blade.php ]]; then
    npx blade-formatter -e -w --sort-tailwindcss-classes --wrap-attributes-min-attrs=2 "$file"
elif [[ $file == *.php ]]; then
    ./vendor/bin/pint "$file"
else
    echo "No formatter configured for this file type."
fi
Originally created by @zutritt on GitHub (Oct 24, 2025). Originally assigned to: @rekram1-node on GitHub. ### Feature hasn't been suggested before. - [x] I have verified this feature I'm about to request hasn't been suggested before. ### Describe the enhancement you want to request I think it would be beneficial to modify the formatter logic to support pattern matching or compare against config defined extension rather than extracting it from the file path. For ex. problematic extensions: `.php` + `.blade.php` Currently the workaround for this is creating single rule: ```json "formatter": { "php": { "extensions": [".php"], "command": [ "./.opencode/format.sh", "$FILE" ], }, }, ``` and adding a script to handle the differentiation: ```sh #!/bin/bash file=$1 if [[ $file == *.blade.php ]]; then npx blade-formatter -e -w --sort-tailwindcss-classes --wrap-attributes-min-attrs=2 "$file" elif [[ $file == *.php ]]; then ./vendor/bin/pint "$file" else echo "No formatter configured for this file type." fi ```
yindo added the help-wanteddiscussiongood first issue labels 2026-02-16 17:34:56 -05:00
Author
Owner

@rekram1-node commented on GitHub (Oct 25, 2025):

yeah I think this makes sense, i feel like pattern matching would be best probably

@rekram1-node commented on GitHub (Oct 25, 2025): yeah I think this makes sense, i feel like pattern matching would be best probably
Author
Owner

@dkudos commented on GitHub (Nov 1, 2025):

I needed to rip out and analyst compare to give a few more options to compare for info to see if we missed anything that A.I. could give us to think about:

Alternative Approaches for Formatter Pattern Matching

This document explores different design approaches for solving the compound extension problem in the opencode formatter, each with trade-offs and use cases.


Approach 1: Glob Pattern Matching (Original Proposal)

Implementation:

patterns: z.array(z.string()).optional()

Example Config:

{
  "formatter": {
    "blade": {
      "patterns": ["*.blade.php", "**/*.blade.php"],
      "command": ["blade-formatter", "$FILE"]
    }
  }
}

Pros:

  • Familiar syntax for developers
  • Powerful (supports wildcards, paths)
  • Can match on path segments, not just extensions
  • Library support (minimatch, picomatch)

Cons:

  • May be overkill for just extensions
  • Performance overhead for glob matching on every file
  • Potential for overly complex patterns

Approach 2: Extension Priority Order

Implementation:
Instead of extracting just the last extension, extract ALL potential extension combinations and check against a priority-ordered list.

// For file: "component.spec.ts"
// Extract: [".spec.ts", ".ts"]
// Check formatters in order until match found

Example Config:

{
  "formatter": {
    "blade": {
      "extensions": [".blade.php"],
      "command": ["blade-formatter", "$FILE"]
    },
    "php": {
      "extensions": [".php"],
      "command": ["pint", "$FILE"]
    }
  }
}

Implementation in format/index.ts:

function extractExtensions(filepath: string): string[] {
  const basename = path.basename(filepath)
  const parts = basename.split('.')
  const extensions: string[] = []
  
  // Extract all possible compound extensions
  for (let i = 1; i < parts.length; i++) {
    extensions.push('.' + parts.slice(i).join('.'))
  }
  
  return extensions // [".spec.ts", ".ts"] for "file.spec.ts"
}

async function getFormatter(filepath: string) {
  const extensions = extractExtensions(filepath)
  const formatters = await state().then((x) => x.formatters)
  const result = []
  
  for (const item of Object.values(formatters)) {
    // Check most specific extension first
    for (const ext of extensions) {
      if (item.extensions.includes(ext)) {
        if (await isEnabled(item)) {
          result.push(item)
        }
        break // Found match, stop checking this formatter
      }
    }
  }
  
  return result
}

Pros:

  • No new config fields needed
  • Backward compatible
  • Simple, intuitive behavior
  • No external dependencies
  • Efficient (simple string operations)

Cons:

  • Only works for extensions (can't match on directory paths)
  • Relies on formatter definition order in config
  • Less flexible than patterns

Approach 3: Regex Pattern Matching

Implementation:

patterns: z.array(z.string()).optional()

Example Config:

{
  "formatter": {
    "blade": {
      "patterns": ["\\.blade\\.php$"],
      "command": ["blade-formatter", "$FILE"]
    },
    "spec-files": {
      "patterns": ["\\.spec\\.(ts|js)$"],
      "command": ["spec-formatter", "$FILE"]
    }
  }
}

Pros:

  • Extremely flexible and powerful
  • Can match complex patterns
  • No external dependencies (native RegExp)

Cons:

  • Harder for non-technical users
  • Easy to write incorrect patterns
  • Less readable than glob patterns
  • Security concerns (ReDoS attacks if user-provided)

Approach 4: File Matcher Function (Most Flexible)

Implementation:
Allow custom matcher functions or predefined matchers.

matcher: z.union([
  z.array(z.string()),           // Simple extensions
  z.object({
    type: z.literal('glob'),
    patterns: z.array(z.string())
  }),
  z.object({
    type: z.literal('regex'),
    patterns: z.array(z.string())
  }),
  z.object({
    type: z.literal('extension'),
    values: z.array(z.string()),
    compound: z.boolean()          // Enable compound extension matching
  })
])

Example Config:

{
  "formatter": {
    "blade": {
      "matcher": {
        "type": "glob",
        "patterns": ["*.blade.php"]
      },
      "command": ["blade-formatter", "$FILE"]
    },
    "php": {
      "matcher": {
        "type": "extension",
        "values": [".php"],
        "compound": false
      },
      "command": ["pint", "$FILE"]
    }
  }
}

Pros:

  • Maximum flexibility
  • Explicit matching strategy
  • Can add new matcher types later

Cons:

  • Most complex to implement
  • Steeper learning curve
  • More verbose configuration

Approach 5: Hybrid - Extensions + Optional Patterns

Implementation:
Keep extensions for backward compatibility, add optional patterns for advanced cases.

extensions: z.array(z.string()).optional()
patterns: z.array(z.string()).optional()
// Match order: patterns first, then compound extensions, then simple extensions

Example Config:

{
  "formatter": {
    "blade": {
      "patterns": ["*.blade.php"],
      "command": ["blade-formatter", "$FILE"]
    },
    "typescript-spec": {
      "extensions": [".spec.ts", ".test.ts"],
      "command": ["spec-formatter", "$FILE"]
    },
    "php": {
      "extensions": [".php"],
      "command": ["pint", "$FILE"]
    }
  }
}

Matching Logic:

async function getFormatter(filepath: string) {
  const formatters = await state().then((x) => x.formatters)
  const result = []
  
  for (const item of Object.values(formatters)) {
    let matched = false
    
    // 1. Check patterns first (if defined)
    if (item.patterns) {
      for (const pattern of item.patterns) {
        if (minimatch(filepath, pattern)) {
          matched = true
          break
        }
      }
    }
    
    // 2. Fall back to compound extension matching
    if (!matched && item.extensions) {
      const compoundExts = extractExtensions(filepath)
      for (const ext of compoundExts) {
        if (item.extensions.includes(ext)) {
          matched = true
          break
        }
      }
    }
    
    if (matched && await isEnabled(item)) {
      result.push(item)
    }
  }
  
  return result
}

Pros:

  • Backward compatible
  • Solves your use case elegantly
  • Progressive enhancement (simple → advanced)
  • Clear upgrade path for users

Cons:

  • Two ways to do similar things
  • Slightly more complex matching logic

Approach 6: Extension Suffix Matching (Simplest)

Implementation:
Just change how extensions are extracted - use suffix matching instead of path.extname().

// Current: path.extname("file.blade.php") → ".php"
// New: checkExtension("file.blade.php", [".blade.php", ".php"]) → ".blade.php"

function matchesExtension(filepath: string, extensions: string[]): string | null {
  const filename = path.basename(filepath)
  
  // Sort by length (longest first) to match most specific
  const sorted = [...extensions].sort((a, b) => b.length - a.length)
  
  for (const ext of sorted) {
    if (filename.endsWith(ext)) {
      return ext
    }
  }
  
  return null
}

Example Config (NO CHANGES NEEDED):

{
  "formatter": {
    "blade": {
      "extensions": [".blade.php"],
      "command": ["blade-formatter", "$FILE"]
    },
    "php": {
      "extensions": [".php"],
      "command": ["pint", "$FILE"]
    }
  }
}

Pros:

  • ZERO config changes needed
  • Completely backward compatible
  • Solves the exact problem described
  • Simple implementation
  • Intuitive behavior (more specific extensions match first)
  • No new dependencies

Cons:

  • Can't match on directory patterns
  • Limited to suffix matching only

My Recommendation: Approach 6 (Extension Suffix Matching)

Why?

  1. Solves your exact use case with zero config changes
  2. Simplest implementation - just change one function
  3. No breaking changes - fully backward compatible
  4. Intuitive behavior - more specific matches win
  5. No new concepts to learn

Implementation Summary:

  1. Replace path.extname(file) with a suffix matching function
  2. Sort extensions by length (longest first) when checking
  3. Return first match

This would allow your desired config to work immediately:

{
  "formatter": {
    "blade": {
      "extensions": [".blade.php"],
      "command": ["blade-formatter", "$FILE"]
    },
    "php": {
      "extensions": [".php"],
      "command": ["pint", "$FILE"]
    }
  }
}

If you need path-based matching later, you could add Approach 5 (Hybrid) as an enhancement.


Comparison Matrix

Approach Complexity Backward Compat Flexibility Config Changes
1. Glob Patterns Medium High Optional
2. Priority Order Low Medium None
3. Regex Medium Very High Optional
4. Matcher Function High Very High Optional
5. Hybrid Medium High Optional
6. Suffix Match Low Medium None

Next Steps

Recommended: Implement Approach 6 for immediate value, with potential to add Approach 5 later if path-based patterns are needed.


Problem Context

Current Issue

The formatter currently uses path.extname() to extract file extensions, which only captures the last extension segment (e.g., .php from .blade.php). This creates issues for compound extensions like:

  • .blade.php → only matches .php
  • .html.erb → only matches .erb
  • .spec.ts → only matches .ts

Current Workaround

Users must create a wrapper script to handle pattern matching manually:

#!/bin/bash
file=$1
if [[ $file == *.blade.php ]]; then
    npx blade-formatter -e -w --sort-tailwindcss-classes --wrap-attributes-min-attrs=2 "$file"
elif [[ $file == *.php ]]; then
    ./vendor/bin/pint "$file"
else
    echo "No formatter configured for this file type."
fi

With the recommended approach, this workaround would no longer be necessary.

I am taking what was said and looking though a few more options while noting the security concerns.

@dkudos commented on GitHub (Nov 1, 2025): I needed to rip out and analyst compare to give a few more options to compare for info to see if we missed anything that A.I. could give us to think about: # Alternative Approaches for Formatter Pattern Matching This document explores different design approaches for solving the compound extension problem in the opencode formatter, each with trade-offs and use cases. --- ## Approach 1: Glob Pattern Matching (Original Proposal) **Implementation:** ```typescript patterns: z.array(z.string()).optional() ``` **Example Config:** ```json { "formatter": { "blade": { "patterns": ["*.blade.php", "**/*.blade.php"], "command": ["blade-formatter", "$FILE"] } } } ``` **Pros:** - Familiar syntax for developers - Powerful (supports wildcards, paths) - Can match on path segments, not just extensions - Library support (minimatch, picomatch) **Cons:** - May be overkill for just extensions - Performance overhead for glob matching on every file - Potential for overly complex patterns --- ## Approach 2: Extension Priority Order **Implementation:** Instead of extracting just the last extension, extract ALL potential extension combinations and check against a priority-ordered list. ```typescript // For file: "component.spec.ts" // Extract: [".spec.ts", ".ts"] // Check formatters in order until match found ``` **Example Config:** ```json { "formatter": { "blade": { "extensions": [".blade.php"], "command": ["blade-formatter", "$FILE"] }, "php": { "extensions": [".php"], "command": ["pint", "$FILE"] } } } ``` **Implementation in format/index.ts:** ```typescript function extractExtensions(filepath: string): string[] { const basename = path.basename(filepath) const parts = basename.split('.') const extensions: string[] = [] // Extract all possible compound extensions for (let i = 1; i < parts.length; i++) { extensions.push('.' + parts.slice(i).join('.')) } return extensions // [".spec.ts", ".ts"] for "file.spec.ts" } async function getFormatter(filepath: string) { const extensions = extractExtensions(filepath) const formatters = await state().then((x) => x.formatters) const result = [] for (const item of Object.values(formatters)) { // Check most specific extension first for (const ext of extensions) { if (item.extensions.includes(ext)) { if (await isEnabled(item)) { result.push(item) } break // Found match, stop checking this formatter } } } return result } ``` **Pros:** - ✅ No new config fields needed - ✅ Backward compatible - ✅ Simple, intuitive behavior - ✅ No external dependencies - ✅ Efficient (simple string operations) **Cons:** - Only works for extensions (can't match on directory paths) - Relies on formatter definition order in config - Less flexible than patterns --- ## Approach 3: Regex Pattern Matching **Implementation:** ```typescript patterns: z.array(z.string()).optional() ``` **Example Config:** ```json { "formatter": { "blade": { "patterns": ["\\.blade\\.php$"], "command": ["blade-formatter", "$FILE"] }, "spec-files": { "patterns": ["\\.spec\\.(ts|js)$"], "command": ["spec-formatter", "$FILE"] } } } ``` **Pros:** - Extremely flexible and powerful - Can match complex patterns - No external dependencies (native RegExp) **Cons:** - ❌ Harder for non-technical users - ❌ Easy to write incorrect patterns - ❌ Less readable than glob patterns - ❌ Security concerns (ReDoS attacks if user-provided) --- ## Approach 4: File Matcher Function (Most Flexible) **Implementation:** Allow custom matcher functions or predefined matchers. ```typescript matcher: z.union([ z.array(z.string()), // Simple extensions z.object({ type: z.literal('glob'), patterns: z.array(z.string()) }), z.object({ type: z.literal('regex'), patterns: z.array(z.string()) }), z.object({ type: z.literal('extension'), values: z.array(z.string()), compound: z.boolean() // Enable compound extension matching }) ]) ``` **Example Config:** ```json { "formatter": { "blade": { "matcher": { "type": "glob", "patterns": ["*.blade.php"] }, "command": ["blade-formatter", "$FILE"] }, "php": { "matcher": { "type": "extension", "values": [".php"], "compound": false }, "command": ["pint", "$FILE"] } } } ``` **Pros:** - Maximum flexibility - Explicit matching strategy - Can add new matcher types later **Cons:** - ❌ Most complex to implement - ❌ Steeper learning curve - ❌ More verbose configuration --- ## Approach 5: Hybrid - Extensions + Optional Patterns **Implementation:** Keep `extensions` for backward compatibility, add optional `patterns` for advanced cases. ```typescript extensions: z.array(z.string()).optional() patterns: z.array(z.string()).optional() // Match order: patterns first, then compound extensions, then simple extensions ``` **Example Config:** ```json { "formatter": { "blade": { "patterns": ["*.blade.php"], "command": ["blade-formatter", "$FILE"] }, "typescript-spec": { "extensions": [".spec.ts", ".test.ts"], "command": ["spec-formatter", "$FILE"] }, "php": { "extensions": [".php"], "command": ["pint", "$FILE"] } } } ``` **Matching Logic:** ```typescript async function getFormatter(filepath: string) { const formatters = await state().then((x) => x.formatters) const result = [] for (const item of Object.values(formatters)) { let matched = false // 1. Check patterns first (if defined) if (item.patterns) { for (const pattern of item.patterns) { if (minimatch(filepath, pattern)) { matched = true break } } } // 2. Fall back to compound extension matching if (!matched && item.extensions) { const compoundExts = extractExtensions(filepath) for (const ext of compoundExts) { if (item.extensions.includes(ext)) { matched = true break } } } if (matched && await isEnabled(item)) { result.push(item) } } return result } ``` **Pros:** - ✅ Backward compatible - ✅ Solves your use case elegantly - ✅ Progressive enhancement (simple → advanced) - ✅ Clear upgrade path for users **Cons:** - Two ways to do similar things - Slightly more complex matching logic --- ## Approach 6: Extension Suffix Matching (Simplest) **Implementation:** Just change how extensions are extracted - use suffix matching instead of `path.extname()`. ```typescript // Current: path.extname("file.blade.php") → ".php" // New: checkExtension("file.blade.php", [".blade.php", ".php"]) → ".blade.php" function matchesExtension(filepath: string, extensions: string[]): string | null { const filename = path.basename(filepath) // Sort by length (longest first) to match most specific const sorted = [...extensions].sort((a, b) => b.length - a.length) for (const ext of sorted) { if (filename.endsWith(ext)) { return ext } } return null } ``` **Example Config (NO CHANGES NEEDED):** ```json { "formatter": { "blade": { "extensions": [".blade.php"], "command": ["blade-formatter", "$FILE"] }, "php": { "extensions": [".php"], "command": ["pint", "$FILE"] } } } ``` **Pros:** - ✅ **ZERO config changes needed** - ✅ Completely backward compatible - ✅ Solves the exact problem described - ✅ Simple implementation - ✅ Intuitive behavior (more specific extensions match first) - ✅ No new dependencies **Cons:** - Can't match on directory patterns - Limited to suffix matching only --- ## My Recommendation: **Approach 6** (Extension Suffix Matching) **Why?** 1. **Solves your exact use case** with zero config changes 2. **Simplest implementation** - just change one function 3. **No breaking changes** - fully backward compatible 4. **Intuitive behavior** - more specific matches win 5. **No new concepts** to learn **Implementation Summary:** 1. Replace `path.extname(file)` with a suffix matching function 2. Sort extensions by length (longest first) when checking 3. Return first match This would allow your desired config to work immediately: ```json { "formatter": { "blade": { "extensions": [".blade.php"], "command": ["blade-formatter", "$FILE"] }, "php": { "extensions": [".php"], "command": ["pint", "$FILE"] } } } ``` **If you need path-based matching later**, you could add Approach 5 (Hybrid) as an enhancement. --- ## Comparison Matrix | Approach | Complexity | Backward Compat | Flexibility | Config Changes | |----------|-----------|-----------------|-------------|----------------| | 1. Glob Patterns | Medium | ✅ | High | Optional | | 2. Priority Order | Low | ✅ | Medium | None | | 3. Regex | Medium | ✅ | Very High | Optional | | 4. Matcher Function | High | ✅ | Very High | Optional | | 5. Hybrid | Medium | ✅ | High | Optional | | **6. Suffix Match** | **Low** | **✅** | **Medium** | **None** | --- ## Next Steps **Recommended:** Implement **Approach 6** for immediate value, with potential to add **Approach 5** later if path-based patterns are needed. --- ## Problem Context ### Current Issue The formatter currently uses `path.extname()` to extract file extensions, which only captures the **last** extension segment (e.g., `.php` from `.blade.php`). This creates issues for compound extensions like: - `.blade.php` → only matches `.php` - `.html.erb` → only matches `.erb` - `.spec.ts` → only matches `.ts` ### Current Workaround Users must create a wrapper script to handle pattern matching manually: ```bash #!/bin/bash file=$1 if [[ $file == *.blade.php ]]; then npx blade-formatter -e -w --sort-tailwindcss-classes --wrap-attributes-min-attrs=2 "$file" elif [[ $file == *.php ]]; then ./vendor/bin/pint "$file" else echo "No formatter configured for this file type." fi ``` With the recommended approach, this workaround would no longer be necessary. I am taking what was said and looking though a few more options while noting the security concerns.
Author
Owner

@OpeOginni commented on GitHub (Nov 2, 2025):

Made a PR, with Exact Matching of extentions

.ts will work for files like index.ts but not index.test.ts

*.ts will work for both index.ts and index.test.ts

*.test.ts will work for only index.test.ts

@OpeOginni commented on GitHub (Nov 2, 2025): Made a PR, with Exact Matching of extentions `.ts` will work for files like `index.ts` but not `index.test.ts` `*.ts` will work for both `index.ts` and `index.test.ts` `*.test.ts` will work for only `index.test.ts`
Author
Owner

@magicprinc commented on GitHub (Jan 11, 2026):

+1 for .editorconfig support

https://editorconfig.org/

@magicprinc commented on GitHub (Jan 11, 2026): +1 for .editorconfig support https://editorconfig.org/
Author
Owner

@Taqinou commented on GitHub (Jan 30, 2026):

Hi! 👋

I've read through the discussion and the different approaches suggested (thanks @dkudos for the comprehensive analysis!).

I've implemented Approach 6: Extension Suffix Matching on my fork to solve the compound extension problem (.blade.php, .spec.ts, .html.erb, etc.).

Implementation Details

Problem: The current path.extname() only extracts the last extension:

  • component.blade.php.php
  • test.spec.ts.ts

Solution: Added extractExtensions() function that extracts ALL possible extensions from most specific to most general:

// For "component.blade.php"
extractExtensions() returns: [".blade.php", ".php"]

// Matching logic then checks if any formatter extension
// matches any of these file extensions

Changes made:

  • Modified packages/opencode/src/format/index.ts
  • Added extractExtensions(filepath: string): string[] helper function
  • Updated getFormatter() to accept full filepath instead of single extension
  • Changed matching logic from path.extname() to suffix matching

Why Approach 6:

  • Zero breaking changes - fully backward compatible
  • No config changes required - existing formatter configs work immediately
  • Simplest implementation - only ~15 lines of code changed
  • Intuitive behavior - more specific extensions match first
  • No new dependencies

Example config that now works:

{
  "formatter": {
    "blade": {
      "extensions": [".blade.php"],
      "command": ["blade-formatter", "$FILE"]
    },
    "php": {
      "extensions": [".php"],
      "command": ["pint", "$FILE"]
    }
  }
}

For component.blade.php, both formatters are now found (instead of just the PHP formatter).

Testing:

  • Tested with .blade.php, .spec.ts, .html.erb
  • Backward compatible with simple extensions (.php, .ts, .js)
  • Handles edge cases (files without extension, full paths)

Is this feature still considered open / up for grabs? I'd be happy to open a PR if you're interested in this direction!

Thanks!

@Taqinou commented on GitHub (Jan 30, 2026): Hi! 👋 I've read through the discussion and the different approaches suggested (thanks @dkudos for the comprehensive analysis!). I've implemented **Approach 6: Extension Suffix Matching** on my fork to solve the compound extension problem (`.blade.php`, `.spec.ts`, `.html.erb`, etc.). ## Implementation Details **Problem:** The current `path.extname()` only extracts the last extension: - `component.blade.php` → `.php` ❌ - `test.spec.ts` → `.ts` ❌ **Solution:** Added `extractExtensions()` function that extracts ALL possible extensions from most specific to most general: ```typescript // For "component.blade.php" extractExtensions() returns: [".blade.php", ".php"] // Matching logic then checks if any formatter extension // matches any of these file extensions ``` **Changes made:** - Modified `packages/opencode/src/format/index.ts` - Added `extractExtensions(filepath: string): string[]` helper function - Updated `getFormatter()` to accept full filepath instead of single extension - Changed matching logic from `path.extname()` to suffix matching **Why Approach 6:** - ✅ **Zero breaking changes** - fully backward compatible - ✅ **No config changes required** - existing formatter configs work immediately - ✅ **Simplest implementation** - only ~15 lines of code changed - ✅ **Intuitive behavior** - more specific extensions match first - ✅ **No new dependencies** **Example config that now works:** ```json { "formatter": { "blade": { "extensions": [".blade.php"], "command": ["blade-formatter", "$FILE"] }, "php": { "extensions": [".php"], "command": ["pint", "$FILE"] } } } ``` For `component.blade.php`, both formatters are now found (instead of just the PHP formatter). **Testing:** - ✅ Tested with `.blade.php`, `.spec.ts`, `.html.erb` - ✅ Backward compatible with simple extensions (`.php`, `.ts`, `.js`) - ✅ Handles edge cases (files without extension, full paths) Is this feature still considered open / up for grabs? I'd be happy to open a PR if you're interested in this direction! Thanks!
Author
Owner

@mohamedbouddi7777-dev commented on GitHub (Feb 14, 2026):

نعم جيد ولكن يجب تطوير هذا يعني لا يجب ان تزعجوني يعني عندي كثير من الاعمال وليس عندي الوقت لكي اجيب على كل انسان فهذا يجب ان يكون يعني عمل يعني اشتغل اشتغل اشتغل كل يوم ولكن الاجوبه مره

@mohamedbouddi7777-dev commented on GitHub (Feb 14, 2026): نعم جيد ولكن يجب تطوير هذا يعني لا يجب ان تزعجوني يعني عندي كثير من الاعمال وليس عندي الوقت لكي اجيب على كل انسان فهذا يجب ان يكون يعني عمل يعني اشتغل اشتغل اشتغل كل يوم ولكن الاجوبه مره
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#2273