@noir-ai/skills 1.15.0 → 1.16.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/dist/index.js CHANGED
@@ -108,6 +108,306 @@ function discoverAll(opts = {}) {
108
108
 
109
109
  // src/quality.ts
110
110
  import { ARTIFACT_TYPES } from "@noir-ai/core";
111
+
112
+ // src/residue.ts
113
+ var RESIDUE = [
114
+ {
115
+ token: "workflow/<task",
116
+ why: "Names a state file of the removed workflow plugin, which nothing in this repository reads."
117
+ },
118
+ {
119
+ token: "noir-workflow.mode",
120
+ why: "Names the mode flag of the removed workflow plugin, which nothing in this repository reads."
121
+ },
122
+ {
123
+ token: "noir-workflow",
124
+ why: "Names the removed workflow plugin as a plugin or a path, so a reader following it finds nothing."
125
+ },
126
+ {
127
+ token: "@uiigateway",
128
+ why: "Names a package from the project this one was extracted from, which no consumer can resolve."
129
+ },
130
+ // 'ClickUp'/'clickup' were forbidden while the predecessor plugin was being
131
+ // ported, because the ClickUp REST precedent lived inside it. ClickUp now
132
+ // ships as a first-class integration, so the token names a legitimate
133
+ // subject and is deliberately absent from this list.
134
+ {
135
+ token: "<EXTREMELY-IMPORTANT",
136
+ why: "Copied rhetorical markup from the upstream tool, which carries no instruction a reader can follow."
137
+ },
138
+ {
139
+ token: "SUBAGENT-STOP",
140
+ why: "Copied rhetorical markup from the upstream tool, which carries no instruction a reader can follow."
141
+ },
142
+ {
143
+ token: "plugins/noir-workflow",
144
+ why: "Names the removed plugin path, which no longer exists in this repository."
145
+ }
146
+ ];
147
+ var FORBIDDEN_RESIDUE = RESIDUE.map((entry) => entry.token);
148
+ var RESIDUE_RULES = RESIDUE.map((entry) => ({
149
+ id: `residue-${entry.token.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "").toLowerCase()}`,
150
+ tier: "fail",
151
+ pattern: new RegExp(`(?<![\\w-])${escapeForRegExp(entry.token)}(?![\\w-])`),
152
+ rationale: entry.why,
153
+ fix: "Remove the reference. If it named a mechanism, name the mechanism that replaced it.",
154
+ appliesTo: "both"
155
+ }));
156
+ function escapeForRegExp(literal2) {
157
+ return literal2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
158
+ }
159
+
160
+ // src/hygiene.ts
161
+ var HYGIENE_EXEMPT_MARKERS = {
162
+ code: "// noir-hygiene: exempt",
163
+ markdown: "<!-- noir-hygiene: exempt -->"
164
+ };
165
+ var MAX_COMMENT_BLOCK_LINES = 12;
166
+ var MARKER = String.raw`(?:^[ \t]*|[ \t])(?:\/\/|#|\/\*|\*)`;
167
+ var VARIATION_SELECTOR = String.fromCharCode(65039);
168
+ var PICTOGRAPH = String.raw`[\u{1F300}-\u{1FAFF}]`;
169
+ var DECORATION_EMOJI = `[\u26A0\u2705\u{1F389}\u2728\u{1F525}\u{1F680}\u{1F9E0}\u{1F4A1}\u{1F527}\u{1F4CC}]${VARIATION_SELECTOR}?`;
170
+ var BACKTICK = String.fromCharCode(96);
171
+ var QUOTE_OR_BACKTICK = `[${BACKTICK}'"]`;
172
+ var MARKER_WORDS = ["TODO", "FIXME"];
173
+ var REASON_WORDS = [
174
+ "because",
175
+ "since",
176
+ "until",
177
+ "once",
178
+ "when",
179
+ "unless",
180
+ "blocked",
181
+ "otherwise",
182
+ "revisit"
183
+ ];
184
+ var OWNER = String.raw`(?:@[A-Za-z0-9_-]|\([A-Za-z][A-Za-z0-9_-]*\)|#\d+)`;
185
+ var DECORATIVE_BANNER = {
186
+ id: "decorative-banner",
187
+ tier: "fail",
188
+ pattern: new RegExp(String.raw`${MARKER}[ \t]*[-=*_~]{4,}`, "m"),
189
+ rationale: "A divider drawn in punctuation marks a section for the person writing the file, not for the reader: it carries no information, and it makes the file read as machine output.",
190
+ fix: "Delete the divider. If the label names a section a reader needs, make it a heading or name it in the declaration below.",
191
+ appliesTo: "both"
192
+ };
193
+ var WORKFLOW_NARRATION = {
194
+ id: "workflow-narration",
195
+ tier: "fail",
196
+ pattern: new RegExp(
197
+ String.raw`${MARKER}[ \t]*(?:step[ \t]*\d+[ \t]*[:.)]|(?:first|second|third|then|next|finally|lastly)[ \t]*[,:](?![ \t]*(?:which|that|who)\b)(?![^\n]*\b(?:so|because|since)\b))`,
198
+ "im"
199
+ ),
200
+ rationale: "Numbered narration restates what the code already says in the order it already says it, and it goes stale the moment that order changes.",
201
+ fix: "Drop the step markers. Keep only what the code cannot show: why this order, which invariant is held, or what breaks otherwise.",
202
+ appliesTo: "code"
203
+ };
204
+ var DECORATIVE_EMOJI_IN_COMMENT = {
205
+ id: "decorative-emoji-comment",
206
+ tier: "fail",
207
+ pattern: new RegExp(
208
+ String.raw`${MARKER}[^\n]*(?<!${QUOTE_OR_BACKTICK})${DECORATION_EMOJI}(?!${VARIATION_SELECTOR}?${QUOTE_OR_BACKTICK})`,
209
+ "mu"
210
+ ),
211
+ rationale: "A decorative emoji in a comment adds no information a reader needs, and it is the clearest single sign that the text was generated rather than written.",
212
+ fix: "Remove the emoji and let the sentence carry the emphasis. If it marked a state, name the state in a word.",
213
+ appliesTo: "code"
214
+ };
215
+ var DECORATIVE_PICTOGRAPH = {
216
+ id: "decorative-pictograph-comment",
217
+ tier: "fail",
218
+ pattern: new RegExp(
219
+ String.raw`${MARKER}[ \t]*(?!${DECORATION_EMOJI})${PICTOGRAPH}${VARIATION_SELECTOR}?`,
220
+ "mu"
221
+ ),
222
+ rationale: "A comment that opens with a pictograph is decoration: the pictograph marks nothing a reader can act on, and it reads as a generated flourish.",
223
+ fix: "Remove the pictograph. If it stood for a state or a result, write that in words.",
224
+ appliesTo: "code"
225
+ };
226
+ var DECORATIVE_EMOJI_IN_PROSE = {
227
+ id: "decorative-emoji-doc",
228
+ tier: "fail",
229
+ pattern: new RegExp(
230
+ String.raw`^[ \t]{0,3}(?:#{1,6}[ \t]+)?(?:[-*+][ \t]+)?(?:${DECORATION_EMOJI}|${PICTOGRAPH}${VARIATION_SELECTOR}?)`,
231
+ "mu"
232
+ ),
233
+ rationale: "A heading or a bullet that opens with a decorative emoji spends a reader attention on the emoji and none of it on the point of the line.",
234
+ fix: "Remove the emoji and put the point of the line in its words.",
235
+ appliesTo: "markdown"
236
+ };
237
+ var IRREGULAR_SCRIPT_SOURCES = [
238
+ String.raw`\u{0370}-\u{03ff}`,
239
+ // Greek
240
+ String.raw`\u{1f00}-\u{1fff}`,
241
+ // Greek extended
242
+ String.raw`\u{0400}-\u{052f}`,
243
+ // Cyrillic and its supplement
244
+ String.raw`\u{2de0}-\u{2dff}`,
245
+ // Cyrillic extended A
246
+ String.raw`\u{a640}-\u{a69f}`,
247
+ // Cyrillic extended B
248
+ String.raw`\u{0590}-\u{05ff}`,
249
+ // Hebrew
250
+ String.raw`\u{0600}-\u{06ff}`,
251
+ // Arabic
252
+ String.raw`\u{0750}-\u{077f}`,
253
+ // Arabic supplement
254
+ String.raw`\u{fb1d}-\u{fb4f}`,
255
+ // Hebrew presentation forms
256
+ String.raw`\u{fb50}-\u{fdff}`,
257
+ // Arabic presentation forms A
258
+ String.raw`\u{fe70}-\u{feff}`,
259
+ // Arabic presentation forms B, and the byte-order mark
260
+ String.raw`\u{0900}-\u{097f}`,
261
+ // Devanagari
262
+ String.raw`\u{0e00}-\u{0e7f}`,
263
+ // Thai
264
+ String.raw`\u{1100}-\u{11ff}`,
265
+ // Hangul Jamo
266
+ String.raw`\u{2e80}-\u{2fdf}`,
267
+ // CJK radicals
268
+ String.raw`\u{3000}-\u{30ff}`,
269
+ // CJK symbols and punctuation, Hiragana, Katakana
270
+ String.raw`\u{3100}-\u{318f}`,
271
+ // Bopomofo, Hangul compatibility Jamo
272
+ String.raw`\u{31c0}-\u{31ef}`,
273
+ // CJK strokes and Katakana extensions
274
+ String.raw`\u{3200}-\u{33ff}`,
275
+ // Enclosed and compatibility CJK
276
+ String.raw`\u{3400}-\u{4dbf}`,
277
+ // CJK unified ideographs extension A
278
+ String.raw`\u{4e00}-\u{9fff}`,
279
+ // CJK unified ideographs
280
+ String.raw`\u{ac00}-\u{d7af}`,
281
+ // Hangul syllables
282
+ String.raw`\u{f900}-\u{faff}`,
283
+ // CJK compatibility ideographs
284
+ String.raw`\u{ff00}-\u{ffef}`,
285
+ // Fullwidth and halfwidth forms
286
+ String.raw`\u{200b}-\u{200f}`,
287
+ // Zero-width space, joiners and the bidirectional marks
288
+ String.raw`\u{202a}-\u{202e}`,
289
+ // Bidirectional embeddings and overrides
290
+ String.raw`\u{2066}-\u{2069}`,
291
+ // Bidirectional isolates
292
+ String.raw`\u{fffd}`,
293
+ // the replacement character
294
+ String.raw`\u{e000}-\u{f8ff}`,
295
+ // Private use area
296
+ String.raw`\u{f0000}-\u{ffffd}`,
297
+ // Private use planes 15 and 16
298
+ String.raw`\u{100000}-\u{10fffd}`
299
+ // Private use plane 17
300
+ ];
301
+ var IRREGULAR_SCRIPT = `[${IRREGULAR_SCRIPT_SOURCES.join("")}]`;
302
+ var IRREGULAR_SCRIPT_IN_TEXT = {
303
+ id: "no-irregular-script",
304
+ tier: "fail",
305
+ pattern: new RegExp(IRREGULAR_SCRIPT, "u"),
306
+ rationale: "Generated text can leak a token from another script, or carry the bytes a mis-decoding turned into a replacement or invisible character. Either way the reader is shown something the writer never meant to say.",
307
+ fix: "Rewrite the text in the project language, and delete the invisible character. Keep a character from another script only in a deliberate fixture, which states its own exemption with the marker above.",
308
+ appliesTo: "both"
309
+ };
310
+ var LONG_COMMENT_BLOCK = {
311
+ id: "long-comment-block",
312
+ tier: "warn",
313
+ pattern: new RegExp(
314
+ String.raw`(?:^[ \t]*(?:\/\/|\/\*|\*|#[ \t])(?![ \t]*[-=*_~]{4})[ \t]*\S[^\n]*(?:\n|$)){${MAX_COMMENT_BLOCK_LINES},}`,
315
+ "m"
316
+ ),
317
+ rationale: `A run of ${MAX_COMMENT_BLOCK_LINES} or more comment lines with no break between them is usually narration or a restatement of the code below it, which a reader skips and which drifts out of date.`,
318
+ fix: "Cut the block to the part a reader cannot get from the code, and move reference detail into the document that owns it.",
319
+ appliesTo: "code"
320
+ };
321
+ var BARE_TODO = {
322
+ id: "bare-todo",
323
+ tier: "warn",
324
+ pattern: new RegExp(
325
+ String.raw`${MARKER}[ \t]*(?:${MARKER_WORDS.join("|")})\b(?![^\n]*${OWNER})(?![^\n]*\b(?:${REASON_WORDS.join("|")})\b)`,
326
+ "im"
327
+ ),
328
+ rationale: "A TODO with no owner and no reason cannot be acted on: no one knows who will do it, or what would make it unnecessary.",
329
+ fix: 'Name an owner or a tracked issue and the condition that retires the marker, such as "revisit once the v2 client lands".',
330
+ appliesTo: "code"
331
+ };
332
+ var HYGIENE_RULES = [
333
+ DECORATIVE_BANNER,
334
+ WORKFLOW_NARRATION,
335
+ DECORATIVE_EMOJI_IN_COMMENT,
336
+ DECORATIVE_PICTOGRAPH,
337
+ DECORATIVE_EMOJI_IN_PROSE,
338
+ IRREGULAR_SCRIPT_IN_TEXT,
339
+ LONG_COMMENT_BLOCK,
340
+ BARE_TODO,
341
+ ...RESIDUE_RULES
342
+ ];
343
+ function checkHygiene(text, kind) {
344
+ const findings = collectFindings(text, kind);
345
+ const exemptAt = exemptionLine(text, kind);
346
+ if (exemptAt !== 0 && exemptAt < (findings[0]?.line ?? Number.POSITIVE_INFINITY)) return [];
347
+ return findings;
348
+ }
349
+ function collectFindings(text, kind) {
350
+ const lineStarts = lineStartOffsets(text);
351
+ const findings = [];
352
+ for (const rule of HYGIENE_RULES) {
353
+ if (rule.appliesTo !== "both" && rule.appliesTo !== kind) continue;
354
+ let reportedLine = 0;
355
+ for (const match of text.matchAll(globalPattern(rule.pattern))) {
356
+ const line = lineNumberAt(lineStarts, match.index ?? 0);
357
+ if (line === reportedLine) continue;
358
+ reportedLine = line;
359
+ findings.push({
360
+ id: rule.id,
361
+ tier: rule.tier,
362
+ line,
363
+ text: lineTextAt(text, lineStarts, line),
364
+ rationale: rule.rationale,
365
+ fix: rule.fix
366
+ });
367
+ }
368
+ }
369
+ return findings.sort((a, b) => a.line - b.line);
370
+ }
371
+ function exemptionLine(text, kind) {
372
+ const marker = HYGIENE_EXEMPT_MARKERS[kind];
373
+ const lines = text.split("\n");
374
+ for (let i = 0; i < lines.length; i++) if ((lines[i] ?? "").trim() === marker) return i + 1;
375
+ return 0;
376
+ }
377
+ function lineStartOffsets(text) {
378
+ const starts = [0];
379
+ for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1)) starts.push(i + 1);
380
+ return starts;
381
+ }
382
+ function lineNumberAt(lineStarts, index) {
383
+ let low = 0;
384
+ let high = lineStarts.length - 1;
385
+ while (low < high) {
386
+ const mid = Math.ceil((low + high) / 2);
387
+ if ((lineStarts[mid] ?? 0) <= index) low = mid;
388
+ else high = mid - 1;
389
+ }
390
+ return low + 1;
391
+ }
392
+ function lineTextAt(text, lineStarts, line) {
393
+ const start = lineStarts[line - 1] ?? 0;
394
+ const end = text.indexOf("\n", start);
395
+ return text.slice(start, end === -1 ? text.length : end).trim();
396
+ }
397
+ function globalPattern(pattern) {
398
+ const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
399
+ return new RegExp(pattern.source, flags);
400
+ }
401
+
402
+ // src/quality.ts
403
+ function bodyOf(md) {
404
+ return md.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
405
+ }
406
+ function authoredBody(body) {
407
+ if (body.startsWith("\r\n")) return body.slice(2);
408
+ if (body.startsWith("\n")) return body.slice(1);
409
+ return body;
410
+ }
111
411
  var MAX_BODY_LINES = 500;
112
412
  var MIN_FULL_BODY_LINES = 20;
113
413
  var WHEN_SECTION = /^## When to use$/im;
@@ -142,23 +442,24 @@ function looksLikeWhenDescription(description) {
142
442
  }
143
443
  function lintWarnings(skill) {
144
444
  const warnings = [];
145
- const body = skill.skillMd;
445
+ const file = skill.skillMd;
446
+ const body = bodyOf(file);
146
447
  const bodyLines = body.split("\n").length;
147
- if (bodyLines < MIN_FULL_BODY_LINES + 6) {
448
+ if (bodyLines < MIN_FULL_BODY_LINES) {
148
449
  warnings.push("thin-body: full playbook body is under 20 lines");
149
450
  }
150
451
  const hasExample = /```/.test(body) || /\bexample:?\b/i.test(body) || /\be\.g\.\b/i.test(body);
151
452
  if (!hasExample) {
152
453
  warnings.push("no-example: no concrete code fence or worked example in the body");
153
454
  }
154
- if (/\b(I|we|you)\s+(will|should|can|need|must|do|write|create|implement|run|build)\b/i.test(body)) {
455
+ if (/\b(I|we|you)\s+(will|should|can|need|must|do|write|create|implement|run|build)\b/i.test(file)) {
155
456
  warnings.push("first-person: narration addresses the reader instead of imperative steps");
156
457
  }
157
- if (/\b(?:wait|sleep|retry|backoff|limit|cap)\s+[a-z]*\s*(\d{1,4})\b/i.test(body) && !/because|to (avoid|prevent|give|let)/i.test(body)) {
458
+ if (/\b(?:wait|sleep|retry|backoff|limit|cap)\s+[a-z]*\s*(\d{1,4})\b/i.test(file) && !/because|to (avoid|prevent|give|let)/i.test(file)) {
158
459
  warnings.push("voodoo-constant: numeric threshold without a stated reason");
159
460
  }
160
- const hasVersionPin = /as of \d{4}|\bversion \d+\.\d+\.\d+\b|"v\d+\.\d+"/i.test(body);
161
- const hasLegacySection = /^## Legacy|^## Old patterns/i.test(body);
461
+ const hasVersionPin = /as of \d{4}|\bversion \d+\.\d+\.\d+\b|"v\d+\.\d+"/i.test(file);
462
+ const hasLegacySection = /^## Legacy|^## Old patterns/i.test(file);
162
463
  if (hasVersionPin && !hasLegacySection) {
163
464
  warnings.push("time-sensitive: version/date pin outside a Legacy section");
164
465
  }
@@ -202,6 +503,56 @@ function artifactPathDrift(skill) {
202
503
  }
203
504
  return [...drifts];
204
505
  }
506
+ function splitFencedBlocks(body) {
507
+ const prose = [];
508
+ const code = [];
509
+ let closing = null;
510
+ for (const line of body.split("\n")) {
511
+ let boundary = false;
512
+ if (closing === null) {
513
+ const open = line.match(/^[ \t]{0,3}(`{3,}|~{3,})/);
514
+ const run = open?.[1];
515
+ if (run) {
516
+ const char = run[0] ?? "";
517
+ closing = new RegExp(`^[ \\t]{0,3}${char}{${run.length},}[ \\t]*$`);
518
+ boundary = true;
519
+ }
520
+ } else if (closing.test(line)) {
521
+ closing = null;
522
+ boundary = true;
523
+ }
524
+ const isCode = closing !== null && !boundary;
525
+ prose.push(isCode ? "" : line);
526
+ code.push(isCode ? line : "");
527
+ }
528
+ return { prose: prose.join("\n"), code: code.join("\n") };
529
+ }
530
+ function exemptionLine2(body) {
531
+ const marker = HYGIENE_EXEMPT_MARKERS.markdown;
532
+ const lines = body.split("\n");
533
+ for (let i = 0; i < lines.length; i++) {
534
+ if ((lines[i] ?? "").trim() === marker) return i + 1;
535
+ }
536
+ return 0;
537
+ }
538
+ function hygieneFindings(body) {
539
+ const text = authoredBody(body);
540
+ const { prose, code } = splitFencedBlocks(text);
541
+ const findings = [...checkHygiene(prose, "markdown"), ...checkHygiene(code, "code")];
542
+ const seen = /* @__PURE__ */ new Set();
543
+ const ordered = findings.filter((f) => {
544
+ const key = `${f.id}:${f.line}`;
545
+ if (seen.has(key)) return false;
546
+ seen.add(key);
547
+ return true;
548
+ }).sort((a, b) => a.line - b.line);
549
+ const exemptAt = exemptionLine2(text);
550
+ if (exemptAt !== 0 && exemptAt < (ordered[0]?.line ?? Number.POSITIVE_INFINITY)) return [];
551
+ return ordered;
552
+ }
553
+ function hygieneMessage(finding) {
554
+ return `${finding.id} (line ${finding.line}): ${finding.rationale} ${finding.fix}`;
555
+ }
205
556
 
206
557
  // src/compiler.ts
207
558
  var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
@@ -218,9 +569,6 @@ function parseFrontmatter(md) {
218
569
  }
219
570
  return fm;
220
571
  }
221
- function bodyOf(md) {
222
- return md.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
223
- }
224
572
  function validateSkill(skill) {
225
573
  const errors = [];
226
574
  const warnings = [];
@@ -253,6 +601,11 @@ function validateSkill(skill) {
253
601
  if (!r.content.trim()) errors.push(`reference "${r.name}" is empty`);
254
602
  }
255
603
  for (const d of artifactPathDrift(skill)) errors.push(d);
604
+ for (const finding of hygieneFindings(body)) {
605
+ const message = hygieneMessage(finding);
606
+ if (finding.tier === "fail") errors.push(message);
607
+ else warnings.push(message);
608
+ }
256
609
  warnings.push(...lintWarnings(skill));
257
610
  return { ok: errors.length === 0, errors, warnings: warnings.length > 0 ? warnings : void 0 };
258
611
  }
@@ -642,12 +995,18 @@ function loadEvalSuites(dir = EVALS_DIR) {
642
995
  }
643
996
  return suites;
644
997
  }
645
- function evaluateSuite(suite) {
646
- return suite.evals.map((e) => ({
647
- id: e.id,
648
- pass: e.assertions ? runAssertions(e.expected_output, e.assertions).pass : true,
649
- failures: e.assertions ? runAssertions(e.expected_output, e.assertions).failures : []
650
- }));
998
+ function evaluateSuite(suite, candidates) {
999
+ return suite.evals.map((e) => {
1000
+ if (candidates && !(e.id in candidates)) {
1001
+ return { id: e.id, pass: false, failures: [`no candidate output for eval "${e.id}"`] };
1002
+ }
1003
+ const output = candidates ? candidates[e.id] ?? e.expected_output : e.expected_output;
1004
+ return {
1005
+ id: e.id,
1006
+ pass: e.assertions ? runAssertions(output, e.assertions).pass : true,
1007
+ failures: e.assertions ? runAssertions(output, e.assertions).failures : []
1008
+ };
1009
+ });
651
1010
  }
652
1011
 
653
1012
  // src/registry.ts
@@ -684,43 +1043,26 @@ function buildRegistry() {
684
1043
  function registryByCategory(category) {
685
1044
  return buildRegistry().filter((e) => e.category === category);
686
1045
  }
687
-
688
- // src/residue.ts
689
- var FORBIDDEN_RESIDUE = [
690
- "workflow/<task",
691
- // predecessor state file
692
- "noir-workflow.mode",
693
- // predecessor mode flag
694
- "noir-workflow",
695
- // predecessor plugin name (as a plugin/path reference)
696
- "@uiigateway",
697
- // predecessor Angular specifics
698
- // NOTE: 'ClickUp'/'clickup' were forbidden during the predecessor-port era (the
699
- // ClickUp REST precedent lived in the deleted noir-workflow plugin). ClickUp is
700
- // now reintroduced as a first-class Noir integration (skills/integrations/noir-clickup),
701
- // so the token is no longer residue — it is the integration's legitimate subject.
702
- "<EXTREMELY-IMPORTANT",
703
- // Superpowers rhetoric
704
- "SUBAGENT-STOP",
705
- // Superpowers rhetoric
706
- "plugins/noir-workflow"
707
- // predecessor path
708
- ];
709
1046
  export {
710
1047
  BUILTIN_DIR,
711
1048
  EVALS_DIR,
712
1049
  FORBIDDEN_RESIDUE,
1050
+ HYGIENE_EXEMPT_MARKERS,
1051
+ HYGIENE_RULES,
713
1052
  INTEGRATIONS_DIR,
714
1053
  IntegrationAuthSchema,
715
1054
  IntegrationDeclarationSchema,
716
1055
  IntegrationMcpSchema,
717
1056
  IntegrationSddSchema,
718
1057
  MAX_BODY_LINES,
1058
+ MAX_COMMENT_BLOCK_LINES,
719
1059
  MIN_FULL_BODY_LINES,
720
1060
  NOIR_NAMESPACE,
1061
+ RESIDUE_RULES,
721
1062
  bodyOf,
722
1063
  buildRegistry,
723
1064
  chainedReferences,
1065
+ checkHygiene,
724
1066
  compileIntegration,
725
1067
  compileSkill,
726
1068
  discoverAll,