sillyspec 3.24.6 → 3.24.8

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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: sillyspec:archive
3
- description: 归档已验证完成的变更:模块影响分析 + 同步模块文档 + 移动到 archive + 更新 ROADMAP。
3
+ description: 用于归档已验证完成的变更。适合用户说"归档、archive、收尾这个变更"。执行模块影响分析 + 同步模块文档 + 移动到 archive 目录 + 更新 ROADMAP。
4
4
  ---
5
5
 
6
6
  ## 何时使用
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: sillyspec:brainstorm
3
- description: 正式开发前的需求澄清与技术方案设计。产出结构化方案(design/proposal/requirements/tasks 四件套),不直接写代码。
3
+ description: 用于正式开始开发前的需求澄清和技术方案设计。适合用户提出新功能、新模块、架构调整、复杂改造,或说"先做需求分析、输出技术方案、创建变更前先梳理、帮我设计下"。产出结构化方案(design/proposal/requirements/tasks 四件套),但不直接写代码。
4
4
  ---
5
5
 
6
6
  ## 交互规范
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: sillyspec:doctor
3
- description: SillySpec 自检与状态修复:全量扫描进度一致性,修复进度数据与实际产出不匹配。
3
+ description: 用于 SillySpec 自检和状态修复。适合用户说"检查下状态、修复 progress、doctor、状态不对"。全量扫描进度一致性,修复进度数据与实际产出不匹配的问题。
4
4
  ---
5
5
 
6
6
  ## 何时使用
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: sillyspec:execute
3
- description: 按 plan 执行代码实现:依 plan.md 的 Wave/Task 逐步实现,遵循 design.md 与模块文档。
3
+ description: 用于按 plan 执行代码实现。适合用户说"开始写代码、执行任务、跑 execute、开干"。按 plan.md 中的 Wave 和 Task 逐步实现,遵循 design.md 和模块文档。
4
4
  ---
5
5
 
6
6
  ## 何时使用
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: sillyspec:explore
3
- description: 用于自由讨论、代码库调研、方案比较、画 ASCII 图。只读,不写代码不实现功能。
3
+ description: 用于自由讨论、代码库调研、方案比较、画 ASCII 图、澄清问题。适合用户说"分析下、讨论下、看看怎么设计、先别写代码、调研一下、画个结构图、帮我再分析下"。只读,不修改文件,不实现功能。
4
4
  ---
5
5
 
6
6
  ## 交互规范
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: sillyspec:plan
3
- description: 把 design 拆解为可执行的实现计划。产出 plan.md(Wave 分组 + Task 列表 + 依赖关系)。
3
+ description: 用于把 design 拆解为可执行的实现计划。适合用户说"拆任务、做计划、排 wave、规划实现步骤"。产出 plan.md(Wave 分组 + Task 列表 + 依赖关系)。
4
4
  ---
5
5
 
6
6
  ## 何时使用
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: sillyspec:propose
3
- description: 生成结构化规范:proposal.md + design.md + tasks.md(+ 可能 requirements.md)。
3
+ description: 生成结构化规范 — proposal + design + tasks。适合用户说"生成规范、补全四件套、propose"。产出 proposal.md + design.md + tasks.md(+ 可能的 requirements.md)。
4
4
  ---
5
5
 
6
6
  ## 何时使用
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: sillyspec:quick
3
- description: 用于明确、低风险、小范围的直接修改(改文案/修小 bug/更新单文件)。跳过 brainstorm/plan,仍走 quick 流程。
3
+ description: 用于明确、低风险、范围很小的直接任务。适合用户说"直接改、快速修、顺手调整、改个文案、修个小 bug、更新一个文件、不要完整流程"。跳过 brainstorm/plan,但仍按 sillyspec quick 流程执行。
4
4
  ---
5
5
 
6
6
  ## 何时使用
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: sillyspec:scan
3
- description: 扫描项目代码库,生成架构文档、代码约定、目录结构。产出 7 份扫描文档 + 模块映射。
3
+ description: 用于扫描项目代码库,生成架构文档、代码约定、目录结构等。适合用户说"扫描项目、分析代码库、生成文档、scan"。产出 7 份扫描文档 + 模块映射。
4
4
  ---
5
5
 
6
6
  ## 何时使用
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: sillyspec:status
3
- description: 查看 SillySpec 当前进度与状态:当前阶段、步骤完成度、活跃变更。
3
+ description: 用于查看 SillySpec 当前进度和状态。适合用户说"看下状态、当前进度、status"。显示当前阶段、步骤完成度、活跃变更。
4
4
  ---
5
5
 
6
6
  ## 何时使用
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: sillyspec:verify
3
- description: 验证代码实现是否符合 design 与模块文档。对照 design.md + 模块文档检查任务完成度、设计一致性、运行测试。
3
+ description: 用于验证代码实现是否符合 design 和模块文档。适合用户说"验证下、检查下、跑 verify"。对照 design.md + 模块文档检查任务完成度、设计一致性、运行测试。
4
4
  ---
5
5
 
6
6
  ## 何时使用
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sillyspec",
3
- "version": "3.24.6",
3
+ "version": "3.24.8",
4
4
  "description": "SillySpec CLI — 流程状态机,让 AI 严格按步骤来",
5
5
  "icon": "logo.jpg",
6
6
  "homepage": "https://sillyspec.ppdmq.top/",
package/src/constants.js CHANGED
@@ -68,3 +68,15 @@ export function isPointerStale(pointer) {
68
68
  export function isPointerCorrupted(pointer) {
69
69
  return !pointer || !pointer.specRoot || !pointer.savedAt
70
70
  }
71
+
72
+ // ── scan 必需文档清单(stage-contract validator 与 scan-postcheck 共用单一真相源)──
73
+ // 曾分两处硬编码同一份清单,加新文档需同步改两处,易分叉。现收敛到此处一处。
74
+ export const SCAN_REQUIRED_DOCS = Object.freeze([
75
+ 'ARCHITECTURE.md',
76
+ 'CONVENTIONS.md',
77
+ 'STRUCTURE.md',
78
+ 'INTEGRATIONS.md',
79
+ 'TESTING.md',
80
+ 'CONCERNS.md',
81
+ 'PROJECT.md',
82
+ ])
@@ -0,0 +1,69 @@
1
+ /**
2
+ * fs-atomic.js — 原子文件写 + Windows 友好的 rename 重试
3
+ *
4
+ * 用于 .runtime/*.json、pointer、guard.json 等「会被其他进程 / hook 读取」的配置文件。
5
+ * 保证读者要么看到旧完整内容、要么看到新完整内容,不会读到半截 JSON。
6
+ *
7
+ * 注意:db.js 有自己的 _atomicWriteSync(含 .bak 备份),那是 DB 专属持久化逻辑,
8
+ * 已经过稳定性验证,此处不复用、不替换——保持 db.js 零改动。
9
+ */
10
+ import { writeFileSync, renameSync, unlinkSync } from 'fs';
11
+ import { dirname, basename, join } from 'path';
12
+
13
+ // Windows 上杀毒 / 索引 / IDE 占用文件时,rename 偶发这些错误,短退避重试即可
14
+ const RENAME_RETRY_CODES = ['EPERM', 'EBUSY', 'EACCES', 'ENOTEMPTY'];
15
+
16
+ /** 同步退避:优先 Atomics.wait,不可用时退化忙等(仅在 rename 冲突的极端情况触发)。 */
17
+ function sleepSync(ms) {
18
+ try {
19
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
20
+ } catch {
21
+ const end = Date.now() + ms;
22
+ while (Date.now() < end) { /* busy wait */ }
23
+ }
24
+ }
25
+
26
+ /**
27
+ * rename 带退避重试,覆盖 Windows 偶发的 EPERM/EBUSY/EACCES/ENOTEMPTY。
28
+ * 目录 rename 同样适用(归档移动目录时可复用)。
29
+ * @param {string} from
30
+ * @param {string} to
31
+ * @param {number} [retries=5]
32
+ */
33
+ export function renameSyncRetry(from, to, retries = 5) {
34
+ let lastErr;
35
+ for (let attempt = 0; attempt <= retries; attempt++) {
36
+ try {
37
+ renameSync(from, to);
38
+ return;
39
+ } catch (err) {
40
+ lastErr = err;
41
+ const code = err && err.code;
42
+ // 非可重试错误码立即抛出,不浪费时间
43
+ if (!code || !RENAME_RETRY_CODES.includes(code)) throw err;
44
+ if (attempt < retries) sleepSync(15 * (attempt + 1));
45
+ }
46
+ }
47
+ throw lastErr;
48
+ }
49
+
50
+ /**
51
+ * 原子写文本文件:同目录 tmp 写入 → rename 覆盖目标(带 Windows 退避重试)。
52
+ * - tmp 名含 pid,避免多进程并发写同一目标时 tmp 名碰撞(如多会话写 gate-status.json)。
53
+ * - rename 失败时清理 tmp,避免留下孤儿 .tmp 文件。
54
+ * - Node 的 rename 在 Windows 上用 MoveFileEx(REPLACE_EXISTING),可覆盖已存在目标;
55
+ * EPERM 仅在文件被其他进程锁定时出现,由 renameSyncRetry 退避重试。
56
+ * @param {string} filePath - 目标文件绝对路径
57
+ * @param {string} content - 文件内容
58
+ */
59
+ export function writeAtomicSync(filePath, content) {
60
+ const dir = dirname(filePath);
61
+ const tmpPath = join(dir, `.${basename(filePath)}.${process.pid}.tmp`);
62
+ writeFileSync(tmpPath, content);
63
+ try {
64
+ renameSyncRetry(tmpPath, filePath);
65
+ } catch (err) {
66
+ try { unlinkSync(tmpPath); } catch { /* tmp 可能已被 rename 走,忽略 */ }
67
+ throw err;
68
+ }
69
+ }
package/src/index.js CHANGED
@@ -7,6 +7,7 @@
7
7
  * 状态管理通过 sillyspec.db(SQLite)完成,使用 `sillyspec progress` 命令。
8
8
  */
9
9
  import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'fs';
10
+ import { writeAtomicSync } from './fs-atomic.js';
10
11
  import { join, resolve } from 'path';
11
12
  import { cmdInit, getVersion } from './init.js';
12
13
  import { ProgressManager, resolvePlatformSpecDir } from './progress.js';
@@ -280,7 +281,7 @@ async function main() {
280
281
  }
281
282
  case 'batch': {
282
283
  if (filteredArgs.includes('--status')) {
283
- const bp = pm.readBatchProgress(dir, progChangeName);
284
+ const bp = await pm.readBatchProgress(dir, progChangeName);
284
285
  if (!bp) { console.log('📭 无批量进度数据'); break; }
285
286
  const line = pm._renderBatchProgress(bp);
286
287
  console.log(line || '📭 无批量进度数据');
@@ -299,7 +300,7 @@ async function main() {
299
300
  console.log(' sillyspec progress batch --status');
300
301
  break;
301
302
  }
302
- pm.updateBatchProgress(dir, batchData, progChangeName);
303
+ await pm.updateBatchProgress(dir, batchData, progChangeName);
303
304
  console.log('✅ 批量进度已更新');
304
305
  }
305
306
  break;
@@ -1171,11 +1172,9 @@ SillySpec modules — 模块文档管理
1171
1172
  lines.push('# order-service: "order/"');
1172
1173
  const yamlText = lines.join('\n') + '\n';
1173
1174
 
1174
- // 原子写:mkdir -p specRoot → 写 tmp → rename
1175
+ // 原子写(tmp + rename + Windows EPERM 重试),避免半截 local.yaml
1175
1176
  mkdirSync(specRoot, { recursive: true });
1176
- const tmpPath = localYamlPath + '.tmp';
1177
- writeFileSync(tmpPath, yamlText, 'utf8');
1178
- renameSync(tmpPath, localYamlPath);
1177
+ writeAtomicSync(localYamlPath, yamlText);
1179
1178
 
1180
1179
  console.log(`✅ 已生成 local.yaml (type: ${detected.project.type})`);
1181
1180
  console.log(` 路径: ${localYamlPath}`);
package/src/modules.js CHANGED
@@ -79,7 +79,7 @@ export async function rebuildModuleMap(cwd) {
79
79
  const content = readFileSync(join(modulesDir, f), 'utf8');
80
80
  const fm = parseModuleCardFrontmatter(content);
81
81
  const moduleId = fm.module_id || parseModuleIdFromFilename(f);
82
- cards.push({ filename: f, moduleId });
82
+ cards.push({ filename: f, moduleId, content });
83
83
  }
84
84
  }
85
85
 
@@ -134,7 +134,7 @@ export async function rebuildModuleMap(cwd) {
134
134
 
135
135
  for (const moduleId of allModuleIds) {
136
136
  const card = cards.find(c => c.moduleId === moduleId);
137
- const cardContent = card ? readFileSync(join(modulesDir, card.filename), 'utf8') : '';
137
+ const cardContent = card?.content || '';
138
138
 
139
139
  // 从模块卡片提取 context index 字段
140
140
  const role = extractSection(cardContent, '定位') || '';
package/src/progress.js CHANGED
@@ -13,6 +13,7 @@
13
13
  import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, unlinkSync, appendFileSync } from 'fs';
14
14
  import { join, basename, dirname, resolve } from 'path';
15
15
  import { DB } from './db.js';
16
+ import { writeAtomicSync } from './fs-atomic.js';
16
17
 
17
18
  // 默认规范目录名(相对于 cwd)
18
19
  const SPEC_DIR_NAME = '.sillyspec';
@@ -156,7 +157,14 @@ export class ProgressManager {
156
157
  /** 获取 specDir(优先自定义,否则向上查找含 .sillyspec 的目录,fallback 到 cwd/.sillyspec) */
157
158
  _getSpecDir(cwd) {
158
159
  if (this._customSpecDir) return this._customSpecDir;
159
- return resolveSpecDir(cwd);
160
+ // specDir 对给定 cwd 在进程生命周期内不变(.sillyspec 目录不会被移动),
161
+ // 按 cwd 缓存,避免每次路径解析都向上 existsSync 遍历目录树(热路径)。
162
+ if (!this._specDirCache) this._specDirCache = new Map();
163
+ const cached = this._specDirCache.get(cwd);
164
+ if (cached) return cached;
165
+ const resolved = resolveSpecDir(cwd);
166
+ this._specDirCache.set(cwd, resolved);
167
+ return resolved;
160
168
  }
161
169
 
162
170
  _runtimePath(cwd, ...parts) {
@@ -1064,7 +1072,7 @@ export class ProgressManager {
1064
1072
  mkdirSync(historyDir, { recursive: true });
1065
1073
  const ts = now.replace(/[:.TZ-]/g, '');
1066
1074
  const stageData = data.stages[stage];
1067
- writeFileSync(
1075
+ writeAtomicSync(
1068
1076
  join(historyDir, `${cn}-${stage}-${ts}.json`),
1069
1077
  JSON.stringify({ change: cn, stage, data: stageData, completedAt: now }, null, 2) + '\n'
1070
1078
  );
@@ -1666,8 +1674,7 @@ export class ProgressManager {
1666
1674
  const changeId = changeRow[0].values[0][0];
1667
1675
  sqlDb.run('DELETE FROM steps WHERE stage_id IN (SELECT id FROM stages WHERE change_id = ?)', [changeId]);
1668
1676
  sqlDb.run('DELETE FROM stages WHERE change_id = ?', [changeId]);
1669
- sqlDb.run('UPDATE stages SET status = "pending", started_at = NULL, completed_at = NULL WHERE change_id = ?', [changeId]);
1670
- // 重新插入所有阶段
1677
+ // 重新插入所有阶段(注意:上方已 DELETE stages,无需再 UPDATE)
1671
1678
  for (const s of VALID_STAGES) {
1672
1679
  sqlDb.run('INSERT OR IGNORE INTO stages (change_id, stage, status) VALUES (?, ?, "pending")', [changeId, s]);
1673
1680
  }
@@ -1865,9 +1872,7 @@ export class ProgressManager {
1865
1872
  updatedAt: new Date().toISOString(),
1866
1873
  ...(hasNoWorktree ? { noWorktree: true } : {}),
1867
1874
  };
1868
- const tmpPath = gatePath + '.tmp';
1869
- writeFileSync(tmpPath, JSON.stringify(gateData, null, 2) + '\n');
1870
- renameSync(tmpPath, gatePath);
1875
+ writeAtomicSync(gatePath, JSON.stringify(gateData, null, 2) + '\n');
1871
1876
  } catch (err) {
1872
1877
  console.warn('⚠️ 写入 gate-status.json 失败:', err.message);
1873
1878
  }
package/src/run.js CHANGED
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { basename, join, resolve, dirname, relative, isAbsolute } from 'path'
8
8
  import { existsSync, readdirSync, mkdirSync, writeFileSync, appendFileSync, readFileSync, rmSync, statSync } from 'fs'
9
+ import { writeAtomicSync, renameSyncRetry } from './fs-atomic.js'
9
10
  import { randomBytes, randomUUID } from 'crypto'
10
11
  import { createRequire } from 'module'
11
12
  const require = createRequire(import.meta.url)
@@ -1245,12 +1246,15 @@ async function executeScanPreflight(cwd, platformOpts, scanProfile) {
1245
1246
  const projectName = basename(cwd)
1246
1247
  console.log(` 📁 项目: ${projectName}`)
1247
1248
  console.log(` 📊 Profile: ${scanProfile.mode} (${scanProfile.reason})`)
1248
- // 快速列出顶层结构
1249
+ // 快速列出顶层结构(readdirSync 跨平台,避免 spawn shell + 5 个 grep 进程)
1249
1250
  try {
1250
- const { execSync } = await import('child_process')
1251
- const dirs = execSync(`ls -d */ 2>/dev/null | grep -v node_modules | grep -v '.git' | grep -v '.sillyspec' | grep -v '.claude' | head -20`, { cwd, encoding: 'utf8' }).trim()
1252
- if (dirs) {
1253
- console.log(` 📂 目录: ${dirs.split('\n').map(d => d.replace(/\/$/, '')).join(', ')}`)
1251
+ const skip = new Set(['node_modules', '.git', '.sillyspec', '.claude'])
1252
+ const dirNames = readdirSync(cwd, { withFileTypes: true })
1253
+ .filter(d => d.isDirectory() && !skip.has(d.name) && !d.name.startsWith('.'))
1254
+ .slice(0, 20)
1255
+ .map(d => d.name)
1256
+ if (dirNames.length) {
1257
+ console.log(` 📂 目录: ${dirNames.join(', ')}`)
1254
1258
  }
1255
1259
  } catch {}
1256
1260
  console.log(` ✅ Preflight 完成,准备生成核心文档\n`)
@@ -2199,7 +2203,7 @@ async function runStage(pm, progress, stageName, cwd, changeName, skipApproval =
2199
2203
  // 写入 .runtime/quick-sessions/<sessionId>/guard.json 供 worktree-guard hook 读取
2200
2204
  // (D-002:按 session 存,多会话各自 guard 不互覆盖。runStage 作用域内 sessionId == changeName == quick-<uuid8>,见 §4.4/4.5)
2201
2205
  mkdirSync(sessionGuardDir, { recursive: true })
2202
- writeFileSync(guardFile, JSON.stringify(progress.quickGuard, null, 2))
2206
+ writeAtomicSync(guardFile, JSON.stringify(progress.quickGuard, null, 2))
2203
2207
  const parts = [`${baselineFiles.length} 个已有脏文件`]
2204
2208
  if (allowedFiles.length > 0) parts.push(`${allowedFiles.length} 个 allowedFiles`)
2205
2209
  if (allowNew) parts.push('允许新增文件')
@@ -2383,7 +2387,13 @@ async function archiveChangeDirectory(pm, cwd, progress, specBase) {
2383
2387
  process.exit(1)
2384
2388
  }
2385
2389
  mkdirSync(archiveDir, { recursive: true })
2386
- renameSync(srcDir, destDir)
2390
+ try {
2391
+ renameSyncRetry(srcDir, destDir)
2392
+ } catch (e) {
2393
+ console.error(`❌ 归档失败:移动变更目录时出错(${e.message})`)
2394
+ console.error(` 常见原因:变更目录内文件被 IDE / 杀毒 / 索引占用,已重试 5 次仍失败。请关闭相关程序后重试。`)
2395
+ process.exit(1)
2396
+ }
2387
2397
 
2388
2398
  if (!existsSync(destDir) || existsSync(srcDir)) {
2389
2399
  console.error('❌ 归档校验失败:移动操作异常')
@@ -2749,7 +2759,11 @@ async function enforceDepsGate(stageName, cwd, changeName, step, steps, currentI
2749
2759
  const { WorktreeManager } = await import('./worktree.js')
2750
2760
  wm = new WorktreeManager({ cwd })
2751
2761
  meta = wm.getMeta(changeName)
2752
- } catch {}
2762
+ } catch (e) {
2763
+ // WorktreeManager 构造/读取失败(git 不可用 / worktree 元数据损坏)不阻断 deps gate;
2764
+ // 下游已有物理目录存在性判定兜底(G2/R3),这里 warn 留下根因线索,避免静默误诊。
2765
+ console.warn(`⚠️ worktree meta 读取失败(deps gate 将走物理目录判定): ${e.message}`)
2766
+ }
2753
2767
  const depsStatus = meta?.depsStatus
2754
2768
  if (['linked', 'installed', 'n/a'].includes(depsStatus)) return true
2755
2769
  const changeDir = changeName ? join(specBase, 'changes', changeName) : null
@@ -3402,8 +3416,12 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
3402
3416
  pointer.status = POINTER_STATUS.SCAN_COMPLETED
3403
3417
  pointer.completedAt = new Date().toISOString()
3404
3418
  pointer.scanStatus = postResult.status
3405
- writeFileSync(pointerPath, JSON.stringify(pointer, null, 2) + '\n')
3406
- } catch {}
3419
+ writeAtomicSync(pointerPath, JSON.stringify(pointer, null, 2) + '\n')
3420
+ } catch (e) {
3421
+ // 不阻断 scan 主流程,但暴露失败——pointer 写失败会让平台看不到 scan_completed,
3422
+ // 与项目 fail-loud 原则一致:宁可可见地 warn,也不静默吞错。
3423
+ console.warn(`⚠️ 更新平台指针状态失败(scan_completed 可能未落盘): ${e.message}`)
3424
+ }
3407
3425
 
3408
3426
  // failed_post_check 时强制阻止 clean success
3409
3427
  if (postResult.status === 'failed_post_check') {
@@ -7,17 +7,9 @@
7
7
 
8
8
  import { existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync } from 'fs'
9
9
  import { join, basename } from 'path'
10
- import { SCAN_STATUS, CHECK_SEVERITY } from './constants.js'
10
+ import { SCAN_STATUS, CHECK_SEVERITY, SCAN_REQUIRED_DOCS } from './constants.js'
11
11
 
12
- const REQUIRED_SCAN_DOCS = [
13
- 'ARCHITECTURE.md',
14
- 'CONVENTIONS.md',
15
- 'STRUCTURE.md',
16
- 'INTEGRATIONS.md',
17
- 'TESTING.md',
18
- 'CONCERNS.md',
19
- 'PROJECT.md',
20
- ]
12
+ const REQUIRED_SCAN_DOCS = SCAN_REQUIRED_DOCS
21
13
 
22
14
  /**
23
15
  * @param {object} opts
@@ -9,6 +9,7 @@ import { existsSync, readdirSync, readFileSync } from 'fs'
9
9
  import { join, basename } from 'path'
10
10
  import { execFileSync } from 'child_process'
11
11
  import { detectChangeRisk, checkIntegrationEvidence } from './change-risk-profile.js'
12
+ import { SCAN_REQUIRED_DOCS } from './constants.js'
12
13
 
13
14
  /**
14
15
  * 校验结果
@@ -215,15 +216,7 @@ function validateScanOutputs(cwd, changeName, context = {}) {
215
216
  ? join(specBase, 'docs', projectName, 'scan')
216
217
  : join(specBase, 'docs', 'scan')
217
218
 
218
- const requiredDocs = [
219
- 'ARCHITECTURE.md',
220
- 'CONVENTIONS.md',
221
- 'STRUCTURE.md',
222
- 'INTEGRATIONS.md',
223
- 'TESTING.md',
224
- 'CONCERNS.md',
225
- 'PROJECT.md',
226
- ]
219
+ const requiredDocs = SCAN_REQUIRED_DOCS
227
220
 
228
221
  const errors = []
229
222
  const warnings = []
@@ -25,7 +25,7 @@ export const definition = {
25
25
  3. **检查变更名称**:如果当前变更名是自动生成的(如 \`2026-06-02-new-change-a3f2b7c1\`),直接重命名为有意义名称,然后运行 \`sillyspec change-rename <旧名> <新名>\`
26
26
 
27
27
  ### 加载上下文
28
- 4. 读取 CODEBASE-OVERVIEW.md + 共享规范 + 子项目上下文
28
+ 4. 读取项目总览 \`.sillyspec/docs/<project>/scan/PROJECT.md\`(如存在)+ 共享规范 + 子项目上下文
29
29
  5. 加载项目信息:\`cat .sillyspec/projects/*.yaml 2>/dev/null\`
30
30
  6. 加载本地配置:\`cat .sillyspec/local.yaml 2>/dev/null\`
31
31
  7. 棕地项目:读取 scan 下的 STRUCTURE.md、CONVENTIONS.md、ARCHITECTURE.md