@j0hanz/filesystem-mcp 2.1.4 → 2.1.6
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/dist/cli.d.ts +2 -14
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +14 -22
- package/dist/cli.js.map +1 -1
- package/dist/core/cursor.d.ts +2 -14
- package/dist/core/cursor.d.ts.map +1 -1
- package/dist/core/cursor.js +25 -39
- package/dist/core/cursor.js.map +1 -1
- package/dist/core/diff.d.ts +6 -0
- package/dist/core/diff.d.ts.map +1 -0
- package/dist/core/diff.js +21 -0
- package/dist/core/diff.js.map +1 -0
- package/dist/core/errors.d.ts +2 -31
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +15 -86
- package/dist/core/errors.js.map +1 -1
- package/dist/core/file-uri.d.ts +18 -2
- package/dist/core/file-uri.d.ts.map +1 -1
- package/dist/core/file-uri.js +34 -6
- package/dist/core/file-uri.js.map +1 -1
- package/dist/core/fmt.js +5 -5
- package/dist/core/fmt.js.map +1 -1
- package/dist/core/fs.d.ts +4 -13
- package/dist/core/fs.d.ts.map +1 -1
- package/dist/core/fs.js +4 -41
- package/dist/core/fs.js.map +1 -1
- package/dist/core/glob.d.ts +1 -7
- package/dist/core/glob.d.ts.map +1 -1
- package/dist/core/glob.js +1 -11
- package/dist/core/glob.js.map +1 -1
- package/dist/core/input-required.d.ts +2 -10
- package/dist/core/input-required.d.ts.map +1 -1
- package/dist/core/input-required.js +3 -12
- package/dist/core/input-required.js.map +1 -1
- package/dist/core/path-completer.d.ts.map +1 -1
- package/dist/core/path-completer.js +3 -7
- package/dist/core/path-completer.js.map +1 -1
- package/dist/core/path-discovery.js +2 -2
- package/dist/core/path-discovery.js.map +1 -1
- package/dist/core/path.d.ts +10 -25
- package/dist/core/path.d.ts.map +1 -1
- package/dist/core/path.js +28 -66
- package/dist/core/path.js.map +1 -1
- package/dist/core/primitives.d.ts +8 -3
- package/dist/core/primitives.d.ts.map +1 -1
- package/dist/core/primitives.js +10 -5
- package/dist/core/primitives.js.map +1 -1
- package/dist/core/read.d.ts +0 -16
- package/dist/core/read.d.ts.map +1 -1
- package/dist/core/read.js +19 -56
- package/dist/core/read.js.map +1 -1
- package/dist/core/schema.d.ts +1 -32
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +7 -66
- package/dist/core/schema.js.map +1 -1
- package/dist/core/search.d.ts +16 -5
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +118 -108
- package/dist/core/search.js.map +1 -1
- package/dist/core/sensitive.d.ts.map +1 -1
- package/dist/core/sensitive.js +7 -15
- package/dist/core/sensitive.js.map +1 -1
- package/dist/core/store.d.ts +1 -3
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/store.js +1 -5
- package/dist/core/store.js.map +1 -1
- package/dist/core/watcher-registry.d.ts +1 -8
- package/dist/core/watcher-registry.d.ts.map +1 -1
- package/dist/core/watcher-registry.js +3 -12
- package/dist/core/watcher-registry.js.map +1 -1
- package/dist/index.js +1 -8
- package/dist/index.js.map +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +7 -33
- package/dist/prompts.js.map +1 -1
- package/dist/resources.d.ts +1 -1
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +3 -9
- package/dist/resources.js.map +1 -1
- package/dist/server.d.ts +0 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -5
- package/dist/server.js.map +1 -1
- package/dist/tools/batch.d.ts +5 -69
- package/dist/tools/batch.d.ts.map +1 -1
- package/dist/tools/batch.js +7 -117
- package/dist/tools/batch.js.map +1 -1
- package/dist/tools/create.d.ts.map +1 -1
- package/dist/tools/create.js +8 -18
- package/dist/tools/create.js.map +1 -1
- package/dist/tools/define.d.ts +1 -4
- package/dist/tools/define.d.ts.map +1 -1
- package/dist/tools/define.js +6 -24
- package/dist/tools/define.js.map +1 -1
- package/dist/tools/delete-file.d.ts.map +1 -1
- package/dist/tools/delete-file.js +6 -6
- package/dist/tools/delete-file.js.map +1 -1
- package/dist/tools/diff.d.ts.map +1 -1
- package/dist/tools/diff.js +3 -10
- package/dist/tools/diff.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +43 -87
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/index.d.ts +0 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +0 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/list.js +3 -2
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/move.d.ts.map +1 -1
- package/dist/tools/move.js +169 -119
- package/dist/tools/move.js.map +1 -1
- package/dist/tools/patch.d.ts.map +1 -1
- package/dist/tools/patch.js +5 -23
- package/dist/tools/patch.js.map +1 -1
- package/dist/tools/progress.d.ts +0 -1
- package/dist/tools/progress.d.ts.map +1 -1
- package/dist/tools/progress.js +2 -8
- package/dist/tools/progress.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +21 -25
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/replace-in-files.d.ts.map +1 -1
- package/dist/tools/replace-in-files.js +25 -44
- package/dist/tools/replace-in-files.js.map +1 -1
- package/dist/tools/search-content.d.ts.map +1 -1
- package/dist/tools/search-content.js +17 -49
- package/dist/tools/search-content.js.map +1 -1
- package/dist/tools/search-files.d.ts.map +1 -1
- package/dist/tools/search-files.js +1 -2
- package/dist/tools/search-files.js.map +1 -1
- package/dist/tools/stat.d.ts.map +1 -1
- package/dist/tools/stat.js +5 -6
- package/dist/tools/stat.js.map +1 -1
- package/dist/transport/http-policy.d.ts +6 -0
- package/dist/transport/http-policy.d.ts.map +1 -1
- package/dist/transport/http-policy.js +2 -2
- package/dist/transport/http-policy.js.map +1 -1
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +4 -11
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/stdio.js +1 -1
- package/dist/transport/stdio.js.map +1 -1
- package/dist/transport.d.ts +1 -3
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +5 -2
- package/dist/transport.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-completer.js","sourceRoot":"","sources":["../../src/core/path-completer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAErF,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC7E,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,uBAAuB,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErF,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAElE,MAAM,oBAAoB,GAAG,GAAG,CAAC;AACjC,MAAM,wBAAwB,GAAG,GAAG,CAAC;AACrC,MAAM,yBAAyB,GAAG,GAAG,CAAC;AAOtC,+EAA+E;AAC/E,+EAA+E;AAC/E,8EAA8E;AAC9E,sEAAsE;AAEtE,SAAS,oBAAoB,CAAC,KAAa;IACzC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;AACzE,CAAC;AAED,SAAS,eAAe,CACtB,IAAY,EACZ,QAAgB,EAChB,iBAA0B;IAE1B,MAAM,eAAe,GAAG,aAAa,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC/D,IAAI,iBAAiB;QAAE,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACzE,OAAO;QACL,SAAS,EAAE,OAAO,CAAC,eAAe,CAAC;QACnC,MAAM,EAAE,QAAQ,CAAC,eAAe,CAAC;KAClC,CAAC;AACJ,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAa;IACxC,MAAM,eAAe,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAC3C,IAAI,CAAC,eAAe;QAAE,OAAO,SAAS,CAAC;IACvC,MAAM,UAAU,GAAG,eAAe,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAChD,IAAI,UAAU,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;IAC3E,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IACtD,IAAI,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,eAAe,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,EAAE,CAAC;AACxE,CAAC;AAED,SAAS,qBAAqB,CAAC,QAAgB,EAAE,OAA0B;IACzE,MAAM,kBAAkB,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;IAClD,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,KAAK,kBAAkB,CAAC,CAAC;AAC/F,CAAC;AAED,SAAS,uBAAuB,CAC9B,YAAoB,EACpB,OAA0B;IAE1B,MAAM,MAAM,GAAG,mBAAmB,CAAC,YAAY,CAAC,CAAC;IACjD,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,MAAM,IAAI,GAAG,qBAAqB,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC7D,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,YAAY,CAAC,CAAC;IAC7D,OAAO,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;AACpE,CAAC;AAED,KAAK,UAAU,4BAA4B,CACzC,IAAY,EACZ,OAA0B;IAE1B,IAAI,CAAC,uBAAuB,CAAC,IAAI,EAAE,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1D,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;YAAE,OAAO,KAAK,CAAC;QACvC,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,CAAC;QACzC,OAAO,IAAI,KAAK,IAAI,IAAI,uBAAuB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACjE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,EAAE,CAAC;YAC1E,MAAM,CAAC,KAAK,CAAC,sDAAsD,EAAE;gBACnE,IAAI;gBACJ,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC;aACnB,CAAC,CAAC;QACL,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,sBAAsB,CAAC,KAAa;IAC3C,OAAO,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,GAAG,EAAE,CAAC;AACxD,CAAC;AAED,SAAS,mBAAmB,CAC1B,OAA0B,EAC1B,SAAoC;IAEpC,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,IAAI,SAAS,CAAC,IAAI,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC;IAClE,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,aAAa,CAAC,YAAoB;IACzC,MAAM,eAAe,GAAG,WAAW,CAAC,YAAY,CAAC,CAAC;IAClD,MAAM,UAAU,GAAG,eAAe,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAChD,OAAO,CAAC,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;AACpG,CAAC;AAED,SAAS,qBAAqB,CAAC,YAAoB,EAAE,OAA0B;IAC7E,MAAM,UAAU,GAAG,aAAa,CAAC,YAAY,CAAC,CAAC;IAC/C,IAAI,CAAC,UAAU;QAAE,OAAO,mBAAmB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IACjE,OAAO,mBAAmB,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAC3C,QAAQ,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,CACpD,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CACxB,SAAiB,EACjB,MAAc,EACd,OAA0B;IAE1B,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IACzC,MAAM,mBAAmB,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;IACrD,OAAO,mBAAmB,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;QAC3C,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,mBAAmB,CAAC;YAAE,OAAO,KAAK,CAAC;QAC5D,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,qBAAqB,CAAC,OAAiB;IAC9C,MAAM,OAAO,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAClC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC3B,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,OAAO,CAAC;QAC/D,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,OAAO,CAAC;QAClE,IAAI,SAAS,IAAI,CAAC,UAAU;YAAE,OAAO,CAAC,CAAC,CAAC;QACxC,IAAI,CAAC,SAAS,IAAI,UAAU;YAAE,OAAO,CAAC,CAAC;QACvC,OAAO,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,sBAAsB,CAAC,GAAG,WAA2C;IAC5E,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;IACxC,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;QAChC,KAAK,MAAM,KAAK,IAAI,KAAK;YAAE,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACtD,CAAC;IACD,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IACzC,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC9B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,KAAK,UAAU,sBAAsB,CACnC,SAAiB,EACjB,MAAc,EACd,OAA0B,EAC1B,WAAuC;IAEvC,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,CAAC,CAAC,MAAM,4BAA4B,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAAE,OAAO,OAAO,CAAC;IAC9E,IAAI,CAAC;QACH,uEAAuE;QACvE,uEAAuE;QACvE,sEAAsE;QACtE,sEAAsE;QACtE,+BAA+B;QAC/B,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC;QACrC,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;YACzC,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;gBAC9B,IAAI,MAAM,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC;oBAAE,SAAS;gBACjF,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC7C,IAAI,WAAW,EAAE,CAAC,QAAQ,CAAC;oBAAE,SAAS;gBACtC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,GAAG,QAAQ,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;YACrE,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;gBAC3B,kDAAkD;YACpD,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,EAAE,CAAC;YAC1E,MAAM,CAAC,IAAI,CAAC,sDAAsD,EAAE;gBAClE,SAAS;gBACT,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC;aACnB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,gBAAgB,CACvB,YAAoB,EACpB,OAA0B;IAE1B,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,YAAY,CAAC,CAAC;IAC7D,IAAI,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC7B,OAAO,eAAe,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,IAAI,IAAI,GAAG,EAAE,YAAY,EAAE,iBAAiB,CAAC,CAAC;IAC3F,CAAC;IACD,MAAM,gBAAgB,GAAG,uBAAuB,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IACxE,IAAI,gBAAgB;QAAE,OAAO,gBAAgB,CAAC;IAC9C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACxB,IAAI,IAAI;YAAE,OAAO,eAAe,CAAC,IAAI,EAAE,YAAY,EAAE,iBAAiB,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,gFAAgF;AAEhF,MAAM,OAAO,aAAa;IAChB,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC7B,SAAS,CAAY;IAEtC,YAAY,SAAoB;QAC9B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,KAAa;QACzB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAEzC,IAAI,UAAU,IAAI,GAAG,GAAG,UAAU,CAAC,EAAE,GAAG,wBAAwB,EAAE,CAAC;YACjE,OAAO,UAAU,CAAC,MAAM,CAAC;QAC3B,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QAC/C,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;QACxD,OAAO,OAAO,CAAC;IACjB,CAAC;IAEO,aAAa,CAAC,GAAW,EAAE,KAAiB;QAClD,2EAA2E;QAC3E,2EAA2E;QAC3E,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,yBAAyB;YAAE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACrE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC7B,CAAC;IAEO,KAAK,CAAC,YAAY,CAAC,KAAa;QACtC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,qBAAqB,EAAE,CAAC;QAEvD,IAAI,CAAC;YACH,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAC;YAChD,CAAC;YAED,MAAM,OAAO,GAAG,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;YACjD,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAC;YAC9E,CAAC;YAED,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;YACtC,MAAM,UAAU,GAAG,MAAM,sBAAsB,CAC7C,SAAS,EACT,MAAM,EACN,OAAO,EACP,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAChD,CAAC;YACF,MAAM,WAAW,GAAG,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;YAClE,OAAO,sBAAsB,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAC;QACxF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,gBAAgB,CAAC,KAAK,CAAC,CAAC;YACxB,MAAM,CAAC,IAAI,CAAC,wDAAwD,EAAE;gBACpE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC/E,CAAC,CAAC;YACH,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;CACF","sourcesContent":["import { opendir, stat } from 'node:fs/promises';\nimport { basename, dirname, isAbsolute, join, parse, resolve, sep } from 'node:path';\n\nimport { isNodeError, isNotFoundErrno, rethrowIfAborted } from './errors.js';\nimport { Logger } from './observability.js';\nimport { isPathWithinDirectories, isSamePath, normalizePath } from './path-utils.js';\nimport type { PathGuard } from './path.js';\nimport { isSlash, resolveRealPath, toPosixPath } from './path.js';\n\nconst MAX_COMPLETION_ITEMS = 100;\nconst COMPLETION_RATE_LIMIT_MS = 100;\nconst MAX_COMPLETION_CACHE_KEYS = 128;\n\ninterface CacheEntry {\n ms: number;\n result: string[];\n}\n\n// ─── pure path-completion helpers (no instance state) ───────────────────────\n// Formerly static methods on PathCompleter. They take `allowed` explicitly and\n// touch no state, so they read as plain functions rather than a class used as\n// a namespace. PathCompleter keeps only the cache and the path guard.\n\nfunction hasTrailingSeparator(value: string): boolean {\n return value.length > 0 && isSlash(value.charCodeAt(value.length - 1));\n}\n\nfunction resolveFromBase(\n base: string,\n rawValue: string,\n trailingSeparator: boolean,\n): { searchDir: string; prefix: string } {\n const normalizedValue = normalizePath(resolve(base, rawValue));\n if (trailingSeparator) return { searchDir: normalizedValue, prefix: '' };\n return {\n searchDir: dirname(normalizedValue),\n prefix: basename(normalizedValue),\n };\n}\n\nfunction parseNamedRootInput(value: string): { rootName: string; remainder: string } | undefined {\n const normalizedInput = toPosixPath(value);\n if (!normalizedInput) return undefined;\n const slashIndex = normalizedInput.indexOf('/');\n if (slashIndex === -1) return { rootName: normalizedInput, remainder: '' };\n const rootName = normalizedInput.slice(0, slashIndex);\n if (!rootName) return undefined;\n return { rootName, remainder: normalizedInput.slice(slashIndex + 1) };\n}\n\nfunction findAllowedRootByName(rootName: string, allowed: readonly string[]): string | undefined {\n const normalizedRootName = rootName.toLowerCase();\n return allowed.find((candidate) => basename(candidate).toLowerCase() === normalizedRootName);\n}\n\nfunction resolveNamedRootContext(\n currentValue: string,\n allowed: readonly string[],\n): { searchDir: string; prefix: string } | undefined {\n const parsed = parseNamedRootInput(currentValue);\n if (!parsed) return undefined;\n const root = findAllowedRootByName(parsed.rootName, allowed);\n if (!root) return undefined;\n const trailingSeparator = hasTrailingSeparator(currentValue);\n return resolveFromBase(root, parsed.remainder, trailingSeparator);\n}\n\nasync function isAllowedCompletionDirectory(\n path: string,\n allowed: readonly string[],\n): Promise<boolean> {\n if (!isPathWithinDirectories(path, allowed)) return false;\n try {\n const stats = await stat(path);\n if (!stats.isDirectory()) return false;\n const real = await resolveRealPath(path);\n return real !== null && isPathWithinDirectories(real, allowed);\n } catch (err) {\n if (!isNotFoundErrno(err) && (!isNodeError(err) || err.code !== 'EACCES')) {\n Logger.debug('isAllowedCompletionDirectory: unexpected probe error', {\n path,\n error: String(err),\n });\n }\n return false;\n }\n}\n\nfunction withDirectorySeparator(value: string): string {\n return value.endsWith(sep) ? value : `${value}${sep}`;\n}\n\nfunction collectAllowedRoots(\n allowed: readonly string[],\n predicate: (root: string) => boolean,\n): string[] {\n const matches: string[] = [];\n for (const root of allowed) {\n if (predicate(root)) matches.push(withDirectorySeparator(root));\n }\n return matches;\n}\n\nfunction getRootPrefix(currentValue: string): string {\n const normalizedInput = toPosixPath(currentValue);\n const slashIndex = normalizedInput.indexOf('/');\n return (slashIndex === -1 ? normalizedInput : normalizedInput.slice(0, slashIndex)).toLowerCase();\n}\n\nfunction findRootPrefixMatches(currentValue: string, allowed: readonly string[]): string[] {\n const rootPrefix = getRootPrefix(currentValue);\n if (!rootPrefix) return collectAllowedRoots(allowed, () => true);\n return collectAllowedRoots(allowed, (root) =>\n basename(root).toLowerCase().startsWith(rootPrefix),\n );\n}\n\nfunction findMatchingRoots(\n searchDir: string,\n prefix: string,\n allowed: readonly string[],\n): string[] {\n const lowerPrefix = prefix.toLowerCase();\n const normalizedSearchDir = normalizePath(searchDir);\n return collectAllowedRoots(allowed, (root) => {\n const rootDir = dirname(root);\n if (!isSamePath(rootDir, normalizedSearchDir)) return false;\n return basename(root).toLowerCase().startsWith(lowerPrefix);\n });\n}\n\nfunction sortCompletionMatches(matches: string[]): void {\n const sepCode = sep.charCodeAt(0);\n matches.sort((left, right) => {\n const leftIsDir = left.charCodeAt(left.length - 1) === sepCode;\n const rightIsDir = right.charCodeAt(right.length - 1) === sepCode;\n if (leftIsDir && !rightIsDir) return -1;\n if (!leftIsDir && rightIsDir) return 1;\n return left.localeCompare(right);\n });\n}\n\nfunction mergeCompletionMatches(...matchGroups: readonly (readonly string[])[]): string[] {\n const uniqueMatches = new Set<string>();\n for (const group of matchGroups) {\n for (const match of group) uniqueMatches.add(match);\n }\n const merged = Array.from(uniqueMatches);\n sortCompletionMatches(merged);\n return merged;\n}\n\nasync function findMatchesInDirectory(\n searchDir: string,\n prefix: string,\n allowed: readonly string[],\n isSensitive?: (path: string) => boolean,\n): Promise<string[]> {\n const matches: string[] = [];\n if (!(await isAllowedCompletionDirectory(searchDir, allowed))) return matches;\n try {\n // Stream via opendir and collect every match; the MAX_COMPLETION_ITEMS\n // cap is applied AFTER the alphabetical sort in mergeCompletionMatches\n // (slice at the call site). Capping here would keep the opendir-first\n // 100, not the alphabetically-first 100 — the sort would only reorder\n // an already-arbitrary subset.\n const dir = await opendir(searchDir);\n try {\n const lowerPrefix = prefix.toLowerCase();\n for await (const entry of dir) {\n if (prefix !== '' && !entry.name.toLowerCase().startsWith(lowerPrefix)) continue;\n const fullPath = join(searchDir, entry.name);\n if (isSensitive?.(fullPath)) continue;\n matches.push(entry.isDirectory() ? `${fullPath}${sep}` : fullPath);\n }\n } finally {\n await dir.close().catch(() => {\n /* dir already closed or opendir never resolved */\n });\n }\n } catch (err) {\n if (!isNodeError(err) || (err.code !== 'ENOENT' && err.code !== 'EACCES')) {\n Logger.warn('PathCompleter.findMatchesInDirectory: readdir failed', {\n searchDir,\n error: String(err),\n });\n }\n }\n return matches;\n}\n\nfunction getSearchContext(\n currentValue: string,\n allowed: readonly string[],\n): { searchDir: string; prefix: string } | undefined {\n const trailingSeparator = hasTrailingSeparator(currentValue);\n if (isAbsolute(currentValue)) {\n return resolveFromBase(parse(currentValue).root || sep, currentValue, trailingSeparator);\n }\n const namedRootContext = resolveNamedRootContext(currentValue, allowed);\n if (namedRootContext) return namedRootContext;\n if (allowed.length === 1) {\n const base = allowed[0];\n if (base) return resolveFromBase(base, currentValue, trailingSeparator);\n }\n return undefined;\n}\n\n// ─── PathCompleter: cache + path guard ───────────────────────────────────────\n\nexport class PathCompleter {\n private cache = new Map<string, CacheEntry>();\n private readonly pathGuard: PathGuard;\n\n constructor(pathGuard: PathGuard) {\n this.pathGuard = pathGuard;\n }\n\n async suggest(value: string): Promise<string[]> {\n const now = Date.now();\n const cacheEntry = this.cache.get(value);\n\n if (cacheEntry && now - cacheEntry.ms < COMPLETION_RATE_LIMIT_MS) {\n return cacheEntry.result;\n }\n\n const results = await this.completePath(value);\n this.setCacheValue(value, { ms: now, result: results });\n return results;\n }\n\n private setCacheValue(key: string, entry: CacheEntry): void {\n // Entries are stale after COMPLETION_RATE_LIMIT_MS anyway, so the cap only\n // has to bound memory — dropping the whole map beats per-key LRU eviction.\n if (this.cache.size >= MAX_COMPLETION_CACHE_KEYS) this.cache.clear();\n this.cache.set(key, entry);\n }\n\n private async completePath(value: string): Promise<string[]> {\n const allowed = this.pathGuard.getAllowedDirectories();\n\n try {\n if (!value) {\n return allowed.slice(0, MAX_COMPLETION_ITEMS);\n }\n\n const context = getSearchContext(value, allowed);\n if (!context) {\n return findRootPrefixMatches(value, allowed).slice(0, MAX_COMPLETION_ITEMS);\n }\n\n const { searchDir, prefix } = context;\n const dirMatches = await findMatchesInDirectory(\n searchDir,\n prefix,\n allowed,\n this.pathGuard.isSensitive.bind(this.pathGuard),\n );\n const rootMatches = findMatchingRoots(searchDir, prefix, allowed);\n return mergeCompletionMatches(dirMatches, rootMatches).slice(0, MAX_COMPLETION_ITEMS);\n } catch (error) {\n rethrowIfAborted(error);\n Logger.warn('PathCompleter: completion failed, returning empty list', {\n error: error instanceof Error ? (error.stack ?? error.message) : String(error),\n });\n return [];\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"path-completer.js","sourceRoot":"","sources":["../../src/core/path-completer.ts"],"names":[],"mappings":"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","sourcesContent":["import { opendir, stat } from 'node:fs/promises';\nimport { basename, dirname, isAbsolute, join, parse, resolve, sep } from 'node:path';\n\nimport { isNodeError, isNotFoundErrno, rethrowIfAborted } from './errors.js';\nimport { Logger } from './observability.js';\nimport { isPathWithinDirectories, isSamePath, normalizePath } from './path-utils.js';\nimport type { PathGuard } from './path.js';\nimport { resolveRealPath } from './path.js';\nimport { isSlash, toPosixPath } from './primitives.js';\n\nconst MAX_COMPLETION_ITEMS = 100;\nconst COMPLETION_RATE_LIMIT_MS = 100;\nconst MAX_COMPLETION_CACHE_KEYS = 128;\n\ninterface CacheEntry {\n ms: number;\n result: string[];\n}\n\n// ─── pure path-completion helpers (no instance state) ───────────────────────\n// Formerly static methods on PathCompleter. They take `allowed` explicitly and\n// touch no state, so they read as plain functions rather than a class used as\n// a namespace. PathCompleter keeps only the cache and the path guard.\n\nfunction hasTrailingSeparator(value: string): boolean {\n return value.length > 0 && isSlash(value.charCodeAt(value.length - 1));\n}\n\nfunction resolveFromBase(\n base: string,\n rawValue: string,\n trailingSeparator: boolean,\n): { searchDir: string; prefix: string } {\n const normalizedValue = normalizePath(resolve(base, rawValue));\n if (trailingSeparator) return { searchDir: normalizedValue, prefix: '' };\n return {\n searchDir: dirname(normalizedValue),\n prefix: basename(normalizedValue),\n };\n}\n\nfunction parseNamedRootInput(value: string): { rootName: string; remainder: string } | undefined {\n const normalizedInput = toPosixPath(value);\n if (!normalizedInput) return undefined;\n const slashIndex = normalizedInput.indexOf('/');\n if (slashIndex === -1) return { rootName: normalizedInput, remainder: '' };\n const rootName = normalizedInput.slice(0, slashIndex);\n if (!rootName) return undefined;\n return { rootName, remainder: normalizedInput.slice(slashIndex + 1) };\n}\n\nfunction findAllowedRootByName(rootName: string, allowed: readonly string[]): string | undefined {\n const normalizedRootName = rootName.toLowerCase();\n return allowed.find((candidate) => basename(candidate).toLowerCase() === normalizedRootName);\n}\n\nfunction resolveNamedRootContext(\n currentValue: string,\n allowed: readonly string[],\n): { searchDir: string; prefix: string } | undefined {\n const parsed = parseNamedRootInput(currentValue);\n if (!parsed) return undefined;\n const root = findAllowedRootByName(parsed.rootName, allowed);\n if (!root) return undefined;\n const trailingSeparator = hasTrailingSeparator(currentValue);\n return resolveFromBase(root, parsed.remainder, trailingSeparator);\n}\n\nasync function isAllowedCompletionDirectory(\n path: string,\n allowed: readonly string[],\n): Promise<boolean> {\n if (!isPathWithinDirectories(path, allowed)) return false;\n try {\n const stats = await stat(path);\n if (!stats.isDirectory()) return false;\n const real = await resolveRealPath(path);\n return real !== null && isPathWithinDirectories(real, allowed);\n } catch (err) {\n if (!isNotFoundErrno(err) && (!isNodeError(err) || err.code !== 'EACCES')) {\n Logger.debug('isAllowedCompletionDirectory: unexpected probe error', {\n path,\n error: String(err),\n });\n }\n return false;\n }\n}\n\nfunction withDirectorySeparator(value: string): string {\n return value.endsWith(sep) ? value : `${value}${sep}`;\n}\n\nfunction collectAllowedRoots(\n allowed: readonly string[],\n predicate: (root: string) => boolean,\n): string[] {\n const matches: string[] = [];\n for (const root of allowed) {\n if (predicate(root)) matches.push(withDirectorySeparator(root));\n }\n return matches;\n}\n\nfunction findRootPrefixMatches(currentValue: string, allowed: readonly string[]): string[] {\n const rootPrefix = parseNamedRootInput(currentValue)?.rootName.toLowerCase() ?? '';\n if (!rootPrefix) return collectAllowedRoots(allowed, () => true);\n return collectAllowedRoots(allowed, (root) =>\n basename(root).toLowerCase().startsWith(rootPrefix),\n );\n}\n\nfunction findMatchingRoots(\n searchDir: string,\n prefix: string,\n allowed: readonly string[],\n): string[] {\n const lowerPrefix = prefix.toLowerCase();\n const normalizedSearchDir = normalizePath(searchDir);\n return collectAllowedRoots(allowed, (root) => {\n const rootDir = dirname(root);\n if (!isSamePath(rootDir, normalizedSearchDir)) return false;\n return basename(root).toLowerCase().startsWith(lowerPrefix);\n });\n}\n\nfunction sortCompletionMatches(matches: string[]): void {\n const sepCode = sep.charCodeAt(0);\n matches.sort((left, right) => {\n const leftIsDir = left.charCodeAt(left.length - 1) === sepCode;\n const rightIsDir = right.charCodeAt(right.length - 1) === sepCode;\n if (leftIsDir && !rightIsDir) return -1;\n if (!leftIsDir && rightIsDir) return 1;\n return left.localeCompare(right);\n });\n}\n\nfunction mergeCompletionMatches(...matchGroups: readonly (readonly string[])[]): string[] {\n const uniqueMatches = new Set<string>();\n for (const group of matchGroups) {\n for (const match of group) uniqueMatches.add(match);\n }\n const merged = Array.from(uniqueMatches);\n sortCompletionMatches(merged);\n return merged;\n}\n\nasync function findMatchesInDirectory(\n searchDir: string,\n prefix: string,\n allowed: readonly string[],\n isSensitive?: (path: string) => boolean,\n): Promise<string[]> {\n const matches: string[] = [];\n if (!(await isAllowedCompletionDirectory(searchDir, allowed))) return matches;\n try {\n // Stream via opendir and collect every match; the MAX_COMPLETION_ITEMS\n // cap is applied AFTER the alphabetical sort in mergeCompletionMatches\n // (slice at the call site). Capping here would keep the opendir-first\n // 100, not the alphabetically-first 100 — the sort would only reorder\n // an already-arbitrary subset.\n const dir = await opendir(searchDir);\n try {\n const lowerPrefix = prefix.toLowerCase();\n for await (const entry of dir) {\n if (prefix !== '' && !entry.name.toLowerCase().startsWith(lowerPrefix)) continue;\n const fullPath = join(searchDir, entry.name);\n if (isSensitive?.(fullPath)) continue;\n matches.push(entry.isDirectory() ? `${fullPath}${sep}` : fullPath);\n }\n } finally {\n await dir.close().catch(() => {\n /* dir already closed or opendir never resolved */\n });\n }\n } catch (err) {\n if (!isNodeError(err) || (err.code !== 'ENOENT' && err.code !== 'EACCES')) {\n Logger.warn('PathCompleter.findMatchesInDirectory: readdir failed', {\n searchDir,\n error: String(err),\n });\n }\n }\n return matches;\n}\n\nfunction getSearchContext(\n currentValue: string,\n allowed: readonly string[],\n): { searchDir: string; prefix: string } | undefined {\n const trailingSeparator = hasTrailingSeparator(currentValue);\n if (isAbsolute(currentValue)) {\n return resolveFromBase(parse(currentValue).root || sep, currentValue, trailingSeparator);\n }\n const namedRootContext = resolveNamedRootContext(currentValue, allowed);\n if (namedRootContext) return namedRootContext;\n if (allowed.length === 1) {\n const base = allowed[0];\n if (base) return resolveFromBase(base, currentValue, trailingSeparator);\n }\n return undefined;\n}\n\n// ─── PathCompleter: cache + path guard ───────────────────────────────────────\n\nexport class PathCompleter {\n private cache = new Map<string, CacheEntry>();\n private readonly pathGuard: PathGuard;\n\n constructor(pathGuard: PathGuard) {\n this.pathGuard = pathGuard;\n }\n\n async suggest(value: string): Promise<string[]> {\n const now = Date.now();\n const cacheEntry = this.cache.get(value);\n\n if (cacheEntry && now - cacheEntry.ms < COMPLETION_RATE_LIMIT_MS) {\n return cacheEntry.result;\n }\n\n const results = await this.completePath(value);\n this.setCacheValue(value, { ms: now, result: results });\n return results;\n }\n\n private setCacheValue(key: string, entry: CacheEntry): void {\n // Entries are stale after COMPLETION_RATE_LIMIT_MS anyway, so the cap only\n // has to bound memory — dropping the whole map beats per-key LRU eviction.\n if (this.cache.size >= MAX_COMPLETION_CACHE_KEYS) this.cache.clear();\n this.cache.set(key, entry);\n }\n\n private async completePath(value: string): Promise<string[]> {\n const allowed = this.pathGuard.getAllowedDirectories();\n\n try {\n if (!value) {\n return allowed.slice(0, MAX_COMPLETION_ITEMS);\n }\n\n const context = getSearchContext(value, allowed);\n if (!context) {\n return findRootPrefixMatches(value, allowed).slice(0, MAX_COMPLETION_ITEMS);\n }\n\n const { searchDir, prefix } = context;\n const dirMatches = await findMatchesInDirectory(\n searchDir,\n prefix,\n allowed,\n this.pathGuard.isSensitive.bind(this.pathGuard),\n );\n const rootMatches = findMatchingRoots(searchDir, prefix, allowed);\n return mergeCompletionMatches(dirMatches, rootMatches).slice(0, MAX_COMPLETION_ITEMS);\n } catch (error) {\n rethrowIfAborted(error);\n Logger.warn('PathCompleter: completion failed, returning empty list', {\n error: error instanceof Error ? (error.stack ?? error.message) : String(error),\n });\n return [];\n }\n }\n}\n"]}
|
|
@@ -4,7 +4,7 @@ import { dirname, join, parse } from 'node:path';
|
|
|
4
4
|
import { formatUnknownErrorMessage } from './errors.js';
|
|
5
5
|
import { Logger } from './observability.js';
|
|
6
6
|
import { isPathWithinDirectories, isSamePath, normalizePath } from './path-utils.js';
|
|
7
|
-
import {
|
|
7
|
+
import { splitDirList } from './primitives.js';
|
|
8
8
|
// Resolve a configured env-var directory list (FS_ALLOWED_DIRS / FS_ROOT_BOUNDARY)
|
|
9
9
|
// into normalized, verified directories. Each entry is stat'd; a non-directory
|
|
10
10
|
// warns and is dropped, a missing entry warns unless `allowMissing` is set (in
|
|
@@ -14,7 +14,7 @@ import { parseEnvDirList, splitDirList } from './primitives.js';
|
|
|
14
14
|
// A CLI override supplies `rawValue`, which beats the environment entirely.
|
|
15
15
|
// Both warning messages are templated on `envVar` so operator output is stable.
|
|
16
16
|
export async function resolveConfiguredDirs(envVar, opts = {}) {
|
|
17
|
-
const raw =
|
|
17
|
+
const raw = splitDirList(opts.rawValue ?? process.env[envVar]);
|
|
18
18
|
const result = [];
|
|
19
19
|
for (const rawPath of raw) {
|
|
20
20
|
const normalized = normalizePath(rawPath);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-discovery.js","sourceRoot":"","sources":["../../src/core/path-discovery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,uBAAuB,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrF,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"path-discovery.js","sourceRoot":"","sources":["../../src/core/path-discovery.ts"],"names":[],"mappings":"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","sourcesContent":["import { realpath, stat } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { dirname, join, parse } from 'node:path';\n\nimport { formatUnknownErrorMessage } from './errors.js';\nimport { Logger } from './observability.js';\nimport { isPathWithinDirectories, isSamePath, normalizePath } from './path-utils.js';\nimport { splitDirList } from './primitives.js';\n\n// Resolve a configured env-var directory list (FS_ALLOWED_DIRS / FS_ROOT_BOUNDARY)\n// into normalized, verified directories. Each entry is stat'd; a non-directory\n// warns and is dropped, a missing entry warns unless `allowMissing` is set (in\n// which case the normalized path is kept). When `resolveReal` is set, a\n// directory entry is pushed as its realpath (normalized) instead of the raw\n// path — FS_ROOT_BOUNDARY uses this so a symlinked root resolves to its target.\n// A CLI override supplies `rawValue`, which beats the environment entirely.\n// Both warning messages are templated on `envVar` so operator output is stable.\nexport async function resolveConfiguredDirs(\n envVar: string,\n opts: { allowMissing?: boolean; resolveReal?: boolean; rawValue?: string } = {},\n): Promise<string[]> {\n const raw = splitDirList(opts.rawValue ?? process.env[envVar]);\n const result: string[] = [];\n for (const rawPath of raw) {\n const normalized = normalizePath(rawPath);\n try {\n const s = await stat(normalized);\n if (s.isDirectory()) {\n result.push(opts.resolveReal ? normalizePath(await realpath(normalized)) : normalized);\n } else {\n Logger.emit('warning', `Path configured in ${envVar} is not a directory: ${rawPath}`);\n }\n } catch (error) {\n if (opts.allowMissing) {\n result.push(normalized);\n } else {\n Logger.emit(\n 'warning',\n `Path configured in ${envVar} is invalid or does not exist: ${rawPath} (${formatUnknownErrorMessage(error)})`,\n );\n }\n }\n }\n return result;\n}\n\nexport function isUnsafeCwdPath(normalizedCwd: string): boolean {\n const norm = normalizedCwd.toLowerCase();\n\n // 1. Filesystem root check\n const root = parse(normalizedCwd).root;\n if (isSamePath(normalizedCwd, root)) {\n return true;\n }\n\n // 2. Home directory check\n if (isSamePath(normalizedCwd, homedir())) {\n return true;\n }\n\n // 3. Hard-coded unsafe paths check\n const unsafePaths = new Set(\n [\n '/usr',\n '/etc',\n '/bin',\n '/sbin',\n '/System',\n 'C:\\\\Windows',\n 'C:\\\\Program Files',\n 'C:\\\\Program Files (x86)',\n ].map((p) => normalizePath(p).toLowerCase()),\n );\n\n if (unsafePaths.has(norm)) {\n return true;\n }\n\n return false;\n}\n\nconst MAX_PROJECT_ROOT_WALK_DEPTH = 32;\n\nexport async function findProjectRoot(startDir: string, ceiling: string[]): Promise<string> {\n const normCeiling = ceiling.map(normalizePath);\n let current = normalizePath(startDir);\n let depth = 0;\n\n for (;;) {\n if (depth++ >= MAX_PROJECT_ROOT_WALK_DEPTH) {\n break;\n }\n // Check if the current directory contains any markers\n const markers = ['.git', 'package.json', 'pyproject.toml'];\n for (const marker of markers) {\n const markerPath = join(current, marker);\n try {\n const s = await stat(markerPath);\n if (s.isDirectory() || s.isFile()) {\n return current;\n }\n } catch (_error) {\n // Skip and try next marker\n }\n }\n\n // Move to parent directory\n const parent = normalizePath(dirname(current));\n\n // Check if we hit the filesystem root\n if (parent === current) {\n break;\n }\n\n // Check if the parent is still within the ceiling\n if (!isPathWithinDirectories(parent, normCeiling)) {\n break;\n }\n\n current = parent;\n }\n\n return normalizePath(startDir);\n}\n"]}
|
package/dist/core/path.d.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { isSlash, toPosixPath } from './primitives.js';
|
|
2
|
-
import type { EntryType } from './primitives.js';
|
|
3
|
-
export { isSlash, toPosixPath };
|
|
4
1
|
declare const ValidatedPathBrand: unique symbol;
|
|
5
2
|
export type ValidatedPath = string & {
|
|
6
3
|
readonly [ValidatedPathBrand]: true;
|
|
@@ -17,12 +14,9 @@ export interface ServerOptions {
|
|
|
17
14
|
}
|
|
18
15
|
/** `path.relative` with forward slashes, so displayed paths match across platforms. */
|
|
19
16
|
export declare function toPosixRelative(from: string, to: string): string;
|
|
20
|
-
export interface AllowedDirectoriesState {
|
|
21
|
-
primary: string[];
|
|
22
|
-
expanded: string[];
|
|
23
|
-
}
|
|
24
17
|
export declare function normalizeAllowedDirectories(dirs: readonly string[]): string[];
|
|
25
18
|
export declare function resolveRealPath(normalized: string, signal?: AbortSignal): Promise<string | null>;
|
|
19
|
+
export declare function resolveAllowedDirectoriesState(dirs: readonly string[], signal?: AbortSignal): Promise<string[]>;
|
|
26
20
|
/**
|
|
27
21
|
* Accepted risk: validation methods resolve/verify a path (symlinks, boundaries,
|
|
28
22
|
* sensitivity) and then return a plain string; the actual fs operation happens
|
|
@@ -54,16 +48,9 @@ export declare class PathGuard {
|
|
|
54
48
|
*/
|
|
55
49
|
private grantedDirectories;
|
|
56
50
|
private rootBoundaries;
|
|
57
|
-
readonly options
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
* invocation. Operator-facing configuration warnings are suppressed unless
|
|
61
|
-
* it is set, so `--print-config` and unit construction stay quiet.
|
|
62
|
-
*/
|
|
63
|
-
readonly isServerContext: boolean;
|
|
64
|
-
constructor(options?: ServerOptions, isServerContext?: boolean);
|
|
65
|
-
static fromAllowedDirectories(dirs: readonly string[], signal?: AbortSignal): Promise<PathGuard>;
|
|
66
|
-
initialize(state: AllowedDirectoriesState): void;
|
|
51
|
+
private readonly options;
|
|
52
|
+
constructor(options?: ServerOptions);
|
|
53
|
+
initialize(expanded: readonly string[]): void;
|
|
67
54
|
isInitialized(): boolean;
|
|
68
55
|
/**
|
|
69
56
|
* Run `fn` as the next holder of the guard's single mutation lock. Every
|
|
@@ -72,7 +59,6 @@ export declare class PathGuard {
|
|
|
72
59
|
*/
|
|
73
60
|
private runExclusive;
|
|
74
61
|
getAllowedDirectories(): string[];
|
|
75
|
-
getRootBoundaries(): string[];
|
|
76
62
|
isSensitive(filePath: string): boolean;
|
|
77
63
|
/**
|
|
78
64
|
* True when `entryPath` is both within the guard's allowed directories and
|
|
@@ -81,16 +67,14 @@ export declare class PathGuard {
|
|
|
81
67
|
* directory pointing outside the sandbox would otherwise pass the lexical
|
|
82
68
|
* containment check (fs.glob follows symlinks and yields external entries as
|
|
83
69
|
* non-symlink dirents). validateExistingPathDetailed re-checks containment on
|
|
84
|
-
* the real path against this.allowedDirectoriesState
|
|
70
|
+
* the real path against this.allowedDirectoriesState (the guard's full
|
|
85
71
|
* allowed set — a superset of the single-root `bounds` callers pass) and
|
|
86
72
|
* re-checks sensitivity on the resolved target, throwing ACCESS_DENIED for
|
|
87
|
-
* escapes/sensitive, which the catch below turns into a filter.
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* entry is filtered, not fatal). The accepted TOCTOU window is documented at
|
|
91
|
-
* the class docstring above.
|
|
73
|
+
* escapes/sensitive, which the catch below turns into a filter. Skippable
|
|
74
|
+
* errno/fs errors return false (the entry is filtered, not fatal). The
|
|
75
|
+
* accepted TOCTOU window is documented at the class docstring above.
|
|
92
76
|
*/
|
|
93
|
-
isEntryAccessible(entryPath: string
|
|
77
|
+
isEntryAccessible(entryPath: string): Promise<boolean>;
|
|
94
78
|
validateExistingPath(requestedPath: string): Promise<ValidatedPath>;
|
|
95
79
|
/** True when a normalized path equals its own filesystem root (`C:\`, `/`). */
|
|
96
80
|
private isFilesystemRoot;
|
|
@@ -145,4 +129,5 @@ export declare class PathGuard {
|
|
|
145
129
|
validatePathForDelete(requestedPath: string): Promise<ValidatedPath>;
|
|
146
130
|
recomputeAllowedDirectories(): Promise<void>;
|
|
147
131
|
}
|
|
132
|
+
export {};
|
|
148
133
|
//# sourceMappingURL=path.d.ts.map
|
package/dist/core/path.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/core/path.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/core/path.ts"],"names":[],"mappings":"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"}
|
package/dist/core/path.js
CHANGED
|
@@ -3,16 +3,13 @@ import { homedir } from 'node:os';
|
|
|
3
3
|
import { basename, dirname, isAbsolute, join, parse, relative, resolve } from 'node:path';
|
|
4
4
|
import { timedSignal, withAbort } from './concurrency.js';
|
|
5
5
|
import { cli } from './config.js';
|
|
6
|
-
import { ERRNO_MAP, ErrorCode,
|
|
6
|
+
import { ERRNO_MAP, ErrorCode, FsError, isFsError, isNodeError, isNotFoundErrno, rethrowIfAborted, SKIPPABLE_ERRNOS, SKIPPABLE_FS_CODES, } from './errors.js';
|
|
7
7
|
import { Logger } from './observability.js';
|
|
8
8
|
import { findProjectRoot, isUnsafeCwdPath, resolveConfiguredDirs } from './path-discovery.js';
|
|
9
9
|
import { getReservedDeviceNameForPath, isPathWithinDirectories, isSamePath, isWindowsDriveRelativePath, normalizeAllowedDirectory, normalizePath, } from './path-utils.js';
|
|
10
|
-
import {
|
|
10
|
+
import { parseTrueEnvFlag, toPosixPath } from './primitives.js';
|
|
11
11
|
import { SensitiveMatcher } from './sensitive.js';
|
|
12
12
|
import { ROOTS_TIMEOUT_MS } from './util.js';
|
|
13
|
-
// Re-exported for path-completer.ts, the one consumer that reaches these
|
|
14
|
-
// through this module rather than through primitives.ts directly.
|
|
15
|
-
export { isSlash, toPosixPath };
|
|
16
13
|
/** True when `normalizedRoot` really resolves inside `bounds` (FS_ROOT_BOUNDARY). */
|
|
17
14
|
async function isRootWithin(normalizedRoot, bounds, label, signal) {
|
|
18
15
|
try {
|
|
@@ -38,18 +35,8 @@ async function filterRootsWithin(roots, bounds, label, signal) {
|
|
|
38
35
|
if (normalizedRoots.length === 0) {
|
|
39
36
|
return [];
|
|
40
37
|
}
|
|
41
|
-
const results = await Promise.
|
|
42
|
-
return normalizedRoots.filter((
|
|
43
|
-
const result = results[i];
|
|
44
|
-
if (result?.status === 'rejected') {
|
|
45
|
-
Logger.warn(`${label}: root check threw unexpectedly`, {
|
|
46
|
-
root,
|
|
47
|
-
error: String(result.reason),
|
|
48
|
-
});
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
return result?.status === 'fulfilled' && result.value;
|
|
52
|
-
});
|
|
38
|
+
const results = await Promise.all(normalizedRoots.map((root) => isRootWithin(root, normalizedBounds, label, signal)));
|
|
39
|
+
return normalizedRoots.filter((_, i) => results[i]);
|
|
53
40
|
}
|
|
54
41
|
/** `path.relative` with forward slashes, so displayed paths match across platforms. */
|
|
55
42
|
export function toPosixRelative(from, to) {
|
|
@@ -101,10 +88,9 @@ async function expandAllowedDirectories(primaryDirs, signal) {
|
|
|
101
88
|
}
|
|
102
89
|
return [...new Set(expanded)];
|
|
103
90
|
}
|
|
104
|
-
async function resolveAllowedDirectoriesState(dirs, signal) {
|
|
91
|
+
export async function resolveAllowedDirectoriesState(dirs, signal) {
|
|
105
92
|
const primary = normalizeAllowedDirectories(dirs);
|
|
106
|
-
|
|
107
|
-
return { primary, expanded };
|
|
93
|
+
return expandAllowedDirectories(primary, signal);
|
|
108
94
|
}
|
|
109
95
|
// ---------------------------------------------------------------------------
|
|
110
96
|
// Windows helpers
|
|
@@ -144,27 +130,11 @@ export class PathGuard {
|
|
|
144
130
|
// stdio + InMemoryEventStore single-process model.
|
|
145
131
|
#mutex = Promise.resolve();
|
|
146
132
|
options;
|
|
147
|
-
|
|
148
|
-
* True when this guard backs a live MCP server rather than a one-shot CLI
|
|
149
|
-
* invocation. Operator-facing configuration warnings are suppressed unless
|
|
150
|
-
* it is set, so `--print-config` and unit construction stay quiet.
|
|
151
|
-
*/
|
|
152
|
-
isServerContext;
|
|
153
|
-
constructor(options, isServerContext = false) {
|
|
133
|
+
constructor(options) {
|
|
154
134
|
this.options = options;
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const state = await resolveAllowedDirectoriesState(dirs, signal);
|
|
159
|
-
const guard = new PathGuard();
|
|
160
|
-
guard.initialize(state);
|
|
161
|
-
return guard;
|
|
162
|
-
}
|
|
163
|
-
initialize(state) {
|
|
164
|
-
this.allowedDirectoriesState = {
|
|
165
|
-
primary: [...new Set(state.primary)],
|
|
166
|
-
expanded: normalizeAllowedDirectories(state.expanded),
|
|
167
|
-
};
|
|
135
|
+
}
|
|
136
|
+
initialize(expanded) {
|
|
137
|
+
this.allowedDirectoriesState = normalizeAllowedDirectories(expanded);
|
|
168
138
|
}
|
|
169
139
|
isInitialized() {
|
|
170
140
|
return this.allowedDirectoriesState !== undefined;
|
|
@@ -183,10 +153,7 @@ export class PathGuard {
|
|
|
183
153
|
if (!this.allowedDirectoriesState) {
|
|
184
154
|
return [];
|
|
185
155
|
}
|
|
186
|
-
return [...this.allowedDirectoriesState
|
|
187
|
-
}
|
|
188
|
-
getRootBoundaries() {
|
|
189
|
-
return [...this.rootBoundaries];
|
|
156
|
+
return [...this.allowedDirectoriesState];
|
|
190
157
|
}
|
|
191
158
|
isSensitive(filePath) {
|
|
192
159
|
return this.sensitive.isSensitive(filePath);
|
|
@@ -198,16 +165,14 @@ export class PathGuard {
|
|
|
198
165
|
* directory pointing outside the sandbox would otherwise pass the lexical
|
|
199
166
|
* containment check (fs.glob follows symlinks and yields external entries as
|
|
200
167
|
* non-symlink dirents). validateExistingPathDetailed re-checks containment on
|
|
201
|
-
* the real path against this.allowedDirectoriesState
|
|
168
|
+
* the real path against this.allowedDirectoriesState (the guard's full
|
|
202
169
|
* allowed set — a superset of the single-root `bounds` callers pass) and
|
|
203
170
|
* re-checks sensitivity on the resolved target, throwing ACCESS_DENIED for
|
|
204
|
-
* escapes/sensitive, which the catch below turns into a filter.
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
* entry is filtered, not fatal). The accepted TOCTOU window is documented at
|
|
208
|
-
* the class docstring above.
|
|
171
|
+
* escapes/sensitive, which the catch below turns into a filter. Skippable
|
|
172
|
+
* errno/fs errors return false (the entry is filtered, not fatal). The
|
|
173
|
+
* accepted TOCTOU window is documented at the class docstring above.
|
|
209
174
|
*/
|
|
210
|
-
async isEntryAccessible(entryPath
|
|
175
|
+
async isEntryAccessible(entryPath) {
|
|
211
176
|
const isSensitive = (requestedPath, resolvedPath) => this.isSensitive(requestedPath) || this.isSensitive(resolvedPath);
|
|
212
177
|
try {
|
|
213
178
|
const validated = await this.validateExistingPathDetailed(entryPath);
|
|
@@ -288,7 +253,7 @@ export class PathGuard {
|
|
|
288
253
|
if (!this.allowedDirectoriesState || paths.length === 0) {
|
|
289
254
|
return [];
|
|
290
255
|
}
|
|
291
|
-
const allowedDirs = this.allowedDirectoriesState
|
|
256
|
+
const allowedDirs = this.allowedDirectoriesState;
|
|
292
257
|
const grantDirs = [];
|
|
293
258
|
for (const requested of paths) {
|
|
294
259
|
if (!requested) {
|
|
@@ -402,7 +367,7 @@ export class PathGuard {
|
|
|
402
367
|
throw new FsError(ErrorCode.ACCESS_DENIED, `Reserved Windows device name not allowed: ${reservedDevice}.`, requestedPath);
|
|
403
368
|
}
|
|
404
369
|
const normalizedRequested = normalizePath(requestedPath);
|
|
405
|
-
const allowedDirs = this.allowedDirectoriesState
|
|
370
|
+
const allowedDirs = this.allowedDirectoriesState;
|
|
406
371
|
const accessDeniedHint = allowedDirs.length > 0
|
|
407
372
|
? `Allowed: ${allowedDirs.join(', ')}`
|
|
408
373
|
: 'No allowed directories configured.';
|
|
@@ -417,7 +382,7 @@ export class PathGuard {
|
|
|
417
382
|
}
|
|
418
383
|
return {
|
|
419
384
|
normalizedRequested,
|
|
420
|
-
allowedDirs: this.allowedDirectoriesState
|
|
385
|
+
allowedDirs: this.allowedDirectoriesState,
|
|
421
386
|
accessDeniedHint,
|
|
422
387
|
};
|
|
423
388
|
}
|
|
@@ -440,12 +405,10 @@ export class PathGuard {
|
|
|
440
405
|
throw ancestorErr;
|
|
441
406
|
}
|
|
442
407
|
}
|
|
443
|
-
throw new FsError(ErrorCode.NOT_FOUND, 'Path not found', requestedPath,
|
|
408
|
+
throw new FsError(ErrorCode.NOT_FOUND, 'Path not found', requestedPath, error);
|
|
444
409
|
}
|
|
445
410
|
const mapped = isNodeError(error) && error.code !== undefined ? ERRNO_MAP[error.code] : undefined;
|
|
446
|
-
throw new FsError(mapped ?? ErrorCode.UNKNOWN, 'Cannot access path', requestedPath,
|
|
447
|
-
originalError: formatUnknownErrorMessage(error),
|
|
448
|
-
}, error instanceof Error ? error : undefined);
|
|
411
|
+
throw new FsError(mapped ?? ErrorCode.UNKNOWN, 'Cannot access path', requestedPath, error instanceof Error ? error : undefined);
|
|
449
412
|
}
|
|
450
413
|
async validateExistingPathDetailed(requestedPath) {
|
|
451
414
|
const { normalizedRequested, allowedDirs, accessDeniedHint } = this.validateAccessAndSensitivity(requestedPath);
|
|
@@ -477,9 +440,7 @@ export class PathGuard {
|
|
|
477
440
|
stats = await stat(details.resolvedPath);
|
|
478
441
|
}
|
|
479
442
|
catch (error) {
|
|
480
|
-
throw new FsError(ErrorCode.UNKNOWN, 'Cannot access directory', requestedPath,
|
|
481
|
-
originalError: formatUnknownErrorMessage(error),
|
|
482
|
-
}, error instanceof Error ? error : undefined);
|
|
443
|
+
throw new FsError(ErrorCode.UNKNOWN, 'Cannot access directory', requestedPath, error instanceof Error ? error : undefined);
|
|
483
444
|
}
|
|
484
445
|
if (!stats.isDirectory()) {
|
|
485
446
|
throw new FsError(ErrorCode.NOT_DIRECTORY, 'Not a directory', requestedPath);
|
|
@@ -552,12 +513,12 @@ export class PathGuard {
|
|
|
552
513
|
// ENOENT is expected during ancestor walk — the entry simply doesn't exist.
|
|
553
514
|
// Any other error (EACCES, EIO, ELOOP) is unexpected; fail safe.
|
|
554
515
|
if (!isNodeError(lstatErr) || lstatErr.code !== 'ENOENT') {
|
|
555
|
-
throw new FsError(ErrorCode.UNKNOWN, 'Cannot probe symlink ancestor', requestedPath,
|
|
516
|
+
throw new FsError(ErrorCode.UNKNOWN, 'Cannot probe symlink ancestor', requestedPath, lstatErr instanceof Error ? lstatErr : undefined);
|
|
556
517
|
}
|
|
557
518
|
}
|
|
558
519
|
const parent = dirname(current);
|
|
559
520
|
if (parent === current) {
|
|
560
|
-
throw new FsError(ErrorCode.UNKNOWN, 'Cannot resolve path', requestedPath,
|
|
521
|
+
throw new FsError(ErrorCode.UNKNOWN, 'Cannot resolve path', requestedPath, error instanceof Error ? error : undefined);
|
|
561
522
|
}
|
|
562
523
|
missingSegments.push(basename(current));
|
|
563
524
|
current = parent;
|
|
@@ -593,7 +554,7 @@ export class PathGuard {
|
|
|
593
554
|
realParent = await realpath(parent);
|
|
594
555
|
}
|
|
595
556
|
catch (error) {
|
|
596
|
-
throw new FsError(ErrorCode.NOT_FOUND, 'Parent directory not found', requestedPath,
|
|
557
|
+
throw new FsError(ErrorCode.NOT_FOUND, 'Parent directory not found', requestedPath, error instanceof Error ? error : undefined);
|
|
597
558
|
}
|
|
598
559
|
const normalizedRealParent = normalizePath(realParent);
|
|
599
560
|
if (!isPathWithinDirectories(normalizedRealParent, allowedDirs)) {
|
|
@@ -665,8 +626,9 @@ export class PathGuard {
|
|
|
665
626
|
: this.grantedDirectories;
|
|
666
627
|
const combined = [...baseline, ...grantsToInclude];
|
|
667
628
|
const nextState = await resolveAllowedDirectoriesState(combined, signal);
|
|
668
|
-
// Commit
|
|
669
|
-
// rejecting recompute leaves the guard's previous,
|
|
629
|
+
// Commit boundaries and the allowed set together, after every await has
|
|
630
|
+
// resolved, so a rejecting recompute leaves the guard's previous,
|
|
631
|
+
// consistent view intact.
|
|
670
632
|
this.rootBoundaries = boundaries;
|
|
671
633
|
this.initialize(nextState);
|
|
672
634
|
}
|