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,325 @@
1
+ 'use strict';
2
+
3
+ const { execFile } = require('child_process');
4
+ const https = require('https');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ // ── Simulation mode ─────────────────────────────────────────────────────────
9
+
10
+ function isSimulation() {
11
+ return process.env.TEAM_CONTEXT_SIMULATE === '1';
12
+ }
13
+
14
+ function loadFixture(fixturesDir, endpoint) {
15
+ if (endpoint.includes('/issues')) {
16
+ return JSON.parse(fs.readFileSync(path.join(fixturesDir, 'issues.json'), 'utf8'));
17
+ }
18
+ if (endpoint.includes('/pulls')) {
19
+ return JSON.parse(fs.readFileSync(path.join(fixturesDir, 'pull_requests.json'), 'utf8'));
20
+ }
21
+ if (endpoint.includes('/actions/runs')) {
22
+ const data = JSON.parse(fs.readFileSync(path.join(fixturesDir, 'workflow_runs.json'), 'utf8'));
23
+ return data.workflow_runs || data;
24
+ }
25
+ return [];
26
+ }
27
+
28
+ function getFixturesDir() {
29
+ return process.env.TEAM_CONTEXT_SIM_FIXTURES || '';
30
+ }
31
+
32
+ // ── gh CLI transport ────────────────────────────────────────────────────────
33
+
34
+ const ghCli = {
35
+ /**
36
+ * Check whether the gh CLI is authenticated and available.
37
+ * @returns {Promise<boolean>}
38
+ */
39
+ available() {
40
+ return new Promise((resolve) => {
41
+ execFile('gh', ['auth', 'status'], (err) => {
42
+ resolve(!err);
43
+ });
44
+ });
45
+ },
46
+
47
+ /**
48
+ * List authenticated gh CLI accounts.
49
+ * @returns {Promise<string[]>} Array of usernames
50
+ */
51
+ listAccounts() {
52
+ return new Promise((resolve) => {
53
+ execFile('gh', ['auth', 'status'], (err, stdout, stderr) => {
54
+ const output = stderr || stdout || '';
55
+ const accounts = [];
56
+ const re = /Logged in to github\.com account (\S+)/g;
57
+ let m;
58
+ while ((m = re.exec(output)) !== null) {
59
+ accounts.push(m[1]);
60
+ }
61
+ resolve(accounts);
62
+ });
63
+ });
64
+ },
65
+
66
+ /**
67
+ * Get the auth token for a specific gh CLI user.
68
+ * @param {string} username
69
+ * @returns {Promise<string>}
70
+ */
71
+ tokenForUser(username) {
72
+ return new Promise((resolve, reject) => {
73
+ execFile('gh', ['auth', 'token', '-u', username], (err, stdout) => {
74
+ if (err) {
75
+ reject(new Error(`Failed to get token for gh user "${username}": ${err.message}`));
76
+ return;
77
+ }
78
+ resolve(stdout.trim());
79
+ });
80
+ });
81
+ },
82
+
83
+ /**
84
+ * Fetch a paginated GitHub API endpoint via gh CLI.
85
+ * @param {string} endpoint - e.g. /repos/{owner}/{repo}/issues
86
+ * @param {Object} [params] - query string parameters
87
+ * @param {Object} [opts] - options
88
+ * @param {string} [opts.ghUser] - specific gh CLI user to authenticate as
89
+ * @returns {Promise<Array>}
90
+ */
91
+ get(endpoint, params, opts) {
92
+ if (isSimulation()) {
93
+ return Promise.resolve(loadFixture(getFixturesDir(), endpoint));
94
+ }
95
+
96
+ const resolveToken = (opts && opts.ghUser)
97
+ ? ghCli.tokenForUser(opts.ghUser)
98
+ : Promise.resolve(null);
99
+
100
+ return resolveToken.then((token) => new Promise((resolve, reject) => {
101
+ // Build query string into the URL — using -f flags would send POST form
102
+ // fields, causing GET endpoints to 422 or 404.
103
+ let fullEndpoint = endpoint;
104
+ if (params) {
105
+ const qs = Object.entries(params)
106
+ .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
107
+ .join('&');
108
+ fullEndpoint += (endpoint.includes('?') ? '&' : '?') + qs;
109
+ }
110
+ const args = ['api', '--paginate', fullEndpoint];
111
+
112
+ const execOpts = { maxBuffer: 50 * 1024 * 1024 };
113
+ if (token) {
114
+ execOpts.env = { ...process.env, GH_TOKEN: token };
115
+ }
116
+
117
+ execFile('gh', args, execOpts, (err, stdout, stderr) => {
118
+ if (err) {
119
+ reject(new Error(`gh api failed: ${stderr || err.message}`));
120
+ return;
121
+ }
122
+
123
+ const trimmed = stdout.trim();
124
+ try {
125
+ // gh --paginate may emit multiple JSON arrays — one per page.
126
+ // Concatenate them into a single array.
127
+ if (!trimmed) {
128
+ resolve([]);
129
+ return;
130
+ }
131
+
132
+ // Try parsing as a single JSON value first
133
+ const parsed = JSON.parse(trimmed);
134
+ if (Array.isArray(parsed)) {
135
+ resolve(parsed);
136
+ } else if (parsed && typeof parsed === 'object') {
137
+ // Unwrap GitHub API wrapper objects like { workflow_runs: [...] }
138
+ const arrayProp = Object.values(parsed).find(v => Array.isArray(v));
139
+ resolve(arrayProp || [parsed]);
140
+ } else {
141
+ resolve([parsed]);
142
+ }
143
+ } catch (_parseErr) {
144
+ // gh --paginate may output multiple JSON values concatenated
145
+ // together: arrays (][), objects (}{), or newline-separated.
146
+ try {
147
+ const results = [];
148
+ // Split into individual JSON values by tracking brace/bracket depth
149
+ let depth = 0;
150
+ let start = 0;
151
+ let inString = false;
152
+ let escape = false;
153
+ for (let i = 0; i < trimmed.length; i++) {
154
+ const c = trimmed[i];
155
+ if (escape) { escape = false; continue; }
156
+ if (c === '\\' && inString) { escape = true; continue; }
157
+ if (c === '"') { inString = !inString; continue; }
158
+ if (inString) continue;
159
+ if (c === '{' || c === '[') depth++;
160
+ else if (c === '}' || c === ']') {
161
+ depth--;
162
+ if (depth === 0) {
163
+ const chunk = trimmed.slice(start, i + 1);
164
+ const val = JSON.parse(chunk);
165
+ if (Array.isArray(val)) {
166
+ results.push(...val);
167
+ } else if (val && typeof val === 'object') {
168
+ const arrayProp = Object.values(val).find(v => Array.isArray(v));
169
+ if (arrayProp) {
170
+ results.push(...arrayProp);
171
+ } else {
172
+ results.push(val);
173
+ }
174
+ } else {
175
+ results.push(val);
176
+ }
177
+ start = i + 1;
178
+ }
179
+ }
180
+ }
181
+ resolve(results);
182
+ } catch (mergeErr) {
183
+ reject(new Error(`Failed to parse gh api output: ${mergeErr.message}`));
184
+ }
185
+ }
186
+ });
187
+ }));
188
+ },
189
+ };
190
+
191
+ // ── HTTPS transport ─────────────────────────────────────────────────────────
192
+
193
+ const httpsTransport = {
194
+ /**
195
+ * Fetch a paginated GitHub API endpoint via Node's built-in https module.
196
+ * @param {string} token - GitHub personal access token
197
+ * @param {string} endpoint - e.g. /repos/{owner}/{repo}/issues
198
+ * @param {Object} [params] - query string parameters
199
+ * @returns {Promise<Array>}
200
+ */
201
+ get(token, endpoint, params) {
202
+ if (isSimulation()) {
203
+ return Promise.resolve(loadFixture(getFixturesDir(), endpoint));
204
+ }
205
+
206
+ const results = [];
207
+
208
+ function buildPath(ep, p) {
209
+ const qs = p ? Object.entries(p).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join('&') : '';
210
+ return qs ? `${ep}?${qs}` : ep;
211
+ }
212
+
213
+ function fetchPage(url) {
214
+ return new Promise((resolve, reject) => {
215
+ const opts = typeof url === 'string' && url.startsWith('https://')
216
+ ? new URL(url)
217
+ : {
218
+ hostname: 'api.github.com',
219
+ path: buildPath(endpoint, params),
220
+ method: 'GET',
221
+ };
222
+
223
+ const reqOpts = {
224
+ hostname: opts.hostname || 'api.github.com',
225
+ path: opts.pathname ? opts.pathname + (opts.search || '') : opts.path,
226
+ method: 'GET',
227
+ headers: {
228
+ 'User-Agent': 'wayfind',
229
+ Accept: 'application/vnd.github+json',
230
+ Authorization: `token ${token}`,
231
+ },
232
+ };
233
+
234
+ const req = https.request(reqOpts, (res) => {
235
+ const chunks = [];
236
+ res.on('data', (chunk) => chunks.push(chunk));
237
+ res.on('end', () => {
238
+ if (res.statusCode < 200 || res.statusCode >= 300) {
239
+ reject(new Error(`GitHub API returned ${res.statusCode}: ${Buffer.concat(chunks).toString()}`));
240
+ return;
241
+ }
242
+
243
+ try {
244
+ const body = JSON.parse(Buffer.concat(chunks).toString());
245
+ let items;
246
+ if (Array.isArray(body)) {
247
+ items = body;
248
+ } else if (body && typeof body === 'object') {
249
+ // Unwrap GitHub API wrapper objects like { workflow_runs: [...] }
250
+ const arrayProp = Object.values(body).find(v => Array.isArray(v));
251
+ items = arrayProp || [body];
252
+ } else {
253
+ items = [body];
254
+ }
255
+ results.push(...items);
256
+
257
+ // Follow pagination via Link header
258
+ const linkHeader = res.headers.link;
259
+ const nextUrl = parseLinkNext(linkHeader);
260
+ if (nextUrl) {
261
+ // Clear params for subsequent pages — the URL already contains them
262
+ fetchPage(nextUrl).then(resolve).catch(reject);
263
+ } else {
264
+ resolve(results);
265
+ }
266
+ } catch (parseErr) {
267
+ reject(new Error(`Failed to parse GitHub API response: ${parseErr.message}`));
268
+ }
269
+ });
270
+ });
271
+
272
+ req.on('error', reject);
273
+ req.end();
274
+ });
275
+ }
276
+
277
+ return fetchPage(null);
278
+ },
279
+ };
280
+
281
+ /**
282
+ * Parse the `next` URL from a GitHub Link header.
283
+ * @param {string|undefined} header
284
+ * @returns {string|null}
285
+ */
286
+ function parseLinkNext(header) {
287
+ if (!header) return null;
288
+ const parts = header.split(',');
289
+ for (const part of parts) {
290
+ const match = part.match(/<([^>]+)>;\s*rel="next"/);
291
+ if (match) return match[1];
292
+ }
293
+ return null;
294
+ }
295
+
296
+ // ── Auto-detect transport ───────────────────────────────────────────────────
297
+
298
+ /**
299
+ * Detect the best available transport method.
300
+ * Prefers gh CLI; falls back to HTTPS with a token from gh or environment.
301
+ * @returns {Promise<{ type: 'gh-cli' } | { type: 'https', token: string }>}
302
+ */
303
+ async function detect() {
304
+ const hasGh = await ghCli.available();
305
+ if (hasGh) {
306
+ return { type: 'gh-cli' };
307
+ }
308
+
309
+ // Fall back to HTTPS — look for a token in the environment
310
+ const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN || '';
311
+ if (token) {
312
+ return { type: 'https', token };
313
+ }
314
+
315
+ // No transport available
316
+ return { type: 'https', token: '' };
317
+ }
318
+
319
+ module.exports = {
320
+ detect,
321
+ ghCli,
322
+ https: httpsTransport,
323
+ loadFixture,
324
+ isSimulation,
325
+ };