slopless 0.2.22 → 0.2.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -160,10 +160,6 @@ npm audit signatures slopless
160
160
 
161
161
  This confirms the tarball you installed was built in the published source repository's Actions environment, from the commit the GitHub Release points at.
162
162
 
163
- ## Star history
164
-
165
- [![Star History Chart](https://api.star-history.com/svg?repos=seochecks-ai/slopless&type=Date)](https://star-history.com/#seochecks-ai/slopless&Date)
166
-
167
163
  ## Credits
168
164
 
169
165
  [Graham Rowe](https://github.com/grahamrowe82/antislop), thanks for giving me new ideas for classes of slop to detect. Your lib is now fully incorporated with your permission.
@@ -0,0 +1,33 @@
1
+ {
2
+ "id": "empty-quantification",
3
+ "class": "empty-quantification",
4
+ "purpose": "Catch claims that minimize an unexplained act of quantification instead of naming the metric, measurement, or result.",
5
+ "matchMode": "suffix",
6
+ "maxTokens": 24,
7
+ "templates": ["{minimizer} put a number on {deicticObject}."],
8
+ "slots": {
9
+ "minimizer": ["just", "simply", "merely", "basically"],
10
+ "deicticObject": ["it", "this", "that"]
11
+ },
12
+ "rejectIf": [
13
+ "sentence names the metric, measured value, scale, or numbered label",
14
+ "sentence explains how the number was calculated or where it came from",
15
+ "sentence uses put literally rather than as an empty quantification claim"
16
+ ],
17
+ "positiveExamples": [
18
+ "The report just put a number on it.",
19
+ "The dashboard simply put a number on this.",
20
+ "The analysis merely put a number on that.",
21
+ "The model basically put a number on it."
22
+ ],
23
+ "negativeExamples": [
24
+ "The analyst labeled the third bar 42.",
25
+ "The meter measured the pressure at 18 PSI.",
26
+ "The report calculated the score from twelve survey responses.",
27
+ "She put the numbered label on the archive box."
28
+ ],
29
+ "notes": [
30
+ "The template begins at the minimizer so suffix mode permits arbitrary preceding subjects without enumerating them.",
31
+ "Suffix mode permits arbitrary preceding subjects but requires the vague quantification phrase to end the sentence, excluding measurements, explanations, and literal continuations."
32
+ ]
33
+ }
@@ -1,5 +1,7 @@
1
+ import emptyQuantification from "../patterns/empty-quantification.json" with { type: "json" };
1
2
  import recursiveMeaningFrame from "../patterns/recursive-meaning-frame.json" with { type: "json" };
2
3
  export const semanticThinnessPatternSetE = [
4
+ emptyQuantification,
3
5
  recursiveMeaningFrame
4
6
  ];
5
7
  //# sourceMappingURL=pattern-data-e.js.map
@@ -86,7 +86,12 @@ function compileSlotValues(slots) {
86
86
  return compiled;
87
87
  }
88
88
  export function compileSemanticThinnessPatterns(patterns) {
89
- const matchModeFor = (pattern) => pattern.matchMode === "contains" ? "contains" : "full";
89
+ const matchModeFor = (pattern) => {
90
+ if (pattern.matchMode === "contains" || pattern.matchMode === "suffix") {
91
+ return pattern.matchMode;
92
+ }
93
+ return "full";
94
+ };
90
95
  const defaultMaxTokensFor = (pattern) => matchModeFor(pattern) === "contains"
91
96
  ? CONTAINS_MAX_TOKENS
92
97
  : FULL_MAX_TOKENS;
@@ -2,6 +2,10 @@ import { defineTextlintRule } from "../../../adapters/textlint/rule.js";
2
2
  import { sectionLastSentenceUnits, sentenceUnits } from "../../../adapters/textlint/units.js";
3
3
  import { wordTokens } from "../../../shared/text/tokens.js";
4
4
  const CLOSERS = ["and that's the key.", "that's what matters."];
5
+ const EXACT_FORMULA_LINES = new Set([
6
+ "that's the good stuff",
7
+ "that is the good stuff"
8
+ ]);
5
9
  const FORMULA_TAILS = [
6
10
  "key",
7
11
  "point",
@@ -13,9 +17,15 @@ const FORMULA_TAILS = [
13
17
  ];
14
18
  function isFormulaLine(text) {
15
19
  const lower = text.toLocaleLowerCase("en");
16
- return (wordTokens(text).length <= 6 &&
17
- (lower.startsWith("that's the ") || lower.startsWith("that is the ")) &&
18
- FORMULA_TAILS.some((tail) => lower.includes(tail)));
20
+ let punctuationStart = lower.length;
21
+ while ([".", "!", "?"].includes(lower[punctuationStart - 1] ?? "")) {
22
+ punctuationStart -= 1;
23
+ }
24
+ const withoutTerminalPunctuation = lower.slice(0, punctuationStart);
25
+ return (EXACT_FORMULA_LINES.has(withoutTerminalPunctuation) ||
26
+ (wordTokens(text).length <= 6 &&
27
+ (lower.startsWith("that's the ") || lower.startsWith("that is the ")) &&
28
+ FORMULA_TAILS.some((tail) => lower.includes(tail))));
19
29
  }
20
30
  const rule = defineTextlintRule({
21
31
  detector: {
@@ -0,0 +1,41 @@
1
+ import { NEGATION_WORDS, findCopularNegation, pronounCopulaStart, skipOptionalAdverbs, startsWithPronounCopula, startsWithWords, startsWithSubjectCopula, validSubject, words } from "./negation-reframe-parts.js";
2
+ export function sameSubjectCopularReframe(aTokens, bTokens) {
3
+ const negation = findCopularNegation(aTokens);
4
+ if (negation === undefined || !validSubject(negation.subject)) {
5
+ return false;
6
+ }
7
+ return startsWithSubjectCopula(bTokens, negation.subject, negation.affirmativeAux);
8
+ }
9
+ export function pronounCopularReframe(aTokens, bTokens) {
10
+ const negation = findCopularNegation(aTokens);
11
+ return (negation !== undefined &&
12
+ validSubject(negation.subject) &&
13
+ !startsWithNegatedPronounCopula(bTokens) &&
14
+ startsWithPronounCopula(bTokens));
15
+ }
16
+ export function progressiveVerbMirror(aTokens, bTokens) {
17
+ const negation = findCopularNegation(aTokens);
18
+ if (negation === undefined || !validSubject(negation.subject)) {
19
+ return false;
20
+ }
21
+ const tokenWords = words(aTokens);
22
+ const predicateIndex = skipOptionalAdverbs(tokenWords, negation.negatedPredicateStart);
23
+ const verb = tokenWords[predicateIndex];
24
+ return (verb !== undefined &&
25
+ verb.endsWith("ing") &&
26
+ startsWithWords(bTokens, [
27
+ ...negation.subject,
28
+ negation.affirmativeAux,
29
+ verb
30
+ ]));
31
+ }
32
+ export function startsWithNegatedPronounCopula(tokens) {
33
+ const start = pronounCopulaStart(tokens);
34
+ if (start === undefined) {
35
+ return false;
36
+ }
37
+ const tokenWords = words(tokens);
38
+ const predicateIndex = skipOptionalAdverbs(tokenWords, start.predicateStart);
39
+ return NEGATION_WORDS.has(tokenWords[predicateIndex] ?? "");
40
+ }
41
+ //# sourceMappingURL=copular-reframe.js.map
@@ -1,9 +1,9 @@
1
- import { DO_NEGATIONS, EXPLICIT_DO_AUXILIARIES, NEGATION_WORDS, PASSIVE_DEFINITION_VERBS, PRONOUN_REFRAME_STARTS, contrastPivotSubject, findCopularNegation, findNegationIndex, isCompleteSentence, skipOptionalAdverbs, startsWithAny, startsWithSubjectOrPronoun, startsWithSubjectVerb, startsWithWords, stripLeadingPairPivot, validSubject, words } from "./negation-reframe-parts.js";
1
+ import { DO_NEGATIONS, EXPLICIT_DO_AUXILIARIES, contrastPivotSubject, findCopularNegation, findNegationIndex, isCompleteSentence, skipOptionalAdverbs, startsWithSubjectOrPronoun, startsWithSubjectVerb, startsWithWords, stripLeadingPairPivot, validSubject, words } from "./negation-reframe-parts.js";
2
2
  import { hasAbstractCommaContrast, hasAbstractNegationPayoff, hasFactualConnectorAfterNegation, hasMetaContext } from "./negation-context-gates.js";
3
3
  import { hasInlineContrastConnectorAfterNegation } from "./inline-contrast-connector.js";
4
4
  import { inlineNotBecauseReframe } from "./inline-not-because-reframe.js";
5
5
  import { inlineNotJustCopularReframe, inlineShortNegatedBeat } from "./inline-short-negation.js";
6
- import { hasNegativeSlopPairSignal, negativeSlopReframe } from "./negative-slop-frames.js";
6
+ import { hasNegativeSlopPairSignal, negatedActionPronounPayoff, negatedActionSetReplacement, negativeSlopReframe, progressiveVerbMirror, pronounCopularReframe, sameSubjectCopularReframe, shouldReportCopularReframe, startsWithNegatedPronounCopula } from "./negative-slop-frames.js";
7
7
  import { makeMeaningReframe, meaningReframe } from "./meaning-reframe.js";
8
8
  import { hasConcreteCorrectionEvidence } from "../../../../shared/matchers/concrete-evidence.js";
9
9
  import { splitSentences } from "../../../../shared/text/sentences.js";
@@ -60,51 +60,6 @@ function inlineNegationContrast(sentence) {
60
60
  }
61
61
  : undefined;
62
62
  }
63
- function sameSubjectCopularReframe(aTokens, bTokens) {
64
- const negation = findCopularNegation(aTokens);
65
- if (negation === undefined || !validSubject(negation.subject)) {
66
- return false;
67
- }
68
- return startsWithWords(bTokens, [
69
- ...negation.subject,
70
- negation.affirmativeAux
71
- ]);
72
- }
73
- function pronounCopularReframe(aTokens, bTokens) {
74
- const negation = findCopularNegation(aTokens);
75
- return (negation !== undefined &&
76
- validSubject(negation.subject) &&
77
- !looksLikePassiveDefinition(aTokens, bTokens) &&
78
- !startsWithNegatedPronounCopula(bTokens) &&
79
- startsWithAny(bTokens, PRONOUN_REFRAME_STARTS));
80
- }
81
- function startsWithNegatedPronounCopula(tokens) {
82
- for (const start of PRONOUN_REFRAME_STARTS) {
83
- if (!startsWithWords(tokens, start)) {
84
- continue;
85
- }
86
- const tokenWords = words(tokens);
87
- const predicateIndex = skipOptionalAdverbs(tokenWords, start.length);
88
- return NEGATION_WORDS.has(tokenWords[predicateIndex] ?? "");
89
- }
90
- return false;
91
- }
92
- function progressiveVerbMirror(aTokens, bTokens) {
93
- const negation = findCopularNegation(aTokens);
94
- if (negation === undefined || !validSubject(negation.subject)) {
95
- return false;
96
- }
97
- const tokenWords = words(aTokens);
98
- const predicateIndex = skipOptionalAdverbs(tokenWords, negation.negatedPredicateStart);
99
- const verb = tokenWords[predicateIndex];
100
- return (verb !== undefined &&
101
- verb.endsWith("ing") &&
102
- startsWithWords(bTokens, [
103
- ...negation.subject,
104
- negation.affirmativeAux,
105
- verb
106
- ]));
107
- }
108
63
  function needReframe(aTokens, bTokens) {
109
64
  const tokenWords = words(aTokens);
110
65
  for (let index = 0; index < tokenWords.length; index += 1) {
@@ -172,43 +127,36 @@ function startsWithExplicitReplacement(tokens) {
172
127
  }
173
128
  function hasPairNegationSignal(tokens) {
174
129
  return (findNegationIndex(tokens) !== undefined ||
130
+ findCopularNegation(tokens) !== undefined ||
175
131
  contrastPivotSubject(tokens) !== undefined ||
176
132
  hasNegativeSlopPairSignal(tokens));
177
133
  }
178
- function looksLikePassiveDefinition(aTokens, bTokens) {
179
- const aWords = words(aTokens);
180
- const bWords = words(bTokens);
181
- for (let index = 0; index < aWords.length - 2; index += 1) {
182
- const current = aWords[index];
183
- if ((current === "has" || current === "have" || current === "had") &&
184
- aWords[index + 1] === "not" &&
185
- aWords[index + 2] === "been" &&
186
- startsWithWords(bTokens, ["it", "is"]) &&
187
- PASSIVE_DEFINITION_VERBS.has(bWords[2] ?? "")) {
188
- return true;
189
- }
190
- }
191
- return false;
192
- }
193
134
  function sentencePairReframe(a, b) {
194
135
  const aTokens = wordTokens(a.text);
195
136
  const bTokens = wordTokens(b.text);
137
+ const pairText = `${a.text} ${b.text}`;
138
+ const hasNegatedActionSetReplacement = negatedActionSetReplacement(aTokens, bTokens);
139
+ const hasAllowedReplacementColon = b.text.trimEnd().endsWith(":") && hasNegatedActionSetReplacement;
196
140
  if (!isCompleteSentence(a) ||
197
- !isCompleteSentence(b) ||
141
+ (!isCompleteSentence(b) && !hasAllowedReplacementColon) ||
198
142
  !hasPairNegationSignal(aTokens)) {
199
143
  return undefined;
200
144
  }
201
- if (sameSubjectCopularReframe(aTokens, bTokens) ||
202
- pronounCopularReframe(aTokens, bTokens) ||
203
- progressiveVerbMirror(aTokens, bTokens) ||
145
+ if ((shouldReportCopularReframe(aTokens, bTokens, pairText) &&
146
+ (sameSubjectCopularReframe(aTokens, bTokens) ||
147
+ pronounCopularReframe(aTokens, bTokens) ||
148
+ progressiveVerbMirror(aTokens, bTokens))) ||
204
149
  (startsWithExplicitReplacement(bTokens) &&
205
- !hasConcreteCorrectionEvidence(`${a.text} ${b.text}`)) ||
150
+ shouldReportCopularReframe(aTokens, bTokens, pairText) &&
151
+ !hasConcreteCorrectionEvidence(pairText)) ||
206
152
  meaningReframe(aTokens, bTokens) ||
207
153
  makeMeaningReframe(aTokens, bTokens) ||
208
154
  needReframe(aTokens, bTokens) ||
209
155
  actionVerbMirror(aTokens, bTokens) ||
156
+ negatedActionPronounPayoff(aTokens, bTokens) ||
210
157
  negativeSlopReframe(aTokens, bTokens) ||
211
- explicitContrastPivotReframe(aTokens, bTokens)) {
158
+ (shouldReportCopularReframe(aTokens, bTokens, pairText) &&
159
+ explicitContrastPivotReframe(aTokens, bTokens))) {
212
160
  return {
213
161
  end: b.end,
214
162
  start: a.start,
@@ -1,5 +1,6 @@
1
1
  export const NEGATION_WORDS = new Set([
2
2
  "not",
3
+ "never",
3
4
  "isn't",
4
5
  "aren't",
5
6
  "wasn't",
@@ -52,18 +53,22 @@ export const PRONOUN_REFRAME_STARTS = [
52
53
  ["you", "are"],
53
54
  ["we", "are"]
54
55
  ];
55
- export const PASSIVE_DEFINITION_VERBS = new Set([
56
- "associated",
57
- "caused",
58
- "classified",
59
- "defined",
60
- "described",
61
- "linked",
62
- "marked",
63
- "produced"
64
- ]);
65
56
  const IRREGULAR_PAST_TENSE = new Map([["go", "went"]]);
66
57
  const CONTRAST_PIVOT_AFTER_NOT = new Set(["just", "merely", "only"]);
58
+ const NEGATIVE_COPULAR_PREDICATES = new Set([
59
+ "never",
60
+ "nobody",
61
+ "none",
62
+ "nothing",
63
+ "nowhere"
64
+ ]);
65
+ const CONTRACTED_PRONOUN_COPULAS = new Map([
66
+ ["it's", { affirmativeAux: "is", subject: ["it"] }],
67
+ ["that's", { affirmativeAux: "is", subject: ["that"] }],
68
+ ["they're", { affirmativeAux: "are", subject: ["they"] }],
69
+ ["we're", { affirmativeAux: "are", subject: ["we"] }],
70
+ ["you're", { affirmativeAux: "are", subject: ["you"] }]
71
+ ]);
67
72
  export function findNegationIndex(tokens) {
68
73
  for (let index = 0; index < tokens.length; index += 1) {
69
74
  const token = tokens[index];
@@ -121,6 +126,39 @@ export function skipOptionalAdverbs(tokens, start) {
121
126
  export function startsWithAny(tokens, starts) {
122
127
  return starts.some((start) => startsWithWords(tokens, start));
123
128
  }
129
+ export function pronounCopulaStart(tokens) {
130
+ const firstToken = tokens[0];
131
+ const contracted = firstToken === undefined
132
+ ? undefined
133
+ : CONTRACTED_PRONOUN_COPULAS.get(firstToken.normalized);
134
+ if (contracted !== undefined) {
135
+ return { ...contracted, predicateStart: 1 };
136
+ }
137
+ for (const start of PRONOUN_REFRAME_STARTS) {
138
+ if (startsWithWords(tokens, start)) {
139
+ return {
140
+ affirmativeAux: start[1],
141
+ predicateStart: start.length,
142
+ subject: [start[0]]
143
+ };
144
+ }
145
+ }
146
+ return undefined;
147
+ }
148
+ export function startsWithPronounCopula(tokens) {
149
+ return pronounCopulaStart(tokens) !== undefined;
150
+ }
151
+ export function startsWithSubjectCopula(tokens, subject, affirmativeAux) {
152
+ if (startsWithWords(tokens, [...subject, affirmativeAux])) {
153
+ return true;
154
+ }
155
+ const contracted = CONTRACTED_PRONOUN_COPULAS.get(tokens[0]?.normalized ?? "");
156
+ if (contracted?.affirmativeAux !== affirmativeAux) {
157
+ return false;
158
+ }
159
+ return (subject.length === contracted.subject.length &&
160
+ subject.every((word, index) => word === contracted.subject[index]));
161
+ }
124
162
  export function isCompleteSentence(sentence) {
125
163
  const trimmed = sentence.text.trim();
126
164
  const last = trimmed.at(-1);
@@ -161,6 +199,24 @@ export function findCopularNegation(tokens) {
161
199
  subject: tokenWords.slice(0, index)
162
200
  };
163
201
  }
202
+ if (explicitAux !== undefined &&
203
+ next !== undefined &&
204
+ NEGATIVE_COPULAR_PREDICATES.has(next)) {
205
+ return {
206
+ affirmativeAux: explicitAux,
207
+ negatedPredicateStart: index + 1,
208
+ subject: tokenWords.slice(0, index)
209
+ };
210
+ }
211
+ if (explicitAux !== undefined &&
212
+ next === "no" &&
213
+ (tokenWords[index + 2] === "one" || tokenWords[index + 2] === "single")) {
214
+ return {
215
+ affirmativeAux: explicitAux,
216
+ negatedPredicateStart: index + 1,
217
+ subject: tokenWords.slice(0, index)
218
+ };
219
+ }
164
220
  }
165
221
  return undefined;
166
222
  }
@@ -1,3 +1,7 @@
1
+ import { hasFactualConnectorAfterNegation } from "./negation-context-gates.js";
2
+ import { hasAbstractPolicyDirectObject } from "./policy-object.js";
3
+ export { shouldReportCopularReframe } from "./reframe-classification.js";
4
+ export { progressiveVerbMirror, pronounCopularReframe, sameSubjectCopularReframe, startsWithNegatedPronounCopula } from "./copular-reframe.js";
1
5
  import { DO_NEGATIONS, EXPLICIT_DO_AUXILIARIES, FACTUAL_NEGATION_CONNECTORS, PRONOUN_REFRAME_STARTS, hasAnyWord, skipOptionalAdverbs, startsWithAny, startsWithSubjectOrPronoun, startsWithWords, stripLeadingPairPivot, validSubject, words } from "./negation-reframe-parts.js";
2
6
  const GENERIC_ACTION_VERBS = new Set([
3
7
  "asked",
@@ -27,6 +31,40 @@ const GENERIC_ACTION_VERBS = new Set([
27
31
  "walked",
28
32
  "went"
29
33
  ]);
34
+ const NEGATED_ACTION_REFRAME_VERBS = new Set([
35
+ "avoid",
36
+ "change",
37
+ "create",
38
+ "end",
39
+ "erase",
40
+ "fix",
41
+ "guarantee",
42
+ "make",
43
+ "mean",
44
+ "remove",
45
+ "replace",
46
+ "require",
47
+ "skip",
48
+ "solve"
49
+ ]);
50
+ const REPLACEMENT_SUBJECT_PRONOUNS = new Set(["he", "she"]);
51
+ const PRONOUN_PAYOFF_VERBS = new Set([
52
+ "becomes",
53
+ "creates",
54
+ "depends",
55
+ "exposes",
56
+ "lands",
57
+ "means",
58
+ "moves",
59
+ "needs",
60
+ "points",
61
+ "requires",
62
+ "reveals",
63
+ "shifts",
64
+ "shows",
65
+ "turns"
66
+ ]);
67
+ const PRONOUN_SUBJECTS = new Set(["it", "this", "that", "they", "we", "you"]);
30
68
  function startsWithPassiveCopula(tokens) {
31
69
  const tokenWords = words(tokens);
32
70
  const predicateIndex = startsWithAny(tokens, PRONOUN_REFRAME_STARTS)
@@ -81,7 +119,34 @@ function negatedActionSubject(tokens) {
81
119
  }
82
120
  return undefined;
83
121
  }
84
- function negativeActionReplacement(aTokens, bTokens) {
122
+ function constrainedSetSubjectLength(tokens, subject) {
123
+ if (startsWithWords(tokens, subject)) {
124
+ return subject.length;
125
+ }
126
+ if (startsWithSubjectOrPronoun(tokens, subject)) {
127
+ return 1;
128
+ }
129
+ return subject.length > 1 &&
130
+ REPLACEMENT_SUBJECT_PRONOUNS.has(tokens[0]?.normalized ?? "")
131
+ ? 1
132
+ : undefined;
133
+ }
134
+ export function negatedActionReplacement(aTokens, bTokens) {
135
+ const tokenWords = words(aTokens);
136
+ const subject = negatedActionSubject(aTokens);
137
+ if (subject === undefined) {
138
+ return false;
139
+ }
140
+ const bContentTokens = stripLeadingPairPivot(bTokens);
141
+ const bWords = words(bContentTokens);
142
+ const genericVerbIndex = skipOptionalAdverbs(bWords, subject.length);
143
+ return (tokenWords.length <= 14 &&
144
+ !hasAnyWord(tokenWords, FACTUAL_NEGATION_CONNECTORS) &&
145
+ ((startsWithSubjectOrPronoun(bContentTokens, subject) &&
146
+ GENERIC_ACTION_VERBS.has(bWords[genericVerbIndex] ?? "")) ||
147
+ negatedActionSetReplacement(aTokens, bTokens)));
148
+ }
149
+ export function negatedActionSetReplacement(aTokens, bTokens) {
85
150
  const tokenWords = words(aTokens);
86
151
  const subject = negatedActionSubject(aTokens);
87
152
  if (subject === undefined) {
@@ -89,13 +154,14 @@ function negativeActionReplacement(aTokens, bTokens) {
89
154
  }
90
155
  const bContentTokens = stripLeadingPairPivot(bTokens);
91
156
  const bWords = words(bContentTokens);
92
- const verbIndex = skipOptionalAdverbs(bWords, subject.length);
93
- const verb = bWords[verbIndex];
157
+ const subjectLength = constrainedSetSubjectLength(bContentTokens, subject);
158
+ const verbIndex = subjectLength === undefined
159
+ ? undefined
160
+ : skipOptionalAdverbs(bWords, subjectLength);
94
161
  return (tokenWords.length <= 14 &&
95
162
  !hasAnyWord(tokenWords, FACTUAL_NEGATION_CONNECTORS) &&
96
- startsWithSubjectOrPronoun(bContentTokens, subject) &&
97
- verb !== undefined &&
98
- GENERIC_ACTION_VERBS.has(verb));
163
+ verbIndex !== undefined &&
164
+ hasAbstractPolicyDirectObject(bWords, verbIndex));
99
165
  }
100
166
  function notBecauseReframe(aTokens, bTokens) {
101
167
  return (startsWithWords(aTokens, ["not", "because"]) &&
@@ -130,10 +196,47 @@ export function hasNegativeSlopPairSignal(tokens) {
130
196
  hasTrailingProblemFrame(tokenWords) ||
131
197
  hasLeadingProblemFrame(tokenWords));
132
198
  }
199
+ export function negatedActionPronounPayoff(aTokens, bTokens) {
200
+ const aWords = words(aTokens);
201
+ const bWords = words(stripLeadingPairPivot(bTokens));
202
+ const pronounStart = PRONOUN_SUBJECTS.has(bWords[0] ?? "") ? 1 : undefined;
203
+ if (pronounStart === undefined) {
204
+ return false;
205
+ }
206
+ const payoffVerbIndex = skipOptionalAdverbs(bWords, pronounStart);
207
+ if (!PRONOUN_PAYOFF_VERBS.has(bWords[payoffVerbIndex] ?? "")) {
208
+ return false;
209
+ }
210
+ for (let index = 0; index < aWords.length; index += 1) {
211
+ if (hasNegatedReframeVerb(aTokens, aWords, index)) {
212
+ return true;
213
+ }
214
+ }
215
+ return false;
216
+ }
217
+ function hasNegatedReframeVerb(tokens, tokenWords, index) {
218
+ const current = tokenWords[index];
219
+ const next = tokenWords[index + 1];
220
+ const subject = tokenWords.slice(0, index);
221
+ if (!validSubject(subject)) {
222
+ return false;
223
+ }
224
+ const negationIndex = DO_NEGATIONS.has(current ?? "")
225
+ ? index
226
+ : EXPLICIT_DO_AUXILIARIES.has(current ?? "") && next === "not"
227
+ ? index + 1
228
+ : undefined;
229
+ if (negationIndex === undefined ||
230
+ hasFactualConnectorAfterNegation(tokens, negationIndex)) {
231
+ return false;
232
+ }
233
+ const verbIndex = skipOptionalAdverbs(tokenWords, negationIndex + 1);
234
+ return NEGATED_ACTION_REFRAME_VERBS.has(tokenWords[verbIndex] ?? "");
235
+ }
133
236
  export function negativeSlopReframe(aTokens, bTokens) {
134
237
  return (noLongerCopularReframe(aTokens, bTokens) ||
135
238
  fragmentDefinitionReframe(aTokens, bTokens) ||
136
- negativeActionReplacement(aTokens, bTokens) ||
239
+ negatedActionReplacement(aTokens, bTokens) ||
137
240
  notBecauseReframe(aTokens, bTokens) ||
138
241
  notProblemReframe(aTokens, bTokens));
139
242
  }
@@ -0,0 +1,55 @@
1
+ const ABSTRACT_POLICY_OBJECTS = new Set([
2
+ "condition",
3
+ "criterion",
4
+ "policy",
5
+ "principle",
6
+ "requirement",
7
+ "rule",
8
+ "standard",
9
+ "threshold"
10
+ ]);
11
+ const DIRECT_OBJECT_DETERMINERS = new Set([
12
+ "a",
13
+ "an",
14
+ "one",
15
+ "that",
16
+ "the",
17
+ "this"
18
+ ]);
19
+ const POLICY_OBJECT_MODIFIERS = new Set([
20
+ "access",
21
+ "approval",
22
+ "business",
23
+ "content",
24
+ "deployment",
25
+ "editorial",
26
+ "governance",
27
+ "launch",
28
+ "operating",
29
+ "pricing",
30
+ "quality",
31
+ "release",
32
+ "review",
33
+ "safety",
34
+ "security",
35
+ "service",
36
+ "spending",
37
+ "technical",
38
+ "testing",
39
+ "usage"
40
+ ]);
41
+ export function hasAbstractPolicyDirectObject(tokenWords, verbIndex) {
42
+ if (!["set", "sets"].includes(tokenWords[verbIndex] ?? "")) {
43
+ return false;
44
+ }
45
+ const objectTokens = tokenWords.slice(verbIndex + 1);
46
+ const directObject = DIRECT_OBJECT_DETERMINERS.has(objectTokens[0] ?? "")
47
+ ? objectTokens.slice(1)
48
+ : objectTokens;
49
+ const [first, second] = directObject;
50
+ return ((directObject.length === 1 && ABSTRACT_POLICY_OBJECTS.has(first ?? "")) ||
51
+ (directObject.length === 2 &&
52
+ POLICY_OBJECT_MODIFIERS.has(first ?? "") &&
53
+ ABSTRACT_POLICY_OBJECTS.has(second ?? "")));
54
+ }
55
+ //# sourceMappingURL=policy-object.js.map