@j0hanz/filesystem-mcp 2.5.2 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -19
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +12 -3
- package/dist/cli.js.map +1 -1
- package/dist/core/concurrency.d.ts +7 -4
- package/dist/core/concurrency.d.ts.map +1 -1
- package/dist/core/concurrency.js +9 -4
- package/dist/core/concurrency.js.map +1 -1
- package/dist/core/config.d.ts +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/cursor.d.ts.map +1 -1
- package/dist/core/cursor.js +11 -1
- package/dist/core/cursor.js.map +1 -1
- package/dist/core/diff.d.ts +15 -2
- package/dist/core/diff.d.ts.map +1 -1
- package/dist/core/diff.js +38 -7
- package/dist/core/diff.js.map +1 -1
- package/dist/core/errors.d.ts +4 -5
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +14 -14
- package/dist/core/errors.js.map +1 -1
- package/dist/core/file-uri.d.ts +23 -9
- package/dist/core/file-uri.d.ts.map +1 -1
- package/dist/core/file-uri.js +29 -14
- package/dist/core/file-uri.js.map +1 -1
- package/dist/core/fmt.d.ts +3 -1
- package/dist/core/fmt.d.ts.map +1 -1
- package/dist/core/fmt.js +3 -3
- package/dist/core/fmt.js.map +1 -1
- package/dist/core/fs.d.ts +2 -8
- package/dist/core/fs.d.ts.map +1 -1
- package/dist/core/fs.js +32 -20
- package/dist/core/fs.js.map +1 -1
- package/dist/core/glob.d.ts +1 -1
- package/dist/core/glob.d.ts.map +1 -1
- package/dist/core/glob.js +77 -80
- package/dist/core/glob.js.map +1 -1
- package/dist/core/mime.d.ts +1 -0
- package/dist/core/mime.d.ts.map +1 -1
- package/dist/core/mime.js +1 -1
- package/dist/core/mime.js.map +1 -1
- package/dist/core/observability.js +1 -1
- package/dist/core/observability.js.map +1 -1
- package/dist/core/path-completer.d.ts +2 -1
- package/dist/core/path-completer.d.ts.map +1 -1
- package/dist/core/path-completer.js +14 -13
- package/dist/core/path-completer.js.map +1 -1
- package/dist/core/path-discovery.d.ts.map +1 -1
- package/dist/core/path-discovery.js +29 -21
- package/dist/core/path-discovery.js.map +1 -1
- package/dist/core/path-utils.d.ts +8 -0
- package/dist/core/path-utils.d.ts.map +1 -1
- package/dist/core/path-utils.js +24 -17
- package/dist/core/path-utils.js.map +1 -1
- package/dist/core/path.d.ts +13 -1
- package/dist/core/path.d.ts.map +1 -1
- package/dist/core/path.js +27 -11
- package/dist/core/path.js.map +1 -1
- package/dist/core/read.d.ts +1 -0
- package/dist/core/read.d.ts.map +1 -1
- package/dist/core/read.js +23 -12
- package/dist/core/read.js.map +1 -1
- package/dist/core/schema.d.ts +10 -0
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +20 -6
- package/dist/core/schema.js.map +1 -1
- package/dist/core/search.d.ts +12 -4
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +42 -26
- package/dist/core/search.js.map +1 -1
- package/dist/core/sensitive.d.ts +7 -0
- package/dist/core/sensitive.d.ts.map +1 -1
- package/dist/core/sensitive.js +21 -2
- package/dist/core/sensitive.js.map +1 -1
- package/dist/core/watcher-registry.d.ts.map +1 -1
- package/dist/core/watcher-registry.js +24 -9
- package/dist/core/watcher-registry.js.map +1 -1
- package/dist/instructions.js +4 -4
- package/dist/instructions.js.map +1 -1
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +21 -3
- package/dist/resources.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +16 -3
- package/dist/server.js.map +1 -1
- package/dist/tools/batch.d.ts +2 -5
- package/dist/tools/batch.d.ts.map +1 -1
- package/dist/tools/batch.js +1 -2
- package/dist/tools/batch.js.map +1 -1
- package/dist/tools/create.d.ts.map +1 -1
- package/dist/tools/create.js +32 -19
- package/dist/tools/create.js.map +1 -1
- package/dist/tools/define.d.ts +1 -1
- package/dist/tools/define.d.ts.map +1 -1
- package/dist/tools/define.js.map +1 -1
- package/dist/tools/delete.d.ts.map +1 -1
- package/dist/tools/delete.js +2 -7
- package/dist/tools/delete.js.map +1 -1
- package/dist/tools/diff.d.ts.map +1 -1
- package/dist/tools/diff.js +8 -5
- package/dist/tools/diff.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +89 -46
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/find-files.d.ts.map +1 -1
- package/dist/tools/find-files.js +1 -2
- package/dist/tools/find-files.js.map +1 -1
- package/dist/tools/list-roots.d.ts.map +1 -1
- package/dist/tools/list-roots.js +1 -3
- package/dist/tools/list-roots.js.map +1 -1
- package/dist/tools/list.js +4 -4
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/move.d.ts +2 -0
- package/dist/tools/move.d.ts.map +1 -1
- package/dist/tools/move.js +10 -4
- package/dist/tools/move.js.map +1 -1
- package/dist/tools/patch.d.ts.map +1 -1
- package/dist/tools/patch.js +11 -3
- package/dist/tools/patch.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +7 -29
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/replace-text.d.ts.map +1 -1
- package/dist/tools/replace-text.js +20 -17
- package/dist/tools/replace-text.js.map +1 -1
- package/dist/tools/search-text.d.ts.map +1 -1
- package/dist/tools/search-text.js +3 -1
- package/dist/tools/search-text.js.map +1 -1
- package/dist/tools/stat.d.ts.map +1 -1
- package/dist/tools/stat.js +4 -9
- package/dist/tools/stat.js.map +1 -1
- package/dist/transport/http-policy.d.ts +3 -3
- package/dist/transport/http-policy.d.ts.map +1 -1
- package/dist/transport/http-policy.js +10 -9
- package/dist/transport/http-policy.js.map +1 -1
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +38 -14
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/shared.d.ts +2 -2
- package/dist/transport/shared.d.ts.map +1 -1
- package/dist/transport/shared.js.map +1 -1
- package/dist/transport/stdio.d.ts +1 -1
- package/dist/transport/stdio.js +1 -1
- package/dist/transport/stdio.js.map +1 -1
- package/package.json +1 -1
package/dist/core/search.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/core/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,MAAM,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGrC,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAExC,OAAO,EAAE,WAAW,EAAkB,MAAM,WAAW,CAAC;AAExD,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAgB/C,+EAA+E;AAC/E,MAAM,oBAAoB,GAAG,OAAO,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,UAAuC,EAAE;IACrF,MAAM,KAAK,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;IACnD,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,CAAC,KAAK,YAAY,WAAW,CAAC;YAAE,MAAM,KAAK,CAAC;QACjD,MAAM,IAAI,WAAW,CACnB,GAAG,KAAK,CAAC,OAAO,iEAAiE,EACjF,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,KAAwB;IAChD,MAAM,MAAM,GAAI,KAAsE,EAAE,OAAO,CAAC;IAChG,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,+DAA+D;IACjE,CAAC;AACH,CAAC;AAeD,qFAAqF;AACrF,SAAS,eAAe,CAAC,IAAY;IACnC,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,MAAM,EAAE,CAAC;YAC7F,CAAC,EAAE,CAAC;QACN,CAAC;QACD,MAAM,EAAE,CAAC;IACX,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,SAAS,CAAC,CAAC,WAAW,CAAC,KAAY,EAAE,IAAY;IACrD,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;IACpB,0EAA0E;IAC1E,0EAA0E;IAC1E,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,KAA0B,CAAC;IAC/B,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC3C,OAAO,QAAQ,GAAG,KAAK,CAAC,KAAK,IAAI,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YACzD,MAAM,MAAM,GACV,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,KAAK,MAAM;gBAChD,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,MAAM,CAAC;YACvD,SAAS,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5B,QAAQ,EAAE,CAAC;QACb,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,SAAS,CAAC;QACxB,MAAM;YACJ,IAAI,EAAE,OAAO;YACb,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;YACtB,KAAK,EAAE,KAAK,CAAC,MAAM;YACnB,KAAK;YACL,GAAG,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM;SAC5B,CAAC;QACF,4EAA4E;QAC5E,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,KAAK,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;IACxF,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,eAAe,CACtB,KAAY,EACZ,IAAY;IAEZ,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC;IAChB,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC;QAC7C,IAAI,MAAM,KAAK,CAAC,CAAC;YAAE,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC;QACxC,KAAK,EAAE,CAAC;QACR,IAAI,KAAK,IAAI,oBAAoB;YAAE,MAAM;IAC3C,CAAC;IACD,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AACnD,CAAC;AAyCD,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,SAAiB,EACjB,OAAe,EACf,OAA6B,EAC7B,SAAoB;IAEpB,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE;QACzF,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;KAC9C,CAAC,CAAC;IACH,IAAI,CAAC;QACH,MAAM,OAAO,GAAmB,EAAE,CAAC;QACnC,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,GAAG,CAAC;QAC7C,MAAM,WAAW,GAAG,kBAAkB,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC;QAErC,MAAM,OAAO,GAAG,WAAW,CAAC;YAC1B,GAAG,EAAE,SAAS;YACd,OAAO,EAAE,OAAO,CAAC,WAAW,IAAI,MAAM;YACtC,yEAAyE;YACzE,uEAAuE;YACvE,aAAa,EAAE,IAAI;YACnB,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;YAC7C,WAAW,EAAE,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC;YACzC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrD,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,GAAG;YACjC,cAAc,EAAE,IAAI;SACrB,CAAC,CAAC;QAEH,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,IAAI,aAAa,GAAG,CAAC,CAAC;QACtB,IAAI,eAAe,GAAG,CAAC,CAAC;QACxB,MAAM,QAAQ,GAAG,EAAE,mBAAmB,EAAE,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;QAEnE,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;YACvF,IAAI,OAAO,CAAC,MAAM,IAAI,UAAU;gBAAE,MAAM;YAExC,2EAA2E;YAC3E,0EAA0E;YAC1E,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACvC,IAAI,KAAK,CAAC,IAAI,GAAG,WAAW,EAAE,CAAC;oBAC7B,eAAe,EAAE,CAAC;oBAClB,SAAS;gBACX,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,QAAQ,CAAC,mBAAmB,EAAE,CAAC;gBAC/B,SAAS;YACX,CAAC;YAED,YAAY,EAAE,CAAC;YAEf,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;gBAC1F,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAClC,uEAAuE;gBACvE,6CAA6C;gBAC7C,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;gBAC3E,IAAI,WAAW,GAAG,KAAK,CAAC;gBACxB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;oBACtB,IAAI,IAAI,KAAK,SAAS;wBAAE,SAAS;oBACjC,oEAAoE;oBACpE,8CAA8C;oBAC9C,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;oBAC3C,IAAI,KAAK,EAAE,CAAC;wBACV,WAAW,GAAG,IAAI,CAAC;wBACnB,aAAa,EAAE,CAAC;wBAChB,OAAO,CAAC,IAAI,CAAC;4BACX,IAAI,EAAE,KAAK,CAAC,IAAI;4BAChB,IAAI,EAAE,CAAC,GAAG,CAAC;4BACX,MAAM,EAAE,KAAK,CAAC,MAAM;4BACpB,OAAO,EAAE,IAAI;4BACb,UAAU,EAAE,KAAK,CAAC,KAAK;4BACvB,GAAG,CAAC,OAAO,GAAG,CAAC;gCACb,CAAC,CAAC;oCACE,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC;oCAChD,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC;iCAChE;gCACH,CAAC,CAAC,EAAE,CAAC;yBACR,CAAC,CAAC;wBACH,IAAI,OAAO,CAAC,MAAM,IAAI,UAAU;4BAAE,MAAM;oBAC1C,CAAC;gBACH,CAAC;gBACD,IAAI,WAAW;oBAAE,YAAY,EAAE,CAAC;YAClC,CAAC;YAAC,MAAM,CAAC;gBACP,kEAAkE;gBAClE,sEAAsE;gBACtE,uCAAuC;gBACvC,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;oBAC5B,QAAQ,CAAC,cAAc,GAAG,IAAI,CAAC;oBAC/B,MAAM;gBACR,CAAC;gBACD,yCAAyC;YAC3C,CAAC;QACH,CAAC;QAED,6EAA6E;QAC7E,4EAA4E;QAC5E,2DAA2D;QAC3D,MAAM,aAAa,GACjB,OAAO,CAAC,MAAM,IAAI,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;QAEhG,OAAO;YACL,QAAQ,EAAE,SAAS;YACnB,OAAO;YACP,OAAO,EAAE;gBACP,aAAa;gBACb,YAAY;gBACZ,YAAY;gBACZ,SAAS,EAAE,aAAa,KAAK,SAAS;gBACtC,mBAAmB,EAAE,QAAQ,CAAC,mBAAmB;gBACjD,eAAe;gBACf,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC5C;SACF,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,KAAK,CAAC,CAAC;IACnB,CAAC;AACH,CAAC;AAED,KAAK,SAAS,CAAC,CAAC,cAAc,CAC5B,OAAiC,EACjC,SAAoB,EACpB,MAA+B,EAC/B,QAAkE;IAElE,6EAA6E;IAC7E,yEAAyE;IACzE,4DAA4D;IAC5D,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAClC,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YACpB,QAAQ,CAAC,cAAc,GAAG,IAAI,CAAC;YAC/B,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,oBAAoB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ,CAAC,mBAAmB,EAAE,CAAC;YAC/B,SAAS;QACX,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,SAAiB,EACjB,OAAe,EACf,OAOC,EACD,SAAoB;IAapB,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,GAAG,CAAC;IAC7C,MAAM,OAAO,GAAG,WAAW,CAAC;QAC1B,GAAG,EAAE,SAAS;QACd,OAAO;QACP,aAAa,EAAE,IAAI;QACnB,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;QAC7C,WAAW,EAAE,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC;QACzC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrD,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,GAAG;QACjC,cAAc,EAAE,IAAI;KACrB,CAAC,CAAC;IACH,MAAM,OAAO,GAAuB,EAAE,CAAC;IACvC,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,MAAM,QAAQ,GAAG,EAAE,mBAAmB,EAAE,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;IAEnE,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;QACvF,IAAI,OAAO,CAAC,MAAM,IAAI,UAAU;YAAE,MAAM;QACxC,YAAY,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IACrC,CAAC;IAED,yEAAyE;IACzE,mEAAmE;IACnE,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3E,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACvD,CAAC;IAED,6EAA6E;IAC7E,MAAM,aAAa,GACjB,OAAO,CAAC,MAAM,IAAI,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;IAEhG,OAAO;QACL,QAAQ,EAAE,SAAS;QACnB,OAAO;QACP,OAAO,EAAE;YACP,OAAO,EAAE,OAAO,CAAC,MAAM;YACvB,YAAY;YACZ,SAAS,EAAE,aAAa,KAAK,SAAS;YACtC,mBAAmB,EAAE,QAAQ,CAAC,mBAAmB;YACjD,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5C;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { stat as fsStat, readFile } from 'node:fs/promises';\nimport { basename } from 'node:path';\n\nimport type { RE2ExecArray } from '@adguard/re2-wasm';\nimport { RE2 } from '@adguard/re2-wasm';\n\nimport { globEntries, type GlobEntry } from './glob.ts';\nimport type { PathGuard } from './path.ts';\nimport { getMaxTextFileSize } from './util.ts';\n\ninterface SearchResult {\n file: string;\n line: number;\n /** 0-indexed column of the first occurrence on the line. */\n column: number;\n content: string;\n matchCount?: number;\n /** Up to `context` lines either side of the match; absent when context is 0. */\n before?: string[];\n after?: string[];\n}\n\nexport type Regex = RE2;\n\n/** Occurrence-counting bound, so one pathological line cannot spin forever. */\nconst MAX_MATCHES_PER_LINE = 100_000;\n\n/**\n * Compile a pattern on RE2 rather than on V8's irregexp.\n *\n * Patterns arrive from the MCP client, so a backtracking engine would let one\n * request pin the event loop with no way out: an abort signal cannot preempt a\n * synchronous `exec`, and on stdio that wedges the whole server. RE2 matches in\n * time linear in the input and cannot backtrack at all, so the hazard is gone\n * rather than bounded.\n *\n * RE2 rejects the constructs the tool schema documents as unsupported —\n * lookahead, lookbehind, backreferences — with its own {@link SyntaxError},\n * which the tool layer turns into a normal tool error. It always matches in\n * Unicode mode and requires the `u` flag to say so.\n *\n * Every compiled pattern owns memory in re2-wasm's fixed 16 MB heap, which\n * `ALLOW_MEMORY_GROWTH` is off for. re2-wasm never frees it and a\n * FinalizationRegistry does not keep up (V8 sees no pressure from the wasm\n * heap), so exhaustion is an emscripten `abort()` that kills regex search for\n * the rest of the process. Every caller MUST pass the result to\n * {@link freeRegex} when it is done with it.\n */\nexport function compileRegex(pattern: string, options: { caseSensitive?: boolean } = {}): Regex {\n const flags = options.caseSensitive ? 'gu' : 'giu';\n try {\n return new RE2(pattern, flags);\n } catch (error) {\n if (!(error instanceof SyntaxError)) throw error;\n throw new SyntaxError(\n `${error.message} — lookahead, lookbehind, and backreferences are not supported.`,\n { cause: error },\n );\n }\n}\n\n/**\n * Release a compiled pattern's wasm memory. re2-wasm exposes no disposal of its\n * own, so this reaches the embind handle it holds privately; a version that\n * renames the field degrades to the pre-existing leak rather than throwing.\n * Idempotent — a second call on an already-freed handle is swallowed. Using a\n * {@link Regex} after freeing it is undefined behaviour in the wasm heap, so\n * free only in a `finally` that owns the compile.\n */\nexport function freeRegex(regex: Regex | undefined): void {\n const handle = (regex as unknown as { wrapper?: { delete?: () => void } } | undefined)?.wrapper;\n try {\n handle?.delete?.();\n } catch {\n // already deleted, or a re2-wasm build without embind disposal\n }\n}\n\nexport interface RegexMatch {\n /** The matched text. */\n text: string;\n /** Capture groups, 1-indexed in the pattern, 0-indexed here. */\n groups: (string | undefined)[];\n /** Named capture groups, when the pattern declares any. */\n named: Record<string, string> | undefined;\n /** Start offset in UTF-16 code units — usable with `String.prototype.slice`. */\n start: number;\n /** End offset in UTF-16 code units. */\n end: number;\n}\n\n/** Code-point length of `text`, i.e. its UTF-16 length minus its surrogate pairs. */\nfunction codePointLength(text: string): number {\n let length = 0;\n for (let i = 0; i < text.length; i++) {\n if ((text.charCodeAt(i) & 0xfc00) === 0xd800 && (text.charCodeAt(i + 1) & 0xfc00) === 0xdc00) {\n i++;\n }\n length++;\n }\n return length;\n}\n\n/**\n * Iterate a global pattern's non-overlapping matches with offsets JS can use.\n *\n * re2-wasm reports `index` and `lastIndex` in **code points**, while every JS\n * string operation indexes in UTF-16 code units. The two agree only until the\n * first astral character (most emoji), past which every offset is short by one\n * per surrogate pair — slicing on the raw index cuts into the middle of\n * neighbouring text. Its `lastIndex` bookkeeping is worse still: it adds the\n * match's UTF-16 length to a code-point index, so an astral character inside a\n * match can skip the next one. This is the single place that corrects both; no\n * caller should read `match.index` or `regex.lastIndex` directly.\n *\n * Zero-length matches (e.g. `a*`) advance by one code point so they cannot loop\n * forever. The regex is global and shared across calls, so `lastIndex` is reset\n * on entry.\n */\nexport function* execMatches(regex: Regex, text: string): Generator<RegexMatch, undefined> {\n regex.lastIndex = 0;\n // Cursor into `text`, carried forward across matches: RE2 reports them in\n // ascending order, so the walk is O(text) in total rather than per match.\n let cursorCp = 0;\n let cursorU16 = 0;\n let match: RE2ExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n while (cursorCp < match.index && cursorU16 < text.length) {\n const isPair =\n (text.charCodeAt(cursorU16) & 0xfc00) === 0xd800 &&\n (text.charCodeAt(cursorU16 + 1) & 0xfc00) === 0xdc00;\n cursorU16 += isPair ? 2 : 1;\n cursorCp++;\n }\n const matched = match[0] ?? '';\n const start = cursorU16;\n yield {\n text: matched,\n groups: match.slice(1),\n named: match.groups,\n start,\n end: start + matched.length,\n };\n // Own the advance rather than trusting the wrapper's mixed-unit arithmetic.\n regex.lastIndex = match.index + (matched.length === 0 ? 1 : codePointLength(matched));\n }\n}\n\n/**\n * Find non-overlapping occurrences of a global regex in a single line, reporting\n * the first match's column alongside the count.\n */\nfunction findLineMatches(\n regex: Regex,\n line: string,\n): { count: number; column: number } | undefined {\n let count = 0;\n let column = -1;\n for (const match of execMatches(regex, line)) {\n if (column === -1) column = match.start;\n count++;\n if (count >= MAX_MATCHES_PER_LINE) break;\n }\n return count > 0 ? { count, column } : undefined;\n}\n\nexport interface SearchContentOptions {\n caseSensitive?: boolean;\n isRegex?: boolean;\n maxResults?: number;\n filePattern?: string;\n skipIgnored?: boolean;\n includeHidden?: boolean;\n maxDepth?: number;\n /** Lines of context to carry either side of each match; 0 (default) carries none. */\n context?: number;\n signal?: AbortSignal;\n}\n\nexport interface SearchContentOutcome {\n basePath: string;\n matches: SearchResult[];\n summary: {\n /**\n * Matching *lines*, one per entry in `matches` — not pattern occurrences.\n * A line with three occurrences counts once here and reports 3 in its own\n * `SearchResult.matchCount`.\n */\n matchingLines: number;\n filesScanned: number;\n filesMatched: number;\n truncated: boolean;\n /** Files the guard rejected or that could not be stat'd. */\n skippedInaccessible: number;\n /** Files skipped unread because they exceed maxFileSize. */\n skippedTooLarge: number;\n /**\n * `StoppedReason` narrowed to the stops these scans can produce: both call\n * only `hitMaxResults`/`hitAbort` (see concurrency.ts) — never\n * `hitMaxFiles`, which belongs to `replace_text`'s per-file cap.\n */\n stoppedReason?: 'maxResults' | 'timeout';\n };\n}\n\nexport async function searchContent(\n directory: string,\n pattern: string,\n options: SearchContentOptions,\n pathGuard: PathGuard,\n): Promise<SearchContentOutcome> {\n const regex = compileRegex(options.isRegex ? pattern || '' : RegExp.escape(pattern || ''), {\n caseSensitive: Boolean(options.caseSensitive),\n });\n try {\n const matches: SearchResult[] = [];\n const maxResults = options.maxResults ?? 100;\n const maxFileSize = getMaxTextFileSize();\n const context = options.context ?? 0;\n\n const entries = globEntries({\n cwd: directory,\n pattern: options.filePattern ?? '**/*',\n // Same rule as replace_text, so a search with the same glob previews the\n // files a replace would touch: a slash-free glob matches at any depth.\n baseNameMatch: true,\n includeHidden: Boolean(options.includeHidden),\n skipIgnored: Boolean(options.skipIgnored),\n ...(options.signal ? { signal: options.signal } : {}),\n maxDepth: options.maxDepth ?? 100,\n suppressErrors: true,\n });\n\n let filesScanned = 0;\n let filesMatched = 0;\n let matchingLines = 0;\n let skippedTooLarge = 0;\n const counters = { skippedInaccessible: 0, stoppedByAbort: false };\n\n for await (const entry of guardedEntries(entries, pathGuard, options.signal, counters)) {\n if (matches.length >= maxResults) break;\n\n // Skip oversized files before reading to avoid unbounded memory use. Count\n // them: \"no matches\" for a reason other than the pattern must be visible.\n try {\n const stats = await fsStat(entry.path);\n if (stats.size > maxFileSize) {\n skippedTooLarge++;\n continue;\n }\n } catch {\n counters.skippedInaccessible++;\n continue;\n }\n\n filesScanned++;\n\n try {\n const content = await readFile(entry.path, { encoding: 'utf-8', signal: options.signal });\n const lines = content.split('\\n');\n // A trailing newline splits into a phantom empty last element; context\n // must not report it as a line the file has.\n const lineCount = content.endsWith('\\n') ? lines.length - 1 : lines.length;\n let matchedFile = false;\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (line === undefined) continue;\n // One scan per line: findLineMatches resets lastIndex itself, so it\n // doubles as the \"does this line match\" test.\n const found = findLineMatches(regex, line);\n if (found) {\n matchedFile = true;\n matchingLines++;\n matches.push({\n file: entry.path,\n line: i + 1,\n column: found.column,\n content: line,\n matchCount: found.count,\n ...(context > 0\n ? {\n before: lines.slice(Math.max(0, i - context), i),\n after: lines.slice(i + 1, Math.min(lineCount, i + 1 + context)),\n }\n : {}),\n });\n if (matches.length >= maxResults) break;\n }\n }\n if (matchedFile) filesMatched++;\n } catch {\n // A read failure while the signal is aborted IS the abort, not an\n // unreadable file — stop rather than spend another iteration and then\n // report a cut-short scan as complete.\n if (options.signal?.aborted) {\n counters.stoppedByAbort = true;\n break;\n }\n // ignore read errors (e.g. binary files)\n }\n }\n\n // The result cap is the definite cause even when the abort fired on the same\n // iteration, matching StopReasonTracker's precedence for the two stops this\n // scan can record (see the summary type's narrowing note).\n const stoppedReason =\n matches.length >= maxResults ? 'maxResults' : counters.stoppedByAbort ? 'timeout' : undefined;\n\n return {\n basePath: directory,\n matches,\n summary: {\n matchingLines,\n filesScanned,\n filesMatched,\n truncated: stoppedReason !== undefined,\n skippedInaccessible: counters.skippedInaccessible,\n skippedTooLarge,\n ...(stoppedReason ? { stoppedReason } : {}),\n },\n };\n } finally {\n freeRegex(regex);\n }\n}\n\nasync function* guardedEntries(\n entries: AsyncIterable<GlobEntry>,\n pathGuard: PathGuard,\n signal: AbortSignal | undefined,\n counters: { skippedInaccessible: number; stoppedByAbort: boolean },\n): AsyncGenerator<GlobEntry> {\n // The signal carries both client cancellation and the tool's search timeout,\n // so an abort means \"return what we have, marked incomplete\" rather than\n // throw — but it must never be reported as a finished scan.\n for await (const entry of entries) {\n if (signal?.aborted) {\n counters.stoppedByAbort = true;\n return;\n }\n try {\n await pathGuard.validateExistingPath(entry.path);\n } catch {\n counters.skippedInaccessible++;\n continue;\n }\n yield entry;\n }\n}\n\nexport async function searchFiles(\n directory: string,\n pattern: string,\n options: {\n maxResults?: number;\n includeHidden?: boolean;\n sortBy?: 'name' | 'path';\n skipIgnored?: boolean;\n maxDepth?: number;\n signal?: AbortSignal;\n },\n pathGuard: PathGuard,\n): Promise<{\n basePath: string;\n results: { path: string }[];\n summary: {\n matched: number;\n filesScanned: number;\n truncated: boolean;\n skippedInaccessible: number;\n /** Narrowed like SearchContentOutcome's — scans never hit `hitMaxFiles`. */\n stoppedReason?: 'maxResults' | 'timeout';\n };\n}> {\n const maxResults = options.maxResults ?? 100;\n const entries = globEntries({\n cwd: directory,\n pattern,\n baseNameMatch: true,\n includeHidden: Boolean(options.includeHidden),\n skipIgnored: Boolean(options.skipIgnored),\n ...(options.signal ? { signal: options.signal } : {}),\n maxDepth: options.maxDepth ?? 100,\n suppressErrors: true,\n });\n const results: { path: string }[] = [];\n let filesScanned = 0;\n const counters = { skippedInaccessible: 0, stoppedByAbort: false };\n\n for await (const entry of guardedEntries(entries, pathGuard, options.signal, counters)) {\n if (results.length >= maxResults) break;\n filesScanned++;\n results.push({ path: entry.path });\n }\n\n // Sorting — only name / path are supported; size / modified were removed\n // (the glob never collected stats, so they were always undefined).\n if (options.sortBy === 'name') {\n results.sort((a, b) => basename(a.path).localeCompare(basename(b.path)));\n } else {\n results.sort((a, b) => a.path.localeCompare(b.path));\n }\n\n // Same precedence as above: the result cap wins over a same-iteration abort.\n const stoppedReason =\n results.length >= maxResults ? 'maxResults' : counters.stoppedByAbort ? 'timeout' : undefined;\n\n return {\n basePath: directory,\n results,\n summary: {\n matched: results.length,\n filesScanned,\n truncated: stoppedReason !== undefined,\n skippedInaccessible: counters.skippedInaccessible,\n ...(stoppedReason ? { stoppedReason } : {}),\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/core/search.ts"],"names":[],"mappings":"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","sourcesContent":["import { stat as fsStat, readFile } from 'node:fs/promises';\nimport { basename } from 'node:path';\n\nimport type { RE2ExecArray } from '@adguard/re2-wasm';\nimport { RE2 } from '@adguard/re2-wasm';\n\nimport { resolveStopReason } from './concurrency.ts';\nimport { globEntries, type GlobEntry } from './glob.ts';\nimport type { PathGuard } from './path.ts';\nimport { getMaxTextFileSize, MIB } from './util.ts';\n\n/**\n * A fresh, flat copy of `s`. V8 keeps a substring of a long string as a slice\n * that references its parent, so a kept line would otherwise pin the whole\n * file's text for as long as the match list lives (60 s in the page store).\n */\nconst own = (s: string): string => Buffer.from(s, 'utf8').toString('utf8');\n\ninterface SearchResult {\n file: string;\n line: number;\n /** 0-indexed column of the first occurrence on the line. */\n column: number;\n content: string;\n matchCount?: number;\n /** Up to `context` lines either side of the match; absent when context is 0. */\n before?: string[];\n after?: string[];\n}\n\nexport type Regex = RE2;\n\n/** Occurrence-counting bound, so one pathological line cannot spin forever. */\nconst MAX_MATCHES_PER_LINE = 100_000;\n\n/**\n * Largest input one RE2 call may scan as a single string. The input is copied\n * into the same fixed 16 MB wasm heap as the patterns; about 10 MB of input\n * aborts the module. 4 MiB leaves room for the copy and the pattern set.\n */\nexport const RE2_MAX_INPUT_BYTES = 4 * MIB;\n\n/**\n * Compile a pattern on RE2 rather than on V8's irregexp.\n *\n * Patterns arrive from the MCP client, so a backtracking engine would let one\n * request pin the event loop with no way out: an abort signal cannot preempt a\n * synchronous `exec`, and on stdio that wedges the whole server. RE2 matches in\n * time linear in the input and cannot backtrack at all, so the hazard is gone\n * rather than bounded.\n *\n * RE2 rejects the constructs the tool schema documents as unsupported —\n * lookahead, lookbehind, backreferences — with its own {@link SyntaxError},\n * which the tool layer turns into a normal tool error. It always matches in\n * Unicode mode and requires the `u` flag to say so.\n *\n * Every compiled pattern owns memory in re2-wasm's fixed 16 MB heap, which\n * `ALLOW_MEMORY_GROWTH` is off for. re2-wasm never frees it and a\n * FinalizationRegistry does not keep up (V8 sees no pressure from the wasm\n * heap), so exhaustion is an emscripten `abort()` that kills regex search for\n * the rest of the process. Every caller MUST pass the result to\n * {@link freeRegex} when it is done with it.\n */\nexport function compileRegex(pattern: string, options: { caseSensitive?: boolean } = {}): Regex {\n const flags = options.caseSensitive ? 'gu' : 'giu';\n try {\n return new RE2(pattern, flags);\n } catch (error) {\n if (!(error instanceof SyntaxError)) throw error;\n throw new SyntaxError(\n `${error.message} — lookahead, lookbehind, and backreferences are not supported.`,\n { cause: error },\n );\n }\n}\n\n/**\n * Release a compiled pattern's wasm memory. re2-wasm exposes no disposal of its\n * own, so this reaches the embind handle it holds privately; a version that\n * renames the field degrades to the pre-existing leak rather than throwing.\n * Idempotent — a second call on an already-freed handle is swallowed. Using a\n * {@link Regex} after freeing it is undefined behaviour in the wasm heap, so\n * free only in a `finally` that owns the compile.\n */\nexport function freeRegex(regex: Regex | undefined): void {\n const handle = (regex as unknown as { wrapper?: { delete?: () => void } } | undefined)?.wrapper;\n try {\n handle?.delete?.();\n } catch {\n // already deleted, or a re2-wasm build without embind disposal\n }\n}\n\nexport interface RegexMatch {\n /** The matched text. */\n text: string;\n /** Capture groups, 1-indexed in the pattern, 0-indexed here. */\n groups: (string | undefined)[];\n /** Named capture groups, when the pattern declares any. */\n named: Record<string, string> | undefined;\n /** Start offset in UTF-16 code units — usable with `String.prototype.slice`. */\n start: number;\n /** End offset in UTF-16 code units. */\n end: number;\n}\n\n/** Code-point length of `text`, i.e. its UTF-16 length minus its surrogate pairs. */\nconst codePointLength = (text: string): number =>\n // eslint-disable-next-line @typescript-eslint/no-misused-spread -- code points are the intended unit (re2-wasm offsets count code points); grapheme segmentation would change the count, not preserve it.\n [...text].length;\n\n/**\n * Iterate a global pattern's non-overlapping matches with offsets JS can use.\n *\n * re2-wasm reports `index` and `lastIndex` in **code points**, while every JS\n * string operation indexes in UTF-16 code units. The two agree only until the\n * first astral character (most emoji), past which every offset is short by one\n * per surrogate pair — slicing on the raw index cuts into the middle of\n * neighbouring text. Its `lastIndex` bookkeeping is worse still: it adds the\n * match's UTF-16 length to a code-point index, so an astral character inside a\n * match can skip the next one. This is the single place that corrects both; no\n * caller should read `match.index` or `regex.lastIndex` directly.\n *\n * Zero-length matches (e.g. `a*`) advance by one code point so they cannot loop\n * forever. The regex is global and shared across calls, so `lastIndex` is reset\n * on entry.\n */\nexport function* execMatches(regex: Regex, text: string): Generator<RegexMatch, undefined> {\n regex.lastIndex = 0;\n // Cursor into `text`, carried forward across matches: RE2 reports them in\n // ascending order, so the walk is O(text) in total rather than per match.\n let cursorCp = 0;\n let cursorU16 = 0;\n let match: RE2ExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n while (cursorCp < match.index && cursorU16 < text.length) {\n const isPair =\n (text.charCodeAt(cursorU16) & 0xfc00) === 0xd800 &&\n (text.charCodeAt(cursorU16 + 1) & 0xfc00) === 0xdc00;\n cursorU16 += isPair ? 2 : 1;\n cursorCp++;\n }\n const matched = match[0] ?? '';\n const start = cursorU16;\n yield {\n text: matched,\n groups: match.slice(1),\n named: match.groups,\n start,\n end: start + matched.length,\n };\n // Own the advance rather than trusting the wrapper's mixed-unit arithmetic.\n regex.lastIndex = match.index + (matched.length === 0 ? 1 : codePointLength(matched));\n }\n}\n\n/**\n * Find non-overlapping occurrences of a global regex in a single line, reporting\n * the first match's column alongside the count.\n */\nfunction findLineMatches(\n regex: Regex,\n line: string,\n): { count: number; column: number } | undefined {\n let count = 0;\n let column = -1;\n for (const match of execMatches(regex, line)) {\n if (column === -1) column = match.start;\n count++;\n if (count >= MAX_MATCHES_PER_LINE) break;\n }\n return count > 0 ? { count, column } : undefined;\n}\n\nexport interface SearchContentOptions {\n caseSensitive?: boolean;\n isRegex?: boolean;\n maxResults?: number;\n filePattern?: string;\n skipIgnored?: boolean;\n includeHidden?: boolean;\n maxDepth?: number;\n /** Lines of context to carry either side of each match; 0 (default) carries none. */\n context?: number;\n signal?: AbortSignal;\n}\n\nexport interface SearchContentOutcome {\n basePath: string;\n matches: SearchResult[];\n summary: {\n /**\n * Matching *lines*, one per entry in `matches` — not pattern occurrences.\n * A line with three occurrences counts once here and reports 3 in its own\n * `SearchResult.matchCount`.\n */\n matchingLines: number;\n filesScanned: number;\n filesMatched: number;\n truncated: boolean;\n /** Files the guard rejected or that could not be stat'd. */\n skippedInaccessible: number;\n /** Files skipped unread because they exceed maxFileSize. */\n skippedTooLarge: number;\n /** Files skipped because they contain a NUL byte (binary). */\n skippedBinary: number;\n /**\n * `StoppedReason` narrowed to the stops these scans can produce: both\n * resolve the stop through `resolveStopReason` (concurrency.ts) — never\n * `maxFiles`, which belongs to `replace_text`'s per-file cap.\n */\n stoppedReason?: 'maxResults' | 'timeout';\n };\n}\n\nexport async function searchContent(\n directory: string,\n pattern: string,\n options: SearchContentOptions,\n pathGuard: PathGuard,\n): Promise<SearchContentOutcome> {\n const regex = compileRegex(options.isRegex ? pattern || '' : RegExp.escape(pattern || ''), {\n caseSensitive: Boolean(options.caseSensitive),\n });\n try {\n const matches: SearchResult[] = [];\n const maxResults = options.maxResults ?? 100;\n const maxFileSize = getMaxTextFileSize();\n const context = options.context ?? 0;\n\n const entries = globEntries({\n cwd: directory,\n pattern: options.filePattern ?? '**/*',\n // Same rule as replace_text, so a search with the same glob previews the\n // files a replace would touch: a slash-free glob matches at any depth.\n baseNameMatch: true,\n includeHidden: Boolean(options.includeHidden),\n skipIgnored: Boolean(options.skipIgnored),\n ...(options.signal ? { signal: options.signal } : {}),\n maxDepth: options.maxDepth ?? 100,\n suppressErrors: true,\n });\n\n let filesScanned = 0;\n let filesMatched = 0;\n let matchingLines = 0;\n let skippedTooLarge = 0;\n let skippedBinary = 0;\n const counters = { skippedInaccessible: 0, stoppedByAbort: false };\n\n for await (const entry of guardedEntries(entries, pathGuard, options.signal, counters)) {\n if (matches.length >= maxResults) break;\n\n // Skip oversized files before reading to avoid unbounded memory use. Count\n // them: \"no matches\" for a reason other than the pattern must be visible.\n try {\n const stats = await fsStat(entry.path);\n if (stats.size > maxFileSize) {\n skippedTooLarge++;\n continue;\n }\n } catch {\n counters.skippedInaccessible++;\n continue;\n }\n\n filesScanned++;\n\n try {\n const buffer = await readFile(entry.path, { signal: options.signal });\n // A NUL byte marks a binary file, as it does for `read` and `grep -I`.\n // Non-UTF-8 text is still searched: a lossy decode only affects the\n // bytes a pattern could not have matched anyway.\n if (buffer.includes(0)) {\n skippedBinary++;\n continue;\n }\n const content = buffer.toString('utf-8');\n const lines = content.split(/\\r?\\n/u);\n // A trailing newline splits into a phantom empty last element, and an\n // empty file splits into one empty element: neither is a line the\n // file has, for matching or for context.\n const lineCount =\n content.length === 0 ? 0 : content.endsWith('\\n') ? lines.length - 1 : lines.length;\n let matchedFile = false;\n for (let i = 0; i < lineCount; i++) {\n const line = lines[i];\n if (line === undefined) continue;\n // One scan per line: findLineMatches resets lastIndex itself, so it\n // doubles as the \"does this line match\" test.\n const found = findLineMatches(regex, line);\n if (found) {\n matchedFile = true;\n matchingLines++;\n matches.push({\n file: entry.path,\n line: i + 1,\n column: found.column,\n content: own(line),\n matchCount: found.count,\n ...(context > 0\n ? {\n before: lines.slice(Math.max(0, i - context), i).map(own),\n after: lines.slice(i + 1, Math.min(lineCount, i + 1 + context)).map(own),\n }\n : {}),\n });\n if (matches.length >= maxResults) break;\n }\n }\n if (matchedFile) filesMatched++;\n } catch {\n // A read failure while the signal is aborted IS the abort, not an\n // unreadable file — stop rather than spend another iteration and then\n // report a cut-short scan as complete.\n if (options.signal?.aborted) {\n counters.stoppedByAbort = true;\n break;\n }\n // unreadable mid-scan (deleted, permission changed): skip the file\n }\n }\n\n // The result cap is the definite cause even when the abort fired on the\n // same iteration (see the summary type's narrowing note).\n const stoppedReason = resolveStopReason(matches.length >= maxResults, counters.stoppedByAbort);\n\n return {\n basePath: directory,\n matches,\n summary: {\n matchingLines,\n filesScanned,\n filesMatched,\n truncated: stoppedReason !== undefined,\n skippedInaccessible: counters.skippedInaccessible,\n skippedTooLarge,\n skippedBinary,\n ...(stoppedReason ? { stoppedReason } : {}),\n },\n };\n } finally {\n freeRegex(regex);\n }\n}\n\nasync function* guardedEntries(\n entries: AsyncIterable<GlobEntry>,\n pathGuard: PathGuard,\n signal: AbortSignal | undefined,\n counters: { skippedInaccessible: number; stoppedByAbort: boolean },\n): AsyncGenerator<GlobEntry> {\n // The signal carries both client cancellation and the tool's search timeout,\n // so an abort means \"return what we have, marked incomplete\" rather than\n // throw — but it must never be reported as a finished scan.\n for await (const entry of entries) {\n if (signal?.aborted) {\n counters.stoppedByAbort = true;\n return;\n }\n try {\n await pathGuard.validateExistingPath(entry.path);\n } catch {\n counters.skippedInaccessible++;\n continue;\n }\n yield entry;\n }\n}\n\nexport async function searchFiles(\n directory: string,\n pattern: string,\n options: {\n maxResults?: number;\n includeHidden?: boolean;\n sortBy?: 'name' | 'path';\n skipIgnored?: boolean;\n maxDepth?: number;\n signal?: AbortSignal;\n },\n pathGuard: PathGuard,\n): Promise<{\n basePath: string;\n results: { path: string }[];\n summary: {\n matched: number;\n filesScanned: number;\n truncated: boolean;\n skippedInaccessible: number;\n /** Narrowed like SearchContentOutcome's — scans never `maxFiles`. */\n stoppedReason?: 'maxResults' | 'timeout';\n };\n}> {\n const maxResults = options.maxResults ?? 100;\n const entries = globEntries({\n cwd: directory,\n pattern,\n baseNameMatch: true,\n includeHidden: Boolean(options.includeHidden),\n skipIgnored: Boolean(options.skipIgnored),\n ...(options.signal ? { signal: options.signal } : {}),\n maxDepth: options.maxDepth ?? 100,\n suppressErrors: true,\n });\n const results: { path: string }[] = [];\n let filesScanned = 0;\n const counters = { skippedInaccessible: 0, stoppedByAbort: false };\n\n for await (const entry of guardedEntries(entries, pathGuard, options.signal, counters)) {\n if (results.length >= maxResults) break;\n filesScanned++;\n results.push({ path: entry.path });\n }\n\n // Sorting — only name / path are supported; size / modified were removed\n // (the glob never collected stats, so they were always undefined).\n if (options.sortBy === 'name') {\n results.sort((a, b) => basename(a.path).localeCompare(basename(b.path)));\n } else {\n results.sort((a, b) => a.path.localeCompare(b.path));\n }\n\n // Same precedence as above: the result cap wins over a same-iteration abort.\n const stoppedReason = resolveStopReason(results.length >= maxResults, counters.stoppedByAbort);\n\n return {\n basePath: directory,\n results,\n summary: {\n matched: results.length,\n filesScanned,\n truncated: stoppedReason !== undefined,\n skippedInaccessible: counters.skippedInaccessible,\n ...(stoppedReason ? { stoppedReason } : {}),\n },\n };\n}\n"]}
|
package/dist/core/sensitive.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/** The three pattern tiers as configured, for `--print-config`. Read-only view; the matcher compiles its own. */
|
|
2
|
+
export declare function describeSensitivePolicy(): {
|
|
3
|
+
allowSensitive: boolean;
|
|
4
|
+
builtinDeny: readonly string[];
|
|
5
|
+
operatorDeny: readonly string[];
|
|
6
|
+
allow: readonly string[];
|
|
7
|
+
};
|
|
1
8
|
export declare class SensitiveMatcher {
|
|
2
9
|
private readonly builtin;
|
|
3
10
|
private readonly operator;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sensitive.d.ts","sourceRoot":"","sources":["../../src/core/sensitive.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sensitive.d.ts","sourceRoot":"","sources":["../../src/core/sensitive.ts"],"names":[],"mappings":"AA0UA,iHAAiH;AACjH,wBAAgB,uBAAuB,IAAI;IACzC,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B,CAQA;AAID,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;gBAK/B,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE;IAanE,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;CAcvC"}
|
package/dist/core/sensitive.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// normalization that feeds it, and the NTFS alternate-data-stream stripping
|
|
3
3
|
// Windows needs. Split out of path.ts so the denylist has its own home — it
|
|
4
4
|
// shares only isAlpha / toPosixPath / IS_WINDOWS with the primitives in
|
|
5
|
-
//
|
|
6
|
-
//
|
|
5
|
+
// path-utils.ts, not the allowed-directory assembly. path.ts and
|
|
6
|
+
// path-completer.ts reach it through PathGuard.isSensitive, which delegates here.
|
|
7
7
|
import { normalize, posix, sep } from 'node:path';
|
|
8
8
|
import { cli } from "./config.js";
|
|
9
9
|
import { IS_WINDOWS, isAlpha, parseTrueEnvFlag, toPosixPath } from "./path-utils.js";
|
|
@@ -234,8 +234,15 @@ const DEFAULT_SENSITIVE_PATTERNS = [
|
|
|
234
234
|
'.env.*',
|
|
235
235
|
'.npmrc',
|
|
236
236
|
'.pypirc',
|
|
237
|
+
'.netrc',
|
|
238
|
+
'_netrc',
|
|
239
|
+
'.git-credentials',
|
|
240
|
+
'.pgpass',
|
|
237
241
|
'.aws/credentials',
|
|
238
242
|
'.aws/config',
|
|
243
|
+
'.docker/config.json',
|
|
244
|
+
'.kube/config',
|
|
245
|
+
'application_default_credentials.json',
|
|
239
246
|
'.mcpregistry_*_token',
|
|
240
247
|
'*.pem',
|
|
241
248
|
'*.key',
|
|
@@ -245,6 +252,8 @@ const DEFAULT_SENSITIVE_PATTERNS = [
|
|
|
245
252
|
'*.cer',
|
|
246
253
|
'*id_rsa*',
|
|
247
254
|
'*id_dsa*',
|
|
255
|
+
'*id_ecdsa*',
|
|
256
|
+
'*id_ed25519*',
|
|
248
257
|
];
|
|
249
258
|
// Built-ins and operator-supplied deny entries live in separate tiers because
|
|
250
259
|
// they answer to different relief switches: an allow entry (FS_ALLOWLIST /
|
|
@@ -312,6 +321,16 @@ function buildAllowPatterns() {
|
|
|
312
321
|
const flagAllowlist = cli.allowPatterns ?? [];
|
|
313
322
|
return [...new Set([...envAllowlist, ...flagAllowlist])];
|
|
314
323
|
}
|
|
324
|
+
/** The three pattern tiers as configured, for `--print-config`. Read-only view; the matcher compiles its own. */
|
|
325
|
+
export function describeSensitivePolicy() {
|
|
326
|
+
const tiers = buildDenyTiers();
|
|
327
|
+
return {
|
|
328
|
+
allowSensitive: tiers.builtin.length === 0,
|
|
329
|
+
builtinDeny: tiers.builtin,
|
|
330
|
+
operatorDeny: tiers.operator,
|
|
331
|
+
allow: buildAllowPatterns(),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
315
334
|
const EMPTY_PATTERN_SET = { pathGlobs: [], nameGlobs: [] };
|
|
316
335
|
export class SensitiveMatcher {
|
|
317
336
|
builtin;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sensitive.js","sourceRoot":"","sources":["../../src/core/sensitive.ts"],"names":[],"mappings":"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","sourcesContent":["// Sensitive-file denylist policy: the compiled pattern set, the path\n// normalization that feeds it, and the NTFS alternate-data-stream stripping\n// Windows needs. Split out of path.ts so the denylist has its own home — it\n// shares only isAlpha / toPosixPath / IS_WINDOWS with the primitives in\n// primitives.ts, not the allowed-directory assembly. path-completer.ts and\n// glob.ts reach it through PathGuard.isSensitive, which delegates here.\nimport { normalize, posix, sep } from 'node:path';\n\nimport { cli } from './config.ts';\nimport { IS_WINDOWS, isAlpha, parseTrueEnvFlag, toPosixPath } from './path-utils.ts';\n\nconst CHAR_COLON = 58;\n\nfunction normalizeForMatch(input: string): string {\n // Always lowercase for case-insensitive denylist matching on all platforms.\n // normalize() preserves the double leading slash of UNC paths\n // (//server/share/...) on Windows; collapse it to one so the compiled\n // patterns — which carry no leading slash — still match their segments.\n return toPosixPath(normalize(input)).toLowerCase().replace(/^\\/+/u, '/');\n}\n\ninterface CompiledPatternSet {\n pathGlobs: readonly RegExp[];\n nameGlobs: readonly RegExp[];\n}\n\n// posix.matchesGlob runs with dot:false semantics and does not honor a dot\n// option at all (verified on Node 24): '*' and '**' never match dot-leading\n// path segments, so a deny of `secrets/**` silently let `secrets/.env`\n// through — fail-open against exactly the hidden files this list exists to\n// guard. Compile our own matcher instead. Supported metacharacters: '*'\n// (any run within a segment), '**' (any run of whole segments), '?' (one\n// char), '[...]' classes, and '{a,b}' alternation; everything else is\n// literal, and dot-leading segments match like any other.\n\n// Character classes can carry '{', '}' and ',' as members, so every scanner\n// below must skip a class's interior. classEnd returns the index of the ']'\n// closing the class opened at openIdx, or -1 when unclosed (then '[' is a\n// literal, matching globToRegExp's fallback). A ']' right after '[' or the\n// '!' negation is a literal member, per glob syntax.\nfunction classEnd(glob: string, openIdx: number): number {\n let i = openIdx + 1;\n if (glob[i] === '!') i++;\n if (glob[i] === ']') i++;\n while (i < glob.length && glob[i] !== ']') i++;\n return i < glob.length ? i : -1;\n}\n\nfunction expandBraces(glob: string): string[] {\n // First '{' outside any [...] class.\n let open = -1;\n for (let i = 0; i < glob.length; i++) {\n const char = glob[i] ?? '';\n if (char === '[') {\n const end = classEnd(glob, i);\n if (end !== -1) i = end;\n } else if (char === '{') {\n open = i;\n break;\n }\n }\n if (open === -1) return [glob];\n // Pair the outer '{' with ITS matching '}' — indexOf would grab the inner\n // group's closer and produce garbage like 'data/x}/secret'. Then split the\n // body on depth-0 commas so nested groups stay intact for the recursion.\n let depth = 0;\n let close = -1;\n for (let i = open; i < glob.length; i++) {\n const char = glob[i] ?? '';\n if (char === '[') {\n const end = classEnd(glob, i);\n if (end !== -1) {\n i = end;\n continue;\n }\n }\n if (char === '{') depth++;\n else if (char === '}') {\n depth--;\n if (depth === 0) {\n close = i;\n break;\n }\n }\n }\n if (close === -1) return [glob];\n const prefix = glob.slice(0, open);\n const suffix = glob.slice(close + 1);\n const body = glob.slice(open + 1, close);\n const parts: string[] = [];\n let start = 0;\n depth = 0;\n for (let i = 0; i < body.length; i++) {\n const char = body[i] ?? '';\n if (char === '[') {\n const end = classEnd(body, i);\n if (end !== -1) {\n i = end;\n continue;\n }\n }\n if (char === '{') depth++;\n else if (char === '}') depth--;\n else if (char === ',' && depth === 0) {\n parts.push(body.slice(start, i));\n start = i + 1;\n }\n }\n parts.push(body.slice(start));\n return parts.flatMap((part) => expandBraces(`${prefix}${part}${suffix}`));\n}\n\nfunction globToRegExp(glob: string): RegExp {\n const segments = glob.split('/');\n let source = '';\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i] ?? '';\n const last = i === segments.length - 1;\n if (segment === '**') {\n source += last ? '(?:.*)?' : '(?:[^/]+/)*';\n continue;\n }\n for (let j = 0; j < segment.length; j++) {\n const char = segment[j] ?? '';\n if (char === '*') {\n while (segment[j + 1] === '*') j++;\n source += '[^/]*';\n } else if (char === '?') {\n source += '[^/]';\n } else if (char === '[') {\n // Same classEnd rule the scanners use: a ']' right after '[' (or\n // after the '!' negation) is a literal member, not the closer.\n const close = classEnd(segment, j);\n if (close !== -1) {\n // Pass the class through, flipping glob '!' negation to '^' and\n // escaping backslashes so no body can make new RegExp throw. A\n // leading ']' must be escaped or the 'u' flag parses '[]a]' as an\n // empty class followed by stray literals; a leading '^' is a\n // literal member (glob negation is '!' only), unescaped it would\n // negate the class and relieve more than the operator named.\n let body = segment.slice(j + 1, close).replace(/\\\\/gu, '\\\\\\\\');\n const negated = body.startsWith('!');\n if (negated) body = body.slice(1);\n if (body.startsWith(']') || body.startsWith('^')) body = `\\\\${body}`;\n source += `[${negated ? '^' : ''}${body}]`;\n j = close;\n } else {\n source += RegExp.escape(char);\n }\n } else {\n source += RegExp.escape(char);\n }\n }\n if (!last) source += '/';\n }\n // Candidates are absolute paths but patterns carry no leading '/', so the\n // anchor optionally eats the root slash — `**/` then reaches from any\n // parent, exactly as matchesGlob's globstar did. '$' is strict in\n // JavaScript (it matches end-of-input only, unlike PCRE's, which also\n // matches before a final newline), so an exact-name pattern can never\n // match a '.env\\n' lookalike. The 's' flag makes the terminal-`**` `.`\n // match newlines too: filenames can contain them, and a deny ending in\n // '/**' must not fail open on 'secrets/plan\\nnotes'.\n return new RegExp(`^/?${source}$`, 'su');\n}\n\nfunction compilePatternGlobs(normalizedPattern: string): readonly string[] {\n // A leading '/' (POSIX-absolute) is rooted like a Windows drive path:\n // aliasing '/abs/secret' as '**/abs/secret' would also match\n // 'other/abs/secret', and on the allow side that is over-relief. The\n // drive-letter regex is safe on [a-z] because normalizeForMatch lowercased.\n const isRooted =\n normalizedPattern.startsWith('/') ||\n normalizedPattern.startsWith('**/') ||\n /^[a-z]:\\//u.test(normalizedPattern);\n const withoutRoot = isRooted ? '' : normalizedPattern.replace(/^\\/+/, '');\n return withoutRoot ? [normalizedPattern, `**/${withoutRoot}`] : [normalizedPattern];\n}\n\nfunction toPatternSet(patterns: readonly string[]): CompiledPatternSet {\n const pathGlobs = new Set<RegExp>();\n const nameGlobs = new Set<RegExp>();\n\n const deduped = Array.from(new Set(patterns.map((p) => p.trim()).filter((p) => p.length > 0)));\n for (const pattern of deduped) {\n const normalized = normalizeForMatch(pattern);\n const matchesPath = normalized.includes('/');\n const target = matchesPath ? pathGlobs : nameGlobs;\n const globs = matchesPath ? compilePatternGlobs(normalized) : [normalized];\n for (const glob of globs) {\n for (const expanded of expandBraces(glob)) {\n target.add(globToRegExp(expanded));\n }\n }\n }\n\n return {\n pathGlobs: [...pathGlobs],\n nameGlobs: [...nameGlobs],\n };\n}\n\nfunction matchesAnyGlob(globs: readonly RegExp[], candidate: string): boolean {\n return globs.some((glob) => glob.test(candidate));\n}\n\n// Strip NTFS alternate-data-stream suffixes (the \":stream\" after a segment)\n// before denylist matching, so `.env:secret` is caught as `.env`. Preserves the\n// drive-letter colon in the first segment of an absolute path. Also trims\n// trailing dots/spaces: Win32 strips them at the syscall boundary (a request\n// to create `.env ` actually creates `.env`), so without trimming them here the\n// exact-name patterns (`.env`, `.npmrc`) are bypassed on Windows. Mirrors the\n// precedent in getReservedDeviceName (path.ts) which trims for the same reason.\nfunction stripAlternateDataStreams(filePath: string): string {\n const parts = filePath.split(/[\\\\/]/);\n const stripped = parts.map((segment, i) => {\n if (\n i === 0 &&\n segment.length === 2 &&\n isAlpha(segment.charCodeAt(0)) &&\n segment.charCodeAt(1) === CHAR_COLON\n ) {\n return segment;\n }\n const colonIdx = segment.indexOf(':');\n const withoutStream = colonIdx !== -1 ? segment.slice(0, colonIdx) : segment;\n // Win32 strips trailing dots and spaces at the syscall boundary, so a\n // request for \".env \" creates \".env\". Strip them before denylist matching\n // or the exact-name patterns (\".env\", \".npmrc\") are bypassed on Windows.\n return trimTrailingDotsAndSpaces(withoutStream);\n });\n return stripped.join(sep);\n}\n\nconst trimTrailingDotsAndSpaces = (segment: string): string => segment.replace(/[. ]+$/, '');\n\nconst DEFAULT_SENSITIVE_PATTERNS = [\n '.env',\n '.env.*',\n '.npmrc',\n '.pypirc',\n '.aws/credentials',\n '.aws/config',\n '.mcpregistry_*_token',\n '*.pem',\n '*.key',\n '*.p12',\n '*.pfx',\n '*.crt',\n '*.cer',\n '*id_rsa*',\n '*id_dsa*',\n] as const;\n\n// Built-ins and operator-supplied deny entries live in separate tiers because\n// they answer to different relief switches: an allow entry (FS_ALLOWLIST /\n// --allow) can lift a built-in hit, but never an explicit deny.\n\n// Split an env pattern list on commas and newlines at brace depth 0 only and\n// outside any [...] class, so documented syntax like '*.{pem,key}' or\n// 'x[1,2].env' survives as one pattern instead of tearing apart.\nfunction splitPatternList(value: string): string[] {\n const naive = (): string[] =>\n value\n .split(/,|\\n/u)\n .map((t) => t.trim())\n .filter((t) => t.length > 0);\n const parts: string[] = [];\n let depth = 0;\n let start = 0;\n let unbalanced = false;\n for (let i = 0; i < value.length; i++) {\n const char = value[i] ?? '';\n if (char === '[') {\n const end = classEnd(value, i);\n if (end !== -1) {\n i = end;\n continue;\n }\n }\n if (char === '{') depth++;\n else if (char === '}') {\n if (depth === 0) unbalanced = true;\n else depth--;\n } else if (depth === 0 && (char === ',' || char === '\\n')) {\n parts.push(value.slice(start, i));\n start = i + 1;\n }\n }\n // An unbalanced brace would swallow every separator behind it into one\n // bogus literal pattern and silently drop the real entries — '{bad,\n // secrets/**' must not lose 'secrets/**'. A typo'd brace costs the brace\n // pattern only: fall back to the naive split.\n if (unbalanced || depth !== 0) return naive();\n parts.push(value.slice(start));\n return parts.map((t) => t.trim()).filter((t) => t.length > 0);\n}\n\nfunction buildDenyTiers(): { builtin: readonly string[]; operator: readonly string[] } {\n const allowSensitive =\n cli.allowSensitive ?? parseTrueEnvFlag(process.env['FS_ALLOW_SENSITIVE'], 'FS_ALLOW_SENSITIVE');\n const envDenylist = process.env['FS_DENYLIST']\n ? splitPatternList(process.env['FS_DENYLIST'])\n : [];\n const flagDenylist = cli.denyPatterns ?? [];\n // FS_ALLOW_SENSITIVE suppresses built-ins only; deny entries (env and --deny)\n // always apply. Set-dedupe so a pattern in both sources matches once.\n return {\n builtin: allowSensitive ? [] : DEFAULT_SENSITIVE_PATTERNS,\n operator: [...new Set([...envDenylist, ...flagDenylist])],\n };\n}\n\nfunction buildAllowPatterns(): readonly string[] {\n const envAllowlist = process.env['FS_ALLOWLIST']\n ? splitPatternList(process.env['FS_ALLOWLIST'])\n : [];\n const flagAllowlist = cli.allowPatterns ?? [];\n return [...new Set([...envAllowlist, ...flagAllowlist])];\n}\n\nconst EMPTY_PATTERN_SET: CompiledPatternSet = { pathGlobs: [], nameGlobs: [] };\n\nexport class SensitiveMatcher {\n private readonly builtin: CompiledPatternSet;\n private readonly operator: CompiledPatternSet;\n private readonly allow: CompiledPatternSet;\n\n // An explicit pattern list (tests, custom guards) is one relievable tier;\n // the default construction splits built-ins from operator-supplied\n // FS_DENYLIST/--deny entries, which allow entries can never lift.\n constructor(patterns?: readonly string[], allow?: readonly string[]) {\n if (patterns !== undefined || allow !== undefined) {\n this.builtin = toPatternSet(patterns ?? []);\n this.operator = EMPTY_PATTERN_SET;\n this.allow = toPatternSet(allow ?? []);\n } else {\n const tiers = buildDenyTiers();\n this.builtin = toPatternSet(tiers.builtin);\n this.operator = toPatternSet(tiers.operator);\n this.allow = toPatternSet(buildAllowPatterns());\n }\n }\n\n isSensitive(filePath: string): boolean {\n const pathToCheck = IS_WINDOWS ? stripAlternateDataStreams(filePath) : filePath;\n const normalizedPath = normalizeForMatch(pathToCheck);\n const name = posix.basename(normalizedPath);\n const matches = (set: CompiledPatternSet): boolean =>\n matchesAnyGlob(set.pathGlobs, normalizedPath) || matchesAnyGlob(set.nameGlobs, name);\n\n // Explicit deny entries (FS_DENYLIST/--deny) always apply.\n if (matches(this.operator)) return true;\n if (!matches(this.builtin)) return false;\n // A built-in hit is lifted only by an allow entry that matches the same\n // normalized candidate — a pattern that matches nothing relieves nothing.\n return !matches(this.allow);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"sensitive.js","sourceRoot":"","sources":["../../src/core/sensitive.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,4EAA4E;AAC5E,4EAA4E;AAC5E,wEAAwE;AACxE,iEAAiE;AACjE,kFAAkF;AAClF,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAElD,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAErF,MAAM,UAAU,GAAG,EAAE,CAAC;AAEtB,SAAS,iBAAiB,CAAC,KAAa;IACtC,4EAA4E;IAC5E,8DAA8D;IAC9D,sEAAsE;IACtE,wEAAwE;IACxE,OAAO,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;AAC3E,CAAC;AAOD,2EAA2E;AAC3E,4EAA4E;AAC5E,uEAAuE;AACvE,2EAA2E;AAC3E,wEAAwE;AACxE,yEAAyE;AACzE,sEAAsE;AACtE,0DAA0D;AAE1D,4EAA4E;AAC5E,4EAA4E;AAC5E,0EAA0E;AAC1E,2EAA2E;AAC3E,qDAAqD;AACrD,SAAS,QAAQ,CAAC,IAAY,EAAE,OAAe;IAC7C,IAAI,CAAC,GAAG,OAAO,GAAG,CAAC,CAAC;IACpB,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,CAAC,EAAE,CAAC;IACzB,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,CAAC,EAAE,CAAC;IACzB,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,CAAC,EAAE,CAAC;IAC/C,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,SAAS,YAAY,CAAC,IAAY;IAChC,qCAAqC;IACrC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3B,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YAC9B,IAAI,GAAG,KAAK,CAAC,CAAC;gBAAE,CAAC,GAAG,GAAG,CAAC;QAC1B,CAAC;aAAM,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACxB,IAAI,GAAG,CAAC,CAAC;YACT,MAAM;QACR,CAAC;IACH,CAAC;IACD,IAAI,IAAI,KAAK,CAAC,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,0EAA0E;IAC1E,2EAA2E;IAC3E,yEAAyE;IACzE,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC;IACf,KAAK,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3B,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YAC9B,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;gBACf,CAAC,GAAG,GAAG,CAAC;gBACR,SAAS;YACX,CAAC;QACH,CAAC;QACD,IAAI,IAAI,KAAK,GAAG;YAAE,KAAK,EAAE,CAAC;aACrB,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACtB,KAAK,EAAE,CAAC;YACR,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;gBAChB,KAAK,GAAG,CAAC,CAAC;gBACV,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IACD,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IACnC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;IACzC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,GAAG,CAAC,CAAC;IACV,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3B,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YAC9B,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;gBACf,CAAC,GAAG,GAAG,CAAC;gBACR,SAAS;YACX,CAAC;QACH,CAAC;QACD,IAAI,IAAI,KAAK,GAAG;YAAE,KAAK,EAAE,CAAC;aACrB,IAAI,IAAI,KAAK,GAAG;YAAE,KAAK,EAAE,CAAC;aAC1B,IAAI,IAAI,KAAK,GAAG,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YACrC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;YACjC,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC;IACH,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IAC9B,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM,EAAE,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED,SAAS,YAAY,CAAC,IAAY;IAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,CAAC,KAAK,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;QACvC,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC;YAC3C,SAAS;QACX,CAAC;QACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACxC,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC9B,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBACjB,OAAO,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG;oBAAE,CAAC,EAAE,CAAC;gBACnC,MAAM,IAAI,OAAO,CAAC;YACpB,CAAC;iBAAM,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBACxB,MAAM,IAAI,MAAM,CAAC;YACnB,CAAC;iBAAM,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBACxB,iEAAiE;gBACjE,+DAA+D;gBAC/D,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;gBACnC,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;oBACjB,gEAAgE;oBAChE,+DAA+D;oBAC/D,kEAAkE;oBAClE,6DAA6D;oBAC7D,iEAAiE;oBACjE,6DAA6D;oBAC7D,IAAI,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;oBAC/D,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;oBACrC,IAAI,OAAO;wBAAE,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;oBAClC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;wBAAE,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;oBACrE,MAAM,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,GAAG,CAAC;oBAC3C,CAAC,GAAG,KAAK,CAAC;gBACZ,CAAC;qBAAM,CAAC;oBACN,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBAChC,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAChC,CAAC;QACH,CAAC;QACD,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,GAAG,CAAC;IAC3B,CAAC;IACD,0EAA0E;IAC1E,sEAAsE;IACtE,kEAAkE;IAClE,sEAAsE;IACtE,sEAAsE;IACtE,uEAAuE;IACvE,uEAAuE;IACvE,qDAAqD;IACrD,OAAO,IAAI,MAAM,CAAC,MAAM,MAAM,GAAG,EAAE,IAAI,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,mBAAmB,CAAC,iBAAyB;IACpD,sEAAsE;IACtE,6DAA6D;IAC7D,qEAAqE;IACrE,4EAA4E;IAC5E,MAAM,QAAQ,GACZ,iBAAiB,CAAC,UAAU,CAAC,GAAG,CAAC;QACjC,iBAAiB,CAAC,UAAU,CAAC,KAAK,CAAC;QACnC,YAAY,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACvC,MAAM,WAAW,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC1E,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,iBAAiB,EAAE,MAAM,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC;AACtF,CAAC;AAED,SAAS,YAAY,CAAC,QAA2B;IAC/C,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IACpC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IAEpC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/F,KAAK,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC;QAC9B,MAAM,UAAU,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC9C,MAAM,WAAW,GAAG,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;QACnD,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QAC3E,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,KAAK,MAAM,QAAQ,IAAI,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC1C,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;YACrC,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,SAAS,EAAE,CAAC,GAAG,SAAS,CAAC;QACzB,SAAS,EAAE,CAAC,GAAG,SAAS,CAAC;KAC1B,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,KAAwB,EAAE,SAAiB;IACjE,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;AACpD,CAAC;AAED,4EAA4E;AAC5E,gFAAgF;AAChF,0EAA0E;AAC1E,6EAA6E;AAC7E,gFAAgF;AAChF,8EAA8E;AAC9E,gFAAgF;AAChF,SAAS,yBAAyB,CAAC,QAAgB;IACjD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE;QACxC,IACE,CAAC,KAAK,CAAC;YACP,OAAO,CAAC,MAAM,KAAK,CAAC;YACpB,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YAC9B,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,UAAU,EACpC,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,aAAa,GAAG,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;QAC7E,sEAAsE;QACtE,0EAA0E;QAC1E,yEAAyE;QACzE,OAAO,yBAAyB,CAAC,aAAa,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IACH,OAAO,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,yBAAyB,GAAG,CAAC,OAAe,EAAU,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AAE7F,MAAM,0BAA0B,GAAG;IACjC,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,kBAAkB;IAClB,SAAS;IACT,kBAAkB;IAClB,aAAa;IACb,qBAAqB;IACrB,cAAc;IACd,sCAAsC;IACtC,sBAAsB;IACtB,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,UAAU;IACV,UAAU;IACV,YAAY;IACZ,cAAc;CACN,CAAC;AAEX,8EAA8E;AAC9E,2EAA2E;AAC3E,gEAAgE;AAEhE,6EAA6E;AAC7E,sEAAsE;AACtE,iEAAiE;AACjE,SAAS,gBAAgB,CAAC,KAAa;IACrC,MAAM,KAAK,GAAG,GAAa,EAAE,CAC3B,KAAK;SACF,KAAK,CAAC,OAAO,CAAC;SACd,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACjC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YAC/B,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;gBACf,CAAC,GAAG,GAAG,CAAC;gBACR,SAAS;YACX,CAAC;QACH,CAAC;QACD,IAAI,IAAI,KAAK,GAAG;YAAE,KAAK,EAAE,CAAC;aACrB,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACtB,IAAI,KAAK,KAAK,CAAC;gBAAE,UAAU,GAAG,IAAI,CAAC;;gBAC9B,KAAK,EAAE,CAAC;QACf,CAAC;aAAM,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;YAC1D,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;YAClC,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC;IACH,CAAC;IACD,uEAAuE;IACvE,oEAAoE;IACpE,yEAAyE;IACzE,8CAA8C;IAC9C,IAAI,UAAU,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,KAAK,EAAE,CAAC;IAC9C,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IAC/B,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,cAAc;IACrB,MAAM,cAAc,GAClB,GAAG,CAAC,cAAc,IAAI,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC,CAAC;IAClG,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;QAC5C,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QAC9C,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,YAAY,GAAG,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC;IAC5C,8EAA8E;IAC9E,sEAAsE;IACtE,OAAO;QACL,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,0BAA0B;QACzD,QAAQ,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,WAAW,EAAE,GAAG,YAAY,CAAC,CAAC,CAAC;KAC1D,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB;IACzB,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;QAC9C,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAC/C,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,aAAa,GAAG,GAAG,CAAC,aAAa,IAAI,EAAE,CAAC;IAC9C,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,YAAY,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,iHAAiH;AACjH,MAAM,UAAU,uBAAuB;IAMrC,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;IAC/B,OAAO;QACL,cAAc,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;QAC1C,WAAW,EAAE,KAAK,CAAC,OAAO;QAC1B,YAAY,EAAE,KAAK,CAAC,QAAQ;QAC5B,KAAK,EAAE,kBAAkB,EAAE;KAC5B,CAAC;AACJ,CAAC;AAED,MAAM,iBAAiB,GAAuB,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;AAE/E,MAAM,OAAO,gBAAgB;IACV,OAAO,CAAqB;IAC5B,QAAQ,CAAqB;IAC7B,KAAK,CAAqB;IAE3C,0EAA0E;IAC1E,mEAAmE;IACnE,kEAAkE;IAClE,YAAY,QAA4B,EAAE,KAAyB;QACjE,IAAI,QAAQ,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YAClD,IAAI,CAAC,OAAO,GAAG,YAAY,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;YAC5C,IAAI,CAAC,QAAQ,GAAG,iBAAiB,CAAC;YAClC,IAAI,CAAC,KAAK,GAAG,YAAY,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;QACzC,CAAC;aAAM,CAAC;YACN,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;YAC/B,IAAI,CAAC,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC3C,IAAI,CAAC,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YAC7C,IAAI,CAAC,KAAK,GAAG,YAAY,CAAC,kBAAkB,EAAE,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAED,WAAW,CAAC,QAAgB;QAC1B,MAAM,WAAW,GAAG,UAAU,CAAC,CAAC,CAAC,yBAAyB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QAChF,MAAM,cAAc,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;QACtD,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;QAC5C,MAAM,OAAO,GAAG,CAAC,GAAuB,EAAW,EAAE,CACnD,cAAc,CAAC,GAAG,CAAC,SAAS,EAAE,cAAc,CAAC,IAAI,cAAc,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QAEvF,2DAA2D;QAC3D,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;QACxC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO,KAAK,CAAC;QACzC,wEAAwE;QACxE,0EAA0E;QAC1E,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;CACF","sourcesContent":["// Sensitive-file denylist policy: the compiled pattern set, the path\n// normalization that feeds it, and the NTFS alternate-data-stream stripping\n// Windows needs. Split out of path.ts so the denylist has its own home — it\n// shares only isAlpha / toPosixPath / IS_WINDOWS with the primitives in\n// path-utils.ts, not the allowed-directory assembly. path.ts and\n// path-completer.ts reach it through PathGuard.isSensitive, which delegates here.\nimport { normalize, posix, sep } from 'node:path';\n\nimport { cli } from './config.ts';\nimport { IS_WINDOWS, isAlpha, parseTrueEnvFlag, toPosixPath } from './path-utils.ts';\n\nconst CHAR_COLON = 58;\n\nfunction normalizeForMatch(input: string): string {\n // Always lowercase for case-insensitive denylist matching on all platforms.\n // normalize() preserves the double leading slash of UNC paths\n // (//server/share/...) on Windows; collapse it to one so the compiled\n // patterns — which carry no leading slash — still match their segments.\n return toPosixPath(normalize(input)).toLowerCase().replace(/^\\/+/u, '/');\n}\n\ninterface CompiledPatternSet {\n pathGlobs: readonly RegExp[];\n nameGlobs: readonly RegExp[];\n}\n\n// posix.matchesGlob runs with dot:false semantics and does not honor a dot\n// option at all (verified on Node 24): '*' and '**' never match dot-leading\n// path segments, so a deny of `secrets/**` silently let `secrets/.env`\n// through — fail-open against exactly the hidden files this list exists to\n// guard. Compile our own matcher instead. Supported metacharacters: '*'\n// (any run within a segment), '**' (any run of whole segments), '?' (one\n// char), '[...]' classes, and '{a,b}' alternation; everything else is\n// literal, and dot-leading segments match like any other.\n\n// Character classes can carry '{', '}' and ',' as members, so every scanner\n// below must skip a class's interior. classEnd returns the index of the ']'\n// closing the class opened at openIdx, or -1 when unclosed (then '[' is a\n// literal, matching globToRegExp's fallback). A ']' right after '[' or the\n// '!' negation is a literal member, per glob syntax.\nfunction classEnd(glob: string, openIdx: number): number {\n let i = openIdx + 1;\n if (glob[i] === '!') i++;\n if (glob[i] === ']') i++;\n while (i < glob.length && glob[i] !== ']') i++;\n return i < glob.length ? i : -1;\n}\n\nfunction expandBraces(glob: string): string[] {\n // First '{' outside any [...] class.\n let open = -1;\n for (let i = 0; i < glob.length; i++) {\n const char = glob[i] ?? '';\n if (char === '[') {\n const end = classEnd(glob, i);\n if (end !== -1) i = end;\n } else if (char === '{') {\n open = i;\n break;\n }\n }\n if (open === -1) return [glob];\n // Pair the outer '{' with ITS matching '}' — indexOf would grab the inner\n // group's closer and produce garbage like 'data/x}/secret'. Then split the\n // body on depth-0 commas so nested groups stay intact for the recursion.\n let depth = 0;\n let close = -1;\n for (let i = open; i < glob.length; i++) {\n const char = glob[i] ?? '';\n if (char === '[') {\n const end = classEnd(glob, i);\n if (end !== -1) {\n i = end;\n continue;\n }\n }\n if (char === '{') depth++;\n else if (char === '}') {\n depth--;\n if (depth === 0) {\n close = i;\n break;\n }\n }\n }\n if (close === -1) return [glob];\n const prefix = glob.slice(0, open);\n const suffix = glob.slice(close + 1);\n const body = glob.slice(open + 1, close);\n const parts: string[] = [];\n let start = 0;\n depth = 0;\n for (let i = 0; i < body.length; i++) {\n const char = body[i] ?? '';\n if (char === '[') {\n const end = classEnd(body, i);\n if (end !== -1) {\n i = end;\n continue;\n }\n }\n if (char === '{') depth++;\n else if (char === '}') depth--;\n else if (char === ',' && depth === 0) {\n parts.push(body.slice(start, i));\n start = i + 1;\n }\n }\n parts.push(body.slice(start));\n return parts.flatMap((part) => expandBraces(`${prefix}${part}${suffix}`));\n}\n\nfunction globToRegExp(glob: string): RegExp {\n const segments = glob.split('/');\n let source = '';\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i] ?? '';\n const last = i === segments.length - 1;\n if (segment === '**') {\n source += last ? '(?:.*)?' : '(?:[^/]+/)*';\n continue;\n }\n for (let j = 0; j < segment.length; j++) {\n const char = segment[j] ?? '';\n if (char === '*') {\n while (segment[j + 1] === '*') j++;\n source += '[^/]*';\n } else if (char === '?') {\n source += '[^/]';\n } else if (char === '[') {\n // Same classEnd rule the scanners use: a ']' right after '[' (or\n // after the '!' negation) is a literal member, not the closer.\n const close = classEnd(segment, j);\n if (close !== -1) {\n // Pass the class through, flipping glob '!' negation to '^' and\n // escaping backslashes so no body can make new RegExp throw. A\n // leading ']' must be escaped or the 'u' flag parses '[]a]' as an\n // empty class followed by stray literals; a leading '^' is a\n // literal member (glob negation is '!' only), unescaped it would\n // negate the class and relieve more than the operator named.\n let body = segment.slice(j + 1, close).replace(/\\\\/gu, '\\\\\\\\');\n const negated = body.startsWith('!');\n if (negated) body = body.slice(1);\n if (body.startsWith(']') || body.startsWith('^')) body = `\\\\${body}`;\n source += `[${negated ? '^' : ''}${body}]`;\n j = close;\n } else {\n source += RegExp.escape(char);\n }\n } else {\n source += RegExp.escape(char);\n }\n }\n if (!last) source += '/';\n }\n // Candidates are absolute paths but patterns carry no leading '/', so the\n // anchor optionally eats the root slash — `**/` then reaches from any\n // parent, exactly as matchesGlob's globstar did. '$' is strict in\n // JavaScript (it matches end-of-input only, unlike PCRE's, which also\n // matches before a final newline), so an exact-name pattern can never\n // match a '.env\\n' lookalike. The 's' flag makes the terminal-`**` `.`\n // match newlines too: filenames can contain them, and a deny ending in\n // '/**' must not fail open on 'secrets/plan\\nnotes'.\n return new RegExp(`^/?${source}$`, 'su');\n}\n\nfunction compilePatternGlobs(normalizedPattern: string): readonly string[] {\n // A leading '/' (POSIX-absolute) is rooted like a Windows drive path:\n // aliasing '/abs/secret' as '**/abs/secret' would also match\n // 'other/abs/secret', and on the allow side that is over-relief. The\n // drive-letter regex is safe on [a-z] because normalizeForMatch lowercased.\n const isRooted =\n normalizedPattern.startsWith('/') ||\n normalizedPattern.startsWith('**/') ||\n /^[a-z]:\\//u.test(normalizedPattern);\n const withoutRoot = isRooted ? '' : normalizedPattern.replace(/^\\/+/, '');\n return withoutRoot ? [normalizedPattern, `**/${withoutRoot}`] : [normalizedPattern];\n}\n\nfunction toPatternSet(patterns: readonly string[]): CompiledPatternSet {\n const pathGlobs = new Set<RegExp>();\n const nameGlobs = new Set<RegExp>();\n\n const deduped = Array.from(new Set(patterns.map((p) => p.trim()).filter((p) => p.length > 0)));\n for (const pattern of deduped) {\n const normalized = normalizeForMatch(pattern);\n const matchesPath = normalized.includes('/');\n const target = matchesPath ? pathGlobs : nameGlobs;\n const globs = matchesPath ? compilePatternGlobs(normalized) : [normalized];\n for (const glob of globs) {\n for (const expanded of expandBraces(glob)) {\n target.add(globToRegExp(expanded));\n }\n }\n }\n\n return {\n pathGlobs: [...pathGlobs],\n nameGlobs: [...nameGlobs],\n };\n}\n\nfunction matchesAnyGlob(globs: readonly RegExp[], candidate: string): boolean {\n return globs.some((glob) => glob.test(candidate));\n}\n\n// Strip NTFS alternate-data-stream suffixes (the \":stream\" after a segment)\n// before denylist matching, so `.env:secret` is caught as `.env`. Preserves the\n// drive-letter colon in the first segment of an absolute path. Also trims\n// trailing dots/spaces: Win32 strips them at the syscall boundary (a request\n// to create `.env ` actually creates `.env`), so without trimming them here the\n// exact-name patterns (`.env`, `.npmrc`) are bypassed on Windows. Mirrors the\n// precedent in getReservedDeviceName (path.ts) which trims for the same reason.\nfunction stripAlternateDataStreams(filePath: string): string {\n const parts = filePath.split(/[\\\\/]/);\n const stripped = parts.map((segment, i) => {\n if (\n i === 0 &&\n segment.length === 2 &&\n isAlpha(segment.charCodeAt(0)) &&\n segment.charCodeAt(1) === CHAR_COLON\n ) {\n return segment;\n }\n const colonIdx = segment.indexOf(':');\n const withoutStream = colonIdx !== -1 ? segment.slice(0, colonIdx) : segment;\n // Win32 strips trailing dots and spaces at the syscall boundary, so a\n // request for \".env \" creates \".env\". Strip them before denylist matching\n // or the exact-name patterns (\".env\", \".npmrc\") are bypassed on Windows.\n return trimTrailingDotsAndSpaces(withoutStream);\n });\n return stripped.join(sep);\n}\n\nconst trimTrailingDotsAndSpaces = (segment: string): string => segment.replace(/[. ]+$/, '');\n\nconst DEFAULT_SENSITIVE_PATTERNS = [\n '.env',\n '.env.*',\n '.npmrc',\n '.pypirc',\n '.netrc',\n '_netrc',\n '.git-credentials',\n '.pgpass',\n '.aws/credentials',\n '.aws/config',\n '.docker/config.json',\n '.kube/config',\n 'application_default_credentials.json',\n '.mcpregistry_*_token',\n '*.pem',\n '*.key',\n '*.p12',\n '*.pfx',\n '*.crt',\n '*.cer',\n '*id_rsa*',\n '*id_dsa*',\n '*id_ecdsa*',\n '*id_ed25519*',\n] as const;\n\n// Built-ins and operator-supplied deny entries live in separate tiers because\n// they answer to different relief switches: an allow entry (FS_ALLOWLIST /\n// --allow) can lift a built-in hit, but never an explicit deny.\n\n// Split an env pattern list on commas and newlines at brace depth 0 only and\n// outside any [...] class, so documented syntax like '*.{pem,key}' or\n// 'x[1,2].env' survives as one pattern instead of tearing apart.\nfunction splitPatternList(value: string): string[] {\n const naive = (): string[] =>\n value\n .split(/,|\\n/u)\n .map((t) => t.trim())\n .filter((t) => t.length > 0);\n const parts: string[] = [];\n let depth = 0;\n let start = 0;\n let unbalanced = false;\n for (let i = 0; i < value.length; i++) {\n const char = value[i] ?? '';\n if (char === '[') {\n const end = classEnd(value, i);\n if (end !== -1) {\n i = end;\n continue;\n }\n }\n if (char === '{') depth++;\n else if (char === '}') {\n if (depth === 0) unbalanced = true;\n else depth--;\n } else if (depth === 0 && (char === ',' || char === '\\n')) {\n parts.push(value.slice(start, i));\n start = i + 1;\n }\n }\n // An unbalanced brace would swallow every separator behind it into one\n // bogus literal pattern and silently drop the real entries — '{bad,\n // secrets/**' must not lose 'secrets/**'. A typo'd brace costs the brace\n // pattern only: fall back to the naive split.\n if (unbalanced || depth !== 0) return naive();\n parts.push(value.slice(start));\n return parts.map((t) => t.trim()).filter((t) => t.length > 0);\n}\n\nfunction buildDenyTiers(): { builtin: readonly string[]; operator: readonly string[] } {\n const allowSensitive =\n cli.allowSensitive ?? parseTrueEnvFlag(process.env['FS_ALLOW_SENSITIVE'], 'FS_ALLOW_SENSITIVE');\n const envDenylist = process.env['FS_DENYLIST']\n ? splitPatternList(process.env['FS_DENYLIST'])\n : [];\n const flagDenylist = cli.denyPatterns ?? [];\n // FS_ALLOW_SENSITIVE suppresses built-ins only; deny entries (env and --deny)\n // always apply. Set-dedupe so a pattern in both sources matches once.\n return {\n builtin: allowSensitive ? [] : DEFAULT_SENSITIVE_PATTERNS,\n operator: [...new Set([...envDenylist, ...flagDenylist])],\n };\n}\n\nfunction buildAllowPatterns(): readonly string[] {\n const envAllowlist = process.env['FS_ALLOWLIST']\n ? splitPatternList(process.env['FS_ALLOWLIST'])\n : [];\n const flagAllowlist = cli.allowPatterns ?? [];\n return [...new Set([...envAllowlist, ...flagAllowlist])];\n}\n\n/** The three pattern tiers as configured, for `--print-config`. Read-only view; the matcher compiles its own. */\nexport function describeSensitivePolicy(): {\n allowSensitive: boolean;\n builtinDeny: readonly string[];\n operatorDeny: readonly string[];\n allow: readonly string[];\n} {\n const tiers = buildDenyTiers();\n return {\n allowSensitive: tiers.builtin.length === 0,\n builtinDeny: tiers.builtin,\n operatorDeny: tiers.operator,\n allow: buildAllowPatterns(),\n };\n}\n\nconst EMPTY_PATTERN_SET: CompiledPatternSet = { pathGlobs: [], nameGlobs: [] };\n\nexport class SensitiveMatcher {\n private readonly builtin: CompiledPatternSet;\n private readonly operator: CompiledPatternSet;\n private readonly allow: CompiledPatternSet;\n\n // An explicit pattern list (tests, custom guards) is one relievable tier;\n // the default construction splits built-ins from operator-supplied\n // FS_DENYLIST/--deny entries, which allow entries can never lift.\n constructor(patterns?: readonly string[], allow?: readonly string[]) {\n if (patterns !== undefined || allow !== undefined) {\n this.builtin = toPatternSet(patterns ?? []);\n this.operator = EMPTY_PATTERN_SET;\n this.allow = toPatternSet(allow ?? []);\n } else {\n const tiers = buildDenyTiers();\n this.builtin = toPatternSet(tiers.builtin);\n this.operator = toPatternSet(tiers.operator);\n this.allow = toPatternSet(buildAllowPatterns());\n }\n }\n\n isSensitive(filePath: string): boolean {\n const pathToCheck = IS_WINDOWS ? stripAlternateDataStreams(filePath) : filePath;\n const normalizedPath = normalizeForMatch(pathToCheck);\n const name = posix.basename(normalizedPath);\n const matches = (set: CompiledPatternSet): boolean =>\n matchesAnyGlob(set.pathGlobs, normalizedPath) || matchesAnyGlob(set.nameGlobs, name);\n\n // Explicit deny entries (FS_DENYLIST/--deny) always apply.\n if (matches(this.operator)) return true;\n if (!matches(this.builtin)) return false;\n // A built-in hit is lifted only by an allow entry that matches the same\n // normalized candidate — a pattern that matches nothing relieves nothing.\n return !matches(this.allow);\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watcher-registry.d.ts","sourceRoot":"","sources":["../../src/core/watcher-registry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"watcher-registry.d.ts","sourceRoot":"","sources":["../../src/core/watcher-registry.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAK3C,eAAO,MAAM,YAAY,QAA+C,CAAC;AAMzE;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,eAAe,CAAA;CAAE,GACvE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE1D;;;;;;;GAOG;AACH,wBAAgB,qBAAqB;sBAiGV,MAAM,KAAG,OAAO;IAyGvC,wFAAwF;gBAC9E,MAAM;mBA1DI,MAAM,KAAG,IAAI;IA8DjC;;;;;;;;;;;;;;;;;;;;;;OAsBG;uBAEU,SAAS,OACf,MAAM,UACH,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,sBACF;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GACrD,OAAO,CAAC,mBAAmB,CAAC;eA6DpB,IAAI;EAmBlB;AAED,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { statSync, watch } from 'node:fs';
|
|
2
|
+
import { basename, dirname } from 'node:path';
|
|
2
3
|
import { formatUnknownErrorMessage } from "./errors.js";
|
|
3
4
|
import { extractPath } from "./file-uri.js";
|
|
4
5
|
import { Logger } from "./observability.js";
|
|
@@ -166,16 +167,30 @@ export function createWatcherRegistry() {
|
|
|
166
167
|
};
|
|
167
168
|
const attach = (uri, resolvedPath) => {
|
|
168
169
|
try {
|
|
169
|
-
//
|
|
170
|
+
// Directories: one recursive watch (children included). `{ recursive:
|
|
171
|
+
// true }` is honored on macOS, Windows, and — since Node 20.13 —
|
|
172
|
+
// Linux; `engines.node` is >=24, so all three are covered.
|
|
173
|
+
//
|
|
174
|
+
// Files: watch the PARENT directory and filter by name. A watch on
|
|
175
|
+
// the file itself binds to its inode on Linux/macOS, and every atomic
|
|
176
|
+
// write here renames a new inode over the path (fs.ts), after which
|
|
177
|
+
// the old watch never fires again (Node docs, fs.watch "Inodes").
|
|
178
|
+
// Windows' libuv already watches the parent; this makes the other two
|
|
179
|
+
// match. `filename` can be null on some platforms/events; then notify
|
|
180
|
+
// anyway — a spurious debounced notification beats a missed one.
|
|
181
|
+
//
|
|
170
182
|
// `fs.watch` async errors arrive via the 'error' event below, not as a
|
|
171
|
-
// sync throw
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
183
|
+
// sync throw; the outer catch handles sync throws (inotify
|
|
184
|
+
// exhaustion, path-race).
|
|
185
|
+
const isDirectory = statSync(resolvedPath).isDirectory();
|
|
186
|
+
const watcher = isDirectory
|
|
187
|
+
? watch(resolvedPath, { recursive: true }, () => {
|
|
188
|
+
notifyAll(uri);
|
|
189
|
+
})
|
|
190
|
+
: watch(dirname(resolvedPath), (_event, filename) => {
|
|
191
|
+
if (filename === null || filename === basename(resolvedPath))
|
|
192
|
+
notifyAll(uri);
|
|
193
|
+
});
|
|
179
194
|
watcher.on('error', (err) => {
|
|
180
195
|
Logger.warn(`Watcher error for ${uri}: ${err.message}`);
|
|
181
196
|
dropWatcher(uri, watcher);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watcher-registry.js","sourceRoot":"","sources":["../../src/core/watcher-registry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAE1C,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,0EAA0E;AAC1E,6EAA6E;AAC7E,MAAM,CAAC,MAAM,YAAY,GAAG,WAAW,CAAC,iBAAiB,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;AAEzE,SAAS,cAAc,CAAC,GAAW;IACjC,MAAM,CAAC,IAAI,CAAC,uBAAuB,GAAG,yBAAyB,YAAY,YAAY,CAAC,CAAC;AAC3F,CAAC;AAaD;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB;IACnC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAqB,CAAC;IAC9C,4EAA4E;IAC5E,0EAA0E;IAC1E,8EAA8E;IAC9E,8DAA8D;IAC9D,0EAA0E;IAC1E,6EAA6E;IAC7E,6EAA6E;IAC7E,6EAA6E;IAC7E,4EAA4E;IAC5E,6EAA6E;IAC7E,0EAA0E;IAC1E,MAAM,eAAe,GAAG,IAAI,GAAG,EAAsC,CAAC;IACtE,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAkB,CAAC;IACnD,MAAM,YAAY,GAAG,IAAI,GAAG,EAA0C,CAAC;IACvE,MAAM,cAAc,GAAG,IAAI,GAAG,EAA0B,CAAC;IACzD,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,oEAAoE;IACpE,6EAA6E;IAC7E,6EAA6E;IAC7E,0EAA0E;IAC1E,yEAAyE;IACzE,yEAAyE;IACzE,sEAAsE;IACtE,MAAM,kBAAkB,GAAG,CAAC,GAAW,EAAQ,EAAE;QAC/C,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,OAAO,KAAK,aAAa,EAAE,CAAC;YAC9B,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QACxC,CAAC;aAAM,IAAI,OAAO,KAAK,cAAc,EAAE,CAAC;YACtC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC,CAAC;IAEF,4EAA4E;IAC5E,6EAA6E;IAC7E,uEAAuE;IACvE,8EAA8E;IAC9E,2EAA2E;IAC3E,MAAM,WAAW,GAAG,CAAC,GAAW,EAAE,OAAkB,EAAQ,EAAE;QAC5D,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,OAAO,KAAK,OAAO;YAAE,OAAO;QAChC,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,KAAK,EAAE,CAAC;YACV,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;QACD,OAAO,CAAC,KAAK,EAAE,CAAC;QAChB,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACrB,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC5B,kBAAkB,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC,CAAC;IAEF,yEAAyE;IACzE,MAAM,IAAI,GAAG,CAAC,GAAW,EAAQ,EAAE;QACjC,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,KAAK,EAAE,CAAC;YACV,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;QACD,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,OAAO;YAAE,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACvC,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC5B,gBAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7B,kBAAkB,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,CAAC,GAAW,EAAQ,EAAE;QACtC,MAAM,SAAS,GAAG,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC3C,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO;QAC/C,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACzC,IAAI,QAAQ,EAAE,CAAC;YACb,YAAY,CAAC,QAAQ,CAAC,CAAC;QACzB,CAAC;QACD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC3B,MAAM,GAAG,GAAG,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACrC,IAAI,CAAC,GAAG;gBAAE,OAAO;YACjB,yEAAyE;YACzE,yEAAyE;YACzE,cAAc;YACd,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;gBACrB,IAAI,CAAC;oBACH,EAAE,CAAC,GAAG,CAAC,CAAC;gBACV,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,MAAM,CAAC,IAAI,CAAC,6BAA6B,GAAG,KAAK,yBAAyB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBACrF,CAAC;YACH,CAAC;QACH,CAAC,EAAE,EAAE,CAAC,CAAC;QACP,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,cAAc,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC,CAAC;IAEF,4EAA4E;IAC5E,uEAAuE;IAEvE,MAAM,UAAU,GAAG,CAAC,GAAW,EAAW,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAE/D,MAAM,OAAO,GAAG,GAAY,EAAE,CAAC,QAAQ,CAAC,IAAI,IAAI,YAAY,CAAC;IAE7D,2EAA2E;IAC3E,MAAM,OAAO,GAAG,CAAC,GAAW,EAAW,EAAE,CAAC,SAAS,IAAI,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,cAAc,CAAC;IAEhG,MAAM,cAAc,GAAG,CAAC,GAAW,EAAQ,EAAE;QAC3C,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;IACvC,CAAC,CAAC;IAEF;;;;;;;;;OASG;IACH,MAAM,eAAe,GAAG,CAAC,GAAW,EAAQ,EAAE;QAC5C,IAAI,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,aAAa;YAAE,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACxE,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,CAAC,GAAW,EAAE,MAA6B,EAAQ,EAAE;QACvE,IAAI,SAAS,GAAG,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACzC,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,SAAS,GAAG,IAAI,GAAG,EAAE,CAAC;YACtB,eAAe,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QACtC,CAAC;QACD,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACtB,0EAA0E;QAC1E,iEAAiE;QACjE,2EAA2E;QAC3E,+EAA+E;QAC/E,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,CAAC,GAAW,EAAQ,EAAE;QACnC,gBAAgB,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAClE,CAAC,CAAC;IAEF;;;;OAIG;IACH,MAAM,OAAO,GAAG,CAAC,GAAW,EAAQ,EAAE;QACpC,MAAM,SAAS,GAAG,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACvD,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;YAClB,gBAAgB,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;YACrC,OAAO;QACT,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,CAAC,GAAW,EAAE,YAAoB,EAAW,EAAE;QAC5D,IAAI,CAAC;YACH,qEAAqE;YACrE,uEAAuE;YACvE,sEAAsE;YACtE,mEAAmE;YACnE,uEAAuE;YACvE,mEAAmE;YACnE,MAAM,SAAS,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAC;YACvD,MAAM,OAAO,GAAG,KAAK,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,GAAG,EAAE;gBACpF,SAAS,CAAC,GAAG,CAAC,CAAC;YACjB,CAAC,CAAC,CAAC;YACH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;gBACjC,MAAM,CAAC,IAAI,CAAC,qBAAqB,GAAG,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;gBACxD,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAC5B,CAAC,CAAC,CAAC;YACH,qEAAqE;YACrE,uEAAuE;YACvE,wEAAwE;YACxE,uEAAuE;YACvE,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtB,OAAO,CAAC,KAAK,EAAE,CAAC;gBAChB,OAAO,IAAI,CAAC;YACd,CAAC;YACD,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAC3B,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,gCAAgC,GAAG,KAAK,yBAAyB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACvF,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC,CAAC;IAEF,OAAO;QACL,UAAU;QAEV,wFAAwF;QACxF,IAAI,EAAE,GAAW,EAAE,CAAC,QAAQ,CAAC,IAAI;QAEjC,OAAO;QAEP;;;;;;;;;;;;;;;;;;;;;;WAsBG;QACH,KAAK,CAAC,OAAO,CACX,SAAoB,EACpB,GAAW,EACX,MAA6B,EAC7B,EAAE,aAAa,GAAG,KAAK,KAAkC,EAAE;YAE3D,2EAA2E;YAC3E,8DAA8D;YAC9D,MAAM,IAAI,GAAG,CAAC,MAA2C,EAAuB,EAAE;gBAChF,IAAI,aAAa;oBAAE,eAAe,CAAC,GAAG,CAAC,CAAC;gBACxC,OAAO,MAAM,CAAC;YAChB,CAAC,CAAC;YAEF,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACpB,uEAAuE;gBACvE,wEAAwE;gBACxE,yCAAyC;gBACzC,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;gBACzB,MAAM,CAAC,GAAG,CAAC,CAAC;gBACZ,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;YACtB,CAAC;YACD,yEAAyE;YACzE,iDAAiD;YACjD,IAAI,OAAO,EAAE,EAAE,CAAC;gBACd,cAAc,CAAC,GAAG,CAAC,CAAC;gBACpB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;YACzC,CAAC;YAED,IAAI,aAAa;gBAAE,cAAc,CAAC,GAAG,CAAC,CAAC;YAEvC,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;YAClC,IAAI,CAAC,QAAQ;gBAAE,OAAO,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;YAE7D,IAAI,QAAgB,CAAC;YACrB,IAAI,CAAC;gBACH,QAAQ,GAAG,MAAM,SAAS,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;YAC5D,CAAC;YAAC,OAAO,KAAc,EAAE,CAAC;gBACxB,OAAO,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC;YAC5D,CAAC;YAED,qEAAqE;YACrE,wEAAwE;YACxE,+DAA+D;YAC/D,IAAI,OAAO,CAAC,GAAG,CAAC;gBAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;YACxD,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACpB,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;gBACzB,MAAM,CAAC,GAAG,CAAC,CAAC;gBACZ,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;YACtB,CAAC;YACD,IAAI,OAAO,EAAE,EAAE,CAAC;gBACd,cAAc,CAAC,GAAG,CAAC,CAAC;gBACpB,OAAO,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;YAC/C,CAAC;YAED,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACzB,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,CAAC;gBAC3B,wEAAwE;gBACxE,sEAAsE;gBACtE,8DAA8D;gBAC9D,OAAO,CAAC,GAAG,CAAC,CAAC;gBACb,OAAO,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC;YACtD,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,CAAC;YACZ,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;QACtB,CAAC;QAED,OAAO;YACL,SAAS,GAAG,IAAI,CAAC;YACjB,KAAK,MAAM,KAAK,IAAI,cAAc,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC5C,YAAY,CAAC,KAAK,CAAC,CAAC;YACtB,CAAC;YACD,cAAc,CAAC,KAAK,EAAE,CAAC;YACvB,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;gBACxC,IAAI,CAAC;oBACH,OAAO,CAAC,KAAK,EAAE,CAAC;gBAClB,CAAC;gBAAC,MAAM,CAAC;oBACP,uDAAuD;gBACzD,CAAC;YACH,CAAC;YACD,QAAQ,CAAC,KAAK,EAAE,CAAC;YACjB,eAAe,CAAC,KAAK,EAAE,CAAC;YACxB,gBAAgB,CAAC,KAAK,EAAE,CAAC;YACzB,YAAY,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { FSWatcher } from 'node:fs';\nimport { statSync, watch } from 'node:fs';\n\nimport { formatUnknownErrorMessage } from './errors.ts';\nimport { extractPath } from './file-uri.ts';\nimport { Logger } from './observability.ts';\nimport type { PathGuard } from './path.ts';\nimport { parseEnvInt } from './util.ts';\n\n// Cap concurrent file watchers to avoid exhausting OS-level watch handles\n// (e.g. Linux inotify, default ~8192/user). One subscription == one watcher.\nexport const MAX_WATCHERS = parseEnvInt('FS_MAX_WATCHERS', 256, 1, 4096);\n\nfunction warnWatcherCap(uri: string): void {\n Logger.warn(`Cannot subscribe to ${uri}: MAX_WATCHERS limit (${MAX_WATCHERS}) reached.`);\n}\n\n/**\n * `ok` means a watcher is live for this uri and `notify` is registered. Every\n * other outcome names why, and only `invalid-path` carries what\n * `validateExistingPath` threw — so `error` is unreachable on a branch that has\n * none.\n */\nexport type WatcherAttachResult =\n | { ok: true }\n | { ok: false; reason: 'stale' | 'capped' | 'bad-uri' | 'attach-failed' }\n | { ok: false; reason: 'invalid-path'; error: unknown };\n\n/**\n * Owns the uri → FSWatcher map and the subscription bookkeeping around it:\n * notify callbacks, desired subscribe/unsubscribe state, and the watcher cap —\n * plus `acquire`, the one ladder that sequences them correctly. `acquire` awaits\n * path validation midway and re-checks `isStale` and `hasWatcher` itself\n * afterwards, so callers do not: they take a lease and release it. The\n * individual primitives stay closure-private.\n */\nexport function createWatcherRegistry() {\n const watchers = new Map<string, FSWatcher>();\n // A URI may have several notification sinks and several independent leases.\n // Callback identity and lifetime are intentionally separate: the HTTP leg\n // uses one stable bus-publishing callback for every listen stream, while each\n // stream still owns one lease that must be released on close.\n // ponytail: no per-subscription id in the SDK unsubscribe contract, so we\n // ref-count by URI. A departed direct-notification callback can linger until\n // the final lease ends; sends to a closed transport already fail harmlessly.\n // The count outlives the watcher (see `dropWatcher`), so a URI whose watcher\n // errored keeps one map entry per still-unreleased lease until they drain —\n // no watcher slot, just bookkeeping. Per-subscription-id keying retires both\n // this and the unsubscribe-by-URI over-release if that churn is observed.\n const activeCallbacks = new Map<string, Set<(uri: string) => void>>();\n const subscriberCounts = new Map<string, number>();\n const desiredState = new Map<string, 'unsubscribed' | 'subscribing'>();\n const debounceTimers = new Map<string, NodeJS.Timeout>();\n let destroyed = false;\n\n // 'unsubscribed' exists only to abort a subscribe that is mid-await\n // (isStale). If no subscribe is in flight, keep no entry at all: a lingering\n // 'unsubscribed' would permanently block the modern attach path (which never\n // calls startSubscribe) and leak one map entry per URI ever watched. This\n // must be idempotent: remove()'s teardown branch calls this directly and\n // then unconditionally calls dropWatcher (which also calls this) for the\n // same uri — the second call must not clobber what the first one set.\n const settleDesiredState = (uri: string): void => {\n const current = desiredState.get(uri);\n if (current === 'subscribing') {\n desiredState.set(uri, 'unsubscribed');\n } else if (current !== 'unsubscribed') {\n desiredState.delete(uri);\n }\n };\n\n // Tears down the watcher itself, NOT the ref-count. The 'error' event calls\n // this with leases still outstanding: clearing the count there would let the\n // next release from one of those leases decrement from zero and drop a\n // watcher a later subscriber re-established for the same URI. `drop` below is\n // the only path that ends leases, and it is the only one that clears them.\n const dropWatcher = (uri: string, watcher: FSWatcher): void => {\n const current = watchers.get(uri);\n if (current !== watcher) return;\n const timer = debounceTimers.get(uri);\n if (timer) {\n clearTimeout(timer);\n debounceTimers.delete(uri);\n }\n watcher.close();\n watchers.delete(uri);\n activeCallbacks.delete(uri);\n settleDesiredState(uri);\n };\n\n /** The last lease ended: drop the watcher and every trace of the URI. */\n const drop = (uri: string): void => {\n const timer = debounceTimers.get(uri);\n if (timer) {\n clearTimeout(timer);\n debounceTimers.delete(uri);\n }\n const watcher = watchers.get(uri);\n if (watcher) dropWatcher(uri, watcher);\n activeCallbacks.delete(uri);\n subscriberCounts.delete(uri);\n settleDesiredState(uri);\n };\n\n const notifyAll = (uri: string): void => {\n const callbacks = activeCallbacks.get(uri);\n if (!callbacks || callbacks.size === 0) return;\n const existing = debounceTimers.get(uri);\n if (existing) {\n clearTimeout(existing);\n }\n const timer = setTimeout(() => {\n debounceTimers.delete(uri);\n const cbs = activeCallbacks.get(uri);\n if (!cbs) return;\n // One debounce timer per URI — fan out to every subscriber inside it, so\n // a burst of changes still fires one notification round per URI, not per\n // subscriber.\n for (const cb of cbs) {\n try {\n cb(uri);\n } catch (err) {\n Logger.warn(`Notify callback error for ${uri}: ${formatUnknownErrorMessage(err)}`);\n }\n }\n }, 50);\n timer.unref();\n debounceTimers.set(uri, timer);\n };\n\n // The primitives below are hoisted out of the returned literal so `acquire`\n // can sequence them from inside the closure. They are closure-private.\n\n const hasWatcher = (uri: string): boolean => watchers.has(uri);\n\n const isAtCap = (): boolean => watchers.size >= MAX_WATCHERS;\n\n /** The registry was destroyed, or this uri was unsubscribed, mid-await. */\n const isStale = (uri: string): boolean => destroyed || desiredState.get(uri) === 'unsubscribed';\n\n const startSubscribe = (uri: string): void => {\n desiredState.set(uri, 'subscribing');\n };\n\n /**\n * A subscribe that declared intent and then failed — bad URI, unvalidatable\n * path, cap, fs.watch refusal — drops the entry entirely. Not\n * `settleDesiredState`: that maps 'subscribing' onto 'unsubscribed' to abort\n * an attach still mid-await, and a *finished* failure leaving 'unsubscribed'\n * behind would make `isStale` true forever, so the modern attach path (which\n * never calls `startSubscribe`) could never watch this uri again. Only\n * clears its own 'subscribing' — a concurrent unsubscribe that already set\n * 'unsubscribed' still wins.\n */\n const cancelSubscribe = (uri: string): void => {\n if (desiredState.get(uri) === 'subscribing') desiredState.delete(uri);\n };\n\n const addCallback = (uri: string, notify: (uri: string) => void): void => {\n let callbacks = activeCallbacks.get(uri);\n if (!callbacks) {\n callbacks = new Set();\n activeCallbacks.set(uri, callbacks);\n }\n callbacks.add(notify);\n // A live subscription needs no marker: every reader treats \"no entry\" the\n // way it treated 'subscribed'. Clearing here is what retires the\n // 'subscribing' marker `startSubscribe` set, so teardown deletes the entry\n // instead of mapping it onto a 'unsubscribed' that would block a later attach.\n desiredState.delete(uri);\n };\n\n const retain = (uri: string): void => {\n subscriberCounts.set(uri, (subscriberCounts.get(uri) ?? 0) + 1);\n };\n\n /**\n * Ref-count by URI: only tear down the shared watcher when the last lease\n * ends. Callback identity is independent from this count. A release with no\n * lease outstanding (a rolled-back attach) drops the watcher outright.\n */\n const release = (uri: string): void => {\n const remaining = (subscriberCounts.get(uri) ?? 0) - 1;\n if (remaining > 0) {\n subscriberCounts.set(uri, remaining);\n return;\n }\n drop(uri);\n };\n\n const attach = (uri: string, resolvedPath: string): boolean => {\n try {\n // Watch directories recursively (children included) and files as-is.\n // `fs.watch` async errors arrive via the 'error' event below, not as a\n // sync throw, so no recursive-fallback try/catch is needed here — the\n // outer catch handles sync throws (inotify exhaustion, path-race).\n // `{ recursive: true }` is honored on macOS, Windows, and — since Node\n // 20.13 — Linux; `engines.node` is >=24, so all three are covered.\n const recursive = statSync(resolvedPath).isDirectory();\n const watcher = watch(resolvedPath, recursive ? { recursive: true } : undefined, () => {\n notifyAll(uri);\n });\n watcher.on('error', (err: Error) => {\n Logger.warn(`Watcher error for ${uri}: ${err.message}`);\n dropWatcher(uri, watcher);\n });\n // Two attaches that both cleared `hasWatcher` before either finished\n // validating land here for the same uri. Keep the one already wired to\n // the callback set and close this one — overwriting the map entry would\n // strand the first watcher's fd with nothing left holding a reference.\n if (watchers.has(uri)) {\n watcher.close();\n return true;\n }\n watchers.set(uri, watcher);\n return true;\n } catch (err) {\n Logger.error(`Failed to create watcher for ${uri}: ${formatUnknownErrorMessage(err)}`);\n return false;\n }\n };\n\n return {\n hasWatcher,\n\n /** Live watcher count, for pre-checking a batched listen against remaining capacity. */\n size: (): number => watchers.size,\n\n release,\n\n /**\n * The one attach ladder both watcher entry points run: `resources/subscribe`\n * (2025 era) and the `subscriptions/listen` filter (modern era, HTTP and\n * stdio). Never throws — it reports the outcome and lets each caller decide\n * what that is worth: subscribe owes its caller a precise error, and listen\n * (`prepareListenWatchers`) treats the batch as all-or-nothing, releasing\n * every lease it already took and rejecting the request. Idempotent per URI\n * — a second call for an already-watched URI re-registers the notify\n * callback (one watcher per URI).\n *\n * `markSubscribe` is the one branch that differs: only `resources/subscribe`\n * declares desired state (what `isStale` aborts against). The listen path\n * must not, or a rejected attach past that point strands a `'subscribing'`\n * entry nothing settles. Whoever declares it, this function settles it:\n * every failing exit past that point cancels the declaration, so no uri is\n * poisoned for a later attach.\n *\n * Every `ok` return takes one lease; lifetime is the caller's to manage.\n * HTTP releases when the listen response closes. Stdio tracks the listen\n * request ID and releases on cancellation, SDK rejection, or graceful\n * completion. In both legs the ref-count is by URI, so a watcher another\n * stream still holds survives.\n */\n async acquire(\n pathGuard: PathGuard,\n uri: string,\n notify: (uri: string) => void,\n { markSubscribe = false }: { markSubscribe?: boolean } = {},\n ): Promise<WatcherAttachResult> {\n // Every failing exit below routes through here, so the declaration made by\n // `startSubscribe` can never outlive the attach that made it.\n const fail = (result: WatcherAttachResult & { ok: false }): WatcherAttachResult => {\n if (markSubscribe) cancelSubscribe(uri);\n return result;\n };\n\n if (hasWatcher(uri)) {\n // A watcher already tracks this uri; just (re)register the callback so\n // its change events reach the new subscriber. No validation or cap work\n // is needed for an already-live watcher.\n addCallback(uri, notify);\n retain(uri);\n return { ok: true };\n }\n // A cap hit before validation and one found after the await are the same\n // condition, and both are reported the same way.\n if (isAtCap()) {\n warnWatcherCap(uri);\n return { ok: false, reason: 'capped' };\n }\n\n if (markSubscribe) startSubscribe(uri);\n\n const filePath = extractPath(uri);\n if (!filePath) return fail({ ok: false, reason: 'bad-uri' });\n\n let resolved: string;\n try {\n resolved = await pathGuard.validateExistingPath(filePath);\n } catch (error: unknown) {\n return fail({ ok: false, reason: 'invalid-path', error });\n }\n\n // Re-check what the await could have changed. A stale uri is the one\n // failure that must NOT cancel: an unsubscribe landed mid-await and its\n // 'unsubscribed' marker is the thing that aborted this attach.\n if (isStale(uri)) return { ok: false, reason: 'stale' };\n if (hasWatcher(uri)) {\n addCallback(uri, notify);\n retain(uri);\n return { ok: true };\n }\n if (isAtCap()) {\n warnWatcherCap(uri);\n return fail({ ok: false, reason: 'capped' });\n }\n\n addCallback(uri, notify);\n if (!attach(uri, resolved)) {\n // fs.watch threw (inotify exhaustion, or a race deleted the path): roll\n // back so no dangling callback is left believing a watcher exists. No\n // lease was taken yet, so `release` drops the entry outright.\n release(uri);\n return fail({ ok: false, reason: 'attach-failed' });\n }\n retain(uri);\n return { ok: true };\n },\n\n destroy(): void {\n destroyed = true;\n for (const timer of debounceTimers.values()) {\n clearTimeout(timer);\n }\n debounceTimers.clear();\n for (const watcher of watchers.values()) {\n try {\n watcher.close();\n } catch {\n /* ignore close errors so all watchers are attempted */\n }\n }\n watchers.clear();\n activeCallbacks.clear();\n subscriberCounts.clear();\n desiredState.clear();\n },\n };\n}\n\nexport type WatcherRegistry = ReturnType<typeof createWatcherRegistry>;\n"]}
|
|
1
|
+
{"version":3,"file":"watcher-registry.js","sourceRoot":"","sources":["../../src/core/watcher-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FSWatcher } from 'node:fs';\nimport { statSync, watch } from 'node:fs';\nimport { basename, dirname } from 'node:path';\n\nimport { formatUnknownErrorMessage } from './errors.ts';\nimport { extractPath } from './file-uri.ts';\nimport { Logger } from './observability.ts';\nimport type { PathGuard } from './path.ts';\nimport { parseEnvInt } from './util.ts';\n\n// Cap concurrent file watchers to avoid exhausting OS-level watch handles\n// (e.g. Linux inotify, default ~8192/user). One subscription == one watcher.\nexport const MAX_WATCHERS = parseEnvInt('FS_MAX_WATCHERS', 256, 1, 4096);\n\nfunction warnWatcherCap(uri: string): void {\n Logger.warn(`Cannot subscribe to ${uri}: MAX_WATCHERS limit (${MAX_WATCHERS}) reached.`);\n}\n\n/**\n * `ok` means a watcher is live for this uri and `notify` is registered. Every\n * other outcome names why, and only `invalid-path` carries what\n * `validateExistingPath` threw — so `error` is unreachable on a branch that has\n * none.\n */\nexport type WatcherAttachResult =\n | { ok: true }\n | { ok: false; reason: 'stale' | 'capped' | 'bad-uri' | 'attach-failed' }\n | { ok: false; reason: 'invalid-path'; error: unknown };\n\n/**\n * Owns the uri → FSWatcher map and the subscription bookkeeping around it:\n * notify callbacks, desired subscribe/unsubscribe state, and the watcher cap —\n * plus `acquire`, the one ladder that sequences them correctly. `acquire` awaits\n * path validation midway and re-checks `isStale` and `hasWatcher` itself\n * afterwards, so callers do not: they take a lease and release it. The\n * individual primitives stay closure-private.\n */\nexport function createWatcherRegistry() {\n const watchers = new Map<string, FSWatcher>();\n // A URI may have several notification sinks and several independent leases.\n // Callback identity and lifetime are intentionally separate: the HTTP leg\n // uses one stable bus-publishing callback for every listen stream, while each\n // stream still owns one lease that must be released on close.\n // ponytail: no per-subscription id in the SDK unsubscribe contract, so we\n // ref-count by URI. A departed direct-notification callback can linger until\n // the final lease ends; sends to a closed transport already fail harmlessly.\n // The count outlives the watcher (see `dropWatcher`), so a URI whose watcher\n // errored keeps one map entry per still-unreleased lease until they drain —\n // no watcher slot, just bookkeeping. Per-subscription-id keying retires both\n // this and the unsubscribe-by-URI over-release if that churn is observed.\n const activeCallbacks = new Map<string, Set<(uri: string) => void>>();\n const subscriberCounts = new Map<string, number>();\n const desiredState = new Map<string, 'unsubscribed' | 'subscribing'>();\n const debounceTimers = new Map<string, NodeJS.Timeout>();\n let destroyed = false;\n\n // 'unsubscribed' exists only to abort a subscribe that is mid-await\n // (isStale). If no subscribe is in flight, keep no entry at all: a lingering\n // 'unsubscribed' would permanently block the modern attach path (which never\n // calls startSubscribe) and leak one map entry per URI ever watched. This\n // must be idempotent: remove()'s teardown branch calls this directly and\n // then unconditionally calls dropWatcher (which also calls this) for the\n // same uri — the second call must not clobber what the first one set.\n const settleDesiredState = (uri: string): void => {\n const current = desiredState.get(uri);\n if (current === 'subscribing') {\n desiredState.set(uri, 'unsubscribed');\n } else if (current !== 'unsubscribed') {\n desiredState.delete(uri);\n }\n };\n\n // Tears down the watcher itself, NOT the ref-count. The 'error' event calls\n // this with leases still outstanding: clearing the count there would let the\n // next release from one of those leases decrement from zero and drop a\n // watcher a later subscriber re-established for the same URI. `drop` below is\n // the only path that ends leases, and it is the only one that clears them.\n const dropWatcher = (uri: string, watcher: FSWatcher): void => {\n const current = watchers.get(uri);\n if (current !== watcher) return;\n const timer = debounceTimers.get(uri);\n if (timer) {\n clearTimeout(timer);\n debounceTimers.delete(uri);\n }\n watcher.close();\n watchers.delete(uri);\n activeCallbacks.delete(uri);\n settleDesiredState(uri);\n };\n\n /** The last lease ended: drop the watcher and every trace of the URI. */\n const drop = (uri: string): void => {\n const timer = debounceTimers.get(uri);\n if (timer) {\n clearTimeout(timer);\n debounceTimers.delete(uri);\n }\n const watcher = watchers.get(uri);\n if (watcher) dropWatcher(uri, watcher);\n activeCallbacks.delete(uri);\n subscriberCounts.delete(uri);\n settleDesiredState(uri);\n };\n\n const notifyAll = (uri: string): void => {\n const callbacks = activeCallbacks.get(uri);\n if (!callbacks || callbacks.size === 0) return;\n const existing = debounceTimers.get(uri);\n if (existing) {\n clearTimeout(existing);\n }\n const timer = setTimeout(() => {\n debounceTimers.delete(uri);\n const cbs = activeCallbacks.get(uri);\n if (!cbs) return;\n // One debounce timer per URI — fan out to every subscriber inside it, so\n // a burst of changes still fires one notification round per URI, not per\n // subscriber.\n for (const cb of cbs) {\n try {\n cb(uri);\n } catch (err) {\n Logger.warn(`Notify callback error for ${uri}: ${formatUnknownErrorMessage(err)}`);\n }\n }\n }, 50);\n timer.unref();\n debounceTimers.set(uri, timer);\n };\n\n // The primitives below are hoisted out of the returned literal so `acquire`\n // can sequence them from inside the closure. They are closure-private.\n\n const hasWatcher = (uri: string): boolean => watchers.has(uri);\n\n const isAtCap = (): boolean => watchers.size >= MAX_WATCHERS;\n\n /** The registry was destroyed, or this uri was unsubscribed, mid-await. */\n const isStale = (uri: string): boolean => destroyed || desiredState.get(uri) === 'unsubscribed';\n\n const startSubscribe = (uri: string): void => {\n desiredState.set(uri, 'subscribing');\n };\n\n /**\n * A subscribe that declared intent and then failed — bad URI, unvalidatable\n * path, cap, fs.watch refusal — drops the entry entirely. Not\n * `settleDesiredState`: that maps 'subscribing' onto 'unsubscribed' to abort\n * an attach still mid-await, and a *finished* failure leaving 'unsubscribed'\n * behind would make `isStale` true forever, so the modern attach path (which\n * never calls `startSubscribe`) could never watch this uri again. Only\n * clears its own 'subscribing' — a concurrent unsubscribe that already set\n * 'unsubscribed' still wins.\n */\n const cancelSubscribe = (uri: string): void => {\n if (desiredState.get(uri) === 'subscribing') desiredState.delete(uri);\n };\n\n const addCallback = (uri: string, notify: (uri: string) => void): void => {\n let callbacks = activeCallbacks.get(uri);\n if (!callbacks) {\n callbacks = new Set();\n activeCallbacks.set(uri, callbacks);\n }\n callbacks.add(notify);\n // A live subscription needs no marker: every reader treats \"no entry\" the\n // way it treated 'subscribed'. Clearing here is what retires the\n // 'subscribing' marker `startSubscribe` set, so teardown deletes the entry\n // instead of mapping it onto a 'unsubscribed' that would block a later attach.\n desiredState.delete(uri);\n };\n\n const retain = (uri: string): void => {\n subscriberCounts.set(uri, (subscriberCounts.get(uri) ?? 0) + 1);\n };\n\n /**\n * Ref-count by URI: only tear down the shared watcher when the last lease\n * ends. Callback identity is independent from this count. A release with no\n * lease outstanding (a rolled-back attach) drops the watcher outright.\n */\n const release = (uri: string): void => {\n const remaining = (subscriberCounts.get(uri) ?? 0) - 1;\n if (remaining > 0) {\n subscriberCounts.set(uri, remaining);\n return;\n }\n drop(uri);\n };\n\n const attach = (uri: string, resolvedPath: string): boolean => {\n try {\n // Directories: one recursive watch (children included). `{ recursive:\n // true }` is honored on macOS, Windows, and — since Node 20.13 —\n // Linux; `engines.node` is >=24, so all three are covered.\n //\n // Files: watch the PARENT directory and filter by name. A watch on\n // the file itself binds to its inode on Linux/macOS, and every atomic\n // write here renames a new inode over the path (fs.ts), after which\n // the old watch never fires again (Node docs, fs.watch \"Inodes\").\n // Windows' libuv already watches the parent; this makes the other two\n // match. `filename` can be null on some platforms/events; then notify\n // anyway — a spurious debounced notification beats a missed one.\n //\n // `fs.watch` async errors arrive via the 'error' event below, not as a\n // sync throw; the outer catch handles sync throws (inotify\n // exhaustion, path-race).\n const isDirectory = statSync(resolvedPath).isDirectory();\n const watcher = isDirectory\n ? watch(resolvedPath, { recursive: true }, () => {\n notifyAll(uri);\n })\n : watch(dirname(resolvedPath), (_event, filename) => {\n if (filename === null || filename === basename(resolvedPath)) notifyAll(uri);\n });\n watcher.on('error', (err: Error) => {\n Logger.warn(`Watcher error for ${uri}: ${err.message}`);\n dropWatcher(uri, watcher);\n });\n // Two attaches that both cleared `hasWatcher` before either finished\n // validating land here for the same uri. Keep the one already wired to\n // the callback set and close this one — overwriting the map entry would\n // strand the first watcher's fd with nothing left holding a reference.\n if (watchers.has(uri)) {\n watcher.close();\n return true;\n }\n watchers.set(uri, watcher);\n return true;\n } catch (err) {\n Logger.error(`Failed to create watcher for ${uri}: ${formatUnknownErrorMessage(err)}`);\n return false;\n }\n };\n\n return {\n hasWatcher,\n\n /** Live watcher count, for pre-checking a batched listen against remaining capacity. */\n size: (): number => watchers.size,\n\n release,\n\n /**\n * The one attach ladder both watcher entry points run: `resources/subscribe`\n * (2025 era) and the `subscriptions/listen` filter (modern era, HTTP and\n * stdio). Never throws — it reports the outcome and lets each caller decide\n * what that is worth: subscribe owes its caller a precise error, and listen\n * (`prepareListenWatchers`) treats the batch as all-or-nothing, releasing\n * every lease it already took and rejecting the request. Idempotent per URI\n * — a second call for an already-watched URI re-registers the notify\n * callback (one watcher per URI).\n *\n * `markSubscribe` is the one branch that differs: only `resources/subscribe`\n * declares desired state (what `isStale` aborts against). The listen path\n * must not, or a rejected attach past that point strands a `'subscribing'`\n * entry nothing settles. Whoever declares it, this function settles it:\n * every failing exit past that point cancels the declaration, so no uri is\n * poisoned for a later attach.\n *\n * Every `ok` return takes one lease; lifetime is the caller's to manage.\n * HTTP releases when the listen response closes. Stdio tracks the listen\n * request ID and releases on cancellation, SDK rejection, or graceful\n * completion. In both legs the ref-count is by URI, so a watcher another\n * stream still holds survives.\n */\n async acquire(\n pathGuard: PathGuard,\n uri: string,\n notify: (uri: string) => void,\n { markSubscribe = false }: { markSubscribe?: boolean } = {},\n ): Promise<WatcherAttachResult> {\n // Every failing exit below routes through here, so the declaration made by\n // `startSubscribe` can never outlive the attach that made it.\n const fail = (result: WatcherAttachResult & { ok: false }): WatcherAttachResult => {\n if (markSubscribe) cancelSubscribe(uri);\n return result;\n };\n\n if (hasWatcher(uri)) {\n // A watcher already tracks this uri; just (re)register the callback so\n // its change events reach the new subscriber. No validation or cap work\n // is needed for an already-live watcher.\n addCallback(uri, notify);\n retain(uri);\n return { ok: true };\n }\n // A cap hit before validation and one found after the await are the same\n // condition, and both are reported the same way.\n if (isAtCap()) {\n warnWatcherCap(uri);\n return { ok: false, reason: 'capped' };\n }\n\n if (markSubscribe) startSubscribe(uri);\n\n const filePath = extractPath(uri);\n if (!filePath) return fail({ ok: false, reason: 'bad-uri' });\n\n let resolved: string;\n try {\n resolved = await pathGuard.validateExistingPath(filePath);\n } catch (error: unknown) {\n return fail({ ok: false, reason: 'invalid-path', error });\n }\n\n // Re-check what the await could have changed. A stale uri is the one\n // failure that must NOT cancel: an unsubscribe landed mid-await and its\n // 'unsubscribed' marker is the thing that aborted this attach.\n if (isStale(uri)) return { ok: false, reason: 'stale' };\n if (hasWatcher(uri)) {\n addCallback(uri, notify);\n retain(uri);\n return { ok: true };\n }\n if (isAtCap()) {\n warnWatcherCap(uri);\n return fail({ ok: false, reason: 'capped' });\n }\n\n addCallback(uri, notify);\n if (!attach(uri, resolved)) {\n // fs.watch threw (inotify exhaustion, or a race deleted the path): roll\n // back so no dangling callback is left believing a watcher exists. No\n // lease was taken yet, so `release` drops the entry outright.\n release(uri);\n return fail({ ok: false, reason: 'attach-failed' });\n }\n retain(uri);\n return { ok: true };\n },\n\n destroy(): void {\n destroyed = true;\n for (const timer of debounceTimers.values()) {\n clearTimeout(timer);\n }\n debounceTimers.clear();\n for (const watcher of watchers.values()) {\n try {\n watcher.close();\n } catch {\n /* ignore close errors so all watchers are attempted */\n }\n }\n watchers.clear();\n activeCallbacks.clear();\n subscriberCounts.clear();\n desiredState.clear();\n },\n };\n}\n\nexport type WatcherRegistry = ReturnType<typeof createWatcherRegistry>;\n"]}
|
package/dist/instructions.js
CHANGED
|
@@ -46,10 +46,10 @@ export function buildSectionsRecord(readOnly) {
|
|
|
46
46
|
'```',
|
|
47
47
|
`allowed_roots: Startup roots come from CLI paths, FS_ALLOWED_DIRS, or --allow-cwd; accepted modern grants are additive. Call ${LIST_ROOTS.name} to read the current set.`,
|
|
48
48
|
'legacy_roots: Legacy clients may additionally seed roots through the deprecated roots/list flow.',
|
|
49
|
-
'sensitive_paths: Sensitive file paths (.env, *.pem, *id_rsa*) are denied by default.',
|
|
50
|
-
`enforced_limits: max file size ${maxFileMb} MB, file search cap ${MAX_SEARCH_RESULTS} results, content search
|
|
49
|
+
'sensitive_paths: Sensitive file paths (.env, *.pem, SSH private keys such as *id_rsa* and *id_ed25519*, .netrc, .git-credentials) are denied by default.',
|
|
50
|
+
`enforced_limits: max file size ${maxFileMb} MB, file search cap ${MAX_SEARCH_RESULTS} results, content search scans up to ${MAX_SEARCH_RESULTS} matches, paged by maxResults (default ${DEFAULT_SEARCH_CONTENT_RESULTS}).`,
|
|
51
51
|
'ephemeral_results: When a result carries a resource_link or a resourceUri (in structuredContent or _meta), call resources/read immediately — cached results are ephemeral and expire after ~60 seconds, eviction, or restart.',
|
|
52
|
-
'pagination: nextCursor appears in the result text and in _meta, backed by a snapshot on the same ~60s clock.
|
|
52
|
+
'pagination: nextCursor appears in the result text and in _meta, backed by a snapshot on the same ~60s clock. Send the printed Next page call as-is (the original arguments plus cursor); if a cursor is rejected, start again without one. resourceUri appears on the first page only.',
|
|
53
53
|
'```',
|
|
54
54
|
].join('\n'),
|
|
55
55
|
error_recovery: [
|
|
@@ -58,7 +58,7 @@ export function buildSectionsRecord(readOnly) {
|
|
|
58
58
|
`ACCESS_DENIED: Run ${LIST_ROOTS.name}; configure a missing startup root or retry a concrete path and approve the grant.`,
|
|
59
59
|
`NOT_FOUND: Run ${LIST.name} or ${FIND_FILES.name} to verify the path.`,
|
|
60
60
|
`TOO_LARGE: Use ${READ.name} with head/tail or startLine/endLine, or split across several calls.`,
|
|
61
|
-
'TIMEOUT:
|
|
61
|
+
'TIMEOUT: Narrow path or pattern, or lower maxDepth; maxResults only sizes a page.',
|
|
62
62
|
'INVALID_INPUT: Re-read the tool schema in tools/list.',
|
|
63
63
|
'```',
|
|
64
64
|
].join('\n'),
|
package/dist/instructions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instructions.js","sourceRoot":"","sources":["../src/instructions.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,UAAU,EACV,IAAI,EACJ,UAAU,EACV,mBAAmB,EACnB,IAAI,EACJ,WAAW,EACX,IAAI,GACL,MAAM,kBAAkB,CAAC;AAE1B,SAAS,kBAAkB,CAAC,QAAiB;IAC3C,MAAM,IAAI,GAAyB;QACjC,CAAC,UAAU,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;QAC3D,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;QAC1C,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;KACtB,CAAC;IAEF,4EAA4E;IAC5E,4EAA4E;IAC5E,iCAAiC;IACjC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,mBAAmB,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC3E,OAAO,WAAW,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC;AAClD,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG,yBAAyB,CAAC;AAE1D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAC/B,6DAA6D;IAC7D,kHAAkH,CAAC;AAErH,MAAM,UAAU,mBAAmB,CAAC,QAAiB;IACnD,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,kBAAkB,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC;IACjE,OAAO;QACL,UAAU,EAAE;YACV,aAAa;YACb,KAAK;YACL,gBAAgB,UAAU,CAAC,IAAI,0EAA0E;YACzG,qKAAqK;YACrK,wCAAwC,IAAI,CAAC,IAAI,OAAO,UAAU,CAAC,IAAI,uBAAuB;YAC9F,KAAK;SACN,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,cAAc,EAAE;YACd,iBAAiB;YACjB,kBAAkB,CAAC,QAAQ,CAAC;YAC5B,EAAE;YACF,kEAAkE;SACnE,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,WAAW,EAAE;YACX,cAAc;YACd,KAAK;YACL,gIAAgI,UAAU,CAAC,IAAI,2BAA2B;YAC1K,kGAAkG;YAClG,
|
|
1
|
+
{"version":3,"file":"instructions.js","sourceRoot":"","sources":["../src/instructions.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,UAAU,EACV,IAAI,EACJ,UAAU,EACV,mBAAmB,EACnB,IAAI,EACJ,WAAW,EACX,IAAI,GACL,MAAM,kBAAkB,CAAC;AAE1B,SAAS,kBAAkB,CAAC,QAAiB;IAC3C,MAAM,IAAI,GAAyB;QACjC,CAAC,UAAU,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;QAC3D,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;QAC1C,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;KACtB,CAAC;IAEF,4EAA4E;IAC5E,4EAA4E;IAC5E,iCAAiC;IACjC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,mBAAmB,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC3E,OAAO,WAAW,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC;AAClD,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG,yBAAyB,CAAC;AAE1D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAC/B,6DAA6D;IAC7D,kHAAkH,CAAC;AAErH,MAAM,UAAU,mBAAmB,CAAC,QAAiB;IACnD,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,kBAAkB,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC;IACjE,OAAO;QACL,UAAU,EAAE;YACV,aAAa;YACb,KAAK;YACL,gBAAgB,UAAU,CAAC,IAAI,0EAA0E;YACzG,qKAAqK;YACrK,wCAAwC,IAAI,CAAC,IAAI,OAAO,UAAU,CAAC,IAAI,uBAAuB;YAC9F,KAAK;SACN,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,cAAc,EAAE;YACd,iBAAiB;YACjB,kBAAkB,CAAC,QAAQ,CAAC;YAC5B,EAAE;YACF,kEAAkE;SACnE,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,WAAW,EAAE;YACX,cAAc;YACd,KAAK;YACL,gIAAgI,UAAU,CAAC,IAAI,2BAA2B;YAC1K,kGAAkG;YAClG,0JAA0J;YAC1J,kCAAkC,SAAS,wBAAwB,kBAAkB,wCAAwC,kBAAkB,0CAA0C,8BAA8B,IAAI;YAC3N,+NAA+N;YAC/N,wRAAwR;YACxR,KAAK;SACN,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,cAAc,EAAE;YACd,iBAAiB;YACjB,KAAK;YACL,sBAAsB,UAAU,CAAC,IAAI,oFAAoF;YACzH,kBAAkB,IAAI,CAAC,IAAI,OAAO,UAAU,CAAC,IAAI,sBAAsB;YACvE,kBAAkB,IAAI,CAAC,IAAI,sEAAsE;YACjG,mFAAmF;YACnF,uDAAuD;YACvD,KAAK;SACN,CAAC,IAAI,CAAC,IAAI,CAAC;KACb,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,QAAgC;IAC7D,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;AACvD,CAAC","sourcesContent":["import {\n DEFAULT_SEARCH_CONTENT_RESULTS,\n getMaxTextFileSize,\n MAX_SEARCH_RESULTS,\n} from './core/util.ts';\nimport {\n FIND_FILES,\n LIST,\n LIST_ROOTS,\n MUTATING_TOOL_NAMES,\n READ,\n SEARCH_TEXT,\n STAT,\n} from './tools/index.ts';\n\nfunction buildToolsOverview(readOnly: boolean): string {\n const rows: [string, string[]][] = [\n ['Navigate', [LIST_ROOTS.name, LIST.name, FIND_FILES.name]],\n ['Inspect', [STAT.name, SEARCH_TEXT.name]],\n ['Read', [READ.name]],\n ];\n\n // Under --read-only the mutating tools are never registered, so advertising\n // them here would point the model at tools that are not there. Drop the row\n // rather than emit an empty one.\n if (!readOnly) {\n rows.push(['Write', [...MUTATING_TOOL_NAMES]]);\n }\n\n const rowLines = rows.map(([cat, names]) => `${cat}: ${names.join(', ')}`);\n return `\\`\\`\\`\\n${rowLines.join('\\n')}\\n\\`\\`\\``;\n}\n\nexport const INSTRUCTIONS_URI = 'internal://instructions';\n\n/**\n * The one statement of what this server is and how to approach it. The server's\n * `instructions`, the `get-help` prompt, and the instructions resource all\n * describe the same document, and three hand-maintained paraphrases of it drift\n * against each other and cost the client the same sentence three times.\n */\nexport const INSTRUCTIONS_SUMMARY =\n 'Navigation guide for filesystem-mcp tools and constraints. ' +\n 'Use list_roots to inspect configured or accepted roots, then list/find_files -> stat -> read. Never guess paths.';\n\nexport function buildSectionsRecord(readOnly: boolean): Record<string, string> {\n const maxFileMb = Math.floor(getMaxTextFileSize() / 1024 / 1024);\n return {\n guidelines: [\n 'Guidelines:',\n '```',\n `root_access: ${LIST_ROOTS.name} lists configured or accepted roots; every other tool is scoped to them.`,\n 'modern_root_grants: Modern clients do not automatically send workspace roots. Call a tool with a concrete path and approve its grant when elicitation is available.',\n `path_resolution: Confirm a path with ${LIST.name} or ${FIND_FILES.name} before acting on it.`,\n '```',\n ].join('\\n'),\n tools_overview: [\n 'Tools Overview:',\n buildToolsOverview(readOnly),\n '',\n 'Full schemas, descriptions, and annotations are in `tools/list`.',\n ].join('\\n'),\n constraints: [\n 'Constraints:',\n '```',\n `allowed_roots: Startup roots come from CLI paths, FS_ALLOWED_DIRS, or --allow-cwd; accepted modern grants are additive. Call ${LIST_ROOTS.name} to read the current set.`,\n 'legacy_roots: Legacy clients may additionally seed roots through the deprecated roots/list flow.',\n 'sensitive_paths: Sensitive file paths (.env, *.pem, SSH private keys such as *id_rsa* and *id_ed25519*, .netrc, .git-credentials) are denied by default.',\n `enforced_limits: max file size ${maxFileMb} MB, file search cap ${MAX_SEARCH_RESULTS} results, content search scans up to ${MAX_SEARCH_RESULTS} matches, paged by maxResults (default ${DEFAULT_SEARCH_CONTENT_RESULTS}).`,\n 'ephemeral_results: When a result carries a resource_link or a resourceUri (in structuredContent or _meta), call resources/read immediately — cached results are ephemeral and expire after ~60 seconds, eviction, or restart.',\n 'pagination: nextCursor appears in the result text and in _meta, backed by a snapshot on the same ~60s clock. Send the printed Next page call as-is (the original arguments plus cursor); if a cursor is rejected, start again without one. resourceUri appears on the first page only.',\n '```',\n ].join('\\n'),\n error_recovery: [\n 'Error Recovery:',\n '```',\n `ACCESS_DENIED: Run ${LIST_ROOTS.name}; configure a missing startup root or retry a concrete path and approve the grant.`,\n `NOT_FOUND: Run ${LIST.name} or ${FIND_FILES.name} to verify the path.`,\n `TOO_LARGE: Use ${READ.name} with head/tail or startLine/endLine, or split across several calls.`,\n 'TIMEOUT: Narrow path or pattern, or lower maxDepth; maxResults only sizes a page.',\n 'INVALID_INPUT: Re-read the tool schema in tools/list.',\n '```',\n ].join('\\n'),\n };\n}\n\nexport function renderSections(sections: Record<string, string>): string {\n return `\\n${Object.values(sections).join('\\n\\n')}\\n`;\n}\n"]}
|
package/dist/resources.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,gCAAgC,EAChC,SAAS,EACT,kBAAkB,EAClB,QAAQ,EAER,IAAI,EACJ,aAAa,EACb,cAAc,EAGf,MAAM,8BAA8B,CAAC;AAoBtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,4BAA4B,CAAC;AAYpC,MAAM,WAAW,2BAA2B;IAC1C,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,SAAS,CAAC;IACrB,8FAA8F;IAC9F,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,qFAAqF;IACrF,GAAG,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B;;;;OAIG;IACH,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;CACnC;AAED,KAAK,qBAAqB,GAAG,IAAI,CAAC,2BAA2B,EAAE,WAAW,GAAG,UAAU,CAAC,GAAG;IACzF,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAoBF,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,WAAW,CAAC,EAAE;QACZ,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,gEAAgE;IAChE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,gCAAgC,CAAA;KAAE,CAAC;IACtD,QAAQ,CAAC,IAAI,EAAE,CACb,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,EAC5C,GAAG,EAAE,aAAa,KACf,OAAO,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,CAAC;IACtD,QAAQ,CAAC,SAAS,CAAC,EAAE,CACnB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,KAC1B,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,GAAG,OAAO,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,CACd,GAAG,EAAE,aAAa,KACf,OAAO,CAAC;QAAE,SAAS,EAAE,QAAQ,EAAE,CAAA;KAAE,CAAC,GAAG;QAAE,SAAS,EAAE,QAAQ,EAAE,CAAA;KAAE,CAAC;CACrE;
|
|
1
|
+
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,gCAAgC,EAChC,SAAS,EACT,kBAAkB,EAClB,QAAQ,EAER,IAAI,EACJ,aAAa,EACb,cAAc,EAGf,MAAM,8BAA8B,CAAC;AAoBtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,4BAA4B,CAAC;AAYpC,MAAM,WAAW,2BAA2B;IAC1C,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,SAAS,CAAC;IACrB,8FAA8F;IAC9F,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,qFAAqF;IACrF,GAAG,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B;;;;OAIG;IACH,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;CACnC;AAED,KAAK,qBAAqB,GAAG,IAAI,CAAC,2BAA2B,EAAE,WAAW,GAAG,UAAU,CAAC,GAAG;IACzF,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAoBF,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,WAAW,CAAC,EAAE;QACZ,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,gEAAgE;IAChE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,gCAAgC,CAAA;KAAE,CAAC;IACtD,QAAQ,CAAC,IAAI,EAAE,CACb,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,EAC5C,GAAG,EAAE,aAAa,KACf,OAAO,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,CAAC;IACtD,QAAQ,CAAC,SAAS,CAAC,EAAE,CACnB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,KAC1B,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,GAAG,OAAO,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,CACd,GAAG,EAAE,aAAa,KACf,OAAO,CAAC;QAAE,SAAS,EAAE,QAAQ,EAAE,CAAA;KAAE,CAAC,GAAG;QAAE,SAAS,EAAE,QAAQ,EAAE,CAAA;KAAE,CAAC;CACrE;AA4PD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,GAAG,gBAAgB,EAAE,CAM7F;AA8DD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,qBAAqB,GAAG;IAAE,OAAO,IAAI,IAAI,CAAA;CAAE,CAoHlF"}
|