sillyspec 3.20.2 → 3.20.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.
Files changed (133) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +21 -21
  2. package/.claude/skills/sillyspec-auto/SKILL.md +83 -83
  3. package/.claude/skills/sillyspec-brainstorm/SKILL.md +44 -44
  4. package/.claude/skills/sillyspec-commit/SKILL.md +106 -106
  5. package/.claude/skills/sillyspec-continue/SKILL.md +45 -45
  6. package/.claude/skills/sillyspec-doctor/SKILL.md +31 -31
  7. package/.claude/skills/sillyspec-execute/SKILL.md +30 -30
  8. package/.claude/skills/sillyspec-explore/SKILL.md +109 -109
  9. package/.claude/skills/sillyspec-knowledge/SKILL.md +269 -269
  10. package/.claude/skills/sillyspec-plan/SKILL.md +21 -21
  11. package/.claude/skills/sillyspec-propose/SKILL.md +21 -21
  12. package/.claude/skills/sillyspec-quick/SKILL.md +21 -21
  13. package/.claude/skills/sillyspec-resume/SKILL.md +68 -68
  14. package/.claude/skills/sillyspec-scan/SKILL.md +21 -21
  15. package/.claude/skills/sillyspec-state/SKILL.md +54 -54
  16. package/.claude/skills/sillyspec-status/SKILL.md +21 -21
  17. package/.claude/skills/sillyspec-verify/SKILL.md +21 -21
  18. package/.claude/skills/sillyspec-workspace/SKILL.md +157 -157
  19. package/.husky/pre-push +13 -13
  20. package/CLAUDE.md +18 -18
  21. package/README.md +198 -188
  22. package/SKILL.md +90 -91
  23. package/bin/sillyspec.js +2 -2
  24. package/docs/brainstorm-plan-contract.md +64 -64
  25. package/docs/plan-execute-contract.md +123 -123
  26. package/docs/platform-scan-protocol.md +298 -298
  27. package/docs/revision-mode.md +115 -115
  28. package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +99 -99
  29. package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +218 -218
  30. package/docs/sillyspec/file-lifecycle/stage-artifacts.md +167 -167
  31. package/docs/sillyspec/file-lifecycle/storage-and-state.md +148 -148
  32. package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +211 -193
  33. package/docs/sillyspec/file-lifecycle.md +125 -125
  34. package/docs/workflow-contract-regression.md +106 -106
  35. package/docs/worktree-isolation.md +252 -252
  36. package/package.json +40 -40
  37. package/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -7446
  38. package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -1
  39. package/packages/dashboard/dist/index.html +16 -16
  40. package/packages/dashboard/index.html +15 -15
  41. package/packages/dashboard/package-lock.json +2384 -2384
  42. package/packages/dashboard/package.json +25 -25
  43. package/packages/dashboard/server/executor.js +86 -86
  44. package/packages/dashboard/server/index.js +588 -588
  45. package/packages/dashboard/server/parser.js +526 -526
  46. package/packages/dashboard/server/watcher.js +344 -344
  47. package/packages/dashboard/src/App.vue +558 -558
  48. package/packages/dashboard/src/components/ActionBar.vue +93 -93
  49. package/packages/dashboard/src/components/CommandPalette.vue +96 -96
  50. package/packages/dashboard/src/components/DetailPanel.vue +137 -137
  51. package/packages/dashboard/src/components/LogStream.vue +65 -65
  52. package/packages/dashboard/src/components/PipelineStage.vue +95 -95
  53. package/packages/dashboard/src/components/PipelineView.vue +156 -156
  54. package/packages/dashboard/src/components/ProjectList.vue +210 -210
  55. package/packages/dashboard/src/components/StageBadge.vue +67 -67
  56. package/packages/dashboard/src/components/StepCard.vue +94 -94
  57. package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -48
  58. package/packages/dashboard/src/components/detail/GitDetail.vue +61 -61
  59. package/packages/dashboard/src/components/detail/TechDetail.vue +43 -43
  60. package/packages/dashboard/src/composables/useDashboard.js +170 -170
  61. package/packages/dashboard/src/composables/useKeyboard.js +119 -119
  62. package/packages/dashboard/src/composables/useWebSocket.js +129 -129
  63. package/packages/dashboard/src/main.js +8 -8
  64. package/packages/dashboard/src/style.css +132 -132
  65. package/packages/dashboard/vite.config.js +18 -18
  66. package/src/brainstorm-postcheck.js +158 -158
  67. package/src/change-list.js +52 -52
  68. package/src/change-risk-profile.js +352 -352
  69. package/src/classify-change.js +73 -73
  70. package/src/constants.js +70 -70
  71. package/src/contract-matrix.js +278 -278
  72. package/src/db.js +201 -201
  73. package/src/endpoint-extractor.js +315 -315
  74. package/src/hooks/claude-pre-tool-use.cjs +125 -125
  75. package/src/hooks/worktree-guard.js +653 -653
  76. package/src/index.js +922 -900
  77. package/src/init.js +431 -431
  78. package/src/knowledge-match.js +130 -130
  79. package/src/migrate.js +117 -117
  80. package/src/modules.js +482 -482
  81. package/src/progress.js +1734 -1734
  82. package/src/run.js +3465 -3358
  83. package/src/scan-postcheck.js +387 -383
  84. package/src/setup.js +398 -398
  85. package/src/stage-contract.js +700 -700
  86. package/src/stages/archive.js +160 -160
  87. package/src/stages/brainstorm-auto.js +229 -229
  88. package/src/stages/brainstorm.js +645 -645
  89. package/src/stages/doctor.js +365 -365
  90. package/src/stages/execute.js +625 -625
  91. package/src/stages/explore.js +34 -34
  92. package/src/stages/index.js +29 -29
  93. package/src/stages/knowledge.js +498 -498
  94. package/src/stages/plan-postcheck.js +511 -513
  95. package/src/stages/plan.js +582 -582
  96. package/src/stages/propose.js +174 -174
  97. package/src/stages/quick.js +82 -82
  98. package/src/stages/scan.js +558 -558
  99. package/src/stages/status.js +65 -65
  100. package/src/stages/verify.js +322 -322
  101. package/src/sync.js +497 -497
  102. package/src/task-review.js +346 -346
  103. package/src/workflow.js +785 -785
  104. package/src/worktree-apply.js +549 -549
  105. package/src/worktree-deps.js +185 -0
  106. package/src/worktree.js +982 -932
  107. package/templates/workflows/archive-impact.yaml +79 -79
  108. package/templates/workflows/scan-docs.yaml +132 -132
  109. package/test/brainstorm-plan-contract.test.mjs +273 -273
  110. package/test/check-syntax.mjs +26 -26
  111. package/test/contract-artifacts.test.mjs +323 -323
  112. package/test/decision-supersede.test.mjs +277 -277
  113. package/test/knowledge-match.test.mjs +231 -231
  114. package/test/plan-execute-contract.test.mjs +330 -330
  115. package/test/plan-optimization.test.mjs +572 -572
  116. package/test/platform-artifacts.test.mjs +166 -166
  117. package/test/platform-failure-samples.test.mjs +199 -199
  118. package/test/platform-recovery-chain.test.mjs +167 -167
  119. package/test/platform-recovery.test.mjs +136 -136
  120. package/test/platform-scan-p0.test.mjs +168 -168
  121. package/test/revision-v1.test.mjs +1145 -1145
  122. package/test/run-scan-project-parse.test.mjs +200 -200
  123. package/test/run-tests.mjs +48 -48
  124. package/test/scan-knowledge.test.mjs +175 -175
  125. package/test/scan-paths.test.mjs +68 -68
  126. package/test/scan-postcheck.test.mjs +197 -197
  127. package/test/spec-dir.test.mjs +206 -206
  128. package/test/stage-contract.test.mjs +299 -299
  129. package/test/stage-definitions.test.mjs +39 -39
  130. package/test/wait-gates.test.mjs +496 -496
  131. package/test/worktree-deps-provision.test.mjs +148 -0
  132. package/test/worktree-guard.test.mjs +71 -71
  133. package/test/worktree-native-overlay.test.mjs +188 -188
@@ -1,35 +1,35 @@
1
- /**
2
- * 平台 scan 产物协议测试
3
- *
4
- * 验证:
5
- * 1. saveWorkflowRun 传入 runtimeRoot + scanRunId 时写到正确路径
6
- * 2. manifest.json 结构包含产物指针(postcheck_result_path, workflow_runs_dir)
7
- * 3. 非平台模式下 workflow-runs 写入 cwd/.sillyspec/.runtime/
8
- *
9
- * 跑法: node test/platform-artifacts.test.mjs
10
- */
11
-
1
+ /**
2
+ * 平台 scan 产物协议测试
3
+ *
4
+ * 验证:
5
+ * 1. saveWorkflowRun 传入 runtimeRoot + scanRunId 时写到正确路径
6
+ * 2. manifest.json 结构包含产物指针(postcheck_result_path, workflow_runs_dir)
7
+ * 3. 非平台模式下 workflow-runs 写入 cwd/.sillyspec/.runtime/
8
+ *
9
+ * 跑法: node test/platform-artifacts.test.mjs
10
+ */
11
+
12
12
  import { join, dirname } from 'path'
13
13
  import { existsSync, mkdirSync, rmSync, readFileSync, readdirSync, writeFileSync } from 'fs'
14
14
  import { fileURLToPath } from 'url'
15
15
  import { randomUUID } from 'crypto'
16
16
  import { tmpdir } from 'os'
17
-
18
- const __dirname = dirname(fileURLToPath(import.meta.url))
19
- const passed = []
20
- const failed = []
21
-
22
- function assert(label, condition, detail) {
23
- if (condition) {
24
- passed.push(label)
25
- console.log(` ✅ PASS: ${label}`)
26
- } else {
27
- failed.push({ label, detail })
28
- console.log(` ❌ FAIL: ${label}`)
29
- if (detail) console.log(` ${detail}`)
30
- }
31
- }
32
-
17
+
18
+ const __dirname = dirname(fileURLToPath(import.meta.url))
19
+ const passed = []
20
+ const failed = []
21
+
22
+ function assert(label, condition, detail) {
23
+ if (condition) {
24
+ passed.push(label)
25
+ console.log(` ✅ PASS: ${label}`)
26
+ } else {
27
+ failed.push({ label, detail })
28
+ console.log(` ❌ FAIL: ${label}`)
29
+ if (detail) console.log(` ${detail}`)
30
+ }
31
+ }
32
+
33
33
  function cleanup(dir) {
34
34
  try { rmSync(dir, { recursive: true, force: true }) } catch {}
35
35
  }
@@ -41,150 +41,150 @@ function hasPathSegments(value, segments) {
41
41
  }
42
42
  return false
43
43
  }
44
-
45
- // ── 测试 1:saveWorkflowRun 平台模式路径正确 ──
46
- console.log('\n=== Test 1: saveWorkflowRun 平台模式写入路径 ===')
47
- {
44
+
45
+ // ── 测试 1:saveWorkflowRun 平台模式路径正确 ──
46
+ console.log('\n=== Test 1: saveWorkflowRun 平台模式写入路径 ===')
47
+ {
48
48
  const { saveWorkflowRun } = await import('../src/workflow.js')
49
49
  const tmpRoot = join(tmpdir(), `test-artifacts-${randomUUID().slice(0, 8)}`)
50
- const runtimeRoot = join(tmpRoot, 'runtime')
51
- const scanRunId = 'scan-20260614-test-001'
52
-
53
- const result = {
54
- workflow: 'scan-docs',
55
- project: 'test-project',
56
- status: 'pass',
57
- spec_version: 1,
58
- roles: [],
59
- workflow_checks: [],
60
- failures: [],
61
- }
62
-
63
- const saved = saveWorkflowRun(result, {
64
- cwd: '/fake/cwd',
65
- source: 'test',
66
- stage: 'scan',
67
- runtimeRoot,
68
- scanRunId,
69
- })
70
-
71
- const expectedDir = join(runtimeRoot, 'scan-runs', scanRunId, 'workflow-runs')
72
- assert('workflow-runs 目录存在', existsSync(expectedDir))
73
- assert('workflow-runs 文件存在', existsSync(saved), `路径: ${saved}`)
50
+ const runtimeRoot = join(tmpRoot, 'runtime')
51
+ const scanRunId = 'scan-20260614-test-001'
52
+
53
+ const result = {
54
+ workflow: 'scan-docs',
55
+ project: 'test-project',
56
+ status: 'pass',
57
+ spec_version: 1,
58
+ roles: [],
59
+ workflow_checks: [],
60
+ failures: [],
61
+ }
62
+
63
+ const saved = saveWorkflowRun(result, {
64
+ cwd: '/fake/cwd',
65
+ source: 'test',
66
+ stage: 'scan',
67
+ runtimeRoot,
68
+ scanRunId,
69
+ })
70
+
71
+ const expectedDir = join(runtimeRoot, 'scan-runs', scanRunId, 'workflow-runs')
72
+ assert('workflow-runs 目录存在', existsSync(expectedDir))
73
+ assert('workflow-runs 文件存在', existsSync(saved), `路径: ${saved}`)
74
74
  assert('路径在 runtime-root 下', saved.startsWith(runtimeRoot), `路径: ${saved}`)
75
75
  assert('路径包含 scan-runs', hasPathSegments(saved, ['scan-runs', scanRunId, 'workflow-runs']), `路径: ${saved}`)
76
76
  assert('路径包含 scanRunId', hasPathSegments(saved, [scanRunId]), `路径: ${saved}`)
77
-
78
- // 验证 JSON 内容
79
- const content = JSON.parse(readFileSync(saved, 'utf8'))
80
- assert('JSON 有 run_id', !!content.run_id)
81
- assert('JSON 有 created_at', !!content.created_at)
82
- assert('JSON source = test', content.source === 'test')
83
- assert('JSON stage = scan', content.stage === 'scan')
84
- assert('JSON workflow = scan-docs', content.workflow === 'scan-docs')
85
-
86
- cleanup(tmpRoot)
87
- }
88
-
89
- // ── 测试 2:saveWorkflowRun 本地模式路径正确 ──
90
- console.log('\n=== Test 2: saveWorkflowRun 本地模式写入路径 ===')
91
- {
77
+
78
+ // 验证 JSON 内容
79
+ const content = JSON.parse(readFileSync(saved, 'utf8'))
80
+ assert('JSON 有 run_id', !!content.run_id)
81
+ assert('JSON 有 created_at', !!content.created_at)
82
+ assert('JSON source = test', content.source === 'test')
83
+ assert('JSON stage = scan', content.stage === 'scan')
84
+ assert('JSON workflow = scan-docs', content.workflow === 'scan-docs')
85
+
86
+ cleanup(tmpRoot)
87
+ }
88
+
89
+ // ── 测试 2:saveWorkflowRun 本地模式路径正确 ──
90
+ console.log('\n=== Test 2: saveWorkflowRun 本地模式写入路径 ===')
91
+ {
92
92
  const { saveWorkflowRun } = await import('../src/workflow.js')
93
93
  const tmpCwd = join(tmpdir(), `test-artifacts-local-${randomUUID().slice(0, 8)}`)
94
- const sillyspecDir = join(tmpCwd, '.sillyspec', '.runtime', 'workflow-runs')
95
-
96
- const result = {
97
- workflow: 'test-wf',
98
- project: 'default',
99
- status: 'fail',
100
- spec_version: 1,
101
- roles: [],
102
- workflow_checks: [],
103
- failures: ['check-1 failed'],
104
- }
105
-
106
- const saved = saveWorkflowRun(result, {
107
- cwd: tmpCwd,
108
- source: 'test',
109
- stage: 'scan',
110
- // 不传 runtimeRoot 和 scanRunId
111
- })
112
-
113
- assert('本地模式文件存在', existsSync(saved))
94
+ const sillyspecDir = join(tmpCwd, '.sillyspec', '.runtime', 'workflow-runs')
95
+
96
+ const result = {
97
+ workflow: 'test-wf',
98
+ project: 'default',
99
+ status: 'fail',
100
+ spec_version: 1,
101
+ roles: [],
102
+ workflow_checks: [],
103
+ failures: ['check-1 failed'],
104
+ }
105
+
106
+ const saved = saveWorkflowRun(result, {
107
+ cwd: tmpCwd,
108
+ source: 'test',
109
+ stage: 'scan',
110
+ // 不传 runtimeRoot 和 scanRunId
111
+ })
112
+
113
+ assert('本地模式文件存在', existsSync(saved))
114
114
  assert('本地路径在 .sillyspec/.runtime 下', hasPathSegments(saved, ['.sillyspec', '.runtime', 'workflow-runs']), `路径: ${saved}`)
115
-
116
- const content = JSON.parse(readFileSync(saved, 'utf8'))
117
- assert('本地 JSON status = fail', content.status === 'fail')
118
- assert('本地 JSON 有 failures', Array.isArray(content.failures))
119
-
120
- cleanup(tmpCwd)
121
- }
122
-
123
- // ── 测试 3:manifest 结构验证(从 run.js 源码静态检查) ──
124
- console.log('\n=== Test 3: manifest.json 结构字段 ===')
125
- {
126
- const { readFile } = await import('fs/promises')
127
- const runSrc = await readFile(join(__dirname, '..', 'src', 'run.js'), 'utf8')
128
-
129
- // 平台模式 manifest 初始化
130
- assert('manifest 包含 workspace_id', runSrc.includes('workspace_id:'))
131
- assert('manifest 包含 scan_run_id', runSrc.includes('scan_run_id:'))
132
- assert('manifest 包含 source_commit', runSrc.includes('source_commit:'))
133
- assert('manifest 包含 source_commit_error', runSrc.includes('source_commit_error:'))
134
- assert('manifest 包含 generated_at', runSrc.includes('generated_at:'))
135
- assert('manifest 包含 schema_version', runSrc.includes('schema_version:'))
136
- assert('manifest 包含 postcheck_result_path', runSrc.includes('postcheck_result_path:'))
137
- assert('manifest 包含 workflow_runs_dir', runSrc.includes('workflow_runs_dir:'))
138
-
139
- // postcheck_result_path 在 postcheck 写入后填充
140
- assert('manifest.postcheck_result_path 下游填充', runSrc.includes('manifest.postcheck_result_path = postcheckJsonPath'))
141
-
142
- // workflow_runs_dir 使用 runtimeRoot + scanRunId
143
- assert('workflow_runs_dir 基于 runtimeRoot', runSrc.includes("join(platformOpts.runtimeRoot, 'scan-runs'"))
144
- }
145
-
146
- // ── 测试 4:平台指针状态更新(源码检查) ──
147
- console.log('\n=== Test 4: 平台指针 scan 完成后状态更新 ===')
148
- {
149
- const { readFile } = await import('fs/promises')
150
- const runSrc = await readFile(join(__dirname, '..', 'src', 'run.js'), 'utf8')
151
-
152
- assert('scan 完成后读取 pointer 文件', runSrc.includes('pointerPath'))
153
- assert('pointer status 使用 POINTER_STATUS 枚举', runSrc.includes('POINTER_STATUS'))
154
- assert('pointer 记录 completedAt', runSrc.includes('pointer.completedAt'))
155
- assert('pointer 记录 scanStatus', runSrc.includes('pointer.scanStatus'))
156
- }
157
-
158
- // ── 测试 5:saveWorkflowRun 调用点传入 runtimeRoot(源码检查) ──
159
- console.log('\n=== Test 5: run.js 调用 saveWorkflowRun 传入平台参数 ===')
160
- {
161
- const { readFile } = await import('fs/promises')
162
- const runSrc = await readFile(join(__dirname, '..', 'src', 'run.js'), 'utf8')
163
-
164
- // 找到 saveWorkflowRun 调用
165
- const calls = runSrc.match(/saveWorkflowRun\([^)]+\{[^}]+\}/gs)
166
- assert('至少有 2 处 saveWorkflowRun 调用', calls && calls.length >= 2, `实际: ${calls?.length}`)
167
-
168
- // 检查调用是否包含 runtimeRoot 传递
169
- const hasRuntimeRoot = runSrc.includes('platformOpts.runtimeRoot ? { runtimeRoot: platformOpts.runtimeRoot }')
170
- assert('saveWorkflowRun 调用传入 runtimeRoot', hasRuntimeRoot, '未发现 runtimeRoot 传递')
171
-
172
- const hasScanRunId = runSrc.includes('platformOpts.scanRunId ? { scanRunId: platformOpts.scanRunId }')
173
- assert('saveWorkflowRun 调用传入 scanRunId', hasScanRunId, '未发现 scanRunId 传递')
174
- }
175
-
176
- // ── 结果 ──
177
- console.log(`\n${'='.repeat(50)}`)
178
- console.log(`✅ 通过: ${passed.length} ❌ 失败: ${failed.length}`)
179
- console.log(`${'='.repeat(50)}`)
180
-
181
- if (failed.length > 0) {
182
- console.log('\n失败详情:')
183
- for (const f of failed) {
184
- console.log(` ❌ ${f.label}`)
185
- if (f.detail) console.log(` ${f.detail}`)
186
- }
187
- throw new Error('platform-artifacts test failed')
188
- } else {
189
- console.log('\n🎉 平台产物协议测试全部通过!')
190
- }
115
+
116
+ const content = JSON.parse(readFileSync(saved, 'utf8'))
117
+ assert('本地 JSON status = fail', content.status === 'fail')
118
+ assert('本地 JSON 有 failures', Array.isArray(content.failures))
119
+
120
+ cleanup(tmpCwd)
121
+ }
122
+
123
+ // ── 测试 3:manifest 结构验证(从 run.js 源码静态检查) ──
124
+ console.log('\n=== Test 3: manifest.json 结构字段 ===')
125
+ {
126
+ const { readFile } = await import('fs/promises')
127
+ const runSrc = await readFile(join(__dirname, '..', 'src', 'run.js'), 'utf8')
128
+
129
+ // 平台模式 manifest 初始化
130
+ assert('manifest 包含 workspace_id', runSrc.includes('workspace_id:'))
131
+ assert('manifest 包含 scan_run_id', runSrc.includes('scan_run_id:'))
132
+ assert('manifest 包含 source_commit', runSrc.includes('source_commit:'))
133
+ assert('manifest 包含 source_commit_error', runSrc.includes('source_commit_error:'))
134
+ assert('manifest 包含 generated_at', runSrc.includes('generated_at:'))
135
+ assert('manifest 包含 schema_version', runSrc.includes('schema_version:'))
136
+ assert('manifest 包含 postcheck_result_path', runSrc.includes('postcheck_result_path:'))
137
+ assert('manifest 包含 workflow_runs_dir', runSrc.includes('workflow_runs_dir:'))
138
+
139
+ // postcheck_result_path 在 postcheck 写入后填充
140
+ assert('manifest.postcheck_result_path 下游填充', runSrc.includes('manifest.postcheck_result_path = postcheckJsonPath'))
141
+
142
+ // workflow_runs_dir 使用 runtimeRoot + scanRunId
143
+ assert('workflow_runs_dir 基于 runtimeRoot', runSrc.includes("join(platformOpts.runtimeRoot, 'scan-runs'"))
144
+ }
145
+
146
+ // ── 测试 4:平台指针状态更新(源码检查) ──
147
+ console.log('\n=== Test 4: 平台指针 scan 完成后状态更新 ===')
148
+ {
149
+ const { readFile } = await import('fs/promises')
150
+ const runSrc = await readFile(join(__dirname, '..', 'src', 'run.js'), 'utf8')
151
+
152
+ assert('scan 完成后读取 pointer 文件', runSrc.includes('pointerPath'))
153
+ assert('pointer status 使用 POINTER_STATUS 枚举', runSrc.includes('POINTER_STATUS'))
154
+ assert('pointer 记录 completedAt', runSrc.includes('pointer.completedAt'))
155
+ assert('pointer 记录 scanStatus', runSrc.includes('pointer.scanStatus'))
156
+ }
157
+
158
+ // ── 测试 5:saveWorkflowRun 调用点传入 runtimeRoot(源码检查) ──
159
+ console.log('\n=== Test 5: run.js 调用 saveWorkflowRun 传入平台参数 ===')
160
+ {
161
+ const { readFile } = await import('fs/promises')
162
+ const runSrc = await readFile(join(__dirname, '..', 'src', 'run.js'), 'utf8')
163
+
164
+ // 找到 saveWorkflowRun 调用
165
+ const calls = runSrc.match(/saveWorkflowRun\([^)]+\{[^}]+\}/gs)
166
+ assert('至少有 2 处 saveWorkflowRun 调用', calls && calls.length >= 2, `实际: ${calls?.length}`)
167
+
168
+ // 检查调用是否包含 runtimeRoot 传递
169
+ const hasRuntimeRoot = runSrc.includes('platformOpts.runtimeRoot ? { runtimeRoot: platformOpts.runtimeRoot }')
170
+ assert('saveWorkflowRun 调用传入 runtimeRoot', hasRuntimeRoot, '未发现 runtimeRoot 传递')
171
+
172
+ const hasScanRunId = runSrc.includes('platformOpts.scanRunId ? { scanRunId: platformOpts.scanRunId }')
173
+ assert('saveWorkflowRun 调用传入 scanRunId', hasScanRunId, '未发现 scanRunId 传递')
174
+ }
175
+
176
+ // ── 结果 ──
177
+ console.log(`\n${'='.repeat(50)}`)
178
+ console.log(`✅ 通过: ${passed.length} ❌ 失败: ${failed.length}`)
179
+ console.log(`${'='.repeat(50)}`)
180
+
181
+ if (failed.length > 0) {
182
+ console.log('\n失败详情:')
183
+ for (const f of failed) {
184
+ console.log(` ❌ ${f.label}`)
185
+ if (f.detail) console.log(` ${f.detail}`)
186
+ }
187
+ throw new Error('platform-artifacts test failed')
188
+ } else {
189
+ console.log('\n🎉 平台产物协议测试全部通过!')
190
+ }