sillyspec 3.26.15 → 3.27.1
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-explore/SKILL.md +10 -3
- package/.claude/skills/sillyspec-verify/SKILL.md +2 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/packages/dashboard/server/index.js +22 -6
- package/src/change-risk-profile.js +21 -6
- package/src/db.js +14 -3
- package/src/docs-check.js +598 -570
- package/src/doctor-diagnostics.js +53 -1
- package/src/endpoint-extractor.js +21 -2
- package/src/fs-atomic.js +4 -2
- package/src/git-helper.js +73 -5
- package/src/hooks/claude-pre-tool-use.cjs +10 -5
- package/src/hooks/worktree-guard.js +22 -3
- package/src/index.js +49 -6
- package/src/module-impact.js +21 -1
- package/src/module-resolve.js +211 -0
- package/src/modules.js +82 -2
- package/src/progress/step-store.js +12 -8
- package/src/progress.js +6 -2
- package/src/run/complete-handlers.js +17 -2
- package/src/run/complete.js +43 -7
- package/src/run/gates.js +83 -14
- package/src/run/prompt.js +16 -2
- package/src/run/quick-audit.js +5 -2
- package/src/run/shared.js +46 -18
- package/src/run/stage.js +37 -3
- package/src/stage-contract.js +14 -1
- package/src/stages/archive.js +3 -1
- package/src/stages/execute.js +170 -18
- package/src/stages/explore.js +11 -2
- package/src/stages/quick.js +4 -3
- package/src/stages/verify.js +9 -2
- package/src/sync.js +44 -3
- package/src/task-review.js +55 -7
- package/src/taskcard.js +52 -22
- package/src/verify-postcheck.js +223 -6
- package/src/worktree-apply.js +28 -10
- package/src/worktree-deps.js +59 -5
- package/src/worktree.js +70 -6
package/src/worktree.js
CHANGED
|
@@ -1003,7 +1003,7 @@ export class WorktreeManager {
|
|
|
1003
1003
|
* @param {string} wtPath - worktree 根目录(meta.worktreePath)
|
|
1004
1004
|
* @returns {{ ok: boolean, msg: string }}
|
|
1005
1005
|
*/
|
|
1006
|
-
_doctorReprovision(name, wtPath) {
|
|
1006
|
+
_doctorReprovision(name, wtPath, opts = {}) {
|
|
1007
1007
|
try {
|
|
1008
1008
|
const meta = this.getMeta(name) || {};
|
|
1009
1009
|
const isInPlace = meta.mode === 'in-place-fallback';
|
|
@@ -1033,9 +1033,14 @@ export class WorktreeManager {
|
|
|
1033
1033
|
}
|
|
1034
1034
|
}
|
|
1035
1035
|
}
|
|
1036
|
+
// 触发分流(坑 doctor-reprovision-junction-missing,2026-08-22 实证:junction 未建的
|
|
1037
|
+
// missing/failed 场景被 force install 强制重装——install 静默失败面大且慢;lockfile 一致时
|
|
1038
|
+
// 重建 junction 才是对症动作。opts.relinkFirst(missing/failed 触发)→ 非 force 跑:解链后
|
|
1039
|
+
// node_modules 不存在,tryLink 直接重建;lockfile 漂移自动落 install。stale/main-drift 维持
|
|
1040
|
+
// force 重装(依赖确实变了)。
|
|
1036
1041
|
const deps = provisionDeps(wtPath, this.cwd, {
|
|
1037
1042
|
specBase: join(this.cwd, '.sillyspec'),
|
|
1038
|
-
force:
|
|
1043
|
+
force: !opts.relinkFirst,
|
|
1039
1044
|
}) || {};
|
|
1040
1045
|
const metaPath = join(this.getWorktreePath(name), META_FILE);
|
|
1041
1046
|
// 成功重供时清掉旧 depsError(provisionDeps 成功结果不含该键,{...meta,...deps} 会残留旧值)
|
|
@@ -1153,7 +1158,29 @@ export class WorktreeManager {
|
|
|
1153
1158
|
// 放宽原 909 in-place-fallback 守卫:in-place 也跑(main-drift/stale/failed 对它同样有意义,
|
|
1154
1159
|
// 真 in-place 的 wtPath===主仓→wtHash===mainHash→不会误报 main-drift,仅 failed/stale 有意义)。
|
|
1155
1160
|
// fix 时 _doctorReprovision 自行判断是否解链(in-place 不解链,仅 install)。
|
|
1161
|
+
// 归档态前置闸(坑 doctor-reprovision-archived-change,2026-08-21 实证:已归档变更的
|
|
1162
|
+
// worktree 被 doctor 当活跃任务 re-provision——给死目录装依赖)。归档判定(changes/archive/
|
|
1163
|
+
// 实体 或 进度库 status='archived')命中 → 跳过供给,fix 时走 cleanup(其内建
|
|
1164
|
+
// hasUnappliedChanges 护栏仍在,未落仓内容照样拒删)。
|
|
1156
1165
|
if (meta && meta.worktreePath && existsSync(meta.worktreePath)) {
|
|
1166
|
+
const isArchivedChange = existsSync(join(this.cwd, '.sillyspec', 'changes', 'archive', name))
|
|
1167
|
+
|| this._isChangeArchivedInDb(name);
|
|
1168
|
+
if (isArchivedChange) {
|
|
1169
|
+
issues.push({ type: 'worktree-archived-change', name, fixable: meta.mode !== 'native-worktree',
|
|
1170
|
+
detail: `变更 ${name} 已归档但 worktree 残留——不供给依赖(死目录),建议清理(cleanup 内建未落仓护栏)` });
|
|
1171
|
+
if (fix && meta.mode !== 'native-worktree') {
|
|
1172
|
+
try {
|
|
1173
|
+
const r = this.cleanup(name);
|
|
1174
|
+
if (r.result === 'cleaned' || r.result === 'force-cleaned') {
|
|
1175
|
+
fixed.push(`cleaned archived-change worktree: ${name}`);
|
|
1176
|
+
} else if (r.result === 'blocked') {
|
|
1177
|
+
unfixable.push(`cleanup blocked (archived change 有未落仓内容): ${name} — 先 apply/commit 再清`);
|
|
1178
|
+
} else {
|
|
1179
|
+
unfixable.push(`cleanup skipped (archived change): ${name}`);
|
|
1180
|
+
}
|
|
1181
|
+
} catch { unfixable.push(`cleanup failed (archived change): ${name}`); }
|
|
1182
|
+
}
|
|
1183
|
+
} else {
|
|
1157
1184
|
const wtPath = meta.worktreePath;
|
|
1158
1185
|
const fresh = checkDepsFreshness(meta, wtPath, this.cwd);
|
|
1159
1186
|
const issueTypeByStatus = {
|
|
@@ -1166,10 +1193,13 @@ export class WorktreeManager {
|
|
|
1166
1193
|
if (issueType) {
|
|
1167
1194
|
issues.push({ type: issueType, name, detail: fresh.detail, fixable: true });
|
|
1168
1195
|
if (fix) {
|
|
1169
|
-
|
|
1196
|
+
// relinkFirst:missing/failed 多为 junction 丢失/上次供给失败——link 优先重建;
|
|
1197
|
+
// stale/main-drift 是依赖真变了——force 重装
|
|
1198
|
+
const r = this._doctorReprovision(name, wtPath, { relinkFirst: issueType === 'deps-missing' || issueType === 'deps-failed' });
|
|
1170
1199
|
(r.ok ? fixed : unfixable).push(r.msg);
|
|
1171
1200
|
}
|
|
1172
1201
|
}
|
|
1202
|
+
}
|
|
1173
1203
|
}
|
|
1174
1204
|
|
|
1175
1205
|
// meta 存在但 worktree 目录不存在
|
|
@@ -1311,10 +1341,24 @@ export class WorktreeManager {
|
|
|
1311
1341
|
|
|
1312
1342
|
// 2) 候选集中尚未落到主工作区 HEAD 的子集
|
|
1313
1343
|
const pending = this._changesAlreadyOnMain(worktreePath, tracked, untracked);
|
|
1314
|
-
|
|
1315
|
-
|
|
1344
|
+
// 3) main 工作区副本降噪层(坑 unapplied-false-positive-workspace-copy,2026-08-21 实证:
|
|
1345
|
+
// apply 后 main 工作区有逐字节一致副本但未 commit → HEAD-only 判定报「未落仓」虚警拦
|
|
1346
|
+
// doctor)。原注释「不查 main 工作区未提交副本(防误删)」的保护意图是内容未落地——
|
|
1347
|
+
// 逐字节一致时内容确已落地(删 worktree 无损),仅逐字节不同的才保守保留。
|
|
1348
|
+
const pendingAfterWs = pending.filter(f => {
|
|
1349
|
+
try {
|
|
1350
|
+
const mainCopy = join(this.cwd, f);
|
|
1351
|
+
if (!existsSync(mainCopy)) return true; // main 工作区无副本 → 仍算未落仓
|
|
1352
|
+
const wtCopy = join(worktreePath, f);
|
|
1353
|
+
if (!existsSync(wtCopy)) return true; // worktree 侧是删除态、main 侧却在 → 内容分叉,保留
|
|
1354
|
+
const a = readFileSync(wtCopy); const b = readFileSync(mainCopy);
|
|
1355
|
+
return !(a.length === b.length && a.equals(b)); // 逐字节一致 → 已落地(剔除)
|
|
1356
|
+
} catch { return true }
|
|
1357
|
+
})
|
|
1358
|
+
if (pendingAfterWs.length === 0) {
|
|
1359
|
+
return { hasChanges: false, changedFiles: [], reason: 'all changes already on main (HEAD or identical workspace copy)' };
|
|
1316
1360
|
}
|
|
1317
|
-
return { hasChanges: true, changedFiles:
|
|
1361
|
+
return { hasChanges: true, changedFiles: pendingAfterWs };
|
|
1318
1362
|
} catch (e) {
|
|
1319
1363
|
// 检测失败时保守处理:视为有变更,保留 worktree
|
|
1320
1364
|
return { hasChanges: true, changedFiles: [], reason: `check failed: ${e.message}` };
|
|
@@ -1385,6 +1429,26 @@ export class WorktreeManager {
|
|
|
1385
1429
|
return map;
|
|
1386
1430
|
}
|
|
1387
1431
|
|
|
1432
|
+
/**
|
|
1433
|
+
* DB 归档态探测(坑 doctor-reprovision-archived-change):changes 表 status='archived'。
|
|
1434
|
+
* 只读直查(node:sqlite)——doctor 是同步方法,惰性动态 import 不可用;DB 不存在/无行/
|
|
1435
|
+
* 读失败 → false(保守退回普通 deps 供给路径)。
|
|
1436
|
+
* @param {string} name
|
|
1437
|
+
* @returns {boolean}
|
|
1438
|
+
*/
|
|
1439
|
+
_isChangeArchivedInDb(name) {
|
|
1440
|
+
try {
|
|
1441
|
+
const dbPath = join(this.cwd, '.sillyspec', '.runtime', 'sillyspec.db');
|
|
1442
|
+
if (!existsSync(dbPath)) return false;
|
|
1443
|
+
const { openDatabase } = require('./db-engine.js');
|
|
1444
|
+
const db = openDatabase(dbPath, { readOnly: true });
|
|
1445
|
+
try {
|
|
1446
|
+
const row = db.prepare('SELECT status FROM changes WHERE name = ?').get(name);
|
|
1447
|
+
return row?.status === 'archived';
|
|
1448
|
+
} finally { try { db.close() } catch {} }
|
|
1449
|
+
} catch { return false }
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1388
1452
|
/**
|
|
1389
1453
|
* 从 worktree 路径反推 changeName
|
|
1390
1454
|
* @private
|