Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/changes/unreleased/1938-skill-manual-answers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
kind: fixed
title: Skill questions reach a definition, capability answer and troubleshooting
pr: 1938
surface: [chat, docs]
invalidates:
- "Asking what a skill is led with a folder list; the manual now returns a direct definition first."
- "The four original skill capability questions had no explicit retrieval regression coverage; they now check their answer sections and memory-independent behavior."
---

The manual explains how to choose a skill, why one may be unavailable, and that
turning automatic memory saving off does not disable skills. Retrieval and
live-terminal tests cover the four questions from issue #1937.
66 changes: 66 additions & 0 deletions internal/e2e/skill_manual_e2e_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//go:build e2e

package e2e

import (
"fmt"
"path/filepath"
"strings"
"testing"
"time"

"github.com/Agent-Field/codeaf/internal/session"
)

// Read the actual tool result from a fresh terminal conversation for each
// question: an answer or a tool-call argument alone cannot prove retrieval.
func TestSkillManualOnTheTerminal(t *testing.T) {
requireTmuxAndKey(t)
const model = "deepseek/deepseek-v4.1-flash"
if e2eModel != model {
t.Fatalf("requires CODEAF_E2E_MODEL=%s", model)
}
for _, memory := range []string{"on", "off"} {
for index, question := range []struct{ query, heading string }{
{"what is a skill", "What is a skill"},
{"can you use skills", "Can you use skills"},
{"do you have skills", "Can you use skills"},
{"why can't you use my skills", "Why can't you use my skills"},
} {
t.Run(memory+"/"+question.query, func(t *testing.T) {
home := newHome(t, map[string]any{"memory.enabled": memory, "models.fallbacks": "", "model_pool": "off"})
workspace := newWorkspace(t, "skill-manual", false)
stopRecoveryEngineOnCleanup(t, nil, home, workspace)
stage := fmt.Sprintf("skill-manual-%s-%d", memory, index)
r := startDefault(t, stage, home, workspace, 140, 42,
"chat", "--model", model, "--one-model", "--debug")
r.lit(fmt.Sprintf("Look up this question with the manual tool, setting query to exactly %q, then answer it briefly in your own words. Explain whether automatic memory saving being off disables skills. Finish with SKILL_MANUAL_CHECK_DONE. Do not change files.", question.query))
r.keys("Enter")
cleanChatWaitAnswer(t, r, "SKILL_MANUAL_CHECK_DONE", 2*time.Minute)
places, err := filepath.Glob(filepath.Join(home, "v3", "projects", "*", "*"))
if err != nil || len(places) != 1 {
t.Fatalf("want one conversation place, got %v: %v", places, err)
}
matched := false
for _, call := range manualCalls(t, session.Place{Dir: places[0]}) {
if call.Query != question.query {
continue
}
for at, section := range renderedSections(call.Output) {
if strings.HasPrefix(section.Title, question.heading) && strings.Contains(section.Body, "memory.enabled") {
matched = true
if index == 0 && at != 0 {
t.Error("the definition was not the first returned section")
}
}
}
}
if !matched {
t.Fatal("the exact manual query did not deliver its answer and memory independence to the model")
}
cleanChatCapture(t, r, stage)
cleanChatAuditModels(t, home, model, stage)
})
}
}
}
36 changes: 22 additions & 14 deletions internal/manual/chat/skills-from-other-tools.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
# Skills from Claude Code, Codex, OpenCode, Goose and other tools

## Can you use my Claude Code, Codex, OpenCode or Goose skills

Yes, directly. Any skill you already installed for Claude Code, Codex, Cursor,
Gemini, OpenCode, Goose or another agentskills.io tool — a folder holding a `SKILL.md` whose
frontmatter has a `name` and a `description` — is read where it lives. Nothing
is copied or reinstalled. Each launch reads the folders again before the first
message, so a skill you add or edit shows up the next time you open codeaf.

Once found, a skill is used two ways. Automatically: the model is shown every
skill's name and what it is for, and when a request fits one it opens it with
`use_skill` and follows it, even when your words share none with the skill's
description. A message whose words do match a skill's description also carries
that skill with it, and a dim `skills ·` line in the turn names it. By hand: `/skill` puts one in front of the conversation until you take it
off.
## What is a skill — skills in codeaf

A skill is a reusable set of instructions for a kind of work, such as reviewing
code or extracting PDF tables. Its folder holds a `SKILL.md` with a name,
description and instructions, plus any scripts or references it needs.

Type `/skill` to choose a skill for this conversation, or `/skill <folder>` to
attach one. The model can also open a relevant skill through `use_skill`.
Skills work with automatic memory saving off: `memory.enabled` does not turn
skills off.

## Can you use skills — do you have skills from other tools

Yes. codeaf can use skills installed for Claude Code, Codex, Cursor, Gemini,
OpenCode, Goose and other agentskills.io tools. A skill folder holds a `SKILL.md`
with a name and description; it is read in place, never copied or reinstalled.
New and edited skills are discovered at the next launch.

Use `/skill` to choose a skill for this conversation. The model can also use
`use_skill` to open one that fits your request. Matching words in its description
carry the skill automatically, and the dim `skills ·` line names it. Automatic
memory saving can be off: `memory.enabled` does not disable skills.

## Which folders are read

Expand Down
18 changes: 18 additions & 0 deletions internal/manual/chat/use-skill.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,24 @@ and `/skill` still attaches them. The procedures codeaf saved after watching
them run live in that database, so without it only the skills in folders are on
the shelf.

## Why can't you use my skills

Skills work with automatic memory saving off: `memory.enabled` does not disable
them. If a skill is missing, open `/skill` and check its row for a warning, or
type `/skill <folder>` to attach it directly. The folder needs a readable
`SKILL.md` with a name and description. A missing file or invalid frontmatter
can keep it off the shelf. After adding or editing a skill in a scanned folder,
open a new conversation so discovery reads it again.

For a Claude Code plugin skill, the plugin must be installed and enabled in
Claude Code; browsing a marketplace is not installation. If two skills share a
name, the higher-precedence copy wins. The page `skills-from-other-tools`
explains those folders and precedence rules.

A message with a picture does not automatically carry skill instructions, even
for an attached skill. Send the picture, then send the instruction in a message
of its own; the model can still open the skill with `use_skill`.

## Where the shelf comes from

Every launch reads the skill folders before the first message is built and
Expand Down
32 changes: 32 additions & 0 deletions internal/manual/chat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4439,6 +4439,38 @@ func TestTheCrossComputerQuestionsReachTheFactThatAnswersThem(t *testing.T) {
}
}

// These are the four original capability questions from #1378 and #1937.
func TestSkillCapabilityQuestionsReachTheirAnswer(t *testing.T) {
for _, row := range []struct {
question, page, heading string
facts []string
}{
{"what is a skill", "skills-from-other-tools", "What is a skill", []string{"SKILL.md", "/skill"}},
{"can you use skills", "skills-from-other-tools", "Can you use skills", []string{"Yes", "SKILL.md", "/skill"}},
{"do you have skills", "skills-from-other-tools", "Can you use skills", []string{"Yes", "SKILL.md", "/skill"}},
{"why can't you use my skills", "use-skill", "Why can't you use my skills", []string{"memory.enabled", "SKILL.md", "/skill"}},
} {
t.Run(row.question, func(t *testing.T) {
found := Chat().Search(row.question, DefaultResults)
for index, section := range found {
if section.Page != row.page || !strings.HasPrefix(section.Title, row.heading) {
continue
}
if row.question == "what is a skill" && index != 0 {
t.Errorf("the definition is answer %d, want the first answer", index+1)
}
for _, fact := range row.facts {
if !strings.Contains(section.Body, fact) {
t.Errorf("answer does not explain %q", fact)
}
}
return
}
t.Fatalf("question did not reach its %q answer in %d results", row.heading, DefaultResults)
})
}
}

// N4 pins all shared refusals quoted by the Windows manual sections.
func TestWindowsManualQuotesTheShellRefusal(t *testing.T) {
page, ok := Chat().Page("running-commands-on-windows")
Expand Down
Loading