oc-tweaks 0.11.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli/init.js +311 -6
  2. package/dist/index.js +3709 -3487
  3. package/package.json +1 -1
package/dist/cli/init.js CHANGED
@@ -4,6 +4,282 @@
4
4
  // src/cli/init.ts
5
5
  import { mkdir } from "fs/promises";
6
6
  import { dirname } from "path";
7
+
8
+ // src/plugins/auto-memory/diag.ts
9
+ import { statSync } from "node:fs";
10
+ import { join as join2 } from "node:path";
11
+
12
+ // src/plugins/auto-memory/registry.ts
13
+ import { closeSync, openSync, readdirSync, readSync } from "node:fs";
14
+ import { join } from "node:path";
15
+
16
+ // src/plugins/auto-memory/frontmatter.ts
17
+ class MemoryFrontmatterParseError extends Error {
18
+ cause;
19
+ constructor(message, cause) {
20
+ super(message);
21
+ this.cause = cause;
22
+ this.name = "MemoryFrontmatterParseError";
23
+ }
24
+ }
25
+ var DEFAULT_META = {
26
+ id: "",
27
+ scope: "global",
28
+ type: "note",
29
+ source: "user",
30
+ created_at: "",
31
+ updated_at: "",
32
+ trusted_as_instruction: false
33
+ };
34
+ function parseMinimalYaml(yaml) {
35
+ const result = {};
36
+ for (const rawLine of yaml.split(`
37
+ `)) {
38
+ const line = rawLine.trim();
39
+ if (!line || line.startsWith("#"))
40
+ continue;
41
+ const colonIdx = line.indexOf(":");
42
+ if (colonIdx === -1)
43
+ continue;
44
+ const key = line.slice(0, colonIdx).trim();
45
+ const rawVal = line.slice(colonIdx + 1).trim();
46
+ if (!key)
47
+ continue;
48
+ if (/[\[{]/.test(rawVal) && !/[\]}]/.test(rawVal)) {
49
+ throw new MemoryFrontmatterParseError(`Invalid YAML value for key "${key}": unclosed bracket/brace`);
50
+ }
51
+ result[key] = parseYamlValue(rawVal);
52
+ }
53
+ return result;
54
+ }
55
+ function parseYamlValue(raw) {
56
+ if (raw.startsWith('"') && raw.endsWith('"') || raw.startsWith("'") && raw.endsWith("'")) {
57
+ return raw.slice(1, -1);
58
+ }
59
+ if (raw === "false")
60
+ return false;
61
+ if (raw === "true")
62
+ return true;
63
+ if (/^-?\d+$/.test(raw))
64
+ return parseInt(raw, 10);
65
+ return raw;
66
+ }
67
+ function parseFrontmatter(raw) {
68
+ const stripped = raw.startsWith("\uFEFF") ? raw.slice(1) : raw;
69
+ if (!stripped.startsWith("---")) {
70
+ return { meta: { ...DEFAULT_META }, body: raw };
71
+ }
72
+ const afterOpen = stripped.slice(3);
73
+ const closeIdx = afterOpen.indexOf(`
74
+ ---`);
75
+ if (closeIdx === -1) {
76
+ return { meta: { ...DEFAULT_META }, body: raw };
77
+ }
78
+ const yamlBlock = afterOpen.slice(0, closeIdx);
79
+ const afterClose = afterOpen.slice(closeIdx + 4);
80
+ const body = afterClose.startsWith(`
81
+ `) ? afterClose.slice(1) : afterClose;
82
+ let parsed;
83
+ try {
84
+ parsed = parseMinimalYaml(yamlBlock);
85
+ } catch (e) {
86
+ if (e instanceof MemoryFrontmatterParseError)
87
+ throw e;
88
+ throw new MemoryFrontmatterParseError("Failed to parse frontmatter YAML", e);
89
+ }
90
+ const meta = {
91
+ ...DEFAULT_META,
92
+ ...parsed.id !== undefined && { id: String(parsed.id) },
93
+ ...parsed.scope !== undefined && { scope: parsed.scope },
94
+ ...parsed.type !== undefined && { type: String(parsed.type) },
95
+ ...parsed.source !== undefined && { source: String(parsed.source) },
96
+ ...parsed.created_at !== undefined && { created_at: String(parsed.created_at) },
97
+ ...parsed.updated_at !== undefined && { updated_at: String(parsed.updated_at) },
98
+ trusted_as_instruction: false,
99
+ ...parsed.summary !== undefined && { summary: String(parsed.summary) },
100
+ ...parsed.disabled !== undefined && { disabled: Boolean(parsed.disabled) },
101
+ ...parsed.usage_count !== undefined && { usage_count: Number(parsed.usage_count) },
102
+ ...parsed.last_usage !== undefined && { last_usage: String(parsed.last_usage) }
103
+ };
104
+ return { meta, body };
105
+ }
106
+
107
+ // src/plugins/auto-memory/registry.ts
108
+ var SKIP_PATTERNS = [
109
+ /^readme/i,
110
+ /^\.ds_store$/i,
111
+ /\.swp$/,
112
+ /\.lock$/,
113
+ /^\.gitignore$/,
114
+ /^\.gitkeep$/
115
+ ];
116
+ function shouldSkip(filename) {
117
+ return SKIP_PATTERNS.some((p) => p.test(filename));
118
+ }
119
+ var MAX_READ_BYTES = 2048;
120
+ function readPartial(absPath) {
121
+ const fd = openSync(absPath, "r");
122
+ try {
123
+ const buf = Buffer.alloc(MAX_READ_BYTES);
124
+ const bytesRead = readSync(fd, buf, 0, MAX_READ_BYTES, 0);
125
+ return buf.subarray(0, bytesRead).toString("utf8");
126
+ } finally {
127
+ closeSync(fd);
128
+ }
129
+ }
130
+ var MAX_SUMMARY_CHARS = 240;
131
+ var MAX_SUMMARY_LINES = 5;
132
+ function extractSummary(meta, body) {
133
+ if (meta.summary) {
134
+ return meta.summary.slice(0, MAX_SUMMARY_CHARS);
135
+ }
136
+ const lines = body.split(`
137
+ `);
138
+ const collected = [];
139
+ for (const line of lines) {
140
+ if (collected.length >= MAX_SUMMARY_LINES)
141
+ break;
142
+ if (collected.length > 0 && line.trim() === "")
143
+ break;
144
+ collected.push(line);
145
+ }
146
+ return collected.join(`
147
+ `).slice(0, MAX_SUMMARY_CHARS);
148
+ }
149
+ function scanDir(dir, scope) {
150
+ const map = new Map;
151
+ let filenames;
152
+ try {
153
+ filenames = readdirSync(dir, { withFileTypes: false });
154
+ } catch {
155
+ return map;
156
+ }
157
+ for (const filename of filenames) {
158
+ if (typeof filename !== "string")
159
+ continue;
160
+ if (!filename.endsWith(".md"))
161
+ continue;
162
+ if (shouldSkip(filename))
163
+ continue;
164
+ const absPath = join(dir, filename);
165
+ let partial;
166
+ try {
167
+ partial = readPartial(absPath);
168
+ } catch {
169
+ continue;
170
+ }
171
+ const { meta, body } = parseFrontmatter(partial);
172
+ const summary = extractSummary(meta, body);
173
+ const tokenEstimate = Math.ceil((summary.length + JSON.stringify(meta).length) / 4);
174
+ const entry = {
175
+ meta,
176
+ absPath,
177
+ scope,
178
+ tokenEstimate,
179
+ summary
180
+ };
181
+ const key = meta.id || filename;
182
+ map.set(key, entry);
183
+ }
184
+ return map;
185
+ }
186
+ function scanMemoryRoots(globalDir, projectDir) {
187
+ const globalMap = scanDir(globalDir, "global");
188
+ const projectMap = scanDir(projectDir, "project");
189
+ const merged = new Map([...globalMap, ...projectMap]);
190
+ return Array.from(merged.values());
191
+ }
192
+
193
+ // src/plugins/auto-memory/diag.ts
194
+ function resolveRoots(opts) {
195
+ if (opts.root) {
196
+ return {
197
+ globalDir: join2(opts.root, "global"),
198
+ projectDir: join2(opts.root, "project", ".opencode", "memory")
199
+ };
200
+ }
201
+ return {
202
+ globalDir: opts.globalRoot ?? "",
203
+ projectDir: opts.projectRoot ?? ""
204
+ };
205
+ }
206
+ function fileSizeBytes(absPath) {
207
+ try {
208
+ return statSync(absPath).size;
209
+ } catch {
210
+ return 0;
211
+ }
212
+ }
213
+ async function runDiag(opts) {
214
+ const { globalDir, projectDir } = resolveRoots(opts);
215
+ const allEntries = scanMemoryRoots(globalDir, projectDir);
216
+ const globalEntries = allEntries.filter((e) => e.scope === "global");
217
+ const projectEntries = allEntries.filter((e) => e.scope === "project");
218
+ function scopeStats(entries, scope, root) {
219
+ const totalBytes2 = entries.reduce((s, e) => s + fileSizeBytes(e.absPath), 0);
220
+ const estimatedTokens = entries.reduce((s, e) => s + e.tokenEstimate, 0);
221
+ return { scope, root, fileCount: entries.length, totalBytes: totalBytes2, estimatedTokens };
222
+ }
223
+ const scopes = [
224
+ scopeStats(globalEntries, "global", globalDir),
225
+ scopeStats(projectEntries, "project", projectDir)
226
+ ];
227
+ const totalBytes = scopes.reduce((s, sc) => s + sc.totalBytes, 0);
228
+ const totalEstimatedTokens = scopes.reduce((s, sc) => s + sc.estimatedTokens, 0);
229
+ const top5ByUsageCount = [...allEntries].sort((a, b) => (b.meta.usage_count ?? 0) - (a.meta.usage_count ?? 0)).slice(0, 5);
230
+ const top5ByUpdatedAt = [...allEntries].sort((a, b) => (b.meta.updated_at ?? "").localeCompare(a.meta.updated_at ?? "")).slice(0, 5);
231
+ return {
232
+ roots: { global: globalDir, project: projectDir },
233
+ scopes,
234
+ totalFiles: allEntries.length,
235
+ totalBytes,
236
+ totalEstimatedTokens,
237
+ top5ByUsageCount,
238
+ top5ByUpdatedAt,
239
+ autoWriteEvents: "no buffer in this session"
240
+ };
241
+ }
242
+ function formatDiagReport(report) {
243
+ const lines = [];
244
+ lines.push("=== memory diag ===");
245
+ lines.push("");
246
+ lines.push("memory roots:");
247
+ lines.push(` global: ${report.roots.global}`);
248
+ lines.push(` project: ${report.roots.project}`);
249
+ lines.push("");
250
+ lines.push("file count per scope:");
251
+ for (const sc of report.scopes) {
252
+ lines.push(` ${sc.scope.padEnd(8)} root=${sc.root} files=${sc.fileCount} bytes=${sc.totalBytes} estimated tokens=${sc.estimatedTokens}`);
253
+ }
254
+ lines.push(` total files=${report.totalFiles} bytes=${report.totalBytes} estimated tokens=${report.totalEstimatedTokens}`);
255
+ lines.push("");
256
+ lines.push("estimated tokens (total): " + report.totalEstimatedTokens);
257
+ lines.push("");
258
+ lines.push("top 5 by usage_count (desc):");
259
+ if (report.top5ByUsageCount.length === 0) {
260
+ lines.push(" (none)");
261
+ } else {
262
+ for (const e of report.top5ByUsageCount) {
263
+ lines.push(` [${e.scope}] ${e.meta.id || e.absPath} usage_count=${e.meta.usage_count ?? 0} updated_at=${e.meta.updated_at}`);
264
+ }
265
+ }
266
+ lines.push("");
267
+ lines.push("top 5 by updated_at (desc):");
268
+ if (report.top5ByUpdatedAt.length === 0) {
269
+ lines.push(" (none)");
270
+ } else {
271
+ for (const e of report.top5ByUpdatedAt) {
272
+ lines.push(` [${e.scope}] ${e.meta.id || e.absPath} updated_at=${e.meta.updated_at} usage_count=${e.meta.usage_count ?? 0}`);
273
+ }
274
+ }
275
+ lines.push("");
276
+ lines.push("auto-write events: " + report.autoWriteEvents);
277
+ return lines.join(`
278
+ `);
279
+ }
280
+ if (false) {}
281
+
282
+ // src/cli/init.ts
7
283
  var DEFAULT_CONFIG = {
8
284
  notify: { enabled: true },
9
285
  compaction: { enabled: true },
@@ -25,18 +301,47 @@ async function initConfig() {
25
301
  `);
26
302
  return { created: true, path: configPath };
27
303
  }
304
+ function parseCliArgs(argv) {
305
+ const args = argv[0] === "tweaks" ? argv.slice(1) : argv;
306
+ if (args.length === 0 || args[0] === "init")
307
+ return { command: "init" };
308
+ if (args[0] === "memory" && args[1] === "diag") {
309
+ return { command: "memory-diag", opts: parseDiagOpts(args.slice(2)) };
310
+ }
311
+ return { command: "init" };
312
+ }
313
+ function parseDiagOpts(args) {
314
+ const opts = {};
315
+ for (let i = 0;i < args.length; i++) {
316
+ const arg = args[i];
317
+ if (arg === "--root" && args[i + 1])
318
+ opts.root = args[++i];
319
+ else if (arg === "--global-root" && args[i + 1])
320
+ opts.globalRoot = args[++i];
321
+ else if (arg === "--project-root" && args[i + 1])
322
+ opts.projectRoot = args[++i];
323
+ }
324
+ return opts;
325
+ }
28
326
  var isMain = typeof Bun !== "undefined" && Bun.main === import.meta.path;
29
327
  if (isMain) {
30
- const result = await initConfig();
31
- if (result.created) {
32
- console.log(`Created: ${result.path}`);
33
- console.log("All plugins configured. Edit the file to customize.");
328
+ const parsed = parseCliArgs(process.argv.slice(2));
329
+ if (parsed.command === "memory-diag") {
330
+ const report = await runDiag(parsed.opts);
331
+ console.log(formatDiagReport(report));
34
332
  } else {
35
- console.log(`Config already exists: ${result.path}`);
36
- console.log("Nothing changed. Edit the file manually to update your configuration.");
333
+ const result = await initConfig();
334
+ if (result.created) {
335
+ console.log(`Created: ${result.path}`);
336
+ console.log("All plugins configured. Edit the file to customize.");
337
+ } else {
338
+ console.log(`Config already exists: ${result.path}`);
339
+ console.log("Nothing changed. Edit the file manually to update your configuration.");
340
+ }
37
341
  }
38
342
  }
39
343
  export {
344
+ parseCliArgs,
40
345
  initConfig,
41
346
  DEFAULT_CONFIG
42
347
  };