sillyspec 3.25.9 → 3.26.0

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 (76) hide show
  1. package/.claude/skills/sillyspec-auto/SKILL.md +8 -13
  2. package/.claude/skills/sillyspec-brainstorm/SKILL.md +8 -6
  3. package/.claude/skills/sillyspec-continue/SKILL.md +3 -3
  4. package/.claude/skills/sillyspec-doctor/SKILL.md +1 -1
  5. package/.claude/skills/sillyspec-execute/SKILL.md +10 -4
  6. package/.claude/skills/sillyspec-plan/SKILL.md +1 -1
  7. package/.claude/skills/sillyspec-quick/SKILL.md +3 -1
  8. package/.claude/skills/sillyspec-verify/SKILL.md +1 -1
  9. package/.sillyspec-platform-cleaned +1 -0
  10. package/.sillyspec-platform.json +7 -0
  11. package/README.md +9 -0
  12. package/package.json +2 -2
  13. package/src/change-list.js +11 -8
  14. package/src/change-risk-profile.js +22 -0
  15. package/src/db.js +170 -161
  16. package/src/dispatch/backends/local-agent.js +152 -0
  17. package/src/dispatch/backends/sillyhub-mcp.js +242 -0
  18. package/src/dispatch/probe.js +210 -0
  19. package/src/dispatch/strategy.js +121 -0
  20. package/src/doctor-diagnostics.js +19 -25
  21. package/src/fs-atomic.js +69 -69
  22. package/src/git-helper.js +66 -0
  23. package/src/hooks/worktree-guard.js +64 -55
  24. package/src/index.js +326 -36
  25. package/src/init.js +7 -14
  26. package/src/local-detect.js +120 -110
  27. package/src/machine-interface.js +54 -19
  28. package/src/migrate.js +14 -4
  29. package/src/modules.js +16 -5
  30. package/src/progress/change-registry.js +85 -70
  31. package/src/progress/consistency-doctor.js +71 -14
  32. package/src/progress/shared.js +8 -5
  33. package/src/progress/stage-machine.js +67 -65
  34. package/src/progress/step-store.js +72 -67
  35. package/src/progress.js +448 -236
  36. package/src/quicklog.js +59 -12
  37. package/src/run/command.js +78 -39
  38. package/src/run/complete-handlers.js +1028 -889
  39. package/src/run/complete.js +132 -212
  40. package/src/run/concurrent-detect.js +132 -0
  41. package/src/run/gates.js +302 -19
  42. package/src/run/prompt.js +22 -68
  43. package/src/run/quick-audit.js +10 -9
  44. package/src/run/shared.js +722 -600
  45. package/src/run/stage.js +43 -24
  46. package/src/scan-postcheck.js +9 -31
  47. package/src/sillyhub-mcp/client.js +394 -0
  48. package/src/sillyhub-mcp/config.js +62 -0
  49. package/src/spec-dir-typo.js +71 -0
  50. package/src/stage-contract-engine.js +22 -1
  51. package/src/stage-contract-spec.js +11 -4
  52. package/src/stage-contract.js +26 -3
  53. package/src/stage-review.js +118 -10
  54. package/src/stages/archive.js +4 -6
  55. package/src/stages/brainstorm-auto.js +26 -20
  56. package/src/stages/brainstorm.js +29 -5
  57. package/src/stages/cmd-existence.js +145 -0
  58. package/src/stages/doctor.js +10 -7
  59. package/src/stages/execute.js +64 -22
  60. package/src/stages/plan-postcheck.js +152 -0
  61. package/src/stages/plan.js +16 -31
  62. package/src/stages/quick.js +25 -9
  63. package/src/stages/scan.js +53 -8
  64. package/src/stages/status.js +0 -1
  65. package/src/stages/verify.js +14 -9
  66. package/src/sync.js +433 -16
  67. package/src/task-review.js +59 -1
  68. package/src/verify-postcheck.js +938 -862
  69. package/src/version.js +19 -0
  70. package/src/worktree-apply.js +218 -93
  71. package/src/worktree-deps.js +89 -5
  72. package/src/worktree.js +213 -111
  73. package/templates/prompts/taskcard-rules.md +27 -0
  74. package/templates/prompts/verify-probes.md +3 -3
  75. package/.claude/skills/sillyspec-propose/SKILL.md +0 -87
  76. package/src/stages/propose.js +0 -181
@@ -66,7 +66,7 @@ function extractUserInstall(yamlText) {
66
66
  }
67
67
 
68
68
  /** 从 local.yaml 提取 project.type;缺失时按文件特征推断 */
69
- function detectProjectType(worktreePath, specBase) {
69
+ export function detectProjectType(worktreePath, specBase) {
70
70
  const yamlText = readLocalYaml(specBase, worktreePath);
71
71
  if (yamlText) {
72
72
  const m = yamlText.match(/type:\s*(\S+)/);
@@ -75,11 +75,14 @@ function detectProjectType(worktreePath, specBase) {
75
75
  if (existsSync(join(worktreePath, 'pom.xml'))) return 'maven';
76
76
  if (existsSync(join(worktreePath, 'build.gradle')) || existsSync(join(worktreePath, 'build.gradle.kts'))) return 'gradle';
77
77
  if (existsSync(join(worktreePath, 'package.json'))) return 'nodejs';
78
+ // Python:pyproject.toml(uv/poetry/pdm)或 requirements.txt(pip)——治 worktree 内 ruff/pre-commit 等
79
+ // 二进制不供给(exec 复盘 3-②:detectProjectType 原无 python 分支,python 项目根被误判 generic → n/a)
80
+ if (existsSync(join(worktreePath, 'pyproject.toml')) || existsSync(join(worktreePath, 'requirements.txt'))) return 'python';
78
81
  return 'generic';
79
82
  }
80
83
 
81
84
  /** 按 project.type + lockfile 推断 install 命令(无 commands.install 时)*/
82
- function inferInstallCommand(projectType, worktreePath, userInstall) {
85
+ export function inferInstallCommand(projectType, worktreePath, userInstall) {
83
86
  if (userInstall) return userInstall;
84
87
  switch (projectType) {
85
88
  case 'nodejs':
@@ -91,6 +94,11 @@ function inferInstallCommand(projectType, worktreePath, userInstall) {
91
94
  return 'mvn -o test';
92
95
  case 'gradle':
93
96
  return './gradlew test';
97
+ case 'python':
98
+ // uv 优先(现代 Python 工具链,pyproject.toml/uv.lock 项目走 uv sync 建 .venv + 装依赖,
99
+ // 与 execute worktree 环境预告一致);纯 requirements.txt(无 pyproject)回退 pip
100
+ if (existsSync(join(worktreePath, 'uv.lock')) || existsSync(join(worktreePath, 'pyproject.toml'))) return 'uv sync';
101
+ return 'pip install -r requirements.txt';
94
102
  default:
95
103
  return null; // generic → n/a
96
104
  }
@@ -190,11 +198,16 @@ function linkOneDir(wtDir, mainDir) {
190
198
  *
191
199
  * @param {string} worktreePath - worktree 根目录
192
200
  * @param {string} mainCwd - 主 checkout 根目录(node_modules 来源)
193
- * @param {{ specBase?: string, timeout?: number }} opts
201
+ * @param {{ specBase?: string, timeout?: number, force?: boolean }} opts
202
+ * - force=true(doctor --fix 强制重装):绕过根目录 lockfile 一致快路径(下方 line 216 区块),
203
+ * 强制走 install 分支重装。该快路径是 tryLink 的唯一根调用点,绕过它即同时绕过 tryLink
204
+ * 的 preexisting 幂等短路(101-110),实现"强制重装"语义。配合调用方先解 junction
205
+ * (worktree.js _doctorReprovision),避免 install 经 junction 误改主仓 node_modules。
206
+ * modules 子模块 link 不受 force 影响(仅根 install)。
194
207
  * @returns {{ depsStatus, depsMethod, depsSource, depsLockHash, depsCheckedAt, depsError?, depsModules? }}
195
208
  */
196
209
  export function provisionDeps(worktreePath, mainCwd, opts = {}) {
197
- const { specBase = null, timeout = DEFAULT_TIMEOUT_MS } = opts;
210
+ const { specBase = null, timeout = DEFAULT_TIMEOUT_MS, force = false } = opts;
198
211
  const depsCheckedAt = new Date().toISOString();
199
212
  const yamlText = readLocalYaml(specBase, worktreePath);
200
213
  const wtHash = lockfileHash(worktreePath);
@@ -210,10 +223,11 @@ export function provisionDeps(worktreePath, mainCwd, opts = {}) {
210
223
  result = { depsStatus: 'n/a', depsMethod: null, depsSource: null, depsLockHash: wtHash };
211
224
  } else {
212
225
  // 快路径:main 有 node_modules 且 lockfile hash 一致 → junction/symlink
226
+ // force=true 时绕过(doctor --fix 强制重装;绕过此块即同时绕过 tryLink 101-110 幂等短路)
213
227
  const mainNodeModules = mainCwd ? join(mainCwd, 'node_modules') : null;
214
228
  const mainHash = lockfileHash(mainCwd);
215
229
  let linked = false;
216
- if (mainNodeModules && existsSync(mainNodeModules) && mainHash && wtHash && mainHash === wtHash) {
230
+ if (!force && mainNodeModules && existsSync(mainNodeModules) && mainHash && wtHash && mainHash === wtHash) {
217
231
  const linkResult = tryLink(mainNodeModules, join(worktreePath, 'node_modules'));
218
232
  if (linkResult.ok) {
219
233
  result = {
@@ -265,3 +279,73 @@ export function provisionDeps(worktreePath, mainCwd, opts = {}) {
265
279
 
266
280
  return result;
267
281
  }
282
+
283
+ /**
284
+ * 统一的 worktree deps 新鲜度判定(H1,change 2026-08-05-tooling-feedback-fixes)。
285
+ * 供 doctor(worktree.js)与 ensureDepsFreshness(run/stage.js)共用,消除两处双写漂移。
286
+ *
287
+ * 判定优先级:failed → missing → stale → main-drift → fresh
288
+ * - failed: meta.depsStatus==='failed'(上次供给失败,占最高优先级)
289
+ * - missing: depsStatus ∈ {linked, installed} 且 node_modules 不存在
290
+ * - stale: wtHash 与 meta.depsLockHash 不一致(worktree 自身 lockfile 在上次供给后变化)
291
+ * - main-drift: wtHash 与主仓 mainHash 不一致(主仓 lockfile 漂移;复用 linkOneDir 177-178 的 mismatch 判据)
292
+ * - fresh: 其余情况
293
+ *
294
+ * lockfileHash 返回 null(无 lockfile/package.json)时相关比较优雅降级——不报 stale / main-drift,
295
+ * 避免无 lockfile 项目(如纯 generic / 非 nodejs)误判漂移。
296
+ *
297
+ * @param {object} [meta] - worktree meta(读 depsStatus / depsLockHash / depsError)
298
+ * @param {string} wtPath - worktree 根目录
299
+ * @param {string} mainCwd - 主 checkout 根目录
300
+ * @returns {{ status: 'fresh'|'missing'|'stale'|'main-drift'|'failed', detail: string, wtHash: (string|null), mainHash: (string|null), metaLockHash: (string|null) }}
301
+ */
302
+ export function checkDepsFreshness(meta, wtPath, mainCwd) {
303
+ const depsStatus = meta && meta.depsStatus ? meta.depsStatus : null;
304
+ const metaLockHash = meta && meta.depsLockHash ? meta.depsLockHash : null;
305
+ const wtHash = lockfileHash(wtPath);
306
+ const mainHash = lockfileHash(mainCwd);
307
+
308
+ // 1. failed 最高优先级(对齐 task-01 蓝图:failed → missing → stale → main-drift → fresh)
309
+ if (depsStatus === 'failed') {
310
+ return {
311
+ status: 'failed',
312
+ detail: '上次依赖供给失败' + (meta && meta.depsError ? ': ' + meta.depsError : ''),
313
+ wtHash, mainHash, metaLockHash,
314
+ };
315
+ }
316
+
317
+ // 2. missing:曾 link/install 但 node_modules 已丢失(对齐 doctor 914-915 / ensure 403)
318
+ const nmExists = wtPath ? existsSync(join(wtPath, 'node_modules')) : false;
319
+ if (['linked', 'installed'].includes(depsStatus) && !nmExists) {
320
+ return {
321
+ status: 'missing',
322
+ detail: `meta.depsStatus=${depsStatus} 但 node_modules 缺失`,
323
+ wtHash, mainHash, metaLockHash,
324
+ };
325
+ }
326
+
327
+ // 3. stale:worktree 自身 lockfile 与 meta 快照不一致(对齐 doctor 916-917 / ensure 404)
328
+ if (metaLockHash && wtHash && wtHash !== metaLockHash) {
329
+ return {
330
+ status: 'stale',
331
+ detail: `lockfile 变化 (${metaLockHash} -> ${wtHash})`,
332
+ wtHash, mainHash, metaLockHash,
333
+ };
334
+ }
335
+
336
+ // 4. main-drift(新增):wtHash 与主仓 mainHash 不一致——主仓 lockfile 更新过、worktree 未跟
337
+ if (wtHash && mainHash && wtHash !== mainHash) {
338
+ return {
339
+ status: 'main-drift',
340
+ detail: `worktree lockfile 与主仓不一致 (wt=${wtHash} main=${mainHash})`,
341
+ wtHash, mainHash, metaLockHash,
342
+ };
343
+ }
344
+
345
+ // 5. fresh
346
+ return {
347
+ status: 'fresh',
348
+ detail: '依赖新鲜',
349
+ wtHash, mainHash, metaLockHash,
350
+ };
351
+ }