sillyspec 3.18.1 → 3.18.3
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/.claude/skills/sillyspec-brainstorm/SKILL.md +24 -23
- package/.claude/skills/sillyspec-execute/SKILL.md +8 -1
- package/docs/brainstorm-plan-contract.md +64 -0
- package/docs/plan-execute-contract.md +123 -0
- package/docs/revision-mode.md +115 -0
- package/docs/sillyspec/file-lifecycle.md +13 -4
- package/docs/workflow-contract-regression.md +106 -0
- package/package.json +1 -1
- package/packages/dashboard/dist/assets/{index-DpLHK4jv.js → index-Bq_Z2hne.js} +568 -568
- package/packages/dashboard/dist/assets/{index-BcM2J-hv.css → index-O2W5RV4z.css} +1 -1
- package/packages/dashboard/dist/index.html +16 -16
- package/packages/dashboard/src/components/PipelineStage.vue +22 -2
- package/packages/dashboard/src/components/PipelineView.vue +10 -2
- package/packages/dashboard/src/components/StageBadge.vue +17 -3
- package/packages/dashboard/src/components/StepCard.vue +7 -2
- package/src/change-risk-profile.js +167 -0
- package/src/db.js +6 -0
- package/src/index.js +17 -1
- package/src/knowledge-match.js +130 -0
- package/src/progress.js +464 -11
- package/src/run.js +269 -29
- package/src/scan-postcheck.js +34 -2
- package/src/stage-contract.js +90 -5
- package/src/stages/brainstorm.js +23 -0
- package/src/stages/execute.js +122 -16
- package/src/stages/plan.js +82 -0
- package/src/stages/scan.js +40 -0
- package/src/stages/verify.js +38 -2
- package/test/brainstorm-plan-contract.test.mjs +273 -0
- package/test/knowledge-match.test.mjs +231 -0
- package/test/plan-execute-contract.test.mjs +330 -0
- package/test/platform-failure-samples.test.mjs +4 -0
- package/test/revision-v1.test.mjs +1145 -0
- package/test/scan-knowledge.test.mjs +175 -0
- package/test/scan-postcheck.test.mjs +3 -0
- package/test/spec-dir.test.mjs +8 -3
- package/test/stage-definitions.test.mjs +1 -1
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scan-knowledge.test.mjs — knowledge 产物校验测试
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { join, resolve, dirname, basename } from 'path'
|
|
6
|
+
import { existsSync, mkdirSync, writeFileSync, rmSync } from 'fs'
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from 'url'
|
|
8
|
+
import { tmpdir } from 'os'
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
11
|
+
const __dirname = dirname(__filename)
|
|
12
|
+
const root = resolve(__dirname, '..')
|
|
13
|
+
|
|
14
|
+
const { runScanPostCheck } = await import(pathToFileURL(join(root, 'src', 'scan-postcheck.js')).href)
|
|
15
|
+
|
|
16
|
+
let passed = 0, failed = 0
|
|
17
|
+
|
|
18
|
+
function assert(cond, msg) {
|
|
19
|
+
if (cond) { console.log(` ✅ PASS: ${msg}`); passed++ }
|
|
20
|
+
else { console.log(` ❌ FAIL: ${msg}`); failed++ }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function setup(name) {
|
|
24
|
+
const cwd = join(tmpdir(), `kn-${name}`)
|
|
25
|
+
mkdirSync(cwd, { recursive: true })
|
|
26
|
+
return cwd
|
|
27
|
+
}
|
|
28
|
+
function specSetup(name) {
|
|
29
|
+
const d = join(tmpdir(), `kn-${name}-spec`)
|
|
30
|
+
mkdirSync(d, { recursive: true })
|
|
31
|
+
return d
|
|
32
|
+
}
|
|
33
|
+
function clean(...dirs) { for (const d of dirs) try { rmSync(d, { recursive: true, force: true }) } catch {} }
|
|
34
|
+
|
|
35
|
+
const DOCS = ['ARCHITECTURE.md','CONVENTIONS.md','STRUCTURE.md','INTEGRATIONS.md','TESTING.md','CONCERNS.md','PROJECT.md']
|
|
36
|
+
|
|
37
|
+
function writeFull(cwd, specDir) {
|
|
38
|
+
const proj = basename(cwd)
|
|
39
|
+
for (const d of DOCS) {
|
|
40
|
+
const p = join(specDir, 'docs', proj, 'scan', d)
|
|
41
|
+
mkdirSync(dirname(p), { recursive: true })
|
|
42
|
+
writeFileSync(p, 'author: bot\ncreated_at: 2026-06-19 10:00:00\n# doc\n')
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── 1: knowledge INDEX.md 存在,引用真实文件 → 通过 ──
|
|
47
|
+
console.log('\n=== Test 1: knowledge INDEX.md 存在且引用有效 → no knowledge warning ===')
|
|
48
|
+
{
|
|
49
|
+
const cwd = setup('t1'), spec = specSetup('t1')
|
|
50
|
+
try {
|
|
51
|
+
writeFull(cwd, spec)
|
|
52
|
+
const knowledgeDir = join(spec, 'knowledge')
|
|
53
|
+
mkdirSync(knowledgeDir, { recursive: true })
|
|
54
|
+
writeFileSync(join(knowledgeDir, 'INDEX.md'),
|
|
55
|
+
'# Knowledge Index\n\n## Conventions\n- [kebab-case naming](conventions.md#kebab-case-naming)\n')
|
|
56
|
+
writeFileSync(join(knowledgeDir, 'conventions.md'),
|
|
57
|
+
'# Conventions\n\n## kebab-case-naming\nFiles use kebab-case.\n')
|
|
58
|
+
|
|
59
|
+
const result = runScanPostCheck({ cwd, specDir: spec })
|
|
60
|
+
const knowledgeChecks = result.checks.filter(c => c.name.startsWith('knowledge_'))
|
|
61
|
+
assert(knowledgeChecks.length === 0, 'no knowledge warnings when INDEX.md + referenced files exist')
|
|
62
|
+
} finally { clean(cwd, spec) }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ── 2: knowledge 目录不存在 → dir_missing 警告 ──
|
|
66
|
+
console.log('\n=== Test 2: knowledge 目录不存在 → knowledge_dir_missing ===')
|
|
67
|
+
{
|
|
68
|
+
const cwd = setup('t2'), spec = specSetup('t2')
|
|
69
|
+
try {
|
|
70
|
+
writeFull(cwd, spec)
|
|
71
|
+
|
|
72
|
+
const result = runScanPostCheck({ cwd, specDir: spec })
|
|
73
|
+
const missing = result.checks.find(c => c.name === 'knowledge_dir_missing')
|
|
74
|
+
assert(!!missing, 'knowledge_dir_missing check exists')
|
|
75
|
+
assert(missing.detail.includes('knowledge/ 目录不存在'), 'detail text correct')
|
|
76
|
+
} finally { clean(cwd, spec) }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── 3: INDEX.md 不存在 → index_missing 警告 ──
|
|
80
|
+
console.log('\n=== Test 3: INDEX.md 不存在 → knowledge_index_missing ===')
|
|
81
|
+
{
|
|
82
|
+
const cwd = setup('t3'), spec = specSetup('t3')
|
|
83
|
+
try {
|
|
84
|
+
writeFull(cwd, spec)
|
|
85
|
+
const knowledgeDir = join(spec, 'knowledge')
|
|
86
|
+
mkdirSync(knowledgeDir, { recursive: true })
|
|
87
|
+
// INDEX.md not created
|
|
88
|
+
|
|
89
|
+
const result = runScanPostCheck({ cwd, specDir: spec })
|
|
90
|
+
const missing = result.checks.find(c => c.name === 'knowledge_index_missing')
|
|
91
|
+
assert(!!missing, 'knowledge_index_missing check exists')
|
|
92
|
+
assert(missing.detail.includes('INDEX.md 不存在'), 'detail text correct')
|
|
93
|
+
} finally { clean(cwd, spec) }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── 4: INDEX.md 引用不存在文件 → broken_refs 警告 ──
|
|
97
|
+
console.log('\n=== Test 4: INDEX.md 引用不存在的文件 → knowledge_broken_refs ===')
|
|
98
|
+
{
|
|
99
|
+
const cwd = setup('t4'), spec = specSetup('t4')
|
|
100
|
+
try {
|
|
101
|
+
writeFull(cwd, spec)
|
|
102
|
+
const knowledgeDir = join(spec, 'knowledge')
|
|
103
|
+
mkdirSync(knowledgeDir, { recursive: true })
|
|
104
|
+
writeFileSync(join(knowledgeDir, 'INDEX.md'),
|
|
105
|
+
'# Knowledge Index\n\n## Conventions\n- [naming](conventions.md#naming)\n\n## Patterns\n- [auth pattern](patterns.md#auth-pattern)\n')
|
|
106
|
+
// conventions.md exists, patterns.md does not
|
|
107
|
+
writeFileSync(join(knowledgeDir, 'conventions.md'), '# Conventions\n')
|
|
108
|
+
|
|
109
|
+
const result = runScanPostCheck({ cwd, specDir: spec })
|
|
110
|
+
const broken = result.checks.find(c => c.name === 'knowledge_broken_refs')
|
|
111
|
+
assert(!!broken, 'knowledge_broken_refs check exists')
|
|
112
|
+
assert(broken.detail.includes('patterns.md'), 'reports missing patterns.md')
|
|
113
|
+
} finally { clean(cwd, spec) }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── 5: uncategorized.md 为空但存在 → 通过 ──
|
|
117
|
+
console.log('\n=== Test 5: uncategorized.md 空但存在 → no knowledge warning ===')
|
|
118
|
+
{
|
|
119
|
+
const cwd = setup('t5'), spec = specSetup('t5')
|
|
120
|
+
try {
|
|
121
|
+
writeFull(cwd, spec)
|
|
122
|
+
const knowledgeDir = join(spec, 'knowledge')
|
|
123
|
+
mkdirSync(knowledgeDir, { recursive: true })
|
|
124
|
+
writeFileSync(join(knowledgeDir, 'INDEX.md'),
|
|
125
|
+
'# Knowledge Index\n\n(no entries yet)\n')
|
|
126
|
+
writeFileSync(join(knowledgeDir, 'uncategorized.md'), '# Uncategorized\n')
|
|
127
|
+
|
|
128
|
+
const result = runScanPostCheck({ cwd, specDir: spec })
|
|
129
|
+
const knowledgeChecks = result.checks.filter(c => c.name.startsWith('knowledge_'))
|
|
130
|
+
assert(knowledgeChecks.length === 0, 'empty uncategorized.md passes')
|
|
131
|
+
} finally { clean(cwd, spec) }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── 6: categorized 文件存在但 INDEX.md 无条目 → 通过(不强求 categorized 非空)──
|
|
135
|
+
console.log('\n=== Test 6: categorized 文件存在 + INDEX.md 有效 → no knowledge warning ===')
|
|
136
|
+
{
|
|
137
|
+
const cwd = setup('t6'), spec = specSetup('t6')
|
|
138
|
+
try {
|
|
139
|
+
writeFull(cwd, spec)
|
|
140
|
+
const knowledgeDir = join(spec, 'knowledge')
|
|
141
|
+
mkdirSync(knowledgeDir, { recursive: true })
|
|
142
|
+
writeFileSync(join(knowledgeDir, 'INDEX.md'),
|
|
143
|
+
'# Knowledge Index\n\n## Known Issues\n- [GLM proxy](known-issues.md#glm-proxy)\n')
|
|
144
|
+
writeFileSync(join(knowledgeDir, 'known-issues.md'),
|
|
145
|
+
'# Known Issues\n\n## glm-proxy\nGLM proxy has usage metadata limits.\n')
|
|
146
|
+
writeFileSync(join(knowledgeDir, 'uncategorized.md'), '# Uncategorized\n')
|
|
147
|
+
|
|
148
|
+
const result = runScanPostCheck({ cwd, specDir: spec })
|
|
149
|
+
const knowledgeChecks = result.checks.filter(c => c.name.startsWith('knowledge_'))
|
|
150
|
+
assert(knowledgeChecks.length === 0, 'at least one categorized file + valid INDEX passes')
|
|
151
|
+
} finally { clean(cwd, spec) }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── 7: source_root knowledge 泄漏检测(平台模式已有检查)──
|
|
155
|
+
console.log('\n=== Test 7: source_root knowledge 泄漏 → source_root_leak ===')
|
|
156
|
+
{
|
|
157
|
+
const cwd = setup('t7'), spec = specSetup('t7')
|
|
158
|
+
try {
|
|
159
|
+
writeFull(cwd, spec)
|
|
160
|
+
// 写入 source_root/.sillyspec/knowledge/ 泄漏
|
|
161
|
+
const leakedKnowledgeDir = join(cwd, '.sillyspec', 'knowledge')
|
|
162
|
+
mkdirSync(leakedKnowledgeDir, { recursive: true })
|
|
163
|
+
writeFileSync(join(leakedKnowledgeDir, 'INDEX.md'), '# leaked\n')
|
|
164
|
+
|
|
165
|
+
const result = runScanPostCheck({ cwd, specDir: spec })
|
|
166
|
+
const leak = result.checks.find(c => c.name === 'source_root_leak')
|
|
167
|
+
assert(!!leak, 'source_root knowledge leak detected')
|
|
168
|
+
assert(leak.severity === 'failed', 'leak severity is failed')
|
|
169
|
+
} finally { clean(cwd, spec) }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── 汇总 ──
|
|
173
|
+
console.log(`\n${'='.repeat(40)}`)
|
|
174
|
+
console.log(`scan-knowledge tests: ${passed} passed, ${failed} failed, ${passed + failed} total`)
|
|
175
|
+
if (failed > 0) process.exit(1)
|
|
@@ -42,6 +42,9 @@ function writeFull(cwd, specDir) {
|
|
|
42
42
|
mkdirSync(dirname(p), { recursive: true })
|
|
43
43
|
writeFileSync(p, 'author: bot\ncreated_at: 2026-06-08 10:00:00\n# doc\n')
|
|
44
44
|
}
|
|
45
|
+
// knowledge 目录 + INDEX.md(scan 已产出知识)
|
|
46
|
+
mkdirSync(join(specDir, 'knowledge'), { recursive: true })
|
|
47
|
+
writeFileSync(join(specDir, 'knowledge', 'INDEX.md'), '# Knowledge Index\n')
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
// 写入前 N 份文档
|
package/test/spec-dir.test.mjs
CHANGED
|
@@ -63,8 +63,12 @@ console.log('\n=== Test 1: ProgressManager 外部 specDir ===')
|
|
|
63
63
|
const pm = new ProgressManager({ specDir })
|
|
64
64
|
assert(pm._getSpecDir(tmp) === specDir, `_getSpecDir 返回自定义路径`)
|
|
65
65
|
|
|
66
|
+
// 无自定义 specDir 时,resolveSpecDir 会向上查找 .sillyspec 目录
|
|
67
|
+
// 在测试环境中可能命中上层已有的 .sillyspec,所以只检查返回值是否有效路径
|
|
66
68
|
const pm2 = new ProgressManager()
|
|
67
|
-
|
|
69
|
+
const resolved = pm2._getSpecDir(tmp)
|
|
70
|
+
assert(typeof resolved === 'string' && resolved.length > 0 && resolved.endsWith('.sillyspec'),
|
|
71
|
+
`_getSpecDir 无自定义时返回有效 .sillyspec 路径 (got: ${resolved})`)
|
|
68
72
|
|
|
69
73
|
assert(pm._runtimePath(tmp) === join(specDir, '.runtime'), `_runtimePath 基于 specDir`)
|
|
70
74
|
assert(pm._changePath(tmp, 'c') === join(specDir, 'changes', 'c'), `_changePath 基于 specDir`)
|
|
@@ -120,9 +124,10 @@ console.log('\n=== Test 4: 平台模式 prompt 注入 ===')
|
|
|
120
124
|
|
|
121
125
|
run(`node "${binCLI}" init "${projectDir}" --spec-dir "${specDir}"`)
|
|
122
126
|
|
|
123
|
-
|
|
127
|
+
// execute 阶段会自动创建 worktree,在非 git 环境下会失败,跳过
|
|
128
|
+
const stages = ['scan', 'brainstorm', 'plan', 'verify', 'quick']
|
|
124
129
|
for (const stage of stages) {
|
|
125
|
-
const output = run(`node "${binCLI}" --dir "${projectDir}" --spec-dir "${specDir}" run ${stage}`)
|
|
130
|
+
const output = run(`node "${binCLI}" --dir "${projectDir}" --spec-dir "${specDir}" run ${stage} --skip-approval`)
|
|
126
131
|
assert(output.includes('平台模式'), `${stage}: 包含平台模式指令`)
|
|
127
132
|
assert(output.includes(`规范目录(specDir): \`${specDir}\``), `${stage}: 包含正确的 specDir 路径`)
|
|
128
133
|
}
|
|
@@ -27,7 +27,7 @@ function assertContains(stage, expectedNames) {
|
|
|
27
27
|
assert.equal(stageSteps.brainstorm.length, 13, 'brainstorm should include optional demand clarification and default Design Grill gates')
|
|
28
28
|
assertContains('brainstorm', ['需求澄清 Grill', '写设计文档并自审', 'Design Grill 交叉审查', '用户确认并生成规范文件'])
|
|
29
29
|
|
|
30
|
-
assert.equal(stageSteps.scan.length,
|
|
30
|
+
assert.equal(stageSteps.scan.length, 11, 'scan base definition should be 11 steps (with Extract Project Knowledge) before per-project expansion')
|
|
31
31
|
assertContains('scan', ['构建扫描项目列表', '生成本地配置', '生成模块映射'])
|
|
32
32
|
|
|
33
33
|
assert.equal(stageSteps.quick.length, 3, 'quick should remain a short auxiliary workflow')
|