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,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
|
+
};
|