wayfind 0.0.1 → 2.0.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 (60) hide show
  1. package/BOOTSTRAP_PROMPT.md +120 -0
  2. package/bin/connectors/github.js +617 -0
  3. package/bin/connectors/index.js +13 -0
  4. package/bin/connectors/intercom.js +595 -0
  5. package/bin/connectors/llm.js +469 -0
  6. package/bin/connectors/notion.js +747 -0
  7. package/bin/connectors/transport.js +325 -0
  8. package/bin/content-store.js +2006 -0
  9. package/bin/digest.js +813 -0
  10. package/bin/rebuild-status.js +297 -0
  11. package/bin/slack-bot.js +1535 -0
  12. package/bin/slack.js +342 -0
  13. package/bin/storage/index.js +171 -0
  14. package/bin/storage/json-backend.js +348 -0
  15. package/bin/storage/sqlite-backend.js +415 -0
  16. package/bin/team-context.js +4209 -0
  17. package/bin/telemetry.js +159 -0
  18. package/doctor.sh +291 -0
  19. package/install.sh +144 -0
  20. package/journal-summary.sh +577 -0
  21. package/package.json +48 -6
  22. package/setup.sh +641 -0
  23. package/specializations/claude-code/CLAUDE.md-global-fragment.md +53 -0
  24. package/specializations/claude-code/CLAUDE.md-repo-fragment.md +16 -0
  25. package/specializations/claude-code/README.md +99 -0
  26. package/specializations/claude-code/commands/doctor.md +31 -0
  27. package/specializations/claude-code/commands/init-memory.md +154 -0
  28. package/specializations/claude-code/commands/init-team.md +415 -0
  29. package/specializations/claude-code/commands/journal.md +66 -0
  30. package/specializations/claude-code/commands/review-prs.md +119 -0
  31. package/specializations/claude-code/hooks/check-global-state.sh +20 -0
  32. package/specializations/claude-code/hooks/session-end.sh +36 -0
  33. package/specializations/claude-code/settings.json +15 -0
  34. package/specializations/cursor/README.md +120 -0
  35. package/specializations/cursor/global-rule.mdc +53 -0
  36. package/specializations/cursor/repo-rule.mdc +25 -0
  37. package/specializations/generic/README.md +47 -0
  38. package/templates/autopilot/design.md +22 -0
  39. package/templates/autopilot/engineering.md +22 -0
  40. package/templates/autopilot/product.md +22 -0
  41. package/templates/autopilot/strategy.md +22 -0
  42. package/templates/autopilot/unified.md +24 -0
  43. package/templates/deploy/.env.example +110 -0
  44. package/templates/deploy/docker-compose.yml +63 -0
  45. package/templates/deploy/slack-app-manifest.json +45 -0
  46. package/templates/github-actions/meridian-digest.yml +85 -0
  47. package/templates/global.md +79 -0
  48. package/templates/memory-file.md +18 -0
  49. package/templates/personal-state.md +14 -0
  50. package/templates/personas.json +28 -0
  51. package/templates/product-state.md +41 -0
  52. package/templates/prompts-readme.md +19 -0
  53. package/templates/repo-state.md +18 -0
  54. package/templates/session-protocol-fragment.md +46 -0
  55. package/templates/slack-app-manifest.json +27 -0
  56. package/templates/statusline.sh +22 -0
  57. package/templates/strategy-state.md +39 -0
  58. package/templates/team-state.md +55 -0
  59. package/uninstall.sh +105 -0
  60. package/README.md +0 -4
@@ -0,0 +1,415 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * SQLite storage backend for Wayfind's content store.
5
+ * Drop-in replacement for JSON file persistence using better-sqlite3.
6
+ * DB file: {storePath}/content-store.db
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+
12
+ let Database;
13
+ try {
14
+ Database = require('better-sqlite3');
15
+ } catch {
16
+ Database = null;
17
+ }
18
+
19
+ const INDEX_VERSION = '2.0.0';
20
+ const SCHEMA_VERSION = '1';
21
+ const DB_FILENAME = 'content-store.db';
22
+
23
+ const SCHEMA_SQL = `
24
+ CREATE TABLE IF NOT EXISTS metadata (
25
+ key TEXT PRIMARY KEY,
26
+ value TEXT NOT NULL
27
+ );
28
+
29
+ CREATE TABLE IF NOT EXISTS decisions (
30
+ id TEXT PRIMARY KEY,
31
+ date TEXT NOT NULL,
32
+ repo TEXT NOT NULL,
33
+ title TEXT NOT NULL,
34
+ source TEXT DEFAULT 'journal',
35
+ user TEXT DEFAULT '',
36
+ drifted INTEGER DEFAULT 0,
37
+ content_hash TEXT NOT NULL,
38
+ content_length INTEGER DEFAULT 0,
39
+ tags TEXT DEFAULT '[]',
40
+ has_embedding INTEGER DEFAULT 0,
41
+ has_reasoning INTEGER DEFAULT 0,
42
+ has_alternatives INTEGER DEFAULT 0,
43
+ created_at INTEGER,
44
+ updated_at INTEGER
45
+ );
46
+
47
+ CREATE INDEX IF NOT EXISTS idx_decisions_date ON decisions(date);
48
+ CREATE INDEX IF NOT EXISTS idx_decisions_repo ON decisions(repo);
49
+ CREATE INDEX IF NOT EXISTS idx_decisions_source ON decisions(source);
50
+ CREATE INDEX IF NOT EXISTS idx_decisions_user ON decisions(user);
51
+
52
+ CREATE TABLE IF NOT EXISTS embeddings (
53
+ id TEXT PRIMARY KEY,
54
+ vector BLOB NOT NULL
55
+ );
56
+
57
+ CREATE TABLE IF NOT EXISTS conversation_index (
58
+ file_path TEXT PRIMARY KEY,
59
+ fingerprint TEXT NOT NULL,
60
+ entry_ids TEXT DEFAULT '[]',
61
+ extracted_at INTEGER
62
+ );
63
+
64
+ CREATE TABLE IF NOT EXISTS digest_feedback (
65
+ key TEXT PRIMARY KEY,
66
+ date TEXT NOT NULL,
67
+ persona TEXT NOT NULL,
68
+ channel TEXT,
69
+ ts TEXT,
70
+ delivered_at TEXT,
71
+ reactions TEXT DEFAULT '{}',
72
+ comments TEXT DEFAULT '[]'
73
+ );
74
+ `;
75
+
76
+ // ── Helpers ──────────────────────────────────────────────────────────────────
77
+
78
+ function entryToRow(id, entry) {
79
+ return {
80
+ id,
81
+ date: entry.date || '',
82
+ repo: entry.repo || '',
83
+ title: entry.title || '',
84
+ source: entry.source || 'journal',
85
+ user: entry.user || '',
86
+ drifted: entry.drifted ? 1 : 0,
87
+ content_hash: entry.contentHash || '',
88
+ content_length: entry.contentLength || 0,
89
+ tags: JSON.stringify(entry.tags || []),
90
+ has_embedding: entry.hasEmbedding ? 1 : 0,
91
+ has_reasoning: entry.hasReasoning ? 1 : 0,
92
+ has_alternatives: entry.hasAlternatives ? 1 : 0,
93
+ created_at: entry.createdAt || Date.now(),
94
+ updated_at: Date.now(),
95
+ };
96
+ }
97
+
98
+ function rowToEntry(row) {
99
+ return {
100
+ date: row.date,
101
+ repo: row.repo,
102
+ title: row.title,
103
+ source: row.source,
104
+ user: row.user,
105
+ drifted: !!row.drifted,
106
+ contentHash: row.content_hash,
107
+ contentLength: row.content_length,
108
+ tags: JSON.parse(row.tags || '[]'),
109
+ hasEmbedding: !!row.has_embedding,
110
+ hasReasoning: !!row.has_reasoning,
111
+ hasAlternatives: !!row.has_alternatives,
112
+ };
113
+ }
114
+
115
+ // ── SqliteBackend ────────────────────────────────────────────────────────────
116
+
117
+ class SqliteBackend {
118
+ constructor(storePath) {
119
+ this.storePath = storePath;
120
+ this.dbPath = path.join(storePath, DB_FILENAME);
121
+ this.db = null;
122
+ }
123
+
124
+ open() {
125
+ if (!Database) {
126
+ throw new Error(
127
+ 'better-sqlite3 is not installed. Run: npm install better-sqlite3'
128
+ );
129
+ }
130
+ fs.mkdirSync(this.storePath, { recursive: true });
131
+ this.db = new Database(this.dbPath);
132
+ this.db.pragma('journal_mode = WAL');
133
+ this.db.exec(SCHEMA_SQL);
134
+ fs.chmodSync(this.dbPath, 0o600);
135
+
136
+ const existing = this.db.prepare('SELECT value FROM metadata WHERE key = ?').get('schema_version');
137
+ if (!existing) {
138
+ this.db.prepare('INSERT INTO metadata (key, value) VALUES (?, ?)').run('schema_version', SCHEMA_VERSION);
139
+ }
140
+ }
141
+
142
+ close() {
143
+ if (this.db) {
144
+ this.db.close();
145
+ this.db = null;
146
+ }
147
+ }
148
+
149
+ isOpen() {
150
+ return this.db !== null;
151
+ }
152
+
153
+ // ── Index (decisions table) ──────────────────────────────────────────────
154
+
155
+ loadIndex() {
156
+ const rows = this.db.prepare('SELECT * FROM decisions').all();
157
+ const entries = {};
158
+ for (const row of rows) {
159
+ entries[row.id] = rowToEntry(row);
160
+ }
161
+ return {
162
+ version: INDEX_VERSION,
163
+ lastUpdated: Date.now(),
164
+ entryCount: rows.length,
165
+ entries,
166
+ };
167
+ }
168
+
169
+ saveIndex(index) {
170
+ const entries = index.entries || {};
171
+ const txn = this.db.transaction(() => {
172
+ this.db.prepare('DELETE FROM decisions').run();
173
+ const stmt = this.db.prepare(`
174
+ INSERT INTO decisions (id, date, repo, title, source, user, drifted,
175
+ content_hash, content_length, tags, has_embedding, has_reasoning,
176
+ has_alternatives, created_at, updated_at)
177
+ VALUES (@id, @date, @repo, @title, @source, @user, @drifted,
178
+ @content_hash, @content_length, @tags, @has_embedding, @has_reasoning,
179
+ @has_alternatives, @created_at, @updated_at)
180
+ `);
181
+ for (const [id, entry] of Object.entries(entries)) {
182
+ stmt.run(entryToRow(id, entry));
183
+ }
184
+ });
185
+ txn();
186
+ }
187
+
188
+ getEntry(id) {
189
+ const row = this.db.prepare('SELECT * FROM decisions WHERE id = ?').get(id);
190
+ return row ? rowToEntry(row) : null;
191
+ }
192
+
193
+ upsertEntry(id, entry) {
194
+ this.db.prepare(`
195
+ INSERT OR REPLACE INTO decisions (id, date, repo, title, source, user, drifted,
196
+ content_hash, content_length, tags, has_embedding, has_reasoning,
197
+ has_alternatives, created_at, updated_at)
198
+ VALUES (@id, @date, @repo, @title, @source, @user, @drifted,
199
+ @content_hash, @content_length, @tags, @has_embedding, @has_reasoning,
200
+ @has_alternatives, @created_at, @updated_at)
201
+ `).run(entryToRow(id, entry));
202
+ }
203
+
204
+ bulkUpsertEntries(entriesMap) {
205
+ const txn = this.db.transaction(() => {
206
+ const stmt = this.db.prepare(`
207
+ INSERT OR REPLACE INTO decisions (id, date, repo, title, source, user, drifted,
208
+ content_hash, content_length, tags, has_embedding, has_reasoning,
209
+ has_alternatives, created_at, updated_at)
210
+ VALUES (@id, @date, @repo, @title, @source, @user, @drifted,
211
+ @content_hash, @content_length, @tags, @has_embedding, @has_reasoning,
212
+ @has_alternatives, @created_at, @updated_at)
213
+ `);
214
+ for (const [id, entry] of Object.entries(entriesMap)) {
215
+ stmt.run(entryToRow(id, entry));
216
+ }
217
+ });
218
+ txn();
219
+ }
220
+
221
+ removeEntries(ids) {
222
+ if (!ids || ids.length === 0) return;
223
+ const placeholders = ids.map(() => '?').join(',');
224
+ this.db.prepare(`DELETE FROM decisions WHERE id IN (${placeholders})`).run(...ids);
225
+ }
226
+
227
+ queryEntries(filters = {}) {
228
+ const conditions = [];
229
+ const params = {};
230
+
231
+ if (filters.repo) {
232
+ conditions.push('repo = @repo');
233
+ params.repo = filters.repo;
234
+ }
235
+ if (filters.since) {
236
+ conditions.push('date >= @since');
237
+ params.since = filters.since;
238
+ }
239
+ if (filters.until) {
240
+ conditions.push('date <= @until');
241
+ params.until = filters.until;
242
+ }
243
+ if (filters.drifted !== undefined) {
244
+ conditions.push('drifted = @drifted');
245
+ params.drifted = filters.drifted ? 1 : 0;
246
+ }
247
+ if (filters.user) {
248
+ conditions.push('user = @user');
249
+ params.user = filters.user;
250
+ }
251
+ if (filters.source) {
252
+ conditions.push('source = @source');
253
+ params.source = filters.source;
254
+ }
255
+
256
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
257
+ const rows = this.db.prepare(`SELECT * FROM decisions ${where}`).all(params);
258
+ return rows.map(row => ({ id: row.id, entry: rowToEntry(row) }));
259
+ }
260
+
261
+ // ── Embeddings ───────────────────────────────────────────────────────────
262
+
263
+ loadEmbeddings() {
264
+ const rows = this.db.prepare('SELECT * FROM embeddings').all();
265
+ const result = {};
266
+ for (const row of rows) {
267
+ result[row.id] = Array.from(new Float64Array(row.vector.buffer, row.vector.byteOffset, row.vector.byteLength / 8));
268
+ }
269
+ return result;
270
+ }
271
+
272
+ saveEmbeddings(map) {
273
+ const txn = this.db.transaction(() => {
274
+ this.db.prepare('DELETE FROM embeddings').run();
275
+ const stmt = this.db.prepare('INSERT INTO embeddings (id, vector) VALUES (?, ?)');
276
+ for (const [id, vector] of Object.entries(map)) {
277
+ stmt.run(id, Buffer.from(new Float64Array(vector).buffer));
278
+ }
279
+ });
280
+ txn();
281
+ }
282
+
283
+ getEmbedding(id) {
284
+ const row = this.db.prepare('SELECT vector FROM embeddings WHERE id = ?').get(id);
285
+ if (!row) return null;
286
+ return Array.from(new Float64Array(row.vector.buffer, row.vector.byteOffset, row.vector.byteLength / 8));
287
+ }
288
+
289
+ upsertEmbedding(id, vector) {
290
+ this.db.prepare('INSERT OR REPLACE INTO embeddings (id, vector) VALUES (?, ?)')
291
+ .run(id, Buffer.from(new Float64Array(vector).buffer));
292
+ }
293
+
294
+ removeEmbeddings(ids) {
295
+ if (!ids || ids.length === 0) return;
296
+ const placeholders = ids.map(() => '?').join(',');
297
+ this.db.prepare(`DELETE FROM embeddings WHERE id IN (${placeholders})`).run(...ids);
298
+ }
299
+
300
+ // ── Conversation index ───────────────────────────────────────────────────
301
+
302
+ loadConversationIndex() {
303
+ const rows = this.db.prepare('SELECT * FROM conversation_index').all();
304
+ const result = {};
305
+ for (const row of rows) {
306
+ result[row.file_path] = {
307
+ fingerprint: row.fingerprint,
308
+ entryIds: JSON.parse(row.entry_ids || '[]'),
309
+ extractedAt: row.extracted_at,
310
+ };
311
+ }
312
+ return result;
313
+ }
314
+
315
+ saveConversationIndex(convIndex) {
316
+ const txn = this.db.transaction(() => {
317
+ this.db.prepare('DELETE FROM conversation_index').run();
318
+ const stmt = this.db.prepare(`
319
+ INSERT INTO conversation_index (file_path, fingerprint, entry_ids, extracted_at)
320
+ VALUES (?, ?, ?, ?)
321
+ `);
322
+ for (const [filePath, entry] of Object.entries(convIndex)) {
323
+ stmt.run(filePath, entry.fingerprint, JSON.stringify(entry.entryIds || []), entry.extractedAt || null);
324
+ }
325
+ });
326
+ txn();
327
+ }
328
+
329
+ getConversationEntry(filePath) {
330
+ const row = this.db.prepare('SELECT * FROM conversation_index WHERE file_path = ?').get(filePath);
331
+ if (!row) return null;
332
+ return {
333
+ fingerprint: row.fingerprint,
334
+ entryIds: JSON.parse(row.entry_ids || '[]'),
335
+ extractedAt: row.extracted_at,
336
+ };
337
+ }
338
+
339
+ upsertConversationEntry(filePath, entry) {
340
+ this.db.prepare(`
341
+ INSERT OR REPLACE INTO conversation_index (file_path, fingerprint, entry_ids, extracted_at)
342
+ VALUES (?, ?, ?, ?)
343
+ `).run(filePath, entry.fingerprint, JSON.stringify(entry.entryIds || []), entry.extractedAt || null);
344
+ }
345
+
346
+ // ── Digest feedback ──────────────────────────────────────────────────────
347
+
348
+ loadFeedback() {
349
+ const rows = this.db.prepare('SELECT * FROM digest_feedback').all();
350
+ const digests = {};
351
+ for (const row of rows) {
352
+ digests[row.key] = {
353
+ date: row.date,
354
+ persona: row.persona,
355
+ channel: row.channel,
356
+ ts: row.ts,
357
+ deliveredAt: row.delivered_at,
358
+ reactions: JSON.parse(row.reactions || '{}'),
359
+ comments: JSON.parse(row.comments || '[]'),
360
+ };
361
+ }
362
+ return { digests };
363
+ }
364
+
365
+ saveFeedback(feedback) {
366
+ const digests = (feedback && feedback.digests) || {};
367
+ const txn = this.db.transaction(() => {
368
+ this.db.prepare('DELETE FROM digest_feedback').run();
369
+ const stmt = this.db.prepare(`
370
+ INSERT INTO digest_feedback (key, date, persona, channel, ts, delivered_at, reactions, comments)
371
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
372
+ `);
373
+ for (const [key, d] of Object.entries(digests)) {
374
+ stmt.run(
375
+ key, d.date, d.persona, d.channel || null, d.ts || null,
376
+ d.deliveredAt || null, JSON.stringify(d.reactions || {}), JSON.stringify(d.comments || [])
377
+ );
378
+ }
379
+ });
380
+ txn();
381
+ }
382
+
383
+ findDigestByTs(messageTs) {
384
+ const row = this.db.prepare('SELECT * FROM digest_feedback WHERE ts = ?').get(messageTs);
385
+ if (!row) return null;
386
+ return {
387
+ date: row.date,
388
+ persona: row.persona,
389
+ channel: row.channel,
390
+ ts: row.ts,
391
+ deliveredAt: row.delivered_at,
392
+ reactions: JSON.parse(row.reactions || '{}'),
393
+ comments: JSON.parse(row.comments || '[]'),
394
+ };
395
+ }
396
+
397
+ upsertDigest(key, digest) {
398
+ this.db.prepare(`
399
+ INSERT OR REPLACE INTO digest_feedback (key, date, persona, channel, ts, delivered_at, reactions, comments)
400
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
401
+ `).run(
402
+ key, digest.date, digest.persona, digest.channel || null, digest.ts || null,
403
+ digest.deliveredAt || null, JSON.stringify(digest.reactions || {}), JSON.stringify(digest.comments || [])
404
+ );
405
+ }
406
+
407
+ // ── Meta ─────────────────────────────────────────────────────────────────
408
+
409
+ getSchemaVersion() {
410
+ const row = this.db.prepare('SELECT value FROM metadata WHERE key = ?').get('schema_version');
411
+ return row ? row.value : null;
412
+ }
413
+ }
414
+
415
+ module.exports = SqliteBackend;