@fleetagent/pi-coding-agent 0.0.11 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/dist/cli/args.d.ts +3 -2
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +20 -8
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts +9 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +59 -11
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +30 -7
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/rules.d.ts.map +1 -1
- package/dist/core/rules.js +20 -15
- package/dist/core/rules.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +20 -15
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/source-info.d.ts.map +1 -1
- package/dist/core/source-info.js +1 -1
- package/dist/core/source-info.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +6 -3
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/operations.d.ts +49 -4
- package/dist/core/tools/operations.d.ts.map +1 -1
- package/dist/core/tools/operations.js +340 -4
- package/dist/core/tools/operations.js.map +1 -1
- package/dist/core/tools/read.d.ts +2 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +12 -5
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +2 -0
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +30 -15
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +3 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +41 -20
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +11 -4
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +6 -6
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +11 -7
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +10 -4
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/extensions.md +2 -2
- package/docs/rpc.md +31 -0
- package/docs/usage.md +4 -1
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
package/dist/core/rules.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/core/rules.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAA6B,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AA2D5D,MAAM,WAAW,eAAe;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,IAAI;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;IACvB,sBAAsB,EAAE,OAAO,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,WAAW,EAAE,kBAAkB,EAAE,CAAC;CAClC;AA2CD,MAAM,WAAW,uBAAuB;IACvC,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;CACf;AA0BD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,eAAe,CAGlF;AAyJD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CA0B1D;AAWD,MAAM,WAAW,gBAAgB;IAChC,iDAAiD;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,yCAAyC;IACzC,eAAe,EAAE,OAAO,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAoGpE;AA6JD,MAAM,WAAW,8BAA+B,SAAQ,gBAAgB;IACvE,UAAU,EAAE,cAAc,CAAC;CAC3B;AAED,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,8BAA8B,GAAG,OAAO,CAAC,eAAe,CAAC,CAuF/G","sourcesContent":["import { existsSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport ignore from \"ignore\";\nimport { basename, dirname, join, relative, resolve, sep } from \"path\";\nimport { CONFIG_DIR_NAME, getAgentDir } from \"../config.ts\";\nimport { parseFrontmatter } from \"../utils/frontmatter.ts\";\nimport { canonicalizePath, resolvePath } from \"../utils/paths.ts\";\nimport type { ResourceDiagnostic } from \"./diagnostics.ts\";\nimport { createSyntheticSourceInfo, type SourceInfo } from \"./source-info.ts\";\nimport type { ToolOperations } from \"./tools/operations.ts\";\n\n/** Max name length per spec */\nconst MAX_NAME_LENGTH = 64;\n\n/** Max description length per spec */\nconst MAX_DESCRIPTION_LENGTH = 1024;\n\nconst IGNORE_FILE_NAMES = [\".gitignore\", \".ignore\", \".fdignore\"];\n\ntype IgnoreMatcher = ReturnType<typeof ignore>;\n\nfunction toPosixPath(p: string): string {\n\treturn p.split(sep).join(\"/\");\n}\n\nfunction prefixIgnorePattern(line: string, prefix: string): string | null {\n\tconst trimmed = line.trim();\n\tif (!trimmed) return null;\n\tif (trimmed.startsWith(\"#\") && !trimmed.startsWith(\"\\\\#\")) return null;\n\n\tlet pattern = line;\n\tlet negated = false;\n\n\tif (pattern.startsWith(\"!\")) {\n\t\tnegated = true;\n\t\tpattern = pattern.slice(1);\n\t} else if (pattern.startsWith(\"\\\\!\")) {\n\t\tpattern = pattern.slice(1);\n\t}\n\n\tif (pattern.startsWith(\"/\")) {\n\t\tpattern = pattern.slice(1);\n\t}\n\n\tconst prefixed = prefix ? `${prefix}${pattern}` : pattern;\n\treturn negated ? `!${prefixed}` : prefixed;\n}\n\nfunction addIgnoreRules(ig: IgnoreMatcher, dir: string, rootDir: string): void {\n\tconst relativeDir = relative(rootDir, dir);\n\tconst prefix = relativeDir ? `${toPosixPath(relativeDir)}/` : \"\";\n\n\tfor (const filename of IGNORE_FILE_NAMES) {\n\t\tconst ignorePath = join(dir, filename);\n\t\tif (!existsSync(ignorePath)) continue;\n\t\ttry {\n\t\t\tconst content = readFileSync(ignorePath, \"utf-8\");\n\t\t\tconst patterns = content\n\t\t\t\t.split(/\\r?\\n/)\n\t\t\t\t.map((line) => prefixIgnorePattern(line, prefix))\n\t\t\t\t.filter((line): line is string => Boolean(line));\n\t\t\tif (patterns.length > 0) {\n\t\t\t\tig.add(patterns);\n\t\t\t}\n\t\t} catch {}\n\t}\n}\n\nexport interface RuleFrontmatter {\n\tname?: string;\n\tdescription?: string;\n\t\"disable-model-invocation\"?: boolean;\n\t[key: string]: unknown;\n}\n\nexport interface Rule {\n\tname: string;\n\tdescription: string;\n\tfilePath: string;\n\tbaseDir: string;\n\tsourceInfo: SourceInfo;\n\tdisableModelInvocation: boolean;\n\tcontent?: string;\n}\n\nexport interface LoadRulesResult {\n\trules: Rule[];\n\tdiagnostics: ResourceDiagnostic[];\n}\n\n/**\n * Validate rule name.\n * Returns array of validation error messages (empty if valid).\n */\nfunction validateName(name: string): string[] {\n\tconst errors: string[] = [];\n\n\tif (name.length > MAX_NAME_LENGTH) {\n\t\terrors.push(`name exceeds ${MAX_NAME_LENGTH} characters (${name.length})`);\n\t}\n\n\tif (!/^[a-z0-9-]+$/.test(name)) {\n\t\terrors.push(`name contains invalid characters (must be lowercase a-z, 0-9, hyphens only)`);\n\t}\n\n\tif (name.startsWith(\"-\") || name.endsWith(\"-\")) {\n\t\terrors.push(`name must not start or end with a hyphen`);\n\t}\n\n\tif (name.includes(\"--\")) {\n\t\terrors.push(`name must not contain consecutive hyphens`);\n\t}\n\n\treturn errors;\n}\n\n/**\n * Validate description.\n */\nfunction validateDescription(description: string | undefined): string[] {\n\tconst errors: string[] = [];\n\n\tif (!description || description.trim() === \"\") {\n\t\terrors.push(\"description is required\");\n\t} else if (description.length > MAX_DESCRIPTION_LENGTH) {\n\t\terrors.push(`description exceeds ${MAX_DESCRIPTION_LENGTH} characters (${description.length})`);\n\t}\n\n\treturn errors;\n}\n\nexport interface LoadRulesFromDirOptions {\n\t/** Directory to scan for rules */\n\tdir: string;\n\t/** Source identifier for these rules */\n\tsource: string;\n}\n\nfunction createRuleSourceInfo(filePath: string, baseDir: string, source: string): SourceInfo {\n\tswitch (source) {\n\t\tcase \"user\":\n\t\t\treturn createSyntheticSourceInfo(filePath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"user\",\n\t\t\t\tbaseDir,\n\t\t\t});\n\t\tcase \"project\":\n\t\t\treturn createSyntheticSourceInfo(filePath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"project\",\n\t\t\t\tbaseDir,\n\t\t\t});\n\t\tcase \"path\":\n\t\t\treturn createSyntheticSourceInfo(filePath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tbaseDir,\n\t\t\t});\n\t\tdefault:\n\t\t\treturn createSyntheticSourceInfo(filePath, { source, baseDir });\n\t}\n}\n\n/**\n * Load rules from a directory.\n *\n * Discovery rules:\n * - if a directory contains RULES.md, treat it as a rule root and do not recurse further\n * - otherwise, load direct .md children in the root\n * - recurse into subdirectories to find RULES.md\n */\nexport function loadRulesFromDir(options: LoadRulesFromDirOptions): LoadRulesResult {\n\tconst { dir, source } = options;\n\treturn loadRulesFromDirInternal(dir, source, true);\n}\n\nfunction loadRulesFromDirInternal(\n\tdir: string,\n\tsource: string,\n\tincludeRootFiles: boolean,\n\tignoreMatcher?: IgnoreMatcher,\n\trootDir?: string,\n): LoadRulesResult {\n\tconst rules: Rule[] = [];\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn { rules, diagnostics };\n\t}\n\n\tconst root = rootDir ?? dir;\n\tconst ig = ignoreMatcher ?? ignore();\n\taddIgnoreRules(ig, dir, root);\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name !== \"RULES.md\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tisFile = statSync(fullPath).isFile();\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tif (!isFile || ig.ignores(relPath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst result = loadRuleFromFile(fullPath, source);\n\t\t\tif (result.rule) {\n\t\t\t\trules.push(result.rule);\n\t\t\t}\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t\treturn { rules, diagnostics };\n\t\t}\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name.startsWith(\".\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Skip node_modules to avoid scanning dependencies\n\t\t\tif (entry.name === \"node_modules\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\t// For symlinks, check if they point to a directory and follow them\n\t\t\tlet isDirectory = entry.isDirectory();\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(fullPath);\n\t\t\t\t\tisDirectory = stats.isDirectory();\n\t\t\t\t\tisFile = stats.isFile();\n\t\t\t\t} catch {\n\t\t\t\t\t// Broken symlink, skip it\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tconst ignorePath = isDirectory ? `${relPath}/` : relPath;\n\t\t\tif (ig.ignores(ignorePath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (isDirectory) {\n\t\t\t\tconst subResult = loadRulesFromDirInternal(fullPath, source, false, ig, root);\n\t\t\t\trules.push(...subResult.rules);\n\t\t\t\tdiagnostics.push(...subResult.diagnostics);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!isFile || !includeRootFiles || !entry.name.endsWith(\".md\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst result = loadRuleFromFile(fullPath, source);\n\t\t\tif (result.rule) {\n\t\t\t\trules.push(result.rule);\n\t\t\t}\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t}\n\t} catch {}\n\n\treturn { rules, diagnostics };\n}\n\nfunction loadRuleFromFile(filePath: string, source: string): { rule: Rule | null; diagnostics: ResourceDiagnostic[] } {\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\ttry {\n\t\tconst rawContent = readFileSync(filePath, \"utf-8\");\n\t\tconst { frontmatter } = parseFrontmatter<RuleFrontmatter>(rawContent);\n\t\tconst ruleDir = dirname(filePath);\n\t\tconst parentDirName = basename(ruleDir);\n\n\t\t// Validate description\n\t\tconst descErrors = validateDescription(frontmatter.description);\n\t\tfor (const error of descErrors) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\t// Use name from frontmatter, or fall back to parent directory name\n\t\tconst name = frontmatter.name || parentDirName;\n\n\t\t// Validate name\n\t\tconst nameErrors = validateName(name);\n\t\tfor (const error of nameErrors) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\t// Still load the rule even with warnings (unless description is completely missing)\n\t\tif (!frontmatter.description || frontmatter.description.trim() === \"\") {\n\t\t\treturn { rule: null, diagnostics };\n\t\t}\n\n\t\treturn {\n\t\t\trule: {\n\t\t\t\tname,\n\t\t\t\tdescription: frontmatter.description,\n\t\t\t\tfilePath,\n\t\t\t\tbaseDir: ruleDir,\n\t\t\t\tsourceInfo: createRuleSourceInfo(filePath, ruleDir, source),\n\t\t\t\tdisableModelInvocation: frontmatter[\"disable-model-invocation\"] === true,\n\t\t\t},\n\t\t\tdiagnostics,\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : \"failed to parse rule file\";\n\t\tdiagnostics.push({ type: \"warning\", message, path: filePath });\n\t\treturn { rule: null, diagnostics };\n\t}\n}\n\n/**\n * Format rules for inclusion in a system prompt.\n * Rules with disableModelInvocation=true are excluded from the prompt\n * (they can only be invoked explicitly via /rule:name commands).\n */\nexport function formatRulesForPrompt(rules: Rule[]): string {\n\tconst visibleRules = rules.filter((s) => !s.disableModelInvocation);\n\n\tif (visibleRules.length === 0) {\n\t\treturn \"\";\n\t}\n\n\tconst lines = [\n\t\t\"\\n\\nThe following rules provide mandatory constraints and policies.\",\n\t\t\"Use the read tool to load a rule's file when the task or files match its description; applicable rules are mandatory.\",\n\t\t\"When a rule file references a relative path, resolve it against the rule directory (parent of RULES.md / dirname of the path) and use that absolute path in tool commands.\",\n\t\t\"\",\n\t\t\"<available_rules>\",\n\t];\n\n\tfor (const rule of visibleRules) {\n\t\tlines.push(\" <rule>\");\n\t\tlines.push(` <name>${escapeXml(rule.name)}</name>`);\n\t\tlines.push(` <description>${escapeXml(rule.description)}</description>`);\n\t\tlines.push(` <location>${escapeXml(rule.filePath)}</location>`);\n\t\tlines.push(\" </rule>\");\n\t}\n\n\tlines.push(\"</available_rules>\");\n\n\treturn lines.join(\"\\n\");\n}\n\nfunction escapeXml(str: string): string {\n\treturn str\n\t\t.replace(/&/g, \"&\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\");\n}\n\nexport interface LoadRulesOptions {\n\t/** Working directory for project-local rules. */\n\tcwd: string;\n\t/** Agent config directory for global rules. */\n\tagentDir: string;\n\t/** Explicit rule paths (files or directories) */\n\trulePaths: string[];\n\t/** Include default rules directories. */\n\tincludeDefaults: boolean;\n}\n\n/**\n * Load rules from all configured locations.\n * Returns rules and any validation diagnostics.\n */\nexport function loadRules(options: LoadRulesOptions): LoadRulesResult {\n\tconst { agentDir, rulePaths, includeDefaults } = options;\n\n\t// Resolve agentDir - if not provided, use default from config\n\tconst resolvedCwd = resolvePath(options.cwd);\n\tconst resolvedAgentDir = resolvePath(agentDir ?? getAgentDir());\n\n\tconst ruleMap = new Map<string, Rule>();\n\tconst realPathSet = new Set<string>();\n\tconst allDiagnostics: ResourceDiagnostic[] = [];\n\tconst collisionDiagnostics: ResourceDiagnostic[] = [];\n\n\tfunction addRules(result: LoadRulesResult) {\n\t\tallDiagnostics.push(...result.diagnostics);\n\t\tfor (const rule of result.rules) {\n\t\t\t// Resolve symlinks to detect duplicate files\n\t\t\tconst realPath = canonicalizePath(rule.filePath);\n\n\t\t\t// Skip silently if we've already loaded this exact file (via symlink)\n\t\t\tif (realPathSet.has(realPath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst existing = ruleMap.get(rule.name);\n\t\t\tif (existing) {\n\t\t\t\tcollisionDiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"${rule.name}\" collision`,\n\t\t\t\t\tpath: rule.filePath,\n\t\t\t\t\tcollision: {\n\t\t\t\t\t\tresourceType: \"rule\",\n\t\t\t\t\t\tname: rule.name,\n\t\t\t\t\t\twinnerPath: existing.filePath,\n\t\t\t\t\t\tloserPath: rule.filePath,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\truleMap.set(rule.name, rule);\n\t\t\t\trealPathSet.add(realPath);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (includeDefaults) {\n\t\taddRules(loadRulesFromDirInternal(join(resolvedAgentDir, \"rules\"), \"user\", true));\n\t\taddRules(loadRulesFromDirInternal(resolve(resolvedCwd, CONFIG_DIR_NAME, \"rules\"), \"project\", true));\n\t}\n\n\tconst userRulesDir = join(resolvedAgentDir, \"rules\");\n\tconst projectRulesDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"rules\");\n\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) {\n\t\t\treturn true;\n\t\t}\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\n\tconst getSource = (resolvedPath: string): \"user\" | \"project\" | \"path\" => {\n\t\tif (!includeDefaults) {\n\t\t\tif (isUnderPath(resolvedPath, userRulesDir)) return \"user\";\n\t\t\tif (isUnderPath(resolvedPath, projectRulesDir)) return \"project\";\n\t\t}\n\t\treturn \"path\";\n\t};\n\n\tfor (const rawPath of rulePaths) {\n\t\tconst resolvedPath = resolvePath(rawPath, resolvedCwd, { trim: true });\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tallDiagnostics.push({ type: \"warning\", message: \"rule path does not exist\", path: resolvedPath });\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst stats = statSync(resolvedPath);\n\t\t\tconst source = getSource(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\taddRules(loadRulesFromDirInternal(resolvedPath, source, true));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst result = loadRuleFromFile(resolvedPath, source);\n\t\t\t\tif (result.rule) {\n\t\t\t\t\taddRules({ rules: [result.rule], diagnostics: result.diagnostics });\n\t\t\t\t} else {\n\t\t\t\t\tallDiagnostics.push(...result.diagnostics);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tallDiagnostics.push({ type: \"warning\", message: \"rule path is not a markdown file\", path: resolvedPath });\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to read rule path\";\n\t\t\tallDiagnostics.push({ type: \"warning\", message, path: resolvedPath });\n\t\t}\n\t}\n\n\treturn {\n\t\trules: Array.from(ruleMap.values()),\n\t\tdiagnostics: [...allDiagnostics, ...collisionDiagnostics],\n\t};\n}\n\nasync function backendPathExists(operations: ToolOperations, path: string): Promise<boolean> {\n\ttry {\n\t\tawait operations.access(path, \"exists\");\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nasync function addIgnoreRulesWithOperations(\n\toperations: ToolOperations,\n\tig: IgnoreMatcher,\n\tdir: string,\n\trootDir: string,\n): Promise<void> {\n\tconst relativeDir = relative(rootDir, dir);\n\tconst prefix = relativeDir ? `${toPosixPath(relativeDir)}/` : \"\";\n\n\tfor (const filename of IGNORE_FILE_NAMES) {\n\t\tconst ignorePath = join(dir, filename);\n\t\tif (!(await backendPathExists(operations, ignorePath))) continue;\n\t\ttry {\n\t\t\tconst content = (await operations.readFile(ignorePath)).toString(\"utf-8\");\n\t\t\tconst patterns = content\n\t\t\t\t.split(/\\r?\\n/)\n\t\t\t\t.map((line) => prefixIgnorePattern(line, prefix))\n\t\t\t\t.filter((line): line is string => Boolean(line));\n\t\t\tif (patterns.length > 0) {\n\t\t\t\tig.add(patterns);\n\t\t\t}\n\t\t} catch {}\n\t}\n}\n\nasync function loadRuleFromFileWithOperations(\n\toperations: ToolOperations,\n\tfilePath: string,\n\tsource: string,\n): Promise<{ rule: Rule | null; diagnostics: ResourceDiagnostic[] }> {\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\ttry {\n\t\tconst rawContent = (await operations.readFile(filePath)).toString(\"utf-8\");\n\t\tconst { frontmatter } = parseFrontmatter<RuleFrontmatter>(rawContent);\n\t\tconst ruleDir = dirname(filePath);\n\t\tconst parentDirName = basename(ruleDir);\n\n\t\tfor (const error of validateDescription(frontmatter.description)) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\tconst name = frontmatter.name || parentDirName;\n\t\tfor (const error of validateName(name)) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\tif (!frontmatter.description || frontmatter.description.trim() === \"\") {\n\t\t\treturn { rule: null, diagnostics };\n\t\t}\n\n\t\treturn {\n\t\t\trule: {\n\t\t\t\tname,\n\t\t\t\tdescription: frontmatter.description,\n\t\t\t\tfilePath,\n\t\t\t\tbaseDir: ruleDir,\n\t\t\t\tsourceInfo: createRuleSourceInfo(filePath, ruleDir, source),\n\t\t\t\tdisableModelInvocation: frontmatter[\"disable-model-invocation\"] === true,\n\t\t\t},\n\t\t\tdiagnostics,\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : \"failed to parse rule file\";\n\t\tdiagnostics.push({ type: \"warning\", message, path: filePath });\n\t\treturn { rule: null, diagnostics };\n\t}\n}\n\nasync function loadRulesFromDirInternalWithOperations(\n\toperations: ToolOperations,\n\tdir: string,\n\tsource: string,\n\tincludeRootFiles: boolean,\n\tignoreMatcher?: IgnoreMatcher,\n\trootDir?: string,\n): Promise<LoadRulesResult> {\n\tconst rules: Rule[] = [];\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\tif (!(await backendPathExists(operations, dir))) {\n\t\treturn { rules, diagnostics };\n\t}\n\n\tconst root = rootDir ?? dir;\n\tconst ig = ignoreMatcher ?? ignore();\n\tawait addIgnoreRulesWithOperations(operations, ig, dir, root);\n\n\ttry {\n\t\tconst entries = await operations.readdir(dir);\n\n\t\tfor (const name of entries) {\n\t\t\tif (name !== \"RULES.md\") continue;\n\t\t\tconst fullPath = join(dir, name);\n\t\t\tlet isFile = false;\n\t\t\ttry {\n\t\t\t\tisFile = (await operations.stat(fullPath)).isFile();\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tif (!isFile || ig.ignores(relPath)) continue;\n\t\t\tconst result = await loadRuleFromFileWithOperations(operations, fullPath, source);\n\t\t\tif (result.rule) rules.push(result.rule);\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t\treturn { rules, diagnostics };\n\t\t}\n\n\t\tfor (const name of entries) {\n\t\t\tif (name.startsWith(\".\") || name === \"node_modules\") continue;\n\t\t\tconst fullPath = join(dir, name);\n\t\t\tlet isDirectory = false;\n\t\t\tlet isFile = false;\n\t\t\ttry {\n\t\t\t\tconst stats = await operations.stat(fullPath);\n\t\t\t\tisDirectory = stats.isDirectory();\n\t\t\t\tisFile = stats.isFile();\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tconst ignorePath = isDirectory ? `${relPath}/` : relPath;\n\t\t\tif (ig.ignores(ignorePath)) continue;\n\t\t\tif (isDirectory) {\n\t\t\t\tconst subResult = await loadRulesFromDirInternalWithOperations(\n\t\t\t\t\toperations,\n\t\t\t\t\tfullPath,\n\t\t\t\t\tsource,\n\t\t\t\t\tfalse,\n\t\t\t\t\tig,\n\t\t\t\t\troot,\n\t\t\t\t);\n\t\t\t\trules.push(...subResult.rules);\n\t\t\t\tdiagnostics.push(...subResult.diagnostics);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!isFile || !includeRootFiles || !name.endsWith(\".md\")) continue;\n\t\t\tconst result = await loadRuleFromFileWithOperations(operations, fullPath, source);\n\t\t\tif (result.rule) rules.push(result.rule);\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t}\n\t} catch {}\n\n\treturn { rules, diagnostics };\n}\n\nexport interface LoadRulesWithOperationsOptions extends LoadRulesOptions {\n\toperations: ToolOperations;\n}\n\nexport async function loadRulesWithOperations(options: LoadRulesWithOperationsOptions): Promise<LoadRulesResult> {\n\tconst { agentDir, rulePaths, includeDefaults, operations } = options;\n\tconst resolvedCwd = resolvePath(options.cwd);\n\tconst resolvedAgentDir = resolvePath(agentDir ?? getAgentDir());\n\tconst ruleMap = new Map<string, Rule>();\n\tconst pathSet = new Set<string>();\n\tconst allDiagnostics: ResourceDiagnostic[] = [];\n\tconst collisionDiagnostics: ResourceDiagnostic[] = [];\n\n\tfunction addRules(result: LoadRulesResult) {\n\t\tallDiagnostics.push(...result.diagnostics);\n\t\tfor (const rule of result.rules) {\n\t\t\tconst canonicalPath = resolve(rule.filePath);\n\t\t\tif (pathSet.has(canonicalPath)) continue;\n\t\t\tconst existing = ruleMap.get(rule.name);\n\t\t\tif (existing) {\n\t\t\t\tcollisionDiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"${rule.name}\" collision`,\n\t\t\t\t\tpath: rule.filePath,\n\t\t\t\t\tcollision: {\n\t\t\t\t\t\tresourceType: \"rule\",\n\t\t\t\t\t\tname: rule.name,\n\t\t\t\t\t\twinnerPath: existing.filePath,\n\t\t\t\t\t\tloserPath: rule.filePath,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\truleMap.set(rule.name, rule);\n\t\t\t\tpathSet.add(canonicalPath);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (includeDefaults) {\n\t\taddRules(await loadRulesFromDirInternalWithOperations(operations, join(resolvedAgentDir, \"rules\"), \"user\", true));\n\t\taddRules(\n\t\t\tawait loadRulesFromDirInternalWithOperations(\n\t\t\t\toperations,\n\t\t\t\tresolve(resolvedCwd, CONFIG_DIR_NAME, \"rules\"),\n\t\t\t\t\"project\",\n\t\t\t\ttrue,\n\t\t\t),\n\t\t);\n\t}\n\n\tconst userRulesDir = join(resolvedAgentDir, \"rules\");\n\tconst projectRulesDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"rules\");\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) return true;\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\tconst getSource = (resolvedPath: string): \"user\" | \"project\" | \"path\" => {\n\t\tif (!includeDefaults) {\n\t\t\tif (isUnderPath(resolvedPath, userRulesDir)) return \"user\";\n\t\t\tif (isUnderPath(resolvedPath, projectRulesDir)) return \"project\";\n\t\t}\n\t\treturn \"path\";\n\t};\n\n\tfor (const rawPath of rulePaths) {\n\t\tconst resolvedPath = resolvePath(rawPath, resolvedCwd, { trim: true });\n\t\tif (!(await backendPathExists(operations, resolvedPath))) {\n\t\t\tallDiagnostics.push({ type: \"warning\", message: \"rule path does not exist\", path: resolvedPath });\n\t\t\tcontinue;\n\t\t}\n\t\ttry {\n\t\t\tconst stats = await operations.stat(resolvedPath);\n\t\t\tconst source = getSource(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\taddRules(await loadRulesFromDirInternalWithOperations(operations, resolvedPath, source, true));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst result = await loadRuleFromFileWithOperations(operations, resolvedPath, source);\n\t\t\t\tif (result.rule) addRules({ rules: [result.rule], diagnostics: result.diagnostics });\n\t\t\t\telse allDiagnostics.push(...result.diagnostics);\n\t\t\t} else {\n\t\t\t\tallDiagnostics.push({ type: \"warning\", message: \"rule path is not a markdown file\", path: resolvedPath });\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to read rule path\";\n\t\t\tallDiagnostics.push({ type: \"warning\", message, path: resolvedPath });\n\t\t}\n\t}\n\n\treturn { rules: Array.from(ruleMap.values()), diagnostics: [...allDiagnostics, ...collisionDiagnostics] };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/core/rules.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAA6B,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AA2D5D,MAAM,WAAW,eAAe;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,IAAI;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;IACvB,sBAAsB,EAAE,OAAO,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,WAAW,EAAE,kBAAkB,EAAE,CAAC;CAClC;AA2CD,MAAM,WAAW,uBAAuB;IACvC,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;CACf;AA0BD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,eAAe,CAGlF;AA0JD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CA0B1D;AAWD,MAAM,WAAW,gBAAgB;IAChC,iDAAiD;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,yCAAyC;IACzC,eAAe,EAAE,OAAO,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAoGpE;AAkKD,MAAM,WAAW,8BAA+B,SAAQ,gBAAgB;IACvE,UAAU,EAAE,cAAc,CAAC;CAC3B;AAED,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,8BAA8B,GAAG,OAAO,CAAC,eAAe,CAAC,CAuF/G","sourcesContent":["import { existsSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport ignore from \"ignore\";\nimport { dirname, join, relative, resolve, sep } from \"path\";\nimport { CONFIG_DIR_NAME, getAgentDir } from \"../config.ts\";\nimport { parseFrontmatter } from \"../utils/frontmatter.ts\";\nimport { canonicalizePath, resolvePath } from \"../utils/paths.ts\";\nimport type { ResourceDiagnostic } from \"./diagnostics.ts\";\nimport { createSyntheticSourceInfo, type SourceInfo } from \"./source-info.ts\";\nimport type { ToolOperations } from \"./tools/operations.ts\";\n\n/** Max name length per spec */\nconst MAX_NAME_LENGTH = 64;\n\n/** Max description length per spec */\nconst MAX_DESCRIPTION_LENGTH = 1024;\n\nconst IGNORE_FILE_NAMES = [\".gitignore\", \".ignore\", \".fdignore\"];\n\ntype IgnoreMatcher = ReturnType<typeof ignore>;\n\nfunction toPosixPath(p: string): string {\n\treturn p.split(sep).join(\"/\");\n}\n\nfunction prefixIgnorePattern(line: string, prefix: string): string | null {\n\tconst trimmed = line.trim();\n\tif (!trimmed) return null;\n\tif (trimmed.startsWith(\"#\") && !trimmed.startsWith(\"\\\\#\")) return null;\n\n\tlet pattern = line;\n\tlet negated = false;\n\n\tif (pattern.startsWith(\"!\")) {\n\t\tnegated = true;\n\t\tpattern = pattern.slice(1);\n\t} else if (pattern.startsWith(\"\\\\!\")) {\n\t\tpattern = pattern.slice(1);\n\t}\n\n\tif (pattern.startsWith(\"/\")) {\n\t\tpattern = pattern.slice(1);\n\t}\n\n\tconst prefixed = prefix ? `${prefix}${pattern}` : pattern;\n\treturn negated ? `!${prefixed}` : prefixed;\n}\n\nfunction addIgnoreRules(ig: IgnoreMatcher, dir: string, rootDir: string): void {\n\tconst relativeDir = relative(rootDir, dir);\n\tconst prefix = relativeDir ? `${toPosixPath(relativeDir)}/` : \"\";\n\n\tfor (const filename of IGNORE_FILE_NAMES) {\n\t\tconst ignorePath = join(dir, filename);\n\t\tif (!existsSync(ignorePath)) continue;\n\t\ttry {\n\t\t\tconst content = readFileSync(ignorePath, \"utf-8\");\n\t\t\tconst patterns = content\n\t\t\t\t.split(/\\r?\\n/)\n\t\t\t\t.map((line) => prefixIgnorePattern(line, prefix))\n\t\t\t\t.filter((line): line is string => Boolean(line));\n\t\t\tif (patterns.length > 0) {\n\t\t\t\tig.add(patterns);\n\t\t\t}\n\t\t} catch {}\n\t}\n}\n\nexport interface RuleFrontmatter {\n\tname?: string;\n\tdescription?: string;\n\t\"disable-model-invocation\"?: boolean;\n\t[key: string]: unknown;\n}\n\nexport interface Rule {\n\tname: string;\n\tdescription: string;\n\tfilePath: string;\n\tbaseDir: string;\n\tsourceInfo: SourceInfo;\n\tdisableModelInvocation: boolean;\n\tcontent?: string;\n}\n\nexport interface LoadRulesResult {\n\trules: Rule[];\n\tdiagnostics: ResourceDiagnostic[];\n}\n\n/**\n * Validate rule name.\n * Returns array of validation error messages (empty if valid).\n */\nfunction validateName(name: string): string[] {\n\tconst errors: string[] = [];\n\n\tif (name.length > MAX_NAME_LENGTH) {\n\t\terrors.push(`name exceeds ${MAX_NAME_LENGTH} characters (${name.length})`);\n\t}\n\n\tif (!/^[a-z0-9-]+$/.test(name)) {\n\t\terrors.push(`name contains invalid characters (must be lowercase a-z, 0-9, hyphens only)`);\n\t}\n\n\tif (name.startsWith(\"-\") || name.endsWith(\"-\")) {\n\t\terrors.push(`name must not start or end with a hyphen`);\n\t}\n\n\tif (name.includes(\"--\")) {\n\t\terrors.push(`name must not contain consecutive hyphens`);\n\t}\n\n\treturn errors;\n}\n\n/**\n * Validate description.\n */\nfunction validateDescription(description: string | undefined): string[] {\n\tconst errors: string[] = [];\n\n\tif (!description || description.trim() === \"\") {\n\t\terrors.push(\"description is required\");\n\t} else if (description.length > MAX_DESCRIPTION_LENGTH) {\n\t\terrors.push(`description exceeds ${MAX_DESCRIPTION_LENGTH} characters (${description.length})`);\n\t}\n\n\treturn errors;\n}\n\nexport interface LoadRulesFromDirOptions {\n\t/** Directory to scan for rules */\n\tdir: string;\n\t/** Source identifier for these rules */\n\tsource: string;\n}\n\nfunction createRuleSourceInfo(filePath: string, baseDir: string, source: string): SourceInfo {\n\tswitch (source) {\n\t\tcase \"user\":\n\t\t\treturn createSyntheticSourceInfo(filePath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"user\",\n\t\t\t\tbaseDir,\n\t\t\t});\n\t\tcase \"project\":\n\t\t\treturn createSyntheticSourceInfo(filePath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"project\",\n\t\t\t\tbaseDir,\n\t\t\t});\n\t\tcase \"path\":\n\t\t\treturn createSyntheticSourceInfo(filePath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tbaseDir,\n\t\t\t});\n\t\tdefault:\n\t\t\treturn createSyntheticSourceInfo(filePath, { source, baseDir });\n\t}\n}\n\n/**\n * Load rules from a directory.\n *\n * Discovery rules:\n * - if a directory contains RULES.md, treat it as a rule root and do not recurse further\n * - otherwise, load direct .md children in the root\n * - recurse into subdirectories to find RULES.md\n */\nexport function loadRulesFromDir(options: LoadRulesFromDirOptions): LoadRulesResult {\n\tconst { dir, source } = options;\n\treturn loadRulesFromDirInternal(dir, source, true);\n}\n\nfunction loadRulesFromDirInternal(\n\tdir: string,\n\tsource: string,\n\tincludeRootFiles: boolean,\n\tignoreMatcher?: IgnoreMatcher,\n\trootDir?: string,\n): LoadRulesResult {\n\tconst rules: Rule[] = [];\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn { rules, diagnostics };\n\t}\n\n\tconst root = rootDir ?? dir;\n\tconst ig = ignoreMatcher ?? ignore();\n\taddIgnoreRules(ig, dir, root);\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name !== \"RULES.md\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tisFile = statSync(fullPath).isFile();\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tif (!isFile || ig.ignores(relPath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst result = loadRuleFromFile(fullPath, source);\n\t\t\tif (result.rule) {\n\t\t\t\trules.push(result.rule);\n\t\t\t}\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t\treturn { rules, diagnostics };\n\t\t}\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name.startsWith(\".\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Skip node_modules to avoid scanning dependencies\n\t\t\tif (entry.name === \"node_modules\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\t// For symlinks, check if they point to a directory and follow them\n\t\t\tlet isDirectory = entry.isDirectory();\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(fullPath);\n\t\t\t\t\tisDirectory = stats.isDirectory();\n\t\t\t\t\tisFile = stats.isFile();\n\t\t\t\t} catch {\n\t\t\t\t\t// Broken symlink, skip it\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tconst ignorePath = isDirectory ? `${relPath}/` : relPath;\n\t\t\tif (ig.ignores(ignorePath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (isDirectory) {\n\t\t\t\tconst subResult = loadRulesFromDirInternal(fullPath, source, false, ig, root);\n\t\t\t\trules.push(...subResult.rules);\n\t\t\t\tdiagnostics.push(...subResult.diagnostics);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!isFile || !includeRootFiles || !entry.name.endsWith(\".md\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst result = loadRuleFromFile(fullPath, source);\n\t\t\tif (result.rule) {\n\t\t\t\trules.push(result.rule);\n\t\t\t}\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t}\n\t} catch {}\n\n\treturn { rules, diagnostics };\n}\n\nfunction loadRuleFromFile(filePath: string, source: string): { rule: Rule | null; diagnostics: ResourceDiagnostic[] } {\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\ttry {\n\t\tconst rawContent = readFileSync(filePath, \"utf-8\");\n\t\tconst { frontmatter } = parseFrontmatter<RuleFrontmatter>(rawContent);\n\t\tconst ruleDir = dirname(filePath);\n\n\t\t// Validate description\n\t\tconst descErrors = validateDescription(frontmatter.description);\n\t\tfor (const error of descErrors) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\tif (!frontmatter.name || frontmatter.name.trim() === \"\") {\n\t\t\tdiagnostics.push({ type: \"warning\", message: \"name is required\", path: filePath });\n\t\t}\n\n\t\tconst name = frontmatter.name;\n\t\tif (name) {\n\t\t\tfor (const error of validateName(name)) {\n\t\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t\t}\n\t\t}\n\n\t\t// Still load the rule even with warnings (unless required fields are missing)\n\t\tif (!name || !frontmatter.description || frontmatter.description.trim() === \"\") {\n\t\t\treturn { rule: null, diagnostics };\n\t\t}\n\n\t\treturn {\n\t\t\trule: {\n\t\t\t\tname,\n\t\t\t\tdescription: frontmatter.description,\n\t\t\t\tfilePath,\n\t\t\t\tbaseDir: ruleDir,\n\t\t\t\tsourceInfo: createRuleSourceInfo(filePath, ruleDir, source),\n\t\t\t\tdisableModelInvocation: frontmatter[\"disable-model-invocation\"] === true,\n\t\t\t},\n\t\t\tdiagnostics,\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : \"failed to parse rule file\";\n\t\tdiagnostics.push({ type: \"warning\", message, path: filePath });\n\t\treturn { rule: null, diagnostics };\n\t}\n}\n\n/**\n * Format rules for inclusion in a system prompt.\n * Rules with disableModelInvocation=true are excluded from the prompt\n * (they can only be invoked explicitly via /rule:name commands).\n */\nexport function formatRulesForPrompt(rules: Rule[]): string {\n\tconst visibleRules = rules.filter((s) => !s.disableModelInvocation);\n\n\tif (visibleRules.length === 0) {\n\t\treturn \"\";\n\t}\n\n\tconst lines = [\n\t\t\"\\n\\nThe following rules provide mandatory constraints and policies.\",\n\t\t\"Use the read tool to load a rule's file when the task or files match its description; applicable rules are mandatory.\",\n\t\t\"When a rule file references a relative path, resolve it against the rule directory (parent of RULES.md / dirname of the path) and use that absolute path in tool commands.\",\n\t\t\"\",\n\t\t\"<available_rules>\",\n\t];\n\n\tfor (const rule of visibleRules) {\n\t\tlines.push(\" <rule>\");\n\t\tlines.push(` <name>${escapeXml(rule.name)}</name>`);\n\t\tlines.push(` <description>${escapeXml(rule.description)}</description>`);\n\t\tlines.push(` <location>${escapeXml(rule.filePath)}</location>`);\n\t\tlines.push(\" </rule>\");\n\t}\n\n\tlines.push(\"</available_rules>\");\n\n\treturn lines.join(\"\\n\");\n}\n\nfunction escapeXml(str: string): string {\n\treturn str\n\t\t.replace(/&/g, \"&\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\");\n}\n\nexport interface LoadRulesOptions {\n\t/** Working directory for project-local rules. */\n\tcwd: string;\n\t/** Agent config directory for global rules. */\n\tagentDir: string;\n\t/** Explicit rule paths (files or directories) */\n\trulePaths: string[];\n\t/** Include default rules directories. */\n\tincludeDefaults: boolean;\n}\n\n/**\n * Load rules from all configured locations.\n * Returns rules and any validation diagnostics.\n */\nexport function loadRules(options: LoadRulesOptions): LoadRulesResult {\n\tconst { agentDir, rulePaths, includeDefaults } = options;\n\n\t// Resolve agentDir - if not provided, use default from config\n\tconst resolvedCwd = resolvePath(options.cwd);\n\tconst resolvedAgentDir = resolvePath(agentDir ?? getAgentDir());\n\n\tconst ruleMap = new Map<string, Rule>();\n\tconst realPathSet = new Set<string>();\n\tconst allDiagnostics: ResourceDiagnostic[] = [];\n\tconst collisionDiagnostics: ResourceDiagnostic[] = [];\n\n\tfunction addRules(result: LoadRulesResult) {\n\t\tallDiagnostics.push(...result.diagnostics);\n\t\tfor (const rule of result.rules) {\n\t\t\t// Resolve symlinks to detect duplicate files\n\t\t\tconst realPath = canonicalizePath(rule.filePath);\n\n\t\t\t// Skip silently if we've already loaded this exact file (via symlink)\n\t\t\tif (realPathSet.has(realPath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst existing = ruleMap.get(rule.name);\n\t\t\tif (existing) {\n\t\t\t\tcollisionDiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"${rule.name}\" collision`,\n\t\t\t\t\tpath: rule.filePath,\n\t\t\t\t\tcollision: {\n\t\t\t\t\t\tresourceType: \"rule\",\n\t\t\t\t\t\tname: rule.name,\n\t\t\t\t\t\twinnerPath: existing.filePath,\n\t\t\t\t\t\tloserPath: rule.filePath,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\truleMap.set(rule.name, rule);\n\t\t\t\trealPathSet.add(realPath);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (includeDefaults) {\n\t\taddRules(loadRulesFromDirInternal(join(resolvedAgentDir, \"rules\"), \"user\", true));\n\t\taddRules(loadRulesFromDirInternal(resolve(resolvedCwd, CONFIG_DIR_NAME, \"rules\"), \"project\", true));\n\t}\n\n\tconst userRulesDir = join(resolvedAgentDir, \"rules\");\n\tconst projectRulesDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"rules\");\n\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) {\n\t\t\treturn true;\n\t\t}\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\n\tconst getSource = (resolvedPath: string): \"user\" | \"project\" | \"path\" => {\n\t\tif (!includeDefaults) {\n\t\t\tif (isUnderPath(resolvedPath, userRulesDir)) return \"user\";\n\t\t\tif (isUnderPath(resolvedPath, projectRulesDir)) return \"project\";\n\t\t}\n\t\treturn \"path\";\n\t};\n\n\tfor (const rawPath of rulePaths) {\n\t\tconst resolvedPath = resolvePath(rawPath, resolvedCwd, { trim: true });\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tallDiagnostics.push({ type: \"warning\", message: \"rule path does not exist\", path: resolvedPath });\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst stats = statSync(resolvedPath);\n\t\t\tconst source = getSource(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\taddRules(loadRulesFromDirInternal(resolvedPath, source, true));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst result = loadRuleFromFile(resolvedPath, source);\n\t\t\t\tif (result.rule) {\n\t\t\t\t\taddRules({ rules: [result.rule], diagnostics: result.diagnostics });\n\t\t\t\t} else {\n\t\t\t\t\tallDiagnostics.push(...result.diagnostics);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tallDiagnostics.push({ type: \"warning\", message: \"rule path is not a markdown file\", path: resolvedPath });\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to read rule path\";\n\t\t\tallDiagnostics.push({ type: \"warning\", message, path: resolvedPath });\n\t\t}\n\t}\n\n\treturn {\n\t\trules: Array.from(ruleMap.values()),\n\t\tdiagnostics: [...allDiagnostics, ...collisionDiagnostics],\n\t};\n}\n\nasync function backendPathExists(operations: ToolOperations, path: string): Promise<boolean> {\n\ttry {\n\t\tawait operations.access(path, \"exists\");\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nasync function addIgnoreRulesWithOperations(\n\toperations: ToolOperations,\n\tig: IgnoreMatcher,\n\tdir: string,\n\trootDir: string,\n): Promise<void> {\n\tconst relativeDir = relative(rootDir, dir);\n\tconst prefix = relativeDir ? `${toPosixPath(relativeDir)}/` : \"\";\n\n\tfor (const filename of IGNORE_FILE_NAMES) {\n\t\tconst ignorePath = join(dir, filename);\n\t\tif (!(await backendPathExists(operations, ignorePath))) continue;\n\t\ttry {\n\t\t\tconst content = (await operations.readFile(ignorePath)).toString(\"utf-8\");\n\t\t\tconst patterns = content\n\t\t\t\t.split(/\\r?\\n/)\n\t\t\t\t.map((line) => prefixIgnorePattern(line, prefix))\n\t\t\t\t.filter((line): line is string => Boolean(line));\n\t\t\tif (patterns.length > 0) {\n\t\t\t\tig.add(patterns);\n\t\t\t}\n\t\t} catch {}\n\t}\n}\n\nasync function loadRuleFromFileWithOperations(\n\toperations: ToolOperations,\n\tfilePath: string,\n\tsource: string,\n): Promise<{ rule: Rule | null; diagnostics: ResourceDiagnostic[] }> {\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\ttry {\n\t\tconst rawContent = (await operations.readFile(filePath)).toString(\"utf-8\");\n\t\tconst { frontmatter } = parseFrontmatter<RuleFrontmatter>(rawContent);\n\t\tconst ruleDir = dirname(filePath);\n\n\t\tfor (const error of validateDescription(frontmatter.description)) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\tif (!frontmatter.name || frontmatter.name.trim() === \"\") {\n\t\t\tdiagnostics.push({ type: \"warning\", message: \"name is required\", path: filePath });\n\t\t}\n\n\t\tconst name = frontmatter.name;\n\t\tif (name) {\n\t\t\tfor (const error of validateName(name)) {\n\t\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t\t}\n\t\t}\n\n\t\tif (!name || !frontmatter.description || frontmatter.description.trim() === \"\") {\n\t\t\treturn { rule: null, diagnostics };\n\t\t}\n\n\t\treturn {\n\t\t\trule: {\n\t\t\t\tname,\n\t\t\t\tdescription: frontmatter.description,\n\t\t\t\tfilePath,\n\t\t\t\tbaseDir: ruleDir,\n\t\t\t\tsourceInfo: createRuleSourceInfo(filePath, ruleDir, source),\n\t\t\t\tdisableModelInvocation: frontmatter[\"disable-model-invocation\"] === true,\n\t\t\t},\n\t\t\tdiagnostics,\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : \"failed to parse rule file\";\n\t\tdiagnostics.push({ type: \"warning\", message, path: filePath });\n\t\treturn { rule: null, diagnostics };\n\t}\n}\n\nasync function loadRulesFromDirInternalWithOperations(\n\toperations: ToolOperations,\n\tdir: string,\n\tsource: string,\n\tincludeRootFiles: boolean,\n\tignoreMatcher?: IgnoreMatcher,\n\trootDir?: string,\n): Promise<LoadRulesResult> {\n\tconst rules: Rule[] = [];\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\tif (!(await backendPathExists(operations, dir))) {\n\t\treturn { rules, diagnostics };\n\t}\n\n\tconst root = rootDir ?? dir;\n\tconst ig = ignoreMatcher ?? ignore();\n\tawait addIgnoreRulesWithOperations(operations, ig, dir, root);\n\n\ttry {\n\t\tconst entries = await operations.readdir(dir);\n\n\t\tfor (const name of entries) {\n\t\t\tif (name !== \"RULES.md\") continue;\n\t\t\tconst fullPath = join(dir, name);\n\t\t\tlet isFile = false;\n\t\t\ttry {\n\t\t\t\tisFile = (await operations.stat(fullPath)).isFile();\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tif (!isFile || ig.ignores(relPath)) continue;\n\t\t\tconst result = await loadRuleFromFileWithOperations(operations, fullPath, source);\n\t\t\tif (result.rule) rules.push(result.rule);\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t\treturn { rules, diagnostics };\n\t\t}\n\n\t\tfor (const name of entries) {\n\t\t\tif (name.startsWith(\".\") || name === \"node_modules\") continue;\n\t\t\tconst fullPath = join(dir, name);\n\t\t\tlet isDirectory = false;\n\t\t\tlet isFile = false;\n\t\t\ttry {\n\t\t\t\tconst stats = await operations.stat(fullPath);\n\t\t\t\tisDirectory = stats.isDirectory();\n\t\t\t\tisFile = stats.isFile();\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tconst ignorePath = isDirectory ? `${relPath}/` : relPath;\n\t\t\tif (ig.ignores(ignorePath)) continue;\n\t\t\tif (isDirectory) {\n\t\t\t\tconst subResult = await loadRulesFromDirInternalWithOperations(\n\t\t\t\t\toperations,\n\t\t\t\t\tfullPath,\n\t\t\t\t\tsource,\n\t\t\t\t\tfalse,\n\t\t\t\t\tig,\n\t\t\t\t\troot,\n\t\t\t\t);\n\t\t\t\trules.push(...subResult.rules);\n\t\t\t\tdiagnostics.push(...subResult.diagnostics);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!isFile || !includeRootFiles || !name.endsWith(\".md\")) continue;\n\t\t\tconst result = await loadRuleFromFileWithOperations(operations, fullPath, source);\n\t\t\tif (result.rule) rules.push(result.rule);\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t}\n\t} catch {}\n\n\treturn { rules, diagnostics };\n}\n\nexport interface LoadRulesWithOperationsOptions extends LoadRulesOptions {\n\toperations: ToolOperations;\n}\n\nexport async function loadRulesWithOperations(options: LoadRulesWithOperationsOptions): Promise<LoadRulesResult> {\n\tconst { agentDir, rulePaths, includeDefaults, operations } = options;\n\tconst resolvedCwd = resolvePath(options.cwd);\n\tconst resolvedAgentDir = resolvePath(agentDir ?? getAgentDir());\n\tconst ruleMap = new Map<string, Rule>();\n\tconst pathSet = new Set<string>();\n\tconst allDiagnostics: ResourceDiagnostic[] = [];\n\tconst collisionDiagnostics: ResourceDiagnostic[] = [];\n\n\tfunction addRules(result: LoadRulesResult) {\n\t\tallDiagnostics.push(...result.diagnostics);\n\t\tfor (const rule of result.rules) {\n\t\t\tconst canonicalPath = resolve(rule.filePath);\n\t\t\tif (pathSet.has(canonicalPath)) continue;\n\t\t\tconst existing = ruleMap.get(rule.name);\n\t\t\tif (existing) {\n\t\t\t\tcollisionDiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"${rule.name}\" collision`,\n\t\t\t\t\tpath: rule.filePath,\n\t\t\t\t\tcollision: {\n\t\t\t\t\t\tresourceType: \"rule\",\n\t\t\t\t\t\tname: rule.name,\n\t\t\t\t\t\twinnerPath: existing.filePath,\n\t\t\t\t\t\tloserPath: rule.filePath,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\truleMap.set(rule.name, rule);\n\t\t\t\tpathSet.add(canonicalPath);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (includeDefaults) {\n\t\taddRules(await loadRulesFromDirInternalWithOperations(operations, join(resolvedAgentDir, \"rules\"), \"user\", true));\n\t\taddRules(\n\t\t\tawait loadRulesFromDirInternalWithOperations(\n\t\t\t\toperations,\n\t\t\t\tresolve(resolvedCwd, CONFIG_DIR_NAME, \"rules\"),\n\t\t\t\t\"project\",\n\t\t\t\ttrue,\n\t\t\t),\n\t\t);\n\t}\n\n\tconst userRulesDir = join(resolvedAgentDir, \"rules\");\n\tconst projectRulesDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"rules\");\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) return true;\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\tconst getSource = (resolvedPath: string): \"user\" | \"project\" | \"path\" => {\n\t\tif (!includeDefaults) {\n\t\t\tif (isUnderPath(resolvedPath, userRulesDir)) return \"user\";\n\t\t\tif (isUnderPath(resolvedPath, projectRulesDir)) return \"project\";\n\t\t}\n\t\treturn \"path\";\n\t};\n\n\tfor (const rawPath of rulePaths) {\n\t\tconst resolvedPath = resolvePath(rawPath, resolvedCwd, { trim: true });\n\t\tif (!(await backendPathExists(operations, resolvedPath))) {\n\t\t\tallDiagnostics.push({ type: \"warning\", message: \"rule path does not exist\", path: resolvedPath });\n\t\t\tcontinue;\n\t\t}\n\t\ttry {\n\t\t\tconst stats = await operations.stat(resolvedPath);\n\t\t\tconst source = getSource(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\taddRules(await loadRulesFromDirInternalWithOperations(operations, resolvedPath, source, true));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst result = await loadRuleFromFileWithOperations(operations, resolvedPath, source);\n\t\t\t\tif (result.rule) addRules({ rules: [result.rule], diagnostics: result.diagnostics });\n\t\t\t\telse allDiagnostics.push(...result.diagnostics);\n\t\t\t} else {\n\t\t\t\tallDiagnostics.push({ type: \"warning\", message: \"rule path is not a markdown file\", path: resolvedPath });\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to read rule path\";\n\t\t\tallDiagnostics.push({ type: \"warning\", message, path: resolvedPath });\n\t\t}\n\t}\n\n\treturn { rules: Array.from(ruleMap.values()), diagnostics: [...allDiagnostics, ...collisionDiagnostics] };\n}\n"]}
|
package/dist/core/rules.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
2
2
|
import ignore from "ignore";
|
|
3
|
-
import {
|
|
3
|
+
import { dirname, join, relative, resolve, sep } from "path";
|
|
4
4
|
import { CONFIG_DIR_NAME, getAgentDir } from "../config.js";
|
|
5
5
|
import { parseFrontmatter } from "../utils/frontmatter.js";
|
|
6
6
|
import { canonicalizePath, resolvePath } from "../utils/paths.js";
|
|
@@ -211,21 +211,22 @@ function loadRuleFromFile(filePath, source) {
|
|
|
211
211
|
const rawContent = readFileSync(filePath, "utf-8");
|
|
212
212
|
const { frontmatter } = parseFrontmatter(rawContent);
|
|
213
213
|
const ruleDir = dirname(filePath);
|
|
214
|
-
const parentDirName = basename(ruleDir);
|
|
215
214
|
// Validate description
|
|
216
215
|
const descErrors = validateDescription(frontmatter.description);
|
|
217
216
|
for (const error of descErrors) {
|
|
218
217
|
diagnostics.push({ type: "warning", message: error, path: filePath });
|
|
219
218
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
219
|
+
if (!frontmatter.name || frontmatter.name.trim() === "") {
|
|
220
|
+
diagnostics.push({ type: "warning", message: "name is required", path: filePath });
|
|
221
|
+
}
|
|
222
|
+
const name = frontmatter.name;
|
|
223
|
+
if (name) {
|
|
224
|
+
for (const error of validateName(name)) {
|
|
225
|
+
diagnostics.push({ type: "warning", message: error, path: filePath });
|
|
226
|
+
}
|
|
226
227
|
}
|
|
227
|
-
// Still load the rule even with warnings (unless
|
|
228
|
-
if (!frontmatter.description || frontmatter.description.trim() === "") {
|
|
228
|
+
// Still load the rule even with warnings (unless required fields are missing)
|
|
229
|
+
if (!name || !frontmatter.description || frontmatter.description.trim() === "") {
|
|
229
230
|
return { rule: null, diagnostics };
|
|
230
231
|
}
|
|
231
232
|
return {
|
|
@@ -416,15 +417,19 @@ async function loadRuleFromFileWithOperations(operations, filePath, source) {
|
|
|
416
417
|
const rawContent = (await operations.readFile(filePath)).toString("utf-8");
|
|
417
418
|
const { frontmatter } = parseFrontmatter(rawContent);
|
|
418
419
|
const ruleDir = dirname(filePath);
|
|
419
|
-
const parentDirName = basename(ruleDir);
|
|
420
420
|
for (const error of validateDescription(frontmatter.description)) {
|
|
421
421
|
diagnostics.push({ type: "warning", message: error, path: filePath });
|
|
422
422
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
423
|
+
if (!frontmatter.name || frontmatter.name.trim() === "") {
|
|
424
|
+
diagnostics.push({ type: "warning", message: "name is required", path: filePath });
|
|
425
|
+
}
|
|
426
|
+
const name = frontmatter.name;
|
|
427
|
+
if (name) {
|
|
428
|
+
for (const error of validateName(name)) {
|
|
429
|
+
diagnostics.push({ type: "warning", message: error, path: filePath });
|
|
430
|
+
}
|
|
426
431
|
}
|
|
427
|
-
if (!frontmatter.description || frontmatter.description.trim() === "") {
|
|
432
|
+
if (!name || !frontmatter.description || frontmatter.description.trim() === "") {
|
|
428
433
|
return { rule: null, diagnostics };
|
|
429
434
|
}
|
|
430
435
|
return {
|
package/dist/core/rules.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rules.js","sourceRoot":"","sources":["../../src/core/rules.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport ignore from \"ignore\";\nimport { basename, dirname, join, relative, resolve, sep } from \"path\";\nimport { CONFIG_DIR_NAME, getAgentDir } from \"../config.ts\";\nimport { parseFrontmatter } from \"../utils/frontmatter.ts\";\nimport { canonicalizePath, resolvePath } from \"../utils/paths.ts\";\nimport type { ResourceDiagnostic } from \"./diagnostics.ts\";\nimport { createSyntheticSourceInfo, type SourceInfo } from \"./source-info.ts\";\nimport type { ToolOperations } from \"./tools/operations.ts\";\n\n/** Max name length per spec */\nconst MAX_NAME_LENGTH = 64;\n\n/** Max description length per spec */\nconst MAX_DESCRIPTION_LENGTH = 1024;\n\nconst IGNORE_FILE_NAMES = [\".gitignore\", \".ignore\", \".fdignore\"];\n\ntype IgnoreMatcher = ReturnType<typeof ignore>;\n\nfunction toPosixPath(p: string): string {\n\treturn p.split(sep).join(\"/\");\n}\n\nfunction prefixIgnorePattern(line: string, prefix: string): string | null {\n\tconst trimmed = line.trim();\n\tif (!trimmed) return null;\n\tif (trimmed.startsWith(\"#\") && !trimmed.startsWith(\"\\\\#\")) return null;\n\n\tlet pattern = line;\n\tlet negated = false;\n\n\tif (pattern.startsWith(\"!\")) {\n\t\tnegated = true;\n\t\tpattern = pattern.slice(1);\n\t} else if (pattern.startsWith(\"\\\\!\")) {\n\t\tpattern = pattern.slice(1);\n\t}\n\n\tif (pattern.startsWith(\"/\")) {\n\t\tpattern = pattern.slice(1);\n\t}\n\n\tconst prefixed = prefix ? `${prefix}${pattern}` : pattern;\n\treturn negated ? `!${prefixed}` : prefixed;\n}\n\nfunction addIgnoreRules(ig: IgnoreMatcher, dir: string, rootDir: string): void {\n\tconst relativeDir = relative(rootDir, dir);\n\tconst prefix = relativeDir ? `${toPosixPath(relativeDir)}/` : \"\";\n\n\tfor (const filename of IGNORE_FILE_NAMES) {\n\t\tconst ignorePath = join(dir, filename);\n\t\tif (!existsSync(ignorePath)) continue;\n\t\ttry {\n\t\t\tconst content = readFileSync(ignorePath, \"utf-8\");\n\t\t\tconst patterns = content\n\t\t\t\t.split(/\\r?\\n/)\n\t\t\t\t.map((line) => prefixIgnorePattern(line, prefix))\n\t\t\t\t.filter((line): line is string => Boolean(line));\n\t\t\tif (patterns.length > 0) {\n\t\t\t\tig.add(patterns);\n\t\t\t}\n\t\t} catch {}\n\t}\n}\n\nexport interface RuleFrontmatter {\n\tname?: string;\n\tdescription?: string;\n\t\"disable-model-invocation\"?: boolean;\n\t[key: string]: unknown;\n}\n\nexport interface Rule {\n\tname: string;\n\tdescription: string;\n\tfilePath: string;\n\tbaseDir: string;\n\tsourceInfo: SourceInfo;\n\tdisableModelInvocation: boolean;\n\tcontent?: string;\n}\n\nexport interface LoadRulesResult {\n\trules: Rule[];\n\tdiagnostics: ResourceDiagnostic[];\n}\n\n/**\n * Validate rule name.\n * Returns array of validation error messages (empty if valid).\n */\nfunction validateName(name: string): string[] {\n\tconst errors: string[] = [];\n\n\tif (name.length > MAX_NAME_LENGTH) {\n\t\terrors.push(`name exceeds ${MAX_NAME_LENGTH} characters (${name.length})`);\n\t}\n\n\tif (!/^[a-z0-9-]+$/.test(name)) {\n\t\terrors.push(`name contains invalid characters (must be lowercase a-z, 0-9, hyphens only)`);\n\t}\n\n\tif (name.startsWith(\"-\") || name.endsWith(\"-\")) {\n\t\terrors.push(`name must not start or end with a hyphen`);\n\t}\n\n\tif (name.includes(\"--\")) {\n\t\terrors.push(`name must not contain consecutive hyphens`);\n\t}\n\n\treturn errors;\n}\n\n/**\n * Validate description.\n */\nfunction validateDescription(description: string | undefined): string[] {\n\tconst errors: string[] = [];\n\n\tif (!description || description.trim() === \"\") {\n\t\terrors.push(\"description is required\");\n\t} else if (description.length > MAX_DESCRIPTION_LENGTH) {\n\t\terrors.push(`description exceeds ${MAX_DESCRIPTION_LENGTH} characters (${description.length})`);\n\t}\n\n\treturn errors;\n}\n\nexport interface LoadRulesFromDirOptions {\n\t/** Directory to scan for rules */\n\tdir: string;\n\t/** Source identifier for these rules */\n\tsource: string;\n}\n\nfunction createRuleSourceInfo(filePath: string, baseDir: string, source: string): SourceInfo {\n\tswitch (source) {\n\t\tcase \"user\":\n\t\t\treturn createSyntheticSourceInfo(filePath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"user\",\n\t\t\t\tbaseDir,\n\t\t\t});\n\t\tcase \"project\":\n\t\t\treturn createSyntheticSourceInfo(filePath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"project\",\n\t\t\t\tbaseDir,\n\t\t\t});\n\t\tcase \"path\":\n\t\t\treturn createSyntheticSourceInfo(filePath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tbaseDir,\n\t\t\t});\n\t\tdefault:\n\t\t\treturn createSyntheticSourceInfo(filePath, { source, baseDir });\n\t}\n}\n\n/**\n * Load rules from a directory.\n *\n * Discovery rules:\n * - if a directory contains RULES.md, treat it as a rule root and do not recurse further\n * - otherwise, load direct .md children in the root\n * - recurse into subdirectories to find RULES.md\n */\nexport function loadRulesFromDir(options: LoadRulesFromDirOptions): LoadRulesResult {\n\tconst { dir, source } = options;\n\treturn loadRulesFromDirInternal(dir, source, true);\n}\n\nfunction loadRulesFromDirInternal(\n\tdir: string,\n\tsource: string,\n\tincludeRootFiles: boolean,\n\tignoreMatcher?: IgnoreMatcher,\n\trootDir?: string,\n): LoadRulesResult {\n\tconst rules: Rule[] = [];\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn { rules, diagnostics };\n\t}\n\n\tconst root = rootDir ?? dir;\n\tconst ig = ignoreMatcher ?? ignore();\n\taddIgnoreRules(ig, dir, root);\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name !== \"RULES.md\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tisFile = statSync(fullPath).isFile();\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tif (!isFile || ig.ignores(relPath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst result = loadRuleFromFile(fullPath, source);\n\t\t\tif (result.rule) {\n\t\t\t\trules.push(result.rule);\n\t\t\t}\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t\treturn { rules, diagnostics };\n\t\t}\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name.startsWith(\".\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Skip node_modules to avoid scanning dependencies\n\t\t\tif (entry.name === \"node_modules\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\t// For symlinks, check if they point to a directory and follow them\n\t\t\tlet isDirectory = entry.isDirectory();\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(fullPath);\n\t\t\t\t\tisDirectory = stats.isDirectory();\n\t\t\t\t\tisFile = stats.isFile();\n\t\t\t\t} catch {\n\t\t\t\t\t// Broken symlink, skip it\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tconst ignorePath = isDirectory ? `${relPath}/` : relPath;\n\t\t\tif (ig.ignores(ignorePath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (isDirectory) {\n\t\t\t\tconst subResult = loadRulesFromDirInternal(fullPath, source, false, ig, root);\n\t\t\t\trules.push(...subResult.rules);\n\t\t\t\tdiagnostics.push(...subResult.diagnostics);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!isFile || !includeRootFiles || !entry.name.endsWith(\".md\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst result = loadRuleFromFile(fullPath, source);\n\t\t\tif (result.rule) {\n\t\t\t\trules.push(result.rule);\n\t\t\t}\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t}\n\t} catch {}\n\n\treturn { rules, diagnostics };\n}\n\nfunction loadRuleFromFile(filePath: string, source: string): { rule: Rule | null; diagnostics: ResourceDiagnostic[] } {\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\ttry {\n\t\tconst rawContent = readFileSync(filePath, \"utf-8\");\n\t\tconst { frontmatter } = parseFrontmatter<RuleFrontmatter>(rawContent);\n\t\tconst ruleDir = dirname(filePath);\n\t\tconst parentDirName = basename(ruleDir);\n\n\t\t// Validate description\n\t\tconst descErrors = validateDescription(frontmatter.description);\n\t\tfor (const error of descErrors) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\t// Use name from frontmatter, or fall back to parent directory name\n\t\tconst name = frontmatter.name || parentDirName;\n\n\t\t// Validate name\n\t\tconst nameErrors = validateName(name);\n\t\tfor (const error of nameErrors) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\t// Still load the rule even with warnings (unless description is completely missing)\n\t\tif (!frontmatter.description || frontmatter.description.trim() === \"\") {\n\t\t\treturn { rule: null, diagnostics };\n\t\t}\n\n\t\treturn {\n\t\t\trule: {\n\t\t\t\tname,\n\t\t\t\tdescription: frontmatter.description,\n\t\t\t\tfilePath,\n\t\t\t\tbaseDir: ruleDir,\n\t\t\t\tsourceInfo: createRuleSourceInfo(filePath, ruleDir, source),\n\t\t\t\tdisableModelInvocation: frontmatter[\"disable-model-invocation\"] === true,\n\t\t\t},\n\t\t\tdiagnostics,\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : \"failed to parse rule file\";\n\t\tdiagnostics.push({ type: \"warning\", message, path: filePath });\n\t\treturn { rule: null, diagnostics };\n\t}\n}\n\n/**\n * Format rules for inclusion in a system prompt.\n * Rules with disableModelInvocation=true are excluded from the prompt\n * (they can only be invoked explicitly via /rule:name commands).\n */\nexport function formatRulesForPrompt(rules: Rule[]): string {\n\tconst visibleRules = rules.filter((s) => !s.disableModelInvocation);\n\n\tif (visibleRules.length === 0) {\n\t\treturn \"\";\n\t}\n\n\tconst lines = [\n\t\t\"\\n\\nThe following rules provide mandatory constraints and policies.\",\n\t\t\"Use the read tool to load a rule's file when the task or files match its description; applicable rules are mandatory.\",\n\t\t\"When a rule file references a relative path, resolve it against the rule directory (parent of RULES.md / dirname of the path) and use that absolute path in tool commands.\",\n\t\t\"\",\n\t\t\"<available_rules>\",\n\t];\n\n\tfor (const rule of visibleRules) {\n\t\tlines.push(\" <rule>\");\n\t\tlines.push(` <name>${escapeXml(rule.name)}</name>`);\n\t\tlines.push(` <description>${escapeXml(rule.description)}</description>`);\n\t\tlines.push(` <location>${escapeXml(rule.filePath)}</location>`);\n\t\tlines.push(\" </rule>\");\n\t}\n\n\tlines.push(\"</available_rules>\");\n\n\treturn lines.join(\"\\n\");\n}\n\nfunction escapeXml(str: string): string {\n\treturn str\n\t\t.replace(/&/g, \"&\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\");\n}\n\nexport interface LoadRulesOptions {\n\t/** Working directory for project-local rules. */\n\tcwd: string;\n\t/** Agent config directory for global rules. */\n\tagentDir: string;\n\t/** Explicit rule paths (files or directories) */\n\trulePaths: string[];\n\t/** Include default rules directories. */\n\tincludeDefaults: boolean;\n}\n\n/**\n * Load rules from all configured locations.\n * Returns rules and any validation diagnostics.\n */\nexport function loadRules(options: LoadRulesOptions): LoadRulesResult {\n\tconst { agentDir, rulePaths, includeDefaults } = options;\n\n\t// Resolve agentDir - if not provided, use default from config\n\tconst resolvedCwd = resolvePath(options.cwd);\n\tconst resolvedAgentDir = resolvePath(agentDir ?? getAgentDir());\n\n\tconst ruleMap = new Map<string, Rule>();\n\tconst realPathSet = new Set<string>();\n\tconst allDiagnostics: ResourceDiagnostic[] = [];\n\tconst collisionDiagnostics: ResourceDiagnostic[] = [];\n\n\tfunction addRules(result: LoadRulesResult) {\n\t\tallDiagnostics.push(...result.diagnostics);\n\t\tfor (const rule of result.rules) {\n\t\t\t// Resolve symlinks to detect duplicate files\n\t\t\tconst realPath = canonicalizePath(rule.filePath);\n\n\t\t\t// Skip silently if we've already loaded this exact file (via symlink)\n\t\t\tif (realPathSet.has(realPath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst existing = ruleMap.get(rule.name);\n\t\t\tif (existing) {\n\t\t\t\tcollisionDiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"${rule.name}\" collision`,\n\t\t\t\t\tpath: rule.filePath,\n\t\t\t\t\tcollision: {\n\t\t\t\t\t\tresourceType: \"rule\",\n\t\t\t\t\t\tname: rule.name,\n\t\t\t\t\t\twinnerPath: existing.filePath,\n\t\t\t\t\t\tloserPath: rule.filePath,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\truleMap.set(rule.name, rule);\n\t\t\t\trealPathSet.add(realPath);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (includeDefaults) {\n\t\taddRules(loadRulesFromDirInternal(join(resolvedAgentDir, \"rules\"), \"user\", true));\n\t\taddRules(loadRulesFromDirInternal(resolve(resolvedCwd, CONFIG_DIR_NAME, \"rules\"), \"project\", true));\n\t}\n\n\tconst userRulesDir = join(resolvedAgentDir, \"rules\");\n\tconst projectRulesDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"rules\");\n\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) {\n\t\t\treturn true;\n\t\t}\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\n\tconst getSource = (resolvedPath: string): \"user\" | \"project\" | \"path\" => {\n\t\tif (!includeDefaults) {\n\t\t\tif (isUnderPath(resolvedPath, userRulesDir)) return \"user\";\n\t\t\tif (isUnderPath(resolvedPath, projectRulesDir)) return \"project\";\n\t\t}\n\t\treturn \"path\";\n\t};\n\n\tfor (const rawPath of rulePaths) {\n\t\tconst resolvedPath = resolvePath(rawPath, resolvedCwd, { trim: true });\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tallDiagnostics.push({ type: \"warning\", message: \"rule path does not exist\", path: resolvedPath });\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst stats = statSync(resolvedPath);\n\t\t\tconst source = getSource(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\taddRules(loadRulesFromDirInternal(resolvedPath, source, true));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst result = loadRuleFromFile(resolvedPath, source);\n\t\t\t\tif (result.rule) {\n\t\t\t\t\taddRules({ rules: [result.rule], diagnostics: result.diagnostics });\n\t\t\t\t} else {\n\t\t\t\t\tallDiagnostics.push(...result.diagnostics);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tallDiagnostics.push({ type: \"warning\", message: \"rule path is not a markdown file\", path: resolvedPath });\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to read rule path\";\n\t\t\tallDiagnostics.push({ type: \"warning\", message, path: resolvedPath });\n\t\t}\n\t}\n\n\treturn {\n\t\trules: Array.from(ruleMap.values()),\n\t\tdiagnostics: [...allDiagnostics, ...collisionDiagnostics],\n\t};\n}\n\nasync function backendPathExists(operations: ToolOperations, path: string): Promise<boolean> {\n\ttry {\n\t\tawait operations.access(path, \"exists\");\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nasync function addIgnoreRulesWithOperations(\n\toperations: ToolOperations,\n\tig: IgnoreMatcher,\n\tdir: string,\n\trootDir: string,\n): Promise<void> {\n\tconst relativeDir = relative(rootDir, dir);\n\tconst prefix = relativeDir ? `${toPosixPath(relativeDir)}/` : \"\";\n\n\tfor (const filename of IGNORE_FILE_NAMES) {\n\t\tconst ignorePath = join(dir, filename);\n\t\tif (!(await backendPathExists(operations, ignorePath))) continue;\n\t\ttry {\n\t\t\tconst content = (await operations.readFile(ignorePath)).toString(\"utf-8\");\n\t\t\tconst patterns = content\n\t\t\t\t.split(/\\r?\\n/)\n\t\t\t\t.map((line) => prefixIgnorePattern(line, prefix))\n\t\t\t\t.filter((line): line is string => Boolean(line));\n\t\t\tif (patterns.length > 0) {\n\t\t\t\tig.add(patterns);\n\t\t\t}\n\t\t} catch {}\n\t}\n}\n\nasync function loadRuleFromFileWithOperations(\n\toperations: ToolOperations,\n\tfilePath: string,\n\tsource: string,\n): Promise<{ rule: Rule | null; diagnostics: ResourceDiagnostic[] }> {\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\ttry {\n\t\tconst rawContent = (await operations.readFile(filePath)).toString(\"utf-8\");\n\t\tconst { frontmatter } = parseFrontmatter<RuleFrontmatter>(rawContent);\n\t\tconst ruleDir = dirname(filePath);\n\t\tconst parentDirName = basename(ruleDir);\n\n\t\tfor (const error of validateDescription(frontmatter.description)) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\tconst name = frontmatter.name || parentDirName;\n\t\tfor (const error of validateName(name)) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\tif (!frontmatter.description || frontmatter.description.trim() === \"\") {\n\t\t\treturn { rule: null, diagnostics };\n\t\t}\n\n\t\treturn {\n\t\t\trule: {\n\t\t\t\tname,\n\t\t\t\tdescription: frontmatter.description,\n\t\t\t\tfilePath,\n\t\t\t\tbaseDir: ruleDir,\n\t\t\t\tsourceInfo: createRuleSourceInfo(filePath, ruleDir, source),\n\t\t\t\tdisableModelInvocation: frontmatter[\"disable-model-invocation\"] === true,\n\t\t\t},\n\t\t\tdiagnostics,\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : \"failed to parse rule file\";\n\t\tdiagnostics.push({ type: \"warning\", message, path: filePath });\n\t\treturn { rule: null, diagnostics };\n\t}\n}\n\nasync function loadRulesFromDirInternalWithOperations(\n\toperations: ToolOperations,\n\tdir: string,\n\tsource: string,\n\tincludeRootFiles: boolean,\n\tignoreMatcher?: IgnoreMatcher,\n\trootDir?: string,\n): Promise<LoadRulesResult> {\n\tconst rules: Rule[] = [];\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\tif (!(await backendPathExists(operations, dir))) {\n\t\treturn { rules, diagnostics };\n\t}\n\n\tconst root = rootDir ?? dir;\n\tconst ig = ignoreMatcher ?? ignore();\n\tawait addIgnoreRulesWithOperations(operations, ig, dir, root);\n\n\ttry {\n\t\tconst entries = await operations.readdir(dir);\n\n\t\tfor (const name of entries) {\n\t\t\tif (name !== \"RULES.md\") continue;\n\t\t\tconst fullPath = join(dir, name);\n\t\t\tlet isFile = false;\n\t\t\ttry {\n\t\t\t\tisFile = (await operations.stat(fullPath)).isFile();\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tif (!isFile || ig.ignores(relPath)) continue;\n\t\t\tconst result = await loadRuleFromFileWithOperations(operations, fullPath, source);\n\t\t\tif (result.rule) rules.push(result.rule);\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t\treturn { rules, diagnostics };\n\t\t}\n\n\t\tfor (const name of entries) {\n\t\t\tif (name.startsWith(\".\") || name === \"node_modules\") continue;\n\t\t\tconst fullPath = join(dir, name);\n\t\t\tlet isDirectory = false;\n\t\t\tlet isFile = false;\n\t\t\ttry {\n\t\t\t\tconst stats = await operations.stat(fullPath);\n\t\t\t\tisDirectory = stats.isDirectory();\n\t\t\t\tisFile = stats.isFile();\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tconst ignorePath = isDirectory ? `${relPath}/` : relPath;\n\t\t\tif (ig.ignores(ignorePath)) continue;\n\t\t\tif (isDirectory) {\n\t\t\t\tconst subResult = await loadRulesFromDirInternalWithOperations(\n\t\t\t\t\toperations,\n\t\t\t\t\tfullPath,\n\t\t\t\t\tsource,\n\t\t\t\t\tfalse,\n\t\t\t\t\tig,\n\t\t\t\t\troot,\n\t\t\t\t);\n\t\t\t\trules.push(...subResult.rules);\n\t\t\t\tdiagnostics.push(...subResult.diagnostics);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!isFile || !includeRootFiles || !name.endsWith(\".md\")) continue;\n\t\t\tconst result = await loadRuleFromFileWithOperations(operations, fullPath, source);\n\t\t\tif (result.rule) rules.push(result.rule);\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t}\n\t} catch {}\n\n\treturn { rules, diagnostics };\n}\n\nexport interface LoadRulesWithOperationsOptions extends LoadRulesOptions {\n\toperations: ToolOperations;\n}\n\nexport async function loadRulesWithOperations(options: LoadRulesWithOperationsOptions): Promise<LoadRulesResult> {\n\tconst { agentDir, rulePaths, includeDefaults, operations } = options;\n\tconst resolvedCwd = resolvePath(options.cwd);\n\tconst resolvedAgentDir = resolvePath(agentDir ?? getAgentDir());\n\tconst ruleMap = new Map<string, Rule>();\n\tconst pathSet = new Set<string>();\n\tconst allDiagnostics: ResourceDiagnostic[] = [];\n\tconst collisionDiagnostics: ResourceDiagnostic[] = [];\n\n\tfunction addRules(result: LoadRulesResult) {\n\t\tallDiagnostics.push(...result.diagnostics);\n\t\tfor (const rule of result.rules) {\n\t\t\tconst canonicalPath = resolve(rule.filePath);\n\t\t\tif (pathSet.has(canonicalPath)) continue;\n\t\t\tconst existing = ruleMap.get(rule.name);\n\t\t\tif (existing) {\n\t\t\t\tcollisionDiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"${rule.name}\" collision`,\n\t\t\t\t\tpath: rule.filePath,\n\t\t\t\t\tcollision: {\n\t\t\t\t\t\tresourceType: \"rule\",\n\t\t\t\t\t\tname: rule.name,\n\t\t\t\t\t\twinnerPath: existing.filePath,\n\t\t\t\t\t\tloserPath: rule.filePath,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\truleMap.set(rule.name, rule);\n\t\t\t\tpathSet.add(canonicalPath);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (includeDefaults) {\n\t\taddRules(await loadRulesFromDirInternalWithOperations(operations, join(resolvedAgentDir, \"rules\"), \"user\", true));\n\t\taddRules(\n\t\t\tawait loadRulesFromDirInternalWithOperations(\n\t\t\t\toperations,\n\t\t\t\tresolve(resolvedCwd, CONFIG_DIR_NAME, \"rules\"),\n\t\t\t\t\"project\",\n\t\t\t\ttrue,\n\t\t\t),\n\t\t);\n\t}\n\n\tconst userRulesDir = join(resolvedAgentDir, \"rules\");\n\tconst projectRulesDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"rules\");\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) return true;\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\tconst getSource = (resolvedPath: string): \"user\" | \"project\" | \"path\" => {\n\t\tif (!includeDefaults) {\n\t\t\tif (isUnderPath(resolvedPath, userRulesDir)) return \"user\";\n\t\t\tif (isUnderPath(resolvedPath, projectRulesDir)) return \"project\";\n\t\t}\n\t\treturn \"path\";\n\t};\n\n\tfor (const rawPath of rulePaths) {\n\t\tconst resolvedPath = resolvePath(rawPath, resolvedCwd, { trim: true });\n\t\tif (!(await backendPathExists(operations, resolvedPath))) {\n\t\t\tallDiagnostics.push({ type: \"warning\", message: \"rule path does not exist\", path: resolvedPath });\n\t\t\tcontinue;\n\t\t}\n\t\ttry {\n\t\t\tconst stats = await operations.stat(resolvedPath);\n\t\t\tconst source = getSource(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\taddRules(await loadRulesFromDirInternalWithOperations(operations, resolvedPath, source, true));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst result = await loadRuleFromFileWithOperations(operations, resolvedPath, source);\n\t\t\t\tif (result.rule) addRules({ rules: [result.rule], diagnostics: result.diagnostics });\n\t\t\t\telse allDiagnostics.push(...result.diagnostics);\n\t\t\t} else {\n\t\t\t\tallDiagnostics.push({ type: \"warning\", message: \"rule path is not a markdown file\", path: resolvedPath });\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to read rule path\";\n\t\t\tallDiagnostics.push({ type: \"warning\", message, path: resolvedPath });\n\t\t}\n\t}\n\n\treturn { rules: Array.from(ruleMap.values()), diagnostics: [...allDiagnostics, ...collisionDiagnostics] };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"rules.js","sourceRoot":"","sources":["../../src/core/rules.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport ignore from \"ignore\";\nimport { dirname, join, relative, resolve, sep } from \"path\";\nimport { CONFIG_DIR_NAME, getAgentDir } from \"../config.ts\";\nimport { parseFrontmatter } from \"../utils/frontmatter.ts\";\nimport { canonicalizePath, resolvePath } from \"../utils/paths.ts\";\nimport type { ResourceDiagnostic } from \"./diagnostics.ts\";\nimport { createSyntheticSourceInfo, type SourceInfo } from \"./source-info.ts\";\nimport type { ToolOperations } from \"./tools/operations.ts\";\n\n/** Max name length per spec */\nconst MAX_NAME_LENGTH = 64;\n\n/** Max description length per spec */\nconst MAX_DESCRIPTION_LENGTH = 1024;\n\nconst IGNORE_FILE_NAMES = [\".gitignore\", \".ignore\", \".fdignore\"];\n\ntype IgnoreMatcher = ReturnType<typeof ignore>;\n\nfunction toPosixPath(p: string): string {\n\treturn p.split(sep).join(\"/\");\n}\n\nfunction prefixIgnorePattern(line: string, prefix: string): string | null {\n\tconst trimmed = line.trim();\n\tif (!trimmed) return null;\n\tif (trimmed.startsWith(\"#\") && !trimmed.startsWith(\"\\\\#\")) return null;\n\n\tlet pattern = line;\n\tlet negated = false;\n\n\tif (pattern.startsWith(\"!\")) {\n\t\tnegated = true;\n\t\tpattern = pattern.slice(1);\n\t} else if (pattern.startsWith(\"\\\\!\")) {\n\t\tpattern = pattern.slice(1);\n\t}\n\n\tif (pattern.startsWith(\"/\")) {\n\t\tpattern = pattern.slice(1);\n\t}\n\n\tconst prefixed = prefix ? `${prefix}${pattern}` : pattern;\n\treturn negated ? `!${prefixed}` : prefixed;\n}\n\nfunction addIgnoreRules(ig: IgnoreMatcher, dir: string, rootDir: string): void {\n\tconst relativeDir = relative(rootDir, dir);\n\tconst prefix = relativeDir ? `${toPosixPath(relativeDir)}/` : \"\";\n\n\tfor (const filename of IGNORE_FILE_NAMES) {\n\t\tconst ignorePath = join(dir, filename);\n\t\tif (!existsSync(ignorePath)) continue;\n\t\ttry {\n\t\t\tconst content = readFileSync(ignorePath, \"utf-8\");\n\t\t\tconst patterns = content\n\t\t\t\t.split(/\\r?\\n/)\n\t\t\t\t.map((line) => prefixIgnorePattern(line, prefix))\n\t\t\t\t.filter((line): line is string => Boolean(line));\n\t\t\tif (patterns.length > 0) {\n\t\t\t\tig.add(patterns);\n\t\t\t}\n\t\t} catch {}\n\t}\n}\n\nexport interface RuleFrontmatter {\n\tname?: string;\n\tdescription?: string;\n\t\"disable-model-invocation\"?: boolean;\n\t[key: string]: unknown;\n}\n\nexport interface Rule {\n\tname: string;\n\tdescription: string;\n\tfilePath: string;\n\tbaseDir: string;\n\tsourceInfo: SourceInfo;\n\tdisableModelInvocation: boolean;\n\tcontent?: string;\n}\n\nexport interface LoadRulesResult {\n\trules: Rule[];\n\tdiagnostics: ResourceDiagnostic[];\n}\n\n/**\n * Validate rule name.\n * Returns array of validation error messages (empty if valid).\n */\nfunction validateName(name: string): string[] {\n\tconst errors: string[] = [];\n\n\tif (name.length > MAX_NAME_LENGTH) {\n\t\terrors.push(`name exceeds ${MAX_NAME_LENGTH} characters (${name.length})`);\n\t}\n\n\tif (!/^[a-z0-9-]+$/.test(name)) {\n\t\terrors.push(`name contains invalid characters (must be lowercase a-z, 0-9, hyphens only)`);\n\t}\n\n\tif (name.startsWith(\"-\") || name.endsWith(\"-\")) {\n\t\terrors.push(`name must not start or end with a hyphen`);\n\t}\n\n\tif (name.includes(\"--\")) {\n\t\terrors.push(`name must not contain consecutive hyphens`);\n\t}\n\n\treturn errors;\n}\n\n/**\n * Validate description.\n */\nfunction validateDescription(description: string | undefined): string[] {\n\tconst errors: string[] = [];\n\n\tif (!description || description.trim() === \"\") {\n\t\terrors.push(\"description is required\");\n\t} else if (description.length > MAX_DESCRIPTION_LENGTH) {\n\t\terrors.push(`description exceeds ${MAX_DESCRIPTION_LENGTH} characters (${description.length})`);\n\t}\n\n\treturn errors;\n}\n\nexport interface LoadRulesFromDirOptions {\n\t/** Directory to scan for rules */\n\tdir: string;\n\t/** Source identifier for these rules */\n\tsource: string;\n}\n\nfunction createRuleSourceInfo(filePath: string, baseDir: string, source: string): SourceInfo {\n\tswitch (source) {\n\t\tcase \"user\":\n\t\t\treturn createSyntheticSourceInfo(filePath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"user\",\n\t\t\t\tbaseDir,\n\t\t\t});\n\t\tcase \"project\":\n\t\t\treturn createSyntheticSourceInfo(filePath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"project\",\n\t\t\t\tbaseDir,\n\t\t\t});\n\t\tcase \"path\":\n\t\t\treturn createSyntheticSourceInfo(filePath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tbaseDir,\n\t\t\t});\n\t\tdefault:\n\t\t\treturn createSyntheticSourceInfo(filePath, { source, baseDir });\n\t}\n}\n\n/**\n * Load rules from a directory.\n *\n * Discovery rules:\n * - if a directory contains RULES.md, treat it as a rule root and do not recurse further\n * - otherwise, load direct .md children in the root\n * - recurse into subdirectories to find RULES.md\n */\nexport function loadRulesFromDir(options: LoadRulesFromDirOptions): LoadRulesResult {\n\tconst { dir, source } = options;\n\treturn loadRulesFromDirInternal(dir, source, true);\n}\n\nfunction loadRulesFromDirInternal(\n\tdir: string,\n\tsource: string,\n\tincludeRootFiles: boolean,\n\tignoreMatcher?: IgnoreMatcher,\n\trootDir?: string,\n): LoadRulesResult {\n\tconst rules: Rule[] = [];\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn { rules, diagnostics };\n\t}\n\n\tconst root = rootDir ?? dir;\n\tconst ig = ignoreMatcher ?? ignore();\n\taddIgnoreRules(ig, dir, root);\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name !== \"RULES.md\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tisFile = statSync(fullPath).isFile();\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tif (!isFile || ig.ignores(relPath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst result = loadRuleFromFile(fullPath, source);\n\t\t\tif (result.rule) {\n\t\t\t\trules.push(result.rule);\n\t\t\t}\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t\treturn { rules, diagnostics };\n\t\t}\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name.startsWith(\".\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Skip node_modules to avoid scanning dependencies\n\t\t\tif (entry.name === \"node_modules\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\t// For symlinks, check if they point to a directory and follow them\n\t\t\tlet isDirectory = entry.isDirectory();\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(fullPath);\n\t\t\t\t\tisDirectory = stats.isDirectory();\n\t\t\t\t\tisFile = stats.isFile();\n\t\t\t\t} catch {\n\t\t\t\t\t// Broken symlink, skip it\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tconst ignorePath = isDirectory ? `${relPath}/` : relPath;\n\t\t\tif (ig.ignores(ignorePath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (isDirectory) {\n\t\t\t\tconst subResult = loadRulesFromDirInternal(fullPath, source, false, ig, root);\n\t\t\t\trules.push(...subResult.rules);\n\t\t\t\tdiagnostics.push(...subResult.diagnostics);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!isFile || !includeRootFiles || !entry.name.endsWith(\".md\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst result = loadRuleFromFile(fullPath, source);\n\t\t\tif (result.rule) {\n\t\t\t\trules.push(result.rule);\n\t\t\t}\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t}\n\t} catch {}\n\n\treturn { rules, diagnostics };\n}\n\nfunction loadRuleFromFile(filePath: string, source: string): { rule: Rule | null; diagnostics: ResourceDiagnostic[] } {\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\ttry {\n\t\tconst rawContent = readFileSync(filePath, \"utf-8\");\n\t\tconst { frontmatter } = parseFrontmatter<RuleFrontmatter>(rawContent);\n\t\tconst ruleDir = dirname(filePath);\n\n\t\t// Validate description\n\t\tconst descErrors = validateDescription(frontmatter.description);\n\t\tfor (const error of descErrors) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\tif (!frontmatter.name || frontmatter.name.trim() === \"\") {\n\t\t\tdiagnostics.push({ type: \"warning\", message: \"name is required\", path: filePath });\n\t\t}\n\n\t\tconst name = frontmatter.name;\n\t\tif (name) {\n\t\t\tfor (const error of validateName(name)) {\n\t\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t\t}\n\t\t}\n\n\t\t// Still load the rule even with warnings (unless required fields are missing)\n\t\tif (!name || !frontmatter.description || frontmatter.description.trim() === \"\") {\n\t\t\treturn { rule: null, diagnostics };\n\t\t}\n\n\t\treturn {\n\t\t\trule: {\n\t\t\t\tname,\n\t\t\t\tdescription: frontmatter.description,\n\t\t\t\tfilePath,\n\t\t\t\tbaseDir: ruleDir,\n\t\t\t\tsourceInfo: createRuleSourceInfo(filePath, ruleDir, source),\n\t\t\t\tdisableModelInvocation: frontmatter[\"disable-model-invocation\"] === true,\n\t\t\t},\n\t\t\tdiagnostics,\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : \"failed to parse rule file\";\n\t\tdiagnostics.push({ type: \"warning\", message, path: filePath });\n\t\treturn { rule: null, diagnostics };\n\t}\n}\n\n/**\n * Format rules for inclusion in a system prompt.\n * Rules with disableModelInvocation=true are excluded from the prompt\n * (they can only be invoked explicitly via /rule:name commands).\n */\nexport function formatRulesForPrompt(rules: Rule[]): string {\n\tconst visibleRules = rules.filter((s) => !s.disableModelInvocation);\n\n\tif (visibleRules.length === 0) {\n\t\treturn \"\";\n\t}\n\n\tconst lines = [\n\t\t\"\\n\\nThe following rules provide mandatory constraints and policies.\",\n\t\t\"Use the read tool to load a rule's file when the task or files match its description; applicable rules are mandatory.\",\n\t\t\"When a rule file references a relative path, resolve it against the rule directory (parent of RULES.md / dirname of the path) and use that absolute path in tool commands.\",\n\t\t\"\",\n\t\t\"<available_rules>\",\n\t];\n\n\tfor (const rule of visibleRules) {\n\t\tlines.push(\" <rule>\");\n\t\tlines.push(` <name>${escapeXml(rule.name)}</name>`);\n\t\tlines.push(` <description>${escapeXml(rule.description)}</description>`);\n\t\tlines.push(` <location>${escapeXml(rule.filePath)}</location>`);\n\t\tlines.push(\" </rule>\");\n\t}\n\n\tlines.push(\"</available_rules>\");\n\n\treturn lines.join(\"\\n\");\n}\n\nfunction escapeXml(str: string): string {\n\treturn str\n\t\t.replace(/&/g, \"&\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\");\n}\n\nexport interface LoadRulesOptions {\n\t/** Working directory for project-local rules. */\n\tcwd: string;\n\t/** Agent config directory for global rules. */\n\tagentDir: string;\n\t/** Explicit rule paths (files or directories) */\n\trulePaths: string[];\n\t/** Include default rules directories. */\n\tincludeDefaults: boolean;\n}\n\n/**\n * Load rules from all configured locations.\n * Returns rules and any validation diagnostics.\n */\nexport function loadRules(options: LoadRulesOptions): LoadRulesResult {\n\tconst { agentDir, rulePaths, includeDefaults } = options;\n\n\t// Resolve agentDir - if not provided, use default from config\n\tconst resolvedCwd = resolvePath(options.cwd);\n\tconst resolvedAgentDir = resolvePath(agentDir ?? getAgentDir());\n\n\tconst ruleMap = new Map<string, Rule>();\n\tconst realPathSet = new Set<string>();\n\tconst allDiagnostics: ResourceDiagnostic[] = [];\n\tconst collisionDiagnostics: ResourceDiagnostic[] = [];\n\n\tfunction addRules(result: LoadRulesResult) {\n\t\tallDiagnostics.push(...result.diagnostics);\n\t\tfor (const rule of result.rules) {\n\t\t\t// Resolve symlinks to detect duplicate files\n\t\t\tconst realPath = canonicalizePath(rule.filePath);\n\n\t\t\t// Skip silently if we've already loaded this exact file (via symlink)\n\t\t\tif (realPathSet.has(realPath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst existing = ruleMap.get(rule.name);\n\t\t\tif (existing) {\n\t\t\t\tcollisionDiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"${rule.name}\" collision`,\n\t\t\t\t\tpath: rule.filePath,\n\t\t\t\t\tcollision: {\n\t\t\t\t\t\tresourceType: \"rule\",\n\t\t\t\t\t\tname: rule.name,\n\t\t\t\t\t\twinnerPath: existing.filePath,\n\t\t\t\t\t\tloserPath: rule.filePath,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\truleMap.set(rule.name, rule);\n\t\t\t\trealPathSet.add(realPath);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (includeDefaults) {\n\t\taddRules(loadRulesFromDirInternal(join(resolvedAgentDir, \"rules\"), \"user\", true));\n\t\taddRules(loadRulesFromDirInternal(resolve(resolvedCwd, CONFIG_DIR_NAME, \"rules\"), \"project\", true));\n\t}\n\n\tconst userRulesDir = join(resolvedAgentDir, \"rules\");\n\tconst projectRulesDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"rules\");\n\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) {\n\t\t\treturn true;\n\t\t}\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\n\tconst getSource = (resolvedPath: string): \"user\" | \"project\" | \"path\" => {\n\t\tif (!includeDefaults) {\n\t\t\tif (isUnderPath(resolvedPath, userRulesDir)) return \"user\";\n\t\t\tif (isUnderPath(resolvedPath, projectRulesDir)) return \"project\";\n\t\t}\n\t\treturn \"path\";\n\t};\n\n\tfor (const rawPath of rulePaths) {\n\t\tconst resolvedPath = resolvePath(rawPath, resolvedCwd, { trim: true });\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tallDiagnostics.push({ type: \"warning\", message: \"rule path does not exist\", path: resolvedPath });\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst stats = statSync(resolvedPath);\n\t\t\tconst source = getSource(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\taddRules(loadRulesFromDirInternal(resolvedPath, source, true));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst result = loadRuleFromFile(resolvedPath, source);\n\t\t\t\tif (result.rule) {\n\t\t\t\t\taddRules({ rules: [result.rule], diagnostics: result.diagnostics });\n\t\t\t\t} else {\n\t\t\t\t\tallDiagnostics.push(...result.diagnostics);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tallDiagnostics.push({ type: \"warning\", message: \"rule path is not a markdown file\", path: resolvedPath });\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to read rule path\";\n\t\t\tallDiagnostics.push({ type: \"warning\", message, path: resolvedPath });\n\t\t}\n\t}\n\n\treturn {\n\t\trules: Array.from(ruleMap.values()),\n\t\tdiagnostics: [...allDiagnostics, ...collisionDiagnostics],\n\t};\n}\n\nasync function backendPathExists(operations: ToolOperations, path: string): Promise<boolean> {\n\ttry {\n\t\tawait operations.access(path, \"exists\");\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nasync function addIgnoreRulesWithOperations(\n\toperations: ToolOperations,\n\tig: IgnoreMatcher,\n\tdir: string,\n\trootDir: string,\n): Promise<void> {\n\tconst relativeDir = relative(rootDir, dir);\n\tconst prefix = relativeDir ? `${toPosixPath(relativeDir)}/` : \"\";\n\n\tfor (const filename of IGNORE_FILE_NAMES) {\n\t\tconst ignorePath = join(dir, filename);\n\t\tif (!(await backendPathExists(operations, ignorePath))) continue;\n\t\ttry {\n\t\t\tconst content = (await operations.readFile(ignorePath)).toString(\"utf-8\");\n\t\t\tconst patterns = content\n\t\t\t\t.split(/\\r?\\n/)\n\t\t\t\t.map((line) => prefixIgnorePattern(line, prefix))\n\t\t\t\t.filter((line): line is string => Boolean(line));\n\t\t\tif (patterns.length > 0) {\n\t\t\t\tig.add(patterns);\n\t\t\t}\n\t\t} catch {}\n\t}\n}\n\nasync function loadRuleFromFileWithOperations(\n\toperations: ToolOperations,\n\tfilePath: string,\n\tsource: string,\n): Promise<{ rule: Rule | null; diagnostics: ResourceDiagnostic[] }> {\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\ttry {\n\t\tconst rawContent = (await operations.readFile(filePath)).toString(\"utf-8\");\n\t\tconst { frontmatter } = parseFrontmatter<RuleFrontmatter>(rawContent);\n\t\tconst ruleDir = dirname(filePath);\n\n\t\tfor (const error of validateDescription(frontmatter.description)) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\tif (!frontmatter.name || frontmatter.name.trim() === \"\") {\n\t\t\tdiagnostics.push({ type: \"warning\", message: \"name is required\", path: filePath });\n\t\t}\n\n\t\tconst name = frontmatter.name;\n\t\tif (name) {\n\t\t\tfor (const error of validateName(name)) {\n\t\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t\t}\n\t\t}\n\n\t\tif (!name || !frontmatter.description || frontmatter.description.trim() === \"\") {\n\t\t\treturn { rule: null, diagnostics };\n\t\t}\n\n\t\treturn {\n\t\t\trule: {\n\t\t\t\tname,\n\t\t\t\tdescription: frontmatter.description,\n\t\t\t\tfilePath,\n\t\t\t\tbaseDir: ruleDir,\n\t\t\t\tsourceInfo: createRuleSourceInfo(filePath, ruleDir, source),\n\t\t\t\tdisableModelInvocation: frontmatter[\"disable-model-invocation\"] === true,\n\t\t\t},\n\t\t\tdiagnostics,\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : \"failed to parse rule file\";\n\t\tdiagnostics.push({ type: \"warning\", message, path: filePath });\n\t\treturn { rule: null, diagnostics };\n\t}\n}\n\nasync function loadRulesFromDirInternalWithOperations(\n\toperations: ToolOperations,\n\tdir: string,\n\tsource: string,\n\tincludeRootFiles: boolean,\n\tignoreMatcher?: IgnoreMatcher,\n\trootDir?: string,\n): Promise<LoadRulesResult> {\n\tconst rules: Rule[] = [];\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\tif (!(await backendPathExists(operations, dir))) {\n\t\treturn { rules, diagnostics };\n\t}\n\n\tconst root = rootDir ?? dir;\n\tconst ig = ignoreMatcher ?? ignore();\n\tawait addIgnoreRulesWithOperations(operations, ig, dir, root);\n\n\ttry {\n\t\tconst entries = await operations.readdir(dir);\n\n\t\tfor (const name of entries) {\n\t\t\tif (name !== \"RULES.md\") continue;\n\t\t\tconst fullPath = join(dir, name);\n\t\t\tlet isFile = false;\n\t\t\ttry {\n\t\t\t\tisFile = (await operations.stat(fullPath)).isFile();\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tif (!isFile || ig.ignores(relPath)) continue;\n\t\t\tconst result = await loadRuleFromFileWithOperations(operations, fullPath, source);\n\t\t\tif (result.rule) rules.push(result.rule);\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t\treturn { rules, diagnostics };\n\t\t}\n\n\t\tfor (const name of entries) {\n\t\t\tif (name.startsWith(\".\") || name === \"node_modules\") continue;\n\t\t\tconst fullPath = join(dir, name);\n\t\t\tlet isDirectory = false;\n\t\t\tlet isFile = false;\n\t\t\ttry {\n\t\t\t\tconst stats = await operations.stat(fullPath);\n\t\t\t\tisDirectory = stats.isDirectory();\n\t\t\t\tisFile = stats.isFile();\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tconst ignorePath = isDirectory ? `${relPath}/` : relPath;\n\t\t\tif (ig.ignores(ignorePath)) continue;\n\t\t\tif (isDirectory) {\n\t\t\t\tconst subResult = await loadRulesFromDirInternalWithOperations(\n\t\t\t\t\toperations,\n\t\t\t\t\tfullPath,\n\t\t\t\t\tsource,\n\t\t\t\t\tfalse,\n\t\t\t\t\tig,\n\t\t\t\t\troot,\n\t\t\t\t);\n\t\t\t\trules.push(...subResult.rules);\n\t\t\t\tdiagnostics.push(...subResult.diagnostics);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!isFile || !includeRootFiles || !name.endsWith(\".md\")) continue;\n\t\t\tconst result = await loadRuleFromFileWithOperations(operations, fullPath, source);\n\t\t\tif (result.rule) rules.push(result.rule);\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t}\n\t} catch {}\n\n\treturn { rules, diagnostics };\n}\n\nexport interface LoadRulesWithOperationsOptions extends LoadRulesOptions {\n\toperations: ToolOperations;\n}\n\nexport async function loadRulesWithOperations(options: LoadRulesWithOperationsOptions): Promise<LoadRulesResult> {\n\tconst { agentDir, rulePaths, includeDefaults, operations } = options;\n\tconst resolvedCwd = resolvePath(options.cwd);\n\tconst resolvedAgentDir = resolvePath(agentDir ?? getAgentDir());\n\tconst ruleMap = new Map<string, Rule>();\n\tconst pathSet = new Set<string>();\n\tconst allDiagnostics: ResourceDiagnostic[] = [];\n\tconst collisionDiagnostics: ResourceDiagnostic[] = [];\n\n\tfunction addRules(result: LoadRulesResult) {\n\t\tallDiagnostics.push(...result.diagnostics);\n\t\tfor (const rule of result.rules) {\n\t\t\tconst canonicalPath = resolve(rule.filePath);\n\t\t\tif (pathSet.has(canonicalPath)) continue;\n\t\t\tconst existing = ruleMap.get(rule.name);\n\t\t\tif (existing) {\n\t\t\t\tcollisionDiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"${rule.name}\" collision`,\n\t\t\t\t\tpath: rule.filePath,\n\t\t\t\t\tcollision: {\n\t\t\t\t\t\tresourceType: \"rule\",\n\t\t\t\t\t\tname: rule.name,\n\t\t\t\t\t\twinnerPath: existing.filePath,\n\t\t\t\t\t\tloserPath: rule.filePath,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\truleMap.set(rule.name, rule);\n\t\t\t\tpathSet.add(canonicalPath);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (includeDefaults) {\n\t\taddRules(await loadRulesFromDirInternalWithOperations(operations, join(resolvedAgentDir, \"rules\"), \"user\", true));\n\t\taddRules(\n\t\t\tawait loadRulesFromDirInternalWithOperations(\n\t\t\t\toperations,\n\t\t\t\tresolve(resolvedCwd, CONFIG_DIR_NAME, \"rules\"),\n\t\t\t\t\"project\",\n\t\t\t\ttrue,\n\t\t\t),\n\t\t);\n\t}\n\n\tconst userRulesDir = join(resolvedAgentDir, \"rules\");\n\tconst projectRulesDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"rules\");\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) return true;\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\tconst getSource = (resolvedPath: string): \"user\" | \"project\" | \"path\" => {\n\t\tif (!includeDefaults) {\n\t\t\tif (isUnderPath(resolvedPath, userRulesDir)) return \"user\";\n\t\t\tif (isUnderPath(resolvedPath, projectRulesDir)) return \"project\";\n\t\t}\n\t\treturn \"path\";\n\t};\n\n\tfor (const rawPath of rulePaths) {\n\t\tconst resolvedPath = resolvePath(rawPath, resolvedCwd, { trim: true });\n\t\tif (!(await backendPathExists(operations, resolvedPath))) {\n\t\t\tallDiagnostics.push({ type: \"warning\", message: \"rule path does not exist\", path: resolvedPath });\n\t\t\tcontinue;\n\t\t}\n\t\ttry {\n\t\t\tconst stats = await operations.stat(resolvedPath);\n\t\t\tconst source = getSource(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\taddRules(await loadRulesFromDirInternalWithOperations(operations, resolvedPath, source, true));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst result = await loadRuleFromFileWithOperations(operations, resolvedPath, source);\n\t\t\t\tif (result.rule) addRules({ rules: [result.rule], diagnostics: result.diagnostics });\n\t\t\t\telse allDiagnostics.push(...result.diagnostics);\n\t\t\t} else {\n\t\t\t\tallDiagnostics.push({ type: \"warning\", message: \"rule path is not a markdown file\", path: resolvedPath });\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to read rule path\";\n\t\t\tallDiagnostics.push({ type: \"warning\", message, path: resolvedPath });\n\t\t}\n\t}\n\n\treturn { rules: Array.from(ruleMap.values()), diagnostics: [...allDiagnostics, ...collisionDiagnostics] };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/core/skills.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAA6B,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AA2D5D,MAAM,WAAW,gBAAgB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,KAAK;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;IACvB,sBAAsB,EAAE,OAAO,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,WAAW,EAAE,kBAAkB,EAAE,CAAC;CAClC;AA2CD,MAAM,WAAW,wBAAwB;IACxC,mCAAmC;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;CACf;AA0BD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,gBAAgB,CAGrF;AA4JD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CA0B7D;AAWD,MAAM,WAAW,iBAAiB;IACjC,kDAAkD;IAClD,GAAG,EAAE,MAAM,CAAC;IACZ,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,0CAA0C;IAC1C,eAAe,EAAE,OAAO,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAoGvE;AA6JD,MAAM,WAAW,+BAAgC,SAAQ,iBAAiB;IACzE,UAAU,EAAE,cAAc,CAAC;CAC3B;AAED,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,+BAA+B,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAyFlH","sourcesContent":["import { existsSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport ignore from \"ignore\";\nimport { basename, dirname, join, relative, resolve, sep } from \"path\";\nimport { CONFIG_DIR_NAME, getAgentDir } from \"../config.ts\";\nimport { parseFrontmatter } from \"../utils/frontmatter.ts\";\nimport { canonicalizePath, resolvePath } from \"../utils/paths.ts\";\nimport type { ResourceDiagnostic } from \"./diagnostics.ts\";\nimport { createSyntheticSourceInfo, type SourceInfo } from \"./source-info.ts\";\nimport type { ToolOperations } from \"./tools/operations.ts\";\n\n/** Max name length per spec */\nconst MAX_NAME_LENGTH = 64;\n\n/** Max description length per spec */\nconst MAX_DESCRIPTION_LENGTH = 1024;\n\nconst IGNORE_FILE_NAMES = [\".gitignore\", \".ignore\", \".fdignore\"];\n\ntype IgnoreMatcher = ReturnType<typeof ignore>;\n\nfunction toPosixPath(p: string): string {\n\treturn p.split(sep).join(\"/\");\n}\n\nfunction prefixIgnorePattern(line: string, prefix: string): string | null {\n\tconst trimmed = line.trim();\n\tif (!trimmed) return null;\n\tif (trimmed.startsWith(\"#\") && !trimmed.startsWith(\"\\\\#\")) return null;\n\n\tlet pattern = line;\n\tlet negated = false;\n\n\tif (pattern.startsWith(\"!\")) {\n\t\tnegated = true;\n\t\tpattern = pattern.slice(1);\n\t} else if (pattern.startsWith(\"\\\\!\")) {\n\t\tpattern = pattern.slice(1);\n\t}\n\n\tif (pattern.startsWith(\"/\")) {\n\t\tpattern = pattern.slice(1);\n\t}\n\n\tconst prefixed = prefix ? `${prefix}${pattern}` : pattern;\n\treturn negated ? `!${prefixed}` : prefixed;\n}\n\nfunction addIgnoreRules(ig: IgnoreMatcher, dir: string, rootDir: string): void {\n\tconst relativeDir = relative(rootDir, dir);\n\tconst prefix = relativeDir ? `${toPosixPath(relativeDir)}/` : \"\";\n\n\tfor (const filename of IGNORE_FILE_NAMES) {\n\t\tconst ignorePath = join(dir, filename);\n\t\tif (!existsSync(ignorePath)) continue;\n\t\ttry {\n\t\t\tconst content = readFileSync(ignorePath, \"utf-8\");\n\t\t\tconst patterns = content\n\t\t\t\t.split(/\\r?\\n/)\n\t\t\t\t.map((line) => prefixIgnorePattern(line, prefix))\n\t\t\t\t.filter((line): line is string => Boolean(line));\n\t\t\tif (patterns.length > 0) {\n\t\t\t\tig.add(patterns);\n\t\t\t}\n\t\t} catch {}\n\t}\n}\n\nexport interface SkillFrontmatter {\n\tname?: string;\n\tdescription?: string;\n\t\"disable-model-invocation\"?: boolean;\n\t[key: string]: unknown;\n}\n\nexport interface Skill {\n\tname: string;\n\tdescription: string;\n\tfilePath: string;\n\tbaseDir: string;\n\tsourceInfo: SourceInfo;\n\tdisableModelInvocation: boolean;\n\tcontent?: string;\n}\n\nexport interface LoadSkillsResult {\n\tskills: Skill[];\n\tdiagnostics: ResourceDiagnostic[];\n}\n\n/**\n * Validate skill name per Agent Skills spec.\n * Returns array of validation error messages (empty if valid).\n */\nfunction validateName(name: string): string[] {\n\tconst errors: string[] = [];\n\n\tif (name.length > MAX_NAME_LENGTH) {\n\t\terrors.push(`name exceeds ${MAX_NAME_LENGTH} characters (${name.length})`);\n\t}\n\n\tif (!/^[a-z0-9-]+$/.test(name)) {\n\t\terrors.push(`name contains invalid characters (must be lowercase a-z, 0-9, hyphens only)`);\n\t}\n\n\tif (name.startsWith(\"-\") || name.endsWith(\"-\")) {\n\t\terrors.push(`name must not start or end with a hyphen`);\n\t}\n\n\tif (name.includes(\"--\")) {\n\t\terrors.push(`name must not contain consecutive hyphens`);\n\t}\n\n\treturn errors;\n}\n\n/**\n * Validate description per Agent Skills spec.\n */\nfunction validateDescription(description: string | undefined): string[] {\n\tconst errors: string[] = [];\n\n\tif (!description || description.trim() === \"\") {\n\t\terrors.push(\"description is required\");\n\t} else if (description.length > MAX_DESCRIPTION_LENGTH) {\n\t\terrors.push(`description exceeds ${MAX_DESCRIPTION_LENGTH} characters (${description.length})`);\n\t}\n\n\treturn errors;\n}\n\nexport interface LoadSkillsFromDirOptions {\n\t/** Directory to scan for skills */\n\tdir: string;\n\t/** Source identifier for these skills */\n\tsource: string;\n}\n\nfunction createSkillSourceInfo(filePath: string, baseDir: string, source: string): SourceInfo {\n\tswitch (source) {\n\t\tcase \"user\":\n\t\t\treturn createSyntheticSourceInfo(filePath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"user\",\n\t\t\t\tbaseDir,\n\t\t\t});\n\t\tcase \"project\":\n\t\t\treturn createSyntheticSourceInfo(filePath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"project\",\n\t\t\t\tbaseDir,\n\t\t\t});\n\t\tcase \"path\":\n\t\t\treturn createSyntheticSourceInfo(filePath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tbaseDir,\n\t\t\t});\n\t\tdefault:\n\t\t\treturn createSyntheticSourceInfo(filePath, { source, baseDir });\n\t}\n}\n\n/**\n * Load skills from a directory.\n *\n * Discovery rules:\n * - if a directory contains SKILL.md, treat it as a skill root and do not recurse further\n * - otherwise, load direct .md children in the root\n * - recurse into subdirectories to find SKILL.md\n */\nexport function loadSkillsFromDir(options: LoadSkillsFromDirOptions): LoadSkillsResult {\n\tconst { dir, source } = options;\n\treturn loadSkillsFromDirInternal(dir, source, true);\n}\n\nfunction loadSkillsFromDirInternal(\n\tdir: string,\n\tsource: string,\n\tincludeRootFiles: boolean,\n\tignoreMatcher?: IgnoreMatcher,\n\trootDir?: string,\n): LoadSkillsResult {\n\tconst skills: Skill[] = [];\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn { skills, diagnostics };\n\t}\n\n\tconst root = rootDir ?? dir;\n\tconst ig = ignoreMatcher ?? ignore();\n\taddIgnoreRules(ig, dir, root);\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name !== \"SKILL.md\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tisFile = statSync(fullPath).isFile();\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tif (!isFile || ig.ignores(relPath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst result = loadSkillFromFile(fullPath, source);\n\t\t\tif (result.skill) {\n\t\t\t\tskills.push(result.skill);\n\t\t\t}\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t\treturn { skills, diagnostics };\n\t\t}\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name.startsWith(\".\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Skip node_modules to avoid scanning dependencies\n\t\t\tif (entry.name === \"node_modules\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\t// For symlinks, check if they point to a directory and follow them\n\t\t\tlet isDirectory = entry.isDirectory();\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(fullPath);\n\t\t\t\t\tisDirectory = stats.isDirectory();\n\t\t\t\t\tisFile = stats.isFile();\n\t\t\t\t} catch {\n\t\t\t\t\t// Broken symlink, skip it\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tconst ignorePath = isDirectory ? `${relPath}/` : relPath;\n\t\t\tif (ig.ignores(ignorePath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (isDirectory) {\n\t\t\t\tconst subResult = loadSkillsFromDirInternal(fullPath, source, false, ig, root);\n\t\t\t\tskills.push(...subResult.skills);\n\t\t\t\tdiagnostics.push(...subResult.diagnostics);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!isFile || !includeRootFiles || !entry.name.endsWith(\".md\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst result = loadSkillFromFile(fullPath, source);\n\t\t\tif (result.skill) {\n\t\t\t\tskills.push(result.skill);\n\t\t\t}\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t}\n\t} catch {}\n\n\treturn { skills, diagnostics };\n}\n\nfunction loadSkillFromFile(\n\tfilePath: string,\n\tsource: string,\n): { skill: Skill | null; diagnostics: ResourceDiagnostic[] } {\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\ttry {\n\t\tconst rawContent = readFileSync(filePath, \"utf-8\");\n\t\tconst { frontmatter } = parseFrontmatter<SkillFrontmatter>(rawContent);\n\t\tconst skillDir = dirname(filePath);\n\t\tconst parentDirName = basename(skillDir);\n\n\t\t// Validate description\n\t\tconst descErrors = validateDescription(frontmatter.description);\n\t\tfor (const error of descErrors) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\t// Use name from frontmatter, or fall back to parent directory name\n\t\tconst name = frontmatter.name || parentDirName;\n\n\t\t// Validate name\n\t\tconst nameErrors = validateName(name);\n\t\tfor (const error of nameErrors) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\t// Still load the skill even with warnings (unless description is completely missing)\n\t\tif (!frontmatter.description || frontmatter.description.trim() === \"\") {\n\t\t\treturn { skill: null, diagnostics };\n\t\t}\n\n\t\treturn {\n\t\t\tskill: {\n\t\t\t\tname,\n\t\t\t\tdescription: frontmatter.description,\n\t\t\t\tfilePath,\n\t\t\t\tbaseDir: skillDir,\n\t\t\t\tsourceInfo: createSkillSourceInfo(filePath, skillDir, source),\n\t\t\t\tdisableModelInvocation: frontmatter[\"disable-model-invocation\"] === true,\n\t\t\t},\n\t\t\tdiagnostics,\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : \"failed to parse skill file\";\n\t\tdiagnostics.push({ type: \"warning\", message, path: filePath });\n\t\treturn { skill: null, diagnostics };\n\t}\n}\n\n/**\n * Format skills for inclusion in a system prompt.\n * Uses XML format per Agent Skills standard.\n * See: https://agentskills.io/integrate-skills\n *\n * Skills with disableModelInvocation=true are excluded from the prompt\n * (they can only be invoked explicitly via /skill:name commands).\n */\nexport function formatSkillsForPrompt(skills: Skill[]): string {\n\tconst visibleSkills = skills.filter((s) => !s.disableModelInvocation);\n\n\tif (visibleSkills.length === 0) {\n\t\treturn \"\";\n\t}\n\n\tconst lines = [\n\t\t\"\\n\\nThe following skills provide specialized instructions for specific tasks.\",\n\t\t\"Use the read tool to load a skill's file when the task matches its description.\",\n\t\t\"When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\",\n\t\t\"\",\n\t\t\"<available_skills>\",\n\t];\n\n\tfor (const skill of visibleSkills) {\n\t\tlines.push(\" <skill>\");\n\t\tlines.push(` <name>${escapeXml(skill.name)}</name>`);\n\t\tlines.push(` <description>${escapeXml(skill.description)}</description>`);\n\t\tlines.push(` <location>${escapeXml(skill.filePath)}</location>`);\n\t\tlines.push(\" </skill>\");\n\t}\n\n\tlines.push(\"</available_skills>\");\n\n\treturn lines.join(\"\\n\");\n}\n\nfunction escapeXml(str: string): string {\n\treturn str\n\t\t.replace(/&/g, \"&\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\");\n}\n\nexport interface LoadSkillsOptions {\n\t/** Working directory for project-local skills. */\n\tcwd: string;\n\t/** Agent config directory for global skills. */\n\tagentDir: string;\n\t/** Explicit skill paths (files or directories) */\n\tskillPaths: string[];\n\t/** Include default skills directories. */\n\tincludeDefaults: boolean;\n}\n\n/**\n * Load skills from all configured locations.\n * Returns skills and any validation diagnostics.\n */\nexport function loadSkills(options: LoadSkillsOptions): LoadSkillsResult {\n\tconst { agentDir, skillPaths, includeDefaults } = options;\n\n\t// Resolve agentDir - if not provided, use default from config\n\tconst resolvedCwd = resolvePath(options.cwd);\n\tconst resolvedAgentDir = resolvePath(agentDir ?? getAgentDir());\n\n\tconst skillMap = new Map<string, Skill>();\n\tconst realPathSet = new Set<string>();\n\tconst allDiagnostics: ResourceDiagnostic[] = [];\n\tconst collisionDiagnostics: ResourceDiagnostic[] = [];\n\n\tfunction addSkills(result: LoadSkillsResult) {\n\t\tallDiagnostics.push(...result.diagnostics);\n\t\tfor (const skill of result.skills) {\n\t\t\t// Resolve symlinks to detect duplicate files\n\t\t\tconst realPath = canonicalizePath(skill.filePath);\n\n\t\t\t// Skip silently if we've already loaded this exact file (via symlink)\n\t\t\tif (realPathSet.has(realPath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst existing = skillMap.get(skill.name);\n\t\t\tif (existing) {\n\t\t\t\tcollisionDiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"${skill.name}\" collision`,\n\t\t\t\t\tpath: skill.filePath,\n\t\t\t\t\tcollision: {\n\t\t\t\t\t\tresourceType: \"skill\",\n\t\t\t\t\t\tname: skill.name,\n\t\t\t\t\t\twinnerPath: existing.filePath,\n\t\t\t\t\t\tloserPath: skill.filePath,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tskillMap.set(skill.name, skill);\n\t\t\t\trealPathSet.add(realPath);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (includeDefaults) {\n\t\taddSkills(loadSkillsFromDirInternal(join(resolvedAgentDir, \"skills\"), \"user\", true));\n\t\taddSkills(loadSkillsFromDirInternal(resolve(resolvedCwd, CONFIG_DIR_NAME, \"skills\"), \"project\", true));\n\t}\n\n\tconst userSkillsDir = join(resolvedAgentDir, \"skills\");\n\tconst projectSkillsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"skills\");\n\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) {\n\t\t\treturn true;\n\t\t}\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\n\tconst getSource = (resolvedPath: string): \"user\" | \"project\" | \"path\" => {\n\t\tif (!includeDefaults) {\n\t\t\tif (isUnderPath(resolvedPath, userSkillsDir)) return \"user\";\n\t\t\tif (isUnderPath(resolvedPath, projectSkillsDir)) return \"project\";\n\t\t}\n\t\treturn \"path\";\n\t};\n\n\tfor (const rawPath of skillPaths) {\n\t\tconst resolvedPath = resolvePath(rawPath, resolvedCwd, { trim: true });\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tallDiagnostics.push({ type: \"warning\", message: \"skill path does not exist\", path: resolvedPath });\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst stats = statSync(resolvedPath);\n\t\t\tconst source = getSource(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\taddSkills(loadSkillsFromDirInternal(resolvedPath, source, true));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst result = loadSkillFromFile(resolvedPath, source);\n\t\t\t\tif (result.skill) {\n\t\t\t\t\taddSkills({ skills: [result.skill], diagnostics: result.diagnostics });\n\t\t\t\t} else {\n\t\t\t\t\tallDiagnostics.push(...result.diagnostics);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tallDiagnostics.push({ type: \"warning\", message: \"skill path is not a markdown file\", path: resolvedPath });\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to read skill path\";\n\t\t\tallDiagnostics.push({ type: \"warning\", message, path: resolvedPath });\n\t\t}\n\t}\n\n\treturn {\n\t\tskills: Array.from(skillMap.values()),\n\t\tdiagnostics: [...allDiagnostics, ...collisionDiagnostics],\n\t};\n}\n\nasync function backendPathExists(operations: ToolOperations, path: string): Promise<boolean> {\n\ttry {\n\t\tawait operations.access(path, \"exists\");\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nasync function addIgnoreRulesWithOperations(\n\toperations: ToolOperations,\n\tig: IgnoreMatcher,\n\tdir: string,\n\trootDir: string,\n): Promise<void> {\n\tconst relativeDir = relative(rootDir, dir);\n\tconst prefix = relativeDir ? `${toPosixPath(relativeDir)}/` : \"\";\n\n\tfor (const filename of IGNORE_FILE_NAMES) {\n\t\tconst ignorePath = join(dir, filename);\n\t\tif (!(await backendPathExists(operations, ignorePath))) continue;\n\t\ttry {\n\t\t\tconst content = (await operations.readFile(ignorePath)).toString(\"utf-8\");\n\t\t\tconst patterns = content\n\t\t\t\t.split(/\\r?\\n/)\n\t\t\t\t.map((line) => prefixIgnorePattern(line, prefix))\n\t\t\t\t.filter((line): line is string => Boolean(line));\n\t\t\tif (patterns.length > 0) {\n\t\t\t\tig.add(patterns);\n\t\t\t}\n\t\t} catch {}\n\t}\n}\n\nasync function loadSkillFromFileWithOperations(\n\toperations: ToolOperations,\n\tfilePath: string,\n\tsource: string,\n): Promise<{ skill: Skill | null; diagnostics: ResourceDiagnostic[] }> {\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\ttry {\n\t\tconst rawContent = (await operations.readFile(filePath)).toString(\"utf-8\");\n\t\tconst { frontmatter } = parseFrontmatter<SkillFrontmatter>(rawContent);\n\t\tconst skillDir = dirname(filePath);\n\t\tconst parentDirName = basename(skillDir);\n\n\t\tfor (const error of validateDescription(frontmatter.description)) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\tconst name = frontmatter.name || parentDirName;\n\t\tfor (const error of validateName(name)) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\tif (!frontmatter.description || frontmatter.description.trim() === \"\") {\n\t\t\treturn { skill: null, diagnostics };\n\t\t}\n\n\t\treturn {\n\t\t\tskill: {\n\t\t\t\tname,\n\t\t\t\tdescription: frontmatter.description,\n\t\t\t\tfilePath,\n\t\t\t\tbaseDir: skillDir,\n\t\t\t\tsourceInfo: createSkillSourceInfo(filePath, skillDir, source),\n\t\t\t\tdisableModelInvocation: frontmatter[\"disable-model-invocation\"] === true,\n\t\t\t},\n\t\t\tdiagnostics,\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : \"failed to parse skill file\";\n\t\tdiagnostics.push({ type: \"warning\", message, path: filePath });\n\t\treturn { skill: null, diagnostics };\n\t}\n}\n\nasync function loadSkillsFromDirInternalWithOperations(\n\toperations: ToolOperations,\n\tdir: string,\n\tsource: string,\n\tincludeRootFiles: boolean,\n\tignoreMatcher?: IgnoreMatcher,\n\trootDir?: string,\n): Promise<LoadSkillsResult> {\n\tconst skills: Skill[] = [];\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\tif (!(await backendPathExists(operations, dir))) {\n\t\treturn { skills, diagnostics };\n\t}\n\n\tconst root = rootDir ?? dir;\n\tconst ig = ignoreMatcher ?? ignore();\n\tawait addIgnoreRulesWithOperations(operations, ig, dir, root);\n\n\ttry {\n\t\tconst entries = await operations.readdir(dir);\n\n\t\tfor (const name of entries) {\n\t\t\tif (name !== \"SKILL.md\") continue;\n\t\t\tconst fullPath = join(dir, name);\n\t\t\tlet isFile = false;\n\t\t\ttry {\n\t\t\t\tisFile = (await operations.stat(fullPath)).isFile();\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tif (!isFile || ig.ignores(relPath)) continue;\n\t\t\tconst result = await loadSkillFromFileWithOperations(operations, fullPath, source);\n\t\t\tif (result.skill) skills.push(result.skill);\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t\treturn { skills, diagnostics };\n\t\t}\n\n\t\tfor (const name of entries) {\n\t\t\tif (name.startsWith(\".\") || name === \"node_modules\") continue;\n\t\t\tconst fullPath = join(dir, name);\n\t\t\tlet isDirectory = false;\n\t\t\tlet isFile = false;\n\t\t\ttry {\n\t\t\t\tconst stats = await operations.stat(fullPath);\n\t\t\t\tisDirectory = stats.isDirectory();\n\t\t\t\tisFile = stats.isFile();\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tconst ignorePath = isDirectory ? `${relPath}/` : relPath;\n\t\t\tif (ig.ignores(ignorePath)) continue;\n\t\t\tif (isDirectory) {\n\t\t\t\tconst subResult = await loadSkillsFromDirInternalWithOperations(\n\t\t\t\t\toperations,\n\t\t\t\t\tfullPath,\n\t\t\t\t\tsource,\n\t\t\t\t\tfalse,\n\t\t\t\t\tig,\n\t\t\t\t\troot,\n\t\t\t\t);\n\t\t\t\tskills.push(...subResult.skills);\n\t\t\t\tdiagnostics.push(...subResult.diagnostics);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!isFile || !includeRootFiles || !name.endsWith(\".md\")) continue;\n\t\t\tconst result = await loadSkillFromFileWithOperations(operations, fullPath, source);\n\t\t\tif (result.skill) skills.push(result.skill);\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t}\n\t} catch {}\n\n\treturn { skills, diagnostics };\n}\n\nexport interface LoadSkillsWithOperationsOptions extends LoadSkillsOptions {\n\toperations: ToolOperations;\n}\n\nexport async function loadSkillsWithOperations(options: LoadSkillsWithOperationsOptions): Promise<LoadSkillsResult> {\n\tconst { agentDir, skillPaths, includeDefaults, operations } = options;\n\tconst resolvedCwd = resolvePath(options.cwd);\n\tconst resolvedAgentDir = resolvePath(agentDir ?? getAgentDir());\n\tconst skillMap = new Map<string, Skill>();\n\tconst pathSet = new Set<string>();\n\tconst allDiagnostics: ResourceDiagnostic[] = [];\n\tconst collisionDiagnostics: ResourceDiagnostic[] = [];\n\n\tfunction addSkills(result: LoadSkillsResult) {\n\t\tallDiagnostics.push(...result.diagnostics);\n\t\tfor (const skill of result.skills) {\n\t\t\tconst canonicalPath = resolve(skill.filePath);\n\t\t\tif (pathSet.has(canonicalPath)) continue;\n\t\t\tconst existing = skillMap.get(skill.name);\n\t\t\tif (existing) {\n\t\t\t\tcollisionDiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"${skill.name}\" collision`,\n\t\t\t\t\tpath: skill.filePath,\n\t\t\t\t\tcollision: {\n\t\t\t\t\t\tresourceType: \"skill\",\n\t\t\t\t\t\tname: skill.name,\n\t\t\t\t\t\twinnerPath: existing.filePath,\n\t\t\t\t\t\tloserPath: skill.filePath,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tskillMap.set(skill.name, skill);\n\t\t\t\tpathSet.add(canonicalPath);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (includeDefaults) {\n\t\taddSkills(\n\t\t\tawait loadSkillsFromDirInternalWithOperations(operations, join(resolvedAgentDir, \"skills\"), \"user\", true),\n\t\t);\n\t\taddSkills(\n\t\t\tawait loadSkillsFromDirInternalWithOperations(\n\t\t\t\toperations,\n\t\t\t\tresolve(resolvedCwd, CONFIG_DIR_NAME, \"skills\"),\n\t\t\t\t\"project\",\n\t\t\t\ttrue,\n\t\t\t),\n\t\t);\n\t}\n\n\tconst userSkillsDir = join(resolvedAgentDir, \"skills\");\n\tconst projectSkillsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"skills\");\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) return true;\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\tconst getSource = (resolvedPath: string): \"user\" | \"project\" | \"path\" => {\n\t\tif (!includeDefaults) {\n\t\t\tif (isUnderPath(resolvedPath, userSkillsDir)) return \"user\";\n\t\t\tif (isUnderPath(resolvedPath, projectSkillsDir)) return \"project\";\n\t\t}\n\t\treturn \"path\";\n\t};\n\n\tfor (const rawPath of skillPaths) {\n\t\tconst resolvedPath = resolvePath(rawPath, resolvedCwd, { trim: true });\n\t\tif (!(await backendPathExists(operations, resolvedPath))) {\n\t\t\tallDiagnostics.push({ type: \"warning\", message: \"skill path does not exist\", path: resolvedPath });\n\t\t\tcontinue;\n\t\t}\n\t\ttry {\n\t\t\tconst stats = await operations.stat(resolvedPath);\n\t\t\tconst source = getSource(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\taddSkills(await loadSkillsFromDirInternalWithOperations(operations, resolvedPath, source, true));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst result = await loadSkillFromFileWithOperations(operations, resolvedPath, source);\n\t\t\t\tif (result.skill) addSkills({ skills: [result.skill], diagnostics: result.diagnostics });\n\t\t\t\telse allDiagnostics.push(...result.diagnostics);\n\t\t\t} else {\n\t\t\t\tallDiagnostics.push({ type: \"warning\", message: \"skill path is not a markdown file\", path: resolvedPath });\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to read skill path\";\n\t\t\tallDiagnostics.push({ type: \"warning\", message, path: resolvedPath });\n\t\t}\n\t}\n\n\treturn { skills: Array.from(skillMap.values()), diagnostics: [...allDiagnostics, ...collisionDiagnostics] };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/core/skills.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAA6B,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AA2D5D,MAAM,WAAW,gBAAgB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,KAAK;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;IACvB,sBAAsB,EAAE,OAAO,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,WAAW,EAAE,kBAAkB,EAAE,CAAC;CAClC;AA2CD,MAAM,WAAW,wBAAwB;IACxC,mCAAmC;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;CACf;AA0BD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,gBAAgB,CAGrF;AA6JD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CA0B7D;AAWD,MAAM,WAAW,iBAAiB;IACjC,kDAAkD;IAClD,GAAG,EAAE,MAAM,CAAC;IACZ,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,0CAA0C;IAC1C,eAAe,EAAE,OAAO,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAoGvE;AAkKD,MAAM,WAAW,+BAAgC,SAAQ,iBAAiB;IACzE,UAAU,EAAE,cAAc,CAAC;CAC3B;AAED,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,+BAA+B,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAyFlH","sourcesContent":["import { existsSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport ignore from \"ignore\";\nimport { dirname, join, relative, resolve, sep } from \"path\";\nimport { CONFIG_DIR_NAME, getAgentDir } from \"../config.ts\";\nimport { parseFrontmatter } from \"../utils/frontmatter.ts\";\nimport { canonicalizePath, resolvePath } from \"../utils/paths.ts\";\nimport type { ResourceDiagnostic } from \"./diagnostics.ts\";\nimport { createSyntheticSourceInfo, type SourceInfo } from \"./source-info.ts\";\nimport type { ToolOperations } from \"./tools/operations.ts\";\n\n/** Max name length per spec */\nconst MAX_NAME_LENGTH = 64;\n\n/** Max description length per spec */\nconst MAX_DESCRIPTION_LENGTH = 1024;\n\nconst IGNORE_FILE_NAMES = [\".gitignore\", \".ignore\", \".fdignore\"];\n\ntype IgnoreMatcher = ReturnType<typeof ignore>;\n\nfunction toPosixPath(p: string): string {\n\treturn p.split(sep).join(\"/\");\n}\n\nfunction prefixIgnorePattern(line: string, prefix: string): string | null {\n\tconst trimmed = line.trim();\n\tif (!trimmed) return null;\n\tif (trimmed.startsWith(\"#\") && !trimmed.startsWith(\"\\\\#\")) return null;\n\n\tlet pattern = line;\n\tlet negated = false;\n\n\tif (pattern.startsWith(\"!\")) {\n\t\tnegated = true;\n\t\tpattern = pattern.slice(1);\n\t} else if (pattern.startsWith(\"\\\\!\")) {\n\t\tpattern = pattern.slice(1);\n\t}\n\n\tif (pattern.startsWith(\"/\")) {\n\t\tpattern = pattern.slice(1);\n\t}\n\n\tconst prefixed = prefix ? `${prefix}${pattern}` : pattern;\n\treturn negated ? `!${prefixed}` : prefixed;\n}\n\nfunction addIgnoreRules(ig: IgnoreMatcher, dir: string, rootDir: string): void {\n\tconst relativeDir = relative(rootDir, dir);\n\tconst prefix = relativeDir ? `${toPosixPath(relativeDir)}/` : \"\";\n\n\tfor (const filename of IGNORE_FILE_NAMES) {\n\t\tconst ignorePath = join(dir, filename);\n\t\tif (!existsSync(ignorePath)) continue;\n\t\ttry {\n\t\t\tconst content = readFileSync(ignorePath, \"utf-8\");\n\t\t\tconst patterns = content\n\t\t\t\t.split(/\\r?\\n/)\n\t\t\t\t.map((line) => prefixIgnorePattern(line, prefix))\n\t\t\t\t.filter((line): line is string => Boolean(line));\n\t\t\tif (patterns.length > 0) {\n\t\t\t\tig.add(patterns);\n\t\t\t}\n\t\t} catch {}\n\t}\n}\n\nexport interface SkillFrontmatter {\n\tname?: string;\n\tdescription?: string;\n\t\"disable-model-invocation\"?: boolean;\n\t[key: string]: unknown;\n}\n\nexport interface Skill {\n\tname: string;\n\tdescription: string;\n\tfilePath: string;\n\tbaseDir: string;\n\tsourceInfo: SourceInfo;\n\tdisableModelInvocation: boolean;\n\tcontent?: string;\n}\n\nexport interface LoadSkillsResult {\n\tskills: Skill[];\n\tdiagnostics: ResourceDiagnostic[];\n}\n\n/**\n * Validate skill name per Agent Skills spec.\n * Returns array of validation error messages (empty if valid).\n */\nfunction validateName(name: string): string[] {\n\tconst errors: string[] = [];\n\n\tif (name.length > MAX_NAME_LENGTH) {\n\t\terrors.push(`name exceeds ${MAX_NAME_LENGTH} characters (${name.length})`);\n\t}\n\n\tif (!/^[a-z0-9-]+$/.test(name)) {\n\t\terrors.push(`name contains invalid characters (must be lowercase a-z, 0-9, hyphens only)`);\n\t}\n\n\tif (name.startsWith(\"-\") || name.endsWith(\"-\")) {\n\t\terrors.push(`name must not start or end with a hyphen`);\n\t}\n\n\tif (name.includes(\"--\")) {\n\t\terrors.push(`name must not contain consecutive hyphens`);\n\t}\n\n\treturn errors;\n}\n\n/**\n * Validate description per Agent Skills spec.\n */\nfunction validateDescription(description: string | undefined): string[] {\n\tconst errors: string[] = [];\n\n\tif (!description || description.trim() === \"\") {\n\t\terrors.push(\"description is required\");\n\t} else if (description.length > MAX_DESCRIPTION_LENGTH) {\n\t\terrors.push(`description exceeds ${MAX_DESCRIPTION_LENGTH} characters (${description.length})`);\n\t}\n\n\treturn errors;\n}\n\nexport interface LoadSkillsFromDirOptions {\n\t/** Directory to scan for skills */\n\tdir: string;\n\t/** Source identifier for these skills */\n\tsource: string;\n}\n\nfunction createSkillSourceInfo(filePath: string, baseDir: string, source: string): SourceInfo {\n\tswitch (source) {\n\t\tcase \"user\":\n\t\t\treturn createSyntheticSourceInfo(filePath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"user\",\n\t\t\t\tbaseDir,\n\t\t\t});\n\t\tcase \"project\":\n\t\t\treturn createSyntheticSourceInfo(filePath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"project\",\n\t\t\t\tbaseDir,\n\t\t\t});\n\t\tcase \"path\":\n\t\t\treturn createSyntheticSourceInfo(filePath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tbaseDir,\n\t\t\t});\n\t\tdefault:\n\t\t\treturn createSyntheticSourceInfo(filePath, { source, baseDir });\n\t}\n}\n\n/**\n * Load skills from a directory.\n *\n * Discovery rules:\n * - if a directory contains SKILL.md, treat it as a skill root and do not recurse further\n * - otherwise, load direct .md children in the root\n * - recurse into subdirectories to find SKILL.md\n */\nexport function loadSkillsFromDir(options: LoadSkillsFromDirOptions): LoadSkillsResult {\n\tconst { dir, source } = options;\n\treturn loadSkillsFromDirInternal(dir, source, true);\n}\n\nfunction loadSkillsFromDirInternal(\n\tdir: string,\n\tsource: string,\n\tincludeRootFiles: boolean,\n\tignoreMatcher?: IgnoreMatcher,\n\trootDir?: string,\n): LoadSkillsResult {\n\tconst skills: Skill[] = [];\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn { skills, diagnostics };\n\t}\n\n\tconst root = rootDir ?? dir;\n\tconst ig = ignoreMatcher ?? ignore();\n\taddIgnoreRules(ig, dir, root);\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name !== \"SKILL.md\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tisFile = statSync(fullPath).isFile();\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tif (!isFile || ig.ignores(relPath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst result = loadSkillFromFile(fullPath, source);\n\t\t\tif (result.skill) {\n\t\t\t\tskills.push(result.skill);\n\t\t\t}\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t\treturn { skills, diagnostics };\n\t\t}\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name.startsWith(\".\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Skip node_modules to avoid scanning dependencies\n\t\t\tif (entry.name === \"node_modules\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\t// For symlinks, check if they point to a directory and follow them\n\t\t\tlet isDirectory = entry.isDirectory();\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(fullPath);\n\t\t\t\t\tisDirectory = stats.isDirectory();\n\t\t\t\t\tisFile = stats.isFile();\n\t\t\t\t} catch {\n\t\t\t\t\t// Broken symlink, skip it\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tconst ignorePath = isDirectory ? `${relPath}/` : relPath;\n\t\t\tif (ig.ignores(ignorePath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (isDirectory) {\n\t\t\t\tconst subResult = loadSkillsFromDirInternal(fullPath, source, false, ig, root);\n\t\t\t\tskills.push(...subResult.skills);\n\t\t\t\tdiagnostics.push(...subResult.diagnostics);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!isFile || !includeRootFiles || !entry.name.endsWith(\".md\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst result = loadSkillFromFile(fullPath, source);\n\t\t\tif (result.skill) {\n\t\t\t\tskills.push(result.skill);\n\t\t\t}\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t}\n\t} catch {}\n\n\treturn { skills, diagnostics };\n}\n\nfunction loadSkillFromFile(\n\tfilePath: string,\n\tsource: string,\n): { skill: Skill | null; diagnostics: ResourceDiagnostic[] } {\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\ttry {\n\t\tconst rawContent = readFileSync(filePath, \"utf-8\");\n\t\tconst { frontmatter } = parseFrontmatter<SkillFrontmatter>(rawContent);\n\t\tconst skillDir = dirname(filePath);\n\n\t\t// Validate description\n\t\tconst descErrors = validateDescription(frontmatter.description);\n\t\tfor (const error of descErrors) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\tif (!frontmatter.name || frontmatter.name.trim() === \"\") {\n\t\t\tdiagnostics.push({ type: \"warning\", message: \"name is required\", path: filePath });\n\t\t}\n\n\t\tconst name = frontmatter.name;\n\t\tif (name) {\n\t\t\tfor (const error of validateName(name)) {\n\t\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t\t}\n\t\t}\n\n\t\t// Still load the skill even with warnings (unless required fields are missing)\n\t\tif (!name || !frontmatter.description || frontmatter.description.trim() === \"\") {\n\t\t\treturn { skill: null, diagnostics };\n\t\t}\n\n\t\treturn {\n\t\t\tskill: {\n\t\t\t\tname,\n\t\t\t\tdescription: frontmatter.description,\n\t\t\t\tfilePath,\n\t\t\t\tbaseDir: skillDir,\n\t\t\t\tsourceInfo: createSkillSourceInfo(filePath, skillDir, source),\n\t\t\t\tdisableModelInvocation: frontmatter[\"disable-model-invocation\"] === true,\n\t\t\t},\n\t\t\tdiagnostics,\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : \"failed to parse skill file\";\n\t\tdiagnostics.push({ type: \"warning\", message, path: filePath });\n\t\treturn { skill: null, diagnostics };\n\t}\n}\n\n/**\n * Format skills for inclusion in a system prompt.\n * Uses XML format per Agent Skills standard.\n * See: https://agentskills.io/integrate-skills\n *\n * Skills with disableModelInvocation=true are excluded from the prompt\n * (they can only be invoked explicitly via /skill:name commands).\n */\nexport function formatSkillsForPrompt(skills: Skill[]): string {\n\tconst visibleSkills = skills.filter((s) => !s.disableModelInvocation);\n\n\tif (visibleSkills.length === 0) {\n\t\treturn \"\";\n\t}\n\n\tconst lines = [\n\t\t\"\\n\\nThe following skills provide specialized instructions for specific tasks.\",\n\t\t\"Use the read tool to load a skill's file when the task matches its description.\",\n\t\t\"When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\",\n\t\t\"\",\n\t\t\"<available_skills>\",\n\t];\n\n\tfor (const skill of visibleSkills) {\n\t\tlines.push(\" <skill>\");\n\t\tlines.push(` <name>${escapeXml(skill.name)}</name>`);\n\t\tlines.push(` <description>${escapeXml(skill.description)}</description>`);\n\t\tlines.push(` <location>${escapeXml(skill.filePath)}</location>`);\n\t\tlines.push(\" </skill>\");\n\t}\n\n\tlines.push(\"</available_skills>\");\n\n\treturn lines.join(\"\\n\");\n}\n\nfunction escapeXml(str: string): string {\n\treturn str\n\t\t.replace(/&/g, \"&\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\");\n}\n\nexport interface LoadSkillsOptions {\n\t/** Working directory for project-local skills. */\n\tcwd: string;\n\t/** Agent config directory for global skills. */\n\tagentDir: string;\n\t/** Explicit skill paths (files or directories) */\n\tskillPaths: string[];\n\t/** Include default skills directories. */\n\tincludeDefaults: boolean;\n}\n\n/**\n * Load skills from all configured locations.\n * Returns skills and any validation diagnostics.\n */\nexport function loadSkills(options: LoadSkillsOptions): LoadSkillsResult {\n\tconst { agentDir, skillPaths, includeDefaults } = options;\n\n\t// Resolve agentDir - if not provided, use default from config\n\tconst resolvedCwd = resolvePath(options.cwd);\n\tconst resolvedAgentDir = resolvePath(agentDir ?? getAgentDir());\n\n\tconst skillMap = new Map<string, Skill>();\n\tconst realPathSet = new Set<string>();\n\tconst allDiagnostics: ResourceDiagnostic[] = [];\n\tconst collisionDiagnostics: ResourceDiagnostic[] = [];\n\n\tfunction addSkills(result: LoadSkillsResult) {\n\t\tallDiagnostics.push(...result.diagnostics);\n\t\tfor (const skill of result.skills) {\n\t\t\t// Resolve symlinks to detect duplicate files\n\t\t\tconst realPath = canonicalizePath(skill.filePath);\n\n\t\t\t// Skip silently if we've already loaded this exact file (via symlink)\n\t\t\tif (realPathSet.has(realPath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst existing = skillMap.get(skill.name);\n\t\t\tif (existing) {\n\t\t\t\tcollisionDiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"${skill.name}\" collision`,\n\t\t\t\t\tpath: skill.filePath,\n\t\t\t\t\tcollision: {\n\t\t\t\t\t\tresourceType: \"skill\",\n\t\t\t\t\t\tname: skill.name,\n\t\t\t\t\t\twinnerPath: existing.filePath,\n\t\t\t\t\t\tloserPath: skill.filePath,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tskillMap.set(skill.name, skill);\n\t\t\t\trealPathSet.add(realPath);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (includeDefaults) {\n\t\taddSkills(loadSkillsFromDirInternal(join(resolvedAgentDir, \"skills\"), \"user\", true));\n\t\taddSkills(loadSkillsFromDirInternal(resolve(resolvedCwd, CONFIG_DIR_NAME, \"skills\"), \"project\", true));\n\t}\n\n\tconst userSkillsDir = join(resolvedAgentDir, \"skills\");\n\tconst projectSkillsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"skills\");\n\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) {\n\t\t\treturn true;\n\t\t}\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\n\tconst getSource = (resolvedPath: string): \"user\" | \"project\" | \"path\" => {\n\t\tif (!includeDefaults) {\n\t\t\tif (isUnderPath(resolvedPath, userSkillsDir)) return \"user\";\n\t\t\tif (isUnderPath(resolvedPath, projectSkillsDir)) return \"project\";\n\t\t}\n\t\treturn \"path\";\n\t};\n\n\tfor (const rawPath of skillPaths) {\n\t\tconst resolvedPath = resolvePath(rawPath, resolvedCwd, { trim: true });\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tallDiagnostics.push({ type: \"warning\", message: \"skill path does not exist\", path: resolvedPath });\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst stats = statSync(resolvedPath);\n\t\t\tconst source = getSource(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\taddSkills(loadSkillsFromDirInternal(resolvedPath, source, true));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst result = loadSkillFromFile(resolvedPath, source);\n\t\t\t\tif (result.skill) {\n\t\t\t\t\taddSkills({ skills: [result.skill], diagnostics: result.diagnostics });\n\t\t\t\t} else {\n\t\t\t\t\tallDiagnostics.push(...result.diagnostics);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tallDiagnostics.push({ type: \"warning\", message: \"skill path is not a markdown file\", path: resolvedPath });\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to read skill path\";\n\t\t\tallDiagnostics.push({ type: \"warning\", message, path: resolvedPath });\n\t\t}\n\t}\n\n\treturn {\n\t\tskills: Array.from(skillMap.values()),\n\t\tdiagnostics: [...allDiagnostics, ...collisionDiagnostics],\n\t};\n}\n\nasync function backendPathExists(operations: ToolOperations, path: string): Promise<boolean> {\n\ttry {\n\t\tawait operations.access(path, \"exists\");\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nasync function addIgnoreRulesWithOperations(\n\toperations: ToolOperations,\n\tig: IgnoreMatcher,\n\tdir: string,\n\trootDir: string,\n): Promise<void> {\n\tconst relativeDir = relative(rootDir, dir);\n\tconst prefix = relativeDir ? `${toPosixPath(relativeDir)}/` : \"\";\n\n\tfor (const filename of IGNORE_FILE_NAMES) {\n\t\tconst ignorePath = join(dir, filename);\n\t\tif (!(await backendPathExists(operations, ignorePath))) continue;\n\t\ttry {\n\t\t\tconst content = (await operations.readFile(ignorePath)).toString(\"utf-8\");\n\t\t\tconst patterns = content\n\t\t\t\t.split(/\\r?\\n/)\n\t\t\t\t.map((line) => prefixIgnorePattern(line, prefix))\n\t\t\t\t.filter((line): line is string => Boolean(line));\n\t\t\tif (patterns.length > 0) {\n\t\t\t\tig.add(patterns);\n\t\t\t}\n\t\t} catch {}\n\t}\n}\n\nasync function loadSkillFromFileWithOperations(\n\toperations: ToolOperations,\n\tfilePath: string,\n\tsource: string,\n): Promise<{ skill: Skill | null; diagnostics: ResourceDiagnostic[] }> {\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\ttry {\n\t\tconst rawContent = (await operations.readFile(filePath)).toString(\"utf-8\");\n\t\tconst { frontmatter } = parseFrontmatter<SkillFrontmatter>(rawContent);\n\t\tconst skillDir = dirname(filePath);\n\n\t\tfor (const error of validateDescription(frontmatter.description)) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\tif (!frontmatter.name || frontmatter.name.trim() === \"\") {\n\t\t\tdiagnostics.push({ type: \"warning\", message: \"name is required\", path: filePath });\n\t\t}\n\n\t\tconst name = frontmatter.name;\n\t\tif (name) {\n\t\t\tfor (const error of validateName(name)) {\n\t\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t\t}\n\t\t}\n\n\t\tif (!name || !frontmatter.description || frontmatter.description.trim() === \"\") {\n\t\t\treturn { skill: null, diagnostics };\n\t\t}\n\n\t\treturn {\n\t\t\tskill: {\n\t\t\t\tname,\n\t\t\t\tdescription: frontmatter.description,\n\t\t\t\tfilePath,\n\t\t\t\tbaseDir: skillDir,\n\t\t\t\tsourceInfo: createSkillSourceInfo(filePath, skillDir, source),\n\t\t\t\tdisableModelInvocation: frontmatter[\"disable-model-invocation\"] === true,\n\t\t\t},\n\t\t\tdiagnostics,\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : \"failed to parse skill file\";\n\t\tdiagnostics.push({ type: \"warning\", message, path: filePath });\n\t\treturn { skill: null, diagnostics };\n\t}\n}\n\nasync function loadSkillsFromDirInternalWithOperations(\n\toperations: ToolOperations,\n\tdir: string,\n\tsource: string,\n\tincludeRootFiles: boolean,\n\tignoreMatcher?: IgnoreMatcher,\n\trootDir?: string,\n): Promise<LoadSkillsResult> {\n\tconst skills: Skill[] = [];\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\tif (!(await backendPathExists(operations, dir))) {\n\t\treturn { skills, diagnostics };\n\t}\n\n\tconst root = rootDir ?? dir;\n\tconst ig = ignoreMatcher ?? ignore();\n\tawait addIgnoreRulesWithOperations(operations, ig, dir, root);\n\n\ttry {\n\t\tconst entries = await operations.readdir(dir);\n\n\t\tfor (const name of entries) {\n\t\t\tif (name !== \"SKILL.md\") continue;\n\t\t\tconst fullPath = join(dir, name);\n\t\t\tlet isFile = false;\n\t\t\ttry {\n\t\t\t\tisFile = (await operations.stat(fullPath)).isFile();\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tif (!isFile || ig.ignores(relPath)) continue;\n\t\t\tconst result = await loadSkillFromFileWithOperations(operations, fullPath, source);\n\t\t\tif (result.skill) skills.push(result.skill);\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t\treturn { skills, diagnostics };\n\t\t}\n\n\t\tfor (const name of entries) {\n\t\t\tif (name.startsWith(\".\") || name === \"node_modules\") continue;\n\t\t\tconst fullPath = join(dir, name);\n\t\t\tlet isDirectory = false;\n\t\t\tlet isFile = false;\n\t\t\ttry {\n\t\t\t\tconst stats = await operations.stat(fullPath);\n\t\t\t\tisDirectory = stats.isDirectory();\n\t\t\t\tisFile = stats.isFile();\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tconst ignorePath = isDirectory ? `${relPath}/` : relPath;\n\t\t\tif (ig.ignores(ignorePath)) continue;\n\t\t\tif (isDirectory) {\n\t\t\t\tconst subResult = await loadSkillsFromDirInternalWithOperations(\n\t\t\t\t\toperations,\n\t\t\t\t\tfullPath,\n\t\t\t\t\tsource,\n\t\t\t\t\tfalse,\n\t\t\t\t\tig,\n\t\t\t\t\troot,\n\t\t\t\t);\n\t\t\t\tskills.push(...subResult.skills);\n\t\t\t\tdiagnostics.push(...subResult.diagnostics);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!isFile || !includeRootFiles || !name.endsWith(\".md\")) continue;\n\t\t\tconst result = await loadSkillFromFileWithOperations(operations, fullPath, source);\n\t\t\tif (result.skill) skills.push(result.skill);\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t}\n\t} catch {}\n\n\treturn { skills, diagnostics };\n}\n\nexport interface LoadSkillsWithOperationsOptions extends LoadSkillsOptions {\n\toperations: ToolOperations;\n}\n\nexport async function loadSkillsWithOperations(options: LoadSkillsWithOperationsOptions): Promise<LoadSkillsResult> {\n\tconst { agentDir, skillPaths, includeDefaults, operations } = options;\n\tconst resolvedCwd = resolvePath(options.cwd);\n\tconst resolvedAgentDir = resolvePath(agentDir ?? getAgentDir());\n\tconst skillMap = new Map<string, Skill>();\n\tconst pathSet = new Set<string>();\n\tconst allDiagnostics: ResourceDiagnostic[] = [];\n\tconst collisionDiagnostics: ResourceDiagnostic[] = [];\n\n\tfunction addSkills(result: LoadSkillsResult) {\n\t\tallDiagnostics.push(...result.diagnostics);\n\t\tfor (const skill of result.skills) {\n\t\t\tconst canonicalPath = resolve(skill.filePath);\n\t\t\tif (pathSet.has(canonicalPath)) continue;\n\t\t\tconst existing = skillMap.get(skill.name);\n\t\t\tif (existing) {\n\t\t\t\tcollisionDiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"${skill.name}\" collision`,\n\t\t\t\t\tpath: skill.filePath,\n\t\t\t\t\tcollision: {\n\t\t\t\t\t\tresourceType: \"skill\",\n\t\t\t\t\t\tname: skill.name,\n\t\t\t\t\t\twinnerPath: existing.filePath,\n\t\t\t\t\t\tloserPath: skill.filePath,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tskillMap.set(skill.name, skill);\n\t\t\t\tpathSet.add(canonicalPath);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (includeDefaults) {\n\t\taddSkills(\n\t\t\tawait loadSkillsFromDirInternalWithOperations(operations, join(resolvedAgentDir, \"skills\"), \"user\", true),\n\t\t);\n\t\taddSkills(\n\t\t\tawait loadSkillsFromDirInternalWithOperations(\n\t\t\t\toperations,\n\t\t\t\tresolve(resolvedCwd, CONFIG_DIR_NAME, \"skills\"),\n\t\t\t\t\"project\",\n\t\t\t\ttrue,\n\t\t\t),\n\t\t);\n\t}\n\n\tconst userSkillsDir = join(resolvedAgentDir, \"skills\");\n\tconst projectSkillsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"skills\");\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) return true;\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\tconst getSource = (resolvedPath: string): \"user\" | \"project\" | \"path\" => {\n\t\tif (!includeDefaults) {\n\t\t\tif (isUnderPath(resolvedPath, userSkillsDir)) return \"user\";\n\t\t\tif (isUnderPath(resolvedPath, projectSkillsDir)) return \"project\";\n\t\t}\n\t\treturn \"path\";\n\t};\n\n\tfor (const rawPath of skillPaths) {\n\t\tconst resolvedPath = resolvePath(rawPath, resolvedCwd, { trim: true });\n\t\tif (!(await backendPathExists(operations, resolvedPath))) {\n\t\t\tallDiagnostics.push({ type: \"warning\", message: \"skill path does not exist\", path: resolvedPath });\n\t\t\tcontinue;\n\t\t}\n\t\ttry {\n\t\t\tconst stats = await operations.stat(resolvedPath);\n\t\t\tconst source = getSource(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\taddSkills(await loadSkillsFromDirInternalWithOperations(operations, resolvedPath, source, true));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst result = await loadSkillFromFileWithOperations(operations, resolvedPath, source);\n\t\t\t\tif (result.skill) addSkills({ skills: [result.skill], diagnostics: result.diagnostics });\n\t\t\t\telse allDiagnostics.push(...result.diagnostics);\n\t\t\t} else {\n\t\t\t\tallDiagnostics.push({ type: \"warning\", message: \"skill path is not a markdown file\", path: resolvedPath });\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to read skill path\";\n\t\t\tallDiagnostics.push({ type: \"warning\", message, path: resolvedPath });\n\t\t}\n\t}\n\n\treturn { skills: Array.from(skillMap.values()), diagnostics: [...allDiagnostics, ...collisionDiagnostics] };\n}\n"]}
|