sillyspec 3.16.0 → 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/README.md +1 -1
- package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +99 -0
- package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +218 -0
- package/docs/sillyspec/file-lifecycle/stage-artifacts.md +167 -0
- package/docs/sillyspec/file-lifecycle/storage-and-state.md +148 -0
- package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +193 -0
- package/docs/sillyspec/file-lifecycle.md +106 -1297
- package/package.json +3 -3
- package/src/hooks/worktree-guard.js +166 -47
- package/src/progress.js +37 -0
- package/src/run.js +309 -56
- package/src/stage-contract.js +349 -0
- package/src/stages/archive.js +6 -10
- package/src/stages/brainstorm.js +4 -1
- package/src/stages/doctor.js +1 -2
- package/src/stages/propose.js +4 -1
- package/src/stages/scan.js +3 -3
- package/test/check-syntax.mjs +26 -0
- package/test/run-tests.mjs +20 -0
- package/test/stage-contract.test.mjs +185 -0
- package/test/stage-definitions.test.mjs +43 -0
- package/test/worktree-guard.test.mjs +78 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { stageRegistry } from '../src/stages/index.js'
|
|
3
|
+
import { buildPlanSteps } from '../src/stages/plan.js'
|
|
4
|
+
import { buildExecuteSteps } from '../src/stages/execute.js'
|
|
5
|
+
|
|
6
|
+
const stageSteps = {
|
|
7
|
+
brainstorm: stageRegistry.brainstorm.steps,
|
|
8
|
+
propose: stageRegistry.propose.steps,
|
|
9
|
+
scan: stageRegistry.scan.steps,
|
|
10
|
+
quick: stageRegistry.quick.steps,
|
|
11
|
+
archive: stageRegistry.archive.steps,
|
|
12
|
+
verify: stageRegistry.verify.steps,
|
|
13
|
+
plan: buildPlanSteps(null),
|
|
14
|
+
execute: buildExecuteSteps(null),
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function names(stage) {
|
|
18
|
+
return stageSteps[stage].map(step => step.name)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function assertContains(stage, expectedNames) {
|
|
22
|
+
const actual = names(stage)
|
|
23
|
+
for (const name of expectedNames) {
|
|
24
|
+
assert.ok(actual.includes(name), `${stage} should include step "${name}". Actual: ${actual.join(', ')}`)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
assert.equal(stageSteps.brainstorm.length, 11, 'brainstorm should expose design self-review and final confirmation separately')
|
|
29
|
+
assertContains('brainstorm', ['写设计文档并自审', '用户确认并生成规范文件'])
|
|
30
|
+
|
|
31
|
+
assert.equal(stageSteps.propose.length, 7, 'propose should expose generation and self-check separately')
|
|
32
|
+
assertContains('propose', ['生成规范文件', '自检门控', '展示并更新进度'])
|
|
33
|
+
|
|
34
|
+
assert.equal(stageSteps.scan.length, 10, 'scan base definition should stay at 10 steps before per-project expansion')
|
|
35
|
+
assertContains('scan', ['构建扫描项目列表', '生成本地配置', '生成模块映射'])
|
|
36
|
+
|
|
37
|
+
assert.equal(stageSteps.quick.length, 3, 'quick should remain a short auxiliary workflow')
|
|
38
|
+
assertContains('quick', ['理解任务', '实现并验证', '暂存和更新记录'])
|
|
39
|
+
|
|
40
|
+
assert.equal(stageSteps.archive.length, 5, 'archive should keep its five-step lifecycle')
|
|
41
|
+
assertContains('archive', ['extract-module-impact', 'sync-module-docs', '确认归档'])
|
|
42
|
+
|
|
43
|
+
console.log('✅ stage definition regression checks passed')
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { mkdirSync, rmSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
5
|
+
import { shouldBlock } from '../src/hooks/worktree-guard.js'
|
|
6
|
+
|
|
7
|
+
const root = join(tmpdir(), `sillyspec-guard-test-${Date.now()}`)
|
|
8
|
+
const changeName = '2026-06-04-guard-test'
|
|
9
|
+
const runtimeDir = join(root, '.sillyspec', '.runtime')
|
|
10
|
+
const registeredWorktree = join(runtimeDir, 'worktrees', changeName)
|
|
11
|
+
const unregisteredWorktree = join(runtimeDir, 'worktrees', 'other-change')
|
|
12
|
+
|
|
13
|
+
mkdirSync(registeredWorktree, { recursive: true })
|
|
14
|
+
mkdirSync(unregisteredWorktree, { recursive: true })
|
|
15
|
+
writeFileSync(join(runtimeDir, 'gate-status.json'), JSON.stringify({
|
|
16
|
+
stage: 'execute',
|
|
17
|
+
changes: [changeName],
|
|
18
|
+
updatedAt: new Date().toISOString(),
|
|
19
|
+
}, null, 2))
|
|
20
|
+
writeFileSync(join(registeredWorktree, 'meta.json'), JSON.stringify({
|
|
21
|
+
changeName,
|
|
22
|
+
worktreePath: registeredWorktree,
|
|
23
|
+
mode: 'worktree',
|
|
24
|
+
}, null, 2))
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
assert.equal(
|
|
28
|
+
shouldBlock({ tool: 'Write', filePath: join(registeredWorktree, 'src', 'ok.js'), cwd: root }).blocked,
|
|
29
|
+
false,
|
|
30
|
+
'registered worktree writes should be allowed'
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
assert.equal(
|
|
34
|
+
shouldBlock({ tool: 'Bash', command: 'npm run build', cwd: registeredWorktree }).blocked,
|
|
35
|
+
false,
|
|
36
|
+
'bash commands from a registered worktree cwd should be allowed'
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
assert.equal(
|
|
40
|
+
shouldBlock({ tool: 'Write', filePath: join(unregisteredWorktree, 'src', 'blocked.js'), cwd: root }).blocked,
|
|
41
|
+
true,
|
|
42
|
+
'unregistered worktree storage writes should be blocked'
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
assert.equal(
|
|
46
|
+
shouldBlock({ tool: 'Write', filePath: join(root, '.sillyspec', 'docs', 'note.md'), cwd: root }).blocked,
|
|
47
|
+
false,
|
|
48
|
+
'ordinary .sillyspec docs should remain writable'
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
rmSync(join(runtimeDir, 'gate-status.json'), { force: true })
|
|
52
|
+
writeFileSync(join(root, '.sillyspec', 'local.yaml'), [
|
|
53
|
+
'worktreeHook:',
|
|
54
|
+
' readonlyCommands:',
|
|
55
|
+
' - custom-read',
|
|
56
|
+
'',
|
|
57
|
+
].join('\n'))
|
|
58
|
+
assert.equal(
|
|
59
|
+
shouldBlock({ tool: 'Bash', command: 'custom-read status', cwd: root }).blocked,
|
|
60
|
+
false,
|
|
61
|
+
'.sillyspec/local.yaml readonlyCommands should extend the bash whitelist'
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
writeFileSync(join(runtimeDir, 'gate-status.json'), JSON.stringify({
|
|
65
|
+
stage: 'quick',
|
|
66
|
+
changes: [changeName],
|
|
67
|
+
updatedAt: new Date().toISOString(),
|
|
68
|
+
}, null, 2))
|
|
69
|
+
assert.equal(
|
|
70
|
+
shouldBlock({ tool: 'Write', filePath: join(root, 'src', 'quick.js'), cwd: root }).blocked,
|
|
71
|
+
false,
|
|
72
|
+
'quick writes should still be allowed in the main workspace'
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
console.log('✅ worktree guard regression checks passed')
|
|
76
|
+
} finally {
|
|
77
|
+
rmSync(root, { recursive: true, force: true })
|
|
78
|
+
}
|