ruige-skill 1.1.7 → 1.1.9
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/README.md +3 -1
- package/SKILL.md +49 -4
- package/bin/ruige-skill.mjs +102 -4
- package/knowledge//345/256/277/344/270/273/346/223/215/344/275/234/345/272/223//345/256/277/344/270/273-Studio One-/351/253/230/351/242/221/345/277/253/346/215/267/351/224/256.md" +7 -1
- package/knowledge//347/274/226/346/233/262/345/210/266/344/275/234/345/256/236/346/210/230/351/227/256/347/255/224/07-/347/274/226/346/233/262/346/200/235/347/273/264/344/270/216/345/267/245/344/275/234/346/265/201/345/256/236/346/210/230/351/227/256/347/255/224.md +11 -10
- package/manifest.json +5 -5
- package/package.json +5 -1
- package/references/aesthetic-translation.md +5 -1
- package/references/beginner-teaching.md +7 -3
- package/references/guided-learning.md +10 -6
- package/references/knowledge-map.md +4 -0
- package/references/mixing-guidance.md +5 -6
- package/references/suno-prompt-iteration.md +298 -0
- package/scripts/manage-prompt-library.mjs +257 -0
- package/scripts/search-knowledge.sh +12 -2
- package/scripts/validate-project.mjs +217 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# Suno 提示词与生成结果迭代
|
|
2
|
+
|
|
3
|
+
## 目录
|
|
4
|
+
|
|
5
|
+
- 目标
|
|
6
|
+
- 参考艺人和作品不是完整需求
|
|
7
|
+
- 先分清使用方式
|
|
8
|
+
- 内部提示词 Brief
|
|
9
|
+
- 核心目标对齐闸门
|
|
10
|
+
- 第一版输出
|
|
11
|
+
- 参数建议与单变量实验
|
|
12
|
+
- 知识来源说明
|
|
13
|
+
- 本地提示词资料库
|
|
14
|
+
- 当前平台能力边界
|
|
15
|
+
|
|
16
|
+
## 目标
|
|
17
|
+
|
|
18
|
+
提示词不是一次写完的答案,而是把已经确认的音乐目标交给 Suno,并通过生成结果继续校准目标的实验记录。
|
|
19
|
+
|
|
20
|
+
核心闭环:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
原始目标
|
|
24
|
+
→ 确认当前核心目标
|
|
25
|
+
→ 生成可直接使用的第一版
|
|
26
|
+
→ 检查提示词是否直接表达核心目标
|
|
27
|
+
→ 用户实际生成并描述偏差
|
|
28
|
+
→ 下一版只改一个变量
|
|
29
|
+
→ 记录什么表达在什么条件下有效
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
不要把“提示词更长、更专业、更像某个艺人”当成质量标准。质量取决于:提示词有没有保留用户真正要的东西,生成结果能否帮助他判断下一步该改什么。
|
|
33
|
+
|
|
34
|
+
## 参考艺人和作品不是完整需求
|
|
35
|
+
|
|
36
|
+
用户只说“某位艺人风格”“像某首歌”时,不能把模型对该作品的印象直接当成用户已经确认的目标。不同人可能喜欢同一首歌的不同部分。
|
|
37
|
+
|
|
38
|
+
先确认最影响方向的一项,例如:
|
|
39
|
+
|
|
40
|
+
- 律动和重心;
|
|
41
|
+
- 人声状态与表达方式;
|
|
42
|
+
- 配器密度和声部职能;
|
|
43
|
+
- 音色、年代感与制作质感;
|
|
44
|
+
- 段落推进和能量变化。
|
|
45
|
+
|
|
46
|
+
一次只确认一个最决定方向的维度。方向未确认时,不根据作品名擅自补写 BPM、段落、乐器、演唱细节或“招牌特征”。没有实际听取对应版本时,不声称已经准确拆解该录音。
|
|
47
|
+
|
|
48
|
+
如果用户明确说“先给我一版再说”,可以输出一版标明假设的草案,并把最关键的假设压缩成一句话。不能把草案写成对参考作品的事实分析。
|
|
49
|
+
|
|
50
|
+
## 先分清使用方式
|
|
51
|
+
|
|
52
|
+
同一句提示词在不同任务中承担的责任不同。只确认当前真正相关的方式:
|
|
53
|
+
|
|
54
|
+
1. **纯文字生成**:提示词需要承担更多音乐方向信息。
|
|
55
|
+
2. **已有歌词**:Style 只负责用户确认的音乐方向,不擅自改写歌词或新增段落。
|
|
56
|
+
3. **上传已有音频继续生成**:先确认哪些旋律、节奏、结构或情绪必须保留,哪些部分允许 Suno 改变。
|
|
57
|
+
4. **只实验一个局部方向**:只写这一小任务,不把整首歌重新设计一遍。
|
|
58
|
+
|
|
59
|
+
没有说明具体版本、模型或界面时,不凭旧经验给固定的平台参数。平台功能和模型能力会变化;具体字段、按钮和限制需要使用当前官方资料核对。
|
|
60
|
+
|
|
61
|
+
## 内部提示词 Brief
|
|
62
|
+
|
|
63
|
+
生成前在内部整理下面七项,只使用用户已经确认的内容。缺失项不自动补齐,也不要求用户机械填表。
|
|
64
|
+
|
|
65
|
+
1. **任务**:纯文字生成、已有歌词、上传音频继续生成,或局部实验。
|
|
66
|
+
2. **当前核心目标**:这一版最不能丢的一项,保留用户原话并给出对应音乐表达。
|
|
67
|
+
3. **主体**:谁承担主要注意力,谁只负责支撑。
|
|
68
|
+
4. **律动**:速度感、拍感、松紧、重心和推动方式。
|
|
69
|
+
5. **配器与质感**:关键声部、密度、明暗、软硬、空间和年代感。
|
|
70
|
+
6. **结构与动态**:仅保留用户已经确认的段落职能与能量变化。
|
|
71
|
+
7. **保留与避免**:哪些必须存在,哪些方向不能出现。
|
|
72
|
+
|
|
73
|
+
不是每次都要把七项展示给用户。普通生成请求只给最终提示词和极短说明;用户明确要学习、复盘或建立自己的方法时,再展示结构。
|
|
74
|
+
|
|
75
|
+
## 核心目标对齐闸门
|
|
76
|
+
|
|
77
|
+
输出或保存任何版本前,检查:
|
|
78
|
+
|
|
79
|
+
1. 当前核心目标是否来自用户已经确认的内容,而不是模型自行补写。
|
|
80
|
+
2. 提示词是否直接表达当前核心目标。
|
|
81
|
+
3. 辅助音色、氛围、乐器和制作词是否只是支撑,而没有取代核心目标。
|
|
82
|
+
4. `目标.md`、`当前版本.md` 和版本记录中的当前核心目标是否一致。
|
|
83
|
+
5. “本版只改了什么”是否真的只对应一个变量。
|
|
84
|
+
|
|
85
|
+
例如,用户确认最在意的是“松弹的律动和重心”,提示词里就必须直接出现对应的律动、拍感或低频推动表达;只写 airy、dreamy、warm、clean 等质感词不算对齐。
|
|
86
|
+
|
|
87
|
+
## 第一版输出
|
|
88
|
+
|
|
89
|
+
给学员教学时,可以使用下面这条基础骨架:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
速度与律动 + 风格与年代方向 + 最关键的声部或乐器职能 + 整体听感与空间
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
只有任务确实需要时,再加入人声状态、已确认的段落变化和避免项。这是帮助人整理目标的教学骨架,不是 Suno 的固定字段顺序,也不代表写在前面的词一定权重更高。
|
|
96
|
+
|
|
97
|
+
方向和工具都已经确认时,按下面结构输出:
|
|
98
|
+
|
|
99
|
+
```text
|
|
100
|
+
Suno Style
|
|
101
|
+
一段可直接使用的提示词
|
|
102
|
+
|
|
103
|
+
本版只锁定
|
|
104
|
+
一句话说明当前核心目标
|
|
105
|
+
|
|
106
|
+
建议起始参数
|
|
107
|
+
只给当前任务真正需要的滑块建议,并说明是本轮起点
|
|
108
|
+
|
|
109
|
+
下一轮只比较
|
|
110
|
+
一个用户生成后可以明确反馈的维度
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
用户已经给出歌词时,不附新歌词。用户没有要求歌词时,不生成歌词、段落标签或完整歌曲结构。
|
|
114
|
+
|
|
115
|
+
提示词可以是精简标签,也可以是较完整的自然语言。不要把“越短越好”或“越详细越好”写成跨版本定律。选择哪种形式取决于当前模型、任务复杂度和用户的实际 A/B 结果。
|
|
116
|
+
|
|
117
|
+
滑块建议可以直接给,不要求用户自己猜。它们是当前任务的起始处方,不是平台定律。下一版如果测试某个滑块,只改这个滑块;不要同时大改 Style 提示词。下一版如果改 Style 提示词,则先保持主要滑块不变。
|
|
118
|
+
|
|
119
|
+
不要声称:
|
|
120
|
+
|
|
121
|
+
- 某个词放前面就一定获得更高权重;
|
|
122
|
+
- 某个否定词一定生效或一定无效;
|
|
123
|
+
- 某个固定长度、字段顺序或参数适用于所有版本;
|
|
124
|
+
- 写出艺人名就等于完成了音乐分析。
|
|
125
|
+
|
|
126
|
+
这些都只能作为待验证的版本假设。
|
|
127
|
+
|
|
128
|
+
## 生成后的复盘
|
|
129
|
+
|
|
130
|
+
用户带回结果时,不立即重写整段提示词。先让他指出最明显的一个偏差:
|
|
131
|
+
|
|
132
|
+
- 主体错了;
|
|
133
|
+
- 律动不对;
|
|
134
|
+
- 配器或音色方向不对;
|
|
135
|
+
- 密度和空间不对;
|
|
136
|
+
- 段落能量不对;
|
|
137
|
+
- 该保留的内容丢了;
|
|
138
|
+
- 出现了明确不想要的内容。
|
|
139
|
+
|
|
140
|
+
然后:
|
|
141
|
+
|
|
142
|
+
1. 保留上一版已经有效的部分。
|
|
143
|
+
2. 只修改一个最能解释当前偏差的变量。
|
|
144
|
+
3. 给新版本编号。
|
|
145
|
+
4. 说明这一版改了什么、其他部分保持什么。
|
|
146
|
+
5. 等用户实际生成后再判断。
|
|
147
|
+
|
|
148
|
+
不能一次同时改速度、风格、乐器、人声、结构和避免项,否则无法知道哪一处修改有效。提示词与滑块属于同一套实验变量;一次只能选择其中一个主要变化。
|
|
149
|
+
|
|
150
|
+
## 学员自己的本地提示词资料库
|
|
151
|
+
|
|
152
|
+
沉淀是长期使用方法的一部分,但不能代替第一版结果。学员可以在第一次拿到可用提示词后立即建库,也可以在第一次生成并反馈后再建。无论何时建库,都先取得同意并让用户指定一个精确目录。未经允许不创建、不读取、不修改。
|
|
153
|
+
|
|
154
|
+
当用户询问“有没有提示词知识包”时,直接说明三层区别:
|
|
155
|
+
|
|
156
|
+
1. Skill 有音乐转译、提示词组织和版本迭代方法。
|
|
157
|
+
2. Skill 可以按需使用公开音乐知识,但没有必要把艺人名做成固定提示词答案库。
|
|
158
|
+
3. 用户自己的资料库记录真实生成结果和个人判断,只有这一层会随着他的使用逐渐变得更贴合他。
|
|
159
|
+
|
|
160
|
+
不要笼统回答“有知识库”或“没有知识包”后结束;要告诉用户当前这一轮可以怎样开始积累。
|
|
161
|
+
|
|
162
|
+
如果用户把“有没有知识包”“我想沉淀”和“给我一条建库提示词”放在同一句里,这仍然是一个需要完整作答的方法问题,不是让助教静默写入记忆。当前回答至少包含:
|
|
163
|
+
|
|
164
|
+
1. 现在能用的知识与方法;
|
|
165
|
+
2. 通用方法和个人生成证据的区别;
|
|
166
|
+
3. 一条可以原样复制、只需替换路径的启动话术。
|
|
167
|
+
|
|
168
|
+
没有拿到精确目录并完成写入检查时,禁止只回复“已经记下”“已经保存”或其他完成式确认。
|
|
169
|
+
|
|
170
|
+
### 给学员的启动话术
|
|
171
|
+
|
|
172
|
+
用户要求学习怎样建立本地系统时,给他下面这类可复制话术,并让他把目录替换为自己明确选择的绝对路径:
|
|
173
|
+
|
|
174
|
+
```text
|
|
175
|
+
/rg 请在“这里填写这首歌的绝对文件夹路径”为这首歌建立 Suno 提示词迭代资料库。先把我们已经确认的目标和当前提示词保存为 V001。以后每次根据我真实生成后的反馈,只改一个变量,保留旧版本,不要覆盖,也不要把预期效果写成已经发生的结果。
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
如果用户还没有绝对路径,只让他先在电脑上新建一个项目文件夹,再把完整路径发过来;不要扫描桌面、父目录或相似名称目录替他选择。
|
|
179
|
+
|
|
180
|
+
### 建立与更新
|
|
181
|
+
|
|
182
|
+
得到当轮授权和精确目录后,首次建立必须先运行:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
node scripts/manage-prompt-library.mjs init --dir '/用户明确指定的绝对路径'
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
然后把当前对话里已经确认的目标、提示词和参数填写进脚本返回的文件,不留下空壳。首次尚未生成时,把结果状态明确写成“等待生成”,不能把预期写成事实。
|
|
189
|
+
|
|
190
|
+
用户实际生成、反馈结果并同意记录后,先运行:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
node scripts/manage-prompt-library.mjs revision --dir '/用户明确指定的绝对路径'
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
然后只填写脚本返回的文件。旧版本记录只追加、不覆盖;不得自行创造另一套文件名或扫描父目录寻找其他资料库。
|
|
197
|
+
|
|
198
|
+
写入后必须运行:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
node scripts/manage-prompt-library.mjs check --dir '/用户明确指定的绝对路径'
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
并回读本轮改动的文件,确认核心目标、版本号、提示词和记录状态一致。检查失败时说明没有完成,不得把“准备保存”说成“已经保存”。
|
|
205
|
+
|
|
206
|
+
旧项目缺少新版栏目时,在用户已经授权更新该精确目录的前提下运行:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
node scripts/manage-prompt-library.mjs upgrade --dir '/用户明确指定的绝对路径'
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
这个动作只追加缺失栏目,不覆盖旧内容。升级后仍要运行 `check` 并由用户或助教填写新增的当前核心目标和对齐检查,脚本不替用户猜。
|
|
213
|
+
|
|
214
|
+
推荐的用户可见结构:
|
|
215
|
+
|
|
216
|
+
```text
|
|
217
|
+
项目名/
|
|
218
|
+
├── 目标.md
|
|
219
|
+
├── 当前版本.md
|
|
220
|
+
└── 生成记录/
|
|
221
|
+
├── V001.md
|
|
222
|
+
└── V002.md
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`目标.md` 只保存相对稳定的内容:
|
|
226
|
+
|
|
227
|
+
```markdown
|
|
228
|
+
# 目标
|
|
229
|
+
|
|
230
|
+
## 使用方式
|
|
231
|
+
## 当前核心目标
|
|
232
|
+
## 想保留的音乐特征
|
|
233
|
+
## 明确避免项
|
|
234
|
+
## 当前参考及真正参考的维度
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
`当前版本.md` 保存当前正在验证的版本:
|
|
238
|
+
|
|
239
|
+
```markdown
|
|
240
|
+
# 当前版本
|
|
241
|
+
|
|
242
|
+
## 版本号
|
|
243
|
+
## 当前核心目标
|
|
244
|
+
## 本版提示词
|
|
245
|
+
## 建议起始参数
|
|
246
|
+
## 本版只改了什么
|
|
247
|
+
## 当前已经验证有效的部分
|
|
248
|
+
## 等待验证的问题
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
每份生成记录保存:
|
|
252
|
+
|
|
253
|
+
```markdown
|
|
254
|
+
# V001
|
|
255
|
+
|
|
256
|
+
## 当前核心目标
|
|
257
|
+
## 本版提示词
|
|
258
|
+
## 参数设置
|
|
259
|
+
## 核心目标对齐检查
|
|
260
|
+
## 本版只改了什么
|
|
261
|
+
## 生成结果中的可见事实
|
|
262
|
+
## 用户认为符合的部分
|
|
263
|
+
## 用户认为偏离的部分
|
|
264
|
+
## 下一版只改什么
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
只记录用户实际生成和反馈后的结果,不把模型预期写成实验结论。艺人名可以作为用户的检索线索,但真正可复用的是被验证过的音乐维度、表达方式和适用条件。
|
|
268
|
+
|
|
269
|
+
### WorkBuddy 降级路径
|
|
270
|
+
|
|
271
|
+
纯 Skill 在不同 WorkBuddy 环境中不一定能稳定调用脚本。无法运行脚本或无法写入时:
|
|
272
|
+
|
|
273
|
+
1. 不声称已经建立资料库。
|
|
274
|
+
2. 按 `目标.md`、`当前版本.md` 和 `生成记录/V001.md` 三个标题输出可复制内容。
|
|
275
|
+
3. 告诉用户把这些内容保存到他刚才指定的项目文件夹。
|
|
276
|
+
4. 后续仍可以在同一任务中继续版本迭代。
|
|
277
|
+
|
|
278
|
+
只有实际检查到文件存在且内容一致时,才报告保存成功。
|
|
279
|
+
|
|
280
|
+
## 当前官方能力边界
|
|
281
|
+
|
|
282
|
+
Suno 的模型和界面会更新。需要说明当前字段、模型或功能时,优先核对官方帮助中心,不沿用旧课程中的固定结论。
|
|
283
|
+
|
|
284
|
+
截至本参考更新时,官方资料已经说明:
|
|
285
|
+
|
|
286
|
+
- 较新的模型能够理解更详细的自然语言 Style 描述,不再只有简短标签一种写法;
|
|
287
|
+
- 可以通过 Reuse Prompt 修改 Style、歌词和标题后继续生成;
|
|
288
|
+
- Creative Sliders 中的 Weirdness、Style Influence 和 Audio Influence 分别控制变化程度、贴近 Style 的程度和贴近上传音频的程度,但官方没有给所有任务统一的最佳数值;
|
|
289
|
+
- Suno 也在提供基于用户使用习惯的个性化功能,但本地资料库仍用于保留用户自己的目标、判断依据和跨工具可迁移的实验记录。
|
|
290
|
+
|
|
291
|
+
这些是平台能力,不代表某个具体词序一定产生固定结果。最终仍以用户当次模型和生成结果为准。
|
|
292
|
+
|
|
293
|
+
官方资料:
|
|
294
|
+
|
|
295
|
+
- https://help.suno.com/en/articles/5782849
|
|
296
|
+
- https://help.suno.com/en/articles/2417409
|
|
297
|
+
- https://help.suno.com/en/articles/6141377
|
|
298
|
+
- https://help.suno.com/en/articles/11362561
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { access, appendFile, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import { isAbsolute, resolve, sep } from "node:path";
|
|
5
|
+
|
|
6
|
+
const command = process.argv[2];
|
|
7
|
+
const args = new Map();
|
|
8
|
+
for (let index = 3; index < process.argv.length; index += 2) {
|
|
9
|
+
const key = process.argv[index];
|
|
10
|
+
const value = process.argv[index + 1];
|
|
11
|
+
if (!key?.startsWith("--") || value === undefined) {
|
|
12
|
+
throw new Error(`Invalid argument near: ${key ?? "(missing)"}`);
|
|
13
|
+
}
|
|
14
|
+
args.set(key.slice(2), value);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const requestedDir = args.get("dir");
|
|
18
|
+
if (!requestedDir || !isAbsolute(requestedDir)) {
|
|
19
|
+
throw new Error("--dir must be an absolute path explicitly selected by the user");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const libraryDir = resolve(requestedDir);
|
|
23
|
+
const skillDir = resolve(import.meta.dirname, "..");
|
|
24
|
+
if (libraryDir === skillDir || libraryDir.startsWith(`${skillDir}${sep}`)) {
|
|
25
|
+
throw new Error("Prompt libraries must stay outside the Skill directory");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const goalPath = resolve(libraryDir, "目标.md");
|
|
29
|
+
const currentPath = resolve(libraryDir, "当前版本.md");
|
|
30
|
+
const recordsDir = resolve(libraryDir, "生成记录");
|
|
31
|
+
|
|
32
|
+
const goalTemplate = `# 目标
|
|
33
|
+
|
|
34
|
+
## 使用方式
|
|
35
|
+
|
|
36
|
+
## 当前核心目标
|
|
37
|
+
|
|
38
|
+
## 想保留的音乐特征
|
|
39
|
+
|
|
40
|
+
## 明确避免项
|
|
41
|
+
|
|
42
|
+
## 当前参考及真正参考的维度
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
const currentTemplate = `# 当前版本
|
|
46
|
+
|
|
47
|
+
## 版本号
|
|
48
|
+
|
|
49
|
+
## 当前核心目标
|
|
50
|
+
|
|
51
|
+
## 本版提示词
|
|
52
|
+
|
|
53
|
+
## 建议起始参数
|
|
54
|
+
|
|
55
|
+
## 本版只改了什么
|
|
56
|
+
|
|
57
|
+
## 当前已经验证有效的部分
|
|
58
|
+
|
|
59
|
+
## 等待验证的问题
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
const recordTemplate = (version) => `# ${version}
|
|
63
|
+
|
|
64
|
+
## 当前核心目标
|
|
65
|
+
|
|
66
|
+
## 本版提示词
|
|
67
|
+
|
|
68
|
+
## 参数设置
|
|
69
|
+
|
|
70
|
+
## 核心目标对齐检查
|
|
71
|
+
|
|
72
|
+
## 本版只改了什么
|
|
73
|
+
|
|
74
|
+
## 生成结果中的可见事实
|
|
75
|
+
|
|
76
|
+
## 用户认为符合的部分
|
|
77
|
+
|
|
78
|
+
## 用户认为偏离的部分
|
|
79
|
+
|
|
80
|
+
## 下一版只改什么
|
|
81
|
+
`;
|
|
82
|
+
|
|
83
|
+
const requiredHeadings = {
|
|
84
|
+
"目标.md": [
|
|
85
|
+
"## 使用方式",
|
|
86
|
+
"## 当前核心目标",
|
|
87
|
+
"## 想保留的音乐特征",
|
|
88
|
+
"## 明确避免项",
|
|
89
|
+
"## 当前参考及真正参考的维度",
|
|
90
|
+
],
|
|
91
|
+
"当前版本.md": [
|
|
92
|
+
"## 版本号",
|
|
93
|
+
"## 当前核心目标",
|
|
94
|
+
"## 本版提示词",
|
|
95
|
+
"## 建议起始参数",
|
|
96
|
+
"## 本版只改了什么",
|
|
97
|
+
"## 当前已经验证有效的部分",
|
|
98
|
+
"## 等待验证的问题",
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
async function exists(path) {
|
|
103
|
+
try {
|
|
104
|
+
await access(path);
|
|
105
|
+
return true;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (error.code === "ENOENT") return false;
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function writeIfMissing(path, content) {
|
|
113
|
+
try {
|
|
114
|
+
await writeFile(path, content, { encoding: "utf8", flag: "wx" });
|
|
115
|
+
return "created";
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (error.code === "EEXIST") return "kept";
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function inspectHeadings(path, headings) {
|
|
123
|
+
if (!(await exists(path))) {
|
|
124
|
+
return { exists: false, missingHeadings: headings };
|
|
125
|
+
}
|
|
126
|
+
const content = await readFile(path, "utf8");
|
|
127
|
+
return {
|
|
128
|
+
exists: true,
|
|
129
|
+
missingHeadings: headings.filter((heading) => !content.includes(heading)),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function appendMissingHeadings(path, headings) {
|
|
134
|
+
const inspection = await inspectHeadings(path, headings);
|
|
135
|
+
if (!inspection.exists) return inspection;
|
|
136
|
+
if (inspection.missingHeadings.length) {
|
|
137
|
+
const addition = inspection.missingHeadings
|
|
138
|
+
.map((heading) => `\n${heading}\n\n`)
|
|
139
|
+
.join("");
|
|
140
|
+
await appendFile(path, addition, "utf8");
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
exists: true,
|
|
144
|
+
addedHeadings: inspection.missingHeadings,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function init() {
|
|
149
|
+
await mkdir(recordsDir, { recursive: true });
|
|
150
|
+
process.stdout.write(`${JSON.stringify({
|
|
151
|
+
libraryDir,
|
|
152
|
+
files: {
|
|
153
|
+
"目标.md": await writeIfMissing(goalPath, goalTemplate),
|
|
154
|
+
"当前版本.md": await writeIfMissing(currentPath, currentTemplate),
|
|
155
|
+
},
|
|
156
|
+
recordsDir: "生成记录",
|
|
157
|
+
}, null, 2)}\n`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function nextVersion() {
|
|
161
|
+
const records = (await exists(recordsDir))
|
|
162
|
+
? await readdir(recordsDir)
|
|
163
|
+
: [];
|
|
164
|
+
const numbers = records
|
|
165
|
+
.map((name) => /^V(\d{3})\.md$/.exec(name))
|
|
166
|
+
.filter(Boolean)
|
|
167
|
+
.map((match) => Number(match[1]));
|
|
168
|
+
return `V${String((numbers.length ? Math.max(...numbers) : 0) + 1).padStart(3, "0")}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function revision() {
|
|
172
|
+
if (!(await exists(goalPath)) || !(await exists(currentPath))) {
|
|
173
|
+
throw new Error("Prompt library is missing 目标.md or 当前版本.md; run init first with user permission");
|
|
174
|
+
}
|
|
175
|
+
await mkdir(recordsDir, { recursive: true });
|
|
176
|
+
const version = args.get("version") ?? await nextVersion();
|
|
177
|
+
if (!/^V\d{3}$/.test(version)) {
|
|
178
|
+
throw new Error("--version must use V001 format");
|
|
179
|
+
}
|
|
180
|
+
const path = resolve(recordsDir, `${version}.md`);
|
|
181
|
+
await writeFile(path, recordTemplate(version), { encoding: "utf8", flag: "wx" });
|
|
182
|
+
process.stdout.write(`${JSON.stringify({
|
|
183
|
+
libraryDir,
|
|
184
|
+
version,
|
|
185
|
+
record: `生成记录/${version}.md`,
|
|
186
|
+
}, null, 2)}\n`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function check() {
|
|
190
|
+
const records = (await exists(recordsDir))
|
|
191
|
+
? (await readdir(recordsDir)).filter((name) => /^V\d{3}\.md$/.test(name)).sort()
|
|
192
|
+
: [];
|
|
193
|
+
const goal = await inspectHeadings(goalPath, requiredHeadings["目标.md"]);
|
|
194
|
+
const current = await inspectHeadings(currentPath, requiredHeadings["当前版本.md"]);
|
|
195
|
+
const recordChecks = {};
|
|
196
|
+
for (const record of records) {
|
|
197
|
+
recordChecks[record] = await inspectHeadings(resolve(recordsDir, record), [
|
|
198
|
+
"## 当前核心目标",
|
|
199
|
+
"## 本版提示词",
|
|
200
|
+
"## 参数设置",
|
|
201
|
+
"## 核心目标对齐检查",
|
|
202
|
+
"## 本版只改了什么",
|
|
203
|
+
"## 生成结果中的可见事实",
|
|
204
|
+
"## 用户认为符合的部分",
|
|
205
|
+
"## 用户认为偏离的部分",
|
|
206
|
+
"## 下一版只改什么",
|
|
207
|
+
]);
|
|
208
|
+
}
|
|
209
|
+
const valid = goal.exists
|
|
210
|
+
&& current.exists
|
|
211
|
+
&& goal.missingHeadings.length === 0
|
|
212
|
+
&& current.missingHeadings.length === 0
|
|
213
|
+
&& Object.values(recordChecks).every((item) => item.missingHeadings.length === 0);
|
|
214
|
+
process.stdout.write(`${JSON.stringify({
|
|
215
|
+
libraryDir,
|
|
216
|
+
valid,
|
|
217
|
+
goal,
|
|
218
|
+
current,
|
|
219
|
+
records,
|
|
220
|
+
recordChecks,
|
|
221
|
+
}, null, 2)}\n`);
|
|
222
|
+
if (!valid) process.exitCode = 1;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function upgrade() {
|
|
226
|
+
if (!(await exists(goalPath)) || !(await exists(currentPath))) {
|
|
227
|
+
throw new Error("Prompt library is missing 目标.md or 当前版本.md; run init first with user permission");
|
|
228
|
+
}
|
|
229
|
+
const records = (await exists(recordsDir))
|
|
230
|
+
? (await readdir(recordsDir)).filter((name) => /^V\d{3}\.md$/.test(name)).sort()
|
|
231
|
+
: [];
|
|
232
|
+
const upgraded = {
|
|
233
|
+
"目标.md": await appendMissingHeadings(goalPath, requiredHeadings["目标.md"]),
|
|
234
|
+
"当前版本.md": await appendMissingHeadings(currentPath, requiredHeadings["当前版本.md"]),
|
|
235
|
+
records: {},
|
|
236
|
+
};
|
|
237
|
+
for (const record of records) {
|
|
238
|
+
upgraded.records[record] = await appendMissingHeadings(resolve(recordsDir, record), [
|
|
239
|
+
"## 当前核心目标",
|
|
240
|
+
"## 本版提示词",
|
|
241
|
+
"## 参数设置",
|
|
242
|
+
"## 核心目标对齐检查",
|
|
243
|
+
"## 本版只改了什么",
|
|
244
|
+
"## 生成结果中的可见事实",
|
|
245
|
+
"## 用户认为符合的部分",
|
|
246
|
+
"## 用户认为偏离的部分",
|
|
247
|
+
"## 下一版只改什么",
|
|
248
|
+
]);
|
|
249
|
+
}
|
|
250
|
+
process.stdout.write(`${JSON.stringify({ libraryDir, upgraded }, null, 2)}\n`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (command === "init") await init();
|
|
254
|
+
else if (command === "revision") await revision();
|
|
255
|
+
else if (command === "check") await check();
|
|
256
|
+
else if (command === "upgrade") await upgrade();
|
|
257
|
+
else throw new Error("Usage: manage-prompt-library.mjs <init|revision|check|upgrade> --dir /absolute/path [--version V001]");
|
|
@@ -16,6 +16,16 @@ fi
|
|
|
16
16
|
|
|
17
17
|
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
|
18
18
|
skill_dir=$(cd -- "$script_dir/.." && pwd)
|
|
19
|
-
|
|
19
|
+
if [[ -d $skill_dir/knowledge ]]; then
|
|
20
|
+
knowledge_root=$(cd -- "$skill_dir/knowledge" && pwd)
|
|
21
|
+
else
|
|
22
|
+
knowledge_root=$(cd -- "$skill_dir/../knowledge" && pwd)
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
matches=$(rg -l -i --glob '*.md' -- "$pattern" "$knowledge_root" || true)
|
|
26
|
+
if [[ -z $matches ]]; then
|
|
27
|
+
printf '未找到匹配内容\n'
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
20
30
|
|
|
21
|
-
|
|
31
|
+
printf '%s\n' "$matches" | sed -n "1,${limit}p"
|