slopless 0.2.10 → 0.2.11

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 (71) hide show
  1. package/README.md +9 -9
  2. package/dist/registries/academic-slop.js +4 -0
  3. package/dist/registries/academic-slop.js.map +1 -1
  4. package/dist/registries/narrative-slop.js +4 -0
  5. package/dist/registries/narrative-slop.js.map +1 -1
  6. package/dist/rules/academic-slop/academic-boilerplate.js +17 -0
  7. package/dist/rules/academic-slop/academic-boilerplate.js.map +1 -0
  8. package/dist/rules/academic-slop/academic-formula-frames.js +17 -0
  9. package/dist/rules/academic-slop/academic-formula-frames.js.map +1 -0
  10. package/dist/rules/academic-slop/data/academic-boilerplate.json +22 -0
  11. package/dist/rules/academic-slop/data/academic-formula-frames.json +252 -0
  12. package/dist/rules/narrative-slop/emotion-telling.js +58 -0
  13. package/dist/rules/narrative-slop/emotion-telling.js.map +1 -0
  14. package/dist/rules/narrative-slop/empty-beat.js +124 -0
  15. package/dist/rules/narrative-slop/empty-beat.js.map +1 -0
  16. package/dist/rules/narrative-slop/low-information-beat-density.js +24 -1
  17. package/dist/rules/narrative-slop/low-information-beat-density.js.map +1 -1
  18. package/dist/rules/narrative-slop/perception-verb-density.js +4 -0
  19. package/dist/rules/narrative-slop/perception-verb-density.js.map +1 -1
  20. package/dist/rules/orthography/colon-dramatic.js +38 -1
  21. package/dist/rules/orthography/colon-dramatic.js.map +1 -1
  22. package/dist/rules/phrases/corporate-speak.js +30 -1
  23. package/dist/rules/phrases/corporate-speak.js.map +1 -1
  24. package/dist/rules/phrases/data/corporate-abstraction-patterns.json +60 -0
  25. package/dist/rules/phrases/data/corporate-speak.json +20 -0
  26. package/dist/rules/phrases/data/prohibited-phrases.json +6 -1
  27. package/dist/rules/semantic-thinness/patterns/abstract-agency-personification.json +145 -0
  28. package/dist/rules/semantic-thinness/patterns/body-knows.json +17 -2
  29. package/dist/rules/semantic-thinness/patterns/deictic-summary.json +10 -2
  30. package/dist/rules/semantic-thinness/patterns/emotion-as-substance.json +17 -1
  31. package/dist/rules/semantic-thinness/patterns/empty-scene-state.json +18 -1
  32. package/dist/rules/semantic-thinness/patterns/recursive-meaning-frame.json +79 -0
  33. package/dist/rules/semantic-thinness/private/concrete-guards.js +9 -0
  34. package/dist/rules/semantic-thinness/private/concrete-guards.js.map +1 -1
  35. package/dist/rules/semantic-thinness/private/pattern-data-c.js +2 -0
  36. package/dist/rules/semantic-thinness/private/pattern-data-c.js.map +1 -1
  37. package/dist/rules/semantic-thinness/private/pattern-data-e.js +5 -0
  38. package/dist/rules/semantic-thinness/private/pattern-data-e.js.map +1 -0
  39. package/dist/rules/semantic-thinness/private/pattern-data.js +3 -1
  40. package/dist/rules/semantic-thinness/private/pattern-data.js.map +1 -1
  41. package/dist/rules/semantic-thinness/private/pattern-matcher.js +7 -1
  42. package/dist/rules/semantic-thinness/private/pattern-matcher.js.map +1 -1
  43. package/dist/rules/syntactic-patterns/contrast/contrastive-aphorism.js +2 -151
  44. package/dist/rules/syntactic-patterns/contrast/contrastive-aphorism.js.map +1 -1
  45. package/dist/rules/syntactic-patterns/contrast/private/inline-contrast-connector.js +16 -0
  46. package/dist/rules/syntactic-patterns/contrast/private/inline-contrast-connector.js.map +1 -0
  47. package/dist/rules/syntactic-patterns/contrast/private/inline-not-because-reframe.js +22 -0
  48. package/dist/rules/syntactic-patterns/contrast/private/inline-not-because-reframe.js.map +1 -0
  49. package/dist/rules/syntactic-patterns/contrast/private/inline-short-negation.js +62 -0
  50. package/dist/rules/syntactic-patterns/contrast/private/inline-short-negation.js.map +1 -0
  51. package/dist/rules/syntactic-patterns/contrast/private/negation-context-gates.js +72 -0
  52. package/dist/rules/syntactic-patterns/contrast/private/negation-context-gates.js.map +1 -0
  53. package/dist/rules/syntactic-patterns/contrast/private/negation-reframe-matcher.js +20 -17
  54. package/dist/rules/syntactic-patterns/contrast/private/negation-reframe-matcher.js.map +1 -1
  55. package/dist/rules/syntactic-patterns/contrast/private/same-sentence-contrast.js +47 -16
  56. package/dist/rules/syntactic-patterns/contrast/private/same-sentence-contrast.js.map +1 -1
  57. package/dist/rules/syntactic-patterns/contrast/private/single-sentence-aphorism.js +178 -0
  58. package/dist/rules/syntactic-patterns/contrast/private/single-sentence-aphorism.js.map +1 -0
  59. package/dist/rules/syntactic-patterns/lead-ins/generic-signposting.js +50 -0
  60. package/dist/rules/syntactic-patterns/lead-ins/generic-signposting.js.map +1 -1
  61. package/dist/rules/words/llm-vocabulary-density.js +41 -1
  62. package/dist/rules/words/llm-vocabulary-density.js.map +1 -1
  63. package/dist/rules/words/llm-vocabulary.js +5 -7
  64. package/dist/rules/words/llm-vocabulary.js.map +1 -1
  65. package/dist/rules/words/private/vocabulary-context.js +125 -0
  66. package/dist/rules/words/private/vocabulary-context.js.map +1 -0
  67. package/dist/rules/words/prohibited-words.js +2 -2
  68. package/dist/rules/words/prohibited-words.js.map +1 -1
  69. package/dist/shared/matchers/concrete-evidence.js +4 -0
  70. package/dist/shared/matchers/concrete-evidence.js.map +1 -1
  71. package/package.json +10 -6
package/README.md CHANGED
@@ -3,8 +3,8 @@
3
3
  [![npm](https://img.shields.io/npm/v/slopless?label=npm)](https://www.npmjs.com/package/slopless)
4
4
  [![downloads](https://img.shields.io/npm/dm/slopless)](https://www.npmjs.com/package/slopless)
5
5
  [![license](https://img.shields.io/npm/l/slopless)](LICENSE)
6
- [![ci](https://img.shields.io/github/actions/workflow/status/agent-quality-controls%2Fslopless/ci.yml?branch=main&label=ci)](/actions/workflows/ci.yml)
7
- [![node](https://img.shields.io/node/v/slopless)](package.json)
6
+ [![ci](https://img.shields.io/github/actions/workflow/status/seochecks-ai%2Fslopless/ci.yml?branch=main&label=ci)](/actions/workflows/ci.yml)
7
+ [![node](https://img.shields.io/badge/node-22%2B-brightgreen)](package.json)
8
8
 
9
9
  Catch AI and human slop in English Markdown without calling an LLM. Slopless ships 50+ deterministic textlint rules and a CLI that emits structured JSON findings.
10
10
 
@@ -85,14 +85,14 @@ Something shifted in the room.
85
85
 
86
86
  ## More
87
87
 
88
- - [Philosophy](/wiki/Philosophy) - what slopless is for, design principles, why deterministic.
89
- - [Comparison](/wiki/Comparison) - slopless vs proselint, write-good, alex, vale, default textlint presets.
90
- - [Rules](/wiki/Rules) - full 50+ rule inventory across seven families.
91
- - [Behavior](/wiki/Behavior) - CLI flags, exit codes, JSON output shape, direct textlint integration.
92
- - [Ignore rules](/wiki/Ignore-Rules) - inline `textlint-disable` block syntax.
93
- - [Thanks](/wiki/Thanks) - direct rule sources, dependencies, and acknowledgments.
88
+ - [Philosophy](https://github.com/seochecks-ai/slopless/wiki/Philosophy) - what slopless is for, design principles, why deterministic.
89
+ - [Comparison](https://github.com/seochecks-ai/slopless/wiki/Comparison) - slopless vs proselint, write-good, alex, vale, default textlint presets.
90
+ - [Rules](https://github.com/seochecks-ai/slopless/wiki/Rules) - full 50+ rule inventory across seven families.
91
+ - [Behavior](https://github.com/seochecks-ai/slopless/wiki/Behavior) - CLI flags, exit codes, JSON output shape, direct textlint integration.
92
+ - [Ignore rules](https://github.com/seochecks-ai/slopless/wiki/Ignore-Rules) - inline `textlint-disable` block syntax.
93
+ - [Thanks](https://github.com/seochecks-ai/slopless/wiki/Thanks) - direct rule sources, dependencies, and acknowledgments.
94
94
  - [Contributing](.github/CONTRIBUTING.md) - open a detailed issue first; PRs must pass the G3TS pre-commit gate.
95
95
 
96
96
  ---
97
97
 
98
- Part of [Agent Quality Controls](https://github.com/agent-quality-controls).
98
+ Developed by [seochecks.ai](https://seochecks.ai) to keep content specific, useful, and recognizably human.
@@ -1,5 +1,9 @@
1
+ import academicBoilerplate from "../rules/academic-slop/academic-boilerplate.js";
2
+ import academicFormulaFrames from "../rules/academic-slop/academic-formula-frames.js";
1
3
  import torturedPhrases from "../rules/academic-slop/tortured-phrases.js";
2
4
  export const academicSlopRules = {
5
+ "academic-boilerplate": academicBoilerplate,
6
+ "academic-formula-frames": academicFormulaFrames,
3
7
  "tortured-phrases": torturedPhrases
4
8
  };
5
9
  //# sourceMappingURL=academic-slop.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"academic-slop.js","sourceRoot":"","sources":["../../src/registries/academic-slop.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,4CAA4C,CAAC;AAEzE,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,kBAAkB,EAAE,eAAe;CACpC,CAAC"}
1
+ {"version":3,"file":"academic-slop.js","sourceRoot":"","sources":["../../src/registries/academic-slop.ts"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,MAAM,gDAAgD,CAAC;AACjF,OAAO,qBAAqB,MAAM,mDAAmD,CAAC;AACtF,OAAO,eAAe,MAAM,4CAA4C,CAAC;AAEzE,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,sBAAsB,EAAE,mBAAmB;IAC3C,yBAAyB,EAAE,qBAAqB;IAChD,kBAAkB,EAAE,eAAe;CACpC,CAAC"}
@@ -1,10 +1,14 @@
1
1
  import bodyActionDensity from "../rules/narrative-slop/body-action-density.js";
2
+ import emptyBeat from "../rules/narrative-slop/empty-beat.js";
3
+ import emotionTelling from "../rules/narrative-slop/emotion-telling.js";
2
4
  import flatActionCadence from "../rules/narrative-slop/flat-action-cadence.js";
3
5
  import lowInformationBeatDensity from "../rules/narrative-slop/low-information-beat-density.js";
4
6
  import narrativeCliches from "../rules/narrative-slop/narrative-cliches.js";
5
7
  import perceptionVerbDensity from "../rules/narrative-slop/perception-verb-density.js";
6
8
  export const narrativeSlopRules = {
7
9
  "body-action-density": bodyActionDensity,
10
+ "empty-beat": emptyBeat,
11
+ "emotion-telling": emotionTelling,
8
12
  "flat-action-cadence": flatActionCadence,
9
13
  "low-information-beat-density": lowInformationBeatDensity,
10
14
  "perception-verb-density": perceptionVerbDensity,
@@ -1 +1 @@
1
- {"version":3,"file":"narrative-slop.js","sourceRoot":"","sources":["../../src/registries/narrative-slop.ts"],"names":[],"mappings":"AAAA,OAAO,iBAAiB,MAAM,gDAAgD,CAAC;AAC/E,OAAO,iBAAiB,MAAM,gDAAgD,CAAC;AAC/E,OAAO,yBAAyB,MAAM,yDAAyD,CAAC;AAChG,OAAO,gBAAgB,MAAM,8CAA8C,CAAC;AAC5E,OAAO,qBAAqB,MAAM,oDAAoD,CAAC;AAEvF,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,qBAAqB,EAAE,iBAAiB;IACxC,qBAAqB,EAAE,iBAAiB;IACxC,8BAA8B,EAAE,yBAAyB;IACzD,yBAAyB,EAAE,qBAAqB;IAChD,mBAAmB,EAAE,gBAAgB;CACtC,CAAC"}
1
+ {"version":3,"file":"narrative-slop.js","sourceRoot":"","sources":["../../src/registries/narrative-slop.ts"],"names":[],"mappings":"AAAA,OAAO,iBAAiB,MAAM,gDAAgD,CAAC;AAC/E,OAAO,SAAS,MAAM,uCAAuC,CAAC;AAC9D,OAAO,cAAc,MAAM,4CAA4C,CAAC;AACxE,OAAO,iBAAiB,MAAM,gDAAgD,CAAC;AAC/E,OAAO,yBAAyB,MAAM,yDAAyD,CAAC;AAChG,OAAO,gBAAgB,MAAM,8CAA8C,CAAC;AAC5E,OAAO,qBAAqB,MAAM,oDAAoD,CAAC;AAEvF,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,qBAAqB,EAAE,iBAAiB;IACxC,YAAY,EAAE,SAAS;IACvB,iBAAiB,EAAE,cAAc;IACjC,qBAAqB,EAAE,iBAAiB;IACxC,8BAA8B,EAAE,yBAAyB;IACzD,yBAAyB,EAAE,qBAAqB;IAChD,mBAAmB,EAAE,gBAAgB;CACtC,CAAC"}
@@ -0,0 +1,17 @@
1
+ import academicBoilerplate from "./data/academic-boilerplate.json" with { type: "json" };
2
+ import { findUnquotedPhraseMatches } from "../../shared/matchers/phrases.js";
3
+ import { oneToOneRule } from "../private/textlint-rule-builders.js";
4
+ const rule = oneToOneRule({
5
+ detect: (unit) => findUnquotedPhraseMatches(unit.text, academicBoilerplate).map((match) => ({
6
+ evidence: match.text,
7
+ label: match.text,
8
+ range: { start: match.start, end: match.end }
9
+ })),
10
+ family: "academic-slop",
11
+ formatMessage: (report) => `Academic boilerplate found: "${report.evidence}". Replace it with the concrete claim, method, or result.`,
12
+ ignoredAncestorTypes: ["Link", "LinkReference"],
13
+ ruleId: "academic-slop:academic-boilerplate",
14
+ unitKind: "str"
15
+ });
16
+ export default rule;
17
+ //# sourceMappingURL=academic-boilerplate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"academic-boilerplate.js","sourceRoot":"","sources":["../../../src/rules/academic-slop/academic-boilerplate.ts"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,MAAM,kCAAkC,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AACzF,OAAO,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAC;AAC7E,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AAEpE,MAAM,IAAI,GAAG,YAAY,CAAC;IACxB,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CACf,yBAAyB,CAAC,IAAI,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACxE,QAAQ,EAAE,KAAK,CAAC,IAAI;QACpB,KAAK,EAAE,KAAK,CAAC,IAAI;QACjB,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE;KAC9C,CAAC,CAAC;IACL,MAAM,EAAE,eAAe;IACvB,aAAa,EAAE,CAAC,MAAM,EAAE,EAAE,CACxB,gCAAgC,MAAM,CAAC,QAAQ,2DAA2D;IAC5G,oBAAoB,EAAE,CAAC,MAAM,EAAE,eAAe,CAAC;IAC/C,MAAM,EAAE,oCAAoC;IAC5C,QAAQ,EAAE,KAAK;CAChB,CAAC,CAAC;AAEH,eAAe,IAAI,CAAC"}
@@ -0,0 +1,17 @@
1
+ import academicFormulaFrames from "./data/academic-formula-frames.json" with { type: "json" };
2
+ import { findUnquotedTokenTemplateMatches } from "../../shared/matchers/phrases.js";
3
+ import { oneToOneRule } from "../private/textlint-rule-builders.js";
4
+ const rule = oneToOneRule({
5
+ detect: (unit) => findUnquotedTokenTemplateMatches(unit.text, academicFormulaFrames).map((match) => ({
6
+ evidence: match.text,
7
+ label: match.template,
8
+ range: { start: match.start, end: match.end }
9
+ })),
10
+ family: "academic-slop",
11
+ formatMessage: (report) => `Academic formula frame found: "${report.evidence}". Replace it with the concrete claim, method, or result.`,
12
+ ignoredAncestorTypes: ["Link", "LinkReference"],
13
+ ruleId: "academic-slop:academic-formula-frames",
14
+ unitKind: "str"
15
+ });
16
+ export default rule;
17
+ //# sourceMappingURL=academic-formula-frames.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"academic-formula-frames.js","sourceRoot":"","sources":["../../../src/rules/academic-slop/academic-formula-frames.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,MAAM,qCAAqC,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAC9F,OAAO,EAAE,gCAAgC,EAAE,MAAM,kCAAkC,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AAEpE,MAAM,IAAI,GAAG,YAAY,CAAC;IACxB,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CACf,gCAAgC,CAAC,IAAI,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC,GAAG,CACpE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACV,QAAQ,EAAE,KAAK,CAAC,IAAI;QACpB,KAAK,EAAE,KAAK,CAAC,QAAQ;QACrB,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE;KAC9C,CAAC,CACH;IACH,MAAM,EAAE,eAAe;IACvB,aAAa,EAAE,CAAC,MAAM,EAAE,EAAE,CACxB,kCAAkC,MAAM,CAAC,QAAQ,2DAA2D;IAC9G,oBAAoB,EAAE,CAAC,MAAM,EAAE,eAAe,CAAC;IAC/C,MAAM,EAAE,uCAAuC;IAC/C,QAAQ,EAAE,KAAK;CAChB,CAAC,CAAC;AAEH,eAAe,IAAI,CAAC"}
@@ -0,0 +1,22 @@
1
+ [
2
+ "academic praxis",
3
+ "bridge theory and practice",
4
+ "broader ecology",
5
+ "complex interplay",
6
+ "ever-evolving landscape",
7
+ "fertile ground",
8
+ "foregrounding the lived realities",
9
+ "growing body of literature",
10
+ "holistic lens",
11
+ "lived experience",
12
+ "lived realities",
13
+ "multifaceted dynamics",
14
+ "nuanced ways in which",
15
+ "paint a rich picture",
16
+ "pivotal role",
17
+ "rich picture",
18
+ "shed light on",
19
+ "situates itself at the intersection",
20
+ "underscores the pivotal role",
21
+ "unpack the multifaceted dynamics"
22
+ ]
@@ -0,0 +1,252 @@
1
+ [
2
+ {
3
+ "id": "contributes-to-dialogue",
4
+ "slots": {
5
+ "paperNoun": ["article", "chapter", "paper", "study"],
6
+ "valueNoun": [
7
+ "fresh perspective",
8
+ "important contribution",
9
+ "meaningful contribution",
10
+ "nuanced perspective",
11
+ "valuable contribution"
12
+ ],
13
+ "discourseNoun": [
14
+ "conversation",
15
+ "debate",
16
+ "dialogue",
17
+ "discourse",
18
+ "literature"
19
+ ],
20
+ "academicObject": [
21
+ "belonging",
22
+ "community engagement",
23
+ "digital transformation",
24
+ "institutional change",
25
+ "interdisciplinary work",
26
+ "knowledge production",
27
+ "meaning making",
28
+ "pedagogical innovation",
29
+ "social change",
30
+ "student success"
31
+ ]
32
+ },
33
+ "templates": [
34
+ "the {paperNoun} contributes a {valueNoun} to the ongoing {discourseNoun} about {academicObject}",
35
+ "the {paperNoun} contributes an {valueNoun} to the ongoing {discourseNoun} about {academicObject}"
36
+ ]
37
+ },
38
+ {
39
+ "id": "findings-enable-abstraction",
40
+ "slots": {
41
+ "findingNoun": ["findings", "results"],
42
+ "abstractActor": [
43
+ "collaboration",
44
+ "mentorship",
45
+ "participation",
46
+ "reflection",
47
+ "storytelling"
48
+ ],
49
+ "enableVerb": ["create", "enable", "unlock"],
50
+ "abstractOutcome": [
51
+ "new forms of academic belonging",
52
+ "new forms of collective agency",
53
+ "new modes of participation",
54
+ "new pathways for transformation"
55
+ ]
56
+ },
57
+ "templates": [
58
+ "the {findingNoun} indicate that {abstractActor} can {enableVerb} {abstractOutcome}",
59
+ "the {findingNoun} suggest that {abstractActor} can {enableVerb} {abstractOutcome}"
60
+ ]
61
+ },
62
+ {
63
+ "id": "situates-broader-matrix",
64
+ "slots": {
65
+ "paperNoun": ["article", "chapter", "paper", "study"],
66
+ "academicObject": [
67
+ "these practices",
68
+ "these processes",
69
+ "these strategies",
70
+ "these tensions",
71
+ "this intervention"
72
+ ],
73
+ "matrixNoun": ["landscape", "matrix", "terrain", "web"],
74
+ "abstractChange": [
75
+ "cultural change",
76
+ "institutional change",
77
+ "meaning making",
78
+ "social transformation",
79
+ "systemic transformation"
80
+ ]
81
+ },
82
+ "templates": [
83
+ "the {paperNoun} situates {academicObject} within a broader {matrixNoun} of {abstractChange}"
84
+ ]
85
+ },
86
+ {
87
+ "id": "roadmap-for-scholars",
88
+ "slots": {
89
+ "paperNoun": ["article", "chapter", "paper", "study"],
90
+ "scholarNoun": ["practitioners", "researchers", "scholars"],
91
+ "academicVerb": ["address", "engage", "navigate", "unpack"],
92
+ "complexityNoun": ["complexities", "complexity"],
93
+ "academicObject": [
94
+ "community engagement",
95
+ "digital transformation",
96
+ "institutional change",
97
+ "interdisciplinary work",
98
+ "knowledge production",
99
+ "student success"
100
+ ]
101
+ },
102
+ "templates": [
103
+ "the {paperNoun} offers a roadmap for {scholarNoun} seeking to {academicVerb} the {complexityNoun} of {academicObject}"
104
+ ]
105
+ },
106
+ {
107
+ "id": "contours-of-success",
108
+ "slots": {
109
+ "discussionNoun": ["analysis", "discussion", "paper", "study"],
110
+ "processNoun": [
111
+ "collaborative practices",
112
+ "meaning making practices",
113
+ "participatory practices",
114
+ "reflective practices"
115
+ ],
116
+ "successNoun": [
117
+ "collective success",
118
+ "institutional success",
119
+ "student success",
120
+ "transformative success"
121
+ ]
122
+ },
123
+ "templates": [
124
+ "the {discussionNoun} reveals how {processNoun} shape the contours of {successNoun}"
125
+ ]
126
+ },
127
+ {
128
+ "id": "potential-to-catalyze",
129
+ "slots": {
130
+ "paperNoun": ["article", "chapter", "paper", "study"],
131
+ "abstractSubject": [
132
+ "collaboration",
133
+ "cross sector partnerships",
134
+ "dialogue",
135
+ "participation",
136
+ "shared leadership"
137
+ ],
138
+ "abstractOutcome": [
139
+ "collective transformation",
140
+ "institutional transformation",
141
+ "social transformation",
142
+ "systemic transformation"
143
+ ]
144
+ },
145
+ "templates": [
146
+ "the {paperNoun} reveals the potential of {abstractSubject} to catalyze {abstractOutcome}"
147
+ ]
148
+ },
149
+ {
150
+ "id": "comprehensive-discourse-overview",
151
+ "slots": {
152
+ "paperNoun": ["article", "paper", "study"],
153
+ "discourseObject": [
154
+ "the evolving discourse surrounding ethical data governance",
155
+ "the evolving nature of professional identity",
156
+ "the shifting contours of disciplinary practice"
157
+ ]
158
+ },
159
+ "templates": [
160
+ "the {paperNoun} provides a comprehensive overview of {discourseObject}"
161
+ ]
162
+ },
163
+ {
164
+ "id": "key-driver-change",
165
+ "slots": {
166
+ "evidenceNoun": ["evidence", "findings", "results"],
167
+ "abstractActor": [
168
+ "collaboration",
169
+ "dialogue",
170
+ "mentorship",
171
+ "reflective practice",
172
+ "trust"
173
+ ],
174
+ "abstractChange": [
175
+ "meaningful change",
176
+ "collaborative practice",
177
+ "inclusive curriculum design",
178
+ "student success"
179
+ ]
180
+ },
181
+ "templates": [
182
+ "the {evidenceNoun} suggests that {abstractActor} serves as a key driver of {abstractChange}"
183
+ ]
184
+ },
185
+ {
186
+ "id": "robust-pathway-contours",
187
+ "slots": {
188
+ "frameworkNoun": ["framework", "model", "paper", "study"],
189
+ "contourObject": [
190
+ "the shifting contours of disciplinary practice",
191
+ "the broader ecology of knowledge exchange",
192
+ "the layered realities of academic work"
193
+ ]
194
+ },
195
+ "templates": [
196
+ "this {frameworkNoun} offers a robust pathway for understanding {contourObject}",
197
+ "the {frameworkNoun} offers a robust pathway for understanding {contourObject}"
198
+ ]
199
+ },
200
+ {
201
+ "id": "centering-and-touchstone",
202
+ "slots": {
203
+ "discussionNoun": ["discussion", "paper", "study"],
204
+ "academicObject": ["student voice", "care", "equity", "relationality"],
205
+ "abstractConcept": ["reflexivity", "relationality", "care", "agency"],
206
+ "academicGerund": [
207
+ "reimagining scholarly engagement",
208
+ "rethinking institutional practice",
209
+ "building resilient learning communities"
210
+ ]
211
+ },
212
+ "templates": [
213
+ "the {discussionNoun} highlights the importance of centering {academicObject}",
214
+ "{abstractConcept} emerges as a critical touchstone for {academicGerund}"
215
+ ]
216
+ },
217
+ {
218
+ "id": "illuminates-ways",
219
+ "slots": {
220
+ "paperNoun": ["article", "paper", "study"],
221
+ "academicActor": ["policy actors", "institutions", "students"],
222
+ "academicVerb": ["negotiate", "navigate", "operationalize"],
223
+ "abstractObject": [
224
+ "complexity",
225
+ "equity",
226
+ "identity",
227
+ "knowledge exchange"
228
+ ]
229
+ },
230
+ "templates": [
231
+ "the {paperNoun} illuminates the ways in which {academicActor} {academicVerb} {abstractObject}"
232
+ ]
233
+ },
234
+ {
235
+ "id": "profound-implications-gestures",
236
+ "slots": {
237
+ "insightNoun": ["these insights", "the findings", "the results"],
238
+ "scholarNoun": ["researchers", "scholars", "practitioners"],
239
+ "paperNoun": ["analysis", "article", "paper", "study"],
240
+ "academicObject": [
241
+ "what counts as scholarly impact",
242
+ "digital literacy",
243
+ "professional identity",
244
+ "student agency"
245
+ ]
246
+ },
247
+ "templates": [
248
+ "{insightNoun} have profound implications for {scholarNoun} seeking to bridge theory and practice",
249
+ "the {paperNoun} gestures toward a more expansive understanding of {academicObject}"
250
+ ]
251
+ }
252
+ ]
@@ -0,0 +1,58 @@
1
+ import { wordTokens } from "../../shared/text/tokens.js";
2
+ import { oneToOneRule } from "../private/textlint-rule-builders.js";
3
+ const RULE_ID = "narrative-slop:emotion-telling";
4
+ function wordSet(words) {
5
+ return new Set(words.split(" "));
6
+ }
7
+ const PERSON_PRONOUNS = wordSet("he i she they we");
8
+ const LINKING_VERBS = wordSet("felt was were");
9
+ const INTENSIFIERS = wordSet("really so still too very");
10
+ const EMOTION_WORDS = wordSet("afraid angry ashamed confused furious glad happy lonely mad nervous sad scared upset worried");
11
+ function isCapitalized(token) {
12
+ const first = token.text[0];
13
+ if (first === undefined) {
14
+ return false;
15
+ }
16
+ return (first.toLocaleUpperCase("en") === first &&
17
+ first.toLocaleLowerCase("en") !== first);
18
+ }
19
+ function hasPersonSubject(token) {
20
+ return (token !== undefined &&
21
+ (PERSON_PRONOUNS.has(token.normalized) || isCapitalized(token)));
22
+ }
23
+ function emotionIndex(tokens) {
24
+ if (!hasPersonSubject(tokens[0]) ||
25
+ !LINKING_VERBS.has(tokens[1]?.normalized ?? "")) {
26
+ return undefined;
27
+ }
28
+ let index = 2;
29
+ while (INTENSIFIERS.has(tokens[index]?.normalized ?? "")) {
30
+ index += 1;
31
+ }
32
+ return EMOTION_WORDS.has(tokens[index]?.normalized ?? "") ? index : undefined;
33
+ }
34
+ function isBluntEmotionLabel(tokens) {
35
+ const index = emotionIndex(tokens);
36
+ return index !== undefined && tokens[index + 1] === undefined;
37
+ }
38
+ const rule = oneToOneRule({
39
+ detect: (unit) => {
40
+ const tokens = wordTokens(unit.text);
41
+ if (!isBluntEmotionLabel(tokens)) {
42
+ return [];
43
+ }
44
+ return [
45
+ {
46
+ evidence: unit.text,
47
+ label: "blunt emotion label",
48
+ range: { end: unit.text.length, start: 0 }
49
+ }
50
+ ];
51
+ },
52
+ family: "narrative-slop",
53
+ formatMessage: (report) => `Emotion telling: ${report.detections[0]?.evidence}. Replace the blunt emotion label with a concrete action, thought, or choice.`,
54
+ ruleId: RULE_ID,
55
+ unitKind: "sentence"
56
+ });
57
+ export default rule;
58
+ //# sourceMappingURL=emotion-telling.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emotion-telling.js","sourceRoot":"","sources":["../../../src/rules/narrative-slop/emotion-telling.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,124 @@
1
+ import { splitSentences } from "../../shared/text/sentences.js";
2
+ import { wordTokens } from "../../shared/text/tokens.js";
3
+ import { oneToOneRule } from "../private/textlint-rule-builders.js";
4
+ const RULE_ID = "narrative-slop:empty-beat";
5
+ function wordSet(words) {
6
+ return new Set(words.split(" "));
7
+ }
8
+ const EMPTY_PAUSE_VERBS = wordSet("hesitated lingered paused stopped waited");
9
+ const EMPTY_PLACEHOLDER_VERBS = wordSet("lingered remained stood waited");
10
+ const EMPTY_DURATION_WORDS = wordSet("beat moment second");
11
+ const PLACE_PREPOSITIONS = wordSet("at beside by in near next outside");
12
+ const CAUSE_OR_PURPOSE_MARKERS = wordSet("after as because before if since to until when while");
13
+ const PERSON_PRONOUNS = wordSet("he i she they we");
14
+ const POSSESSIVES = wordSet("her his its my our their your");
15
+ const BODY_PARTS = wordSet("ear ears tail tails whisker whiskers");
16
+ const BODY_TAG_VERBS = wordSet("angled angling drooped drooping flicked flicking flattened flattening twitched twitching");
17
+ const KEPT_PACE_FOLLOWERS = wordSet("beside behind near next with");
18
+ function isCapitalized(token) {
19
+ const first = token.text[0];
20
+ if (first === undefined) {
21
+ return false;
22
+ }
23
+ return (first.toLocaleUpperCase("en") === first &&
24
+ first.toLocaleLowerCase("en") !== first);
25
+ }
26
+ function hasPersonSubject(tokens) {
27
+ const first = tokens[0];
28
+ return (first !== undefined &&
29
+ (PERSON_PRONOUNS.has(first.normalized) || isCapitalized(first)));
30
+ }
31
+ function hasCauseOrPurpose(tokens) {
32
+ return tokens.some((token) => CAUSE_OR_PURPOSE_MARKERS.has(token.normalized));
33
+ }
34
+ function sentenceDetection(sentence, label) {
35
+ return {
36
+ evidence: sentence.text,
37
+ label,
38
+ range: { end: sentence.end, start: sentence.start }
39
+ };
40
+ }
41
+ function isEmptyDurationPause(tokens) {
42
+ return (hasPersonSubject(tokens) &&
43
+ EMPTY_PAUSE_VERBS.has(tokens[1]?.normalized ?? "") &&
44
+ tokens[2]?.normalized === "for" &&
45
+ (tokens[3]?.normalized === "a" || tokens[3]?.normalized === "one") &&
46
+ EMPTY_DURATION_WORDS.has(tokens[4]?.normalized ?? "") &&
47
+ !hasCauseOrPurpose(tokens.slice(5)));
48
+ }
49
+ function isEmptyPlaceholder(tokens) {
50
+ return (hasPersonSubject(tokens) &&
51
+ EMPTY_PLACEHOLDER_VERBS.has(tokens[1]?.normalized ?? "") &&
52
+ tokens.some((token, index) => index > 1 && PLACE_PREPOSITIONS.has(token.normalized)) &&
53
+ !hasCauseOrPurpose(tokens.slice(2)));
54
+ }
55
+ function isEmptyKeptPace(tokens) {
56
+ return (hasPersonSubject(tokens) &&
57
+ tokens[1]?.normalized === "kept" &&
58
+ tokens[2]?.normalized === "pace" &&
59
+ (tokens.length === 3 ||
60
+ KEPT_PACE_FOLLOWERS.has(tokens[3]?.normalized ?? "")) &&
61
+ !hasCauseOrPurpose(tokens.slice(3)));
62
+ }
63
+ function bodyTagStart(tokens) {
64
+ for (let index = 0; index < tokens.length - 2; index += 1) {
65
+ if (POSSESSIVES.has(tokens[index]?.normalized ?? "") &&
66
+ BODY_PARTS.has(tokens[index + 1]?.normalized ?? "") &&
67
+ BODY_TAG_VERBS.has(tokens[index + 2]?.normalized ?? "")) {
68
+ return index;
69
+ }
70
+ }
71
+ return undefined;
72
+ }
73
+ function bodyTagDetection(sentence) {
74
+ const tokens = wordTokens(sentence.text);
75
+ const start = bodyTagStart(tokens);
76
+ if (start === undefined || hasCauseOrPurpose(tokens.slice(start + 3))) {
77
+ return undefined;
78
+ }
79
+ const first = tokens[start];
80
+ const last = tokens[start + 2];
81
+ if (first === undefined || last === undefined) {
82
+ return undefined;
83
+ }
84
+ return {
85
+ evidence: sentence.text.slice(first.start, last.end),
86
+ label: "generic animal body tag",
87
+ range: {
88
+ end: sentence.start + last.end,
89
+ start: sentence.start + first.start
90
+ }
91
+ };
92
+ }
93
+ function sentenceDetections(sentence) {
94
+ const tokens = wordTokens(sentence.text);
95
+ const bodyTag = bodyTagDetection(sentence);
96
+ if (isEmptyDurationPause(tokens)) {
97
+ return [
98
+ sentenceDetection(sentence, "empty hesitation beat"),
99
+ ...(bodyTag === undefined ? [] : [bodyTag])
100
+ ];
101
+ }
102
+ if (isEmptyPlaceholder(tokens)) {
103
+ return [
104
+ sentenceDetection(sentence, "empty placeholding beat"),
105
+ ...(bodyTag === undefined ? [] : [bodyTag])
106
+ ];
107
+ }
108
+ if (isEmptyKeptPace(tokens)) {
109
+ return [
110
+ sentenceDetection(sentence, "empty movement beat"),
111
+ ...(bodyTag === undefined ? [] : [bodyTag])
112
+ ];
113
+ }
114
+ return bodyTag === undefined ? [] : [bodyTag];
115
+ }
116
+ const rule = oneToOneRule({
117
+ detect: (unit) => splitSentences(unit.text).flatMap(sentenceDetections),
118
+ family: "narrative-slop",
119
+ formatMessage: (report) => `Narrative empty beat: ${report.detections[0]?.label}. Replace placeholder hesitation, waiting, or animal body shorthand with concrete action or cause.`,
120
+ ruleId: RULE_ID,
121
+ unitKind: "paragraph"
122
+ });
123
+ export default rule;
124
+ //# sourceMappingURL=empty-beat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty-beat.js","sourceRoot":"","sources":["../../../src/rules/narrative-slop/empty-beat.ts"],"names":[],"mappings":"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"}
@@ -5,22 +5,32 @@ const RULE_ID = "narrative-slop:low-information-beat-density";
5
5
  const GROUP = "low-information beat";
6
6
  const MAX_PARAGRAPH_TOKENS = 95;
7
7
  const MAX_WINDOW_TOKENS = 65;
8
- const MIN_HITS = 5;
8
+ const MIN_HITS = 4;
9
9
  const WINDOW_SENTENCES = 5;
10
10
  const LOW_INFORMATION_WORDS = new Set([
11
11
  "blinked",
12
12
  "breath",
13
13
  "chest",
14
+ "fingers",
15
+ "flicked",
16
+ "floor",
14
17
  "eyes",
15
18
  "face",
16
19
  "felt",
17
20
  "found",
21
+ "gaze",
18
22
  "glanced",
19
23
  "heart",
24
+ "lean",
25
+ "look",
20
26
  "looked",
27
+ "narrowed",
28
+ "shifted",
21
29
  "shoulders",
22
30
  "stepped",
23
31
  "stomach",
32
+ "swallowed",
33
+ "tail",
24
34
  "throat",
25
35
  "tightened",
26
36
  "turned",
@@ -31,8 +41,21 @@ const CONCRETE_ACTION_WORDS = new Set([
31
41
  "copied",
32
42
  "count",
33
43
  "counted",
44
+ "cuff",
45
+ "den",
46
+ "drank",
47
+ "drill",
48
+ "field",
34
49
  "gate",
35
50
  "guards",
51
+ "kits",
52
+ "logged",
53
+ "map",
54
+ "measured",
55
+ "nurse",
56
+ "pulse",
57
+ "recorded",
58
+ "stage",
36
59
  "symbol"
37
60
  ]);
38
61
  function hasConcreteAction(tokens) {