preflight-mcp 0.1.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.
@@ -0,0 +1,222 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ function evidence(p, start, end) {
4
+ return `(evidence: ${p}:${start}-${end})`;
5
+ }
6
+ function parseOwnerRepoId(repoId) {
7
+ const parts = repoId.split('/');
8
+ if (parts.length !== 2)
9
+ return null;
10
+ const [owner, repo] = parts;
11
+ if (!owner || !repo)
12
+ return null;
13
+ return { owner, repo };
14
+ }
15
+ async function renderRepoMetaFacts(bundleRootDir, repoId) {
16
+ const parsed = parseOwnerRepoId(repoId);
17
+ if (!parsed)
18
+ return [];
19
+ const rel = `repos/${parsed.owner}/${parsed.repo}/meta.json`;
20
+ const abs = path.join(bundleRootDir, 'repos', parsed.owner, parsed.repo, 'meta.json');
21
+ let lines;
22
+ try {
23
+ lines = await readLines(abs);
24
+ }
25
+ catch {
26
+ return [];
27
+ }
28
+ let obj;
29
+ try {
30
+ obj = JSON.parse(lines.join('\n'));
31
+ }
32
+ catch {
33
+ return [];
34
+ }
35
+ const out = [];
36
+ const pushIf = (label, key) => {
37
+ const val = obj?.[key];
38
+ if (val === undefined)
39
+ return;
40
+ const ln = firstLineNumberContaining(lines, `"${key}"`);
41
+ if (!ln)
42
+ return;
43
+ out.push(`- ${label}: ${JSON.stringify(val)}. ${evidence(rel, ln, ln)}`);
44
+ };
45
+ pushIf('Snapshot commit', 'headSha');
46
+ pushIf('Fetched at', 'fetchedAt');
47
+ pushIf('Clone URL', 'cloneUrl');
48
+ pushIf('Ingested files', 'ingestedFiles');
49
+ return out;
50
+ }
51
+ function firstLineNumberContaining(lines, needle) {
52
+ for (let i = 0; i < lines.length; i++) {
53
+ if ((lines[i] ?? '').includes(needle))
54
+ return i + 1;
55
+ }
56
+ return null;
57
+ }
58
+ async function readLines(filePath) {
59
+ const raw = await fs.readFile(filePath, 'utf8');
60
+ // Norm files are LF; keep stable.
61
+ return raw.split('\n');
62
+ }
63
+ function getRepoDocFiles(files) {
64
+ return files
65
+ .filter((f) => f.kind === 'doc')
66
+ .sort((a, b) => a.repoRelativePath.localeCompare(b.repoRelativePath));
67
+ }
68
+ async function renderNodePackageFacts(files) {
69
+ const pkg = files.find((f) => f.repoRelativePath === 'package.json');
70
+ if (!pkg)
71
+ return [];
72
+ const lines = await readLines(pkg.bundleNormAbsPath);
73
+ const out = [];
74
+ out.push(`- Found package.json at ${pkg.bundleNormRelativePath}. ${evidence(pkg.bundleNormRelativePath, 1, 1)}`);
75
+ // Best-effort parse to list scripts.
76
+ try {
77
+ const obj = JSON.parse(lines.join('\n'));
78
+ const scripts = obj?.scripts && typeof obj.scripts === 'object' ? obj.scripts : null;
79
+ if (scripts) {
80
+ const keys = Object.keys(scripts).slice(0, 20);
81
+ for (const k of keys) {
82
+ const v = scripts[k];
83
+ if (typeof v !== 'string')
84
+ continue;
85
+ const ln = firstLineNumberContaining(lines, `"${k}"`);
86
+ const start = ln ?? 1;
87
+ const end = ln ?? 1;
88
+ out.push(` - script "${k}": ${JSON.stringify(v)}. ${evidence(pkg.bundleNormRelativePath, start, end)}`);
89
+ }
90
+ }
91
+ }
92
+ catch {
93
+ // If JSON is not parseable, stay silent.
94
+ }
95
+ // Best-effort main/module/types.
96
+ for (const key of ['main', 'module', 'types', 'typings']) {
97
+ const ln = firstLineNumberContaining(lines, `"${key}"`);
98
+ if (ln) {
99
+ out.push(`- package.json contains key "${key}". ${evidence(pkg.bundleNormRelativePath, ln, ln)}`);
100
+ }
101
+ }
102
+ return out;
103
+ }
104
+ function safeContext7IdSegments(context7Id) {
105
+ const raw = context7Id.trim().replace(/^\/+/, '');
106
+ const parts = raw.split('/').filter(Boolean);
107
+ return parts.filter((p) => p !== '.' && p !== '..');
108
+ }
109
+ function slug(s) {
110
+ return s
111
+ .trim()
112
+ .toLowerCase()
113
+ .replace(/[^a-z0-9._-]+/g, '_')
114
+ .replace(/^_+|_+$/g, '')
115
+ .slice(0, 64);
116
+ }
117
+ function context7MetaRelPath(lib) {
118
+ if (lib.id) {
119
+ const segs = safeContext7IdSegments(lib.id);
120
+ return `libraries/context7/${segs.join('/')}/meta.json`;
121
+ }
122
+ return `libraries/context7/_unresolved/${slug(lib.input) || 'library'}/meta.json`;
123
+ }
124
+ async function renderContext7LibraryFacts(bundleRootDir, lib) {
125
+ const relMeta = context7MetaRelPath(lib);
126
+ const absMeta = path.join(bundleRootDir, ...relMeta.split('/'));
127
+ let lines;
128
+ try {
129
+ lines = await readLines(absMeta);
130
+ }
131
+ catch {
132
+ return [];
133
+ }
134
+ const out = [];
135
+ // Existence pointer.
136
+ out.push(`- Meta file: ${relMeta}. ${evidence(relMeta, 1, 1)}`);
137
+ const pushIf = (label, key) => {
138
+ const ln = firstLineNumberContaining(lines, `"${key}"`);
139
+ if (!ln)
140
+ return;
141
+ out.push(`- ${label}. ${evidence(relMeta, ln, ln)}`);
142
+ };
143
+ pushIf('Library input recorded', 'input');
144
+ pushIf('Context7 ID recorded', 'id');
145
+ pushIf('Fetched at recorded', 'fetchedAt');
146
+ pushIf('Topics recorded', 'topics');
147
+ if (lib.files && lib.files.length) {
148
+ out.push('- Docs files:');
149
+ for (const f of lib.files.slice(0, 20)) {
150
+ out.push(` - ${f}. ${evidence(f, 1, 1)}`);
151
+ }
152
+ }
153
+ else {
154
+ const ln = firstLineNumberContaining(lines, `"files"`) ?? 1;
155
+ out.push(`- No docs files listed. ${evidence(relMeta, ln, ln)}`);
156
+ }
157
+ return out;
158
+ }
159
+ export async function generateOverviewMarkdown(params) {
160
+ const header = `# OVERVIEW.md - Preflight Bundle ${params.bundleId}
161
+
162
+ This file is generated. It contains **only factual statements** with evidence pointers into bundle files.
163
+
164
+ `;
165
+ const sections = [header];
166
+ for (const r of params.repos) {
167
+ sections.push(`## Repo: ${r.repoId}`);
168
+ const metaFacts = await renderRepoMetaFacts(params.bundleRootDir, r.repoId);
169
+ if (metaFacts.length) {
170
+ sections.push('### Snapshot facts');
171
+ sections.push(...metaFacts);
172
+ }
173
+ const nodeFacts = await renderNodePackageFacts(r.files);
174
+ if (nodeFacts.length) {
175
+ sections.push('### Node/JS facts');
176
+ sections.push(...nodeFacts);
177
+ }
178
+ const docs = getRepoDocFiles(r.files).slice(0, 50);
179
+ if (docs.length) {
180
+ sections.push('### Documentation files (first 50)');
181
+ for (const d of docs) {
182
+ sections.push(`- ${d.bundleNormRelativePath}. ${evidence(d.bundleNormRelativePath, 1, 1)}`);
183
+ }
184
+ }
185
+ // Give a small hint about where code lives, without guessing entry points.
186
+ const codeSamples = r.files
187
+ .filter((f) => f.kind === 'code')
188
+ .map((f) => f.repoRelativePath)
189
+ .filter((p) => p.startsWith('src/') || p.startsWith('lib/'))
190
+ .slice(0, 10);
191
+ if (codeSamples.length) {
192
+ sections.push('### Code paths spotted (sample)');
193
+ for (const p of codeSamples) {
194
+ const file = r.files.find((f) => f.repoRelativePath === p);
195
+ if (!file)
196
+ continue;
197
+ sections.push(`- ${file.bundleNormRelativePath}. ${evidence(file.bundleNormRelativePath, 1, 1)}`);
198
+ }
199
+ }
200
+ sections.push('');
201
+ }
202
+ const libs = params.libraries ?? [];
203
+ if (libs.length) {
204
+ sections.push('## Context7 libraries');
205
+ for (const lib of libs) {
206
+ const facts = await renderContext7LibraryFacts(params.bundleRootDir, lib);
207
+ sections.push(`### ${lib.input}`);
208
+ if (facts.length) {
209
+ sections.push(...facts);
210
+ }
211
+ else {
212
+ sections.push('- No library facts available.');
213
+ }
214
+ sections.push('');
215
+ }
216
+ }
217
+ return sections.join('\n') + '\n';
218
+ }
219
+ export async function writeOverviewFile(targetPath, markdown) {
220
+ await fs.mkdir(path.dirname(targetPath), { recursive: true });
221
+ await fs.writeFile(targetPath, markdown, 'utf8');
222
+ }
@@ -0,0 +1,29 @@
1
+ import path from 'node:path';
2
+ export function getBundlePaths(storageDir, bundleId) {
3
+ const rootDir = path.join(storageDir, bundleId);
4
+ const indexesDir = path.join(rootDir, 'indexes');
5
+ return {
6
+ bundleId,
7
+ rootDir,
8
+ manifestPath: path.join(rootDir, 'manifest.json'),
9
+ startHerePath: path.join(rootDir, 'START_HERE.md'),
10
+ agentsPath: path.join(rootDir, 'AGENTS.md'),
11
+ overviewPath: path.join(rootDir, 'OVERVIEW.md'),
12
+ indexesDir,
13
+ searchDbPath: path.join(indexesDir, 'search.sqlite3'),
14
+ reposDir: path.join(rootDir, 'repos'),
15
+ librariesDir: path.join(rootDir, 'libraries'),
16
+ };
17
+ }
18
+ export function repoRootDir(paths, owner, repo) {
19
+ return path.join(paths.reposDir, owner, repo);
20
+ }
21
+ export function repoRawDir(paths, owner, repo) {
22
+ return path.join(repoRootDir(paths, owner, repo), 'raw');
23
+ }
24
+ export function repoNormDir(paths, owner, repo) {
25
+ return path.join(repoRootDir(paths, owner, repo), 'norm');
26
+ }
27
+ export function repoMetaPath(paths, owner, repo) {
28
+ return path.join(repoRootDir(paths, owner, repo), 'meta.json');
29
+ }