@postalsys/email-content 0.0.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.
Files changed (210) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +288 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/analyze.d.ts +91 -0
  5. package/dist/analyze.d.ts.map +1 -0
  6. package/dist/analyze.js +274 -0
  7. package/dist/analyze.js.map +1 -0
  8. package/dist/annotate/client.d.ts +64 -0
  9. package/dist/annotate/client.d.ts.map +1 -0
  10. package/dist/annotate/client.js +139 -0
  11. package/dist/annotate/client.js.map +1 -0
  12. package/dist/annotate/llm.d.ts +46 -0
  13. package/dist/annotate/llm.d.ts.map +1 -0
  14. package/dist/annotate/llm.js +107 -0
  15. package/dist/annotate/llm.js.map +1 -0
  16. package/dist/annotate/prompt.d.ts +38 -0
  17. package/dist/annotate/prompt.d.ts.map +1 -0
  18. package/dist/annotate/prompt.js +165 -0
  19. package/dist/annotate/prompt.js.map +1 -0
  20. package/dist/annotate/store.d.ts +43 -0
  21. package/dist/annotate/store.d.ts.map +1 -0
  22. package/dist/annotate/store.js +46 -0
  23. package/dist/annotate/store.js.map +1 -0
  24. package/dist/canonical/html.d.ts +33 -0
  25. package/dist/canonical/html.d.ts.map +1 -0
  26. package/dist/canonical/html.js +156 -0
  27. package/dist/canonical/html.js.map +1 -0
  28. package/dist/canonical/text.d.ts +75 -0
  29. package/dist/canonical/text.d.ts.map +1 -0
  30. package/dist/canonical/text.js +217 -0
  31. package/dist/canonical/text.js.map +1 -0
  32. package/dist/classify/confidence.d.ts +38 -0
  33. package/dist/classify/confidence.d.ts.map +1 -0
  34. package/dist/classify/confidence.js +59 -0
  35. package/dist/classify/confidence.js.map +1 -0
  36. package/dist/classify/reconcile.d.ts +52 -0
  37. package/dist/classify/reconcile.d.ts.map +1 -0
  38. package/dist/classify/reconcile.js +100 -0
  39. package/dist/classify/reconcile.js.map +1 -0
  40. package/dist/classify/rules.d.ts +51 -0
  41. package/dist/classify/rules.d.ts.map +1 -0
  42. package/dist/classify/rules.js +648 -0
  43. package/dist/classify/rules.js.map +1 -0
  44. package/dist/cli/annotate.d.ts +23 -0
  45. package/dist/cli/annotate.d.ts.map +1 -0
  46. package/dist/cli/annotate.js +328 -0
  47. package/dist/cli/annotate.js.map +1 -0
  48. package/dist/cli/args.d.ts +17 -0
  49. package/dist/cli/args.d.ts.map +1 -0
  50. package/dist/cli/args.js +72 -0
  51. package/dist/cli/args.js.map +1 -0
  52. package/dist/cli/benchmark.d.ts +13 -0
  53. package/dist/cli/benchmark.d.ts.map +1 -0
  54. package/dist/cli/benchmark.js +172 -0
  55. package/dist/cli/benchmark.js.map +1 -0
  56. package/dist/cli/corpus.d.ts +9 -0
  57. package/dist/cli/corpus.d.ts.map +1 -0
  58. package/dist/cli/corpus.js +152 -0
  59. package/dist/cli/corpus.js.map +1 -0
  60. package/dist/cli/disagreements.d.ts +14 -0
  61. package/dist/cli/disagreements.d.ts.map +1 -0
  62. package/dist/cli/disagreements.js +133 -0
  63. package/dist/cli/disagreements.js.map +1 -0
  64. package/dist/cli/evaluate.d.ts +15 -0
  65. package/dist/cli/evaluate.d.ts.map +1 -0
  66. package/dist/cli/evaluate.js +215 -0
  67. package/dist/cli/evaluate.js.map +1 -0
  68. package/dist/cli/extract.d.ts +10 -0
  69. package/dist/cli/extract.d.ts.map +1 -0
  70. package/dist/cli/extract.js +139 -0
  71. package/dist/cli/extract.js.map +1 -0
  72. package/dist/cli/main.d.ts +9 -0
  73. package/dist/cli/main.d.ts.map +1 -0
  74. package/dist/cli/main.js +147 -0
  75. package/dist/cli/main.js.map +1 -0
  76. package/dist/corpus/analyze.d.ts +66 -0
  77. package/dist/corpus/analyze.d.ts.map +1 -0
  78. package/dist/corpus/analyze.js +214 -0
  79. package/dist/corpus/analyze.js.map +1 -0
  80. package/dist/corpus/migrations/index.d.ts +14 -0
  81. package/dist/corpus/migrations/index.d.ts.map +1 -0
  82. package/dist/corpus/migrations/index.js +158 -0
  83. package/dist/corpus/migrations/index.js.map +1 -0
  84. package/dist/corpus/sample.d.ts +66 -0
  85. package/dist/corpus/sample.d.ts.map +1 -0
  86. package/dist/corpus/sample.js +98 -0
  87. package/dist/corpus/sample.js.map +1 -0
  88. package/dist/corpus/scanner.d.ts +79 -0
  89. package/dist/corpus/scanner.d.ts.map +1 -0
  90. package/dist/corpus/scanner.js +256 -0
  91. package/dist/corpus/scanner.js.map +1 -0
  92. package/dist/corpus/stats.d.ts +76 -0
  93. package/dist/corpus/stats.d.ts.map +1 -0
  94. package/dist/corpus/stats.js +240 -0
  95. package/dist/corpus/stats.js.map +1 -0
  96. package/dist/corpus/store.d.ts +67 -0
  97. package/dist/corpus/store.d.ts.map +1 -0
  98. package/dist/corpus/store.js +196 -0
  99. package/dist/corpus/store.js.map +1 -0
  100. package/dist/corpus/thread-index.d.ts +31 -0
  101. package/dist/corpus/thread-index.d.ts.map +1 -0
  102. package/dist/corpus/thread-index.js +182 -0
  103. package/dist/corpus/thread-index.js.map +1 -0
  104. package/dist/corpus/worker.d.ts +21 -0
  105. package/dist/corpus/worker.d.ts.map +1 -0
  106. package/dist/corpus/worker.js +74 -0
  107. package/dist/corpus/worker.js.map +1 -0
  108. package/dist/detect/client.d.ts +28 -0
  109. package/dist/detect/client.d.ts.map +1 -0
  110. package/dist/detect/client.js +121 -0
  111. package/dist/detect/client.js.map +1 -0
  112. package/dist/detect/disclaimer.d.ts +12 -0
  113. package/dist/detect/disclaimer.d.ts.map +1 -0
  114. package/dist/detect/disclaimer.js +28 -0
  115. package/dist/detect/disclaimer.js.map +1 -0
  116. package/dist/detect/forward-header.d.ts +10 -0
  117. package/dist/detect/forward-header.d.ts.map +1 -0
  118. package/dist/detect/forward-header.js +35 -0
  119. package/dist/detect/forward-header.js.map +1 -0
  120. package/dist/detect/patterns/disclaimers.json +113 -0
  121. package/dist/detect/patterns/forward-headers.json +40 -0
  122. package/dist/detect/patterns/html-clients.json +157 -0
  123. package/dist/detect/patterns/quoted-headers.json +41 -0
  124. package/dist/detect/patterns/reply-headers.json +139 -0
  125. package/dist/detect/patterns/signatures.json +58 -0
  126. package/dist/detect/patterns.d.ts +43 -0
  127. package/dist/detect/patterns.d.ts.map +1 -0
  128. package/dist/detect/patterns.js +125 -0
  129. package/dist/detect/patterns.js.map +1 -0
  130. package/dist/detect/reply-header.d.ts +44 -0
  131. package/dist/detect/reply-header.d.ts.map +1 -0
  132. package/dist/detect/reply-header.js +134 -0
  133. package/dist/detect/reply-header.js.map +1 -0
  134. package/dist/detect/signature.d.ts +28 -0
  135. package/dist/detect/signature.d.ts.map +1 -0
  136. package/dist/detect/signature.js +77 -0
  137. package/dist/detect/signature.js.map +1 -0
  138. package/dist/detect/traffic.d.ts +26 -0
  139. package/dist/detect/traffic.d.ts.map +1 -0
  140. package/dist/detect/traffic.js +98 -0
  141. package/dist/detect/traffic.js.map +1 -0
  142. package/dist/eval/metrics.d.ts +87 -0
  143. package/dist/eval/metrics.d.ts.map +1 -0
  144. package/dist/eval/metrics.js +185 -0
  145. package/dist/eval/metrics.js.map +1 -0
  146. package/dist/index.d.ts +54 -0
  147. package/dist/index.d.ts.map +1 -0
  148. package/dist/index.js +163 -0
  149. package/dist/index.js.map +1 -0
  150. package/dist/match/history.d.ts +62 -0
  151. package/dist/match/history.d.ts.map +1 -0
  152. package/dist/match/history.js +128 -0
  153. package/dist/match/history.js.map +1 -0
  154. package/dist/match/normalize.d.ts +33 -0
  155. package/dist/match/normalize.d.ts.map +1 -0
  156. package/dist/match/normalize.js +63 -0
  157. package/dist/match/normalize.js.map +1 -0
  158. package/dist/match/shingle.d.ts +44 -0
  159. package/dist/match/shingle.d.ts.map +1 -0
  160. package/dist/match/shingle.js +78 -0
  161. package/dist/match/shingle.js.map +1 -0
  162. package/dist/mime/charset.d.ts +33 -0
  163. package/dist/mime/charset.d.ts.map +1 -0
  164. package/dist/mime/charset.js +118 -0
  165. package/dist/mime/charset.js.map +1 -0
  166. package/dist/mime/headers.d.ts +32 -0
  167. package/dist/mime/headers.d.ts.map +1 -0
  168. package/dist/mime/headers.js +127 -0
  169. package/dist/mime/headers.js.map +1 -0
  170. package/dist/mime/model.d.ts +111 -0
  171. package/dist/mime/model.d.ts.map +1 -0
  172. package/dist/mime/model.js +17 -0
  173. package/dist/mime/model.js.map +1 -0
  174. package/dist/mime/parse.d.ts +27 -0
  175. package/dist/mime/parse.d.ts.map +1 -0
  176. package/dist/mime/parse.js +343 -0
  177. package/dist/mime/parse.js.map +1 -0
  178. package/dist/segment/features.d.ts +29 -0
  179. package/dist/segment/features.d.ts.map +1 -0
  180. package/dist/segment/features.js +102 -0
  181. package/dist/segment/features.js.map +1 -0
  182. package/dist/segment/html-blocks.d.ts +28 -0
  183. package/dist/segment/html-blocks.d.ts.map +1 -0
  184. package/dist/segment/html-blocks.js +596 -0
  185. package/dist/segment/html-blocks.js.map +1 -0
  186. package/dist/segment/text-blocks.d.ts +30 -0
  187. package/dist/segment/text-blocks.d.ts.map +1 -0
  188. package/dist/segment/text-blocks.js +204 -0
  189. package/dist/segment/text-blocks.js.map +1 -0
  190. package/dist/segment/types.d.ts +88 -0
  191. package/dist/segment/types.d.ts.map +1 -0
  192. package/dist/segment/types.js +11 -0
  193. package/dist/segment/types.js.map +1 -0
  194. package/dist/types.d.ts +85 -0
  195. package/dist/types.d.ts.map +1 -0
  196. package/dist/types.js +17 -0
  197. package/dist/types.js.map +1 -0
  198. package/dist/util/hash.d.ts +49 -0
  199. package/dist/util/hash.d.ts.map +1 -0
  200. package/dist/util/hash.js +0 -0
  201. package/dist/util/hash.js.map +1 -0
  202. package/dist/util/lang.d.ts +25 -0
  203. package/dist/util/lang.d.ts.map +1 -0
  204. package/dist/util/lang.js +135 -0
  205. package/dist/util/lang.js.map +1 -0
  206. package/dist/util/worker-pool.d.ts +51 -0
  207. package/dist/util/worker-pool.d.ts.map +1 -0
  208. package/dist/util/worker-pool.js +155 -0
  209. package/dist/util/worker-pool.js.map +1 -0
  210. package/package.json +74 -0
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ /**
3
+ * Pattern set loader.
4
+ *
5
+ * Patterns live in JSON so they can be extended or overridden without touching code, which matters
6
+ * because the multilingual and client specific surface keeps growing. A broken pattern is skipped
7
+ * with a warning rather than crashing the process: a bad regex in a config file must never take
8
+ * down a corpus run.
9
+ *
10
+ * Set EMAIL_CONTENT_PATTERNS to a directory to override the bundled sets. Files are matched by
11
+ * name, so an override directory may contain only the files it wants to replace.
12
+ */
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.loadPatterns = loadPatterns;
18
+ exports.resetPatternCache = resetPatternCache;
19
+ const node_fs_1 = __importDefault(require("node:fs"));
20
+ const node_path_1 = __importDefault(require("node:path"));
21
+ const BUNDLED_DIR = node_path_1.default.join(__dirname, 'patterns');
22
+ function readJson(dir, file) {
23
+ try {
24
+ const raw = node_fs_1.default.readFileSync(node_path_1.default.join(dir, file), 'utf8');
25
+ const parsed = JSON.parse(raw);
26
+ return parsed && typeof parsed === 'object' ? parsed : null;
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ }
32
+ function loadFile(file, overrideDir) {
33
+ if (overrideDir) {
34
+ const override = readJson(overrideDir, file);
35
+ if (override) {
36
+ return override;
37
+ }
38
+ }
39
+ return readJson(BUNDLED_DIR, file) || {};
40
+ }
41
+ function compileLinePatterns(input, invalid) {
42
+ if (!Array.isArray(input)) {
43
+ return [];
44
+ }
45
+ const out = [];
46
+ for (const item of input) {
47
+ const id = typeof item.id === 'string' ? item.id : null;
48
+ const source = typeof item.pattern === 'string' ? item.pattern : null;
49
+ if (!id || !source) {
50
+ invalid.push(String(item.id ?? 'unnamed'));
51
+ continue;
52
+ }
53
+ try {
54
+ out.push({
55
+ id,
56
+ lang: typeof item.lang === 'string' ? item.lang : undefined,
57
+ role: typeof item.role === 'string' ? item.role : undefined,
58
+ client: typeof item.client === 'string' ? item.client : undefined,
59
+ strength: typeof item.strength === 'number' ? item.strength : 0.5,
60
+ regex: new RegExp(source, typeof item.flags === 'string' ? item.flags : '')
61
+ });
62
+ }
63
+ catch {
64
+ invalid.push(id);
65
+ }
66
+ }
67
+ return out;
68
+ }
69
+ function compileSelectors(input, invalid) {
70
+ if (!Array.isArray(input)) {
71
+ return [];
72
+ }
73
+ const out = [];
74
+ for (const item of input) {
75
+ const id = typeof item.id === 'string' ? item.id : null;
76
+ const selector = typeof item.selector === 'string' ? item.selector : null;
77
+ if (!id || !selector) {
78
+ invalid.push(String(item.id ?? 'unnamed'));
79
+ continue;
80
+ }
81
+ out.push({
82
+ id,
83
+ client: typeof item.client === 'string' ? item.client : '*',
84
+ role: (typeof item.role === 'string' ? item.role : 'hint'),
85
+ strength: typeof item.strength === 'number' ? item.strength : 0.5,
86
+ selector,
87
+ prevalence: typeof item.prevalence === 'number' ? item.prevalence : 0
88
+ });
89
+ }
90
+ return out;
91
+ }
92
+ let cached = null;
93
+ /** Load and compile all pattern sets. Cached after the first call. */
94
+ function loadPatterns(overrideDir) {
95
+ if (cached && overrideDir === undefined) {
96
+ return cached;
97
+ }
98
+ const dir = overrideDir === undefined ? process.env.EMAIL_CONTENT_PATTERNS || null : overrideDir;
99
+ const invalid = [];
100
+ const reply = loadFile('reply-headers.json', dir);
101
+ const forward = loadFile('forward-headers.json', dir);
102
+ const quoted = loadFile('quoted-headers.json', dir);
103
+ const signatures = loadFile('signatures.json', dir);
104
+ const disclaimers = loadFile('disclaimers.json', dir);
105
+ const html = loadFile('html-clients.json', dir);
106
+ const sets = {
107
+ replyHeaders: compileLinePatterns(reply.patterns, invalid),
108
+ forwardHeaders: compileLinePatterns(forward.patterns, invalid),
109
+ quotedHeaderFields: compileLinePatterns(quoted.fields, invalid),
110
+ signatureDelimiters: compileLinePatterns(signatures.delimiters, invalid),
111
+ signaturePhrases: compileLinePatterns(signatures.phrases, invalid),
112
+ disclaimerPhrases: compileLinePatterns(disclaimers.phrases, invalid),
113
+ htmlSelectors: compileSelectors(html.selectors, invalid),
114
+ invalid
115
+ };
116
+ if (overrideDir === undefined) {
117
+ cached = sets;
118
+ }
119
+ return sets;
120
+ }
121
+ /** Reset the cache. Used by tests that swap pattern directories. */
122
+ function resetPatternCache() {
123
+ cached = null;
124
+ }
125
+ //# sourceMappingURL=patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.js","sourceRoot":"","sources":["../../src/detect/patterns.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Reply attribution detection.
3
+ *
4
+ * Two shapes are handled, and the difference matters:
5
+ *
6
+ * 1. A single attribution line: "On Mon, 13 Jan 2025 at 09:15, Bob wrote:". One line, strong.
7
+ * 2. An embedded copy of the previous message's headers, which Outlook and several webmail
8
+ * clients emit as consecutive From/Sent/To/Subject lines.
9
+ *
10
+ * The second shape needs care. A single "From:" line is not evidence of anything, because ordinary
11
+ * prose starts that way often enough to matter. Only a run of consecutive field lines including a
12
+ * date-like or subject field counts, and even then the strength is capped below the single line
13
+ * attribution, because a person pasting an address block looks identical.
14
+ */
15
+ import { type Evidence } from '../segment/types';
16
+ export interface ReplyHeaderMatch {
17
+ /** First line index of the anchor, inclusive. */
18
+ startLine: number;
19
+ /** Last line index of the anchor, inclusive. */
20
+ endLine: number;
21
+ evidence: Evidence;
22
+ }
23
+ /**
24
+ * Detect a single line attribution.
25
+ *
26
+ * Lines longer than 300 characters are rejected: a genuine attribution line is short, and a long
27
+ * one is almost always a paragraph that happens to end in "wrote:".
28
+ */
29
+ export declare function detectAttributionLine(line: string): Evidence | null;
30
+ /**
31
+ * Detect an embedded header block starting at or after `from`.
32
+ *
33
+ * Returns the first match found, scanning forward. The caller supplies de-prefixed line content so
34
+ * that a header block inside a quote is detected the same way as one outside it.
35
+ */
36
+ export declare function detectQuotedHeaderBlock(lines: readonly string[], from?: number): ReplyHeaderMatch | null;
37
+ /**
38
+ * Detect the Outlook style horizontal rule, a long run of underscores on its own line.
39
+ *
40
+ * Weak on its own. It is only meaningful when an embedded header block follows within a few lines,
41
+ * which the segmenter checks.
42
+ */
43
+ export declare function detectRuleSeparator(line: string): Evidence | null;
44
+ //# sourceMappingURL=reply-header.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reply-header.d.ts","sourceRoot":"","sources":["../../src/detect/reply-header.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AA4BjD,MAAM,WAAW,gBAAgB;IAC7B,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,QAAQ,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAgBnE;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,SAAI,GAAG,gBAAgB,GAAG,IAAI,CAmDnG;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CASjE"}
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ /**
3
+ * Reply attribution detection.
4
+ *
5
+ * Two shapes are handled, and the difference matters:
6
+ *
7
+ * 1. A single attribution line: "On Mon, 13 Jan 2025 at 09:15, Bob wrote:". One line, strong.
8
+ * 2. An embedded copy of the previous message's headers, which Outlook and several webmail
9
+ * clients emit as consecutive From/Sent/To/Subject lines.
10
+ *
11
+ * The second shape needs care. A single "From:" line is not evidence of anything, because ordinary
12
+ * prose starts that way often enough to matter. Only a run of consecutive field lines including a
13
+ * date-like or subject field counts, and even then the strength is capped below the single line
14
+ * attribution, because a person pasting an address block looks identical.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.detectAttributionLine = detectAttributionLine;
18
+ exports.detectQuotedHeaderBlock = detectQuotedHeaderBlock;
19
+ exports.detectRuleSeparator = detectRuleSeparator;
20
+ const patterns_1 = require("./patterns");
21
+ /** Longest run of blank lines tolerated inside an embedded header block. */
22
+ const MAX_GAP = 1;
23
+ /** Minimum distinct fields before a run of header-like lines counts as an anchor. */
24
+ const MIN_FIELDS = 3;
25
+ /**
26
+ * An embedded From: line carries an actual address, in one form or another. Prose that happens to
27
+ * begin with "From:" does not.
28
+ *
29
+ * This check is what separates a real Outlook header block from a sentence like
30
+ * "From: the address that sent the message", which appears in `text-mentions-headers.eml` and in
31
+ * real support mail often enough to matter. Without it, an instruction list describing email
32
+ * headers is classified as reply history and disappears.
33
+ */
34
+ const ORIGINATOR_VALUE = /(@[\w-]+\.[\w.-]+|<[^>]*@[^>]*>|mailto:|\[[^\]]*@[^\]]*\])/i;
35
+ /** A date field carries something date shaped: a numeric date, a month name, or a clock time. */
36
+ const DATE_VALUE = /(\d{1,4}[./-]\d{1,2}[./-]\d{1,4}|\b\d{1,2}:\d{2}\b|\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|mon|tue|wed|thu|fri|sat|sun)[a-z]*\.?\b|\b(esmaspäev|teisipäev|kolmapäev|neljapäev|reede|laupäev|pühapäev|montag|dienstag|mittwoch|donnerstag|freitag|samstag|sonntag|lundi|mardi|mercredi|jeudi|vendredi|samedi|dimanche)\b)/i;
37
+ function valueOf(line) {
38
+ const colon = line.indexOf(':');
39
+ return colon === -1 ? '' : line.slice(colon + 1).trim();
40
+ }
41
+ /**
42
+ * Detect a single line attribution.
43
+ *
44
+ * Lines longer than 300 characters are rejected: a genuine attribution line is short, and a long
45
+ * one is almost always a paragraph that happens to end in "wrote:".
46
+ */
47
+ function detectAttributionLine(line) {
48
+ const trimmed = line.trim();
49
+ if (!trimmed || trimmed.length > 300) {
50
+ return null;
51
+ }
52
+ const patterns = (0, patterns_1.loadPatterns)();
53
+ let best = null;
54
+ for (const pattern of patterns.replyHeaders) {
55
+ if (!pattern.regex.test(trimmed)) {
56
+ continue;
57
+ }
58
+ if (!best || pattern.strength > best.strength) {
59
+ best = { id: `reply:${pattern.id}`, kind: 'reply-anchor', strength: pattern.strength };
60
+ }
61
+ }
62
+ return best;
63
+ }
64
+ /**
65
+ * Detect an embedded header block starting at or after `from`.
66
+ *
67
+ * Returns the first match found, scanning forward. The caller supplies de-prefixed line content so
68
+ * that a header block inside a quote is detected the same way as one outside it.
69
+ */
70
+ function detectQuotedHeaderBlock(lines, from = 0) {
71
+ const patterns = (0, patterns_1.loadPatterns)();
72
+ for (let i = from; i < lines.length; i++) {
73
+ const roles = new Set();
74
+ let gap = 0;
75
+ let last = -1;
76
+ for (let j = i; j < lines.length; j++) {
77
+ const line = (lines[j] ?? '').trim();
78
+ if (!line) {
79
+ gap++;
80
+ if (gap > MAX_GAP) {
81
+ break;
82
+ }
83
+ continue;
84
+ }
85
+ const matched = patterns.quotedHeaderFields.find(pattern => pattern.regex.test(line));
86
+ if (!matched) {
87
+ break;
88
+ }
89
+ gap = 0;
90
+ const role = matched.role || matched.id;
91
+ const value = valueOf(line);
92
+ // Only fields whose value looks like a real header value count towards the threshold.
93
+ // The line still continues the block either way, so a "Subject:" with unusual content
94
+ // does not truncate an otherwise convincing header block.
95
+ if (role === 'originator' ? ORIGINATOR_VALUE.test(value) : role === 'date' ? DATE_VALUE.test(value) : true) {
96
+ roles.add(role);
97
+ }
98
+ last = j;
99
+ }
100
+ // A convincing block names the originator plus at least a date or a subject, so that a
101
+ // pasted "To: ... Cc: ..." address list on its own does not qualify.
102
+ const strong = roles.size >= MIN_FIELDS && roles.has('originator') && (roles.has('date') || roles.has('subject'));
103
+ if (strong && last >= i) {
104
+ return {
105
+ startLine: i,
106
+ endLine: last,
107
+ evidence: {
108
+ id: 'reply:quoted_header_block',
109
+ kind: 'reply-anchor',
110
+ // Below a single line attribution on purpose: a person can paste this shape.
111
+ strength: 0.88
112
+ }
113
+ };
114
+ }
115
+ }
116
+ return null;
117
+ }
118
+ /**
119
+ * Detect the Outlook style horizontal rule, a long run of underscores on its own line.
120
+ *
121
+ * Weak on its own. It is only meaningful when an embedded header block follows within a few lines,
122
+ * which the segmenter checks.
123
+ */
124
+ function detectRuleSeparator(line) {
125
+ const trimmed = line.trim();
126
+ if (/^_{10,}$/.test(trimmed)) {
127
+ return { id: 'reply:outlook_underscore_rule', kind: 'separator', strength: 0.6 };
128
+ }
129
+ if (/^[*=~]{10,}$/.test(trimmed)) {
130
+ return { id: 'separator:symbol_rule', kind: 'separator', strength: 0.35 };
131
+ }
132
+ return null;
133
+ }
134
+ //# sourceMappingURL=reply-header.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reply-header.js","sourceRoot":"","sources":["../../src/detect/reply-header.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Signature detection.
3
+ *
4
+ * Signatures are collapsed, never hidden, so this only needs to be good enough to demote a trailing
5
+ * block. Two rules carry almost all the weight:
6
+ *
7
+ * - The RFC 3676 delimiter, a line containing exactly "-- " (trailing space) or "--". This is the
8
+ * one high confidence signal.
9
+ * - Position plus shape: a short block of short lines at the very end of a message, containing
10
+ * contact-like content.
11
+ *
12
+ * A signature that contains quote-looking text, a legal footer, or a pasted email address must not
13
+ * be promoted into reply history, which is why signature evidence is emitted for the block rather
14
+ * than for individual lines.
15
+ */
16
+ import { type Evidence } from '../segment/types';
17
+ import { type BlockFeatures } from '../segment/types';
18
+ export declare function detectSignatureDelimiter(line: string): Evidence | null;
19
+ /** Phrase level evidence, such as "Sent from my phone" or a closing greeting. */
20
+ export declare function detectSignaturePhrases(lines: readonly string[]): Evidence[];
21
+ /**
22
+ * Shape evidence for a whole block.
23
+ *
24
+ * Deliberately weak. It exists to nudge a genuine trailing signature over the collapse threshold
25
+ * when combined with a phrase or a delimiter, not to classify on its own.
26
+ */
27
+ export declare function detectSignatureShape(features: BlockFeatures): Evidence | null;
28
+ //# sourceMappingURL=signature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../../src/detect/signature.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAQtD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAQtE;AAED,iFAAiF;AACjF,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,QAAQ,EAAE,CAmB3E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,aAAa,GAAG,QAAQ,GAAG,IAAI,CAc7E"}
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ /**
3
+ * Signature detection.
4
+ *
5
+ * Signatures are collapsed, never hidden, so this only needs to be good enough to demote a trailing
6
+ * block. Two rules carry almost all the weight:
7
+ *
8
+ * - The RFC 3676 delimiter, a line containing exactly "-- " (trailing space) or "--". This is the
9
+ * one high confidence signal.
10
+ * - Position plus shape: a short block of short lines at the very end of a message, containing
11
+ * contact-like content.
12
+ *
13
+ * A signature that contains quote-looking text, a legal footer, or a pasted email address must not
14
+ * be promoted into reply history, which is why signature evidence is emitted for the block rather
15
+ * than for individual lines.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.detectSignatureDelimiter = detectSignatureDelimiter;
19
+ exports.detectSignaturePhrases = detectSignaturePhrases;
20
+ exports.detectSignatureShape = detectSignatureShape;
21
+ const patterns_1 = require("./patterns");
22
+ /** Signatures beyond this many lines are almost always something else. */
23
+ const MAX_SIGNATURE_LINES = 12;
24
+ /** A signature block sits in the last part of the message. */
25
+ const MIN_POSITION = 0.5;
26
+ function detectSignatureDelimiter(line) {
27
+ const patterns = (0, patterns_1.loadPatterns)();
28
+ for (const pattern of patterns.signatureDelimiters) {
29
+ if (pattern.regex.test(line)) {
30
+ return { id: `signature:${pattern.id}`, kind: 'signature', strength: pattern.strength };
31
+ }
32
+ }
33
+ return null;
34
+ }
35
+ /** Phrase level evidence, such as "Sent from my phone" or a closing greeting. */
36
+ function detectSignaturePhrases(lines) {
37
+ const patterns = (0, patterns_1.loadPatterns)();
38
+ const found = new Map();
39
+ for (const line of lines) {
40
+ const trimmed = line.trim();
41
+ if (!trimmed || trimmed.length > 200) {
42
+ continue;
43
+ }
44
+ for (const pattern of patterns.signaturePhrases) {
45
+ if (pattern.regex.test(trimmed)) {
46
+ const id = `signature:${pattern.id}`;
47
+ const existing = found.get(id);
48
+ if (!existing || existing.strength < pattern.strength) {
49
+ found.set(id, { id, kind: 'signature', strength: pattern.strength });
50
+ }
51
+ }
52
+ }
53
+ }
54
+ return [...found.values()];
55
+ }
56
+ /**
57
+ * Shape evidence for a whole block.
58
+ *
59
+ * Deliberately weak. It exists to nudge a genuine trailing signature over the collapse threshold
60
+ * when combined with a phrase or a delimiter, not to classify on its own.
61
+ */
62
+ function detectSignatureShape(features) {
63
+ if (features.isAllBlank || features.position < MIN_POSITION) {
64
+ return null;
65
+ }
66
+ if (features.lineCount > MAX_SIGNATURE_LINES || features.wordCount > 80) {
67
+ return null;
68
+ }
69
+ const contactish = features.hasEmailAddress || features.hasPhoneNumber || features.hasUrl;
70
+ if (!contactish || features.shortLineRatio < 0.6) {
71
+ return null;
72
+ }
73
+ // Trailing position and contact details, but no delimiter and no phrase. Enough to collapse
74
+ // only in combination with something else.
75
+ return { id: 'signature:shape', kind: 'signature', strength: 0.45 };
76
+ }
77
+ //# sourceMappingURL=signature.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signature.js","sourceRoot":"","sources":["../../src/detect/signature.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;AAYH,4DAQC;AAGD,wDAmBC;AAQD,oDAcC;AA9DD,yCAA0C;AAI1C,0EAA0E;AAC1E,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAE/B,8DAA8D;AAC9D,MAAM,YAAY,GAAG,GAAG,CAAC;AAEzB,SAAgB,wBAAwB,CAAC,IAAY;IACjD,MAAM,QAAQ,GAAG,IAAA,uBAAY,GAAE,CAAC;IAChC,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,mBAAmB,EAAE,CAAC;QACjD,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3B,OAAO,EAAE,EAAE,EAAE,aAAa,OAAO,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;QAC5F,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,iFAAiF;AACjF,SAAgB,sBAAsB,CAAC,KAAwB;IAC3D,MAAM,QAAQ,GAAG,IAAA,uBAAY,GAAE,CAAC;IAChC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC1C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;YACnC,SAAS;QACb,CAAC;QACD,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,gBAAgB,EAAE,CAAC;YAC9C,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC9B,MAAM,EAAE,GAAG,aAAa,OAAO,CAAC,EAAE,EAAE,CAAC;gBACrC,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBAC/B,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;oBACpD,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;gBACzE,CAAC;YACL,CAAC;QACL,CAAC;IACL,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;AAC/B,CAAC;AAED;;;;;GAKG;AACH,SAAgB,oBAAoB,CAAC,QAAuB;IACxD,IAAI,QAAQ,CAAC,UAAU,IAAI,QAAQ,CAAC,QAAQ,GAAG,YAAY,EAAE,CAAC;QAC1D,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,IAAI,QAAQ,CAAC,SAAS,GAAG,mBAAmB,IAAI,QAAQ,CAAC,SAAS,GAAG,EAAE,EAAE,CAAC;QACtE,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,MAAM,UAAU,GAAG,QAAQ,CAAC,eAAe,IAAI,QAAQ,CAAC,cAAc,IAAI,QAAQ,CAAC,MAAM,CAAC;IAC1F,IAAI,CAAC,UAAU,IAAI,QAAQ,CAAC,cAAc,GAAG,GAAG,EAAE,CAAC;QAC/C,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,4FAA4F;IAC5F,2CAA2C;IAC3C,OAAO,EAAE,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AACxE,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Traffic classification: is this message part of a human conversation, or machine generated?
3
+ *
4
+ * This matters more than it looks. Measured on the working corpus, only about 13 percent of
5
+ * messages are human authored, roughly 40 percent are GitHub notifications, and 56 percent carry
6
+ * mailing list or bulk headers. Sampling and evaluation target the human slice, so a reliable
7
+ * split keeps the annotation budget from being spent on templated machine formats.
8
+ *
9
+ * The rules are header based and deliberately ordered from most to least specific. They classify
10
+ * traffic, never content: a notification message is still extracted normally.
11
+ */
12
+ import { type HeaderEntry, type MessageHeaders } from '../mime/model';
13
+ export type TrafficClass = 'human' | 'mailing-list' | 'bulk' | 'notification' | 'ticketing' | 'auto-reply' | 'unknown';
14
+ export interface TrafficVerdict {
15
+ traffic: TrafficClass;
16
+ /** Ids of the rules that fired, in order. */
17
+ signals: string[];
18
+ }
19
+ /**
20
+ * Classify message traffic.
21
+ *
22
+ * @param headers decoded well known headers
23
+ * @param entries every header line of the outermost message
24
+ */
25
+ export declare function classifyTraffic(headers: MessageHeaders, entries: readonly HeaderEntry[]): TrafficVerdict;
26
+ //# sourceMappingURL=traffic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"traffic.d.ts","sourceRoot":"","sources":["../../src/detect/traffic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAEtE,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,cAAc,GAAG,MAAM,GAAG,cAAc,GAAG,WAAW,GAAG,YAAY,GAAG,SAAS,CAAC;AAEvH,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,YAAY,CAAC;IACtB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,EAAE,CAAC;CACrB;AAqCD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,cAAc,CA+CxG"}
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ /**
3
+ * Traffic classification: is this message part of a human conversation, or machine generated?
4
+ *
5
+ * This matters more than it looks. Measured on the working corpus, only about 13 percent of
6
+ * messages are human authored, roughly 40 percent are GitHub notifications, and 56 percent carry
7
+ * mailing list or bulk headers. Sampling and evaluation target the human slice, so a reliable
8
+ * split keeps the annotation budget from being spent on templated machine formats.
9
+ *
10
+ * The rules are header based and deliberately ordered from most to least specific. They classify
11
+ * traffic, never content: a notification message is still extracted normally.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.classifyTraffic = classifyTraffic;
15
+ const NOREPLY = /(^|[.\-_])(no-?reply|do-?not-?reply|donotreply|notifications?|mailer-daemon|postmaster|bounces?|automated)([.\-_@]|$)/i;
16
+ const VENDOR_HEADERS = [
17
+ 'x-github-recipient',
18
+ 'x-github-sender',
19
+ 'x-gitlab-project',
20
+ 'x-jira-fingerprint',
21
+ 'x-atlassian-mail-generated',
22
+ 'x-zendesk-from-account-id',
23
+ 'x-freshdesk-account',
24
+ 'x-intercom-message-id',
25
+ 'x-hubspot-message-id',
26
+ 'x-mailgun-sid',
27
+ 'x-ses-outgoing',
28
+ 'x-sg-eid',
29
+ 'x-campaign-id',
30
+ 'feedback-id',
31
+ 'x-mailchimp-id'
32
+ ];
33
+ const TICKETING_HEADERS = new Set([
34
+ 'x-github-recipient',
35
+ 'x-github-sender',
36
+ 'x-gitlab-project',
37
+ 'x-jira-fingerprint',
38
+ 'x-atlassian-mail-generated',
39
+ 'x-zendesk-from-account-id',
40
+ 'x-freshdesk-account',
41
+ 'x-intercom-message-id'
42
+ ]);
43
+ function hasHeader(entries, key) {
44
+ return entries.some(entry => entry.key === key);
45
+ }
46
+ /**
47
+ * Classify message traffic.
48
+ *
49
+ * @param headers decoded well known headers
50
+ * @param entries every header line of the outermost message
51
+ */
52
+ function classifyTraffic(headers, entries) {
53
+ const signals = [];
54
+ const autoSubmitted = (headers.autoSubmitted || '').toLowerCase();
55
+ if (autoSubmitted && autoSubmitted !== 'no') {
56
+ signals.push(`auto-submitted:${autoSubmitted.split(/[\s;]/)[0]}`);
57
+ }
58
+ if (headers.listId) {
59
+ signals.push('list-id');
60
+ }
61
+ if (headers.listUnsubscribe) {
62
+ signals.push('list-unsubscribe');
63
+ }
64
+ const precedence = (headers.precedence || '').toLowerCase();
65
+ if (precedence === 'bulk' || precedence === 'junk' || precedence === 'list') {
66
+ signals.push(`precedence:${precedence}`);
67
+ }
68
+ const vendor = VENDOR_HEADERS.filter(key => hasHeader(entries, key));
69
+ signals.push(...vendor);
70
+ if (headers.from && NOREPLY.test(headers.from)) {
71
+ signals.push('noreply-from');
72
+ }
73
+ // Most specific first. Ticketing and notification systems often also set list headers, and
74
+ // knowing that a message is a GitHub notification is more useful than knowing it is a list.
75
+ if (vendor.some(key => TICKETING_HEADERS.has(key))) {
76
+ return { traffic: 'ticketing', signals };
77
+ }
78
+ if (autoSubmitted === 'auto-replied' || autoSubmitted === 'auto-generated') {
79
+ return { traffic: 'auto-reply', signals };
80
+ }
81
+ if (headers.listId) {
82
+ return { traffic: 'mailing-list', signals };
83
+ }
84
+ if (headers.listUnsubscribe || vendor.length > 0) {
85
+ return { traffic: 'bulk', signals };
86
+ }
87
+ if (precedence === 'bulk' || precedence === 'junk') {
88
+ return { traffic: 'bulk', signals };
89
+ }
90
+ if (headers.from && NOREPLY.test(headers.from)) {
91
+ return { traffic: 'notification', signals };
92
+ }
93
+ if (!headers.from && !headers.messageId) {
94
+ return { traffic: 'unknown', signals };
95
+ }
96
+ return { traffic: 'human', signals };
97
+ }
98
+ //# sourceMappingURL=traffic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"traffic.js","sourceRoot":"","sources":["../../src/detect/traffic.ts"],"names":[],"mappings":"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"}