@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.
@@ -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
- export { BUILTIN_DIR, type BuiltinReference, type BuiltinSkill, type CandidateOutputs, type CompileTarget, type CompiledIntegration, type CompiledSkill, EVALS_DIR, type EmitSummary, type EmittedFile, type EvalAssertion, type EvalSuite, 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, MAX_BODY_LINES, MAX_COMMENT_BLOCK_LINES, MIN_FULL_BODY_LINES, NOIR_NAMESPACE, RESIDUE_RULES, type SkillConflict, type SkillEval, type SkillFrontmatter, type SkillRegistryEntry, type ValidationResult, bodyOf, buildRegistry, chainedReferences, checkHygiene, compileIntegration, compileSkill, discoverAll, discoverBuiltin, discoverIntegrations, emitSkillsToDir, evaluateSuite, isWhatWhenDescription, lintSkill, lintWarnings, loadEvalSuites, looksLikeWhenDescription, missingSections, parseEvalSuite, parseFrontmatter, parseIntegration, registryByCategory, runAssertions, runtimeEmitsHostMcp, validateIntegration, validateSkill, withinLineBudget };
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