|
1 | | -# src/lib — Core Implementation Modules |
2 | | - |
3 | | -Plugin internals. 12 modules handling config, conversion, discovery, and tool implementation. |
4 | | - |
5 | | -## Module Map |
6 | | - |
7 | | -| Module | Purpose | Key Exports | |
8 | | -|--------|---------|-------------| |
9 | | -| `config-handler.ts` | OpenCode config hook | `createConfigHandler()` | |
10 | | -| `skill-tool.ts` | systematic_skill tool | `createSkillTool()` | |
11 | | -| `skill-loader.ts` | Skill file loading | `loadSkill()` | |
12 | | -| `skills.ts` | Skill discovery | `findSkillsInDir()`, `extractFrontmatter()` | |
13 | | -| `agents.ts` | Agent discovery | `findAgentsInDir()`, `extractAgentFrontmatter()` | |
14 | | -| `commands.ts` | Command discovery | `findCommandsInDir()`, `extractCommandFrontmatter()` | |
15 | | -| `converter.ts` | CEP→OpenCode conversion | `convertContent()`, `convertFileWithCache()` | |
16 | | -| `frontmatter.ts` | YAML frontmatter utils | `parseFrontmatter()`, `serializeFrontmatter()` | |
17 | | -| `bootstrap.ts` | System prompt injection | `getBootstrapContent()` | |
18 | | -| `config.ts` | JSONC config loading | `loadConfig()`, `getConfigPaths()` | |
19 | | -| `validation.ts` | Input validation | Validation helpers | |
20 | | -| `walk-dir.ts` | Directory traversal | `walkDir()` | |
| 1 | +# src/lib — Core Implementation |
| 2 | + |
| 3 | +13 modules implementing plugin logic: discovery, conversion, config, and tool registration. |
21 | 4 |
|
22 | 5 | ## Data Flow |
23 | 6 |
|
24 | 7 | ``` |
25 | | -Plugin init |
26 | | - ↓ |
27 | | -loadConfig() ← reads JSONC from project/user paths |
28 | | - ↓ |
29 | | -createConfigHandler() ← merges bundled assets into OpenCode config |
30 | | - │ |
31 | | - ├─ findSkillsInDir() + loadSkill() → skills as commands |
32 | | - ├─ findAgentsInDir() + extractAgentFrontmatter() → agent configs |
33 | | - └─ findCommandsInDir() + extractCommandFrontmatter() → command configs |
34 | | - │ |
35 | | - └─ convertContent() / convertFileWithCache() ← CEP→OpenCode transform |
36 | | -
|
37 | | -createSkillTool() ← registers systematic_skill tool |
38 | | - │ |
39 | | - └─ findSkillsInDir() → formats skill list as XML |
40 | | - └─ loadSkill() → returns skill body on demand |
41 | | -
|
42 | | -getBootstrapContent() ← reads using-systematic SKILL.md for system prompt |
43 | | -``` |
| 8 | +loadConfig() → createConfigHandler() → { |
| 9 | + findSkillsInDir() → loadSkillAsCommand() → OpenCode config |
| 10 | + findAgentsInDir() → loadAgentAsConfig() → OpenCode config |
| 11 | + findCommandsInDir() → loadCommandAsConfig() → OpenCode config |
| 12 | +} |
44 | 13 |
|
45 | | -## Key Interfaces |
| 14 | +createSkillTool() → discoverSkillFiles() → loadSkill() → formatted output |
| 15 | +getBootstrapContent() → reads using-systematic SKILL.md → system prompt |
| 16 | +``` |
46 | 17 |
|
47 | | -```typescript |
48 | | -// skills.ts |
49 | | -interface SkillInfo { |
50 | | - path: string |
51 | | - skillFile: string |
52 | | - name: string |
53 | | - description: string |
54 | | - // ... frontmatter fields |
55 | | -} |
| 18 | +All discovery follows same pattern: `dir → walkDir() → find files → parseFrontmatter() → typed array` |
56 | 19 |
|
57 | | -// config.ts |
58 | | -interface SystematicConfig { |
59 | | - disabled_skills: string[] |
60 | | - disabled_agents: string[] |
61 | | - disabled_commands: string[] |
62 | | - bootstrap: { enabled: boolean; file?: string } |
63 | | -} |
| 20 | +## Modules |
64 | 21 |
|
65 | | -// config-handler.ts |
66 | | -interface ConfigHandlerDeps { |
67 | | - directory: string |
68 | | - bundledSkillsDir: string |
69 | | - bundledAgentsDir: string |
70 | | - bundledCommandsDir: string |
71 | | -} |
72 | | -``` |
| 22 | +### Discovery Layer |
73 | 23 |
|
74 | | -## Converter Details |
| 24 | +| Module | Key Exports | Role | |
| 25 | +|--------|-------------|------| |
| 26 | +| `walk-dir.ts` | `walkDir`, `WalkEntry`, `WalkOptions` | Recursive dir walker with depth + category tracking | |
| 27 | +| `skills.ts` | `findSkillsInDir`, `SkillInfo`, `SkillFrontmatter` | Skill discovery (maxDepth, frontmatter extraction) | |
| 28 | +| `agents.ts` | `findAgentsInDir`, `AgentInfo`, `AgentFrontmatter` | Agent discovery (category from subdir name) | |
| 29 | +| `commands.ts` | `findCommandsInDir`, `CommandInfo`, `CommandFrontmatter` | Command discovery | |
| 30 | +| `frontmatter.ts` | `parseFrontmatter`, `formatFrontmatter`, `stripFrontmatter` | YAML frontmatter parse/format/strip | |
75 | 31 |
|
76 | | -Transforms Claude Code (CEP) content to OpenCode format: |
| 32 | +### Conversion Layer |
77 | 33 |
|
78 | | -| Transformation | From | To | |
79 | | -|----------------|------|-----| |
80 | | -| Tool names | `TodoWrite` | `todowrite` | |
81 | | -| Tool refs | `Task` | `delegate_task` | |
82 | | -| Path separators | `\` | `/` | |
83 | | -| Model names | `claude-3-opus` | Normalized | |
84 | | -| Temperature | Inferred from content | 0.0-1.0 | |
| 34 | +| Module | Key Exports | Role | |
| 35 | +|--------|-------------|------| |
| 36 | +| `converter.ts` | `convertContent`, `convertFileWithCache`, `clearConverterCache` | CEP→OpenCode transforms (tool names, models, body refs) | |
| 37 | +| `skill-loader.ts` | `loadSkill`, `LoadedSkill`, `SKILL_PREFIX` | Loads + wraps skill content in XML template | |
| 38 | +| `validation.ts` | `isAgentMode`, `isPermissionSetting`, `buildPermissionObject` | Agent config extraction + type guards | |
85 | 39 |
|
86 | | -Caching: `convertFileWithCache()` uses file mtime to avoid re-parsing. |
| 40 | +### Config & Integration Layer |
87 | 41 |
|
88 | | -## Discovery Patterns |
| 42 | +| Module | Key Exports | Role | |
| 43 | +|--------|-------------|------| |
| 44 | +| `config.ts` | `loadConfig`, `getConfigPaths`, `SystematicConfig`, `DEFAULT_CONFIG` | JSONC config loading + merging | |
| 45 | +| `config-handler.ts` | `createConfigHandler`, `ConfigHandlerDeps` | OpenCode config hook (collects + converts all assets) | |
| 46 | +| `skill-tool.ts` | `createSkillTool`, `SkillToolOptions` | `systematic_skill` tool (XML description, skill execution) | |
| 47 | +| `bootstrap.ts` | `getBootstrapContent`, `BootstrapDeps` | System prompt injection (using-systematic skill) | |
89 | 48 |
|
90 | | -All discovery functions: |
91 | | -1. Take a directory path |
92 | | -2. Use `walkDir()` for traversal |
93 | | -3. Look for specific files (SKILL.md, *.md) |
94 | | -4. Extract YAML frontmatter |
95 | | -5. Return typed array of results |
| 49 | +## Key Interfaces |
96 | 50 |
|
97 | | -Example: |
98 | 51 | ```typescript |
99 | | -const skills = findSkillsInDir(bundledSkillsDir, 'bundled', 3) |
100 | | -// Returns SkillInfo[] with name, description, path, etc. |
| 52 | +// Discovery |
| 53 | +interface SkillInfo { path, skillFile, name, description } |
| 54 | +interface AgentInfo { name, file, category } |
| 55 | +interface CommandInfo { name, file, category } |
| 56 | +interface WalkEntry { path, name, isDirectory, depth, category } |
| 57 | + |
| 58 | +// Config |
| 59 | +interface SystematicConfig { disabled_skills, disabled_agents, disabled_commands, bootstrap: BootstrapConfig } |
| 60 | +interface ConfigHandlerDeps { directory, bundledSkillsDir, bundledAgentsDir, bundledCommandsDir } |
| 61 | + |
| 62 | +// Conversion |
| 63 | +type ContentType = 'skill' | 'agent' | 'command' |
| 64 | +type SourceType = 'cep' | 'opencode' |
| 65 | +interface ConvertOptions { source, agentMode, skipBodyTransform } |
101 | 66 | ``` |
102 | 67 |
|
103 | | -## Config Loading |
| 68 | +## Converter Details |
104 | 69 |
|
105 | | -Priority chain: |
106 | | -1. `loadConfig(projectDir)` reads from: |
107 | | - - `~/.config/opencode/systematic.json` (user) |
108 | | - - `<projectDir>/.opencode/systematic.json` (project) |
109 | | -2. Merges with `DEFAULT_CONFIG` |
110 | | -3. Arrays merged uniquely via `mergeArraysUnique()` |
| 70 | +CEP→OpenCode transforms: |
| 71 | +- **Tool names**: `TodoWrite`→`todowrite`, `Task`→`delegate_task`, `Skill`→`skill` |
| 72 | +- **Models**: Claude model name normalization |
| 73 | +- **Body**: Replaces tool references outside code blocks (regex-based) |
| 74 | +- **Frontmatter**: Strips CEP-only fields, adds OpenCode fields |
| 75 | +- **Caching**: `convertFileWithCache` uses file mtime for invalidation |
111 | 76 |
|
112 | | -## Testing |
| 77 | +## Patterns |
113 | 78 |
|
114 | | -Unit tests in `tests/unit/`: |
115 | | -- `skills.test.ts` — skill discovery |
116 | | -- `config-handler.test.ts` — config merging |
117 | | -- `converter.test.ts` — CEP conversion |
118 | | -- `frontmatter.test.ts` — YAML parsing |
| 79 | +- **Function-only**: Zero classes. All modules export factory functions or pure helpers |
| 80 | +- **Interface-first**: Data shapes defined as interfaces, logic as functions |
| 81 | +- **Null returns**: Non-critical failures return `null`/`undefined` (not throws) |
| 82 | +- **Type guards**: `validation.ts` provides safe extraction from `unknown` frontmatter data |
| 83 | +- **Const enums**: `AgentMode`, `PermissionSetting` for compile-time safety |
119 | 84 |
|
120 | | -Pattern: |
121 | | -```typescript |
122 | | -describe('moduleName', () => { |
123 | | - let testDir: string |
124 | | - beforeEach(() => { testDir = fs.mkdtempSync(...) }) |
125 | | - afterEach(() => { fs.rmSync(testDir, { recursive: true }) }) |
126 | | - test('behavior', () => { ... }) |
127 | | -}) |
128 | | -``` |
| 85 | +## Notes |
| 86 | + |
| 87 | +- `findSkillsInDir` is highest-centrality function (6 references across 3 modules) |
| 88 | +- `SKILL_PREFIX` = `'systematic:'` — all skills registered with this prefix |
| 89 | +- `parseFrontmatter` is regex-based (not a YAML library for delimiter detection) |
| 90 | +- `formatFrontmatter` uses `js-yaml` dump with `noRefs` and core schema |
| 91 | +- `config-handler.ts` contains internal `loadAgentAsConfig`/`loadCommandAsConfig`/`loadSkillAsCommand` — the glue between discovery and OpenCode config output |
0 commit comments