sillyspec 3.28.7 → 3.28.9
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-quick/SKILL.md +2 -0
- package/.husky/pre-push +3 -1
- package/AGENTS.md +3 -2
- package/README.md +35 -11
- package/SKILL.md +24 -10
- package/SillySpec-/350/203/275/345/212/233/344/272/256/347/202/271/345/205/250/346/231/257-2026-09-14.pptx +0 -0
- package/package.json +1 -1
- package/src/agent-session-log.js +446 -35
- package/src/change-risk-profile.js +20 -0
- package/src/config-cat.js +6 -3
- package/src/config-schema.js +32 -0
- package/src/constants.js +3 -0
- package/src/db.js +23 -6
- package/src/decision-distill.js +15 -3
- package/src/design-facts.js +33 -3
- package/src/docs-check.js +137 -12
- package/src/docs-gate.js +108 -6
- package/src/doctor-diagnostics.js +243 -9
- package/src/friction-ledger.js +171 -0
- package/src/friction-tally.js +7 -2
- package/src/git-helper.js +6 -4
- package/src/hooks/worktree-guard.js +72 -30
- package/src/index.js +356 -81
- package/src/knowledge-classify.js +363 -0
- package/src/knowledge-hits.js +74 -0
- package/src/knowledge-stats.js +180 -0
- package/src/local-detect.js +30 -15
- package/src/local-register.js +13 -4
- package/src/mcp-server.js +38 -8
- package/src/progress/change-registry.js +224 -43
- package/src/progress/consistency-doctor.js +11 -4
- package/src/progress/shared.js +3 -2
- package/src/progress/stage-machine.js +3 -1
- package/src/progress/step-store.js +30 -17
- package/src/progress.js +123 -8
- package/src/quick-gate-profile.js +251 -0
- package/src/quicklog.js +199 -32
- package/src/review-dispatch.js +2 -1
- package/src/run/command.js +124 -15
- package/src/run/complete-handlers.js +514 -24
- package/src/run/complete.js +82 -25
- package/src/run/concurrent-detect.js +99 -1
- package/src/run/gate-snapshot.js +115 -5
- package/src/run/gates.js +85 -14
- package/src/run/prompt.js +143 -6
- package/src/run/quick-audit.js +136 -15
- package/src/run/shared.js +174 -20
- package/src/run/stage.js +6 -0
- package/src/run/verify-quality-scan.js +14 -3
- package/src/scan-diff.js +61 -30
- package/src/scan-postcheck.js +74 -0
- package/src/scan-refresh.js +477 -0
- package/src/scan-staleness.js +25 -13
- package/src/scope-audit.js +365 -35
- package/src/spec-sync.js +19 -14
- package/src/stage-contract-spec.js +4 -3
- package/src/stage-contract.js +185 -2
- package/src/stage-review.js +29 -12
- package/src/stages/brainstorm.js +7 -1
- package/src/stages/execute.js +91 -4
- package/src/stages/knowledge.js +52 -3
- package/src/stages/plan-postcheck.js +57 -12
- package/src/stages/quick.js +1 -1
- package/src/stages/verify.js +1 -0
- package/src/sync-noise.js +114 -6
- package/src/sync.js +201 -11
- package/src/task-review.js +333 -27
- package/src/verify-facts-schema.js +10 -3
- package/src/verify-postcheck.js +41 -6
- package/src/verify-probes.js +348 -7
- package/src/worktree-apply.js +594 -62
- package/src/worktree-cross.js +12 -18
- package/src/worktree-deps.js +100 -2
- package/src/worktree.js +255 -30
- package/src/wt-commit.js +128 -0
- package/templates/agents-instruction.md +2 -2
- package/templates/prompts/testcase-design.md +2 -1
- package/templates/prompts/verify-probes.md +4 -1
- package/sa-rename-probe/src/new.js +0 -4
package/src/wt-commit.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sillyspec wt-commit —— worktree 内 per-task 串行化提交(提交队列)
|
|
3
|
+
*
|
|
4
|
+
* 坑 wt-parallel-commit-race(2026-08-29 用户实证×2):同 Wave 多个 task 子代理共享同一个
|
|
5
|
+
* 主仓 worktree,各自裸跑 git add/commit 有两条竞态:① `git add -A` 把并行兄弟的 WIP 文件卷进
|
|
6
|
+
* 自己的提交;② 并发写 index 撞 index.lock。两次都靠子代理自觉救回,属结构性风险——本命令把
|
|
7
|
+
* 「自觉」换成「机制」:
|
|
8
|
+
*
|
|
9
|
+
* ① 文件锁串行化:withFileLock 排他锁 <mainRepo>/.sillyspec/.runtime/wt-commit-<change>.lock,
|
|
10
|
+
* 同 change 的 wt-commit 全局排队(per-task 提交队列效果),锁内 add→commit 原子完成;
|
|
11
|
+
* ② 强制显式 pathspec:-- 后逐路径(或 --pathspec-from-file)——命令语义上杜绝 add -A 扫入
|
|
12
|
+
* 他人 WIP;commit 带同一 pathspec(--only 语义),不动其他并行者已暂存的内容。
|
|
13
|
+
*
|
|
14
|
+
* 边界:锁只约束 wt-commit 调用方;兄弟子代理若仍裸跑 git,靠 ② 的 pathspec 隔离兜底 + git 侧
|
|
15
|
+
* index.lock 冲突时退避重试。跨仓 task 不走本命令(跨仓有独立 workdir / 直写主干,execute 指引
|
|
16
|
+
* 要求显式 pathspec 提交)。锁文件在 .runtime(运行时目录,不入 git 历史)。
|
|
17
|
+
*
|
|
18
|
+
* 成功返回 HEAD hash(review.json 的 head 锚点直接可用);无变更 → ok=true + skipped。
|
|
19
|
+
*/
|
|
20
|
+
import { join, resolve } from 'node:path'
|
|
21
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
22
|
+
import { withFileLock } from './quicklog.js'
|
|
23
|
+
import { safeGit } from './git-helper.js'
|
|
24
|
+
import { WorktreeManager } from './worktree.js'
|
|
25
|
+
|
|
26
|
+
const sleep = (ms) => new Promise(r => setTimeout(r, ms))
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* git 子操作退避重试:撞 index.lock / HEAD 锁(锁队列外裸跑 git 的兄弟进程短暂持锁)时等待重试。
|
|
30
|
+
* 非 lock 类错误(路径不存在 / pathspec 不匹配 / 权限)直接抛,不空转。
|
|
31
|
+
*/
|
|
32
|
+
async function gitWithLockRetry(cwd, args, label, { retries = 6, waitMs = 1000 } = {}) {
|
|
33
|
+
for (let i = 0; ; i++) {
|
|
34
|
+
const { value, error } = safeGit(cwd, args, { timeout: 30000 })
|
|
35
|
+
if (!error) return value
|
|
36
|
+
const msg = String((error && error.message) || error)
|
|
37
|
+
if (/index\.lock|HEAD\.lock|cannot lock ref/i.test(msg) && i < retries) {
|
|
38
|
+
await sleep(waitMs)
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
41
|
+
throw new Error(`${label} 失败:${msg.split('\n')[0].trim()}`)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 执行 worktree 串行化提交。
|
|
47
|
+
*
|
|
48
|
+
* @param {object} opts
|
|
49
|
+
* @param {string} opts.changeName - 变更名(worktree 名)
|
|
50
|
+
* @param {string} opts.message - 提交信息(建议 "<task-NN 摘要>")
|
|
51
|
+
* @param {string[]} opts.pathspecs - 显式路径列表(必填,防 add -A 扫入并行兄弟 WIP)
|
|
52
|
+
* @param {string|null} [opts.pathspecFile] - --pathspec-from-file:每行一个路径(与 pathspecs 二选一或叠加)
|
|
53
|
+
* @param {string} [opts.cwd] - 调用方 cwd(主仓根或 worktree 内均可——WorktreeManager 经
|
|
54
|
+
* git-common-dir 反推主仓根,worktree 内调用同样定位正确)
|
|
55
|
+
* @returns {Promise<{ok:boolean,skipped:boolean,head:string,shortHead:string,worktreePath:string,changeName:string,files:string[]}>}
|
|
56
|
+
* ok=false 时抛错(调用方打错误 + exit 1),skipped=无变更可提交(HEAD 不动,不算失败)。
|
|
57
|
+
*/
|
|
58
|
+
export async function runWtCommit({ changeName, message, pathspecs = [], pathspecFile = null, cwd = process.cwd() }) {
|
|
59
|
+
if (!changeName) throw new Error('缺少 --change <变更名>(worktree 名;在 worktree 内运行时可省略,CLI 从 cwd 推断)')
|
|
60
|
+
if (!message || !message.trim()) throw new Error('缺少 -m/--message <提交信息>(建议 "<task-NN 摘要>")')
|
|
61
|
+
|
|
62
|
+
// pathspec 汇总:显式路径 + 文件逐行(# 注释与空行忽略,与 git pathspec-from-file 习惯一致)
|
|
63
|
+
const files = [...pathspecs.map(p => p.trim()).filter(Boolean)]
|
|
64
|
+
if (pathspecFile) {
|
|
65
|
+
if (!existsSync(pathspecFile)) throw new Error(`--pathspec-from-file 文件不存在: ${pathspecFile}`)
|
|
66
|
+
for (const line of readFileSync(pathspecFile, 'utf8').split(/\r?\n/)) {
|
|
67
|
+
const t = line.trim()
|
|
68
|
+
if (t && !t.startsWith('#')) files.push(t)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (files.length === 0) {
|
|
72
|
+
throw new Error('缺少提交路径:`--` 后逐个给出本 task 的精确路径(或 --pathspec-from-file <file>)。wt-commit 刻意不支持 add -A / 空 pathspec——同 Wave 兄弟子代理共享本 worktree,整树 add 会把对方 WIP 卷进你的提交')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const wm = new WorktreeManager({ cwd })
|
|
76
|
+
const worktreePath = wm.getWorktreePath(changeName)
|
|
77
|
+
// 隔离模式(worktree 目录存在)→ 提交目标 = worktree 本体;in-place-fallback(meta 声明无
|
|
78
|
+
// worktree 直改主仓)→ 主仓根(与 MultiRepoContext 主仓 entry 的 worktreePath 兜底口径一致)。
|
|
79
|
+
// 顺序先看目录再看 meta:worktree 目录存在即隔离态,meta 缺失/损坏不改变目标(fail-safe)。
|
|
80
|
+
let target = null
|
|
81
|
+
if (existsSync(worktreePath)) {
|
|
82
|
+
target = worktreePath
|
|
83
|
+
} else {
|
|
84
|
+
const meta = wm.getMeta(changeName)
|
|
85
|
+
if (meta && meta.mode === 'in-place-fallback') {
|
|
86
|
+
target = resolve(wm.worktreeBase, '..', '..', '..') // <main>/.sillyspec/.runtime/worktrees → <main>
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (!target) {
|
|
90
|
+
throw new Error(`worktree 不存在: ${worktreePath}(确认 --change 变更名;execute 尚未创建 worktree 时先跑 sillyspec run execute)`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 锁:主仓 .sillyspec/.runtime/ 下按 change 一把(in-place 模式同目录可达,零分叉)
|
|
94
|
+
const lockPath = join(wm.worktreeBase, '..', `wt-commit-${changeName}.lock`)
|
|
95
|
+
const envTimeout = parseInt(process.env.SILLYSPEC_WT_COMMIT_LOCK_TIMEOUT_MS || '', 10)
|
|
96
|
+
const lockTimeoutMs = Number.isFinite(envTimeout) && envTimeout > 0 ? envTimeout : 120000
|
|
97
|
+
|
|
98
|
+
const result = await withFileLock(lockPath, async () => {
|
|
99
|
+
const headBefore = safeGit(target, ['rev-parse', 'HEAD'], { timeout: 10000 }).value || ''
|
|
100
|
+
await gitWithLockRetry(target, ['add', '--', ...files], 'git add')
|
|
101
|
+
// pathspec 范围内是否有 staged 变更(vs HEAD):空 = 无可提交(task 幂等重跑场景),
|
|
102
|
+
// 跳过 commit 保持 skipped 语义——不靠解析 git commit 的 "nothing to commit" stdout
|
|
103
|
+
// (safeGit 的 error 只保留命令首行,stdout 不可得)。
|
|
104
|
+
const staged = safeGit(target, ['diff', '--cached', '--name-only', '--', ...files], { timeout: 10000 }).value || ''
|
|
105
|
+
if (!staged.trim()) {
|
|
106
|
+
return { ok: true, skipped: true, head: headBefore }
|
|
107
|
+
}
|
|
108
|
+
await gitWithLockRetry(target, ['commit', '-m', message, '--', ...files], 'git commit')
|
|
109
|
+
const head = safeGit(target, ['rev-parse', 'HEAD'], { timeout: 10000 }).value || headBefore
|
|
110
|
+
return { ok: true, skipped: false, head }
|
|
111
|
+
}, {
|
|
112
|
+
// 持有者语义:超时报错时能看出是谁卡着队列
|
|
113
|
+
content: JSON.stringify({ pid: process.pid, changeName, purpose: 'wt-commit' }),
|
|
114
|
+
staleMs: 120000, // 持锁进程崩溃残留的回收阈值(commit 通常秒级)
|
|
115
|
+
timeoutMs: lockTimeoutMs, // 排队上限:同 Wave 并行 task 全走队列,给足余量
|
|
116
|
+
retryMs: 200,
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
ok: true,
|
|
121
|
+
skipped: !!result.skipped,
|
|
122
|
+
head: result.head,
|
|
123
|
+
shortHead: (result.head || '').slice(0, 8),
|
|
124
|
+
worktreePath: target,
|
|
125
|
+
changeName,
|
|
126
|
+
files,
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
1. **禁止绕过本文件规则和 SillySpec 流程**。所有变更走 sillyspec 流程,不裸改裸提交。
|
|
8
8
|
2. **改代码前必须先说明依据**——依据的文档路径(design.md / 模块文档 / file-lifecycle.md)或现有代码依据,无依据不改。
|
|
9
9
|
3. **新功能 / 大改动走完整流程**:`brainstorm → plan → execute → verify → archive`。
|
|
10
|
-
4. **小修复 / 小调整走 quick
|
|
10
|
+
4. **小修复 / 小调整走 quick**(= 无需要落盘的设计决策的改动,选道判据见第 6 条):`sillyspec run quick`。
|
|
11
11
|
5. **执行顺序**:文档 → 读代码 → 写测试 → 写实现 → 跑测试 → 验收 → 更新文档。
|
|
12
|
-
6.
|
|
12
|
+
6. **判规模选档**:看本次改动有无需要落盘的设计决策——有(新能力 / 行为契约变更 / 跨模块取舍 / 多阶段推进)走完整流程,无(范围明确的局部修补)走 `quick`。文件数不是选道判据,只作出口绊线——改动大或跨模块时 quick 收尾门禁会自动加查(分级提示补文件注记 / 测试增量 / 模块文档认领)。
|
|
13
13
|
7. **代码先行不补流程(倒推 B 模式)**:代码若已先写好,**不回头补 brainstorm/plan 装样子**——用 `quick --done` 收尾 + 补 quicklog 条目,把已落盘改动如实登记进进度库。
|
|
14
14
|
8. **实证核验再 `--done`**:触及 `src`/`test` 的改动,CLI 会在 `quick --done` 时**亲自实测** `.sillyspec/local.yaml` 的 `commands.test` / `commands.lint`(门禁:实测失败阻断 --done 回 pending;纯 doc/配置与未配置命令自动跳过;倒推 B 模式按 --files 声明边界兜底判定)。agent 侧预跑**可选**——想省「--done 被拦→修复→重跑」一轮时才预跑;纯 doc/配置改动无需跑。以落盘文件与测试结果为准,不信口头"已完成"。
|
|
15
15
|
9. **中途停下不靠额外命令存进度**——进度已由上一次 `--done` 自动落盘;恢复时用 `sillyspec progress show` 查看进度,再用 `sillyspec run <stage>` 续跑,不直接 commit 半成品。
|
|
@@ -9,4 +9,5 @@
|
|
|
9
9
|
3. **行为 vs 实现**:走公开 API 测行为;重构后功能不变,测试不应失败。
|
|
10
10
|
4. **契约与回归**:覆盖本次改动会破坏的既有行为与相邻模块接口;改公共签名/文案/常量时同步修复受影响既有测试并跑全量,禁止删断言凑绿。
|
|
11
11
|
5. **时间敏感分支**:超时/重试/并发分支别真等——超时参数做成可注入,测试传毫秒级极小值或注入可控时钟推进,再断言超时后的行为(走哪个分支/返回什么/资源是否回收)。
|
|
12
|
-
6. **隔离确定性**:独立 fixture、可重复跑,不依赖共享状态/执行顺序。
|
|
12
|
+
6. **隔离确定性**:独立 fixture、可重复跑,不依赖共享状态/执行顺序。
|
|
13
|
+
7. **覆盖对账**:每条 TaskCard acceptance 至少对应一个测试用例,或显式标注 non-testable(文档/部署类——证据列写一句理由);对应关系由 verify 探针 7 矩阵机械核对(判定 covered/partial 须给测试锚点)——写测试前先读卡的 acceptance 列表逐条对齐,勿只覆盖顺手的主路径。
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> 🔧 **先跑 CLI 机械探针(一条命令)**:`sillyspec verify-probes --change <change-name> [--init]`
|
|
4
4
|
> 输出已包含四个纯机械探针的结果(可直接进验证报告):**探针 1** 未实现标记扫描(design 清单文件逐行 TODO/FIXME 命中 + 行号)、**探针 3** 测试文件递归查找(含 co-located tests/,逐 task 覆盖)、**探针 5** API 契约对账表(endpoints.json × 前端调用 diff)、**探针 6** 删除对账三态(git 事实 × design 声明)。`--init` 顺带生成 verify-result.md 九章节骨架(探针结果已预填,`<!--TODO-->` 占位替换即可)。
|
|
5
5
|
>
|
|
6
|
-
> 下列条目中,只有**半语义部分**需要你执行:探针 2(关键词提取)、探针 3.4 集成盲区标注、探针 3.5 断言有效性抽查、探针 4
|
|
6
|
+
> 下列条目中,只有**半语义部分**需要你执行:探针 2(关键词提取)、探针 3.4 集成盲区标注、探针 3.5 断言有效性抽查、探针 4(决策追踪闭环判定)、探针 7(验收×测试覆盖矩阵判定填槽)。CLI 已跑的部分勿重复手跑——对 CLI 输出的 warning 项做语义复核即可。
|
|
7
7
|
|
|
8
8
|
**探针 2:设计关键词覆盖探针(你执行——关键词提取是语义)**
|
|
9
9
|
1. 读取 design.md,从中提取所有能力关键词(如"登录"、"导出"、"批量"、"删除"、"搜索"等动作词)
|
|
@@ -31,3 +31,6 @@ CLI 的 missing backend endpoint 是 advisory(不硬阻断归档)——但 c
|
|
|
31
31
|
|
|
32
32
|
**探针 6 补充(CLI 已出三态判定,你做终审)**
|
|
33
33
|
以 git 事实为准(真实 > 声明);CLI 的三态(合规/高风险/未声明)是机械判定,是否构成 FAIL blocker 由你诚实判定,务必如实记录——静默删除代码是 verify 的最大盲区。
|
|
34
|
+
|
|
35
|
+
**探针 7 补充(CLI 已预填矩阵归属与提示,你填判定与证据)**
|
|
36
|
+
验收×测试覆盖矩阵:CLI 机械半边已预填——逐 task 解析 TaskCard acceptance(frontmatter)、双源结构归属测试文件(allowed_paths 测试模式 ∪ execute review changedFiles 中 test/ 路径)、关键词命中提示(命中≠判定,仅提示)。你逐行填判定槽四枚举(covered / partial / uncovered / non-testable——non-testable 是文档/部署类显式逃生门)与证据:covered/partial 附测试锚点(`.test.` 文件或 file:line),non-testable 附一句理由;判定或证据未填 fail-closed,阻断 verify `--done`。与探针 3 口径差异:探针 3 查模块目录有没有测试文件(存在性面),探针 7 查每条 acceptance 由哪些测试承接(承接面);两者并排冲突以 7 为准。
|