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 +1 -1
- package/src/run.js +34 -0
- package/src/scan-postcheck.js +24 -2
package/package.json
CHANGED
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
|
|
package/src/scan-postcheck.js
CHANGED
|
@@ -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.
|
|
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
|
|