sillyspec 3.15.2 → 3.16.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.
@@ -0,0 +1,347 @@
1
+ /**
2
+ * StageContract — 阶段协议
3
+ *
4
+ * 每个阶段声明:允许的前置阶段、必须的产出、校验器、后续阶段。
5
+ * CLI 不再相信 prompt 完成,completeStep 后必须过 validator。
6
+ */
7
+
8
+ import { existsSync, readdirSync } from 'fs'
9
+ import { join, basename } from 'path'
10
+
11
+ /**
12
+ * 校验结果
13
+ * @typedef {{ ok: boolean, errors: string[], warnings: string[] }} ValidationResult
14
+ */
15
+
16
+ /**
17
+ * 阶段合约
18
+ * @typedef {{
19
+ * stage: string,
20
+ * description: string,
21
+ * allowedFrom: string[],
22
+ * allowedTo: string[],
23
+ * validators: Function[],
24
+ * }} StageContract
25
+ */
26
+
27
+ // ============ Validators ============
28
+
29
+ /**
30
+ * scan 完成校验:检查 7 份 scan 文档 + manifest
31
+ */
32
+ function validateScanOutputs(cwd, changeName, context = {}) {
33
+ const { projectName } = context
34
+ const docsRoot = projectName
35
+ ? join(cwd, '.sillyspec', 'docs', projectName, 'scan')
36
+ : join(cwd, '.sillyspec', 'docs', 'scan')
37
+
38
+ const requiredDocs = [
39
+ 'ARCHITECTURE.md',
40
+ 'CONVENTIONS.md',
41
+ 'STRUCTURE.md',
42
+ 'INTEGRATIONS.md',
43
+ 'TESTING.md',
44
+ 'CONCERNS.md',
45
+ 'PROJECT.md',
46
+ ]
47
+
48
+ const errors = []
49
+ const warnings = []
50
+
51
+ for (const doc of requiredDocs) {
52
+ if (!existsSync(join(docsRoot, doc))) {
53
+ errors.push(`scan 文档缺失: ${docsRoot}/${doc}`)
54
+ }
55
+ }
56
+
57
+ // 检查 modules 目录
58
+ const modulesRoot = projectName
59
+ ? join(cwd, '.sillyspec', 'docs', projectName, 'modules')
60
+ : join(cwd, '.sillyspec', 'docs', 'modules')
61
+ if (!existsSync(modulesRoot)) {
62
+ warnings.push('modules 目录不存在')
63
+ } else {
64
+ const modules = readdirSync(modulesRoot).filter(f => f.endsWith('.md'))
65
+ if (modules.length === 0) {
66
+ warnings.push('modules 目录为空')
67
+ }
68
+ }
69
+
70
+ return { ok: errors.length === 0, errors, warnings }
71
+ }
72
+
73
+ /**
74
+ * plan 完成校验:检查 plan.md 生成
75
+ */
76
+ function validatePlanOutputs(cwd, changeName) {
77
+ const planDir = join(cwd, '.sillyspec', 'changes', changeName)
78
+ const planFile = join(planDir, 'plan.md')
79
+ const errors = []
80
+
81
+ if (!existsSync(planFile)) {
82
+ errors.push(`plan.md 缺失: ${planFile}`)
83
+ }
84
+
85
+ const warnings = []
86
+ return { ok: errors.length === 0, errors, warnings }
87
+ }
88
+
89
+ /**
90
+ * verify 完成校验:检查 verify 报告存在
91
+ */
92
+ function validateVerifyOutputs(cwd, changeName) {
93
+ const planDir = join(cwd, '.sillyspec', 'changes', changeName)
94
+ const errors = []
95
+ const warnings = []
96
+
97
+ // verify 至少应该有 run 记录
98
+ if (!existsSync(join(planDir, 'plan.md'))) {
99
+ errors.push(`变更目录缺失: ${planDir}`)
100
+ }
101
+
102
+ return { ok: errors.length === 0, errors, warnings }
103
+ }
104
+
105
+ /**
106
+ * archive 完成校验:检查归档目录完整性
107
+ */
108
+ function validateArchiveOutputs(cwd, changeName) {
109
+ const errors = []
110
+ const warnings = []
111
+ const archiveDir = join(cwd, '.sillyspec', 'changes', 'archive')
112
+ const date = new Date().toISOString().slice(0, 10)
113
+ const destDir = join(archiveDir, `${date}-${changeName}`)
114
+
115
+ // 检查归档目录是否存在
116
+ if (!existsSync(destDir)) {
117
+ errors.push(`归档目录缺失: ${destDir}`)
118
+ return { ok: false, errors, warnings }
119
+ }
120
+
121
+ // 检查核心文档
122
+ const requiredDocs = ['plan.md']
123
+ const recommendedDocs = ['design.md', 'module-impact.md']
124
+
125
+ for (const doc of requiredDocs) {
126
+ if (!existsSync(join(destDir, doc))) {
127
+ errors.push(`归档目录缺失核心文档: ${doc}`)
128
+ }
129
+ }
130
+
131
+ for (const doc of recommendedDocs) {
132
+ if (!existsSync(join(destDir, doc))) {
133
+ warnings.push(`归档目录缺少推荐文档: ${doc}`)
134
+ }
135
+ }
136
+
137
+ return { ok: errors.length === 0, errors, warnings }
138
+ }
139
+
140
+ /**
141
+ * archive 前置校验:所有主流程阶段完成
142
+ */
143
+ function validateChangeClosed(cwd, changeName) {
144
+ const errors = []
145
+ const warnings = []
146
+
147
+ // 检查前置阶段状态
148
+ const progressDir = join(cwd, '.sillyspec', '.runtime')
149
+ // 这里只做文件层面的检查,DB 检查在 run.js 里做
150
+ const changeDir = join(cwd, '.sillyspec', 'changes', changeName)
151
+ if (!existsSync(changeDir)) {
152
+ errors.push(`变更目录不存在: ${changeDir}`)
153
+ return { ok: false, errors, warnings }
154
+ }
155
+
156
+ if (!existsSync(join(changeDir, 'plan.md'))) {
157
+ errors.push(`plan.md 缺失 — 请确保 plan 阶段已完成`)
158
+ }
159
+
160
+ return { ok: errors.length === 0, errors, warnings }
161
+ }
162
+
163
+ // ============ Contract Registry ============
164
+
165
+ /**
166
+ * 主流程阶段(有严格转换顺序)
167
+ */
168
+ const mainFlowStages = ['brainstorm', 'plan', 'execute', 'verify']
169
+
170
+ /**
171
+ * 辅助阶段(可独立运行,无严格转换顺序)
172
+ */
173
+ const auxiliaryStages = ['scan', 'quick', 'explore', 'archive', 'status', 'doctor']
174
+
175
+ /**
176
+ * @type {Object<string, StageContract>}
177
+ */
178
+ const contracts = {
179
+ // === 主流程 ===
180
+ brainstorm: {
181
+ stage: 'brainstorm',
182
+ description: '需求分析与设计',
183
+ allowedFrom: [], // 任何变更的起始阶段
184
+ allowedTo: ['plan'],
185
+ validators: [],
186
+ },
187
+ plan: {
188
+ stage: 'plan',
189
+ description: '任务拆解与规划',
190
+ allowedFrom: ['brainstorm'],
191
+ allowedTo: ['execute'],
192
+ validators: [validatePlanOutputs],
193
+ },
194
+ execute: {
195
+ stage: 'execute',
196
+ description: '代码实现',
197
+ allowedFrom: ['plan'],
198
+ allowedTo: ['verify'],
199
+ validators: [],
200
+ },
201
+ verify: {
202
+ stage: 'verify',
203
+ description: '验证与测试',
204
+ allowedFrom: ['execute'],
205
+ allowedTo: ['archive'],
206
+ validators: [validateVerifyOutputs],
207
+ },
208
+ archive: {
209
+ stage: 'archive',
210
+ description: '归档与收口',
211
+ allowedFrom: ['verify'],
212
+ allowedTo: [],
213
+ validators: [validateChangeClosed, validateArchiveOutputs],
214
+ },
215
+
216
+ // === 辅助阶段 ===
217
+ scan: {
218
+ stage: 'scan',
219
+ description: '项目扫描',
220
+ allowedFrom: [], // 无前置要求
221
+ allowedTo: [], // 不进入主流程
222
+ validators: [validateScanOutputs],
223
+ },
224
+ quick: {
225
+ stage: 'quick',
226
+ description: '快速任务',
227
+ allowedFrom: [], // 无前置要求
228
+ allowedTo: [], // 不进入主流程
229
+ validators: [],
230
+ },
231
+ explore: {
232
+ stage: 'explore',
233
+ description: '代码探索',
234
+ allowedFrom: [],
235
+ allowedTo: [],
236
+ validators: [],
237
+ },
238
+ status: {
239
+ stage: 'status',
240
+ description: '状态查看',
241
+ allowedFrom: [],
242
+ allowedTo: [],
243
+ validators: [],
244
+ },
245
+ doctor: {
246
+ stage: 'doctor',
247
+ description: '环境诊断',
248
+ allowedFrom: [],
249
+ allowedTo: [],
250
+ validators: [],
251
+ },
252
+ }
253
+
254
+ // ============ Public API ============
255
+
256
+ /**
257
+ * 获取阶段合约
258
+ */
259
+ export function getContract(stageName) {
260
+ return contracts[stageName] || null
261
+ }
262
+
263
+ /**
264
+ * 校验状态转换是否允许
265
+ * @param {string} fromStage - 当前阶段(空字符串表示变更起始)
266
+ * @param {string} toStage - 目标阶段
267
+ * @returns {{ allowed: boolean, reason?: string }}
268
+ */
269
+ export function checkTransition(fromStage, toStage) {
270
+ const contract = contracts[toStage]
271
+ if (!contract) {
272
+ return { allowed: false, reason: `未知阶段: ${toStage}` }
273
+ }
274
+
275
+ // 辅助阶段随时可执行(archive 除外:从主流程进入 archive 需要校验)
276
+ if (auxiliaryStages.includes(toStage) && toStage !== 'archive') {
277
+ return { allowed: true }
278
+ }
279
+
280
+ // archive 特殊处理:从 verify 来的允许,从其他主流程阶段来的需要校验
281
+ if (toStage === 'archive') {
282
+ if (fromStage === 'verify') {
283
+ return { allowed: true }
284
+ }
285
+ // 独立运行 archive(无前置)也允许
286
+ if (!fromStage || auxiliaryStages.includes(fromStage)) {
287
+ return { allowed: true }
288
+ }
289
+ return { allowed: false, reason: 'archive 的前置阶段是 verify,不能从 ' + fromStage + ' 跳转' }
290
+ }
291
+
292
+ // 从辅助阶段进入主流程:允许(用户可能 scan 完直接 brainstorm 或 plan)
293
+ if (auxiliaryStages.includes(fromStage)) {
294
+ return { allowed: true }
295
+ }
296
+
297
+ // 无前置阶段(变更起始):只能开始 brainstorm 或辅助阶段
298
+ if (!fromStage) {
299
+ // 主流程必须从 brainstorm 开始
300
+ if (contract.allowedFrom.length === 0) {
301
+ return { allowed: true }
302
+ }
303
+ return { allowed: false, reason: `${toStage} 需要先完成 ${contract.allowedFrom.join(' 或 ')}` }
304
+ }
305
+
306
+ // 主流程内部跳转:检查目标阶段的 allowedFrom 是否包含 fromStage
307
+ if (contract.allowedFrom.includes(fromStage)) {
308
+ return { allowed: true }
309
+ }
310
+
311
+ return { allowed: false, reason: `${toStage} 的前置阶段是 ${contract.allowedFrom.join(' 或 ')},不能从 ${fromStage} 跳转` }
312
+ }
313
+
314
+ /**
315
+ * 执行阶段完成校验
316
+ * @param {string} stageName
317
+ * @param {string} cwd
318
+ * @param {string} changeName
319
+ * @param {object} context - 额外上下文(如 projectName)
320
+ * @returns {ValidationResult}
321
+ */
322
+ export function runValidators(stageName, cwd, changeName, context = {}) {
323
+ const contract = contracts[stageName]
324
+ if (!contract || contract.validators.length === 0) {
325
+ return { ok: true, errors: [], warnings: [] }
326
+ }
327
+
328
+ const allErrors = []
329
+ const allWarnings = []
330
+
331
+ for (const validator of contract.validators) {
332
+ try {
333
+ const result = validator(cwd, changeName, context)
334
+ allErrors.push(...(result.errors || []))
335
+ allWarnings.push(...(result.warnings || []))
336
+ } catch (e) {
337
+ allErrors.push(`校验器 ${validator.name || 'unknown'} 异常: ${e.message}`)
338
+ }
339
+ }
340
+
341
+ return { ok: allErrors.length === 0, errors: allErrors, warnings: allWarnings }
342
+ }
343
+
344
+ /**
345
+ * 获取所有主流程阶段
346
+ */
347
+ export { mainFlowStages, auxiliaryStages }
@@ -124,15 +124,14 @@ module_id: <module-id>
124
124
  },
125
125
  {
126
126
  name: '确认归档',
127
- prompt: `确认归档内容并执行目录移动。
127
+ prompt: `确认归档内容,由 CLI 执行目录移动。
128
128
 
129
129
  ### 操作
130
130
  1. 展示:变更目录名、包含的文件列表(含 module-impact.md)、生成总结
131
- 2. **直接执行归档**(本步骤完成后自动移动):
132
- - 创建 archive 目录:\`mkdir -p .sillyspec/changes/archive\`
133
- - 移动变更目录:\`mv .sillyspec/changes/<change-name> .sillyspec/changes/archive/<change-name>\`
134
- - 确认移动成功:\`ls .sillyspec/changes/archive/<change-name>/\`
135
- 3. 确保所有 checkbox 都已勾选
131
+ 2. 确保所有 checkbox 都已勾选
132
+ 3. 让用户确认后,用 \`--confirm\` 完成本步骤:
133
+ \`sillyspec run archive --done --confirm --output "确认归档"\`
134
+ 4. CLI 会创建 \`.sillyspec/changes/archive/\`,并将变更目录移动到 \`.sillyspec/changes/archive/YYYY-MM-DD-<change-name>/\`
136
135
 
137
136
  ### 输出
138
137
  归档完成 + archive 目录路径`,
@@ -147,10 +146,7 @@ module_id: <module-id>
147
146
  1. 如果 \`.sillyspec/ROADMAP.md\` 存在,标记对应 Phase 为已完成
148
147
  2. \`git add .sillyspec/changes/\` — 暂存归档结果(不要 commit,由用户通过统一提交工具处理)
149
148
  3. \`git add .sillyspec/docs/\` — 暂存模块文档更新(如有)
150
- 4. 更新 sillyspec.db 中的阶段状态:
151
- - 清除当前变更信息(归档后不再活跃)
152
- - 如果是主变更(有 MASTER.md),标记所有阶段为 ✅,然后清除
153
- - 历史记录追加时间 + 归档完成
149
+ 4. 确认 sillyspec.db 中该变更已不再 active(确认归档步骤由 CLI 调用 unregisterChange)
154
150
 
155
151
  ### 输出
156
152
  归档完成确认 + 累积规范统计`,
@@ -274,7 +274,10 @@ design.md 文件路径 + 自审结果
274
274
  ### 注意
275
275
  - 自审不通过不要进入下一步
276
276
  - 不确定的问题标注「⚠️ 自审存疑」`,
277
-
277
+ outputHint: 'design.md 文件路径 + 自审结果',
278
+ optional: false
279
+ },
280
+ {
278
281
  name: '用户确认并生成规范文件',
279
282
  prompt: `用户确认设计方案,生成规范文件。
280
283
 
@@ -84,13 +84,54 @@ for f in .sillyspec/projects/*.yaml; do
84
84
  stack_md="$p/.sillyspec/STACK.md"
85
85
  [ -f "$local_yaml" ] && echo "✅ local.yaml ($name)" || echo "⚠️ local.yaml ($name) — 不存在"
86
86
  if [ -f "$local_yaml" ]; then
87
- grep -q 'build:' "$local_yaml" && echo " ✅ build 命令已配置" || echo " ⚠️ 缺少 build 命令"
88
87
  grep -q 'test:' "$local_yaml" && echo " ✅ test 命令已配置" || echo " ⚠️ 缺少 test 命令"
89
88
  fi
90
89
  [ -f "$stack_md" ] && echo "✅ STACK.md ($name)" || echo "⚠️ STACK.md ($name) — 不存在"
91
90
  done
92
91
  \`\`\`
93
92
 
93
+ ### 6. Worktree 隔离环境检查
94
+ \`\`\`bash
95
+ # 检测当前目录是否在 submodule 中
96
+ SUPERPROJECT=$(git rev-parse --show-superproject-working-tree 2>/dev/null)
97
+ if [ -n "$SUPERPROJECT" ]; then
98
+ echo "⚠️ 当前目录在 git submodule 内,worktree 隔离不可用"
99
+ else
100
+ echo "✅ 不在 submodule 中"
101
+ fi
102
+
103
+ # 检测是否已在 linked worktree 中
104
+ GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
105
+ GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
106
+ if [ "$GIT_DIR" != "$GIT_COMMON" ] && [ -z "$SUPERPROJECT" ]; then
107
+ echo "✅ 已在 linked worktree 中"
108
+ else
109
+ echo "ℹ️ 在主仓库中(非 worktree)"
110
+ fi
111
+
112
+ # 检查 worktree 存储目录是否被 .gitignore 忽略
113
+ WT_DIR='.sillyspec/.runtime/worktrees'
114
+ if git check-ignore -q "$WT_DIR" 2>/dev/null; then
115
+ echo "✅ worktree 目录已被 .gitignore 忽略 ($WT_DIR)"
116
+ else
117
+ echo "❌ worktree 目录未被 .gitignore 忽略 ($WT_DIR) — worktree 创建将被阻断"
118
+ echo " 修复: 在 .gitignore 中添加 $WT_DIR/"
119
+ fi
120
+
121
+ # 检查 DB 中的 isolation 状态
122
+ DB_FILE='.sillyspec/.runtime/sillyspec.db'
123
+ if [ -f "$DB_FILE" ]; then
124
+ echo ""
125
+ echo "isolation 状态(来自 sillyspec.db):"
126
+ sqlite3 -header -column "$DB_FILE" "SELECT name, isolation_status AS status, isolation_mode AS mode, isolation_reason AS reason FROM changes WHERE status='active'" 2>/dev/null || echo "⚠️ 查询 isolation 失败"
127
+ else
128
+ echo ""
129
+ echo "ℹ️ sillyspec.db 不存在(尚未初始化)"
130
+ fi
131
+ else
132
+ echo "ℹ️ gate-status.json 不存在(尚未进入 execute 阶段)"
133
+ fi
134
+ \`\`\`\n
94
135
  ### 输出
95
136
  汇总所有检查结果,按以下格式:
96
137
  \`\`\`
@@ -304,7 +345,7 @@ timeout 5 which docker 2>/dev/null && echo "✅ Docker 可用" || echo "ℹ️ D
304
345
  **常见问题及修复:**
305
346
  - CLI 未安装 → \`npm install -g sillyspec\`
306
347
  - 缺少 local.yaml → \`sillyspec init\` 重新生成,或手动创建
307
- - local.yaml 缺少 build/test → 补充对应命令
348
+ - local.yaml 缺少 test 命令 → 补充对应命令
308
349
  - 缺少 STACK.md → \`sillyspec run scan\` 重新扫描
309
350
  - sillyspec.db 状态不一致 → \`sillyspec run <阶段> --reset\` 重置对应阶段
310
351
  - 孤儿目录 → 确认后 \`rm -rf .sillyspec/changes/<目录名>\`
@@ -61,8 +61,14 @@ const fixedPrefix = [
61
61
  3. 后续所有子代理的 cwd 设为该 worktree 路径
62
62
  4. 如果创建失败 → 报错并停止(不要在无隔离状态下继续)
63
63
 
64
+ ### 降级模式
65
+ CLI 可能自动降级(sandbox 限制、已在 linked worktree 中):
66
+ - \`mode: native-worktree\` — 已在 linked worktree,直接复用
67
+ - \`mode: in-place-fallback\` — git worktree add 失败,降级为 in-place + baseline protection
68
+ - 这两种模式都会输出 worktree 路径和分支名,正常继续即可
69
+
64
70
  ### 输出
65
- worktree 路径 + 分支名
71
+ worktree 路径 + 分支名 + 模式(如果有)
66
72
 
67
73
  ### 完成后执行
68
74
  sillyspec run execute --done --output "worktree 路径 + 分支名"`,
@@ -184,19 +190,40 @@ const fixedSuffix = [
184
190
  name: '完成确认',
185
191
  prompt: `所有任务完成后的收尾。
186
192
 
187
- ### 操作(有 worktree)
193
+ 先检查当前 worktree 的隔离模式:
194
+ \`\`\`bash
195
+ node -e "import('./src/worktree.js').then(w => { const wm = new w.WorktreeManager(); const m = wm.getMeta('<change-name>'); console.log(m ? JSON.stringify({mode: m.mode, path: m.worktreePath}) : 'no meta'); })"
196
+ # 或从 DB 读取:
197
+ sqlite3 -json .sillyspec/.runtime/sillyspec.db "SELECT isolation_status, isolation_mode, isolation_reason FROM changes WHERE name='<change-name>'" 2>/dev/null
198
+ \`\`\`
199
+
200
+ ### 操作(mode = worktree,SillySpec 创建的隔离 worktree)
188
201
  1. 运行 \`sillyspec worktree apply --check-only <change-name>\`
189
202
  2. 展示 diff 摘要(文件列表 + 变更统计)
190
203
  3. 检查结果说明(是否通过文件清单校验)
191
204
  4. 用户确认后运行 \`sillyspec worktree apply <change-name>\`
192
- 5. apply 成功 → 自动 cleanup
205
+ 5. apply 成功 → 运行 \`sillyspec worktree cleanup <change-name>\` → 输出 Worktree: cleaned
193
206
  6. apply 失败 → 展示错误详情,用户选择重试或手动处理
194
207
  7. 如果用户不想 apply → 运行 \`sillyspec worktree cleanup <change-name>\` 丢弃
195
208
  8. 建议下一步:\`sillyspec run verify\`
196
209
 
210
+ ### 操作(mode = native-worktree,用户已有的 linked worktree)
211
+ 1. 运行 \`sillyspec worktree apply --check-only <change-name>\`
212
+ 2. 展示 diff 摘要
213
+ 3. 用户确认后运行 \`sillyspec worktree apply <change-name>\`
214
+ 4. **不要运行 cleanup** — 这是用户自己的 worktree,SillySpec 不能删除
215
+ 5. 输出 Worktree: kept(SillySpec 未创建此 worktree,保留不动)
216
+ 6. 建议下一步:\`sillyspec run verify\`
217
+
218
+ ### 操作(mode = in-place-fallback,降级模式无隔离目录)
219
+ 1. 展示本次执行摘要(\`git diff\` 查看变更)
220
+ 2. 跳过 apply 和 cleanup(没有隔离 worktree)
221
+ 3. 输出 Worktree: none(降级为 in-place,无隔离目录需要清理)
222
+ 4. 建议下一步:\`sillyspec run verify\`
223
+
197
224
  ### 操作(无 worktree / --no-worktree 模式)
198
- 1. 跳过 apply 和 cleanup 步骤(因为没有 worktree)
199
- 2. 展示本次执行摘要
225
+ 1. 展示本次执行摘要
226
+ 2. 输出 Worktree: none
200
227
  3. 提示用户直接使用 \`git diff\` 查看变更
201
228
  4. 建议下一步:\`sillyspec run verify\`
202
229
 
@@ -109,7 +109,10 @@ export const definition = {
109
109
  - 表名/字段名/类名必须来自真实代码或标注"新增"
110
110
  - 用户场景必须用 Given/When/Then 格式
111
111
  - tasks.md 只列任务名,细节在 plan 阶段展开`,
112
-
112
+ outputHint: '四个文件路径',
113
+ optional: false
114
+ },
115
+ {
113
116
  name: '自检门控',
114
117
  prompt: `自检生成的规范文件。
115
118
 
@@ -27,12 +27,12 @@ export const definition = {
27
27
  1. 使用预注入的 git 用户名:\`<git-user>\`
28
28
  2. 无 \`--change\`:创建 .sillyspec/quicklog/QUICKLOG-\`<git-user>\`.md\`(已存在则追加),写入:
29
29
  \`\`\`
30
- ## ql-<YYYYMMDD>-<NNN>-<short4> | <now-datetime> | <一句话任务描述>
30
+ ## ql-<YYYYMMDD>-<NNN>-<XXXX> | <now-datetime> | <一句话任务描述>
31
31
  状态:进行中
32
32
  文件:<预估要改的文件>
33
33
  \`\`\`
34
- - ID 格式:\`ql-YYYYMMDD-NNN-XXXX\`(如 ql-20260603-001-a3f2)
35
- - \`XXXX\` 是 4 位随机十六进制(防多文件/并发冲突)
34
+ - ID 格式:\`ql-YYYYMMDD-NNN-XXXX\`
35
+ - \`XXXX\` 是 4 位随机十六进制字符(如 a3f2、b7c1、00ef),**不是描述词缩写**
36
36
  - 追加前扫描文件中已有的 \`ql-<当天日期>-\` 前缀的最大序号,+1 作为新序号
37
37
  - 每天从 001 开始,跨日重新计数
38
38
  - 此 ID 可被 design.md / plan.md / archive / module 变更索引引用
@@ -12,7 +12,7 @@ export const definition = {
12
12
  1. 列出项目顶层目录:\`ls -d */ 2>/dev/null | grep -v node_modules | grep -v '.git' | grep -v '.sillyspec'\`
13
13
  2. 对每个顶层目录,快速判断是否为独立项目(检查 package.json / pom.xml / build.gradle / pyproject.toml / go.mod 等构建文件)
14
14
  3. 对每个疑似独立项目,检测技术栈:\`cat <dir>/package.json 2>/dev/null | head -5\` 或类似
15
- 4. 对比 \`.sillyspec/projects/\` 已有配置,找出未注册的子项目
15
+ 4. 对比 \`{PROJECTS_ROOT}/\` 已有配置,找出未注册的子项目
16
16
 
17
17
  ### 判断标准(满足任一即为子项目)
18
18
  - 有独立的构建文件(package.json, pom.xml, build.gradle, pyproject.toml 等)
@@ -41,8 +41,8 @@ export const definition = {
41
41
  prompt: `确定本次要扫描的项目列表。
42
42
 
43
43
  ### 操作
44
- 1. \`ls .sillyspec/projects/*.yaml 2>/dev/null\` — 列出所有已注册项目
45
- 2. 对每个项目,检查已有的 scan 文档状态:\`ls .sillyspec/docs/<project>/scan/*.md 2>/dev/null\`
44
+ 1. \`ls {PROJECTS_ROOT}/*.yaml 2>/dev/null\` — 列出所有已注册项目
45
+ 2. 对每个项目,检查已有的 scan 文档状态:\`ls {DOCS_ROOT}/scan/*.md 2>/dev/null\`
46
46
  3. 按以下格式展示:
47
47
 
48
48
  \`\`\`
@@ -75,7 +75,7 @@ export const definition = {
75
75
  1. 进入项目目录(子项目用其 path,如 \`packages/dashboard/\`)
76
76
  2. \`cat package.json pom.xml build.gradle go.mod Cargo.toml requirements.txt pyproject.toml Gemfile composer.json 2>/dev/null\`
77
77
  3. \`find <project-dir> -maxdepth 2 -name "*.config.*" -not -path "*/node_modules/*" -not -path "*/.git/*" | head -20 | xargs cat 2>/dev/null\`
78
- 4. 结果保存到 \`.sillyspec/docs/<project>/scan/_env-detect.md\`(临时文件,扫描完删除)
78
+ 4. 结果保存到 \`{DOCS_ROOT}/scan/_env-detect.md\`(临时文件,扫描完删除)
79
79
 
80
80
  ### 输出
81
81
  每个项目的环境探测结果摘要`,
@@ -90,7 +90,7 @@ export const definition = {
90
90
  ### 操作
91
91
  对扫描列表中的每个项目分别执行:
92
92
  1. 检查 7 份文档是否存在:ARCHITECTURE、STRUCTURE、CONVENTIONS、INTEGRATIONS、TESTING、CONCERNS、PROJECT
93
- 路径:\`.sillyspec/docs/<project>/scan/<DOC>.md\`
93
+ 路径:\`{DOCS_ROOT}/scan/<DOC>.md\`
94
94
  2. 列出已有 ✅ 和缺失 ⬜
95
95
 
96
96
  ### 输出
@@ -139,16 +139,16 @@ export const definition = {
139
139
  },
140
140
  {
141
141
  name: '生成本地配置',
142
- prompt: `自动生成 .sillyspec/.runtime/local.yaml 本地配置文件。
142
+ prompt: `自动生成 .sillyspec/local.yaml 本地配置文件。
143
143
 
144
144
  ### 操作
145
- 1. 检查 .sillyspec/.runtime/local.yaml 是否已存在,已存在则跳过(提示"local.yaml 已存在,跳过生成")
145
+ 1. 检查 .sillyspec/local.yaml 是否已存在,已存在则跳过(提示"local.yaml 已存在,跳过生成")
146
146
  2. 根据项目类型生成默认配置:
147
147
  - **Node.js**(有 package.json):build: "npm run build", test: "npm test", lint: "npm run lint", type: nodejs
148
148
  - **Maven**(有 pom.xml):build: "mvn compile", test: "mvn test", lint: "mvn checkstyle:check", type: maven
149
149
  - **Gradle**(有 build.gradle):build: "./gradlew build", test: "./gradlew test", type: gradle
150
150
  - **通用项目**:只写注释模板, type: generic
151
- 3. 确保目录存在:mkdir -p .sillyspec/.runtime
151
+ 3. 确保目录存在:mkdir -p .sillyspec
152
152
  4. 原子写入(先写 tmp 文件再 rename)
153
153
 
154
154
  ### 文件格式
@@ -187,7 +187,7 @@ local.yaml 生成结果(已存在/已生成)`,
187
187
 
188
188
  ### 操作
189
189
  对扫描列表中的每个项目分别执行:
190
- 1. 检查 \`.sillyspec/docs/<project>/modules/_module-map.yaml\` 是否已存在,已存在则跳过
190
+ 1. 检查 \`{DOCS_ROOT}/modules/_module-map.yaml\` 是否已存在,已存在则跳过
191
191
  2. 分析项目源码目录结构,识别模块划分:
192
192
  - 用 \`find . -maxdepth 3 -type d -not -path "*/node_modules/*" -not -path "*/.git/*"\` 查看目录结构
193
193
  - 每个有明确职责的独立目录识别为一个模块
@@ -200,7 +200,7 @@ local.yaml 生成结果(已存在/已生成)`,
200
200
  4. 分析跨模块依赖关系:
201
201
  - 用 grep import/require 分析模块间的引用链
202
202
  - 填充 depends_on(本模块依赖谁)和 used_by(谁依赖本模块)
203
- 5. 生成 \`.sillyspec/docs/<project>/modules/_module-map.yaml\`
203
+ 5. 生成 \`{DOCS_ROOT}/modules/_module-map.yaml\`
204
204
  6. 如果 modules/ 目录不存在,先创建
205
205
  7. 原子写入(先写 tmp 文件再 rename)
206
206
 
@@ -305,8 +305,8 @@ _module-map.yaml 生成结果(已存在/已生成/模块列表)`,
305
305
 
306
306
  ### 操作
307
307
  对扫描列表中的每个项目分别执行:
308
- 1. 读取 \`.sillyspec/docs/<project>/modules/_module-map.yaml\`,获取模块列表和路径
309
- 2. 检查 \`.sillyspec/docs/<project>/modules/\` 下已有的模块文档(<module>.md)
308
+ 1. 读取 \`{DOCS_ROOT}/modules/_module-map.yaml\`,获取模块列表和路径
309
+ 2. 检查 \`{DOCS_ROOT}/modules/\` 下已有的模块文档(<module>.md)
310
310
  3. 列出每个模块的状态:已有文档 / 缺失
311
311
  4. **必须停下来问用户**:
312
312
  - 展示模块列表及现有文档状态
@@ -323,7 +323,7 @@ _module-map.yaml 生成结果(已存在/已生成/模块列表)`,
323
323
  \`\`\`
324
324
  模块名:<module-id>
325
325
  模块路径:<glob patterns>
326
- 目标文件:.sillyspec/docs/<project>/modules/<module-id>.md
326
+ 目标文件:{DOCS_ROOT}/modules/<module-id>.md
327
327
 
328
328
  操作:
329
329
  1. 用 grep/rg 搜索模块路径范围内的源码(禁止读源码全文)
@@ -380,7 +380,7 @@ module_id: <module-id>
380
380
  ⚠️ 这一步是可选的。如果项目模块简单、流程不明显,可以跳过。
381
381
 
382
382
  ### flows/ 目录
383
- 目标目录:\`.sillyspec/docs/<project>/flows/\`
383
+ 目标目录:\`{DOCS_ROOT}/flows/\`
384
384
 
385
385
  根据 _module-map.yaml 中的模块依赖关系,识别跨模块业务流程:
386
386
  1. 读取 \`_module-map.yaml\`,分析 used_by 链条
@@ -410,7 +410,7 @@ step1 → step2 → step3
410
410
  \`\`\`
411
411
 
412
412
  ### glossary.md
413
- 目标文件:\`.sillyspec/docs/<project>/glossary.md\`
413
+ 目标文件:\`{DOCS_ROOT}/glossary.md\`
414
414
 
415
415
  提取项目专有术语:
416
416
  1. 用 grep 搜索 TODO/FIXME 注释中的术语定义
@@ -446,11 +446,11 @@ step1 → step2 → step3
446
446
 
447
447
  ### 操作
448
448
  对扫描列表中的每个项目分别执行:
449
- 1. 检查 7 份 scan 文档是否全部生成(\`.sillyspec/docs/<project>/scan/\`)
450
- 2. 检查模块文档状态(\`.sillyspec/docs/<project>/modules/\`)
449
+ 1. 检查 7 份 scan 文档是否全部生成(\`{DOCS_ROOT}/scan/\`)
450
+ 2. 检查模块文档状态(\`{DOCS_ROOT}/modules/\`)
451
451
  3. 自检门控:ARCHITECTURE(技术栈+Schema摘要)、CONVENTIONS(隐形规则+代码风格)、STRUCTURE(目录结构)、INTEGRATIONS(外部依赖)、TESTING(测试现状)、CONCERNS(技术债务)、PROJECT(项目概览)
452
452
  4. 检查 flows/ 和 glossary.md 是否已生成(如有)
453
- 5. 清理:\`rm -f .sillyspec/docs/<project>/scan/_env-detect.md\`
453
+ 5. 清理:\`rm -f {DOCS_ROOT}/scan/_env-detect.md\`
454
454
  6. \`git add .sillyspec/\` — 暂存扫描结果(不要 commit,由用户通过统一提交工具处理)
455
455
 
456
456
  ### 输出