preflight-mcp 0.1.4 → 0.1.5
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/dist/bundle/githubArchive.js +1 -1
- package/dist/bundle/service.js +63 -18
- package/dist/context7/client.js +1 -1
- package/dist/server.js +51 -13
- package/package.json +1 -1
package/dist/bundle/service.js
CHANGED
|
@@ -746,6 +746,7 @@ async function cloneAndIngestGitHubRepo(params) {
|
|
|
746
746
|
let repoRootForIngest = tmpCheckoutGit;
|
|
747
747
|
let headSha;
|
|
748
748
|
const notes = [];
|
|
749
|
+
const warnings = [];
|
|
749
750
|
let source = 'git';
|
|
750
751
|
let fetchedAt = nowIso();
|
|
751
752
|
let refUsed = params.ref;
|
|
@@ -763,23 +764,49 @@ async function cloneAndIngestGitHubRepo(params) {
|
|
|
763
764
|
catch (err) {
|
|
764
765
|
// Fallback: GitHub archive download (zipball) + extract.
|
|
765
766
|
source = 'archive';
|
|
766
|
-
const
|
|
767
|
-
notes.push(`git clone failed; used GitHub archive fallback: ${
|
|
767
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
768
|
+
notes.push(`git clone failed; used GitHub archive fallback: ${errMsg}`);
|
|
769
|
+
// User-facing warning: communicate the network issue clearly
|
|
770
|
+
warnings.push(`⚠️ [${repoId}] Git clone failed (network issue), switched to ZIP download.\n` +
|
|
771
|
+
` Reason: ${errMsg.slice(0, 200)}${errMsg.length > 200 ? '...' : ''}`);
|
|
768
772
|
params.onProgress?.('downloading', 0, `Downloading ${repoId} archive...`);
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
773
|
+
// Track zip path for error message
|
|
774
|
+
const zipPath = path.join(tmpArchiveDir, `github-zipball-${params.owner}-${params.repo}-partial.zip`);
|
|
775
|
+
try {
|
|
776
|
+
const archive = await downloadAndExtractGitHubArchive({
|
|
777
|
+
cfg: params.cfg,
|
|
778
|
+
owner: params.owner,
|
|
779
|
+
repo: params.repo,
|
|
780
|
+
ref: params.ref,
|
|
781
|
+
destDir: tmpArchiveDir,
|
|
782
|
+
onProgress: (downloaded, total, msg) => {
|
|
783
|
+
const percent = total ? Math.round((downloaded / total) * 100) : 0;
|
|
784
|
+
params.onProgress?.('downloading', percent, `${repoId}: ${msg}`);
|
|
785
|
+
},
|
|
786
|
+
});
|
|
787
|
+
repoRootForIngest = archive.repoRoot;
|
|
788
|
+
fetchedAt = archive.fetchedAt;
|
|
789
|
+
refUsed = archive.refUsed;
|
|
790
|
+
// Success: ZIP download completed
|
|
791
|
+
warnings.push(`✅ [${repoId}] ZIP download completed successfully as fallback.`);
|
|
792
|
+
}
|
|
793
|
+
catch (zipErr) {
|
|
794
|
+
// ZIP download also failed - provide helpful error with temp path
|
|
795
|
+
const zipErrMsg = zipErr instanceof Error ? zipErr.message : String(zipErr);
|
|
796
|
+
// Check if partial file exists
|
|
797
|
+
const partialExists = await statOrNull(tmpArchiveDir);
|
|
798
|
+
const tempPathMsg = partialExists
|
|
799
|
+
? `\n Partial files may exist in: ${tmpArchiveDir}`
|
|
800
|
+
: '';
|
|
801
|
+
throw new Error(`Both git clone and ZIP download failed for ${repoId}.\n\n` +
|
|
802
|
+
`Git error: ${errMsg.slice(0, 150)}\n` +
|
|
803
|
+
`ZIP error: ${zipErrMsg.slice(0, 150)}${tempPathMsg}\n\n` +
|
|
804
|
+
`Suggestions:\n` +
|
|
805
|
+
`1. Check your network connection\n` +
|
|
806
|
+
`2. Verify the repository exists: https://github.com/${repoId}\n` +
|
|
807
|
+
`3. If you have the repo locally, use 'kind: local' with 'path: /your/local/path'\n` +
|
|
808
|
+
`4. If behind a proxy, configure GITHUB_TOKEN environment variable`);
|
|
809
|
+
}
|
|
783
810
|
}
|
|
784
811
|
const bundlePaths = getBundlePaths(params.storageDir, params.bundleId);
|
|
785
812
|
const rawDest = repoRawDir(bundlePaths, params.owner, params.repo);
|
|
@@ -813,7 +840,7 @@ async function cloneAndIngestGitHubRepo(params) {
|
|
|
813
840
|
});
|
|
814
841
|
await rmIfExists(tmpCheckoutGit);
|
|
815
842
|
await rmIfExists(tmpArchiveDir);
|
|
816
|
-
return { headSha, files: ingested.files, skipped: ingested.skipped, notes, source };
|
|
843
|
+
return { headSha, files: ingested.files, skipped: ingested.skipped, notes, warnings, source };
|
|
817
844
|
}
|
|
818
845
|
function groupFilesByRepoId(files) {
|
|
819
846
|
const byRepo = new Map();
|
|
@@ -912,6 +939,9 @@ async function createBundleInternal(cfg, input, options) {
|
|
|
912
939
|
const finalPaths = getBundlePaths(effectiveStorageDir, bundleId);
|
|
913
940
|
const allIngestedFiles = [];
|
|
914
941
|
const reposSummary = [];
|
|
942
|
+
const allWarnings = [];
|
|
943
|
+
// Track temp checkout directory for cleanup
|
|
944
|
+
const tmpCheckoutsDir = path.join(cfg.tmpDir, 'checkouts', bundleId);
|
|
915
945
|
try {
|
|
916
946
|
// All operations happen in tmpPaths (temporary directory)
|
|
917
947
|
const totalRepos = input.repos.length;
|
|
@@ -923,7 +953,7 @@ async function createBundleInternal(cfg, input, options) {
|
|
|
923
953
|
const { owner, repo } = parseOwnerRepo(repoInput.repo);
|
|
924
954
|
reportProgress('cloning', repoProgress, `[${repoIndex}/${totalRepos}] Fetching ${owner}/${repo}...`);
|
|
925
955
|
tracker.updateProgress(taskId, 'cloning', repoProgress, `Fetching ${owner}/${repo}...`);
|
|
926
|
-
const { headSha, files, skipped, notes, source } = await cloneAndIngestGitHubRepo({
|
|
956
|
+
const { headSha, files, skipped, notes, warnings, source } = await cloneAndIngestGitHubRepo({
|
|
927
957
|
cfg,
|
|
928
958
|
bundleId,
|
|
929
959
|
storageDir: tmpBundlesDir,
|
|
@@ -938,6 +968,7 @@ async function createBundleInternal(cfg, input, options) {
|
|
|
938
968
|
},
|
|
939
969
|
});
|
|
940
970
|
allIngestedFiles.push(...files);
|
|
971
|
+
allWarnings.push(...warnings);
|
|
941
972
|
reposSummary.push({
|
|
942
973
|
kind: 'github',
|
|
943
974
|
id: `${owner}/${repo}`,
|
|
@@ -1112,6 +1143,7 @@ async function createBundleInternal(cfg, input, options) {
|
|
|
1112
1143
|
updatedAt: createdAt,
|
|
1113
1144
|
repos: reposSummary,
|
|
1114
1145
|
libraries: librariesSummary,
|
|
1146
|
+
warnings: allWarnings.length > 0 ? allWarnings : undefined,
|
|
1115
1147
|
};
|
|
1116
1148
|
return summary;
|
|
1117
1149
|
}
|
|
@@ -1135,6 +1167,10 @@ async function createBundleInternal(cfg, input, options) {
|
|
|
1135
1167
|
await rmIfExists(tmpPaths.rootDir).catch((err) => {
|
|
1136
1168
|
logger.debug('Failed to cleanup temp bundle directory in finally block (non-critical)', err instanceof Error ? err : undefined);
|
|
1137
1169
|
});
|
|
1170
|
+
// Clean up temp checkouts directory (git clones, zip extracts)
|
|
1171
|
+
await rmIfExists(tmpCheckoutsDir).catch((err) => {
|
|
1172
|
+
logger.debug('Failed to cleanup temp checkouts directory in finally block (non-critical)', err instanceof Error ? err : undefined);
|
|
1173
|
+
});
|
|
1138
1174
|
}
|
|
1139
1175
|
}
|
|
1140
1176
|
/** Check if a bundle has upstream changes without applying updates. */
|
|
@@ -1297,6 +1333,14 @@ export async function repairBundle(cfg, bundleId, options) {
|
|
|
1297
1333
|
// Manifest is required for safe repairs (no fetching/re-ingest).
|
|
1298
1334
|
const manifest = await readManifest(paths.manifestPath);
|
|
1299
1335
|
const actionsTaken = [];
|
|
1336
|
+
const unfixableIssues = [];
|
|
1337
|
+
// Check for unfixable issues (require re-download, can't be repaired offline)
|
|
1338
|
+
const reposHasContent = before.missingComponents.every(c => !c.includes('repos/'));
|
|
1339
|
+
if (!reposHasContent) {
|
|
1340
|
+
unfixableIssues.push('repos/ directory is empty or missing - this requires re-downloading the repository. ' +
|
|
1341
|
+
'Use preflight_delete_bundle and preflight_create_bundle to start fresh, ' +
|
|
1342
|
+
'or use preflight_update_bundle with force:true to re-fetch.');
|
|
1343
|
+
}
|
|
1300
1344
|
// Determine what needs repair.
|
|
1301
1345
|
const stAgents = await statOrNull(paths.agentsPath);
|
|
1302
1346
|
const stStartHere = await statOrNull(paths.startHerePath);
|
|
@@ -1384,6 +1428,7 @@ export async function repairBundle(cfg, bundleId, options) {
|
|
|
1384
1428
|
mode,
|
|
1385
1429
|
repaired: actionsTaken.length > 0,
|
|
1386
1430
|
actionsTaken,
|
|
1431
|
+
unfixableIssues: unfixableIssues.length > 0 ? unfixableIssues : undefined,
|
|
1387
1432
|
before,
|
|
1388
1433
|
after,
|
|
1389
1434
|
updatedAt,
|
package/dist/context7/client.js
CHANGED
|
@@ -20,7 +20,7 @@ export async function connectContext7(cfg) {
|
|
|
20
20
|
maxRetries: 1,
|
|
21
21
|
},
|
|
22
22
|
});
|
|
23
|
-
const client = new Client({ name: 'preflight-context7', version: '0.1.
|
|
23
|
+
const client = new Client({ name: 'preflight-context7', version: '0.1.5' });
|
|
24
24
|
await client.connect(transport);
|
|
25
25
|
return {
|
|
26
26
|
client,
|
package/dist/server.js
CHANGED
|
@@ -99,7 +99,7 @@ export async function startServer() {
|
|
|
99
99
|
startHttpServer(cfg);
|
|
100
100
|
const server = new McpServer({
|
|
101
101
|
name: 'preflight-mcp',
|
|
102
|
-
version: '0.1.
|
|
102
|
+
version: '0.1.5',
|
|
103
103
|
description: 'Create evidence-based preflight bundles for repositories (docs + code) with SQLite FTS search.',
|
|
104
104
|
}, {
|
|
105
105
|
capabilities: {
|
|
@@ -382,22 +382,23 @@ export async function startServer() {
|
|
|
382
382
|
description: 'Create a new bundle from GitHub repos or local directories (or update an existing one if ifExists=updateExisting). Use when: "index this repo", "create bundle for", "add repo to preflight", "索引这个仓库", "创建bundle", "添加GitHub项目", "学习这个项目". NOTE: If the bundle contains code files, consider asking user if they want to generate dependency graph (preflight_evidence_dependency_graph) or establish trace links (preflight_trace_upsert).',
|
|
383
383
|
inputSchema: CreateBundleInputSchema,
|
|
384
384
|
outputSchema: {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
385
|
+
// Normal completion fields
|
|
386
|
+
bundleId: z.string().optional(),
|
|
387
|
+
createdAt: z.string().optional(),
|
|
388
|
+
updatedAt: z.string().optional(),
|
|
388
389
|
resources: z.object({
|
|
389
390
|
startHere: z.string(),
|
|
390
391
|
agents: z.string(),
|
|
391
392
|
overview: z.string(),
|
|
392
393
|
manifest: z.string(),
|
|
393
|
-
}),
|
|
394
|
+
}).optional(),
|
|
394
395
|
repos: z.array(z.object({
|
|
395
396
|
kind: z.enum(['github', 'local']),
|
|
396
397
|
id: z.string(),
|
|
397
398
|
source: z.enum(['git', 'archive', 'local']).optional(),
|
|
398
399
|
headSha: z.string().optional(),
|
|
399
400
|
notes: z.array(z.string()).optional(),
|
|
400
|
-
})),
|
|
401
|
+
})).optional(),
|
|
401
402
|
libraries: z
|
|
402
403
|
.array(z.object({
|
|
403
404
|
kind: z.literal('context7'),
|
|
@@ -408,6 +409,20 @@ export async function startServer() {
|
|
|
408
409
|
files: z.array(z.string()).optional(),
|
|
409
410
|
}))
|
|
410
411
|
.optional(),
|
|
412
|
+
// User-facing warnings (e.g., git clone failed, used zip fallback)
|
|
413
|
+
warnings: z.array(z.string()).optional(),
|
|
414
|
+
// In-progress status fields
|
|
415
|
+
status: z.enum(['in-progress', 'complete']).optional(),
|
|
416
|
+
message: z.string().optional(),
|
|
417
|
+
taskId: z.string().optional(),
|
|
418
|
+
fingerprint: z.string().optional(),
|
|
419
|
+
/** Repo IDs requested (for in-progress status only, different from repos array) */
|
|
420
|
+
requestedRepos: z.array(z.string()).optional(),
|
|
421
|
+
startedAt: z.string().optional(),
|
|
422
|
+
elapsedSeconds: z.number().optional(),
|
|
423
|
+
currentPhase: z.string().optional(),
|
|
424
|
+
currentProgress: z.number().optional(),
|
|
425
|
+
currentMessage: z.string().optional(),
|
|
411
426
|
},
|
|
412
427
|
annotations: {
|
|
413
428
|
openWorldHint: true,
|
|
@@ -431,8 +446,19 @@ export async function startServer() {
|
|
|
431
446
|
...summary,
|
|
432
447
|
resources,
|
|
433
448
|
};
|
|
449
|
+
// Build text response - prominently show warnings if any
|
|
450
|
+
let textResponse = '';
|
|
451
|
+
if (summary.warnings && summary.warnings.length > 0) {
|
|
452
|
+
textResponse += '📢 **Network Issues Encountered:**\n';
|
|
453
|
+
for (const warn of summary.warnings) {
|
|
454
|
+
textResponse += `${warn}\n`;
|
|
455
|
+
}
|
|
456
|
+
textResponse += '\n';
|
|
457
|
+
}
|
|
458
|
+
textResponse += `✅ Bundle created: ${summary.bundleId}\n`;
|
|
459
|
+
textResponse += `Repos: ${summary.repos.map(r => `${r.id} (${r.source})`).join(', ')}`;
|
|
434
460
|
return {
|
|
435
|
-
content: [{ type: 'text', text:
|
|
461
|
+
content: [{ type: 'text', text: textResponse }],
|
|
436
462
|
structuredContent: out,
|
|
437
463
|
};
|
|
438
464
|
}
|
|
@@ -450,7 +476,7 @@ export async function startServer() {
|
|
|
450
476
|
message: `Bundle creation already in progress. Use preflight_get_task_status to check progress.`,
|
|
451
477
|
taskId: err.taskId,
|
|
452
478
|
fingerprint: err.fingerprint,
|
|
453
|
-
|
|
479
|
+
requestedRepos: err.repos,
|
|
454
480
|
startedAt: err.startedAt,
|
|
455
481
|
elapsedSeconds: elapsedSec,
|
|
456
482
|
currentPhase: task?.phase,
|
|
@@ -474,6 +500,8 @@ export async function startServer() {
|
|
|
474
500
|
mode: z.enum(['validate', 'repair']),
|
|
475
501
|
repaired: z.boolean(),
|
|
476
502
|
actionsTaken: z.array(z.string()),
|
|
503
|
+
/** Issues that cannot be fixed by repair (require re-download) */
|
|
504
|
+
unfixableIssues: z.array(z.string()).optional(),
|
|
477
505
|
before: z.object({
|
|
478
506
|
isValid: z.boolean(),
|
|
479
507
|
missingComponents: z.array(z.string()),
|
|
@@ -495,11 +523,21 @@ export async function startServer() {
|
|
|
495
523
|
rebuildGuides: args.rebuildGuides,
|
|
496
524
|
rebuildOverview: args.rebuildOverview,
|
|
497
525
|
});
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
: out.
|
|
501
|
-
|
|
502
|
-
|
|
526
|
+
let summaryLine;
|
|
527
|
+
if (out.mode === 'validate') {
|
|
528
|
+
summaryLine = `VALIDATE ${out.bundleId}: ${out.before.isValid ? 'OK' : 'INVALID'} (${out.before.missingComponents.length} issue(s))`;
|
|
529
|
+
}
|
|
530
|
+
else if (out.unfixableIssues && out.unfixableIssues.length > 0) {
|
|
531
|
+
// Has unfixable issues - clearly communicate this
|
|
532
|
+
summaryLine = `⚠️ UNFIXABLE ${out.bundleId}: ${out.unfixableIssues.length} issue(s) cannot be repaired offline.\n` +
|
|
533
|
+
out.unfixableIssues.map(i => ` - ${i}`).join('\n');
|
|
534
|
+
}
|
|
535
|
+
else if (out.repaired) {
|
|
536
|
+
summaryLine = `REPAIRED ${out.bundleId}: ${out.actionsTaken.length} action(s), now ${out.after.isValid ? 'OK' : 'STILL_INVALID'} (${out.after.missingComponents.length} issue(s))`;
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
summaryLine = `NOOP ${out.bundleId}: nothing to repair (already OK)`;
|
|
540
|
+
}
|
|
503
541
|
return {
|
|
504
542
|
content: [{ type: 'text', text: summaryLine }],
|
|
505
543
|
structuredContent: out,
|