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,348 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ // ── Constants ────────────────────────────────────────────────────────────────
7
+
8
+ const INDEX_FILE = 'index.json';
9
+ const EMBEDDINGS_FILE = 'embeddings.json';
10
+ const CONVERSATION_INDEX_FILE = 'conversation-index.json';
11
+ const FEEDBACK_FILE = 'digest-feedback.json';
12
+ const INDEX_VERSION = '2.0.0';
13
+ const FILE_PERMS = 0o600;
14
+
15
+ // ── Helpers ──────────────────────────────────────────────────────────────────
16
+
17
+ /**
18
+ * Atomic write: write to .tmp then rename into place.
19
+ * @param {string} filePath
20
+ * @param {string} content
21
+ */
22
+ function atomicWrite(filePath, content) {
23
+ const tmp = filePath + '.tmp';
24
+ fs.writeFileSync(tmp, content, { mode: FILE_PERMS });
25
+ fs.renameSync(tmp, filePath);
26
+ }
27
+
28
+ /**
29
+ * Read and parse a JSON file, returning fallback on any error.
30
+ * @param {string} filePath
31
+ * @param {*} fallback
32
+ * @returns {*}
33
+ */
34
+ function readJson(filePath, fallback) {
35
+ try {
36
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
37
+ } catch {
38
+ return fallback;
39
+ }
40
+ }
41
+
42
+ // ── JsonBackend ──────────────────────────────────────────────────────────────
43
+
44
+ class JsonBackend {
45
+ /**
46
+ * @param {string} storePath - Directory for JSON storage files
47
+ */
48
+ constructor(storePath) {
49
+ this._storePath = storePath;
50
+ this._open = false;
51
+ }
52
+
53
+ // ── Lifecycle ────────────────────────────────────────────────────────────
54
+
55
+ /**
56
+ * Ensure the store directory exists and mark backend as open.
57
+ */
58
+ open() {
59
+ if (!fs.existsSync(this._storePath)) {
60
+ fs.mkdirSync(this._storePath, { recursive: true });
61
+ }
62
+ this._open = true;
63
+ }
64
+
65
+ /**
66
+ * No-op for JSON backend — nothing to tear down.
67
+ */
68
+ close() {
69
+ this._open = false;
70
+ }
71
+
72
+ /**
73
+ * @returns {boolean}
74
+ */
75
+ isOpen() {
76
+ return this._open;
77
+ }
78
+
79
+ // ── Index ────────────────────────────────────────────────────────────────
80
+
81
+ /**
82
+ * Load the full index from disk.
83
+ * @returns {{ version: string, lastUpdated: number, entryCount: number, entries: Object }|null}
84
+ */
85
+ loadIndex() {
86
+ const data = readJson(path.join(this._storePath, INDEX_FILE), null);
87
+ if (data && data.version === INDEX_VERSION) return data;
88
+ return null;
89
+ }
90
+
91
+ /**
92
+ * Save the full index to disk (atomic write).
93
+ * @param {Object} index
94
+ */
95
+ saveIndex(index) {
96
+ this._ensureDir();
97
+ index.lastUpdated = Date.now();
98
+ index.entryCount = Object.keys(index.entries).length;
99
+ const content = JSON.stringify(index, null, 2) + '\n';
100
+ atomicWrite(path.join(this._storePath, INDEX_FILE), content);
101
+ }
102
+
103
+ /**
104
+ * Get a single entry by id.
105
+ * @param {string} id
106
+ * @returns {Object|null}
107
+ */
108
+ getEntry(id) {
109
+ const index = this.loadIndex();
110
+ if (!index) return null;
111
+ return index.entries[id] || null;
112
+ }
113
+
114
+ /**
115
+ * Insert or update a single entry, then persist.
116
+ * @param {string} id
117
+ * @param {Object} entry
118
+ */
119
+ upsertEntry(id, entry) {
120
+ let index = this.loadIndex();
121
+ if (!index) {
122
+ index = { version: INDEX_VERSION, lastUpdated: 0, entryCount: 0, entries: {} };
123
+ }
124
+ index.entries[id] = entry;
125
+ this.saveIndex(index);
126
+ }
127
+
128
+ /**
129
+ * Merge a map of { id: entry } into the index and persist once.
130
+ * @param {Object} entriesMap - { id: entry }
131
+ */
132
+ bulkUpsertEntries(entriesMap) {
133
+ let index = this.loadIndex();
134
+ if (!index) {
135
+ index = { version: INDEX_VERSION, lastUpdated: 0, entryCount: 0, entries: {} };
136
+ }
137
+ Object.assign(index.entries, entriesMap);
138
+ this.saveIndex(index);
139
+ }
140
+
141
+ /**
142
+ * Remove entries by id array and persist.
143
+ * @param {string[]} ids
144
+ */
145
+ removeEntries(ids) {
146
+ const index = this.loadIndex();
147
+ if (!index) return;
148
+ for (const id of ids) {
149
+ delete index.entries[id];
150
+ }
151
+ this.saveIndex(index);
152
+ }
153
+
154
+ /**
155
+ * Iterate entries and apply filters. Returns matching [{ id, entry }].
156
+ *
157
+ * Filters: { repo, since, until, drifted, user, source }
158
+ * - repo/user: case-insensitive exact match
159
+ * - since/until: inclusive date string comparison (YYYY-MM-DD)
160
+ * - drifted: boolean match
161
+ * - source: exact match
162
+ *
163
+ * Note: repo exclusion (TEAM_CONTEXT_EXCLUDE_REPOS) is handled at a higher
164
+ * level in content-store.js, not here.
165
+ *
166
+ * @param {Object} filters
167
+ * @returns {Array<{ id: string, entry: Object }>}
168
+ */
169
+ queryEntries(filters = {}) {
170
+ const index = this.loadIndex();
171
+ if (!index) return [];
172
+
173
+ const results = [];
174
+ for (const [id, entry] of Object.entries(index.entries)) {
175
+ if (filters.repo && entry.repo &&
176
+ entry.repo.toLowerCase() !== filters.repo.toLowerCase()) continue;
177
+ if (filters.since && entry.date < filters.since) continue;
178
+ if (filters.until && entry.date > filters.until) continue;
179
+ if (filters.drifted !== undefined && entry.drifted !== filters.drifted) continue;
180
+ if (filters.user && entry.user &&
181
+ entry.user.toLowerCase() !== filters.user.toLowerCase()) continue;
182
+ if (filters.source && entry.source !== filters.source) continue;
183
+ results.push({ id, entry });
184
+ }
185
+
186
+ // Sort by date descending
187
+ results.sort((a, b) => (b.entry.date || '').localeCompare(a.entry.date || ''));
188
+ return results;
189
+ }
190
+
191
+ // ── Embeddings ───────────────────────────────────────────────────────────
192
+
193
+ /**
194
+ * Load embeddings map from disk.
195
+ * @returns {Object} - { id: vector[] }
196
+ */
197
+ loadEmbeddings() {
198
+ return readJson(path.join(this._storePath, EMBEDDINGS_FILE), {});
199
+ }
200
+
201
+ /**
202
+ * Save full embeddings map to disk (atomic write).
203
+ * @param {Object} map - { id: vector[] }
204
+ */
205
+ saveEmbeddings(map) {
206
+ this._ensureDir();
207
+ const content = JSON.stringify(map) + '\n';
208
+ atomicWrite(path.join(this._storePath, EMBEDDINGS_FILE), content);
209
+ }
210
+
211
+ /**
212
+ * Get a single embedding vector by id.
213
+ * @param {string} id
214
+ * @returns {number[]|null}
215
+ */
216
+ getEmbedding(id) {
217
+ const map = this.loadEmbeddings();
218
+ return map[id] || null;
219
+ }
220
+
221
+ /**
222
+ * Insert or update a single embedding, then persist.
223
+ * @param {string} id
224
+ * @param {number[]} vector
225
+ */
226
+ upsertEmbedding(id, vector) {
227
+ const map = this.loadEmbeddings();
228
+ map[id] = vector;
229
+ this.saveEmbeddings(map);
230
+ }
231
+
232
+ /**
233
+ * Remove embeddings by id array and persist.
234
+ * @param {string[]} ids
235
+ */
236
+ removeEmbeddings(ids) {
237
+ const map = this.loadEmbeddings();
238
+ for (const id of ids) {
239
+ delete map[id];
240
+ }
241
+ this.saveEmbeddings(map);
242
+ }
243
+
244
+ // ── Conversation index ───────────────────────────────────────────────────
245
+
246
+ /**
247
+ * Load the conversation index.
248
+ * @returns {Object} - { filePath: { fingerprint, entryIds, extractedAt } }
249
+ */
250
+ loadConversationIndex() {
251
+ return readJson(path.join(this._storePath, CONVERSATION_INDEX_FILE), {});
252
+ }
253
+
254
+ /**
255
+ * Save the full conversation index (atomic write).
256
+ * @param {Object} convIndex
257
+ */
258
+ saveConversationIndex(convIndex) {
259
+ this._ensureDir();
260
+ const content = JSON.stringify(convIndex, null, 2) + '\n';
261
+ atomicWrite(path.join(this._storePath, CONVERSATION_INDEX_FILE), content);
262
+ }
263
+
264
+ /**
265
+ * Get a single conversation entry by file path.
266
+ * @param {string} filePath
267
+ * @returns {Object|null}
268
+ */
269
+ getConversationEntry(filePath) {
270
+ const convIndex = this.loadConversationIndex();
271
+ return convIndex[filePath] || null;
272
+ }
273
+
274
+ /**
275
+ * Insert or update a conversation entry, then persist.
276
+ * @param {string} filePath
277
+ * @param {Object} entry
278
+ */
279
+ upsertConversationEntry(filePath, entry) {
280
+ const convIndex = this.loadConversationIndex();
281
+ convIndex[filePath] = entry;
282
+ this.saveConversationIndex(convIndex);
283
+ }
284
+
285
+ // ── Digest feedback ──────────────────────────────────────────────────────
286
+
287
+ /**
288
+ * Load digest feedback data.
289
+ * @returns {{ digests: Object }}
290
+ */
291
+ loadFeedback() {
292
+ return readJson(path.join(this._storePath, FEEDBACK_FILE), { digests: {} });
293
+ }
294
+
295
+ /**
296
+ * Save digest feedback data (atomic write).
297
+ * @param {Object} feedback
298
+ */
299
+ saveFeedback(feedback) {
300
+ this._ensureDir();
301
+ const content = JSON.stringify(feedback, null, 2) + '\n';
302
+ atomicWrite(path.join(this._storePath, FEEDBACK_FILE), content);
303
+ }
304
+
305
+ /**
306
+ * Find a digest entry whose ts matches the given messageTs.
307
+ * @param {string} messageTs
308
+ * @returns {{ key: string, digest: Object }|null}
309
+ */
310
+ findDigestByTs(messageTs) {
311
+ const feedback = this.loadFeedback();
312
+ for (const [key, digest] of Object.entries(feedback.digests || {})) {
313
+ if (digest.ts === messageTs) return { key, digest };
314
+ }
315
+ return null;
316
+ }
317
+
318
+ /**
319
+ * Insert or update a single digest entry, then persist.
320
+ * @param {string} key
321
+ * @param {Object} digest
322
+ */
323
+ upsertDigest(key, digest) {
324
+ const feedback = this.loadFeedback();
325
+ feedback.digests[key] = digest;
326
+ this.saveFeedback(feedback);
327
+ }
328
+
329
+ // ── Meta ─────────────────────────────────────────────────────────────────
330
+
331
+ /**
332
+ * Returns the schema identifier for this backend.
333
+ * @returns {string}
334
+ */
335
+ getSchemaVersion() {
336
+ return 'json';
337
+ }
338
+
339
+ // ── Internal ─────────────────────────────────────────────────────────────
340
+
341
+ _ensureDir() {
342
+ if (!fs.existsSync(this._storePath)) {
343
+ fs.mkdirSync(this._storePath, { recursive: true });
344
+ }
345
+ }
346
+ }
347
+
348
+ module.exports = JsonBackend;