slopless 0.2.23 → 0.2.25
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/rules/semantic-thinness/patterns/abstract-metaphor-claim.json +83 -1
- package/dist/rules/semantic-thinness/patterns/deictic-summary.json +31 -2
- package/dist/rules/semantic-thinness/patterns/empty-quantification.json +33 -0
- package/dist/rules/semantic-thinness/patterns/empty-scene-transition.json +2 -1
- package/dist/rules/semantic-thinness/private/concrete-guards.js +2 -0
- package/dist/rules/semantic-thinness/private/pattern-data-e.js +2 -0
- package/dist/rules/semantic-thinness/private/pattern-matcher.js +28 -7
- package/dist/rules/syntactic-patterns/closers/affirmation-closers.js +13 -3
- package/dist/rules/syntactic-patterns/contrast/contrastive-aphorism.js +2 -0
- package/dist/rules/syntactic-patterns/contrast/negation-reframe.js +1 -1
- package/dist/rules/syntactic-patterns/contrast/private/action-reframe-vocabulary.js +47 -0
- package/dist/rules/syntactic-patterns/contrast/private/audience-replacement.js +267 -0
- package/dist/rules/syntactic-patterns/contrast/private/copular-reframe.js +79 -0
- package/dist/rules/syntactic-patterns/contrast/private/evidence-limitation-pair.js +307 -0
- package/dist/rules/syntactic-patterns/contrast/private/inline-semicolon-reframe.js +50 -0
- package/dist/rules/syntactic-patterns/contrast/private/inline-short-negation.js +1 -0
- package/dist/rules/syntactic-patterns/contrast/private/negation-reframe-matcher.js +78 -73
- package/dist/rules/syntactic-patterns/contrast/private/negation-reframe-parts.js +99 -31
- package/dist/rules/syntactic-patterns/contrast/private/negation-vocabulary.js +27 -0
- package/dist/rules/syntactic-patterns/contrast/private/negative-consequence.js +301 -0
- package/dist/rules/syntactic-patterns/contrast/private/negative-slop-frames.js +155 -39
- package/dist/rules/syntactic-patterns/contrast/private/policy-object.js +55 -0
- package/dist/rules/syntactic-patterns/contrast/private/reframe-classification.js +214 -0
- package/dist/rules/syntactic-patterns/contrast/private/sequence-reframes.js +9 -0
- package/dist/rules/syntactic-patterns/contrast/private/temporal-reframe.js +318 -0
- package/dist/rules/syntactic-patterns/lead-ins/generic-signposting.js +96 -102
- package/dist/rules/syntactic-patterns/lead-ins/private/component-assignment-frame.js +239 -0
- package/dist/rules/syntactic-patterns/lead-ins/private/discourse-evaluation.js +150 -4
- package/dist/rules/syntactic-patterns/lead-ins/private/evaluative-colon-frame.js +166 -0
- package/dist/rules/syntactic-patterns/lead-ins/private/reaction-frame.js +29 -0
- package/dist/rules/syntactic-patterns/lead-ins/private/relative-discourse-frame.js +119 -0
- package/package.json +1 -1
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import { defineTextlintRule } from "../../../adapters/textlint/rule.js";
|
|
2
|
+
import { paragraphUnits, sentenceUnits } from "../../../adapters/textlint/units.js";
|
|
1
3
|
import { hasConcreteImplementationSummary } from "../../../shared/matchers/concrete-evidence.js";
|
|
2
4
|
import { cleanSentence, containsAny, tokens } from "../../../shared/matchers/prose-patterns.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
+
import { splitSentences } from "../../../shared/text/sentences.js";
|
|
6
|
+
import { matchComponentAssignmentFrame } from "./private/component-assignment-frame.js";
|
|
7
|
+
import { isAbstractAuditFrame, matchReactionFrame, matchDiscourseEvaluationFrame, matchExpandedDiscourseFrame } from "./private/discourse-evaluation.js";
|
|
8
|
+
import { matchEvaluativeColonFrame } from "./private/evaluative-colon-frame.js";
|
|
9
|
+
import { matchRelativeDiscourseFrame } from "./private/relative-discourse-frame.js";
|
|
5
10
|
const PREFIXES = ["however, ", "but ", "and ", "so "];
|
|
6
11
|
// "as such" was removed: it is a normal anaphoric connective ("a registered adviser; as
|
|
7
12
|
// such, it must...") and was the dominant false positive, not a signposting frame.
|
|
@@ -47,7 +52,6 @@ const FRAME_PATTERNS = [
|
|
|
47
52
|
"the useful version is",
|
|
48
53
|
"the point is plain enough"
|
|
49
54
|
];
|
|
50
|
-
const DETERMINERS = ["a", "an", "the", "this", "that"];
|
|
51
55
|
const SEQUENCE_PATTERNS = [
|
|
52
56
|
"a simple sequence works well",
|
|
53
57
|
"a simple pattern works well",
|
|
@@ -88,72 +92,22 @@ const WHAT_FRAME_TAIL_STARTERS = [
|
|
|
88
92
|
"true",
|
|
89
93
|
"usually"
|
|
90
94
|
];
|
|
91
|
-
const
|
|
92
|
-
"
|
|
93
|
-
"
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
"
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
"
|
|
100
|
-
"
|
|
101
|
-
"
|
|
102
|
-
"
|
|
103
|
-
"hardest",
|
|
104
|
-
"honest",
|
|
105
|
-
"important",
|
|
106
|
-
"main",
|
|
107
|
-
"only",
|
|
108
|
-
"obvious",
|
|
109
|
-
"practical",
|
|
110
|
-
"real",
|
|
111
|
-
"simple",
|
|
112
|
-
"useful"
|
|
113
|
-
];
|
|
114
|
-
const FRAME_NOUNS = [
|
|
115
|
-
"answer",
|
|
116
|
-
"approach",
|
|
117
|
-
"challenge",
|
|
118
|
-
"choice",
|
|
119
|
-
"conclusion",
|
|
120
|
-
"fact",
|
|
121
|
-
"fix",
|
|
122
|
-
"focus",
|
|
123
|
-
"frame",
|
|
124
|
-
"idea",
|
|
125
|
-
"lesson",
|
|
126
|
-
"move",
|
|
127
|
-
"path",
|
|
128
|
-
"point",
|
|
129
|
-
"principle",
|
|
130
|
-
"priority",
|
|
131
|
-
"problem",
|
|
132
|
-
"question",
|
|
133
|
-
"result",
|
|
134
|
-
"rule",
|
|
135
|
-
"shift",
|
|
136
|
-
"signal",
|
|
137
|
-
"strategy",
|
|
138
|
-
"test",
|
|
139
|
-
"thing",
|
|
140
|
-
"tradeoff",
|
|
141
|
-
"truth",
|
|
142
|
-
"version",
|
|
143
|
-
"way",
|
|
144
|
-
"win"
|
|
95
|
+
const WHAT_MATTERS_CLAUSE_STARTERS = [
|
|
96
|
+
"how",
|
|
97
|
+
"if",
|
|
98
|
+
"that",
|
|
99
|
+
"what",
|
|
100
|
+
"when",
|
|
101
|
+
"where",
|
|
102
|
+
"whether",
|
|
103
|
+
"which",
|
|
104
|
+
"who",
|
|
105
|
+
"whose",
|
|
106
|
+
"why"
|
|
145
107
|
];
|
|
146
|
-
const ABSTRACT_FRAME_VERBS = ["is", "are", "was"];
|
|
147
108
|
const POINT_NOUNS = ["goal", "job", "key", "point", "takeaway", "trick"];
|
|
148
109
|
function matchModifiedAbstractFrame(words) {
|
|
149
|
-
const [first, second, third, fourth
|
|
150
|
-
if (first === "the" &&
|
|
151
|
-
second === "result" &&
|
|
152
|
-
third === "worth" &&
|
|
153
|
-
fourth === "caring" &&
|
|
154
|
-
fifth === "about") {
|
|
155
|
-
return "the-result-worth-caring-about";
|
|
156
|
-
}
|
|
110
|
+
const [first, second, third, fourth] = words;
|
|
157
111
|
if (first !== "the") {
|
|
158
112
|
return undefined;
|
|
159
113
|
}
|
|
@@ -167,20 +121,6 @@ function matchModifiedAbstractFrame(words) {
|
|
|
167
121
|
]);
|
|
168
122
|
return matches.get(key);
|
|
169
123
|
}
|
|
170
|
-
function matchEvaluativeFrame(words) {
|
|
171
|
-
const [first, second, third, fourth] = words;
|
|
172
|
-
if (first !== undefined &&
|
|
173
|
-
second !== undefined &&
|
|
174
|
-
third !== undefined &&
|
|
175
|
-
fourth !== undefined &&
|
|
176
|
-
DETERMINERS.includes(first) &&
|
|
177
|
-
FRAME_ADJECTIVES.includes(second) &&
|
|
178
|
-
FRAME_NOUNS.includes(third) &&
|
|
179
|
-
ABSTRACT_FRAME_VERBS.includes(fourth)) {
|
|
180
|
-
return `the-${second}-${third}-${fourth}`;
|
|
181
|
-
}
|
|
182
|
-
return undefined;
|
|
183
|
-
}
|
|
184
124
|
function matchPointIsToFrame(words) {
|
|
185
125
|
const [first, second, third, fourth, fifth] = words;
|
|
186
126
|
if (first === "the" &&
|
|
@@ -201,7 +141,8 @@ function matchWhatFrame(words) {
|
|
|
201
141
|
second === "changes") &&
|
|
202
142
|
third === "is" &&
|
|
203
143
|
fourth !== undefined &&
|
|
204
|
-
WHAT_FRAME_TAIL_STARTERS.includes(fourth)
|
|
144
|
+
(WHAT_FRAME_TAIL_STARTERS.includes(fourth) ||
|
|
145
|
+
(second === "matters" && !WHAT_MATTERS_CLAUSE_STARTERS.includes(fourth)))) {
|
|
205
146
|
return `what-${second}-is`;
|
|
206
147
|
}
|
|
207
148
|
if (first === "what" && second === "matters" && third === "most") {
|
|
@@ -217,11 +158,13 @@ function matchWhatFrame(words) {
|
|
|
217
158
|
}
|
|
218
159
|
function matchAbstractFrame(text) {
|
|
219
160
|
const words = tokens(text);
|
|
220
|
-
return (
|
|
221
|
-
|
|
161
|
+
return (matchRelativeDiscourseFrame(text, words) ??
|
|
162
|
+
matchExpandedDiscourseFrame(words) ??
|
|
163
|
+
matchModifiedAbstractFrame(words) ??
|
|
222
164
|
matchDiscourseEvaluationFrame(words) ??
|
|
223
165
|
matchPointIsToFrame(words) ??
|
|
224
|
-
matchWhatFrame(words)
|
|
166
|
+
matchWhatFrame(words) ??
|
|
167
|
+
matchReactionFrame(words));
|
|
225
168
|
}
|
|
226
169
|
function matchFormulaicContentSetup(text) {
|
|
227
170
|
if (text.startsWith("each ") &&
|
|
@@ -266,11 +209,20 @@ function matchGeneratedFormula(text) {
|
|
|
266
209
|
}
|
|
267
210
|
function matchSignposting(sentence) {
|
|
268
211
|
const stripped = cleanSentence(sentence, PREFIXES);
|
|
269
|
-
const concreteImplementation = hasConcreteImplementationSummary(stripped);
|
|
270
212
|
const abstract = matchAbstractFrame(stripped);
|
|
213
|
+
const strippedWords = tokens(stripped);
|
|
214
|
+
const auditStart = isAbstractAuditFrame(strippedWords)
|
|
215
|
+
? stripped.indexOf("audit")
|
|
216
|
+
: -1;
|
|
217
|
+
const concreteImplementation = hasConcreteImplementationSummary(abstract !== undefined && auditStart >= 0
|
|
218
|
+
? stripped.slice(0, auditStart) + stripped.slice(auditStart + 5)
|
|
219
|
+
: stripped);
|
|
271
220
|
const formulaicSetup = matchFormulaicContentSetup(stripped);
|
|
272
221
|
const generatedFormula = matchGeneratedFormula(stripped);
|
|
273
|
-
if (abstract !== undefined &&
|
|
222
|
+
if (abstract !== undefined &&
|
|
223
|
+
(abstract === "what-matters-is" ||
|
|
224
|
+
abstract.startsWith("relative-") ||
|
|
225
|
+
!concreteImplementation)) {
|
|
274
226
|
return { kind: "abstract-evaluation-frame", signal: abstract };
|
|
275
227
|
}
|
|
276
228
|
if (generatedFormula !== undefined && !concreteImplementation) {
|
|
@@ -279,6 +231,9 @@ function matchSignposting(sentence) {
|
|
|
279
231
|
if (formulaicSetup !== undefined) {
|
|
280
232
|
return { kind: "formulaic-content-setup", signal: formulaicSetup };
|
|
281
233
|
}
|
|
234
|
+
if (stripped.startsWith("the fun part is:") && !concreteImplementation) {
|
|
235
|
+
return { kind: "frame-signpost", signal: "the fun part is:" };
|
|
236
|
+
}
|
|
282
237
|
const checks = [
|
|
283
238
|
["transition", TRANSITION_PATTERNS],
|
|
284
239
|
["consultation-signpost", CONSULTATION_PATTERNS],
|
|
@@ -302,24 +257,63 @@ function matchSignposting(sentence) {
|
|
|
302
257
|
}
|
|
303
258
|
return undefined;
|
|
304
259
|
}
|
|
305
|
-
const rule =
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
260
|
+
const rule = defineTextlintRule({
|
|
261
|
+
detector: {
|
|
262
|
+
detect: ({ units }) => {
|
|
263
|
+
const detections = units.flatMap((unit) => {
|
|
264
|
+
if (unit.kind === "paragraph") {
|
|
265
|
+
const sentences = splitSentences(unit.text);
|
|
266
|
+
const pairDetections = [];
|
|
267
|
+
for (let index = 0; index < sentences.length - 1; index += 1) {
|
|
268
|
+
const current = sentences[index];
|
|
269
|
+
const next = sentences[index + 1];
|
|
270
|
+
if (current === undefined || next === undefined) {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
const signal = matchComponentAssignmentFrame(current.text, next.text);
|
|
274
|
+
if (signal !== undefined) {
|
|
275
|
+
pairDetections.push({
|
|
276
|
+
evidence: signal,
|
|
277
|
+
label: "component-assignment-frame",
|
|
278
|
+
range: { start: current.start, end: next.end },
|
|
279
|
+
ruleId: "syntactic-patterns:generic-signposting",
|
|
280
|
+
unitId: unit.id
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return pairDetections;
|
|
285
|
+
}
|
|
286
|
+
const componentSignal = matchComponentAssignmentFrame(unit.text);
|
|
287
|
+
const colonSignal = matchEvaluativeColonFrame(unit.text);
|
|
288
|
+
const matched = componentSignal === undefined && colonSignal === undefined
|
|
289
|
+
? matchSignposting(unit.text)
|
|
290
|
+
: undefined;
|
|
291
|
+
const signal = componentSignal ?? colonSignal ?? matched?.signal;
|
|
292
|
+
if (signal === undefined) {
|
|
293
|
+
return [];
|
|
294
|
+
}
|
|
295
|
+
return [
|
|
296
|
+
{
|
|
297
|
+
evidence: signal,
|
|
298
|
+
label: componentSignal !== undefined
|
|
299
|
+
? "component-assignment-frame"
|
|
300
|
+
: colonSignal !== undefined
|
|
301
|
+
? "evaluative-colon-frame"
|
|
302
|
+
: (matched?.kind ?? "generic-signposting"),
|
|
303
|
+
range: { start: 0, end: unit.text.length },
|
|
304
|
+
ruleId: "syntactic-patterns:generic-signposting",
|
|
305
|
+
unitId: unit.id
|
|
306
|
+
}
|
|
307
|
+
];
|
|
308
|
+
});
|
|
309
|
+
return detections;
|
|
310
|
+
},
|
|
311
|
+
family: "syntactic-patterns",
|
|
312
|
+
id: "syntactic-patterns:generic-signposting"
|
|
318
313
|
},
|
|
319
|
-
family: "syntactic-patterns",
|
|
320
314
|
formatMessage: (report) => `Generic signposting found: ${report.evidence}. Replace the frame with the concrete claim.`,
|
|
321
|
-
|
|
322
|
-
|
|
315
|
+
reportPolicy: { kind: "one-to-one" },
|
|
316
|
+
units: (document) => [...sentenceUnits(document), ...paragraphUnits(document)]
|
|
323
317
|
});
|
|
324
318
|
export default rule;
|
|
325
319
|
//# sourceMappingURL=generic-signposting.js.map
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { tokens } from "../../../../shared/matchers/prose-patterns.js";
|
|
2
|
+
import { normalizeForMatch } from "../../../../shared/text/normalize.js";
|
|
3
|
+
const COPULAS = new Set(["are", "is", "was", "were"]);
|
|
4
|
+
const DETERMINERS = new Set(["a", "an", "the"]);
|
|
5
|
+
const ACTOR_ROLES = new Set([
|
|
6
|
+
"agent",
|
|
7
|
+
"analyst",
|
|
8
|
+
"author",
|
|
9
|
+
"creator",
|
|
10
|
+
"editor",
|
|
11
|
+
"model",
|
|
12
|
+
"producer",
|
|
13
|
+
"researcher",
|
|
14
|
+
"writer"
|
|
15
|
+
]);
|
|
16
|
+
const WRITING_ROLES = new Set([
|
|
17
|
+
"author",
|
|
18
|
+
"creator",
|
|
19
|
+
"editor",
|
|
20
|
+
"producer",
|
|
21
|
+
"writer"
|
|
22
|
+
]);
|
|
23
|
+
const COMPONENT_HEADS = new Set([
|
|
24
|
+
"basis",
|
|
25
|
+
"context",
|
|
26
|
+
"goal",
|
|
27
|
+
"input",
|
|
28
|
+
"material",
|
|
29
|
+
"output",
|
|
30
|
+
"source"
|
|
31
|
+
]);
|
|
32
|
+
const WRITING_MATERIALS = new Set([
|
|
33
|
+
"article",
|
|
34
|
+
"brief",
|
|
35
|
+
"content",
|
|
36
|
+
"copy",
|
|
37
|
+
"draft",
|
|
38
|
+
"evidence",
|
|
39
|
+
"findings",
|
|
40
|
+
"interviews",
|
|
41
|
+
"notes",
|
|
42
|
+
"recommendation",
|
|
43
|
+
"report",
|
|
44
|
+
"research",
|
|
45
|
+
"transcript"
|
|
46
|
+
]);
|
|
47
|
+
const EXPLANATION_WORDS = new Set([
|
|
48
|
+
"although",
|
|
49
|
+
"because",
|
|
50
|
+
"if",
|
|
51
|
+
"since",
|
|
52
|
+
"so",
|
|
53
|
+
"therefore",
|
|
54
|
+
"unless",
|
|
55
|
+
"when",
|
|
56
|
+
"where",
|
|
57
|
+
"which",
|
|
58
|
+
"while",
|
|
59
|
+
"who"
|
|
60
|
+
]);
|
|
61
|
+
const DETAIL_PREPOSITIONS = new Set([
|
|
62
|
+
"after",
|
|
63
|
+
"at",
|
|
64
|
+
"before",
|
|
65
|
+
"by",
|
|
66
|
+
"from",
|
|
67
|
+
"in",
|
|
68
|
+
"into",
|
|
69
|
+
"of",
|
|
70
|
+
"on",
|
|
71
|
+
"through",
|
|
72
|
+
"to",
|
|
73
|
+
"under",
|
|
74
|
+
"with"
|
|
75
|
+
]);
|
|
76
|
+
const TECHNICAL_LABELS = new Set([
|
|
77
|
+
"array",
|
|
78
|
+
"bytes",
|
|
79
|
+
"classifier",
|
|
80
|
+
"csv",
|
|
81
|
+
"event",
|
|
82
|
+
"file",
|
|
83
|
+
"image",
|
|
84
|
+
"interface",
|
|
85
|
+
"json",
|
|
86
|
+
"object",
|
|
87
|
+
"request",
|
|
88
|
+
"response",
|
|
89
|
+
"schema",
|
|
90
|
+
"service",
|
|
91
|
+
"string",
|
|
92
|
+
"tensor",
|
|
93
|
+
"vector"
|
|
94
|
+
]);
|
|
95
|
+
function withoutDeterminer(words) {
|
|
96
|
+
return DETERMINERS.has(words[0] ?? "") ? words.slice(1) : words;
|
|
97
|
+
}
|
|
98
|
+
function trimTerminalPunctuation(text) {
|
|
99
|
+
let end = text.length;
|
|
100
|
+
while (end > 0 && [".", "!", "?"].includes(text[end - 1] ?? "")) {
|
|
101
|
+
end -= 1;
|
|
102
|
+
}
|
|
103
|
+
return text.slice(0, end);
|
|
104
|
+
}
|
|
105
|
+
function hasTechnicalSyntax(text) {
|
|
106
|
+
const blockedCharacters = new Set([
|
|
107
|
+
"`",
|
|
108
|
+
"(",
|
|
109
|
+
")",
|
|
110
|
+
"[",
|
|
111
|
+
"]",
|
|
112
|
+
"{",
|
|
113
|
+
"}",
|
|
114
|
+
"=",
|
|
115
|
+
"<",
|
|
116
|
+
">",
|
|
117
|
+
"/",
|
|
118
|
+
"\\"
|
|
119
|
+
]);
|
|
120
|
+
for (const character of text) {
|
|
121
|
+
if (blockedCharacters.has(character) ||
|
|
122
|
+
(character >= "0" && character <= "9")) {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
function parseNormalizedAssignment(normalized) {
|
|
129
|
+
if (normalized.length === 0 || hasTechnicalSyntax(normalized)) {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
const words = tokens(normalized);
|
|
133
|
+
const copulaIndex = words.findIndex((word) => COPULAS.has(word));
|
|
134
|
+
if (copulaIndex < 1 ||
|
|
135
|
+
copulaIndex > 3 ||
|
|
136
|
+
words.length - copulaIndex - 1 < 1 ||
|
|
137
|
+
words.length - copulaIndex - 1 > 4) {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
const subject = withoutDeterminer(words.slice(0, copulaIndex));
|
|
141
|
+
const complement = withoutDeterminer(words.slice(copulaIndex + 1));
|
|
142
|
+
const blocked = [...subject, ...complement].some((word) => EXPLANATION_WORDS.has(word) || DETAIL_PREPOSITIONS.has(word));
|
|
143
|
+
return subject.length === 0 || complement.length === 0 || blocked
|
|
144
|
+
? undefined
|
|
145
|
+
: { complement, subject };
|
|
146
|
+
}
|
|
147
|
+
function assignmentClauseCandidates(clause) {
|
|
148
|
+
const normalized = trimTerminalPunctuation(normalizeForMatch(clause));
|
|
149
|
+
const candidates = [normalized];
|
|
150
|
+
const delimiterIndexes = [
|
|
151
|
+
normalized.indexOf(","),
|
|
152
|
+
normalized.indexOf(";"),
|
|
153
|
+
normalized.indexOf(" - "),
|
|
154
|
+
normalized.indexOf(" \u2013 "),
|
|
155
|
+
normalized.indexOf(" \u2014 ")
|
|
156
|
+
].filter((index) => index > 0);
|
|
157
|
+
const firstDelimiter = Math.min(...delimiterIndexes);
|
|
158
|
+
if (Number.isFinite(firstDelimiter)) {
|
|
159
|
+
candidates.push(normalized.slice(0, firstDelimiter).trim());
|
|
160
|
+
}
|
|
161
|
+
return candidates;
|
|
162
|
+
}
|
|
163
|
+
function parseAssignment(clause) {
|
|
164
|
+
for (const candidate of assignmentClauseCandidates(clause)) {
|
|
165
|
+
const assignment = parseNormalizedAssignment(candidate);
|
|
166
|
+
if (assignment !== undefined) {
|
|
167
|
+
return assignment;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
function isActorAssignment(assignment) {
|
|
173
|
+
const subjectHead = assignment.subject.at(-1) ?? "";
|
|
174
|
+
const complementHead = assignment.complement.at(-1) ?? "";
|
|
175
|
+
return ACTOR_ROLES.has(subjectHead) && ACTOR_ROLES.has(complementHead);
|
|
176
|
+
}
|
|
177
|
+
function isComponentAssignment(assignment) {
|
|
178
|
+
const subjectHead = assignment.subject.at(-1) ?? "";
|
|
179
|
+
return COMPONENT_HEADS.has(subjectHead);
|
|
180
|
+
}
|
|
181
|
+
function hasWritingContext(actor, component) {
|
|
182
|
+
const actorWords = [...actor.subject, ...actor.complement];
|
|
183
|
+
const componentHead = component.complement.at(-1) ?? "";
|
|
184
|
+
return (actorWords.some((word) => WRITING_ROLES.has(word)) ||
|
|
185
|
+
WRITING_MATERIALS.has(componentHead));
|
|
186
|
+
}
|
|
187
|
+
function isTechnicalInterface(actor, component) {
|
|
188
|
+
return [...actor.complement, ...component.complement].some((word) => TECHNICAL_LABELS.has(word));
|
|
189
|
+
}
|
|
190
|
+
function splitClauses(text) {
|
|
191
|
+
const normalized = normalizeForMatch(text);
|
|
192
|
+
const separators = [];
|
|
193
|
+
for (let index = 0; index < normalized.length; index += 1) {
|
|
194
|
+
const character = normalized[index] ?? "";
|
|
195
|
+
const isPunctuationSeparator = character === "," || character === ";";
|
|
196
|
+
const isSpacedDash = ["-", "\u2013", "\u2014"].includes(character) &&
|
|
197
|
+
(normalized[index - 1] ?? "").trim() === "" &&
|
|
198
|
+
(normalized[index + 1] ?? "").trim() === "";
|
|
199
|
+
if (isPunctuationSeparator || isSpacedDash) {
|
|
200
|
+
separators.push(index);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (separators.length !== 1) {
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
const separator = separators[0] ?? -1;
|
|
207
|
+
const first = normalized.slice(0, separator).trim();
|
|
208
|
+
const second = normalized.slice(separator + 1).trim();
|
|
209
|
+
return first.length > 0 && second.length > 0 ? [first, second] : undefined;
|
|
210
|
+
}
|
|
211
|
+
export function matchComponentAssignmentFrame(first, second) {
|
|
212
|
+
const pair = second === undefined ? splitClauses(first) : [first, second];
|
|
213
|
+
if (pair === undefined) {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
const firstAssignment = parseAssignment(pair[0]);
|
|
217
|
+
const secondAssignment = parseAssignment(pair[1]);
|
|
218
|
+
if (firstAssignment === undefined || secondAssignment === undefined) {
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
const actor = isActorAssignment(firstAssignment)
|
|
222
|
+
? firstAssignment
|
|
223
|
+
: isActorAssignment(secondAssignment)
|
|
224
|
+
? secondAssignment
|
|
225
|
+
: undefined;
|
|
226
|
+
const component = isComponentAssignment(firstAssignment)
|
|
227
|
+
? firstAssignment
|
|
228
|
+
: isComponentAssignment(secondAssignment)
|
|
229
|
+
? secondAssignment
|
|
230
|
+
: undefined;
|
|
231
|
+
return actor !== undefined &&
|
|
232
|
+
component !== undefined &&
|
|
233
|
+
actor !== component &&
|
|
234
|
+
hasWritingContext(actor, component) &&
|
|
235
|
+
!isTechnicalInterface(actor, component)
|
|
236
|
+
? "component-assignment-frame"
|
|
237
|
+
: undefined;
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=component-assignment-frame.js.map
|
|
@@ -1,4 +1,75 @@
|
|
|
1
|
+
export { matchReactionFrame } from "./reaction-frame.js";
|
|
1
2
|
const ABSTRACT_FRAME_VERBS = ["is", "are", "was", "were"];
|
|
3
|
+
const DISCOURSE_WORK_TAILS = [
|
|
4
|
+
["doing", "real", "work"],
|
|
5
|
+
["load", "bearing"]
|
|
6
|
+
];
|
|
7
|
+
const DETERMINERS = new Set(["a", "an", "the", "this", "that"]);
|
|
8
|
+
const FRAME_ADJECTIVES = new Set([
|
|
9
|
+
"basic",
|
|
10
|
+
"best",
|
|
11
|
+
"better",
|
|
12
|
+
"biggest",
|
|
13
|
+
"bigger",
|
|
14
|
+
"central",
|
|
15
|
+
"clearest",
|
|
16
|
+
"core",
|
|
17
|
+
"easiest",
|
|
18
|
+
"final",
|
|
19
|
+
"first",
|
|
20
|
+
"hardest",
|
|
21
|
+
"honest",
|
|
22
|
+
"important",
|
|
23
|
+
"main",
|
|
24
|
+
"only",
|
|
25
|
+
"obvious",
|
|
26
|
+
"practical",
|
|
27
|
+
"real",
|
|
28
|
+
"simple",
|
|
29
|
+
"useful"
|
|
30
|
+
]);
|
|
31
|
+
const FRAME_NOUNS = new Set([
|
|
32
|
+
"answer",
|
|
33
|
+
"approach",
|
|
34
|
+
"audit",
|
|
35
|
+
"challenge",
|
|
36
|
+
"choice",
|
|
37
|
+
"conclusion",
|
|
38
|
+
"diagnosis",
|
|
39
|
+
"fact",
|
|
40
|
+
"fix",
|
|
41
|
+
"focus",
|
|
42
|
+
"frame",
|
|
43
|
+
"idea",
|
|
44
|
+
"lesson",
|
|
45
|
+
"move",
|
|
46
|
+
"path",
|
|
47
|
+
"point",
|
|
48
|
+
"principle",
|
|
49
|
+
"priority",
|
|
50
|
+
"problem",
|
|
51
|
+
"question",
|
|
52
|
+
"result",
|
|
53
|
+
"rule",
|
|
54
|
+
"shift",
|
|
55
|
+
"signal",
|
|
56
|
+
"strategy",
|
|
57
|
+
"test",
|
|
58
|
+
"thing",
|
|
59
|
+
"tradeoff",
|
|
60
|
+
"truth",
|
|
61
|
+
"version",
|
|
62
|
+
"way",
|
|
63
|
+
"win"
|
|
64
|
+
]);
|
|
65
|
+
const VAGUE_FRAME_VERBS = new Set(["begins", "happens", "lives", "starts"]);
|
|
66
|
+
const VAGUE_FRAME_LOCATIONS = new Set([
|
|
67
|
+
"downstream",
|
|
68
|
+
"earlier",
|
|
69
|
+
"here",
|
|
70
|
+
"there",
|
|
71
|
+
"upstream"
|
|
72
|
+
]);
|
|
2
73
|
const EVALUATION_TAILS = new Set([
|
|
3
74
|
"boring",
|
|
4
75
|
"clear",
|
|
@@ -72,15 +143,90 @@ function isDiscourseEvaluationSubject(first, subject) {
|
|
|
72
143
|
}
|
|
73
144
|
return subject.some((word) => DISCOURSE_SUBJECT_HEADS.has(word));
|
|
74
145
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
!
|
|
146
|
+
function matchDiscourseWorkClaim(words, verbIndex, subject) {
|
|
147
|
+
if (verbIndex <= 0 ||
|
|
148
|
+
words[verbIndex] !== "is" ||
|
|
149
|
+
!DISCOURSE_SUBJECT_HEADS.has(subject.at(-1) ?? "")) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
const tail = DISCOURSE_WORK_TAILS.find((candidate) => candidate.every((word, index) => words[verbIndex + index + 1] === word));
|
|
153
|
+
return tail === undefined ? undefined : `is-${tail.join("-")}`;
|
|
154
|
+
}
|
|
155
|
+
function frameNounIndex(words) {
|
|
156
|
+
if (FRAME_NOUNS.has(words[1] ?? "")) {
|
|
157
|
+
return 1;
|
|
158
|
+
}
|
|
159
|
+
return FRAME_ADJECTIVES.has(words[1] ?? "") && FRAME_NOUNS.has(words[2] ?? "")
|
|
160
|
+
? 2
|
|
161
|
+
: -1;
|
|
162
|
+
}
|
|
163
|
+
function matchWorthAttentionFrame(words) {
|
|
164
|
+
if (words[0] !== "the") {
|
|
79
165
|
return undefined;
|
|
80
166
|
}
|
|
167
|
+
const nounIndex = frameNounIndex(words);
|
|
168
|
+
if (nounIndex < 0 || words[nounIndex + 1] !== "worth") {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
const tail = words.slice(nounIndex + 2);
|
|
172
|
+
const matches = tail[0] === "noticing" ||
|
|
173
|
+
tail[0] === "watching" ||
|
|
174
|
+
tail[0] === "tracking" ||
|
|
175
|
+
(tail[0] === "caring" && tail[1] === "about") ||
|
|
176
|
+
(tail[0] === "paying" && tail[1] === "attention" && tail[2] === "to");
|
|
177
|
+
return matches
|
|
178
|
+
? `the-${words.slice(1, nounIndex + 1).join("-")}-worth-attention`
|
|
179
|
+
: undefined;
|
|
180
|
+
}
|
|
181
|
+
function matchVagueFrameLocation(words) {
|
|
182
|
+
const [first, adjective, noun, verb, location] = words;
|
|
183
|
+
return words.length === 5 &&
|
|
184
|
+
first === "the" &&
|
|
185
|
+
FRAME_ADJECTIVES.has(adjective ?? "") &&
|
|
186
|
+
FRAME_NOUNS.has(noun ?? "") &&
|
|
187
|
+
VAGUE_FRAME_VERBS.has(verb ?? "") &&
|
|
188
|
+
VAGUE_FRAME_LOCATIONS.has(location ?? "")
|
|
189
|
+
? `the-${adjective ?? "useful"}-${noun ?? "frame"}-${verb ?? "starts"}-${location ?? "here"}`
|
|
190
|
+
: undefined;
|
|
191
|
+
}
|
|
192
|
+
function matchEvaluativeFrame(words) {
|
|
193
|
+
const [first, adjective, noun, verb] = words;
|
|
194
|
+
return first !== undefined &&
|
|
195
|
+
adjective !== undefined &&
|
|
196
|
+
noun !== undefined &&
|
|
197
|
+
verb !== undefined &&
|
|
198
|
+
DETERMINERS.has(first) &&
|
|
199
|
+
FRAME_ADJECTIVES.has(adjective) &&
|
|
200
|
+
FRAME_NOUNS.has(noun) &&
|
|
201
|
+
["is", "are", "was"].includes(verb)
|
|
202
|
+
? `the-${adjective}-${noun}-${verb}`
|
|
203
|
+
: undefined;
|
|
204
|
+
}
|
|
205
|
+
export function isAbstractAuditFrame(words) {
|
|
206
|
+
return words[0] === "the" && frameNounIndex(words) > 0
|
|
207
|
+
? words[frameNounIndex(words)] === "audit"
|
|
208
|
+
: false;
|
|
209
|
+
}
|
|
210
|
+
export function matchExpandedDiscourseFrame(words) {
|
|
211
|
+
return (matchWorthAttentionFrame(words) ??
|
|
212
|
+
matchVagueFrameLocation(words) ??
|
|
213
|
+
matchEvaluativeFrame(words));
|
|
214
|
+
}
|
|
215
|
+
export function matchDiscourseEvaluationFrame(words) {
|
|
216
|
+
const [first] = words;
|
|
81
217
|
const verbIndex = words.findIndex((word) => ABSTRACT_FRAME_VERBS.includes(word));
|
|
82
218
|
const tail = words.at(-1);
|
|
83
219
|
const subject = verbIndex > 0 ? words.slice(0, verbIndex) : [];
|
|
220
|
+
const workClaim = matchDiscourseWorkClaim(words, verbIndex, subject);
|
|
221
|
+
if (workClaim !== undefined) {
|
|
222
|
+
return workClaim;
|
|
223
|
+
}
|
|
224
|
+
if (words.length > 8) {
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
if (!["the", "this", "that", "it"].includes(first ?? "")) {
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
84
230
|
return verbIndex > 0 &&
|
|
85
231
|
tail !== undefined &&
|
|
86
232
|
EVALUATION_TAILS.has(tail) &&
|