sillyspec 3.29.4 → 3.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/.claude/skills/sillyspec-auto/SKILL.md +1 -1
  2. package/.claude/skills/sillyspec-brainstorm/SKILL.md +2 -2
  3. package/.claude/skills/sillyspec-commit/SKILL.md +1 -1
  4. package/.claude/skills/sillyspec-flow/SKILL.md +57 -0
  5. package/.claude/skills/sillyspec-quick/SKILL.md +7 -105
  6. package/AGENTS.md +38 -20
  7. package/SKILL.md +9 -3
  8. package/assets/command-cards/flow.md +32 -0
  9. package/assets/command-cards/run-archive.md +26 -0
  10. package/assets/command-cards/run-brainstorm.md +33 -0
  11. package/assets/command-cards/run-execute.md +29 -0
  12. package/assets/command-cards/run-plan.md +27 -0
  13. package/assets/command-cards/run-quick.md +17 -0
  14. package/assets/command-cards/run-verify.md +29 -0
  15. package/assets/command-cards/status.md +27 -0
  16. package/package.json +2 -1
  17. package/packages/dashboard/server/executor.js +2 -1
  18. package/packages/dashboard/server/parser.js +7 -7
  19. package/r7-test-env/README-R7-TEST.md +93 -0
  20. package/r7-test-env/demo-project/lib/calc.js +6 -0
  21. package/r7-test-env/demo-project/package.json +1 -0
  22. package/r7-test-env/demo-project/test.js +10 -0
  23. package/r7-test-env/prompt-R7-SMOKE.md +41 -0
  24. package/r7-test-env/sillyspec-r7.cmd +3 -0
  25. package/round4/account.mjs +98 -0
  26. package/round4/b1-analyze-task08.mjs +40 -0
  27. package/round4/b1-b1-target-validation.md +32 -0
  28. package/round4/b3a-prompt-audit-result.md +66 -0
  29. package/round4/b3a-prompt-audit.mjs +89 -0
  30. package/round4/brief-autocompact.md +34 -0
  31. package/round4/brief-session-replay.md +50 -0
  32. package/round4/deep-read-synthesis.md +38 -0
  33. package/round4/extract-full.mjs +38 -0
  34. package/round4/extract-timeline.mjs +43 -0
  35. package/round4/find-sessions.mjs +13 -0
  36. package/round4/full-r4-l.md +4707 -0
  37. package/round4/full-r4-o-l.md +4300 -0
  38. package/round4/full-r4-os.md +1681 -0
  39. package/round4/full-r4-sf.md +4255 -0
  40. package/round4/full-r4-sq.md +1734 -0
  41. package/round4/gate-audit-relay-receipt.md +30 -0
  42. package/round4/gate-value-audit.md +52 -0
  43. package/round4/optimization-plan.md +165 -0
  44. package/round4/prereg.md +67 -0
  45. package/round4/prompt-R4-L.md +63 -0
  46. package/round4/prompt-R4-O-L.md +59 -0
  47. package/round4/prompt-R4-O-S.md +56 -0
  48. package/round4/prompt-R4-S-F.md +60 -0
  49. package/round4/prompt-R4-S-Q.md +66 -0
  50. package/round4/r4-final-report.html +347 -0
  51. package/round4/r4-os-timeline.txt +297 -0
  52. package/round4/r4-sf-timeline.txt +738 -0
  53. package/round4/r4-sq-timeline.txt +322 -0
  54. package/round4/r4l-timeline.txt +793 -0
  55. package/round4/r4ol-timeline.txt +729 -0
  56. package/round4/r4sf-timeline.txt +852 -0
  57. package/round4/r5-scenario-matrix.md +46 -0
  58. package/round4/result-S.md +171 -0
  59. package/round4/result-final.md +46 -0
  60. package/round4/result-quality.md +63 -0
  61. package/src/change-list.js +9 -0
  62. package/src/classify-change.js +6 -0
  63. package/src/command-cards.js +74 -0
  64. package/src/commit-guard.js +2 -0
  65. package/src/config-schema.js +64 -12
  66. package/src/contract-matrix.js +26 -5
  67. package/src/db.js +35 -4
  68. package/src/decision-distill.js +9 -3
  69. package/src/diagnostic-codes.js +2 -1
  70. package/src/docs-check.js +1 -1
  71. package/src/doctor-diagnostics.js +44 -2
  72. package/src/flow-draft.js +748 -0
  73. package/src/flow-parity.js +236 -0
  74. package/src/flow-review.js +184 -0
  75. package/src/flow.js +1492 -0
  76. package/src/foreign-declared.js +17 -1
  77. package/src/fr-index.js +396 -22
  78. package/src/fs-atomic.js +5 -1
  79. package/src/git-helper.js +2 -2
  80. package/src/greenfield-bootstrap.js +98 -0
  81. package/src/handoff.js +120 -7
  82. package/src/hunk-attribution.js +191 -0
  83. package/src/index.js +602 -21
  84. package/src/init.js +25 -8
  85. package/src/knowledge-classify.js +47 -1
  86. package/src/knowledge-digest.js +162 -0
  87. package/src/knowledge-quicklog.js +246 -0
  88. package/src/machine-draft.js +128 -0
  89. package/src/machine-interface.js +194 -7
  90. package/src/mcp-server.js +1 -0
  91. package/src/module-impact.js +51 -22
  92. package/src/module-resolve.js +3 -1
  93. package/src/preview-progress.js +160 -0
  94. package/src/progress/change-registry.js +11 -3
  95. package/src/progress/shared.js +3 -2
  96. package/src/progress.js +56 -13
  97. package/src/quicklog.js +44 -6
  98. package/src/redomain.js +147 -0
  99. package/src/review-material-pack.js +204 -1
  100. package/src/run/archive-distill.js +4 -2
  101. package/src/run/bg-sync.js +67 -0
  102. package/src/run/command.js +127 -10
  103. package/src/run/complete-handlers.js +363 -73
  104. package/src/run/complete.js +356 -74
  105. package/src/run/gate-snapshot-ledger.js +213 -0
  106. package/src/run/gate-snapshot.js +260 -38
  107. package/src/run/gates.js +482 -268
  108. package/src/run/green-cache.js +123 -0
  109. package/src/run/junction-rm.js +70 -0
  110. package/src/run/next.js +14 -0
  111. package/src/run/prompt.js +185 -55
  112. package/src/run/quick-audit.js +213 -14
  113. package/src/run/shared.js +123 -4
  114. package/src/run/stage.js +182 -216
  115. package/src/run/test-ledger.js +264 -0
  116. package/src/run/verify-quality-scan.js +84 -7
  117. package/src/scope-audit.js +6 -1
  118. package/src/sentinel-assertions.js +114 -0
  119. package/src/setup.js +3 -3
  120. package/src/spec-sync.js +41 -1
  121. package/src/stage-contract.js +26 -8
  122. package/src/stage-review-checklist.js +2 -0
  123. package/src/stage-review.js +7 -2
  124. package/src/stage-templates.js +157 -0
  125. package/src/stages/archive.js +2 -0
  126. package/src/stages/brainstorm.js +24 -106
  127. package/src/stages/execute.js +222 -85
  128. package/src/stages/knowledge.js +74 -1
  129. package/src/stages/plan-postcheck.js +385 -20
  130. package/src/stages/plan.js +23 -1
  131. package/src/stages/quick.js +10 -2
  132. package/src/stages/verify.js +4 -2
  133. package/src/sync.js +81 -23
  134. package/src/task-done.js +181 -0
  135. package/src/task-review.js +8 -55
  136. package/src/taskcard-frontmatter.js +57 -0
  137. package/src/test-bindings.js +549 -0
  138. package/src/timeline.js +218 -0
  139. package/src/ui-visual.js +175 -0
  140. package/src/verify-draft.js +214 -0
  141. package/src/verify-postcheck.js +919 -159
  142. package/src/verify-probes.js +103 -25
  143. package/src/watcher.js +1031 -0
  144. package/src/worktree-apply.js +44 -4
  145. package/src/worktree-deps.js +22 -8
  146. package/src/worktree-policy.js +42 -0
  147. package/src/worktree.js +102 -15
  148. package/templates/agents-instruction.md +37 -18
@@ -0,0 +1,213 @@
1
+ /**
2
+ * gate-snapshot-ledger.js — 门禁隔离快照的生命周期账本与陈旧回收
3
+ * (2026-09-24-gate-snapshot-lifecycle,task-01;D-003@v2 / D-004@v2 / D-005@v1)
4
+ *
5
+ * 存在理由:门禁快照(%TEMP%/sillyspec-gate-*,src/run/gate-snapshot.js 建、quick/verify
6
+ * 门 finally 清)在三条路径必然泄漏——进程被杀(cleanup 根本没跑)、Windows rmSync 无重试
7
+ * 遇 EPERM/junction 锁、git worktree remove 失败留 prunable 注册。实证攒到 41 个目录+2 个
8
+ * prunable 注册且全仓无清扫机制。本模块提供:
9
+ * ①账本(create 登记 / cleanup 销账,幂等原子写)——崩溃残留的可追踪凭据;
10
+ * ②路径与条目 fail-closed 守卫——账本是可变 JSON,删除原语调用前必过(D-005@v1);
11
+ * ③TTL×pid 三态保守判定(D-003@v2)与「目录+worktree 注册双清」回收执行(D-004@v2):
12
+ * 双清确认才销号——删不掉的残留必须留在账本里等下一轮,否则永久失追踪。
13
+ *
14
+ * 信任边界:删除原语仅由本模块的回收路径调用,且每个条目先过 isSafeLedgerEntry;
15
+ * gate-snapshot.js 自身 cleanup 的 root 来自 mkdtemp(生成即受信)不经过账本。
16
+ * 异常面:全部 fail-open 吞掉(账本/回收任一异常零阻断,退现状残留)。
17
+ */
18
+ import { execFileSync } from 'node:child_process'
19
+ import { existsSync, mkdirSync, readFileSync } from 'node:fs'
20
+ import { basename, dirname, join, resolve, sep } from 'node:path'
21
+ import { tmpdir } from 'node:os'
22
+ import { writeAtomicSync } from '../fs-atomic.js'
23
+ import { safeRemoveDirWithLinks, unlinkLinkPath, collectLinkPathsUnder } from './junction-rm.js'
24
+
25
+ const LEDGER_FILE = 'active-gate-snapshots.json'
26
+ const HOUR_MS = 3600_000
27
+ const STALE_HOURS_DEFAULT = 24
28
+
29
+ /** 账本文件路径(runtime 域:.sillyspec/.runtime/active-gate-snapshots.json) */
30
+ export function gateSnapshotLedgerPath(runtimeRoot) {
31
+ return join(runtimeRoot, LEDGER_FILE)
32
+ }
33
+
34
+ /** 读账本(损坏/缺失/非数组 → 空数组,fail-open) */
35
+ export function readGateSnapshotLedger(runtimeRoot) {
36
+ if (!runtimeRoot) return []
37
+ try {
38
+ const p = gateSnapshotLedgerPath(runtimeRoot)
39
+ if (!existsSync(p)) return []
40
+ const data = JSON.parse(readFileSync(p, 'utf8'))
41
+ if (!Array.isArray(data)) return []
42
+ return data.filter((e) => e && typeof e === 'object' && typeof e.snapshotRoot === 'string')
43
+ } catch {
44
+ return []
45
+ }
46
+ }
47
+
48
+ function writeLedger(runtimeRoot, entries) {
49
+ mkdirSync(runtimeRoot, { recursive: true })
50
+ writeAtomicSync(gateSnapshotLedgerPath(runtimeRoot), JSON.stringify(entries, null, 2))
51
+ }
52
+
53
+ /** 登记快照(幂等:同 root 重复调用不追加;runtimeRoot 缺失退 no-op) */
54
+ export function registerGateSnapshot({ runtimeRoot, snapshotRoot, pid = process.pid }) {
55
+ if (!runtimeRoot || !snapshotRoot) return
56
+ try {
57
+ const entries = readGateSnapshotLedger(runtimeRoot)
58
+ if (entries.some((e) => e.snapshotRoot === snapshotRoot)) return
59
+ entries.push({ snapshotRoot, pid, createdAt: Date.now() })
60
+ writeLedger(runtimeRoot, entries)
61
+ } catch { /* fail-open:登记失败仅退化为残留不可追踪,不阻断门禁 */ }
62
+ }
63
+
64
+ /** 销账(幂等:缺失不抛;调用方负责先确认双清——本函数不判目录/注册状态) */
65
+ export function unregisterGateSnapshot({ runtimeRoot, snapshotRoot }) {
66
+ if (!runtimeRoot || !snapshotRoot) return
67
+ try {
68
+ const entries = readGateSnapshotLedger(runtimeRoot)
69
+ const next = entries.filter((e) => e.snapshotRoot !== snapshotRoot)
70
+ if (next.length === entries.length) return
71
+ writeLedger(runtimeRoot, next)
72
+ } catch { /* fail-open:销账失败保留条目,下轮重试 */ }
73
+ }
74
+
75
+ /**
76
+ * 路径守卫(D-005@v1):必须是 tmpdir 的**直接子目录**且 basename 严格匹配
77
+ * sillyspec-gate-* ——账本可被篡改,删除原语调用前唯一路径闸门。
78
+ */
79
+ export function isSafeSnapshotRoot(snapshotRoot, { tmpBase = tmpdir() } = {}) {
80
+ if (typeof snapshotRoot !== 'string' || !snapshotRoot) return false
81
+ if (snapshotRoot.includes('..')) return false
82
+ // 路径比较口径:win32 不区分大小写(TEMP/TMP 跨进程大小写漂移会让合法残留永不满足守卫)
83
+ const fold = (p) => (process.platform === 'win32' ? p.toLowerCase() : p)
84
+ const norm = resolve(snapshotRoot)
85
+ if (fold(dirname(norm)) !== fold(resolve(tmpBase))) return false
86
+ return /^sillyspec-gate-/.test(basename(norm))
87
+ }
88
+
89
+ /** 条目结构守卫:根过路径守卫 ∧ pid 正整数 ∧ createdAt 有限毫秒值 */
90
+ export function isSafeLedgerEntry(entry, opts) {
91
+ return !!entry
92
+ && isSafeSnapshotRoot(entry.snapshotRoot, opts)
93
+ && Number.isInteger(entry.pid) && entry.pid > 0
94
+ && Number.isFinite(entry.createdAt)
95
+ }
96
+
97
+ /** staleHours 值域:非有限/非正 → 回退 24h(D-003@v2 单一入口) */
98
+ export function resolveStaleHours(value = process.env.SILLYSPEC_GATE_SNAPSHOT_STALE_HOURS) {
99
+ const n = Number(value)
100
+ return Number.isFinite(n) && n > 0 ? n : STALE_HOURS_DEFAULT
101
+ }
102
+
103
+ /**
104
+ * pid 活性三态(D-003@v2,破坏性场景从严):成功/EPERM=活,ESRCH=死,
105
+ * 无效 pid/其他异常=按活跳过。与 bg-sync 终止扫描口径(无效 pid 按死)有意分歧——
106
+ * 那边误判代价=进程残留,这边误判代价=删掉活跃快照目录。
107
+ */
108
+ function defaultIsProcessAlive(pid) {
109
+ if (!Number.isInteger(pid) || pid <= 0) return true
110
+ try {
111
+ process.kill(pid, 0)
112
+ return true
113
+ } catch (e) {
114
+ return !(e && e.code === 'ESRCH')
115
+ }
116
+ }
117
+
118
+ /**
119
+ * 纯判定:守卫 ∧ 超 TTL ∧ 明确死 三合取才 stale;其余(未超期/存活/守卫不过/
120
+ * 异常)一律保留。staleHours 非法值回退 24h。
121
+ */
122
+ export function selectStaleSnapshots(entries, { now = Date.now(), staleHours, isProcessAlive = defaultIsProcessAlive } = {}) {
123
+ const hours = resolveStaleHours(staleHours)
124
+ const probe = typeof isProcessAlive === 'function' ? isProcessAlive : defaultIsProcessAlive
125
+ const stale = []
126
+ const alive = []
127
+ for (const e of Array.isArray(entries) ? entries : []) {
128
+ if (!isSafeLedgerEntry(e)) { alive.push(e); continue }
129
+ if ((now - e.createdAt) / HOUR_MS <= hours) { alive.push(e); continue }
130
+ let isAlive
131
+ try { isAlive = !!probe(e.pid) } catch { isAlive = true }
132
+ if (isAlive) { alive.push(e); continue }
133
+ stale.push(e)
134
+ }
135
+ return { stale, alive }
136
+ }
137
+
138
+ function defaultRunGit(cwd, args) {
139
+ return execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 60000, windowsHide: true }).trim()
140
+ }
141
+
142
+ function defaultRemoveDir(p) {
143
+ // junction 安全删除(坑 junction-pierce-rm-sync,2026-09-24 实证):残留快照内
144
+ // node_modules/venv junction 的目标是主仓真身,裸 rmSync(recursive) 穿透链接清空目标
145
+ // (收口期三次实证)——删前先解链(junction-rm.js 单一实现);解链失败抛错由调用方
146
+ // 按「删不掉保留条目待下轮」处置,绝不回落裸 rmSync。
147
+ safeRemoveDirWithLinks(p, { maxRetries: 5, retryDelay: 100 })
148
+ }
149
+
150
+ /** worktree 注册是否仍登记该 root(跨平台路径归一 + win32 大小写不敏感) */
151
+ function defaultWorktreeRegistered(cwd, snapshotRoot) {
152
+ const out = defaultRunGit(cwd, ['worktree', 'list', '--porcelain'])
153
+ const norm = (s) => {
154
+ const v = String(s).replace(/\\/g, '/')
155
+ return process.platform === 'win32' ? v.toLowerCase() : v
156
+ }
157
+ const target = norm(snapshotRoot)
158
+ return out.split('\n').some((line) => line.startsWith('worktree ') && norm(line.slice('worktree '.length)) === target)
159
+ }
160
+
161
+ /**
162
+ * 回收执行(D-004@v2):逐条 remove → rmSync(remove 失败补 prune),双清确认
163
+ * (目录不存在 ∧ worktree list 无注册)才销号;删不掉的计入 skipped 保留条目。
164
+ * 返回 {reclaimed, skipped};异常全吞退零副作用。
165
+ */
166
+ export function reclaimStaleGateSnapshots({
167
+ runtimeRoot,
168
+ cwd,
169
+ now = Date.now(),
170
+ staleHours,
171
+ isProcessAlive = defaultIsProcessAlive,
172
+ runGit = defaultRunGit,
173
+ removeDir = defaultRemoveDir,
174
+ worktreeRegistered = defaultWorktreeRegistered,
175
+ } = {}) {
176
+ if (!runtimeRoot || !cwd) return { reclaimed: [], skipped: [] }
177
+ try {
178
+ const { stale } = selectStaleSnapshots(readGateSnapshotLedger(runtimeRoot), { now, staleHours, isProcessAlive })
179
+ const reclaimed = []
180
+ const skipped = []
181
+ for (const e of stale) {
182
+ const root = e.snapshotRoot
183
+ let removeFailed = false
184
+ // 先解链(坑 git-worktree-remove-pierce,2026-09-24 实证:git worktree remove --force
185
+ // 在 Windows 跟随 junction 删主仓真身内容——残留快照内 node_modules/venv/copy 链接必须
186
+ // 先解链再删。解链失败按删不掉处置:保留条目待下轮,绝不带着链接跑删除)。
187
+ let linksCleared = true
188
+ try {
189
+ for (const link of collectLinkPathsUnder(root)) unlinkLinkPath(link)
190
+ } catch { linksCleared = false }
191
+ if (linksCleared) {
192
+ try { runGit(cwd, ['worktree', 'remove', '--force', root]) } catch { removeFailed = true }
193
+ try { removeDir(root) } catch { /* 目录删不掉→下方双清判定保留条目 */ }
194
+ } else {
195
+ removeFailed = true
196
+ }
197
+ if (removeFailed) { try { runGit(cwd, ['worktree', 'prune']) } catch { /* prune 失败→下方判定保留 */ } }
198
+ let dirGone = false
199
+ let regGone = false
200
+ try { dirGone = !existsSync(root) } catch { dirGone = false }
201
+ try { regGone = !worktreeRegistered(cwd, root) } catch { regGone = false }
202
+ if (dirGone && regGone) {
203
+ unregisterGateSnapshot({ runtimeRoot, snapshotRoot: root })
204
+ reclaimed.push(root)
205
+ } else {
206
+ skipped.push(root)
207
+ }
208
+ }
209
+ return { reclaimed, skipped }
210
+ } catch {
211
+ return { reclaimed: [], skipped: [] }
212
+ }
213
+ }