sillyspec 3.17.12 → 3.17.14
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 +30 -15
- package/src/stages/scan.js +5 -0
package/package.json
CHANGED
package/src/run.js
CHANGED
|
@@ -1073,9 +1073,7 @@ export async function runCommand(args, cwd, specDir = null) {
|
|
|
1073
1073
|
}
|
|
1074
1074
|
|
|
1075
1075
|
const isAuxiliary = auxiliaryStages.includes(stageName)
|
|
1076
|
-
// scan
|
|
1077
|
-
let _scanProjectListParsed = undefined // undefined=未到达step2, true=解析成功, false=解析失败
|
|
1078
|
-
let _scanManifestWritten = undefined // undefined=未尝试, true=成功, false=失败
|
|
1076
|
+
// scan 元数据追踪(存储在 stageData.scanMeta 中,completeStep 通过 progress 访问)
|
|
1079
1077
|
|
|
1080
1078
|
const pm = new ProgressManager({ specDir: specRoot })
|
|
1081
1079
|
let progress = await pm.read(cwd, changeName)
|
|
@@ -1725,37 +1723,42 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
1725
1723
|
if (stageName === 'scan' && steps[currentIdx]?.name === '构建扫描项目列表') {
|
|
1726
1724
|
// 解析项目列表:从 step 2 输出提取,或回退读取 projects/*.yaml
|
|
1727
1725
|
let projectNames = []
|
|
1726
|
+
// 项目名清洗:只保留 ASCII 字母/数字/横线/下划线/点,过滤中文和特殊字符
|
|
1727
|
+
const sanitizeProjectName = (name) => {
|
|
1728
|
+
const clean = name.replace(/[^a-zA-Z0-9_\-.]/g, '').trim()
|
|
1729
|
+
return clean || null
|
|
1730
|
+
}
|
|
1728
1731
|
if (outputText) {
|
|
1729
1732
|
// 匹配方式 1: "1. project-name" 编号列表
|
|
1730
1733
|
const numbered = outputText.match(/^\s*\d+\.\s+(\S+)/gm)
|
|
1731
1734
|
if (numbered) {
|
|
1732
|
-
|
|
1733
|
-
|
|
1735
|
+
const raw = numbered.map(m => m.replace(/^\s*\d+\.\s+/, '').replace(/[—\-:].*$/, '').trim())
|
|
1736
|
+
projectNames = raw.map(sanitizeProjectName).filter(Boolean)
|
|
1737
|
+
if (projectNames.length > 0) { stageData.scanMeta = stageData.scanMeta || {}; stageData.scanMeta.projectListParsed = true; }
|
|
1734
1738
|
}
|
|
1735
1739
|
// 匹配方式 2: 括号枚举 "子项目frontend/order-service/user-service" 或 "项目: a, b, c"
|
|
1736
1740
|
if (projectNames.length === 0) {
|
|
1737
1741
|
const parenMatch = outputText.match(/(?:子项目|项目)[\s::]*(\S+(?:[\/、,,]+\S+)*)/)
|
|
1738
1742
|
if (parenMatch) {
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
.filter(Boolean)
|
|
1743
|
-
if (projectNames.length > 0) { _scanProjectListParsed = true; stageData.scanMeta = stageData.scanMeta || {}; stageData.scanMeta.projectListParsed = true; }
|
|
1743
|
+
const raw = parenMatch[1].split(/[\/、,,]+/).map(s => s.trim()).filter(Boolean)
|
|
1744
|
+
projectNames = raw.map(sanitizeProjectName).filter(Boolean)
|
|
1745
|
+
if (projectNames.length > 0) { stageData.scanMeta = stageData.scanMeta || {}; stageData.scanMeta.projectListParsed = true; }
|
|
1744
1746
|
}
|
|
1745
1747
|
}
|
|
1746
1748
|
// 匹配方式 3: 结构化 YAML block "scan_projects:\n - id: name"
|
|
1747
1749
|
if (projectNames.length === 0) {
|
|
1748
1750
|
const yamlMatch = outputText.match(/scan_projects:\s*\n((?:\s+-\s+id:\s+\S+\s*\n?)+)/)
|
|
1749
1751
|
if (yamlMatch) {
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
+
const raw = [...yamlMatch[1].matchAll(/-\s+id:\s*(\S+)/g)].map(m => m[1])
|
|
1753
|
+
projectNames = raw.map(sanitizeProjectName).filter(Boolean)
|
|
1754
|
+
if (projectNames.length > 0) { stageData.scanMeta = stageData.scanMeta || {}; stageData.scanMeta.projectListParsed = true; }
|
|
1752
1755
|
}
|
|
1753
1756
|
}
|
|
1754
1757
|
}
|
|
1755
1758
|
if (projectNames.length === 0) {
|
|
1756
1759
|
// 回退:读取所有已注册项目
|
|
1757
1760
|
console.warn('⚠️ 未能从 step 2 输出解析项目列表,回退扫描所有注册项目')
|
|
1758
|
-
|
|
1761
|
+
stageData.scanMeta = stageData.scanMeta || {}; stageData.scanMeta.projectListParsed = false;
|
|
1759
1762
|
const projectsDir = join(specBase, 'projects')
|
|
1760
1763
|
if (existsSync(projectsDir)) {
|
|
1761
1764
|
projectNames = readdirSync(projectsDir)
|
|
@@ -1859,6 +1862,18 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
1859
1862
|
if (steps[wsIdx].waitReason) console.log(` 原因:${steps[wsIdx].waitReason}`)
|
|
1860
1863
|
return { stageCompleted: false, currentIdx, nextPendingIdx: -1 }
|
|
1861
1864
|
}
|
|
1865
|
+
// quick 阶段完成前强制检查 quicklog 是否创建
|
|
1866
|
+
if (stageName === 'quick') {
|
|
1867
|
+
const quicklogDir = join(specBase, '.sillyspec', 'quicklog')
|
|
1868
|
+
const hasQuicklog = existsSync(quicklogDir) && readdirSync(quicklogDir).some(f => f.endsWith('.md') && f.startsWith('QUICKLOG'))
|
|
1869
|
+
if (!hasQuicklog) {
|
|
1870
|
+
console.error(`\n❌ quick 阶段完成校验失败:未检测到 QUICKLOG 记录文件。`)
|
|
1871
|
+
console.error(` step 2 要求创建 quicklog 记录,但文件不存在。`)
|
|
1872
|
+
console.error(` 请先创建 quicklog 记录再 --done,或使用 --skip-approval 跳过此校验。`)
|
|
1873
|
+
return { stageCompleted: false, currentIdx, nextPendingIdx: -1 }
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1862
1877
|
stageData.status = 'completed'
|
|
1863
1878
|
stageData.completedAt = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
1864
1879
|
progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
@@ -1875,7 +1890,7 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
1875
1890
|
// 平台模式:scan 完成后生成 manifest.json + post-check
|
|
1876
1891
|
if (stageName === 'scan' && (platformOpts.specRoot || platformOpts.runtimeRoot)) {
|
|
1877
1892
|
try {
|
|
1878
|
-
|
|
1893
|
+
stageData.scanMeta = stageData.scanMeta || {}; stageData.scanMeta.manifestWritten = false; // 默认失败
|
|
1879
1894
|
const { mkdirSync, writeFileSync } = await import('fs')
|
|
1880
1895
|
const { join } = await import('path')
|
|
1881
1896
|
const { execSync } = await import('child_process')
|
|
@@ -1896,7 +1911,7 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
1896
1911
|
const manifestPath = join(manifestDir, 'manifest.json')
|
|
1897
1912
|
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n')
|
|
1898
1913
|
console.log(`📄 manifest.json 已写入: ${manifestPath}`)
|
|
1899
|
-
|
|
1914
|
+
stageData.scanMeta = stageData.scanMeta || {}; stageData.scanMeta.manifestWritten = true;
|
|
1900
1915
|
if (!sourceCommit) {
|
|
1901
1916
|
console.log(`⚠️ source_commit 无法获取(可能非 git 目录),已设为 null`)
|
|
1902
1917
|
}
|
package/src/stages/scan.js
CHANGED
|
@@ -462,6 +462,11 @@ step1 → step2 → step3
|
|
|
462
462
|
9. 检查 7 份文档 header 是否包含 author 和 created_at
|
|
463
463
|
10. 检查 local.yaml 中 commands 是否在 package.json scripts 中真实存在,不存在的必须标记 unavailable
|
|
464
464
|
|
|
465
|
+
### ⛔ API 错误处理
|
|
466
|
+
- 遇到 API Error 529(服务过载)或 rate_limit 时,**停止当前操作并报告**,不要自动重试
|
|
467
|
+
- 遇到 tool_use_error 时,记录错误信息并跳过该文件/操作,继续处理下一项
|
|
468
|
+
- 如果连续 3 次操作失败,输出失败摘要并停止
|
|
469
|
+
|
|
465
470
|
### ⛔ 最终状态判定
|
|
466
471
|
如果出现以下**任意**情况,最终状态**不能**写"全部通过",只能写 \`completed_with_warnings\` 或 \`failed_post_check\`:
|
|
467
472
|
- 源码目录下存在 docs(路径合规检查失败)
|