@@ -1587,4 +1587,186 @@ describe("Rules directory reading", () => {
15871587 const result = await loadRuleFiles ( "/fake/path" )
15881588 expect ( result ) . toBe ( "\n# Rules from .roorules:\nfallback content\n" )
15891589 } )
1590+
1591+ it ( "should load AGENTS.local.md alongside AGENTS.md for personal overrides" , async ( ) => {
1592+ // Simulate no .roo/rules-test-mode directory
1593+ statMock . mockRejectedValueOnce ( { code : "ENOENT" } )
1594+
1595+ // Mock lstat to indicate both AGENTS.md and AGENTS.local.md exist (not symlinks)
1596+ lstatMock . mockImplementation ( ( filePath : PathLike ) => {
1597+ const pathStr = filePath . toString ( )
1598+ if ( pathStr . endsWith ( "AGENTS.md" ) || pathStr . endsWith ( "AGENTS.local.md" ) ) {
1599+ return Promise . resolve ( {
1600+ isSymbolicLink : vi . fn ( ) . mockReturnValue ( false ) ,
1601+ } )
1602+ }
1603+ return Promise . reject ( { code : "ENOENT" } )
1604+ } )
1605+
1606+ readFileMock . mockImplementation ( ( filePath : PathLike ) => {
1607+ const pathStr = filePath . toString ( )
1608+ if ( pathStr . endsWith ( "AGENTS.local.md" ) ) {
1609+ return Promise . resolve ( "Local overrides from AGENTS.local.md" )
1610+ }
1611+ if ( pathStr . endsWith ( "AGENTS.md" ) ) {
1612+ return Promise . resolve ( "Base rules from AGENTS.md" )
1613+ }
1614+ return Promise . reject ( { code : "ENOENT" } )
1615+ } )
1616+
1617+ const result = await addCustomInstructions (
1618+ "mode instructions" ,
1619+ "global instructions" ,
1620+ "/fake/path" ,
1621+ "test-mode" ,
1622+ {
1623+ settings : {
1624+ todoListEnabled : true ,
1625+ useAgentRules : true ,
1626+ newTaskRequireTodos : false ,
1627+ } ,
1628+ } ,
1629+ )
1630+
1631+ // Should contain both AGENTS.md and AGENTS.local.md content
1632+ expect ( result ) . toContain ( "# Agent Rules Standard (AGENTS.md):" )
1633+ expect ( result ) . toContain ( "Base rules from AGENTS.md" )
1634+ expect ( result ) . toContain ( "# Agent Rules Local (AGENTS.local.md):" )
1635+ expect ( result ) . toContain ( "Local overrides from AGENTS.local.md" )
1636+ } )
1637+
1638+ it ( "should load AGENT.local.md alongside AGENT.md when AGENTS.md is not found" , async ( ) => {
1639+ // Simulate no .roo/rules-test-mode directory
1640+ statMock . mockRejectedValueOnce ( { code : "ENOENT" } )
1641+
1642+ // Mock lstat to indicate AGENTS.md doesn't exist but AGENT.md and AGENT.local.md do
1643+ lstatMock . mockImplementation ( ( filePath : PathLike ) => {
1644+ const pathStr = filePath . toString ( )
1645+ if ( pathStr . endsWith ( "AGENTS.md" ) || pathStr . endsWith ( "AGENTS.local.md" ) ) {
1646+ return Promise . reject ( { code : "ENOENT" } )
1647+ }
1648+ if ( pathStr . endsWith ( "AGENT.md" ) || pathStr . endsWith ( "AGENT.local.md" ) ) {
1649+ return Promise . resolve ( {
1650+ isSymbolicLink : vi . fn ( ) . mockReturnValue ( false ) ,
1651+ } )
1652+ }
1653+ return Promise . reject ( { code : "ENOENT" } )
1654+ } )
1655+
1656+ readFileMock . mockImplementation ( ( filePath : PathLike ) => {
1657+ const pathStr = filePath . toString ( )
1658+ if ( pathStr . endsWith ( "AGENT.local.md" ) ) {
1659+ return Promise . resolve ( "Local overrides from AGENT.local.md" )
1660+ }
1661+ if ( pathStr . endsWith ( "AGENT.md" ) ) {
1662+ return Promise . resolve ( "Base rules from AGENT.md" )
1663+ }
1664+ return Promise . reject ( { code : "ENOENT" } )
1665+ } )
1666+
1667+ const result = await addCustomInstructions (
1668+ "mode instructions" ,
1669+ "global instructions" ,
1670+ "/fake/path" ,
1671+ "test-mode" ,
1672+ {
1673+ settings : {
1674+ todoListEnabled : true ,
1675+ useAgentRules : true ,
1676+ newTaskRequireTodos : false ,
1677+ } ,
1678+ } ,
1679+ )
1680+
1681+ // Should contain both AGENT.md and AGENT.local.md content
1682+ expect ( result ) . toContain ( "# Agent Rules Standard (AGENT.md):" )
1683+ expect ( result ) . toContain ( "Base rules from AGENT.md" )
1684+ expect ( result ) . toContain ( "# Agent Rules Local (AGENT.local.md):" )
1685+ expect ( result ) . toContain ( "Local overrides from AGENT.local.md" )
1686+ } )
1687+
1688+ it ( "should not load AGENTS.local.md when base AGENTS.md does not exist" , async ( ) => {
1689+ // Simulate no .roo/rules-test-mode directory
1690+ statMock . mockRejectedValueOnce ( { code : "ENOENT" } )
1691+
1692+ // Mock lstat to indicate only AGENTS.local.md exists (no base file)
1693+ lstatMock . mockImplementation ( ( filePath : PathLike ) => {
1694+ const pathStr = filePath . toString ( )
1695+ if ( pathStr . endsWith ( "AGENTS.local.md" ) ) {
1696+ return Promise . resolve ( {
1697+ isSymbolicLink : vi . fn ( ) . mockReturnValue ( false ) ,
1698+ } )
1699+ }
1700+ return Promise . reject ( { code : "ENOENT" } )
1701+ } )
1702+
1703+ readFileMock . mockImplementation ( ( filePath : PathLike ) => {
1704+ const pathStr = filePath . toString ( )
1705+ if ( pathStr . endsWith ( "AGENTS.local.md" ) ) {
1706+ return Promise . resolve ( "Local overrides without base file" )
1707+ }
1708+ return Promise . reject ( { code : "ENOENT" } )
1709+ } )
1710+
1711+ const result = await addCustomInstructions (
1712+ "mode instructions" ,
1713+ "global instructions" ,
1714+ "/fake/path" ,
1715+ "test-mode" ,
1716+ {
1717+ settings : {
1718+ todoListEnabled : true ,
1719+ useAgentRules : true ,
1720+ newTaskRequireTodos : false ,
1721+ } ,
1722+ } ,
1723+ )
1724+
1725+ // Should NOT contain AGENTS.local.md content since there's no base AGENTS.md
1726+ expect ( result ) . not . toContain ( "AGENTS.local.md" )
1727+ expect ( result ) . not . toContain ( "Local overrides without base file" )
1728+ } )
1729+
1730+ it ( "should load AGENTS.md without .local.md when local file does not exist" , async ( ) => {
1731+ // Simulate no .roo/rules-test-mode directory
1732+ statMock . mockRejectedValueOnce ( { code : "ENOENT" } )
1733+
1734+ // Mock lstat to indicate only AGENTS.md exists (no local override)
1735+ lstatMock . mockImplementation ( ( filePath : PathLike ) => {
1736+ const pathStr = filePath . toString ( )
1737+ if ( pathStr . endsWith ( "AGENTS.md" ) ) {
1738+ return Promise . resolve ( {
1739+ isSymbolicLink : vi . fn ( ) . mockReturnValue ( false ) ,
1740+ } )
1741+ }
1742+ return Promise . reject ( { code : "ENOENT" } )
1743+ } )
1744+
1745+ readFileMock . mockImplementation ( ( filePath : PathLike ) => {
1746+ const pathStr = filePath . toString ( )
1747+ if ( pathStr . endsWith ( "AGENTS.md" ) ) {
1748+ return Promise . resolve ( "Base rules from AGENTS.md only" )
1749+ }
1750+ return Promise . reject ( { code : "ENOENT" } )
1751+ } )
1752+
1753+ const result = await addCustomInstructions (
1754+ "mode instructions" ,
1755+ "global instructions" ,
1756+ "/fake/path" ,
1757+ "test-mode" ,
1758+ {
1759+ settings : {
1760+ todoListEnabled : true ,
1761+ useAgentRules : true ,
1762+ newTaskRequireTodos : false ,
1763+ } ,
1764+ } ,
1765+ )
1766+
1767+ // Should contain only AGENTS.md content
1768+ expect ( result ) . toContain ( "# Agent Rules Standard (AGENTS.md):" )
1769+ expect ( result ) . toContain ( "Base rules from AGENTS.md only" )
1770+ expect ( result ) . not . toContain ( "AGENTS.local.md" )
1771+ } )
15901772} )
0 commit comments