548d958f18
Build: Production / build (push) Failing after 12m39s
The fork's listRuntimeSkillEntries rematerialized every skill's files from the DB on every heartbeat run dispatch — fs.rm + fs.mkdir + per-file readFile/writeFile, sequentially per skill. With 24 configured skills and 5 concurrent agents, this saturated the Node event loop badly enough that executeRun continuations couldn't reach activeRunExecutions.add() within the orphan-reaper's 5-min threshold, causing reaper to false-positive runs as "process_lost". Upstream's listRuntimeSkillEntries calls resolveRuntimeSkillSource, which checks if the materialized directory already exists on disk and short- circuits when it does. Fixes the symptom at the root. Replaces these files with upstream/master content: - server/src/services/company-skills.ts - server/src/services/heartbeat.ts - server/src/services/workspace-runtime.ts - server/src/services/company-portability.ts - server/src/routes/company-skills.ts - server/src/routes/agents.ts - packages/adapter-utils/src/server-utils.ts Pulls in supporting upstream files: - server/src/services/catalog-provenance.ts - server/src/services/skills-catalog.ts - server/src/services/github-fetch.ts - server/src/services/portable-path.ts - packages/skills-catalog/ (new package) - packages/db document_annotation_* schema + migration 0091 - packages/shared document-annotation types/validators Drops fork features (to be re-evaluated later): - Gitea/Forgejo git skill sources (server/src/services/git-source.ts deleted) - PAT support for private skill repos - Fork-specific secret-export portability extensions Adds agentId: null to acquireRunLease test-probe call in routes/agents.ts to satisfy the fork's environment-runtime agentId requirement (kept). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
91 lines
3.8 KiB
TypeScript
91 lines
3.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { catalogManifest, catalogSkills, resolveCatalogSkillRef } from "./index.js";
|
|
import type { CatalogSkill } from "./types.js";
|
|
|
|
const EXPECTED_BUNDLED_KEYS = [
|
|
"paperclipai/bundled/docs/doc-maintenance",
|
|
"paperclipai/bundled/paperclip-operations/issue-triage",
|
|
"paperclipai/bundled/paperclip-operations/task-planning",
|
|
"paperclipai/bundled/quality/qa-acceptance",
|
|
"paperclipai/bundled/software-development/github-pr-workflow",
|
|
];
|
|
|
|
const EXPECTED_OPTIONAL_KEYS = [
|
|
"paperclipai/optional/browser/agent-browser",
|
|
"paperclipai/optional/content/release-announcement",
|
|
"paperclipai/optional/product/design-critique",
|
|
];
|
|
|
|
describe("shipped skills catalog", () => {
|
|
it("ships the expected bundled and optional skill set", () => {
|
|
const bundledKeys = catalogSkills
|
|
.filter((skill) => skill.kind === "bundled")
|
|
.map((skill) => skill.key)
|
|
.sort();
|
|
const optionalKeys = catalogSkills
|
|
.filter((skill) => skill.kind === "optional")
|
|
.map((skill) => skill.key)
|
|
.sort();
|
|
|
|
expect(bundledKeys).toEqual(EXPECTED_BUNDLED_KEYS);
|
|
expect(optionalKeys).toEqual(EXPECTED_OPTIONAL_KEYS);
|
|
});
|
|
|
|
it("keeps every shipped skill markdown-only until a script-bearing skill clears security review", () => {
|
|
const scriptBearing = catalogSkills.filter((skill) => skill.trustLevel !== "markdown_only");
|
|
expect(scriptBearing, formatViolations("script-bearing skills require security review", scriptBearing)).toEqual([]);
|
|
});
|
|
|
|
it("populates browse/search-relevant fields for every shipped skill", () => {
|
|
const issues: string[] = [];
|
|
for (const skill of catalogSkills) {
|
|
if (skill.compatibility !== "compatible") {
|
|
issues.push(`${skill.key} compatibility=${skill.compatibility}`);
|
|
}
|
|
if (!skill.description || skill.description.length < 40) {
|
|
issues.push(`${skill.key} description must be at least 40 characters for catalog browse/search`);
|
|
}
|
|
if (skill.recommendedForRoles.length === 0) {
|
|
issues.push(`${skill.key} must list recommendedForRoles`);
|
|
}
|
|
if (skill.tags.length === 0) {
|
|
issues.push(`${skill.key} must list tags`);
|
|
}
|
|
}
|
|
expect(issues).toEqual([]);
|
|
});
|
|
|
|
it("uses canonical paperclipai keys derived from kind/category/slug", () => {
|
|
const violations: string[] = [];
|
|
for (const skill of catalogSkills) {
|
|
const expectedKey = `paperclipai/${skill.kind}/${skill.category}/${skill.slug}`;
|
|
const expectedId = `paperclipai:${skill.kind}:${skill.category}:${skill.slug}`;
|
|
if (skill.key !== expectedKey) violations.push(`${skill.key} should be ${expectedKey}`);
|
|
if (skill.id !== expectedId) violations.push(`${skill.id} should be ${expectedId}`);
|
|
}
|
|
expect(violations).toEqual([]);
|
|
});
|
|
|
|
it("exposes a stable manifest header for downstream consumers", () => {
|
|
expect(catalogManifest.schemaVersion).toBe(1);
|
|
expect(catalogManifest.packageName).toBe("@paperclipai/skills-catalog");
|
|
expect(catalogSkills.length).toBe(EXPECTED_BUNDLED_KEYS.length + EXPECTED_OPTIONAL_KEYS.length);
|
|
});
|
|
|
|
it("resolves shipped skills by id, key, and unique slug", () => {
|
|
const sample = catalogSkills.find((skill) => skill.key === "paperclipai/bundled/software-development/github-pr-workflow");
|
|
expect(sample, "expected github-pr-workflow to ship in the bundled catalog").toBeDefined();
|
|
if (!sample) return;
|
|
|
|
expect(resolveCatalogSkillRef(sample.id)).toMatchObject({ key: sample.key });
|
|
expect(resolveCatalogSkillRef(sample.key)).toMatchObject({ key: sample.key });
|
|
expect(resolveCatalogSkillRef(sample.slug)).toMatchObject({ key: sample.key });
|
|
});
|
|
});
|
|
|
|
function formatViolations(label: string, skills: CatalogSkill[]) {
|
|
if (skills.length === 0) return label;
|
|
const detail = skills.map((skill) => `${skill.key} (${skill.trustLevel})`).join(", ");
|
|
return `${label}: ${detail}`;
|
|
}
|