sillyspec 3.27.1 → 3.27.5
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-execute/SKILL.md +8 -0
- package/.claude/skills/sillyspec-quick/SKILL.md +2 -1
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-archive/SKILL.md +87 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-auto/SKILL.md +93 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-commit/SKILL.md +67 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-continue/SKILL.md +47 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-execute/SKILL.md +214 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-explore/SKILL.md +102 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-export/SKILL.md +57 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-init/SKILL.md +177 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-plan/SKILL.md +156 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-quick/SKILL.md +116 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-resume/SKILL.md +70 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-scan/SKILL.md +79 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-state/SKILL.md +64 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-status/SKILL.md +54 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-verify/SKILL.md +87 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-workspace/SKILL.md +175 -0
- package/.claude/worktrees/sillyspec-data-model-research/.gitattributes +2 -0
- package/.claude/worktrees/sillyspec-data-model-research/.husky/pre-push +20 -0
- package/.claude/worktrees/sillyspec-data-model-research/README.md +203 -0
- package/.claude/worktrees/sillyspec-data-model-research/SKILL.md +91 -0
- package/.claude/worktrees/sillyspec-data-model-research/bin/sillyspec.js +2 -0
- package/.claude/worktrees/sillyspec-data-model-research/logo.jpg +0 -0
- package/.claude/worktrees/sillyspec-data-model-research/package-lock.json +977 -0
- package/.claude/worktrees/sillyspec-data-model-research/package.json +39 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/favicon.jpg +0 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/index.html +16 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/logo.jpg +0 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/prototype-dashboard.html +836 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/prototype-overview.html +256 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/package.json +25 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/executor.js +95 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/index.js +679 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/parser.js +526 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/watcher.js +357 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/agent-session-log.js +803 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/change-list.js +290 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/change-risk-profile.js +270 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/check-primitives.js +65 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/classify-change.js +109 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/commit-suggest.js +202 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/config-cat.js +88 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/config-schema.js +328 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/constants.js +97 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/contract-matrix.js +480 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/datetime.js +20 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/db-engine.js +55 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/db.js +381 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/decision-distill.js +517 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/backends/local-agent.js +155 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/backends/sillyhub-mcp.js +245 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/probe.js +246 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/strategy.js +121 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/docs-check.js +825 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/docs-debt.js +393 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/docs-gate.js +107 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/doctor-diagnostics.js +1099 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/endpoint-extractor.js +420 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/foreign-declared.js +78 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/fs-atomic.js +73 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/git-helper.js +150 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/hooks/claude-pre-tool-use.cjs +130 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/hooks/worktree-guard.js +892 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/index.js +2798 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/init.js +626 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/knowledge-match.js +231 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/local-detect.js +128 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/local-register.js +104 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/machine-interface.js +543 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/migrate.js +127 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/module-impact.js +166 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/module-resolve.js +222 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/modules.js +580 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/change-registry.js +336 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/consistency-doctor.js +355 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/shared.js +36 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/stage-machine.js +594 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/step-store.js +265 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress.js +1304 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/quick-recommend.js +115 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/quicklog.js +852 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/review-tier.js +77 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/command.js +1595 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/complete-handlers.js +1552 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/complete.js +1259 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/concurrent-detect.js +156 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/gates.js +1084 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/multi-repo-context.js +208 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/next.js +128 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/prompt.js +843 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/quick-audit.js +186 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/scan-profile.js +269 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/shared.js +1562 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/stage.js +584 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run.js +23 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/scan-diff.js +388 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/scan-postcheck.js +512 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/scan-staleness.js +95 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/setup.js +414 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/sillyhub-mcp/client.js +597 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/sillyhub-mcp/config.js +62 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/spec-dir-typo.js +50 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/spec-sync.js +358 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract-engine.js +171 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract-spec.js +550 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract.js +889 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stage-review.js +597 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/archive.js +199 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/brainstorm-auto.js +236 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/brainstorm.js +617 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/cmd-existence.js +145 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/doctor.js +540 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/execute.js +1275 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/explore.js +43 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/index.js +29 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/knowledge.js +514 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/plan-postcheck.js +1344 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/plan.js +650 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/quick.js +167 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/scan.js +608 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/status.js +64 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/verify.js +246 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/sync.js +1470 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/task-review.js +1498 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/taskcard.js +210 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/verify-postcheck.js +1718 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/verify-probes.js +277 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/version.js +19 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/workflow.js +600 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/workspace.js +125 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/worktree-apply.js +1446 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/worktree-deps.js +576 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/worktree.js +1715 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/claude-instruction.md +23 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/taskcard-rules.md +28 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/testcase-design.md +12 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/verify-probes.md +32 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/workflows/archive-impact.yaml +79 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/workflows/scan-docs.yaml +132 -0
- package/README.md +1 -1
- package/eval/README.md +86 -0
- package/eval/eval.config.json +28 -0
- package/eval/lib/adapters/cli.mjs +63 -0
- package/eval/lib/adapters/dryrun.mjs +27 -0
- package/eval/lib/config.mjs +82 -0
- package/eval/lib/db.mjs +76 -0
- package/eval/lib/report.mjs +120 -0
- package/eval/lib/runner.mjs +157 -0
- package/eval/lib/tasks.mjs +73 -0
- package/eval/lib/util.mjs +107 -0
- package/eval/lib/workdir.mjs +30 -0
- package/eval/run.mjs +83 -0
- package/eval/tasks/001-slugify/files/package.json +6 -0
- package/eval/tasks/001-slugify/solution/src/slugify.mjs +26 -0
- package/eval/tasks/001-slugify/task.json +6 -0
- package/eval/tasks/001-slugify/verify.mjs +28 -0
- package/eval/tasks/002-each-day/files/package.json +6 -0
- package/eval/tasks/002-each-day/files/src/dates.mjs +4 -0
- package/eval/tasks/002-each-day/solution/src/dates.mjs +30 -0
- package/eval/tasks/002-each-day/task.json +6 -0
- package/eval/tasks/002-each-day/verify.mjs +31 -0
- package/eval/tasks/003-word-report/files/data/notes.txt +5 -0
- package/eval/tasks/003-word-report/files/package.json +6 -0
- package/eval/tasks/003-word-report/solution/bin/report.mjs +36 -0
- package/eval/tasks/003-word-report/task.json +6 -0
- package/eval/tasks/003-word-report/verify.mjs +38 -0
- package/package.json +4 -2
- package/src/agent-session-log.js +803 -0
- package/src/change-list.js +22 -2
- package/src/change-risk-profile.js +83 -14
- package/src/config-schema.js +18 -3
- package/src/contract-matrix.js +153 -8
- package/src/datetime.js +20 -0
- package/src/decision-distill.js +517 -0
- package/src/dispatch/backends/local-agent.js +4 -4
- package/src/docs-check.js +227 -0
- package/src/docs-debt.js +187 -24
- package/src/doctor-diagnostics.js +102 -2
- package/src/foreign-declared.js +78 -0
- package/src/index.js +99 -10
- package/src/init.js +9 -7
- package/src/knowledge-match.js +107 -6
- package/src/local-register.js +7 -1
- package/src/module-resolve.js +12 -1
- package/src/modules.js +2 -1
- package/src/plan-adopt-waves.js +126 -0
- package/src/quicklog.js +61 -7
- package/src/run/command.js +139 -20
- package/src/run/complete-handlers.js +48 -19
- package/src/run/complete.js +43 -3
- package/src/run/gates.js +87 -22
- package/src/run/prompt.js +84 -42
- package/src/run/quick-audit.js +28 -0
- package/src/run/shared.js +176 -11
- package/src/run/stage.js +23 -0
- package/src/run.js +2 -2
- package/src/scan-postcheck.js +30 -17
- package/src/stage-contract-spec.js +2 -1
- package/src/stage-contract.js +68 -4
- package/src/stages/archive.js +53 -12
- package/src/stages/brainstorm-auto.js +7 -0
- package/src/stages/brainstorm.js +11 -0
- package/src/stages/doctor.js +157 -1
- package/src/stages/execute.js +33 -6
- package/src/stages/plan-postcheck.js +79 -18
- package/src/stages/plan.js +4 -0
- package/src/stages/quick.js +6 -1
- package/src/stages/verify.js +31 -14
- package/src/sync.js +94 -2
- package/src/task-review.js +2 -2
- package/src/taskcard-placeholders.js +21 -0
- package/src/taskcard.js +10 -1
- package/src/verify-postcheck.js +315 -13
- package/src/verify-probes.js +46 -4
- package/src/worktree-apply.js +184 -7
- package/src/worktree-deps.js +69 -0
- package/src/worktree.js +201 -58
- package/templates/prompts/taskcard-rules.md +1 -0
package/src/worktree.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { execFileSync } from 'child_process';
|
|
12
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync, readdirSync, statSync, lstatSync, readlinkSync, unlinkSync } from 'fs';
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync, readdirSync, statSync, lstatSync, readlinkSync, unlinkSync, copyFileSync } from 'fs';
|
|
13
13
|
import { join, resolve, dirname, relative, isAbsolute } from 'path';
|
|
14
14
|
import { createHash } from 'crypto';
|
|
15
15
|
import { provisionDeps, checkDepsFreshness } from './worktree-deps.js';
|
|
@@ -24,6 +24,65 @@ const WORKTREES_REL = '.sillyspec/.runtime/worktrees';
|
|
|
24
24
|
const BRANCH_PREFIX = 'sillyspec/';
|
|
25
25
|
const META_FILE = 'meta.json';
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* 解除 worktree 内全部 node_modules junction/symlink(根 + meta.depsModules 各子模块)。
|
|
29
|
+
*
|
|
30
|
+
* 坑 ghost-dir-junction-pierce(2026-08-23 实证:worktree apply 后目录残留,人工 rm -rf 经
|
|
31
|
+
* junction 穿透删主仓 node_modules——user-inputs 两次事故实录;npm ci / git worktree remove
|
|
32
|
+
* 同样跟随 junction)。任何删除 worktree 目录的路径(cleanup / create 幽灵强删 / doctor 修复)
|
|
33
|
+
* 必须先解链;此前解链逻辑三处复制粘贴且**不覆盖 modules 子模块 junction**
|
|
34
|
+
* (meta.depsModules 记录的 wt/<module>/node_modules)。
|
|
35
|
+
*
|
|
36
|
+
* lstat/rmdir 失败 fail-loud(D-001@v1:静默跳过解链的后果是穿透删主仓,宁可阻断)。
|
|
37
|
+
*
|
|
38
|
+
* @param {string} worktreePath worktree 根目录
|
|
39
|
+
* @param {object} [meta] 可含 depsModules([{path}] 子模块清单);null 时只解根
|
|
40
|
+
* @param {string[]} [details] 可选,记录解链明细(cleanup details 兼容)
|
|
41
|
+
* @returns {number} 实际解除的链接数
|
|
42
|
+
*/
|
|
43
|
+
export function unlinkNodeModulesLinks(worktreePath, meta = null, details = null) {
|
|
44
|
+
let removed = 0;
|
|
45
|
+
const targets = [join(worktreePath, 'node_modules')];
|
|
46
|
+
for (const m of Array.isArray(meta && meta.depsModules) ? meta.depsModules : []) {
|
|
47
|
+
// 子模块 path 安全校验(与 worktree-deps isSafeModulePath 同口径):拒绝绝对路径与 .. 段
|
|
48
|
+
if (m && typeof m.path === 'string' && m.path && !isAbsolute(m.path) && !m.path.split(/[\\/]+/).includes('..')) {
|
|
49
|
+
targets.push(join(worktreePath, m.path, 'node_modules'));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (const nm of targets) {
|
|
53
|
+
if (!existsSync(nm)) continue;
|
|
54
|
+
let isLink;
|
|
55
|
+
try {
|
|
56
|
+
isLink = lstatSync(nm).isSymbolicLink();
|
|
57
|
+
} catch (e) {
|
|
58
|
+
throw new Error(`worktree node_modules junction 检测失败(疑似 EPERM:杀毒/索引锁 junction),阻断删除以保护主仓 node_modules:${e.message}。请关闭占用进程或手动 rmdir "${nm}" 后重试`);
|
|
59
|
+
}
|
|
60
|
+
if (!isLink) continue;
|
|
61
|
+
try {
|
|
62
|
+
if (process.platform === 'win32') {
|
|
63
|
+
// Windows rmdir 删 junction(reparse point)不跟随目标;execFileSync 数组形式不经 shell
|
|
64
|
+
execFileSync('cmd.exe', ['/c', 'rmdir', nm]);
|
|
65
|
+
} else {
|
|
66
|
+
unlinkSync(nm);
|
|
67
|
+
}
|
|
68
|
+
removed++;
|
|
69
|
+
if (details) details.push(`worktree node_modules junction/symlink removed: ${relative(worktreePath, nm) || '(root)'} (protect main checkout)`);
|
|
70
|
+
} catch (e) {
|
|
71
|
+
throw new Error(`worktree node_modules junction 解链失败,阻断删除以保护主仓 node_modules:${e.message}。请手动 rmdir "${nm}" 后重试`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return removed;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 安全删除 worktree 目录:先解链全部 node_modules junction 再 rmSync。
|
|
79
|
+
* 幽灵目录清理(create 强删 / doctor ghost 修复)的统一出口——不再裸 rmSync。
|
|
80
|
+
*/
|
|
81
|
+
export function safeRemoveWorktreeDir(worktreePath, meta = null) {
|
|
82
|
+
unlinkNodeModulesLinks(worktreePath, meta);
|
|
83
|
+
rmSync(worktreePath, { recursive: true, force: true });
|
|
84
|
+
}
|
|
85
|
+
|
|
27
86
|
// 进程级缓存:cwd → 主仓库根目录。避免每次 new WorktreeManager 都 spawn 一次
|
|
28
87
|
// `git rev-parse --git-common-dir`(Windows 上每次 spawn 约 30-100ms,execute 一次命令
|
|
29
88
|
// 会 new 多个 WorktreeManager)。git-common-dir 在进程内对同一 cwd 稳定,缓存安全。
|
|
@@ -416,7 +475,12 @@ export class WorktreeManager {
|
|
|
416
475
|
);
|
|
417
476
|
}
|
|
418
477
|
console.log(`⚠️ 检测到幽灵 worktree 目录(无 meta.json,无未提交改动),自动清理...`);
|
|
419
|
-
|
|
478
|
+
// 安全删除(坑 ghost-dir-junction-pierce):git status 拦不住 junction(gitignored 的
|
|
479
|
+
// node_modules 不进 status,junction 目标内容更非「未提交改动」),裸 rmSync/人工 rm -rf
|
|
480
|
+
// 会穿透 junction 删主仓 node_modules——先解链再删。
|
|
481
|
+
try { safeRemoveWorktreeDir(worktreePath); } catch (unlinkErr) {
|
|
482
|
+
throw new Error(`幽灵 worktree 安全清理失败(已阻断,主仓 node_modules 未受影响):${unlinkErr.message}`)
|
|
483
|
+
}
|
|
420
484
|
// 同步清理 git worktree 注册 + 残留分支,否则目录虽删但 git 内部状态未清,
|
|
421
485
|
// 后续 git worktree add 会因「worktree 已注册」或「分支已存在」失败
|
|
422
486
|
try { gitQuiet(this.cwd, ['worktree', 'prune'], { timeout: 30000 }); } catch {}
|
|
@@ -800,6 +864,69 @@ export class WorktreeManager {
|
|
|
800
864
|
return refs;
|
|
801
865
|
}
|
|
802
866
|
|
|
867
|
+
/**
|
|
868
|
+
* 清理前打捞 spec 流程产物(坑 worktree-spec-artifact-misplace,2026-08-24 用户实证)。
|
|
869
|
+
*
|
|
870
|
+
* 子代理 cwd=worktree 时会把流程产物(verify-result.md / module-impact.md / 任务卡 / 模块文档)
|
|
871
|
+
* 写进 worktree 内的 .sillyspec——而 apply 的 filterDeliverableFiles 把 .sillyspec/changes/ 排除在
|
|
872
|
+
* 交付外(spec 文档不进代码 patch),cleanup 删除 worktree 这些产物即蒸发、主仓流程看不到。
|
|
873
|
+
* 删除前扫 worktree 内两棵子树做最后打捞:
|
|
874
|
+
* changes/<name>/** → 主仓缺失则 copy 回(verify-result.md 等核心场景);同名不同内容仅列
|
|
875
|
+
* 清单 warn 不覆盖(主仓版本可能是更新的,覆盖会倒退)
|
|
876
|
+
* docs/** → 主仓缺失则 copy 回(模块文档卡片;同名差异不提示——tracked checkout 副本常态)
|
|
877
|
+
* 只扫本变更子树(其它 change 的副本文件不越权搬运)。异常只 warn 不阻断清理。
|
|
878
|
+
* @private
|
|
879
|
+
*/
|
|
880
|
+
_salvageSpecArtifacts(name, worktreePath, details) {
|
|
881
|
+
try {
|
|
882
|
+
const mainSpec = join(this._resolveMainRepoRoot(), '.sillyspec');
|
|
883
|
+
const wtSpec = join(worktreePath, '.sillyspec');
|
|
884
|
+
if (!existsSync(wtSpec)) return;
|
|
885
|
+
if (resolve(wtSpec) === resolve(mainSpec)) return; // in-place:同一目录,无需打捞
|
|
886
|
+
const salvaged = [];
|
|
887
|
+
const conflicts = [];
|
|
888
|
+
const salvageTree = (subDir, reportConflict) => {
|
|
889
|
+
const srcRoot = join(wtSpec, subDir);
|
|
890
|
+
if (!existsSync(srcRoot)) return;
|
|
891
|
+
const walk = (dir) => {
|
|
892
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
893
|
+
const src = join(dir, entry.name);
|
|
894
|
+
if (entry.isDirectory()) { walk(src); continue; }
|
|
895
|
+
if (!entry.isFile()) continue;
|
|
896
|
+
const rel = relative(wtSpec, src);
|
|
897
|
+
const dst = join(mainSpec, rel);
|
|
898
|
+
try {
|
|
899
|
+
if (!existsSync(dst)) {
|
|
900
|
+
mkdirSync(dirname(dst), { recursive: true });
|
|
901
|
+
copyFileSync(src, dst);
|
|
902
|
+
salvaged.push(rel);
|
|
903
|
+
} else if (reportConflict && readFileSync(src, 'utf8') !== readFileSync(dst, 'utf8')) {
|
|
904
|
+
conflicts.push(rel);
|
|
905
|
+
}
|
|
906
|
+
} catch { /* 单文件失败不拖垮整体打捞 */ }
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
walk(srcRoot);
|
|
910
|
+
};
|
|
911
|
+
salvageTree(join('changes', name), true);
|
|
912
|
+
salvageTree('docs', false);
|
|
913
|
+
if (salvaged.length > 0) {
|
|
914
|
+
console.warn(`⚠️ worktree 清理前打捞:${salvaged.length} 个 spec 流程产物只存在于 worktree 副本,已复制回主仓(apply 不搬运 .sillyspec/changes,不捞即随清理蒸发):`);
|
|
915
|
+
for (const f of salvaged.slice(0, 20)) console.warn(` ${join(mainSpec, f)}`);
|
|
916
|
+
if (salvaged.length > 20) console.warn(` …等共 ${salvaged.length} 个`);
|
|
917
|
+
details.push(`salvaged ${salvaged.length} spec artifacts to main repo`);
|
|
918
|
+
}
|
|
919
|
+
if (conflicts.length > 0) {
|
|
920
|
+
console.warn(`⚠️ worktree 与主仓存在同名但内容不同的流程产物(不覆盖,请人工确认是否需要合并):`);
|
|
921
|
+
for (const f of conflicts.slice(0, 20)) console.warn(` ${join(mainSpec, f)}`);
|
|
922
|
+
if (conflicts.length > 20) console.warn(` …等共 ${conflicts.length} 个`);
|
|
923
|
+
details.push(`conflicting spec artifacts: ${conflicts.length}`);
|
|
924
|
+
}
|
|
925
|
+
} catch (e) {
|
|
926
|
+
console.warn(`⚠️ spec 产物打捞跳过(不阻断清理): ${e?.message || e}`);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
803
930
|
cleanup(changeName, { force = false, maxRetries = 3 } = {}) {
|
|
804
931
|
const name = validateChangeName(changeName);
|
|
805
932
|
const meta = this.getMeta(name);
|
|
@@ -839,34 +966,17 @@ export class WorktreeManager {
|
|
|
839
966
|
|
|
840
967
|
const branch = (meta && meta.branch) || BRANCH_PREFIX + name;
|
|
841
968
|
|
|
842
|
-
//
|
|
843
|
-
//
|
|
969
|
+
// 打捞 spec 流程产物(坑 worktree-spec-artifact-misplace,2026-08-24 用户实证):必须在任何
|
|
970
|
+
// 删除动作之前。in-place(worktree 即主工作区)与 native-worktree(外部隔离环境)跳过。
|
|
971
|
+
if (!isInPlace && mode !== 'native-worktree' && existsSync(worktreePath)) {
|
|
972
|
+
this._salvageSpecArtifacts(name, worktreePath, details);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// Windows 保护:先解链接 worktree 内全部 node_modules junction(根 + modules 子模块,
|
|
976
|
+
// 坑 ghost-dir-junction-pierce:子模块 junction 此前不覆盖——npm ci / rm -rf / git worktree
|
|
977
|
+
// remove 跟随任一 junction 都会穿透删主仓对应 node_modules)。
|
|
844
978
|
if (!isInPlace && existsSync(worktreePath)) {
|
|
845
|
-
|
|
846
|
-
if (existsSync(wtNodeModules)) {
|
|
847
|
-
// lstat 失败 fail-loud(D-001@v1 / review-2026-08-09 #4):EPERM(杀毒/索引锁 junction)若静默跳过解链,
|
|
848
|
-
// 后续 git worktree remove / rmSync recursive 会跟随 junction 误删主仓 node_modules
|
|
849
|
-
let isLink;
|
|
850
|
-
try {
|
|
851
|
-
isLink = lstatSync(wtNodeModules).isSymbolicLink();
|
|
852
|
-
} catch (e) {
|
|
853
|
-
throw new Error(`worktree node_modules junction 检测失败(疑似 EPERM:杀毒/索引占用),阻断 cleanup 保护主仓 node_modules:${e.message}。请关闭占用进程或手动 rmdir "${wtNodeModules}" 后重试 sillyspec worktree cleanup`);
|
|
854
|
-
}
|
|
855
|
-
if (isLink) {
|
|
856
|
-
try {
|
|
857
|
-
if (process.platform === 'win32') {
|
|
858
|
-
// Windows rmdir 删 junction(reparse point)不跟随目标,保护主 checkout
|
|
859
|
-
// execFileSync 数组形式:路径含引号/特殊字符时不会经 shell 解析(安全收敛,与 git-helper 同范式)
|
|
860
|
-
execFileSync('cmd.exe', ['/c', 'rmdir', wtNodeModules]);
|
|
861
|
-
} else {
|
|
862
|
-
unlinkSync(wtNodeModules);
|
|
863
|
-
}
|
|
864
|
-
details.push('worktree node_modules junction/symlink removed (protect main checkout)');
|
|
865
|
-
} catch (e) {
|
|
866
|
-
throw new Error(`worktree node_modules junction 解链失败,阻断 cleanup 保护主仓 node_modules:${e.message}。请手动 rmdir "${wtNodeModules}" 后重试`);
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
}
|
|
979
|
+
unlinkNodeModulesLinks(worktreePath, meta, details)
|
|
870
980
|
}
|
|
871
981
|
|
|
872
982
|
// 1. git worktree remove(带 retry)—— in-place 跳过:无 git worktree 注册,且 worktreePath 即主工作区
|
|
@@ -1007,31 +1117,11 @@ export class WorktreeManager {
|
|
|
1007
1117
|
try {
|
|
1008
1118
|
const meta = this.getMeta(name) || {};
|
|
1009
1119
|
const isInPlace = meta.mode === 'in-place-fallback';
|
|
1010
|
-
// 先解 junction(非 in-place
|
|
1120
|
+
// 先解 junction(非 in-place):统一走共享解链(坑 ghost-dir-junction-pierce:覆盖根 +
|
|
1121
|
+
// depsModules 子模块;lstat/rmdir 失败 fail-loud,D-001@v1 / review-2026-08-09 #4——
|
|
1122
|
+
// 静默跳过解链会让 install 经 junction 误改主仓 node_modules)
|
|
1011
1123
|
if (!isInPlace && existsSync(wtPath)) {
|
|
1012
|
-
|
|
1013
|
-
if (existsSync(wtNodeModules)) {
|
|
1014
|
-
// lstat 失败 fail-loud(D-001@v1)+ 解链失败 fail-loud(D-002@v1 / review-2026-08-09 #4):
|
|
1015
|
-
// 原 best-effort「交 provisionDeps install」会经 junction 误改主仓 node_modules,正是 #4 坑
|
|
1016
|
-
let isLink;
|
|
1017
|
-
try {
|
|
1018
|
-
isLink = lstatSync(wtNodeModules).isSymbolicLink();
|
|
1019
|
-
} catch (e) {
|
|
1020
|
-
throw new Error(`worktree node_modules junction 检测失败(疑似 EPERM:杀毒/索引占用),阻断 doctor reprovision 保护主仓 node_modules:${e.message}。请关闭占用进程或手动 rmdir "${wtNodeModules}" 后重试`);
|
|
1021
|
-
}
|
|
1022
|
-
if (isLink) {
|
|
1023
|
-
try {
|
|
1024
|
-
if (process.platform === 'win32') {
|
|
1025
|
-
// execFileSync 数组形式:不经 shell,防 meta.worktreePath 注入(安全收敛)
|
|
1026
|
-
execFileSync('cmd.exe', ['/c', 'rmdir', wtNodeModules]);
|
|
1027
|
-
} else {
|
|
1028
|
-
unlinkSync(wtNodeModules);
|
|
1029
|
-
}
|
|
1030
|
-
} catch (e) {
|
|
1031
|
-
throw new Error(`worktree node_modules junction 解链失败,阻断 doctor reprovision(不调 provisionDeps 避免经 junction 误改主仓):${e.message}。请手动 rmdir "${wtNodeModules}" 后重试`);
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1124
|
+
unlinkNodeModulesLinks(wtPath, meta)
|
|
1035
1125
|
}
|
|
1036
1126
|
// 触发分流(坑 doctor-reprovision-junction-missing,2026-08-22 实证:junction 未建的
|
|
1037
1127
|
// missing/failed 场景被 force install 强制重装——install 静默失败面大且慢;lockfile 一致时
|
|
@@ -1043,11 +1133,18 @@ export class WorktreeManager {
|
|
|
1043
1133
|
force: !opts.relinkFirst,
|
|
1044
1134
|
}) || {};
|
|
1045
1135
|
const metaPath = join(this.getWorktreePath(name), META_FILE);
|
|
1046
|
-
//
|
|
1136
|
+
// 错误保真(坑 doctor-reprovision-error-erased,2026-08-23 实证:provisionDeps 的子模块
|
|
1137
|
+
// 后验证/mismatch 摘要追加 depsError 但不降级 depsStatus——原「depsStatus!=='failed' 即删
|
|
1138
|
+
// depsError」把失败证据从 meta 抹掉,msg 也不含 → doctor 报 re-provisioned 成功而子模块
|
|
1139
|
+
// 实际无链接无提示)。三改:①本次供给全量干净(无 depsError 且非 failed)才清旧值;
|
|
1140
|
+
// ②msg 拼上 depsError;③failed/有错误 → ok:false 落 unfixable(原无条件 ok:true 把失败
|
|
1141
|
+
// 也进 fixed 数组,CLI 打 ✅ 误导「已修好」)。
|
|
1142
|
+
const hasDepError = deps.depsStatus === 'failed' || !!deps.depsError;
|
|
1047
1143
|
const merged = { ...meta, ...deps };
|
|
1048
|
-
if (
|
|
1144
|
+
if (!hasDepError) delete merged.depsError;
|
|
1049
1145
|
writeMetaAtomic(metaPath, merged);
|
|
1050
|
-
|
|
1146
|
+
const msg = `re-provisioned ${name}: depsStatus=${deps.depsStatus}` + (deps.depsError ? `;⚠️ ${deps.depsError}` : '');
|
|
1147
|
+
return hasDepError ? { ok: false, msg } : { ok: true, msg };
|
|
1051
1148
|
} catch (e) {
|
|
1052
1149
|
return { ok: false, msg: `re-provision failed for ${name}: ${e.message}` };
|
|
1053
1150
|
}
|
|
@@ -1217,10 +1314,21 @@ export class WorktreeManager {
|
|
|
1217
1314
|
if (files.length === 0 || (files.length === 1 && files[0] === META_FILE)) {
|
|
1218
1315
|
issues.push({ type: 'ghost-dir', name, detail: `空目录/幽灵目录: ${dirPath}`, fixable: true });
|
|
1219
1316
|
if (fix) {
|
|
1220
|
-
|
|
1317
|
+
// 安全删除(坑 ghost-dir-junction-pierce):统一先解 node_modules junction 再删
|
|
1318
|
+
try { safeRemoveWorktreeDir(dirPath); fixed.push(`removed ghost dir: ${name}`); } catch (e) { unfixable.push(`remove failed for: ${name} (${e.message.slice(0, 120)})`); }
|
|
1221
1319
|
}
|
|
1222
1320
|
} else {
|
|
1223
|
-
|
|
1321
|
+
// junction 侦测(坑 ghost-dir-junction-pierce):无 meta 的含文件目录不自动删(内容
|
|
1322
|
+
// 不明,fixable:false 交人工裁决),但必须警示穿透风险 + 给安全手动指引——人工
|
|
1323
|
+
// rm -rf(Git Bash coreutils 跟随 junction)会删主仓 node_modules。
|
|
1324
|
+
const junctionHits = [];
|
|
1325
|
+
for (const cand of [join(dirPath, 'node_modules'), ...files.filter(f => !f.startsWith('.')).slice(0, 20).map(f => join(dirPath, f, 'node_modules'))]) {
|
|
1326
|
+
try { if (lstatSync(cand).isSymbolicLink()) junctionHits.push(relative(dirPath, cand) || '(root)') } catch {}
|
|
1327
|
+
}
|
|
1328
|
+
const junctionNote = junctionHits.length > 0
|
|
1329
|
+
? ` ⚠️ 含 node_modules junction(${junctionHits.join(', ')})——手动删除前必须先解链(cmd /c rmdir "<目录>\\<相对路径>"),直接 rm -rf 会穿透删除主仓 node_modules;解链后删除目录或跑 sillyspec worktree doctor --fix`
|
|
1330
|
+
: '';
|
|
1331
|
+
issues.push({ type: 'ghost-dir-with-files', name, detail: `目录存在但无 meta.json: ${dirPath} (含 ${files.length} 文件)${junctionNote}`, fixable: false });
|
|
1224
1332
|
}
|
|
1225
1333
|
}
|
|
1226
1334
|
|
|
@@ -1323,6 +1431,41 @@ export class WorktreeManager {
|
|
|
1323
1431
|
}
|
|
1324
1432
|
|
|
1325
1433
|
const isDeliverable = f => f && !f.startsWith('.sillyspec/') && f !== 'meta.json';
|
|
1434
|
+
const isDeliverablePath = isDeliverable;
|
|
1435
|
+
|
|
1436
|
+
// 合并可达性短路(坑 cleanup-merged-branch-byte-false-positive,2026-08-23 实证:分支已
|
|
1437
|
+
// merge 进主仓后主仓后续提交又改了同一批文件——下方逐字节判定必然不等 → 误报「未落地」
|
|
1438
|
+
// 拦 cleanup 要 --force,用户只能 git branch --merged 自证。历史可达性才是「交付已进主仓」
|
|
1439
|
+
// 的权威口径:分支 tip 是主仓 HEAD 的祖先 ⟺ diffBase..tip 的全部交付已在主仓历史里,后续
|
|
1440
|
+
// 演进不改写这一事实)。四条件全真才短路,任一拿不准(ref 已删/漂移/git 失败)落回原逐字节
|
|
1441
|
+
// 逻辑 fail-open,cherry-pick 落地(tip 非祖先)同样走原路径——零回归。
|
|
1442
|
+
try {
|
|
1443
|
+
const branch = meta.branch;
|
|
1444
|
+
if (branch) {
|
|
1445
|
+
const tip = gitQuiet(this.cwd, ['rev-parse', '--verify', branch]);
|
|
1446
|
+
const wtHead = gitQuiet(worktreePath, ['rev-parse', 'HEAD']);
|
|
1447
|
+
// 条件②③:worktree HEAD 就是分支 tip(无漂移/未删),且工作区无未 commit/未 track 的
|
|
1448
|
+
// 交付改动(那些不在祖先可达性内,须走逐字节路径逐个核对)
|
|
1449
|
+
if (tip && wtHead && tip === wtHead) {
|
|
1450
|
+
const wtStatus = gitQuiet(worktreePath, ['status', '--porcelain'], { timeout: 30000 });
|
|
1451
|
+
if (wtStatus !== null) {
|
|
1452
|
+
const deliverableDirty = wtStatus.split('\n').filter(Boolean)
|
|
1453
|
+
.map(l => l.slice(3).split(' -> ').pop())
|
|
1454
|
+
.some(isDeliverablePath);
|
|
1455
|
+
if (!deliverableDirty) {
|
|
1456
|
+
// 条件④:merge-base(branch, HEAD) === tip ⟺ tip 是 HEAD 祖先(不用 --is-ancestor:
|
|
1457
|
+
// gitQuiet 把 exit 1「非祖先」与真失败都归 null 无法区分;merge-base 恒 exit 0,
|
|
1458
|
+
// 输出等于 tip 即祖先,ref 失败返回 null 自然落回原逻辑)
|
|
1459
|
+
const mb = gitQuiet(this.cwd, ['merge-base', branch, 'HEAD']);
|
|
1460
|
+
if (mb === tip) {
|
|
1461
|
+
return { hasChanges: false, changedFiles: [], reason: `branch fully merged into main HEAD (tip ${tip.slice(0, 8)} is ancestor; post-merge main edits are later history)` };
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
} catch { /* 可达性判定异常 → 落回原逐字节逻辑(fail-open) */ }
|
|
1468
|
+
|
|
1326
1469
|
try {
|
|
1327
1470
|
// 1) 候选交付变更(worktree 工作区相对 diffBase)。--no-renames:rename 退化成 D+A,两侧文件都进集
|
|
1328
1471
|
const trackedRaw = gitQuiet(worktreePath, ['diff', '--no-renames', '--name-only', diffBase], { timeout: 30000 });
|
|
@@ -22,6 +22,7 @@ TaskCard 格式规则(必须严格遵守):
|
|
|
22
22
|
- 如果存在 decisions.md,无法覆盖的 D-xxx@vN 在 constraints 中标注
|
|
23
23
|
- **保存前格式自检**(plan-postcheck 会硬校验,不通过到 Step 4 会批量报错,先在这里逐条自查):
|
|
24
24
|
- **硬校验字段**(缺失 plan-postcheck 直接报错阻断,必须齐全):id、title、title_zh、allowed_paths、goal、implementation、acceptance、verify、constraints
|
|
25
|
+
- **占位符硬拦**(未替换视同缺字段,plan --done 直接报错阻断):FR-XX(requirement_ids)、D-XXX(decision_ids)、src/example/file.ts(allowed_paths)、一句话说明这个 task(goal)、具体步骤 1(implementation)、可验证的验收条件 1(acceptance)、边界约束 1(constraints)——骨架带出来的占位值必须逐个替换成真实内容
|
|
25
26
|
- **规范约定字段**(应填但不硬校验,缺失不阻断完成、只影响规范性):author、created_at、priority、depends_on、blocks
|
|
26
27
|
- allowed_paths 非空(至少一个真实源文件路径;回归类 task 无源码改动时填被验证的关键入口文件)
|
|
27
28
|
- acceptance 与 verify 都在 frontmatter 内(漏写会被 plan-postcheck 报「缺少验收标准」/「缺少验证步骤」)
|