mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 16:27:18 +02:00
Automatically import and update project repository skills (#16066)
Automatically import skills from new GitHub project repositories and update enabled sources in the background. Preserve project-derived access, saved selections, immutable execution versions and explicit pins; add legacy adoption, refresh and connection repair in Sources. Include durable scheduling and bounded work credits, production Storybook journeys, real GitHub-to-routine acceptance receipts and final-head CI/security/Greptile qualification. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
de335b42d4
commit
473c80d13f
101 files changed
+5991
-186
No files matched your search
@@ -14,9 +14,10 @@ export interface GitHubRead {
|
||||
withScan?: <T>(operation: () => Promise<T>) => Promise<T>;
|
||||
openSnapshot: (input: { repositoryUrl: string; ref: string; commitSha?: string }, options?: GitSkillSnapshotOptions) => Promise<GitSkillSnapshot>;
|
||||
readonly connectionId?: string | null;
|
||||
readonly grantId?: string | null;
|
||||
}
|
||||
export type DiscoveredSkill = SkillSourceCandidate & { files: CompanySkillVersionFileInventoryEntry[] };
|
||||
export type ScannedSkillSource = SkillSourceDiscovery & { skills: DiscoveredSkill[]; defaultBranch: string; repositoryFiles?: CompanySkillVersionFileInventoryEntry[] };
|
||||
export type ScannedSkillSource = SkillSourceDiscovery & { skills: DiscoveredSkill[]; defaultBranch: string; treeSha?: string; downloadBytes?: number; scanBytes?: number; repositoryFiles?: CompanySkillVersionFileInventoryEntry[] };
|
||||
const MAX_FILE_BYTES = 1024 * 1024;
|
||||
const MAX_SCAN_BYTES = 100 * 1024 * 1024;
|
||||
const MAX_SCAN_PACKAGES = 1_000;
|
||||
@@ -42,6 +43,28 @@ export function scanGitHubSkills(input: SkillScanInput, providerRead: GitHubRead
|
||||
return providerRead.withScan ? providerRead.withScan(scan) : scan();
|
||||
}
|
||||
|
||||
/** Oversized repositories may still contain valid, bounded skill packages. */
|
||||
export async function scanGitHubSkillsWithPackageFallback(input: SkillScanInput, providerRead: GitHubRead, options: SkillScanOptions = {}): Promise<ScannedSkillSource> {
|
||||
try { return await scanGitHubSkills(input, providerRead, options); }
|
||||
catch (error) {
|
||||
if (input.packageMode !== 'repository' || !(error instanceof Error) || !/^Repository package exceeds/.test(error.message)) throw error;
|
||||
options.signal?.throwIfAborted();
|
||||
const scan = await scanGitHubSkills({ ...input, packageMode: 'skills' }, providerRead, options);
|
||||
rejectIncompleteSkillPackages(scan);
|
||||
scan.warnings.push('Only skill packages were imported because the repository exceeds the supported package size.');
|
||||
return scan;
|
||||
}
|
||||
}
|
||||
|
||||
export function rejectIncompleteSkillPackages(scan: ScannedSkillSource): void {
|
||||
for (const skill of scan.skills) {
|
||||
if (!skill.error && skill.inspection?.references.length) {
|
||||
skill.error = 'This skill depends on files that its imported package cannot provide. Its dependencies could not be preserved. Import a supported repository package or include the dependencies in this skill package.';
|
||||
}
|
||||
}
|
||||
scan.candidates = scan.skills.map(({ files: _files, ...candidate }) => candidate);
|
||||
}
|
||||
|
||||
async function scanRepository(input: SkillScanInput, providerRead: GitHubRead, options: SkillScanOptions): Promise<ScannedSkillSource> {
|
||||
const read = async (apiPath: string) => {
|
||||
options.signal?.throwIfAborted();
|
||||
@@ -249,7 +272,9 @@ async function scanRepository(input: SkillScanInput, providerRead: GitHubRead, o
|
||||
if (skill.inspection) skill.inspection.references = skill.inspection.references.filter(ref => !included.has(ref.resolvedPath));
|
||||
}
|
||||
}
|
||||
return { ...(input.packageMode ? { packageMode: input.packageMode } : {}), ...(repositoryPackage ? { repositoryPackage } : {}),
|
||||
return { treeSha: snapshot.treeSha, downloadBytes: snapshot.downloadBytes,
|
||||
scanBytes: Math.min(MAX_SCAN_BYTES, snapshot.entries.reduce((sum, entry) => sum + (entry.type === 'blob' ? entry.size ?? MAX_SCAN_BYTES : 0), 0)),
|
||||
...(input.packageMode ? { packageMode: input.packageMode } : {}), ...(repositoryPackage ? { repositoryPackage } : {}),
|
||||
...(repositoryFiles && options.retainFiles !== false ? { repositoryFiles } : {}),
|
||||
connectionId: providerRead.connectionId ?? null, repositoryId: String(repo.id), repositoryUrl: `https://github.com/${repo.full_name.toLowerCase()}`, fullName: repo.full_name, trackingRef, commitSha: commit.sha,
|
||||
defaultBranch: repo.default_branch, candidates: skills.map(({ files: _files, ...candidate }) => candidate), warnings, skills };
|
||||
|
||||
Reference in new issue
Block a user