sillyspec 3.27.5 → 3.27.6
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/.worktrees/8d2d0392/.claude/skills/sillyspec-archive/SKILL.md +87 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-auto/SKILL.md +93 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-commit/SKILL.md +67 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-continue/SKILL.md +47 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-execute/SKILL.md +214 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-explore/SKILL.md +102 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-export/SKILL.md +57 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-init/SKILL.md +177 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-plan/SKILL.md +156 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-propose/SKILL.md +87 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-quick/SKILL.md +116 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-resume/SKILL.md +70 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-scan/SKILL.md +79 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-state/SKILL.md +64 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-status/SKILL.md +54 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-verify/SKILL.md +87 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-workspace/SKILL.md +175 -0
- package/.worktrees/8d2d0392/.claude/skills/verify-per-user/SKILL.md +6 -0
- package/.worktrees/8d2d0392/.gitattributes +2 -0
- package/.worktrees/8d2d0392/.husky/pre-push +20 -0
- package/.worktrees/8d2d0392/README.md +203 -0
- package/.worktrees/8d2d0392/SKILL.md +91 -0
- package/.worktrees/8d2d0392/architecture-analysis.md +448 -0
- package/.worktrees/8d2d0392/bin/sillyspec.js +2 -0
- package/.worktrees/8d2d0392/eval/README.md +86 -0
- package/.worktrees/8d2d0392/eval/eval.config.json +28 -0
- package/.worktrees/8d2d0392/eval/lib/adapters/cli.mjs +63 -0
- package/.worktrees/8d2d0392/eval/lib/adapters/dryrun.mjs +27 -0
- package/.worktrees/8d2d0392/eval/lib/config.mjs +82 -0
- package/.worktrees/8d2d0392/eval/lib/db.mjs +76 -0
- package/.worktrees/8d2d0392/eval/lib/report.mjs +120 -0
- package/.worktrees/8d2d0392/eval/lib/runner.mjs +157 -0
- package/.worktrees/8d2d0392/eval/lib/tasks.mjs +73 -0
- package/.worktrees/8d2d0392/eval/lib/util.mjs +107 -0
- package/.worktrees/8d2d0392/eval/lib/workdir.mjs +30 -0
- package/.worktrees/8d2d0392/eval/run.mjs +83 -0
- package/.worktrees/8d2d0392/eval/tasks/001-slugify/files/package.json +6 -0
- package/.worktrees/8d2d0392/eval/tasks/001-slugify/solution/src/slugify.mjs +26 -0
- package/.worktrees/8d2d0392/eval/tasks/001-slugify/task.json +6 -0
- package/.worktrees/8d2d0392/eval/tasks/001-slugify/verify.mjs +28 -0
- package/.worktrees/8d2d0392/eval/tasks/002-each-day/files/package.json +6 -0
- package/.worktrees/8d2d0392/eval/tasks/002-each-day/files/src/dates.mjs +4 -0
- package/.worktrees/8d2d0392/eval/tasks/002-each-day/solution/src/dates.mjs +30 -0
- package/.worktrees/8d2d0392/eval/tasks/002-each-day/task.json +6 -0
- package/.worktrees/8d2d0392/eval/tasks/002-each-day/verify.mjs +31 -0
- package/.worktrees/8d2d0392/eval/tasks/003-word-report/files/data/notes.txt +5 -0
- package/.worktrees/8d2d0392/eval/tasks/003-word-report/files/package.json +6 -0
- package/.worktrees/8d2d0392/eval/tasks/003-word-report/solution/bin/report.mjs +36 -0
- package/.worktrees/8d2d0392/eval/tasks/003-word-report/task.json +6 -0
- package/.worktrees/8d2d0392/eval/tasks/003-word-report/verify.mjs +38 -0
- package/.worktrees/8d2d0392/logo.jpg +0 -0
- package/.worktrees/8d2d0392/package-lock.json +977 -0
- package/.worktrees/8d2d0392/package.json +41 -0
- package/.worktrees/8d2d0392/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -0
- package/.worktrees/8d2d0392/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
- package/.worktrees/8d2d0392/packages/dashboard/dist/favicon.jpg +0 -0
- package/.worktrees/8d2d0392/packages/dashboard/dist/index.html +16 -0
- package/.worktrees/8d2d0392/packages/dashboard/dist/logo.jpg +0 -0
- package/.worktrees/8d2d0392/packages/dashboard/dist/prototype-dashboard.html +836 -0
- package/.worktrees/8d2d0392/packages/dashboard/dist/prototype-overview.html +256 -0
- package/.worktrees/8d2d0392/packages/dashboard/package.json +25 -0
- package/.worktrees/8d2d0392/packages/dashboard/server/executor.js +95 -0
- package/.worktrees/8d2d0392/packages/dashboard/server/index.js +679 -0
- package/.worktrees/8d2d0392/packages/dashboard/server/parser.js +526 -0
- package/.worktrees/8d2d0392/packages/dashboard/server/watcher.js +357 -0
- package/.worktrees/8d2d0392/src/agent-session-log.js +803 -0
- package/.worktrees/8d2d0392/src/change-list.js +290 -0
- package/.worktrees/8d2d0392/src/change-risk-profile.js +339 -0
- package/.worktrees/8d2d0392/src/check-primitives.js +65 -0
- package/.worktrees/8d2d0392/src/classify-change.js +109 -0
- package/.worktrees/8d2d0392/src/commit-suggest.js +202 -0
- package/.worktrees/8d2d0392/src/config-cat.js +88 -0
- package/.worktrees/8d2d0392/src/config-schema.js +328 -0
- package/.worktrees/8d2d0392/src/constants.js +97 -0
- package/.worktrees/8d2d0392/src/contract-matrix.js +533 -0
- package/.worktrees/8d2d0392/src/datetime.js +20 -0
- package/.worktrees/8d2d0392/src/db-engine.js +55 -0
- package/.worktrees/8d2d0392/src/db.js +381 -0
- package/.worktrees/8d2d0392/src/decision-distill.js +517 -0
- package/.worktrees/8d2d0392/src/dispatch/backends/local-agent.js +155 -0
- package/.worktrees/8d2d0392/src/dispatch/backends/sillyhub-mcp.js +245 -0
- package/.worktrees/8d2d0392/src/dispatch/probe.js +246 -0
- package/.worktrees/8d2d0392/src/dispatch/strategy.js +121 -0
- package/.worktrees/8d2d0392/src/docs-check.js +825 -0
- package/.worktrees/8d2d0392/src/docs-debt.js +393 -0
- package/.worktrees/8d2d0392/src/docs-gate.js +107 -0
- package/.worktrees/8d2d0392/src/doctor-diagnostics.js +1099 -0
- package/.worktrees/8d2d0392/src/endpoint-extractor.js +409 -0
- package/.worktrees/8d2d0392/src/foreign-declared.js +78 -0
- package/.worktrees/8d2d0392/src/fs-atomic.js +73 -0
- package/.worktrees/8d2d0392/src/git-helper.js +150 -0
- package/.worktrees/8d2d0392/src/hooks/claude-pre-tool-use.cjs +130 -0
- package/.worktrees/8d2d0392/src/hooks/worktree-guard.js +892 -0
- package/.worktrees/8d2d0392/src/index.js +2854 -0
- package/.worktrees/8d2d0392/src/init.js +626 -0
- package/.worktrees/8d2d0392/src/knowledge-match.js +231 -0
- package/.worktrees/8d2d0392/src/local-detect.js +128 -0
- package/.worktrees/8d2d0392/src/local-register.js +104 -0
- package/.worktrees/8d2d0392/src/machine-interface.js +543 -0
- package/.worktrees/8d2d0392/src/migrate.js +127 -0
- package/.worktrees/8d2d0392/src/module-impact.js +166 -0
- package/.worktrees/8d2d0392/src/module-resolve.js +222 -0
- package/.worktrees/8d2d0392/src/modules.js +580 -0
- package/.worktrees/8d2d0392/src/plan-adopt-waves.js +126 -0
- package/.worktrees/8d2d0392/src/progress/change-registry.js +336 -0
- package/.worktrees/8d2d0392/src/progress/consistency-doctor.js +355 -0
- package/.worktrees/8d2d0392/src/progress/shared.js +36 -0
- package/.worktrees/8d2d0392/src/progress/stage-machine.js +594 -0
- package/.worktrees/8d2d0392/src/progress/step-store.js +265 -0
- package/.worktrees/8d2d0392/src/progress.js +1304 -0
- package/.worktrees/8d2d0392/src/quick-recommend.js +115 -0
- package/.worktrees/8d2d0392/src/quicklog.js +883 -0
- package/.worktrees/8d2d0392/src/review-tier.js +77 -0
- package/.worktrees/8d2d0392/src/run/command.js +1641 -0
- package/.worktrees/8d2d0392/src/run/complete-handlers.js +1552 -0
- package/.worktrees/8d2d0392/src/run/complete.js +1259 -0
- package/.worktrees/8d2d0392/src/run/concurrent-detect.js +156 -0
- package/.worktrees/8d2d0392/src/run/gates.js +1094 -0
- package/.worktrees/8d2d0392/src/run/multi-repo-context.js +208 -0
- package/.worktrees/8d2d0392/src/run/next.js +128 -0
- package/.worktrees/8d2d0392/src/run/prompt.js +843 -0
- package/.worktrees/8d2d0392/src/run/quick-audit.js +186 -0
- package/.worktrees/8d2d0392/src/run/scan-profile.js +269 -0
- package/.worktrees/8d2d0392/src/run/shared.js +1562 -0
- package/.worktrees/8d2d0392/src/run/stage.js +587 -0
- package/.worktrees/8d2d0392/src/run.js +23 -0
- package/.worktrees/8d2d0392/src/scan-diff.js +388 -0
- package/.worktrees/8d2d0392/src/scan-postcheck.js +524 -0
- package/.worktrees/8d2d0392/src/scan-staleness.js +95 -0
- package/.worktrees/8d2d0392/src/setup.js +414 -0
- package/.worktrees/8d2d0392/src/sillyhub-mcp/client.js +597 -0
- package/.worktrees/8d2d0392/src/sillyhub-mcp/config.js +62 -0
- package/.worktrees/8d2d0392/src/spec-dir-typo.js +50 -0
- package/.worktrees/8d2d0392/src/spec-sync.js +358 -0
- package/.worktrees/8d2d0392/src/stage-contract-engine.js +171 -0
- package/.worktrees/8d2d0392/src/stage-contract-spec.js +551 -0
- package/.worktrees/8d2d0392/src/stage-contract.js +948 -0
- package/.worktrees/8d2d0392/src/stage-review.js +597 -0
- package/.worktrees/8d2d0392/src/stages/archive.js +199 -0
- package/.worktrees/8d2d0392/src/stages/brainstorm-auto.js +285 -0
- package/.worktrees/8d2d0392/src/stages/brainstorm.js +624 -0
- package/.worktrees/8d2d0392/src/stages/cmd-existence.js +145 -0
- package/.worktrees/8d2d0392/src/stages/doctor.js +540 -0
- package/.worktrees/8d2d0392/src/stages/execute.js +1277 -0
- package/.worktrees/8d2d0392/src/stages/explore.js +43 -0
- package/.worktrees/8d2d0392/src/stages/index.js +29 -0
- package/.worktrees/8d2d0392/src/stages/knowledge.js +514 -0
- package/.worktrees/8d2d0392/src/stages/plan-postcheck.js +1398 -0
- package/.worktrees/8d2d0392/src/stages/plan.js +651 -0
- package/.worktrees/8d2d0392/src/stages/quick.js +167 -0
- package/.worktrees/8d2d0392/src/stages/scan.js +608 -0
- package/.worktrees/8d2d0392/src/stages/status.js +64 -0
- package/.worktrees/8d2d0392/src/stages/verify.js +246 -0
- package/.worktrees/8d2d0392/src/sync.js +1470 -0
- package/.worktrees/8d2d0392/src/task-review.js +1498 -0
- package/.worktrees/8d2d0392/src/taskcard-placeholders.js +21 -0
- package/.worktrees/8d2d0392/src/taskcard.js +217 -0
- package/.worktrees/8d2d0392/src/verify-postcheck.js +1718 -0
- package/.worktrees/8d2d0392/src/verify-probes.js +327 -0
- package/.worktrees/8d2d0392/src/version.js +19 -0
- package/.worktrees/8d2d0392/src/workflow.js +600 -0
- package/.worktrees/8d2d0392/src/workspace.js +125 -0
- package/.worktrees/8d2d0392/src/worktree-apply.js +1611 -0
- package/.worktrees/8d2d0392/src/worktree-deps.js +576 -0
- package/.worktrees/8d2d0392/src/worktree.js +1838 -0
- package/.worktrees/8d2d0392/templates/claude-instruction.md +23 -0
- package/.worktrees/8d2d0392/templates/prompts/taskcard-rules.md +29 -0
- package/.worktrees/8d2d0392/templates/prompts/testcase-design.md +12 -0
- package/.worktrees/8d2d0392/templates/prompts/verify-probes.md +32 -0
- package/.worktrees/8d2d0392/templates/workflows/archive-impact.yaml +79 -0
- package/.worktrees/8d2d0392/templates/workflows/scan-docs.yaml +132 -0
- package/architecture-analysis.md +448 -0
- package/package.json +1 -1
- package/src/contract-matrix.js +1 -1
- package/src/endpoint-extractor.js +41 -52
- package/src/foreign-declared.js +78 -1
- package/src/index.js +9 -7
- package/src/run/command.js +2 -1
- package/src/run/gates.js +17 -0
- package/src/run/stage.js +8 -5
- package/src/stage-contract-spec.js +1 -1
- package/src/stages/brainstorm-auto.js +43 -1
- package/src/stages/brainstorm.js +2 -0
- package/src/stages/plan.js +8 -4
- package/src/stages/verify.js +1 -1
- package/src/taskcard.js +33 -0
- package/src/verify-probes.js +27 -6
- package/src/worktree-apply.js +451 -286
- package/src/worktree-deps.js +93 -1
- package/src/worktree.js +84 -19
- package/templates/prompts/taskcard-rules.md +1 -1
package/src/foreign-declared.js
CHANGED
|
@@ -13,6 +13,81 @@
|
|
|
13
13
|
import { existsSync, readFileSync, readdirSync } from 'fs'
|
|
14
14
|
import { join } from 'path'
|
|
15
15
|
import { parseFileChangeListDetailed } from './change-list.js'
|
|
16
|
+
import { safeGit, unquoteGitPath } from './git-helper.js'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 主仓未提交文件集(staged + unstaged + untracked,rename 取新路径)。
|
|
20
|
+
* 活性判定的事实源:他者声明的文件若已不在未提交集,说明其工作已 apply+commit(或从未发生),
|
|
21
|
+
* 声明随之失效(坑 foreign-declared-stale-noise,2026-08-25 实证:并行会话 pathspec 重叠期
|
|
22
|
+
* execute/verify 每轮重复刷「已排除 N 个并行会话声明的文件」,其中大量是对方早已 apply+commit
|
|
23
|
+
* 的存量声明——design §6 清单与残留 quick guard 不随 commit 自动失效,纯噪音)。
|
|
24
|
+
* @param {string} cwd 项目根
|
|
25
|
+
* @returns {Set<string>|null} null = git 调用失败(调用方 fail-closed 不过滤)
|
|
26
|
+
*/
|
|
27
|
+
function getMainDirtySet(cwd) {
|
|
28
|
+
const r = safeGit(cwd, ['status', '--porcelain', '--untracked-files=all'], { trim: false })
|
|
29
|
+
if (r.value === null) return null
|
|
30
|
+
const set = new Set()
|
|
31
|
+
for (const line of r.value.split('\n')) {
|
|
32
|
+
if (!line.trim()) continue
|
|
33
|
+
let p = line.slice(3).trim()
|
|
34
|
+
const arrow = p.lastIndexOf(' -> ')
|
|
35
|
+
if (arrow !== -1) p = p.slice(arrow + 4)
|
|
36
|
+
if (p.startsWith('"') && p.endsWith('"')) p = unquoteGitPath(p.slice(1, -1))
|
|
37
|
+
p = p.replace(/\\/g, '/')
|
|
38
|
+
if (p) set.add(p)
|
|
39
|
+
}
|
|
40
|
+
return set
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 变更 worktree 活性速查(.runtime/worktrees/<change>/meta.json)。
|
|
45
|
+
* isolated=true:有存活隔离 worktree(meta 在、非 in-place、worktreePath 目录在)——
|
|
46
|
+
* 其 WIP 在 worktree 内主仓 porcelain 不可见,整份 design 声明按在途处理。
|
|
47
|
+
* @returns {Map<string, { isolated: boolean }>|null} null = 读取失败(fail-closed)
|
|
48
|
+
*/
|
|
49
|
+
function loadWorktreeLiveness(specBase) {
|
|
50
|
+
const map = new Map()
|
|
51
|
+
try {
|
|
52
|
+
const dir = join(specBase, '.runtime', 'worktrees')
|
|
53
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
54
|
+
if (!e.isDirectory()) continue
|
|
55
|
+
try {
|
|
56
|
+
const meta = JSON.parse(readFileSync(join(dir, e.name, 'meta.json'), 'utf8'))
|
|
57
|
+
const isolated = Boolean(meta && meta.mode !== 'in-place-fallback'
|
|
58
|
+
&& meta.worktreePath && existsSync(meta.worktreePath))
|
|
59
|
+
map.set(meta.changeName || e.name, { isolated })
|
|
60
|
+
} catch { /* 单条损坏跳过(该变更走主仓 dirty 判定) */ }
|
|
61
|
+
}
|
|
62
|
+
} catch { return null }
|
|
63
|
+
return map
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 他者声明活性收敛:声明只在「工作仍在途」时有效,已 apply+commit 的存量声明自动失效。
|
|
68
|
+
* - quick 会话(主仓直接改):文件仍在主仓未提交集才算在途
|
|
69
|
+
* - 变更:有存活隔离 worktree → 整份声明在途;否则与 quick 同口径看主仓未提交集
|
|
70
|
+
* 判定所需事实读不出来(git/meta 失败)→ 一律保留(fail-closed,不放大放行面)。
|
|
71
|
+
* @param {Map<string, string[]>} foreign collectForeignDeclaredFiles 的原始结果(原地收敛)
|
|
72
|
+
*/
|
|
73
|
+
function filterStaleForeignDeclarations(foreign, cwd, specBase) {
|
|
74
|
+
if (foreign.size === 0) return
|
|
75
|
+
const dirty = getMainDirtySet(cwd)
|
|
76
|
+
const worktrees = loadWorktreeLiveness(specBase)
|
|
77
|
+
if (dirty === null && worktrees === null) return // 两个事实源全失败 → 原样保留
|
|
78
|
+
for (const [file, owners] of [...foreign.entries()]) {
|
|
79
|
+
const live = owners.filter(owner => {
|
|
80
|
+
if (!owner.startsWith('quick-')) {
|
|
81
|
+
const wt = worktrees ? worktrees.get(owner) : undefined
|
|
82
|
+
if (wt && wt.isolated) return true // 隔离 worktree 在途,声明整份有效
|
|
83
|
+
}
|
|
84
|
+
// quick 会话 / 无存活 worktree 的变更:看主仓未提交集;dirty 集不可用则保守保留
|
|
85
|
+
return dirty === null ? true : dirty.has(file)
|
|
86
|
+
})
|
|
87
|
+
if (live.length === 0) foreign.delete(file)
|
|
88
|
+
else if (live.length < owners.length) foreign.set(file, live)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
16
91
|
|
|
17
92
|
/**
|
|
18
93
|
* @param {string} cwd 项目根
|
|
@@ -27,8 +102,8 @@ export function collectForeignDeclaredFiles(cwd, currentChangeName) {
|
|
|
27
102
|
if (!foreign.has(f)) foreign.set(f, [])
|
|
28
103
|
if (!foreign.get(f).includes(owner)) foreign.get(f).push(owner)
|
|
29
104
|
}
|
|
105
|
+
const specBase = join(cwd, '.sillyspec')
|
|
30
106
|
try {
|
|
31
|
-
const specBase = join(cwd, '.sillyspec')
|
|
32
107
|
// ① 其他 quick 会话的 --files 显式声明(guard.json)
|
|
33
108
|
const sessionsDir = join(specBase, '.runtime', 'quick-sessions')
|
|
34
109
|
let sessionDirs = []
|
|
@@ -53,6 +128,8 @@ export function collectForeignDeclaredFiles(cwd, currentChangeName) {
|
|
|
53
128
|
} catch { /* 清单解析失败跳过该变更 */ }
|
|
54
129
|
}
|
|
55
130
|
} catch { /* 整体异常 → 空集(等价不过滤,回到原行为) */ }
|
|
131
|
+
// 活性收敛(坑 foreign-declared-stale-noise):已 apply+commit 的他者声明不再产生排除噪音
|
|
132
|
+
try { filterStaleForeignDeclarations(foreign, cwd, specBase) } catch { /* 收敛失败 → 原样保留(fail-closed) */ }
|
|
56
133
|
return foreign
|
|
57
134
|
}
|
|
58
135
|
|
package/src/index.js
CHANGED
|
@@ -1553,8 +1553,8 @@ async function main() {
|
|
|
1553
1553
|
SillySpec worktree — git worktree 隔离管理
|
|
1554
1554
|
|
|
1555
1555
|
用法:
|
|
1556
|
-
sillyspec worktree create <change-name> [--base <branch>] 创建隔离 worktree
|
|
1557
|
-
sillyspec worktree apply <change-name> [--check-only] [--base merge-base|baseline] [--merge]
|
|
1556
|
+
sillyspec worktree create <change-name> [--base <branch>] [--adopt-branch] 创建隔离 worktree(--adopt-branch:收编既有同名分支为工作分支,分支现状作 baseline)
|
|
1557
|
+
sillyspec worktree apply <change-name> [--check-only] [--base merge-base|baseline] [--merge] [--skip-overlap] [--stash-dirty] 校验并应用变更到主工作区(--stash-dirty:主仓在途改动自动 stash→apply→恢复,SHA 兜底可审计)
|
|
1558
1558
|
sillyspec worktree assess <change-name> 风险审计 + 自动 apply
|
|
1559
1559
|
sillyspec worktree diff <change-name> [--base <commit>] 查看 worktree 相对 base 的变更
|
|
1560
1560
|
sillyspec worktree list 列出所有活跃 worktree
|
|
@@ -1572,7 +1572,7 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
1572
1572
|
switch (wtSubCmd) {
|
|
1573
1573
|
case 'create': {
|
|
1574
1574
|
if (!wtName) {
|
|
1575
|
-
console.error('❌ 用法: sillyspec worktree create <change-name> [--base <branch>]');
|
|
1575
|
+
console.error('❌ 用法: sillyspec worktree create <change-name> [--base <branch>] [--adopt-branch]');
|
|
1576
1576
|
process.exit(1);
|
|
1577
1577
|
}
|
|
1578
1578
|
// F6 路径穿越消毒:worktree 名用于分支名 + worktree 目录,含 ../ 会逃出 .sillyspec/worktrees/。
|
|
@@ -1580,8 +1580,9 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
1580
1580
|
catch (e) { console.error(`❌ ${e.message}`); process.exit(2); }
|
|
1581
1581
|
const baseIdx = args.indexOf('--base');
|
|
1582
1582
|
const base = baseIdx >= 0 && args[baseIdx + 1] ? args[baseIdx + 1] : undefined;
|
|
1583
|
+
const adoptBranch = args.includes('--adopt-branch'); // 收编既有同名分支为工作分支(坑 worktree-user-branch-conflict)
|
|
1583
1584
|
try {
|
|
1584
|
-
const info = wm.create(wtName, { base });
|
|
1585
|
+
const info = wm.create(wtName, { base, adoptBranch });
|
|
1585
1586
|
console.log(`✅ worktree 已创建`);
|
|
1586
1587
|
console.log(` 分支: ${info.branch}`);
|
|
1587
1588
|
console.log(` 路径: ${info.worktreePath}`);
|
|
@@ -1601,12 +1602,13 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
1601
1602
|
}
|
|
1602
1603
|
case 'apply': {
|
|
1603
1604
|
if (!wtName) {
|
|
1604
|
-
console.error('❌ 用法: sillyspec worktree apply <change-name> [--check-only] [--base merge-base|baseline] [--merge] [--skip-overlap]');
|
|
1605
|
+
console.error('❌ 用法: sillyspec worktree apply <change-name> [--check-only] [--base merge-base|baseline] [--merge] [--skip-overlap] [--stash-dirty]');
|
|
1605
1606
|
process.exit(1);
|
|
1606
1607
|
}
|
|
1607
1608
|
const checkOnly = args.includes('--check-only');
|
|
1608
1609
|
const merge = args.includes('--merge');
|
|
1609
1610
|
const skipOverlap = args.includes('--skip-overlap');
|
|
1611
|
+
const stashDirty = args.includes('--stash-dirty');
|
|
1610
1612
|
|
|
1611
1613
|
// 解析 --base 参数(默认 merge-base)
|
|
1612
1614
|
let base = 'merge-base';
|
|
@@ -1637,8 +1639,8 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
1637
1639
|
let result;
|
|
1638
1640
|
try {
|
|
1639
1641
|
result = checkOnly
|
|
1640
|
-
? applyWorktree(wtName, { cwd: dir, checkOnly, merge, base, ctx: _applyCtx, skipOverlap })
|
|
1641
|
-
: await withMainRepoLock(dir, wtName, 'apply', () => applyWorktree(wtName, { cwd: dir, checkOnly, merge, base, ctx: _applyCtx, skipOverlap }));
|
|
1642
|
+
? applyWorktree(wtName, { cwd: dir, checkOnly, merge, base, ctx: _applyCtx, skipOverlap, stashDirty })
|
|
1643
|
+
: await withMainRepoLock(dir, wtName, 'apply', () => applyWorktree(wtName, { cwd: dir, checkOnly, merge, base, ctx: _applyCtx, skipOverlap, stashDirty }));
|
|
1642
1644
|
} catch (lockErr) {
|
|
1643
1645
|
console.error(`❌ ${lockErr.message}`);
|
|
1644
1646
|
process.exit(1);
|
package/src/run/command.js
CHANGED
|
@@ -723,6 +723,7 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
|
|
|
723
723
|
'--json', '--dir', '--help',
|
|
724
724
|
'--reopen', '--from-step', '--mode',
|
|
725
725
|
'--deep', '--quick', '--standard', // scan profile 三档显式选择(scan-profile.js 从 argv 读;互斥见下方 PROFILE_FLAGS 检测)
|
|
726
|
+
'--adopt-branch', // execute 显式收编既有 sillyspec/<change> 分支为 worktree 工作分支(坑 worktree-user-branch-conflict)
|
|
726
727
|
'--diff', '--base', '--full', '--report', // scan diff(D-001:command.js 只补 flag,裸 token 解析归 index.js 子命令拦截)
|
|
727
728
|
'-h',
|
|
728
729
|
])
|
|
@@ -1241,7 +1242,7 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
|
|
|
1241
1242
|
}
|
|
1242
1243
|
|
|
1243
1244
|
// 默认:输出当前步骤
|
|
1244
|
-
return await runStage(pm, progress, stageName, cwd, effectiveChange, isSkipApproval, platformOpts, { quickFiles, isAllowNew, isAllowDelete, isForceBaseline, isForceRescan, linkedChanges, taskDescription: inputText })
|
|
1245
|
+
return await runStage(pm, progress, stageName, cwd, effectiveChange, isSkipApproval, platformOpts, { quickFiles, isAllowNew, isAllowDelete, isForceBaseline, isForceRescan, linkedChanges, taskDescription: inputText, adoptBranch: stageName === 'execute' && flags.includes('--adopt-branch') })
|
|
1245
1246
|
}
|
|
1246
1247
|
|
|
1247
1248
|
/**
|
package/src/run/gates.js
CHANGED
|
@@ -473,6 +473,23 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
473
473
|
ensureDecisionDocHeader(resolveChangeDir(cwd, changeName, platformOpts?.specRoot))
|
|
474
474
|
} catch { /* 补齐失败不阻断 gate */ }
|
|
475
475
|
}
|
|
476
|
+
// TaskCard 骨架幂等预生成(坑 taskcard-parallel-cli-lock,2026-08-25 用户实证:并行 batch
|
|
477
|
+
// 子代理各自起 sillyspec taskcard CLI 撞进度库 SQLite 锁,改主代理预生成+子代理只 Edit 才稳)。
|
|
478
|
+
// gate 前主流程单进程补齐缺卡骨架(已存在跳过),与 ensureDecisionDocHeader 同层同范式——
|
|
479
|
+
// 补的是 CLI 可自证的机械骨架,占位符硬拦(plan-postcheck)仍逼子代理 Edit 填语义。
|
|
480
|
+
if (stageName === 'plan' && changeName) {
|
|
481
|
+
try {
|
|
482
|
+
const { ensureTaskcardSkeletons } = await import('../taskcard.js')
|
|
483
|
+
const r = ensureTaskcardSkeletons(changeName, { cwd, specDir: platformOpts?.specRoot })
|
|
484
|
+
if (r.created.length > 0) {
|
|
485
|
+
console.log(`📌 已预生成 ${r.created.length} 张 TaskCard 骨架(主流程单进程生成,子代理只 Edit 填充,勿并行跑 taskcard CLI):`)
|
|
486
|
+
for (const f of r.created.slice(0, 8)) console.log(` + ${f}`)
|
|
487
|
+
if (r.created.length > 8) console.log(` … 共 ${r.created.length} 张`)
|
|
488
|
+
}
|
|
489
|
+
} catch (e) {
|
|
490
|
+
console.warn(`⚠️ TaskCard 骨架预生成失败(不阻断 gate,仍可逐卡手动 sillyspec taskcard 生成): ${e.message}`)
|
|
491
|
+
}
|
|
492
|
+
}
|
|
476
493
|
const contractResult = runValidators(stageName, cwd, changeName, { projectName, specRoot: platformOpts?.specRoot })
|
|
477
494
|
if (contractResult.errors.length > 0) {
|
|
478
495
|
console.error(`\n❌ 阶段 ${stageName} 校验失败:`)
|
package/src/run/stage.js
CHANGED
|
@@ -74,14 +74,17 @@ export async function runStage(pm, progress, stageName, cwd, changeName, skipApp
|
|
|
74
74
|
console.log(`🔗 worktree 已存在: ${existingMeta.worktreePath} (${existingMeta.mode})`)
|
|
75
75
|
} else {
|
|
76
76
|
try {
|
|
77
|
-
const result = wm.create(effectiveChange)
|
|
78
|
-
console.log(`🔗 worktree 已创建: ${result.worktreePath} (分支: ${result.branch}, 模式: ${result.mode})`)
|
|
77
|
+
const result = wm.create(effectiveChange, { adoptBranch: !!(quickOpts && quickOpts.adoptBranch) })
|
|
78
|
+
console.log(`🔗 worktree 已创建: ${result.worktreePath} (分支: ${result.branch}, 模式: ${result.mode}${result.adoptedBranch ? ', 收编既有分支' : ''})`)
|
|
79
79
|
} catch (e) {
|
|
80
80
|
console.error(`❌ worktree 创建失败: ${e.message}`)
|
|
81
|
+
// 修复建议不再无条件推荐 git branch -D(坑 worktree-user-branch-conflict,2026-08-24:
|
|
82
|
+
// 同名分支可能是用户有意创建的——分支冲突时 create 报错已带三选一决策菜单,删不删
|
|
83
|
+
// 交由菜单/人工判断;doctor 的分支删除也有 review 锚点 + 无库保守双保护)。
|
|
81
84
|
console.error(` 修复建议:`)
|
|
82
|
-
console.error(` 1.
|
|
83
|
-
console.error(` 2.
|
|
84
|
-
console.error(` 3.
|
|
85
|
+
console.error(` 1. 同名分支冲突/收编:按上方报错内三选一菜单处置(遗留分支确认作废后才删;用户指定分支用 --adopt-branch 收编)`)
|
|
86
|
+
console.error(` 2. worktree 状态异常:sillyspec worktree doctor --fix(分支删除有 review 锚点保护,不会误删在用分支)`)
|
|
87
|
+
console.error(` 3. 目录残留:确认无未提交改动后清理 .sillyspec/.runtime/worktrees/${effectiveChange}/ 再重试`)
|
|
85
88
|
process.exit(1)
|
|
86
89
|
}
|
|
87
90
|
}
|
|
@@ -223,7 +223,7 @@ const VERIFY_RULES = [
|
|
|
223
223
|
target: { root: 'change', path: 'verify-result.md', scope: 'full' },
|
|
224
224
|
data: {},
|
|
225
225
|
spec: 'verify-result.md 结论不得为 FAIL(FAIL 阻断 verify 完成)。结论在含「结论/Conclusion/Result/结果」的二级标题后,值为 PASS / PASS WITH NOTES / FAIL。',
|
|
226
|
-
failMessage: 'verify-result.md 结论为 FAIL — 验证未通过,不能标记 verify
|
|
226
|
+
failMessage: 'verify-result.md 结论为 FAIL — 验证未通过,不能标记 verify 完成;请修复后重新运行验证。⚠️ 重验成本预告:修复后 verify --done 时 CLI 会重新执行 local.yaml 的 commands.test 全量对账(同步执行,长套件可达数分钟、中途无输出属正常;可按 local.yaml test_strategy: module + modules 收窄命中模块),请在修复时一并评估测试范围',
|
|
227
227
|
noConclusionWarning: 'verify-result.md 未识别到结论章节(含 结论/Conclusion/Result/结果 的二级标题,后跟 PASS / PASS WITH NOTES / FAIL)',
|
|
228
228
|
},
|
|
229
229
|
// integration-evidence(custom,D10 事前契约):风险门控。判定算法 detectChangeRisk +
|
|
@@ -149,7 +149,49 @@ AC-011: 不涉及业务规则/产品范围/默认行为/用户可见行为变更
|
|
|
149
149
|
### 产物文件
|
|
150
150
|
|
|
151
151
|
#### design.md(必填,写入变更根目录 \`design.md\`)
|
|
152
|
-
包含:背景、设计目标、非目标、总体方案、文件变更清单、接口定义、数据模型(如涉及)、兼容策略(brownfield
|
|
152
|
+
包含:背景、设计目标、非目标、总体方案、文件变更清单、接口定义、数据模型(如涉及)、兼容策略(brownfield)、风险登记、决策追踪、自审。
|
|
153
|
+
**章节骨架必须按下述字面格式写**(校验器按字面短语匹配,措辞偏离会连环卡门——坑 literal-template-trap,2026-08-24 用户反馈五期②):
|
|
154
|
+
\`\`\`markdown
|
|
155
|
+
# 设计文档(Design)— <变更简述>
|
|
156
|
+
|
|
157
|
+
## 背景
|
|
158
|
+
<为什么做、解决什么问题>
|
|
159
|
+
|
|
160
|
+
## 设计目标
|
|
161
|
+
<要达成什么>
|
|
162
|
+
|
|
163
|
+
## 非目标
|
|
164
|
+
<明确不做的事——标题必须字面含「非目标」(Non-Goals 也认,但大小写须完全一致)>
|
|
165
|
+
|
|
166
|
+
## 总体方案
|
|
167
|
+
<技术方案>
|
|
168
|
+
|
|
169
|
+
## 文件变更清单
|
|
170
|
+
| 操作 | 文件路径 | 说明 |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| 新增 | src/xxx/NewFile.ts | ... |
|
|
173
|
+
| 修改 | src/xxx/ExistingFile.ts | ... |
|
|
174
|
+
|
|
175
|
+
## 生命周期契约表
|
|
176
|
+
<仅当涉及 session/lease/agent_run/daemon/lifecycle/state_transition/claim/heartbeat 关键词时必填;
|
|
177
|
+
若不涉及,删掉本节或在正文写紧邻豁免短语:「不涉及生命周期契约」或「生命周期契约:无/N/A」
|
|
178
|
+
——「无需 lifecycle 事件」这类宽写法不被识别(否定词必须紧邻「生命周期契约/lifecycle contract」)>
|
|
179
|
+
|
|
180
|
+
| 事件 | 发起方 | 接收方 | 必需字段 | 状态变化 |
|
|
181
|
+
|---|---|---|---|---|
|
|
182
|
+
| <事件名> | <发起方> | <接收方> | <字段清单> | <初始态 → 终态> |
|
|
183
|
+
|
|
184
|
+
## 风险登记
|
|
185
|
+
| 编号 | 风险 | 等级 | 应对策略 |
|
|
186
|
+
|---|---|---|---|
|
|
187
|
+
| R-01 | ... | P0/P1/P2 | ... |
|
|
188
|
+
|
|
189
|
+
## 决策追踪
|
|
190
|
+
<列出 D-xxx@vN 决策 ID 及覆盖关系;无决策可写「无」>
|
|
191
|
+
|
|
192
|
+
## 自审
|
|
193
|
+
<对自身设计的校验——标题必须字面含「自审」或「Self-Review」(自我审查等宽写法不识别)>
|
|
194
|
+
\`\`\`
|
|
153
195
|
|
|
154
196
|
#### decisions.md(必填,写入变更根目录 \`decisions.md\`)
|
|
155
197
|
记录所有决策:
|
package/src/stages/brainstorm.js
CHANGED
|
@@ -308,6 +308,8 @@ design.md 第一行标题必须用中文:# 设计文档(Design)— <变更
|
|
|
308
308
|
|
|
309
309
|
判断规则:
|
|
310
310
|
- design.md 或需求中出现上述关键词 → 必须生成此表
|
|
311
|
+
- 确实不涉及 → 写**紧邻豁免短语**(照抄其一):「不涉及生命周期契约」「不适用 lifecycle contract」「生命周期契约:无/N/A」
|
|
312
|
+
——「本变更无需 lifecycle 事件」「lifecycle 状态无变化」这类宽写法**不被识别**(否定词必须紧邻「生命周期契约/lifecycle contract」)
|
|
311
313
|
- 表中的每个事件 → 必须有对应代码任务、接口任务、测试任务
|
|
312
314
|
- 表中的必需字段 → 必须出现在相关 DTO/interface 定义中
|
|
313
315
|
- 缺少任一事件 → 在 design.md 风险登记中明确记录
|
package/src/stages/plan.js
CHANGED
|
@@ -456,7 +456,12 @@ ${taskList}
|
|
|
456
456
|
|
|
457
457
|
**你必须使用 Agent tool 启动子代理来写卡片,不要自己写。**
|
|
458
458
|
|
|
459
|
-
|
|
459
|
+
0. **先由你(主 agent)一次性预生成全部骨架**(2026-08-25 实证:并行子代理各自跑 taskcard CLI 撞进度库 SQLite 锁,主代理单进程预生成 + 子代理只 Edit 才稳):
|
|
460
|
+
\`\`\`
|
|
461
|
+
sillyspec taskcard ${changeName} --all
|
|
462
|
+
\`\`\`
|
|
463
|
+
幂等,已存在的卡跳过不覆盖;骨架带 LF 行尾 + 闭合 frontmatter + 硬校验 9 字段 + depends_on 反填。**只有主 agent 跑这一次,子代理一律不再运行 taskcard CLI。**
|
|
464
|
+
1. 确认 \`${changeDir}/tasks/\` 目录存在(上一步预生成会自动创建)
|
|
460
465
|
2. **按 batch 分派子代理(减少总子代理数量,而不是一个 task 一个子代理):**
|
|
461
466
|
- 把任务按「同一 Wave + 同一模块/相近能力 + 无跨 batch 强依赖」原则分组
|
|
462
467
|
- **每个 batch 包含 2~4 个 task**;Wave 内任务数 ≤4 时整个 Wave 可作为一个 batch
|
|
@@ -474,7 +479,7 @@ ${taskList}
|
|
|
474
479
|
你是一个专注的 TaskCard 生成器。你的任务是为指定 batch 生成全部 TaskCard。
|
|
475
480
|
|
|
476
481
|
## ⚠️ 格式形态(第一眼必读,坑 taskcard-body-section-rework)
|
|
477
|
-
TaskCard 的契约字段全部在 **frontmatter**(首对 --- 包裹的 YAML 键值对:id/title/title_zh/allowed_paths/goal/...)——**不是 body 章节**(## 标题下的段落)。goal 是 frontmatter 的 \`goal: >\` 多行标量、implementation/acceptance/verify/constraints 是 frontmatter 的列表项;写成 body 章节(\`## goal\` / \`## Goal\`
|
|
482
|
+
TaskCard 的契约字段全部在 **frontmatter**(首对 --- 包裹的 YAML 键值对:id/title/title_zh/allowed_paths/goal/...)——**不是 body 章节**(## 标题下的段落)。goal 是 frontmatter 的 \`goal: >\` 多行标量、implementation/acceptance/verify/constraints 是 frontmatter 的列表项;写成 body 章节(\`## goal\` / \`## Goal\` 等)会三组校验全挂返工。骨架已由主 agent 预生成(正确形态),直接 Edit 填充即可。
|
|
478
483
|
|
|
479
484
|
## 输入
|
|
480
485
|
- 变更目录:${changeDir}
|
|
@@ -486,8 +491,7 @@ TaskCard 的契约字段全部在 **frontmatter**(首对 --- 包裹的 YAML
|
|
|
486
491
|
## 操作
|
|
487
492
|
1. 读取 ${changeDir}/design.md 和 ${changeDir}/plan.md 了解整体上下文
|
|
488
493
|
2. 读取本 batch 涉及的相关源文件
|
|
489
|
-
3.
|
|
490
|
-
sillyspec taskcard ${changeName} --task <本 batch 任务号,逗号分隔,如 task-01,task-02>
|
|
494
|
+
3. **骨架已由主 agent 预生成**(\`${changeDir}/tasks/task-NN.md\` 已存在,LF 行尾 + 闭合 frontmatter + 硬校验 9 字段齐全)。**禁止再运行 \`sillyspec taskcard\` CLI**——并行子代理各起 CLI 进程会撞进度库 SQLite 锁(2026-08-25 实证);若发现本 batch 某卡骨架缺失,报告主 agent 补跑,不要自己跑
|
|
491
495
|
4. 用 Edit tool 逐卡填充骨架占位符(allowed_paths/goal/implementation/acceptance/verify/constraints 等)。**禁止用 Write 整文件重写**——手写整卡是 CRLF 行尾/漏闭合 --- /漏硬校验字段三类 postcheck 拒绝的根源,骨架 + Edit 从源头消灭
|
|
492
496
|
5. 骨架字段含义与可选字段(provides/expects_from/related_tests,按需插进 frontmatter)参考下述模板:
|
|
493
497
|
|
package/src/stages/verify.js
CHANGED
|
@@ -236,7 +236,7 @@ verify-result.md 路径 + 验证报告摘要 + 下一步命令
|
|
|
236
236
|
|
|
237
237
|
### 注意
|
|
238
238
|
- PASS → 运行 \`sillyspec run archive\` 归档
|
|
239
|
-
- FAIL → 修复后运行 \`sillyspec run verify\`
|
|
239
|
+
- FAIL → 修复后运行 \`sillyspec run verify\` 重新验证——注意重验时 CLI 会重新执行 commands.test 全量对账(长套件数分钟、同步无输出属正常;可 local.yaml test_strategy: module 收窄)
|
|
240
240
|
- verify-result.md 是变更包的正式验收记录,归档后保留
|
|
241
241
|
- **CLI 对账机制**:本步骤 --done 时 CLI 会亲自执行 local.yaml 的 commands.test;结论写 PASS 但实测失败 → verify 完成被阻断`,
|
|
242
242
|
outputHint: '验证报告',
|
package/src/taskcard.js
CHANGED
|
@@ -126,6 +126,39 @@ constraints:
|
|
|
126
126
|
`
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
/**
|
|
130
|
+
* TaskCard 骨架幂等预生成(坑 taskcard-parallel-cli-lock,2026-08-25 实证:plan 生成
|
|
131
|
+
* TaskCard 步骤让并行 batch 子代理各自跑 sillyspec taskcard CLI,多进程并发撞进度库
|
|
132
|
+
* SQLite 锁——用户改为主代理预生成骨架+子代理只 Edit 才稳。本函数把该实践内建:
|
|
133
|
+
* plan gate 前主流程单进程调用,注册表(tasks.md)里有声明而 tasks/ 缺卡的任务一律
|
|
134
|
+
* 补骨架,已存在跳过不覆盖;骨架占位符硬拦(plan-postcheck)不变——预生成只消灭
|
|
135
|
+
* CLI 并发与格式错误,不替子代理产语义内容)。
|
|
136
|
+
*
|
|
137
|
+
* @param {string} changeName
|
|
138
|
+
* @param {{ cwd: string, specDir?: string|null }} opts
|
|
139
|
+
* @returns {{ created: string[], skipped: string[] }}
|
|
140
|
+
*/
|
|
141
|
+
export function ensureTaskcardSkeletons(changeName, opts = {}) {
|
|
142
|
+
const { cwd, specDir = null } = opts
|
|
143
|
+
assertSafeChangeName(changeName, '变更名')
|
|
144
|
+
const changeDir = join(resolveSpecDir(cwd, { specDir }), 'changes', changeName)
|
|
145
|
+
if (!existsSync(changeDir)) return { created: [], skipped: [] }
|
|
146
|
+
// 注册表口径与 cmdTaskcard 同源:tasks.md 优先,回退 plan.md(旧归档变更兼容读侧)
|
|
147
|
+
const tasksMdPath = join(changeDir, 'tasks.md')
|
|
148
|
+
const planPath = join(changeDir, 'plan.md')
|
|
149
|
+
const registryPath = existsSync(tasksMdPath) ? tasksMdPath : (existsSync(planPath) ? planPath : null)
|
|
150
|
+
if (!registryPath) return { created: [], skipped: [] }
|
|
151
|
+
const registry = parseTaskRegistry(readFileSync(registryPath, 'utf8').replace(/\r\n/g, '\n'))
|
|
152
|
+
if (registry.length === 0) return { created: [], skipped: [] }
|
|
153
|
+
const tasksDir = join(changeDir, 'tasks')
|
|
154
|
+
const missing = registry.map(t => t.id).filter(id => !existsSync(join(tasksDir, `${id}.md`)))
|
|
155
|
+
if (missing.length === 0) {
|
|
156
|
+
return { created: [], skipped: registry.map(t => join(tasksDir, `${t.id}.md`)) }
|
|
157
|
+
}
|
|
158
|
+
return cmdTaskcard(changeName, { cwd, specDir, taskIds: missing })
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
129
162
|
/**
|
|
130
163
|
* taskcard 命令主逻辑:定位变更目录 → 解析任务清单(--task 显式列表 / 'all' 从 plan.md 取)→
|
|
131
164
|
* 逐个写骨架。
|
package/src/verify-probes.js
CHANGED
|
@@ -19,7 +19,7 @@ import { join, dirname, basename } from 'path'
|
|
|
19
19
|
import { gitQuiet } from './git-helper.js'
|
|
20
20
|
import { parseFileChangeListDetailed } from './change-list.js'
|
|
21
21
|
import { parseAllowedPaths } from './stages/plan-postcheck.js'
|
|
22
|
-
import { verifyApiParity } from './contract-matrix.js'
|
|
22
|
+
import { verifyApiParity, _readWorktreeMeta } from './contract-matrix.js'
|
|
23
23
|
import { splitOwnVsForeignDiffFiles } from './foreign-declared.js'
|
|
24
24
|
import { resolveSpecDir, resolveRuntimeRoot, detectWorktreeSpecDrift } from './run/shared.js'
|
|
25
25
|
|
|
@@ -101,17 +101,29 @@ export function runVerifyProbes({ cwd, changeName, specDir = null }) {
|
|
|
101
101
|
const designOps = new Map(detailed.map(e => [e.path, e.operation]))
|
|
102
102
|
|
|
103
103
|
// ── 探针 1:未实现标记扫描(design 清单具体文件;glob 项列出让 agent 展开)──
|
|
104
|
-
|
|
104
|
+
// worktree 路径回退(坑 probe1-worktree-path-blind,2026-08-24 用户实证:verify 从主仓跑、
|
|
105
|
+
// apply 前 design 清单新文件只在 worktree → 6 个新文件被报「不存在」跳过不扫)。主仓路径
|
|
106
|
+
// 缺失且存在真实 worktree(gitDir≠cwd,与探针 5 的 _readWorktreeMeta 同源解析)→ 读 worktree
|
|
107
|
+
// 版本并计数;两处皆缺才进 skippedFiles。in-place meta(gitDir==cwd)零行为变化。
|
|
108
|
+
const wtInfo = _readWorktreeMeta(specBase, cwd, changeName)
|
|
109
|
+
const wtRoot = (wtInfo && wtInfo.gitDir && wtInfo.gitDir !== cwd) ? wtInfo.gitDir : null
|
|
110
|
+
const probe1 = { matches: [], globEntries: [], skippedFiles: [], worktreeHits: 0 }
|
|
105
111
|
for (const e of detailed) {
|
|
106
112
|
if (e.path.startsWith('.sillyspec/')) continue
|
|
107
113
|
if (/[*?[\]]/.test(e.path)) {
|
|
108
114
|
probe1.globEntries.push(e.path)
|
|
109
115
|
continue
|
|
110
116
|
}
|
|
111
|
-
|
|
117
|
+
let abs = join(cwd, e.path)
|
|
112
118
|
if (!existsSync(abs)) {
|
|
113
|
-
|
|
114
|
-
|
|
119
|
+
const wtAbs = wtRoot ? join(wtRoot, e.path) : null
|
|
120
|
+
if (wtAbs && existsSync(wtAbs)) {
|
|
121
|
+
abs = wtAbs
|
|
122
|
+
probe1.worktreeHits++
|
|
123
|
+
} else {
|
|
124
|
+
probe1.skippedFiles.push(e.path)
|
|
125
|
+
continue
|
|
126
|
+
}
|
|
115
127
|
}
|
|
116
128
|
try {
|
|
117
129
|
const lines = readFileSync(abs, 'utf8').split('\n')
|
|
@@ -134,7 +146,15 @@ export function runVerifyProbes({ cwd, changeName, specDir = null }) {
|
|
|
134
146
|
const moduleDirs = [...new Set(allowed.map(p => dirname(p.split('\\').join('/'))).filter(d => d && d !== '.'))]
|
|
135
147
|
const testFiles = []
|
|
136
148
|
for (const d of moduleDirs) {
|
|
137
|
-
|
|
149
|
+
// 同探针 1 的 worktree 回退(坑 probe1-worktree-path-blind):新模块目录 apply 前只在
|
|
150
|
+
// worktree——主仓目录缺失时到 worktree 找 co-located 测试(路径相对 worktree 根呈现)
|
|
151
|
+
let searchRoot = join(cwd, d)
|
|
152
|
+
let relBase = cwd
|
|
153
|
+
if (!existsSync(searchRoot) && wtRoot && existsSync(join(wtRoot, d))) {
|
|
154
|
+
searchRoot = join(wtRoot, d)
|
|
155
|
+
relBase = wtRoot
|
|
156
|
+
}
|
|
157
|
+
for (const f of findTestFiles(searchRoot, relBase)) {
|
|
138
158
|
if (!testFiles.includes(f)) testFiles.push(f)
|
|
139
159
|
}
|
|
140
160
|
}
|
|
@@ -200,6 +220,7 @@ export function renderVerifyProbesReport(result) {
|
|
|
200
220
|
for (const m of probe1.matches) L.push(`- ⚠️ \`${m.file}:${m.line}\` ${m.content}`)
|
|
201
221
|
}
|
|
202
222
|
if (probe1.globEntries.length > 0) L.push(`- ℹ️ glob 项未展开(agent 手动展开扫描):${probe1.globEntries.join('、')}`)
|
|
223
|
+
if (probe1.worktreeHits > 0) L.push(`- ℹ️ ${probe1.worktreeHits} 个清单文件主仓不存在、已从 worktree 读取(apply 前新文件形态)`)
|
|
203
224
|
if (probe1.skippedFiles.length > 0) L.push(`- ℹ️ 清单文件不存在(跳过):${probe1.skippedFiles.join('、')}`)
|
|
204
225
|
L.push('')
|
|
205
226
|
|