sillyspec 3.17.3 → 3.17.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sillyspec",
3
- "version": "3.17.3",
3
+ "version": "3.17.4",
4
4
  "description": "SillySpec CLI — 流程状态机,让 AI 严格按步骤来",
5
5
  "icon": "logo.jpg",
6
6
  "homepage": "https://sillyspec.ppdmq.top/",
package/src/run.js CHANGED
@@ -605,6 +605,9 @@ export async function runCommand(args, cwd, specDir = null) {
605
605
  }
606
606
 
607
607
  const isAuxiliary = auxiliaryStages.includes(stageName)
608
+ // scan 元数据追踪(供 postcheck 使用)
609
+ let _scanProjectListParsed = undefined // undefined=未到达step2, true=解析成功, false=解析失败
610
+ let _scanManifestWritten = undefined // undefined=未尝试, true=成功, false=失败
608
611
 
609
612
  const pm = new ProgressManager({ specDir: specRoot })
610
613
  let progress = await pm.read(cwd, changeName)
@@ -1024,6 +1027,7 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
1024
1027
  const numbered = outputText.match(/^\s*\d+\.\s+(\S+)/gm)
1025
1028
  if (numbered) {
1026
1029
  projectNames = numbered.map(m => m.replace(/^\s*\d+\.\s+/, '').replace(/[—\-:].*$/, '').trim())
1030
+ if (projectNames.length > 0) _scanProjectListParsed = true
1027
1031
  }
1028
1032
  // 匹配方式 2: 括号枚举 "子项目frontend/order-service/user-service" 或 "项目: a, b, c"
1029
1033
  if (projectNames.length === 0) {
@@ -1033,6 +1037,7 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
1033
1037
  .split(/[\/、,,]+/)
1034
1038
  .map(s => s.trim())
1035
1039
  .filter(Boolean)
1040
+ if (projectNames.length > 0) _scanProjectListParsed = true
1036
1041
  }
1037
1042
  }
1038
1043
  // 匹配方式 3: 结构化 YAML block "scan_projects:\n - id: name"
@@ -1040,12 +1045,14 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
1040
1045
  const yamlMatch = outputText.match(/scan_projects:\s*\n((?:\s+-\s+id:\s+\S+\s*\n?)+)/)
1041
1046
  if (yamlMatch) {
1042
1047
  projectNames = [...yamlMatch[1].matchAll(/-\s+id:\s*(\S+)/g)].map(m => m[1])
1048
+ if (projectNames.length > 0) _scanProjectListParsed = true
1043
1049
  }
1044
1050
  }
1045
1051
  }
1046
1052
  if (projectNames.length === 0) {
1047
1053
  // 回退:读取所有已注册项目
1048
1054
  console.warn('⚠️ 未能从 step 2 输出解析项目列表,回退扫描所有注册项目')
1055
+ _scanProjectListParsed = false
1049
1056
  const projectsDir = join(specBase, 'projects')
1050
1057
  if (existsSync(projectsDir)) {
1051
1058
  projectNames = readdirSync(projectsDir)
@@ -1057,6 +1064,27 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
1057
1064
  projectNames = ['sillyspec'] // 最终兜底
1058
1065
  }
1059
1066
 
1067
+ // 自动注册未注册的子项目(确保 projects/*.yaml 存在,避免展开时 projectRoot 缺失)
1068
+ const projectsDir = join(specBase, 'projects')
1069
+ for (const pName of projectNames) {
1070
+ const projYaml = join(projectsDir, `${pName}.yaml`)
1071
+ if (!existsSync(projYaml)) {
1072
+ mkdirSync(projectsDir, { recursive: true })
1073
+ // 子项目路径推测:检查 cwd 下是否有同名目录
1074
+ const candidates = [
1075
+ join(cwd, pName), // cwd/frontend
1076
+ join(cwd, 'backend', pName), // cwd/backend/user-service
1077
+ join(cwd, 'packages', pName), // monorepo packages
1078
+ join(cwd, 'apps', pName), // monorepo apps
1079
+ join(cwd, 'services', pName), // monorepo services
1080
+ ]
1081
+ const detected = candidates.find(c => existsSync(c))
1082
+ const regPath = detected || join(cwd, pName)
1083
+ writeFileSync(projYaml, `name: ${pName}\npath: ${regPath}\nstatus: active\n`)
1084
+ console.log(` 📝 自动注册子项目: ${pName} → ${regPath}`)
1085
+ }
1086
+ }
1087
+
1060
1088
  // 保存到 runtime 供后续使用 + 防重复展开
1061
1089
  const scanStatePath = join(specBase, '.runtime', 'scan-projects.json')
1062
1090
  mkdirSync(join(specBase, '.runtime'), { recursive: true })
@@ -1133,6 +1161,7 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
1133
1161
  // 平台模式:scan 完成后生成 manifest.json + post-check
1134
1162
  if (stageName === 'scan' && (platformOpts.specRoot || platformOpts.runtimeRoot)) {
1135
1163
  try {
1164
+ _scanManifestWritten = false // 默认失败
1136
1165
  const { mkdirSync, writeFileSync } = await import('fs')
1137
1166
  const { join } = await import('path')
1138
1167
  const { execSync } = await import('child_process')
@@ -1152,6 +1181,7 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
1152
1181
  const manifestPath = join(manifestDir, 'manifest.json')
1153
1182
  writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n')
1154
1183
  console.log(`📄 manifest.json 已写入: ${manifestPath}`)
1184
+ _scanManifestWritten = true
1155
1185
  if (!sourceCommit) {
1156
1186
  console.log(`⚠️ source_commit 无法获取(可能非 git 目录),已设为 null`)
1157
1187
  }
@@ -1166,6 +1196,10 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
1166
1196
  cwd,
1167
1197
  specDir: platformOpts.specRoot,
1168
1198
  outputText,
1199
+ scanMeta: {
1200
+ projectListParsed: _scanProjectListParsed,
1201
+ manifestWritten: _scanManifestWritten,
1202
+ },
1169
1203
  })
1170
1204
  printScanPostCheckResult(postResult)
1171
1205
 
@@ -23,9 +23,13 @@ const REQUIRED_SCAN_DOCS = [
23
23
  * @param {string} opts.cwd - 源码项目根目录 (source_root)
24
24
  * @param {string} opts.specDir - 规范目录 (spec-root),null 时为非平台模式
25
25
  * @param {string} [opts.outputText] - 最后一步(自检)的 AI 输出文本
26
+ * @param {object} [opts.scanMeta] - scan 元数据(由 runCommand 传入)
27
+ * @param {boolean} [opts.scanMeta.projectListParsed] - Step 2 项目列表是否成功解析
28
+ * @param {boolean} [opts.scanMeta.manifestWritten] - manifest.json 是否写入成功
29
+ * @param {number} [opts.scanMeta.projectCount] - 实际展开的项目数量
26
30
  * @returns {{ status: 'success'|'completed_with_warnings'|'failed_post_check', checks: Array<{name, severity, detail}> }}
27
31
  */
28
- export function runScanPostCheck({ cwd, specDir, outputText = '' }) {
32
+ export function runScanPostCheck({ cwd, specDir, outputText = '', scanMeta = {} } ) {
29
33
  const isPlatform = !!specDir
30
34
  const checks = []
31
35
 
@@ -146,7 +150,25 @@ export function runScanPostCheck({ cwd, specDir, outputText = '' }) {
146
150
  }
147
151
  }
148
152
 
149
- // 6. 计算 finalStatus
153
+ // 6. manifest 写入状态检查
154
+ if (scanMeta.manifestWritten === false) {
155
+ checks.push({
156
+ name: 'manifest_write_failed',
157
+ severity: 'failed',
158
+ detail: 'manifest.json 写入失败,平台无法消费 scan 结果'
159
+ })
160
+ }
161
+
162
+ // 7. 项目列表解析状态检查
163
+ if (scanMeta.projectListParsed === false) {
164
+ checks.push({
165
+ name: 'project_list_parse_failed',
166
+ severity: 'warning',
167
+ detail: 'Step 2 项目列表解析失败,回退到注册项目列表,可能遗漏子项目'
168
+ })
169
+ }
170
+
171
+ // 8. 计算 finalStatus
150
172
  const hasFailed = checks.some(c => c.severity === 'failed')
151
173
  const hasWarning = checks.some(c => c.severity === 'warning')
152
174