@swifty.js/swifty 0.0.1-canary

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/ch10.md ADDED
@@ -0,0 +1,122 @@
1
+ # Slash Command
2
+
3
+ Slash Command: 以 / 开头的输入会被命令解析器拦截
4
+
5
+ ## 命令的分类
6
+
7
+ <!-- 源码: src/commands/commands.ts -->
8
+
9
+ - local: 不参与 Agent Loop
10
+ - local_ui: 不参与 Agent Loop, 需要重新渲染 UI
11
+ - prompt: 参与 Agent Loop, CLI 负责构造 prompt
12
+ - skill_fork: skill 加载后自动注册为 Slash Command (prompt 命令)
13
+
14
+ > 不参与 Agent Loop != 不调用 LLM API
15
+ >
16
+ > 例如 /compact 命令调用 LLM API 生成摘要, 但是不参与 Agent Loop
17
+
18
+ 命令拦截时机: 消息发送给 LLM API 前, 用户按下回车, 先判断输入是不是命令, 如果是命令则走命令处理逻辑, 不是命令才发送给 LLM API
19
+
20
+ ## local 命令
21
+
22
+ `/help` 打印帮助信息
23
+
24
+ <!-- 源码: src/commands/commands.ts -->
25
+
26
+ ```txt
27
+ - /help, /h, /? 打印帮助信息
28
+ - /compact, /c 压缩上下文
29
+ - /clear 清除对话历史
30
+ - /plan, /p 切换到 plan 模式
31
+ - /session 会话管理
32
+ - /memory 记忆管理
33
+ - /permission 权限管理
34
+ - /status, /s 打印状态信息
35
+ - /review 代码审查
36
+
37
+ 输入 /help <命令名> 查看详细用法
38
+ ```
39
+
40
+ `/compact` 手动触发上下文压缩, `/compact [description]` 指定上下文保留重点; 如果当前上下文窗口 <= 5k token, 直接提示无需压缩
41
+
42
+ <!-- 源码: src/commands/commands.ts, 部分子命令未实现, TODO -->
43
+
44
+ `/session` 会话信息
45
+
46
+ - `/session` 打印当前会话信息
47
+ - `/session list` 打印历史会话列表
48
+ - `/session resume <id>` 切换到指定 ID 的会话
49
+ - `/session new` 开启新会话
50
+ - `/session delete <id>` 删除指定 ID 的会话
51
+
52
+ `/memory` 记忆管理
53
+
54
+ - `/memory list` 打印记忆列表
55
+ - `/memory add <content>` 添加一条记忆
56
+ - `/memory clear` 清空记忆, 需要用户确认
57
+
58
+ `/permission` 权限管理
59
+
60
+ - `/permission` 打印权限模式和生效的权限规则数量
61
+ - `/permission mode <plan | default | acceptEdits | bypassPermissions>` 切换权限模式
62
+ - `/permission rules` 打印生效的权限规则列表
63
+ - `/permission add <rule> <effect>` 添加一条本地权限规则
64
+ - `/permission reset` 重置本地权限规则
65
+
66
+ `/status` 打印当前状态
67
+
68
+ <!-- 源码: src/commands/commands.ts -->
69
+
70
+ ```txt
71
+ Swifty Status
72
+ ──────────────
73
+ Mode: default
74
+ Tokens: 45230 in / 1200 out
75
+ Tools: 6 enabled
76
+ Memories: 8 entries
77
+ Model: claude-sonnet-4-20250514
78
+ Directory: /path/to/project
79
+ ```
80
+
81
+ `/skills` skill 管理
82
+
83
+ <!-- 源码: src/commands/commands.ts -->
84
+
85
+ - `/skills list` 打印 skill 列表
86
+ - `/skills info <name>` 打印指定 skill 的 frontmatter 和路径
87
+ - `/skills reload` 重新扫描并加载所有 skill
88
+
89
+ `/mcp` MCP 服务器连接状态
90
+
91
+ <!-- 源码: src/commands/commands.ts -->
92
+
93
+ - `/mcp` 打印 MCP 服务器连接状态
94
+
95
+ `/code-review` 代码审查 agent team 管理
96
+
97
+ <!-- 源码: src/commands/commands.ts -->
98
+
99
+ - `/code-review`, `/cr` 管理代码审查 agent team
100
+ - `/cr create` TODO
101
+ - `/cr add <name>` TODO
102
+ - `/cr remove <name>` TODO
103
+ - `/cr list` TODO
104
+ - `/cr status` TODO
105
+
106
+ ## local_ui 命令
107
+
108
+ <!-- 源码: src/commands/commands.ts -->
109
+
110
+ - `/clear` 开启新对话, 关闭当前会话, 持久化到磁盘的 jsonl 会话日志
111
+ - `/compact, /c` 强制上下文压缩
112
+ - `/plan, /p` 切换 plan 模式 (toggle), `/plan [description]` 指定任务描述
113
+ - `/quit, /exit, /q` 退出 Swifty
114
+ - `/resume, /r` 恢复以前的会话
115
+ - `/rewind` 回退对话到以前的检查点
116
+ - `/worktree, /wt` 管理 git worktree
117
+
118
+ ## prompt 命令
119
+
120
+ <!-- 源码: src/commands/commands.ts -->
121
+
122
+ `/review` CLI 将预设的代码审查 prompt 发送给 LLM API, 分析未提交的代码变更, `/review [description]` 指定代码审查重点; prompt 命令消耗 token
package/docs/ch11.md ADDED
@@ -0,0 +1,163 @@
1
+ # Skill
2
+
3
+ skill 是写给 AI 的 SOP (Standard Operation Procedure)
4
+
5
+ 背景: ch10 的 Slash Command 中, prompt 类型的命令可以将硬编码的代码审查 prompt 发送给 LLM, 不独立、不可更新、不可移植
6
+
7
+ 1. skill 是独立、可 (热) 更新、可移植
8
+ 2. 可以显式指定 skill, 提高 LLM 的准确率
9
+ 3. skill 可以携带资产:
10
+ - SKILL.md: SOP
11
+ - API reference、docs、example、scripts
12
+ 4. 执行模式: inline 或 fork
13
+ 5. 参数 $ARGUMENTS
14
+
15
+ ## Frontmatter
16
+
17
+ ```md
18
+ ---
19
+ name: skill-creator <!-- 不能和 Slash Comment 重名 -->
20
+ description: Create new skills...
21
+ allowed_tools: [] <!-- 可选的, 指定 skill 的工具白名单 -->
22
+ mode: skill 执行模式, inline 或 fork
23
+ model: 可选的, 指定 skill 使用的模型
24
+ fork_context: 可选的, 仅在 mode: fork 时生效, 决定携带的上下文; 可以是 full 对话摘要、recent 最近的 5 条消息、none 不携带对话历史; inline 模式共享对话历史, fork_context 字段会被静默忽略 <!-- 源码: src/skills/catalog.ts -->
25
+ ---
26
+ ```
27
+
28
+ ## skills 目录, 优先级从高到低
29
+
30
+ 1. 项目级: ${workDir}/.swifty/skills/
31
+ 2. 用户级: ~/.swifty/skills
32
+ 3. 内置级: Swifty 内置的 skills
33
+
34
+ ## skill 执行模式
35
+
36
+ ### inline 模式
37
+
38
+ 默认执行模式, 内联 skill 的 prompt 到用户消息, 参与 Agent Loop
39
+
40
+ ### fork 模式
41
+
42
+ 开启新对话, 调用 LLM API, 只将 LLM API 的响应返回给主对话, 不参与 Agent Loop
43
+
44
+ fork 模式的实现:
45
+
46
+ - 开启新对话
47
+ - 根据 skill.fork_context
48
+ - full 压缩上下文, 生成对话摘要, 携带对话摘要
49
+ - recent 携带最近的 5 条消息
50
+ - none 不携带对话历史 (默认)
51
+ - 如果用户传递参数, 则替换或追加 skill prompt 中的参数 $ARGUMENTS
52
+ - 使用 skill.model 创建一个临时 Agent, 拿到 LLM API 的响应
53
+ - 将 LLM API 的响应作为 assistant 消息返回给主对话
54
+
55
+ ## `$ARGUMENTS`
56
+
57
+ ```md
58
+ 请审查以下代码变更
59
+ $ARGUMENTS
60
+ 如果没有指定代码审查范围, 请审查 `git diff` 的所有变更
61
+ ```
62
+
63
+ 用户输入 `/review 重点关注类型问题` (replaceAll 字符串替换)
64
+
65
+ ```md
66
+ 请审查以下代码变更
67
+ 重点关注类型问题
68
+ 如果没有指定代码审查范围, 请审查 `git diff` 的所有变更
69
+ ```
70
+
71
+ fork 模式
72
+
73
+ 如果用户有输入, 则在 prompt 末尾追加 `\n\nARGUMENTS: ${args}`
74
+
75
+ ## 自动注册为 Slash Command (prompt 命令)
76
+
77
+ skill 加载后自动注册为 Slash Command (prompt 命令)
78
+
79
+ 热加载: 每次使用 skill 时都重新读文件
80
+
81
+ ## 意图识别: 让 Agent 自己选择 skill
82
+
83
+ ### 两阶段加载
84
+
85
+ 第一阶段: 轻量启动
86
+
87
+ Swifty 启动时, 扫描 skills 目录, 加载所有 skill 的 frontmatter 元信息 (name, description), 不加载完整 prompt 正文, 将 skill 元信息注入到 system prompt, 告诉 LLM 有哪些 skill
88
+
89
+ 第二阶段: 按需加载
90
+
91
+ LLM 判断用户意图匹配某个 skill 时, 调用 LoadSkill 工具
92
+
93
+ LoadSkill 工具根据该 skill 的 name, 读取 SKILL.md 的 SOP 作为工具调用结果返回, LLM 在下一轮 Agent Loop turn 中可以看到该 skill 的 SOP
94
+
95
+ ### LoadSkill
96
+
97
+ - properties: name
98
+ - metadata: 只读、非破坏性、分类 read
99
+
100
+ ```js
101
+ function LoadSkill(name) {
102
+ const skill = this.catalog.get(name);
103
+ if (!skill) {
104
+ return {
105
+ output: `Skill '${name}' not found. Available skills: ${available}`,
106
+ isError: true,
107
+ };
108
+ }
109
+ const body = runInline(skill, "", this.host);
110
+ // 记录已激活
111
+ // 用于上下文压缩, CLI 侧压缩恢复, 恢复关键上下文 skills
112
+ return {
113
+ output: `Skill '${name}' activated.\n\n${body}`,
114
+ isError: false,
115
+ };
116
+ }
117
+ ```
118
+
119
+ ## 渐进式披露
120
+
121
+ 渐进式披露: Agent 启动时, 扫描 skills 目录, 加载所有 skill 的 frontmatter 元信息 (name, description), 选择压力很低, LLM 判断用户意图匹配某个 skill 时, 才加载完整 SOP, 注意力集中在当前任务
122
+
123
+ ## 目录型 skill
124
+
125
+ - 单文件 skill 只有 SOP
126
+ - 目录型 skill 可以携带资产: SOP、API reference、docs、example、scripts, 整套技能方便打包、移植
127
+
128
+ ```txt
129
+ $ tree ~/.claude/skills/skill-creator
130
+ .claude/skills/skill-creator
131
+ ├── agents
132
+ │   └── xxx.md
133
+ ├── assets
134
+ │   └── xxx.html
135
+ ├── docs
136
+ │   └── ...
137
+ ├── example
138
+ │   └── xxx.md
139
+ ├── LICENSE.txt
140
+ ├── references
141
+ │   └── xxx.md
142
+ ├── scripts
143
+ │   ├── xxx.py
144
+ │   └── yyy.py
145
+ └── SKILL.md
146
+ ```
147
+
148
+ ## 完整流程
149
+
150
+ ```txt
151
+ 1. 启动时扫描 skills 目录, 加载所有 skill 的 frontmatter 元信息 (name, description), 轻量启动
152
+ 2. 将 skill 元信息注入到 system prompt, 告诉 LLM 有哪些 skill
153
+ 3. skill 加载后自动注册为 Slash Command (prompt 命令)
154
+ 4. 用户输入: 做一个 react 计数器 app
155
+ 5. 意图识别: LLM 判断匹配 react-best-practices skill
156
+ 6. Agent 请求调用工具 LoadSkill("react-best-practices")
157
+ 7. SOP 作为工具调用结果返回, 注入到 messages
158
+ 8. LLM 按 SKILL.md 的 critical/high/medium/low 规则、进行开发
159
+ ```
160
+
161
+ - MCP 负责接入
162
+ - 工具调用负责调用
163
+ - skill 负责编排工作流
package/docs/ch12.md ADDED
@@ -0,0 +1,288 @@
1
+ # Hook
2
+
3
+ 在 Agent 的生命周期事件上挂载自动化动作
4
+
5
+ ## Hook 的三要素
6
+
7
+ - 事件
8
+ - 条件, 可以省略, 如果省略则无条件触发
9
+ - 动作
10
+
11
+ 配置:
12
+
13
+ - ~/.swifty/config.yaml
14
+ - ${projectRoot}/.swifty/config.yaml
15
+ - ${projectRoot}/.swifty/config.local.yaml
16
+
17
+ 3 个配置文件按顺序加载, 后加载的 hook 配置不会覆盖先加载的 hook 配置, 多个 hook 配置会被拼接; 一个事件有多个 hook 时, 按 yaml 中的顺序逐个执行
18
+
19
+ <!-- 源码: src/config/config.ts HookConfigSchema -->
20
+
21
+ ```yaml
22
+ hooks:
23
+ - event: post_tool_use
24
+ condition: tool == "WriteFile"
25
+ action:
26
+ type: command
27
+ command: "prettier -w $SWIFTY_FILE_PATH"
28
+ ```
29
+
30
+ <!-- 源码: src/hooks/hooks.ts 环境变量 SWIFTY_EVENT, SWIFTY_TOOL, SWIFTY_FILE_PATH -->
31
+
32
+ 每当 Agent 调用 WriteFile 工具写文件后, 自动 format 代码
33
+
34
+ - 事件: post_tool_use
35
+ - 条件: tool == "WriteFile"
36
+ - 动作: format 代码
37
+
38
+ ## 事件
39
+
40
+ <!-- 源码: src/hooks/hooks.ts EventName 类型定义 -->
41
+
42
+ ### 会话级
43
+
44
+ - session_start 会话开始时触发
45
+ - session_end 会话结束时触发
46
+
47
+ ### Agent Loop 轮次级
48
+
49
+ - turn_start 用户发送新消息时触发, 一轮对话开始
50
+ - turn_end LLM 退出 Agent Loop 时触发, 一轮对话结束
51
+ - LLM 决定退出循环
52
+ - 超过最大循环次数后强制退出循环
53
+ - 用户按 esc 退出循环
54
+
55
+ ### 工具级
56
+
57
+ - pre_tool_use 工具调用前触发, 可以拦截工具调用
58
+ - post_tool_use 工具调用后触发
59
+
60
+ ### 消息级
61
+
62
+ - pre_send 用户消息发送给 LLM API 前触发
63
+ - post_receive 收到 LLM API 响应后触发
64
+
65
+ ### 系统级
66
+
67
+ - startup: Swifty 启动时触发
68
+ - shutdown: Swifty 退出时触发
69
+ - error: Swifty 发生错误时触发
70
+ - compact: 上下文压缩时触发
71
+ - permission_request: 权限请求时触发
72
+ - file_change: 文件被修改时触发
73
+ - command_execute: Slash Command 执行时触发
74
+
75
+ ## pre_tool_use
76
+
77
+ ```yaml
78
+ hooks:
79
+ - event: pre_tool_use
80
+ condition: 'tool == "WriteFile" && path == "pnpm-lock.yaml"'
81
+ action:
82
+ type: command
83
+ command: "echo 'Never manually edit pnpm-lock.yaml'"
84
+ reject: true
85
+
86
+ - event: pre_tool_use
87
+ condition: 'tool == "Bash" && command =~ "rm\\s+-rf\\s+\\/"'
88
+ action:
89
+ type: command
90
+ command: "echo 'Dangerous rm command detected'"
91
+ reject: true
92
+ ```
93
+
94
+ <!-- 源码: src/hooks/hooks.ts -->
95
+
96
+ reject: true 是 pre_tool_use 事件的特殊字段, reject: true 时, 工具调用被拒绝, CLI 将 hook 抛出的错误 reason 作为工具调用结果 `{ content: reason, isError: true }` 返回给 LLM, LLM 调整策略
97
+
98
+ pre_tool_use 事件的动作默认同步执行 (`async: false`)、阻塞等待返回值, 检查是否拒绝
99
+
100
+ <!-- 源码: src/hooks/hooks.ts reject 仅在 pre_tool_use 事件中断循环 -->
101
+
102
+ ## 条件
103
+
104
+ <!-- 源码: src/hooks/hooks.ts evaluateSingleCondition -->
105
+
106
+ - == 等于, equal match
107
+ - != 不等于, not equal match
108
+ - =~ 正则匹配, regex match, 语法: `变量 =~ "正则表达式"`
109
+ - =\* glob (filesystem 通配符语法) 匹配, glob match
110
+ - 支持 && || 组合多个条件
111
+
112
+ 变量名:
113
+
114
+ - tool: 匹配 ctx.toolName
115
+ - event: 匹配 ctx.event
116
+ - file_path 匹配 ctx.filePath
117
+ - message 匹配 ctx.message
118
+ - 其他变量名在工具参数字典 ctx.args 中按 key 查找, 例如 path 匹配 args.path, command 匹配 args.command
119
+
120
+ <!-- 源码: src/hooks/hooks.ts getContextValue -->
121
+
122
+ ## 动作
123
+
124
+ <!-- 源码: src/hooks/hooks.ts executeAction -->
125
+
126
+ 四种动作执行器
127
+
128
+ - command: 执行 shell 命令
129
+ - prompt: 注入 system prompt
130
+ - http: 发送 http 请求
131
+ - agent: 启动 subagent
132
+
133
+ ### command
134
+
135
+ ```yaml
136
+ action:
137
+ type: command
138
+ command: "prettier -w $SWIFTY_FILE_PATH"
139
+ ```
140
+
141
+ <!-- 源码: src/hooks/hooks.ts executeAction command 分支 -->
142
+
143
+ - 执行 shell 命令, shell 命令执行前会设置环境变量 SWIFTY_EVENT, SWIFTY_TOOL, SWIFTY_FILE_PATH
144
+ - 启动一个 shell 子进程执行命令, 拿到输出和退出码
145
+ - timeout 字段指定命令超时时间
146
+
147
+ <!-- 源码: src/hooks/hooks.ts timeout 硬编码 30000ms, HookConfigSchema 没有 timeout 字段 -->
148
+
149
+ ### prompt
150
+
151
+ <!-- 源码: src/hooks/hooks.ts executeAction prompt 分支 -->
152
+
153
+ ```yaml
154
+ action:
155
+ type: prompt
156
+ prompt: 先阅读 ARCHITECTURE.md 了解项目架构, 再开始工作
157
+ ```
158
+
159
+ - 使用 `<hook-notification />` 标签包裹, 注入到 system prompt, LLM 在下一轮 Agent Loop turn 请求中可以读到
160
+ - 可以只在指定项目中注入, 或者只在第一轮对话中注入 (使用执行控制 `once: true`)
161
+
162
+ ### http
163
+
164
+ <!-- 源码: src/hooks/hooks.ts executeAction http 分支 -->
165
+
166
+ ```yaml
167
+ action:
168
+ type: http
169
+ url: https://api.example.com
170
+ method: POST
171
+ ```
172
+
173
+ <!-- 源码: src/hooks/hooks.ts http 请求体固定为 JSON.stringify(context), HookConfigSchema 没有 body 字段 -->
174
+
175
+ 发送 http 请求, 请求体是 HookContext 的 json 字符串 (包含 event, toolName, args, filePath, message 字段), 场景有: 发送 App 通知、日志收集、监控报警
176
+
177
+ ### agent
178
+
179
+ ```yaml
180
+ action:
181
+ type: agent
182
+ prompt: "review 刚刚写入的 $SWIFTY_FILE_PATH 是否有安全漏洞"
183
+ ```
184
+
185
+ <!-- 源码: src/hooks/hooks.ts executeAction agent 分支 -->
186
+
187
+ 启动 subagent, 见 ch13
188
+
189
+ ## hook 执行控制
190
+
191
+ - once: 只执行一次
192
+ - async: 异步执行, 注意: pre_tool_use 事件的动作默认同步执行 (`async: false`)、阻塞等待返回值, 检查是否拒绝
193
+ - on_error: hook 执行错误时的行为, 枚举值:
194
+ - ignore: 默认, 忽略错误
195
+ - fail 标记 hook 执行错误
196
+ - reject 拒绝工具调用
197
+ - 错误捕获: hook 执行错误不能中断 Agent
198
+
199
+ <!-- 源码: src/hooks/hooks.ts on_error 处理逻辑 -->
200
+
201
+ ```yaml
202
+ hooks:
203
+ - event: session_start
204
+ action:
205
+ type: prompt
206
+ prompt: "使用英文注释"
207
+ once: true
208
+
209
+ - event: post_tool_use
210
+ condition: tool == "WriteFile"
211
+ action:
212
+ type: http
213
+ url: https://api.example.com
214
+ method: POST
215
+ async: true
216
+ ```
217
+
218
+ ## 上下文变量
219
+
220
+ 事件触发时, 创建一个 HookContext 对象, 包含事件的上下文信息:
221
+
222
+ <!-- 源码: src/hooks/hooks.ts HookContext 接口定义 -->
223
+
224
+ HookContext 属性:
225
+
226
+ - event 事件名 $SWIFTY_EVENT
227
+ - toolName 工具名 $SWIFTY_TOOL
228
+ - args 工具参数字典
229
+ - filePath 文件路径 $SWIFTY_FILE_PATH
230
+ - message 消息内容
231
+
232
+ <!-- 源码: src/hooks/hooks.ts 暴露 SWIFTY_EVENT, SWIFTY_TOOL, SWIFTY_FILE_PATH 环境变量 -->
233
+
234
+ ## 示例配置
235
+
236
+ ```yaml
237
+ hooks:
238
+ - id: auto-format
239
+ event: post_tool_use
240
+ condition: 'tool == "WriteFile" && path =* "*.ts"' # glob match
241
+ action:
242
+ type: command
243
+ command: "prettier -w $SWIFTY_FILE_PATH"
244
+
245
+ - id: readonly-vendor
246
+ event: pre_tool_use
247
+ condition: 'tool == "WriteFile" && path =* "node_modules/*"'
248
+ action:
249
+ type: command
250
+ command: "echo 'Never manually edit node_modules/*'"
251
+ reject: true
252
+
253
+ - id: project-context
254
+ event: session_start
255
+ action:
256
+ type: prompt
257
+ prompt: |
258
+ - Tech Stack: TypeScript, React, Ink, Zod, Vitest, ESLint, oxfmt, pnpm workspace deps: `@swifty.js/glob-addon`, `@swifty.js/glob-wasm`.
259
+ - Code Conventions: Enforced by `eslint.config.js`.
260
+ - Reference: Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
261
+ once: true
262
+
263
+ - id: block-dangerous-rm
264
+ event: pre_tool_use
265
+ condition: 'tool == "Bash" && command =~ "rm\\s+-rf\\s+\\/"'
266
+ action:
267
+ type: command
268
+ command: "echo 'Dangerous rm command detected'"
269
+ reject: true
270
+
271
+ - id: app-notify
272
+ event: post_tool_use
273
+ condition: 'tool == "WriteFile"'
274
+ action:
275
+ type: http
276
+ url: https://api.example.com
277
+ method: POST
278
+ async: true
279
+ ```
280
+
281
+ ## 配置校验
282
+
283
+ - reject 字段只能用于 pre_tool_use 事件
284
+ - async 字段不能用于 pre_tool_use 事件
285
+ - command 动作必须有 command 字段
286
+ - prompt 动作必须有 prompt 字段
287
+ - http 动作必须有 url 字段
288
+ - agent 动作必须有 prompt 字段或 command 字段