oh-my-opencode-serverlocal 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.
Files changed (177) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja-JP.md +775 -0
  3. package/README.ko-KR.md +796 -0
  4. package/README.md +792 -0
  5. package/README.zh-CN.md +765 -0
  6. package/dist/agents/council.d.ts +27 -0
  7. package/dist/agents/councillor.d.ts +2 -0
  8. package/dist/agents/designer.d.ts +2 -0
  9. package/dist/agents/explorer.d.ts +2 -0
  10. package/dist/agents/fixer.d.ts +2 -0
  11. package/dist/agents/index.d.ts +31 -0
  12. package/dist/agents/librarian.d.ts +2 -0
  13. package/dist/agents/observer.d.ts +2 -0
  14. package/dist/agents/oracle.d.ts +2 -0
  15. package/dist/agents/orchestrator.d.ts +28 -0
  16. package/dist/agents/permissions.d.ts +10 -0
  17. package/dist/cli/background-subagents.d.ts +13 -0
  18. package/dist/cli/companion.d.ts +4 -0
  19. package/dist/cli/config-io.d.ts +25 -0
  20. package/dist/cli/config-manager.d.ts +4 -0
  21. package/dist/cli/custom-skills-registry.d.ts +18 -0
  22. package/dist/cli/custom-skills.d.ts +13 -0
  23. package/dist/cli/doctor.d.ts +38 -0
  24. package/dist/cli/index.d.ts +3 -0
  25. package/dist/cli/index.js +3565 -0
  26. package/dist/cli/install.d.ts +7 -0
  27. package/dist/cli/model-key-normalization.d.ts +1 -0
  28. package/dist/cli/paths.d.ts +35 -0
  29. package/dist/cli/providers.d.ts +145 -0
  30. package/dist/cli/skills.d.ts +24 -0
  31. package/dist/cli/system.d.ts +6 -0
  32. package/dist/cli/types.d.ts +47 -0
  33. package/dist/companion/manager.d.ts +50 -0
  34. package/dist/companion/updater.d.ts +36 -0
  35. package/dist/config/agent-mcps.d.ts +11 -0
  36. package/dist/config/constants.d.ts +38 -0
  37. package/dist/config/council-schema.d.ts +137 -0
  38. package/dist/config/index.d.ts +5 -0
  39. package/dist/config/loader.d.ts +88 -0
  40. package/dist/config/runtime-preset.d.ts +11 -0
  41. package/dist/config/schema.d.ts +1107 -0
  42. package/dist/config/strip-orchestrator-model.d.ts +9 -0
  43. package/dist/config/utils.d.ts +18 -0
  44. package/dist/council/council-manager.d.ts +53 -0
  45. package/dist/council/index.d.ts +1 -0
  46. package/dist/hooks/apply-patch/codec.d.ts +7 -0
  47. package/dist/hooks/apply-patch/errors.d.ts +25 -0
  48. package/dist/hooks/apply-patch/execution-context.d.ts +27 -0
  49. package/dist/hooks/apply-patch/index.d.ts +15 -0
  50. package/dist/hooks/apply-patch/matching.d.ts +26 -0
  51. package/dist/hooks/apply-patch/operations.d.ts +3 -0
  52. package/dist/hooks/apply-patch/prepared-changes.d.ts +17 -0
  53. package/dist/hooks/apply-patch/resolution.d.ts +19 -0
  54. package/dist/hooks/apply-patch/rewrite.d.ts +7 -0
  55. package/dist/hooks/apply-patch/test-helpers.d.ts +6 -0
  56. package/dist/hooks/apply-patch/types.d.ts +80 -0
  57. package/dist/hooks/auto-update-checker/cache.d.ts +11 -0
  58. package/dist/hooks/auto-update-checker/checker.d.ts +38 -0
  59. package/dist/hooks/auto-update-checker/constants.d.ts +12 -0
  60. package/dist/hooks/auto-update-checker/index.d.ts +18 -0
  61. package/dist/hooks/auto-update-checker/skill-sync.d.ts +67 -0
  62. package/dist/hooks/auto-update-checker/types.d.ts +34 -0
  63. package/dist/hooks/chat-headers.d.ts +16 -0
  64. package/dist/hooks/command-hook-utils.d.ts +5 -0
  65. package/dist/hooks/deepwork/index.d.ts +13 -0
  66. package/dist/hooks/delegate-task-retry/hook.d.ts +8 -0
  67. package/dist/hooks/delegate-task-retry/patterns.d.ts +11 -0
  68. package/dist/hooks/filter-available-skills/index.d.ts +19 -0
  69. package/dist/hooks/foreground-fallback/index.d.ts +122 -0
  70. package/dist/hooks/image-hook.d.ts +8 -0
  71. package/dist/hooks/index.d.ts +16 -0
  72. package/dist/hooks/json-error-recovery/hook.d.ts +18 -0
  73. package/dist/hooks/loop-command/index.d.ts +13 -0
  74. package/dist/hooks/phase-reminder/index.d.ts +25 -0
  75. package/dist/hooks/post-file-tool-nudge/index.d.ts +23 -0
  76. package/dist/hooks/reflect/index.d.ts +13 -0
  77. package/dist/hooks/session-lifecycle.d.ts +10 -0
  78. package/dist/hooks/task-session-manager/index.d.ts +58 -0
  79. package/dist/hooks/task-session-manager/pending-call-tracker.d.ts +13 -0
  80. package/dist/hooks/task-session-manager/task-context-tracker.d.ts +14 -0
  81. package/dist/hooks/types.d.ts +26 -0
  82. package/dist/index.d.ts +5 -0
  83. package/dist/index.js +39519 -0
  84. package/dist/interview/dashboard-manager.d.ts +21 -0
  85. package/dist/interview/dashboard.d.ts +67 -0
  86. package/dist/interview/document.d.ts +29 -0
  87. package/dist/interview/helpers.d.ts +10 -0
  88. package/dist/interview/index.d.ts +1 -0
  89. package/dist/interview/manager.d.ts +21 -0
  90. package/dist/interview/parser.d.ts +11 -0
  91. package/dist/interview/prompts.d.ts +7 -0
  92. package/dist/interview/server.d.ts +15 -0
  93. package/dist/interview/service.d.ts +45 -0
  94. package/dist/interview/session-server.d.ts +21 -0
  95. package/dist/interview/types.d.ts +111 -0
  96. package/dist/interview/ui.d.ts +11 -0
  97. package/dist/loop/loop-session.d.ts +64 -0
  98. package/dist/mcp/context7.d.ts +6 -0
  99. package/dist/mcp/grep-app.d.ts +6 -0
  100. package/dist/mcp/index.d.ts +8 -0
  101. package/dist/mcp/types.d.ts +12 -0
  102. package/dist/mcp/websearch.d.ts +9 -0
  103. package/dist/multiplexer/cmux/close-policy.d.ts +20 -0
  104. package/dist/multiplexer/cmux/index.d.ts +102 -0
  105. package/dist/multiplexer/cmux/session-lifecycle.d.ts +92 -0
  106. package/dist/multiplexer/cmux/session-state.d.ts +45 -0
  107. package/dist/multiplexer/factory.d.ts +17 -0
  108. package/dist/multiplexer/herdr/index.d.ts +35 -0
  109. package/dist/multiplexer/index.d.ts +12 -0
  110. package/dist/multiplexer/session-manager.d.ts +69 -0
  111. package/dist/multiplexer/shared.d.ts +31 -0
  112. package/dist/multiplexer/tmux/index.d.ts +26 -0
  113. package/dist/multiplexer/types.d.ts +57 -0
  114. package/dist/multiplexer/zellij/index.d.ts +47 -0
  115. package/dist/tools/acp-run.d.ts +3 -0
  116. package/dist/tools/ast-grep/cli.d.ts +15 -0
  117. package/dist/tools/ast-grep/constants.d.ts +25 -0
  118. package/dist/tools/ast-grep/downloader.d.ts +5 -0
  119. package/dist/tools/ast-grep/index.d.ts +10 -0
  120. package/dist/tools/ast-grep/tools.d.ts +3 -0
  121. package/dist/tools/ast-grep/types.d.ts +30 -0
  122. package/dist/tools/ast-grep/utils.d.ts +4 -0
  123. package/dist/tools/cancel-task.d.ts +16 -0
  124. package/dist/tools/council.d.ts +10 -0
  125. package/dist/tools/index.d.ts +7 -0
  126. package/dist/tools/preset-manager.d.ts +26 -0
  127. package/dist/tools/smartfetch/binary.d.ts +3 -0
  128. package/dist/tools/smartfetch/cache.d.ts +6 -0
  129. package/dist/tools/smartfetch/constants.d.ts +12 -0
  130. package/dist/tools/smartfetch/index.d.ts +3 -0
  131. package/dist/tools/smartfetch/network.d.ts +38 -0
  132. package/dist/tools/smartfetch/secondary-model.d.ts +35 -0
  133. package/dist/tools/smartfetch/tool.d.ts +3 -0
  134. package/dist/tools/smartfetch/types.d.ts +122 -0
  135. package/dist/tools/smartfetch/utils.d.ts +20 -0
  136. package/dist/tui-state.d.ts +18 -0
  137. package/dist/tui.d.ts +13 -0
  138. package/dist/tui.js +1112 -0
  139. package/dist/utils/agent-variant.d.ts +23 -0
  140. package/dist/utils/background-job-board.d.ts +113 -0
  141. package/dist/utils/background-job-coordinator.d.ts +72 -0
  142. package/dist/utils/background-job-store.d.ts +46 -0
  143. package/dist/utils/compat.d.ts +29 -0
  144. package/dist/utils/councillor-models.d.ts +20 -0
  145. package/dist/utils/env.d.ts +3 -0
  146. package/dist/utils/escape-html.d.ts +1 -0
  147. package/dist/utils/frontmatter.d.ts +6 -0
  148. package/dist/utils/guards.d.ts +4 -0
  149. package/dist/utils/index.d.ts +10 -0
  150. package/dist/utils/internal-initiator.d.ts +11 -0
  151. package/dist/utils/logger.d.ts +6 -0
  152. package/dist/utils/polling.d.ts +21 -0
  153. package/dist/utils/session.d.ts +75 -0
  154. package/dist/utils/subagent-depth.d.ts +35 -0
  155. package/dist/utils/system-collapse.d.ts +6 -0
  156. package/dist/utils/task.d.ts +20 -0
  157. package/dist/utils/zip-extractor.d.ts +1 -0
  158. package/package.json +108 -0
  159. package/src/companion/companion-manifest.json +12 -0
  160. package/src/skills/clonedeps/README.md +23 -0
  161. package/src/skills/clonedeps/SKILL.md +240 -0
  162. package/src/skills/clonedeps/codemap.md +32 -0
  163. package/src/skills/codemap/README.md +59 -0
  164. package/src/skills/codemap/SKILL.md +163 -0
  165. package/src/skills/codemap/codemap.md +36 -0
  166. package/src/skills/codemap/scripts/codemap.mjs +483 -0
  167. package/src/skills/codemap/scripts/codemap.test.ts +129 -0
  168. package/src/skills/codemap.md +78 -0
  169. package/src/skills/deepwork/SKILL.md +122 -0
  170. package/src/skills/loop-engineering/SKILL.md +30 -0
  171. package/src/skills/oh-my-opencode-slim/SKILL.md +326 -0
  172. package/src/skills/reflect/SKILL.md +326 -0
  173. package/src/skills/simplify/README.md +19 -0
  174. package/src/skills/simplify/SKILL.md +138 -0
  175. package/src/skills/simplify/codemap.md +36 -0
  176. package/src/skills/verification-planning/SKILL.md +102 -0
  177. package/src/skills/worktrees/SKILL.md +171 -0
@@ -0,0 +1,483 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createHash } from 'node:crypto';
4
+ import {
5
+ existsSync,
6
+ mkdirSync,
7
+ readdirSync,
8
+ readFileSync,
9
+ renameSync,
10
+ statSync,
11
+ writeFileSync,
12
+ } from 'node:fs';
13
+ import path from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+
16
+ export const VERSION = '1.0.0';
17
+ export const STATE_DIR = '.slim';
18
+ export const STATE_FILE = 'codemap.json';
19
+ export const LEGACY_STATE_FILE = 'cartography.json';
20
+ export const CODEMAP_FILE = 'codemap.md';
21
+
22
+ export class PatternMatcher {
23
+ regex;
24
+
25
+ constructor(patterns) {
26
+ if (!patterns.length) {
27
+ this.regex = null;
28
+ return;
29
+ }
30
+
31
+ const regexParts = patterns.map((pattern) => {
32
+ let reg = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
33
+ reg = reg.replace(/\\\*\\\*\//g, '(?:.*/)?');
34
+ reg = reg.replace(/\\\*\\\*/g, '.*');
35
+ reg = reg.replace(/\\\*/g, '[^/]*');
36
+ reg = reg.replace(/\\\?/g, '.');
37
+
38
+ if (pattern.endsWith('/')) {
39
+ reg += '.*';
40
+ }
41
+
42
+ if (pattern.startsWith('/')) {
43
+ reg = `^${reg.slice(1)}`;
44
+ } else {
45
+ reg = `(?:^|.*/)${reg}`;
46
+ }
47
+
48
+ return `(?:${reg}$)`;
49
+ });
50
+
51
+ this.regex = new RegExp(regexParts.join('|'));
52
+ }
53
+
54
+ matches(filePath) {
55
+ if (!this.regex) return false;
56
+ return this.regex.test(filePath);
57
+ }
58
+ }
59
+
60
+ export function loadGitignore(root) {
61
+ const gitignorePath = path.join(root, '.gitignore');
62
+ if (!existsSync(gitignorePath)) return [];
63
+
64
+ return readFileSync(gitignorePath, 'utf8')
65
+ .split('\n')
66
+ .map((line) => line.trim())
67
+ .filter((line) => line && !line.startsWith('#'));
68
+ }
69
+
70
+ function walkFiles(root) {
71
+ const files = [];
72
+
73
+ function visit(currentDir) {
74
+ for (const entry of readdirSync(currentDir, { withFileTypes: true })) {
75
+ const fullPath = path.join(currentDir, entry.name);
76
+ if (entry.isDirectory()) {
77
+ if (!entry.name.startsWith('.')) {
78
+ visit(fullPath);
79
+ }
80
+ continue;
81
+ }
82
+
83
+ if (entry.isFile()) {
84
+ files.push(fullPath);
85
+ }
86
+ }
87
+ }
88
+
89
+ visit(root);
90
+ return files.sort();
91
+ }
92
+
93
+ export function selectFiles(
94
+ root,
95
+ includePatterns,
96
+ excludePatterns,
97
+ exceptions,
98
+ gitignorePatterns,
99
+ ) {
100
+ const includeMatcher = new PatternMatcher(includePatterns);
101
+ const excludeMatcher = new PatternMatcher(excludePatterns);
102
+ const gitignoreMatcher = new PatternMatcher(gitignorePatterns);
103
+ const exceptionSet = new Set(exceptions);
104
+
105
+ return walkFiles(root).filter((fullPath) => {
106
+ let relPath = path.relative(root, fullPath).replaceAll(path.sep, '/');
107
+ if (relPath.startsWith('./')) {
108
+ relPath = relPath.slice(2);
109
+ }
110
+
111
+ if (gitignoreMatcher.matches(relPath)) return false;
112
+ if (excludeMatcher.matches(relPath) && !exceptionSet.has(relPath)) {
113
+ return false;
114
+ }
115
+
116
+ return includeMatcher.matches(relPath) || exceptionSet.has(relPath);
117
+ });
118
+ }
119
+
120
+ export function computeFileHash(filePath) {
121
+ try {
122
+ const buffer = readFileSync(filePath);
123
+ return createHash('md5').update(buffer).digest('hex');
124
+ } catch {
125
+ return '';
126
+ }
127
+ }
128
+
129
+ export function computeFolderHash(folder, fileHashes) {
130
+ const folderFiles = Object.entries(fileHashes)
131
+ .filter(
132
+ ([filePath]) =>
133
+ filePath.startsWith(`${folder}/`) ||
134
+ (folder === '.' && !filePath.includes('/')),
135
+ )
136
+ .sort(([a], [b]) => a.localeCompare(b));
137
+
138
+ if (!folderFiles.length) return '';
139
+
140
+ const hasher = createHash('md5');
141
+ for (const [filePath, hash] of folderFiles) {
142
+ hasher.update(`${filePath}:${hash}\n`);
143
+ }
144
+ return hasher.digest('hex');
145
+ }
146
+
147
+ export function getFoldersWithFiles(files, root) {
148
+ const folders = new Set(['.']);
149
+
150
+ for (const filePath of files) {
151
+ const relPath = path.relative(root, filePath).replaceAll(path.sep, '/');
152
+ const parts = relPath.split('/').slice(0, -1);
153
+ for (let i = 0; i < parts.length; i++) {
154
+ folders.add(parts.slice(0, i + 1).join('/'));
155
+ }
156
+ }
157
+
158
+ return folders;
159
+ }
160
+
161
+ export function migrateLegacyState(root) {
162
+ const stateDir = path.join(root, STATE_DIR);
163
+ const legacyPath = path.join(stateDir, LEGACY_STATE_FILE);
164
+ const statePath = path.join(stateDir, STATE_FILE);
165
+
166
+ if (existsSync(statePath) || !existsSync(legacyPath)) {
167
+ return false;
168
+ }
169
+
170
+ mkdirSync(stateDir, { recursive: true });
171
+ renameSync(legacyPath, statePath);
172
+ console.log(
173
+ `Migrated ${STATE_DIR}/${LEGACY_STATE_FILE} -> ${STATE_DIR}/${STATE_FILE}`,
174
+ );
175
+ return true;
176
+ }
177
+
178
+ export function loadState(root) {
179
+ migrateLegacyState(root);
180
+ const statePath = path.join(root, STATE_DIR, STATE_FILE);
181
+ if (!existsSync(statePath)) return null;
182
+
183
+ try {
184
+ return JSON.parse(readFileSync(statePath, 'utf8'));
185
+ } catch {
186
+ return null;
187
+ }
188
+ }
189
+
190
+ export function saveState(root, state) {
191
+ const stateDir = path.join(root, STATE_DIR);
192
+ mkdirSync(stateDir, { recursive: true });
193
+ writeFileSync(
194
+ path.join(stateDir, STATE_FILE),
195
+ `${JSON.stringify(state, null, 2)}\n`,
196
+ );
197
+ }
198
+
199
+ export function createEmptyCodemap(folderPath, folderName) {
200
+ const codemapPath = path.join(folderPath, CODEMAP_FILE);
201
+ if (existsSync(codemapPath)) return;
202
+
203
+ const content = `# ${folderName}/
204
+
205
+ <!-- Fixer: Fill in this section with architectural understanding -->
206
+
207
+ ## Responsibility
208
+
209
+ <!-- What is this folder's job in the system? -->
210
+
211
+ ## Design
212
+
213
+ <!-- Key patterns, abstractions, architectural decisions -->
214
+
215
+ ## Flow
216
+
217
+ <!-- How does data/control flow through this module? -->
218
+
219
+ ## Integration
220
+
221
+ <!-- How does it connect to other parts of the system? -->
222
+ `;
223
+
224
+ writeFileSync(codemapPath, content);
225
+ }
226
+
227
+ function buildState(
228
+ root,
229
+ includePatterns,
230
+ excludePatterns,
231
+ exceptions,
232
+ selectedFiles,
233
+ ) {
234
+ const fileHashes = {};
235
+ for (const filePath of selectedFiles) {
236
+ const relPath = path.relative(root, filePath).replaceAll(path.sep, '/');
237
+ fileHashes[relPath] = computeFileHash(filePath);
238
+ }
239
+
240
+ const folders = getFoldersWithFiles(selectedFiles, root);
241
+ const folderHashes = {};
242
+ for (const folder of folders) {
243
+ folderHashes[folder] = computeFolderHash(folder, fileHashes);
244
+ }
245
+
246
+ const state = {
247
+ metadata: {
248
+ version: VERSION,
249
+ last_run: new Date().toISOString(),
250
+ root,
251
+ include_patterns: includePatterns,
252
+ exclude_patterns: excludePatterns,
253
+ exceptions,
254
+ },
255
+ file_hashes: fileHashes,
256
+ folder_hashes: folderHashes,
257
+ };
258
+
259
+ return { state, folders };
260
+ }
261
+
262
+ export function cmdInit({ root, include = [], exclude = [], exception = [] }) {
263
+ const resolvedRoot = path.resolve(root);
264
+ if (!existsSync(resolvedRoot) || !statSync(resolvedRoot).isDirectory()) {
265
+ console.error(`Error: ${resolvedRoot} is not a directory`);
266
+ return 1;
267
+ }
268
+
269
+ const includePatterns = include.length ? include : ['**/*'];
270
+ const excludePatterns = exclude;
271
+ const exceptions = exception;
272
+ const gitignore = loadGitignore(resolvedRoot);
273
+
274
+ console.log(`Scanning ${resolvedRoot}...`);
275
+ console.log(`Include patterns: ${JSON.stringify(includePatterns)}`);
276
+ console.log(`Exclude patterns: ${JSON.stringify(excludePatterns)}`);
277
+ console.log(`Exceptions: ${JSON.stringify(exceptions)}`);
278
+
279
+ const selectedFiles = selectFiles(
280
+ resolvedRoot,
281
+ includePatterns,
282
+ excludePatterns,
283
+ exceptions,
284
+ gitignore,
285
+ );
286
+
287
+ console.log(`Selected ${selectedFiles.length} files`);
288
+
289
+ const { state, folders } = buildState(
290
+ resolvedRoot,
291
+ includePatterns,
292
+ excludePatterns,
293
+ exceptions,
294
+ selectedFiles,
295
+ );
296
+
297
+ saveState(resolvedRoot, state);
298
+ console.log(`Created ${STATE_DIR}/${STATE_FILE}`);
299
+
300
+ for (const folder of folders) {
301
+ const folderPath =
302
+ folder === '.' ? resolvedRoot : path.join(resolvedRoot, folder);
303
+ const folderName = folder === '.' ? path.basename(resolvedRoot) : folder;
304
+ createEmptyCodemap(folderPath, folderName);
305
+ }
306
+
307
+ console.log(`Created ${folders.size} empty codemap.md files`);
308
+ return 0;
309
+ }
310
+
311
+ export function cmdChanges({ root }) {
312
+ const resolvedRoot = path.resolve(root);
313
+ const state = loadState(resolvedRoot);
314
+ if (!state) {
315
+ console.error("No codemap state found. Run 'init' first.");
316
+ return 1;
317
+ }
318
+
319
+ const metadata = state.metadata ?? {};
320
+ const includePatterns = metadata.include_patterns ?? ['**/*'];
321
+ const excludePatterns = metadata.exclude_patterns ?? [];
322
+ const exceptions = metadata.exceptions ?? [];
323
+ const gitignore = loadGitignore(resolvedRoot);
324
+
325
+ const currentFiles = selectFiles(
326
+ resolvedRoot,
327
+ includePatterns,
328
+ excludePatterns,
329
+ exceptions,
330
+ gitignore,
331
+ );
332
+
333
+ const currentHashes = Object.fromEntries(
334
+ currentFiles.map((filePath) => [
335
+ path.relative(resolvedRoot, filePath).replaceAll(path.sep, '/'),
336
+ computeFileHash(filePath),
337
+ ]),
338
+ );
339
+
340
+ const savedHashes = state.file_hashes ?? {};
341
+ const currentPaths = new Set(Object.keys(currentHashes));
342
+ const savedPaths = new Set(Object.keys(savedHashes));
343
+
344
+ const added = [...currentPaths]
345
+ .filter((filePath) => !savedPaths.has(filePath))
346
+ .sort();
347
+ const removed = [...savedPaths]
348
+ .filter((filePath) => !currentPaths.has(filePath))
349
+ .sort();
350
+ const modified = [...currentPaths]
351
+ .filter((filePath) => savedPaths.has(filePath))
352
+ .filter((filePath) => currentHashes[filePath] !== savedHashes[filePath])
353
+ .sort();
354
+
355
+ if (!added.length && !removed.length && !modified.length) {
356
+ console.log('No changes detected.');
357
+ return 0;
358
+ }
359
+
360
+ if (added.length) {
361
+ console.log(`\n${added.length} added:`);
362
+ for (const filePath of added) console.log(` + ${filePath}`);
363
+ }
364
+
365
+ if (removed.length) {
366
+ console.log(`\n${removed.length} removed:`);
367
+ for (const filePath of removed) console.log(` - ${filePath}`);
368
+ }
369
+
370
+ if (modified.length) {
371
+ console.log(`\n${modified.length} modified:`);
372
+ for (const filePath of modified) console.log(` ~ ${filePath}`);
373
+ }
374
+
375
+ const affectedFolders = new Set(['.']);
376
+ for (const filePath of [...added, ...removed, ...modified]) {
377
+ const parts = filePath.split('/').slice(0, -1);
378
+ for (let i = 0; i < parts.length; i++) {
379
+ affectedFolders.add(parts.slice(0, i + 1).join('/'));
380
+ }
381
+ }
382
+
383
+ const sortedFolders = [...affectedFolders].sort();
384
+ console.log(`\n${sortedFolders.length} folders affected:`);
385
+ for (const folder of sortedFolders) {
386
+ console.log(` ${folder}/`);
387
+ }
388
+
389
+ return 0;
390
+ }
391
+
392
+ export function cmdUpdate({ root }) {
393
+ const resolvedRoot = path.resolve(root);
394
+ const state = loadState(resolvedRoot);
395
+ if (!state) {
396
+ console.error("No codemap state found. Run 'init' first.");
397
+ return 1;
398
+ }
399
+
400
+ const metadata = state.metadata ?? {};
401
+ const includePatterns = metadata.include_patterns ?? ['**/*'];
402
+ const excludePatterns = metadata.exclude_patterns ?? [];
403
+ const exceptions = metadata.exceptions ?? [];
404
+ const gitignore = loadGitignore(resolvedRoot);
405
+
406
+ const selectedFiles = selectFiles(
407
+ resolvedRoot,
408
+ includePatterns,
409
+ excludePatterns,
410
+ exceptions,
411
+ gitignore,
412
+ );
413
+
414
+ const { state: nextState } = buildState(
415
+ resolvedRoot,
416
+ includePatterns,
417
+ excludePatterns,
418
+ exceptions,
419
+ selectedFiles,
420
+ );
421
+
422
+ saveState(resolvedRoot, nextState);
423
+ console.log(
424
+ `Updated ${STATE_DIR}/${STATE_FILE} with ${selectedFiles.length} files`,
425
+ );
426
+ return 0;
427
+ }
428
+
429
+ export function parseArgs(argv) {
430
+ const [command, ...rest] = argv;
431
+ const options = { include: [], exclude: [], exception: [] };
432
+
433
+ for (let i = 0; i < rest.length; i++) {
434
+ const arg = rest[i];
435
+ const value = rest[i + 1];
436
+
437
+ if (!arg?.startsWith('--')) continue;
438
+ if (value === undefined || value.startsWith('--')) {
439
+ throw new Error(`Missing value for ${arg}`);
440
+ }
441
+
442
+ const key = arg.slice(2);
443
+ if (key === 'include' || key === 'exclude' || key === 'exception') {
444
+ options[key].push(value);
445
+ } else if (key === 'root') {
446
+ options.root = value;
447
+ } else {
448
+ throw new Error(`Unknown option: ${arg}`);
449
+ }
450
+
451
+ i++;
452
+ }
453
+
454
+ return { command, options };
455
+ }
456
+
457
+ export function main(argv = process.argv.slice(2)) {
458
+ try {
459
+ const { command, options } = parseArgs(argv);
460
+
461
+ if (!command || !options.root) {
462
+ console.error(
463
+ 'Usage: codemap.mjs <init|changes|update> --root /path [--include glob] [--exclude glob] [--exception path]',
464
+ );
465
+ return 1;
466
+ }
467
+
468
+ if (command === 'init') return cmdInit(options);
469
+ if (command === 'changes') return cmdChanges(options);
470
+ if (command === 'update') return cmdUpdate(options);
471
+
472
+ console.error(`Unknown command: ${command}`);
473
+ return 1;
474
+ } catch (error) {
475
+ console.error(error instanceof Error ? error.message : String(error));
476
+ return 1;
477
+ }
478
+ }
479
+
480
+ const currentFilePath = fileURLToPath(import.meta.url);
481
+ if (process.argv[1] && path.resolve(process.argv[1]) === currentFilePath) {
482
+ process.exit(main());
483
+ }
@@ -0,0 +1,129 @@
1
+ import { afterEach, describe, expect, mock, test } from 'bun:test';
2
+ import {
3
+ existsSync,
4
+ mkdirSync,
5
+ mkdtempSync,
6
+ readFileSync,
7
+ rmSync,
8
+ writeFileSync,
9
+ } from 'node:fs';
10
+ import os from 'node:os';
11
+ import path from 'node:path';
12
+
13
+ mock.restore();
14
+
15
+ const {
16
+ computeFileHash,
17
+ computeFolderHash,
18
+ loadState,
19
+ PatternMatcher,
20
+ selectFiles,
21
+ } = await import('./codemap.mjs');
22
+
23
+ const tempDirs: string[] = [];
24
+
25
+ function createTempDir() {
26
+ const dir = mkdtempSync(path.join(os.tmpdir(), 'codemap-'));
27
+ tempDirs.push(dir);
28
+ return dir;
29
+ }
30
+
31
+ afterEach(() => {
32
+ for (const dir of tempDirs.splice(0)) {
33
+ rmSync(dir, { force: true, recursive: true });
34
+ }
35
+ });
36
+
37
+ describe('PatternMatcher', () => {
38
+ test('matches expected paths', () => {
39
+ const matcher = new PatternMatcher([
40
+ 'node_modules/',
41
+ 'dist/',
42
+ '*.log',
43
+ 'src/**/*.ts',
44
+ ]);
45
+
46
+ expect(matcher.matches('node_modules/foo.js')).toBe(true);
47
+ expect(matcher.matches('vendor/node_modules/bar.js')).toBe(true);
48
+ expect(matcher.matches('dist/main.js')).toBe(true);
49
+ expect(matcher.matches('src/dist/output.js')).toBe(true);
50
+ expect(matcher.matches('error.log')).toBe(true);
51
+ expect(matcher.matches('logs/access.log')).toBe(true);
52
+ expect(matcher.matches('src/index.ts')).toBe(true);
53
+ expect(matcher.matches('src/utils/helper.ts')).toBe(true);
54
+ expect(matcher.matches('README.md')).toBe(false);
55
+ expect(matcher.matches('tests/test.py')).toBe(false);
56
+ });
57
+ });
58
+
59
+ describe('hash helpers', () => {
60
+ test('computes file hash', () => {
61
+ const dir = createTempDir();
62
+ const filePath = path.join(dir, 'file.txt');
63
+ writeFileSync(filePath, 'test content');
64
+
65
+ expect(computeFileHash(filePath)).toBe('9473fdd0d880a43c21b7778d34872157');
66
+ });
67
+
68
+ test('computes stable folder hash', () => {
69
+ const fileHashes = {
70
+ 'src/a.ts': 'hash-a',
71
+ 'src/b.ts': 'hash-b',
72
+ 'tests/test.ts': 'hash-test',
73
+ };
74
+
75
+ const hash1 = computeFolderHash('src', fileHashes);
76
+ const hash2 = computeFolderHash('src', fileHashes);
77
+ const hash3 = computeFolderHash('src', {
78
+ 'src/a.ts': 'hash-a-modified',
79
+ 'src/b.ts': 'hash-b',
80
+ });
81
+
82
+ expect(hash1).toBe(hash2);
83
+ expect(hash1).not.toBe(hash3);
84
+ });
85
+ });
86
+
87
+ describe('selectFiles', () => {
88
+ test('respects include and exclude patterns', () => {
89
+ const root = createTempDir();
90
+ mkdirSync(path.join(root, 'src'));
91
+ mkdirSync(path.join(root, 'node_modules'));
92
+ writeFileSync(path.join(root, 'src', 'index.ts'), 'code');
93
+ writeFileSync(path.join(root, 'src', 'index.test.ts'), 'test');
94
+ writeFileSync(path.join(root, 'node_modules', 'foo.js'), 'dep');
95
+ writeFileSync(path.join(root, 'package.json'), '{}');
96
+
97
+ const selected = selectFiles(
98
+ root,
99
+ ['src/**/*.ts', 'package.json'],
100
+ ['**/*.test.ts', 'node_modules/'],
101
+ [],
102
+ [],
103
+ ).map((filePath) =>
104
+ path.relative(root, filePath).split(path.sep).join('/'),
105
+ );
106
+
107
+ expect(selected).toEqual(['package.json', 'src/index.ts']);
108
+ });
109
+ });
110
+
111
+ describe('loadState', () => {
112
+ test('migrates legacy cartography state', () => {
113
+ const root = createTempDir();
114
+ const slimDir = path.join(root, '.slim');
115
+ mkdirSync(slimDir);
116
+
117
+ const legacyState = { metadata: { version: '1.0.0' } };
118
+ writeFileSync(
119
+ path.join(slimDir, 'cartography.json'),
120
+ JSON.stringify(legacyState),
121
+ );
122
+
123
+ expect(loadState(root)).toEqual(legacyState);
124
+ expect(existsSync(path.join(slimDir, 'cartography.json'))).toBe(false);
125
+ expect(
126
+ JSON.parse(readFileSync(path.join(slimDir, 'codemap.json'), 'utf8')),
127
+ ).toEqual(legacyState);
128
+ });
129
+ });
@@ -0,0 +1,78 @@
1
+ # src/skills/
2
+
3
+ ## Responsibility
4
+
5
+ - Owns metadata-driven OpenCode custom skills shipped with this package
6
+ - Maintains the skill contract artifacts (`SKILL.md`, `README.md`, per-skill helper files) that are copied into `${configDir}/skills` at install time
7
+ - Preserves a canonical registry boundary: runtime code consumes skill definitions as data, not as executable plugin dependencies
8
+ - Skills are partitioned into orchestrator-only workflows and general-purpose skills for broad reuse
9
+
10
+ ## Design
11
+
12
+ ### Skill Registry
13
+
14
+ - `CUSTOM_SKILLS` in `src/cli/custom-skills-registry.ts` is the authoritative skill manifest for bundled skills
15
+ - Each entry maps folder name + `sourcePath` to an install-time consumer
16
+ - Skills are categorized by purpose and access scope:
17
+
18
+ | Skill | Type | Purpose |
19
+ |-------|------|-------|
20
+ | `codemap/` | General-purpose | Repository mapping and codebase documentation skill |
21
+ | `clonedeps/` | General-purpose | Workflow skill for dependency source mirroring and inspection |
22
+ | `simplify/` | General-purpose | Readability and maintainability guidance skill |
23
+ | `deepwork/` | Orchestrator-only | Heavy coding sessions, multi-phase implementation, and risky refactors |
24
+ | `verification-planning/` | Orchestrator-only | Project-specific evidence planning and verification affordances before non-trivial implementation |
25
+ | `reflect/` | Orchestrator-only | Learning from repeated work and suggesting reusable improvements |
26
+ | `worktrees/` | Orchestrator-only | Safe Git worktree lanes for parallel, risky, or isolated work |
27
+ | `oh-my-opencode-slim/` | Orchestrator-only | Plugin configuration and self-improvement guidance |
28
+
29
+ ### Installation Pipeline
30
+
31
+ - `install.ts` runs `installCustomSkill()` which recursively copies bundled skill directories into the OpenCode skills directory
32
+ - During plugin release, the `files` whitelist in `package.json` must include `src/skills` so `src/skills/**` survive `npm pack`
33
+ - OpenCode plugin startup discovers these installed folders and reads each `SKILL.md` as a prompt-level contract
34
+
35
+ ### Runtime Consumption
36
+
37
+ - Files are considered static runtime payload
38
+ - No plugin TS module in `src/` imports these files directly
39
+ - They are loaded by OpenCode via filesystem installation at runtime
40
+
41
+ ## Flow
42
+
43
+ 1. **Skill Discovery**: `src/cli/custom-skills-registry.ts` defines `CUSTOM_SKILLS` array with skill metadata
44
+ 2. **Installation**: `bun run install` delegates to `src/cli/install.ts`, where `installCustomSkills()` gates copying of each `CUSTOM_SKILLS` entry
45
+ 3. **Validation**: `installCustomSkill()` computes `packageRoot`, validates `sourcePath`, then performs a recursive directory copy via `copyDirRecursive()`
46
+ 4. **Distribution**: During plugin release, `package.json` `files` whitelist ensures `src/skills/**` are included in the published tarball
47
+ 5. **Runtime Discovery**: OpenCode plugin startup discovers installed skill folders and reads each `SKILL.md` as a prompt-level contract
48
+
49
+ ## Integration
50
+
51
+ ### Build & Release Dependencies
52
+
53
+ - `src/cli/custom-skills-registry.ts`: Source-of-truth registry consumed by installer and permission helpers
54
+ - `src/cli/install.ts`: Contains `installCustomSkills()` and `installCustomSkill()` functions
55
+ - `verify-release-artifact.ts`: Enforces artifact completeness by asserting key bundled skill payloads are present in the tarball:
56
+ - `src/skills/simplify/SKILL.md`
57
+ - `src/skills/codemap/SKILL.md`
58
+ - `src/skills/clonedeps/SKILL.md`
59
+ - `src/skills/deepwork/SKILL.md`
60
+ - `src/skills/verification-planning/SKILL.md`
61
+ - `src/skills/reflect/SKILL.md`
62
+ - `src/skills/worktrees/SKILL.md`
63
+ - `src/skills/oh-my-opencode-slim/SKILL.md`
64
+ - `package.json` scripts (`verify:release`, `build`) rely on these assets to ensure install-time skill availability
65
+
66
+ ### Permission System
67
+
68
+ - `src/cli/skills.ts:getSkillPermissionsForAgent()` auto-populates permission rules for bundled skills when agent policy is derived from built-in recommendations
69
+ - Bundled skills are treated as data payloads with explicit permission boundaries defined in the skill manifests
70
+
71
+ ### Skill Contracts
72
+
73
+ Each skill directory contains:
74
+ - `SKILL.md`: Skill contract defining name, description, and usage contract
75
+ - `README.md`: Documentation and examples for the skill
76
+ - Optional helper files and subdirectories for skill-specific functionality
77
+
78
+ These artifacts are copied verbatim to the OpenCode skills directory during installation and serve as the skill's interface definition.