-
Notifications
You must be signed in to change notification settings - Fork 135
feat(workspace): publish a project skill to the workspace over serve HTTP
#1371
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
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| // altimate_change - new file | ||
| // | ||
| // Which of the skills this project can reach may be published to its workspace, for the serve | ||
| // routes the IDE extension calls. The CLI's `skill publish` and the TUI's "Publish to workspace" | ||
| // row apply the same rules inline (`skillSource`, `isManagedSkill`, `assertProjectSkill`); the | ||
| // refusal wording here matches theirs so a user moving between surfaces reads the same thing. | ||
| import path from "path" | ||
| import { skillSource } from "@/cli/cmd/skill-helpers" | ||
| import { assertProjectSkill, IDE_DELIVERED_MARKER, isManagedSkill } from "./skill-publish" | ||
|
|
||
| export type PublishEligibility = "publishable" | "builtin" | "personal" | "workspace" | "outside-project" | ||
|
|
||
| /** The boundary a skill must lie within: the worktree, since discovery walks up to it — except for | ||
| * a project with no git, whose worktree is the sentinel `/`, which would contain everything. */ | ||
| export function projectRootFor(directory: string, worktree: string): string { | ||
| return worktree !== "/" ? worktree : directory | ||
| } | ||
|
|
||
| /** Whether a skill at `location` (its `SKILL.md`) may be published from `projectDirectory`. */ | ||
| export function publishEligibility(location: string, projectDirectory: string, projectRoot: string): PublishEligibility { | ||
| if (!path.isAbsolute(location) || skillSource(location) === "builtin") return "builtin" | ||
| if (skillSource(location) === "global") return "personal" | ||
| const skillDirectory = path.dirname(location) | ||
| if (isManagedSkill(projectDirectory, skillDirectory)) return "workspace" | ||
| try { | ||
| assertProjectSkill(projectRoot, skillDirectory) | ||
| } catch { | ||
| return "outside-project" | ||
| } | ||
| return "publishable" | ||
| } | ||
|
|
||
| /** Why a skill cannot be published, in the words the CLI uses, or null when it can. */ | ||
| export function explainIneligible(name: string, location: string, eligibility: PublishEligibility): string | null { | ||
| switch (eligibility) { | ||
| case "publishable": | ||
| return null | ||
| case "builtin": | ||
| return `"${name}" is a built-in skill and cannot be published.` | ||
| case "personal": | ||
| return ( | ||
| `"${name}" is a personal skill (${path.dirname(location)}), not one of this project's. ` + | ||
| `Copy it into the project's skills directory to publish it.` | ||
| ) | ||
| case "workspace": | ||
| return ( | ||
| `"${name}" is a skill this workspace sent to you, not one you authored. ` + | ||
| `Publishing it would send the workspace's own skill back to it. ` + | ||
| `If you copied it to make your own, rename it and delete any ${IDE_DELIVERED_MARKER} in its folder.` | ||
| ) | ||
| case "outside-project": | ||
| return `"${name}" is not inside this project (${path.dirname(location)}), so it cannot be published from here.` | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| // altimate_change - new file | ||
| // | ||
| // Whether a path lies inside a workspace skill snapshot (`.altimate-code/skill/_workspace`), judged | ||
| // by path segments rather than against one project directory: discovery walks config directories up | ||
| // to the worktree, so a session started in `repo/sub` also reads `repo/.altimate-code/...`. | ||
| // Dependency-free on purpose — skill discovery imports it, and the workspace modules are heavy. | ||
| import path from "path" | ||
|
|
||
| const SNAPSHOT_SEGMENTS = [".altimate-code", "skill", "_workspace"] | ||
|
|
||
| export function isInWorkspaceSnapshot(location: string): boolean { | ||
| const parts = path.resolve(location).split(path.sep) | ||
| for (let i = 0; i + SNAPSHOT_SEGMENTS.length <= parts.length; i++) { | ||
| if (SNAPSHOT_SEGMENTS.every((segment, j) => parts[i + j] === segment)) return true | ||
| } | ||
| return false | ||
| } | ||
|
|
||
| /** Whether `location` lies inside `root`, by path segments. */ | ||
| export function isWithin(root: string, location: string): boolean { | ||
| const rel = path.relative(path.resolve(root), path.resolve(location)) | ||
| return rel === "" || (!rel.startsWith(".." + path.sep) && rel !== ".." && !path.isAbsolute(rel)) | ||
| } | ||
|
|
||
| /** Whether a skill found in the workspace snapshot must yield to a same-name skill already | ||
| * registered at `existingLocation`: only when that one is the user's own, inside the project. | ||
| * Built-in (`builtin:` / `<built-in>`), personal and snapshot entries are overridden as before. */ | ||
| export function snapshotCopyYields(match: string, existingLocation: unknown, projectRoot: string | undefined): boolean { | ||
| return ( | ||
| !!projectRoot && | ||
| typeof existingLocation === "string" && | ||
| isInWorkspaceSnapshot(match) && | ||
| path.isAbsolute(existingLocation) && | ||
| !isInWorkspaceSnapshot(existingLocation) && | ||
| isWithin(projectRoot, existingLocation) | ||
| ) | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
P3: The new snapshot check in
isManagedSkillmatches the.altimate-code/skill/_workspacesegment sequence anywhere in the path, unscoped to the current project.publishEligibility(andpublishSkillat line 657) therefore classify any skill whose location contains those three segments as "workspace" and refuse to publish it, even when that directory is the user's own nested project inside the worktree rather than the workspace-owned snapshot. Scope the segment match to the project/worktree boundary (e.g. verify the match sits on or aboveprojectDirectory's worktree, matching how discovery walks config dirs up to the worktree) instead of matching globally.Prompt for AI agents
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.
Not changing this.
.altimate-code/skill/_workspaceis written only by the workspace skill sync, so a path containing it is always a snapshot, never something a user authored. Discovery reaches that directory only through config dirs walked up from the session directory to the worktree, which is why the segment check exists: a session inrepo/subreadsrepo/.altimate-code/.... A nested project below the session directory isn't a discovery root at all.