Skip to content

feat: a Skills tab per project, to review drafts and edit the skills a project has - #27

Merged
lucazagaia merged 11 commits into
mainfrom
feat/project-skills
Sep 29, 2026
Merged

lucazagaia merged 11 commits into
mainfrom
feat/project-skills

Conversation

@lucazagaia

@lucazagaia lucazagaia commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

Why

Skill++ takes a candidate all the way to an installed skill, and then the review page had nothing for it. Revise refused installed skills while the Drafts tab still showed the button, skills made by hand never appeared, and a skill could only be changed by hand.

What it does

The Drafts tab becomes Skills, one block per project: that project's drafts to review first, then every skill in its .claude/skills/, whoever made it, three cards to a row (two, then one, on narrower screens).

  • A card is the skill's name and what it is for, with a label while an edit runs or waits. Clicking it opens the skill: its SKILL.md, rendered through the page's own escaped markdown, and never read through a link.
  • Edit: your agent (SKILL_PLUS_PLUS_AGENT, claude -p by default) edits a copy in a temp folder, and the result waits as a per-file diff until Apply or Discard. The project is never written before Apply, and Apply is refused if the skill changed after the copy was taken. Nothing is proposed when the agent changed nothing, declined, renamed the skill, added a link, or touched the project's copy while it ran. skill-plus-plus edit-skill does the same from the terminal.
  • Undo, right after Apply, puts the skill back as it was. It lasts until the next action or a reload of the page: Apply hands the page a token that the page drops at its next action, the copy stays in Skill++'s own folder, and Undo is refused once anything changed the skill since.
  • Install copies a draft into its project's .claude/skills/<name>/ and keeps nothing else about it: from then on the folder is the skill. Deleting the folder takes it out, and its draft comes back to review. revise refuses an installed draft and names edit-skill instead.
  • Every candidate belongs to a project. Dictation (/skill-plus-plus-new) now records the folder it runs in; it used to record none, which is where every "No project" entry came from. The page shows only candidates with a project, so the project menu has no "No project" any more.
  • One project at a time, always. The project menu has no All projects: one project is always chosen, even when it is the only one. The page opens on the project chosen last, or else the one skill-plus-plus web was started in, or else the first.
  • The page's own pop-ups. Questions (Ignore, Discard) and errors open in a <dialog> of the page. The browser's confirm and alert can be switched off, and Claude's in-app browser does: Ignore then did nothing, and errors never showed.

A request names a project the page lists and a folder in it, never a path. Every change to a project's skills, and every background job, runs under one lock.

Removed

Uninstall, Just for me, Download, Update, the personal gallery and web --skills-dir: a skill belongs to the project it was made from.

Turning a skill off is left out on purpose. 86cc7d6 takes it out, and it comes back on a branch of its own, which can start by reverting that commit.

Also fixed on the way

  • tier no longer deletes a skill already in the target tier (the tiers are shared by every project).
  • A revision starting with - (--shorter) reaches the agent.
  • install.read_settings / write_settings write settings files whole.
  • Every test gets a fake agent command by default, so a test that forgets to stub the agent fails at once instead of running claude -p.

Reading the history

The first six commits are the first design: a separate Project skills tab with View, Turn off and Archive. The last five reshape it into what is described above, so the whole diff reads better than commit by commit.

Developed on a stack of #25 and #26. After they merged, main was byte-identical to that stack, so the first six commits were replayed onto main with no conflicts.

Verified

  • 611 tests pass: listing and cards, the viewer's caps and links, edit-skill (dry run, copy only, rename, decline, workspace removed, writes into the project caught), the edit flow end to end with a stub agent, Undo (its token, only the last Apply, refused after a change, cleared by a new server), install and a hand-deleted folder, request safety, dictation's project, and the page code in node (galleries, the project menu, pop-ups, escaped diffs). scripts/leak_guard.py: 0 hits.
  • In a browser, on a scratch ledger with two projects:
    • the tab at 3, 2 and 1 columns, with no horizontal scroll on a phone;
    • Install from To review;
    • Edit through a stub agent to a diff, then Apply and Undo;
    • Undo gone after another action, and after a reload;
    • Ignore and Discard asking through the page's pop-up, and an error shown in it;
    • a hand-deleted skill's draft back to review;
    • the project menu listing only real projects, one always chosen, and the choice kept across a reload.

Not verified here

  • A real edit with claude -p (only a stub agent ran).
  • The README screenshots docs/images/skill-installed.png and docs/images/draft-questions.png still show the old Drafts tab, and need retaking.

lucazagaia and others added 6 commits September 29, 2026 12:04
…es are whole

- `lifecycle.move_tier` removed a folder of the same name already in the
  target tier to make room. The cold and archive tiers are shared by every
  project, so archiving one project's `deploy` deleted another's. It now
  raises, and `skill-plus-plus tier` says so and exits 1.
- The page passed a revision as `--instruction` and the text as two
  arguments, so an instruction such as `--shorter` was read as an unknown
  option and the run refused. It is one `--instruction=` argument now, as
  the draft note already was.
- `install.read_settings` and `write_settings`: one strict reader for
  Claude Code settings files (an unparsable file raises instead of being
  written over), and a write through a temporary file and `os.replace`,
  since Claude Code reloads these files as they change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`skills` holds what the page and the upcoming `edit-skill` command both need
about installed skills: `draft_dir`, `draft_files`, `digest`,
`install_record`, `install_stale` and `changed_since_install`, plus
`SAFE_NAME` and `NOT_SKILL_FILES`. Moved as they were; every call site in
web.py now names `skills.<name>`, and the private copies are gone rather
than kept as aliases. No behaviour changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ore it

The backend for a Project skills tab, read side and the changes that need
no agent:

- `skills.list_project`: every folder in `<project>/.claude/skills/` with a
  SKILL.md, whoever made it, as a card: name (and the frontmatter name when
  it differs), description, files, who made it, the draft it was installed
  from and whether that draft is newer, usage, and whether it is on. Reads
  files only, so `/api/state` still starts no process.
- `/api/skill/read`: a skill in full for a viewer. Text is shown up to a
  cap; binaries and links are named, never read or followed.
- `/api/skill/off` and `/api/skill/on`: `skillOverrides` in the project's
  `.claude/settings.local.json`, the file Claude Code's own `/skills` menu
  writes (the main checkout's in a worktree). Other settings keep their
  order; an unparsable file is never written over; a file Skill++ creates
  gets one `.git/info/exclude` line so it is not committed; an "off" from
  the committed settings is shown and left alone.
- `/api/skill/archive` and `/api/skill/restore`: the folder moves to
  `archive/<project key>/` under Skill++'s root and back. Nothing is ever
  replaced or deleted; a skill installed from a draft takes its install
  record along and is linked again on restore.
- Which projects a request may name has one definition, `known_projects`,
  from the same entries the page lists. A request names a project and a
  folder, never a path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `skill-plus-plus edit-skill <folder> --project=DIR --instruction=TEXT`:
  the developer's agent edits a copy of the skill in a temp folder, and the
  result is kept under `<root>/edits/` as a proposal. The project is never
  written. Nothing is proposed when the agent changed nothing, declined,
  renamed the skill, removed SKILL.md, added a link, or when the project's
  copy changed while it ran. Linked skills and skills over 100 files or
  1 MB are edited by hand instead.
- The page runs it in the background (`/api/skill/edit`), shows the change
  as unified diffs with warnings about the frontmatter (`/api/skill/proposal`),
  and writes it only on Apply (`/api/skill/apply`), which is refused when
  the skill changed after the copy was taken; a failed write is rolled
  back. The replaced version goes to `history/`, the last 10 kept.
  `/api/skill/discard` drops Skill++'s copy.
- A skill installed from a draft gets the same edit in its draft and new
  hashes in `installed.json`, so Update and Uninstall keep working. Update,
  Uninstall and Archive wait while an edit is running or waiting, and an
  edit waits while the draft is newer than the installed copy.
- Every change to a project's skills and every job runs under one lock,
  `_jobs_lock`, now re-entrant, through a `@_locked` decorator. The job's
  freshness check is `_job_fresh`, shared with drafts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d archive

The review page's third tab shows every skill each project has in
`.claude/skills/`, three to a row (two, then one, on narrower screens), one
gallery per project under All projects.

- A card: name, description, badges (off for you or for the team, from a
  draft, update available, editing / edit ready / edit failed, linked,
  named differently from its folder), files and usage, and View, Edit,
  Turn off / Turn on and Archive. A button that cannot act says why.
- The viewer, a `<dialog>`: the file list, SKILL.md rendered through the
  page's own escaped markdown (or as source), other files as text,
  binaries and links named. Its edit panel sends the agent the change,
  shows the running edit, then the proposal as per-file diffs with
  warnings, and Apply or Discard. Only the panel repaints while the page
  polls, so the file being read keeps its place.
- Archived skills are listed under each gallery, with Restore.
- In Drafts, "in <project>" opens the installed skill in the gallery, and
  Revise on an installed draft, which the server always refused, becomes
  "Edit under Project skills".
- On a phone the header wraps instead of widening the page, and `.run`
  gets a colour that exists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
usage.md gets a section on the tab (what Edit, Turn off and Archive do and
where each writes), `edit-skill` in the commands table, a troubleshooting
entry for a failed edit, and a note that deleting Skill++'s folder deletes
archived skills too. README, CHANGELOG (Added, and the three fixes under
Fixed), architecture.md and SECURITY.md follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lucazagaia and others added 4 commits September 29, 2026 14:24
Drafts and installed skills share one tab, laid out per project: each
project's drafts to review first, then its skills as plain cards (name,
what it is for, and a label when it is off or an edit is running or
waiting). Clicking a card opens its SKILL.md; Edit, Turn off/on and Close
sit in its header, and the edit box shows only after Edit. A skill that
is off is struck through and hatched, with a solid Off label. A failed
edit can be dismissed.

Removed: Uninstall, Just for me, Download, Archive and Restore, the
personal gallery, Update, and `web --skills-dir`. A skill belongs to its
project, and once installed its folder is the skill: nothing else is
kept of the install (no installed.json), an edit changes only the
folder, and deleting the folder takes the skill out and brings its draft
back to review. Install never writes over a folder that is there, and
clears an edit left waiting by a skill of the same name. `revise`
refuses an installed draft and names the `edit-skill` command instead.

The page asks its questions and shows its errors in a <dialog> of its
own. The browser's confirm and alert boxes can be switched off, and
Claude's in-app browser does: Ignore and Discard then did nothing, and
errors never showed.

Every test now gets a fake agent command by default, so a test that
forgets to stub the agent fails at once instead of running `claude -p`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tory

Right after Apply, the opened skill offers Undo, which puts the skill back
as it was before that Apply. It lasts until the next action or a reload
of the page: Apply hands the page a token, the page drops it at its next
action and on a reload, and the next Apply replaces it. The copy Undo
puts back is the one the edit was made on, kept in Skill++'s own edits
folder and never in the project. Undo is refused once anything changed
the skill since, so it never throws a later change away. A new server
clears what an earlier one kept.

The history of the last ten applied edits is gone with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Turn off and Turn on in the opened skill come out, and with them the Off
label and the struck-through card, reading and writing Claude Code's
`skillOverrides` in the project's settings, the line added to the
clone's `.git/info/exclude`, the settings backup, and finding a
worktree's main checkout. A card is the skill's name, what it is for,
and a label while an edit is running or waiting.

Turning a skill off comes back on a branch of its own, which can start
by reverting this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dictation (`/skill-plus-plus-new`, `skill-plus-plus dictate`) records the
folder it runs in as its project, and is matched only against that
project's dictated candidates. It used to record none, which is where
every "No project" entry came from: captured work always has a folder.

The review page shows only candidates that belong to a project. Its
project menu has no "No project" any more, and the Skills tab no block
for one. Older entries without a project stay in the ledger, off the
page; `skill-plus-plus review --all` still lists them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lucazagaia lucazagaia changed the title feat: Project skills: view, edit, turn off and archive installed skills feat: a Skills tab per project, to review drafts and edit the skills a project has Sep 29, 2026
The project menu has no All projects any more: a candidate and the skill
made from it belong to one project, and nothing on the page is about
several. One project is always chosen, even when it is the only one. The
page opens on the project chosen last, or else the one
`skill-plus-plus web` was started in, or else the first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lucazagaia
lucazagaia merged commit 19685a1 into main Sep 29, 2026
20 checks passed
@lucazagaia
lucazagaia deleted the feat/project-skills branch September 29, 2026 15:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants