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
@@ -0,0 +1,148 @@
1
+ /**
2
+ * worktree-deps-provision 测试
3
+ * 覆盖 FR-01/02/03/05:lockfileHash + provisionDeps(junction 快路径 / install 兜底判定 / generic→n/a)
4
+ * change 2026-06-28-worktree-deps-provision
5
+ */
6
+ import { provisionDeps, lockfileHash } from '../src/worktree-deps.js'
7
+ import { mkdtempSync, writeFileSync, mkdirSync, existsSync, rmSync } from 'fs'
8
+ import { join } from 'path'
9
+ import { tmpdir } from 'os'
10
+
11
+ let failed = 0
12
+ let passed = 0
13
+ const failures = []
14
+ function assert(cond, msg) {
15
+ if (cond) { passed++; console.log(` ✅ PASS: ${msg}`) }
16
+ else { failed++; failures.push(msg); console.log(` ❌ FAIL: ${msg}`) }
17
+ }
18
+
19
+ const tmpDirs = []
20
+ function mkTmp(prefix) {
21
+ const d = mkdtempSync(join(tmpdir(), `wtdeps-${prefix}-`))
22
+ tmpDirs.push(d)
23
+ return d
24
+ }
25
+ function cleanup() {
26
+ for (const d of tmpDirs) {
27
+ try { rmSync(d, { recursive: true, force: true }) } catch {}
28
+ }
29
+ }
30
+
31
+ console.log('=== worktree-deps-provision 测试 ===\n')
32
+
33
+ // ── lockfileHash ──
34
+ console.log('--- lockfileHash ---')
35
+ {
36
+ const d = mkTmp('lh')
37
+ assert(lockfileHash(d) === null, '空目录返回 null')
38
+ writeFileSync(join(d, 'pnpm-lock.yaml'), 'lockversion: 6.0\npackages: []\n')
39
+ const h1 = lockfileHash(d)
40
+ assert(typeof h1 === 'string' && h1.length === 16, `pnpm-lock 返回 16 位 hash: ${h1}`)
41
+
42
+ const d2 = mkTmp('lh2')
43
+ writeFileSync(join(d2, 'package-lock.json'), '{"lockfileVersion":3}')
44
+ const h2 = lockfileHash(d2)
45
+ assert(typeof h2 === 'string' && h2.length === 16, `package-lock 返回 hash: ${h2}`)
46
+ assert(h1 !== h2, '不同 lockfile 内容产生不同 hash')
47
+
48
+ const d3 = mkTmp('lh3')
49
+ writeFileSync(join(d3, 'yarn.lock'), '# yarn lock v1\n')
50
+ assert(typeof lockfileHash(d3) === 'string', 'yarn.lock 返回 hash')
51
+
52
+ const d4 = mkTmp('lh4')
53
+ writeFileSync(join(d4, 'package.json'), '{"name":"x"}')
54
+ assert(typeof lockfileHash(d4) === 'string', '无 lockfile 时回退 hash package.json')
55
+
56
+ // 一致性:相同内容相同 hash
57
+ const d5 = mkTmp('lh5')
58
+ writeFileSync(join(d5, 'pnpm-lock.yaml'), 'lockversion: 6.0\npackages: []\n')
59
+ assert(lockfileHash(d5) === h1, '相同 lockfile 内容产生相同 hash(快路径判定基础)')
60
+ }
61
+
62
+ // ── provisionDeps: generic → n/a ──
63
+ console.log('\n--- provisionDeps: generic / 无依赖 → n/a ---')
64
+ {
65
+ const wt = mkTmp('gen-wt')
66
+ const main = mkTmp('gen-main')
67
+ const r = provisionDeps(wt, main, {})
68
+ assert(r.depsStatus === 'n/a', `无 package.json/lockfile 的目录 → n/a(实际 ${r.depsStatus})`)
69
+ assert(!existsSync(join(wt, 'node_modules')), 'n/a 不创建 node_modules')
70
+ }
71
+
72
+ // ── provisionDeps: nodejs junction 快路径(lockfile 一致)──
73
+ console.log('\n--- provisionDeps: nodejs lockfile 一致 → linked (junction/symlink) ---')
74
+ {
75
+ const main = mkTmp('link-main')
76
+ const wt = mkTmp('link-wt')
77
+ // 主 checkout:有 node_modules + lockfile
78
+ mkdirSync(join(main, 'node_modules'))
79
+ writeFileSync(join(main, 'node_modules', '.placeholder'), 'x')
80
+ const lockContent = 'lockversion: 6.0\npackages: []\n'
81
+ writeFileSync(join(main, 'package-lock.json'), lockContent)
82
+ writeFileSync(join(main, 'package.json'), '{"name":"main","scripts":{}}')
83
+ // worktree:相同 lockfile(hash 一致)+ package.json
84
+ writeFileSync(join(wt, 'package-lock.json'), lockContent)
85
+ writeFileSync(join(wt, 'package.json'), '{"name":"wt","scripts":{}}')
86
+
87
+ const r = provisionDeps(wt, main, {})
88
+ assert(r.depsStatus === 'linked', `lockfile 一致 → linked(实际 ${r.depsStatus})`)
89
+ assert(['junction', 'symlink', 'install'].includes(r.depsMethod), `depsMethod 合法(${r.depsMethod})`)
90
+ assert(r.depsSource === 'main-checkout', `depsSource=main-checkout(${r.depsSource})`)
91
+ assert(existsSync(join(wt, 'node_modules')), 'worktree/node_modules 已创建(junction/symlink)')
92
+ assert(existsSync(join(wt, 'node_modules', '.placeholder')), 'junction 指向 main 的内容可访问')
93
+ assert(typeof r.depsLockHash === 'string', 'depsLockHash 已记录')
94
+ }
95
+
96
+ // ── provisionDeps: lockfile 不一致 → 走 install 兜底(不验证真实 install,只验证不走 linked)──
97
+ console.log('\n--- provisionDeps: lockfile 不一致 → 不走 linked 快路径 ---')
98
+ {
99
+ const main = mkTmp('mis-main')
100
+ const wt = mkTmp('mis-wt')
101
+ mkdirSync(join(main, 'node_modules'))
102
+ writeFileSync(join(main, 'package-lock.json'), 'lockversion-A')
103
+ writeFileSync(join(main, 'package.json'), '{"name":"main"}')
104
+ writeFileSync(join(wt, 'package-lock.json'), 'lockversion-B-DIFFERENT') // 不一致
105
+ writeFileSync(join(wt, 'package.json'), '{"name":"wt"}')
106
+
107
+ const r = provisionDeps(wt, main, {})
108
+ // lockfile 不一致 → 不会 linked;会尝试 install(真实 install 在 CI 可能失败/超时)
109
+ assert(r.depsStatus !== 'linked', `lockfile 不一致时不应 linked(实际 ${r.depsStatus})`)
110
+ assert(['installed', 'failed'].includes(r.depsStatus), `兜底为 installed 或 failed(实际 ${r.depsStatus})`)
111
+ // install 失败时应带 depsError
112
+ if (r.depsStatus === 'failed') {
113
+ assert(typeof r.depsError === 'string' && r.depsError.length > 0, 'failed 带 depsError 信息')
114
+ }
115
+ }
116
+
117
+ // ── provisionDeps: main 无 node_modules → 不走 linked ──
118
+ console.log('\n--- provisionDeps: main 无 node_modules → 不走 linked ---')
119
+ {
120
+ const main = mkTmp('nomain-main')
121
+ const wt = mkTmp('nomain-wt')
122
+ const lockContent = 'same-lock\n'
123
+ writeFileSync(join(main, 'package-lock.json'), lockContent)
124
+ writeFileSync(join(main, 'package.json'), '{"name":"main"}')
125
+ writeFileSync(join(wt, 'package-lock.json'), lockContent)
126
+ writeFileSync(join(wt, 'package.json'), '{"name":"wt"}')
127
+ // main 无 node_modules
128
+
129
+ const r = provisionDeps(wt, main, {})
130
+ assert(r.depsStatus !== 'linked', `main 无 node_modules 时不应 linked(实际 ${r.depsStatus})`)
131
+ }
132
+
133
+ // ── provisionDeps: maven/gradle 推断(无 node_modules,验证不崩)──
134
+ console.log('\n--- provisionDeps: 非 nodejs 项目类型不崩 ---')
135
+ {
136
+ const wt = mkTmp('maven-wt')
137
+ writeFileSync(join(wt, 'pom.xml'), '<project></project>')
138
+ const r = provisionDeps(wt, mkTmp('maven-main'), {})
139
+ assert(['installed', 'failed'].includes(r.depsStatus), `maven 项目返回 installed/failed(${r.depsStatus},mvn 可能不可用)`)
140
+ }
141
+
142
+ cleanup()
143
+
144
+ console.log(`\n==================================================`)
145
+ console.log(`✅ 通过: ${passed} ❌ 失败: ${failed}`)
146
+ console.log(failed === 0 ? '全部通过' : `❌ 失败项: ${failures.join('; ')}`)
147
+ console.log(`==================================================`)
148
+ process.exit(failed === 0 ? 0 : 1)
@@ -1,47 +1,47 @@
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
-
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
45
  assert.equal(
46
46
  shouldBlock({ tool: 'Write', filePath: join(root, '.sillyspec', 'docs', 'note.md'), cwd: root }).blocked,
47
47
  false,
@@ -107,30 +107,30 @@ try {
107
107
  )
108
108
 
109
109
  rmSync(join(runtimeDir, 'gate-status.json'), { force: true })
110
- writeFileSync(join(root, '.sillyspec', 'local.yaml'), [
111
- 'worktreeHook:',
112
- ' readonlyCommands:',
113
- ' - custom-read',
114
- '',
115
- ].join('\n'))
116
- assert.equal(
117
- shouldBlock({ tool: 'Bash', command: 'custom-read status', cwd: root }).blocked,
118
- false,
119
- '.sillyspec/local.yaml readonlyCommands should extend the bash whitelist'
120
- )
121
-
122
- writeFileSync(join(runtimeDir, 'gate-status.json'), JSON.stringify({
123
- stage: 'quick',
124
- changes: [changeName],
125
- updatedAt: new Date().toISOString(),
126
- }, null, 2))
127
- assert.equal(
128
- shouldBlock({ tool: 'Write', filePath: join(root, 'src', 'quick.js'), cwd: root }).blocked,
129
- false,
130
- 'quick writes should still be allowed in the main workspace'
131
- )
132
-
133
- console.log('✅ worktree guard regression checks passed')
134
- } finally {
135
- rmSync(root, { recursive: true, force: true })
136
- }
110
+ writeFileSync(join(root, '.sillyspec', 'local.yaml'), [
111
+ 'worktreeHook:',
112
+ ' readonlyCommands:',
113
+ ' - custom-read',
114
+ '',
115
+ ].join('\n'))
116
+ assert.equal(
117
+ shouldBlock({ tool: 'Bash', command: 'custom-read status', cwd: root }).blocked,
118
+ false,
119
+ '.sillyspec/local.yaml readonlyCommands should extend the bash whitelist'
120
+ )
121
+
122
+ writeFileSync(join(runtimeDir, 'gate-status.json'), JSON.stringify({
123
+ stage: 'quick',
124
+ changes: [changeName],
125
+ updatedAt: new Date().toISOString(),
126
+ }, null, 2))
127
+ assert.equal(
128
+ shouldBlock({ tool: 'Write', filePath: join(root, 'src', 'quick.js'), cwd: root }).blocked,
129
+ false,
130
+ 'quick writes should still be allowed in the main workspace'
131
+ )
132
+
133
+ console.log('✅ worktree guard regression checks passed')
134
+ } finally {
135
+ rmSync(root, { recursive: true, force: true })
136
+ }