@wenaixi/dsh-ponytail 4.9.0 → 4.10.0-dsh.1
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/AGENTS.md +32 -32
- package/CHANGELOG.md +83 -5
- package/LICENSE +21 -21
- package/README.md +84 -82
- package/assets/benchmark-3model.svg +21 -21
- package/assets/benchmark-agentic.svg +62 -62
- package/assets/logo-dark.png +0 -0
- package/assets/logo-dark.svg +115 -115
- package/assets/logo-greenpt-dark.svg +27 -27
- package/assets/logo-greenpt.svg +27 -27
- package/assets/logo.png +0 -0
- package/cordis.patch.yml +1 -0
- package/lib/ponytail-commands.d.ts +32 -0
- package/lib/ponytail-commands.d.ts.map +1 -0
- package/lib/ponytail-commands.js +69 -0
- package/lib/ponytail-commands.js.map +1 -0
- package/lib/ponytail-config.d.ts +12 -3
- package/lib/ponytail-config.d.ts.map +1 -1
- package/lib/ponytail-config.js +3 -35
- package/lib/ponytail-config.js.map +1 -1
- package/lib/ponytail-instructions.d.ts +7 -1
- package/lib/ponytail-instructions.d.ts.map +1 -1
- package/lib/ponytail-instructions.js +21 -19
- package/lib/ponytail-instructions.js.map +1 -1
- package/lib/ponytail-runtime.d.ts +3 -5
- package/lib/ponytail-runtime.d.ts.map +1 -1
- package/lib/ponytail-runtime.js +12 -13
- package/lib/ponytail-runtime.js.map +1 -1
- package/lib/ponytail-skills.d.ts +27 -0
- package/lib/ponytail-skills.d.ts.map +1 -0
- package/lib/ponytail-skills.js +254 -0
- package/lib/ponytail-skills.js.map +1 -0
- package/lib/ponytail-state.d.ts +25 -0
- package/lib/ponytail-state.d.ts.map +1 -0
- package/lib/ponytail-state.js +63 -0
- package/lib/ponytail-state.js.map +1 -0
- package/lib/ponytail.d.ts +6 -55
- package/lib/ponytail.d.ts.map +1 -1
- package/lib/ponytail.js +96 -412
- package/lib/ponytail.js.map +1 -1
- package/package.json +11 -11
- package/README.en.md +0 -100
package/AGENTS.md
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
# Ponytail, lazy senior dev mode
|
|
2
|
-
|
|
3
|
-
You are a lazy senior developer. Lazy means efficient, not careless. The best code is the code never written.
|
|
4
|
-
|
|
5
|
-
Before writing any code, stop at the first rung that holds:
|
|
6
|
-
|
|
7
|
-
1. Does this need to be built at all? (YAGNI)
|
|
8
|
-
2. Does it already exist in this codebase? Reuse the helper, util, or pattern that's already here, don't re-write it.
|
|
9
|
-
3. Does the standard library already do this? Use it.
|
|
10
|
-
4. Does a native platform feature cover it? Use it.
|
|
11
|
-
5. Does an already-installed dependency solve it? Use it.
|
|
12
|
-
6. Can this be one line? Make it one line.
|
|
13
|
-
7. Only then: write the minimum code that works.
|
|
14
|
-
|
|
15
|
-
The ladder runs after you understand the problem, not instead of it: read the task and the code it touches, trace the real flow end to end, then climb.
|
|
16
|
-
|
|
17
|
-
Bug fix = root cause, not symptom: a report names a symptom. Grep every caller of the function you touch and fix the shared function once — one guard there is a smaller diff than one per caller, and patching only the path the ticket names leaves a sibling caller still broken.
|
|
18
|
-
|
|
19
|
-
Rules:
|
|
20
|
-
|
|
21
|
-
- No abstractions that weren't explicitly requested.
|
|
22
|
-
- No new dependency if it can be avoided.
|
|
23
|
-
- No boilerplate nobody asked for.
|
|
24
|
-
- Deletion over addition. Boring over clever. Fewest files possible.
|
|
25
|
-
- Shortest working diff wins, but only once you understand the problem. The smallest change in the wrong place isn't lazy, it's a second bug.
|
|
26
|
-
- Question complex requests: "Do you actually need X, or does Y cover it?"
|
|
27
|
-
- Pick the edge-case-correct option when two stdlib approaches are the same size, lazy means less code, not the flimsier algorithm.
|
|
28
|
-
- Mark deliberate simplifications that cut a real corner with a known ceiling (global lock, O(n²) scan, naive heuristic) with a `ponytail:` comment naming the ceiling and upgrade path.
|
|
29
|
-
|
|
30
|
-
Not lazy about: understanding the problem (read it fully and trace the real flow before picking a rung, a small diff you don't understand is just laziness dressed up as efficiency), input validation at trust boundaries, error handling that prevents data loss, security, accessibility, the calibration real hardware needs (the platform is never the spec ideal, a clock drifts, a sensor reads off), anything explicitly requested. Lazy code without its check is unfinished: non-trivial logic leaves ONE runnable check behind, the smallest thing that fails if the logic breaks (an assert-based demo/self-check or one small test file; no frameworks, no fixtures). Trivial one-liners need no test.
|
|
31
|
-
|
|
32
|
-
(Yes, this file also applies to agents working on the ponytail repo itself. Especially to them.)
|
|
1
|
+
# Ponytail, lazy senior dev mode
|
|
2
|
+
|
|
3
|
+
You are a lazy senior developer. Lazy means efficient, not careless. The best code is the code never written.
|
|
4
|
+
|
|
5
|
+
Before writing any code, stop at the first rung that holds:
|
|
6
|
+
|
|
7
|
+
1. Does this need to be built at all? (YAGNI)
|
|
8
|
+
2. Does it already exist in this codebase? Reuse the helper, util, or pattern that's already here, don't re-write it.
|
|
9
|
+
3. Does the standard library already do this? Use it.
|
|
10
|
+
4. Does a native platform feature cover it? Use it.
|
|
11
|
+
5. Does an already-installed dependency solve it? Use it.
|
|
12
|
+
6. Can this be one line? Make it one line.
|
|
13
|
+
7. Only then: write the minimum code that works.
|
|
14
|
+
|
|
15
|
+
The ladder runs after you understand the problem, not instead of it: read the task and the code it touches, trace the real flow end to end, then climb.
|
|
16
|
+
|
|
17
|
+
Bug fix = root cause, not symptom: a report names a symptom. Grep every caller of the function you touch and fix the shared function once — one guard there is a smaller diff than one per caller, and patching only the path the ticket names leaves a sibling caller still broken.
|
|
18
|
+
|
|
19
|
+
Rules:
|
|
20
|
+
|
|
21
|
+
- No abstractions that weren't explicitly requested.
|
|
22
|
+
- No new dependency if it can be avoided.
|
|
23
|
+
- No boilerplate nobody asked for.
|
|
24
|
+
- Deletion over addition. Boring over clever. Fewest files possible.
|
|
25
|
+
- Shortest working diff wins, but only once you understand the problem. The smallest change in the wrong place isn't lazy, it's a second bug.
|
|
26
|
+
- Question complex requests: "Do you actually need X, or does Y cover it?"
|
|
27
|
+
- Pick the edge-case-correct option when two stdlib approaches are the same size, lazy means less code, not the flimsier algorithm.
|
|
28
|
+
- Mark deliberate simplifications that cut a real corner with a known ceiling (global lock, O(n²) scan, naive heuristic) with a `ponytail:` comment naming the ceiling and upgrade path.
|
|
29
|
+
|
|
30
|
+
Not lazy about: understanding the problem (read it fully and trace the real flow before picking a rung, a small diff you don't understand is just laziness dressed up as efficiency), input validation at trust boundaries, error handling that prevents data loss, security, accessibility, the calibration real hardware needs (the platform is never the spec ideal, a clock drifts, a sensor reads off), anything explicitly requested. Lazy code without its check is unfinished: non-trivial logic leaves ONE runnable check behind, the smallest thing that fails if the logic breaks (an assert-based demo/self-check or one small test file; no frameworks, no fixtures). Trivial one-liners need no test.
|
|
31
|
+
|
|
32
|
+
(Yes, this file also applies to agents working on the ponytail repo itself. Especially to them.)
|
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,84 @@
|
|
|
2
2
|
|
|
3
3
|
所有重要变更记录于此,格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),版本号与上游 [DietrichGebert/ponytail](https://github.com/DietrichGebert/ponytail) 同步。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
> **版本策略**:每个版本固定带 `-dsh.N` 后缀(如 `4.10.0-dsh.0`、`4.10.0-dsh.1`),初始即 `-dsh.0`。上游发新版时重置为新上游版本的 `-dsh.0`(如 `4.10.0-dsh.0`),用 `pnpm run bump:dsh -- 4.10.0`。递增:`pnpm run bump:dsh`。遵循 SemVer 预发布语义。
|
|
6
|
+
|
|
7
|
+
## [4.10.0-dsh.1] - 2026-10-01
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- 架构深化(6 候选核实后自决 4 实施 2 否决):
|
|
11
|
+
- 等级状态收敛为深模块 `src/ponytail-state.ts`(`createPonytailState`:`get()/set(mode)/syncFromFile()` 三方法,闭包实例随 HMR 重建);`ponytail.ts` 的 `currentMode` 散落读写(apply 启动、section text 漂移修正、handlePromptText 四处双写、agent/created 镜像)全部收口——`set()` 内存赢原子落盘、`syncFromFile()` 文件赢纠偏(行为与 4.10.0-dsh.0 逐行等价)
|
|
12
|
+
- 指令渲染收敛为深模块 `render(skillDir, mode)`(`src/ponytail-instructions.ts`):`systemPrompt` section 回调只留 off 短路 + 一次调用,review 短路指针 + 裁剪 + 失败回退收进模块内,对齐上游 `getPonytailInstructions(mode)` 单一出口形态(行为无可观察变更)
|
|
13
|
+
- 抽取 SkillProvider 子系统为 `src/ponytail-skills.ts`(frontmatter 解析 + 目录扫描 + `PonytailProvider.list/get`,DSH 专属层);`Config` 接口迁 `src/ponytail-config.ts` 为 `PonytailConfig` 共享类型(entry 侧 `export type Config = PonytailConfig` 别名),消除模块循环依赖;不发明工厂——直接导出同名类,注册保持一行 `new PonytailProvider(ctx, control, { providerName, skillDir })`;`src/ponytail.ts` 由 533 行降至 272 行,只留生命周期与监听器(行为无可观察变更)
|
|
14
|
+
- frontmatter 读取瘦身:`stringField/optionalString/optionalMetadata` 折叠为 `readString/readObject/readMetadata`(调用点零改动,净减行数)
|
|
15
|
+
- `apply` 删不可达死代码 `?? null`(分支守卫已排除 undefined);`?? 'full'` 保留并改为 `DEFAULT_MODE` 常量(DSH 测试路径直接 `apply(ctx)` 真实依赖该兜底)
|
|
16
|
+
- `PONYTAIL_DEFAULT_MODE` env 非法值由静默回退改为 `ctx.logger.warn`(DSH 差分,不改变回退语义,上游仍静默);config 文件非法值保持静默(与上游一致)
|
|
17
|
+
- `scripts/verify.mjs` 静态检查(不注册 tool)由单文件 `src/ponytail.ts` 扩为整个 `src/` 目录扫描(新模块 `ponytail-state.ts` 纳入覆盖)
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- `scripts/behavior.test.mjs` 新增 2 例:`/ponytail default lite` 持久化、`/ponytail default foobar` 非法默认不切换(instruction 解析用例 6 → 8,总数 9 → 11)
|
|
21
|
+
|
|
22
|
+
### Cleaned
|
|
23
|
+
- `src/ponytail.ts` 由 563 行减至 ~533 行;`handlePromptText` 四处 `setMode/clearMode` 成对 try/catch 由 `state.set()` 原子化吸收
|
|
24
|
+
|
|
25
|
+
## [4.10.0-dsh.0] - 2026-10-01
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- `agent/session-start` 死监听迁移为 `agent/created`:`payload.source` 为 `startup`/`resume` 时对齐 flag 文件(`off` 清空、其余 `setMode(currentMode)`),`clear`/`compact` 不动作——DSH 官方事件模型不存在 `session-start`,原监听永不被触发
|
|
29
|
+
- 对齐官方 defensive-patterns:`agent/created` 与 `session/event` 监听整体 `try/catch`,坏订阅者不再断链
|
|
30
|
+
- `SkillProvider.list()`/`get()` 尊重 `options.signal`:已 abort 时立即抛 `AbortError` 快速 settle(放弃对 fs 调用的 signal 透传)
|
|
31
|
+
- `SkillProvider.list()` ENOENT/ENOTDIR 由记 `warn` 返回 `[]` 改为显式 `{ candidates: [], complete: false }`(显式「发现未完成、不可缓存」)与警告并行
|
|
32
|
+
- `scripts/verify.mjs` description 长度断言改用 `yaml.parse` 按 YAML 折叠块真实语义计算并输出真实长度;实测六个技能均 ≤500(262/162/165/161/104/165),无需压缩
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
- 版本重置:与上游 v4.10.0 对齐由 `4.9.0-dsh.5` 重置为 `4.10.0-dsh.0`(`pnpm run bump:dsh -- 4.10.0`)
|
|
36
|
+
- peer/devDeps 对齐 DSH 0.2.0-rc.2 运行时:`@deepseek-ai/cordis ^4.0.4`、`@deepseek-ai/dsh-skill >=0.1.0-rc.1 <0.2.0-0 || >=0.2.0-rc.1 <0.3.0-0`、`@deepseek-ai/schemastery ^3.18.4`
|
|
37
|
+
- 指令解析核心由 `handlePromptText` 抽取为纯函数 `parsePonytailCommand`(新增 `src/ponytail-commands.ts`),行为等价,可脱离插件单测;`@`/`$` 前缀与 `/ponytail:ponytail`/`/ponytail:ponytail-review` 前缀形式对齐上游 4.10.0 mode-tracker
|
|
38
|
+
- **未知参数语义对齐上游 else 兜底**:`/ponytail <unknown>` 由 `warn` 不切换(4.9.0-dsh.5 引入的 DSH 差分,上游无此分支)改回静默切到默认等级——与上游 mode-tracker 逐分支一致(含默认 `off` 时落入关闭分支)
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
- `scripts/behavior.test.mjs` 最小行为测试(node 内置 test runner):指令解析 6 例 + 技能裁剪 2 例 + abort 立即 settle 1 例,9/9 通过
|
|
42
|
+
- `scripts/verify.mjs` 新增六个 SKILL.md `description` ≤500 静态断言(官方 `catalogDescriptionMaxLength` 默认截断线,防回归)
|
|
43
|
+
|
|
44
|
+
### Cleaned
|
|
45
|
+
- 删除无消费方的 `Config.hideStatus`/`Config.quietStartup` 配置项与 `getHideStatus`/`getQuietStartup`/`getPonytailInstructions`/`writeHookOutput`/`getStatePath` 死代码;删除 `normalizePersistedMode` 孤儿导出(README 与 CHANGELOG 无对应表述,无需同步清理)
|
|
46
|
+
|
|
47
|
+
## [4.9.0-dsh.5] - 2026-08-22
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
- `isDeactivationCommand` 去除正则重复 `\s`,保持 spec `.!?。!?` + 空白
|
|
51
|
+
- `ponytail-runtime` 平台识别由模块级常量改为函数 `isCopilot()/isCodex()/isQoder()`,修复同进程 env 变更后路径漂移
|
|
52
|
+
- `/ponytail <unknown>` 未知参数由静默切默认改为 `warn` 不切换
|
|
53
|
+
- `extractText` 双路径提取收敛为 `extractTextFromContent` 复用,消除 `agent/pre-step` 与 `session/event` 重复分支
|
|
54
|
+
- `scripts/verify.mjs` 不注册 tool 检查由 `|| / &&` 误优先级改为单正则 `\btools\s*\.\s*register\b|\bdefineTool\b`
|
|
55
|
+
- `publish.yml` 幂等:`VERSION` 检查提到 `if/else` 前共享,`workflow_dispatch` 计入 `if`,`Create Release` 受 `skipped` 守卫
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
- `CLAUDE.md` Flag 描述由“三分支”更正为“四路”;验证命令 `--filter` 补 `scope`
|
|
59
|
+
|
|
60
|
+
## [4.9.0-dsh.4] - 2026-08-22
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
- `CLAUDE.md` 改为本地记忆,不再入仓:加入 `.gitignore`,历史提交中移除该文件,本次构建产物与 `CHANGELOG`/`README` 同步到新版本
|
|
64
|
+
- README 安装示例移除括号补充说明,保持示例纯净
|
|
65
|
+
|
|
66
|
+
## [4.9.0-dsh.3] - 2026-08-22
|
|
67
|
+
|
|
68
|
+
### Fixed
|
|
69
|
+
- `Config` 声明顺序修正为 `interface` 在前、`Schema` 在后,符合 `references/config.md` 的 Schemastery 规范写法(此前 `const` 在前会导致类型声明顺序与上游 `dsh-superpower` 不一致)
|
|
70
|
+
- `isDeactivationCommand` 补全中文全句匹配 `退出 ponytail` / `正常模式`,并兼容中文标点 `。!?`
|
|
71
|
+
|
|
72
|
+
## [4.9.0-dsh.2] - 2026-08-22
|
|
73
|
+
|
|
74
|
+
### Changed
|
|
75
|
+
- README 去版本化:徽章与正文不再写死 `4.9.0-dsh.x`,默认装 `latest`;`logo-dark` 同步为新 logo 相对路径
|
|
76
|
+
|
|
77
|
+
## [4.9.0-dsh.1] - 2026-08-22
|
|
78
|
+
|
|
79
|
+
### Changed
|
|
80
|
+
- 替换 `assets/logo.png` 为用户指定新 Logo(692KB),原文件已移除;README 徽章同步至 `4.9.0-dsh.1`
|
|
81
|
+
|
|
82
|
+
## [4.9.0-dsh.0] - 2026-08-21
|
|
6
83
|
|
|
7
84
|
### Added
|
|
8
85
|
- 完整移植上游 v4.9.0:6 个 Skill(`ponytail` / `ponytail-review` / `ponytail-audit` / `ponytail-debt` / `ponytail-gain` / `ponytail-help`)
|
|
@@ -10,17 +87,18 @@
|
|
|
10
87
|
- 复刻 hooks 行为:`activate` / `mode-tracker` / `subagent` / `config` / `instructions` / `runtime`
|
|
11
88
|
- `PONYTAIL_DEFAULT_MODE` env > cordis 配置 > 配置文件 > `full` 三级回退,`review` 不可作默认,BOM 处理、`isShellSafe`、`isDeactivationCommand` 全量对齐
|
|
12
89
|
- 中文化:6 个 Skill 的 `description` 与正文、fallback 指令、hook 日志、systemPrompt 注入文本全部中文,触发词兼容中英文
|
|
13
|
-
- `CLAUDE.md` 核心记忆库、`LICENSE`(MIT)、`README.md
|
|
90
|
+
- `CLAUDE.md` 核心记忆库、`LICENSE`(MIT)、`README.md`、`CHANGELOG.md`、`assets/`、`AGENTS.md`
|
|
14
91
|
|
|
15
92
|
### Changed
|
|
16
|
-
- DSH 形态:单一插件包
|
|
93
|
+
- DSH 形态:单一插件包 `@wenaixi/dsh-ponytail`,`dsh.bundle.patch = ./cordis.patch.yml`,包名引用挂载
|
|
17
94
|
- 注入文本中文化:`PONYTAIL 已激活 — 等级:…`,`hook` 日志全中文
|
|
95
|
+
- 版本统一带 `-dsh.N` 后缀,初始 `4.9.0-dsh.0`
|
|
18
96
|
|
|
19
97
|
### Fixed
|
|
20
98
|
- `initialMode` 优先级修正为 `env > cordis 显式 config > 文件 > full`,与上游语义一致
|
|
21
99
|
|
|
22
100
|
### Notes
|
|
23
|
-
-
|
|
101
|
+
- 不注册 tool(不在 `ctx.tools` 注册任何占位),`scripts/verify.mjs` 校验
|
|
24
102
|
- 构建:`pnpm build` (`tsc -p tsconfig.build.json`),`pnpm typecheck`,`pnpm verify`
|
|
25
103
|
|
|
26
|
-
[4.9.0]: https://github.com/Wenaixi/dsh-ponytail/releases/tag/v4.9.0
|
|
104
|
+
[4.9.0-dsh.0]: https://github.com/Wenaixi/dsh-ponytail/releases/tag/v4.9.0-dsh.0
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 DietrichGebert
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DietrichGebert
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,139 +1,141 @@
|
|
|
1
1
|
# dsh-ponytail
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<
|
|
4
|
+
<picture>
|
|
5
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/logo-dark.png">
|
|
6
|
+
<img src="assets/logo.png" width="180" alt="Ponytail" />
|
|
7
|
+
</picture>
|
|
5
8
|
</p>
|
|
6
9
|
|
|
7
10
|
<p align="center">
|
|
8
|
-
<strong>懒惰的资深工程师 ·
|
|
11
|
+
<strong>懒惰的资深工程师 · 移植到 DeepSeek Harness</strong><br/>
|
|
9
12
|
<em>The best code is the code you never wrote.</em>
|
|
10
13
|
</p>
|
|
11
14
|
|
|
12
15
|
<p align="center">
|
|
13
16
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-111111?style=flat-square" alt="MIT"/></a>
|
|
14
|
-
<a href="https://www.npmjs.com/package/dsh-ponytail"><img src="https://img.shields.io/npm/v/dsh-ponytail?color=111111&style=flat-square" alt="npm"/></a>
|
|
15
|
-
<img src="https://img.shields.io/badge/
|
|
16
|
-
<img src="https://img.shields.io/badge/DSH-0.1.1--rc.2-333333?style=flat-square" alt="DSH"/>
|
|
17
|
+
<a href="https://www.npmjs.com/package/@wenaixi/dsh-ponytail"><img src="https://img.shields.io/npm/v/@wenaixi/dsh-ponytail?color=111111&style=flat-square" alt="npm"/></a>
|
|
18
|
+
<img src="https://img.shields.io/badge/DSH-333333?style=flat-square" alt="DSH"/>
|
|
17
19
|
<img src="https://img.shields.io/badge/skills-6-008080?style=flat-square" alt="skills"/>
|
|
18
|
-
<img src="https://img.shields.io/badge/node-%3E%3D18-3C873A?style=flat-square" alt="node"/>
|
|
19
20
|
</p>
|
|
20
21
|
|
|
21
22
|
<p align="center">
|
|
22
|
-
<a href="
|
|
23
|
+
<a href="CHANGELOG.md">Changelog</a> · <a href="https://github.com/DietrichGebert/ponytail">上游</a> · <a href="https://www.npmjs.com/package/@wenaixi/dsh-ponytail">npm</a>
|
|
23
24
|
</p>
|
|
24
25
|
|
|
25
26
|
---
|
|
26
27
|
|
|
27
|
-
> [DietrichGebert/ponytail](https://github.com/DietrichGebert/ponytail)
|
|
28
|
-
## �?特�?
|
|
29
|
-
- **Always-on 梯子**�? 阶决策(YAGNI �?复用 �?标准�?�?平台原生 �?已有依赖 �?一�?�?最小实现),每轮模型请求前自动注入,`off` 时静默�?- **6 个中�?Skill**:`ponytail` / `ponytail-review` / `ponytail-audit` / `ponytail-debt` / `ponytail-gain` / `ponytail-help`,`rank: 550` 可被项目级覆盖�?- **三档强度**:`lite`(点出更懒的替代�?/ `full`(默认,强制走梯子) / `ultra`(先删后加,挑战需求本身)�?- **上游行为全量复刻**:`PONYTAIL_DEFAULT_MODE` env > cordis 配置 > 配置文件 > `full`;`review` 不可作默认;`stop ponytail` 全句匹配;`ponytail:` 债务台账;`PONYTAIL_SUBAGENT_MATCHER` 过滤�?- **无空 tool**:不�?`ctx.tools` 注册任何占位 tool,全部能力经 `ctx.skills` 暴露�?
|
|
30
|
-
## 📦 包含能力
|
|
28
|
+
> [DietrichGebert/ponytail](https://github.com/DietrichGebert/ponytail) 的 DSH 完整移植版 — always-on 注入 + 6 个中文 Skill,无 tool 注册。
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|---|---|---|
|
|
34
|
-
| `ponytail` | skill + always-on | 7 级梯子,lite/full/ultra 三档,触发词兼容中英�?|
|
|
35
|
-
| `ponytail-review` | skill | diff 过度设计评审,`delete/stdlib/native/yagni/shrink` |
|
|
36
|
-
| `ponytail-audit` | skill | 全仓审计,按可删收益排序 |
|
|
37
|
-
| `ponytail-debt` | skill | 收割 `ponytail:` 注释台账 |
|
|
38
|
-
| `ponytail-gain` | skill | 收益看板(benchmark 中位数) |
|
|
39
|
-
| `ponytail-help` | skill | 速查�?|
|
|
40
|
-
| 模式切换 | hook | `agent/pre-step` + `session/event` 双路径,/ponytail 族指�?|
|
|
41
|
-
| 持久�?| 文件 | `~/.config/ponytail/config.json`(Win: `%APPDATA%`),`/ponytail default <mode>` 写入 |
|
|
30
|
+
## 🚀 安装(默认装到 `web`)
|
|
42
31
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
### 方式一:npm(推荐,发布后可用)
|
|
32
|
+
> `web` 即日常的 DSH Web UI(3080)。示例中的 `demo` 可换成任意 `--profile <name>`。
|
|
46
33
|
|
|
47
34
|
```bash
|
|
48
|
-
#
|
|
35
|
+
# 方式一:npm
|
|
36
|
+
dsh plugin --profile web add @wenaixi/dsh-ponytail
|
|
49
37
|
|
|
50
|
-
#
|
|
51
|
-
|
|
52
|
-
pnpm add @wenaixi/dsh-ponytail
|
|
53
|
-
```
|
|
38
|
+
# 方式二:GitHub 直装(lib 已提交,无需构建)
|
|
39
|
+
dsh plugin --profile web add github:Wenaixi/dsh-ponytail
|
|
54
40
|
|
|
55
|
-
|
|
56
|
-
|
|
41
|
+
# 方式三:本地 / tarball
|
|
42
|
+
pnpm install && pnpm build && dsh plugin --profile web add ./
|
|
43
|
+
pnpm pack && dsh plugin --profile web add ./wenaixi-dsh-ponytail-*.tgz
|
|
57
44
|
|
|
58
|
-
|
|
59
|
-
dsh plugin --profile
|
|
60
|
-
|
|
61
|
-
|
|
45
|
+
# 卸载
|
|
46
|
+
dsh plugin --profile web remove @wenaixi/dsh-ponytail
|
|
47
|
+
|
|
48
|
+
# 验证
|
|
49
|
+
dsh --profile web --dump-config | grep -A2 ponytail
|
|
62
50
|
```
|
|
63
51
|
|
|
64
|
-
|
|
52
|
+
> `WARN missing peer @deepseek-ai/...` 属正常,peer 由 DSH 运行时提供;`Packages: +2 Done` 即成功。
|
|
65
53
|
|
|
66
|
-
|
|
67
|
-
pnpm install && pnpm build
|
|
68
|
-
dsh plugin --profile demo add ./
|
|
69
|
-
dsh --profile demo --dump-config # 应能看到 ponytail �?
|
|
70
|
-
# �?pnpm pack && dsh plugin --profile demo add ./dsh-ponytail-4.9.0.tgz
|
|
71
|
-
```
|
|
54
|
+
## ✨ 特性
|
|
72
55
|
|
|
73
|
-
|
|
56
|
+
- **梯子 7 阶**:YAGNI → 复用 → 标准库 → 平台原生 → 已有依赖 → 一行 → 最小实现,`off` 时静默
|
|
57
|
+
- **6 个中文 Skill**(`rank: 550`):`ponytail` / `ponytail-review` / `ponytail-audit` / `ponytail-debt` / `ponytail-gain` / `ponytail-help`
|
|
58
|
+
- **三档强度**:`lite` / `full`(默认)/ `ultra`
|
|
59
|
+
- **无 tool 注册**:全部能力经 `ctx.skills` 暴露
|
|
60
|
+
|
|
61
|
+
| 能力 | 说明 |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `ponytail` | 7 阶梯子,触发词兼容中英文 |
|
|
64
|
+
| `ponytail-review` | diff 评审,`delete/stdlib/native/yagni/shrink` |
|
|
65
|
+
| `ponytail-audit` | 全仓审计,按可删收益排序 |
|
|
66
|
+
| `ponytail-debt` | 收割 `ponytail:` 注释 |
|
|
67
|
+
| `ponytail-gain` | 收益看板(benchmark 中位数) |
|
|
68
|
+
| `ponytail-help` | 速查卡 |
|
|
69
|
+
|
|
70
|
+
## 🪝 Hook 注入
|
|
71
|
+
|
|
72
|
+
> 上游 `hooks/` 的 6 个 Hook(config / instructions / runtime / activate / mode-tracker / subagent)已合并进单一 DSH 插件,无需宿主配置。
|
|
73
|
+
|
|
74
|
+
| 上游 | DSH 侧 |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `ponytail-config.js` | `src/ponytail-config.ts`(`env > 文件 > full`,`review` 不可默认) |
|
|
77
|
+
| `ponytail-instructions.js` | `src/ponytail-instructions.ts`(`getPonytailInstructions(mode)` → `render(skillDir, mode)`:review 短路 + 按档裁剪 + 中文 fallback) |
|
|
78
|
+
| `ponytail-runtime.js` | `src/ponytail-runtime.ts`(`.ponytail-active` + 四路识别) |
|
|
79
|
+
| `ponytail-activate.js` | `src/ponytail.ts` 的 `agent/created`(startup/resume 对齐 flag) |
|
|
80
|
+
| `ponytail-mode-tracker.js` | `src/ponytail-commands.ts` + `handlePromptText`(指令解析纯函数 + 副作用层) |
|
|
81
|
+
| `ponytail-subagent.js` | `src/ponytail.ts` 的 `agent/created`(`PONYTAIL_SUBAGENT_MATCHER`) |
|
|
74
82
|
|
|
75
|
-
```bash
|
|
76
|
-
dsh plugin --profile demo remove @wenaixi/dsh-ponytail
|
|
77
83
|
```
|
|
84
|
+
启动: env PONYTAIL_DEFAULT_MODE → cordis defaultMode → ~/.config/ponytail/config.json → full
|
|
85
|
+
→ setMode(flag 文件)
|
|
78
86
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
每轮请求前: agent/pre-step (waterfall, 必须 return next())
|
|
88
|
+
→ 解析 /ponytail 族 / stop ponytail → 切 currentMode + 写 flag → next()
|
|
89
|
+
|
|
90
|
+
注入: systemPrompt:section { name: ponytail, order: 50 }
|
|
91
|
+
order 50 在 persona(0) 之后、工具(100) 之前;回调只做 off 短路,渲染收敛于 render(skillDir, mode)
|
|
92
|
+
off → 空,review → 指向技能,读盘失败 → 中文 fallback,assembly 前以 readMode() 对齐
|
|
93
|
+
|
|
94
|
+
持久化: /ponytail default <mode> → 写入 config.json
|
|
95
|
+
子智能体: PONYTAIL_SUBAGENT_MATCHER 正则(非法 warn 回退),共享同一 section
|
|
96
|
+
HMR: 全部走 ctx,热重载逆序自动清理
|
|
85
97
|
```
|
|
86
98
|
|
|
99
|
+
`systemPrompt` 而非 `agent.inject`:落入日志可重建,`order: 50` 优先级高,每次 `assemble` 动态求值,`off` 零成本。Flag 兼容 `COPILOT_PLUGIN_DATA` / `PLUGIN_DATA` / `QODER_SESSION_ID` / `CLAUDE_PLUGIN_ROOT`。`isCopilot()/isCodex()/isQoder()` 每次读 env,常驻进程 env 变更即时生效。
|
|
100
|
+
|
|
87
101
|
## ⚙️ 配置
|
|
88
102
|
|
|
89
|
-
�?`cordis.patch.yml` 行内覆盖�?
|
|
90
103
|
```yaml
|
|
91
104
|
- insert:
|
|
92
105
|
- id: ponytail
|
|
93
|
-
name: dsh-ponytail
|
|
106
|
+
name: "@wenaixi/dsh-ponytail"
|
|
94
107
|
config:
|
|
95
108
|
providerName: ponytail
|
|
96
|
-
|
|
97
|
-
defaultMode: full # off|lite|full|ultra
|
|
109
|
+
defaultMode: full # off|lite|full|ultra
|
|
98
110
|
```
|
|
99
111
|
|
|
100
|
-
|
|
101
|
-
1. `PONYTAIL_DEFAULT_MODE=ultra` 环境变量(最高)
|
|
102
|
-
2. `cordis.patch.yml` 中显式的 `defaultMode`
|
|
103
|
-
3. `~/.config/ponytail/config.json`(Windows: `%APPDATA%\ponytail\config.json`,`XDG_CONFIG_HOME` 优先�?4. `full`
|
|
112
|
+
优先级:`PONYTAIL_DEFAULT_MODE` env > `cordis.defaultMode` > `~/.config/ponytail/config.json` > `full`。持久化:`/ponytail default <mode>` 写入文件,`review` 不可作默认。
|
|
104
113
|
|
|
105
|
-
持久化:`/ponytail default <mode>` 会写入上述配置文件;`review` 不可作默认�?
|
|
106
114
|
## 🎮 使用
|
|
107
115
|
|
|
108
|
-
-
|
|
109
|
-
-
|
|
110
|
-
-
|
|
111
|
-
|
|
116
|
+
- 自动生效,无需手动触发
|
|
117
|
+
- `/ponytail [lite|full|ultra|off]` / `/ponytail default <mode>` / `/ponytail-review` / `/ponytail-audit` / `/ponytail-debt` / `/ponytail-gain` / `/ponytail-help`
|
|
118
|
+
- 退出:`stop ponytail` / `normal mode`(整句)或 `/ponytail off`
|
|
119
|
+
|
|
120
|
+
## 🛠️ 开发
|
|
112
121
|
|
|
113
|
-
## 🛠�?本地开�?
|
|
114
122
|
```bash
|
|
115
|
-
pnpm typecheck
|
|
116
|
-
pnpm build
|
|
117
|
-
pnpm verify # 6 技�?+ 产物 + 无空 tool 校验
|
|
123
|
+
pnpm typecheck && pnpm build && node scripts/verify.mjs
|
|
118
124
|
dsh --profile web --patch ./cordis.patch.yml --dump-config
|
|
119
|
-
pnpm dsh web --patch ./cordis.patch.yml
|
|
125
|
+
pnpm dsh web --patch ./cordis.patch.yml # 热重载
|
|
126
|
+
# 发版:pnpm run bump:dsh && pnpm build && npm publish --provenance --access public
|
|
127
|
+
# 或 push tag v* 触发 GitHub Actions publish.yml
|
|
128
|
+
```
|
|
120
129
|
|
|
121
|
-
-
|
|
122
|
-
- 发布:`pnpm build && npm publish --access public --provenance`(需 npm Trusted Publisher 或带 2FA �?granular token�?
|
|
123
|
-
## 📂 目录
|
|
130
|
+
- 版本:与上游 SemVer 同步,每个版本固定带 `-dsh.N`(对齐上游 v4.10.0 重置为 `4.10.0-dsh.0`),`pnpm run bump:dsh` 递增,上游发新版时 `pnpm run bump:dsh -- <版本>` 重置
|
|
124
131
|
|
|
125
|
-
|
|
126
|
-
src/ponytail.ts # 唯一入口,name/inject/Config/apply
|
|
127
|
-
src/ponytail-config.ts # env > 文件 > full(含 BOM、review 限制、shell 白名单)
|
|
128
|
-
src/ponytail-instructions.ts # �?mode 裁剪 SKILL.md + 中文 fallback
|
|
129
|
-
src/ponytail-runtime.ts # flag 文件 + 多平台识别(Claude/Codex/Qoder�?skills/ponytail/*.md # 6 个中�?Skill
|
|
130
|
-
assets/ # logo / benchmark �?lib/ # 构建产物(已提交,支�?GitHub 直装�?cordis.patch.yml # - insert: { id: ponytail, name: dsh-ponytail }
|
|
131
|
-
```
|
|
132
|
+
---
|
|
132
133
|
|
|
133
134
|
## 🔗 上游
|
|
134
135
|
|
|
135
|
-
|
|
136
|
-
|
|
136
|
+
原仓 https://github.com/DietrichGebert/ponytail(MIT)
|
|
137
|
+
|
|
137
138
|
## 📄 许可
|
|
138
139
|
|
|
139
|
-
[MIT](LICENSE)
|
|
140
|
+
[MIT](LICENSE)
|
|
141
|
+
© DietrichGebert / Wenaixi
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
<svg viewBox="0 0 860 336" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system, 'Segoe UI', Helvetica, Arial, sans-serif">
|
|
2
|
-
<title>Median lines of code per arm across three models</title>
|
|
3
|
-
<text x="20" y="26" font-size="15" font-weight="600" fill="#8b949e">Median lines of code. 10 runs per cell. Lower is leaner.</text>
|
|
4
|
-
<text x="20" y="45" font-size="12" fill="#8b949e" opacity="0.85">Ponytail writes 80-94% less code, costs 42-75% less, and runs 3-6x faster than a no-skill agent.</text>
|
|
5
|
-
<rect x="20" y="58" width="12" height="12" rx="2" fill="#8b949e"/><text x="38" y="69" font-size="13" fill="#8b949e">baseline (no skill)</text>
|
|
6
|
-
<rect x="190" y="58" width="12" height="12" rx="2" fill="#d9822b"/><text x="208" y="69" font-size="13" fill="#8b949e">caveman</text>
|
|
7
|
-
<rect x="300" y="58" width="12" height="12" rx="2" fill="#2da44e"/><text x="318" y="69" font-size="13" fill="#8b949e">ponytail</text>
|
|
8
|
-
<text x="112" y="119" font-size="13" font-weight="600" fill="#8b949e" text-anchor="end">Haiku</text>
|
|
9
|
-
<rect x="120" y="92" width="508" height="14" rx="2" fill="#8b949e"/><text x="634" y="103" font-size="11" fill="#8b949e">518</text>
|
|
10
|
-
<rect x="120" y="110" width="114" height="14" rx="2" fill="#d9822b"/><text x="240" y="121" font-size="11" fill="#d9822b">116</text>
|
|
11
|
-
<rect x="120" y="128" width="38" height="14" rx="2" fill="#2da44e"/><text x="164" y="139" font-size="11" fill="#2da44e" font-weight="600">39</text>
|
|
12
|
-
<text x="112" y="193" font-size="13" font-weight="600" fill="#8b949e" text-anchor="end">Sonnet</text>
|
|
13
|
-
<rect x="120" y="166" width="680" height="14" rx="2" fill="#8b949e"/><text x="806" y="177" font-size="11" fill="#8b949e">693</text>
|
|
14
|
-
<rect x="120" y="184" width="118" height="14" rx="2" fill="#d9822b"/><text x="244" y="195" font-size="11" fill="#d9822b">120</text>
|
|
15
|
-
<rect x="120" y="202" width="43" height="14" rx="2" fill="#2da44e"/><text x="169" y="213" font-size="11" fill="#2da44e" font-weight="600">44</text>
|
|
16
|
-
<text x="112" y="267" font-size="13" font-weight="600" fill="#8b949e" text-anchor="end">Opus</text>
|
|
17
|
-
<rect x="120" y="240" width="251" height="14" rx="2" fill="#8b949e"/><text x="377" y="251" font-size="11" fill="#8b949e">256</text>
|
|
18
|
-
<rect x="120" y="258" width="66" height="14" rx="2" fill="#d9822b"/><text x="192" y="269" font-size="11" fill="#d9822b">67</text>
|
|
19
|
-
<rect x="120" y="276" width="50" height="14" rx="2" fill="#2da44e"/><text x="176" y="287" font-size="11" fill="#2da44e" font-weight="600">51</text>
|
|
20
|
-
<text x="120" y="324" font-size="11" fill="#8b949e" opacity="0.8">Median of 10 runs/cell, default temperature. 5 tasks (email, debounce, CSV sum, countdown, rate-limit), same model per group. Reproduce: npx promptfoo eval -c benchmarks/promptfooconfig.yaml</text>
|
|
21
|
-
</svg>
|
|
1
|
+
<svg viewBox="0 0 860 336" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system, 'Segoe UI', Helvetica, Arial, sans-serif">
|
|
2
|
+
<title>Median lines of code per arm across three models</title>
|
|
3
|
+
<text x="20" y="26" font-size="15" font-weight="600" fill="#8b949e">Median lines of code. 10 runs per cell. Lower is leaner.</text>
|
|
4
|
+
<text x="20" y="45" font-size="12" fill="#8b949e" opacity="0.85">Ponytail writes 80-94% less code, costs 42-75% less, and runs 3-6x faster than a no-skill agent.</text>
|
|
5
|
+
<rect x="20" y="58" width="12" height="12" rx="2" fill="#8b949e"/><text x="38" y="69" font-size="13" fill="#8b949e">baseline (no skill)</text>
|
|
6
|
+
<rect x="190" y="58" width="12" height="12" rx="2" fill="#d9822b"/><text x="208" y="69" font-size="13" fill="#8b949e">caveman</text>
|
|
7
|
+
<rect x="300" y="58" width="12" height="12" rx="2" fill="#2da44e"/><text x="318" y="69" font-size="13" fill="#8b949e">ponytail</text>
|
|
8
|
+
<text x="112" y="119" font-size="13" font-weight="600" fill="#8b949e" text-anchor="end">Haiku</text>
|
|
9
|
+
<rect x="120" y="92" width="508" height="14" rx="2" fill="#8b949e"/><text x="634" y="103" font-size="11" fill="#8b949e">518</text>
|
|
10
|
+
<rect x="120" y="110" width="114" height="14" rx="2" fill="#d9822b"/><text x="240" y="121" font-size="11" fill="#d9822b">116</text>
|
|
11
|
+
<rect x="120" y="128" width="38" height="14" rx="2" fill="#2da44e"/><text x="164" y="139" font-size="11" fill="#2da44e" font-weight="600">39</text>
|
|
12
|
+
<text x="112" y="193" font-size="13" font-weight="600" fill="#8b949e" text-anchor="end">Sonnet</text>
|
|
13
|
+
<rect x="120" y="166" width="680" height="14" rx="2" fill="#8b949e"/><text x="806" y="177" font-size="11" fill="#8b949e">693</text>
|
|
14
|
+
<rect x="120" y="184" width="118" height="14" rx="2" fill="#d9822b"/><text x="244" y="195" font-size="11" fill="#d9822b">120</text>
|
|
15
|
+
<rect x="120" y="202" width="43" height="14" rx="2" fill="#2da44e"/><text x="169" y="213" font-size="11" fill="#2da44e" font-weight="600">44</text>
|
|
16
|
+
<text x="112" y="267" font-size="13" font-weight="600" fill="#8b949e" text-anchor="end">Opus</text>
|
|
17
|
+
<rect x="120" y="240" width="251" height="14" rx="2" fill="#8b949e"/><text x="377" y="251" font-size="11" fill="#8b949e">256</text>
|
|
18
|
+
<rect x="120" y="258" width="66" height="14" rx="2" fill="#d9822b"/><text x="192" y="269" font-size="11" fill="#d9822b">67</text>
|
|
19
|
+
<rect x="120" y="276" width="50" height="14" rx="2" fill="#2da44e"/><text x="176" y="287" font-size="11" fill="#2da44e" font-weight="600">51</text>
|
|
20
|
+
<text x="120" y="324" font-size="11" fill="#8b949e" opacity="0.8">Median of 10 runs/cell, default temperature. 5 tasks (email, debounce, CSV sum, countdown, rate-limit), same model per group. Reproduce: npx promptfoo eval -c benchmarks/promptfooconfig.yaml</text>
|
|
21
|
+
</svg>
|