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.
- package/.claude/skills/sillyspec-auto/SKILL.md +8 -13
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +8 -6
- package/.claude/skills/sillyspec-continue/SKILL.md +3 -3
- package/.claude/skills/sillyspec-doctor/SKILL.md +1 -1
- package/.claude/skills/sillyspec-execute/SKILL.md +10 -4
- package/.claude/skills/sillyspec-plan/SKILL.md +1 -1
- package/.claude/skills/sillyspec-quick/SKILL.md +3 -1
- package/.claude/skills/sillyspec-verify/SKILL.md +1 -1
- package/.sillyspec-platform-cleaned +1 -0
- package/.sillyspec-platform.json +7 -0
- package/README.md +9 -0
- package/package.json +2 -2
- package/src/change-list.js +11 -8
- package/src/change-risk-profile.js +22 -0
- package/src/db.js +170 -161
- package/src/dispatch/backends/local-agent.js +152 -0
- package/src/dispatch/backends/sillyhub-mcp.js +242 -0
- package/src/dispatch/probe.js +210 -0
- package/src/dispatch/strategy.js +121 -0
- package/src/doctor-diagnostics.js +19 -25
- package/src/fs-atomic.js +69 -69
- package/src/git-helper.js +66 -0
- package/src/hooks/worktree-guard.js +64 -55
- package/src/index.js +326 -36
- package/src/init.js +7 -14
- package/src/local-detect.js +120 -110
- package/src/machine-interface.js +54 -19
- package/src/migrate.js +14 -4
- package/src/modules.js +16 -5
- package/src/progress/change-registry.js +85 -70
- package/src/progress/consistency-doctor.js +71 -14
- package/src/progress/shared.js +8 -5
- package/src/progress/stage-machine.js +67 -65
- package/src/progress/step-store.js +72 -67
- package/src/progress.js +448 -236
- package/src/quicklog.js +59 -12
- package/src/run/command.js +78 -39
- package/src/run/complete-handlers.js +1028 -889
- package/src/run/complete.js +132 -212
- package/src/run/concurrent-detect.js +132 -0
- package/src/run/gates.js +302 -19
- package/src/run/prompt.js +22 -68
- package/src/run/quick-audit.js +10 -9
- package/src/run/shared.js +722 -600
- package/src/run/stage.js +43 -24
- package/src/scan-postcheck.js +9 -31
- package/src/sillyhub-mcp/client.js +394 -0
- package/src/sillyhub-mcp/config.js +62 -0
- package/src/spec-dir-typo.js +71 -0
- package/src/stage-contract-engine.js +22 -1
- package/src/stage-contract-spec.js +11 -4
- package/src/stage-contract.js +26 -3
- package/src/stage-review.js +118 -10
- package/src/stages/archive.js +4 -6
- package/src/stages/brainstorm-auto.js +26 -20
- package/src/stages/brainstorm.js +29 -5
- package/src/stages/cmd-existence.js +145 -0
- package/src/stages/doctor.js +10 -7
- package/src/stages/execute.js +64 -22
- package/src/stages/plan-postcheck.js +152 -0
- package/src/stages/plan.js +16 -31
- package/src/stages/quick.js +25 -9
- package/src/stages/scan.js +53 -8
- package/src/stages/status.js +0 -1
- package/src/stages/verify.js +14 -9
- package/src/sync.js +433 -16
- package/src/task-review.js +59 -1
- package/src/verify-postcheck.js +938 -862
- package/src/version.js +19 -0
- package/src/worktree-apply.js +218 -93
- package/src/worktree-deps.js +89 -5
- package/src/worktree.js +213 -111
- package/templates/prompts/taskcard-rules.md +27 -0
- package/templates/prompts/verify-probes.md +3 -3
- package/.claude/skills/sillyspec-propose/SKILL.md +0 -87
- package/src/stages/propose.js +0 -181
package/src/worktree-deps.js
CHANGED
|
@@ -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
|
+
}
|