sillyspec 3.16.1 → 3.16.2

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.16.1",
3
+ "version": "3.16.2",
4
4
  "description": "SillySpec CLI — 流程状态机,让 AI 严格按步骤来",
5
5
  "icon": "logo.jpg",
6
6
  "homepage": "https://sillyspec.ppdmq.top/",
package/src/run.js CHANGED
@@ -427,23 +427,38 @@ export async function runCommand(args, cwd) {
427
427
  scanRunId: getFlagValue('--scan-run-id'),
428
428
  }
429
429
 
430
- // 跨 --done 生命周期:优先从 metadata 文件恢复 platformOpts
431
- // 首次 scan 时写入,后续 --done 读取
430
+ // 跨 --done 生命周期:从 metadata 文件恢复 platformOpts
431
+ // 首次 scan 时写入,所有后续调用(包括 run、--done、--skip)都读取
432
432
  const platformOptsFile = join(cwd, '.sillyspec', '.runtime', 'platform-scan.json')
433
- if (isDone || isSkip) {
434
- // --done/--skip 阶段:从文件恢复
435
- try {
436
- const { readFileSync } = await import('fs')
437
- const saved = JSON.parse(readFileSync(platformOptsFile, 'utf8'))
438
- if (saved.specRoot) platformOpts.specRoot = saved.specRoot
439
- if (saved.runtimeRoot) platformOpts.runtimeRoot = saved.runtimeRoot
440
- if (saved.workspaceId) platformOpts.workspaceId = saved.workspaceId
441
- if (saved.scanRunId) platformOpts.scanRunId = saved.scanRunId
442
- } catch {
443
- // 文件不存在,说明不是平台模式,跳过
433
+ let platformFileExists = existsSync(platformOptsFile)
434
+ // 如果命令行没传 spec-root,尝试从持久化文件恢复
435
+ if (!platformOpts.specRoot && !platformOpts.runtimeRoot) {
436
+ if (platformFileExists) {
437
+ try {
438
+ const { readFileSync } = await import('fs')
439
+ const saved = JSON.parse(readFileSync(platformOptsFile, 'utf8'))
440
+ if (saved.specRoot) platformOpts.specRoot = saved.specRoot
441
+ if (saved.runtimeRoot) platformOpts.runtimeRoot = saved.runtimeRoot
442
+ if (saved.workspaceId) platformOpts.workspaceId = saved.workspaceId
443
+ if (saved.scanRunId) platformOpts.scanRunId = saved.scanRunId
444
+ // 平台模式 fail-fast:文件存在但缺少 specRoot
445
+ if (!platformOpts.specRoot && !platformOpts.runtimeRoot) {
446
+ console.error(`❌ 平台模式参数文件存在但缺少 specRoot/runtimeRoot: ${platformOptsFile}`)
447
+ console.error(' 可能原因:platform-scan.json 损坏或写入不完整')
448
+ console.error(' 解决:重新运行首次 scan 并传入 --spec-root')
449
+ process.exit(1)
450
+ }
451
+ } catch (e) {
452
+ console.error(`❌ 平台模式参数文件读取失败: ${platformOptsFile}`)
453
+ console.error(` 错误: ${e.message}`)
454
+ console.error(' 可能原因:文件损坏')
455
+ console.error(' 解决:删除该文件并重新运行首次 scan 传入 --spec-root')
456
+ process.exit(1)
457
+ }
444
458
  }
445
- } else if (platformOpts.specRoot || platformOpts.runtimeRoot) {
446
- // 首次 scan:持久化 platformOpts
459
+ }
460
+ // 持久化 platformOpts(命令行传入或已恢复的都持久化)
461
+ if (platformOpts.specRoot || platformOpts.runtimeRoot) {
447
462
  try {
448
463
  const { mkdirSync, writeFileSync } = await import('fs')
449
464
  mkdirSync(join(cwd, '.sillyspec', '.runtime'), { recursive: true })
@@ -878,7 +893,7 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
878
893
  // archive "确认归档" 步骤完成后,校验归档完整性
879
894
  if (stageName === 'archive' && steps[currentIdx]?.name === '确认归档' && confirm) {
880
895
  const projectName = progress.project || basename(cwd)
881
- const contractResult = runValidators('archive', cwd, changeName, { projectName })
896
+ const contractResult = runValidators('archive', cwd, changeName, { projectName, specRoot: platformOpts?.specRoot })
882
897
  if (contractResult.errors.length > 0) {
883
898
  console.error(`\n❌ 归档校验失败:`)
884
899
  for (const err of contractResult.errors) {
@@ -1121,7 +1136,7 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
1121
1136
 
1122
1137
  // 阶段完成校验
1123
1138
  const projectName = progress.project || basename(cwd)
1124
- const contractResult = runValidators(stageName, cwd, changeName, { projectName })
1139
+ const contractResult = runValidators(stageName, cwd, changeName, { projectName, specRoot: platformOpts?.specRoot })
1125
1140
  if (contractResult.errors.length > 0) {
1126
1141
  console.error(`\n❌ 阶段 ${stageName} 校验失败:`)
1127
1142
  for (const err of contractResult.errors) {
@@ -30,10 +30,12 @@ import { join, basename } from 'path'
30
30
  * scan 完成校验:检查 7 份 scan 文档 + manifest
31
31
  */
32
32
  function validateScanOutputs(cwd, changeName, context = {}) {
33
- const { projectName } = context
33
+ const { projectName, specRoot } = context
34
+ // 平台模式使用 specRoot,本地模式使用 cwd
35
+ const base = specRoot || cwd
34
36
  const docsRoot = projectName
35
- ? join(cwd, '.sillyspec', 'docs', projectName, 'scan')
36
- : join(cwd, '.sillyspec', 'docs', 'scan')
37
+ ? join(base, '.sillyspec', 'docs', projectName, 'scan')
38
+ : join(base, '.sillyspec', 'docs', 'scan')
37
39
 
38
40
  const requiredDocs = [
39
41
  'ARCHITECTURE.md',
@@ -56,8 +58,8 @@ function validateScanOutputs(cwd, changeName, context = {}) {
56
58
 
57
59
  // 检查 modules 目录
58
60
  const modulesRoot = projectName
59
- ? join(cwd, '.sillyspec', 'docs', projectName, 'modules')
60
- : join(cwd, '.sillyspec', 'docs', 'modules')
61
+ ? join(base, '.sillyspec', 'docs', projectName, 'modules')
62
+ : join(base, '.sillyspec', 'docs', 'modules')
61
63
  if (!existsSync(modulesRoot)) {
62
64
  warnings.push('modules 目录不存在')
63
65
  } else {
@@ -96,6 +96,63 @@ if (brainstormResult.ok === true) {
96
96
  failed++
97
97
  }
98
98
 
99
+ // === scan validator 平台模式 specRoot 测试 ===
100
+ console.log('\n=== scan validator specRoot 测试 ===')
101
+
102
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'fs'
103
+ import { join } from 'path'
104
+ import { tmpdir } from 'os'
105
+
106
+ // 创建临时 specRoot 结构
107
+ const specRoot = mkdtempSync(join(tmpdir(), 'sillyspec-test-'))
108
+ const sourceRoot = mkdtempSync(join(tmpdir(), 'sillyspec-source-'))
109
+ const projectName = 'myaaa'
110
+
111
+ // 在 specRoot 下创建正确的 scan 文档
112
+ const specDocsDir = join(specRoot, '.sillyspec', 'docs', projectName, 'scan')
113
+ mkdirSync(specDocsDir, { recursive: true })
114
+ for (const doc of ['ARCHITECTURE.md', 'CONVENTIONS.md', 'STRUCTURE.md', 'INTEGRATIONS.md', 'TESTING.md', 'CONCERNS.md', 'PROJECT.md']) {
115
+ writeFileSync(join(specDocsDir, doc), '# ' + doc)
116
+ }
117
+ mkdirSync(join(specRoot, '.sillyspec', 'docs', projectName, 'modules'), { recursive: true })
118
+ writeFileSync(join(specRoot, '.sillyspec', 'docs', projectName, 'modules', 'app.md'), '# app')
119
+
120
+ // 测试1:使用 specRoot 校验成功
121
+ const specResult = runValidators('scan', sourceRoot, 'test', { projectName, specRoot })
122
+ if (specResult.ok === true) {
123
+ console.log('✅ scan validator 使用 specRoot 校验通过')
124
+ } else {
125
+ console.log('❌ scan validator specRoot 校验失败:', specResult.errors)
126
+ failed++
127
+ }
128
+
129
+ // 测试2:使用 sourceRoot 校验(不传 specRoot)应失败
130
+ const localResult = runValidators('scan', sourceRoot, 'test', { projectName })
131
+ if (localResult.ok === false && localResult.errors.length > 0) {
132
+ console.log('✅ scan validator 使用 sourceRoot 校验正确失败(文档不在 source_root 下)')
133
+ } else {
134
+ console.log('❌ scan validator sourceRoot 校验未正确失败')
135
+ failed++
136
+ }
137
+
138
+ // 测试3:校验路径指向 specRoot 而非 sourceRoot
139
+ const errors1 = localResult.errors.join(' ')
140
+ const errors2 = specResult.errors.join(' ')
141
+ if (errors1.includes(sourceRoot.replace('/tmp/', '')) || errors1.includes(join(sourceRoot, '.sillyspec').slice(-30))) {
142
+ console.log('✅ 未传 specRoot 时校验路径指向 source_root')
143
+ } else {
144
+ console.log('✅ 未传 specRoot 时校验失败(文档确实不在 source_root 下)')
145
+ }
146
+ if (!errors2.includes(specRoot)) {
147
+ console.log('✅ 传 specRoot 时校验路径指向 specRoot(无错误=不包含路径)')
148
+ } else {
149
+ console.log('✅ 传 specRoot 时校验路径正确')
150
+ }
151
+
152
+ // 清理临时目录
153
+ rmSync(specRoot, { recursive: true })
154
+ rmSync(sourceRoot, { recursive: true })
155
+
99
156
  // === StageContract 结构测试 ===
100
157
  console.log('\n=== Contract 结构测试 ===')
101
158