@noir-ai/skills 1.16.0 → 1.17.0
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 +1 -1
- package/builtin/{noir-code-hygiene → noir-codebase-audit}/SKILL.md +74 -20
- package/builtin/{noir-code-hygiene → noir-codebase-audit}/references/examples.md +1 -1
- package/builtin/noir-dataviz/SKILL.md +69 -0
- package/builtin/noir-debt/SKILL.md +43 -0
- package/builtin/noir-design/SKILL.md +56 -0
- package/builtin/noir-design-reference/SKILL.md +47 -0
- package/builtin/noir-design-reference/references/design.md +200 -0
- package/builtin/noir-lazy/SKILL.md +43 -0
- package/builtin/noir-over-engineering-review/SKILL.md +43 -0
- package/builtin/noir-readme/SKILL.md +1 -1
- package/builtin/noir-rules/SKILL.md +1 -1
- package/builtin/noir-security/SKILL.md +1 -1
- package/builtin/noir-worktree/SKILL.md +1 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +254 -0
- package/dist/index.js.map +1 -1
- package/evals/{noir-code-hygiene → noir-codebase-audit}/evals.json +1 -1
- package/package.json +2 -2
- package/builtin/noir-frontend/SKILL.md +0 -40
- package/builtin/noir-frontend/references/ui-patterns.md +0 -48
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: noir-over-engineering-review
|
|
3
|
+
description: Use when the user says "review for over-engineering", "what can we delete", "is this over-engineered", or "simplify". Review the current diff for reinvented stdlib, unneeded dependencies, speculative abstractions, and dead flexibility, one line per finding. Do NOT use for a correctness or security review (noir-verifying or noir-security), a read-only codebase search (noir-exploring), a project-health diagnostic (noir-doctor), or a whole-repo audit (noir-codebase-audit).
|
|
4
|
+
metadata:
|
|
5
|
+
category: meta
|
|
6
|
+
version: 1.0.0
|
|
7
|
+
license: MIT
|
|
8
|
+
compatibility: claude · agents-md · gemini · cursor · opencode
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# noir-over-engineering-review
|
|
12
|
+
|
|
13
|
+
Review the current diff for complexity that does not earn its place: code that reimplements the standard library, dependencies pulled in for what a few lines would cover, abstractions with one caller, and flexibility nothing uses. Report one line per finding so each can be cut on the spot.
|
|
14
|
+
|
|
15
|
+
## When to use
|
|
16
|
+
|
|
17
|
+
- The user says "review for over-engineering", "what can we delete", "is this over-engineered", or "simplify".
|
|
18
|
+
- A diff has grown and the reviewer wants a deletion-first pass before the correctness pass.
|
|
19
|
+
- **Do NOT use** for a correctness or security review (`noir-verifying`, `noir-security`), a read-only codebase search (`noir-exploring`), a project-health diagnostic (`noir-doctor`), or the whole-repo audit (`noir-codebase-audit`).
|
|
20
|
+
|
|
21
|
+
## Procedure
|
|
22
|
+
|
|
23
|
+
1. **Read the diff, not the file.** Scope findings to lines the change adds or touches. Existing complexity is out of scope unless the change depends on it.
|
|
24
|
+
2. **Run the four checks in order:** reinvented stdlib, unneeded dependency, speculative abstraction, dead flexibility.
|
|
25
|
+
3. **Write one line per finding.** Name the location, what to cut, and what replaces it. For example: `src/parse.ts:12 hand-rolled slug regex; replace with the slug helper in src/text.ts`.
|
|
26
|
+
4. **Stop at deletion.** When the fix is "delete the thing", write that and move on. A finding that needs a paragraph to justify is not ready to cut today.
|
|
27
|
+
|
|
28
|
+
## Verification
|
|
29
|
+
|
|
30
|
+
- [ ] Every finding names a location, the thing to cut, and what replaces it.
|
|
31
|
+
- [ ] Findings cover only lines the diff adds or touches.
|
|
32
|
+
- [ ] No finding is a correctness bug; those go to `noir-verifying`.
|
|
33
|
+
- [ ] Each finding is one line; anything longer was rewritten to fit.
|
|
34
|
+
|
|
35
|
+
## Notes
|
|
36
|
+
|
|
37
|
+
- Deletion over rewriting: a wrapper that adds nothing gets deleted, not described.
|
|
38
|
+
- A factory for one product, an interface with one implementation, a config knob nobody turns: all delete.
|
|
39
|
+
- Report the highest-value cuts first; the reader may stop after the first three.
|
|
40
|
+
|
|
41
|
+
## When done → next skill
|
|
42
|
+
|
|
43
|
+
→ `noir-verifying` to confirm the deletions left the behavior intact. Or `noir-codebase-audit` for the whole-repo pass.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: noir-readme
|
|
3
|
-
description: Use when generating or updating a project README or documentation from the codebase — keeping docs accurate. Use when the user says "write a README" or "update the docs"; when a new feature ships.
|
|
3
|
+
description: Use when generating or updating a project README or documentation from the codebase — keeping docs accurate. Use when the user says "write a README" or "update the docs"; when a new feature ships. Do NOT use for the code or spec itself; that work belongs to noir-executing-plans or noir-spec.
|
|
4
4
|
metadata:
|
|
5
5
|
category: document
|
|
6
6
|
version: 1.0.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: noir-rules
|
|
3
|
-
description: Use when reviewing or editing the project's AI working-rules (.noir/rules/RULES.md) — decide whether a directive belongs in the always-on contract vs a skill, a memory, or an ADR. Use when the user says "update the rules" or "add a rule".
|
|
3
|
+
description: Use when reviewing or editing the project's AI working-rules (.noir/rules/RULES.md) — decide whether a directive belongs in the always-on contract vs a skill, a memory, or an ADR. Use when the user says "update the rules" or "add a rule". Do NOT use for a directive that applies only sometimes; that belongs in a skill or memory, not the always-on contract.
|
|
4
4
|
metadata:
|
|
5
5
|
category: meta
|
|
6
6
|
version: 1.0.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: noir-security
|
|
3
|
-
description: Use when reviewing code for security vulnerabilities — injection, auth, SSRF, data exposure, and supply chain risks. Use when the user says "security review" or "audit this for security"; before shipping a feature that handles user input, auth, or sensitive data.
|
|
3
|
+
description: Use when reviewing code for security vulnerabilities — injection, auth, SSRF, data exposure, and supply chain risks. Use when the user says "security review" or "audit this for security"; before shipping a feature that handles user input, auth, or sensitive data. Do NOT use for general code review or style; use noir-codebase-audit.
|
|
4
4
|
metadata:
|
|
5
5
|
category: verify
|
|
6
6
|
version: 1.0.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: noir-worktree
|
|
3
|
-
description: Use when creating an isolated git workspace for feature work — keeping the main checkout clean. Use when the user says "worktree" or "isolate this work".
|
|
3
|
+
description: Use when creating an isolated git workspace for feature work — keeping the main checkout clean. Use when the user says "worktree" or "isolate this work". Do NOT use for a plain branch switch or commit; use git directly.
|
|
4
4
|
metadata:
|
|
5
5
|
category: git
|
|
6
6
|
version: 1.0.0
|
package/dist/index.d.ts
CHANGED
|
@@ -294,6 +294,13 @@ declare const HYGIENE_RULES: readonly HygieneRule[];
|
|
|
294
294
|
* Pure and deterministic: each pattern is recompiled per call, so no
|
|
295
295
|
* `lastIndex` state escapes and the same text always gives the same findings. */
|
|
296
296
|
declare function checkHygiene(text: string, kind: HygieneKind): HygieneFinding[];
|
|
297
|
+
/** Whether `text` carries the exemption marker for `kind` above the first
|
|
298
|
+
* finding the hygiene rules report, so every rule — the prose finders
|
|
299
|
+
* included — leaves the file alone. The prose finders run outside
|
|
300
|
+
* `checkHygiene`, so consumers that run them gate the file on this predicate
|
|
301
|
+
* rather than on `checkHygiene`'s findings. A marker placed at or below the
|
|
302
|
+
* first finding exempts nothing. */
|
|
303
|
+
declare function hasHygieneExemption(text: string, kind: HygieneKind): boolean;
|
|
297
304
|
|
|
298
305
|
/** The SKILL.md body — the markdown after the YAML frontmatter block. The
|
|
299
306
|
* frontmatter is metadata the host reads on its own; the body is the playbook
|
|
@@ -485,6 +492,8 @@ declare function evaluateSuite(suite: EvalSuite, candidates?: CandidateOutputs):
|
|
|
485
492
|
failures: string[];
|
|
486
493
|
}>;
|
|
487
494
|
|
|
495
|
+
declare function humanizerFindings(text: string): HygieneFinding[];
|
|
496
|
+
|
|
488
497
|
/** Auth shape. Locked: `env-var` only until keychain lands (refuse OAuth,
|
|
489
498
|
* never silently lower the security bar). `fallback:'manual-paste'` keeps the
|
|
490
499
|
* no-token path honest (the playbook tells the user to paste a value); `'none'`
|
|
@@ -631,4 +640,11 @@ declare const FORBIDDEN_RESIDUE: readonly string[];
|
|
|
631
640
|
* never the only thing matching a line. */
|
|
632
641
|
declare const RESIDUE_RULES: readonly HygieneRule[];
|
|
633
642
|
|
|
634
|
-
|
|
643
|
+
declare const KILL_ON_SIGHT: readonly ["delve", "utilize", "leverage", "facilitate", "elucidate", "embark", "endeavor", "encompass", "multifaceted", "tapestry", "testament", "paradigm", "synergy", "holistic", "catalyze", "juxtapose", "realm", "landscape", "myriad", "plethora"];
|
|
644
|
+
declare const CLUSTER_WORDS: readonly ["robust", "comprehensive", "seamless", "cutting-edge", "innovative", "streamline", "empower", "foster", "enhance", "elevate", "optimize", "scalable", "pivotal", "intricate", "profound", "resonate", "underscore", "harness", "navigate", "cultivate", "bolster", "galvanize", "cornerstone", "game-changer"];
|
|
645
|
+
declare const FILLER_PHRASES: readonly ["it is important to note", "it's important to note", "it is worth noting", "in today's", "in conclusion", "needless to say"];
|
|
646
|
+
declare const SLOP_TRIGRAMS: readonly ["a testament to", "a tapestry of", "in the realm of", "the power of", "serves as a", "in order to", "the fact that", "plays a crucial"];
|
|
647
|
+
declare const SLOP_ALLOWLIST: readonly string[];
|
|
648
|
+
declare function slopFindings(text: string): HygieneFinding[];
|
|
649
|
+
|
|
650
|
+
export { BUILTIN_DIR, type BuiltinReference, type BuiltinSkill, CLUSTER_WORDS, type CandidateOutputs, type CompileTarget, type CompiledIntegration, type CompiledSkill, EVALS_DIR, type EmitSummary, type EmittedFile, type EvalAssertion, type EvalSuite, FILLER_PHRASES, FORBIDDEN_RESIDUE, HYGIENE_EXEMPT_MARKERS, HYGIENE_RULES, type HygieneFinding, type HygieneKind, type HygieneRule, type HygieneTier, INTEGRATIONS_DIR, IntegrationAuthSchema, type IntegrationDeclaration$1 as IntegrationDeclaration, IntegrationDeclarationSchema, IntegrationMcpSchema, IntegrationSddSchema, type IntegrationSkill, KILL_ON_SIGHT, MAX_BODY_LINES, MAX_COMMENT_BLOCK_LINES, MIN_FULL_BODY_LINES, NOIR_NAMESPACE, RESIDUE_RULES, SLOP_ALLOWLIST, SLOP_TRIGRAMS, type SkillConflict, type SkillEval, type SkillFrontmatter, type SkillRegistryEntry, type ValidationResult, bodyOf, buildRegistry, chainedReferences, checkHygiene, compileIntegration, compileSkill, discoverAll, discoverBuiltin, discoverIntegrations, emitSkillsToDir, evaluateSuite, hasHygieneExemption, humanizerFindings, isWhatWhenDescription, lintSkill, lintWarnings, loadEvalSuites, looksLikeWhenDescription, missingSections, parseEvalSuite, parseFrontmatter, parseIntegration, registryByCategory, runAssertions, runtimeEmitsHostMcp, slopFindings, validateIntegration, validateSkill, withinLineBudget };
|
package/dist/index.js
CHANGED
|
@@ -109,6 +109,16 @@ function discoverAll(opts = {}) {
|
|
|
109
109
|
// src/quality.ts
|
|
110
110
|
import { ARTIFACT_TYPES } from "@noir-ai/core";
|
|
111
111
|
|
|
112
|
+
// src/debt-marker.ts
|
|
113
|
+
var DEBT_MARKER_RULE = {
|
|
114
|
+
id: "noir-debt",
|
|
115
|
+
tier: "warn",
|
|
116
|
+
appliesTo: "code",
|
|
117
|
+
pattern: /(?:^[ \t]*|[ \t])(?:\/\/|#|\/\*|\*)[ \t]*noir-debt:(?!.*\b(?:when|if|once)\b)/m,
|
|
118
|
+
rationale: "a debt marker without a ceiling or upgrade trigger is an un-trackable shortcut",
|
|
119
|
+
fix: "name the ceiling and the condition that justifies the upgrade"
|
|
120
|
+
};
|
|
121
|
+
|
|
112
122
|
// src/residue.ts
|
|
113
123
|
var RESIDUE = [
|
|
114
124
|
{
|
|
@@ -338,6 +348,7 @@ var HYGIENE_RULES = [
|
|
|
338
348
|
IRREGULAR_SCRIPT_IN_TEXT,
|
|
339
349
|
LONG_COMMENT_BLOCK,
|
|
340
350
|
BARE_TODO,
|
|
351
|
+
DEBT_MARKER_RULE,
|
|
341
352
|
...RESIDUE_RULES
|
|
342
353
|
];
|
|
343
354
|
function checkHygiene(text, kind) {
|
|
@@ -346,6 +357,12 @@ function checkHygiene(text, kind) {
|
|
|
346
357
|
if (exemptAt !== 0 && exemptAt < (findings[0]?.line ?? Number.POSITIVE_INFINITY)) return [];
|
|
347
358
|
return findings;
|
|
348
359
|
}
|
|
360
|
+
function hasHygieneExemption(text, kind) {
|
|
361
|
+
const exemptAt = exemptionLine(text, kind);
|
|
362
|
+
if (exemptAt === 0) return false;
|
|
363
|
+
const firstFinding = collectFindings(text, kind)[0];
|
|
364
|
+
return exemptAt < (firstFinding?.line ?? Number.POSITIVE_INFINITY);
|
|
365
|
+
}
|
|
349
366
|
function collectFindings(text, kind) {
|
|
350
367
|
const lineStarts = lineStartOffsets(text);
|
|
351
368
|
const findings = [];
|
|
@@ -1009,6 +1026,86 @@ function evaluateSuite(suite, candidates) {
|
|
|
1009
1026
|
});
|
|
1010
1027
|
}
|
|
1011
1028
|
|
|
1029
|
+
// src/humanizer.ts
|
|
1030
|
+
var HEDGES = [
|
|
1031
|
+
"it is worth mentioning",
|
|
1032
|
+
"it should be noted",
|
|
1033
|
+
"it goes without saying",
|
|
1034
|
+
"arguably"
|
|
1035
|
+
];
|
|
1036
|
+
var EM_DASH_PER_PARAGRAPH = 2;
|
|
1037
|
+
function proseOnly(text) {
|
|
1038
|
+
return text.replace(/```[\s\S]*?```/g, "");
|
|
1039
|
+
}
|
|
1040
|
+
function lineOf(text, index) {
|
|
1041
|
+
let line = 1;
|
|
1042
|
+
for (let i = 0; i < index; i++) if (text[i] === "\n") line++;
|
|
1043
|
+
return line;
|
|
1044
|
+
}
|
|
1045
|
+
function firstMatch(text, re) {
|
|
1046
|
+
const match = re.exec(text);
|
|
1047
|
+
return match ? match.index : -1;
|
|
1048
|
+
}
|
|
1049
|
+
function* paragraphsOf(text) {
|
|
1050
|
+
let paragraph = "";
|
|
1051
|
+
let startLine = 1;
|
|
1052
|
+
let line = 1;
|
|
1053
|
+
for (const raw of text.split("\n")) {
|
|
1054
|
+
if (raw.trim() === "") {
|
|
1055
|
+
if (paragraph.trim() !== "") yield { text: paragraph, line: startLine };
|
|
1056
|
+
paragraph = "";
|
|
1057
|
+
startLine = line + 1;
|
|
1058
|
+
} else {
|
|
1059
|
+
if (paragraph === "") startLine = line;
|
|
1060
|
+
paragraph += (paragraph === "" ? "" : "\n") + raw;
|
|
1061
|
+
}
|
|
1062
|
+
line++;
|
|
1063
|
+
}
|
|
1064
|
+
if (paragraph.trim() !== "") yield { text: paragraph, line: startLine };
|
|
1065
|
+
}
|
|
1066
|
+
function humanizerFindings(text) {
|
|
1067
|
+
const prose = proseOnly(text);
|
|
1068
|
+
const findings = [];
|
|
1069
|
+
for (const hedge of HEDGES) {
|
|
1070
|
+
const index = firstMatch(prose, new RegExp(hedge, "i"));
|
|
1071
|
+
if (index !== -1) {
|
|
1072
|
+
findings.push({
|
|
1073
|
+
id: "humanizer-hedge",
|
|
1074
|
+
tier: "warn",
|
|
1075
|
+
line: lineOf(prose, index),
|
|
1076
|
+
text: hedge,
|
|
1077
|
+
rationale: "a hedging opener",
|
|
1078
|
+
fix: "delete it"
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
const contrastIndex = firstMatch(prose, /\bnot\s+\w+[\w\s,]{0,40}\bbut\b/i);
|
|
1083
|
+
if (contrastIndex !== -1) {
|
|
1084
|
+
findings.push({
|
|
1085
|
+
id: "humanizer-contrast",
|
|
1086
|
+
tier: "warn",
|
|
1087
|
+
line: lineOf(prose, contrastIndex),
|
|
1088
|
+
text: "not X but Y",
|
|
1089
|
+
rationale: "a formulaic contrast",
|
|
1090
|
+
fix: "state the second clause alone"
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
for (const { text: paragraph, line } of paragraphsOf(prose)) {
|
|
1094
|
+
const count = (paragraph.match(/—/g) ?? []).length;
|
|
1095
|
+
if (count > EM_DASH_PER_PARAGRAPH) {
|
|
1096
|
+
findings.push({
|
|
1097
|
+
id: "humanizer-em-dash",
|
|
1098
|
+
tier: "warn",
|
|
1099
|
+
line,
|
|
1100
|
+
text: `${count} em-dashes`,
|
|
1101
|
+
rationale: "em-dash density is an AI tell",
|
|
1102
|
+
fix: "use periods or parentheticals"
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
return findings;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1012
1109
|
// src/registry.ts
|
|
1013
1110
|
var NOIR_NAMESPACE = "noir-";
|
|
1014
1111
|
function fallbackCategory(name) {
|
|
@@ -1043,9 +1140,160 @@ function buildRegistry() {
|
|
|
1043
1140
|
function registryByCategory(category) {
|
|
1044
1141
|
return buildRegistry().filter((e) => e.category === category);
|
|
1045
1142
|
}
|
|
1143
|
+
|
|
1144
|
+
// src/slop.ts
|
|
1145
|
+
var KILL_ON_SIGHT = [
|
|
1146
|
+
"delve",
|
|
1147
|
+
"utilize",
|
|
1148
|
+
"leverage",
|
|
1149
|
+
"facilitate",
|
|
1150
|
+
"elucidate",
|
|
1151
|
+
"embark",
|
|
1152
|
+
"endeavor",
|
|
1153
|
+
"encompass",
|
|
1154
|
+
"multifaceted",
|
|
1155
|
+
"tapestry",
|
|
1156
|
+
"testament",
|
|
1157
|
+
"paradigm",
|
|
1158
|
+
"synergy",
|
|
1159
|
+
"holistic",
|
|
1160
|
+
"catalyze",
|
|
1161
|
+
"juxtapose",
|
|
1162
|
+
"realm",
|
|
1163
|
+
"landscape",
|
|
1164
|
+
"myriad",
|
|
1165
|
+
"plethora"
|
|
1166
|
+
];
|
|
1167
|
+
var CLUSTER_WORDS = [
|
|
1168
|
+
"robust",
|
|
1169
|
+
"comprehensive",
|
|
1170
|
+
"seamless",
|
|
1171
|
+
"cutting-edge",
|
|
1172
|
+
"innovative",
|
|
1173
|
+
"streamline",
|
|
1174
|
+
"empower",
|
|
1175
|
+
"foster",
|
|
1176
|
+
"enhance",
|
|
1177
|
+
"elevate",
|
|
1178
|
+
"optimize",
|
|
1179
|
+
"scalable",
|
|
1180
|
+
"pivotal",
|
|
1181
|
+
"intricate",
|
|
1182
|
+
"profound",
|
|
1183
|
+
"resonate",
|
|
1184
|
+
"underscore",
|
|
1185
|
+
"harness",
|
|
1186
|
+
"navigate",
|
|
1187
|
+
"cultivate",
|
|
1188
|
+
"bolster",
|
|
1189
|
+
"galvanize",
|
|
1190
|
+
"cornerstone",
|
|
1191
|
+
"game-changer"
|
|
1192
|
+
];
|
|
1193
|
+
var FILLER_PHRASES = [
|
|
1194
|
+
"it is important to note",
|
|
1195
|
+
"it's important to note",
|
|
1196
|
+
"it is worth noting",
|
|
1197
|
+
"in today's",
|
|
1198
|
+
"in conclusion",
|
|
1199
|
+
"needless to say"
|
|
1200
|
+
];
|
|
1201
|
+
var SLOP_TRIGRAMS = [
|
|
1202
|
+
"a testament to",
|
|
1203
|
+
"a tapestry of",
|
|
1204
|
+
"in the realm of",
|
|
1205
|
+
"the power of",
|
|
1206
|
+
"serves as a",
|
|
1207
|
+
"in order to",
|
|
1208
|
+
"the fact that",
|
|
1209
|
+
"plays a crucial"
|
|
1210
|
+
];
|
|
1211
|
+
var SLOP_ALLOWLIST = ["harness"];
|
|
1212
|
+
function proseOnly2(text) {
|
|
1213
|
+
return text.replace(/```[\s\S]*?```/g, "");
|
|
1214
|
+
}
|
|
1215
|
+
function wordRe(word) {
|
|
1216
|
+
return new RegExp(`\\b${word.replace(/[-']/g, "[-\\s]?")}\\b`, "i");
|
|
1217
|
+
}
|
|
1218
|
+
var CLUSTER_THRESHOLD = 3;
|
|
1219
|
+
function lineOf2(text, index) {
|
|
1220
|
+
let line = 1;
|
|
1221
|
+
for (let i = 0; i < index; i++) if (text[i] === "\n") line++;
|
|
1222
|
+
return line;
|
|
1223
|
+
}
|
|
1224
|
+
function firstMatch2(text, re) {
|
|
1225
|
+
const match = re.exec(text);
|
|
1226
|
+
return match ? match.index : -1;
|
|
1227
|
+
}
|
|
1228
|
+
function* paragraphsOf2(text) {
|
|
1229
|
+
let paragraph = "";
|
|
1230
|
+
let startLine = 1;
|
|
1231
|
+
let line = 1;
|
|
1232
|
+
for (const raw of text.split("\n")) {
|
|
1233
|
+
if (raw.trim() === "") {
|
|
1234
|
+
if (paragraph.trim() !== "") yield { text: paragraph, line: startLine };
|
|
1235
|
+
paragraph = "";
|
|
1236
|
+
startLine = line + 1;
|
|
1237
|
+
} else {
|
|
1238
|
+
if (paragraph === "") startLine = line;
|
|
1239
|
+
paragraph += (paragraph === "" ? "" : "\n") + raw;
|
|
1240
|
+
}
|
|
1241
|
+
line++;
|
|
1242
|
+
}
|
|
1243
|
+
if (paragraph.trim() !== "") yield { text: paragraph, line: startLine };
|
|
1244
|
+
}
|
|
1245
|
+
function slopFindings(text) {
|
|
1246
|
+
const prose = proseOnly2(text);
|
|
1247
|
+
const findings = [];
|
|
1248
|
+
for (const word of KILL_ON_SIGHT) {
|
|
1249
|
+
if (SLOP_ALLOWLIST.includes(word)) continue;
|
|
1250
|
+
const index = firstMatch2(prose, wordRe(word));
|
|
1251
|
+
if (index !== -1) {
|
|
1252
|
+
findings.push({
|
|
1253
|
+
id: "slop-kill",
|
|
1254
|
+
tier: "warn",
|
|
1255
|
+
line: lineOf2(prose, index),
|
|
1256
|
+
text: word,
|
|
1257
|
+
rationale: "a word LLMs overuse, flagged on first use",
|
|
1258
|
+
fix: `replace "${word}" with a plain verb or noun`
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
for (const { text: paragraph, line } of paragraphsOf2(prose)) {
|
|
1263
|
+
const hits = CLUSTER_WORDS.filter(
|
|
1264
|
+
(w) => !SLOP_ALLOWLIST.includes(w) && wordRe(w).test(paragraph)
|
|
1265
|
+
).length;
|
|
1266
|
+
if (hits >= CLUSTER_THRESHOLD) {
|
|
1267
|
+
findings.push({
|
|
1268
|
+
id: "slop-cluster",
|
|
1269
|
+
tier: "warn",
|
|
1270
|
+
line,
|
|
1271
|
+
text: `${hits} cluster words`,
|
|
1272
|
+
rationale: "a paragraph dense with abstract praise reads machine-written",
|
|
1273
|
+
fix: "rewrite in concrete terms; keep at most one such word"
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
for (const phrase of [...FILLER_PHRASES, ...SLOP_TRIGRAMS]) {
|
|
1278
|
+
const index = firstMatch2(prose, new RegExp(phrase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i"));
|
|
1279
|
+
if (index !== -1) {
|
|
1280
|
+
findings.push({
|
|
1281
|
+
id: "slop-phrase",
|
|
1282
|
+
tier: "warn",
|
|
1283
|
+
line: lineOf2(prose, index),
|
|
1284
|
+
text: phrase,
|
|
1285
|
+
rationale: "a filler phrase or slop trigram",
|
|
1286
|
+
fix: "delete it or state the point directly"
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
return findings;
|
|
1291
|
+
}
|
|
1046
1292
|
export {
|
|
1047
1293
|
BUILTIN_DIR,
|
|
1294
|
+
CLUSTER_WORDS,
|
|
1048
1295
|
EVALS_DIR,
|
|
1296
|
+
FILLER_PHRASES,
|
|
1049
1297
|
FORBIDDEN_RESIDUE,
|
|
1050
1298
|
HYGIENE_EXEMPT_MARKERS,
|
|
1051
1299
|
HYGIENE_RULES,
|
|
@@ -1054,11 +1302,14 @@ export {
|
|
|
1054
1302
|
IntegrationDeclarationSchema,
|
|
1055
1303
|
IntegrationMcpSchema,
|
|
1056
1304
|
IntegrationSddSchema,
|
|
1305
|
+
KILL_ON_SIGHT,
|
|
1057
1306
|
MAX_BODY_LINES,
|
|
1058
1307
|
MAX_COMMENT_BLOCK_LINES,
|
|
1059
1308
|
MIN_FULL_BODY_LINES,
|
|
1060
1309
|
NOIR_NAMESPACE,
|
|
1061
1310
|
RESIDUE_RULES,
|
|
1311
|
+
SLOP_ALLOWLIST,
|
|
1312
|
+
SLOP_TRIGRAMS,
|
|
1062
1313
|
bodyOf,
|
|
1063
1314
|
buildRegistry,
|
|
1064
1315
|
chainedReferences,
|
|
@@ -1070,6 +1321,8 @@ export {
|
|
|
1070
1321
|
discoverIntegrations,
|
|
1071
1322
|
emitSkillsToDir,
|
|
1072
1323
|
evaluateSuite,
|
|
1324
|
+
hasHygieneExemption,
|
|
1325
|
+
humanizerFindings,
|
|
1073
1326
|
isWhatWhenDescription,
|
|
1074
1327
|
lintSkill,
|
|
1075
1328
|
lintWarnings,
|
|
@@ -1082,6 +1335,7 @@ export {
|
|
|
1082
1335
|
registryByCategory,
|
|
1083
1336
|
runAssertions,
|
|
1084
1337
|
runtimeEmitsHostMcp,
|
|
1338
|
+
slopFindings,
|
|
1085
1339
|
validateIntegration,
|
|
1086
1340
|
validateSkill,
|
|
1087
1341
|
withinLineBudget
|