[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6QPFMG233JB2JSJ4WWBACSN":3,"skill-same:01M6QPFMG233JB2JSJ4WWBACSN":73},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":22,"publishedAt":43,"linkOnly":44,"markdownContent":45,"markdownBody":46,"frontMatter":47,"refName":52,"commitSha":53,"skillDirectory":54,"forksCount":55,"repositoryHtmlUrl":56,"repositoryHomepageUrl":6,"ownerHtmlUrl":57,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":58,"install":59,"resources":72},"01M6QPFMG233JB2JSJ4WWBACSN","project-coding",null,"Project-specific coding guidelines, naming conventions, architecture patterns, and code examples for la-briguade","theCat69\u002Fla-briguade","theCat69","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F40163881?v=4","01M6QPEW8JP05VD4Q9E4TJDFB3","01M6QPEWCX5VB9ZKJ7E96BXEHQ",1,"MIT","https:\u002F\u002Fgithub.com\u002FtheCat69\u002Fla-briguade\u002Fblob\u002Fmain\u002F.la_briguade\u002Fauto-inject-skills\u002Fproject-coding\u002FSKILL.md",[17],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42],"project coding","project","coding","ask","for","naming","feature","patterns","builder","orchestrator","coding guidelines","architecture","reviewer","planner","designer","architect","architecture patterns","examples","code","guidelines","2026-11-10 10:12:30",false,"---\nname: project-coding\ndescription: Project-specific coding guidelines, naming conventions, architecture patterns, and code examples for la-briguade\nagents:\n  - coder\n  - architect\n  - feature-designer\n  - feature-reviewer\n  - planner\n  - ask\n  - builder\n  - orchestrator\n---\n# Project coding guidelines\n\n## Scope\n\n- **In scope**: production TypeScript coding standards, architecture patterns, dependency policy,\n  and repository-specific implementation constraints.\n- **Out of scope**: release logistics, test-only policy details, and non-code documentation process.\n\n## Invariants\n\n- Changes **MUST** compile under strict TypeScript configuration.\n- External or user-provided data **MUST** be validated at runtime boundaries.\n- Error handling **MUST** be explicit and actionable; silent failure paths are disallowed.\n- Patterns **MUST** preserve content override precedence and non-overwrite permission semantics.\n- Code **MUST NOT** introduce placeholder logic, dead\u002Fcommented-out production paths, or unsafe type assertions.\n- Build\u002Ftest validation **MUST** pass before work is considered complete.\n\n## Validation Checklist\n\n```bash\nnpm run build\nnpm test\n```\n\nFor touched config\u002Fschema boundaries, also validate:\n\n```bash\nnpm run generate-schema\n```\n\n## Failure Handling\n\n- If local context is insufficient, stop and request missing context rather than inventing APIs.\n- If validation fails, fix root cause first; do not bypass checks.\n- If constraints conflict, preserve stronger safety\u002Fcontract wording and document the resolved decision.\n\n## Code Style\n\n- **TypeScript strict mode** is enforced: `strict: true`, `noUncheckedIndexedAccess: true`, `exactOptionalPropertyTypes: true`, `isolatedModules: true`\n- **Target**: ES2024 — **Module**: NodeNext — always use `.js` extension in imports (ESM NodeNext requirement, even when the source file is `.ts`)\n- Max line length: 100 characters; 2-space indentation\n- Use `import type { ... }` for all type-only imports — reduces compiled output and clarifies intent\n- Use `as const` for literal object\u002Farray values; use `satisfies` for constrained inference without widening\n- `verbatimModuleSyntax` is not enabled in this project — use `import type` manually for type-only imports\n\n## Naming Conventions\n\n| Scope | Convention | Example |\n|---|---|---|\n| Files | `kebab-case.ts` | `read-dir.ts`, `frontmatter.ts` |\n| Functions \u002F variables | `camelCase` | `parseFrontmatter`, `contentDir` |\n| Types \u002F interfaces | `PascalCase` | `ParsedFrontmatter`, `ConfigFileResult` |\n| Module-level true constants | `SCREAMING_SNAKE_CASE` | `TRUNCATION_THRESHOLD`, `PLUGIN_NAME` |\n| Agent\u002Fcommand filenames | lowercase or PascalCase per existing pattern | `coder.md`, `Builder.md` |\n\n## Import Ordering\n\nBlank line between each group:\n\n```typescript\n\u002F\u002F 1. Node built-ins\nimport { readdirSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\n\n\u002F\u002F 2. External packages\nimport { Command } from \"commander\";\nimport { parse as parseYaml } from \"yaml\";\nimport { modify, applyEdits } from \"jsonc-parser\";\n\n\u002F\u002F 3. Internal project imports\nimport { readDirSafe } from \".\u002Futils\u002Fread-dir.js\";\nimport type { HooksResult } from \"..\u002Ftypes\u002Fplugin.js\";\n```\n\n## Error Handling\n\n- Use try-catch with `logger.warn(message)` (from `src\u002Futils\u002Fruntime\u002Flogger.ts`) for non-fatal failures (e.g. missing content dirs, YAML parse errors)\n- Never silently swallow errors — always log with context identifying the source\n- Use `Result\u003CT, E>` discriminated union pattern for recoverable errors in library\u002Futility code:\n  ```typescript\n  type Result\u003CT, E> = { ok: true; value: T } | { ok: false; error: E };\n  ```\n- Fail fast at CLI boundaries: set `process.exitCode = 1` and print actionable user-facing messages\n- Always check that `parsed` is a non-null, non-array object before casting to `Record\u003Cstring, unknown>`\n\n## Patterns & Architecture\n\n### Plugin Registration\nThe `Plugin` export (`src\u002Findex.ts`) is an async function that returns `{ config, ...hooks }`. The `config` callback receives the mutable `input` config object and calls `register*` helpers to merge into it. **Never mutate globals** — only mutate the `input` passed to the `config` callback.\n\n`resolveConfigBaseDirs(projectDir)` (from `src\u002Fconfig\u002Findex.ts`) returns `{ globalDir, projectDir }` — the two user content roots (`~\u002Fla_briguade\u002F` and the project root). All content loaders receive ordered arrays of dirs (`[builtin, globalUser, projectUser]`) and use `collectFiles()` \u002F `collectDirs()` from `src\u002Futils\u002Fcontent\u002Fcontent-merge.ts` for priority-based last-wins merging.\n\n```typescript\nconst LaBriguadePlugin: Plugin = async (ctx) => ({\n  config: async (input) => {\n    registerAgents(input, contentDir);\n    registerCommands(input, contentDir);\n    registerSkills(input, contentDir);\n  },\n  ...createHooks(ctx),\n});\n```\n\n### Content-Driven Registration\nAgents, skills, and commands are loaded from `.md` files resolved across ordered layers. All loaders call `collectFiles(dirs, '.md')` (for agents\u002Fcommands\u002Fvendors) or `collectDirs(roots)` (for skills) from `src\u002Futils\u002Fcontent\u002Fcontent-merge.ts`. Later directories in the array override earlier ones by filename stem — this is the user content override mechanism. Agent\u002Fcommand identity comes from the filename (`.md` stripped, first char lowercased for agents). Frontmatter YAML provides metadata.\n\n**User content override paths** (lowest → highest priority):\n\n| Content type | Global user | Project user |\n|---|---|---|\n| Agents | `~\u002Fla_briguade\u002Fagents\u002F` | `\u003Croot>\u002F.la_briguade\u002Fagents\u002F` |\n| Commands | `~\u002Fla_briguade\u002Fcommands\u002F` | `\u003Croot>\u002F.la_briguade\u002Fcommands\u002F` |\n| Skills | `~\u002F.config\u002Fopencode\u002Fskills\u002F` or `~\u002Fla_briguade\u002Fskills\u002F` | `\u003Croot>\u002F.opencode\u002Fskills\u002F` or `\u003Croot>\u002F.la_briguade\u002Fskills\u002F` |\n| Vendor prompts | `~\u002Fla_briguade\u002Fvendor-prompts\u002F` | `\u003Croot>\u002F.la_briguade\u002Fvendor-prompts\u002F` |\n\nFull priority chain for skills: builtin \u003C opencode global (`~\u002F.config\u002Fopencode\u002Fskills\u002F`) \u003C global (`~\u002Fla_briguade\u002Fskills\u002F`) \u003C opencode project (`\u003Croot>\u002F.opencode\u002Fskills\u002F`) \u003C project (`\u003Croot>\u002F.la_briguade\u002Fskills\u002F`). For all other content types: builtin \u003C global \u003C project.\nAgents, commands, and vendor prompts are loaded via `loadContentFiles(dirs, '.md', parseFn)` from `src\u002Futils\u002Fcontent\u002Fload-content.ts` — this centralizes the `collectFiles` → warn-and-skip → parse cycle. Each loader provides its own `parseFn` callback.\n\n### Frontmatter Parsing\nUse the `yaml` library's `parse()` with default `schema: \"core\"` behavior to avoid YAML 1.1 quirks (`on`\u002F`off` → boolean). Always validate the parsed value before casting:\n\n```typescript\nconst parsed = parseYaml(yamlBlock);\nconst attributes =\n  parsed != null && typeof parsed === \"object\" && !Array.isArray(parsed)\n    ? (parsed as Record\u003Cstring, unknown>)\n    : {};\n```\n\n### JSONC Editing\nUse `jsonc-parser`'s `modify()` + `applyEdits()` — **never** raw string replacement or `JSON.parse` + `JSON.stringify` on config files (destroys comments). Use `isArrayInsertion: true` when appending to an existing array.\n\n### Hooks\nHooks are registered via `createHooks(ctx)` returning `Partial\u003CHooksResult>`. They are pure transform functions — mutate the `output` object in-place, no return value. Keep hook logic in small, named helper functions (`truncateLargeOutput`, `appendEditErrorHint`, `detectEmptyResponse`).\n\n### Skill-Embedded MCP Servers\n\nSkills can declare MCP servers in `SKILL.md` frontmatter under the `mcp:` key. At startup, `collectSkillMcps()` reads all skill dirs, validates the frontmatter with `SkillMcpMapSchema`, and converts each entry via `toSdkMcpEntry()`.\n\n**`{env:VAR_NAME}` tokens** are supported in `command` elements, `environment` values, and `headers` values. They are resolved via `resolveEnvTokens()` at startup:\n- Unset var in `environment` value → entry omitted + `logger.debug`\n- Unset var in `command`\u002F`headers` value → `\"\"` + `logger.warn`\n- Resolved command element containing `DISALLOWED_COMMAND_CHARS` (`;`, `|`, `&`, `` ` ``, `\u003C`, `>`, `!`, `$`) → `\"\"` + `logger.warn` (injection guard). `\u002F` and `\\` are **allowed** (needed for scoped packages like `@scope\u002Fpkg`).\n\n`collectSkillMcps()` returns `{ mcpMap, skillMcpIndex }`. The `skillMcpIndex` maps each skill dir basename to its prefixed tool permission map. A skill entry with no `permission:` block defaults to `{ \"\u003Cid>_*\": \"allow\" }`; a custom `permission:` block pre-prefixes each key (e.g. `\"resolve-library-id\"` → `\"\u003Cid>_resolve-library-id\"`). `injectSkillMcpPermissions(input, skillMcpIndex)` is called from the `config()` callback and adds missing prefixed entries to agents that opt in to a skill — without overwriting any key the agent already declares.\n\n### Skill Directory Access — `permission.external_directory`\n\nSkills implicitly grant read\u002Fsearch access to their own directory tree when an agent opts in via `permission.skill`. `collectSkillExternalDirectories(skillDirs)` validates each discovered skill dir and returns a `SkillExternalDirIndex` (`Record\u003CskillName, skillDir>`). `injectSkillExternalDirectoryPermissions(input, skillExternalDirIndex)` then injects missing `permission.external_directory` object entries for both `\u003CskillDir>` and `\u003CskillDir>\u002F**`, without overwriting any agent-defined external-directory rule.\n\nOnly validated skill directories participate: missing `SKILL.md` files are skipped, paths containing glob metacharacters are skipped to avoid over-broad permission patterns, and symlinked content dirs are ignored earlier during directory discovery.\n\nSee `.code-examples-for-ai\u002Fskill-embedded-mcp.md` for a full annotated example.\n\n### Non-MCP Skill Permissions — `permission.bash`\n\nSKILL.md files can also declare bash command permissions under `permission.bash`. These are independent of MCP servers and do not require an `mcp:` block.\n\n`collectSkillBashPermissions(skillDirs)` reads `permission.bash` from each SKILL.md and returns a `SkillBashPermIndex` (`Record\u003CskillName, Record\u003Cstring, string>>`). `injectSkillBashPermissions(input, skillBashPermIndex)` injects missing patterns into `agent.permission.bash` for agents that opt into the skill via `permission.skill`. Injection rules mirror MCP: deny in skill permission → skip; deny value in bash block → skip; existing agent pattern → not overwritten; `permission.bash` is lazily initialised (only created when a non-deny pattern is injected).\n\nKeys in `permission.bash` may contain spaces and glob patterns (e.g. `\"playwright-cli *\": \"allow\"`). They are validated with `isSafePermissionSubKey` (blocks prototype pollution names, allows all other characters).\n\n### Skill-Directed Agent Opt-In — `agents:`\n\nSKILL.md files can declare which agents should automatically receive `permission.skill[\"\u003CskillName>\"] = \"allow\"` at startup. This runs before external-directory, MCP, and bash permission injection, so any skill-directory `permission.external_directory` entries, MCP tools, or bash patterns the skill declares are subsequently injected into those agents as well.\n\n`collectSkillAgents(skillDirs)` reads the optional `agents:` string array from each SKILL.md and returns a `SkillAgentIndex` (`Record\u003CskillName, string[]>`). `injectSkillAgentPermissions(input, skillAgentIndex)` iterates the index and writes the skill's name into each listed agent's `permission.skill` block — without overwriting an existing entry (non-overwrite policy). Unknown agent names (not present in `input.agent`) produce a `logger.warn`. Agent names are validated with `isSafePermissionSubKey` plus a control-character check to guard against log injection.\n\n**Call order in `config()` callback**: `collectSkillAgents` → `injectSkillAgentPermissions` → `collectSkillExternalDirectories` → `injectSkillExternalDirectoryPermissions` → `collectSkillMcps` → `mergeSkillMcps` → `injectSkillMcpPermissions` → `collectSkillBashPermissions` → `injectSkillBashPermissions`. The agent opt-in step must come first so that agents are already opted-in when external-directory\u002FMCP\u002Fbash injection checks `permission.skill`.\n\n**Design note**: this couples a skill to project-specific agent names; it is intended for first-party project skills, not portable community skills.\n\n### No Classes\nPrefer plain functions and type aliases over classes. Stateful configuration lives in the `input` config object passed to `config()`.\n\n## Dependency Guidelines\n\nDeclare all runtime libraries in `dependencies`; keep `devDependencies` build\u002Ftest-only. Before adding a new dependency, verify it ships native ESM or a `\"type\": \"module\"`-compatible dual build.\n\n### Production dependencies\n\n| Package | Pinned range | Latest stable | Status | opencode bundled? | Notes |\n|---|---|---|---|---|---|\n| `commander` | `^14.0.0` | 14.0.3 | Current | Yes | Updated to v14. Review `src\u002Fcli\u002Findex.ts` for any future API changes before upgrading further. |\n| `jsonc-parser` | `^3.3.1` | 3.3.1 | Current | Yes — 3.3.1 exact | Stay at `^3.3.1` |\n| `yaml` | `^2.8.3` | 2.8.3 | Current | Indirect | Stay at `^2.8.3` |\n| `zod` | `^4.3.6` | 4.3.6 | Current | 4.1.8 (older patch) | Stay at `^4.3.6` — we are ahead of opencode's bundled version |\n\n### Dev dependencies\n\n| Package | Pinned range | Status | Notes |\n|---|---|---|---|\n| `typescript` | `^5.8.0` | Current (5.x) | TypeScript 6.0 not yet confirmed stable; wait for ecosystem validation |\n| `vitest` | `^4.1.4` | Current | Stay at `^4.1.4` |\n| `@types\u002Fnode` | `^22.0.0` | Current (22 LTS) | Keep pinned to Node 22 LTS line — do not follow Node version bumps unless `engines.node` is updated first |\n| `@opencode-ai\u002Fplugin` | `^1.4.0` | 1.4.x | Track `^1.4.0` — update when new patch verified stable |\n| `@opencode-ai\u002Fsdk` | `^1.4.0` | 1.4.x | Track `^1.4.0` |\n\n### opencode ecosystem alignment\n\n- opencode 1.4.2 bundles: `zod@4.1.8`, `jsonc-parser@3.3.1`, `commander`, and `yaml` (via gray-matter internally)\n- Plugins run in the same Bun runtime as opencode — shared process but no official module-sharing mechanism\n- No `peerDependencies` mechanism for opencode's bundled libs — always declare own `dependencies`\n- Align major versions with opencode's bundled versions to avoid dual-instance issues in the shared runtime\n\n### Updating deps\n\nBefore updating any dep to a major version, verify:\n1. It is compatible with Node >=22 and TypeScript strict ESM.\n2. It does not conflict with opencode's bundled version of the same library.\n3. All tests still pass after the update (`npm test`).\n\n## Code Examples\n\nSee `.code-examples-for-ai\u002F` for concrete, copy-paste-ready patterns:\n\n- `plugin-registration.md` — Plugin function structure\n- `frontmatter-parsing.md` — Safe YAML frontmatter extraction\n- `hook-creation.md` — Hook registration and output mutation\n- `cli-command.md` — Commander.js command with JSONC editing\n- `safe-dir-read.md` — Defensive directory reading with warning\n- `zod-config-schema.md` — Zod v4 config schema with security constraints and JSON Schema export\n- `model-sections.md` — Parsing and injecting model-family prompt sections from agent `.md` files\n- `load-content-helper.md` — Shared `loadContentFiles()` wrapper that centralizes collectFiles + warn-and-skip parsing\n- `global-prompts-loader.md` — Loading shared vendor prompts from a directory, keyed by lowercased filename stem, with per-file error resilience\n- `agent-permissions.md` — Agent frontmatter `tools` defaults merged with per-agent user config overrides\n- `content-override-merge.md` — Priority-based merge of layered content dirs using `collectFiles()` \u002F `collectDirs()`\n- `logger-notifier.md` — File logger singleton with two-phase init and toast notifier fallback\n\n## Zod v4 Notes\n\nThis project uses **Zod v4** (`zod ^4.3.6`). Key differences from v3 to be aware of:\n\n- **`z.record` requires two arguments**: `z.record(z.string(), valueSchema)` — the one-arg form is removed in v4.\n- **Native JSON Schema**: use `z.toJSONSchema(schema)` — no extra libraries needed (`zod-to-json-schema` is not installed).\n- Zod v4 error format is unchanged: access issues via `result.error.issues.length`.\n","# Project coding guidelines\n\n## Scope\n\n- **In scope**: production TypeScript coding standards, architecture patterns, dependency policy,\n  and repository-specific implementation constraints.\n- **Out of scope**: release logistics, test-only policy details, and non-code documentation process.\n\n## Invariants\n\n- Changes **MUST** compile under strict TypeScript configuration.\n- External or user-provided data **MUST** be validated at runtime boundaries.\n- Error handling **MUST** be explicit and actionable; silent failure paths are disallowed.\n- Patterns **MUST** preserve content override precedence and non-overwrite permission semantics.\n- Code **MUST NOT** introduce placeholder logic, dead\u002Fcommented-out production paths, or unsafe type assertions.\n- Build\u002Ftest validation **MUST** pass before work is considered complete.\n\n## Validation Checklist\n\n```bash\nnpm run build\nnpm test\n```\n\nFor touched config\u002Fschema boundaries, also validate:\n\n```bash\nnpm run generate-schema\n```\n\n## Failure Handling\n\n- If local context is insufficient, stop and request missing context rather than inventing APIs.\n- If validation fails, fix root cause first; do not bypass checks.\n- If constraints conflict, preserve stronger safety\u002Fcontract wording and document the resolved decision.\n\n## Code Style\n\n- **TypeScript strict mode** is enforced: `strict: true`, `noUncheckedIndexedAccess: true`, `exactOptionalPropertyTypes: true`, `isolatedModules: true`\n- **Target**: ES2024 — **Module**: NodeNext — always use `.js` extension in imports (ESM NodeNext requirement, even when the source file is `.ts`)\n- Max line length: 100 characters; 2-space indentation\n- Use `import type { ... }` for all type-only imports — reduces compiled output and clarifies intent\n- Use `as const` for literal object\u002Farray values; use `satisfies` for constrained inference without widening\n- `verbatimModuleSyntax` is not enabled in this project — use `import type` manually for type-only imports\n\n## Naming Conventions\n\n| Scope | Convention | Example |\n|---|---|---|\n| Files | `kebab-case.ts` | `read-dir.ts`, `frontmatter.ts` |\n| Functions \u002F variables | `camelCase` | `parseFrontmatter`, `contentDir` |\n| Types \u002F interfaces | `PascalCase` | `ParsedFrontmatter`, `ConfigFileResult` |\n| Module-level true constants | `SCREAMING_SNAKE_CASE` | `TRUNCATION_THRESHOLD`, `PLUGIN_NAME` |\n| Agent\u002Fcommand filenames | lowercase or PascalCase per existing pattern | `coder.md`, `Builder.md` |\n\n## Import Ordering\n\nBlank line between each group:\n\n```typescript\n\u002F\u002F 1. Node built-ins\nimport { readdirSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\n\n\u002F\u002F 2. External packages\nimport { Command } from \"commander\";\nimport { parse as parseYaml } from \"yaml\";\nimport { modify, applyEdits } from \"jsonc-parser\";\n\n\u002F\u002F 3. Internal project imports\nimport { readDirSafe } from \".\u002Futils\u002Fread-dir.js\";\nimport type { HooksResult } from \"..\u002Ftypes\u002Fplugin.js\";\n```\n\n## Error Handling\n\n- Use try-catch with `logger.warn(message)` (from `src\u002Futils\u002Fruntime\u002Flogger.ts`) for non-fatal failures (e.g. missing content dirs, YAML parse errors)\n- Never silently swallow errors — always log with context identifying the source\n- Use `Result\u003CT, E>` discriminated union pattern for recoverable errors in library\u002Futility code:\n  ```typescript\n  type Result\u003CT, E> = { ok: true; value: T } | { ok: false; error: E };\n  ```\n- Fail fast at CLI boundaries: set `process.exitCode = 1` and print actionable user-facing messages\n- Always check that `parsed` is a non-null, non-array object before casting to `Record\u003Cstring, unknown>`\n\n## Patterns & Architecture\n\n### Plugin Registration\nThe `Plugin` export (`src\u002Findex.ts`) is an async function that returns `{ config, ...hooks }`. The `config` callback receives the mutable `input` config object and calls `register*` helpers to merge into it. **Never mutate globals** — only mutate the `input` passed to the `config` callback.\n\n`resolveConfigBaseDirs(projectDir)` (from `src\u002Fconfig\u002Findex.ts`) returns `{ globalDir, projectDir }` — the two user content roots (`~\u002Fla_briguade\u002F` and the project root). All content loaders receive ordered arrays of dirs (`[builtin, globalUser, projectUser]`) and use `collectFiles()` \u002F `collectDirs()` from `src\u002Futils\u002Fcontent\u002Fcontent-merge.ts` for priority-based last-wins merging.\n\n```typescript\nconst LaBriguadePlugin: Plugin = async (ctx) => ({\n  config: async (input) => {\n    registerAgents(input, contentDir);\n    registerCommands(input, contentDir);\n    registerSkills(input, contentDir);\n  },\n  ...createHooks(ctx),\n});\n```\n\n### Content-Driven Registration\nAgents, skills, and commands are loaded from `.md` files resolved across ordered layers. All loaders call `collectFiles(dirs, '.md')` (for agents\u002Fcommands\u002Fvendors) or `collectDirs(roots)` (for skills) from `src\u002Futils\u002Fcontent\u002Fcontent-merge.ts`. Later directories in the array override earlier ones by filename stem — this is the user content override mechanism. Agent\u002Fcommand identity comes from the filename (`.md` stripped, first char lowercased for agents). Frontmatter YAML provides metadata.\n\n**User content override paths** (lowest → highest priority):\n\n| Content type | Global user | Project user |\n|---|---|---|\n| Agents | `~\u002Fla_briguade\u002Fagents\u002F` | `\u003Croot>\u002F.la_briguade\u002Fagents\u002F` |\n| Commands | `~\u002Fla_briguade\u002Fcommands\u002F` | `\u003Croot>\u002F.la_briguade\u002Fcommands\u002F` |\n| Skills | `~\u002F.config\u002Fopencode\u002Fskills\u002F` or `~\u002Fla_briguade\u002Fskills\u002F` | `\u003Croot>\u002F.opencode\u002Fskills\u002F` or `\u003Croot>\u002F.la_briguade\u002Fskills\u002F` |\n| Vendor prompts | `~\u002Fla_briguade\u002Fvendor-prompts\u002F` | `\u003Croot>\u002F.la_briguade\u002Fvendor-prompts\u002F` |\n\nFull priority chain for skills: builtin \u003C opencode global (`~\u002F.config\u002Fopencode\u002Fskills\u002F`) \u003C global (`~\u002Fla_briguade\u002Fskills\u002F`) \u003C opencode project (`\u003Croot>\u002F.opencode\u002Fskills\u002F`) \u003C project (`\u003Croot>\u002F.la_briguade\u002Fskills\u002F`). For all other content types: builtin \u003C global \u003C project.\nAgents, commands, and vendor prompts are loaded via `loadContentFiles(dirs, '.md', parseFn)` from `src\u002Futils\u002Fcontent\u002Fload-content.ts` — this centralizes the `collectFiles` → warn-and-skip → parse cycle. Each loader provides its own `parseFn` callback.\n\n### Frontmatter Parsing\nUse the `yaml` library's `parse()` with default `schema: \"core\"` behavior to avoid YAML 1.1 quirks (`on`\u002F`off` → boolean). Always validate the parsed value before casting:\n\n```typescript\nconst parsed = parseYaml(yamlBlock);\nconst attributes =\n  parsed != null && typeof parsed === \"object\" && !Array.isArray(parsed)\n    ? (parsed as Record\u003Cstring, unknown>)\n    : {};\n```\n\n### JSONC Editing\nUse `jsonc-parser`'s `modify()` + `applyEdits()` — **never** raw string replacement or `JSON.parse` + `JSON.stringify` on config files (destroys comments). Use `isArrayInsertion: true` when appending to an existing array.\n\n### Hooks\nHooks are registered via `createHooks(ctx)` returning `Partial\u003CHooksResult>`. They are pure transform functions — mutate the `output` object in-place, no return value. Keep hook logic in small, named helper functions (`truncateLargeOutput`, `appendEditErrorHint`, `detectEmptyResponse`).\n\n### Skill-Embedded MCP Servers\n\nSkills can declare MCP servers in `SKILL.md` frontmatter under the `mcp:` key. At startup, `collectSkillMcps()` reads all skill dirs, validates the frontmatter with `SkillMcpMapSchema`, and converts each entry via `toSdkMcpEntry()`.\n\n**`{env:VAR_NAME}` tokens** are supported in `command` elements, `environment` values, and `headers` values. They are resolved via `resolveEnvTokens()` at startup:\n- Unset var in `environment` value → entry omitted + `logger.debug`\n- Unset var in `command`\u002F`headers` value → `\"\"` + `logger.warn`\n- Resolved command element containing `DISALLOWED_COMMAND_CHARS` (`;`, `|`, `&`, `` ` ``, `\u003C`, `>`, `!`, `$`) → `\"\"` + `logger.warn` (injection guard). `\u002F` and `\\` are **allowed** (needed for scoped packages like `@scope\u002Fpkg`).\n\n`collectSkillMcps()` returns `{ mcpMap, skillMcpIndex }`. The `skillMcpIndex` maps each skill dir basename to its prefixed tool permission map. A skill entry with no `permission:` block defaults to `{ \"\u003Cid>_*\": \"allow\" }`; a custom `permission:` block pre-prefixes each key (e.g. `\"resolve-library-id\"` → `\"\u003Cid>_resolve-library-id\"`). `injectSkillMcpPermissions(input, skillMcpIndex)` is called from the `config()` callback and adds missing prefixed entries to agents that opt in to a skill — without overwriting any key the agent already declares.\n\n### Skill Directory Access — `permission.external_directory`\n\nSkills implicitly grant read\u002Fsearch access to their own directory tree when an agent opts in via `permission.skill`. `collectSkillExternalDirectories(skillDirs)` validates each discovered skill dir and returns a `SkillExternalDirIndex` (`Record\u003CskillName, skillDir>`). `injectSkillExternalDirectoryPermissions(input, skillExternalDirIndex)` then injects missing `permission.external_directory` object entries for both `\u003CskillDir>` and `\u003CskillDir>\u002F**`, without overwriting any agent-defined external-directory rule.\n\nOnly validated skill directories participate: missing `SKILL.md` files are skipped, paths containing glob metacharacters are skipped to avoid over-broad permission patterns, and symlinked content dirs are ignored earlier during directory discovery.\n\nSee `.code-examples-for-ai\u002Fskill-embedded-mcp.md` for a full annotated example.\n\n### Non-MCP Skill Permissions — `permission.bash`\n\nSKILL.md files can also declare bash command permissions under `permission.bash`. These are independent of MCP servers and do not require an `mcp:` block.\n\n`collectSkillBashPermissions(skillDirs)` reads `permission.bash` from each SKILL.md and returns a `SkillBashPermIndex` (`Record\u003CskillName, Record\u003Cstring, string>>`). `injectSkillBashPermissions(input, skillBashPermIndex)` injects missing patterns into `agent.permission.bash` for agents that opt into the skill via `permission.skill`. Injection rules mirror MCP: deny in skill permission → skip; deny value in bash block → skip; existing agent pattern → not overwritten; `permission.bash` is lazily initialised (only created when a non-deny pattern is injected).\n\nKeys in `permission.bash` may contain spaces and glob patterns (e.g. `\"playwright-cli *\": \"allow\"`). They are validated with `isSafePermissionSubKey` (blocks prototype pollution names, allows all other characters).\n\n### Skill-Directed Agent Opt-In — `agents:`\n\nSKILL.md files can declare which agents should automatically receive `permission.skill[\"\u003CskillName>\"] = \"allow\"` at startup. This runs before external-directory, MCP, and bash permission injection, so any skill-directory `permission.external_directory` entries, MCP tools, or bash patterns the skill declares are subsequently injected into those agents as well.\n\n`collectSkillAgents(skillDirs)` reads the optional `agents:` string array from each SKILL.md and returns a `SkillAgentIndex` (`Record\u003CskillName, string[]>`). `injectSkillAgentPermissions(input, skillAgentIndex)` iterates the index and writes the skill's name into each listed agent's `permission.skill` block — without overwriting an existing entry (non-overwrite policy). Unknown agent names (not present in `input.agent`) produce a `logger.warn`. Agent names are validated with `isSafePermissionSubKey` plus a control-character check to guard against log injection.\n\n**Call order in `config()` callback**: `collectSkillAgents` → `injectSkillAgentPermissions` → `collectSkillExternalDirectories` → `injectSkillExternalDirectoryPermissions` → `collectSkillMcps` → `mergeSkillMcps` → `injectSkillMcpPermissions` → `collectSkillBashPermissions` → `injectSkillBashPermissions`. The agent opt-in step must come first so that agents are already opted-in when external-directory\u002FMCP\u002Fbash injection checks `permission.skill`.\n\n**Design note**: this couples a skill to project-specific agent names; it is intended for first-party project skills, not portable community skills.\n\n### No Classes\nPrefer plain functions and type aliases over classes. Stateful configuration lives in the `input` config object passed to `config()`.\n\n## Dependency Guidelines\n\nDeclare all runtime libraries in `dependencies`; keep `devDependencies` build\u002Ftest-only. Before adding a new dependency, verify it ships native ESM or a `\"type\": \"module\"`-compatible dual build.\n\n### Production dependencies\n\n| Package | Pinned range | Latest stable | Status | opencode bundled? | Notes |\n|---|---|---|---|---|---|\n| `commander` | `^14.0.0` | 14.0.3 | Current | Yes | Updated to v14. Review `src\u002Fcli\u002Findex.ts` for any future API changes before upgrading further. |\n| `jsonc-parser` | `^3.3.1` | 3.3.1 | Current | Yes — 3.3.1 exact | Stay at `^3.3.1` |\n| `yaml` | `^2.8.3` | 2.8.3 | Current | Indirect | Stay at `^2.8.3` |\n| `zod` | `^4.3.6` | 4.3.6 | Current | 4.1.8 (older patch) | Stay at `^4.3.6` — we are ahead of opencode's bundled version |\n\n### Dev dependencies\n\n| Package | Pinned range | Status | Notes |\n|---|---|---|---|\n| `typescript` | `^5.8.0` | Current (5.x) | TypeScript 6.0 not yet confirmed stable; wait for ecosystem validation |\n| `vitest` | `^4.1.4` | Current | Stay at `^4.1.4` |\n| `@types\u002Fnode` | `^22.0.0` | Current (22 LTS) | Keep pinned to Node 22 LTS line — do not follow Node version bumps unless `engines.node` is updated first |\n| `@opencode-ai\u002Fplugin` | `^1.4.0` | 1.4.x | Track `^1.4.0` — update when new patch verified stable |\n| `@opencode-ai\u002Fsdk` | `^1.4.0` | 1.4.x | Track `^1.4.0` |\n\n### opencode ecosystem alignment\n\n- opencode 1.4.2 bundles: `zod@4.1.8`, `jsonc-parser@3.3.1`, `commander`, and `yaml` (via gray-matter internally)\n- Plugins run in the same Bun runtime as opencode — shared process but no official module-sharing mechanism\n- No `peerDependencies` mechanism for opencode's bundled libs — always declare own `dependencies`\n- Align major versions with opencode's bundled versions to avoid dual-instance issues in the shared runtime\n\n### Updating deps\n\nBefore updating any dep to a major version, verify:\n1. It is compatible with Node >=22 and TypeScript strict ESM.\n2. It does not conflict with opencode's bundled version of the same library.\n3. All tests still pass after the update (`npm test`).\n\n## Code Examples\n\nSee `.code-examples-for-ai\u002F` for concrete, copy-paste-ready patterns:\n\n- `plugin-registration.md` — Plugin function structure\n- `frontmatter-parsing.md` — Safe YAML frontmatter extraction\n- `hook-creation.md` — Hook registration and output mutation\n- `cli-command.md` — Commander.js command with JSONC editing\n- `safe-dir-read.md` — Defensive directory reading with warning\n- `zod-config-schema.md` — Zod v4 config schema with security constraints and JSON Schema export\n- `model-sections.md` — Parsing and injecting model-family prompt sections from agent `.md` files\n- `load-content-helper.md` — Shared `loadContentFiles()` wrapper that centralizes collectFiles + warn-and-skip parsing\n- `global-prompts-loader.md` — Loading shared vendor prompts from a directory, keyed by lowercased filename stem, with per-file error resilience\n- `agent-permissions.md` — Agent frontmatter `tools` defaults merged with per-agent user config overrides\n- `content-override-merge.md` — Priority-based merge of layered content dirs using `collectFiles()` \u002F `collectDirs()`\n- `logger-notifier.md` — File logger singleton with two-phase init and toast notifier fallback\n\n## Zod v4 Notes\n\nThis project uses **Zod v4** (`zod ^4.3.6`). Key differences from v3 to be aware of:\n\n- **`z.record` requires two arguments**: `z.record(z.string(), valueSchema)` — the one-arg form is removed in v4.\n- **Native JSON Schema**: use `z.toJSONSchema(schema)` — no extra libraries needed (`zod-to-json-schema` is not installed).\n- Zod v4 error format is unchanged: access issues via `result.error.issues.length`.\n",{"name":5,"agents":48,"description":7},[49,38,50,51,36,26,31,32],"coder","feature-designer","feature-reviewer","main","f4f3a02e82412dd181a98c114c6650ec02475d0f",".la_briguade\u002Fauto-inject-skills\u002Fproject-coding",0,"https:\u002F\u002Fgithub.com\u002FtheCat69\u002Fla-briguade","https:\u002F\u002Fgithub.com\u002FtheCat69","2026-09-14 16:38:37",{"source":8,"skillName":5,"commands":60,"prompt":70,"zipAvailable":71},[61,64,67],{"tool":62,"command":63},"npx","npx skills add theCat69\u002Fla-briguade --skill project-coding",{"tool":65,"command":66},"bunx","bunx skills add theCat69\u002Fla-briguade --skill project-coding",{"tool":68,"command":69},"pnpm","pnpm dlx skills add theCat69\u002Fla-briguade --skill project-coding","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FtheCat69\u002Fla-briguade\u002Fblob\u002Fmain\u002F.la_briguade\u002Fauto-inject-skills\u002Fproject-coding\u002FSKILL.md",true,[],[74,96,111,122,146,162],{"skillNo":75,"name":76,"nameZh":6,"description":77,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":78,"categories":79,"keywords":80,"publishedAt":43},"01M6QPPWK5A3WJ1SAYDNR5YD6S","unslop","Perform sequential slop-cleanup edits in bounded changed-file scope with behavior-preserving rules; do not use for read-only scanning.","https:\u002F\u002Fgithub.com\u002FtheCat69\u002Fla-briguade\u002Fblob\u002Fmain\u002Fcontent\u002Fskills\u002Funslop\u002FSKILL.md",[],[76,27,81,82,83,84,85,86,87,88,89,31,90,91,92,93,94,95],"slop","bounded","behavior-preserving","scope","only","use","behavior","read","Rules","file","do","cleanup","scanning","with","read-only",{"skillNo":97,"name":98,"nameZh":6,"description":99,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":100,"categories":101,"keywords":102,"publishedAt":43},"01M6QPPPQ49KBWSWH3X9D9R17B","unslop-reviewer","Run a read-only, pass-ordered slop scan covering abstraction and locally fixable boundary findings; never edit files.","https:\u002F\u002Fgithub.com\u002FtheCat69\u002Fla-briguade\u002Fblob\u002Fmain\u002Fcontent\u002Fskills\u002Funslop-reviewer\u002FSKILL.md",[],[76,35,103,88,104,105,106,107,81,95,108,109,85,110],"unslop reviewer","sidekick","boundary","run","edit","scan","files","never",{"skillNo":112,"name":113,"nameZh":6,"description":114,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":115,"categories":116,"keywords":117,"publishedAt":43},"01M6QPPJA6P2HDC19FFR3FZB7A","unslop-coder","Apply unslop-reviewer findings as targeted in-scope code edits in pass order; do not run discovery\u002Fscanning in this skill.","https:\u002F\u002Fgithub.com\u002FtheCat69\u002Fla-briguade\u002Fblob\u002Fmain\u002Fcontent\u002Fskills\u002Funslop-coder\u002FSKILL.md",[],[76,49,118,119,120,84,91,93,35,121,41,106],"unslop coder","as","apply","discovery",{"skillNo":123,"name":124,"nameZh":6,"description":125,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":126,"categories":127,"keywords":129,"publishedAt":43},"01M6QPP53KFSKE5KGJBGAKD3WW","serena","Semantic code retrieval, editing, refactoring and debugging tools using LSP servers","https:\u002F\u002Fgithub.com\u002FtheCat69\u002Fla-briguade\u002Fblob\u002Fmain\u002Fcontent\u002Fskills\u002Fserena\u002FSKILL.md",[128],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},[124,130,131,132,133,24,134,135,31,136,137,138,139,140,41,141,142,143,144,145],"server","mcp-server","retrieval","false","local","lsp","editing","refactoring","open","debugging tools","https","debugging","web","dashboard","github","MCP",{"skillNo":147,"name":148,"nameZh":6,"description":149,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":150,"categories":151,"keywords":155,"publishedAt":43},"01M6QPP2M71X18ENVM3R3B6D10","read-image","guidance to read images in a lightweight fashion using cwebp","https:\u002F\u002Fgithub.com\u002FtheCat69\u002Fla-briguade\u002Fblob\u002Fmain\u002Fcontent\u002Fskills\u002Fread-image\u002FSKILL.md",[152],{"categoryNo":153,"categoryName":154,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000016","图像与多媒体",[156,88,157,31,32,26,49,158,159,160,161],"read image","image","images","fashion","cwebp","guidance",{"skillNo":163,"name":164,"nameZh":6,"description":165,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":166,"categories":167,"keywords":171,"publishedAt":43},"01M6QPNWCSF2V8AGS8KH9D6A54","playwright-cli","Drive browser UI checks with playwright-cli commands, snapshots, and test\u002Fdebug workflows; do not use for backend-only or non-browser tasks.","https:\u002F\u002Fgithub.com\u002FtheCat69\u002Fla-briguade\u002Fblob\u002Fmain\u002Fcontent\u002Fskills\u002Fplaywright-cli\u002FSKILL.md",[168],{"categoryNo":169,"categoryName":170,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000008","测试与质量",[172,173,174,31,164,27,94,175,91,176,85,177,86,49,178,179,180,181,182,183],"playwright","cli","playwright cli","commands","snapshots","backend","ui","browser","tasks","debug","workflows","test"]