@noir-ai/skills 1.17.0 → 1.18.0-beta.1
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 +17 -44
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1026,16 +1026,9 @@ function evaluateSuite(suite, candidates) {
|
|
|
1026
1026
|
});
|
|
1027
1027
|
}
|
|
1028
1028
|
|
|
1029
|
-
// src/
|
|
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;
|
|
1029
|
+
// src/text-scan.ts
|
|
1037
1030
|
function proseOnly(text) {
|
|
1038
|
-
return text.replace(/```[\s\S]*?```/g, "");
|
|
1031
|
+
return text.replace(/```[\s\S]*?```/g, "").split("\n").map((line) => /^[ \t]*\|/.test(line) ? "" : line).join("\n");
|
|
1039
1032
|
}
|
|
1040
1033
|
function lineOf(text, index) {
|
|
1041
1034
|
let line = 1;
|
|
@@ -1063,6 +1056,15 @@ function* paragraphsOf(text) {
|
|
|
1063
1056
|
}
|
|
1064
1057
|
if (paragraph.trim() !== "") yield { text: paragraph, line: startLine };
|
|
1065
1058
|
}
|
|
1059
|
+
|
|
1060
|
+
// src/humanizer.ts
|
|
1061
|
+
var HEDGES = [
|
|
1062
|
+
"it is worth mentioning",
|
|
1063
|
+
"it should be noted",
|
|
1064
|
+
"it goes without saying",
|
|
1065
|
+
"arguably"
|
|
1066
|
+
];
|
|
1067
|
+
var EM_DASH_PER_PARAGRAPH = 2;
|
|
1066
1068
|
function humanizerFindings(text) {
|
|
1067
1069
|
const prose = proseOnly(text);
|
|
1068
1070
|
const findings = [];
|
|
@@ -1209,57 +1211,28 @@ var SLOP_TRIGRAMS = [
|
|
|
1209
1211
|
"plays a crucial"
|
|
1210
1212
|
];
|
|
1211
1213
|
var SLOP_ALLOWLIST = ["harness"];
|
|
1212
|
-
function proseOnly2(text) {
|
|
1213
|
-
return text.replace(/```[\s\S]*?```/g, "");
|
|
1214
|
-
}
|
|
1215
1214
|
function wordRe(word) {
|
|
1216
1215
|
return new RegExp(`\\b${word.replace(/[-']/g, "[-\\s]?")}\\b`, "i");
|
|
1217
1216
|
}
|
|
1218
1217
|
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
1218
|
function slopFindings(text) {
|
|
1246
|
-
const prose =
|
|
1219
|
+
const prose = proseOnly(text);
|
|
1247
1220
|
const findings = [];
|
|
1248
1221
|
for (const word of KILL_ON_SIGHT) {
|
|
1249
1222
|
if (SLOP_ALLOWLIST.includes(word)) continue;
|
|
1250
|
-
const index =
|
|
1223
|
+
const index = firstMatch(prose, wordRe(word));
|
|
1251
1224
|
if (index !== -1) {
|
|
1252
1225
|
findings.push({
|
|
1253
1226
|
id: "slop-kill",
|
|
1254
1227
|
tier: "warn",
|
|
1255
|
-
line:
|
|
1228
|
+
line: lineOf(prose, index),
|
|
1256
1229
|
text: word,
|
|
1257
1230
|
rationale: "a word LLMs overuse, flagged on first use",
|
|
1258
1231
|
fix: `replace "${word}" with a plain verb or noun`
|
|
1259
1232
|
});
|
|
1260
1233
|
}
|
|
1261
1234
|
}
|
|
1262
|
-
for (const { text: paragraph, line } of
|
|
1235
|
+
for (const { text: paragraph, line } of paragraphsOf(prose)) {
|
|
1263
1236
|
const hits = CLUSTER_WORDS.filter(
|
|
1264
1237
|
(w) => !SLOP_ALLOWLIST.includes(w) && wordRe(w).test(paragraph)
|
|
1265
1238
|
).length;
|
|
@@ -1275,12 +1248,12 @@ function slopFindings(text) {
|
|
|
1275
1248
|
}
|
|
1276
1249
|
}
|
|
1277
1250
|
for (const phrase of [...FILLER_PHRASES, ...SLOP_TRIGRAMS]) {
|
|
1278
|
-
const index =
|
|
1251
|
+
const index = firstMatch(prose, new RegExp(phrase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i"));
|
|
1279
1252
|
if (index !== -1) {
|
|
1280
1253
|
findings.push({
|
|
1281
1254
|
id: "slop-phrase",
|
|
1282
1255
|
tier: "warn",
|
|
1283
|
-
line:
|
|
1256
|
+
line: lineOf(prose, index),
|
|
1284
1257
|
text: phrase,
|
|
1285
1258
|
rationale: "a filler phrase or slop trigram",
|
|
1286
1259
|
fix: "delete it or state the point directly"
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/compiler.ts","../src/discover.ts","../src/integrations-schema.ts","../src/quality.ts","../src/debt-marker.ts","../src/residue.ts","../src/hygiene.ts","../src/evals.ts","../src/humanizer.ts","../src/registry.ts","../src/slop.ts"],"sourcesContent":["import { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { basename, dirname, join } from 'node:path';\nimport { sha256Hex12, uniqueAsideSync } from '@noir-ai/core';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport { discoverAll, discoverBuiltin } from './discover.js';\nimport { runtimeEmitsHostMcp } from './integrations-schema.js';\nimport {\n artifactPathDrift,\n bodyOf,\n chainedReferences,\n hygieneFindings,\n hygieneMessage,\n isWhatWhenDescription,\n lintWarnings,\n looksLikeWhenDescription,\n MAX_BODY_LINES,\n missingSections,\n withinLineBudget,\n} from './quality.js';\n\n// Re-exported for backward-compat — callers (hygiene tests, CLI) import these\n// from './compiler.js'. The single source of truth is quality.ts (where the\n// body-structure rules live); these aliases keep the old import surface stable.\nexport { bodyOf, looksLikeWhenDescription } from './quality.js';\n\nimport type {\n BuiltinSkill,\n CompiledIntegration,\n CompiledSkill,\n CompileTarget,\n EmitSummary,\n IntegrationSkill,\n SkillConflict,\n SkillConflictContext,\n SkillConflictResolution,\n SkillConflictResolver,\n SkillFrontmatter,\n ValidationResult,\n} from './types.js';\n\nconst FRONTMATTER_RE = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/;\nconst NAME_RE = /^noir-[a-z0-9]+(?:-[a-z0-9]+)*$/;\nconst MAX_DESC = 1024;\n\nexport function parseFrontmatter(md: string): SkillFrontmatter {\n const m = md.match(FRONTMATTER_RE);\n if (!m) throw new Error('Skill missing YAML frontmatter (expected --- ... ---)');\n // Group 1 always exists when the regex matches; guard past noUncheckedIndexedAccess.\n const yaml = m[1];\n if (yaml === undefined) throw new Error('Skill missing YAML frontmatter (expected --- ... ---)');\n const fm = parseYaml(yaml) as SkillFrontmatter;\n if (typeof fm?.name !== 'string' || typeof fm?.description !== 'string') {\n throw new Error('Skill frontmatter requires string `name` + `description`');\n }\n return fm;\n}\n\nexport function validateSkill(skill: BuiltinSkill): ValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n const { name, description, metadata } = skill.frontmatter;\n if (!name) errors.push('missing `name`');\n else if (!NAME_RE.test(name)) errors.push(`name \"${name}\" must match noir-<kebab>`);\n if (basename(skill.dir) !== name) {\n errors.push(`dir \"${basename(skill.dir)}\" must equal name \"${name}\"`);\n }\n if (!description?.trim()) errors.push('missing `description`');\n else if (description.length > MAX_DESC) errors.push(`description exceeds ${MAX_DESC} chars`);\n else if (!looksLikeWhenDescription(description)) {\n errors.push('description must state WHEN to trigger (e.g. \"Use when…\"), not WHAT it does');\n } else if (!isWhatWhenDescription(description)) {\n // The description must ALSO carry a WHAT clause — the trigger phrase\n // alone (WHEN-only) fails. Errors, not warnings: it's part of the contract.\n errors.push('description must be WHAT+WHEN — a WHAT clause after the trigger phrase');\n }\n // Structural gate: metadata, required sections, line budget, one-level refs.\n if (!metadata?.category?.trim()) errors.push('missing `metadata.category`');\n if (!metadata?.version?.trim()) errors.push('missing `metadata.version`');\n const body = bodyOf(skill.skillMd);\n const missing = missingSections(body);\n for (const sec of missing) errors.push(`missing required section: ${sec}`);\n if (!withinLineBudget(body)) {\n errors.push(`body exceeds ${MAX_BODY_LINES}-line budget (${body.split('\\n').length} lines)`);\n }\n const chained = chainedReferences(skill);\n for (const r of chained)\n errors.push(`reference \"${r}\" chains to another reference (must be one level deep)`);\n for (const r of skill.references) {\n if (!/^[a-z0-9-]+\\.md$/i.test(r.name)) errors.push(`reference \"${r.name}\" must be <kebab>.md`);\n if (!r.content.trim()) errors.push(`reference \"${r.name}\" is empty`);\n }\n // Generated-artifact standard: no `.noir/` output-path drift in the body\n // or references (unknown dir, or a filename missing its type-code prefix).\n for (const d of artifactPathDrift(skill)) errors.push(d);\n // Output hygiene (the rules in hygiene.ts): a fail-tier finding blocks\n // emission exactly as a missing section does, and a warn-tier finding is\n // advisory like the style warnings below.\n for (const finding of hygieneFindings(body)) {\n const message = hygieneMessage(finding);\n if (finding.tier === 'fail') errors.push(message);\n else warnings.push(message);\n }\n // Soft warnings (lint-level) — advisory, non-failing.\n warnings.push(...lintWarnings(skill));\n return { ok: errors.length === 0, errors, warnings: warnings.length > 0 ? warnings : undefined };\n}\n\n/**\n * `lintSkill` — the soft quality gate. Errors = `validateSkill` errors (a\n * skill that fails validation is broken, the hygiene fail tier included);\n * warnings = the hygiene warn tier plus the `quality.ts` style rules (thin\n * body, no examples, first-person narration, …). A skill can validate clean\n * yet still carry lint warnings the author should resolve.\n */\nexport function lintSkill(skill: BuiltinSkill): {\n name: string;\n errors: string[];\n warnings: string[];\n} {\n const res = validateSkill(skill);\n return { name: skill.name, errors: res.errors, warnings: res.warnings ?? [] };\n}\n\n/**\n * Compile a builtin skill into the host-shaped `CompiledSkill`. The validator\n * runs for every target (a malformed skill is malformed in any format).\n *\n * - `claude` | `agents-md` | `gemini` | `opencode` (the AGENTS.md-aligned\n * hosts): canonical format copied verbatim — `SKILL.md` plus its\n * `references/` siblings. These hosts read the same SKILL.md shape;\n * the EMIT-LOCATION (which dir they land in) is the cli/adapter's job, not\n * the compiler's.\n * - `cursor`: transform into a Cursor `.mdc` rule — ONE file `<name>.mdc`\n * whose frontmatter carries the skill description, a wildcard globs entry,\n * and `alwaysApply: false` (the agent decides whether to pull the rule via\n * the description; never auto-applied). The body is the SKILL.md BODY\n * (frontmatter stripped). Cursor's rule format has no references concept,\n * so references are dropped (documented as a known limitation — the body is\n * the surface).\n *\n * The `target` defaults to `'claude'` for backward compatibility with every\n * existing caller; the verbatim branch produces byte-identical output to v1.1\n * (the regression anchor).\n */\nexport function compileSkill(skill: BuiltinSkill, target: CompileTarget = 'claude'): CompiledSkill {\n const res = validateSkill(skill);\n if (!res.ok) throw new Error(`Cannot compile ${skill.name}: ${res.errors.join('; ')}`);\n if (target === 'cursor') {\n return { name: skill.name, files: [{ path: [`${skill.name}.mdc`], content: toMdc(skill) }] };\n }\n // claude / agents-md / gemini / opencode → canonical verbatim format.\n const files = [\n { path: ['SKILL.md'], content: skill.skillMd },\n ...skill.references.map((r) => ({ path: ['references', r.name], content: r.content })),\n ];\n return { name: skill.name, files };\n}\n\n/**\n * Cursor `.mdc` rule transform — frontmatter `{description, globs, alwaysApply:\n * false}` + the SKILL.md body. The description drives Cursor's agent-decided\n * rule selection (the default: `alwaysApply: false`).\n * Cursor's rule format has no references/ concept, so the body alone is the\n * surface; references are dropped (documented risk in the spec).\n *\n * Frontmatter is YAML-serialized via the `yaml` package (already a dep) so\n * description escaping (colons, quotes, multi-line) is correct without hand\n * rolling. `lineWidth: 0` keeps descriptions on a single quoted line (predictable\n * byte shape; Cursor tolerates either). */\nfunction toMdc(skill: BuiltinSkill): string {\n const frontmatter = stringifyYaml(\n {\n description: skill.frontmatter.description,\n globs: ['**/*'],\n alwaysApply: false,\n },\n { lineWidth: 0 },\n ).trimEnd();\n const body = bodyOf(skill.skillMd);\n // Cursor's `.mdc` shape: leading `---\\n`, frontmatter, closing `---\\n`, blank\n // line, body. Trailing newline so files concatenate cleanly on disk.\n return `---\\n${frontmatter}\\n---\\n${body.endsWith('\\n') ? body : `${body}\\n`}`;\n}\n\n/**\n * Compile an integration: validates the SKILL.md (same shape as builtins) +\n * renders the same per-skill files. When the integration's `runtime` widens\n * emission (`mcp-stdio`/`external-mcp`) AND it carries a non-null `mcp`\n * declaration, also expose a provider-neutral `hostMcp` server block the host\n * adapter may merge into the host MCP config (e.g. Claude's `.mcp.json`).\n *\n * For `gated-write-proxy` (ClickUp) + `none` → no `hostMcp` (writes route\n * through Noir's own MCP tool; the host MCP config is unchanged). For\n * `mcp-stdio` the hostMcp is exposed (the compiler widens emission); the\n * Claude adapter still does not emit a NEW server entry for it (Noir's own\n * `noir mcp serve` entry already covers it).\n */\nexport function compileIntegration(\n integration: IntegrationSkill,\n target: CompileTarget = 'claude',\n): CompiledIntegration {\n // Validate the skill half first (throws on a bad SKILL.md — same path as\n // builtins). compileSkill returns a base CompiledSkill; we layer hostMcp on.\n const base = compileSkill(integration, target);\n const { runtime, mcp } = integration.declaration;\n if (runtimeEmitsHostMcp(runtime) && mcp !== null) {\n return {\n ...base,\n hostMcp: {\n serverName: integration.name,\n command: mcp.command,\n args: mcp.args,\n transport: mcp.transport,\n url: mcp.url,\n env: mcp.env,\n },\n };\n }\n return base;\n}\n\nexport async function emitSkillsToDir(\n targetDir: string,\n opts: {\n builtinDir?: string;\n integrationsDir?: string;\n includeIntegrations?: boolean;\n /** CompileTarget — selects the per-skill transform. Defaults to\n * `'claude'` (the v1.1 verbatim SKILL.md shape) so every existing caller\n * stays byte-identical. `'cursor'` compiles each skill to a `.mdc` rule\n * file (frontmatter `description`/`globs`/`alwaysApply:false` + the\n * SKILL.md body); the other targets map to the verbatim shape (their\n * hosts have no skill concept, so emit is skipped upstream — but the\n * default-verbatim policy keeps the signature total). */\n target?: CompileTarget;\n /** Policy for a skill file that exists AND differs from the compiled\n * bytes, when no {@link onConflict} resolver is wired (or non-interactive).\n * Default `'overwrite'` preserves the v1.2 behavior (every sync clobbers).\n * `'preserve'` skips the differing file (the CI / non-TTY default the CLI\n * threads via `buildConflictOpts`). */\n conflictPolicy?: 'overwrite' | 'preserve';\n /** Per-file conflict resolver. The CLI passes its single\n * `buildConflictOpts().onConflict` (clack menu + diff preview + apply-to-\n * all) through unchanged; structurally compatible with @noir-ai/create's\n * `ConflictResolverReturn`. */\n onConflict?: SkillConflictResolver;\n /** Explicit interactivity flag (the engine reads THIS, not process.env,\n * matching @noir-ai/create's hermetic-interactive contract). When `false`,\n * the resolver is NEVER consulted (CI / --json never hangs on a prompt). */\n interactive?: boolean;\n } = {},\n): Promise<EmitSummary> {\n const target: CompileTarget = opts.target ?? 'claude';\n const { builtins, integrations } = discoverAll({\n builtinDir: opts.builtinDir,\n integrationsDir: opts.integrationsDir,\n });\n const emitIntegrations = opts.includeIntegrations ?? true;\n // Validate the whole pack before writing anything (fail-fast, atomic-ish).\n for (const s of builtins) {\n const res = validateSkill(s);\n if (!res.ok) throw new Error(`Invalid builtin skill ${s.name}: ${res.errors.join('; ')}`);\n }\n if (emitIntegrations) {\n for (const i of integrations) {\n const res = validateSkill(i);\n if (!res.ok) throw new Error(`Invalid integration ${i.name}: ${res.errors.join('; ')}`);\n }\n }\n await mkdir(targetDir, { recursive: true });\n let references = 0;\n const emitted: string[] = [];\n const preserved: string[] = [];\n // Every skill this run is responsible for, whether or not its bytes landed.\n // The prune step below keeps these: a preserved skill's files are the user's\n // own, and deleting the directory they live in is the one outcome pruning\n // must never produce.\n const kept: string[] = [];\n const integrationNames: string[] = [];\n const conflicts: SkillConflict[] = [];\n // Apply-to-all memory keyed by artifact CLASS. Skill emission shares one\n // decision across the run (a `noir sync` touching 8 skill files → 1 prompt)\n // when the resolver returns `{resolution, applyToAll: true}`.\n const memory = new Map<string, SkillConflictResolution>();\n const policy: 'overwrite' | 'preserve' = opts.conflictPolicy ?? 'overwrite';\n // Non-interactive guard. The bridge `NOIR_NON_INTERACTIVE` is set by the\n // bin's preAction under --json/--no-input; the explicit `interactive: false`\n // wins over the env (mirrors @noir-ai/create's hermetic flag). When false,\n // the resolver is NEVER consulted — differing files fall back to `policy`.\n const interactive =\n opts.interactive ??\n (process.env.NOIR_NON_INTERACTIVE === undefined || process.env.NOIR_NON_INTERACTIVE === '');\n\n /** Write a compiled skill file through the SAME conflict seam as\n * `regenerate`: read existing; if differs, consult the resolver (when\n * interactive + wired); record the conflict for the structured report.\n *\n * Returns whether the compiled bytes actually reached a Noir-owned path.\n * `'preserved'` means the differing copy on disk stands and nothing was\n * written for it — the caller may not count that file as refreshed. */\n const writeWithConflict = async (\n absDest: string,\n relDest: string,\n content: string,\n isReference: boolean,\n ): Promise<'written' | 'preserved'> => {\n let existing: string | undefined;\n try {\n existing = await readFile(absDest, 'utf8');\n } catch {\n existing = undefined;\n }\n if (existing !== undefined && existing !== content) {\n const MODE = 'skill' as const;\n let resolution: SkillConflictResolution;\n const remembered = memory.get(MODE);\n if (remembered !== undefined) {\n resolution = remembered;\n } else if (interactive && opts.onConflict !== undefined) {\n const ctx: SkillConflictContext = {\n relPath: relDest,\n existing,\n proposed: content,\n mode: MODE,\n };\n const ret = await opts.onConflict(ctx);\n const unwrapped: SkillConflictResolution = typeof ret === 'string' ? ret : ret.resolution;\n if (typeof ret !== 'string' && ret.applyToAll === true) {\n memory.set(MODE, unwrapped);\n }\n resolution = unwrapped;\n } else {\n resolution = policy === 'preserve' ? 'preserve' : 'replace';\n }\n conflicts.push({\n path: relDest,\n mode: MODE,\n similarity: similarity(existing, content),\n existingSha: sha256Hex12(existing),\n proposedSha: sha256Hex12(content),\n resolution,\n });\n if (resolution === 'cancel') {\n throw new Error(`skill emit cancelled by user at ${relDest}`);\n }\n if (resolution === 'preserve') {\n // Skip this file; the user's bytes stand. Do not count as a reference.\n return 'preserved';\n }\n if (resolution === 'rename') {\n // Move the user's file aside (unique suffix), then write the compiled bytes.\n await renameAside(absDest, '.local');\n } else if (resolution === 'duplicate') {\n // Write the compiled bytes to <absDest>.noir; keep the user's file.\n // The compiled version is on disk (just not at the canonical path), so\n // this counts as written — the user explicitly asked to keep theirs.\n await writeFile(await uniqueAside(absDest, '.noir'), content, 'utf8');\n return 'written';\n }\n // 'replace' → fall through and overwrite.\n }\n await writeFile(absDest, content, 'utf8');\n if (isReference) references++;\n return 'written';\n };\n\n // Cursor skills land FLAT under targetDir (`.cursor/rules/<name>.mdc`) —\n // Cursor's rule loader scans `.cursor/rules/*.mdc` and does NOT recurse into\n // per-name subdirs. The verbatim branch (claude/agents-md/gemini/opencode)\n // keeps the canonical nested layout (`<name>/SKILL.md` + `<name>/references/`).\n const flat = target === 'cursor';\n // Partition by outcome, not by attempt: a skill lands in `emitted` only when\n // every one of its files is now Noir's bytes, and in `preserved` when at\n // least one file was left as the user had it. A skill is never in both, and\n // a half-refreshed skill is reported as preserved — under-reporting stale\n // bytes is the failure this split exists to prevent.\n for (const s of builtins) {\n const compiled = compileSkill(s, target);\n let sawPreserved = false;\n for (const f of compiled.files) {\n const dest = flat ? join(targetDir, ...f.path) : join(targetDir, s.name, ...f.path);\n await mkdir(dirname(dest), { recursive: true });\n const rel = flat ? f.path.join('/') : [s.name, ...f.path].join('/');\n const outcome = await writeWithConflict(dest, rel, f.content, f.path[0] !== 'SKILL.md');\n if (outcome === 'preserved') sawPreserved = true;\n }\n kept.push(s.name);\n if (sawPreserved) preserved.push(s.name);\n else emitted.push(s.name);\n }\n if (emitIntegrations) {\n for (const i of integrations) {\n const compiled = compileIntegration(i, target);\n let sawPreserved = false;\n for (const f of compiled.files) {\n const dest = flat ? join(targetDir, ...f.path) : join(targetDir, i.name, ...f.path);\n await mkdir(dirname(dest), { recursive: true });\n const rel = flat ? f.path.join('/') : [i.name, ...f.path].join('/');\n const outcome = await writeWithConflict(dest, rel, f.content, f.path[0] !== 'SKILL.md');\n if (outcome === 'preserved') sawPreserved = true;\n }\n // `hostMcp` is NOT written per-skill — it is surfaced to the host adapter\n // (via discoverAll/compileIntegration) to merge into the host's single\n // MCP config (e.g. `.mcp.json`). Wiring lives in cli/daemon.\n kept.push(i.name);\n if (sawPreserved) preserved.push(i.name);\n else emitted.push(i.name);\n integrationNames.push(i.name);\n }\n }\n\n // --- Prune stale `noir-*` entries from the managed namespace ----------\n // Idempotent hygiene: a previous Noir version may have shipped a builtin that\n // was since renamed/removed (e.g. `noir-old-thing`). Each `noir sync`\n // re-writes the CURRENT pack but a stale entry would otherwise linger forever.\n // After emit, scan `targetDir` for `noir-`-prefixed entries this run did not\n // account for and remove them. ONLY the `noir-` namespace — user skills\n // without the prefix are NEVER touched (they are not Noir's to manage).\n // \"Accounted for\" spans the whole pack, not just the written half: a skill\n // whose files were preserved still owns its directory.\n //\n // `assertNotUserOwned` guard: a `noir-*` entry whose content does NOT\n // match the canonical Noir-emitted shape (SKILL.md with `name: noir-…`\n // frontmatter, OR a `.mdc` with the cursor frontmatter) is treated as\n // USER-AUTHORED and LEFT ALONE. A user hand-rolling `noir-myown/SKILL.md`\n // must never be silently deleted by a sync.\n //\n // Shape awareness: the nested layout (claude/agents-md/gemini/opencode)\n // writes one `noir-<name>/` DIR per skill → prune stale DIRS. The cursor flat\n // layout writes one `noir-<name>.mdc` FILE per skill → prune stale .mdc FILES.\n // Cursor ALSO clears legacy `noir-<name>/` dirs (nesting residue) so an\n // upgrade from nested→flat does not leave orphans under `.cursor/rules/`.\n const keep = new Set(kept);\n const pruned: string[] = [];\n const preservedUserOwned: string[] = [];\n let dirEntries: import('node:fs').Dirent[] = [];\n try {\n dirEntries = await readdir(targetDir, { withFileTypes: true });\n } catch {\n dirEntries = []; // targetDir vanished between the mkdir above and now — nothing to prune.\n }\n for (const ent of dirEntries) {\n if (!ent.name.startsWith('noir-')) continue;\n if (flat) {\n // Cursor flat layout — prune stale FILES (`noir-<name>.mdc`) + legacy\n // nested DIRS (`noir-<name>/`) from a sync.\n if (ent.isFile()) {\n const mdcMatch = ent.name.match(/^(noir-[a-z0-9]+(?:-[a-z0-9]+)*)\\.mdc$/);\n const skillName = mdcMatch?.[1];\n if (skillName && keep.has(skillName)) continue;\n // `assertNotUserOwned`: a `.mdc` without canonical cursor\n // frontmatter (globs + alwaysApply) was hand-rolled by the user; skip.\n if (await isUserOwnedMdc(join(targetDir, ent.name))) {\n preservedUserOwned.push(ent.name);\n continue;\n }\n } else if (ent.isDirectory()) {\n // Legacy cursor nested dir — clear unconditionally (the flat\n // layout has NO `noir-{name}/` dirs under .cursor/rules/; any such dir\n // is stale by definition, regardless of name overlap with the current\n // pack — the fresh `.mdc` file is what's kept, not the dir).\n // Guard: skip if NONE of the `.mdc` files inside look Noir-emitted\n // (canonical cursor frontmatter) — a user hand-rolling a noir-X/ dir\n // with arbitrary content is preserved.\n if (await isUserOwnedCursorLegacyDir(join(targetDir, ent.name))) {\n preservedUserOwned.push(ent.name);\n continue;\n }\n } else {\n continue;\n }\n } else {\n // Nested layout — prune stale DIRS only (the canonical shape).\n if (!ent.isDirectory()) continue;\n if (keep.has(ent.name)) continue;\n // `assertNotUserOwned`: a `noir-*/` dir whose SKILL.md does not\n // carry canonical Noir frontmatter (`name: noir-…`) is user-authored.\n if (await isUserOwnedSkillDir(join(targetDir, ent.name))) {\n preservedUserOwned.push(ent.name);\n continue;\n }\n }\n // Best-effort removal: a failure to remove a stale entry must NOT fail the\n // emit (the fresh skills are already on disk and valid). Surface it via the\n // summary so a caller can warn; the next sync will try again.\n try {\n await rm(join(targetDir, ent.name), { recursive: true, force: true });\n pruned.push(ent.name);\n } catch {\n // Swallow — stale-entry pruning is hygienic, not correctness-critical.\n }\n }\n\n return {\n dir: targetDir,\n emitted,\n references,\n integrations: integrationNames,\n preserved,\n pruned,\n ...(preservedUserOwned.length > 0 ? { preservedUserOwned } : {}),\n ...(conflicts.length > 0 ? { conflicts } : {}),\n };\n}\n\n/** `assertNotUserOwned` for the nested layout: a `noir-{name}/` dir is\n * Noir-managed iff it contains a SKILL.md whose YAML frontmatter carries a\n * `name:` matching `noir-<dir>`. Missing SKILL.md, missing frontmatter, or a\n * non-matching name ⇒ user-authored (left alone). Best-effort: any IO error\n * ⇒ treat as user-owned (the safe default — never silently delete). */\nasync function isUserOwnedSkillDir(dirAbs: string): Promise<boolean> {\n try {\n const skillMd = await readFile(join(dirAbs, 'SKILL.md'), 'utf8');\n const m = skillMd.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/);\n if (!m) return true; // no frontmatter at all → user-authored\n const fm = parseYaml(m[1] ?? '') as { name?: unknown };\n const dirName = basename(dirAbs);\n return typeof fm?.name !== 'string' || fm.name !== dirName;\n } catch {\n return true; // ENOENT (no SKILL.md) or parse error → user-authored (safe)\n }\n}\n\n/** `assertNotUserOwned` for the cursor flat layout: a `noir-*.mdc` file\n * is Noir-managed iff its YAML frontmatter carries `alwaysApply:` (the cursor\n * shape the compiler always emits). Missing frontmatter / no `alwaysApply` ⇒\n * user-authored. */\nasync function isUserOwnedMdc(fileAbs: string): Promise<boolean> {\n try {\n const md = await readFile(fileAbs, 'utf8');\n const m = md.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/);\n if (!m) return true;\n const fm = parseYaml(m[1] ?? '') as { alwaysApply?: unknown };\n return fm?.alwaysApply === undefined;\n } catch {\n return true;\n }\n}\n\n/** `assertNotUserOwned` for the cursor flat-layout's legacy-nested-dir\n * case: a Noir sync left `noir-{name}/noir-{name}.mdc` behind. The dir\n * is Noir-managed iff at least one `.mdc` inside carries canonical cursor\n * frontmatter (`alwaysApply:`). Absent / hand-rolled content ⇒ user-authored.\n * Empty dir ⇒ treat as user-owned (safe — never silently delete). */\nasync function isUserOwnedCursorLegacyDir(dirAbs: string): Promise<boolean> {\n try {\n const entries = await readdir(dirAbs);\n if (entries.length === 0) return true;\n let sawCanonical = false;\n for (const name of entries) {\n if (!name.endsWith('.mdc')) continue;\n if (!(await isUserOwnedMdc(join(dirAbs, name)))) {\n sawCanonical = true;\n break;\n }\n }\n return !sawCanonical;\n } catch {\n return true; // ENOENT or unreadable → safe default (preserve)\n }\n}\n\n/** LCS similarity in [0,1]. Local copy (skills has no create dependency)\n * so the structured report's `similarity` matches the engine's algorithm. */\nfunction similarity(a: string, b: string): number {\n if (a === b) return 1;\n const A = a.split('\\n');\n const B = b.split('\\n');\n if (A.length === 0 && B.length === 0) return 1;\n const n = A.length;\n const m = B.length;\n const dp: number[][] = Array.from({ length: n + 1 }, () => new Array<number>(m + 1).fill(0));\n for (let i = n - 1; i >= 0; i--) {\n const dpi = dp[i] ?? [];\n const dpi1 = dp[i + 1] ?? [];\n const ai = A[i] ?? '';\n for (let j = m - 1; j >= 0; j--) {\n const bj = B[j] ?? '';\n dpi[j] = ai === bj ? (dpi1[j + 1] ?? 0) + 1 : Math.max(dpi1[j] ?? 0, dpi[j + 1] ?? 0);\n }\n }\n const lcs = dp[0]?.[0] ?? 0;\n return n + m === 0 ? 1 : (2 * lcs) / (n + m);\n}\n\n/** Pick a fresh `<abs><suffix>` aside path that does NOT exist (mirrors\n * @noir-ai/create's `uniqueAside`). Used by the `rename`/`duplicate` resolutions\n * so the user's bytes never get silently clobbered. */\nasync function renameAside(abs: string, suffix: string): Promise<void> {\n const { rename } = await import('node:fs/promises');\n const dest = await uniqueAside(abs, suffix);\n await rename(abs, dest);\n}\n\nasync function uniqueAside(abs: string, suffix: string): Promise<string> {\n return uniqueAsideSync(abs, suffix);\n}\n\n// Convenience for callers/tests that already hold raw markdown (unused by emit path;\n// kept so adapters/tests can validate a single in-memory skill without a dir).\nexport { discoverAll, discoverBuiltin };\n","import { readdirSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { parseFrontmatter } from './compiler.js';\nimport { parseIntegration } from './integrations-schema.js';\nimport type { BuiltinSkill, IntegrationDeclaration, IntegrationSkill } from './types.js';\n\n// Package root = ONE level up from THIS module's directory. Works in every layout\n// because `builtin/` is always a sibling of the dir this code runs from:\n// - vitest: .../packages/skills/src/discover.ts -> HERE = src -> PKG_ROOT = package root\n// - built: .../packages/skills/dist/index.js -> HERE = dist -> PKG_ROOT = package root\n// (tsup bundles everything into dist/index.js; `dirname` still yields `dist`.)\nconst HERE = dirname(fileURLToPath(import.meta.url)); // .../src OR .../dist\nconst PKG_ROOT = dirname(HERE); // .../packages/skills\nexport const BUILTIN_DIR = join(PKG_ROOT, 'builtin');\nexport const INTEGRATIONS_DIR = join(PKG_ROOT, 'integrations');\n\n/** Read a `<name>/SKILL.md` + its optional `references/` directory. Shared by\n * builtin + integration discovery (both share the SKILL.md + references shape;\n * the difference is the `integration.json` declaration). */\nfunction readSkillMd(dir: string): Pick<BuiltinSkill, 'skillMd' | 'frontmatter' | 'references'> {\n const skillMd = readFileSync(join(dir, 'SKILL.md'), 'utf8');\n const frontmatter = parseFrontmatter(skillMd);\n const refsDir = join(dir, 'references');\n let references: BuiltinSkill['references'] = [];\n try {\n references = readdirSync(refsDir)\n .filter((f) => f.endsWith('.md'))\n .sort()\n .map((f) => ({ name: f, content: readFileSync(join(refsDir, f), 'utf8') }));\n } catch {\n references = []; // no references/ dir is fine\n }\n return { skillMd, frontmatter, references };\n}\n\nexport function discoverBuiltin(builtinDir: string = BUILTIN_DIR): BuiltinSkill[] {\n const dirs = readdirSync(builtinDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name.startsWith('noir-'))\n .map((e) => e.name)\n .sort();\n return dirs.map((name) => {\n const dir = join(builtinDir, name);\n return { name, dir, ...readSkillMd(dir) };\n });\n}\n\n/**\n * Discover every shipped integration under `integrations/<name>/` (sibling of\n * `builtin/`). Each entry is a builtin-shaped skill (validated `SKILL.md` +\n * optional references) PLUS a parsed + validated `integration.json`\n * declaration. The dir is resolved relative to the package root by default —\n * when `integrationsDir` is overridden (tests), only that dir is scanned.\n *\n * Throws an aggregate error if any integration is malformed (missing\n * `SKILL.md`, bad frontmatter, or an `integration.json` that fails the Zod\n * schema) — fail-fast, atomic-ish, mirroring the builtin compiler's posture.\n */\nexport function discoverIntegrations(\n integrationsDir: string = INTEGRATIONS_DIR,\n): IntegrationSkill[] {\n let entries: string[];\n try {\n entries = readdirSync(integrationsDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name.startsWith('noir-'))\n .map((e) => e.name)\n .sort();\n } catch {\n // No integrations/ dir (or unreadable) = no integrations shipped here.\n return [];\n }\n return entries.map((name) => {\n const dir = join(integrationsDir, name);\n const declarationRaw = JSON.parse(\n readFileSync(join(dir, 'integration.json'), 'utf8'),\n ) as unknown;\n let declaration: IntegrationDeclaration;\n try {\n declaration = parseIntegration(declarationRaw);\n } catch (err) {\n // ZodError → wrap with the integration's dir for an actionable message.\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Invalid integration.json in ${dir}: ${msg}`);\n }\n if (declaration.name !== name) {\n throw new Error(\n `Integration dir \"${name}\" must equal declaration name \"${declaration.name}\"`,\n );\n }\n return { name, dir, ...readSkillMd(dir), declaration, declarationRaw };\n });\n}\n\n/**\n * Discover the full shipped pack — builtins + integrations — in one call. Used\n * by `emitSkillsToDir` so `noir init`/`sync` emit BOTH. The\n * `integrationsDir` defaults to the sibling of `builtinDir` so a test fixture\n * that overrides only `builtinDir` cleanly resolves integrations to a\n * non-existent path (returns `[]`) instead of accidentally picking up the\n * shipped integrations.\n */\nexport function discoverAll(opts: { builtinDir?: string; integrationsDir?: string } = {}): {\n builtins: BuiltinSkill[];\n integrations: IntegrationSkill[];\n} {\n const builtinDir = opts.builtinDir ?? BUILTIN_DIR;\n const integrationsDir = opts.integrationsDir ?? join(dirname(builtinDir), 'integrations');\n return {\n builtins: discoverBuiltin(builtinDir),\n integrations: discoverIntegrations(integrationsDir),\n };\n}\n","// `integration.json` schema. Each integration ships a declaration\n// alongside its `SKILL.md` so the compiler can validate the auth/runtime/SDD\n// contract uniformly and so the host adapter knows whether to widen emission\n// (host MCP config) or stay skill-only.\n//\n// Doctrine: skill-only by default;\n// `gated-write-proxy` for stateless writes routed through a Noir MCP tool\n// `mcp-stdio` for a Noir-spawned stateful runtime (deferred, gated on\n// keychain); `external-mcp` for a config-only pointer at a first-party/community\n// MCP server (emits a host `.mcp.json` server entry, no Noir runtime).\n//\n// Zod v4 (`z.record(keyType, valueType)` is key-first). The schema is additive\n// and defaults to the safest tier — every optional field degrades to \"skill\n// only, manual paste, no host MCP wiring\" — so a minimal `{ \"name\":\n// \"noir-x\" }` declaration still parses + behaves as a pure playbook.\n\nimport * as z from 'zod';\n\n/** Auth shape. Locked: `env-var` only until keychain lands (refuse OAuth,\n * never silently lower the security bar). `fallback:'manual-paste'` keeps the\n * no-token path honest (the playbook tells the user to paste a value); `'none'`\n * is for integrations that genuinely do not need a token (read-only public\n * endpoints). */\nexport const IntegrationAuthSchema = z.object({\n type: z.literal('env-var'),\n tokenEnv: z.string().min(1),\n fallback: z.enum(['manual-paste', 'none']).default('manual-paste'),\n});\n\n/** SDD two-way binding. `intakeFrom` declares the external artifact kind the\n * `noir-brainstorming` skill pulls from (which absorbed noir-intake);\n * `writeBack` enumerates the fields `noir-wrap` pushes back at session end\n * (which absorbed noir-document). Strings (not enums) for `writeBack` so a\n * per-integration vocabulary stays expressible without\n * churning the schema. */\nexport const IntegrationSddSchema = z\n .object({\n intakeFrom: z.enum(['task', 'issue', 'none']).optional(),\n writeBack: z.array(z.string()).default([]),\n })\n .default({ writeBack: [] });\n\n/** Host MCP declaration. Only meaningful when `runtime ∈ {mcp-stdio,\n * external-mcp}`; `null` for `none`/`gated-write-proxy` (ClickUp — writes go\n * through Noir's own MCP tool, never the host's). */\nexport const IntegrationMcpSchema = z\n .object({\n command: z.string().min(1),\n transport: z.enum(['stdio', 'http']),\n args: z.array(z.string()).optional(),\n url: z.string().optional(),\n env: z.record(z.string(), z.string()).optional(),\n })\n .nullable()\n .default(null);\n\n/** Full `integration.json` declaration. `name` enforces the `noir-` prefix\n * shared with builtins so the unified pack reads consistently. */\nexport const IntegrationDeclarationSchema = z.object({\n name: z.string().regex(/^noir-[a-z0-9]+(?:-[a-z0-9]+)*$/, 'must match noir-<kebab>'),\n auth: IntegrationAuthSchema,\n runtime: z.enum(['none', 'gated-write-proxy', 'mcp-stdio', 'external-mcp']),\n sdd: IntegrationSddSchema,\n mcp: IntegrationMcpSchema,\n});\n\nexport type IntegrationDeclaration = z.infer<typeof IntegrationDeclarationSchema>;\n\n/** Coerce + validate an `integration.json` payload. Throws a ZodError on a\n * malformed declaration — the caller (discover) wraps it with the\n * integration's dir for an actionable message. */\nexport function parseIntegration(json: unknown): IntegrationDeclaration {\n return IntegrationDeclarationSchema.parse(json);\n}\n\n/** Non-throwing variant for the hygiene tests + lint helpers. */\nexport function validateIntegration(\n obj: unknown,\n): { ok: true; value: IntegrationDeclaration } | { ok: false; errors: string[] } {\n const parsed = IntegrationDeclarationSchema.safeParse(obj);\n if (parsed.success) return { ok: true, value: parsed.data };\n return { ok: false, errors: parsed.error.issues.map((i: { message: string }) => i.message) };\n}\n\n/** Runtime → host-MCP emission predicate. The compiler widens emission (makes\n * the host MCP block available to the adapter) ONLY for these tiers. The\n * adapter ultimately decides per-host what to render — for Claude, `external-\n * mcp` becomes a `.mcp.json` server entry; `mcp-stdio` registers through the\n * existing `noir mcp serve --stdio` entry so no NEW server entry is emitted. */\nexport function runtimeEmitsHostMcp(runtime: IntegrationDeclaration['runtime']): boolean {\n return runtime === 'mcp-stdio' || runtime === 'external-mcp';\n}\n","// Structural quality checks for the builtin skill pack — the artifact\n// quality gate.\n//\n// Split from compiler.ts so `validateSkill` (hard errors) and `lintSkill` (soft\n// warnings) share one source of truth for the body-structure rules, and so the\n// rules are unit-testable in isolation. Canonical template:\n//\n// Overview → When to use → Procedure → Verification → Notes\n//\n// Rules here implement the researched canon (Anthropic best practices, the\n// agentskills.io spec, Claude Code docs): required sections, a <500-line body\n// budget, one-level-deep references (no chained refs), and a WHAT+WHEN\n// description. `checkRequiredSections` is the load-bearing check — it's what\n// forces every SKILL.md to carry a real playbook shape, not a shell.\n\nimport { ARTIFACT_TYPES } from '@noir-ai/core';\nimport { checkHygiene, HYGIENE_EXEMPT_MARKERS, type HygieneFinding } from './hygiene.js';\nimport type { BuiltinSkill } from './types.js';\n\n/** The SKILL.md body — the markdown after the YAML frontmatter block. The\n * frontmatter is metadata the host reads on its own; the body is the playbook\n * a reader loads. The validator and the two body-structure lint rules both\n * measure this string, so it lives here (single source of truth) rather than\n * in compiler.ts. */\nexport function bodyOf(md: string): string {\n return md.replace(/^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n?/, '');\n}\n\n/** The body with the blank line that separates it from the frontmatter removed\n * — the text the author wrote as the body, which is what a finding's line\n * number has to count in. `bodyOf` keeps that separator line, so numbering\n * findings against its raw output reports every one of them a line past the\n * line the author sees in the file. Only a leading blank line is dropped, and\n * only one, so the rest of the body keeps the numbering it is written with. */\nfunction authoredBody(body: string): string {\n if (body.startsWith('\\r\\n')) return body.slice(2);\n if (body.startsWith('\\n')) return body.slice(1);\n return body;\n}\n\n/** The max body length the canon recommends (Anthropic: \"under 500 lines\").\n * SKILL.md is a navigator, not a repository — split to references/ past this. */\nexport const MAX_BODY_LINES = 500;\n\n/** The floor for a full (non-stub) playbook body. Stubs were ~13 lines; a real\n * playbook needs at least this much substance to be loadable as guidance. */\nexport const MIN_FULL_BODY_LINES = 20;\n\n/** The canonical heading a skill MUST carry to describe its trigger conditions.\n * Accepts the canonical template spelling AND common variants so existing skills\n * aren't forced into a single casing. */\nconst WHEN_SECTION = /^## When to use$/im;\n\n/** The canonical heading a skill MUST carry for its numbered workflow. `## Steps`\n * is accepted for backward-compat with noir-wrap; `## Procedure` is canonical. */\nconst PROCEDURE_SECTION = /^## (Procedure|Steps)$/im;\n\n/** One of these closing sections must exist — evidence/notes/degradation. */\nconst CLOSING_SECTION = /^## (Verification|Notes|Fallbacks|Troubleshooting|Why order matters)$/im;\n\n/** The lead cue regex — the FIRST part of the description must be a trigger. */\nconst WHEN_CUE =\n /^(use|using|used|whenever|when|before|after|while|starting|encountering|completing|creating|about to|upon|during|to|for|on)\\b/i;\n\n/** A chained reference link — a reference file that links to ANOTHER reference\n * file (`references/a.md` → `references/b.md` or `../references/b.md`). The\n * canon forbids deep nesting (agentskills.io: \"no chained references\"). */\nconst CHAINED_REF_RE = /\\]\\((\\/?\\.?\\.?\\/)?(references\\/|\\.\\.\\/references\\/)/i;\n\n/**\n * Which required sections are missing from a skill body. Returns a list of\n * human-readable section names, e.g. `['## When to use', '## Verification']`.\n * Empty array = the body carries the canonical playbook shape.\n */\nexport function missingSections(body: string): string[] {\n const missing: string[] = [];\n if (!WHEN_SECTION.test(body)) missing.push('## When to use');\n if (!PROCEDURE_SECTION.test(body)) missing.push('## Procedure (or ## Steps)');\n if (!CLOSING_SECTION.test(body)) {\n missing.push('one of ## Verification / ## Notes / ## Fallbacks / ## Troubleshooting');\n }\n return missing;\n}\n\n/** True when the body is within the canon line budget. */\nexport function withinLineBudget(body: string, max: number = MAX_BODY_LINES): boolean {\n return body.split('\\n').length <= max;\n}\n\n/** Reference files that point at another reference file (chained — forbidden).\n * Returns the offending reference names. */\nexport function chainedReferences(skill: BuiltinSkill): string[] {\n return skill.references.filter((r) => CHAINED_REF_RE.test(r.content)).map((r) => r.name);\n}\n\n/**\n * True when the description carries BOTH a WHEN trigger lead AND a WHAT clause.\n * The rule: `description` MUST lead with a WHEN cue (existing compiler rule)\n * AND contain a compact WHAT clause naming what the skill does.\n *\n * Two canonical shapes both pass:\n * \"Use when turning an idea into a spec — draft the spec.\" (WHEN → WHAT)\n * \"Use when a task writes back to ClickUp: update its status.\" (WHEN → WHAT)\n * The description MUST lead with a cue (the WHEN-only and WHAT-only schools are\n * both rejected). The WHAT clause is the part after the trigger phrase — split\n * on an em/en dash or a period that ends the trigger phrase. A real WHAT clause\n * has ≥3 words naming the action, so a bare \"Use when…\" fails.\n */\nexport function isWhatWhenDescription(description: string): boolean {\n const trimmed = description.trim();\n if (!trimmed) return false;\n if (!WHEN_CUE.test(trimmed)) return false; // must lead with a cue\n // Split off the trigger phrase: after an em/en dash, or after the first period.\n // \"Use when X — draft the spec.\" → whatPart = \"draft the spec.\"\n const whatPart = trimmed\n .split(/[—–]|(?<=\\.) /)\n .slice(1)\n .join(' ')\n .trim();\n return whatPart.split(/\\s+/).filter(Boolean).length >= 3;\n}\n\n/** True when the description leads with a WHEN cue (the existing rule, kept\n * here so validate + lint + hygiene share one implementation). */\nexport function looksLikeWhenDescription(description: string): boolean {\n return WHEN_CUE.test(description.trim());\n}\n\n/**\n * Soft-quality warnings for `lintSkill`. Each returns a short rule id + message.\n * These are advisory — a skill can pass `validateSkill` and still carry lint\n * warnings that the author should resolve. Rules are drawn from the researched\n * anti-pattern list (no examples, thin body, first/second person narration,\n * voodoo constants, time-sensitive version pins).\n */\nexport function lintWarnings(skill: BuiltinSkill): string[] {\n const warnings: string[] = [];\n // The two body rules below measure the body, not the file: a long description\n // (or any other frontmatter line) must not make a short body look substantial,\n // and a cue in the description must not stand in for a worked example. The\n // prose checks after them read the whole file, so a first-person phrase or a\n // stale version pin is surfaced wherever it sits.\n const file = skill.skillMd;\n const body = bodyOf(file);\n const bodyLines = body.split('\\n').length;\n\n if (bodyLines < MIN_FULL_BODY_LINES) {\n warnings.push('thin-body: full playbook body is under 20 lines');\n }\n // Concrete examples: at least one fenced block or an \"example:\" mention in\n // the body.\n const hasExample = /```/.test(body) || /\\bexample:?\\b/i.test(body) || /\\be\\.g\\.\\b/i.test(body);\n if (!hasExample) {\n warnings.push('no-example: no concrete code fence or worked example in the body');\n }\n // First/second-person narration — \"I/we/you\" as the agent doing work.\n if (\n /\\b(I|we|you)\\s+(will|should|can|need|must|do|write|create|implement|run|build)\\b/i.test(file)\n ) {\n warnings.push('first-person: narration addresses the reader instead of imperative steps');\n }\n // Voodoo constants: a bare number with a magnitude claim but no justification\n // (\"wait 5 seconds\" without why). Very loose — catches \"wait N seconds\" without\n // a \"why\".\n if (\n /\\b(?:wait|sleep|retry|backoff|limit|cap)\\s+[a-z]*\\s*(\\d{1,4})\\b/i.test(file) &&\n !/because|to (avoid|prevent|give|let)/i.test(file)\n ) {\n warnings.push('voodoo-constant: numeric threshold without a stated reason');\n }\n // Time-sensitive version pins outside a Legacy section.\n const hasVersionPin = /as of \\d{4}|\\bversion \\d+\\.\\d+\\.\\d+\\b|\"v\\d+\\.\\d+\"/i.test(file);\n const hasLegacySection = /^## Legacy|^## Old patterns/i.test(file);\n if (hasVersionPin && !hasLegacySection) {\n warnings.push('time-sensitive: version/date pin outside a Legacy section');\n }\n return warnings;\n}\n\n/** `.noir/` subdirectories a skill may legitimately reference that are NOT\n * per-type artifact dirs (store db, audit export, rules, daemon state). */\nconst NON_ARTIFACT_DIRS = new Set(['store', 'audit', 'rules', 'state']);\n\n/** Canonical artifact directory → its type code(s) (from the artifact registry).\n * One directory can host multiple kinds (`subagents/` → BR + RP). */\nconst DIR_TO_CODES: ReadonlyMap<string, ReadonlySet<string>> = (() => {\n const m = new Map<string, Set<string>>();\n for (const t of Object.values(ARTIFACT_TYPES)) {\n const codes = m.get(t.dir);\n if (codes) codes.add(t.code);\n else m.set(t.dir, new Set([t.code]));\n }\n return m;\n})();\n\n/**\n * `.noir/…` output-path drift: a skill body or reference that names a `.noir/`\n * directory absent from the artifact registry (e.g. `.noir/sdd/`), or a file\n * under a canonical artifact directory whose name carries none of that\n * directory's type codes (e.g. `.noir/plans/<date>-<slug>.md` instead of\n * `PL-<NNNN>-…`). Hard errors — the host would mint non-standard artifacts.\n * See `docs/reference/artifact-format.md`.\n */\nexport function artifactPathDrift(skill: BuiltinSkill): string[] {\n const texts = [skill.skillMd, ...skill.references.map((r) => r.content)];\n const drifts = new Set<string>();\n // `.noir/<dir>/<name>` — name runs to whitespace/backtick/quote/paren so\n // `<date>-<slug>.md` placeholders are captured whole.\n const re = /\\.noir\\/([a-zA-Z0-9-]+)\\/([^\\s`'\"()]+)/g;\n for (const text of texts) {\n for (const m of text.matchAll(re)) {\n const dir = m[1];\n const name = m[2];\n if (!dir || !name) continue;\n const codes = DIR_TO_CODES.get(dir);\n if (codes === undefined) {\n if (!NON_ARTIFACT_DIRS.has(dir)) {\n drifts.add(\n `.noir/${dir}/ is not a canonical artifact directory (see docs/reference/artifact-format.md)`,\n );\n }\n continue;\n }\n const expected = [...codes].join('/');\n if (![...codes].some((c) => name.startsWith(`${c}-`))) {\n drifts.add(\n `.noir/${dir}/${name} must be ${expected}-<NNNN>-… (see docs/reference/artifact-format.md)`,\n );\n }\n }\n }\n return [...drifts];\n}\n\n// Output hygiene (the rules live in hygiene.ts).\n//\n// A SKILL.md body is a markdown document that also carries fenced code blocks,\n// so each part is checked with the kind of text it is: the prose as markdown,\n// the fenced blocks as source. Checking the whole body as one kind misfires in\n// both directions — a source rule reads markdown bold (`**text**`) as a block\n// comment, and a prose rule reads a `//` line inside a fence as a heading.\n// The fail tier blocks emission and the warn tier is advisory; which findings\n// go to which list is `validateSkill`'s decision, not this module's.\n\n/** A body split into the two kinds of text it holds: the prose, and the fenced\n * code blocks. Blanking the other side's lines, rather than dropping them,\n * keeps every finding on the line number it has in the body.\n *\n * Fences follow the CommonMark rule: a line opening with a run of at least\n * three backticks or tildes opens a block, and only a run of the same\n * character at least as long closes it — a shorter run of the same character\n * (or the other character) inside the block is content, so a block that\n * documents a fenced example stays one block. An unclosed fence runs to the\n * end of the body, which is where CommonMark renders its content too. */\nfunction splitFencedBlocks(body: string): { prose: string; code: string } {\n const prose: string[] = [];\n const code: string[] = [];\n // The closing fence of the block currently open: same character, at least the\n // opening run's length, with only whitespace after it. Null when not in a\n // fence.\n let closing: RegExp | null = null;\n for (const line of body.split('\\n')) {\n let boundary = false;\n if (closing === null) {\n const open = line.match(/^[ \\t]{0,3}(`{3,}|~{3,})/);\n const run = open?.[1];\n if (run) {\n const char = run[0] ?? '';\n closing = new RegExp(`^[ \\\\t]{0,3}${char}{${run.length},}[ \\\\t]*$`);\n boundary = true;\n }\n } else if (closing.test(line)) {\n closing = null;\n boundary = true;\n }\n // The fence markers themselves are document syntax; the lines between them\n // are source.\n const isCode = closing !== null && !boundary;\n prose.push(isCode ? '' : line);\n code.push(isCode ? line : '');\n }\n return { prose: prose.join('\\n'), code: code.join('\\n') };\n}\n\n/** The 1-based line the body's exemption marker sits on, or 0 when it carries\n * none. A SKILL.md body states its own exemption with the markdown marker. */\nfunction exemptionLine(body: string): number {\n const marker = HYGIENE_EXEMPT_MARKERS.markdown;\n const lines = body.split('\\n');\n for (let i = 0; i < lines.length; i++) {\n if ((lines[i] ?? '').trim() === marker) return i + 1;\n }\n return 0;\n}\n\n/** Every hygiene finding a skill body produces, in reading order.\n *\n * The lines are counted in the authored body (see {@link authoredBody}), so a\n * finding names the line a reader opening the SKILL.md finds the text on: the\n * number is frontmatter-exclusive and starts at the body's own first line.\n *\n * A body that carries the exemption marker above its first finding is exempt\n * in both kinds. The marker is a statement about the file, and a body is one\n * file even though it is read as prose and as source — a fenced code block\n * cannot carry the document's marker, so without this the code rules would\n * keep firing inside a body that declared itself exempt. */\nexport function hygieneFindings(body: string): HygieneFinding[] {\n const text = authoredBody(body);\n const { prose, code } = splitFencedBlocks(text);\n const findings = [...checkHygiene(prose, 'markdown'), ...checkHygiene(code, 'code')];\n const seen = new Set<string>();\n const ordered = findings\n .filter((f) => {\n const key = `${f.id}:${f.line}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n })\n .sort((a, b) => a.line - b.line);\n const exemptAt = exemptionLine(text);\n if (exemptAt !== 0 && exemptAt < (ordered[0]?.line ?? Number.POSITIVE_INFINITY)) return [];\n return ordered;\n}\n\n/** One hygiene finding as a gate message: the rule that fired, the line it\n * fired on, why that line is noise, and what to write instead. */\nexport function hygieneMessage(finding: HygieneFinding): string {\n return `${finding.id} (line ${finding.line}): ${finding.rationale} ${finding.fix}`;\n}\n","import type { HygieneRule } from './hygiene.js';\n\n// A `noir-debt:` comment must name a known ceiling AND an upgrade trigger\n// (when/if/once), otherwise the deliberate shortcut is untrackable. The word\n// \"ceiling\" alone is not a trigger: it names the limit, not the condition that\n// justifies paying for the upgrade.\nexport const DEBT_MARKER_RULE: HygieneRule = {\n id: 'noir-debt',\n tier: 'warn',\n appliesTo: 'code',\n pattern: /(?:^[ \\t]*|[ \\t])(?:\\/\\/|#|\\/\\*|\\*)[ \\t]*noir-debt:(?!.*\\b(?:when|if|once)\\b)/m,\n rationale: 'a debt marker without a ceiling or upgrade trigger is an un-trackable shortcut',\n fix: 'name the ceiling and the condition that justifies the upgrade',\n};\n","// Tokens that must NOT survive a port (predecessor plugin internals + upstream\n// rhetoric), each with the reason it is forbidden. The list is projected into\n// fail-tier hygiene rules, so a token is enforced by the gate and not only by a\n// test assertion.\n//\n// This file is the token table, so it is exempt from the rules it feeds: the\n// tokens must appear here to be forbidden anywhere.\n// noir-hygiene: exempt\n\nimport type { HygieneRule } from './hygiene.js';\n\ninterface ResidueToken {\n /** The literal that must not appear in a shipped artifact. */\n token: string;\n /** Why it is forbidden, in a reader's terms. */\n why: string;\n}\n\nconst RESIDUE: readonly ResidueToken[] = [\n {\n token: 'workflow/<task',\n why: 'Names a state file of the removed workflow plugin, which nothing in this repository reads.',\n },\n {\n token: 'noir-workflow.mode',\n why: 'Names the mode flag of the removed workflow plugin, which nothing in this repository reads.',\n },\n {\n token: 'noir-workflow',\n why: 'Names the removed workflow plugin as a plugin or a path, so a reader following it finds nothing.',\n },\n {\n token: '@uiigateway',\n why: 'Names a package from the project this one was extracted from, which no consumer can resolve.',\n },\n // 'ClickUp'/'clickup' were forbidden while the predecessor plugin was being\n // ported, because the ClickUp REST precedent lived inside it. ClickUp now\n // ships as a first-class integration, so the token names a legitimate\n // subject and is deliberately absent from this list.\n {\n token: '<EXTREMELY-IMPORTANT',\n why: 'Copied rhetorical markup from the upstream tool, which carries no instruction a reader can follow.',\n },\n {\n token: 'SUBAGENT-STOP',\n why: 'Copied rhetorical markup from the upstream tool, which carries no instruction a reader can follow.',\n },\n {\n token: 'plugins/noir-workflow',\n why: 'Names the removed plugin path, which no longer exists in this repository.',\n },\n];\n\n/** The forbidden tokens, in the order they are declared. */\nexport const FORBIDDEN_RESIDUE: readonly string[] = RESIDUE.map((entry) => entry.token);\n\n/** One fail-tier rule per forbidden token. A token matches only when it stands\n * alone — neither side is a word character or a hyphen — so `noir-workflow`\n * does not fire inside a longer identifier such as `noir-workflow-engine-`,\n * which names this repository's own workflow engine rather than the removed\n * plugin. It still fires on `plugins/noir-workflow/` and `noir-workflow.mode`,\n * which name the plugin itself; a token that contains another\n * (`noir-workflow.mode`) is reported by both rules, so the narrower token is\n * never the only thing matching a line. */\nexport const RESIDUE_RULES: readonly HygieneRule[] = RESIDUE.map((entry) => ({\n id: `residue-${entry.token\n .replace(/[^a-z0-9]+/gi, '-')\n .replace(/^-|-$/g, '')\n .toLowerCase()}`,\n tier: 'fail' as const,\n pattern: new RegExp(`(?<![\\\\w-])${escapeForRegExp(entry.token)}(?![\\\\w-])`),\n rationale: entry.why,\n fix: 'Remove the reference. If it named a mechanism, name the mechanism that replaced it.',\n appliesTo: 'both' as const,\n}));\n\n/** Escapes a literal so it can be embedded in a pattern. */\nfunction escapeForRegExp(literal: string): string {\n return literal.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n","// The single source of truth for output hygiene: the patterns that make agent\n// output look machine-generated, each with the tier that decides whether it\n// blocks, the reason it is noise, and what to write instead. Consumers are the\n// skills quality gate, the repository doctor check and continuous integration.\n\n// Self-exemption convention: the rule source, the token table it projects and\n// the gate's own fixtures are exempt from the rules they define, because they\n// must be able to name what they forbid. Any such file carries the exemption\n// marker below, so no consumer needs a path list of its own. This file declares\n// the rules, so it carries the marker:\n// noir-hygiene: exempt\n\nimport { DEBT_MARKER_RULE } from './debt-marker.js';\nimport { RESIDUE_RULES } from './residue.js';\n\nexport type HygieneTier = 'fail' | 'warn';\n\n/** The two kinds of text a rule can be written for: source, or prose. */\nexport type HygieneKind = 'code' | 'markdown';\n\nexport interface HygieneRule {\n id: string;\n tier: HygieneTier;\n /** Anchored: a pattern that matches ordinary writing is the failure this\n * rule source exists to prevent, so every pattern below is reviewed against\n * plain prose and against string literals. */\n pattern: RegExp;\n rationale: string;\n fix: string;\n appliesTo: HygieneKind | 'both';\n}\n\nexport interface HygieneFinding {\n /** The id of the rule that matched, exactly as `HYGIENE_RULES` declares it. */\n id: string;\n tier: HygieneTier;\n /** 1-based number of the line the pattern matched. */\n line: number;\n /** The matched line, trimmed of surrounding whitespace. */\n text: string;\n rationale: string;\n fix: string;\n}\n\n/** The line that exempts a file from every rule. A file that must keep what the\n * rules flag — the rule source, the token table, a document that states the\n * ban — carries the line matching its kind near the top. */\nexport const HYGIENE_EXEMPT_MARKERS: Readonly<Record<HygieneKind, string>> = {\n code: '// noir-hygiene: exempt',\n markdown: '<!-- noir-hygiene: exempt -->',\n};\n\n/** A run of this many consecutive comment lines counts as a comment block. A\n * blank comment line is a paragraph break and a divider is reported on its\n * own, so neither one extends a block. */\nexport const MAX_COMMENT_BLOCK_LINES = 12;\n\n// A comment opener that sits at the start of a line or after whitespace.\n// Requiring that position is what keeps the rules below off ordinary writing:\n// the `//` of `https://example.com` follows a colon, so it opens no comment.\nconst MARKER = String.raw`(?:^[ \\t]*|[ \\t])(?:\\/\\/|#|\\/\\*|\\*)`;\n\n/** The emoji presentation selector. It is invisible in source, so it is named\n * here rather than written; it follows a character that also has a text\n * presentation, as the warning sign does. */\nconst VARIATION_SELECTOR = String.fromCharCode(0xfe0f);\n\n/** The pictograph block, where the icons that decorate generated text live. */\nconst PICTOGRAPH = String.raw`[\\u{1F300}-\\u{1FAFF}]`;\n\n// The decoration set: the icons that arrive as flourishes in generated text and\n// tell a reader nothing they can act on. They are unambiguous on their own, so\n// they are flagged wherever they appear in a comment.\nconst DECORATION_EMOJI = `[⚠✅🎉✨🔥🚀🧠💡🔧📌]${VARIATION_SELECTOR}?`;\n\n/** The backtick. It is named rather than written so the template literal that\n * builds the quote class below does not end early; see VARIATION_SELECTOR. */\nconst BACKTICK = String.fromCharCode(0x60);\n\n/** A quote or a backtick. A comment may legitimately quote the glyph it\n * describes, as the badge documentation quotes its own `⚠ warn` output, so a\n * glyph wrapped in one of these is not decoration. */\nconst QUOTE_OR_BACKTICK = `[${BACKTICK}'\"]`;\n\n/** The markers a reader is expected to act on later. */\nconst MARKER_WORDS = ['TODO', 'FIXME'];\n\n/** Words that give a marker a reason to exist, and so make it actionable. */\nconst REASON_WORDS = [\n 'because',\n 'since',\n 'until',\n 'once',\n 'when',\n 'unless',\n 'blocked',\n 'otherwise',\n 'revisit',\n];\n\n/** An owner for a marker: a handle, a bracketed name, or a tracked issue. */\nconst OWNER = String.raw`(?:@[A-Za-z0-9_-]|\\([A-Za-z][A-Za-z0-9_-]*\\)|#\\d+)`;\n\n/** A run of divider punctuation after a comment opener, which turns a label\n * into a banner: `==== Fetch users ====`. */\nconst DECORATIVE_BANNER: HygieneRule = {\n id: 'decorative-banner',\n tier: 'fail',\n pattern: new RegExp(String.raw`${MARKER}[ \\t]*[-=*_~]{4,}`, 'm'),\n rationale:\n '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.',\n fix: 'Delete the divider. If the label names a section a reader needs, make it a heading or name it in the declaration below.',\n appliesTo: 'both',\n};\n\n/** Narration that walks a reader through the code in the order it runs:\n * `Step 1: load the config`, `First, …`, `Next, …`, `Finally, …`. Two shapes\n * are exempt: an ordinal followed by a relative clause (\"first, which is all\n * the test needs\") describes a position rather than a procedure, and an\n * ordinal that goes on to give the reason (\"first, init so sync can read it\")\n * is the note this rule's own fix asks a reader to keep. */\nconst WORKFLOW_NARRATION: HygieneRule = {\n id: 'workflow-narration',\n tier: 'fail',\n pattern: new RegExp(\n 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))`,\n 'im',\n ),\n rationale:\n 'Numbered narration restates what the code already says in the order it already says it, and it goes stale the moment that order changes.',\n fix: 'Drop the step markers. Keep only what the code cannot show: why this order, which invariant is held, or what breaks otherwise.',\n appliesTo: 'code',\n};\n\n/** An emoji from the decoration set anywhere in a comment — the common flourish\n * in generated comments, opening the comment or trailing it. A glyph the\n * comment has put in quotes is being talked about rather than used, so it is\n * left alone; the trailing check allows for the presentation selector the\n * pattern may have consumed. */\nconst DECORATIVE_EMOJI_IN_COMMENT: HygieneRule = {\n id: 'decorative-emoji-comment',\n tier: 'fail',\n pattern: new RegExp(\n String.raw`${MARKER}[^\\n]*(?<!${QUOTE_OR_BACKTICK})${DECORATION_EMOJI}(?!${VARIATION_SELECTOR}?${QUOTE_OR_BACKTICK})`,\n 'mu',\n ),\n rationale:\n '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.',\n fix: 'Remove the emoji and let the sentence carry the emphasis. If it marked a state, name the state in a word.',\n appliesTo: 'code',\n};\n\n/** A pictograph that opens a comment, for the emoji outside the decoration\n * set: only that position is unambiguously decorative, since a comment may\n * legitimately quote a character it is describing. */\nconst DECORATIVE_PICTOGRAPH: HygieneRule = {\n id: 'decorative-pictograph-comment',\n tier: 'fail',\n pattern: new RegExp(\n String.raw`${MARKER}[ \\t]*(?!${DECORATION_EMOJI})${PICTOGRAPH}${VARIATION_SELECTOR}?`,\n 'mu',\n ),\n rationale:\n '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.',\n fix: 'Remove the pictograph. If it stood for a state or a result, write that in words.',\n appliesTo: 'code',\n};\n\n/** A document line that opens with a decorative emoji or pictograph, after any\n * heading or bullet marker. Only that opening position counts: an emoji inside\n * a sentence is usually something the sentence is talking about. */\nconst DECORATIVE_EMOJI_IN_PROSE: HygieneRule = {\n id: 'decorative-emoji-doc',\n tier: 'fail',\n pattern: new RegExp(\n String.raw`^[ \\t]{0,3}(?:#{1,6}[ \\t]+)?(?:[-*+][ \\t]+)?(?:${DECORATION_EMOJI}|${PICTOGRAPH}${VARIATION_SELECTOR}?)`,\n 'mu',\n ),\n rationale:\n '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.',\n fix: 'Remove the emoji and put the point of the line in its words.',\n appliesTo: 'markdown',\n};\n\n// The code point ranges that no file in this project has a reason to carry.\n// Each one is either a character from another writing system, or a code point\n// that draws nothing at all. Both arrive the same way: text generation can leak\n// a token from a foreign script into the output, and a mis-decoded byte becomes\n// a replacement character or an invisible formatting mark. They are listed as\n// ranges rather than as an allowlist so that ordinary writing in the project's\n// own language is never at risk of being flagged.\nconst IRREGULAR_SCRIPT_SOURCES = [\n String.raw`\\u{0370}-\\u{03ff}`, // Greek\n String.raw`\\u{1f00}-\\u{1fff}`, // Greek extended\n String.raw`\\u{0400}-\\u{052f}`, // Cyrillic and its supplement\n String.raw`\\u{2de0}-\\u{2dff}`, // Cyrillic extended A\n String.raw`\\u{a640}-\\u{a69f}`, // Cyrillic extended B\n String.raw`\\u{0590}-\\u{05ff}`, // Hebrew\n String.raw`\\u{0600}-\\u{06ff}`, // Arabic\n String.raw`\\u{0750}-\\u{077f}`, // Arabic supplement\n String.raw`\\u{fb1d}-\\u{fb4f}`, // Hebrew presentation forms\n String.raw`\\u{fb50}-\\u{fdff}`, // Arabic presentation forms A\n String.raw`\\u{fe70}-\\u{feff}`, // Arabic presentation forms B, and the byte-order mark\n String.raw`\\u{0900}-\\u{097f}`, // Devanagari\n String.raw`\\u{0e00}-\\u{0e7f}`, // Thai\n String.raw`\\u{1100}-\\u{11ff}`, // Hangul Jamo\n String.raw`\\u{2e80}-\\u{2fdf}`, // CJK radicals\n String.raw`\\u{3000}-\\u{30ff}`, // CJK symbols and punctuation, Hiragana, Katakana\n String.raw`\\u{3100}-\\u{318f}`, // Bopomofo, Hangul compatibility Jamo\n String.raw`\\u{31c0}-\\u{31ef}`, // CJK strokes and Katakana extensions\n String.raw`\\u{3200}-\\u{33ff}`, // Enclosed and compatibility CJK\n String.raw`\\u{3400}-\\u{4dbf}`, // CJK unified ideographs extension A\n String.raw`\\u{4e00}-\\u{9fff}`, // CJK unified ideographs\n String.raw`\\u{ac00}-\\u{d7af}`, // Hangul syllables\n String.raw`\\u{f900}-\\u{faff}`, // CJK compatibility ideographs\n String.raw`\\u{ff00}-\\u{ffef}`, // Fullwidth and halfwidth forms\n String.raw`\\u{200b}-\\u{200f}`, // Zero-width space, joiners and the bidirectional marks\n String.raw`\\u{202a}-\\u{202e}`, // Bidirectional embeddings and overrides\n String.raw`\\u{2066}-\\u{2069}`, // Bidirectional isolates\n String.raw`\\u{fffd}`, // the replacement character\n String.raw`\\u{e000}-\\u{f8ff}`, // Private use area\n String.raw`\\u{f0000}-\\u{ffffd}`, // Private use planes 15 and 16\n String.raw`\\u{100000}-\\u{10fffd}`, // Private use plane 17\n];\n\n/** A character from another writing system, or one that draws nothing. */\nconst IRREGULAR_SCRIPT = `[${IRREGULAR_SCRIPT_SOURCES.join('')}]`;\n\n/** A character from another writing system, or an invisible one, anywhere in a\n * line. The project writes in its own language, so a character from a foreign\n * script is a leak rather than a word, and an invisible code point (a\n * zero-width space, a byte-order mark, a replacement character) is a decoding\n * accident. A fixture that has to carry one — a test that measures how a wide\n * glyph is laid out, say — states its own exemption with the marker above\n * rather than teaching this rule which characters to allow. */\nconst IRREGULAR_SCRIPT_IN_TEXT: HygieneRule = {\n id: 'no-irregular-script',\n tier: 'fail',\n pattern: new RegExp(IRREGULAR_SCRIPT, 'u'),\n rationale:\n '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.',\n 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.',\n appliesTo: 'both',\n};\n\n/** Consecutive comment lines long enough to be narration rather than a note. */\nconst LONG_COMMENT_BLOCK: HygieneRule = {\n id: 'long-comment-block',\n tier: 'warn',\n pattern: new RegExp(\n String.raw`(?:^[ \\t]*(?:\\/\\/|\\/\\*|\\*|#[ \\t])(?![ \\t]*[-=*_~]{4})[ \\t]*\\S[^\\n]*(?:\\n|$)){${MAX_COMMENT_BLOCK_LINES},}`,\n 'm',\n ),\n 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.`,\n fix: 'Cut the block to the part a reader cannot get from the code, and move reference detail into the document that owns it.',\n appliesTo: 'code',\n};\n\n/** A TODO or FIXME marker with no owner and no reason, which nobody can act on\n * and nobody can retire. */\nconst BARE_TODO: HygieneRule = {\n id: 'bare-todo',\n tier: 'warn',\n pattern: new RegExp(\n String.raw`${MARKER}[ \\t]*(?:${MARKER_WORDS.join('|')})\\b(?![^\\n]*${OWNER})(?![^\\n]*\\b(?:${REASON_WORDS.join('|')})\\b)`,\n 'im',\n ),\n rationale:\n '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.',\n fix: 'Name an owner or a tracked issue and the condition that retires the marker, such as \"revisit once the v2 client lands\".',\n appliesTo: 'code',\n};\n\n/** Every rule the gate enforces: the deterministic patterns first, then the\n * judgement calls, then the tokens this project must never ship again. */\nexport const HYGIENE_RULES: readonly HygieneRule[] = [\n DECORATIVE_BANNER,\n WORKFLOW_NARRATION,\n DECORATIVE_EMOJI_IN_COMMENT,\n DECORATIVE_PICTOGRAPH,\n DECORATIVE_EMOJI_IN_PROSE,\n IRREGULAR_SCRIPT_IN_TEXT,\n LONG_COMMENT_BLOCK,\n BARE_TODO,\n DEBT_MARKER_RULE,\n ...RESIDUE_RULES,\n];\n\n/** Checks `text` against every rule that applies to `kind`, and returns the\n * findings in reading order: one per rule per line.\n *\n * A file is exempt from every rule when it carries the marker\n * `HYGIENE_EXEMPT_MARKERS` declares for its kind on a line above the first\n * finding — the rule source itself, the token table, and any guidance or\n * fixture file that has to name what the rules forbid. Such a file states its\n * own exemption, so no consumer keeps a list of paths to skip. A marker placed\n * at or below the first finding exempts nothing.\n *\n * Pure and deterministic: each pattern is recompiled per call, so no\n * `lastIndex` state escapes and the same text always gives the same findings. */\nexport function checkHygiene(text: string, kind: HygieneKind): HygieneFinding[] {\n const findings = collectFindings(text, kind);\n const exemptAt = exemptionLine(text, kind);\n if (exemptAt !== 0 && exemptAt < (findings[0]?.line ?? Number.POSITIVE_INFINITY)) return [];\n return findings;\n}\n\n/** Whether `text` carries the exemption marker for `kind` above the first\n * finding the hygiene rules report, so every rule — the prose finders\n * included — leaves the file alone. The prose finders run outside\n * `checkHygiene`, so consumers that run them gate the file on this predicate\n * rather than on `checkHygiene`'s findings. A marker placed at or below the\n * first finding exempts nothing. */\nexport function hasHygieneExemption(text: string, kind: HygieneKind): boolean {\n const exemptAt = exemptionLine(text, kind);\n if (exemptAt === 0) return false;\n const firstFinding = collectFindings(text, kind)[0];\n return exemptAt < (firstFinding?.line ?? Number.POSITIVE_INFINITY);\n}\n\n/** Every finding `text` produces under `kind`, in reading order. */\nfunction collectFindings(text: string, kind: HygieneKind): HygieneFinding[] {\n const lineStarts = lineStartOffsets(text);\n const findings: HygieneFinding[] = [];\n for (const rule of HYGIENE_RULES) {\n if (rule.appliesTo !== 'both' && rule.appliesTo !== kind) continue;\n let reportedLine = 0;\n for (const match of text.matchAll(globalPattern(rule.pattern))) {\n const line = lineNumberAt(lineStarts, match.index ?? 0);\n // A line with three emoji is one defect, not three.\n if (line === reportedLine) continue;\n reportedLine = line;\n findings.push({\n id: rule.id,\n tier: rule.tier,\n line,\n text: lineTextAt(text, lineStarts, line),\n rationale: rule.rationale,\n fix: rule.fix,\n });\n }\n }\n // Reading order; lines that share a number keep rule-table order, because\n // Array.prototype.sort is stable.\n return findings.sort((a, b) => a.line - b.line);\n}\n\n/** The 1-based line the exemption marker for `kind` sits on, or 0 when the text\n * carries none. The marker is recognised with surrounding whitespace ignored,\n * so an indented comment counts. */\nfunction exemptionLine(text: string, kind: HygieneKind): number {\n const marker = HYGIENE_EXEMPT_MARKERS[kind];\n const lines = text.split('\\n');\n for (let i = 0; i < lines.length; i++) if ((lines[i] ?? '').trim() === marker) return i + 1;\n return 0;\n}\n\n/** The offset each line starts at, so a match offset maps to a line number\n * without rescanning the text once per match. */\nfunction lineStartOffsets(text: string): number[] {\n const starts = [0];\n for (let i = text.indexOf('\\n'); i !== -1; i = text.indexOf('\\n', i + 1)) starts.push(i + 1);\n return starts;\n}\n\nfunction lineNumberAt(lineStarts: readonly number[], index: number): number {\n let low = 0;\n let high = lineStarts.length - 1;\n while (low < high) {\n const mid = Math.ceil((low + high) / 2);\n if ((lineStarts[mid] ?? 0) <= index) low = mid;\n else high = mid - 1;\n }\n return low + 1;\n}\n\nfunction lineTextAt(text: string, lineStarts: readonly number[], line: number): string {\n const start = lineStarts[line - 1] ?? 0;\n const end = text.indexOf('\\n', start);\n return text.slice(start, end === -1 ? text.length : end).trim();\n}\n\n/** A fresh global copy of a rule's pattern: sharing one would leave `lastIndex`\n * behind and make the next call start mid-line. */\nfunction globalPattern(pattern: RegExp): RegExp {\n const flags = pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`;\n return new RegExp(pattern.source, flags);\n}\n","// Offline behavioral-eval harness for the skill pack.\n//\n// Skills are declared as `evals/**/evals.json` per the agentskills.io format\n// (`{skill_name, evals:[{id, prompt, expected_output, assertions?}]}`). The\n// harness loads every suite and runs OFFLINE assertions only — `contains`,\n// `not-contains`, `regex`, `length-gte` — no LLM, no network, so it runs in\n// `pnpm test` (the project's offline/free constraint). This is the executable\n// that turns the JSON declaration into a vitest suite (see test/evals.test.ts).\n//\n// The evals are deliberately simple: they assert that a skill's PROMPT → a\n// simulated agent answer satisfies structural expectations. They are NOT\n// LLM-judge evals (not implemented yet); they are the CI-safe\n// baseline that catches regressions in the skill's core directives.\n//\n// Assertions must be able to run against a CANDIDATE answer (what a model or\n// a stub standing in for one actually produced), not only against the\n// self-declared `expected_output` — otherwise a suite can never fail for the\n// right reason. `evaluateSuite` therefore accepts an optional candidate-output\n// source. Passing no source keeps asserting `expected_output`, so suites that\n// predate candidate output work unchanged; passing a source makes an eval it\n// has no entry for a failure, so an unrecorded answer is never scored a pass.\n\nimport { readdirSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n/** A single offline assertion on the produced output. */\nexport type EvalAssertion =\n | { type: 'contains'; value: string }\n | { type: 'not-contains'; value: string }\n | { type: 'regex'; value: string }\n | { type: 'length-gte'; value: number };\n\n/** One eval case: a prompt + the expected directive the skill must produce. */\nexport interface SkillEval {\n id: string;\n prompt: string;\n expected_output: string;\n assertions?: EvalAssertion[];\n}\n\n/** A suite = one skill's evals (the `evals.json` shape). */\nexport interface EvalSuite {\n skill_name: string;\n evals: SkillEval[];\n}\n\n/**\n * Candidate answers keyed by eval id — what a model (or a stub standing in for\n * one) actually produced. An id present here is the text the assertions run\n * against. An id absent is a failure for that eval: the run recorded no answer,\n * and asserting the golden `expected_output` instead would report a pass the\n * harness cannot vouch for. Pass no source at all to assert `expected_output`,\n * which is how suites that predate candidate output keep working.\n */\nexport type CandidateOutputs = Record<string, string>;\n\n/** Validate an assertion's shape (fail-fast on a malformed evals.json). */\nfunction parseAssertion(a: unknown): EvalAssertion {\n if (typeof a !== 'object' || a === null) throw new Error('assertion must be an object');\n const { type, value } = a as { type?: unknown; value?: unknown };\n switch (type) {\n case 'contains':\n case 'not-contains':\n case 'regex':\n if (typeof value !== 'string' || value.length === 0) {\n throw new Error(`assertion '${String(type)}' requires a non-empty string value`);\n }\n return { type, value } as EvalAssertion;\n case 'length-gte':\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new Error(\"assertion 'length-gte' requires a finite number value\");\n }\n return { type, value } as EvalAssertion;\n default:\n throw new Error(`unknown assertion type '${String(type)}'`);\n }\n}\n\n/** Parse + validate a raw `evals.json` payload. Throws on malformed shape. */\nexport function parseEvalSuite(raw: unknown): EvalSuite {\n if (typeof raw !== 'object' || raw === null) throw new Error('evals.json must be an object');\n const { skill_name, evals } = raw as { skill_name?: unknown; evals?: unknown };\n if (typeof skill_name !== 'string' || skill_name.length === 0) {\n throw new Error('evals.json requires a non-empty string `skill_name`');\n }\n if (!Array.isArray(evals) || evals.length === 0) {\n throw new Error(`evals.json for ${skill_name} requires a non-empty 'evals' array`);\n }\n const parsed: SkillEval[] = evals.map((e, i) => {\n if (typeof e !== 'object' || e === null) throw new Error(`eval[${i}] must be an object`);\n const { id, prompt, expected_output, assertions } = e as {\n id?: unknown;\n prompt?: unknown;\n expected_output?: unknown;\n assertions?: unknown;\n };\n if (typeof id !== 'string' || id.length === 0)\n throw new Error(`eval[${i}] requires a string id`);\n if (typeof prompt !== 'string' || prompt.length === 0) {\n throw new Error(`eval ${id} requires a string prompt`);\n }\n if (typeof expected_output !== 'string') {\n throw new Error(`eval ${id} requires a string expected_output`);\n }\n return {\n id,\n prompt,\n expected_output,\n assertions: Array.isArray(assertions) ? assertions.map(parseAssertion) : undefined,\n };\n });\n return { skill_name, evals: parsed };\n}\n\n/** Run a list of assertions against `output`. Returns pass + per-assertion failures. */\nexport function runAssertions(\n output: string,\n assertions: EvalAssertion[],\n): { pass: boolean; failures: string[] } {\n const failures: string[] = [];\n for (const a of assertions) {\n switch (a.type) {\n case 'contains':\n if (!output.includes(a.value)) failures.push(`expected to contain \"${a.value}\"`);\n break;\n case 'not-contains':\n if (output.includes(a.value)) failures.push(`expected NOT to contain \"${a.value}\"`);\n break;\n case 'regex':\n if (!new RegExp(a.value, 'i').test(output)) failures.push(`expected to match /${a.value}/`);\n break;\n case 'length-gte':\n if (output.length < a.value)\n failures.push(`expected length >= ${a.value} (got ${output.length})`);\n break;\n }\n }\n return { pass: failures.length === 0, failures };\n}\n\n// Package root (evals/ is a sibling of src/): resolve the evals dir the same\n// way discover.ts resolves builtin/. Works under vitest (src) + built (dist).\nconst HERE = dirname(fileURLToPath(import.meta.url));\nconst PKG_ROOT = dirname(HERE);\nexport const EVALS_DIR = join(PKG_ROOT, 'evals');\n\n/**\n * Load every eval suite under `dir` (default the shipped `evals/` dir — each\n * subdir carries an `evals.json`). Skips non-`.json` files and missing dirs\n * (no evals = no-op). Fail-fast on a malformed suite (a broken evals.json is a\n * bug, not a skip).\n */\nexport function loadEvalSuites(dir: string = EVALS_DIR): EvalSuite[] {\n let entries: string[];\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => e.name)\n .sort();\n } catch {\n return []; // no evals/ dir (or unreadable) = no suites shipped\n }\n const suites: EvalSuite[] = [];\n for (const name of entries) {\n const file = join(dir, name, 'evals.json');\n try {\n const raw = JSON.parse(readFileSync(file, 'utf8')) as unknown;\n suites.push(parseEvalSuite(raw));\n } catch (err) {\n // Missing evals.json in a subdir is fine (it may be a scratch dir); a\n // malformed one is not. Distinguish by existence.\n try {\n readFileSync(file, 'utf8');\n throw err; // exists but malformed → rethrow\n } catch (re) {\n if (re === err) throw err;\n // ENOENT → skip silently.\n }\n }\n }\n return suites;\n}\n\n/** Evaluate one suite: for each eval, check its assertions against the\n * candidate output supplied for that eval. With no candidate source at all,\n * each eval is checked against `expected_output` (the directive the skill\n * should produce); with a source that omits an eval, that eval fails as\n * unrecorded. Returns pass/fail per eval with the assertion failures. This is\n * the offline core the vitest runner drives. */\nexport function evaluateSuite(\n suite: EvalSuite,\n candidates?: CandidateOutputs,\n): Array<{ id: string; pass: boolean; failures: string[] }> {\n return suite.evals.map((e) => {\n // A supplied candidate source that has no entry for this eval means the\n // run could not record an answer (a misspelled id, or a model call that\n // never happened). Scoring the golden `expected_output` here would report\n // the very pass this harness exists to prevent, so a missing entry is a\n // failure — but only when a source was supplied at all. With no source, a\n // suite that predates candidate output keeps asserting its declared\n // directive (backward compatible).\n if (candidates && !(e.id in candidates)) {\n return { id: e.id, pass: false, failures: [`no candidate output for eval \"${e.id}\"`] };\n }\n const output = candidates ? (candidates[e.id] ?? e.expected_output) : e.expected_output;\n return {\n id: e.id,\n pass: e.assertions ? runAssertions(output, e.assertions).pass : true,\n failures: e.assertions ? runAssertions(output, e.assertions).failures : [],\n };\n });\n}\n","// Prose tells: the markers that make generated prose read as machine output,\n// from em-dash density to hedging openers and formulaic not-X-but-Y contrasts.\n// `humanizerFindings` is a pure scan of a text that has had fenced code\n// stripped, so the rules never read inside ``` blocks.\nimport type { HygieneFinding } from './hygiene.js';\n\nconst HEDGES = [\n 'it is worth mentioning',\n 'it should be noted',\n 'it goes without saying',\n 'arguably',\n] as const;\n\n// More em-dashes than this in one paragraph is the tell the finder reports.\nconst EM_DASH_PER_PARAGRAPH = 2;\n\n// Strip fenced code so prose rules never read inside ``` blocks.\nfunction proseOnly(text: string): string {\n return text.replace(/```[\\s\\S]*?```/g, '');\n}\n\n/** The 1-based line of the character at `index`: one plus the count of newlines\n * before it. */\nfunction lineOf(text: string, index: number): number {\n let line = 1;\n for (let i = 0; i < index; i++) if (text[i] === '\\n') line++;\n return line;\n}\n\n/** The index of the first match, or -1 when the pattern matches nothing. */\nfunction firstMatch(text: string, re: RegExp): number {\n const match = re.exec(text);\n return match ? match.index : -1;\n}\n\n/** Yields each paragraph with the 1-based line it starts on. An empty line ends\n * a paragraph, so a single line break continues the paragraph. */\nfunction* paragraphsOf(text: string): Generator<{ text: string; line: number }> {\n let paragraph = '';\n let startLine = 1;\n let line = 1;\n for (const raw of text.split('\\n')) {\n if (raw.trim() === '') {\n if (paragraph.trim() !== '') yield { text: paragraph, line: startLine };\n paragraph = '';\n startLine = line + 1;\n } else {\n if (paragraph === '') startLine = line;\n paragraph += (paragraph === '' ? '' : '\\n') + raw;\n }\n line++;\n }\n if (paragraph.trim() !== '') yield { text: paragraph, line: startLine };\n}\n\nexport function humanizerFindings(text: string): HygieneFinding[] {\n const prose = proseOnly(text);\n const findings: HygieneFinding[] = [];\n\n for (const hedge of HEDGES) {\n const index = firstMatch(prose, new RegExp(hedge, 'i'));\n if (index !== -1) {\n findings.push({\n id: 'humanizer-hedge',\n tier: 'warn',\n line: lineOf(prose, index),\n text: hedge,\n rationale: 'a hedging opener',\n fix: 'delete it',\n });\n }\n }\n\n const contrastIndex = firstMatch(prose, /\\bnot\\s+\\w+[\\w\\s,]{0,40}\\bbut\\b/i);\n if (contrastIndex !== -1) {\n findings.push({\n id: 'humanizer-contrast',\n tier: 'warn',\n line: lineOf(prose, contrastIndex),\n text: 'not X but Y',\n rationale: 'a formulaic contrast',\n fix: 'state the second clause alone',\n });\n }\n\n for (const { text: paragraph, line } of paragraphsOf(prose)) {\n const count = (paragraph.match(/—/g) ?? []).length;\n if (count > EM_DASH_PER_PARAGRAPH) {\n findings.push({\n id: 'humanizer-em-dash',\n tier: 'warn',\n line,\n text: `${count} em-dashes`,\n rationale: 'em-dash density is an AI tell',\n fix: 'use periods or parentheticals',\n });\n }\n }\n\n return findings;\n}\n","// Runtime-derived skill registry.\n//\n// The registry is NOT a committed file: frontmatter is the single source of\n// truth and `buildRegistry()` derives a queryable index from `discoverAll()`\n// on demand (the CLI calls it; no drift risk, since nothing is persisted). Every\n// field comes from either frontmatter (`metadata.category`, `metadata.version`)\n// or a shape check (`status` from the stub marker, `referenceCount`/`lines`\n// from the discovered files).\n\nimport { discoverAll } from './discover.js';\nimport type { BuiltinSkill } from './types.js';\n\n/** The pack-wide `noir-` namespace a registry entry always belongs to. */\nexport const NOIR_NAMESPACE = 'noir-';\n\n/** One row in the derived registry. */\nexport interface SkillRegistryEntry {\n /** Canonical `noir-<kebab>` id (== dir name). */\n name: string;\n /** `builtin` for the shipped pack; `integration` for an `integrations/<name>/`. */\n kind: 'builtin' | 'integration';\n /** Category from `metadata.category`; falls back to the `noir-`-stripped name\n * (a newly-authored skill still gets a sensible cell). */\n category: string;\n /** Per-skill version from `metadata.version`; defaults to `0.0.0` when absent\n * (the pack-level version in package.json is the authoritative release). */\n version: string;\n /** `full` when the body has no `> **Stub:**` marker; `stub` otherwise. */\n status: 'full' | 'stub';\n /** Lifecycle stage of the skill. `active` = currently shipped and usable;\n * `deprecated` = superseded (a renamed/merged predecessor).\n * The curated pack has no deprecated members today. */\n lifecycle: 'active' | 'deprecated';\n /** The WHAT+WHEN description (the trigger the host sees). */\n description: string;\n /** Number of `references/*.md` files. */\n referenceCount: number;\n /** Total SKILL.md line count (frontmatter + body). */\n lines: number;\n}\n\n/** Derive the category from the skill name when `metadata.category` is absent. */\nfunction fallbackCategory(name: string): string {\n return name.replace(/^noir-/, '') || 'general';\n}\n\n/** Map a discovered builtin/integration to a registry row. */\nfunction toEntry(s: BuiltinSkill, kind: 'builtin' | 'integration'): SkillRegistryEntry {\n const category = s.frontmatter.metadata?.category?.trim() || fallbackCategory(s.name);\n const version = s.frontmatter.metadata?.version?.trim() || '0.0.0';\n const status: 'full' | 'stub' = s.skillMd.includes('> **Stub:**') ? 'stub' : 'full';\n const lines = s.skillMd.split('\\n').length;\n return {\n name: s.name,\n kind,\n category,\n version,\n status,\n // Every shipped skill is active; `deprecated` is reserved for a future\n // superseded member.\n lifecycle: 'active' as const,\n description: typeof s.frontmatter.description === 'string' ? s.frontmatter.description : '',\n referenceCount: s.references.length,\n lines,\n };\n}\n\n/**\n * Build the full registry from `discoverAll()` — builtins + integrations in\n * one list, sorted by name. Pure (no I/O beyond discovery); never throws for\n * a malformed skill (a discovery failure surfaces upstream, the same as the\n * compiler's fail-fast).\n */\nexport function buildRegistry(): SkillRegistryEntry[] {\n const { builtins, integrations } = discoverAll();\n return [\n ...builtins.map((b) => toEntry(b, 'builtin')),\n ...integrations.map((i) => toEntry(i, 'integration')),\n ].sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/** Convenience: registry filtered to a single category (CLI grouping). */\nexport function registryByCategory(category: string): SkillRegistryEntry[] {\n return buildRegistry().filter((e) => e.category === category);\n}\n","// Lexical anti-slop rules: the words and phrases that make generated prose read\n// as machine output. `slopFindings` is a pure scan of a text that has had\n// fenced code stripped, so prose rules never read inside ``` blocks.\nimport type { HygieneFinding } from './hygiene.js';\n\nexport const KILL_ON_SIGHT = [\n 'delve',\n 'utilize',\n 'leverage',\n 'facilitate',\n 'elucidate',\n 'embark',\n 'endeavor',\n 'encompass',\n 'multifaceted',\n 'tapestry',\n 'testament',\n 'paradigm',\n 'synergy',\n 'holistic',\n 'catalyze',\n 'juxtapose',\n 'realm',\n 'landscape',\n 'myriad',\n 'plethora',\n] as const;\n\nexport const CLUSTER_WORDS = [\n 'robust',\n 'comprehensive',\n 'seamless',\n 'cutting-edge',\n 'innovative',\n 'streamline',\n 'empower',\n 'foster',\n 'enhance',\n 'elevate',\n 'optimize',\n 'scalable',\n 'pivotal',\n 'intricate',\n 'profound',\n 'resonate',\n 'underscore',\n 'harness',\n 'navigate',\n 'cultivate',\n 'bolster',\n 'galvanize',\n 'cornerstone',\n 'game-changer',\n] as const;\n\nexport const FILLER_PHRASES = [\n 'it is important to note',\n \"it's important to note\",\n 'it is worth noting',\n \"in today's\",\n 'in conclusion',\n 'needless to say',\n] as const;\n\nexport const SLOP_TRIGRAMS = [\n 'a testament to',\n 'a tapestry of',\n 'in the realm of',\n 'the power of',\n 'serves as a',\n 'in order to',\n 'the fact that',\n 'plays a crucial',\n] as const;\n\n// `harness` is a legitimate technical term (e.g. \"test harness\"); it is never flagged.\nexport const SLOP_ALLOWLIST: readonly string[] = ['harness'];\n\n// Strip fenced code so prose rules never read inside ``` blocks.\nfunction proseOnly(text: string): string {\n return text.replace(/```[\\s\\S]*?```/g, '');\n}\n\n// Whole words only; case-insensitive. A hyphen or apostrophe inside the word may\n// appear as the character or as a space, so \"cutting-edge\" and \"cutting edge\"\n// both match.\nfunction wordRe(word: string): RegExp {\n return new RegExp(`\\\\b${word.replace(/[-']/g, '[-\\\\s]?')}\\\\b`, 'i');\n}\n\nconst CLUSTER_THRESHOLD = 3;\n\n/** The 1-based line of the character at `index`: one plus the count of newlines\n * before it. */\nfunction lineOf(text: string, index: number): number {\n let line = 1;\n for (let i = 0; i < index; i++) if (text[i] === '\\n') line++;\n return line;\n}\n\n/** The index of the first match, or -1 when the pattern matches nothing. */\nfunction firstMatch(text: string, re: RegExp): number {\n const match = re.exec(text);\n return match ? match.index : -1;\n}\n\n/** Yields each paragraph with the 1-based line it starts on. An empty line ends\n * a paragraph, so a single line break continues the paragraph. */\nfunction* paragraphsOf(text: string): Generator<{ text: string; line: number }> {\n let paragraph = '';\n let startLine = 1;\n let line = 1;\n for (const raw of text.split('\\n')) {\n if (raw.trim() === '') {\n if (paragraph.trim() !== '') yield { text: paragraph, line: startLine };\n paragraph = '';\n startLine = line + 1;\n } else {\n if (paragraph === '') startLine = line;\n paragraph += (paragraph === '' ? '' : '\\n') + raw;\n }\n line++;\n }\n if (paragraph.trim() !== '') yield { text: paragraph, line: startLine };\n}\n\nexport function slopFindings(text: string): HygieneFinding[] {\n const prose = proseOnly(text);\n const findings: HygieneFinding[] = [];\n\n for (const word of KILL_ON_SIGHT) {\n if (SLOP_ALLOWLIST.includes(word)) continue;\n const index = firstMatch(prose, wordRe(word));\n if (index !== -1) {\n findings.push({\n id: 'slop-kill',\n tier: 'warn',\n line: lineOf(prose, index),\n text: word,\n rationale: 'a word LLMs overuse, flagged on first use',\n fix: `replace \"${word}\" with a plain verb or noun`,\n });\n }\n }\n\n for (const { text: paragraph, line } of paragraphsOf(prose)) {\n const hits = CLUSTER_WORDS.filter(\n (w) => !SLOP_ALLOWLIST.includes(w) && wordRe(w).test(paragraph),\n ).length;\n if (hits >= CLUSTER_THRESHOLD) {\n findings.push({\n id: 'slop-cluster',\n tier: 'warn',\n line,\n text: `${hits} cluster words`,\n rationale: 'a paragraph dense with abstract praise reads machine-written',\n fix: 'rewrite in concrete terms; keep at most one such word',\n });\n }\n }\n\n for (const phrase of [...FILLER_PHRASES, ...SLOP_TRIGRAMS]) {\n const index = firstMatch(prose, new RegExp(phrase.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'i'));\n if (index !== -1) {\n findings.push({\n id: 'slop-phrase',\n tier: 'warn',\n line: lineOf(prose, index),\n text: phrase,\n rationale: 'a filler phrase or slop trigram',\n fix: 'delete it or state the point directly',\n });\n }\n }\n\n return findings;\n}\n"],"mappings":";AAAA,SAAS,OAAO,SAAS,UAAU,IAAI,iBAAiB;AACxD,SAAS,UAAU,WAAAA,UAAS,QAAAC,aAAY;AACxC,SAAS,aAAa,uBAAuB;AAC7C,SAAS,SAAS,WAAW,aAAa,qBAAqB;;;ACH/D,SAAS,aAAa,oBAAoB;AAC1C,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;;;ACc9B,YAAY,OAAO;AAOZ,IAAM,wBAA0B,SAAO;AAAA,EAC5C,MAAQ,UAAQ,SAAS;AAAA,EACzB,UAAY,SAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,UAAY,OAAK,CAAC,gBAAgB,MAAM,CAAC,EAAE,QAAQ,cAAc;AACnE,CAAC;AAQM,IAAM,uBACV,SAAO;AAAA,EACN,YAAc,OAAK,CAAC,QAAQ,SAAS,MAAM,CAAC,EAAE,SAAS;AAAA,EACvD,WAAa,QAAQ,SAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;AAC3C,CAAC,EACA,QAAQ,EAAE,WAAW,CAAC,EAAE,CAAC;AAKrB,IAAM,uBACV,SAAO;AAAA,EACN,SAAW,SAAO,EAAE,IAAI,CAAC;AAAA,EACzB,WAAa,OAAK,CAAC,SAAS,MAAM,CAAC;AAAA,EACnC,MAAQ,QAAQ,SAAO,CAAC,EAAE,SAAS;AAAA,EACnC,KAAO,SAAO,EAAE,SAAS;AAAA,EACzB,KAAO,SAAS,SAAO,GAAK,SAAO,CAAC,EAAE,SAAS;AACjD,CAAC,EACA,SAAS,EACT,QAAQ,IAAI;AAIR,IAAM,+BAAiC,SAAO;AAAA,EACnD,MAAQ,SAAO,EAAE,MAAM,mCAAmC,yBAAyB;AAAA,EACnF,MAAM;AAAA,EACN,SAAW,OAAK,CAAC,QAAQ,qBAAqB,aAAa,cAAc,CAAC;AAAA,EAC1E,KAAK;AAAA,EACL,KAAK;AACP,CAAC;AAOM,SAAS,iBAAiB,MAAuC;AACtE,SAAO,6BAA6B,MAAM,IAAI;AAChD;AAGO,SAAS,oBACd,KAC+E;AAC/E,QAAM,SAAS,6BAA6B,UAAU,GAAG;AACzD,MAAI,OAAO,QAAS,QAAO,EAAE,IAAI,MAAM,OAAO,OAAO,KAAK;AAC1D,SAAO,EAAE,IAAI,OAAO,QAAQ,OAAO,MAAM,OAAO,IAAI,CAAC,MAA2B,EAAE,OAAO,EAAE;AAC7F;AAOO,SAAS,oBAAoB,SAAqD;AACvF,SAAO,YAAY,eAAe,YAAY;AAChD;;;AD/EA,IAAM,OAAO,QAAQ,cAAc,YAAY,GAAG,CAAC;AACnD,IAAM,WAAW,QAAQ,IAAI;AACtB,IAAM,cAAc,KAAK,UAAU,SAAS;AAC5C,IAAM,mBAAmB,KAAK,UAAU,cAAc;AAK7D,SAAS,YAAY,KAA2E;AAC9F,QAAM,UAAU,aAAa,KAAK,KAAK,UAAU,GAAG,MAAM;AAC1D,QAAM,cAAc,iBAAiB,OAAO;AAC5C,QAAM,UAAU,KAAK,KAAK,YAAY;AACtC,MAAI,aAAyC,CAAC;AAC9C,MAAI;AACF,iBAAa,YAAY,OAAO,EAC7B,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC,EAC/B,KAAK,EACL,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,aAAa,KAAK,SAAS,CAAC,GAAG,MAAM,EAAE,EAAE;AAAA,EAC9E,QAAQ;AACN,iBAAa,CAAC;AAAA,EAChB;AACA,SAAO,EAAE,SAAS,aAAa,WAAW;AAC5C;AAEO,SAAS,gBAAgB,aAAqB,aAA6B;AAChF,QAAM,OAAO,YAAY,YAAY,EAAE,eAAe,KAAK,CAAC,EACzD,OAAO,CAAC,MAAM,EAAE,YAAY,KAAK,EAAE,KAAK,WAAW,OAAO,CAAC,EAC3D,IAAI,CAAC,MAAM,EAAE,IAAI,EACjB,KAAK;AACR,SAAO,KAAK,IAAI,CAAC,SAAS;AACxB,UAAM,MAAM,KAAK,YAAY,IAAI;AACjC,WAAO,EAAE,MAAM,KAAK,GAAG,YAAY,GAAG,EAAE;AAAA,EAC1C,CAAC;AACH;AAaO,SAAS,qBACd,kBAA0B,kBACN;AACpB,MAAI;AACJ,MAAI;AACF,cAAU,YAAY,iBAAiB,EAAE,eAAe,KAAK,CAAC,EAC3D,OAAO,CAAC,MAAM,EAAE,YAAY,KAAK,EAAE,KAAK,WAAW,OAAO,CAAC,EAC3D,IAAI,CAAC,MAAM,EAAE,IAAI,EACjB,KAAK;AAAA,EACV,QAAQ;AAEN,WAAO,CAAC;AAAA,EACV;AACA,SAAO,QAAQ,IAAI,CAAC,SAAS;AAC3B,UAAM,MAAM,KAAK,iBAAiB,IAAI;AACtC,UAAM,iBAAiB,KAAK;AAAA,MAC1B,aAAa,KAAK,KAAK,kBAAkB,GAAG,MAAM;AAAA,IACpD;AACA,QAAI;AACJ,QAAI;AACF,oBAAc,iBAAiB,cAAc;AAAA,IAC/C,SAAS,KAAK;AAEZ,YAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,YAAM,IAAI,MAAM,+BAA+B,GAAG,KAAK,GAAG,EAAE;AAAA,IAC9D;AACA,QAAI,YAAY,SAAS,MAAM;AAC7B,YAAM,IAAI;AAAA,QACR,oBAAoB,IAAI,kCAAkC,YAAY,IAAI;AAAA,MAC5E;AAAA,IACF;AACA,WAAO,EAAE,MAAM,KAAK,GAAG,YAAY,GAAG,GAAG,aAAa,eAAe;AAAA,EACvE,CAAC;AACH;AAUO,SAAS,YAAY,OAA0D,CAAC,GAGrF;AACA,QAAM,aAAa,KAAK,cAAc;AACtC,QAAM,kBAAkB,KAAK,mBAAmB,KAAK,QAAQ,UAAU,GAAG,cAAc;AACxF,SAAO;AAAA,IACL,UAAU,gBAAgB,UAAU;AAAA,IACpC,cAAc,qBAAqB,eAAe;AAAA,EACpD;AACF;;;AEhGA,SAAS,sBAAsB;;;ACTxB,IAAM,mBAAgC;AAAA,EAC3C,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,WAAW;AAAA,EACX,SAAS;AAAA,EACT,WAAW;AAAA,EACX,KAAK;AACP;;;ACKA,IAAM,UAAmC;AAAA,EACvC;AAAA,IACE,OAAO;AAAA,IACP,KAAK;AAAA,EACP;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,KAAK;AAAA,EACP;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,KAAK;AAAA,EACP;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,KAAK;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA,IACE,OAAO;AAAA,IACP,KAAK;AAAA,EACP;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,KAAK;AAAA,EACP;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,KAAK;AAAA,EACP;AACF;AAGO,IAAM,oBAAuC,QAAQ,IAAI,CAAC,UAAU,MAAM,KAAK;AAU/E,IAAM,gBAAwC,QAAQ,IAAI,CAAC,WAAW;AAAA,EAC3E,IAAI,WAAW,MAAM,MAClB,QAAQ,gBAAgB,GAAG,EAC3B,QAAQ,UAAU,EAAE,EACpB,YAAY,CAAC;AAAA,EAChB,MAAM;AAAA,EACN,SAAS,IAAI,OAAO,cAAc,gBAAgB,MAAM,KAAK,CAAC,YAAY;AAAA,EAC1E,WAAW,MAAM;AAAA,EACjB,KAAK;AAAA,EACL,WAAW;AACb,EAAE;AAGF,SAAS,gBAAgBC,UAAyB;AAChD,SAAOA,SAAQ,QAAQ,uBAAuB,MAAM;AACtD;;;AChCO,IAAM,yBAAgE;AAAA,EAC3E,MAAM;AAAA,EACN,UAAU;AACZ;AAKO,IAAM,0BAA0B;AAKvC,IAAM,SAAS,OAAO;AAKtB,IAAM,qBAAqB,OAAO,aAAa,KAAM;AAGrD,IAAM,aAAa,OAAO;AAK1B,IAAM,mBAAmB,sFAAsB,kBAAkB;AAIjE,IAAM,WAAW,OAAO,aAAa,EAAI;AAKzC,IAAM,oBAAoB,IAAI,QAAQ;AAGtC,IAAM,eAAe,CAAC,QAAQ,OAAO;AAGrC,IAAM,eAAe;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGA,IAAM,QAAQ,OAAO;AAIrB,IAAM,oBAAiC;AAAA,EACrC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,SAAS,IAAI,OAAO,OAAO,MAAM,MAAM,qBAAqB,GAAG;AAAA,EAC/D,WACE;AAAA,EACF,KAAK;AAAA,EACL,WAAW;AACb;AAQA,IAAM,qBAAkC;AAAA,EACtC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,SAAS,IAAI;AAAA,IACX,OAAO,MAAM,MAAM;AAAA,IACnB;AAAA,EACF;AAAA,EACA,WACE;AAAA,EACF,KAAK;AAAA,EACL,WAAW;AACb;AAOA,IAAM,8BAA2C;AAAA,EAC/C,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,SAAS,IAAI;AAAA,IACX,OAAO,MAAM,MAAM,aAAa,iBAAiB,IAAI,gBAAgB,MAAM,kBAAkB,IAAI,iBAAiB;AAAA,IAClH;AAAA,EACF;AAAA,EACA,WACE;AAAA,EACF,KAAK;AAAA,EACL,WAAW;AACb;AAKA,IAAM,wBAAqC;AAAA,EACzC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,SAAS,IAAI;AAAA,IACX,OAAO,MAAM,MAAM,YAAY,gBAAgB,IAAI,UAAU,GAAG,kBAAkB;AAAA,IAClF;AAAA,EACF;AAAA,EACA,WACE;AAAA,EACF,KAAK;AAAA,EACL,WAAW;AACb;AAKA,IAAM,4BAAyC;AAAA,EAC7C,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,SAAS,IAAI;AAAA,IACX,OAAO,qDAAqD,gBAAgB,IAAI,UAAU,GAAG,kBAAkB;AAAA,IAC/G;AAAA,EACF;AAAA,EACA,WACE;AAAA,EACF,KAAK;AAAA,EACL,WAAW;AACb;AASA,IAAM,2BAA2B;AAAA,EAC/B,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AAAA,EACP,OAAO;AAAA;AACT;AAGA,IAAM,mBAAmB,IAAI,yBAAyB,KAAK,EAAE,CAAC;AAS9D,IAAM,2BAAwC;AAAA,EAC5C,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,SAAS,IAAI,OAAO,kBAAkB,GAAG;AAAA,EACzC,WACE;AAAA,EACF,KAAK;AAAA,EACL,WAAW;AACb;AAGA,IAAM,qBAAkC;AAAA,EACtC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,SAAS,IAAI;AAAA,IACX,OAAO,mFAAmF,uBAAuB;AAAA,IACjH;AAAA,EACF;AAAA,EACA,WAAW,YAAY,uBAAuB;AAAA,EAC9C,KAAK;AAAA,EACL,WAAW;AACb;AAIA,IAAM,YAAyB;AAAA,EAC7B,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,SAAS,IAAI;AAAA,IACX,OAAO,MAAM,MAAM,YAAY,aAAa,KAAK,GAAG,CAAC,eAAe,KAAK,kBAAkB,aAAa,KAAK,GAAG,CAAC;AAAA,IACjH;AAAA,EACF;AAAA,EACA,WACE;AAAA,EACF,KAAK;AAAA,EACL,WAAW;AACb;AAIO,IAAM,gBAAwC;AAAA,EACnD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL;AAcO,SAAS,aAAa,MAAc,MAAqC;AAC9E,QAAM,WAAW,gBAAgB,MAAM,IAAI;AAC3C,QAAM,WAAW,cAAc,MAAM,IAAI;AACzC,MAAI,aAAa,KAAK,YAAY,SAAS,CAAC,GAAG,QAAQ,OAAO,mBAAoB,QAAO,CAAC;AAC1F,SAAO;AACT;AAQO,SAAS,oBAAoB,MAAc,MAA4B;AAC5E,QAAM,WAAW,cAAc,MAAM,IAAI;AACzC,MAAI,aAAa,EAAG,QAAO;AAC3B,QAAM,eAAe,gBAAgB,MAAM,IAAI,EAAE,CAAC;AAClD,SAAO,YAAY,cAAc,QAAQ,OAAO;AAClD;AAGA,SAAS,gBAAgB,MAAc,MAAqC;AAC1E,QAAM,aAAa,iBAAiB,IAAI;AACxC,QAAM,WAA6B,CAAC;AACpC,aAAW,QAAQ,eAAe;AAChC,QAAI,KAAK,cAAc,UAAU,KAAK,cAAc,KAAM;AAC1D,QAAI,eAAe;AACnB,eAAW,SAAS,KAAK,SAAS,cAAc,KAAK,OAAO,CAAC,GAAG;AAC9D,YAAM,OAAO,aAAa,YAAY,MAAM,SAAS,CAAC;AAEtD,UAAI,SAAS,aAAc;AAC3B,qBAAe;AACf,eAAS,KAAK;AAAA,QACZ,IAAI,KAAK;AAAA,QACT,MAAM,KAAK;AAAA,QACX;AAAA,QACA,MAAM,WAAW,MAAM,YAAY,IAAI;AAAA,QACvC,WAAW,KAAK;AAAA,QAChB,KAAK,KAAK;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,EACF;AAGA,SAAO,SAAS,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI;AAChD;AAKA,SAAS,cAAc,MAAc,MAA2B;AAC9D,QAAM,SAAS,uBAAuB,IAAI;AAC1C,QAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,IAAK,MAAK,MAAM,CAAC,KAAK,IAAI,KAAK,MAAM,OAAQ,QAAO,IAAI;AAC1F,SAAO;AACT;AAIA,SAAS,iBAAiB,MAAwB;AAChD,QAAM,SAAS,CAAC,CAAC;AACjB,WAAS,IAAI,KAAK,QAAQ,IAAI,GAAG,MAAM,IAAI,IAAI,KAAK,QAAQ,MAAM,IAAI,CAAC,EAAG,QAAO,KAAK,IAAI,CAAC;AAC3F,SAAO;AACT;AAEA,SAAS,aAAa,YAA+B,OAAuB;AAC1E,MAAI,MAAM;AACV,MAAI,OAAO,WAAW,SAAS;AAC/B,SAAO,MAAM,MAAM;AACjB,UAAM,MAAM,KAAK,MAAM,MAAM,QAAQ,CAAC;AACtC,SAAK,WAAW,GAAG,KAAK,MAAM,MAAO,OAAM;AAAA,QACtC,QAAO,MAAM;AAAA,EACpB;AACA,SAAO,MAAM;AACf;AAEA,SAAS,WAAW,MAAc,YAA+B,MAAsB;AACrF,QAAM,QAAQ,WAAW,OAAO,CAAC,KAAK;AACtC,QAAM,MAAM,KAAK,QAAQ,MAAM,KAAK;AACpC,SAAO,KAAK,MAAM,OAAO,QAAQ,KAAK,KAAK,SAAS,GAAG,EAAE,KAAK;AAChE;AAIA,SAAS,cAAc,SAAyB;AAC9C,QAAM,QAAQ,QAAQ,MAAM,SAAS,GAAG,IAAI,QAAQ,QAAQ,GAAG,QAAQ,KAAK;AAC5E,SAAO,IAAI,OAAO,QAAQ,QAAQ,KAAK;AACzC;;;AH3WO,SAAS,OAAO,IAAoB;AACzC,SAAO,GAAG,QAAQ,mCAAmC,EAAE;AACzD;AAQA,SAAS,aAAa,MAAsB;AAC1C,MAAI,KAAK,WAAW,MAAM,EAAG,QAAO,KAAK,MAAM,CAAC;AAChD,MAAI,KAAK,WAAW,IAAI,EAAG,QAAO,KAAK,MAAM,CAAC;AAC9C,SAAO;AACT;AAIO,IAAM,iBAAiB;AAIvB,IAAM,sBAAsB;AAKnC,IAAM,eAAe;AAIrB,IAAM,oBAAoB;AAG1B,IAAM,kBAAkB;AAGxB,IAAM,WACJ;AAKF,IAAM,iBAAiB;AAOhB,SAAS,gBAAgB,MAAwB;AACtD,QAAM,UAAoB,CAAC;AAC3B,MAAI,CAAC,aAAa,KAAK,IAAI,EAAG,SAAQ,KAAK,gBAAgB;AAC3D,MAAI,CAAC,kBAAkB,KAAK,IAAI,EAAG,SAAQ,KAAK,4BAA4B;AAC5E,MAAI,CAAC,gBAAgB,KAAK,IAAI,GAAG;AAC/B,YAAQ,KAAK,uEAAuE;AAAA,EACtF;AACA,SAAO;AACT;AAGO,SAAS,iBAAiB,MAAc,MAAc,gBAAyB;AACpF,SAAO,KAAK,MAAM,IAAI,EAAE,UAAU;AACpC;AAIO,SAAS,kBAAkB,OAA+B;AAC/D,SAAO,MAAM,WAAW,OAAO,CAAC,MAAM,eAAe,KAAK,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI;AACzF;AAeO,SAAS,sBAAsB,aAA8B;AAClE,QAAM,UAAU,YAAY,KAAK;AACjC,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,CAAC,SAAS,KAAK,OAAO,EAAG,QAAO;AAGpC,QAAM,WAAW,QACd,MAAM,eAAe,EACrB,MAAM,CAAC,EACP,KAAK,GAAG,EACR,KAAK;AACR,SAAO,SAAS,MAAM,KAAK,EAAE,OAAO,OAAO,EAAE,UAAU;AACzD;AAIO,SAAS,yBAAyB,aAA8B;AACrE,SAAO,SAAS,KAAK,YAAY,KAAK,CAAC;AACzC;AASO,SAAS,aAAa,OAA+B;AAC1D,QAAM,WAAqB,CAAC;AAM5B,QAAM,OAAO,MAAM;AACnB,QAAM,OAAO,OAAO,IAAI;AACxB,QAAM,YAAY,KAAK,MAAM,IAAI,EAAE;AAEnC,MAAI,YAAY,qBAAqB;AACnC,aAAS,KAAK,iDAAiD;AAAA,EACjE;AAGA,QAAM,aAAa,MAAM,KAAK,IAAI,KAAK,iBAAiB,KAAK,IAAI,KAAK,cAAc,KAAK,IAAI;AAC7F,MAAI,CAAC,YAAY;AACf,aAAS,KAAK,kEAAkE;AAAA,EAClF;AAEA,MACE,oFAAoF,KAAK,IAAI,GAC7F;AACA,aAAS,KAAK,0EAA0E;AAAA,EAC1F;AAIA,MACE,mEAAmE,KAAK,IAAI,KAC5E,CAAC,uCAAuC,KAAK,IAAI,GACjD;AACA,aAAS,KAAK,4DAA4D;AAAA,EAC5E;AAEA,QAAM,gBAAgB,qDAAqD,KAAK,IAAI;AACpF,QAAM,mBAAmB,+BAA+B,KAAK,IAAI;AACjE,MAAI,iBAAiB,CAAC,kBAAkB;AACtC,aAAS,KAAK,2DAA2D;AAAA,EAC3E;AACA,SAAO;AACT;AAIA,IAAM,oBAAoB,oBAAI,IAAI,CAAC,SAAS,SAAS,SAAS,OAAO,CAAC;AAItE,IAAM,gBAA0D,MAAM;AACpE,QAAM,IAAI,oBAAI,IAAyB;AACvC,aAAW,KAAK,OAAO,OAAO,cAAc,GAAG;AAC7C,UAAM,QAAQ,EAAE,IAAI,EAAE,GAAG;AACzB,QAAI,MAAO,OAAM,IAAI,EAAE,IAAI;AAAA,QACtB,GAAE,IAAI,EAAE,KAAK,oBAAI,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;AAAA,EACrC;AACA,SAAO;AACT,GAAG;AAUI,SAAS,kBAAkB,OAA+B;AAC/D,QAAM,QAAQ,CAAC,MAAM,SAAS,GAAG,MAAM,WAAW,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC;AACvE,QAAM,SAAS,oBAAI,IAAY;AAG/B,QAAM,KAAK;AACX,aAAW,QAAQ,OAAO;AACxB,eAAW,KAAK,KAAK,SAAS,EAAE,GAAG;AACjC,YAAM,MAAM,EAAE,CAAC;AACf,YAAM,OAAO,EAAE,CAAC;AAChB,UAAI,CAAC,OAAO,CAAC,KAAM;AACnB,YAAM,QAAQ,aAAa,IAAI,GAAG;AAClC,UAAI,UAAU,QAAW;AACvB,YAAI,CAAC,kBAAkB,IAAI,GAAG,GAAG;AAC/B,iBAAO;AAAA,YACL,SAAS,GAAG;AAAA,UACd;AAAA,QACF;AACA;AAAA,MACF;AACA,YAAM,WAAW,CAAC,GAAG,KAAK,EAAE,KAAK,GAAG;AACpC,UAAI,CAAC,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,MAAM,KAAK,WAAW,GAAG,CAAC,GAAG,CAAC,GAAG;AACrD,eAAO;AAAA,UACL,SAAS,GAAG,IAAI,IAAI,YAAY,QAAQ;AAAA,QAC1C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO,CAAC,GAAG,MAAM;AACnB;AAsBA,SAAS,kBAAkB,MAA+C;AACxE,QAAM,QAAkB,CAAC;AACzB,QAAM,OAAiB,CAAC;AAIxB,MAAI,UAAyB;AAC7B,aAAW,QAAQ,KAAK,MAAM,IAAI,GAAG;AACnC,QAAI,WAAW;AACf,QAAI,YAAY,MAAM;AACpB,YAAM,OAAO,KAAK,MAAM,0BAA0B;AAClD,YAAM,MAAM,OAAO,CAAC;AACpB,UAAI,KAAK;AACP,cAAM,OAAO,IAAI,CAAC,KAAK;AACvB,kBAAU,IAAI,OAAO,eAAe,IAAI,IAAI,IAAI,MAAM,YAAY;AAClE,mBAAW;AAAA,MACb;AAAA,IACF,WAAW,QAAQ,KAAK,IAAI,GAAG;AAC7B,gBAAU;AACV,iBAAW;AAAA,IACb;AAGA,UAAM,SAAS,YAAY,QAAQ,CAAC;AACpC,UAAM,KAAK,SAAS,KAAK,IAAI;AAC7B,SAAK,KAAK,SAAS,OAAO,EAAE;AAAA,EAC9B;AACA,SAAO,EAAE,OAAO,MAAM,KAAK,IAAI,GAAG,MAAM,KAAK,KAAK,IAAI,EAAE;AAC1D;AAIA,SAASC,eAAc,MAAsB;AAC3C,QAAM,SAAS,uBAAuB;AACtC,QAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,SAAK,MAAM,CAAC,KAAK,IAAI,KAAK,MAAM,OAAQ,QAAO,IAAI;AAAA,EACrD;AACA,SAAO;AACT;AAaO,SAAS,gBAAgB,MAAgC;AAC9D,QAAM,OAAO,aAAa,IAAI;AAC9B,QAAM,EAAE,OAAO,KAAK,IAAI,kBAAkB,IAAI;AAC9C,QAAM,WAAW,CAAC,GAAG,aAAa,OAAO,UAAU,GAAG,GAAG,aAAa,MAAM,MAAM,CAAC;AACnF,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,UAAU,SACb,OAAO,CAAC,MAAM;AACb,UAAM,MAAM,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI;AAC7B,QAAI,KAAK,IAAI,GAAG,EAAG,QAAO;AAC1B,SAAK,IAAI,GAAG;AACZ,WAAO;AAAA,EACT,CAAC,EACA,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI;AACjC,QAAM,WAAWA,eAAc,IAAI;AACnC,MAAI,aAAa,KAAK,YAAY,QAAQ,CAAC,GAAG,QAAQ,OAAO,mBAAoB,QAAO,CAAC;AACzF,SAAO;AACT;AAIO,SAAS,eAAe,SAAiC;AAC9D,SAAO,GAAG,QAAQ,EAAE,UAAU,QAAQ,IAAI,MAAM,QAAQ,SAAS,IAAI,QAAQ,GAAG;AAClF;;;AHhSA,IAAM,iBAAiB;AACvB,IAAM,UAAU;AAChB,IAAM,WAAW;AAEV,SAAS,iBAAiB,IAA8B;AAC7D,QAAM,IAAI,GAAG,MAAM,cAAc;AACjC,MAAI,CAAC,EAAG,OAAM,IAAI,MAAM,uDAAuD;AAE/E,QAAM,OAAO,EAAE,CAAC;AAChB,MAAI,SAAS,OAAW,OAAM,IAAI,MAAM,uDAAuD;AAC/F,QAAM,KAAK,UAAU,IAAI;AACzB,MAAI,OAAO,IAAI,SAAS,YAAY,OAAO,IAAI,gBAAgB,UAAU;AACvE,UAAM,IAAI,MAAM,0DAA0D;AAAA,EAC5E;AACA,SAAO;AACT;AAEO,SAAS,cAAc,OAAuC;AACnE,QAAM,SAAmB,CAAC;AAC1B,QAAM,WAAqB,CAAC;AAC5B,QAAM,EAAE,MAAM,aAAa,SAAS,IAAI,MAAM;AAC9C,MAAI,CAAC,KAAM,QAAO,KAAK,gBAAgB;AAAA,WAC9B,CAAC,QAAQ,KAAK,IAAI,EAAG,QAAO,KAAK,SAAS,IAAI,2BAA2B;AAClF,MAAI,SAAS,MAAM,GAAG,MAAM,MAAM;AAChC,WAAO,KAAK,QAAQ,SAAS,MAAM,GAAG,CAAC,sBAAsB,IAAI,GAAG;AAAA,EACtE;AACA,MAAI,CAAC,aAAa,KAAK,EAAG,QAAO,KAAK,uBAAuB;AAAA,WACpD,YAAY,SAAS,SAAU,QAAO,KAAK,uBAAuB,QAAQ,QAAQ;AAAA,WAClF,CAAC,yBAAyB,WAAW,GAAG;AAC/C,WAAO,KAAK,kFAA6E;AAAA,EAC3F,WAAW,CAAC,sBAAsB,WAAW,GAAG;AAG9C,WAAO,KAAK,6EAAwE;AAAA,EACtF;AAEA,MAAI,CAAC,UAAU,UAAU,KAAK,EAAG,QAAO,KAAK,6BAA6B;AAC1E,MAAI,CAAC,UAAU,SAAS,KAAK,EAAG,QAAO,KAAK,4BAA4B;AACxE,QAAM,OAAO,OAAO,MAAM,OAAO;AACjC,QAAM,UAAU,gBAAgB,IAAI;AACpC,aAAW,OAAO,QAAS,QAAO,KAAK,6BAA6B,GAAG,EAAE;AACzE,MAAI,CAAC,iBAAiB,IAAI,GAAG;AAC3B,WAAO,KAAK,gBAAgB,cAAc,iBAAiB,KAAK,MAAM,IAAI,EAAE,MAAM,SAAS;AAAA,EAC7F;AACA,QAAM,UAAU,kBAAkB,KAAK;AACvC,aAAW,KAAK;AACd,WAAO,KAAK,cAAc,CAAC,wDAAwD;AACrF,aAAW,KAAK,MAAM,YAAY;AAChC,QAAI,CAAC,oBAAoB,KAAK,EAAE,IAAI,EAAG,QAAO,KAAK,cAAc,EAAE,IAAI,sBAAsB;AAC7F,QAAI,CAAC,EAAE,QAAQ,KAAK,EAAG,QAAO,KAAK,cAAc,EAAE,IAAI,YAAY;AAAA,EACrE;AAGA,aAAW,KAAK,kBAAkB,KAAK,EAAG,QAAO,KAAK,CAAC;AAIvD,aAAW,WAAW,gBAAgB,IAAI,GAAG;AAC3C,UAAM,UAAU,eAAe,OAAO;AACtC,QAAI,QAAQ,SAAS,OAAQ,QAAO,KAAK,OAAO;AAAA,QAC3C,UAAS,KAAK,OAAO;AAAA,EAC5B;AAEA,WAAS,KAAK,GAAG,aAAa,KAAK,CAAC;AACpC,SAAO,EAAE,IAAI,OAAO,WAAW,GAAG,QAAQ,UAAU,SAAS,SAAS,IAAI,WAAW,OAAU;AACjG;AASO,SAAS,UAAU,OAIxB;AACA,QAAM,MAAM,cAAc,KAAK;AAC/B,SAAO,EAAE,MAAM,MAAM,MAAM,QAAQ,IAAI,QAAQ,UAAU,IAAI,YAAY,CAAC,EAAE;AAC9E;AAuBO,SAAS,aAAa,OAAqB,SAAwB,UAAyB;AACjG,QAAM,MAAM,cAAc,KAAK;AAC/B,MAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,kBAAkB,MAAM,IAAI,KAAK,IAAI,OAAO,KAAK,IAAI,CAAC,EAAE;AACrF,MAAI,WAAW,UAAU;AACvB,WAAO,EAAE,MAAM,MAAM,MAAM,OAAO,CAAC,EAAE,MAAM,CAAC,GAAG,MAAM,IAAI,MAAM,GAAG,SAAS,MAAM,KAAK,EAAE,CAAC,EAAE;AAAA,EAC7F;AAEA,QAAM,QAAQ;AAAA,IACZ,EAAE,MAAM,CAAC,UAAU,GAAG,SAAS,MAAM,QAAQ;AAAA,IAC7C,GAAG,MAAM,WAAW,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,cAAc,EAAE,IAAI,GAAG,SAAS,EAAE,QAAQ,EAAE;AAAA,EACvF;AACA,SAAO,EAAE,MAAM,MAAM,MAAM,MAAM;AACnC;AAaA,SAAS,MAAM,OAA6B;AAC1C,QAAM,cAAc;AAAA,IAClB;AAAA,MACE,aAAa,MAAM,YAAY;AAAA,MAC/B,OAAO,CAAC,MAAM;AAAA,MACd,aAAa;AAAA,IACf;AAAA,IACA,EAAE,WAAW,EAAE;AAAA,EACjB,EAAE,QAAQ;AACV,QAAM,OAAO,OAAO,MAAM,OAAO;AAGjC,SAAO;AAAA,EAAQ,WAAW;AAAA;AAAA,EAAU,KAAK,SAAS,IAAI,IAAI,OAAO,GAAG,IAAI;AAAA,CAAI;AAC9E;AAeO,SAAS,mBACd,aACA,SAAwB,UACH;AAGrB,QAAM,OAAO,aAAa,aAAa,MAAM;AAC7C,QAAM,EAAE,SAAS,IAAI,IAAI,YAAY;AACrC,MAAI,oBAAoB,OAAO,KAAK,QAAQ,MAAM;AAChD,WAAO;AAAA,MACL,GAAG;AAAA,MACH,SAAS;AAAA,QACP,YAAY,YAAY;AAAA,QACxB,SAAS,IAAI;AAAA,QACb,MAAM,IAAI;AAAA,QACV,WAAW,IAAI;AAAA,QACf,KAAK,IAAI;AAAA,QACT,KAAK,IAAI;AAAA,MACX;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,gBACpB,WACA,OA2BI,CAAC,GACiB;AACtB,QAAM,SAAwB,KAAK,UAAU;AAC7C,QAAM,EAAE,UAAU,aAAa,IAAI,YAAY;AAAA,IAC7C,YAAY,KAAK;AAAA,IACjB,iBAAiB,KAAK;AAAA,EACxB,CAAC;AACD,QAAM,mBAAmB,KAAK,uBAAuB;AAErD,aAAW,KAAK,UAAU;AACxB,UAAM,MAAM,cAAc,CAAC;AAC3B,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,yBAAyB,EAAE,IAAI,KAAK,IAAI,OAAO,KAAK,IAAI,CAAC,EAAE;AAAA,EAC1F;AACA,MAAI,kBAAkB;AACpB,eAAW,KAAK,cAAc;AAC5B,YAAM,MAAM,cAAc,CAAC;AAC3B,UAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,uBAAuB,EAAE,IAAI,KAAK,IAAI,OAAO,KAAK,IAAI,CAAC,EAAE;AAAA,IACxF;AAAA,EACF;AACA,QAAM,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAC1C,MAAI,aAAa;AACjB,QAAM,UAAoB,CAAC;AAC3B,QAAM,YAAsB,CAAC;AAK7B,QAAM,OAAiB,CAAC;AACxB,QAAM,mBAA6B,CAAC;AACpC,QAAM,YAA6B,CAAC;AAIpC,QAAM,SAAS,oBAAI,IAAqC;AACxD,QAAM,SAAmC,KAAK,kBAAkB;AAKhE,QAAM,cACJ,KAAK,gBACJ,QAAQ,IAAI,yBAAyB,UAAa,QAAQ,IAAI,yBAAyB;AAS1F,QAAM,oBAAoB,OACxB,SACA,SACA,SACA,gBACqC;AACrC,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,SAAS,SAAS,MAAM;AAAA,IAC3C,QAAQ;AACN,iBAAW;AAAA,IACb;AACA,QAAI,aAAa,UAAa,aAAa,SAAS;AAClD,YAAM,OAAO;AACb,UAAI;AACJ,YAAM,aAAa,OAAO,IAAI,IAAI;AAClC,UAAI,eAAe,QAAW;AAC5B,qBAAa;AAAA,MACf,WAAW,eAAe,KAAK,eAAe,QAAW;AACvD,cAAM,MAA4B;AAAA,UAChC,SAAS;AAAA,UACT;AAAA,UACA,UAAU;AAAA,UACV,MAAM;AAAA,QACR;AACA,cAAM,MAAM,MAAM,KAAK,WAAW,GAAG;AACrC,cAAM,YAAqC,OAAO,QAAQ,WAAW,MAAM,IAAI;AAC/E,YAAI,OAAO,QAAQ,YAAY,IAAI,eAAe,MAAM;AACtD,iBAAO,IAAI,MAAM,SAAS;AAAA,QAC5B;AACA,qBAAa;AAAA,MACf,OAAO;AACL,qBAAa,WAAW,aAAa,aAAa;AAAA,MACpD;AACA,gBAAU,KAAK;AAAA,QACb,MAAM;AAAA,QACN,MAAM;AAAA,QACN,YAAY,WAAW,UAAU,OAAO;AAAA,QACxC,aAAa,YAAY,QAAQ;AAAA,QACjC,aAAa,YAAY,OAAO;AAAA,QAChC;AAAA,MACF,CAAC;AACD,UAAI,eAAe,UAAU;AAC3B,cAAM,IAAI,MAAM,mCAAmC,OAAO,EAAE;AAAA,MAC9D;AACA,UAAI,eAAe,YAAY;AAE7B,eAAO;AAAA,MACT;AACA,UAAI,eAAe,UAAU;AAE3B,cAAM,YAAY,SAAS,QAAQ;AAAA,MACrC,WAAW,eAAe,aAAa;AAIrC,cAAM,UAAU,MAAM,YAAY,SAAS,OAAO,GAAG,SAAS,MAAM;AACpE,eAAO;AAAA,MACT;AAAA,IAEF;AACA,UAAM,UAAU,SAAS,SAAS,MAAM;AACxC,QAAI,YAAa;AACjB,WAAO;AAAA,EACT;AAMA,QAAM,OAAO,WAAW;AAMxB,aAAW,KAAK,UAAU;AACxB,UAAM,WAAW,aAAa,GAAG,MAAM;AACvC,QAAI,eAAe;AACnB,eAAW,KAAK,SAAS,OAAO;AAC9B,YAAM,OAAO,OAAOC,MAAK,WAAW,GAAG,EAAE,IAAI,IAAIA,MAAK,WAAW,EAAE,MAAM,GAAG,EAAE,IAAI;AAClF,YAAM,MAAMC,SAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,YAAM,MAAM,OAAO,EAAE,KAAK,KAAK,GAAG,IAAI,CAAC,EAAE,MAAM,GAAG,EAAE,IAAI,EAAE,KAAK,GAAG;AAClE,YAAM,UAAU,MAAM,kBAAkB,MAAM,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,MAAM,UAAU;AACtF,UAAI,YAAY,YAAa,gBAAe;AAAA,IAC9C;AACA,SAAK,KAAK,EAAE,IAAI;AAChB,QAAI,aAAc,WAAU,KAAK,EAAE,IAAI;AAAA,QAClC,SAAQ,KAAK,EAAE,IAAI;AAAA,EAC1B;AACA,MAAI,kBAAkB;AACpB,eAAW,KAAK,cAAc;AAC5B,YAAM,WAAW,mBAAmB,GAAG,MAAM;AAC7C,UAAI,eAAe;AACnB,iBAAW,KAAK,SAAS,OAAO;AAC9B,cAAM,OAAO,OAAOD,MAAK,WAAW,GAAG,EAAE,IAAI,IAAIA,MAAK,WAAW,EAAE,MAAM,GAAG,EAAE,IAAI;AAClF,cAAM,MAAMC,SAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,cAAM,MAAM,OAAO,EAAE,KAAK,KAAK,GAAG,IAAI,CAAC,EAAE,MAAM,GAAG,EAAE,IAAI,EAAE,KAAK,GAAG;AAClE,cAAM,UAAU,MAAM,kBAAkB,MAAM,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,MAAM,UAAU;AACtF,YAAI,YAAY,YAAa,gBAAe;AAAA,MAC9C;AAIA,WAAK,KAAK,EAAE,IAAI;AAChB,UAAI,aAAc,WAAU,KAAK,EAAE,IAAI;AAAA,UAClC,SAAQ,KAAK,EAAE,IAAI;AACxB,uBAAiB,KAAK,EAAE,IAAI;AAAA,IAC9B;AAAA,EACF;AAuBA,QAAM,OAAO,IAAI,IAAI,IAAI;AACzB,QAAM,SAAmB,CAAC;AAC1B,QAAM,qBAA+B,CAAC;AACtC,MAAI,aAAyC,CAAC;AAC9C,MAAI;AACF,iBAAa,MAAM,QAAQ,WAAW,EAAE,eAAe,KAAK,CAAC;AAAA,EAC/D,QAAQ;AACN,iBAAa,CAAC;AAAA,EAChB;AACA,aAAW,OAAO,YAAY;AAC5B,QAAI,CAAC,IAAI,KAAK,WAAW,OAAO,EAAG;AACnC,QAAI,MAAM;AAGR,UAAI,IAAI,OAAO,GAAG;AAChB,cAAM,WAAW,IAAI,KAAK,MAAM,wCAAwC;AACxE,cAAM,YAAY,WAAW,CAAC;AAC9B,YAAI,aAAa,KAAK,IAAI,SAAS,EAAG;AAGtC,YAAI,MAAM,eAAeD,MAAK,WAAW,IAAI,IAAI,CAAC,GAAG;AACnD,6BAAmB,KAAK,IAAI,IAAI;AAChC;AAAA,QACF;AAAA,MACF,WAAW,IAAI,YAAY,GAAG;AAQ5B,YAAI,MAAM,2BAA2BA,MAAK,WAAW,IAAI,IAAI,CAAC,GAAG;AAC/D,6BAAmB,KAAK,IAAI,IAAI;AAChC;AAAA,QACF;AAAA,MACF,OAAO;AACL;AAAA,MACF;AAAA,IACF,OAAO;AAEL,UAAI,CAAC,IAAI,YAAY,EAAG;AACxB,UAAI,KAAK,IAAI,IAAI,IAAI,EAAG;AAGxB,UAAI,MAAM,oBAAoBA,MAAK,WAAW,IAAI,IAAI,CAAC,GAAG;AACxD,2BAAmB,KAAK,IAAI,IAAI;AAChC;AAAA,MACF;AAAA,IACF;AAIA,QAAI;AACF,YAAM,GAAGA,MAAK,WAAW,IAAI,IAAI,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACpE,aAAO,KAAK,IAAI,IAAI;AAAA,IACtB,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AAAA,IACL,KAAK;AAAA,IACL;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA;AAAA,IACA,GAAI,mBAAmB,SAAS,IAAI,EAAE,mBAAmB,IAAI,CAAC;AAAA,IAC9D,GAAI,UAAU,SAAS,IAAI,EAAE,UAAU,IAAI,CAAC;AAAA,EAC9C;AACF;AAOA,eAAe,oBAAoB,QAAkC;AACnE,MAAI;AACF,UAAM,UAAU,MAAM,SAASA,MAAK,QAAQ,UAAU,GAAG,MAAM;AAC/D,UAAM,IAAI,QAAQ,MAAM,6CAA6C;AACrE,QAAI,CAAC,EAAG,QAAO;AACf,UAAM,KAAK,UAAU,EAAE,CAAC,KAAK,EAAE;AAC/B,UAAM,UAAU,SAAS,MAAM;AAC/B,WAAO,OAAO,IAAI,SAAS,YAAY,GAAG,SAAS;AAAA,EACrD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAe,eAAe,SAAmC;AAC/D,MAAI;AACF,UAAM,KAAK,MAAM,SAAS,SAAS,MAAM;AACzC,UAAM,IAAI,GAAG,MAAM,6CAA6C;AAChE,QAAI,CAAC,EAAG,QAAO;AACf,UAAM,KAAK,UAAU,EAAE,CAAC,KAAK,EAAE;AAC/B,WAAO,IAAI,gBAAgB;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOA,eAAe,2BAA2B,QAAkC;AAC1E,MAAI;AACF,UAAM,UAAU,MAAM,QAAQ,MAAM;AACpC,QAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAI,eAAe;AACnB,eAAW,QAAQ,SAAS;AAC1B,UAAI,CAAC,KAAK,SAAS,MAAM,EAAG;AAC5B,UAAI,CAAE,MAAM,eAAeA,MAAK,QAAQ,IAAI,CAAC,GAAI;AAC/C,uBAAe;AACf;AAAA,MACF;AAAA,IACF;AACA,WAAO,CAAC;AAAA,EACV,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAIA,SAAS,WAAW,GAAW,GAAmB;AAChD,MAAI,MAAM,EAAG,QAAO;AACpB,QAAM,IAAI,EAAE,MAAM,IAAI;AACtB,QAAM,IAAI,EAAE,MAAM,IAAI;AACtB,MAAI,EAAE,WAAW,KAAK,EAAE,WAAW,EAAG,QAAO;AAC7C,QAAM,IAAI,EAAE;AACZ,QAAM,IAAI,EAAE;AACZ,QAAM,KAAiB,MAAM,KAAK,EAAE,QAAQ,IAAI,EAAE,GAAG,MAAM,IAAI,MAAc,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC;AAC3F,WAAS,IAAI,IAAI,GAAG,KAAK,GAAG,KAAK;AAC/B,UAAM,MAAM,GAAG,CAAC,KAAK,CAAC;AACtB,UAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC;AAC3B,UAAM,KAAK,EAAE,CAAC,KAAK;AACnB,aAAS,IAAI,IAAI,GAAG,KAAK,GAAG,KAAK;AAC/B,YAAM,KAAK,EAAE,CAAC,KAAK;AACnB,UAAI,CAAC,IAAI,OAAO,MAAM,KAAK,IAAI,CAAC,KAAK,KAAK,IAAI,KAAK,IAAI,KAAK,CAAC,KAAK,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC;AAAA,IACtF;AAAA,EACF;AACA,QAAM,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK;AAC1B,SAAO,IAAI,MAAM,IAAI,IAAK,IAAI,OAAQ,IAAI;AAC5C;AAKA,eAAe,YAAY,KAAa,QAA+B;AACrE,QAAM,EAAE,OAAO,IAAI,MAAM,OAAO,aAAkB;AAClD,QAAM,OAAO,MAAM,YAAY,KAAK,MAAM;AAC1C,QAAM,OAAO,KAAK,IAAI;AACxB;AAEA,eAAe,YAAY,KAAa,QAAiC;AACvE,SAAO,gBAAgB,KAAK,MAAM;AACpC;;;AO9jBA,SAAS,eAAAE,cAAa,gBAAAC,qBAAoB;AAC1C,SAAS,WAAAC,UAAS,QAAAC,aAAY;AAC9B,SAAS,iBAAAC,sBAAqB;AAkC9B,SAAS,eAAe,GAA2B;AACjD,MAAI,OAAO,MAAM,YAAY,MAAM,KAAM,OAAM,IAAI,MAAM,6BAA6B;AACtF,QAAM,EAAE,MAAM,MAAM,IAAI;AACxB,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,UAAI,OAAO,UAAU,YAAY,MAAM,WAAW,GAAG;AACnD,cAAM,IAAI,MAAM,cAAc,OAAO,IAAI,CAAC,qCAAqC;AAAA,MACjF;AACA,aAAO,EAAE,MAAM,MAAM;AAAA,IACvB,KAAK;AACH,UAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,GAAG;AACxD,cAAM,IAAI,MAAM,uDAAuD;AAAA,MACzE;AACA,aAAO,EAAE,MAAM,MAAM;AAAA,IACvB;AACE,YAAM,IAAI,MAAM,2BAA2B,OAAO,IAAI,CAAC,GAAG;AAAA,EAC9D;AACF;AAGO,SAAS,eAAe,KAAyB;AACtD,MAAI,OAAO,QAAQ,YAAY,QAAQ,KAAM,OAAM,IAAI,MAAM,8BAA8B;AAC3F,QAAM,EAAE,YAAY,MAAM,IAAI;AAC9B,MAAI,OAAO,eAAe,YAAY,WAAW,WAAW,GAAG;AAC7D,UAAM,IAAI,MAAM,qDAAqD;AAAA,EACvE;AACA,MAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAAG;AAC/C,UAAM,IAAI,MAAM,kBAAkB,UAAU,qCAAqC;AAAA,EACnF;AACA,QAAM,SAAsB,MAAM,IAAI,CAAC,GAAG,MAAM;AAC9C,QAAI,OAAO,MAAM,YAAY,MAAM,KAAM,OAAM,IAAI,MAAM,QAAQ,CAAC,qBAAqB;AACvF,UAAM,EAAE,IAAI,QAAQ,iBAAiB,WAAW,IAAI;AAMpD,QAAI,OAAO,OAAO,YAAY,GAAG,WAAW;AAC1C,YAAM,IAAI,MAAM,QAAQ,CAAC,wBAAwB;AACnD,QAAI,OAAO,WAAW,YAAY,OAAO,WAAW,GAAG;AACrD,YAAM,IAAI,MAAM,QAAQ,EAAE,2BAA2B;AAAA,IACvD;AACA,QAAI,OAAO,oBAAoB,UAAU;AACvC,YAAM,IAAI,MAAM,QAAQ,EAAE,oCAAoC;AAAA,IAChE;AACA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA,YAAY,MAAM,QAAQ,UAAU,IAAI,WAAW,IAAI,cAAc,IAAI;AAAA,IAC3E;AAAA,EACF,CAAC;AACD,SAAO,EAAE,YAAY,OAAO,OAAO;AACrC;AAGO,SAAS,cACd,QACA,YACuC;AACvC,QAAM,WAAqB,CAAC;AAC5B,aAAW,KAAK,YAAY;AAC1B,YAAQ,EAAE,MAAM;AAAA,MACd,KAAK;AACH,YAAI,CAAC,OAAO,SAAS,EAAE,KAAK,EAAG,UAAS,KAAK,wBAAwB,EAAE,KAAK,GAAG;AAC/E;AAAA,MACF,KAAK;AACH,YAAI,OAAO,SAAS,EAAE,KAAK,EAAG,UAAS,KAAK,4BAA4B,EAAE,KAAK,GAAG;AAClF;AAAA,MACF,KAAK;AACH,YAAI,CAAC,IAAI,OAAO,EAAE,OAAO,GAAG,EAAE,KAAK,MAAM,EAAG,UAAS,KAAK,sBAAsB,EAAE,KAAK,GAAG;AAC1F;AAAA,MACF,KAAK;AACH,YAAI,OAAO,SAAS,EAAE;AACpB,mBAAS,KAAK,sBAAsB,EAAE,KAAK,SAAS,OAAO,MAAM,GAAG;AACtE;AAAA,IACJ;AAAA,EACF;AACA,SAAO,EAAE,MAAM,SAAS,WAAW,GAAG,SAAS;AACjD;AAIA,IAAMC,QAAOH,SAAQE,eAAc,YAAY,GAAG,CAAC;AACnD,IAAME,YAAWJ,SAAQG,KAAI;AACtB,IAAM,YAAYF,MAAKG,WAAU,OAAO;AAQxC,SAAS,eAAe,MAAc,WAAwB;AACnE,MAAI;AACJ,MAAI;AACF,cAAUN,aAAY,KAAK,EAAE,eAAe,KAAK,CAAC,EAC/C,OAAO,CAAC,MAAM,EAAE,YAAY,CAAC,EAC7B,IAAI,CAAC,MAAM,EAAE,IAAI,EACjB,KAAK;AAAA,EACV,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,QAAM,SAAsB,CAAC;AAC7B,aAAW,QAAQ,SAAS;AAC1B,UAAM,OAAOG,MAAK,KAAK,MAAM,YAAY;AACzC,QAAI;AACF,YAAM,MAAM,KAAK,MAAMF,cAAa,MAAM,MAAM,CAAC;AACjD,aAAO,KAAK,eAAe,GAAG,CAAC;AAAA,IACjC,SAAS,KAAK;AAGZ,UAAI;AACF,QAAAA,cAAa,MAAM,MAAM;AACzB,cAAM;AAAA,MACR,SAAS,IAAI;AACX,YAAI,OAAO,IAAK,OAAM;AAAA,MAExB;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAQO,SAAS,cACd,OACA,YAC0D;AAC1D,SAAO,MAAM,MAAM,IAAI,CAAC,MAAM;AAQ5B,QAAI,cAAc,EAAE,EAAE,MAAM,aAAa;AACvC,aAAO,EAAE,IAAI,EAAE,IAAI,MAAM,OAAO,UAAU,CAAC,iCAAiC,EAAE,EAAE,GAAG,EAAE;AAAA,IACvF;AACA,UAAM,SAAS,aAAc,WAAW,EAAE,EAAE,KAAK,EAAE,kBAAmB,EAAE;AACxE,WAAO;AAAA,MACL,IAAI,EAAE;AAAA,MACN,MAAM,EAAE,aAAa,cAAc,QAAQ,EAAE,UAAU,EAAE,OAAO;AAAA,MAChE,UAAU,EAAE,aAAa,cAAc,QAAQ,EAAE,UAAU,EAAE,WAAW,CAAC;AAAA,IAC3E;AAAA,EACF,CAAC;AACH;;;AC9MA,IAAM,SAAS;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGA,IAAM,wBAAwB;AAG9B,SAAS,UAAU,MAAsB;AACvC,SAAO,KAAK,QAAQ,mBAAmB,EAAE;AAC3C;AAIA,SAAS,OAAO,MAAc,OAAuB;AACnD,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,OAAO,IAAK,KAAI,KAAK,CAAC,MAAM,KAAM;AACtD,SAAO;AACT;AAGA,SAAS,WAAW,MAAc,IAAoB;AACpD,QAAM,QAAQ,GAAG,KAAK,IAAI;AAC1B,SAAO,QAAQ,MAAM,QAAQ;AAC/B;AAIA,UAAU,aAAa,MAAyD;AAC9E,MAAI,YAAY;AAChB,MAAI,YAAY;AAChB,MAAI,OAAO;AACX,aAAW,OAAO,KAAK,MAAM,IAAI,GAAG;AAClC,QAAI,IAAI,KAAK,MAAM,IAAI;AACrB,UAAI,UAAU,KAAK,MAAM,GAAI,OAAM,EAAE,MAAM,WAAW,MAAM,UAAU;AACtE,kBAAY;AACZ,kBAAY,OAAO;AAAA,IACrB,OAAO;AACL,UAAI,cAAc,GAAI,aAAY;AAClC,oBAAc,cAAc,KAAK,KAAK,QAAQ;AAAA,IAChD;AACA;AAAA,EACF;AACA,MAAI,UAAU,KAAK,MAAM,GAAI,OAAM,EAAE,MAAM,WAAW,MAAM,UAAU;AACxE;AAEO,SAAS,kBAAkB,MAAgC;AAChE,QAAM,QAAQ,UAAU,IAAI;AAC5B,QAAM,WAA6B,CAAC;AAEpC,aAAW,SAAS,QAAQ;AAC1B,UAAM,QAAQ,WAAW,OAAO,IAAI,OAAO,OAAO,GAAG,CAAC;AACtD,QAAI,UAAU,IAAI;AAChB,eAAS,KAAK;AAAA,QACZ,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,MAAM,OAAO,OAAO,KAAK;AAAA,QACzB,MAAM;AAAA,QACN,WAAW;AAAA,QACX,KAAK;AAAA,MACP,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,gBAAgB,WAAW,OAAO,kCAAkC;AAC1E,MAAI,kBAAkB,IAAI;AACxB,aAAS,KAAK;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,MAAM,OAAO,OAAO,aAAa;AAAA,MACjC,MAAM;AAAA,MACN,WAAW;AAAA,MACX,KAAK;AAAA,IACP,CAAC;AAAA,EACH;AAEA,aAAW,EAAE,MAAM,WAAW,KAAK,KAAK,aAAa,KAAK,GAAG;AAC3D,UAAM,SAAS,UAAU,MAAM,IAAI,KAAK,CAAC,GAAG;AAC5C,QAAI,QAAQ,uBAAuB;AACjC,eAAS,KAAK;AAAA,QACZ,IAAI;AAAA,QACJ,MAAM;AAAA,QACN;AAAA,QACA,MAAM,GAAG,KAAK;AAAA,QACd,WAAW;AAAA,QACX,KAAK;AAAA,MACP,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;;;ACvFO,IAAM,iBAAiB;AA6B9B,SAAS,iBAAiB,MAAsB;AAC9C,SAAO,KAAK,QAAQ,UAAU,EAAE,KAAK;AACvC;AAGA,SAAS,QAAQ,GAAiB,MAAqD;AACrF,QAAM,WAAW,EAAE,YAAY,UAAU,UAAU,KAAK,KAAK,iBAAiB,EAAE,IAAI;AACpF,QAAM,UAAU,EAAE,YAAY,UAAU,SAAS,KAAK,KAAK;AAC3D,QAAM,SAA0B,EAAE,QAAQ,SAAS,aAAa,IAAI,SAAS;AAC7E,QAAM,QAAQ,EAAE,QAAQ,MAAM,IAAI,EAAE;AACpC,SAAO;AAAA,IACL,MAAM,EAAE;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA;AAAA,IAGA,WAAW;AAAA,IACX,aAAa,OAAO,EAAE,YAAY,gBAAgB,WAAW,EAAE,YAAY,cAAc;AAAA,IACzF,gBAAgB,EAAE,WAAW;AAAA,IAC7B;AAAA,EACF;AACF;AAQO,SAAS,gBAAsC;AACpD,QAAM,EAAE,UAAU,aAAa,IAAI,YAAY;AAC/C,SAAO;AAAA,IACL,GAAG,SAAS,IAAI,CAAC,MAAM,QAAQ,GAAG,SAAS,CAAC;AAAA,IAC5C,GAAG,aAAa,IAAI,CAAC,MAAM,QAAQ,GAAG,aAAa,CAAC;AAAA,EACtD,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAC/C;AAGO,SAAS,mBAAmB,UAAwC;AACzE,SAAO,cAAc,EAAE,OAAO,CAAC,MAAM,EAAE,aAAa,QAAQ;AAC9D;;;AC/EO,IAAM,gBAAgB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,gBAAgB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,iBAAiB;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,gBAAgB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,iBAAoC,CAAC,SAAS;AAG3D,SAASM,WAAU,MAAsB;AACvC,SAAO,KAAK,QAAQ,mBAAmB,EAAE;AAC3C;AAKA,SAAS,OAAO,MAAsB;AACpC,SAAO,IAAI,OAAO,MAAM,KAAK,QAAQ,SAAS,SAAS,CAAC,OAAO,GAAG;AACpE;AAEA,IAAM,oBAAoB;AAI1B,SAASC,QAAO,MAAc,OAAuB;AACnD,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,OAAO,IAAK,KAAI,KAAK,CAAC,MAAM,KAAM;AACtD,SAAO;AACT;AAGA,SAASC,YAAW,MAAc,IAAoB;AACpD,QAAM,QAAQ,GAAG,KAAK,IAAI;AAC1B,SAAO,QAAQ,MAAM,QAAQ;AAC/B;AAIA,UAAUC,cAAa,MAAyD;AAC9E,MAAI,YAAY;AAChB,MAAI,YAAY;AAChB,MAAI,OAAO;AACX,aAAW,OAAO,KAAK,MAAM,IAAI,GAAG;AAClC,QAAI,IAAI,KAAK,MAAM,IAAI;AACrB,UAAI,UAAU,KAAK,MAAM,GAAI,OAAM,EAAE,MAAM,WAAW,MAAM,UAAU;AACtE,kBAAY;AACZ,kBAAY,OAAO;AAAA,IACrB,OAAO;AACL,UAAI,cAAc,GAAI,aAAY;AAClC,oBAAc,cAAc,KAAK,KAAK,QAAQ;AAAA,IAChD;AACA;AAAA,EACF;AACA,MAAI,UAAU,KAAK,MAAM,GAAI,OAAM,EAAE,MAAM,WAAW,MAAM,UAAU;AACxE;AAEO,SAAS,aAAa,MAAgC;AAC3D,QAAM,QAAQH,WAAU,IAAI;AAC5B,QAAM,WAA6B,CAAC;AAEpC,aAAW,QAAQ,eAAe;AAChC,QAAI,eAAe,SAAS,IAAI,EAAG;AACnC,UAAM,QAAQE,YAAW,OAAO,OAAO,IAAI,CAAC;AAC5C,QAAI,UAAU,IAAI;AAChB,eAAS,KAAK;AAAA,QACZ,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,MAAMD,QAAO,OAAO,KAAK;AAAA,QACzB,MAAM;AAAA,QACN,WAAW;AAAA,QACX,KAAK,YAAY,IAAI;AAAA,MACvB,CAAC;AAAA,IACH;AAAA,EACF;AAEA,aAAW,EAAE,MAAM,WAAW,KAAK,KAAKE,cAAa,KAAK,GAAG;AAC3D,UAAM,OAAO,cAAc;AAAA,MACzB,CAAC,MAAM,CAAC,eAAe,SAAS,CAAC,KAAK,OAAO,CAAC,EAAE,KAAK,SAAS;AAAA,IAChE,EAAE;AACF,QAAI,QAAQ,mBAAmB;AAC7B,eAAS,KAAK;AAAA,QACZ,IAAI;AAAA,QACJ,MAAM;AAAA,QACN;AAAA,QACA,MAAM,GAAG,IAAI;AAAA,QACb,WAAW;AAAA,QACX,KAAK;AAAA,MACP,CAAC;AAAA,IACH;AAAA,EACF;AAEA,aAAW,UAAU,CAAC,GAAG,gBAAgB,GAAG,aAAa,GAAG;AAC1D,UAAM,QAAQD,YAAW,OAAO,IAAI,OAAO,OAAO,QAAQ,uBAAuB,MAAM,GAAG,GAAG,CAAC;AAC9F,QAAI,UAAU,IAAI;AAChB,eAAS,KAAK;AAAA,QACZ,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,MAAMD,QAAO,OAAO,KAAK;AAAA,QACzB,MAAM;AAAA,QACN,WAAW;AAAA,QACX,KAAK;AAAA,MACP,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;","names":["dirname","join","literal","exemptionLine","join","dirname","readdirSync","readFileSync","dirname","join","fileURLToPath","HERE","PKG_ROOT","proseOnly","lineOf","firstMatch","paragraphsOf"]}
|
|
1
|
+
{"version":3,"sources":["../src/compiler.ts","../src/discover.ts","../src/integrations-schema.ts","../src/quality.ts","../src/debt-marker.ts","../src/residue.ts","../src/hygiene.ts","../src/evals.ts","../src/text-scan.ts","../src/humanizer.ts","../src/registry.ts","../src/slop.ts"],"sourcesContent":["import { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { basename, dirname, join } from 'node:path';\nimport { sha256Hex12, uniqueAsideSync } from '@noir-ai/core';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport { discoverAll, discoverBuiltin } from './discover.js';\nimport { runtimeEmitsHostMcp } from './integrations-schema.js';\nimport {\n artifactPathDrift,\n bodyOf,\n chainedReferences,\n hygieneFindings,\n hygieneMessage,\n isWhatWhenDescription,\n lintWarnings,\n looksLikeWhenDescription,\n MAX_BODY_LINES,\n missingSections,\n withinLineBudget,\n} from './quality.js';\n\n// Re-exported for backward-compat — callers (hygiene tests, CLI) import these\n// from './compiler.js'. The single source of truth is quality.ts (where the\n// body-structure rules live); these aliases keep the old import surface stable.\nexport { bodyOf, looksLikeWhenDescription } from './quality.js';\n\nimport type {\n BuiltinSkill,\n CompiledIntegration,\n CompiledSkill,\n CompileTarget,\n EmitSummary,\n IntegrationSkill,\n SkillConflict,\n SkillConflictContext,\n SkillConflictResolution,\n SkillConflictResolver,\n SkillFrontmatter,\n ValidationResult,\n} from './types.js';\n\nconst FRONTMATTER_RE = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/;\nconst NAME_RE = /^noir-[a-z0-9]+(?:-[a-z0-9]+)*$/;\nconst MAX_DESC = 1024;\n\nexport function parseFrontmatter(md: string): SkillFrontmatter {\n const m = md.match(FRONTMATTER_RE);\n if (!m) throw new Error('Skill missing YAML frontmatter (expected --- ... ---)');\n // Group 1 always exists when the regex matches; guard past noUncheckedIndexedAccess.\n const yaml = m[1];\n if (yaml === undefined) throw new Error('Skill missing YAML frontmatter (expected --- ... ---)');\n const fm = parseYaml(yaml) as SkillFrontmatter;\n if (typeof fm?.name !== 'string' || typeof fm?.description !== 'string') {\n throw new Error('Skill frontmatter requires string `name` + `description`');\n }\n return fm;\n}\n\nexport function validateSkill(skill: BuiltinSkill): ValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n const { name, description, metadata } = skill.frontmatter;\n if (!name) errors.push('missing `name`');\n else if (!NAME_RE.test(name)) errors.push(`name \"${name}\" must match noir-<kebab>`);\n if (basename(skill.dir) !== name) {\n errors.push(`dir \"${basename(skill.dir)}\" must equal name \"${name}\"`);\n }\n if (!description?.trim()) errors.push('missing `description`');\n else if (description.length > MAX_DESC) errors.push(`description exceeds ${MAX_DESC} chars`);\n else if (!looksLikeWhenDescription(description)) {\n errors.push('description must state WHEN to trigger (e.g. \"Use when…\"), not WHAT it does');\n } else if (!isWhatWhenDescription(description)) {\n // The description must ALSO carry a WHAT clause — the trigger phrase\n // alone (WHEN-only) fails. Errors, not warnings: it's part of the contract.\n errors.push('description must be WHAT+WHEN — a WHAT clause after the trigger phrase');\n }\n // Structural gate: metadata, required sections, line budget, one-level refs.\n if (!metadata?.category?.trim()) errors.push('missing `metadata.category`');\n if (!metadata?.version?.trim()) errors.push('missing `metadata.version`');\n const body = bodyOf(skill.skillMd);\n const missing = missingSections(body);\n for (const sec of missing) errors.push(`missing required section: ${sec}`);\n if (!withinLineBudget(body)) {\n errors.push(`body exceeds ${MAX_BODY_LINES}-line budget (${body.split('\\n').length} lines)`);\n }\n const chained = chainedReferences(skill);\n for (const r of chained)\n errors.push(`reference \"${r}\" chains to another reference (must be one level deep)`);\n for (const r of skill.references) {\n if (!/^[a-z0-9-]+\\.md$/i.test(r.name)) errors.push(`reference \"${r.name}\" must be <kebab>.md`);\n if (!r.content.trim()) errors.push(`reference \"${r.name}\" is empty`);\n }\n // Generated-artifact standard: no `.noir/` output-path drift in the body\n // or references (unknown dir, or a filename missing its type-code prefix).\n for (const d of artifactPathDrift(skill)) errors.push(d);\n // Output hygiene (the rules in hygiene.ts): a fail-tier finding blocks\n // emission exactly as a missing section does, and a warn-tier finding is\n // advisory like the style warnings below.\n for (const finding of hygieneFindings(body)) {\n const message = hygieneMessage(finding);\n if (finding.tier === 'fail') errors.push(message);\n else warnings.push(message);\n }\n // Soft warnings (lint-level) — advisory, non-failing.\n warnings.push(...lintWarnings(skill));\n return { ok: errors.length === 0, errors, warnings: warnings.length > 0 ? warnings : undefined };\n}\n\n/**\n * `lintSkill` — the soft quality gate. Errors = `validateSkill` errors (a\n * skill that fails validation is broken, the hygiene fail tier included);\n * warnings = the hygiene warn tier plus the `quality.ts` style rules (thin\n * body, no examples, first-person narration, …). A skill can validate clean\n * yet still carry lint warnings the author should resolve.\n */\nexport function lintSkill(skill: BuiltinSkill): {\n name: string;\n errors: string[];\n warnings: string[];\n} {\n const res = validateSkill(skill);\n return { name: skill.name, errors: res.errors, warnings: res.warnings ?? [] };\n}\n\n/**\n * Compile a builtin skill into the host-shaped `CompiledSkill`. The validator\n * runs for every target (a malformed skill is malformed in any format).\n *\n * - `claude` | `agents-md` | `gemini` | `opencode` (the AGENTS.md-aligned\n * hosts): canonical format copied verbatim — `SKILL.md` plus its\n * `references/` siblings. These hosts read the same SKILL.md shape;\n * the EMIT-LOCATION (which dir they land in) is the cli/adapter's job, not\n * the compiler's.\n * - `cursor`: transform into a Cursor `.mdc` rule — ONE file `<name>.mdc`\n * whose frontmatter carries the skill description, a wildcard globs entry,\n * and `alwaysApply: false` (the agent decides whether to pull the rule via\n * the description; never auto-applied). The body is the SKILL.md BODY\n * (frontmatter stripped). Cursor's rule format has no references concept,\n * so references are dropped (documented as a known limitation — the body is\n * the surface).\n *\n * The `target` defaults to `'claude'` for backward compatibility with every\n * existing caller; the verbatim branch produces byte-identical output to v1.1\n * (the regression anchor).\n */\nexport function compileSkill(skill: BuiltinSkill, target: CompileTarget = 'claude'): CompiledSkill {\n const res = validateSkill(skill);\n if (!res.ok) throw new Error(`Cannot compile ${skill.name}: ${res.errors.join('; ')}`);\n if (target === 'cursor') {\n return { name: skill.name, files: [{ path: [`${skill.name}.mdc`], content: toMdc(skill) }] };\n }\n // claude / agents-md / gemini / opencode → canonical verbatim format.\n const files = [\n { path: ['SKILL.md'], content: skill.skillMd },\n ...skill.references.map((r) => ({ path: ['references', r.name], content: r.content })),\n ];\n return { name: skill.name, files };\n}\n\n/**\n * Cursor `.mdc` rule transform — frontmatter `{description, globs, alwaysApply:\n * false}` + the SKILL.md body. The description drives Cursor's agent-decided\n * rule selection (the default: `alwaysApply: false`).\n * Cursor's rule format has no references/ concept, so the body alone is the\n * surface; references are dropped (documented risk in the spec).\n *\n * Frontmatter is YAML-serialized via the `yaml` package (already a dep) so\n * description escaping (colons, quotes, multi-line) is correct without hand\n * rolling. `lineWidth: 0` keeps descriptions on a single quoted line (predictable\n * byte shape; Cursor tolerates either). */\nfunction toMdc(skill: BuiltinSkill): string {\n const frontmatter = stringifyYaml(\n {\n description: skill.frontmatter.description,\n globs: ['**/*'],\n alwaysApply: false,\n },\n { lineWidth: 0 },\n ).trimEnd();\n const body = bodyOf(skill.skillMd);\n // Cursor's `.mdc` shape: leading `---\\n`, frontmatter, closing `---\\n`, blank\n // line, body. Trailing newline so files concatenate cleanly on disk.\n return `---\\n${frontmatter}\\n---\\n${body.endsWith('\\n') ? body : `${body}\\n`}`;\n}\n\n/**\n * Compile an integration: validates the SKILL.md (same shape as builtins) +\n * renders the same per-skill files. When the integration's `runtime` widens\n * emission (`mcp-stdio`/`external-mcp`) AND it carries a non-null `mcp`\n * declaration, also expose a provider-neutral `hostMcp` server block the host\n * adapter may merge into the host MCP config (e.g. Claude's `.mcp.json`).\n *\n * For `gated-write-proxy` (ClickUp) + `none` → no `hostMcp` (writes route\n * through Noir's own MCP tool; the host MCP config is unchanged). For\n * `mcp-stdio` the hostMcp is exposed (the compiler widens emission); the\n * Claude adapter still does not emit a NEW server entry for it (Noir's own\n * `noir mcp serve` entry already covers it).\n */\nexport function compileIntegration(\n integration: IntegrationSkill,\n target: CompileTarget = 'claude',\n): CompiledIntegration {\n // Validate the skill half first (throws on a bad SKILL.md — same path as\n // builtins). compileSkill returns a base CompiledSkill; we layer hostMcp on.\n const base = compileSkill(integration, target);\n const { runtime, mcp } = integration.declaration;\n if (runtimeEmitsHostMcp(runtime) && mcp !== null) {\n return {\n ...base,\n hostMcp: {\n serverName: integration.name,\n command: mcp.command,\n args: mcp.args,\n transport: mcp.transport,\n url: mcp.url,\n env: mcp.env,\n },\n };\n }\n return base;\n}\n\nexport async function emitSkillsToDir(\n targetDir: string,\n opts: {\n builtinDir?: string;\n integrationsDir?: string;\n includeIntegrations?: boolean;\n /** CompileTarget — selects the per-skill transform. Defaults to\n * `'claude'` (the v1.1 verbatim SKILL.md shape) so every existing caller\n * stays byte-identical. `'cursor'` compiles each skill to a `.mdc` rule\n * file (frontmatter `description`/`globs`/`alwaysApply:false` + the\n * SKILL.md body); the other targets map to the verbatim shape (their\n * hosts have no skill concept, so emit is skipped upstream — but the\n * default-verbatim policy keeps the signature total). */\n target?: CompileTarget;\n /** Policy for a skill file that exists AND differs from the compiled\n * bytes, when no {@link onConflict} resolver is wired (or non-interactive).\n * Default `'overwrite'` preserves the v1.2 behavior (every sync clobbers).\n * `'preserve'` skips the differing file (the CI / non-TTY default the CLI\n * threads via `buildConflictOpts`). */\n conflictPolicy?: 'overwrite' | 'preserve';\n /** Per-file conflict resolver. The CLI passes its single\n * `buildConflictOpts().onConflict` (clack menu + diff preview + apply-to-\n * all) through unchanged; structurally compatible with @noir-ai/create's\n * `ConflictResolverReturn`. */\n onConflict?: SkillConflictResolver;\n /** Explicit interactivity flag (the engine reads THIS, not process.env,\n * matching @noir-ai/create's hermetic-interactive contract). When `false`,\n * the resolver is NEVER consulted (CI / --json never hangs on a prompt). */\n interactive?: boolean;\n } = {},\n): Promise<EmitSummary> {\n const target: CompileTarget = opts.target ?? 'claude';\n const { builtins, integrations } = discoverAll({\n builtinDir: opts.builtinDir,\n integrationsDir: opts.integrationsDir,\n });\n const emitIntegrations = opts.includeIntegrations ?? true;\n // Validate the whole pack before writing anything (fail-fast, atomic-ish).\n for (const s of builtins) {\n const res = validateSkill(s);\n if (!res.ok) throw new Error(`Invalid builtin skill ${s.name}: ${res.errors.join('; ')}`);\n }\n if (emitIntegrations) {\n for (const i of integrations) {\n const res = validateSkill(i);\n if (!res.ok) throw new Error(`Invalid integration ${i.name}: ${res.errors.join('; ')}`);\n }\n }\n await mkdir(targetDir, { recursive: true });\n let references = 0;\n const emitted: string[] = [];\n const preserved: string[] = [];\n // Every skill this run is responsible for, whether or not its bytes landed.\n // The prune step below keeps these: a preserved skill's files are the user's\n // own, and deleting the directory they live in is the one outcome pruning\n // must never produce.\n const kept: string[] = [];\n const integrationNames: string[] = [];\n const conflicts: SkillConflict[] = [];\n // Apply-to-all memory keyed by artifact CLASS. Skill emission shares one\n // decision across the run (a `noir sync` touching 8 skill files → 1 prompt)\n // when the resolver returns `{resolution, applyToAll: true}`.\n const memory = new Map<string, SkillConflictResolution>();\n const policy: 'overwrite' | 'preserve' = opts.conflictPolicy ?? 'overwrite';\n // Non-interactive guard. The bridge `NOIR_NON_INTERACTIVE` is set by the\n // bin's preAction under --json/--no-input; the explicit `interactive: false`\n // wins over the env (mirrors @noir-ai/create's hermetic flag). When false,\n // the resolver is NEVER consulted — differing files fall back to `policy`.\n const interactive =\n opts.interactive ??\n (process.env.NOIR_NON_INTERACTIVE === undefined || process.env.NOIR_NON_INTERACTIVE === '');\n\n /** Write a compiled skill file through the SAME conflict seam as\n * `regenerate`: read existing; if differs, consult the resolver (when\n * interactive + wired); record the conflict for the structured report.\n *\n * Returns whether the compiled bytes actually reached a Noir-owned path.\n * `'preserved'` means the differing copy on disk stands and nothing was\n * written for it — the caller may not count that file as refreshed. */\n const writeWithConflict = async (\n absDest: string,\n relDest: string,\n content: string,\n isReference: boolean,\n ): Promise<'written' | 'preserved'> => {\n let existing: string | undefined;\n try {\n existing = await readFile(absDest, 'utf8');\n } catch {\n existing = undefined;\n }\n if (existing !== undefined && existing !== content) {\n const MODE = 'skill' as const;\n let resolution: SkillConflictResolution;\n const remembered = memory.get(MODE);\n if (remembered !== undefined) {\n resolution = remembered;\n } else if (interactive && opts.onConflict !== undefined) {\n const ctx: SkillConflictContext = {\n relPath: relDest,\n existing,\n proposed: content,\n mode: MODE,\n };\n const ret = await opts.onConflict(ctx);\n const unwrapped: SkillConflictResolution = typeof ret === 'string' ? ret : ret.resolution;\n if (typeof ret !== 'string' && ret.applyToAll === true) {\n memory.set(MODE, unwrapped);\n }\n resolution = unwrapped;\n } else {\n resolution = policy === 'preserve' ? 'preserve' : 'replace';\n }\n conflicts.push({\n path: relDest,\n mode: MODE,\n similarity: similarity(existing, content),\n existingSha: sha256Hex12(existing),\n proposedSha: sha256Hex12(content),\n resolution,\n });\n if (resolution === 'cancel') {\n throw new Error(`skill emit cancelled by user at ${relDest}`);\n }\n if (resolution === 'preserve') {\n // Skip this file; the user's bytes stand. Do not count as a reference.\n return 'preserved';\n }\n if (resolution === 'rename') {\n // Move the user's file aside (unique suffix), then write the compiled bytes.\n await renameAside(absDest, '.local');\n } else if (resolution === 'duplicate') {\n // Write the compiled bytes to <absDest>.noir; keep the user's file.\n // The compiled version is on disk (just not at the canonical path), so\n // this counts as written — the user explicitly asked to keep theirs.\n await writeFile(await uniqueAside(absDest, '.noir'), content, 'utf8');\n return 'written';\n }\n // 'replace' → fall through and overwrite.\n }\n await writeFile(absDest, content, 'utf8');\n if (isReference) references++;\n return 'written';\n };\n\n // Cursor skills land FLAT under targetDir (`.cursor/rules/<name>.mdc`) —\n // Cursor's rule loader scans `.cursor/rules/*.mdc` and does NOT recurse into\n // per-name subdirs. The verbatim branch (claude/agents-md/gemini/opencode)\n // keeps the canonical nested layout (`<name>/SKILL.md` + `<name>/references/`).\n const flat = target === 'cursor';\n // Partition by outcome, not by attempt: a skill lands in `emitted` only when\n // every one of its files is now Noir's bytes, and in `preserved` when at\n // least one file was left as the user had it. A skill is never in both, and\n // a half-refreshed skill is reported as preserved — under-reporting stale\n // bytes is the failure this split exists to prevent.\n for (const s of builtins) {\n const compiled = compileSkill(s, target);\n let sawPreserved = false;\n for (const f of compiled.files) {\n const dest = flat ? join(targetDir, ...f.path) : join(targetDir, s.name, ...f.path);\n await mkdir(dirname(dest), { recursive: true });\n const rel = flat ? f.path.join('/') : [s.name, ...f.path].join('/');\n const outcome = await writeWithConflict(dest, rel, f.content, f.path[0] !== 'SKILL.md');\n if (outcome === 'preserved') sawPreserved = true;\n }\n kept.push(s.name);\n if (sawPreserved) preserved.push(s.name);\n else emitted.push(s.name);\n }\n if (emitIntegrations) {\n for (const i of integrations) {\n const compiled = compileIntegration(i, target);\n let sawPreserved = false;\n for (const f of compiled.files) {\n const dest = flat ? join(targetDir, ...f.path) : join(targetDir, i.name, ...f.path);\n await mkdir(dirname(dest), { recursive: true });\n const rel = flat ? f.path.join('/') : [i.name, ...f.path].join('/');\n const outcome = await writeWithConflict(dest, rel, f.content, f.path[0] !== 'SKILL.md');\n if (outcome === 'preserved') sawPreserved = true;\n }\n // `hostMcp` is NOT written per-skill — it is surfaced to the host adapter\n // (via discoverAll/compileIntegration) to merge into the host's single\n // MCP config (e.g. `.mcp.json`). Wiring lives in cli/daemon.\n kept.push(i.name);\n if (sawPreserved) preserved.push(i.name);\n else emitted.push(i.name);\n integrationNames.push(i.name);\n }\n }\n\n // --- Prune stale `noir-*` entries from the managed namespace ----------\n // Idempotent hygiene: a previous Noir version may have shipped a builtin that\n // was since renamed/removed (e.g. `noir-old-thing`). Each `noir sync`\n // re-writes the CURRENT pack but a stale entry would otherwise linger forever.\n // After emit, scan `targetDir` for `noir-`-prefixed entries this run did not\n // account for and remove them. ONLY the `noir-` namespace — user skills\n // without the prefix are NEVER touched (they are not Noir's to manage).\n // \"Accounted for\" spans the whole pack, not just the written half: a skill\n // whose files were preserved still owns its directory.\n //\n // `assertNotUserOwned` guard: a `noir-*` entry whose content does NOT\n // match the canonical Noir-emitted shape (SKILL.md with `name: noir-…`\n // frontmatter, OR a `.mdc` with the cursor frontmatter) is treated as\n // USER-AUTHORED and LEFT ALONE. A user hand-rolling `noir-myown/SKILL.md`\n // must never be silently deleted by a sync.\n //\n // Shape awareness: the nested layout (claude/agents-md/gemini/opencode)\n // writes one `noir-<name>/` DIR per skill → prune stale DIRS. The cursor flat\n // layout writes one `noir-<name>.mdc` FILE per skill → prune stale .mdc FILES.\n // Cursor ALSO clears legacy `noir-<name>/` dirs (nesting residue) so an\n // upgrade from nested→flat does not leave orphans under `.cursor/rules/`.\n const keep = new Set(kept);\n const pruned: string[] = [];\n const preservedUserOwned: string[] = [];\n let dirEntries: import('node:fs').Dirent[] = [];\n try {\n dirEntries = await readdir(targetDir, { withFileTypes: true });\n } catch {\n dirEntries = []; // targetDir vanished between the mkdir above and now — nothing to prune.\n }\n for (const ent of dirEntries) {\n if (!ent.name.startsWith('noir-')) continue;\n if (flat) {\n // Cursor flat layout — prune stale FILES (`noir-<name>.mdc`) + legacy\n // nested DIRS (`noir-<name>/`) from a sync.\n if (ent.isFile()) {\n const mdcMatch = ent.name.match(/^(noir-[a-z0-9]+(?:-[a-z0-9]+)*)\\.mdc$/);\n const skillName = mdcMatch?.[1];\n if (skillName && keep.has(skillName)) continue;\n // `assertNotUserOwned`: a `.mdc` without canonical cursor\n // frontmatter (globs + alwaysApply) was hand-rolled by the user; skip.\n if (await isUserOwnedMdc(join(targetDir, ent.name))) {\n preservedUserOwned.push(ent.name);\n continue;\n }\n } else if (ent.isDirectory()) {\n // Legacy cursor nested dir — clear unconditionally (the flat\n // layout has NO `noir-{name}/` dirs under .cursor/rules/; any such dir\n // is stale by definition, regardless of name overlap with the current\n // pack — the fresh `.mdc` file is what's kept, not the dir).\n // Guard: skip if NONE of the `.mdc` files inside look Noir-emitted\n // (canonical cursor frontmatter) — a user hand-rolling a noir-X/ dir\n // with arbitrary content is preserved.\n if (await isUserOwnedCursorLegacyDir(join(targetDir, ent.name))) {\n preservedUserOwned.push(ent.name);\n continue;\n }\n } else {\n continue;\n }\n } else {\n // Nested layout — prune stale DIRS only (the canonical shape).\n if (!ent.isDirectory()) continue;\n if (keep.has(ent.name)) continue;\n // `assertNotUserOwned`: a `noir-*/` dir whose SKILL.md does not\n // carry canonical Noir frontmatter (`name: noir-…`) is user-authored.\n if (await isUserOwnedSkillDir(join(targetDir, ent.name))) {\n preservedUserOwned.push(ent.name);\n continue;\n }\n }\n // Best-effort removal: a failure to remove a stale entry must NOT fail the\n // emit (the fresh skills are already on disk and valid). Surface it via the\n // summary so a caller can warn; the next sync will try again.\n try {\n await rm(join(targetDir, ent.name), { recursive: true, force: true });\n pruned.push(ent.name);\n } catch {\n // Swallow — stale-entry pruning is hygienic, not correctness-critical.\n }\n }\n\n return {\n dir: targetDir,\n emitted,\n references,\n integrations: integrationNames,\n preserved,\n pruned,\n ...(preservedUserOwned.length > 0 ? { preservedUserOwned } : {}),\n ...(conflicts.length > 0 ? { conflicts } : {}),\n };\n}\n\n/** `assertNotUserOwned` for the nested layout: a `noir-{name}/` dir is\n * Noir-managed iff it contains a SKILL.md whose YAML frontmatter carries a\n * `name:` matching `noir-<dir>`. Missing SKILL.md, missing frontmatter, or a\n * non-matching name ⇒ user-authored (left alone). Best-effort: any IO error\n * ⇒ treat as user-owned (the safe default — never silently delete). */\nasync function isUserOwnedSkillDir(dirAbs: string): Promise<boolean> {\n try {\n const skillMd = await readFile(join(dirAbs, 'SKILL.md'), 'utf8');\n const m = skillMd.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/);\n if (!m) return true; // no frontmatter at all → user-authored\n const fm = parseYaml(m[1] ?? '') as { name?: unknown };\n const dirName = basename(dirAbs);\n return typeof fm?.name !== 'string' || fm.name !== dirName;\n } catch {\n return true; // ENOENT (no SKILL.md) or parse error → user-authored (safe)\n }\n}\n\n/** `assertNotUserOwned` for the cursor flat layout: a `noir-*.mdc` file\n * is Noir-managed iff its YAML frontmatter carries `alwaysApply:` (the cursor\n * shape the compiler always emits). Missing frontmatter / no `alwaysApply` ⇒\n * user-authored. */\nasync function isUserOwnedMdc(fileAbs: string): Promise<boolean> {\n try {\n const md = await readFile(fileAbs, 'utf8');\n const m = md.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/);\n if (!m) return true;\n const fm = parseYaml(m[1] ?? '') as { alwaysApply?: unknown };\n return fm?.alwaysApply === undefined;\n } catch {\n return true;\n }\n}\n\n/** `assertNotUserOwned` for the cursor flat-layout's legacy-nested-dir\n * case: a Noir sync left `noir-{name}/noir-{name}.mdc` behind. The dir\n * is Noir-managed iff at least one `.mdc` inside carries canonical cursor\n * frontmatter (`alwaysApply:`). Absent / hand-rolled content ⇒ user-authored.\n * Empty dir ⇒ treat as user-owned (safe — never silently delete). */\nasync function isUserOwnedCursorLegacyDir(dirAbs: string): Promise<boolean> {\n try {\n const entries = await readdir(dirAbs);\n if (entries.length === 0) return true;\n let sawCanonical = false;\n for (const name of entries) {\n if (!name.endsWith('.mdc')) continue;\n if (!(await isUserOwnedMdc(join(dirAbs, name)))) {\n sawCanonical = true;\n break;\n }\n }\n return !sawCanonical;\n } catch {\n return true; // ENOENT or unreadable → safe default (preserve)\n }\n}\n\n/** LCS similarity in [0,1]. Local copy (skills has no create dependency)\n * so the structured report's `similarity` matches the engine's algorithm. */\nfunction similarity(a: string, b: string): number {\n if (a === b) return 1;\n const A = a.split('\\n');\n const B = b.split('\\n');\n if (A.length === 0 && B.length === 0) return 1;\n const n = A.length;\n const m = B.length;\n const dp: number[][] = Array.from({ length: n + 1 }, () => new Array<number>(m + 1).fill(0));\n for (let i = n - 1; i >= 0; i--) {\n const dpi = dp[i] ?? [];\n const dpi1 = dp[i + 1] ?? [];\n const ai = A[i] ?? '';\n for (let j = m - 1; j >= 0; j--) {\n const bj = B[j] ?? '';\n dpi[j] = ai === bj ? (dpi1[j + 1] ?? 0) + 1 : Math.max(dpi1[j] ?? 0, dpi[j + 1] ?? 0);\n }\n }\n const lcs = dp[0]?.[0] ?? 0;\n return n + m === 0 ? 1 : (2 * lcs) / (n + m);\n}\n\n/** Pick a fresh `<abs><suffix>` aside path that does NOT exist (mirrors\n * @noir-ai/create's `uniqueAside`). Used by the `rename`/`duplicate` resolutions\n * so the user's bytes never get silently clobbered. */\nasync function renameAside(abs: string, suffix: string): Promise<void> {\n const { rename } = await import('node:fs/promises');\n const dest = await uniqueAside(abs, suffix);\n await rename(abs, dest);\n}\n\nasync function uniqueAside(abs: string, suffix: string): Promise<string> {\n return uniqueAsideSync(abs, suffix);\n}\n\n// Convenience for callers/tests that already hold raw markdown (unused by emit path;\n// kept so adapters/tests can validate a single in-memory skill without a dir).\nexport { discoverAll, discoverBuiltin };\n","import { readdirSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { parseFrontmatter } from './compiler.js';\nimport { parseIntegration } from './integrations-schema.js';\nimport type { BuiltinSkill, IntegrationDeclaration, IntegrationSkill } from './types.js';\n\n// Package root = ONE level up from THIS module's directory. Works in every layout\n// because `builtin/` is always a sibling of the dir this code runs from:\n// - vitest: .../packages/skills/src/discover.ts -> HERE = src -> PKG_ROOT = package root\n// - built: .../packages/skills/dist/index.js -> HERE = dist -> PKG_ROOT = package root\n// (tsup bundles everything into dist/index.js; `dirname` still yields `dist`.)\nconst HERE = dirname(fileURLToPath(import.meta.url)); // .../src OR .../dist\nconst PKG_ROOT = dirname(HERE); // .../packages/skills\nexport const BUILTIN_DIR = join(PKG_ROOT, 'builtin');\nexport const INTEGRATIONS_DIR = join(PKG_ROOT, 'integrations');\n\n/** Read a `<name>/SKILL.md` + its optional `references/` directory. Shared by\n * builtin + integration discovery (both share the SKILL.md + references shape;\n * the difference is the `integration.json` declaration). */\nfunction readSkillMd(dir: string): Pick<BuiltinSkill, 'skillMd' | 'frontmatter' | 'references'> {\n const skillMd = readFileSync(join(dir, 'SKILL.md'), 'utf8');\n const frontmatter = parseFrontmatter(skillMd);\n const refsDir = join(dir, 'references');\n let references: BuiltinSkill['references'] = [];\n try {\n references = readdirSync(refsDir)\n .filter((f) => f.endsWith('.md'))\n .sort()\n .map((f) => ({ name: f, content: readFileSync(join(refsDir, f), 'utf8') }));\n } catch {\n references = []; // no references/ dir is fine\n }\n return { skillMd, frontmatter, references };\n}\n\nexport function discoverBuiltin(builtinDir: string = BUILTIN_DIR): BuiltinSkill[] {\n const dirs = readdirSync(builtinDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name.startsWith('noir-'))\n .map((e) => e.name)\n .sort();\n return dirs.map((name) => {\n const dir = join(builtinDir, name);\n return { name, dir, ...readSkillMd(dir) };\n });\n}\n\n/**\n * Discover every shipped integration under `integrations/<name>/` (sibling of\n * `builtin/`). Each entry is a builtin-shaped skill (validated `SKILL.md` +\n * optional references) PLUS a parsed + validated `integration.json`\n * declaration. The dir is resolved relative to the package root by default —\n * when `integrationsDir` is overridden (tests), only that dir is scanned.\n *\n * Throws an aggregate error if any integration is malformed (missing\n * `SKILL.md`, bad frontmatter, or an `integration.json` that fails the Zod\n * schema) — fail-fast, atomic-ish, mirroring the builtin compiler's posture.\n */\nexport function discoverIntegrations(\n integrationsDir: string = INTEGRATIONS_DIR,\n): IntegrationSkill[] {\n let entries: string[];\n try {\n entries = readdirSync(integrationsDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name.startsWith('noir-'))\n .map((e) => e.name)\n .sort();\n } catch {\n // No integrations/ dir (or unreadable) = no integrations shipped here.\n return [];\n }\n return entries.map((name) => {\n const dir = join(integrationsDir, name);\n const declarationRaw = JSON.parse(\n readFileSync(join(dir, 'integration.json'), 'utf8'),\n ) as unknown;\n let declaration: IntegrationDeclaration;\n try {\n declaration = parseIntegration(declarationRaw);\n } catch (err) {\n // ZodError → wrap with the integration's dir for an actionable message.\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Invalid integration.json in ${dir}: ${msg}`);\n }\n if (declaration.name !== name) {\n throw new Error(\n `Integration dir \"${name}\" must equal declaration name \"${declaration.name}\"`,\n );\n }\n return { name, dir, ...readSkillMd(dir), declaration, declarationRaw };\n });\n}\n\n/**\n * Discover the full shipped pack — builtins + integrations — in one call. Used\n * by `emitSkillsToDir` so `noir init`/`sync` emit BOTH. The\n * `integrationsDir` defaults to the sibling of `builtinDir` so a test fixture\n * that overrides only `builtinDir` cleanly resolves integrations to a\n * non-existent path (returns `[]`) instead of accidentally picking up the\n * shipped integrations.\n */\nexport function discoverAll(opts: { builtinDir?: string; integrationsDir?: string } = {}): {\n builtins: BuiltinSkill[];\n integrations: IntegrationSkill[];\n} {\n const builtinDir = opts.builtinDir ?? BUILTIN_DIR;\n const integrationsDir = opts.integrationsDir ?? join(dirname(builtinDir), 'integrations');\n return {\n builtins: discoverBuiltin(builtinDir),\n integrations: discoverIntegrations(integrationsDir),\n };\n}\n","// `integration.json` schema. Each integration ships a declaration\n// alongside its `SKILL.md` so the compiler can validate the auth/runtime/SDD\n// contract uniformly and so the host adapter knows whether to widen emission\n// (host MCP config) or stay skill-only.\n//\n// Doctrine: skill-only by default;\n// `gated-write-proxy` for stateless writes routed through a Noir MCP tool\n// `mcp-stdio` for a Noir-spawned stateful runtime (deferred, gated on\n// keychain); `external-mcp` for a config-only pointer at a first-party/community\n// MCP server (emits a host `.mcp.json` server entry, no Noir runtime).\n//\n// Zod v4 (`z.record(keyType, valueType)` is key-first). The schema is additive\n// and defaults to the safest tier — every optional field degrades to \"skill\n// only, manual paste, no host MCP wiring\" — so a minimal `{ \"name\":\n// \"noir-x\" }` declaration still parses + behaves as a pure playbook.\n\nimport * as z from 'zod';\n\n/** Auth shape. Locked: `env-var` only until keychain lands (refuse OAuth,\n * never silently lower the security bar). `fallback:'manual-paste'` keeps the\n * no-token path honest (the playbook tells the user to paste a value); `'none'`\n * is for integrations that genuinely do not need a token (read-only public\n * endpoints). */\nexport const IntegrationAuthSchema = z.object({\n type: z.literal('env-var'),\n tokenEnv: z.string().min(1),\n fallback: z.enum(['manual-paste', 'none']).default('manual-paste'),\n});\n\n/** SDD two-way binding. `intakeFrom` declares the external artifact kind the\n * `noir-brainstorming` skill pulls from (which absorbed noir-intake);\n * `writeBack` enumerates the fields `noir-wrap` pushes back at session end\n * (which absorbed noir-document). Strings (not enums) for `writeBack` so a\n * per-integration vocabulary stays expressible without\n * churning the schema. */\nexport const IntegrationSddSchema = z\n .object({\n intakeFrom: z.enum(['task', 'issue', 'none']).optional(),\n writeBack: z.array(z.string()).default([]),\n })\n .default({ writeBack: [] });\n\n/** Host MCP declaration. Only meaningful when `runtime ∈ {mcp-stdio,\n * external-mcp}`; `null` for `none`/`gated-write-proxy` (ClickUp — writes go\n * through Noir's own MCP tool, never the host's). */\nexport const IntegrationMcpSchema = z\n .object({\n command: z.string().min(1),\n transport: z.enum(['stdio', 'http']),\n args: z.array(z.string()).optional(),\n url: z.string().optional(),\n env: z.record(z.string(), z.string()).optional(),\n })\n .nullable()\n .default(null);\n\n/** Full `integration.json` declaration. `name` enforces the `noir-` prefix\n * shared with builtins so the unified pack reads consistently. */\nexport const IntegrationDeclarationSchema = z.object({\n name: z.string().regex(/^noir-[a-z0-9]+(?:-[a-z0-9]+)*$/, 'must match noir-<kebab>'),\n auth: IntegrationAuthSchema,\n runtime: z.enum(['none', 'gated-write-proxy', 'mcp-stdio', 'external-mcp']),\n sdd: IntegrationSddSchema,\n mcp: IntegrationMcpSchema,\n});\n\nexport type IntegrationDeclaration = z.infer<typeof IntegrationDeclarationSchema>;\n\n/** Coerce + validate an `integration.json` payload. Throws a ZodError on a\n * malformed declaration — the caller (discover) wraps it with the\n * integration's dir for an actionable message. */\nexport function parseIntegration(json: unknown): IntegrationDeclaration {\n return IntegrationDeclarationSchema.parse(json);\n}\n\n/** Non-throwing variant for the hygiene tests + lint helpers. */\nexport function validateIntegration(\n obj: unknown,\n): { ok: true; value: IntegrationDeclaration } | { ok: false; errors: string[] } {\n const parsed = IntegrationDeclarationSchema.safeParse(obj);\n if (parsed.success) return { ok: true, value: parsed.data };\n return { ok: false, errors: parsed.error.issues.map((i: { message: string }) => i.message) };\n}\n\n/** Runtime → host-MCP emission predicate. The compiler widens emission (makes\n * the host MCP block available to the adapter) ONLY for these tiers. The\n * adapter ultimately decides per-host what to render — for Claude, `external-\n * mcp` becomes a `.mcp.json` server entry; `mcp-stdio` registers through the\n * existing `noir mcp serve --stdio` entry so no NEW server entry is emitted. */\nexport function runtimeEmitsHostMcp(runtime: IntegrationDeclaration['runtime']): boolean {\n return runtime === 'mcp-stdio' || runtime === 'external-mcp';\n}\n","// Structural quality checks for the builtin skill pack — the artifact\n// quality gate.\n//\n// Split from compiler.ts so `validateSkill` (hard errors) and `lintSkill` (soft\n// warnings) share one source of truth for the body-structure rules, and so the\n// rules are unit-testable in isolation. Canonical template:\n//\n// Overview → When to use → Procedure → Verification → Notes\n//\n// Rules here implement the researched canon (Anthropic best practices, the\n// agentskills.io spec, Claude Code docs): required sections, a <500-line body\n// budget, one-level-deep references (no chained refs), and a WHAT+WHEN\n// description. `checkRequiredSections` is the load-bearing check — it's what\n// forces every SKILL.md to carry a real playbook shape, not a shell.\n\nimport { ARTIFACT_TYPES } from '@noir-ai/core';\nimport { checkHygiene, HYGIENE_EXEMPT_MARKERS, type HygieneFinding } from './hygiene.js';\nimport type { BuiltinSkill } from './types.js';\n\n/** The SKILL.md body — the markdown after the YAML frontmatter block. The\n * frontmatter is metadata the host reads on its own; the body is the playbook\n * a reader loads. The validator and the two body-structure lint rules both\n * measure this string, so it lives here (single source of truth) rather than\n * in compiler.ts. */\nexport function bodyOf(md: string): string {\n return md.replace(/^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n?/, '');\n}\n\n/** The body with the blank line that separates it from the frontmatter removed\n * — the text the author wrote as the body, which is what a finding's line\n * number has to count in. `bodyOf` keeps that separator line, so numbering\n * findings against its raw output reports every one of them a line past the\n * line the author sees in the file. Only a leading blank line is dropped, and\n * only one, so the rest of the body keeps the numbering it is written with. */\nfunction authoredBody(body: string): string {\n if (body.startsWith('\\r\\n')) return body.slice(2);\n if (body.startsWith('\\n')) return body.slice(1);\n return body;\n}\n\n/** The max body length the canon recommends (Anthropic: \"under 500 lines\").\n * SKILL.md is a navigator, not a repository — split to references/ past this. */\nexport const MAX_BODY_LINES = 500;\n\n/** The floor for a full (non-stub) playbook body. Stubs were ~13 lines; a real\n * playbook needs at least this much substance to be loadable as guidance. */\nexport const MIN_FULL_BODY_LINES = 20;\n\n/** The canonical heading a skill MUST carry to describe its trigger conditions.\n * Accepts the canonical template spelling AND common variants so existing skills\n * aren't forced into a single casing. */\nconst WHEN_SECTION = /^## When to use$/im;\n\n/** The canonical heading a skill MUST carry for its numbered workflow. `## Steps`\n * is accepted for backward-compat with noir-wrap; `## Procedure` is canonical. */\nconst PROCEDURE_SECTION = /^## (Procedure|Steps)$/im;\n\n/** One of these closing sections must exist — evidence/notes/degradation. */\nconst CLOSING_SECTION = /^## (Verification|Notes|Fallbacks|Troubleshooting|Why order matters)$/im;\n\n/** The lead cue regex — the FIRST part of the description must be a trigger. */\nconst WHEN_CUE =\n /^(use|using|used|whenever|when|before|after|while|starting|encountering|completing|creating|about to|upon|during|to|for|on)\\b/i;\n\n/** A chained reference link — a reference file that links to ANOTHER reference\n * file (`references/a.md` → `references/b.md` or `../references/b.md`). The\n * canon forbids deep nesting (agentskills.io: \"no chained references\"). */\nconst CHAINED_REF_RE = /\\]\\((\\/?\\.?\\.?\\/)?(references\\/|\\.\\.\\/references\\/)/i;\n\n/**\n * Which required sections are missing from a skill body. Returns a list of\n * human-readable section names, e.g. `['## When to use', '## Verification']`.\n * Empty array = the body carries the canonical playbook shape.\n */\nexport function missingSections(body: string): string[] {\n const missing: string[] = [];\n if (!WHEN_SECTION.test(body)) missing.push('## When to use');\n if (!PROCEDURE_SECTION.test(body)) missing.push('## Procedure (or ## Steps)');\n if (!CLOSING_SECTION.test(body)) {\n missing.push('one of ## Verification / ## Notes / ## Fallbacks / ## Troubleshooting');\n }\n return missing;\n}\n\n/** True when the body is within the canon line budget. */\nexport function withinLineBudget(body: string, max: number = MAX_BODY_LINES): boolean {\n return body.split('\\n').length <= max;\n}\n\n/** Reference files that point at another reference file (chained — forbidden).\n * Returns the offending reference names. */\nexport function chainedReferences(skill: BuiltinSkill): string[] {\n return skill.references.filter((r) => CHAINED_REF_RE.test(r.content)).map((r) => r.name);\n}\n\n/**\n * True when the description carries BOTH a WHEN trigger lead AND a WHAT clause.\n * The rule: `description` MUST lead with a WHEN cue (existing compiler rule)\n * AND contain a compact WHAT clause naming what the skill does.\n *\n * Two canonical shapes both pass:\n * \"Use when turning an idea into a spec — draft the spec.\" (WHEN → WHAT)\n * \"Use when a task writes back to ClickUp: update its status.\" (WHEN → WHAT)\n * The description MUST lead with a cue (the WHEN-only and WHAT-only schools are\n * both rejected). The WHAT clause is the part after the trigger phrase — split\n * on an em/en dash or a period that ends the trigger phrase. A real WHAT clause\n * has ≥3 words naming the action, so a bare \"Use when…\" fails.\n */\nexport function isWhatWhenDescription(description: string): boolean {\n const trimmed = description.trim();\n if (!trimmed) return false;\n if (!WHEN_CUE.test(trimmed)) return false; // must lead with a cue\n // Split off the trigger phrase: after an em/en dash, or after the first period.\n // \"Use when X — draft the spec.\" → whatPart = \"draft the spec.\"\n const whatPart = trimmed\n .split(/[—–]|(?<=\\.) /)\n .slice(1)\n .join(' ')\n .trim();\n return whatPart.split(/\\s+/).filter(Boolean).length >= 3;\n}\n\n/** True when the description leads with a WHEN cue (the existing rule, kept\n * here so validate + lint + hygiene share one implementation). */\nexport function looksLikeWhenDescription(description: string): boolean {\n return WHEN_CUE.test(description.trim());\n}\n\n/**\n * Soft-quality warnings for `lintSkill`. Each returns a short rule id + message.\n * These are advisory — a skill can pass `validateSkill` and still carry lint\n * warnings that the author should resolve. Rules are drawn from the researched\n * anti-pattern list (no examples, thin body, first/second person narration,\n * voodoo constants, time-sensitive version pins).\n */\nexport function lintWarnings(skill: BuiltinSkill): string[] {\n const warnings: string[] = [];\n // The two body rules below measure the body, not the file: a long description\n // (or any other frontmatter line) must not make a short body look substantial,\n // and a cue in the description must not stand in for a worked example. The\n // prose checks after them read the whole file, so a first-person phrase or a\n // stale version pin is surfaced wherever it sits.\n const file = skill.skillMd;\n const body = bodyOf(file);\n const bodyLines = body.split('\\n').length;\n\n if (bodyLines < MIN_FULL_BODY_LINES) {\n warnings.push('thin-body: full playbook body is under 20 lines');\n }\n // Concrete examples: at least one fenced block or an \"example:\" mention in\n // the body.\n const hasExample = /```/.test(body) || /\\bexample:?\\b/i.test(body) || /\\be\\.g\\.\\b/i.test(body);\n if (!hasExample) {\n warnings.push('no-example: no concrete code fence or worked example in the body');\n }\n // First/second-person narration — \"I/we/you\" as the agent doing work.\n if (\n /\\b(I|we|you)\\s+(will|should|can|need|must|do|write|create|implement|run|build)\\b/i.test(file)\n ) {\n warnings.push('first-person: narration addresses the reader instead of imperative steps');\n }\n // Voodoo constants: a bare number with a magnitude claim but no justification\n // (\"wait 5 seconds\" without why). Very loose — catches \"wait N seconds\" without\n // a \"why\".\n if (\n /\\b(?:wait|sleep|retry|backoff|limit|cap)\\s+[a-z]*\\s*(\\d{1,4})\\b/i.test(file) &&\n !/because|to (avoid|prevent|give|let)/i.test(file)\n ) {\n warnings.push('voodoo-constant: numeric threshold without a stated reason');\n }\n // Time-sensitive version pins outside a Legacy section.\n const hasVersionPin = /as of \\d{4}|\\bversion \\d+\\.\\d+\\.\\d+\\b|\"v\\d+\\.\\d+\"/i.test(file);\n const hasLegacySection = /^## Legacy|^## Old patterns/i.test(file);\n if (hasVersionPin && !hasLegacySection) {\n warnings.push('time-sensitive: version/date pin outside a Legacy section');\n }\n return warnings;\n}\n\n/** `.noir/` subdirectories a skill may legitimately reference that are NOT\n * per-type artifact dirs (store db, audit export, rules, daemon state). */\nconst NON_ARTIFACT_DIRS = new Set(['store', 'audit', 'rules', 'state']);\n\n/** Canonical artifact directory → its type code(s) (from the artifact registry).\n * One directory can host multiple kinds (`subagents/` → BR + RP). */\nconst DIR_TO_CODES: ReadonlyMap<string, ReadonlySet<string>> = (() => {\n const m = new Map<string, Set<string>>();\n for (const t of Object.values(ARTIFACT_TYPES)) {\n const codes = m.get(t.dir);\n if (codes) codes.add(t.code);\n else m.set(t.dir, new Set([t.code]));\n }\n return m;\n})();\n\n/**\n * `.noir/…` output-path drift: a skill body or reference that names a `.noir/`\n * directory absent from the artifact registry (e.g. `.noir/sdd/`), or a file\n * under a canonical artifact directory whose name carries none of that\n * directory's type codes (e.g. `.noir/plans/<date>-<slug>.md` instead of\n * `PL-<NNNN>-…`). Hard errors — the host would mint non-standard artifacts.\n * See `docs/reference/artifact-format.md`.\n */\nexport function artifactPathDrift(skill: BuiltinSkill): string[] {\n const texts = [skill.skillMd, ...skill.references.map((r) => r.content)];\n const drifts = new Set<string>();\n // `.noir/<dir>/<name>` — name runs to whitespace/backtick/quote/paren so\n // `<date>-<slug>.md` placeholders are captured whole.\n const re = /\\.noir\\/([a-zA-Z0-9-]+)\\/([^\\s`'\"()]+)/g;\n for (const text of texts) {\n for (const m of text.matchAll(re)) {\n const dir = m[1];\n const name = m[2];\n if (!dir || !name) continue;\n const codes = DIR_TO_CODES.get(dir);\n if (codes === undefined) {\n if (!NON_ARTIFACT_DIRS.has(dir)) {\n drifts.add(\n `.noir/${dir}/ is not a canonical artifact directory (see docs/reference/artifact-format.md)`,\n );\n }\n continue;\n }\n const expected = [...codes].join('/');\n if (![...codes].some((c) => name.startsWith(`${c}-`))) {\n drifts.add(\n `.noir/${dir}/${name} must be ${expected}-<NNNN>-… (see docs/reference/artifact-format.md)`,\n );\n }\n }\n }\n return [...drifts];\n}\n\n// Output hygiene (the rules live in hygiene.ts).\n//\n// A SKILL.md body is a markdown document that also carries fenced code blocks,\n// so each part is checked with the kind of text it is: the prose as markdown,\n// the fenced blocks as source. Checking the whole body as one kind misfires in\n// both directions — a source rule reads markdown bold (`**text**`) as a block\n// comment, and a prose rule reads a `//` line inside a fence as a heading.\n// The fail tier blocks emission and the warn tier is advisory; which findings\n// go to which list is `validateSkill`'s decision, not this module's.\n\n/** A body split into the two kinds of text it holds: the prose, and the fenced\n * code blocks. Blanking the other side's lines, rather than dropping them,\n * keeps every finding on the line number it has in the body.\n *\n * Fences follow the CommonMark rule: a line opening with a run of at least\n * three backticks or tildes opens a block, and only a run of the same\n * character at least as long closes it — a shorter run of the same character\n * (or the other character) inside the block is content, so a block that\n * documents a fenced example stays one block. An unclosed fence runs to the\n * end of the body, which is where CommonMark renders its content too. */\nfunction splitFencedBlocks(body: string): { prose: string; code: string } {\n const prose: string[] = [];\n const code: string[] = [];\n // The closing fence of the block currently open: same character, at least the\n // opening run's length, with only whitespace after it. Null when not in a\n // fence.\n let closing: RegExp | null = null;\n for (const line of body.split('\\n')) {\n let boundary = false;\n if (closing === null) {\n const open = line.match(/^[ \\t]{0,3}(`{3,}|~{3,})/);\n const run = open?.[1];\n if (run) {\n const char = run[0] ?? '';\n closing = new RegExp(`^[ \\\\t]{0,3}${char}{${run.length},}[ \\\\t]*$`);\n boundary = true;\n }\n } else if (closing.test(line)) {\n closing = null;\n boundary = true;\n }\n // The fence markers themselves are document syntax; the lines between them\n // are source.\n const isCode = closing !== null && !boundary;\n prose.push(isCode ? '' : line);\n code.push(isCode ? line : '');\n }\n return { prose: prose.join('\\n'), code: code.join('\\n') };\n}\n\n/** The 1-based line the body's exemption marker sits on, or 0 when it carries\n * none. A SKILL.md body states its own exemption with the markdown marker. */\nfunction exemptionLine(body: string): number {\n const marker = HYGIENE_EXEMPT_MARKERS.markdown;\n const lines = body.split('\\n');\n for (let i = 0; i < lines.length; i++) {\n if ((lines[i] ?? '').trim() === marker) return i + 1;\n }\n return 0;\n}\n\n/** Every hygiene finding a skill body produces, in reading order.\n *\n * The lines are counted in the authored body (see {@link authoredBody}), so a\n * finding names the line a reader opening the SKILL.md finds the text on: the\n * number is frontmatter-exclusive and starts at the body's own first line.\n *\n * A body that carries the exemption marker above its first finding is exempt\n * in both kinds. The marker is a statement about the file, and a body is one\n * file even though it is read as prose and as source — a fenced code block\n * cannot carry the document's marker, so without this the code rules would\n * keep firing inside a body that declared itself exempt. */\nexport function hygieneFindings(body: string): HygieneFinding[] {\n const text = authoredBody(body);\n const { prose, code } = splitFencedBlocks(text);\n const findings = [...checkHygiene(prose, 'markdown'), ...checkHygiene(code, 'code')];\n const seen = new Set<string>();\n const ordered = findings\n .filter((f) => {\n const key = `${f.id}:${f.line}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n })\n .sort((a, b) => a.line - b.line);\n const exemptAt = exemptionLine(text);\n if (exemptAt !== 0 && exemptAt < (ordered[0]?.line ?? Number.POSITIVE_INFINITY)) return [];\n return ordered;\n}\n\n/** One hygiene finding as a gate message: the rule that fired, the line it\n * fired on, why that line is noise, and what to write instead. */\nexport function hygieneMessage(finding: HygieneFinding): string {\n return `${finding.id} (line ${finding.line}): ${finding.rationale} ${finding.fix}`;\n}\n","import type { HygieneRule } from './hygiene.js';\n\n// A `noir-debt:` comment must name a known ceiling AND an upgrade trigger\n// (when/if/once), otherwise the deliberate shortcut is untrackable. The word\n// \"ceiling\" alone is not a trigger: it names the limit, not the condition that\n// justifies paying for the upgrade.\nexport const DEBT_MARKER_RULE: HygieneRule = {\n id: 'noir-debt',\n tier: 'warn',\n appliesTo: 'code',\n pattern: /(?:^[ \\t]*|[ \\t])(?:\\/\\/|#|\\/\\*|\\*)[ \\t]*noir-debt:(?!.*\\b(?:when|if|once)\\b)/m,\n rationale: 'a debt marker without a ceiling or upgrade trigger is an un-trackable shortcut',\n fix: 'name the ceiling and the condition that justifies the upgrade',\n};\n","// Tokens that must NOT survive a port (predecessor plugin internals + upstream\n// rhetoric), each with the reason it is forbidden. The list is projected into\n// fail-tier hygiene rules, so a token is enforced by the gate and not only by a\n// test assertion.\n//\n// This file is the token table, so it is exempt from the rules it feeds: the\n// tokens must appear here to be forbidden anywhere.\n// noir-hygiene: exempt\n\nimport type { HygieneRule } from './hygiene.js';\n\ninterface ResidueToken {\n /** The literal that must not appear in a shipped artifact. */\n token: string;\n /** Why it is forbidden, in a reader's terms. */\n why: string;\n}\n\nconst RESIDUE: readonly ResidueToken[] = [\n {\n token: 'workflow/<task',\n why: 'Names a state file of the removed workflow plugin, which nothing in this repository reads.',\n },\n {\n token: 'noir-workflow.mode',\n why: 'Names the mode flag of the removed workflow plugin, which nothing in this repository reads.',\n },\n {\n token: 'noir-workflow',\n why: 'Names the removed workflow plugin as a plugin or a path, so a reader following it finds nothing.',\n },\n {\n token: '@uiigateway',\n why: 'Names a package from the project this one was extracted from, which no consumer can resolve.',\n },\n // 'ClickUp'/'clickup' were forbidden while the predecessor plugin was being\n // ported, because the ClickUp REST precedent lived inside it. ClickUp now\n // ships as a first-class integration, so the token names a legitimate\n // subject and is deliberately absent from this list.\n {\n token: '<EXTREMELY-IMPORTANT',\n why: 'Copied rhetorical markup from the upstream tool, which carries no instruction a reader can follow.',\n },\n {\n token: 'SUBAGENT-STOP',\n why: 'Copied rhetorical markup from the upstream tool, which carries no instruction a reader can follow.',\n },\n {\n token: 'plugins/noir-workflow',\n why: 'Names the removed plugin path, which no longer exists in this repository.',\n },\n];\n\n/** The forbidden tokens, in the order they are declared. */\nexport const FORBIDDEN_RESIDUE: readonly string[] = RESIDUE.map((entry) => entry.token);\n\n/** One fail-tier rule per forbidden token. A token matches only when it stands\n * alone — neither side is a word character or a hyphen — so `noir-workflow`\n * does not fire inside a longer identifier such as `noir-workflow-engine-`,\n * which names this repository's own workflow engine rather than the removed\n * plugin. It still fires on `plugins/noir-workflow/` and `noir-workflow.mode`,\n * which name the plugin itself; a token that contains another\n * (`noir-workflow.mode`) is reported by both rules, so the narrower token is\n * never the only thing matching a line. */\nexport const RESIDUE_RULES: readonly HygieneRule[] = RESIDUE.map((entry) => ({\n id: `residue-${entry.token\n .replace(/[^a-z0-9]+/gi, '-')\n .replace(/^-|-$/g, '')\n .toLowerCase()}`,\n tier: 'fail' as const,\n pattern: new RegExp(`(?<![\\\\w-])${escapeForRegExp(entry.token)}(?![\\\\w-])`),\n rationale: entry.why,\n fix: 'Remove the reference. If it named a mechanism, name the mechanism that replaced it.',\n appliesTo: 'both' as const,\n}));\n\n/** Escapes a literal so it can be embedded in a pattern. */\nfunction escapeForRegExp(literal: string): string {\n return literal.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n","// The single source of truth for output hygiene: the patterns that make agent\n// output look machine-generated, each with the tier that decides whether it\n// blocks, the reason it is noise, and what to write instead. Consumers are the\n// skills quality gate, the repository doctor check and continuous integration.\n\n// Self-exemption convention: the rule source, the token table it projects and\n// the gate's own fixtures are exempt from the rules they define, because they\n// must be able to name what they forbid. Any such file carries the exemption\n// marker below, so no consumer needs a path list of its own. This file declares\n// the rules, so it carries the marker:\n// noir-hygiene: exempt\n\nimport { DEBT_MARKER_RULE } from './debt-marker.js';\nimport { RESIDUE_RULES } from './residue.js';\n\nexport type HygieneTier = 'fail' | 'warn';\n\n/** The two kinds of text a rule can be written for: source, or prose. */\nexport type HygieneKind = 'code' | 'markdown';\n\nexport interface HygieneRule {\n id: string;\n tier: HygieneTier;\n /** Anchored: a pattern that matches ordinary writing is the failure this\n * rule source exists to prevent, so every pattern below is reviewed against\n * plain prose and against string literals. */\n pattern: RegExp;\n rationale: string;\n fix: string;\n appliesTo: HygieneKind | 'both';\n}\n\nexport interface HygieneFinding {\n /** The id of the rule that matched, exactly as `HYGIENE_RULES` declares it. */\n id: string;\n tier: HygieneTier;\n /** 1-based number of the line the pattern matched. */\n line: number;\n /** The matched line, trimmed of surrounding whitespace. */\n text: string;\n rationale: string;\n fix: string;\n}\n\n/** The line that exempts a file from every rule. A file that must keep what the\n * rules flag — the rule source, the token table, a document that states the\n * ban — carries the line matching its kind near the top. */\nexport const HYGIENE_EXEMPT_MARKERS: Readonly<Record<HygieneKind, string>> = {\n code: '// noir-hygiene: exempt',\n markdown: '<!-- noir-hygiene: exempt -->',\n};\n\n/** A run of this many consecutive comment lines counts as a comment block. A\n * blank comment line is a paragraph break and a divider is reported on its\n * own, so neither one extends a block. */\nexport const MAX_COMMENT_BLOCK_LINES = 12;\n\n// A comment opener that sits at the start of a line or after whitespace.\n// Requiring that position is what keeps the rules below off ordinary writing:\n// the `//` of `https://example.com` follows a colon, so it opens no comment.\nconst MARKER = String.raw`(?:^[ \\t]*|[ \\t])(?:\\/\\/|#|\\/\\*|\\*)`;\n\n/** The emoji presentation selector. It is invisible in source, so it is named\n * here rather than written; it follows a character that also has a text\n * presentation, as the warning sign does. */\nconst VARIATION_SELECTOR = String.fromCharCode(0xfe0f);\n\n/** The pictograph block, where the icons that decorate generated text live. */\nconst PICTOGRAPH = String.raw`[\\u{1F300}-\\u{1FAFF}]`;\n\n// The decoration set: the icons that arrive as flourishes in generated text and\n// tell a reader nothing they can act on. They are unambiguous on their own, so\n// they are flagged wherever they appear in a comment.\nconst DECORATION_EMOJI = `[⚠✅🎉✨🔥🚀🧠💡🔧📌]${VARIATION_SELECTOR}?`;\n\n/** The backtick. It is named rather than written so the template literal that\n * builds the quote class below does not end early; see VARIATION_SELECTOR. */\nconst BACKTICK = String.fromCharCode(0x60);\n\n/** A quote or a backtick. A comment may legitimately quote the glyph it\n * describes, as the badge documentation quotes its own `⚠ warn` output, so a\n * glyph wrapped in one of these is not decoration. */\nconst QUOTE_OR_BACKTICK = `[${BACKTICK}'\"]`;\n\n/** The markers a reader is expected to act on later. */\nconst MARKER_WORDS = ['TODO', 'FIXME'];\n\n/** Words that give a marker a reason to exist, and so make it actionable. */\nconst REASON_WORDS = [\n 'because',\n 'since',\n 'until',\n 'once',\n 'when',\n 'unless',\n 'blocked',\n 'otherwise',\n 'revisit',\n];\n\n/** An owner for a marker: a handle, a bracketed name, or a tracked issue. */\nconst OWNER = String.raw`(?:@[A-Za-z0-9_-]|\\([A-Za-z][A-Za-z0-9_-]*\\)|#\\d+)`;\n\n/** A run of divider punctuation after a comment opener, which turns a label\n * into a banner: `==== Fetch users ====`. */\nconst DECORATIVE_BANNER: HygieneRule = {\n id: 'decorative-banner',\n tier: 'fail',\n pattern: new RegExp(String.raw`${MARKER}[ \\t]*[-=*_~]{4,}`, 'm'),\n rationale:\n '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.',\n fix: 'Delete the divider. If the label names a section a reader needs, make it a heading or name it in the declaration below.',\n appliesTo: 'both',\n};\n\n/** Narration that walks a reader through the code in the order it runs:\n * `Step 1: load the config`, `First, …`, `Next, …`, `Finally, …`. Two shapes\n * are exempt: an ordinal followed by a relative clause (\"first, which is all\n * the test needs\") describes a position rather than a procedure, and an\n * ordinal that goes on to give the reason (\"first, init so sync can read it\")\n * is the note this rule's own fix asks a reader to keep. */\nconst WORKFLOW_NARRATION: HygieneRule = {\n id: 'workflow-narration',\n tier: 'fail',\n pattern: new RegExp(\n 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))`,\n 'im',\n ),\n rationale:\n 'Numbered narration restates what the code already says in the order it already says it, and it goes stale the moment that order changes.',\n fix: 'Drop the step markers. Keep only what the code cannot show: why this order, which invariant is held, or what breaks otherwise.',\n appliesTo: 'code',\n};\n\n/** An emoji from the decoration set anywhere in a comment — the common flourish\n * in generated comments, opening the comment or trailing it. A glyph the\n * comment has put in quotes is being talked about rather than used, so it is\n * left alone; the trailing check allows for the presentation selector the\n * pattern may have consumed. */\nconst DECORATIVE_EMOJI_IN_COMMENT: HygieneRule = {\n id: 'decorative-emoji-comment',\n tier: 'fail',\n pattern: new RegExp(\n String.raw`${MARKER}[^\\n]*(?<!${QUOTE_OR_BACKTICK})${DECORATION_EMOJI}(?!${VARIATION_SELECTOR}?${QUOTE_OR_BACKTICK})`,\n 'mu',\n ),\n rationale:\n '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.',\n fix: 'Remove the emoji and let the sentence carry the emphasis. If it marked a state, name the state in a word.',\n appliesTo: 'code',\n};\n\n/** A pictograph that opens a comment, for the emoji outside the decoration\n * set: only that position is unambiguously decorative, since a comment may\n * legitimately quote a character it is describing. */\nconst DECORATIVE_PICTOGRAPH: HygieneRule = {\n id: 'decorative-pictograph-comment',\n tier: 'fail',\n pattern: new RegExp(\n String.raw`${MARKER}[ \\t]*(?!${DECORATION_EMOJI})${PICTOGRAPH}${VARIATION_SELECTOR}?`,\n 'mu',\n ),\n rationale:\n '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.',\n fix: 'Remove the pictograph. If it stood for a state or a result, write that in words.',\n appliesTo: 'code',\n};\n\n/** A document line that opens with a decorative emoji or pictograph, after any\n * heading or bullet marker. Only that opening position counts: an emoji inside\n * a sentence is usually something the sentence is talking about. */\nconst DECORATIVE_EMOJI_IN_PROSE: HygieneRule = {\n id: 'decorative-emoji-doc',\n tier: 'fail',\n pattern: new RegExp(\n String.raw`^[ \\t]{0,3}(?:#{1,6}[ \\t]+)?(?:[-*+][ \\t]+)?(?:${DECORATION_EMOJI}|${PICTOGRAPH}${VARIATION_SELECTOR}?)`,\n 'mu',\n ),\n rationale:\n '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.',\n fix: 'Remove the emoji and put the point of the line in its words.',\n appliesTo: 'markdown',\n};\n\n// The code point ranges that no file in this project has a reason to carry.\n// Each one is either a character from another writing system, or a code point\n// that draws nothing at all. Both arrive the same way: text generation can leak\n// a token from a foreign script into the output, and a mis-decoded byte becomes\n// a replacement character or an invisible formatting mark. They are listed as\n// ranges rather than as an allowlist so that ordinary writing in the project's\n// own language is never at risk of being flagged.\nconst IRREGULAR_SCRIPT_SOURCES = [\n String.raw`\\u{0370}-\\u{03ff}`, // Greek\n String.raw`\\u{1f00}-\\u{1fff}`, // Greek extended\n String.raw`\\u{0400}-\\u{052f}`, // Cyrillic and its supplement\n String.raw`\\u{2de0}-\\u{2dff}`, // Cyrillic extended A\n String.raw`\\u{a640}-\\u{a69f}`, // Cyrillic extended B\n String.raw`\\u{0590}-\\u{05ff}`, // Hebrew\n String.raw`\\u{0600}-\\u{06ff}`, // Arabic\n String.raw`\\u{0750}-\\u{077f}`, // Arabic supplement\n String.raw`\\u{fb1d}-\\u{fb4f}`, // Hebrew presentation forms\n String.raw`\\u{fb50}-\\u{fdff}`, // Arabic presentation forms A\n String.raw`\\u{fe70}-\\u{feff}`, // Arabic presentation forms B, and the byte-order mark\n String.raw`\\u{0900}-\\u{097f}`, // Devanagari\n String.raw`\\u{0e00}-\\u{0e7f}`, // Thai\n String.raw`\\u{1100}-\\u{11ff}`, // Hangul Jamo\n String.raw`\\u{2e80}-\\u{2fdf}`, // CJK radicals\n String.raw`\\u{3000}-\\u{30ff}`, // CJK symbols and punctuation, Hiragana, Katakana\n String.raw`\\u{3100}-\\u{318f}`, // Bopomofo, Hangul compatibility Jamo\n String.raw`\\u{31c0}-\\u{31ef}`, // CJK strokes and Katakana extensions\n String.raw`\\u{3200}-\\u{33ff}`, // Enclosed and compatibility CJK\n String.raw`\\u{3400}-\\u{4dbf}`, // CJK unified ideographs extension A\n String.raw`\\u{4e00}-\\u{9fff}`, // CJK unified ideographs\n String.raw`\\u{ac00}-\\u{d7af}`, // Hangul syllables\n String.raw`\\u{f900}-\\u{faff}`, // CJK compatibility ideographs\n String.raw`\\u{ff00}-\\u{ffef}`, // Fullwidth and halfwidth forms\n String.raw`\\u{200b}-\\u{200f}`, // Zero-width space, joiners and the bidirectional marks\n String.raw`\\u{202a}-\\u{202e}`, // Bidirectional embeddings and overrides\n String.raw`\\u{2066}-\\u{2069}`, // Bidirectional isolates\n String.raw`\\u{fffd}`, // the replacement character\n String.raw`\\u{e000}-\\u{f8ff}`, // Private use area\n String.raw`\\u{f0000}-\\u{ffffd}`, // Private use planes 15 and 16\n String.raw`\\u{100000}-\\u{10fffd}`, // Private use plane 17\n];\n\n/** A character from another writing system, or one that draws nothing. */\nconst IRREGULAR_SCRIPT = `[${IRREGULAR_SCRIPT_SOURCES.join('')}]`;\n\n/** A character from another writing system, or an invisible one, anywhere in a\n * line. The project writes in its own language, so a character from a foreign\n * script is a leak rather than a word, and an invisible code point (a\n * zero-width space, a byte-order mark, a replacement character) is a decoding\n * accident. A fixture that has to carry one — a test that measures how a wide\n * glyph is laid out, say — states its own exemption with the marker above\n * rather than teaching this rule which characters to allow. */\nconst IRREGULAR_SCRIPT_IN_TEXT: HygieneRule = {\n id: 'no-irregular-script',\n tier: 'fail',\n pattern: new RegExp(IRREGULAR_SCRIPT, 'u'),\n rationale:\n '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.',\n 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.',\n appliesTo: 'both',\n};\n\n/** Consecutive comment lines long enough to be narration rather than a note. */\nconst LONG_COMMENT_BLOCK: HygieneRule = {\n id: 'long-comment-block',\n tier: 'warn',\n pattern: new RegExp(\n String.raw`(?:^[ \\t]*(?:\\/\\/|\\/\\*|\\*|#[ \\t])(?![ \\t]*[-=*_~]{4})[ \\t]*\\S[^\\n]*(?:\\n|$)){${MAX_COMMENT_BLOCK_LINES},}`,\n 'm',\n ),\n 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.`,\n fix: 'Cut the block to the part a reader cannot get from the code, and move reference detail into the document that owns it.',\n appliesTo: 'code',\n};\n\n/** A TODO or FIXME marker with no owner and no reason, which nobody can act on\n * and nobody can retire. */\nconst BARE_TODO: HygieneRule = {\n id: 'bare-todo',\n tier: 'warn',\n pattern: new RegExp(\n String.raw`${MARKER}[ \\t]*(?:${MARKER_WORDS.join('|')})\\b(?![^\\n]*${OWNER})(?![^\\n]*\\b(?:${REASON_WORDS.join('|')})\\b)`,\n 'im',\n ),\n rationale:\n '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.',\n fix: 'Name an owner or a tracked issue and the condition that retires the marker, such as \"revisit once the v2 client lands\".',\n appliesTo: 'code',\n};\n\n/** Every rule the gate enforces: the deterministic patterns first, then the\n * judgement calls, then the tokens this project must never ship again. */\nexport const HYGIENE_RULES: readonly HygieneRule[] = [\n DECORATIVE_BANNER,\n WORKFLOW_NARRATION,\n DECORATIVE_EMOJI_IN_COMMENT,\n DECORATIVE_PICTOGRAPH,\n DECORATIVE_EMOJI_IN_PROSE,\n IRREGULAR_SCRIPT_IN_TEXT,\n LONG_COMMENT_BLOCK,\n BARE_TODO,\n DEBT_MARKER_RULE,\n ...RESIDUE_RULES,\n];\n\n/** Checks `text` against every rule that applies to `kind`, and returns the\n * findings in reading order: one per rule per line.\n *\n * A file is exempt from every rule when it carries the marker\n * `HYGIENE_EXEMPT_MARKERS` declares for its kind on a line above the first\n * finding — the rule source itself, the token table, and any guidance or\n * fixture file that has to name what the rules forbid. Such a file states its\n * own exemption, so no consumer keeps a list of paths to skip. A marker placed\n * at or below the first finding exempts nothing.\n *\n * Pure and deterministic: each pattern is recompiled per call, so no\n * `lastIndex` state escapes and the same text always gives the same findings. */\nexport function checkHygiene(text: string, kind: HygieneKind): HygieneFinding[] {\n const findings = collectFindings(text, kind);\n const exemptAt = exemptionLine(text, kind);\n if (exemptAt !== 0 && exemptAt < (findings[0]?.line ?? Number.POSITIVE_INFINITY)) return [];\n return findings;\n}\n\n/** Whether `text` carries the exemption marker for `kind` above the first\n * finding the hygiene rules report, so every rule — the prose finders\n * included — leaves the file alone. The prose finders run outside\n * `checkHygiene`, so consumers that run them gate the file on this predicate\n * rather than on `checkHygiene`'s findings. A marker placed at or below the\n * first finding exempts nothing. */\nexport function hasHygieneExemption(text: string, kind: HygieneKind): boolean {\n const exemptAt = exemptionLine(text, kind);\n if (exemptAt === 0) return false;\n const firstFinding = collectFindings(text, kind)[0];\n return exemptAt < (firstFinding?.line ?? Number.POSITIVE_INFINITY);\n}\n\n/** Every finding `text` produces under `kind`, in reading order. */\nfunction collectFindings(text: string, kind: HygieneKind): HygieneFinding[] {\n const lineStarts = lineStartOffsets(text);\n const findings: HygieneFinding[] = [];\n for (const rule of HYGIENE_RULES) {\n if (rule.appliesTo !== 'both' && rule.appliesTo !== kind) continue;\n let reportedLine = 0;\n for (const match of text.matchAll(globalPattern(rule.pattern))) {\n const line = lineNumberAt(lineStarts, match.index ?? 0);\n // A line with three emoji is one defect, not three.\n if (line === reportedLine) continue;\n reportedLine = line;\n findings.push({\n id: rule.id,\n tier: rule.tier,\n line,\n text: lineTextAt(text, lineStarts, line),\n rationale: rule.rationale,\n fix: rule.fix,\n });\n }\n }\n // Reading order; lines that share a number keep rule-table order, because\n // Array.prototype.sort is stable.\n return findings.sort((a, b) => a.line - b.line);\n}\n\n/** The 1-based line the exemption marker for `kind` sits on, or 0 when the text\n * carries none. The marker is recognised with surrounding whitespace ignored,\n * so an indented comment counts. */\nfunction exemptionLine(text: string, kind: HygieneKind): number {\n const marker = HYGIENE_EXEMPT_MARKERS[kind];\n const lines = text.split('\\n');\n for (let i = 0; i < lines.length; i++) if ((lines[i] ?? '').trim() === marker) return i + 1;\n return 0;\n}\n\n/** The offset each line starts at, so a match offset maps to a line number\n * without rescanning the text once per match. */\nfunction lineStartOffsets(text: string): number[] {\n const starts = [0];\n for (let i = text.indexOf('\\n'); i !== -1; i = text.indexOf('\\n', i + 1)) starts.push(i + 1);\n return starts;\n}\n\nfunction lineNumberAt(lineStarts: readonly number[], index: number): number {\n let low = 0;\n let high = lineStarts.length - 1;\n while (low < high) {\n const mid = Math.ceil((low + high) / 2);\n if ((lineStarts[mid] ?? 0) <= index) low = mid;\n else high = mid - 1;\n }\n return low + 1;\n}\n\nfunction lineTextAt(text: string, lineStarts: readonly number[], line: number): string {\n const start = lineStarts[line - 1] ?? 0;\n const end = text.indexOf('\\n', start);\n return text.slice(start, end === -1 ? text.length : end).trim();\n}\n\n/** A fresh global copy of a rule's pattern: sharing one would leave `lastIndex`\n * behind and make the next call start mid-line. */\nfunction globalPattern(pattern: RegExp): RegExp {\n const flags = pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`;\n return new RegExp(pattern.source, flags);\n}\n","// Offline behavioral-eval harness for the skill pack.\n//\n// Skills are declared as `evals/**/evals.json` per the agentskills.io format\n// (`{skill_name, evals:[{id, prompt, expected_output, assertions?}]}`). The\n// harness loads every suite and runs OFFLINE assertions only — `contains`,\n// `not-contains`, `regex`, `length-gte` — no LLM, no network, so it runs in\n// `pnpm test` (the project's offline/free constraint). This is the executable\n// that turns the JSON declaration into a vitest suite (see test/evals.test.ts).\n//\n// The evals are deliberately simple: they assert that a skill's PROMPT → a\n// simulated agent answer satisfies structural expectations. They are NOT\n// LLM-judge evals (not implemented yet); they are the CI-safe\n// baseline that catches regressions in the skill's core directives.\n//\n// Assertions must be able to run against a CANDIDATE answer (what a model or\n// a stub standing in for one actually produced), not only against the\n// self-declared `expected_output` — otherwise a suite can never fail for the\n// right reason. `evaluateSuite` therefore accepts an optional candidate-output\n// source. Passing no source keeps asserting `expected_output`, so suites that\n// predate candidate output work unchanged; passing a source makes an eval it\n// has no entry for a failure, so an unrecorded answer is never scored a pass.\n\nimport { readdirSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n/** A single offline assertion on the produced output. */\nexport type EvalAssertion =\n | { type: 'contains'; value: string }\n | { type: 'not-contains'; value: string }\n | { type: 'regex'; value: string }\n | { type: 'length-gte'; value: number };\n\n/** One eval case: a prompt + the expected directive the skill must produce. */\nexport interface SkillEval {\n id: string;\n prompt: string;\n expected_output: string;\n assertions?: EvalAssertion[];\n}\n\n/** A suite = one skill's evals (the `evals.json` shape). */\nexport interface EvalSuite {\n skill_name: string;\n evals: SkillEval[];\n}\n\n/**\n * Candidate answers keyed by eval id — what a model (or a stub standing in for\n * one) actually produced. An id present here is the text the assertions run\n * against. An id absent is a failure for that eval: the run recorded no answer,\n * and asserting the golden `expected_output` instead would report a pass the\n * harness cannot vouch for. Pass no source at all to assert `expected_output`,\n * which is how suites that predate candidate output keep working.\n */\nexport type CandidateOutputs = Record<string, string>;\n\n/** Validate an assertion's shape (fail-fast on a malformed evals.json). */\nfunction parseAssertion(a: unknown): EvalAssertion {\n if (typeof a !== 'object' || a === null) throw new Error('assertion must be an object');\n const { type, value } = a as { type?: unknown; value?: unknown };\n switch (type) {\n case 'contains':\n case 'not-contains':\n case 'regex':\n if (typeof value !== 'string' || value.length === 0) {\n throw new Error(`assertion '${String(type)}' requires a non-empty string value`);\n }\n return { type, value } as EvalAssertion;\n case 'length-gte':\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new Error(\"assertion 'length-gte' requires a finite number value\");\n }\n return { type, value } as EvalAssertion;\n default:\n throw new Error(`unknown assertion type '${String(type)}'`);\n }\n}\n\n/** Parse + validate a raw `evals.json` payload. Throws on malformed shape. */\nexport function parseEvalSuite(raw: unknown): EvalSuite {\n if (typeof raw !== 'object' || raw === null) throw new Error('evals.json must be an object');\n const { skill_name, evals } = raw as { skill_name?: unknown; evals?: unknown };\n if (typeof skill_name !== 'string' || skill_name.length === 0) {\n throw new Error('evals.json requires a non-empty string `skill_name`');\n }\n if (!Array.isArray(evals) || evals.length === 0) {\n throw new Error(`evals.json for ${skill_name} requires a non-empty 'evals' array`);\n }\n const parsed: SkillEval[] = evals.map((e, i) => {\n if (typeof e !== 'object' || e === null) throw new Error(`eval[${i}] must be an object`);\n const { id, prompt, expected_output, assertions } = e as {\n id?: unknown;\n prompt?: unknown;\n expected_output?: unknown;\n assertions?: unknown;\n };\n if (typeof id !== 'string' || id.length === 0)\n throw new Error(`eval[${i}] requires a string id`);\n if (typeof prompt !== 'string' || prompt.length === 0) {\n throw new Error(`eval ${id} requires a string prompt`);\n }\n if (typeof expected_output !== 'string') {\n throw new Error(`eval ${id} requires a string expected_output`);\n }\n return {\n id,\n prompt,\n expected_output,\n assertions: Array.isArray(assertions) ? assertions.map(parseAssertion) : undefined,\n };\n });\n return { skill_name, evals: parsed };\n}\n\n/** Run a list of assertions against `output`. Returns pass + per-assertion failures. */\nexport function runAssertions(\n output: string,\n assertions: EvalAssertion[],\n): { pass: boolean; failures: string[] } {\n const failures: string[] = [];\n for (const a of assertions) {\n switch (a.type) {\n case 'contains':\n if (!output.includes(a.value)) failures.push(`expected to contain \"${a.value}\"`);\n break;\n case 'not-contains':\n if (output.includes(a.value)) failures.push(`expected NOT to contain \"${a.value}\"`);\n break;\n case 'regex':\n if (!new RegExp(a.value, 'i').test(output)) failures.push(`expected to match /${a.value}/`);\n break;\n case 'length-gte':\n if (output.length < a.value)\n failures.push(`expected length >= ${a.value} (got ${output.length})`);\n break;\n }\n }\n return { pass: failures.length === 0, failures };\n}\n\n// Package root (evals/ is a sibling of src/): resolve the evals dir the same\n// way discover.ts resolves builtin/. Works under vitest (src) + built (dist).\nconst HERE = dirname(fileURLToPath(import.meta.url));\nconst PKG_ROOT = dirname(HERE);\nexport const EVALS_DIR = join(PKG_ROOT, 'evals');\n\n/**\n * Load every eval suite under `dir` (default the shipped `evals/` dir — each\n * subdir carries an `evals.json`). Skips non-`.json` files and missing dirs\n * (no evals = no-op). Fail-fast on a malformed suite (a broken evals.json is a\n * bug, not a skip).\n */\nexport function loadEvalSuites(dir: string = EVALS_DIR): EvalSuite[] {\n let entries: string[];\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => e.name)\n .sort();\n } catch {\n return []; // no evals/ dir (or unreadable) = no suites shipped\n }\n const suites: EvalSuite[] = [];\n for (const name of entries) {\n const file = join(dir, name, 'evals.json');\n try {\n const raw = JSON.parse(readFileSync(file, 'utf8')) as unknown;\n suites.push(parseEvalSuite(raw));\n } catch (err) {\n // Missing evals.json in a subdir is fine (it may be a scratch dir); a\n // malformed one is not. Distinguish by existence.\n try {\n readFileSync(file, 'utf8');\n throw err; // exists but malformed → rethrow\n } catch (re) {\n if (re === err) throw err;\n // ENOENT → skip silently.\n }\n }\n }\n return suites;\n}\n\n/** Evaluate one suite: for each eval, check its assertions against the\n * candidate output supplied for that eval. With no candidate source at all,\n * each eval is checked against `expected_output` (the directive the skill\n * should produce); with a source that omits an eval, that eval fails as\n * unrecorded. Returns pass/fail per eval with the assertion failures. This is\n * the offline core the vitest runner drives. */\nexport function evaluateSuite(\n suite: EvalSuite,\n candidates?: CandidateOutputs,\n): Array<{ id: string; pass: boolean; failures: string[] }> {\n return suite.evals.map((e) => {\n // A supplied candidate source that has no entry for this eval means the\n // run could not record an answer (a misspelled id, or a model call that\n // never happened). Scoring the golden `expected_output` here would report\n // the very pass this harness exists to prevent, so a missing entry is a\n // failure — but only when a source was supplied at all. With no source, a\n // suite that predates candidate output keeps asserting its declared\n // directive (backward compatible).\n if (candidates && !(e.id in candidates)) {\n return { id: e.id, pass: false, failures: [`no candidate output for eval \"${e.id}\"`] };\n }\n const output = candidates ? (candidates[e.id] ?? e.expected_output) : e.expected_output;\n return {\n id: e.id,\n pass: e.assertions ? runAssertions(output, e.assertions).pass : true,\n failures: e.assertions ? runAssertions(output, e.assertions).failures : [],\n };\n });\n}\n","// Shared text-scan helpers for the prose finders. `slopFindings` and\n// `humanizerFindings` walk the same shape: prose with fenced code and table\n// rows blanked, 1-based lines, paragraph boundaries. The helpers live here once\n// instead of duplicated per finder.\n\n/** Strip fenced code and blank markdown table rows, so prose rules never read\n * inside a ``` block or a table. Tables are layout, not prose, and an\n * auto-generated inventory can legitimately hold an em-dash in every cell.\n * Rows are blanked rather than dropped, so a finding still names the line it\n * sits on in the file. */\nexport function proseOnly(text: string): string {\n return text\n .replace(/```[\\s\\S]*?```/g, '')\n .split('\\n')\n .map((line) => (/^[ \\t]*\\|/.test(line) ? '' : line))\n .join('\\n');\n}\n\n/** The 1-based line of the character at `index`: one plus the count of newlines\n * before it. */\nexport function lineOf(text: string, index: number): number {\n let line = 1;\n for (let i = 0; i < index; i++) if (text[i] === '\\n') line++;\n return line;\n}\n\n/** The index of the first match, or -1 when the pattern matches nothing. */\nexport function firstMatch(text: string, re: RegExp): number {\n const match = re.exec(text);\n return match ? match.index : -1;\n}\n\n/** Yields each paragraph with the 1-based line it starts on. An empty line ends\n * a paragraph, so a single line break continues the paragraph. */\nexport function* paragraphsOf(text: string): Generator<{ text: string; line: number }> {\n let paragraph = '';\n let startLine = 1;\n let line = 1;\n for (const raw of text.split('\\n')) {\n if (raw.trim() === '') {\n if (paragraph.trim() !== '') yield { text: paragraph, line: startLine };\n paragraph = '';\n startLine = line + 1;\n } else {\n if (paragraph === '') startLine = line;\n paragraph += (paragraph === '' ? '' : '\\n') + raw;\n }\n line++;\n }\n if (paragraph.trim() !== '') yield { text: paragraph, line: startLine };\n}\n","// Prose tells: the markers that make generated prose read as machine output,\n// from em-dash density to hedging openers and formulaic not-X-but-Y contrasts.\n// `humanizerFindings` is a pure scan of a text that has had fenced code\n// stripped, so the rules never read inside ``` blocks.\nimport type { HygieneFinding } from './hygiene.js';\nimport { firstMatch, lineOf, paragraphsOf, proseOnly } from './text-scan.js';\n\nconst HEDGES = [\n 'it is worth mentioning',\n 'it should be noted',\n 'it goes without saying',\n 'arguably',\n] as const;\n\n// More em-dashes than this in one paragraph is the tell the finder reports.\nconst EM_DASH_PER_PARAGRAPH = 2;\n\nexport function humanizerFindings(text: string): HygieneFinding[] {\n const prose = proseOnly(text);\n const findings: HygieneFinding[] = [];\n\n for (const hedge of HEDGES) {\n const index = firstMatch(prose, new RegExp(hedge, 'i'));\n if (index !== -1) {\n findings.push({\n id: 'humanizer-hedge',\n tier: 'warn',\n line: lineOf(prose, index),\n text: hedge,\n rationale: 'a hedging opener',\n fix: 'delete it',\n });\n }\n }\n\n const contrastIndex = firstMatch(prose, /\\bnot\\s+\\w+[\\w\\s,]{0,40}\\bbut\\b/i);\n if (contrastIndex !== -1) {\n findings.push({\n id: 'humanizer-contrast',\n tier: 'warn',\n line: lineOf(prose, contrastIndex),\n text: 'not X but Y',\n rationale: 'a formulaic contrast',\n fix: 'state the second clause alone',\n });\n }\n\n for (const { text: paragraph, line } of paragraphsOf(prose)) {\n const count = (paragraph.match(/—/g) ?? []).length;\n if (count > EM_DASH_PER_PARAGRAPH) {\n findings.push({\n id: 'humanizer-em-dash',\n tier: 'warn',\n line,\n text: `${count} em-dashes`,\n rationale: 'em-dash density is an AI tell',\n fix: 'use periods or parentheticals',\n });\n }\n }\n\n return findings;\n}\n","// Runtime-derived skill registry.\n//\n// The registry is NOT a committed file: frontmatter is the single source of\n// truth and `buildRegistry()` derives a queryable index from `discoverAll()`\n// on demand (the CLI calls it; no drift risk, since nothing is persisted). Every\n// field comes from either frontmatter (`metadata.category`, `metadata.version`)\n// or a shape check (`status` from the stub marker, `referenceCount`/`lines`\n// from the discovered files).\n\nimport { discoverAll } from './discover.js';\nimport type { BuiltinSkill } from './types.js';\n\n/** The pack-wide `noir-` namespace a registry entry always belongs to. */\nexport const NOIR_NAMESPACE = 'noir-';\n\n/** One row in the derived registry. */\nexport interface SkillRegistryEntry {\n /** Canonical `noir-<kebab>` id (== dir name). */\n name: string;\n /** `builtin` for the shipped pack; `integration` for an `integrations/<name>/`. */\n kind: 'builtin' | 'integration';\n /** Category from `metadata.category`; falls back to the `noir-`-stripped name\n * (a newly-authored skill still gets a sensible cell). */\n category: string;\n /** Per-skill version from `metadata.version`; defaults to `0.0.0` when absent\n * (the pack-level version in package.json is the authoritative release). */\n version: string;\n /** `full` when the body has no `> **Stub:**` marker; `stub` otherwise. */\n status: 'full' | 'stub';\n /** Lifecycle stage of the skill. `active` = currently shipped and usable;\n * `deprecated` = superseded (a renamed/merged predecessor).\n * The curated pack has no deprecated members today. */\n lifecycle: 'active' | 'deprecated';\n /** The WHAT+WHEN description (the trigger the host sees). */\n description: string;\n /** Number of `references/*.md` files. */\n referenceCount: number;\n /** Total SKILL.md line count (frontmatter + body). */\n lines: number;\n}\n\n/** Derive the category from the skill name when `metadata.category` is absent. */\nfunction fallbackCategory(name: string): string {\n return name.replace(/^noir-/, '') || 'general';\n}\n\n/** Map a discovered builtin/integration to a registry row. */\nfunction toEntry(s: BuiltinSkill, kind: 'builtin' | 'integration'): SkillRegistryEntry {\n const category = s.frontmatter.metadata?.category?.trim() || fallbackCategory(s.name);\n const version = s.frontmatter.metadata?.version?.trim() || '0.0.0';\n const status: 'full' | 'stub' = s.skillMd.includes('> **Stub:**') ? 'stub' : 'full';\n const lines = s.skillMd.split('\\n').length;\n return {\n name: s.name,\n kind,\n category,\n version,\n status,\n // Every shipped skill is active; `deprecated` is reserved for a future\n // superseded member.\n lifecycle: 'active' as const,\n description: typeof s.frontmatter.description === 'string' ? s.frontmatter.description : '',\n referenceCount: s.references.length,\n lines,\n };\n}\n\n/**\n * Build the full registry from `discoverAll()` — builtins + integrations in\n * one list, sorted by name. Pure (no I/O beyond discovery); never throws for\n * a malformed skill (a discovery failure surfaces upstream, the same as the\n * compiler's fail-fast).\n */\nexport function buildRegistry(): SkillRegistryEntry[] {\n const { builtins, integrations } = discoverAll();\n return [\n ...builtins.map((b) => toEntry(b, 'builtin')),\n ...integrations.map((i) => toEntry(i, 'integration')),\n ].sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/** Convenience: registry filtered to a single category (CLI grouping). */\nexport function registryByCategory(category: string): SkillRegistryEntry[] {\n return buildRegistry().filter((e) => e.category === category);\n}\n","// Lexical anti-slop rules: the words and phrases that make generated prose read\n// as machine output. `slopFindings` is a pure scan of a text that has had\n// fenced code stripped, so prose rules never read inside ``` blocks.\nimport type { HygieneFinding } from './hygiene.js';\nimport { firstMatch, lineOf, paragraphsOf, proseOnly } from './text-scan.js';\n\nexport const KILL_ON_SIGHT = [\n 'delve',\n 'utilize',\n 'leverage',\n 'facilitate',\n 'elucidate',\n 'embark',\n 'endeavor',\n 'encompass',\n 'multifaceted',\n 'tapestry',\n 'testament',\n 'paradigm',\n 'synergy',\n 'holistic',\n 'catalyze',\n 'juxtapose',\n 'realm',\n 'landscape',\n 'myriad',\n 'plethora',\n] as const;\n\nexport const CLUSTER_WORDS = [\n 'robust',\n 'comprehensive',\n 'seamless',\n 'cutting-edge',\n 'innovative',\n 'streamline',\n 'empower',\n 'foster',\n 'enhance',\n 'elevate',\n 'optimize',\n 'scalable',\n 'pivotal',\n 'intricate',\n 'profound',\n 'resonate',\n 'underscore',\n 'harness',\n 'navigate',\n 'cultivate',\n 'bolster',\n 'galvanize',\n 'cornerstone',\n 'game-changer',\n] as const;\n\nexport const FILLER_PHRASES = [\n 'it is important to note',\n \"it's important to note\",\n 'it is worth noting',\n \"in today's\",\n 'in conclusion',\n 'needless to say',\n] as const;\n\nexport const SLOP_TRIGRAMS = [\n 'a testament to',\n 'a tapestry of',\n 'in the realm of',\n 'the power of',\n 'serves as a',\n 'in order to',\n 'the fact that',\n 'plays a crucial',\n] as const;\n\n// `harness` is a legitimate technical term (e.g. \"test harness\"); it is never flagged.\nexport const SLOP_ALLOWLIST: readonly string[] = ['harness'];\n\n// Whole words only; case-insensitive. A hyphen or apostrophe inside the word may\n// appear as the character or as a space, so \"cutting-edge\" and \"cutting edge\"\n// both match.\nfunction wordRe(word: string): RegExp {\n return new RegExp(`\\\\b${word.replace(/[-']/g, '[-\\\\s]?')}\\\\b`, 'i');\n}\n\nconst CLUSTER_THRESHOLD = 3;\n\nexport function slopFindings(text: string): HygieneFinding[] {\n const prose = proseOnly(text);\n const findings: HygieneFinding[] = [];\n\n for (const word of KILL_ON_SIGHT) {\n if (SLOP_ALLOWLIST.includes(word)) continue;\n const index = firstMatch(prose, wordRe(word));\n if (index !== -1) {\n findings.push({\n id: 'slop-kill',\n tier: 'warn',\n line: lineOf(prose, index),\n text: word,\n rationale: 'a word LLMs overuse, flagged on first use',\n fix: `replace \"${word}\" with a plain verb or noun`,\n });\n }\n }\n\n for (const { text: paragraph, line } of paragraphsOf(prose)) {\n const hits = CLUSTER_WORDS.filter(\n (w) => !SLOP_ALLOWLIST.includes(w) && wordRe(w).test(paragraph),\n ).length;\n if (hits >= CLUSTER_THRESHOLD) {\n findings.push({\n id: 'slop-cluster',\n tier: 'warn',\n line,\n text: `${hits} cluster words`,\n rationale: 'a paragraph dense with abstract praise reads machine-written',\n fix: 'rewrite in concrete terms; keep at most one such word',\n });\n }\n }\n\n for (const phrase of [...FILLER_PHRASES, ...SLOP_TRIGRAMS]) {\n const index = firstMatch(prose, new RegExp(phrase.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'i'));\n if (index !== -1) {\n findings.push({\n id: 'slop-phrase',\n tier: 'warn',\n line: lineOf(prose, index),\n text: phrase,\n rationale: 'a filler phrase or slop trigram',\n fix: 'delete it or state the point directly',\n });\n }\n }\n\n return findings;\n}\n"],"mappings":"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","names":["dirname","join","literal","exemptionLine","join","dirname","readdirSync","readFileSync","dirname","join","fileURLToPath","HERE","PKG_ROOT"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noir-ai/skills",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0-beta.1",
|
|
4
4
|
"description": "Noir skills — the builtin noir-* skill pack, the integrations pack (ClickUp first), and a copy-and-validate compiler.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "agaaaptr",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"yaml": "^2.5.0",
|
|
50
50
|
"zod": "^4.2.0",
|
|
51
|
-
"@noir-ai/core": "1.
|
|
51
|
+
"@noir-ai/core": "1.18.0-beta.1"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/node": "^22.17.0"
|