Skip to content

Commit dd71619

Browse files
TyceHerrmanclaude
andcommitted
Fix AGENTS.md patch replacing CLAUDE.md content instead of falling back
The injection was at the wrong location — before `return { content }`, which runs after CLAUDE.md is successfully read, unconditionally replacing its content with AGENTS.md. Moved injection to the early `return null` (after existsSync/statSync check) so alternatives are only tried when CLAUDE.md doesn't exist. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent fb11c51 commit dd71619

2 files changed

Lines changed: 96 additions & 24 deletions

File tree

src/patches/agentsMd.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { writeAgentsMd } from './agentsMd';
3+
4+
const mockFunction =
5+
'function _t7(A,q){try{let K=x1();' +
6+
'if(!K.existsSync(A)||!K.statSync(A).isFile())return null;' +
7+
'let Y=UL9(A).toLowerCase();' +
8+
'if(Y&&!dL9.has(Y))' +
9+
'return(I(`Skipping non-text file in @include: ${A}`),null);' +
10+
'let z=K.readFileSync(A,{encoding:"utf-8"}),' +
11+
'{content:w,paths:H}=cL9(z);' +
12+
'return{path:A,type:q,content:w,globs:H};' +
13+
'}catch(K){' +
14+
'if(K instanceof Error&&K.message.includes("EACCES"))' +
15+
'n("tengu_claude_md_permission_error",{is_access_error:1});' +
16+
'}return null;}';
17+
18+
const altNames = ['AGENTS.md', 'GEMINI.md', 'QWEN.md'];
19+
20+
describe('agentsMd', () => {
21+
describe('writeAgentsMd', () => {
22+
it('should inject fallback at early return null when CLAUDE.md is missing', () => {
23+
const result = writeAgentsMd(mockFunction, altNames);
24+
expect(result).not.toBeNull();
25+
expect(result).toContain('didReroute');
26+
expect(result).toContain('endsWith("/CLAUDE.md")');
27+
expect(result).toContain('AGENTS.md');
28+
expect(result).toMatch(/\.isFile\(\)\)\{.*?return null;\}/);
29+
});
30+
31+
it('should preserve CLAUDE.md content when present', () => {
32+
const result = writeAgentsMd(mockFunction, altNames)!;
33+
const returnIdx = result.indexOf('return{path:');
34+
expect(returnIdx).toBeGreaterThan(-1);
35+
const beforeReturn = result.slice(Math.max(0, returnIdx - 50), returnIdx);
36+
expect(beforeReturn).not.toContain('didReroute');
37+
});
38+
39+
it('should pass didReroute=true in recursive calls', () => {
40+
const result = writeAgentsMd(mockFunction, altNames)!;
41+
expect(result).toContain('return _t7(altPath,q,true)');
42+
});
43+
44+
it('should return null when no alternatives are found', () => {
45+
const result = writeAgentsMd(mockFunction, altNames)!;
46+
expect(result).toMatch(/\}return null;\}/);
47+
});
48+
49+
it('should add didReroute parameter to function signature', () => {
50+
const result = writeAgentsMd(mockFunction, altNames)!;
51+
expect(result).toContain('function _t7(A,q,didReroute)');
52+
});
53+
54+
it('should use the correct fs expression', () => {
55+
const result = writeAgentsMd(mockFunction, altNames)!;
56+
expect(result).toContain('K.existsSync(altPath)');
57+
expect(result).toContain('K.statSync(altPath)');
58+
});
59+
60+
it('should return null when function pattern is not found', () => {
61+
const result = writeAgentsMd('not a valid file', altNames);
62+
expect(result).toBeNull();
63+
});
64+
});
65+
});

src/patches/agentsMd.ts

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import { showDiff } from './index';
44

55
/**
66
* Patches the CLAUDE.md file reading function to also check for alternative
7-
* filenames (e.g., AGENTS.md).
7+
* filenames (e.g., AGENTS.md) when CLAUDE.md doesn't exist.
88
*
99
* This finds the function that reads CLAUDE.md files and modifies it to:
1010
* 1. Add a `didReroute` parameter to the function
11-
* 2. Right before the `return {` statement, check if the path ends with
12-
* CLAUDE.md and try alternative names (unless didReroute is true)
11+
* 2. At the early `return null` (when the file doesn't exist), check if the
12+
* path ends with CLAUDE.md and try alternative names (unless didReroute
13+
* is true)
1314
* 3. Recursive calls pass didReroute=true to avoid infinite loops
1415
*
1516
* CC 2.1.62 (approx. by Claude):
@@ -18,19 +19,22 @@ import { showDiff } from './index';
1819
* +function _t7(A, q, didReroute) {
1920
* try {
2021
* let K = x1();
21-
* if (!K.existsSync(A) || !K.statSync(A).isFile()) return null;
22+
* - if (!K.existsSync(A) || !K.statSync(A).isFile()) return null;
23+
* + if (!K.existsSync(A) || !K.statSync(A).isFile()) {
24+
* + if (!didReroute && (A.endsWith("/CLAUDE.md") || A.endsWith("\\CLAUDE.md"))) {
25+
* + for (let alt of ["AGENTS.md", "GEMINI.md", "QWEN.md"]) {
26+
* + let altPath = A.slice(0, -9) + alt;
27+
* + if (K.existsSync(altPath) && K.statSync(altPath).isFile())
28+
* + return _t7(altPath, q, true);
29+
* + }
30+
* + }
31+
* + return null;
32+
* + }
2233
* let Y = UL9(A).toLowerCase();
2334
* if (Y && !dL9.has(Y))
2435
* return (I(`Skipping non-text file in @include: ${A}`), null);
2536
* let z = K.readFileSync(A, { encoding: "utf-8" }),
2637
* { content: w, paths: H } = cL9(z);
27-
* + if (!didReroute && (A.endsWith("/CLAUDE.md") || A.endsWith("\\CLAUDE.md"))) {
28-
* + for (let alt of ["AGENTS.md", "GEMINI.md", "QWEN.md"]) {
29-
* + let altPath = A.slice(0, -9) + alt;
30-
* + if (K.existsSync(altPath) && K.statSync(altPath).isFile())
31-
* + return _t7(altPath, q, true);
32-
* + }
33-
* + }
3438
* return { path: A, type: q, content: w, globs: H };
3539
* } catch (K) {
3640
* if (K instanceof Error && K.message.includes("EACCES"))
@@ -48,7 +52,7 @@ export const writeAgentsMd = (
4852
altNames: string[]
4953
): string | null => {
5054
const funcPattern =
51-
/(function ([$\w]+)\(([$\w]+),([^)]+?))\)(?:.|\n){0,500}Skipping non-text file in @include(?:.|\n){0,500}return\{path:[$\w]+,.{0,20}?content:[$\w]+/;
55+
/(function ([$\w]+)\(([$\w]+),([^)]+?))\)(?:.|\n){0,500}Skipping non-text file in @include/;
5256

5357
const funcMatch = file.match(funcPattern);
5458
if (!funcMatch || funcMatch.index === undefined) {
@@ -71,33 +75,36 @@ export const writeAgentsMd = (
7175

7276
const altNamesJson = JSON.stringify(altNames);
7377

78+
// Step 1: Add didReroute parameter to function signature
7479
const sigIndex = funcStart + upToFuncParamsClosingParen.length;
7580
let newFile = file.slice(0, sigIndex) + ',didReroute' + file.slice(sigIndex);
7681

7782
showDiff(file, newFile, ',didReroute', sigIndex, sigIndex);
7883

79-
// Step 2: Inject rerouting code right before the `return {`
80-
const returnPattern = /return\{path:[$\w]+,.{0,20}?content:[$\w]+/;
81-
const returnMatch = newFile.slice(funcStart).match(returnPattern);
84+
// Step 2: Inject fallback at the early return null (when file doesn't exist)
85+
const earlyReturnPattern = /\.isFile\(\)\)return null/;
86+
const funcBody = newFile.slice(funcStart);
87+
const earlyReturnMatch = funcBody.match(earlyReturnPattern);
8288

83-
if (!returnMatch || returnMatch.index === undefined) {
89+
if (!earlyReturnMatch || earlyReturnMatch.index === undefined) {
8490
console.error(
85-
'patch: agentsMd: failed to find return statement for injection'
91+
'patch: agentsMd: failed to find early return null for injection'
8692
);
8793
return null;
8894
}
8995

90-
const injection = `if(!didReroute&&(${firstParam}.endsWith("/CLAUDE.md")||${firstParam}.endsWith("\\\\CLAUDE.md"))){for(let alt of ${altNamesJson}){let altPath=${firstParam}.slice(0,-9)+alt;if(${fsExpr}.existsSync(altPath)&&${fsExpr}.statSync(altPath).isFile())return ${functionName}(altPath,${restParams},true);}}`;
96+
const fallback = `if(!didReroute&&(${firstParam}.endsWith("/CLAUDE.md")||${firstParam}.endsWith("\\\\CLAUDE.md"))){for(let alt of ${altNamesJson}){let altPath=${firstParam}.slice(0,-9)+alt;if(${fsExpr}.existsSync(altPath)&&${fsExpr}.statSync(altPath).isFile())return ${functionName}(altPath,${restParams},true);}}`;
9197

92-
const returnStart = funcStart + returnMatch.index;
93-
const replacement = injection + returnMatch[0];
98+
const earlyReturnStart = funcStart + earlyReturnMatch.index;
99+
const oldStr = earlyReturnMatch[0];
100+
const newStr = `.isFile()){${fallback}return null;}`;
94101

95102
newFile =
96-
newFile.slice(0, returnStart) +
97-
replacement +
98-
newFile.slice(returnStart + returnMatch[0].length);
103+
newFile.slice(0, earlyReturnStart) +
104+
newStr +
105+
newFile.slice(earlyReturnStart + oldStr.length);
99106

100-
showDiff(file, newFile, replacement, returnStart, returnStart);
107+
showDiff(file, newFile, newStr, earlyReturnStart, earlyReturnStart);
101108

102109
return newFile;
103110
};

0 commit comments

Comments
 (0)