feat(skills): add managed skill rename API (#9688)

## Thinking Path

> - Paperclip is the open source control plane people use to manage
AI-agent companies.
> - Company skills are reusable capabilities that operators install,
edit, assign, and materialize for agents.
> - Managed local skills currently lack a safe backend operation for
changing their display name and canonical slug/key together.
> - Treating rename as an ordinary save can leave duplicate records,
stale runtime materializations, or agent assignments pointing at the old
key.
> - This pull request adds a company-scoped managed-skill rename
contract, service operation, and REST endpoint with focused
authorization and activity logging.
> - The benefit is an atomic-enough, recoverable rename path that keeps
disk state, database identity, and agent skill assignments synchronized.

## Linked Issues or Issue Description

- Refs #2121
- Problem: managed company skills need a dedicated rename operation
rather than save-time duplication behavior.
- Expected behavior: renaming a managed skill updates its name, slug,
key, source directory, frontmatter, runtime materialization, and
assigned-agent references while preserving version pins.

## What Changed

- Added shared request/result types and Zod validation for managed skill
rename requests.
- Added `POST /api/companies/:companyId/skills/:skillId/rename` with
`skills.edit` policy checks and `company.skill_renamed` activity
logging.
- Restricted renames to Paperclip-managed local skills and added slug,
key, and target-directory conflict handling.
- Moved the managed directory, rewrote only the `SKILL.md` frontmatter
name, updated the database row, and rolled filesystem changes back when
persistence fails.
- Rewrote assigned agents' desired-skill keys while preserving pinned
version IDs and removed stale runtime materialization.
- Added focused route and service coverage for success, no-op, name-only
changes, conflicts, unsupported sources, assignment rewrites,
rollback-sensitive behavior, and runtime cleanup.
- Rejected multiline rename names before they can inject extra
`SKILL.md` frontmatter fields.

## Verification

- `pnpm exec vitest run
server/src/__tests__/company-skills-service.test.ts
server/src/__tests__/company-skills-routes.test.ts` — 106 tests passed.
- `pnpm --filter @paperclipai/shared typecheck` — passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.

## Risks

- Filesystem and database updates cannot share one native transaction;
the service stages filesystem changes and explicitly restores the
original directory and markdown when the database transaction fails.
- Renames intentionally reject catalog, remote, project-scanned, and
unmanaged local skills to avoid changing identities owned by external
sources.
- No database migration is required; the endpoint updates existing
company-skill and agent configuration fields.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex coding agent (exact underlying model ID and
context-window size were not exposed to this runtime), with reasoning,
repository tool use, code execution, and test execution. The rescued
source commit also records assistance from Claude Opus 4.8.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip authored and GitHub committed 2026-07-30 15:29:31 -07:00
1 parent efe8b3b707
commit fcf66f3a91
10 files changed
+717 -2

No files matched your search

@@ -200,6 +200,19 @@ export const companySkillForkPrecheckResultSchema = z.object({
existingForks: z.array(companySkillForkSummarySchema),
});
export const companySkillRenameSchema = z.object({
name: z.string().min(1).regex(/^[^\r\n]+$/, "Name must be a single line"),
slug: z.string().min(1).nullable().optional(),
});
export const companySkillRenameResultSchema = z.object({
skill: companySkillSchema,
previousName: z.string(),
previousSlug: z.string(),
previousKey: z.string(),
reassignments: z.array(companySkillForkReassignmentSchema),
});
export const companySkillUpdateSchema = z.object({
description: z.string().nullable().optional(),
iconUrl: z.string().nullable().optional(),
@@ -569,6 +582,7 @@ export type CompanySkillVersionCreate = z.infer<typeof companySkillVersionCreate
export type CompanySkillCommentCreate = z.infer<typeof companySkillCommentCreateSchema>;
export type CompanySkillCommentUpdate = z.infer<typeof companySkillCommentUpdateSchema>;
export type CompanySkillFork = z.infer<typeof companySkillForkSchema>;
export type CompanySkillRename = z.infer<typeof companySkillRenameSchema>;
export type CompanySkillUpdate = z.infer<typeof companySkillUpdateSchema>;
export type CatalogSkillListQuery = z.infer<typeof catalogSkillListQuerySchema>;
export type CompanySkillInstallCatalog = z.infer<typeof companySkillInstallCatalogSchema>;
+3
View File
@@ -156,6 +156,8 @@ export {
companySkillForkReassignmentSchema,
companySkillForkResultSchema,
companySkillForkPrecheckResultSchema,
companySkillRenameSchema,
companySkillRenameResultSchema,
companySkillUpdateSchema,
companySkillUpdateStatusSchema,
companySkillAuditFindingSchema,
@@ -207,6 +209,7 @@ export {
type CompanySkillCommentCreate,
type CompanySkillCommentUpdate,
type CompanySkillFork,
type CompanySkillRename,
type CatalogSkillListQuery,
type CompanySkillInstallCatalog,
type CompanySkillInstallUpdate,