opencode-diane 0.0.5

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 (80) hide show
  1. package/CHANGELOG.md +180 -0
  2. package/LICENSE +21 -0
  3. package/README.md +206 -0
  4. package/WIKI.md +1430 -0
  5. package/dist/index.d.ts +28 -0
  6. package/dist/index.js +1632 -0
  7. package/dist/ingest/adaptive.d.ts +47 -0
  8. package/dist/ingest/adaptive.js +182 -0
  9. package/dist/ingest/code-health.d.ts +58 -0
  10. package/dist/ingest/code-health.js +202 -0
  11. package/dist/ingest/code-map.d.ts +71 -0
  12. package/dist/ingest/code-map.js +670 -0
  13. package/dist/ingest/cross-refs.d.ts +59 -0
  14. package/dist/ingest/cross-refs.js +1207 -0
  15. package/dist/ingest/docs.d.ts +49 -0
  16. package/dist/ingest/docs.js +325 -0
  17. package/dist/ingest/git.d.ts +77 -0
  18. package/dist/ingest/git.js +390 -0
  19. package/dist/ingest/live-session.d.ts +101 -0
  20. package/dist/ingest/live-session.js +173 -0
  21. package/dist/ingest/project-notes.d.ts +28 -0
  22. package/dist/ingest/project-notes.js +102 -0
  23. package/dist/ingest/project.d.ts +35 -0
  24. package/dist/ingest/project.js +430 -0
  25. package/dist/ingest/session-snapshot.d.ts +63 -0
  26. package/dist/ingest/session-snapshot.js +94 -0
  27. package/dist/ingest/sessions.d.ts +29 -0
  28. package/dist/ingest/sessions.js +164 -0
  29. package/dist/ingest/tables.d.ts +52 -0
  30. package/dist/ingest/tables.js +360 -0
  31. package/dist/mining/skill-miner.d.ts +53 -0
  32. package/dist/mining/skill-miner.js +234 -0
  33. package/dist/search/bm25.d.ts +81 -0
  34. package/dist/search/bm25.js +334 -0
  35. package/dist/search/e5-embedder.d.ts +30 -0
  36. package/dist/search/e5-embedder.js +91 -0
  37. package/dist/search/embed-pass.d.ts +26 -0
  38. package/dist/search/embed-pass.js +43 -0
  39. package/dist/search/embedder.d.ts +58 -0
  40. package/dist/search/embedder.js +85 -0
  41. package/dist/search/inverted-index.d.ts +51 -0
  42. package/dist/search/inverted-index.js +139 -0
  43. package/dist/search/ppr.d.ts +44 -0
  44. package/dist/search/ppr.js +118 -0
  45. package/dist/search/tokenize.d.ts +26 -0
  46. package/dist/search/tokenize.js +98 -0
  47. package/dist/store/eviction.d.ts +16 -0
  48. package/dist/store/eviction.js +37 -0
  49. package/dist/store/repository.d.ts +222 -0
  50. package/dist/store/repository.js +420 -0
  51. package/dist/store/sqlite-store.d.ts +89 -0
  52. package/dist/store/sqlite-store.js +252 -0
  53. package/dist/store/vector-store.d.ts +66 -0
  54. package/dist/store/vector-store.js +160 -0
  55. package/dist/types.d.ts +385 -0
  56. package/dist/types.js +9 -0
  57. package/dist/utils/file-log.d.ts +87 -0
  58. package/dist/utils/file-log.js +215 -0
  59. package/dist/utils/peer-detection.d.ts +45 -0
  60. package/dist/utils/peer-detection.js +90 -0
  61. package/dist/utils/shell.d.ts +43 -0
  62. package/dist/utils/shell.js +110 -0
  63. package/dist/utils/usage-skill.d.ts +42 -0
  64. package/dist/utils/usage-skill.js +129 -0
  65. package/dist/utils/xlsx.d.ts +36 -0
  66. package/dist/utils/xlsx.js +270 -0
  67. package/grammars/tree-sitter-c.wasm +0 -0
  68. package/grammars/tree-sitter-c_sharp.wasm +0 -0
  69. package/grammars/tree-sitter-cpp.wasm +0 -0
  70. package/grammars/tree-sitter-css.wasm +0 -0
  71. package/grammars/tree-sitter-go.wasm +0 -0
  72. package/grammars/tree-sitter-html.wasm +0 -0
  73. package/grammars/tree-sitter-java.wasm +0 -0
  74. package/grammars/tree-sitter-javascript.wasm +0 -0
  75. package/grammars/tree-sitter-json.wasm +0 -0
  76. package/grammars/tree-sitter-php.wasm +0 -0
  77. package/grammars/tree-sitter-python.wasm +0 -0
  78. package/grammars/tree-sitter-rust.wasm +0 -0
  79. package/grammars/tree-sitter-typescript.wasm +0 -0
  80. package/package.json +80 -0
@@ -0,0 +1,36 @@
1
+ /**
2
+ * xlsx.ts — minimal XLSX reader, header-row only.
3
+ *
4
+ * XLSX is a ZIP archive of XML files. We need exactly two of them:
5
+ * - `xl/worksheets/sheet1.xml` — cell data
6
+ * - `xl/sharedStrings.xml` — string table (cells of type "s" are
7
+ * indices into this; not all sheets
8
+ * use it, so it's optional)
9
+ *
10
+ * This file implements the smallest possible code path to extract the
11
+ * first row's cell values:
12
+ *
13
+ * 1. Find the End-of-Central-Directory record at the file tail.
14
+ * 2. Read the Central Directory entries to locate our two files.
15
+ * 3. For each, seek to the local file header, read the compressed
16
+ * data, and `inflateRaw` it to text.
17
+ * 4. Pull the first `<row>` from the sheet XML, walk its `<c>`
18
+ * cells, resolving shared-string indices against the table.
19
+ *
20
+ * **Deliberately limited scope.** Standard PKZIP layout only — no
21
+ * ZIP64 (sheets >4 GiB), no encryption, no compression method other
22
+ * than `stored` (0) or `deflated` (8). Spreadsheets violating any of
23
+ * those return `null` and the caller skips the file. Failing closed
24
+ * is correct here: we promise header discoverability, not row data.
25
+ *
26
+ * **No external dependencies.** This module uses only `node:fs/promises`
27
+ * and `node:zlib`. SheetJS/exceljs would handle every edge case but
28
+ * add 1-2 MB of dependency weight for what is a niche feature in a
29
+ * source-code-indexer. The trade is intentional and documented.
30
+ */
31
+ /**
32
+ * Public entry point. Returns the first row's cell values, or null
33
+ * if the file isn't a readable XLSX, the sheet is empty, or anything
34
+ * about the structure trips the limits above. Never throws.
35
+ */
36
+ export declare function readXlsxFirstRow(absPath: string): Promise<string[] | null>;
@@ -0,0 +1,270 @@
1
+ /**
2
+ * xlsx.ts — minimal XLSX reader, header-row only.
3
+ *
4
+ * XLSX is a ZIP archive of XML files. We need exactly two of them:
5
+ * - `xl/worksheets/sheet1.xml` — cell data
6
+ * - `xl/sharedStrings.xml` — string table (cells of type "s" are
7
+ * indices into this; not all sheets
8
+ * use it, so it's optional)
9
+ *
10
+ * This file implements the smallest possible code path to extract the
11
+ * first row's cell values:
12
+ *
13
+ * 1. Find the End-of-Central-Directory record at the file tail.
14
+ * 2. Read the Central Directory entries to locate our two files.
15
+ * 3. For each, seek to the local file header, read the compressed
16
+ * data, and `inflateRaw` it to text.
17
+ * 4. Pull the first `<row>` from the sheet XML, walk its `<c>`
18
+ * cells, resolving shared-string indices against the table.
19
+ *
20
+ * **Deliberately limited scope.** Standard PKZIP layout only — no
21
+ * ZIP64 (sheets >4 GiB), no encryption, no compression method other
22
+ * than `stored` (0) or `deflated` (8). Spreadsheets violating any of
23
+ * those return `null` and the caller skips the file. Failing closed
24
+ * is correct here: we promise header discoverability, not row data.
25
+ *
26
+ * **No external dependencies.** This module uses only `node:fs/promises`
27
+ * and `node:zlib`. SheetJS/exceljs would handle every edge case but
28
+ * add 1-2 MB of dependency weight for what is a niche feature in a
29
+ * source-code-indexer. The trade is intentional and documented.
30
+ */
31
+ import { open } from "node:fs/promises";
32
+ import { inflateRawSync } from "node:zlib";
33
+ // ── PKZIP wire-format constants ────────────────────────────────────
34
+ const SIG_LOCAL_FILE_HEADER = 0x04034b50;
35
+ const SIG_CENTRAL_DIRECTORY = 0x02014b50;
36
+ const SIG_END_OF_CENTRAL_DIR = 0x06054b50;
37
+ // End-of-Central-Directory record is 22 bytes plus a variable-length
38
+ // comment up to 65535 bytes. We scan the last 64 KiB which covers any
39
+ // realistic case (XLSX files don't carry archive comments).
40
+ const EOCD_SCAN_BYTES = 65_557;
41
+ // Hard ceilings — bounded work per file even on malicious input.
42
+ const MAX_FILE_BYTES = 200 * 1024 * 1024; // 200 MB compressed
43
+ const MAX_UNCOMPRESSED_BYTES = 80 * 1024 * 1024; // 80 MB inflated
44
+ const MAX_COLUMNS = 256;
45
+ /**
46
+ * Public entry point. Returns the first row's cell values, or null
47
+ * if the file isn't a readable XLSX, the sheet is empty, or anything
48
+ * about the structure trips the limits above. Never throws.
49
+ */
50
+ export async function readXlsxFirstRow(absPath) {
51
+ let handle = null;
52
+ try {
53
+ handle = await open(absPath, "r");
54
+ const stat = await handle.stat();
55
+ if (!stat.isFile() || stat.size < 22 || stat.size > MAX_FILE_BYTES)
56
+ return null;
57
+ const entries = await readCentralDirectory(handle, stat.size);
58
+ if (!entries)
59
+ return null;
60
+ // We need sheet1.xml; sharedStrings.xml is optional.
61
+ const sheet = entries.find((e) => e.name === "xl/worksheets/sheet1.xml");
62
+ if (!sheet)
63
+ return null;
64
+ const sharedEntry = entries.find((e) => e.name === "xl/sharedStrings.xml") ?? null;
65
+ const sheetXml = await extractEntryAsText(handle, sheet);
66
+ if (sheetXml === null)
67
+ return null;
68
+ const sharedXml = sharedEntry ? await extractEntryAsText(handle, sharedEntry) : null;
69
+ const sharedStrings = sharedXml ? parseSharedStrings(sharedXml) : [];
70
+ return parseFirstRow(sheetXml, sharedStrings);
71
+ }
72
+ catch {
73
+ return null;
74
+ }
75
+ finally {
76
+ if (handle)
77
+ await handle.close().catch(() => undefined);
78
+ }
79
+ }
80
+ /**
81
+ * Locate the End-of-Central-Directory record near the end of the
82
+ * file, then walk the Central Directory entries. Returns null on any
83
+ * structural problem.
84
+ */
85
+ async function readCentralDirectory(handle, fileSize) {
86
+ const tailSize = Math.min(EOCD_SCAN_BYTES, fileSize);
87
+ const tail = Buffer.alloc(tailSize);
88
+ await handle.read(tail, 0, tailSize, fileSize - tailSize);
89
+ // Scan backwards for the EOCD signature.
90
+ let eocdOffset = -1;
91
+ for (let i = tail.length - 22; i >= 0; i--) {
92
+ if (tail.readUInt32LE(i) === SIG_END_OF_CENTRAL_DIR) {
93
+ eocdOffset = i;
94
+ break;
95
+ }
96
+ }
97
+ if (eocdOffset < 0)
98
+ return null;
99
+ const totalEntries = tail.readUInt16LE(eocdOffset + 10);
100
+ const cdSize = tail.readUInt32LE(eocdOffset + 12);
101
+ const cdOffset = tail.readUInt32LE(eocdOffset + 16);
102
+ // ZIP64 sentinel — out of scope.
103
+ if (cdOffset === 0xffffffff || cdSize === 0xffffffff || totalEntries === 0xffff)
104
+ return null;
105
+ // Read the Central Directory itself.
106
+ const cd = Buffer.alloc(cdSize);
107
+ await handle.read(cd, 0, cdSize, cdOffset);
108
+ const entries = [];
109
+ let p = 0;
110
+ for (let i = 0; i < totalEntries; i++) {
111
+ if (p + 46 > cd.length)
112
+ return null;
113
+ if (cd.readUInt32LE(p) !== SIG_CENTRAL_DIRECTORY)
114
+ return null;
115
+ const compressionMethod = cd.readUInt16LE(p + 10);
116
+ const compressedSize = cd.readUInt32LE(p + 20);
117
+ const uncompressedSize = cd.readUInt32LE(p + 24);
118
+ const nameLen = cd.readUInt16LE(p + 28);
119
+ const extraLen = cd.readUInt16LE(p + 30);
120
+ const commentLen = cd.readUInt16LE(p + 32);
121
+ const localHeaderOffset = cd.readUInt32LE(p + 42);
122
+ if (compressedSize === 0xffffffff || uncompressedSize === 0xffffffff || localHeaderOffset === 0xffffffff)
123
+ return null;
124
+ const name = cd.subarray(p + 46, p + 46 + nameLen).toString("utf-8");
125
+ entries.push({ name, localHeaderOffset, compressionMethod, compressedSize, uncompressedSize });
126
+ p += 46 + nameLen + extraLen + commentLen;
127
+ }
128
+ return entries;
129
+ }
130
+ /**
131
+ * Read one entry from the ZIP: seek to its local file header, skip
132
+ * the variable-length name+extra fields, read the compressed payload,
133
+ * and inflate it. Returns null if the entry is too big, uses an
134
+ * unsupported compression method, or fails to decompress.
135
+ */
136
+ async function extractEntryAsText(handle, entry) {
137
+ if (entry.uncompressedSize > MAX_UNCOMPRESSED_BYTES)
138
+ return null;
139
+ if (entry.compressionMethod !== 0 && entry.compressionMethod !== 8)
140
+ return null; // stored or deflated only
141
+ // Read the local file header to learn the name+extra lengths there
142
+ // (they can differ from the central-directory copy in theory).
143
+ const lfh = Buffer.alloc(30);
144
+ await handle.read(lfh, 0, 30, entry.localHeaderOffset);
145
+ if (lfh.readUInt32LE(0) !== SIG_LOCAL_FILE_HEADER)
146
+ return null;
147
+ const lfhNameLen = lfh.readUInt16LE(26);
148
+ const lfhExtraLen = lfh.readUInt16LE(28);
149
+ const dataOffset = entry.localHeaderOffset + 30 + lfhNameLen + lfhExtraLen;
150
+ const compressed = Buffer.alloc(entry.compressedSize);
151
+ if (entry.compressedSize > 0) {
152
+ await handle.read(compressed, 0, entry.compressedSize, dataOffset);
153
+ }
154
+ let raw;
155
+ if (entry.compressionMethod === 0) {
156
+ raw = compressed;
157
+ }
158
+ else {
159
+ try {
160
+ raw = inflateRawSync(compressed, { maxOutputLength: MAX_UNCOMPRESSED_BYTES });
161
+ }
162
+ catch {
163
+ return null;
164
+ }
165
+ }
166
+ return raw.toString("utf-8");
167
+ }
168
+ // ── Tiny XML extraction (regex; we don't need a real parser) ───────
169
+ //
170
+ // We avoid pulling in an XML library. Two operations are all we need:
171
+ // - From sharedStrings.xml, the ordered sequence of `<t>…</t>` (and
172
+ // `<t xml:space="preserve">…</t>`) under each `<si>`.
173
+ // - From sheet1.xml, the first `<row>…</row>`, and within it each
174
+ // `<c r="A1" t="s">…</c>` cell. The cell value is either a
175
+ // numeric `<v>N</v>` index into the shared-string table (when
176
+ // `t="s"`) or an inline string in `<is><t>…</t></is>`.
177
+ //
178
+ // XLSX cells use a tightly constrained subset of XML — the writers
179
+ // emit predictable shapes. Regex parsing is brittle in general but
180
+ // adequate here, especially since we never write XML out.
181
+ /**
182
+ * Parse the shared-strings table to an indexable array. Each `<si>`
183
+ * element contributes one entry; the entry is the concatenation of
184
+ * its `<t>` children (rich text broken into runs still flattens to
185
+ * a single string, which is what we want for a header label).
186
+ */
187
+ function parseSharedStrings(xml) {
188
+ const result = [];
189
+ // Walk <si>…</si> blocks; inside each, gather all <t>…</t> text.
190
+ const siRe = /<si\b[^>]*>([\s\S]*?)<\/si>/g;
191
+ const tRe = /<t\b[^>]*>([\s\S]*?)<\/t>/g;
192
+ let m;
193
+ while ((m = siRe.exec(xml)) !== null) {
194
+ const inner = m[1];
195
+ let combined = "";
196
+ let tm;
197
+ tRe.lastIndex = 0;
198
+ while ((tm = tRe.exec(inner)) !== null) {
199
+ combined += decodeXmlEntities(tm[1]);
200
+ }
201
+ result.push(combined);
202
+ if (result.length > 100_000)
203
+ break; // sanity ceiling
204
+ }
205
+ return result;
206
+ }
207
+ /**
208
+ * Extract the first row's cell values from a sheet XML, resolving
209
+ * shared-string indices against the supplied table. Returns null if
210
+ * no row is found, an empty array if the row had no cells.
211
+ */
212
+ function parseFirstRow(xml, shared) {
213
+ const rowMatch = /<row\b[^>]*>([\s\S]*?)<\/row>/.exec(xml);
214
+ if (!rowMatch)
215
+ return null;
216
+ const rowInner = rowMatch[1];
217
+ // Each <c r="REF" t="TYPE">…</c>. t may be: "s" (shared string),
218
+ // "str" (formula string), "inlineStr" (inline string), absent
219
+ // (number), "b" (boolean). We only render textually.
220
+ const cellRe = /<c\b([^>]*)>([\s\S]*?)<\/c>/g;
221
+ const cells = [];
222
+ let m;
223
+ while ((m = cellRe.exec(rowInner)) !== null) {
224
+ const attrs = m[1];
225
+ const inner = m[2];
226
+ const tMatch = /\bt="([^"]+)"/.exec(attrs);
227
+ const type = tMatch ? tMatch[1] : null;
228
+ const value = extractCellValue(type, inner, shared);
229
+ cells.push(value);
230
+ if (cells.length > MAX_COLUMNS)
231
+ break;
232
+ }
233
+ return cells;
234
+ }
235
+ function extractCellValue(type, inner, shared) {
236
+ if (type === "s") {
237
+ const v = /<v>([\s\S]*?)<\/v>/.exec(inner);
238
+ if (!v)
239
+ return "";
240
+ const idx = Number.parseInt(v[1].trim(), 10);
241
+ if (!Number.isFinite(idx) || idx < 0 || idx >= shared.length)
242
+ return "";
243
+ return shared[idx];
244
+ }
245
+ if (type === "inlineStr") {
246
+ const ts = [];
247
+ const tRe = /<t\b[^>]*>([\s\S]*?)<\/t>/g;
248
+ let m;
249
+ while ((m = tRe.exec(inner)) !== null)
250
+ ts.push(decodeXmlEntities(m[1]));
251
+ return ts.join("");
252
+ }
253
+ // Number, formula result, boolean, or untyped — fall through to <v>.
254
+ const v = /<v>([\s\S]*?)<\/v>/.exec(inner);
255
+ return v ? decodeXmlEntities(v[1].trim()) : "";
256
+ }
257
+ /**
258
+ * Decode the five XML predefined entities. We don't see numeric
259
+ * character references in XLSX cell text in practice; if a writer
260
+ * does emit them, they pass through literally — acceptable for header
261
+ * labels.
262
+ */
263
+ function decodeXmlEntities(s) {
264
+ return s
265
+ .replace(/&lt;/g, "<")
266
+ .replace(/&gt;/g, ">")
267
+ .replace(/&quot;/g, '"')
268
+ .replace(/&apos;/g, "'")
269
+ .replace(/&amp;/g, "&");
270
+ }
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "opencode-diane",
3
+ "version": "0.0.5",
4
+ "description": "OpenCode plugin: hierarchical, token-efficient memory for any git repository. Convention-free — pre-fills from git diff-structure and project files, no LLM at the core, no commit-message parsing. Optional cross-lingual semantic search; skill mining.",
5
+ "keywords": [
6
+ "opencode",
7
+ "opencode-plugin",
8
+ "memory",
9
+ "experience-reuse",
10
+ "token-saving",
11
+ "bm25",
12
+ "language-agnostic",
13
+ "agents.md"
14
+ ],
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/two-coats-guaranteed/opencode-diane.git"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/two-coats-guaranteed/opencode-diane/issues"
22
+ },
23
+ "homepage": "https://github.com/two-coats-guaranteed/opencode-diane#readme",
24
+ "type": "module",
25
+ "main": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "grammars",
36
+ "README.md",
37
+ "WIKI.md",
38
+ "CHANGELOG.md",
39
+ "LICENSE"
40
+ ],
41
+ "scripts": {
42
+ "build": "tsc -p tsconfig.json",
43
+ "lint": "eslint src tests",
44
+ "clean": "rm -rf dist coverage",
45
+ "prepublishOnly": "bun run clean && bun run build",
46
+ "test": "bun scripts/run-tests.mjs",
47
+ "smoke": "bun scripts/smoke.mjs",
48
+ "check:size": "bun scripts/check-size.mjs",
49
+ "test:coverage": "bun test --coverage",
50
+ "coverage:check": "bun scripts/coverage-check.mjs",
51
+ "verify:semantic": "bun scripts/verify-semantic.mjs",
52
+ "test:analyzer": "python3 tests/test_analyze_logs.py",
53
+ "test:coverage-parser": "node --test scripts/lib/coverage-parser-tests.mjs",
54
+ "typecheck": "tsc --noEmit"
55
+ },
56
+ "dependencies": {
57
+ "@opencode-ai/plugin": ">=1.14.0",
58
+ "web-tree-sitter": "0.25.10",
59
+ "xlsx": "^0.18.5"
60
+ },
61
+ "peerDependencies": {
62
+ "@huggingface/transformers": "^4.2.0"
63
+ },
64
+ "peerDependenciesMeta": {
65
+ "@huggingface/transformers": {
66
+ "optional": true
67
+ }
68
+ },
69
+ "devDependencies": {
70
+ "@eslint/js": "^9.39.4",
71
+ "@types/bun": "^1.1.14",
72
+ "@types/node": "^22.10.0",
73
+ "eslint": "^9.39.4",
74
+ "typescript": "^5.6.3",
75
+ "typescript-eslint": "^8.59.3"
76
+ },
77
+ "engines": {
78
+ "bun": ">=1.1.0"
79
+ }
80
+ }