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,123 @@
1
+ /**
2
+ * green-cache.js — verify 门禁绿结果指纹缓存(R8 对撞修复,2026-09-23)。
3
+ *
4
+ * 问题:verify 收敛循环里同一套 commands.test/lint 被重复真跑——gate verify ×3 +
5
+ * verify --done 收口再跑(2026-09-23 change-events-channel 基线实测 ≈13min 纯重复执行)。
6
+ * 「亲自实测」的防作弊语义只要求结果真实,不要求重复产生:同指纹(HEAD + 代码脏面 +
7
+ * local.yaml)下近期真跑出的绿结果可复用;命中必须明示 cached——结论仍是真的,但
8
+ * 「本次没跑」必须披露,不冒充本次执行(真实性口径:known_failures 失败签名去重
9
+ * (v3.29.3)的成功面对偶)。
10
+ *
11
+ * 指纹口径:代码脏面剔除 .sillyspec/、docs/、*.md(对齐 watcher buildSnapshot
12
+ * dirtyCode / quality-scan 口径)——verify 收敛循环里 verify-result.md 等文档修订
13
+ * 不应击穿缓存,只有代码面变化才 invalid;local.yaml 整文件哈希入指纹(commands
14
+ * 改动即 miss,防换命令吃旧结果)。
15
+ *
16
+ * 逃生阀:env SILLYSPEC_GREEN_CACHE_OFF=1 全关(回退每次真跑,排障口径);
17
+ * SILLYSPEC_GREEN_CACHE_TTL_MIN 覆盖 TTL(默认 30min——覆盖一轮 verify 收敛窗口,
18
+ * 同时防环境漂移(DB/网络态)吃太久前的旧绿)。
19
+ * fail-open:指纹计算/缓存读写任何异常 → miss(回退真跑,零回归)。
20
+ */
21
+ import { execFileSync } from 'node:child_process';
22
+ import { createHash } from 'node:crypto';
23
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
24
+ import { dirname, join } from 'node:path';
25
+
26
+ const DEFAULT_TTL_MIN = 30;
27
+
28
+ function sha1(text) {
29
+ return createHash('sha1').update(text).digest('hex');
30
+ }
31
+
32
+ function safeGit(cwd, args) {
33
+ try {
34
+ return execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 60000, windowsHide: true });
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * porcelain 行过滤为「代码面」:剔 .sillyspec/、docs/ 与 *.md(含 rename 两侧任一命中)。
42
+ * 纯函数(口径单点,watcher dirtyCode / quality-scan 同源判据);排序保序(同输入同输出)。
43
+ */
44
+ export function filterCodePorcelain(porcelain) {
45
+ return String(porcelain || '')
46
+ .split(/\r?\n/)
47
+ .filter((line) => {
48
+ if (!line.trim()) return false;
49
+ const raw = line.slice(3).trim();
50
+ if (!raw) return false;
51
+ // rename 行「old -> new」两侧任一是文档面即整行剔除(防 rename 伪装穿透)
52
+ const parts = raw.split(' -> ').map((p) => p.replace(/^"|"$/g, ''));
53
+ return !parts.every((p) => p.startsWith('.sillyspec/') || p.startsWith('docs/') || /\.md$/i.test(p));
54
+ })
55
+ .sort();
56
+ }
57
+
58
+ /**
59
+ * 计算门禁绿缓存指纹:HEAD + 代码脏面 + local.yaml 内容。
60
+ * git 不可用(非仓目录)返回 null → 调用方按 miss 处理。
61
+ */
62
+ export function computeGateFingerprint({ cwd, specBase }) {
63
+ const head = safeGit(cwd, ['rev-parse', 'HEAD']);
64
+ if (head === null) return null;
65
+ const porcelain = safeGit(cwd, ['status', '--porcelain']) ?? '';
66
+ let localYaml = '';
67
+ try {
68
+ const yamlPath = join(specBase, 'local.yaml');
69
+ if (existsSync(yamlPath)) localYaml = readFileSync(yamlPath, 'utf8');
70
+ } catch { /* 读不到按空——指纹仍含 HEAD+脏面,miss 风险方向安全 */ }
71
+ return sha1([head.trim(), filterCodePorcelain(porcelain).join('\n'), localYaml].join('\u0000'));
72
+ }
73
+
74
+ function greenCacheFile({ runtimeRoot, scope, kind }) {
75
+ // scope 为变更名(assertSafeChangeName 已在上游消毒)或 facet 固定串,不含路径分隔符
76
+ return join(runtimeRoot, 'green-cache', `${scope}.${kind}.json`);
77
+ }
78
+
79
+ function resolveTtlMin(env) {
80
+ const v = Number(env.SILLYSPEC_GREEN_CACHE_TTL_MIN);
81
+ return Number.isFinite(v) && v > 0 ? v : DEFAULT_TTL_MIN;
82
+ }
83
+
84
+ /**
85
+ * 查绿缓存:指纹不符/TTL 过期/阀关/文件坏 → null(一律回退真跑)。
86
+ * 命中返回 { ...rec, ageMs, cached:true }(rec = storeGreenCache 写入的记录)。
87
+ */
88
+ export function lookupGreenCache({ runtimeRoot, scope, kind, fingerprint, now = Date.now(), env = process.env }) {
89
+ if (env.SILLYSPEC_GREEN_CACHE_OFF === '1') return null;
90
+ if (!runtimeRoot || !fingerprint) return null;
91
+ try {
92
+ const file = greenCacheFile({ runtimeRoot, scope, kind });
93
+ if (!existsSync(file)) return null;
94
+ const rec = JSON.parse(readFileSync(file, 'utf8'));
95
+ if (!rec || rec.fingerprint !== fingerprint) return null;
96
+ if (now - rec.storedAt > resolveTtlMin(env) * 60_000) return null;
97
+ return { ...rec, ageMs: now - rec.storedAt, cached: true };
98
+ } catch {
99
+ return null;
100
+ }
101
+ }
102
+
103
+ /**
104
+ * 写绿缓存(best-effort:IO 异常返回 false 不抛——缓存失败不影响门禁本体)。
105
+ */
106
+ export function storeGreenCache({ runtimeRoot, scope, kind, fingerprint, result, now = Date.now(), env = process.env }) {
107
+ if (env.SILLYSPEC_GREEN_CACHE_OFF === '1') return false;
108
+ if (!runtimeRoot || !fingerprint) return false;
109
+ try {
110
+ const file = greenCacheFile({ runtimeRoot, scope, kind });
111
+ mkdirSync(dirname(file), { recursive: true });
112
+ writeFileSync(file, JSON.stringify({ fingerprint, storedAt: now, result }, null, 1));
113
+ return true;
114
+ } catch {
115
+ return false;
116
+ }
117
+ }
118
+
119
+ /** 命中时给调用方的人话披露行(进 gate 结果 warnings,真实性口径)。 */
120
+ export function greenCacheNotice(hit, label = 'verify-test') {
121
+ const ageMin = Math.max(1, Math.round((hit.ageMs || 0) / 60_000));
122
+ return `ℹ️ ${label} 本次未重跑——复用 ${ageMin} 分钟内同指纹真跑绿结果(SILLYSPEC_GREEN_CACHE_OFF=1 可关闭缓存)`;
123
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * junction-rm.js — junction/reparse 点安全删除(坑 git-worktree-remove-pierce,2026-09-24 实证)
3
+ *
4
+ * 根因(R16 批次一收口期三次主仓 node_modules 清空,实证复现):**`git worktree remove --force`
5
+ * 的递归删在 Windows 跟随 junction**,快照内 node_modules/venv/gate_snapshot.copy 链接的目标
6
+ * 是主仓真身——cleanupSnapshot 第一步 git remove 即清空主仓依赖树(js-yaml
7
+ * ERR_MODULE_NOT_FOUND 连环)。worktree.js cleanup 链的 unlinkNodeModulesLinks 先解链正是
8
+ * 防同一穿透(历史注释另点名 MSYS `rm -rf` 人工操作同款;Node v24 rmSync 在受控测试中
9
+ * 未复现穿透,但先解链对两种删除器都是稳健防御)。Windows 上唯一不跟随目标的删除是
10
+ * `cmd /c rmdir`(reparse 点语义)。
11
+ *
12
+ * 契约:safeRemoveDirWithLinks(p) = 递归收集 p 下全部 lstat 符号链接/junction → 逐个
13
+ * rmdir/unlink 解链 → 再 rmSync(p)。collectLinkPathsUnder/unlinkLinkPath 亦供「先解链再
14
+ * 交给 git 删」的调用序使用(cleanupSnapshot / reclaimStaleGateSnapshots)。任一解链失败
15
+ * → 抛错阻断删除(保护链接目标优先于清理彻底性;调用方留账本待下轮回收——双清契约兼容)。
16
+ */
17
+ import { existsSync, readdirSync, lstatSync, rmSync, unlinkSync } from 'node:fs'
18
+ import { join } from 'node:path'
19
+ import { execFileSync } from 'node:child_process'
20
+
21
+ /** 删除单个链接路径(junction/symlink 本体,不跟随目标) */
22
+ export function unlinkLinkPath(p) {
23
+ if (process.platform === 'win32') {
24
+ execFileSync('cmd.exe', ['/c', 'rmdir', p], { windowsHide: true })
25
+ } else {
26
+ unlinkSync(p)
27
+ }
28
+ }
29
+
30
+ /**
31
+ * 递归收集 root 下全部符号链接/junction 绝对路径(lstat 判定;readdir withFileTypes 对
32
+ * junction 报 isDirectory 不可信)。lstat 失败(EPERM/杀毒锁)→ 抛错保护。
33
+ */
34
+ export function collectLinkPathsUnder(root) {
35
+ const links = []
36
+ const walk = (dir) => {
37
+ let entries
38
+ try { entries = readdirSync(dir) } catch { return }
39
+ for (const name of entries) {
40
+ const p = join(dir, name)
41
+ let st
42
+ try { st = lstatSync(p) } catch (e) {
43
+ throw new Error(`junction 检测失败(疑似 EPERM:杀毒/索引锁——阻断删除以保护链接目标):${p}: ${e.message}`)
44
+ }
45
+ if (st.isSymbolicLink()) {
46
+ links.push(p)
47
+ continue
48
+ }
49
+ if (st.isDirectory()) walk(p)
50
+ }
51
+ }
52
+ walk(root)
53
+ return links
54
+ }
55
+
56
+ /**
57
+ * 安全删除目录:先解链其下全部 junction/symlink 再 rmSync。
58
+ * @throws 解链失败时抛错(不回落裸 rmSync——那会穿透链接删目标,见文件首注释)
59
+ */
60
+ export function safeRemoveDirWithLinks(p, opts = {}) {
61
+ if (!existsSync(p)) return
62
+ for (const link of collectLinkPathsUnder(p)) {
63
+ try {
64
+ unlinkLinkPath(link)
65
+ } catch (e) {
66
+ throw new Error(`junction 解链失败(保护链接目标,阻断删除——手动 rmdir "${link}" 后重试): ${e.message}`)
67
+ }
68
+ }
69
+ rmSync(p, { recursive: true, force: true, ...opts })
70
+ }
package/src/run/next.js CHANGED
@@ -47,6 +47,20 @@ function inferChange(changeDir, name, specBase) {
47
47
  const ev = (f) => join(specBase, 'changes', name, f)
48
48
  const t = countTasks(join(changeDir, 'tasks.md'))
49
49
 
50
+ // 轻量变更(thin/flow)优先分支(2026-09-25-quick-channel-retire 评审 P1):flow-state.yaml
51
+ // 在场 = 轻量变更,续跑/收口走 flow 协议——run <stage> 对 thin 变更会被混跑守卫拒
52
+ //(升厚须用户同意带 --upgrade-thick),按 task 勾选给恢复/收口两态
53
+ if (has('flow-state.yaml')) {
54
+ const state = t.total > 0 && t.checked >= t.total
55
+ ? `${name}:轻量变更 task 全勾,待收口`
56
+ : `${name}:轻量变更进行中(task ${t.checked}/${t.total})`
57
+ return {
58
+ state,
59
+ next: `sillyspec flow start --change ${name}(恢复简报;干完跑 flow done 收口)`,
60
+ evidence: [ev('flow-state.yaml'), ev('tasks.md')],
61
+ }
62
+ }
63
+
50
64
  if (!has('proposal.md') && !has('design.md') && !has('tasks.md') && !has('plan.md')) {
51
65
  return { state: `${name}:变更目录为空`, next: 'sillyspec run brainstorm(完善 proposal)或清理该空目录', evidence: [ev('(无产物)')] }
52
66
  }