Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 364
Jminnetian/askskills#1410
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Jminnetian/askskills #1410
Changes from all commits
e6b20a96720a8767d0afba36ae187df801a54d775013e9e03199d2e97e27ead15f554608937166e87376bcbd0bf0bc199a2e3df3e71861cf57a264124398b1000e05c1487fa100ef1ca20df848bd2ce09da8eda7688c2d3a97d4b39d8b7e2517eb704c9bac3a67123365e914ff217135adfd8158e133b4bda4bcc1d07eb9b293ee355726786e5a184e434cd6f7e0ddacf4a1688cc4193ac8a9d8116fe769643b820133ae49c315577488cb92087cf4566f2c7209fe904910aa56bfde52b69899174c48b5File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -576,6 +576,9 @@ | ||
| }, | ||
| "externalWebUrl": { | ||
| "type": "string" | ||
| }, | ||
| "blobSha": { | ||
| "type": "string" | ||
| } | ||
| }, | ||
| "required": [ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| --- | ||
| title: Skills | ||
| --- | ||
| Skills are reusable context for [Ask Sourcebot](/docs/features/ask/ask-sourcebot). | ||
| They can capture organizational knowledge, define a workflow, or give Ask Sourcebot extra context for a class of questions. | ||
| <Frame> | ||
| <img src="/images/skills_page.png" alt="Skills page overview" /> | ||
| </Frame> | ||
| ## Creating a skill | ||
| Go to **Settings -> Skills** and click **Add skill**. | ||
| You can create a skill in three ways: | ||
| - **New skill**: Create a skill directly in Sourcebot. | ||
| - **Import from file**: Upload a local `.md` or `.markdown` file and review it before saving. | ||
| - **Import from repository**: Choose a markdown file from an indexed repository and keep the skill synced to that file. | ||
| Each skill has four fields: | ||
| - **Name**: A recognizable name for the skill. | ||
| - **Command**: The slash command users type to run the skill, such as `/deep-research`. | ||
| - **Description**: A short summary of when Ask Sourcebot should use the skill. | ||
| - **Instructions**: The markdown context or guidance Ask Sourcebot uses when the skill runs. | ||
| <Frame> | ||
| <img src="/images/skills_create.png" alt="Create a skill" /> | ||
| </Frame> | ||
| ## Writing instructions | ||
| Keep the description specific. Ask Sourcebot uses it to decide when a skill is relevant, and users see it when browsing slash commands. | ||
| Put the useful context or guidance in the instructions. | ||
| <Frame> | ||
| <img src="/images/skills_editor.png" alt="Skill editor" /> | ||
| </Frame> | ||
| ## Using skills in chat | ||
| Type `/` at the start of an Ask Sourcebot prompt to browse available skill commands. | ||
| When you select a skill, Ask Sourcebot expands the command into that skill's instructions before answering. If the instructions include file mentions, those files are added to the chat context automatically. | ||
| Ask Sourcebot may also load a matching skill automatically. When this happens, the chat details show the loaded skill name and command. | ||
| <Frame> | ||
| <img src="/images/skills_slash_command.png" alt="Skill slash command suggestions" /> | ||
| </Frame> | ||
| ## Personal and shared skills | ||
| Skills can be personal or shared: | ||
| - **Personal skills** are private to you in the current workspace. | ||
| - **Shared skills** are published to the workspace skill catalog. | ||
| You can add or remove shared skills from your own Ask commands from **Settings -> Skills**. Skill authors and organization owners can edit or delete shared skills. | ||
| Organization owners can manage shared skills from **Settings -> Workspace -> Ask Sourcebot**. Owners can also enable **Auto** for a shared skill, which adds it to members' Ask commands by default. | ||
| <Frame> | ||
| <img src="/images/skills_workspace_manager.png" alt="Workspace shared skills manager" /> | ||
| </Frame> | ||
| ## Importing skills | ||
| ### Import from file | ||
| File imports support `.md` and `.markdown` files. If the file has front matter, Sourcebot uses it to prefill the skill name, command, and description. | ||
| Supported front matter fields: | ||
| ```md | ||
| --- | ||
| name: Debug Build Failure | ||
| slug: debug-build | ||
| description: Investigate a failing CI or build error. | ||
| --- | ||
| ``` | ||
| You can also use `title` instead of `name`, and `command` instead of `slug`. | ||
| After importing a local file, the skill is editable in Sourcebot. | ||
| ### Import from repository | ||
| Repository imports let you choose a markdown file from an indexed repository. Sourcebot prefills the create form from the file so you can review it, add a missing description, and save. The resulting skill stays synced to that file. | ||
| Synced skills behave like manually-created skills, with a link back to their source file: | ||
| - Every field stays editable. Local edits are kept until you sync. | ||
| - Sourcebot checks whether the indexed source file has changed. | ||
| - When an update is available, use **Update from source** to replace the skill's content with the file's. | ||
| - When the skill is already up to date, use **Force sync** to restore the content from the source file. | ||
| - Either action warns you first if it would overwrite local edits. | ||
| - Fields the source file does not provide (for example, a missing `description`) keep their local values when you sync. | ||
| <Frame> | ||
| <img src="/images/skills_synced_from_repo.png" alt="Synced repository skill" /> | ||
| </Frame> | ||
| ## Permissions and synced skills | ||
| Synced skills mirror content from a repository file, so Sourcebot only shows them to users who can access the source repository. | ||
| If repository permission syncing is enabled, sharing a synced skill shows a warning before publishing it to the workspace. Members without access to the source repository will not see the skill. | ||
| Organization owners can still see and manage shared synced skills from workspace settings, even if they do not have access to the source repository. | ||
| ## Managing skills | ||
| From **Settings -> Skills**, you can: | ||
| - Search personal and shared skills. | ||
| - Create, edit, delete, or share skills. | ||
| - Add or remove shared skills from your commands. | ||
| - Make a shared skill personal if you manage it. | ||
| - Update synced skills from their source file. | ||
| Synced skills show their source repository and file path, plus one of these statuses: | ||
| - **Up to date**: The skill matches the indexed source file. | ||
| - **Update available**: The indexed source file has changed. | ||
| - **Source file not found**: The file was renamed, deleted, or no longer resolves at the imported revision. | ||
| - **Source repo unavailable**: The repository is no longer available or visible. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| -- CreateEnum | ||
| CREATE TYPE "AgentSkillVisibility" AS ENUM ('PERSONAL', 'SHARED'); | ||
| -- CreateTable | ||
| CREATE TABLE "AgentSkill" ( | ||
| "id" TEXT NOT NULL, | ||
| "visibility" "AgentSkillVisibility" NOT NULL, | ||
| "scopeId" TEXT NOT NULL, | ||
| "slug" TEXT NOT NULL, | ||
| "name" TEXT NOT NULL, | ||
| "description" TEXT NOT NULL, | ||
| "instructions" TEXT NOT NULL, | ||
| "createdById" TEXT NOT NULL, | ||
| "updatedById" TEXT, | ||
| "orgId" INTEGER NOT NULL, | ||
| "enabled" BOOLEAN NOT NULL DEFAULT true, | ||
| "autoEnrolled" BOOLEAN NOT NULL DEFAULT false, | ||
| "sourceRepoName" TEXT, | ||
| "sourceFilePath" TEXT, | ||
| "sourceRevision" TEXT, | ||
| "sourceBlobSha" TEXT, | ||
| "sourceImportedAt" TIMESTAMP(3), | ||
| "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| "updatedAt" TIMESTAMP(3) NOT NULL, | ||
| CONSTRAINT "AgentSkill_pkey" PRIMARY KEY ("id") | ||
| ); | ||
| -- CreateTable | ||
| CREATE TABLE "AgentSkillAdoption" ( | ||
| "id" TEXT NOT NULL, | ||
| "userId" TEXT NOT NULL, | ||
| "orgId" INTEGER NOT NULL, | ||
| "agentSkillId" TEXT NOT NULL, | ||
| "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| "removedAt" TIMESTAMP(3), | ||
| CONSTRAINT "AgentSkillAdoption_pkey" PRIMARY KEY ("id") | ||
| ); | ||
| -- CreateIndex | ||
| CREATE INDEX "AgentSkill_createdById_idx" ON "AgentSkill"("createdById"); | ||
| -- CreateIndex | ||
| CREATE INDEX "AgentSkill_updatedById_idx" ON "AgentSkill"("updatedById"); | ||
| -- CreateIndex | ||
| CREATE INDEX "AgentSkill_orgId_idx" ON "AgentSkill"("orgId"); | ||
| -- CreateIndex | ||
| CREATE INDEX "AgentSkill_orgId_visibility_scopeId_idx" ON "AgentSkill"("orgId", "visibility", "scopeId"); | ||
| -- CreateIndex | ||
| CREATE INDEX "AgentSkill_shared_catalog_idx" ON "AgentSkill"("orgId", "visibility", "scopeId", "enabled", "updatedAt" DESC, "name"); | ||
| -- CreateIndex | ||
| CREATE UNIQUE INDEX "AgentSkill_orgId_visibility_scopeId_slug_key" ON "AgentSkill"("orgId", "visibility", "scopeId", "slug"); | ||
| -- CreateIndex | ||
| CREATE INDEX "AgentSkillAdoption_userId_orgId_removedAt_idx" ON "AgentSkillAdoption"("userId", "orgId", "removedAt"); | ||
| -- CreateIndex | ||
| CREATE INDEX "AgentSkillAdoption_agentSkillId_idx" ON "AgentSkillAdoption"("agentSkillId"); | ||
| -- CreateIndex | ||
| CREATE INDEX "AgentSkillAdoption_orgId_idx" ON "AgentSkillAdoption"("orgId"); | ||
| -- CreateIndex | ||
| CREATE UNIQUE INDEX "AgentSkillAdoption_orgId_userId_agentSkillId_key" ON "AgentSkillAdoption"("orgId", "userId", "agentSkillId"); | ||
| -- AddForeignKey | ||
| ALTER TABLE "AgentSkill" ADD CONSTRAINT "AgentSkill_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; | ||
| -- AddForeignKey | ||
| ALTER TABLE "AgentSkill" ADD CONSTRAINT "AgentSkill_updatedById_fkey" FOREIGN KEY ("updatedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; | ||
| -- AddForeignKey | ||
| ALTER TABLE "AgentSkill" ADD CONSTRAINT "AgentSkill_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Org"("id") ON DELETE CASCADE ON UPDATE CASCADE; | ||
| -- AddForeignKey | ||
| ALTER TABLE "AgentSkillAdoption" ADD CONSTRAINT "AgentSkillAdoption_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; | ||
| -- AddForeignKey | ||
| ALTER TABLE "AgentSkillAdoption" ADD CONSTRAINT "AgentSkillAdoption_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Org"("id") ON DELETE CASCADE ON UPDATE CASCADE; | ||
| -- AddForeignKey | ||
| ALTER TABLE "AgentSkillAdoption" ADD CONSTRAINT "AgentSkillAdoption_agentSkillId_fkey" FOREIGN KEY ("agentSkillId") REFERENCES "AgentSkill"("id") ON DELETE CASCADE ON UPDATE CASCADE; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -334,6 +334,8 @@ model Org { | ||||||||||||||||||||||||||||||
| repoVisits RepoVisit[] | ||||||||||||||||||||||||||||||
| mcpServers McpServer[] | ||||||||||||||||||||||||||||||
| agentSkills AgentSkill[] | ||||||||||||||||||||||||||||||
| agentSkillAdoptions AgentSkillAdoption[] | ||||||||||||||||||||||||||||||
| license License? | ||||||||||||||||||||||||||||||
| servicePingEvents ServicePingEvent[] | ||||||||||||||||||||||||||||||
| @@ -519,6 +521,9 @@ model User { | ||||||||||||||||||||||||||||||
| sessionVersion Int @default(0) | ||||||||||||||||||||||||||||||
| userMcpServers UserMcpServer[] | ||||||||||||||||||||||||||||||
| createdAgentSkills AgentSkill[] @relation("AgentSkillCreatedBy") | ||||||||||||||||||||||||||||||
| updatedAgentSkills AgentSkill[] @relation("AgentSkillUpdatedBy") | ||||||||||||||||||||||||||||||
| agentSkillAdoptions AgentSkillAdoption[] | ||||||||||||||||||||||||||||||
| createdAt DateTime @default(now()) | ||||||||||||||||||||||||||||||
| updatedAt DateTime @updatedAt | ||||||||||||||||||||||||||||||
| @@ -741,6 +746,93 @@ model ChatAccess { | ||||||||||||||||||||||||||||||
| @@unique([chatId, userId]) | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| enum AgentSkillVisibility { | ||||||||||||||||||||||||||||||
| PERSONAL | ||||||||||||||||||||||||||||||
| SHARED | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| model AgentSkill { | ||||||||||||||||||||||||||||||
| id String @id @default(cuid()) | ||||||||||||||||||||||||||||||
| // Namespace identity for slash-command uniqueness and lookup. | ||||||||||||||||||||||||||||||
| // This is not a foreign key and should not be used as an authorization check by itself. | ||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||
| // Every skill belongs to an org (orgId is always set). | ||||||||||||||||||||||||||||||
| // PERSONAL: scopeId is the user id; the skill is private to that user within the org. | ||||||||||||||||||||||||||||||
| // SHARED: scopeId is String(orgId); the skill is visible to the whole org (opt-in via adoptions). | ||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||
| // Future sharing/grants should treat this as the skill's home namespace, not as the ACL. | ||||||||||||||||||||||||||||||
| visibility AgentSkillVisibility | ||||||||||||||||||||||||||||||
| scopeId String | ||||||||||||||||||||||||||||||
| slug String | ||||||||||||||||||||||||||||||
| name String | ||||||||||||||||||||||||||||||
| description String | ||||||||||||||||||||||||||||||
| instructions String | ||||||||||||||||||||||||||||||
| // The user who authored the skill. This is audit metadata, separate from | ||||||||||||||||||||||||||||||
| // the namespace that owns the slash-command slug. | ||||||||||||||||||||||||||||||
| createdBy User @relation("AgentSkillCreatedBy", fields: [createdById], references: [id], onDelete: Cascade) | ||||||||||||||||||||||||||||||
| createdById String | ||||||||||||||||||||||||||||||
| updatedBy User? @relation("AgentSkillUpdatedBy", fields: [updatedById], references: [id], onDelete: SetNull) | ||||||||||||||||||||||||||||||
| updatedById String? | ||||||||||||||||||||||||||||||
Comment on lines
+773
to
+780
| ||||||||||||||||||||||||||||||
| // The user who authored the skill. This is audit metadata, separate from | |
| // the namespace that owns the slash-command slug. | |
| createdBy User @relation("AgentSkillCreatedBy", fields: [createdById], references: [id], onDelete: Cascade) | |
| createdById String | |
| updatedBy User? @relation("AgentSkillUpdatedBy", fields: [updatedById], references: [id], onDelete: SetNull) | |
| updatedById String? | |
| // The user who authored the skill. This is audit metadata, separate from | |
| // the namespace that owns the slash-command slug. | |
| createdBy User?`@relation`("AgentSkillCreatedBy", fields: [createdById], references: [id], onDelete: SetNull) | |
| createdById String? | |
| updatedBy User? `@relation`("AgentSkillUpdatedBy", fields: [updatedById], references: [id], onDelete: SetNull) | |
| updatedById String? |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/db/prisma/schema.prisma` around lines 773 - 780, The AgentSkill
audit relation is too destructive: `createdBy` currently cascades on user
deletion, which can delete shared/org skills and their adopters. Update the
`AgentSkill` model’s `createdBy`/`createdById` relation in the Prisma schema to
stop using cascade delete and allow the creator reference to be cleared instead,
matching the intent of audit metadata. Then update any `createdById` callers and
mappings that assume a required user, including `toSharedAgentSkillCatalogItem`
and any `Pick<AgentSkill, ... "createdById" ...>` usages, so they handle a
nullable creator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As of current this was actually intentional, but it's unclear if it should be this way or if a shared skill truly becomes an org resource (but then it's weird that the creator is the sole person who can then remove everybodies access to it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jsourcebot Thanks for the context — that's a fair tension to flag. A few ways I'd think about resolving the ambiguity, depending on which mental model you want for SHARED skills:
- Skill is an org resource, ownership decoupled from a single user — switch
createdBy/createdByIdtoSetNull(matchingupdatedBy), and introduce an explicit notion of "current owner/maintainer" that's independent from the original author (e.g., allow any orgOWNERto delete/manage aSHAREDskill, not just the creator). This is the cleanest mental model for "shared skills survive offboarding," but requires updating the delete/update authorization logic (currently gated to the creator inactions.ts) plus makingcreatedByIdnullable everywhere it's consumed (e.g.,toSharedAgentSkillCatalogItem). - Keep cascade, but guard the destructive path — leave the schema as-is, but before deleting a
User, check forSHAREDskills they authored and either block the deletion or prompt to reassign/transfer ownership first. This avoids a schema/type migration but adds friction to user offboarding flows. - Status quo — accept that removing a user prunes their shared skills too, treating "shared" as more like "shared while the author remains a member" rather than a durable org asset. Simplest, but as you noted, has surprising blast radius (also deletes all
AgentSkillAdoptionrows for every other member who adopted it).
Given this is a genuine product/design decision rather than a clear-cut bug, I'll leave it to you and the team to decide the intended semantics. If you'd like, I can open a follow-up issue to track "clarify/rework AgentSkill ownership semantics for org offboarding" so it doesn't get lost — just let me know.
✏️ Learnings added
Learnt from: jsourcebot
Repo: sourcebot-dev/sourcebot PR: 1410
File: packages/db/prisma/schema.prisma:773-780
Timestamp: 2026-07-02T02:23:27.352Z
Learning: In Sourcebot's Prisma schema (packages/db/prisma/schema.prisma), the `AgentSkill.createdBy` relation intentionally uses `onDelete: Cascade` (as of PR `#1410`), meaning deleting a `User` who authored a `SHARED` (org-wide) skill also deletes that skill and all associated `AgentSkillAdoption` rows for every other user who adopted it. The team (jsourcebot) has acknowledged this is a known tradeoff/open design question — it's unclear whether shared skills should become independent org resources decoupled from their creator (which would require reworking ownership/authorization, e.g., allowing any org owner to manage a shared skill) or whether the current author-tied-deletion behavior is intended. Do not flag this as a definitive bug in future reviews unless the schema or stated intent changes; treat it as an open question already known to the maintainers.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: fatmcgav
Repo: sourcebot-dev/sourcebot PR: 1143
File: packages/db/prisma/schema.prisma:640-660
Timestamp: 2026-04-22T07:09:48.693Z
Learning: In Sourcebot’s Prisma schema, treat junction/relationship tables (e.g., AgentConfigToRepo, AgentConfigToConnection) as single-tenant and do not require a denormalized `orgId` column for tenant isolation. Tenant isolation is enforced by foreign keys/cascades from `AgentConfig` (which contains `orgId`). When reviewing queries/relations involving these junction tables, avoid raising multi-tenant isolation concerns as long as access is gated through `AgentConfig` filtered by `orgId`.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.