Elie Schoppik · 2026-01-28

Claude Code 中的技能使用

摘要

本课程深入讲解如何在 Claude Code 中使用技能(Skills)进行代码生成、审查和测试。通过一个命令行任务管理应用的实例,展示如何创建三个核心技能:添加 CLI 命令、生成测试和审查代码。课程还介绍了如何配置专门的子智能体(sub-agents)并将其与技能结合使用,以实现更高效的上下文管理和专业化的任务处理。这种方法让主智能体专注于开发,而子智能体分别负责代码审查和测试生成,显著提升了工作效率和代码质量。

要点

  • CLAUDE.md 文件的作用:项目特定的上下文文件,包含技术栈、架构说明和通用指导,始终在对话上下文中可用
  • 三大核心技能:添加 CLI 命令(确保遵循编码模式和约定)、生成 CLI 测试(使用 pytest 和 fixtures)、审查 CLI 命令(验证最佳实践)
  • 子智能体配置:通过 /agents 命令创建专门的智能体,明确指定工具、技能和职责范围,子智能体不会继承父智能体的技能
  • 技能加载机制:在子智能体中,整个 SKILL.md 文件在调度时被预加载,而不是按需读取其他文件
  • 工作流优化:主智能体负责开发,代码审查子智能体负责质量检查,测试生成子智能体负责测试,实现上下文高效的任务分配

视频信息:Skills with Claude Code


中文翻译

我们现在要切换到 Claude Code,并使用技能来进行代码生成、审查和测试。我们还将设置子智能体并为它们配备技能。让我们来点有趣的。到目前为止,我们已经看到了如何在 Claude AI 中使用技能以及如何使用 Claude Messages API 使用技能。现在让我们更深入地讨论如何在 Claude Code 中使用技能。我正在使用的应用程序是一个命令行应用程序,用于创建需要完成的待办事项并列出它们,最终进行编辑和清除。我将展示 CLAUDE.md 文件,让你了解这个项目是做什么的。在我们深入研究每个单独的文件之前,我们现在要做一个小演示。

在 Claude Code 中,你有能力创建一个 claude.md 文件。此文件是使用 /init 命令创建的,或由用户手动创建。此文件始终在你的上下文中,并且特定于你的项目。在这里,你可以指定关于你正在处理的代码库项目的通用说明、技术栈以及 Claude 在每一次对话中需要知道的事情。再说一次,我们正在构建一个命令行任务管理应用程序,使用 Python,Typer 作为我们的 CLI 框架,使用 dataclasses,Rich,我们将信息存储在 JSON 文件中以进行持久化,并使用 uv 进行依赖管理。

我们的架构遵循此模式。我们有入口点,所有的命令都有自己独立的 Python 文件。我们在 models.py 中设置数据类,在 storage.py 中设置存储、序列化、反序列化的逻辑,然后在 display.py 中在终端中漂亮地显示内容。我们有几个常量,然后是我们的测试。我们可以在这个文件中看到,我们有优先级(Priority)、作为数据模型的任务(Task),以及数据是如何持久化的。再次记住,对于这个 CLAUDE.md,这是在我们进行的每次对话的上下文中始终可用的数据。这是非常有用的信息,可以帮助 Claude 弄清楚在哪里找到东西以及如何最好地构建信息。

考虑到这一点,让我们跳进去玩一下这个应用程序。首先,我要继续激活虚拟环境。所以我将 source .venv 然后 activate。一旦我设置好了,通过 uv sync 确保我的依赖顺序正确。一旦我做到了这一点,我实际上可以直接在命令行中开始使用这个 task 命令。如果我看一下命令,我看到我有像 add、done 和 list 这样的命令,以及一些额外的选项。所以让我们继续看看我这里的任务。目前,我没有找到任何任务。我将清除终端,以便我可以从头开始。

让我们继续添加一个任务。就在这里,我们将其称为"编写最终报告"。我们将给它一个高优先级,并给它一个接下来的完成日期。我们可以看到这里我已经成功添加了它,所以让我们去看看。我们的任务就在那里。我们的 display.py 正在对该信息进行一些漂亮的格式化。现在让我们继续完成它。我将继续使用正确的 ID 将其标记为已完成。然后如果我继续查看我的列表,我可以看到带有那个标志的任务已经完成了。

这节课的计划是为编辑(edit)添加另一个命令行命令。所以我们必须去 src 到 task 并在这里添加另一个用于编辑的命令。但我们也想确保当我们添加这些额外命令时,我们遵循正确的工作流。我们遵循一种正确添加命令的适当方式,以稍微匹配我们在代码库中所做的事情的模式。为了做到这一点,我们将在这里使用一个我们已经添加的名为 adding CLI command(添加 CLI 命令)的技能。技能定义在一个 .claude 文件夹中,后面跟着一个名为 skills 的文件夹。当我们查看这个技能时,首先我们不仅可以看到这在项目级别是可用的。如果我们愿意,我们也并未可以在我们的主目录中的用户级别创建技能。对于这个例子,我们将专注于项目特定的技能。

所以让我们深入了解这个特定技能正在发生什么。就像我们之前看到的那样,我们有一个名称和一个描述,在这里我们将真正倾向于我们在创建新命令时想要的特定编码风格和功能。我们将首先确定必要的工作流并在适当的目录中创建文件。就像我们要有用于 add、done 和 list 的命令一样,我们要确保当我们为命令制作新文件时,它就存在于那里。我们还要确保这些命令在位于 command 文件夹中的 init.py 文件中注册。当我们思考如何创建不同的命令时,可能会有很多不同的种类。可能有些命令涉及子命令或标志或额外的参数。你可以向 Claude 提供纯文本说明,但特别是对于编码示例,当你确切地告诉它你想要遵循什么模式和风格时,Claude 会做得非常好。

在 Typer 库中,有很多不同的方法来完成特定任务。例如,有很多不同的方法可以向正在装饰的参数添加类型注释。在这个特定的例子中,这是我们想要遵循的约定,因为它稍微现代一点。你可以想象在你使用的库和代码库中,有很多不同的做事方式。但技能的用处在于它为我们提供了一组任务的可预测工作流。我们还要确保我们要在这里使用这个 display 对象并调用像 success 和 info 这样的方法,以确保当我们添加命令时,我们不仅在执行业务逻辑,而且在最后向用户显示正确的信息。当我们使用标志时,我们想要特定的简写,我们想要特定的更长寻址方式。我们还要确保也有帮助文本。所有这些部分,类型注释、默认参数、某些返回值,都有价值添加到你的技能中,以便你知道如何最好地进行模式匹配并遵循可预测的工作流。

当我们思考带有子命令的命令时,你不仅可以在这里看到我们希望如何构建单个命令,还可以看到当迁移或版本更改等事情发生时我们希望如何显示。当我们想象可能是破坏性的命令时,当我们开始添加清除(clear)功能时,我们可能想指定我们正在做什么样的删除。如果我们选择进行硬删除,我们要确保在我们继续删除该特定任务之前进行确认。随着 Claude 开始看到可能需要涉及某种删除的额外命令,可以遵循此模式。当我们思考注册时,我们要有意地考虑如何添加单个命令和命令组。所以我们不仅给 Claude 关于如何注册命令的说明,我们还非常具体地说明要遵循什么约定。

最后,当我们谈论约定时,在这里我们可以倾向于对文档字符串的要求,注意退出代码并遵循我们拥有的常量,注意破坏性的命令。这是一个非常有用的地方,这样当我们构建我们可预测的工作流时,我们就确切地知道我们在遵循什么约定。这些约定不必存在于代码库的任何地方,也不必在上下文中随处加载。如果是这样,我们可以把它们放在 CLAUDE.md 中。但在这种情况下,仅为了添加单个命令,有一部分约定需要遵循。让我们只在必要时加载那些。而且我们不仅在使用像 CLI app 这样的通用命名,我们还可以在任何遵循智能体技能约定的平台上使用此技能。根据你正在构建的任何 CLI,如果你希望 Claude 遵循这些特定模式,这个技能可以很容易地适应做到这一点。

现在我们有了用于添加 CLI 命令的技能,让我们也确保当我们开始做这个添加命令的特定工作流时,我们也注意测试并验证我们要编写的命令。这是另一个技能的绝佳用例。在我们的第二个技能中,用于生成 CLI 测试,我们为 Typer 命令生成 pytest 测试。我们在这里包括我们想要什么样的 fixture(测试夹具),如何处理边缘情况,而且这真的很重要,我们要添加做什么以及如何触发这个特定技能。你可以看到这里,当用户要求编写测试、为我的 CLI 或添加测试覆盖率时使用。这总是很重要的,不仅要非常明确描述是什么,还要明确 Claude 如何检测如何运行它。

与其他技能类似,我们指定我们想要的工作流。在编写测试时,利用 fixture 通常是最佳实践。你可以将 fixture 视为每次在你安排测试时运行的信息,以设置信息、设置虚拟数据,以及你为你编写的每个测试所需的任何类型的模拟或测试基础设施。例如在这里,我们指定我们的临时存储可能是什么样子的,我们指定一些样本数据可能是什么样子的。当我们运行这些测试中的每一个时,此信息将被公开,以允许我们在测试单个文件、文件夹和工作流时安排和设置必要的测试。

当我们看一下测试结构时,我们遵循一种安排模式,就像我们提到的 fixture 一样,调用某种动作,然后断言结果就是这种情况。就在这里,我们只是试图构建 Claude 在利用此技能时可以使用和遵循的模式。我们可以继续我们要如何完成这个测试运行器。以及如何按命令类型测试场景。当我们读取、添加时,这里有我们要我们的测试看起来像什么的例子。就像许多测试库一样,有很多方法可以完成类似的任务。对于这些例子,这是我们要遵循的模式。

当我们接近这个技能的尾声时,我们要稍微思考一下要覆盖的边缘情况。在编写测试时,我们要考虑无效输入、任何类型的状态、确认,或者是当找不到或不存在东西时会发生什么。我们还要确保我们在继续编写测试时也遵循清单。最后,为了确保我们要正确运行测试,提供正确的命令以及如何在详细模式下和针对特定文件运行。我们在这里拥有的最后一个技能是总结和审查,并确保我们要按预期执行命令。一旦我们生成了测试并且测试正在运行,让我们确保我们遵循正确的约定。

就像我们在其他技能中看到的那样,我们可以通过几种方式执行必要的任务,然后回来验证事情是否按预期工作。当我们思考审查这些命令时,我们不仅要考虑底层结构,这在正确的位置吗?这是使用了正确的装饰器吗?这注册正确吗?但我们也确保我们要包括那些我们想要的关于类型注释或参数选项或标志的实践(如果可能的话)。提供正面和反面的例子通常很有帮助。所以正如我们提到的,使用这种 Annotated 类型与另一种类型的参数键入方式进行对比。

当我们更多地思考错误处理和输出时,我们确保此清单存在,以便技能可以检查并确认所有这些部分都符合预期。我们不是告诉 Claude 如何执行这些操作,比如添加命令和生成测试。我们要确保它按预期工作。你可以把这看作是对我们拥有的其他技能的评估,并将此技能作为我们工作流的一部分。当我们看底部时,确保我们的最佳实践得到遵循,以及我们可能看到的错误示例和修复方法。当我们看技能的输出格式时,我们要确保我们的所有清单都得到解决,包括摘要和建议的修复。

拥有这个底层审查非常有用,这样当功能完成时,我们可以通过查看这个审查开始,甚至将其作为我们要代码审查的一部分,确保我们正在构建生产级功能,有测试支持,遵循最佳实践。考虑到这一点,让我们开始把所有这些技能放在一起。我们要在这里做的第一件事是添加一个新命令,允许我们编辑单个任务。我们将要确保我们要编辑标题和优先级,并传入一个有效的 ID。

现在,让我们跳进 Claude Code 并使用这些技能。为了确保我已经正确注册了这些,我首先只输入 /skills。这将列出我拥有的可用技能。这些是项目技能。我们也提到我们可以在我们的主目录中添加技能,但现在我们只处理项目技能。我们还可以看到这些技能占用的 token 数量,因为我们只考虑必要的名称和描述。当你在 Claude Code 中创建一个新技能时,你要确保你关闭 Claude Code 并再次打开它,以便该技能可以被识别。所以如果你发现自己看着你的技能列表,但你错过了你可能刚刚创建的那个,请确保关闭那个 Claude Code 实例,再次打开它,你应该就没事了。

现在我们的技能已正确加载,让我们继续添加一个新的编辑命令,以允许用户编辑标题和优先级。我们在这里添加一个小例子,并确保我们要遵循创建新 CLI 命令的约定。所以让我们试一试。我们可以看到这里 Claude Code 正在提示使用 adding-cli-command 技能。这太棒了。我们将继续并确保在未来,它不会提示我们这样做。我们可以看到这里,我们要读取现有的文件和存储以了解约定,以及查看其他命令的示例以供参考。现在我们知道了约定,让我们继续创建该特定文件。

我们可以看到这里有一个新文件 edit.py 正在创建,我们要继续进行该更改。我们会看到 edit.py 出现在这里,我们现在要注册该命令,遵循技能为我们设定的顺序。我们可以看到这里该命令正按预期在我们的 init.py 中注册。我们也要继续进行该更改。所以我们将继续让它继续并运行此命令进行测试,并确保它按预期工作。看起来我们看到了我们期望的,状态良好。所以现在要运行 add 命令,然后继续运行 list 以确保我们已成功添加。它正在播种一些数据,以便我们随后可以确保 edit 命令按预期工作。我们将继续编辑该特定任务。

我们会看到这里我们没有指定标题或优先级。然后我们将继续输入该标题。在这里,我们会看到它按预期进行了编辑。它会再次要求我编辑此任务,这会随着我们开始测试各种示例而一遍又一遍地发生。虽然我可以一遍又一遍地继续,但我们可以想象这可能会开始填满上下文窗口,如果这是一个更大规模的系统,可能非常耗时甚至计算密集。所以我们要在这里做一些稍微不同的事情。我们要利用 Claude Code 拥有的使用子智能体的功能。我们要有一个子智能体来审查代码并遵循标准,以便主智能体可以专注于开发。然后我们要有另一个子智能体使用我们要生成的技能来生成和运行测试。

这样做的好处是我们可以让主智能体专注于开发,而子智能体在它们自己的上下文窗口中专注于生成测试和审查代码。然后我们可以获取反馈和生成的测试,将其带回主智能体,采用一种更加上下文高效的方法。值得注意的是,子智能体不会从父级继承技能,所以我们需要明确我们给予我们制作的每个子智能体的技能。有多种方法可以将技能传递给子智能体。我们要向你展示的一种方法是在子智能体中明确技能的名称。我们将在注释中链接的另一个选项是你可以在其中提供确切的智能体名称以及如何直接从技能中最好地运行它。

考虑到这一点,让我们继续创建我们的子智能体。我们要制作的第一个智能体是我们的代码审查员(code reviewer)。我们要使用 /agents 命令创建这些智能体。我们要创建一个新智能体。我们要在项目基础上这样做,但不是用 Claude 生成,而是遵循手动配置,这样你就可以看到为我们的每个智能体添加名称、描述、工具,最重要的是技能是什么样子的。我要制作的第一个,我们称之为 code-reviewer。这将是我们智能体的唯一标识符,然后我们将向其传递一个提示词。我们要粘贴一个提示词,一旦我们创建了这个,我们就会看一下,但这看起来会非常熟悉我们如何审查以及我们在进行代码审查时做出的见解是多么具体和可操作。

我们要继续给这个智能体一个描述,说明何时应该继续使用它。我们要审查代码质量、安全性等等。我们要尝试使这个智能体对于我们想要的工具集尽可能通用。我们要非常具体地说明我们的子智能体有权访问的工具。所以我们将确保如果需要执行代码,我们给它 Bash、Glob 和 grep 来查找文件,以及 Read 来读取我们要审查的底层文件。一旦我们选择了这些工具,我们就可以继续。我们将决定从父级继承我们使用的模型。我们将继续给这个智能体一个紫色的颜色。

一旦我们设置好了,我们将继续保存这个智能体。我们现在可以在我们的 agents 文件夹中看到,我们要有一个代码审查员智能体可以使用。我们有代码审查员、描述、工具以及我们添加的所有精彩部分。现在是时候确保我们指定我们要这个子智能体使用的技能了。我们将使用 skills 字段来做到这一点,并指定我们要使用的技能名称。在我们的例子中是 reviewing-cli-command。在我们创建下一个智能体之前,让我们快速回顾一下我们在这里制作的内容。我们的智能体有一个名称和描述、可用的工具、我们继承的模型、颜色以及我们引入的技能。我们可以添加多个技能,但现在我们将坚持使用一个。

在我们的提示词中,我们提到它是一个确高质量标准的保代码审查员。当我们指定这个智能体是什么时,我们指定它何时被调用,一般质量检查。如果我们使用 Python 工作,如何做到有意图,CLI 命令也是同样的方式,以及特定的输出格式。就像我们提到的,这个智能体稍微通用一点。虽然我们在特定应用程序的上下文中使用它,但技能可以帮助我们更加具体。但这个智能体可能需要跨各种不同的应用程序使用,所以我们要对我们要在这里尝试做的事情稍微通用一点。值得注意的是,技能在 Claude Code 中作为子智能体的运作方式略有不同。当这个子智能体被调度和创建时,技能不仅加载名称和描述,而且加载整个 SKILL.md。技能在智能体被调度时被预加载。如果有额外的逐步披露、读取其他文件或其他命令,那是不会完成的。但是在调度子智能体时会读取整个 SKILL.md。

现在我们有了代码审查员智能体,让我们添加另一个智能体来生成和运行我们的测试。让我们继续制作我们的第二个智能体。我们要使用手动配置在我们的项目中创建一个新智能体。我们将其称为 test-generator-runner。我们要在这里添加一个提示词,我们稍后会回顾一下,但这将遵循我们看到的与我们制作的另一个智能体类似的模式。我们将继续为这个智能体指定一个描述,我们要指定它应该运行测试并在丢失时生成它们。当用户要求测试或运行测试时,让我们继续确保调度此智能体。

就像我们之前看到的那样,让我们转到我们的高级选项,而不是授予所有工具的访问权限。在我们的高级选项中,我们将继续禁用我们所有的工具。在这里我们将确保我们有一个 Bash 工具、Glob 和 Grep,还有 Read。但我们也需要 Edit 和 Write 单个文件,在我们的例子中,Edit 可能已经存在的文件。一旦我们设置好了这些工具,让我们继续,稍微谈谈我们要使用的模型。就像以前一样,我们将从父级继承,我们将继续为这个子智能体使用黄色。这对我们来说看起来不错,所以我们将继续保存它。一旦我们创建了这个智能体,我们也要确保我们指定正在使用的技能。在这种情况下,我们要使用的技能是 generating-cli-tests 技能。

同样,我们可以添加多个技能,但在这种情况下,我们只使用那一个。正如我们之前看到的那样,我们指定何时调用它,我们展示如何发现测试以及我们想要的输出格式,然后是一些底层规则以确保事情按预期工作。就像我们的另一个智能体一样,稍微通用一点,并依靠技能来提供一致的工作流。现在我们已经创建了我们的子智能体和技能,让我们把这一切放在一起,以确保当我们添加新命令时,我们在必要时调度子智能体,使用我们想要的技能。

让我们从使用我们的 code-reviewer 子智能体来审查我们制作的 edit.py 命令开始。这不仅应该调度子智能体,而且还应该利用我们提供给该子智能体的技能。我们将在这里看到代码审查员智能体已被调度。现在我们将继续使用我们定义的必要技能和工具。我们可以看到这里什么在工作,什么不工作。没有严重的结果,但我们有一些警告。要修复的问题和建议的修复。如果我们愿意,我们可以继续使用主智能体来实现这些。然后我们将使用我们的测试运行器子智能体为我们的 edit.py 命令生成测试。所以我们将继续确保我们引用 edit.py 文件并继续调度第二个子智能体。

我们可以看到这里它生成了必要的命令,并且它提示我确保我们要进行此编辑。所以随着我们为编辑添加测试,我们将继续这样做。我可以使用这个命令 uv run 确认这些测试正在工作。然后我们将继续在详细模式下运行我们所有的测试,以确保事情通过并且没有回归。一旦完成,我们可以看到我们所有的测试都通过了,没有失败的,这也是一个摘要。我们利用了两个不同的子智能体,利用了多种技能,随着我们开始添加更多功能,我们可以将这些放在一起。

我们要做的下一件事是看到我们的代码审查员子智能体和我们的测试运行器子智能体的实际行动。让我们想象有一个 clear.py 文件,这是一个由团队中某个人添加的新命令,他没有遵循最佳实践,也许没有使用我们要设置的所有技能和基础设施。我们将继续使用我们的代码审查员子智能体以及我们的测试运行器子智能体来弄清楚如何最好地修复 clear.py 文件。我们将调度我们的代码审查员来审查 clear.py 命令。然后我们将使用我们的 test generator runner 来生成此命令所需的测试。最后我们将验证事情是否按预期工作,并确保所有测试都通过并遵循我们标准化的最佳实践。

我们可以看到这里有相当多的问题和一些警告。现在我们发现了这些问题,让我们确保主智能体正在读取文件并修复它们。我们要确保我们允许对 clear.py 进行这些编辑。在这里我们可以看到像向控制台显示东西这样的事情是使用像 display 这样的正确方法完成的,正如我们在最佳实践中提到的那样。我们还要确保我们在 init.py 中正确注册此命令。主智能体本身没有为审查和生成测试添加额外的上下文。它只是获取子智能体的输出以更好地执行这些任务。

接下来,是时候为遵循我们最佳实践的 clear 命令生成测试了。我们可以看到这里它正在添加一个文件来测试此 clear 命令。让我们继续批准它。现在我们已经创建了这个文件,让我们继续运行测试,确保它们按预期工作。现在我们可以获得已完成内容的摘要。六个关键问题,四个警告,所有这些都已修复。我们修复了相当多不同的问题,而不是使用不正确的方法,而不是使用格式不正确的标志,不正确的退出代码。然后我们在此基础上添加了测试,以确保我们要确认所有这些最佳实践都按预期完成,并且功能正常工作是我们喜欢的。在下一节课中,我们将离开 Claude Code 并转移到 Claude Agent SDK,并展示在使用 Claude Code 使用的相同工具构建你自己的智能体时如何使用技能。

English Script

We’ll now switch to Claude Code and use skills for code generation, reviewing, and testing. We’ll also set up sub-agents and equip them with skills. Let’s have some fun. So far we’ve seen how to use skills in Claude AI and using the Claude messages API. Now let’s talk about how to use skills in Claude Code in a bit more depth. The application that I’m using is a command line application for creating to-dos that need to be completed and listing them, and eventually editing and clearing. I’m going to show the CLAUDE.md file to give you a sense of what this project does. And now we’re going to do a little demo before we jump into each of the individual files.

In Claude code, you have the ability to create a claude.md file. This file is created using the /init command or manually by the user. This file is always in your context and specific to your project. This is where you can specify general instructions about the code base project you’re working on, technology stack, and things that Claude needs to know in every single conversation. So again, we’re building a command line task management application using Python, Typer as our CLI framework, using dataclasses, Rich, we’re storing information in a JSON file for persistence, and using uv for dependency management.

Our architecture follows according to this pattern. We’ve got our entry point and all of our commands get their own individual Python file. We set up our data class in our models.py our logic for storing, serializing, deserializing in our storage.py, and then to display things nicely in the terminal, our display.py. We have a couple constants and then our tests. We can see in this file, we have our Priority, our Task as Data Models, how data is persisted. And remember again for this CLAUDE.md, this is data that is always available in context in every conversation that we have. This is useful information to help Claude figure out where to find things and how best to structure information.

So with that in mind, let’s hop in and play around with this application. First, I’m going to go ahead and activate the virtual environment. So I’ll source .venv then activate. Once I’ve got that set up, make sure my dependencies are in order with uv sync. And once I’ve done that, I can actually start using this task command directly in the command line. If I take a look at the command, I see I have commands like add and done and list, as well as some additional options. So let’s go ahead and take a look at the tasks that I have right here. Right now, I have none of them that are found. I’ll clear the terminal so I can start from the top.

Let’s go ahead and add a task. Right here, we’ll call this write the final report. and we’ll give this a priority of high and we’ll give this a date to be done with the following. We can see here I’ve added that successfully, so let’s go take a look. We’ve got our task right there. our display.py doing some nice formatting of that information. Now let’s go ahead and complete it. I’ll go ahead and mark that as done with the correct ID. And then if I go ahead and take a look at my list, I can see with that flag that I have this task that is done.

The plan for this lesson here is to add another command line command for edit. So we’re going to have to go to src to task and add another command here for editing. But we also want to make sure that when we add these additional commands, we’re following the correct workflow. We’re following a proper way of adding commands the right way to pattern match a bit of what we’ve done in this code base. In order to do so, we’re going to be using a skill here that we’ve added called adding CLI command. Skills are defined inside of a .claude folder followed by a folder called skills. When we take a look at this skill, first not only we can see that this is available at the project level. We can also create skills at the user level in our home directory if we’d like as well. For this example, we’ll be focusing in project specific skills.

So let’s dive into what’s happening for this particular skill. Just like we saw before, we have a name and a description, and here we’re going to really lean into the particular coding styles and functionality that we want when creating new commands. We’re going to start by identifying the workflow necessary and creating files in the appropriate directories. Just like we have commands for add and done and list, we want to make sure that when we make new files for commands, it lives there. We also want to make sure that these commands are registered in our init.py file that lives in the command folder. When we think about how to create different commands, there may be lots of different kinds. There may be commands that involve subcommands or flags or additional arguments. You can provide plain text instructions to Claude, but especially for coding examples, Claude does really well when you tell it exactly what pattern and style you want to follow.

In the Typer library, there are many different ways of accomplishing particular tasks. For example, there are many different ways of adding type annotations to arguments that are being decorated. In this particular case, this is the convention we want to follow as it’s a little bit more modern. You can imagine in libraries and code bases that you use, there are many different ways of doing things. But what’s useful about skills is it gives us that predictable workflow for a set of tasks. We also want to make sure we’re using this display object here and calling methods like success and info to make sure that when we add a command, we’re not only executing the business logic, but displaying the correct information to the user at the end. When we work with flags, we want particular shorthand, we want particular longer ways of addressing it. We want to make sure that there’s help text as well. All of these pieces, type annotations, default arguments, certain return values, are valuable to add in your skill so that you know how best to pattern match and follow predictable workflows.

As we think about commands with subcommands, not only can you see here how we want to structure individual commands, but also how we want to display when things like migrations or versions are changed. As we imagine commands that might be destructive, as we start adding functionality to clear, we might want to specify what kind of delete we’re doing. If we choose to do a hard delete, we want to make sure that we confirm before we go ahead and delete that particular task. This pattern can be followed as Claude starts to see additional commands that might need to involve some kind of deletion. As we think about registering, we want to be intentional about how to add single commands and command groups. So not only are we giving Claude instructions for how to register commands, we’re being very specific with what conventions to follow.

And finally, as we talk about conventions, here’s where we can lean into requirements on our doc strings, being mindful of exit codes and following constants that we have, being mindful of commands that are destructive. This is a really useful place so that when we build our predictable workflows, we know exactly what conventions we’re following. These are not conventions that have to exist everywhere in the code base and have to be loaded everywhere in context. If so, we could put them in the CLAUDE.md. But in this case, just for adding individual commands, there’s a subset of conventions to follow. Let’s only load those when necessary. And not only are we using generic naming like CLI app, we can use this skill across any platform that follows the agent skills convention. And depending on whatever CLI you’re building, if you want Claude to follow these particular patterns, this skill can easily be adapted to do that.

Now that we have a skill for adding CLI commands, let’s also make sure that when we start to do this particular workflow of adding commands, we’re being mindful of testing and also validating the commands that we write. This is another great use case for another skill. In our second skill, for generating CLI tests, we generate pytest tests for Typer commands. We include here what kinds of fixtures we want, how to handle edge cases, and this is really important that we add what to do and how to trigger this particular skill. You can see here, use when the user asks to write tests, for my CLI or add test coverage. It’s always important to be very explicit not only in what the description is, but how Claude can detect how to run it.

Similar to other skills, we specify the workflow that we want. When writing tests, it’s often best practice to leverage fixtures. You can think of fixtures as information that is run each time as you arrange your tests to set up information, to set up dummy data, as well as any kind of mocking or test infrastructure that you need for each of the tests that you write. For example here, we specify what our temporary storage might look like and we specify what some sample data might look like. As we run each of these tests, this information will be exposed to allow us to arrange and set up the tests necessary when we test individual files, folders, and workflows.

As we take a look at the test structure, we’re following a pattern of arranging like we mentioned with our fixtures, invoking some kind of action, and then asserting that the result is the case. Right here, we’re simply trying to build patterns that Claude can use and follow when this skill is leveraged. We can continue on with how we want this test runner to be done. and how to test scenarios by a command type. As we read, as we add, here are examples for what we want our test to look like. As with many testing libraries, there are lots of ways to accomplish a similar kind of task. For these examples, here is the pattern we want to follow.

As we wrap towards the end of this skill, we want to think a little bit about edge cases to cover. When writing tests, we want to think about invalid input, any kind of state, confirmation, or what happens when things are not found or don’t exist. We want to make sure that we’re following a checklist as well when we go ahead and write our tests. And then finally, to make sure we’re running tests correctly, providing the correct commands as well as how to run in a verbose mode and for specific files. The last skill that we have here is to wrap up and review and make sure that we’re executing the commands as expected. Once we’ve generated the tests and the tests are running, let’s make sure we’re following the correct conventions.

Just like we saw with other skills, there are ways in which we can execute the task necessary and then come back and validate that things are working as expected. As we think about reviewing these commands, not only do we think about the underlying structure, is this in the right location? Is this using the right decorator? Is this registered correctly? But we’re also making sure that some of those practices we wanted around type annotations or options for parameters or flags when possible, always are included. It’s often helpful to provide positive and negative examples. So as we mentioned, using this Annotated type versus a different kind of way to type your arguments.

As we think more about error handling and output, we make sure that this checklist exists so that the skill can go through and confirm that all of these pieces are as expected. We’re not telling Claude how to perform these actions like adding commands and generating tests. We’re making sure that it’s working as expected. You can think of this like an evaluation almost for the other skills that we have and including this skill as part of our workflow. As we take a look at the bottom, making sure that our best practices are followed, as well as examples of mistakes we might see and fixes for those. As we take a look at the output format of the skill, we want to make sure that all of our checklist is addressed, including a summary and suggested fixes.

It’s very useful to have this underlying review so that when features are finished, we can start by taking a look at this review, include this even as part of our code review, make sure we’re building production grade features, backed by tests, following best practices. With that in mind, let’s start to put all of these skills together. The first thing we’re going to do here is to add a new command that allows us to edit individual tasks. We’re going to want to make sure we edit the title and priority and pass in an ID that is valid.

Now, let’s hop into Claude Code and use these skills. To make sure that I’ve registered these correctly, I’m first going to just type in /skills. This is going to list the available skills that I have to me. These are project skills. We also mentioned we can add skills in our home directory, but right now we’re just dealing with project skills. We can also see the amount of tokens that these skills are taking as we just think about the name and description necessary. When you create a new skill in Claude Code, you want to make sure that you close Claude Code and open it up again so that skill can be identified. So if you find yourself looking at your list of skills but you’re missing the one that you might have just created, make sure to close that instance of Claude Code, open it up again and you should be in good shape.

Now that our skills are loaded correctly, let’s go ahead and add a new edit command to allow users to edit the title and priority. We’re adding a little example here and ensuring that we follow the conventions for creating a new CLI command. So let’s give this a shot. We can see here that Claude Code is prompting to use the adding-cli-command skill. And that’s great. We’ll go ahead and make sure that in the future, it doesn’t prompt us to do so. We can see here, we’re going to read the existing files and storage to understand the convention, as well as take a look at examples of other commands for reference. Now that we know the conventions, let’s go ahead and create that particular file.

We can see here there’s a new file edit.py being created, and we’re going to go ahead and make that change. We’ll see here edit.py appears and we’re now going to register that command, following the order that the skill has set out for us. We can see here that the command is being registered inside of our init.py as expected. We’re going to go ahead and make that change as well. So we’ll go ahead and let it proceed and run this command to test out and make sure it’s working as expected. Looks like we’re seeing what we expect and that’s in good shape. So now going to run the add command and then go ahead and run the list to make sure we’ve added successfully. It’s seeding some data so that we can then make sure the edit command works as expected. We’ll go ahead and edit that particular task.

And we’ll see here that we didn’t specify a title or priority. We’ll then go ahead and put in that title. And here, we’ll see that edited as expected. It’s going to ask me again to edit this task, and this is going to happen over and over as we start to test all kinds of examples. And while I could proceed over and over again, we can imagine that this might start to fill up the context window quite a bit, and if this were a larger scale system, maybe very time intensive and even compute intensive. So what we’re going to do here is something slightly different. We’re going to leverage the functionality that Claude Code has for using sub-agents. We’re going to have one sub-agent to review code and follow the criteria so that the main agent can focus on the development. We’re then going to have another sub-agent to generate and run the tests using the skill that we have.

What’s going to be useful about this is that we can have the main agent focus on development, while sub-agents in their own context window focus on generating the tests and reviewing the code. We can then take the feedback and tests generated, bring it back to the main agent, with a much more context-efficient approach. It’s important to note that sub-agents do not inherit skills from a parent, so we need to be explicit with the skills that we give to each sub-agent that we make. There are multiple ways of passing skills to sub-agents. One way we’re going to show you is being explicit with the name of the skill in the sub-agent. Another option that we’ll link in the notes is where you can provide the exact agent name and how best to run it from the skill directly.

With that in mind, let’s go ahead and create our sub-agents. The first agent we’re going to make is our code reviewer. And we’re going to create these agents using the /agents command. We’re going to create a new agent. We’re going to do that on a project basis, but instead of generating with Claude, we’re going to follow a manual configuration so you can see what it looks like to add a name, description, tools, and the most importantly, skills for each of our agents. The first one I’ll make, we’ll call code-reviewer. This will be the unique identifier for our agent, and then we’ll pass a prompt to it. We’re going to paste in a prompt that we’ll take a look at once we’ve created this, but it’s going to look very familiar to how we review and how we’re specific and actionable in the insights we make when doing the code review.

We’re going to go ahead and give this agent a description for when it should go ahead and be used. We’re going to review for code quality, security, and so on. We’re going to try to make this agent as generic as possible for the set of tools that we want here. We want to be very specific with the tools that our sub-agent has access to. So we’ll make sure that if there’s code that needs to be executed, we give it Bash, Glob and grep for finding files, and Read to read underlying files that we’re going to be reviewing. Once we’ve selected these tools, we can go ahead and continue. We’ll decide to inherit from the parent with the model that we use. And we’ll go ahead and give this a color of purple.

Once we’ve got this set up, we’ll go ahead and save this agent. We can see here now in our agents folder that we have a code reviewer agent to work with. We’ve got the code reviewer, a description, tools, and all the wonderful pieces that we added. It’s now time to make sure we specify the skills that we want this sub-agent to use. We’ll do that using the skills field and specify the name of the skill that we’re working with. In our case, reviewing-cli-command. Before we create our next agent, let’s do a quick review of what we made here. Our agent has a name and description, tools available, a model that we inherit, a color, and skills that we brought in. We can add multiple skills, but right now we’re going to stick with one.

In our prompt, we mention it’s a code reviewer ensuring high standards. As we specify what this agent is, we specify when it’s invoked, general quality checks. If we’re working with Python, how to be intentional, CLI commands the same way, and a particular output format. Like we mentioned, this agent is a bit more generic. And while we’re using it in the context of our specific application, skills can help us be more particular. But this agent might need to be used across a different variety of applications, so we want to be a little bit more generic with what we’re trying to do here. It’s important to note that skills operate slightly differently as sub-agents in Claude Code. When this sub-agent is dispatched and created, the skill is not only loading the name and description, but the entire SKILL.md The skills are pre-loaded when the agent is dispatched. If there is additional progressive disclosure, reading of other files or other commands, that is not done. but the entire SKILL.md is read when the sub-agent is dispatched.

Now that we’ve got our code reviewer agent, let’s add another agent for generating and running our tests. Let’s go ahead and make our second agent. We’re going to go create a new agent in our project using the manual configuration. and we’ll call that test-generator-runner. We’re going to go ahead and add a prompt here that we’ll review a little bit later, but it’s going to follow similar patterns to what we saw with the other agent that we made. We’ll go ahead and specify a description for this agent, where we’ll specify that it should run tests and generate them if missing. When the user asks to test or run tests, let’s go ahead and make sure that this agent is dispatched.

Like we saw before, instead of giving access to all tools, let’s go to our advanced options. In our advanced options, we’re going to go ahead and disable all of our tools. and here we’ll make sure we have a Bash tool, Glob and Grep, Read as well. But we’re also going to need to Edit and Write individual files, and in our case, Edit files that may already exist. Once we’ve got these tools set up, let’s move on, talk a little bit about the model we’re going to be using. Just like before, we’ll inherit from the parent and we’ll go ahead and use yellow for this sub-agent. This looks good to us, so we’ll go ahead and save it. Once we’ve created this agent, we also want to make sure we specify what skills are being used. In this case, the skills that we’re going to be using is the generating-cli-tests skill.

Again, we could add multiple skills, but in this case, we’re just going to use that individual one. As we saw before, We specify when it’s invoked, we show how to discover tests and the output format that we want, and then some underlying rules to make sure that things are working as expected. And just like our other agent, to be a little bit more generic and lean on skills to provide consistent workflows. Now that we’ve created our sub-agents and our skills, let’s put this all together to make sure that when we add new commands, we dispatch sub-agents when necessary, using the skills that we want.

Let’s go ahead and start by using our code-reviewer subagent to review the edit.py command that we made. This should not only dispatch the subagent, but also make use of the skill that we provided to that subagent. we’re going to see here that the code reviewer agent has been dispatched. And now we’re going to go ahead and use the necessary skills and tools that we’ve defined. We can see here what’s working and what’s not working. No critical results, but we’ve got some warnings. issues to fix and suggested fixes. We can go ahead and use the main agent to implement those if we’d like. We’re then going to use our test runner sub agent to generate the tests for our edit.py command. So we’ll go ahead and make sure that we’re referencing the edit.py file and go ahead and dispatch the second sub-agent.

We can see here it’s generated the necessary commands and it’s prompting me to make sure that we want to make this edit. So we’ll go ahead and do so as we add tests for editing. I can confirm that these tests are working using this command uv run. And then we’ll go ahead and run all of our tests with verbose mode to make sure things are passing and there are no regressions. Once this is done, we can see that all of our tests are passing, none are failing, and here’s a summary as well. We made use of two different sub-agents, leveraging multiple skills, and as we start to add more features and functionality, we can put these all together.

The next thing we’re going to do is see our code reviewer sub agent and our test runner sub agent in action. Let’s imagine there’s a clear.py file, a new command that’s been added by someone on the team who hasn’t followed best practices and maybe didn’t use all the skills and infrastructure that we set up. We’re going to go ahead and use our code reviewer sub agent as well as our test runner sub agent to figure out how best to fix the clear.py file. We’re going to dispatch our code reviewer to review the clear.py command. then we’re going to use our test generator runner to generate the test necessary for this command. We’ll validate finally that things are working as expected, and make sure all the tests are passing and following the best practices that we’ve standardized.

We can see here there are quite a few issues and some warnings. Now that we found these issues, let’s make sure that the main agent is reading the files and fixing them. We’re going to want to make sure that we allow for these edits to clear.py. And here we can see things like displaying things to the console are done using the correct methods like display as mentioned in our best practices. We also want to make sure that we’re registering this command correctly inside of our init.py The main agent itself is not adding additional context for the reviewing and generating tests. It’s simply taking the output of the sub-agent to better execute these tasks.

Next up, it’s time to generate tests for the clear command that is following our best practices. We can see here it’s adding a file to test this clear command. Let’s go ahead and approve that. Now that we’ve created this file, let’s go ahead and run the tests, make sure they’re working as expected. And now we can get a summary of what’s been completed. Six critical issues, four warnings, and all of them fixed. Instead of using incorrect methods, instead of using flags that are not the right format that we want, incorrect exit codes, we fixed quite a few different issues. We’ve then added tests on top of that to make sure that we’re confirming that all these best practices are done as expected, and the functionality is working that we like. In the next lesson, we’ll shift away from Claude Code and move to the Claude Agent SDK and showcase how to use skills when building your own agents using the same harness that Claude Code uses.