Fix this PegJS grammer to extract filenames and from markdown code blocks

--- FAILING TESTS

FAIL  __tests__/index.test.ts
LLM Apply Changes Parser
  ✓ parses empty input correctly (48 ms)
  ✓ parses explicit comment blocks (33 ms)
  ✓ parses explicit tag blocks (15 ms)
  ✕ parses markdown block with path in preceding paragraph (27 ms)
  ✓ parses markdown block with path in preceding heading (22 ms)
  ✓ parses markdown block with standalone path before it (11 ms)
  ✓ handles markdown block with no preceding path (13 ms)
  ✕ handles mixed explicit and markdown blocks correctly (33 ms)
  ✕ normalizes backslashes in paths (using PEG parser) (3 ms)

● LLM Apply Changes Parser › parses markdown block with path in preceding paragraph

  expect(received).toBe(expected) // Object.is equality

  Expected: 1
  Received: 0

    78 |         const input = fs.readFileSync(path.join(fixturesDir, 'markdown_paragraph_path.md'), 'utf8');
    79 |         const result = parseInput(input);
  > 80 |         expect(result.size).toBe(1);
       |                             ^
    81 |         expect(result.has('src/app.js')).toBe(true);
    82 |         const fileData = result.get('src/app.js');
    83 |         expect(fileData?.format).toBe('Markdown Block');

    at Object.<anonymous> (__tests__/index.test.ts:80:29)

● LLM Apply Changes Parser › handles mixed explicit and markdown blocks correctly

  expect(received).toBe(expected) // Object.is equality

  Expected: 3
  Received: 2

    133 |         const result = parseInput(input);
    134 |
  > 135 |         expect(result.size).toBe(3);
        |                             ^
    136 |
    137 |         expect(result.has('config/settings.yaml')).toBe(true);
    138 |         const yamlData = result.get('config/settings.yaml');

    at Object.<anonymous> (__tests__/index.test.ts:135:29)

● LLM Apply Changes Parser › normalizes backslashes in paths (using PEG parser)

  expect(received).toBe(expected) // Object.is equality

  Expected: 1
  Received: 0

    175 |         }
    176 |
  > 177 |         expect(result.size).toBe(1);
        |                             ^
    178 |         expect(result.has('src/utils/helpers.ts')).toBe(true);
    179 |         const fileData = result.get('src/utils/helpers.ts');
    180 |         expect(fileData).toBeDefined();

    at Object.<anonymous> (__tests__/index.test.ts:177:29)

Test Suites: 1 failed, 1 total
Tests:       3 failed, 6 passed, 9 total
Snapshots:   0 total
Time:        0.946 s, estimated 1 s
Ran all test suites.
error Command failed with exit code 1.

--- CODE FOLLOWS

<files>
This section contains the contents of the repository's files.

<file path="__tests__/fixtures/explicit_comment.txt">
Some text before.

/* START OF src/component.ts */
export class MyComponent {
  // component code
}
/* END OF src/component.ts */

Some text after.
</file>

<file path="__tests__/fixtures/explicit_tag.txt">
<file path="data/config.json">
{
  "key": "value",
  "enabled": true
}
</file>
</file>

<file path="__tests__/fixtures/markdown_heading_path.md">
## File: styles/main.css

```css
body {
  margin: 0;
  padding: 0;
  background-color: #f0f0f0;
}
```
</file>

<file path="__tests__/fixtures/markdown_paragraph_path.md">
Here is the main application file `src/app.js`:

```javascript
console.log('Hello World!');
const version = 1;
```

And that's it.
</file>

<file path="__tests__/fixtures/markdown_standalone_path.md">
path/to/my_script.py

```python
import sys

def main():
    print(f"Args: {sys.argv}")

if __name__ == "__main__":
    main()
```
</file>

<file path="__tests__/fixtures/mixed_blocks.md">
First, the config:

/* START OF config/settings.yaml */
port: 8080
database:
  url: postgres://...
/* END OF config/settings.yaml */

Now the main script. This is `scripts/run.sh`:
```bash
#!/bin/bash
echo "Starting..."
node dist/index.js
echo "Done."
```

<file path="docs/README.md">
# Project Docs
This is the documentation.
</file>
</file>

<file path="__tests__/fixtures/no_path.md">
Just a code block without a clear path before it.

```text
Some plain text content.
```
</file>

<file path="__tests__/fixtures/path_normalization.md">
Windows path: `src\\utils\\helpers.ts`

```typescript
export const helper = () => true;
```
</file>

<file path="src/markdown-blocks.pegjs">
{
  const DEBUG = true;

  function normalizePath(pathStr) {
    const normalized = pathStr.trim().replace(/\\/g, '/');
    if (DEBUG) console.log(`[NormalizePath] Input: "${pathStr}", Output: "${normalized}"`);
    return normalized;
  }

  function logMatch(ruleName, success, text) {
    if (!DEBUG) return;
    const status = success ? "MATCH" : "FAIL";
    const snippet = text.replace(/\n/g, '\\n').substring(0, 60);
    console.log(`[PEG ${status}] ${ruleName} T:"${snippet}"`);
  }
}

Document = segments:Segment* EOF {
  if (DEBUG) console.log("[Document] EOF reached.");
  return segments.filter(s => s && s.format === "Markdown Block");
}

Segment = FileBlock / IgnorableContent

FileBlock =
  &(PathIndicatorLine)
  indicator:PathIndicatorLine
  blanks:BlankLine*
  block:CodeBlock
  {
    logMatch("FileBlock", true, `Path: ${indicator.path}`);
    return { path: indicator.path, content: block, format: "Markdown Block" };
  }

IgnorableContent =
  !(PathIndicatorLine BlankLine* CodeBlock)
  consumed:( CodeBlock / IgnorableLine )
  { return null; }

PathIndicatorLine = content:(StandalonePathLine / PathInHeading / PathInParagraph) (Newline / EOF) { return content; }
BlankLine = Indent Newline { return null; }

IgnorableLine =
  line:$( (!Newline .)* ) Newline
  {
    logMatch("IgnorableLine", true, line);
    return null;
  }

StandalonePathLine = Indent path:FilePath { return { path: normalizePath(path) }; }
PathInHeading = HASH+ Indent (PathMarker ":"? Indent)? path:FilePath "`"? { return { path: normalizePath(path) }; }
PathInParagraph =
  !CodeFenceStart
  Indent
  path:(QuotedFilePath / UnquotedFilePath)
  ( ("`"? Indent)? TextAfterPath? (":" Indent)? )?
  { return { path: normalizePath(path) }; }

QuotedFilePath = "`" path:FilePath "`" { return path; }
UnquotedFilePath = path:FilePath { return path; }

// Allow any extension, not just a fixed list
FilePath = path:$( NameSegment ( PathSeparator NameSegment )* ( "." [a-zA-Z0-9_]+ )? ) { return path; }
NameSegment = $( [a-zA-Z0-9_@.-]+ ) / ".."
PathSeparator = "/" / "\\"

CodeBlock =
  start:CodeFenceLineStart
  content:CodeBlockContent
  end:CodeFenceLineEnd
  {
    const fullBlockText = start + content + end;
    logMatch("CodeBlock", true, fullBlockText);
    return content.replace(/\r?\n$/, '');
  }

CodeFenceLineStart = Indent CodeFenceStart LangTag? optionalWhitespace Newline { return text(); }
CodeFenceLineEnd = Indent CodeFenceEnd optionalWhitespace (Newline / EOF) { return text(); }
CodeBlockContent = $((CodeContentLine)*)

CodeContentLine =
  !(Indent CodeFenceEnd)
  line:$((!Newline .)* Newline)
  { return line; }

CodeFenceStart = "```"
CodeFenceEnd = "```"
LangTag = $( [a-zA-Z0-9_-]+ )
optionalWhitespace = [ \t]*

TextAfterPath = $( (!Newline .)* )
PathMarker = ("File" / "file" / "Path" / "path" / "Updating" / "updating" / "Creating" / "creating")
Indent = [ \t]*
Newline = "\n" / "\r\n"
EOF = !.
HASH = "#"
</file>

<file path="src/parser.ts">
// src/parser.ts

// Import 'marked' only if used for additional parsing logic.
// import { marked, Token } from 'marked';
// import type { Tokens } from 'marked';

// Import the generated parser
// eslint-disable-next-line @typescript-eslint/no-var-requires
const MarkdownBlockParser = require('./generated-markdown-parser.cjs');

// Explicit block regexes
export const explicitCommentBlockRegex = /\/\*\s*START OF\s*(?<path>.*?)\s*\*\/\r?\n?(?<content>.*?)\r?\n?\/\*\s*END OF\s*\1\s*\*\//gs;
export const explicitTagBlockRegex = /<file\s+(?:path|name|filename)\s*=\s*["'](?<path>.*?)["']\s*>\r?\n?(?<content>.*?)\r?\n?<\/file>/gis;

// Type definitions
export type FileData = {
    content: string;
    format: string; // e.g., "Comment Block", "Tag Block", "Markdown Block"
};
export type FilesMap = Map<string, FileData>;

// Parsing Functions

/** Extracts file path and content from explicit markers */
export function extractExplicitBlocks(
    input: string,
    regex: RegExp,
    formatName: string,
    filesToWrite: FilesMap
): string {
    let remainingInput = input;
    const matches = [...input.matchAll(regex)];
    for (const match of matches) {
        const filePath = match.groups?.path?.trim();
        const contentMatch = match.groups?.content;
        const fullMatch = match[0];

        if (filePath && contentMatch !== undefined && fullMatch) {
            const normalizedPath = filePath.replace(/\\/g, '/');
            let content = contentMatch.replace(/^\r?\n/, '').replace(/\r?\n\s*$/, '');
            if (filesToWrite.has(normalizedPath)) {
                console.warn(`[${formatName}] Overwriting prior definition for ${normalizedPath}`);
            }
            filesToWrite.set(normalizedPath, { content, format: formatName });
            remainingInput = remainingInput.replace(fullMatch, `\n[LLM_APPLY_Processed ${formatName} for ${normalizedPath}]\n`);
        } else {
            console.warn(`[${formatName}] Failed to extract path or content from block: ${fullMatch?.substring(0, 100)}...`);
        }
    }
    return remainingInput;
}

/** Extracts file paths and content using the PEG Markdown parser */
export function extractMarkdownBlocksWithPeg(
    markdownInput: string,
    filesToWrite: FilesMap
): void {
    const formatName = "Markdown Block";
    try {
        const parsedBlocks: Array<{ path: string; content: string; format: string }> = MarkdownBlockParser.parse(markdownInput);

        for (const block of parsedBlocks) {
            const normalizedPath = block.path;
            const existingData = filesToWrite.get(normalizedPath);

            if (existingData) {
                if (existingData.format !== formatName) {
                    console.log(`[${formatName}] Skipping ${normalizedPath} (defined by ${existingData.format})`);
                } else {
                    console.warn(`[${formatName}] Overwriting prior Markdown definition for ${normalizedPath}`);
                    filesToWrite.set(normalizedPath, { content: block.content, format: formatName });
                }
            } else {
                console.log(`[${formatName}] Found: ${normalizedPath}`);
                filesToWrite.set(normalizedPath, { content: block.content, format: formatName });
            }
        }
    } catch (error: any) {
        if (error.location && error.format) {
            console.error(`Error parsing Markdown blocks:\n${error.format([{ source: "stdin", text: markdownInput }])}`);
        } else if (error.location) {
            console.error(`Error parsing Markdown blocks at Line ${error.location.start.line}, Column ${error.location.start.column}: ${error.message}`);
            const lines = markdownInput.split('\n');
            const errorLineIndex = error.location.start.line - 1;
            const contextStart = Math.max(0, errorLineIndex - 1);
            const contextEnd = Math.min(lines.length, errorLineIndex + 2);
            const context = lines.slice(contextStart, contextEnd).join('\n');
            console.error(`Context:\n---\n${context}\n---`);
        } else {
            console.error(`Error parsing Markdown blocks: ${error.message}`);
        }
    }
}
</file>

<file path="src/index.ts">
#!/usr/bin/env node
// Shebang required for CLI execution

// llm-apply-cli/src/index.ts
import * as fs from 'fs/promises';
import * as path from 'path';
import {
    extractExplicitBlocks,
    extractMarkdownBlocksWithPeg,
    explicitCommentBlockRegex,
    explicitTagBlockRegex,
    type FilesMap,
    type FileData
} from './parser';

// Helper Functions

/** Reads all data from standard input */
async function readStdin(): Promise<string> {
    return new Promise((resolve, reject) => {
        let data = '';
        process.stdin.setEncoding('utf8');
        process.stdin.on('readable', () => { let chunk; while ((chunk = process.stdin.read()) !== null) { data += chunk; } });
        process.stdin.on('end', () => resolve(data));
        process.stdin.on('error', reject);
    });
}

/** Creates directories recursively if they don’t exist */
async function ensureDirectoryExists(filePath: string): Promise<void> {
    const dirname = path.dirname(filePath);
    if (!dirname || dirname === '.' || dirname === '/') return;
    try {
        await fs.access(dirname);
    } catch (error: any) {
        if (error.code === 'ENOENT') {
            try {
                await fs.mkdir(dirname, { recursive: true });
                console.log(`Created directory: ${dirname}`);
            } catch (mkdirError: any) {
                if (mkdirError.code !== 'EEXIST') {
                    console.error(`Error creating directory ${dirname}:`, mkdirError);
                    throw mkdirError;
                }
            }
        } else {
            console.error(`Error accessing directory ${dirname}:`, error);
            throw error;
        }
    }
}

// Main Execution Logic
async function runCli() {
    const filesToWrite: FilesMap = new Map();

    console.log("Waiting for LLM output via stdin... (Paste content + EOF: Ctrl+D on Linux/macOS, Ctrl+Z then Enter on Windows)");
    const originalInput = await readStdin();

    if (!originalInput?.trim()) {
        console.error("Error: No input received from stdin");
        process.exit(1);
    }
    console.log("\nProcessing input...");
    let processedInput = originalInput;

    // Step 1 & 2: Extract Explicit Blocks
    console.log("\nSearching for /* START OF ... */ blocks...");
    processedInput = extractExplicitBlocks(processedInput, explicitCommentBlockRegex, "Comment Block", filesToWrite);
    console.log("\nSearching for <file path=...> blocks...");
    processedInput = extractExplicitBlocks(processedInput, explicitTagBlockRegex, "Tag Block", filesToWrite);

    // Step 3: Parse Markdown Blocks
    console.log("\nParsing remaining text for Markdown code blocks...");
    extractMarkdownBlocksWithPeg(processedInput, filesToWrite);

    // Step 4: Write Files
    console.log("\nWriting files...");
    let filesProcessed = 0;
    let errors = 0;
    if (filesToWrite.size === 0) {
        console.warn("Warning: No file blocks identified in the input");
        if (originalInput.includes("```")) console.warn("Hint: Ensure a valid file path (e.g., path/to/file.ts) precedes each code block");
        if (originalInput.includes("START OF")) console.warn("Hint: Verify /* */ comment syntax and spacing");
        if (originalInput.includes("<file")) console.warn("Hint: Check <file path=\"...\"> tag format");
        process.exit(0);
    }

    for (const [relativePath, fileData] of filesToWrite.entries()) {
        let normalizedPath: string;
        try {
            normalizedPath = path.normalize(relativePath).replace(/\\/g, '/');
            if (normalizedPath.startsWith('..') || path.isAbsolute(normalizedPath) || normalizedPath.startsWith('/')) {
                console.error(`Error: Unsafe or absolute path detected: ${relativePath}`);
                errors++;
                continue;
            }
        } catch (normError) {
            console.error(`Error: Invalid path format: ${relativePath}`, normError);
            errors++;
            continue;
        }

        const absolutePath = path.resolve(process.cwd(), normalizedPath);
        console.log(`\nProcessing ${normalizedPath} (Detected via: ${fileData.format})`);
        try {
            await ensureDirectoryExists(absolutePath);
            const contentToWrite = (fileData.content && !/\r?\n$/.test(fileData.content))
                ? fileData.content + '\n'
                : fileData.content ?? '';
            await fs.writeFile(absolutePath, contentToWrite, 'utf8');
            console.log(`Wrote ${normalizedPath} successfully`);
            filesProcessed++;
        } catch (error) {
            console.error(`Error writing file ${normalizedPath}:`, error);
            errors++;
        }
    }

    // Summary
    console.log("\nSummary:");
    if (filesProcessed > 0) console.log(`Wrote ${filesProcessed} file(s)`);
    if (errors > 0) {
        console.error(`Encountered ${errors} error(s). See logs for details`);
        process.exit(1);
    } else if (filesProcessed === 0 && filesToWrite.size > 0) {
        console.warn("Finished, but no files written due to errors");
        process.exit(1);
    } else if (filesProcessed === 0) {
        console.warn("Finished, but no files identified or written");
    } else {
        console.log("Finished successfully");
    }
}

// Entry Point
if (require.main === module && process.env.NODE_ENV !== 'test') {
    runCli().catch((error) => {
        console.error("\nUnexpected error during execution:", error);
        process.exit(1);
    });
}
</file>

</files>