From 67967d3adccb5709ea8bfc2e357d8ff685831e12 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Thu, 1 Jan 2026 13:24:03 +0900 Subject: [PATCH 1/5] feat: remove default .docsignore value from configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING CHANGE: The `ignoreFile` configuration no longer defaults to '.docsignore'. To continue using .docsignore, explicitly set `ignoreFile: '.docsignore'` in your configuration file (.coderefrc.json or package.json). This change makes the ignore file behavior explicit rather than implicit, applying ignore patterns only when intentionally configured. All documentation examples now use `.gitignore` instead of `.docsignore` to reflect this generic approach. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- README.md | 4 ++-- docs/user-guide/configuration.md | 17 ++++++++++------- src/cli/validate.ts | 8 ++++---- src/config.test.ts | 11 +++++------ src/config.ts | 3 +-- src/core/validate.test.ts | 1 - src/utils/fix.test.ts | 1 - 7 files changed, 22 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 5fd0bc0..07104d3 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ - 🎯 AST-based symbol searching for TypeScript/JavaScript - 📝 Interactive fix mode with colored diffs - 🎨 Beautiful diff display -- 🚫 .docsignore support for excluding files +- 🚫 Ignore file support for excluding files ## Installation @@ -171,7 +171,7 @@ Create `.coderefrc.json` in your project root: { "projectRoot": ".", "docsDir": "docs", - "ignoreFile": ".docsignore" + "ignoreFile": ".gitignore" } ``` diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md index b8e29fb..825e4bf 100644 --- a/docs/user-guide/configuration.md +++ b/docs/user-guide/configuration.md @@ -8,7 +8,7 @@ Create `.coderefrc.json` in your project root: { "projectRoot": ".", "docsDir": "docs", - "ignoreFile": ".docsignore", + "ignoreFile": ".gitignore", "ignorePatterns": ["**/*.draft.md"], "verbose": false } @@ -55,15 +55,18 @@ The tool loads configuration from multiple sources with the following precedence #### `ignoreFile` - **Type**: `string` (optional) -- **Default**: `".docsignore"` +- **Default**: `undefined` - **Description**: Path to ignore file relative to `projectRoot`. The file follows `.gitignore` syntax. ```json { - "ignoreFile": ".docsignore" + "ignoreFile": ".gitignore" } ``` +> **Note**: Prior to version 0.2.0, the default value was `.docsignore`. +> If you want to continue using `.docsignore`, explicitly set `ignoreFile: '.docsignore'` in your configuration. + #### `ignorePatterns` - **Type**: `string[]` (optional) @@ -161,7 +164,7 @@ Configuration with custom ignore patterns and verbose output: { "projectRoot": ".", "docsDir": "documentation", - "ignoreFile": ".docsignore", + "ignoreFile": ".gitignore", "ignorePatterns": ["**/*.draft.md", "**/archive/**", "**/_*.md"], "verbose": true } @@ -175,7 +178,7 @@ Configuration for a monorepo with multiple documentation directories: { "projectRoot": "packages/my-package", "docsDir": "docs", - "ignoreFile": "../../.docsignore" + "ignoreFile": "../../.gitignore" } ``` @@ -217,9 +220,9 @@ Alternatively, you can define configuration in `package.json`: ## Ignore Files -### .docsignore Syntax +### Ignore File Syntax -The `.docsignore` file follows the same syntax as `.gitignore`: +The ignore file follows the same syntax as `.gitignore`: ``` # Ignore draft files diff --git a/src/cli/validate.ts b/src/cli/validate.ts index 755a5f0..235e7ad 100644 --- a/src/cli/validate.ts +++ b/src/cli/validate.ts @@ -103,14 +103,14 @@ export async function main(args: string[] = process.argv.slice(2)): Promise { const relativePath = path.relative(config.projectRoot, file); return !isIgnored(relativePath, ignorePatterns); @@ -118,7 +118,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise markdownFiles.length) { console.log( - `📋 ${allMarkdownFiles.length - markdownFiles.length} files excluded by .docsignore\n` + `📋 ${allMarkdownFiles.length - markdownFiles.length} files excluded by ignore patterns\n` ); } diff --git a/src/config.test.ts b/src/config.test.ts index 47d4c7e..ba0ded8 100644 --- a/src/config.test.ts +++ b/src/config.test.ts @@ -39,7 +39,6 @@ describe('Config System', () => { expect(config).toEqual({ projectRoot: testDir, docsDir: 'docs', - ignoreFile: '.docsignore', verbose: false, }); }); @@ -219,7 +218,7 @@ describe('Config System', () => { expect(config.docsDir).toBe('custom-docs'); expect(config.projectRoot).toBe(testDir); - expect(config.ignoreFile).toBe('.docsignore'); + expect(config.ignoreFile).toBeUndefined(); }); }); @@ -320,8 +319,9 @@ describe('Config System', () => { describe('getIgnoreFilePath', () => { it('should return absolute path to ignore file when configured', () => { - const ignorePath = getIgnoreFilePath(config); - expect(ignorePath).toBe(path.join(testDir, '.docsignore')); + const customConfig = loadConfig({ ignoreFile: '.gitignore' }); + const ignorePath = getIgnoreFilePath(customConfig); + expect(ignorePath).toBe(path.join(testDir, '.gitignore')); }); it('should return null when ignoreFile is not configured', () => { @@ -343,13 +343,12 @@ describe('Config System', () => { const docsDir = path.join(testDir, 'docs'); fs.mkdirSync(docsDir); fs.writeFileSync(path.join(docsDir, 'README.md'), '# Documentation'); - fs.writeFileSync(path.join(testDir, '.docsignore'), '*.tmp.md'); const config = loadConfig(); expect(config.projectRoot).toBe(testDir); expect(getDocsPath(config)).toBe(docsDir); - expect(getIgnoreFilePath(config)).toBe(path.join(testDir, '.docsignore')); + expect(getIgnoreFilePath(config)).toBeNull(); }); it('should handle monorepo structure with custom docsDir', () => { diff --git a/src/config.ts b/src/config.ts index 01acfeb..148ea49 100644 --- a/src/config.ts +++ b/src/config.ts @@ -26,7 +26,7 @@ export interface CodeRefConfig { docsDir: string; /** - * Path to ignore file relative to projectRoot (default: ".docsignore") + * Path to ignore file relative to projectRoot */ ignoreFile?: string; @@ -80,7 +80,6 @@ function getDefaultConfig(): CodeRefConfig { return { projectRoot: process.cwd(), docsDir: 'docs', - ignoreFile: '.docsignore', verbose: false, }; } diff --git a/src/core/validate.test.ts b/src/core/validate.test.ts index 7674149..3bff612 100644 --- a/src/core/validate.test.ts +++ b/src/core/validate.test.ts @@ -14,7 +14,6 @@ const mockProjectRoot = '/project'; const mockConfig: CodeRefConfig = { projectRoot: mockProjectRoot, docsDir: 'docs', - ignoreFile: '.docsignore', verbose: false, }; diff --git a/src/utils/fix.test.ts b/src/utils/fix.test.ts index b6d4583..722d509 100644 --- a/src/utils/fix.test.ts +++ b/src/utils/fix.test.ts @@ -50,7 +50,6 @@ const mockPrompt = prompt as jest.Mocked; const mockConfig: CodeRefConfig = { projectRoot: path.resolve(__dirname, '../../..'), docsDir: 'docs', - ignoreFile: '.docsignore', verbose: false, }; From f1f52879dbed2d8ffaaf29fdba959e401fc81d2a Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Thu, 1 Jan 2026 13:48:46 +0900 Subject: [PATCH 2/5] fix: exclude CODE_REF comments in unclosed code blocks from validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed an issue where CODE_REF comments inside unclosed markdown code blocks were not being excluded from validation. The getCodeBlockRanges() function now detects unclosed code blocks (starting ``` without closing ```) and treats them as code blocks from the start position to the end of the file. Changes: - Enhanced getCodeBlockRanges() to detect and handle unclosed code blocks - Added comprehensive test cases for code block exclusion scenarios: - CODE_REF in normal code blocks - CODE_REF in inline code - CODE_REF in unclosed code blocks - Multiple code blocks with mixed CODE_REFs This resolves the issue where documentation examples of CODE_REF syntax within code blocks were incorrectly validated. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/core/validate.test.ts | 80 +++++++++++++++++++++++++++++++++++++++ src/core/validate.ts | 24 +++++++++++- 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/src/core/validate.test.ts b/src/core/validate.test.ts index 3bff612..0268b6b 100644 --- a/src/core/validate.test.ts +++ b/src/core/validate.test.ts @@ -162,6 +162,86 @@ describe('validate-docs-code', () => { expect(result).toHaveLength(1); expect(result[0].refPath).toBe('src/example.ts'); }); + + it('コードブロック内のCODE_REFを除外すること', () => { + const content = ` +# Test Document + + + +\`\`\`html + +\`\`\` + + + `; + + const result = extractCodeRefs(content, '/docs/test.md'); + + expect(result).toHaveLength(2); + expect(result[0].refPath).toBe('src/valid.ts'); + expect(result[1].refPath).toBe('src/another-valid.ts'); + }); + + it('インラインコード内のCODE_REFを除外すること', () => { + const content = ` +# Test Document + +Use this syntax: \`\` + + + `; + + const result = extractCodeRefs(content, '/docs/test.md'); + + expect(result).toHaveLength(1); + expect(result[0].refPath).toBe('src/valid.ts'); + }); + + it('閉じていないコードブロック内のCODE_REFを除外すること', () => { + const content = ` +# Test Document + + + +\`\`\`html + + + `; + + const result = extractCodeRefs(content, '/docs/test.md'); + + expect(result).toHaveLength(1); + expect(result[0].refPath).toBe('src/valid.ts'); + }); + + it('複数のコードブロックが混在する場合にCODE_REFを正しく抽出すること', () => { + const content = ` +# Documentation + + + +\`\`\`typescript + +const x = 1; +\`\`\` + + + +\`\`\`javascript + +\`\`\` + + + `; + + const result = extractCodeRefs(content, '/docs/test.md'); + + expect(result).toHaveLength(3); + expect(result[0].refPath).toBe('src/valid1.ts'); + expect(result[1].refPath).toBe('src/valid2.ts'); + expect(result[2].refPath).toBe('src/valid3.ts'); + }); }); describe('validateCodeRef', () => { diff --git a/src/core/validate.ts b/src/core/validate.ts index 80f1644..b6dbde4 100644 --- a/src/core/validate.ts +++ b/src/core/validate.ts @@ -53,7 +53,7 @@ export function findMarkdownFiles(dir: string): string[] { function getCodeBlockRanges(content: string): { start: number; end: number }[] { const ranges: { start: number; end: number }[] = []; - // Triple backtick code blocks + // Triple backtick code blocks (closed) const codeBlockPattern = /```[\s\S]*?```/g; let match: RegExpExecArray | null; @@ -64,6 +64,28 @@ function getCodeBlockRanges(content: string): { start: number; end: number }[] { }); } + // Find unclosed code blocks (starting ``` without closing ```) + const allCodeBlockStarts = /```/g; + const closedRanges = ranges.slice(); // Copy of closed ranges + let startMatch: RegExpExecArray | null; + + while ((startMatch = allCodeBlockStarts.exec(content)) !== null) { + const startPos = startMatch.index; + + // Check if this start position is already part of a closed block + const isPartOfClosedBlock = closedRanges.some( + (range) => startPos >= range.start && startPos < range.end + ); + + if (!isPartOfClosedBlock) { + // This is an unclosed block - treat from start to end of content + ranges.push({ + start: startPos, + end: content.length, + }); + } + } + // Inline code (backticks) const inlineCodePattern = /`[^`\n]+?`/g; while ((match = inlineCodePattern.exec(content)) !== null) { From fd132239a2ed46ff8e966163b8149a9605128b53 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Thu, 1 Jan 2026 13:54:15 +0900 Subject: [PATCH 3/5] refactor: improve code block detection logic using odd/even pairing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improved the getCodeBlockRanges() function to use a more robust odd/even pairing approach instead of the previous implementation that had potential issues with overlapping ranges. Previous implementation issues: - Closing backticks could be incorrectly treated as unclosed blocks - Range check did not account for the 3-character length of triple backticks - Could create overlapping or duplicate ranges New implementation: - Collects all triple backtick positions in order - Pairs them sequentially: indices 0-1, 2-3, 4-5, etc. - If odd number of backticks exists, the last one starts an unclosed block - Cleaner logic with fewer edge cases All existing tests pass (372 tests). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/core/validate.ts | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/src/core/validate.ts b/src/core/validate.ts index b6dbde4..bee5e27 100644 --- a/src/core/validate.ts +++ b/src/core/validate.ts @@ -53,34 +53,30 @@ export function findMarkdownFiles(dir: string): string[] { function getCodeBlockRanges(content: string): { start: number; end: number }[] { const ranges: { start: number; end: number }[] = []; - // Triple backtick code blocks (closed) - const codeBlockPattern = /```[\s\S]*?```/g; + // Find all triple backtick positions + const backtickPositions: number[] = []; + const backtickPattern = /```/g; let match: RegExpExecArray | null; - while ((match = codeBlockPattern.exec(content)) !== null) { - ranges.push({ - start: match.index, - end: match.index + match[0].length, - }); + while ((match = backtickPattern.exec(content)) !== null) { + backtickPositions.push(match.index); } - // Find unclosed code blocks (starting ``` without closing ```) - const allCodeBlockStarts = /```/g; - const closedRanges = ranges.slice(); // Copy of closed ranges - let startMatch: RegExpExecArray | null; - - while ((startMatch = allCodeBlockStarts.exec(content)) !== null) { - const startPos = startMatch.index; + // Pair up backticks: even indices (0, 2, 4...) are opening, odd indices (1, 3, 5...) are closing + for (let i = 0; i < backtickPositions.length; i += 2) { + const start = backtickPositions[i]; + const end = backtickPositions[i + 1]; - // Check if this start position is already part of a closed block - const isPartOfClosedBlock = closedRanges.some( - (range) => startPos >= range.start && startPos < range.end - ); - - if (!isPartOfClosedBlock) { - // This is an unclosed block - treat from start to end of content + if (end !== undefined) { + // Closed code block + ranges.push({ + start, + end: end + 3, // +3 to include the closing ``` + }); + } else { + // Unclosed code block (odd number of backticks) ranges.push({ - start: startPos, + start, end: content.length, }); } From b5674add6060fbb429190ca97a02526ae16c31f2 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Thu, 1 Jan 2026 14:07:35 +0900 Subject: [PATCH 4/5] fix: handle variable-length backtick sequences in code blocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed an issue where 4 or more backticks (````) used to wrap markdown code blocks were not properly handled, causing CODE_REF comments inside these blocks to be incorrectly validated. The previous implementation only detected triple backticks (```), which matched the first 3 characters of longer sequences like ````, leading to incorrect pairing and false positives. New implementation: - Detects all backtick sequences of 3 or more characters - Records both position and length of each sequence - Pairs sequences only if they have matching lengths - Supports arbitrary nesting levels (````, ``````, etc.) Test cases added: - 4-backtick code blocks containing 3-backtick blocks - 5-backtick code blocks with nested structures - Unclosed blocks with variable-length sequences All tests pass (17 test suites, 374 tests). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/core/validate.test.ts | 46 ++++++++++++++++++++++++ src/core/validate.ts | 74 ++++++++++++++++++++++++++++----------- 2 files changed, 100 insertions(+), 20 deletions(-) diff --git a/src/core/validate.test.ts b/src/core/validate.test.ts index 0268b6b..a50a2b2 100644 --- a/src/core/validate.test.ts +++ b/src/core/validate.test.ts @@ -242,6 +242,52 @@ const x = 1; expect(result[1].refPath).toBe('src/valid2.ts'); expect(result[2].refPath).toBe('src/valid3.ts'); }); + + it('4つのバッククォートで囲まれたコードブロック内のCODE_REFを除外すること', () => { + const content = ` +# Implementation Details + +\`\`\`\`markdown +## Class + +\`\`\`html + +\`\`\` + +\`\`\`typescript +export class ClassName {} +\`\`\` +\`\`\`\` + + + `; + + const result = extractCodeRefs(content, '/docs/test.md'); + + expect(result).toHaveLength(1); + expect(result[0].refPath).toBe('src/valid.ts'); + }); + + it('5つのバッククォートで囲まれたコードブロック内のCODE_REFを除外すること', () => { + const content = ` +# Nested Example + +\`\`\`\`\`markdown +\`\`\`\`markdown +\`\`\`html + +\`\`\` +\`\`\`\` +\`\`\`\`\` + + + `; + + const result = extractCodeRefs(content, '/docs/test.md'); + + expect(result).toHaveLength(1); + expect(result[0].refPath).toBe('src/valid.ts'); + }); }); describe('validateCodeRef', () => { diff --git a/src/core/validate.ts b/src/core/validate.ts index bee5e27..5adcd60 100644 --- a/src/core/validate.ts +++ b/src/core/validate.ts @@ -53,37 +53,71 @@ export function findMarkdownFiles(dir: string): string[] { function getCodeBlockRanges(content: string): { start: number; end: number }[] { const ranges: { start: number; end: number }[] = []; - // Find all triple backtick positions - const backtickPositions: number[] = []; - const backtickPattern = /```/g; - let match: RegExpExecArray | null; + // Find all backtick sequences (3 or more consecutive backticks) + const backtickSequences: { position: number; length: number }[] = []; + let i = 0; + + while (i < content.length) { + if (content[i] === '`') { + const start = i; + let count = 0; + + // Count consecutive backticks + while (i < content.length && content[i] === '`') { + count++; + i++; + } - while ((match = backtickPattern.exec(content)) !== null) { - backtickPositions.push(match.index); + // Only consider sequences of 3 or more backticks as code block delimiters + if (count >= 3) { + backtickSequences.push({ position: start, length: count }); + } + } else { + i++; + } } - // Pair up backticks: even indices (0, 2, 4...) are opening, odd indices (1, 3, 5...) are closing - for (let i = 0; i < backtickPositions.length; i += 2) { - const start = backtickPositions[i]; - const end = backtickPositions[i + 1]; + // Pair up backtick sequences with matching lengths + const used = new Set(); - if (end !== undefined) { - // Closed code block - ranges.push({ - start, - end: end + 3, // +3 to include the closing ``` - }); - } else { - // Unclosed code block (odd number of backticks) + for (let i = 0; i < backtickSequences.length; i++) { + if (used.has(i)) continue; + + const opening = backtickSequences[i]; + + // Find the next sequence with the same length + for (let j = i + 1; j < backtickSequences.length; j++) { + if (used.has(j)) continue; + + const closing = backtickSequences[j]; + + if (opening.length === closing.length) { + // Found a matching pair + ranges.push({ + start: opening.position, + end: closing.position + closing.length, + }); + used.add(i); + used.add(j); + break; + } + } + } + + // Handle unclosed code blocks (sequences without a matching pair) + for (let i = 0; i < backtickSequences.length; i++) { + if (!used.has(i)) { ranges.push({ - start, + start: backtickSequences[i].position, end: content.length, }); } } - // Inline code (backticks) + // Inline code (single backticks) const inlineCodePattern = /`[^`\n]+?`/g; + let match: RegExpExecArray | null; + while ((match = inlineCodePattern.exec(content)) !== null) { ranges.push({ start: match.index, From 2b27df9e9524f4e3aebc5b2e5507d34f5e93bb3d Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Thu, 1 Jan 2026 14:13:29 +0900 Subject: [PATCH 5/5] docs: document code block detection algorithm in validation logic --- docs/architecture/overview.md | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index b2bcab9..9ddda62 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -13,3 +13,39 @@ The project is organized into three main directories under `src/`: - `cli/`: Command-line interface implementations (validate.ts, fix.ts) - `core/`: Core validation and fixing logic - `utils/`: Shared utility functions + +## Core Validation Logic + +### CODE_REF Extraction (src/core/validate.ts) + +The validation system extracts CODE_REF comments from markdown files while intelligently excluding references that appear inside code blocks or inline code. This ensures that documentation examples showing CODE_REF syntax are not mistakenly validated. + +#### Code Block Detection Algorithm + +The code block detection uses a sophisticated pairing algorithm to handle various markdown code block formats: + +1. **Backtick Sequence Detection**: Scans the entire document to find all sequences of 3 or more consecutive backticks +2. **Length-Based Pairing**: Matches opening and closing backtick sequences with identical lengths + - ` ``` ` pairs with ` ``` ` (3 backticks) + - ` ```` ` pairs with ` ```` ` (4 backticks) + - ` ````` ` pairs with ` ````` ` (5 backticks) +3. **Unclosed Block Handling**: Treats any unpaired backtick sequence as an unclosed code block extending to the end of the file +4. **Inline Code Detection**: Separately detects single-backtick inline code using pattern matching + +This algorithm correctly handles: + +- Nested code blocks with different backtick lengths +- Markdown examples that show code block syntax +- Unclosed code blocks (common in draft documentation) +- Mixed inline code and code blocks + +#### Validation Process + + + +The `extractCodeRefs` function: + +1. Pre-computes all code block and inline code ranges in the document +2. Finds all CODE_REF comment patterns +3. Filters out CODE_REF comments that fall within code block ranges +4. Returns only CODE_REF comments that should be validated