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.
Files changed (32) hide show
  1. package/dist/rules/semantic-thinness/patterns/abstract-metaphor-claim.json +83 -1
  2. package/dist/rules/semantic-thinness/patterns/deictic-summary.json +31 -2
  3. package/dist/rules/semantic-thinness/patterns/empty-quantification.json +33 -0
  4. package/dist/rules/semantic-thinness/patterns/empty-scene-transition.json +2 -1
  5. package/dist/rules/semantic-thinness/private/concrete-guards.js +2 -0
  6. package/dist/rules/semantic-thinness/private/pattern-data-e.js +2 -0
  7. package/dist/rules/semantic-thinness/private/pattern-matcher.js +28 -7
  8. package/dist/rules/syntactic-patterns/closers/affirmation-closers.js +13 -3
  9. package/dist/rules/syntactic-patterns/contrast/contrastive-aphorism.js +2 -0
  10. package/dist/rules/syntactic-patterns/contrast/negation-reframe.js +1 -1
  11. package/dist/rules/syntactic-patterns/contrast/private/action-reframe-vocabulary.js +47 -0
  12. package/dist/rules/syntactic-patterns/contrast/private/audience-replacement.js +267 -0
  13. package/dist/rules/syntactic-patterns/contrast/private/copular-reframe.js +79 -0
  14. package/dist/rules/syntactic-patterns/contrast/private/evidence-limitation-pair.js +307 -0
  15. package/dist/rules/syntactic-patterns/contrast/private/inline-semicolon-reframe.js +50 -0
  16. package/dist/rules/syntactic-patterns/contrast/private/inline-short-negation.js +1 -0
  17. package/dist/rules/syntactic-patterns/contrast/private/negation-reframe-matcher.js +78 -73
  18. package/dist/rules/syntactic-patterns/contrast/private/negation-reframe-parts.js +99 -31
  19. package/dist/rules/syntactic-patterns/contrast/private/negation-vocabulary.js +27 -0
  20. package/dist/rules/syntactic-patterns/contrast/private/negative-consequence.js +301 -0
  21. package/dist/rules/syntactic-patterns/contrast/private/negative-slop-frames.js +155 -39
  22. package/dist/rules/syntactic-patterns/contrast/private/policy-object.js +55 -0
  23. package/dist/rules/syntactic-patterns/contrast/private/reframe-classification.js +214 -0
  24. package/dist/rules/syntactic-patterns/contrast/private/sequence-reframes.js +9 -0
  25. package/dist/rules/syntactic-patterns/contrast/private/temporal-reframe.js +318 -0
  26. package/dist/rules/syntactic-patterns/lead-ins/generic-signposting.js +96 -102
  27. package/dist/rules/syntactic-patterns/lead-ins/private/component-assignment-frame.js +239 -0
  28. package/dist/rules/syntactic-patterns/lead-ins/private/discourse-evaluation.js +150 -4
  29. package/dist/rules/syntactic-patterns/lead-ins/private/evaluative-colon-frame.js +166 -0
  30. package/dist/rules/syntactic-patterns/lead-ins/private/reaction-frame.js +29 -0
  31. package/dist/rules/syntactic-patterns/lead-ins/private/relative-discourse-frame.js +119 -0
  32. package/package.json +1 -1
@@ -17,7 +17,39 @@
17
17
  "{abstractSubject} {linkingVerb} {infrastructureNoun} with {abstractObject}.",
18
18
  "{abstractSubject} behaves like a {filterNoun}.",
19
19
  "{artifactSubject} adds another {layerNoun} to {diagnosisObject}.",
20
- "{artifactSubject} add another {layerNoun} to {diagnosisObject}."
20
+ "{artifactSubject} add another {layerNoun} to {diagnosisObject}.",
21
+ {
22
+ "text": "{identityArtifact} {linkingVerb} the {artifactRole}.",
23
+ "matchMode": "full"
24
+ },
25
+ {
26
+ "text": "the {identityArtifact} {linkingVerb} the {artifactRole}.",
27
+ "matchMode": "full"
28
+ },
29
+ {
30
+ "text": "{identityArtifact} {linkingVerb} the {artifactRole} now.",
31
+ "matchMode": "full"
32
+ },
33
+ {
34
+ "text": "the {identityArtifact} {linkingVerb} the {artifactRole} now.",
35
+ "matchMode": "full"
36
+ },
37
+ {
38
+ "text": "{audiencePreposition} the {automatedAudience}, the {identityArtifact} {linkingVerb} the {artifactRole}.",
39
+ "matchMode": "full"
40
+ },
41
+ {
42
+ "text": "{audiencePreposition} the {automatedAudience}, {identityArtifact} {linkingVerb} the {artifactRole}.",
43
+ "matchMode": "full"
44
+ },
45
+ {
46
+ "text": "{audiencePreposition} the {automatedAudience} {audienceActivity}, the {identityArtifact} {linkingVerb} the {artifactRole}.",
47
+ "matchMode": "full"
48
+ },
49
+ {
50
+ "text": "{audiencePreposition} the {automatedAudience} {audienceActivity}, {identityArtifact} {linkingVerb} the {artifactRole}.",
51
+ "matchMode": "full"
52
+ }
21
53
  ],
22
54
  "slots": {
23
55
  "abstractSubject": [
@@ -87,6 +119,56 @@
87
119
  "the report",
88
120
  "visibility"
89
121
  ],
122
+ "identityArtifact": [
123
+ "feed",
124
+ "page",
125
+ "prompt",
126
+ "dashboard",
127
+ "database",
128
+ "index",
129
+ "schema",
130
+ "metadata",
131
+ "report",
132
+ "spreadsheet",
133
+ "interface",
134
+ "pipeline",
135
+ "model",
136
+ "content"
137
+ ],
138
+ "artifactRole": [
139
+ "product",
140
+ "strategy",
141
+ "decision",
142
+ "answer",
143
+ "business",
144
+ "brand",
145
+ "storefront",
146
+ "market",
147
+ "distribution",
148
+ "experience",
149
+ "work",
150
+ "source of truth"
151
+ ],
152
+ "audiencePreposition": ["for", "to"],
153
+ "automatedAudience": [
154
+ "agent",
155
+ "ai assistant",
156
+ "algorithm",
157
+ "assistant",
158
+ "crawler",
159
+ "machine",
160
+ "model",
161
+ "ranking system",
162
+ "recommendation engine",
163
+ "search agent"
164
+ ],
165
+ "audienceActivity": [
166
+ "choosing the product",
167
+ "doing the shopping",
168
+ "making the decision",
169
+ "ranking the results",
170
+ "reading the page"
171
+ ],
90
172
  "abstractObject": [
91
173
  "consequences",
92
174
  "diagnosis",
@@ -5,8 +5,26 @@
5
5
  "matchMode": "contains",
6
6
  "maxTokens": 80,
7
7
  "templates": [
8
- "{deicticSubject} {linkingVerb} the {summaryNoun}.",
9
- "{deicticSubject} {linkingVerb} the {evaluativeAdjective} {summaryNoun}.",
8
+ {
9
+ "text": "{deicticCopula} the {summaryNoun}.",
10
+ "matchMode": "full"
11
+ },
12
+ {
13
+ "text": "{deicticCopula} the {evaluativeAdjective} {summaryNoun}.",
14
+ "matchMode": "full"
15
+ },
16
+ {
17
+ "text": "{deicticSubject} {linkingVerb} the {summaryNoun}.",
18
+ "matchMode": "full"
19
+ },
20
+ {
21
+ "text": "{deicticSubject} {linkingVerb} the {summaryNoun}.",
22
+ "matchMode": "connector"
23
+ },
24
+ {
25
+ "text": "{deicticSubject} {linkingVerb} the {evaluativeAdjective} {summaryNoun}.",
26
+ "matchMode": "full"
27
+ },
10
28
  "{deicticSubject} {linkingVerb} the {summaryNoun} that {thinPredicate}.",
11
29
  "{deicticSubject} {linkingVerb} the {summaryNoun} {genericAudience} {reactionVerb}.",
12
30
  "{deicticSubject} {summaryVerb} the {summaryNoun}.",
@@ -14,6 +32,7 @@
14
32
  "{deicticSubject} {meaningVerb} {summaryNoun}.",
15
33
  "{deicticSubject} {explanationVerb} {summaryNoun}.",
16
34
  "{deicticSubject} {explanationVerb} the {summaryNoun}.",
35
+ "{deicticSubject} {speechVerb} {deicticSubject} {linkingVerb} the {summaryNoun}.",
17
36
  "{deicticSubject} {meaningVerb} {abstractSubject} {linkingVerb} {weakJudgment}.",
18
37
  "{deicticSubject} {explanationVerb} why {abstractSubject} {abstractVerb}.",
19
38
  "{deicticSubject} {explanationVerb} how {abstractSubject} {abstractVerb}.",
@@ -30,6 +49,14 @@
30
49
  "{deicticSubject} {linkingVerb} the reason {abstractSubject} {importanceVerb}."
31
50
  ],
32
51
  "slots": {
52
+ "deicticCopula": [
53
+ "that is",
54
+ "this is",
55
+ "it is",
56
+ "that's",
57
+ "this's",
58
+ "it's"
59
+ ],
33
60
  "deicticSubject": ["that", "this", "it"],
34
61
  "linkingVerb": ["is", "was"],
35
62
  "evaluativeAdjective": [
@@ -51,6 +78,7 @@
51
78
  ],
52
79
  "summaryNoun": [
53
80
  "part",
81
+ "piece",
54
82
  "promise",
55
83
  "truth",
56
84
  "lesson",
@@ -99,6 +127,7 @@
99
127
  ],
100
128
  "meaningVerb": ["means", "meant", "does not mean", "doesn't mean"],
101
129
  "explanationVerb": ["shows", "showed", "explains", "explained", "proves"],
130
+ "speechVerb": ["says", "said", "tells", "told"],
102
131
  "weakJudgment": [
103
132
  "useless",
104
133
  "useful",
@@ -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
+ }
@@ -2,11 +2,12 @@
2
2
  "id": "empty-scene-transition",
3
3
  "class": "transition-change",
4
4
  "purpose": "Catch generic transition lines that declare change, passage, or motion without naming what changed or why.",
5
- "matchMode": "contains",
5
+ "matchMode": "full",
6
6
  "templates": [
7
7
  "{genericSubject} {changeVerb}.",
8
8
  "{genericSubject} {changeVerb} {weakModifier}.",
9
9
  "{genericSubject} {changeVerb} {genericObject}.",
10
+ "{weakModifier}, {genericSubject} {changeVerb}.",
10
11
  "{genericSubject} {passageVerb}."
11
12
  ],
12
13
  "slots": {
@@ -1,6 +1,7 @@
1
1
  const CONCRETE_EXPLANATION_TOKENS = new Set([
2
2
  "api",
3
3
  "archive",
4
+ "assigned",
4
5
  "auditor",
5
6
  "billing",
6
7
  "bids",
@@ -20,6 +21,7 @@ const CONCRETE_EXPLANATION_TOKENS = new Set([
20
21
  "invoice",
21
22
  "key",
22
23
  "ledger",
24
+ "lists",
23
25
  "masonry",
24
26
  "mark",
25
27
  "meter",
@@ -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
@@ -16,6 +16,7 @@ const REJECT_TOKENS = new Set([
16
16
  ]);
17
17
  const BROAD_PATTERN_IDS = new Set([
18
18
  "abstract-agency-personification",
19
+ "abstract-metaphor-claim",
19
20
  "abstract-personification-line",
20
21
  "body-emotion-shorthand",
21
22
  "deictic-summary",
@@ -59,15 +60,16 @@ function readSlotName(template, start) {
59
60
  return [name, index];
60
61
  }
61
62
  function compileTemplate(template) {
63
+ const text = typeof template === "string" ? template : template.text;
62
64
  const parts = [];
63
65
  let literal = "";
64
66
  let index = 0;
65
- while (index < template.length) {
66
- const character = template[index];
67
+ while (index < text.length) {
68
+ const character = text[index];
67
69
  if (character === "{") {
68
70
  flushLiteral(parts, literal);
69
71
  literal = "";
70
- const [name, nextIndex] = readSlotName(template, index);
72
+ const [name, nextIndex] = readSlotName(text, index);
71
73
  parts.push({ kind: "slot", name });
72
74
  index = nextIndex;
73
75
  continue;
@@ -76,7 +78,16 @@ function compileTemplate(template) {
76
78
  index += 1;
77
79
  }
78
80
  flushLiteral(parts, literal);
79
- return { parts, signal: template };
81
+ const matchMode = typeof template !== "string" &&
82
+ (template.matchMode === "contains" ||
83
+ template.matchMode === "connector" ||
84
+ template.matchMode === "full" ||
85
+ template.matchMode === "suffix")
86
+ ? template.matchMode
87
+ : undefined;
88
+ return matchMode === undefined
89
+ ? { parts, signal: text }
90
+ : { matchMode, parts, signal: text };
80
91
  }
81
92
  function compileSlotValues(slots) {
82
93
  const compiled = {};
@@ -86,7 +97,12 @@ function compileSlotValues(slots) {
86
97
  return compiled;
87
98
  }
88
99
  export function compileSemanticThinnessPatterns(patterns) {
89
- const matchModeFor = (pattern) => pattern.matchMode === "contains" ? "contains" : "full";
100
+ const matchModeFor = (pattern) => {
101
+ if (pattern.matchMode === "contains" || pattern.matchMode === "suffix") {
102
+ return pattern.matchMode;
103
+ }
104
+ return "full";
105
+ };
90
106
  const defaultMaxTokensFor = (pattern) => matchModeFor(pattern) === "contains"
91
107
  ? CONTAINS_MAX_TOKENS
92
108
  : FULL_MAX_TOKENS;
@@ -133,6 +149,7 @@ function matchPart(source, pattern, part, tokenIndex) {
133
149
  .map((tokens) => tokenIndex + tokens.length);
134
150
  }
135
151
  function templateMatches(source, pattern, template, start) {
152
+ const matchMode = template.matchMode ?? pattern.matchMode;
136
153
  let positions = [start];
137
154
  for (const part of template.parts) {
138
155
  positions = positions.flatMap((position) => matchPart(source, pattern, part, position));
@@ -140,13 +157,17 @@ function templateMatches(source, pattern, template, start) {
140
157
  return false;
141
158
  }
142
159
  }
143
- if (pattern.matchMode === "contains") {
160
+ if (matchMode === "contains") {
144
161
  return positions.length > 0;
145
162
  }
163
+ if (matchMode === "connector") {
164
+ return positions.some((position) => ["and", "but", "so", "yet"].includes(source[position]?.normalized ?? ""));
165
+ }
146
166
  return positions.includes(source.length);
147
167
  }
148
168
  function templateMatchesPattern(source, pattern, template) {
149
- if (pattern.matchMode === "full") {
169
+ const matchMode = template.matchMode ?? pattern.matchMode;
170
+ if (matchMode === "full" || matchMode === "connector") {
150
171
  return templateMatches(source, pattern, template, 0);
151
172
  }
152
173
  for (let start = 0; start < source.length; start += 1) {
@@ -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: {
@@ -2,6 +2,7 @@ import { defineTextlintRule } from "../../../adapters/textlint/rule.js";
2
2
  import { sentenceUnits } from "../../../adapters/textlint/units.js";
3
3
  import { cleanSentence, startsWithWords, tokens, trimTerminalPunctuation } from "../../../shared/matchers/prose-patterns.js";
4
4
  import { pairHasAbstractSubjectOrObject } from "./private/abstract-pair-gates.js";
5
+ import { matchEvidenceLimitationPair } from "./private/evidence-limitation-pair.js";
5
6
  import { matchSameSentenceContrast } from "./private/same-sentence-contrast.js";
6
7
  import { matchSingleSentenceAphorism } from "./private/single-sentence-aphorism.js";
7
8
  const PREFIXES = ["and ", "but ", "so ", "because "];
@@ -104,6 +105,7 @@ const rule = defineTextlintRule({
104
105
  matchLessMorePair(current.text, next.text) ??
105
106
  matchGivesYouPair(current.text, next.text) ??
106
107
  matchGetsOneAnotherPair(current.text, next.text) ??
108
+ matchEvidenceLimitationPair(current.text, next.text) ??
107
109
  matchEvaluativeContrastPair(current.text, next.text);
108
110
  if (signal !== undefined) {
109
111
  detections.push({
@@ -21,7 +21,7 @@ const rule = defineTextlintRule({
21
21
  family: "syntactic-patterns",
22
22
  id: RULE_ID
23
23
  },
24
- formatMessage: (report) => `Negation reframe found: "${report.evidence}". Rewrite without the not-X-then-Y construction.`,
24
+ formatMessage: (report) => `Negation reframe found: "${report.evidence}". Rewrite the staged negation as a direct claim.`,
25
25
  reportPolicy: { kind: "one-to-one" },
26
26
  units: (document) => [
27
27
  ...allParagraphUnits(document),
@@ -0,0 +1,47 @@
1
+ export const GENERIC_ACTION_VERBS = new Set([
2
+ "asked",
3
+ "built",
4
+ "called",
5
+ "checked",
6
+ "checks",
7
+ "compare",
8
+ "compared",
9
+ "compares",
10
+ "crossed",
11
+ "decided",
12
+ "decides",
13
+ "followed",
14
+ "follows",
15
+ "found",
16
+ "gave",
17
+ "kept",
18
+ "left",
19
+ "looked",
20
+ "made",
21
+ "moved",
22
+ "opened",
23
+ "pointed",
24
+ "pulled",
25
+ "raised",
26
+ "rank",
27
+ "ranked",
28
+ "ranks",
29
+ "read",
30
+ "reads",
31
+ "reached",
32
+ "reasoned",
33
+ "reasons",
34
+ "returned",
35
+ "returns",
36
+ "said",
37
+ "sat",
38
+ "shifted",
39
+ "started",
40
+ "stood",
41
+ "stopped",
42
+ "took",
43
+ "turned",
44
+ "walked",
45
+ "went"
46
+ ]);
47
+ //# sourceMappingURL=action-reframe-vocabulary.js.map