@flowdot.ai/guardian-agent 0.1.0

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.
Files changed (188) hide show
  1. package/LICENSE +40 -0
  2. package/README.md +281 -0
  3. package/ROADMAP.md +109 -0
  4. package/dist/audit/attestor.d.ts +102 -0
  5. package/dist/audit/attestor.d.ts.map +1 -0
  6. package/dist/audit/attestor.js +103 -0
  7. package/dist/audit/attestor.js.map +1 -0
  8. package/dist/audit/chain.d.ts +30 -0
  9. package/dist/audit/chain.d.ts.map +1 -0
  10. package/dist/audit/chain.js +65 -0
  11. package/dist/audit/chain.js.map +1 -0
  12. package/dist/audit/correlation.d.ts +114 -0
  13. package/dist/audit/correlation.d.ts.map +1 -0
  14. package/dist/audit/correlation.js +259 -0
  15. package/dist/audit/correlation.js.map +1 -0
  16. package/dist/audit/index.d.ts +13 -0
  17. package/dist/audit/index.d.ts.map +1 -0
  18. package/dist/audit/index.js +8 -0
  19. package/dist/audit/index.js.map +1 -0
  20. package/dist/audit/reader.d.ts +30 -0
  21. package/dist/audit/reader.d.ts.map +1 -0
  22. package/dist/audit/reader.js +85 -0
  23. package/dist/audit/reader.js.map +1 -0
  24. package/dist/audit/signature.d.ts +39 -0
  25. package/dist/audit/signature.d.ts.map +1 -0
  26. package/dist/audit/signature.js +73 -0
  27. package/dist/audit/signature.js.map +1 -0
  28. package/dist/audit/stats.d.ts +106 -0
  29. package/dist/audit/stats.d.ts.map +1 -0
  30. package/dist/audit/stats.js +196 -0
  31. package/dist/audit/stats.js.map +1 -0
  32. package/dist/audit/writer.d.ts +96 -0
  33. package/dist/audit/writer.d.ts.map +1 -0
  34. package/dist/audit/writer.js +263 -0
  35. package/dist/audit/writer.js.map +1 -0
  36. package/dist/cli/guardian-baseline.d.ts +42 -0
  37. package/dist/cli/guardian-baseline.d.ts.map +1 -0
  38. package/dist/cli/guardian-baseline.js +265 -0
  39. package/dist/cli/guardian-baseline.js.map +1 -0
  40. package/dist/cli/guardian-correlator.d.ts +47 -0
  41. package/dist/cli/guardian-correlator.d.ts.map +1 -0
  42. package/dist/cli/guardian-correlator.js +217 -0
  43. package/dist/cli/guardian-correlator.js.map +1 -0
  44. package/dist/cli/guardian-verify.d.ts +30 -0
  45. package/dist/cli/guardian-verify.d.ts.map +1 -0
  46. package/dist/cli/guardian-verify.js +149 -0
  47. package/dist/cli/guardian-verify.js.map +1 -0
  48. package/dist/errors.d.ts +28 -0
  49. package/dist/errors.d.ts.map +1 -0
  50. package/dist/errors.js +40 -0
  51. package/dist/errors.js.map +1 -0
  52. package/dist/estop/heartbeat.d.ts +94 -0
  53. package/dist/estop/heartbeat.d.ts.map +1 -0
  54. package/dist/estop/heartbeat.js +135 -0
  55. package/dist/estop/heartbeat.js.map +1 -0
  56. package/dist/estop/hub.d.ts +76 -0
  57. package/dist/estop/hub.d.ts.map +1 -0
  58. package/dist/estop/hub.js +167 -0
  59. package/dist/estop/hub.js.map +1 -0
  60. package/dist/estop/index.d.ts +12 -0
  61. package/dist/estop/index.d.ts.map +1 -0
  62. package/dist/estop/index.js +6 -0
  63. package/dist/estop/index.js.map +1 -0
  64. package/dist/estop/local.d.ts +31 -0
  65. package/dist/estop/local.d.ts.map +1 -0
  66. package/dist/estop/local.js +101 -0
  67. package/dist/estop/local.js.map +1 -0
  68. package/dist/estop/middleware.d.ts +36 -0
  69. package/dist/estop/middleware.d.ts.map +1 -0
  70. package/dist/estop/middleware.js +40 -0
  71. package/dist/estop/middleware.js.map +1 -0
  72. package/dist/estop/poller.d.ts +36 -0
  73. package/dist/estop/poller.d.ts.map +1 -0
  74. package/dist/estop/poller.js +85 -0
  75. package/dist/estop/poller.js.map +1 -0
  76. package/dist/estop/types.d.ts +31 -0
  77. package/dist/estop/types.d.ts.map +1 -0
  78. package/dist/estop/types.js +5 -0
  79. package/dist/estop/types.js.map +1 -0
  80. package/dist/gate/async-callback.d.ts +27 -0
  81. package/dist/gate/async-callback.d.ts.map +1 -0
  82. package/dist/gate/async-callback.js +79 -0
  83. package/dist/gate/async-callback.js.map +1 -0
  84. package/dist/gate/cli.d.ts +29 -0
  85. package/dist/gate/cli.d.ts.map +1 -0
  86. package/dist/gate/cli.js +83 -0
  87. package/dist/gate/cli.js.map +1 -0
  88. package/dist/gate/data-channel.d.ts +41 -0
  89. package/dist/gate/data-channel.d.ts.map +1 -0
  90. package/dist/gate/data-channel.js +132 -0
  91. package/dist/gate/data-channel.js.map +1 -0
  92. package/dist/gate/index.d.ts +13 -0
  93. package/dist/gate/index.d.ts.map +1 -0
  94. package/dist/gate/index.js +7 -0
  95. package/dist/gate/index.js.map +1 -0
  96. package/dist/gate/options.d.ts +90 -0
  97. package/dist/gate/options.d.ts.map +1 -0
  98. package/dist/gate/options.js +131 -0
  99. package/dist/gate/options.js.map +1 -0
  100. package/dist/gate/programmatic.d.ts +9 -0
  101. package/dist/gate/programmatic.d.ts.map +1 -0
  102. package/dist/gate/programmatic.js +20 -0
  103. package/dist/gate/programmatic.js.map +1 -0
  104. package/dist/gate/two-key.d.ts +90 -0
  105. package/dist/gate/two-key.d.ts.map +1 -0
  106. package/dist/gate/two-key.js +78 -0
  107. package/dist/gate/two-key.js.map +1 -0
  108. package/dist/gate/types.d.ts +25 -0
  109. package/dist/gate/types.d.ts.map +1 -0
  110. package/dist/gate/types.js +5 -0
  111. package/dist/gate/types.js.map +1 -0
  112. package/dist/index.d.ts +33 -0
  113. package/dist/index.d.ts.map +1 -0
  114. package/dist/index.js +26 -0
  115. package/dist/index.js.map +1 -0
  116. package/dist/notify/console.d.ts +13 -0
  117. package/dist/notify/console.d.ts.map +1 -0
  118. package/dist/notify/console.js +27 -0
  119. package/dist/notify/console.js.map +1 -0
  120. package/dist/notify/index.d.ts +8 -0
  121. package/dist/notify/index.d.ts.map +1 -0
  122. package/dist/notify/index.js +4 -0
  123. package/dist/notify/index.js.map +1 -0
  124. package/dist/notify/multi.d.ts +14 -0
  125. package/dist/notify/multi.d.ts.map +1 -0
  126. package/dist/notify/multi.js +22 -0
  127. package/dist/notify/multi.js.map +1 -0
  128. package/dist/notify/types.d.ts +21 -0
  129. package/dist/notify/types.d.ts.map +1 -0
  130. package/dist/notify/types.js +5 -0
  131. package/dist/notify/types.js.map +1 -0
  132. package/dist/notify/webhook.d.ts +21 -0
  133. package/dist/notify/webhook.d.ts.map +1 -0
  134. package/dist/notify/webhook.js +37 -0
  135. package/dist/notify/webhook.js.map +1 -0
  136. package/dist/policy/attribution.d.ts +61 -0
  137. package/dist/policy/attribution.d.ts.map +1 -0
  138. package/dist/policy/attribution.js +116 -0
  139. package/dist/policy/attribution.js.map +1 -0
  140. package/dist/policy/evaluator.d.ts +36 -0
  141. package/dist/policy/evaluator.d.ts.map +1 -0
  142. package/dist/policy/evaluator.js +211 -0
  143. package/dist/policy/evaluator.js.map +1 -0
  144. package/dist/policy/index.d.ts +11 -0
  145. package/dist/policy/index.d.ts.map +1 -0
  146. package/dist/policy/index.js +7 -0
  147. package/dist/policy/index.js.map +1 -0
  148. package/dist/policy/integrity.d.ts +17 -0
  149. package/dist/policy/integrity.d.ts.map +1 -0
  150. package/dist/policy/integrity.js +31 -0
  151. package/dist/policy/integrity.js.map +1 -0
  152. package/dist/policy/loader.d.ts +9 -0
  153. package/dist/policy/loader.d.ts.map +1 -0
  154. package/dist/policy/loader.js +124 -0
  155. package/dist/policy/loader.js.map +1 -0
  156. package/dist/policy/site-key.d.ts +22 -0
  157. package/dist/policy/site-key.d.ts.map +1 -0
  158. package/dist/policy/site-key.js +48 -0
  159. package/dist/policy/site-key.js.map +1 -0
  160. package/dist/policy/store.d.ts +45 -0
  161. package/dist/policy/store.d.ts.map +1 -0
  162. package/dist/policy/store.js +223 -0
  163. package/dist/policy/store.js.map +1 -0
  164. package/dist/policy/types.d.ts +72 -0
  165. package/dist/policy/types.d.ts.map +1 -0
  166. package/dist/policy/types.js +5 -0
  167. package/dist/policy/types.js.map +1 -0
  168. package/dist/runtime/capability.d.ts +125 -0
  169. package/dist/runtime/capability.d.ts.map +1 -0
  170. package/dist/runtime/capability.js +121 -0
  171. package/dist/runtime/capability.js.map +1 -0
  172. package/dist/runtime/honeytokens.d.ts +104 -0
  173. package/dist/runtime/honeytokens.d.ts.map +1 -0
  174. package/dist/runtime/honeytokens.js +115 -0
  175. package/dist/runtime/honeytokens.js.map +1 -0
  176. package/dist/runtime/multi-rate-limiter.d.ts +90 -0
  177. package/dist/runtime/multi-rate-limiter.d.ts.map +1 -0
  178. package/dist/runtime/multi-rate-limiter.js +133 -0
  179. package/dist/runtime/multi-rate-limiter.js.map +1 -0
  180. package/dist/runtime/runtime.d.ts +94 -0
  181. package/dist/runtime/runtime.d.ts.map +1 -0
  182. package/dist/runtime/runtime.js +276 -0
  183. package/dist/runtime/runtime.js.map +1 -0
  184. package/dist/types.d.ts +97 -0
  185. package/dist/types.d.ts.map +1 -0
  186. package/dist/types.js +5 -0
  187. package/dist/types.js.map +1 -0
  188. package/package.json +83 -0
@@ -0,0 +1,27 @@
1
+ /**
2
+ * consoleNotifier — writes notification events to stderr (or a configured stream).
3
+ * SPEC §6.3.
4
+ */
5
+ export function consoleNotifier(options = {}) {
6
+ const stream = options.stream ?? process.stderr;
7
+ const prefix = options.prefix ?? '[guardian]';
8
+ return {
9
+ notify: async (event) => {
10
+ stream.write(`${prefix} ${formatEvent(event)}\n`);
11
+ },
12
+ };
13
+ }
14
+ function formatEvent(event) {
15
+ const parts = [event.kind, `agent=${event.agentId || '-'}`, `source=${event.source}`];
16
+ if (event.userId !== undefined)
17
+ parts.push(`user=${event.userId}`);
18
+ parts.push(`at=${event.ts}`);
19
+ const summary = JSON.stringify(event.summary);
20
+ if (summary !== '{}')
21
+ parts.push(`summary=${summary}`);
22
+ if (event.canonicalClearUrl !== undefined) {
23
+ parts.push(`clear=${event.canonicalClearUrl}`);
24
+ }
25
+ return parts.join(' ');
26
+ }
27
+ //# sourceMappingURL=console.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console.js","sourceRoot":"","sources":["../../src/notify/console.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,MAAM,UAAU,eAAe,CAAC,UAAkC,EAAE;IAClE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IAChD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,YAAY,CAAC;IAC9C,OAAO;QACL,MAAM,EAAE,KAAK,EAAE,KAAwB,EAAiB,EAAE;YACxD,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,IAAI,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACpD,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,KAAwB;IAC3C,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,KAAK,CAAC,OAAO,IAAI,GAAG,EAAE,EAAE,UAAU,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACtF,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACnE,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;IAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC9C,IAAI,OAAO,KAAK,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,OAAO,EAAE,CAAC,CAAC;IACvD,IAAI,KAAK,CAAC,iBAAiB,KAAK,SAAS,EAAE,CAAC;QAC1C,KAAK,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC,iBAAiB,EAAE,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC"}
@@ -0,0 +1,8 @@
1
+ export type { Notifier, NotificationEvent, NotificationKind } from './types.js';
2
+ export { consoleNotifier } from './console.js';
3
+ export type { ConsoleNotifierOptions } from './console.js';
4
+ export { webhookNotifier } from './webhook.js';
5
+ export type { WebhookNotifierOptions } from './webhook.js';
6
+ export { multiNotifier } from './multi.js';
7
+ export type { MultiNotifierOptions } from './multi.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/notify/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,QAAQ,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAChF,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,YAAY,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,YAAY,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,YAAY,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { consoleNotifier } from './console.js';
2
+ export { webhookNotifier } from './webhook.js';
3
+ export { multiNotifier } from './multi.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/notify/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * multiNotifier — fan a notification out to several notifiers in parallel.
3
+ * SPEC §6.3.
4
+ *
5
+ * Failures in one notifier do NOT stop the others. Errors are collected and
6
+ * reported via the optional `onError` callback.
7
+ */
8
+ import type { Notifier, NotificationEvent } from './types.js';
9
+ export interface MultiNotifierOptions {
10
+ notifiers: readonly Notifier[];
11
+ onError?: (err: unknown, event: NotificationEvent, index: number) => void;
12
+ }
13
+ export declare function multiNotifier(options: MultiNotifierOptions): Notifier;
14
+ //# sourceMappingURL=multi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi.d.ts","sourceRoot":"","sources":["../../src/notify/multi.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE9D,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,SAAS,QAAQ,EAAE,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3E;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,QAAQ,CAerE"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * multiNotifier — fan a notification out to several notifiers in parallel.
3
+ * SPEC §6.3.
4
+ *
5
+ * Failures in one notifier do NOT stop the others. Errors are collected and
6
+ * reported via the optional `onError` callback.
7
+ */
8
+ export function multiNotifier(options) {
9
+ const { notifiers, onError } = options;
10
+ return {
11
+ notify: async (event) => {
12
+ const results = await Promise.allSettled(notifiers.map(async (n) => n.notify(event)));
13
+ for (let i = 0; i < results.length; i++) {
14
+ const r = results[i];
15
+ if (r?.status === 'rejected') {
16
+ onError?.(r.reason, event, i);
17
+ }
18
+ }
19
+ },
20
+ };
21
+ }
22
+ //# sourceMappingURL=multi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi.js","sourceRoot":"","sources":["../../src/notify/multi.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,MAAM,UAAU,aAAa,CAAC,OAA6B;IACzD,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IACvC,OAAO;QACL,MAAM,EAAE,KAAK,EAAE,KAAwB,EAAiB,EAAE;YACxD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CACtC,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAC5C,CAAC;YACF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACxC,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;gBACrB,IAAI,CAAC,EAAE,MAAM,KAAK,UAAU,EAAE,CAAC;oBAC7B,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;gBAChC,CAAC;YACH,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Notification types. SPEC §6.
3
+ */
4
+ export type NotificationKind = 'estop_press' | 'estop_clear' | 'policy_breach' | 'gate_denied';
5
+ export interface NotificationEvent {
6
+ kind: NotificationKind;
7
+ /** User identifier on hub-coordinated deployments; undefined in single-process. */
8
+ userId?: string;
9
+ agentId: string;
10
+ ts: string;
11
+ /** "cli" | "native" | "mobile" | "hub" | "local" | host-defined */
12
+ source: string;
13
+ /** Free-form structured summary (counts, IP, reason, …). */
14
+ summary: Record<string, unknown>;
15
+ /** Optional canonical clear URL for hub-coordinated deployments. */
16
+ canonicalClearUrl?: string;
17
+ }
18
+ export interface Notifier {
19
+ notify(event: NotificationEvent): Promise<void>;
20
+ }
21
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/notify/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,gBAAgB,GACxB,aAAa,GACb,aAAa,GACb,eAAe,GACf,aAAa,CAAC;AAElB,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,gBAAgB,CAAC;IACvB,mFAAmF;IACnF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjD"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Notification types. SPEC §6.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/notify/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * webhookNotifier — POSTs notification events as JSON to a URL.
3
+ * SPEC §6.3.
4
+ *
5
+ * Failures are reported via the optional `onError` callback; they do NOT
6
+ * throw, because notifier failures must never block the press/clear flow
7
+ * the notification accompanies.
8
+ */
9
+ import type { Notifier, NotificationEvent } from './types.js';
10
+ export interface WebhookNotifierOptions {
11
+ url: string;
12
+ headers?: Record<string, string>;
13
+ /** Override fetch (for testing). */
14
+ fetch?: typeof fetch;
15
+ /** Callback on non-2xx, network error, or timeout. */
16
+ onError?: (err: unknown, event: NotificationEvent) => void;
17
+ /** Request timeout in ms. Defaults to 5000. */
18
+ timeoutMs?: number;
19
+ }
20
+ export declare function webhookNotifier(options: WebhookNotifierOptions): Notifier;
21
+ //# sourceMappingURL=webhook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/notify/webhook.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE9D,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,oCAAoC;IACpC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,sDAAsD;IACtD,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC3D,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,QAAQ,CA4BzE"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * webhookNotifier — POSTs notification events as JSON to a URL.
3
+ * SPEC §6.3.
4
+ *
5
+ * Failures are reported via the optional `onError` callback; they do NOT
6
+ * throw, because notifier failures must never block the press/clear flow
7
+ * the notification accompanies.
8
+ */
9
+ const DEFAULT_TIMEOUT_MS = 5000;
10
+ export function webhookNotifier(options) {
11
+ const fetchImpl = options.fetch ?? fetch;
12
+ return {
13
+ notify: async (event) => {
14
+ const controller = new AbortController();
15
+ const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
16
+ try {
17
+ const resp = await fetchImpl(options.url, {
18
+ method: 'POST',
19
+ headers: {
20
+ 'content-type': 'application/json',
21
+ ...(options.headers ?? {}),
22
+ },
23
+ body: JSON.stringify(event),
24
+ signal: controller.signal,
25
+ });
26
+ if (!resp.ok) {
27
+ options.onError?.(new Error(`webhook_status_${resp.status}`), event);
28
+ }
29
+ }
30
+ catch (err) {
31
+ options.onError?.(err, event);
32
+ }
33
+ clearTimeout(timer);
34
+ },
35
+ };
36
+ }
37
+ //# sourceMappingURL=webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/notify/webhook.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAeH,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEhC,MAAM,UAAU,eAAe,CAAC,OAA+B;IAC7D,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC;IACzC,OAAO;QACL,MAAM,EAAE,KAAK,EAAE,KAAwB,EAAiB,EAAE;YACxD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,MAAM,KAAK,GAAG,UAAU,CACtB,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EACxB,OAAO,CAAC,SAAS,IAAI,kBAAkB,CACxC,CAAC;YACF,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE;oBACxC,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE;wBACP,cAAc,EAAE,kBAAkB;wBAClC,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;qBAC3B;oBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;oBAC3B,MAAM,EAAE,UAAU,CAAC,MAAM;iBAC1B,CAAC,CAAC;gBACH,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;oBACb,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,KAAK,CAAC,kBAAkB,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;gBACvE,CAAC;YACH,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAChC,CAAC;YACD,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Model-attribution path rendering + glob matching. SPEC §3 extension (v0.7+).
3
+ *
4
+ * A {@link ModelAttribution} renders to a 4-segment path:
5
+ *
6
+ * surface / aggregator / provider / id
7
+ *
8
+ * Missing segments render as `'*'`. Examples:
9
+ *
10
+ * { provider: 'Anthropic', id: 'claude-opus-4.5' }
11
+ * → "* /* /Anthropic/claude-opus-4.5"
12
+ *
13
+ * { surface: 'FlowDot', aggregator: 'RedPill',
14
+ * provider: 'Anthropic', id: 'claude-opus-4.5' }
15
+ * → "FlowDot/RedPill/Anthropic/claude-opus-4.5"
16
+ *
17
+ * Pattern matching is **flat-glob**: `*` matches any run of characters
18
+ * including `/`. This lets simple substring-style patterns like
19
+ * `*claude-opus*` match the rendered path regardless of which provider,
20
+ * aggregator, or surface issued the call. Authors who want to constrain a
21
+ * specific segment write the slashes explicitly:
22
+ *
23
+ * "* /RedPill/* /*" → anything routed through RedPill
24
+ * "FlowDot/* /Anthropic/*" → any Anthropic model from FlowDot, any aggregator
25
+ * "* /* /Anthropic/claude-*-4.5*" → any claude-*-4.5* model from Anthropic
26
+ * "*claude-opus*" → any claude-opus model anywhere
27
+ *
28
+ * Character classes (`[abc]`, `[!abc]`) and single-char wildcards (`?`) are
29
+ * also supported — same semantics as `policy/evaluator.ts:globMatch` but
30
+ * without the segment-bound restriction (since flat-glob has no segments).
31
+ */
32
+ import type { ModelAttribution } from '../types.js';
33
+ /**
34
+ * The placeholder used for missing segments. Chosen so a wildcard pattern
35
+ * like `* /* /Anthropic/*` still matches an attribution missing both surface
36
+ * and aggregator.
37
+ */
38
+ export declare const ATTRIBUTION_MISSING_SEGMENT = "*";
39
+ /**
40
+ * Render a {@link ModelAttribution} as a 4-segment path. Missing fields
41
+ * become `'*'`. The path never contains an empty segment.
42
+ */
43
+ export declare function renderAttributionPath(attribution: ModelAttribution): string;
44
+ /**
45
+ * Test whether a pattern matches the rendered attribution path.
46
+ *
47
+ * Pattern syntax (flat-glob):
48
+ * `*` — any run of characters, including `/`
49
+ * `?` — exactly one character (including `/`)
50
+ * `[seq]` — character class
51
+ * `[!seq]` — negated character class
52
+ *
53
+ * The pattern is anchored: it matches the full path, not a prefix.
54
+ */
55
+ export declare function matchAttributionPath(pattern: string, attribution: ModelAttribution): boolean;
56
+ /**
57
+ * Pattern matching against a raw rendered path string. Exposed for
58
+ * `PolicyEvaluator` and for testing.
59
+ */
60
+ export declare function flatGlobMatch(pattern: string, value: string): boolean;
61
+ //# sourceMappingURL=attribution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attribution.d.ts","sourceRoot":"","sources":["../../src/policy/attribution.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,gBAAgB,GAAG,MAAM,CAI3E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,gBAAgB,GAAG,OAAO,CAE5F;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAErE"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Model-attribution path rendering + glob matching. SPEC §3 extension (v0.7+).
3
+ *
4
+ * A {@link ModelAttribution} renders to a 4-segment path:
5
+ *
6
+ * surface / aggregator / provider / id
7
+ *
8
+ * Missing segments render as `'*'`. Examples:
9
+ *
10
+ * { provider: 'Anthropic', id: 'claude-opus-4.5' }
11
+ * → "* /* /Anthropic/claude-opus-4.5"
12
+ *
13
+ * { surface: 'FlowDot', aggregator: 'RedPill',
14
+ * provider: 'Anthropic', id: 'claude-opus-4.5' }
15
+ * → "FlowDot/RedPill/Anthropic/claude-opus-4.5"
16
+ *
17
+ * Pattern matching is **flat-glob**: `*` matches any run of characters
18
+ * including `/`. This lets simple substring-style patterns like
19
+ * `*claude-opus*` match the rendered path regardless of which provider,
20
+ * aggregator, or surface issued the call. Authors who want to constrain a
21
+ * specific segment write the slashes explicitly:
22
+ *
23
+ * "* /RedPill/* /*" → anything routed through RedPill
24
+ * "FlowDot/* /Anthropic/*" → any Anthropic model from FlowDot, any aggregator
25
+ * "* /* /Anthropic/claude-*-4.5*" → any claude-*-4.5* model from Anthropic
26
+ * "*claude-opus*" → any claude-opus model anywhere
27
+ *
28
+ * Character classes (`[abc]`, `[!abc]`) and single-char wildcards (`?`) are
29
+ * also supported — same semantics as `policy/evaluator.ts:globMatch` but
30
+ * without the segment-bound restriction (since flat-glob has no segments).
31
+ */
32
+ /**
33
+ * The placeholder used for missing segments. Chosen so a wildcard pattern
34
+ * like `* /* /Anthropic/*` still matches an attribution missing both surface
35
+ * and aggregator.
36
+ */
37
+ export const ATTRIBUTION_MISSING_SEGMENT = '*';
38
+ /**
39
+ * Render a {@link ModelAttribution} as a 4-segment path. Missing fields
40
+ * become `'*'`. The path never contains an empty segment.
41
+ */
42
+ export function renderAttributionPath(attribution) {
43
+ const surface = attribution.surface ?? ATTRIBUTION_MISSING_SEGMENT;
44
+ const aggregator = attribution.aggregator ?? ATTRIBUTION_MISSING_SEGMENT;
45
+ return `${surface}/${aggregator}/${attribution.provider}/${attribution.id}`;
46
+ }
47
+ /**
48
+ * Test whether a pattern matches the rendered attribution path.
49
+ *
50
+ * Pattern syntax (flat-glob):
51
+ * `*` — any run of characters, including `/`
52
+ * `?` — exactly one character (including `/`)
53
+ * `[seq]` — character class
54
+ * `[!seq]` — negated character class
55
+ *
56
+ * The pattern is anchored: it matches the full path, not a prefix.
57
+ */
58
+ export function matchAttributionPath(pattern, attribution) {
59
+ return flatGlobMatch(pattern, renderAttributionPath(attribution));
60
+ }
61
+ /**
62
+ * Pattern matching against a raw rendered path string. Exposed for
63
+ * `PolicyEvaluator` and for testing.
64
+ */
65
+ export function flatGlobMatch(pattern, value) {
66
+ return flatGlobToRegExp(pattern).test(value);
67
+ }
68
+ function flatGlobToRegExp(pattern) {
69
+ let out = '^';
70
+ let i = 0;
71
+ while (i < pattern.length) {
72
+ const c = pattern[i];
73
+ if (c === '*') {
74
+ out += '.*';
75
+ i++;
76
+ }
77
+ else if (c === '?') {
78
+ out += '.';
79
+ i++;
80
+ }
81
+ else if (c === '[') {
82
+ let end = i + 1;
83
+ let negate = false;
84
+ if (pattern[end] === '!') {
85
+ negate = true;
86
+ end++;
87
+ }
88
+ let body = '';
89
+ while (end < pattern.length && pattern[end] !== ']') {
90
+ body += pattern[end];
91
+ end++;
92
+ }
93
+ if (end >= pattern.length) {
94
+ out += '\\[';
95
+ i++;
96
+ }
97
+ else {
98
+ out += '[' + (negate ? '^' : '') + escapeForCharClass(body) + ']';
99
+ i = end + 1;
100
+ }
101
+ }
102
+ else {
103
+ out += escapeRegex(c);
104
+ i++;
105
+ }
106
+ }
107
+ out += '$';
108
+ return new RegExp(out);
109
+ }
110
+ function escapeRegex(c) {
111
+ return /[\\^$.*+?()[\]{}|]/.test(c) ? '\\' + c : c;
112
+ }
113
+ function escapeForCharClass(body) {
114
+ return body.replace(/[\\\]]/g, (m) => '\\' + m);
115
+ }
116
+ //# sourceMappingURL=attribution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attribution.js","sourceRoot":"","sources":["../../src/policy/attribution.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * PolicyEvaluator — resolution order + wildcards. SPEC §3.3, §3.4.
3
+ *
4
+ * Resolution order:
5
+ * 1. banned (forever-deny) exact match
6
+ * 2. banned wildcard match
7
+ * 3. forever-allow exact match
8
+ * 4. forever-allow wildcard match
9
+ * 5. session-allow exact match
10
+ * 6. session-allow wildcard match
11
+ * 7. once-allow exact match (rare — `once` is normally not persisted but
12
+ * can appear if a runtime queries with a transient rule loaded)
13
+ * 8. once-allow wildcard match
14
+ * 9. defaults.scope if not 'prompt'
15
+ * 10. 'prompt' (consult gate)
16
+ *
17
+ * Banned-beats-allow at every layer: a `scope: banned` rule cannot be
18
+ * overridden by an allow rule at any other scope.
19
+ */
20
+ import type { ModelAttribution } from '../types.js';
21
+ import type { Policy, PolicyEvaluation, PolicyScope } from './types.js';
22
+ export declare class PolicyEvaluator {
23
+ private readonly policy;
24
+ constructor(policy: Policy);
25
+ /** Evaluate a tool name (optionally with model attribution for `when` rules). */
26
+ evaluate(toolName: string, model?: ModelAttribution): PolicyEvaluation;
27
+ /** Helper for tests / debugging. */
28
+ rankFor(scope: PolicyScope): number;
29
+ private firstMatch;
30
+ }
31
+ /**
32
+ * Minimal fnmatch-equivalent. Supports `*` (any sequence), `?` (one char),
33
+ * and `[seq]` / `[!seq]` character classes.
34
+ */
35
+ export declare function globMatch(pattern: string, name: string): boolean;
36
+ //# sourceMappingURL=evaluator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluator.d.ts","sourceRoot":"","sources":["../../src/policy/evaluator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAc,WAAW,EAAE,MAAM,YAAY,CAAC;AASpF,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,MAAM,EAAE,MAAM;IAI1B,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,gBAAgB,GAAG,gBAAgB;IAkEtE,oCAAoC;IACpC,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM;IAMnC,OAAO,CAAC,UAAU;CAsBnB;AAsCD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAGhE"}
@@ -0,0 +1,211 @@
1
+ /**
2
+ * PolicyEvaluator — resolution order + wildcards. SPEC §3.3, §3.4.
3
+ *
4
+ * Resolution order:
5
+ * 1. banned (forever-deny) exact match
6
+ * 2. banned wildcard match
7
+ * 3. forever-allow exact match
8
+ * 4. forever-allow wildcard match
9
+ * 5. session-allow exact match
10
+ * 6. session-allow wildcard match
11
+ * 7. once-allow exact match (rare — `once` is normally not persisted but
12
+ * can appear if a runtime queries with a transient rule loaded)
13
+ * 8. once-allow wildcard match
14
+ * 9. defaults.scope if not 'prompt'
15
+ * 10. 'prompt' (consult gate)
16
+ *
17
+ * Banned-beats-allow at every layer: a `scope: banned` rule cannot be
18
+ * overridden by an allow rule at any other scope.
19
+ */
20
+ import { matchAttributionPath } from './attribution.js';
21
+ const RANK = {
22
+ banned: 0,
23
+ forever: 1,
24
+ session: 2,
25
+ once: 3,
26
+ };
27
+ export class PolicyEvaluator {
28
+ policy;
29
+ constructor(policy) {
30
+ this.policy = policy;
31
+ }
32
+ /** Evaluate a tool name (optionally with model attribution for `when` rules). */
33
+ evaluate(toolName, model) {
34
+ // Walk rules in resolution order. We sort once on construction to make
35
+ // multiple evaluations cheap, but for code simplicity we do it per-call.
36
+ const banned = this.firstMatch(toolName, (r) => r.scope === 'banned' && whenMatches(r, model));
37
+ if (banned !== null) {
38
+ return {
39
+ decision: 'deny',
40
+ matchedRule: banned.rule,
41
+ matchedAt: banned.matchedAt,
42
+ scope: 'banned',
43
+ };
44
+ }
45
+ // Walk allow scopes in order forever > session > once.
46
+ for (const scope of ['forever', 'session', 'once']) {
47
+ const m = this.firstMatch(toolName, (r) => r.scope === scope && effectiveDecision(r) === 'allow' && whenMatches(r, model));
48
+ if (m !== null) {
49
+ return {
50
+ decision: 'allow',
51
+ matchedRule: m.rule,
52
+ matchedAt: m.matchedAt,
53
+ scope,
54
+ };
55
+ }
56
+ // A deny at this scope also short-circuits (though uncommon outside `banned`).
57
+ const denyAt = this.firstMatch(toolName, (r) => r.scope === scope && effectiveDecision(r) === 'deny' && whenMatches(r, model));
58
+ if (denyAt !== null) {
59
+ return {
60
+ decision: 'deny',
61
+ matchedRule: denyAt.rule,
62
+ matchedAt: denyAt.matchedAt,
63
+ scope,
64
+ };
65
+ }
66
+ }
67
+ // Fall back to defaults.
68
+ const d = this.policy.defaults;
69
+ if (d.scope === 'prompt') {
70
+ return {
71
+ decision: 'prompt',
72
+ matchedRule: undefined,
73
+ matchedAt: 'default',
74
+ scope: 'prompt',
75
+ };
76
+ }
77
+ const decision = d.decision ?? (d.scope === 'banned' ? 'deny' : 'allow');
78
+ return {
79
+ decision,
80
+ matchedRule: undefined,
81
+ matchedAt: 'default',
82
+ scope: d.scope,
83
+ };
84
+ }
85
+ /** Helper for tests / debugging. */
86
+ rankFor(scope) {
87
+ return RANK[scope];
88
+ }
89
+ // ---- internal --------------------------------------------------------------
90
+ firstMatch(toolName, pred) {
91
+ // Exact match: rule.tool equals toolName (and `pred` holds). Category
92
+ // matches (`category:<name>` form) also flow through here — the prefix is
93
+ // just a naming convention. `matchedAt: 'category'` is reserved for a
94
+ // future wildcard-category form not yet specified.
95
+ for (const rule of this.policy.rules) {
96
+ if (rule.tool === toolName && pred(rule)) {
97
+ return { rule, matchedAt: 'exact' };
98
+ }
99
+ }
100
+ // Wildcard match (declaration order wins among multiple matches).
101
+ for (const rule of this.policy.rules) {
102
+ if (rule.tool === toolName)
103
+ continue; // already handled
104
+ if (containsGlobChars(rule.tool) && globMatch(rule.tool, toolName) && pred(rule)) {
105
+ return { rule, matchedAt: 'wildcard' };
106
+ }
107
+ }
108
+ return null;
109
+ }
110
+ }
111
+ function effectiveDecision(rule) {
112
+ /* c8 ignore start */
113
+ if (rule.scope === 'banned')
114
+ return 'deny';
115
+ /* c8 ignore stop */
116
+ return rule.decision ?? 'allow';
117
+ }
118
+ /**
119
+ * Returns true iff the rule's `when` clause is satisfied by the given model.
120
+ * Rules without `when` always match. Glob support: `*`, `?`, `[seq]`.
121
+ *
122
+ * If `when` requires a model attribute but no model is supplied, the rule
123
+ * fails to match (defensive — model-conditional rules SHOULD NOT fire
124
+ * unconditionally).
125
+ */
126
+ function whenMatches(rule, model) {
127
+ if (!rule.when)
128
+ return true;
129
+ if (rule.when['model.provider'] !== undefined) {
130
+ if (!model)
131
+ return false;
132
+ if (!globMatch(rule.when['model.provider'], model.provider))
133
+ return false;
134
+ }
135
+ if (rule.when['model.id'] !== undefined) {
136
+ if (!model)
137
+ return false;
138
+ if (!globMatch(rule.when['model.id'], model.id))
139
+ return false;
140
+ }
141
+ if (rule.when.attribution_path !== undefined) {
142
+ if (!model)
143
+ return false;
144
+ if (!matchAttributionPath(rule.when.attribution_path, model))
145
+ return false;
146
+ }
147
+ return true;
148
+ }
149
+ function containsGlobChars(s) {
150
+ return s.includes('*') || s.includes('?') || s.includes('[');
151
+ }
152
+ /**
153
+ * Minimal fnmatch-equivalent. Supports `*` (any sequence), `?` (one char),
154
+ * and `[seq]` / `[!seq]` character classes.
155
+ */
156
+ export function globMatch(pattern, name) {
157
+ const re = globToRegExp(pattern);
158
+ return re.test(name);
159
+ }
160
+ function globToRegExp(pattern) {
161
+ let out = '^';
162
+ let i = 0;
163
+ while (i < pattern.length) {
164
+ const c = pattern[i];
165
+ if (c === '*') {
166
+ out += '.*';
167
+ i++;
168
+ }
169
+ else if (c === '?') {
170
+ out += '.';
171
+ i++;
172
+ }
173
+ else if (c === '[') {
174
+ // Character class
175
+ let end = i + 1;
176
+ let negate = false;
177
+ if (pattern[end] === '!') {
178
+ negate = true;
179
+ end++;
180
+ }
181
+ let body = '';
182
+ while (end < pattern.length && pattern[end] !== ']') {
183
+ body += pattern[end];
184
+ end++;
185
+ }
186
+ if (end >= pattern.length) {
187
+ // unmatched '[' — treat literally.
188
+ out += '\\[';
189
+ i++;
190
+ }
191
+ else {
192
+ out += '[' + (negate ? '^' : '') + escapeForCharClass(body) + ']';
193
+ i = end + 1;
194
+ }
195
+ }
196
+ else {
197
+ out += escapeRegex(c);
198
+ i++;
199
+ }
200
+ }
201
+ out += '$';
202
+ return new RegExp(out);
203
+ }
204
+ function escapeRegex(c) {
205
+ return /[\\^$.*+?()[\]{}|]/.test(c) ? '\\' + c : c;
206
+ }
207
+ function escapeForCharClass(body) {
208
+ // Inside a char class, escape `]` and `\` only.
209
+ return body.replace(/[\\\]]/g, (m) => '\\' + m);
210
+ }
211
+ //# sourceMappingURL=evaluator.js.map