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.
- package/BOOTSTRAP_PROMPT.md +120 -0
- package/bin/connectors/github.js +617 -0
- package/bin/connectors/index.js +13 -0
- package/bin/connectors/intercom.js +595 -0
- package/bin/connectors/llm.js +469 -0
- package/bin/connectors/notion.js +747 -0
- package/bin/connectors/transport.js +325 -0
- package/bin/content-store.js +2006 -0
- package/bin/digest.js +813 -0
- package/bin/rebuild-status.js +297 -0
- package/bin/slack-bot.js +1535 -0
- package/bin/slack.js +342 -0
- package/bin/storage/index.js +171 -0
- package/bin/storage/json-backend.js +348 -0
- package/bin/storage/sqlite-backend.js +415 -0
- package/bin/team-context.js +4209 -0
- package/bin/telemetry.js +159 -0
- package/doctor.sh +291 -0
- package/install.sh +144 -0
- package/journal-summary.sh +577 -0
- package/package.json +48 -6
- package/setup.sh +641 -0
- package/specializations/claude-code/CLAUDE.md-global-fragment.md +53 -0
- package/specializations/claude-code/CLAUDE.md-repo-fragment.md +16 -0
- package/specializations/claude-code/README.md +99 -0
- package/specializations/claude-code/commands/doctor.md +31 -0
- package/specializations/claude-code/commands/init-memory.md +154 -0
- package/specializations/claude-code/commands/init-team.md +415 -0
- package/specializations/claude-code/commands/journal.md +66 -0
- package/specializations/claude-code/commands/review-prs.md +119 -0
- package/specializations/claude-code/hooks/check-global-state.sh +20 -0
- package/specializations/claude-code/hooks/session-end.sh +36 -0
- package/specializations/claude-code/settings.json +15 -0
- package/specializations/cursor/README.md +120 -0
- package/specializations/cursor/global-rule.mdc +53 -0
- package/specializations/cursor/repo-rule.mdc +25 -0
- package/specializations/generic/README.md +47 -0
- package/templates/autopilot/design.md +22 -0
- package/templates/autopilot/engineering.md +22 -0
- package/templates/autopilot/product.md +22 -0
- package/templates/autopilot/strategy.md +22 -0
- package/templates/autopilot/unified.md +24 -0
- package/templates/deploy/.env.example +110 -0
- package/templates/deploy/docker-compose.yml +63 -0
- package/templates/deploy/slack-app-manifest.json +45 -0
- package/templates/github-actions/meridian-digest.yml +85 -0
- package/templates/global.md +79 -0
- package/templates/memory-file.md +18 -0
- package/templates/personal-state.md +14 -0
- package/templates/personas.json +28 -0
- package/templates/product-state.md +41 -0
- package/templates/prompts-readme.md +19 -0
- package/templates/repo-state.md +18 -0
- package/templates/session-protocol-fragment.md +46 -0
- package/templates/slack-app-manifest.json +27 -0
- package/templates/statusline.sh +22 -0
- package/templates/strategy-state.md +39 -0
- package/templates/team-state.md +55 -0
- package/uninstall.sh +105 -0
- 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;
|