sillyspec 3.32.9 → 3.33.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 (176) hide show
  1. package/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -114
  2. package/.claude/skills/sillyspec-execute/SKILL.md +222 -222
  3. package/.claude/skills/sillyspec-explore/SKILL.md +107 -107
  4. package/.claude/skills/sillyspec-flow/SKILL.md +1 -1
  5. package/.claude/skills/sillyspec-plan/SKILL.md +156 -156
  6. package/.github/workflows/ci.yml +58 -0
  7. package/.husky/pre-push +7 -6
  8. package/AGENTS.md +1 -1
  9. package/assets/command-cards/flow.md +1 -1
  10. package/eval/eval.config.json +3 -3
  11. package/eval/lib/config.mjs +6 -3
  12. package/eval/lib/util.mjs +16 -3
  13. package/eval/lib/workdir.mjs +4 -1
  14. package/eval/results/eval.db +0 -0
  15. package/eval/results/logs/2026-10-15T12-37-22-986Z-004-phase-stats-armA/prompt.txt +10 -0
  16. package/eval/results/logs/2026-10-15T12-37-22-986Z-004-phase-stats-armA/stderr.txt +0 -0
  17. package/eval/results/logs/2026-10-15T12-37-22-986Z-004-phase-stats-armA/stdout.txt +1 -0
  18. package/eval/results/logs/2026-10-15T12-45-37-124Z-004-phase-stats-armB/prompt.txt +10 -0
  19. package/eval/results/logs/2026-10-15T12-45-37-124Z-004-phase-stats-armB/stderr.txt +0 -0
  20. package/eval/results/logs/2026-10-15T12-45-37-124Z-004-phase-stats-armB/stdout.txt +1 -0
  21. package/eval/results/logs/2026-10-15T12-56-19-259Z-004-phase-stats-armA/prompt.txt +10 -0
  22. package/eval/results/logs/2026-10-15T12-56-19-259Z-004-phase-stats-armA/stderr.txt +0 -0
  23. package/eval/results/logs/2026-10-15T12-56-19-259Z-004-phase-stats-armA/stdout.txt +1 -0
  24. package/eval/results/logs/2026-10-15T13-00-05-625Z-004-phase-stats-armB/prompt.txt +10 -0
  25. package/eval/results/logs/2026-10-15T13-04-51-003Z-004-phase-stats-armA/prompt.txt +10 -0
  26. package/eval/results/logs/2026-10-15T13-04-51-003Z-004-phase-stats-armA/stderr.txt +0 -0
  27. package/eval/results/logs/2026-10-15T13-04-51-003Z-004-phase-stats-armA/stdout.txt +1 -0
  28. package/eval/results/logs/2026-10-15T13-06-25-271Z-004-phase-stats-armB/prompt.txt +10 -0
  29. package/eval/results/logs/2026-10-15T13-06-25-271Z-004-phase-stats-armB/stderr.txt +0 -0
  30. package/eval/results/logs/2026-10-15T13-06-25-271Z-004-phase-stats-armB/stdout.txt +1 -0
  31. package/eval/results/logs/2026-10-15T13-24-38-308Z-004-phase-stats-armB/prompt.txt +10 -0
  32. package/eval/results/logs/2026-10-15T13-24-38-308Z-004-phase-stats-armB/stderr.txt +0 -0
  33. package/eval/results/logs/2026-10-15T13-24-38-308Z-004-phase-stats-armB/stdout.txt +1 -0
  34. package/eval/results/logs/2026-10-15T14-50-01-950Z-005-orders-export-armA/prompt.txt +8 -0
  35. package/eval/results/logs/2026-10-15T14-50-01-950Z-005-orders-export-armA/stderr.txt +0 -0
  36. package/eval/results/logs/2026-10-15T14-50-01-950Z-005-orders-export-armA/stdout.txt +1 -0
  37. package/eval/results/logs/2026-10-15T14-51-56-316Z-005-orders-export-armB/prompt.txt +8 -0
  38. package/eval/results/logs/2026-10-15T14-51-56-316Z-005-orders-export-armB/stderr.txt +0 -0
  39. package/eval/results/logs/2026-10-15T14-51-56-316Z-005-orders-export-armB/stdout.txt +1 -0
  40. package/eval/results/workdirs/001-slugify__armA/package.json +6 -0
  41. package/eval/results/workdirs/001-slugify__armA/src/slugify.mjs +26 -0
  42. package/eval/results/workdirs/001-slugify__armB/package.json +6 -0
  43. package/eval/results/workdirs/001-slugify__armB/src/slugify.mjs +26 -0
  44. package/eval/results/workdirs/002-each-day__armA/package.json +6 -0
  45. package/eval/results/workdirs/002-each-day__armA/src/dates.mjs +30 -0
  46. package/eval/results/workdirs/002-each-day__armB/DRY_RUN_WRONG_ANSWER.mjs +1 -0
  47. package/eval/results/workdirs/002-each-day__armB/package.json +6 -0
  48. package/eval/results/workdirs/002-each-day__armB/src/dates.mjs +4 -0
  49. package/eval/results/workdirs/003-word-report__armA/bin/report.mjs +36 -0
  50. package/eval/results/workdirs/003-word-report__armA/data/notes.txt +5 -0
  51. package/eval/results/workdirs/003-word-report__armA/package.json +6 -0
  52. package/eval/results/workdirs/003-word-report__armB/bin/report.mjs +36 -0
  53. package/eval/results/workdirs/003-word-report__armB/data/notes.txt +5 -0
  54. package/eval/results/workdirs/003-word-report__armB/package.json +6 -0
  55. package/eval/results/workdirs/004-phase-stats__armA/data/events.log +16 -0
  56. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/flow.md +44 -0
  57. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-archive.md +27 -0
  58. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-brainstorm.md +34 -0
  59. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-execute.md +30 -0
  60. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-plan.md +28 -0
  61. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-verify.md +30 -0
  62. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/status.md +28 -0
  63. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-archive/SKILL.md +87 -0
  64. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-auto/SKILL.md +54 -0
  65. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
  66. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-commit/SKILL.md +67 -0
  67. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-continue/SKILL.md +17 -0
  68. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
  69. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-execute/SKILL.md +222 -0
  70. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-explore/SKILL.md +107 -0
  71. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-export/SKILL.md +57 -0
  72. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-flow/SKILL.md +62 -0
  73. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-init/SKILL.md +177 -0
  74. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-knowledge/SKILL.md +40 -0
  75. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-plan/SKILL.md +156 -0
  76. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-propose/SKILL.md +16 -0
  77. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-quick/SKILL.md +116 -0
  78. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-resume/SKILL.md +21 -0
  79. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-scan/SKILL.md +79 -0
  80. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-state/SKILL.md +25 -0
  81. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-status/SKILL.md +54 -0
  82. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-verify/SKILL.md +87 -0
  83. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-workspace/SKILL.md +33 -0
  84. package/eval/results/workdirs/004-phase-stats__armB/AGENTS.md +45 -0
  85. package/eval/results/workdirs/004-phase-stats__armB/CLAUDE.md +3 -0
  86. package/eval/results/workdirs/004-phase-stats__armB/data/events.log +16 -0
  87. package/eval/results/workdirs/run-001-slugify__armA/package.json +6 -0
  88. package/eval/results/workdirs/run-001-slugify__armA/src/slugify.mjs +26 -0
  89. package/eval/results/workdirs/run-001-slugify__armB/package.json +6 -0
  90. package/eval/results/workdirs/run-001-slugify__armB/src/slugify.mjs +26 -0
  91. package/eval/results/workdirs/run-002-each-day__armA/package.json +6 -0
  92. package/eval/results/workdirs/run-002-each-day__armA/src/dates.mjs +30 -0
  93. package/eval/results/workdirs/run-002-each-day__armB/DRY_RUN_WRONG_ANSWER.mjs +1 -0
  94. package/eval/results/workdirs/run-002-each-day__armB/package.json +6 -0
  95. package/eval/results/workdirs/run-002-each-day__armB/src/dates.mjs +4 -0
  96. package/eval/results/workdirs/run-003-word-report__armA/bin/report.mjs +36 -0
  97. package/eval/results/workdirs/run-003-word-report__armA/data/notes.txt +5 -0
  98. package/eval/results/workdirs/run-003-word-report__armA/package.json +6 -0
  99. package/eval/results/workdirs/run-003-word-report__armB/bin/report.mjs +36 -0
  100. package/eval/results/workdirs/run-003-word-report__armB/data/notes.txt +5 -0
  101. package/eval/results/workdirs/run-003-word-report__armB/package.json +6 -0
  102. package/eval/results/workdirs/run-004-phase-stats__armA/bin/phase-stats.mjs +37 -0
  103. package/eval/results/workdirs/run-004-phase-stats__armA/data/events.log +16 -0
  104. package/eval/results/workdirs/run-004-phase-stats__armB/DRY_RUN_WRONG_ANSWER.mjs +1 -0
  105. package/eval/results/workdirs/run-004-phase-stats__armB/data/events.log +16 -0
  106. package/eval/results/workdirs/run-005-orders-export__armA/bin/export-orders.mjs +57 -0
  107. package/eval/results/workdirs/run-005-orders-export__armA/data/.gitattributes +1 -0
  108. package/eval/results/workdirs/run-005-orders-export__armA/data/orders.csv +9 -0
  109. package/eval/results/workdirs/run-005-orders-export__armA/out/summary.json +1 -0
  110. package/eval/results/workdirs/run-005-orders-export__armB/bin/export-orders.mjs +57 -0
  111. package/eval/results/workdirs/run-005-orders-export__armB/data/.gitattributes +1 -0
  112. package/eval/results/workdirs/run-005-orders-export__armB/data/orders.csv +9 -0
  113. package/eval/results/workdirs/run-005-orders-export__armB/out/summary.json +1 -0
  114. package/eval/tasks/004-phase-stats/files/data/events.log +16 -0
  115. package/eval/tasks/004-phase-stats/solution/bin/phase-stats.mjs +37 -0
  116. package/eval/tasks/004-phase-stats/task.json +6 -0
  117. package/eval/tasks/004-phase-stats/verify.mjs +38 -0
  118. package/eval/tasks/005-orders-export/files/data/.gitattributes +1 -0
  119. package/eval/tasks/005-orders-export/files/data/orders.csv +9 -0
  120. package/eval/tasks/005-orders-export/solution/bin/export-orders.mjs +57 -0
  121. package/eval/tasks/005-orders-export/task.json +6 -0
  122. package/eval/tasks/005-orders-export/verify.mjs +28 -0
  123. package/package.json +1 -1
  124. package/quicklog/tmp-fix-review.mjs +21 -0
  125. package/src/ceremony-tier.js +2 -1
  126. package/src/commit-suggest.js +45 -4
  127. package/src/config-schema.js +14 -0
  128. package/src/decision-distill.js +106 -24
  129. package/src/decision-redomain.js +148 -0
  130. package/src/decision-renumber.js +157 -0
  131. package/src/design-facts.js +3 -3
  132. package/src/docs-check.js +30 -15
  133. package/src/docs-debt.js +6 -5
  134. package/src/doctor-diagnostics.js +3 -1
  135. package/src/flow-parity.js +28 -4
  136. package/src/flow.js +76 -13
  137. package/src/forensics.js +236 -0
  138. package/src/fr-index.js +17 -2
  139. package/src/hooks/worktree-guard.js +3 -3
  140. package/src/index.js +41 -6
  141. package/src/knowledge-digest.js +23 -6
  142. package/src/knowledge-graph.js +95 -18
  143. package/src/knowledge-match.js +34 -9
  144. package/src/knowledge-stats.js +12 -13
  145. package/src/knowledge-vector.js +98 -7
  146. package/src/machine-interface.js +26 -0
  147. package/src/module-impact.js +2 -5
  148. package/src/modules.js +31 -0
  149. package/src/quicklog.js +6 -1
  150. package/src/run/archive-distill.js +3 -0
  151. package/src/run/command.js +3 -3
  152. package/src/run/complete-handlers.js +8 -3
  153. package/src/run/complete.js +113 -44
  154. package/src/run/gates.js +100 -103
  155. package/src/run/lint-ownership.js +37 -0
  156. package/src/run/prompt.js +43 -28
  157. package/src/run/stage.js +1 -1
  158. package/src/run/verify-quality-scan.js +10 -1
  159. package/src/sentinel-assertions.js +43 -1
  160. package/src/spec-sync.js +81 -2
  161. package/src/stage-contract.js +26 -3
  162. package/src/stage-review.js +46 -12
  163. package/src/stage-templates.js +2 -0
  164. package/src/stages/brainstorm.js +76 -21
  165. package/src/stages/knowledge.js +56 -5
  166. package/src/stages/plan-postcheck.js +13 -2
  167. package/src/stages/plan.js +19 -0
  168. package/src/sync.js +28 -2
  169. package/src/task-tick.js +12 -0
  170. package/src/test-bindings.js +32 -3
  171. package/src/timeline.js +60 -19
  172. package/src/ui-visual.js +119 -4
  173. package/src/verify-postcheck.js +166 -18
  174. package/src/verify-probes.js +61 -18
  175. package/src/worktree-apply.js +26 -2
  176. package/templates/agents-instruction.md +1 -1
@@ -0,0 +1,16 @@
1
+ 2026-10-15T09:00:00Z START s1 login
2
+ 2026-10-15T09:00:03Z START s2 upload
3
+ 2026-10-15T09:00:04Z END s1 login
4
+ 2026-10-15T09:00:05Z START s3 download
5
+ 2026-10-15T09:00:20Z END s2 upload
6
+ 2026-10-15T09:00:15Z END s3 download
7
+
8
+ 2026-10-15T09:05:00Z START s4 login
9
+ 2026-10-15T09:05:09Z END s4 login
10
+ 2026-10-15T09:06:00Z START s5 upload
11
+ 2026-10-15T09:07:00Z END s6 download
12
+ 2026-10-15T09:08:00Z START s7 login
13
+ 2026-10-15T09:08:03Z END s7 login
14
+ 2026-10-15T09:08:30Z START s7 login
15
+ 2026-10-15T09:08:36Z END s7 login
16
+
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ // 参考解(eval dry-run 参考与判别力测试用;不进 agent 工作副本、不进任务指纹)。
3
+ // 覆盖全部隐含面:剥 UTF-8 BOM;容忍 CRLF 行尾;跳过空行;CSV 引号语义(引号内逗号非
4
+ // 分隔符,引号内金额剥千分位逗号);输出前自建 out/ 目录。
5
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
6
+ import { dirname, join } from 'node:path';
7
+
8
+ /** 引号感知的单行 CSV 切分(覆盖本任务数据形态:引号包裹含逗号字段)。 */
9
+ function splitCsvLine(line) {
10
+ const fields = [];
11
+ let cur = '';
12
+ let inQuotes = false;
13
+ for (let i = 0; i < line.length; i++) {
14
+ const ch = line[i];
15
+ if (inQuotes) {
16
+ if (ch === '"') {
17
+ if (line[i + 1] === '"') { cur += '"'; i++; } else { inQuotes = false; }
18
+ } else { cur += ch; }
19
+ } else if (ch === '"') {
20
+ inQuotes = true;
21
+ } else if (ch === ',') {
22
+ fields.push(cur); cur = '';
23
+ } else { cur += ch; }
24
+ }
25
+ fields.push(cur);
26
+ return fields;
27
+ }
28
+
29
+ const raw = readFileSync('data/orders.csv', 'utf8').replace(/^\uFEFF/, ''); // 剥 BOM
30
+ const lines = raw.split(/\r?\n/).map((l) => l.trim()).filter((l) => l !== ''); // CRLF 容忍 + 空行跳过
31
+ const header = splitCsvLine(lines[0]);
32
+ const col = (name) => header.indexOf(name);
33
+ const iRegion = col('region');
34
+ const iAmount = col('amount');
35
+ const iStatus = col('status');
36
+
37
+ const agg = new Map(); // region -> { total, count }
38
+ for (const line of lines.slice(1)) {
39
+ const f = splitCsvLine(line);
40
+ const status = (f[iStatus] ?? '').trim();
41
+ if (status !== 'paid') continue;
42
+ const region = (f[iRegion] ?? '').trim();
43
+ const amount = Number.parseFloat((f[iAmount] ?? '').replace(/,/g, '')); // 引号内千分位剥除
44
+ if (!Number.isFinite(amount)) continue;
45
+ const a = agg.get(region) ?? { total: 0, count: 0 };
46
+ a.total += amount;
47
+ a.count += 1;
48
+ agg.set(region, a);
49
+ }
50
+
51
+ const regions = [...agg.entries()]
52
+ .map(([region, a]) => ({ region, total: Math.round(a.total * 100) / 100, count: a.count }))
53
+ .sort((x, y) => (x.region < y.region ? -1 : x.region > y.region ? 1 : 0));
54
+
55
+ const outPath = join('out', 'summary.json');
56
+ mkdirSync(dirname(outPath), { recursive: true }); // 隐含面:目录不存在须自建
57
+ writeFileSync(outPath, JSON.stringify({ regions }) + '\n');
@@ -0,0 +1,9 @@
1
+ id,region,amount,status
2
+ 1,EU,"1,234.50",paid
3
+ 2,APAC,200.00,paid
4
+ 3,EU,99.99,pending
5
+ 4,APAC,"2,000.00",paid
6
+
7
+ 5,EU,50.00,paid
8
+ 6,APAC,75.25,refunded
9
+ 7,EU,"1,000.00",paid
@@ -0,0 +1 @@
1
+ {"regions":[{"region":"APAC","total":2200,"count":2},{"region":"EU","total":2284.5,"count":3}]}
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ // 参考解(eval dry-run 参考与判别力测试用;不进 agent 工作副本、不进任务指纹)。
3
+ // 覆盖全部隐含面:剥 UTF-8 BOM;容忍 CRLF 行尾;跳过空行;CSV 引号语义(引号内逗号非
4
+ // 分隔符,引号内金额剥千分位逗号);输出前自建 out/ 目录。
5
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
6
+ import { dirname, join } from 'node:path';
7
+
8
+ /** 引号感知的单行 CSV 切分(覆盖本任务数据形态:引号包裹含逗号字段)。 */
9
+ function splitCsvLine(line) {
10
+ const fields = [];
11
+ let cur = '';
12
+ let inQuotes = false;
13
+ for (let i = 0; i < line.length; i++) {
14
+ const ch = line[i];
15
+ if (inQuotes) {
16
+ if (ch === '"') {
17
+ if (line[i + 1] === '"') { cur += '"'; i++; } else { inQuotes = false; }
18
+ } else { cur += ch; }
19
+ } else if (ch === '"') {
20
+ inQuotes = true;
21
+ } else if (ch === ',') {
22
+ fields.push(cur); cur = '';
23
+ } else { cur += ch; }
24
+ }
25
+ fields.push(cur);
26
+ return fields;
27
+ }
28
+
29
+ const raw = readFileSync('data/orders.csv', 'utf8').replace(/^\uFEFF/, ''); // 剥 BOM
30
+ const lines = raw.split(/\r?\n/).map((l) => l.trim()).filter((l) => l !== ''); // CRLF 容忍 + 空行跳过
31
+ const header = splitCsvLine(lines[0]);
32
+ const col = (name) => header.indexOf(name);
33
+ const iRegion = col('region');
34
+ const iAmount = col('amount');
35
+ const iStatus = col('status');
36
+
37
+ const agg = new Map(); // region -> { total, count }
38
+ for (const line of lines.slice(1)) {
39
+ const f = splitCsvLine(line);
40
+ const status = (f[iStatus] ?? '').trim();
41
+ if (status !== 'paid') continue;
42
+ const region = (f[iRegion] ?? '').trim();
43
+ const amount = Number.parseFloat((f[iAmount] ?? '').replace(/,/g, '')); // 引号内千分位剥除
44
+ if (!Number.isFinite(amount)) continue;
45
+ const a = agg.get(region) ?? { total: 0, count: 0 };
46
+ a.total += amount;
47
+ a.count += 1;
48
+ agg.set(region, a);
49
+ }
50
+
51
+ const regions = [...agg.entries()]
52
+ .map(([region, a]) => ({ region, total: Math.round(a.total * 100) / 100, count: a.count }))
53
+ .sort((x, y) => (x.region < y.region ? -1 : x.region > y.region ? 1 : 0));
54
+
55
+ const outPath = join('out', 'summary.json');
56
+ mkdirSync(dirname(outPath), { recursive: true }); // 隐含面:目录不存在须自建
57
+ writeFileSync(outPath, JSON.stringify({ regions }) + '\n');
@@ -0,0 +1,9 @@
1
+ id,region,amount,status
2
+ 1,EU,"1,234.50",paid
3
+ 2,APAC,200.00,paid
4
+ 3,EU,99.99,pending
5
+ 4,APAC,"2,000.00",paid
6
+
7
+ 5,EU,50.00,paid
8
+ 6,APAC,75.25,refunded
9
+ 7,EU,"1,000.00",paid
@@ -0,0 +1 @@
1
+ {"regions":[{"region":"APAC","total":2200,"count":2},{"region":"EU","total":2284.5,"count":3}]}
@@ -0,0 +1,16 @@
1
+ 2026-10-15T09:00:00Z START s1 login
2
+ 2026-10-15T09:00:03Z START s2 upload
3
+ 2026-10-15T09:00:04Z END s1 login
4
+ 2026-10-15T09:00:05Z START s3 download
5
+ 2026-10-15T09:00:20Z END s2 upload
6
+ 2026-10-15T09:00:15Z END s3 download
7
+
8
+ 2026-10-15T09:05:00Z START s4 login
9
+ 2026-10-15T09:05:09Z END s4 login
10
+ 2026-10-15T09:06:00Z START s5 upload
11
+ 2026-10-15T09:07:00Z END s6 download
12
+ 2026-10-15T09:08:00Z START s7 login
13
+ 2026-10-15T09:08:03Z END s7 login
14
+ 2026-10-15T09:08:30Z START s7 login
15
+ 2026-10-15T09:08:36Z END s7 login
16
+
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ // 参考解(eval dry-run 参考与判别力测试用;不进 agent 工作副本、不进任务指纹)。
3
+ // 覆盖全部隐含面:乱序交错行按 (sessionId, phase) 配对;空行/纯空白行跳过;行字段 trim
4
+ // (兼容 CRLF);未闭合 START 与孤儿 END 零贡献;同 (sessionId, phase) 多轮累加。
5
+ import { readFileSync } from 'node:fs';
6
+
7
+ const raw = readFileSync('data/events.log', 'utf8');
8
+ const open = new Map(); // "session|phase" -> start ms
9
+ const agg = new Map(); // phase -> { rounds, totalSeconds }
10
+
11
+ for (const line of raw.split(/\r?\n/)) {
12
+ const t = line.trim();
13
+ if (!t) continue; // 空行 / 纯空白行
14
+ const parts = t.split(/\s+/);
15
+ if (parts.length < 4) continue;
16
+ const [ts, kind, session, phase] = parts;
17
+ const ms = Date.parse(ts);
18
+ if (!Number.isFinite(ms)) continue;
19
+ const key = `${session}|${phase}`;
20
+ if (kind === 'START') {
21
+ if (!open.has(key)) open.set(key, ms); // 重复 START 以首者计,防覆盖丢轮
22
+ } else if (kind === 'END') {
23
+ const start = open.get(key);
24
+ if (start === undefined) continue; // 孤儿 END 零贡献
25
+ open.delete(key);
26
+ const a = agg.get(phase) ?? { rounds: 0, totalSeconds: 0 };
27
+ a.rounds += 1;
28
+ a.totalSeconds += Math.round((ms - start) / 1000);
29
+ agg.set(phase, a);
30
+ }
31
+ // 未闭合 START:循环结束自然零贡献
32
+ }
33
+
34
+ const phases = [...agg.entries()]
35
+ .map(([phase, a]) => ({ phase, rounds: a.rounds, totalSeconds: a.totalSeconds }))
36
+ .sort((x, y) => (x.phase < y.phase ? -1 : x.phase > y.phase ? 1 : 0));
37
+ process.stdout.write(JSON.stringify({ phases }) + '\n');
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "004-phase-stats",
3
+ "title": "日志会话耗时统计 CLI(种植缺陷探针:隐含面在 fixture,不在指令)",
4
+ "tags": ["greenfield", "cli", "planted-defect", "implied-inputs"],
5
+ "instruction": "在当前目录新建 bin/phase-stats.mjs(Node ≥ 18,只用 Node 内置模块,禁止安装任何第三方依赖)。\n\n读取 data/events.log:每行一条事件,格式为「<ISO-8601 UTC 时间戳> <START|END> <sessionId> <phase>」(单个空格分隔,UTF-8)。同一 sessionId 的同名 phase 从 START 到 END 为一轮。\n\n输出一行 JSON 到 stdout(除此之外无任何输出):\n{\"phases\":[{\"phase\":\"<名>\",\"rounds\":<完成轮数>,\"totalSeconds\":<所有完成轮次时长之和,整数秒>}]}\n\nphases 按 phase 字典序升序排列。数据以仓库附带的 data/events.log 为准。确保 node bin/phase-stats.mjs 可运行。"
6
+ }
@@ -0,0 +1,38 @@
1
+ // 判分脚本:cwd=被测工作目录,退出码 0 = 通过。
2
+ // 期望值基于 data/events.log 固定内容硬编码(download r1/10s、login r4/22s、upload r1/17s),
3
+ // 保证判分独立于被测实现。种植缺陷探针(2026-10-15-eval-planted-defect-probe):
4
+ // 指令只命名解析格式与输出契约;五类隐含面(乱序交错/空行/纯空白行/未闭合 START/孤儿 END)
5
+ // 全部由 fixture 承载——失败时下方断言消息逐项定位是哪条隐含面挂了。
6
+ import assert from 'node:assert/strict';
7
+ import { spawnSync } from 'node:child_process';
8
+ import { join } from 'node:path';
9
+ import { cwd } from 'node:process';
10
+
11
+ const run = spawnSync('node', [join('bin', 'phase-stats.mjs')], { encoding: 'utf8' });
12
+ assert.equal(run.status, 0, `CLI 应成功运行(exit=${run.status}): ${String(run.stderr).slice(0, 300)}`);
13
+
14
+ const out = String(run.stdout).trim();
15
+ assert.equal(out.split('\n').length, 1, 'stdout 只允许一行 JSON');
16
+ let parsed;
17
+ assert.doesNotThrow(() => { parsed = JSON.parse(out); }, 'stdout 应为合法 JSON');
18
+ assert.ok(Array.isArray(parsed.phases), 'phases 应为数组');
19
+ assert.deepEqual(
20
+ parsed.phases.map((p) => p.phase),
21
+ ['download', 'login', 'upload'],
22
+ 'phases 应按字典序升序且恰好三个域(隐含面:乱序/空行/空白行的容错解析)'
23
+ );
24
+ assert.deepEqual(
25
+ parsed.phases[0],
26
+ { phase: 'download', rounds: 1, totalSeconds: 10 },
27
+ 'download: s3 一轮 10s(隐含面:孤儿 END s6 零贡献,不得计轮不得崩)'
28
+ );
29
+ assert.deepEqual(
30
+ parsed.phases[1],
31
+ { phase: 'login', rounds: 4, totalSeconds: 22 },
32
+ 'login: s1(4s)+s4(9s)+s7 两轮(3s+6s)=22s(隐含面:跨 session 交错乱序按 (sessionId,phase) 配对、同 session 多轮累加)'
33
+ );
34
+ assert.deepEqual(
35
+ parsed.phases[2],
36
+ { phase: 'upload', rounds: 1, totalSeconds: 17 },
37
+ 'upload: s2 一轮 17s(隐含面:未闭合 START s5 零贡献,不得计入轮数或时长)'
38
+ );
@@ -0,0 +1,9 @@
1
+ id,region,amount,status
2
+ 1,EU,"1,234.50",paid
3
+ 2,APAC,200.00,paid
4
+ 3,EU,99.99,pending
5
+ 4,APAC,"2,000.00",paid
6
+
7
+ 5,EU,50.00,paid
8
+ 6,APAC,75.25,refunded
9
+ 7,EU,"1,000.00",paid
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ // 参考解(eval dry-run 参考与判别力测试用;不进 agent 工作副本、不进任务指纹)。
3
+ // 覆盖全部隐含面:剥 UTF-8 BOM;容忍 CRLF 行尾;跳过空行;CSV 引号语义(引号内逗号非
4
+ // 分隔符,引号内金额剥千分位逗号);输出前自建 out/ 目录。
5
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
6
+ import { dirname, join } from 'node:path';
7
+
8
+ /** 引号感知的单行 CSV 切分(覆盖本任务数据形态:引号包裹含逗号字段)。 */
9
+ function splitCsvLine(line) {
10
+ const fields = [];
11
+ let cur = '';
12
+ let inQuotes = false;
13
+ for (let i = 0; i < line.length; i++) {
14
+ const ch = line[i];
15
+ if (inQuotes) {
16
+ if (ch === '"') {
17
+ if (line[i + 1] === '"') { cur += '"'; i++; } else { inQuotes = false; }
18
+ } else { cur += ch; }
19
+ } else if (ch === '"') {
20
+ inQuotes = true;
21
+ } else if (ch === ',') {
22
+ fields.push(cur); cur = '';
23
+ } else { cur += ch; }
24
+ }
25
+ fields.push(cur);
26
+ return fields;
27
+ }
28
+
29
+ const raw = readFileSync('data/orders.csv', 'utf8').replace(/^\uFEFF/, ''); // 剥 BOM
30
+ const lines = raw.split(/\r?\n/).map((l) => l.trim()).filter((l) => l !== ''); // CRLF 容忍 + 空行跳过
31
+ const header = splitCsvLine(lines[0]);
32
+ const col = (name) => header.indexOf(name);
33
+ const iRegion = col('region');
34
+ const iAmount = col('amount');
35
+ const iStatus = col('status');
36
+
37
+ const agg = new Map(); // region -> { total, count }
38
+ for (const line of lines.slice(1)) {
39
+ const f = splitCsvLine(line);
40
+ const status = (f[iStatus] ?? '').trim();
41
+ if (status !== 'paid') continue;
42
+ const region = (f[iRegion] ?? '').trim();
43
+ const amount = Number.parseFloat((f[iAmount] ?? '').replace(/,/g, '')); // 引号内千分位剥除
44
+ if (!Number.isFinite(amount)) continue;
45
+ const a = agg.get(region) ?? { total: 0, count: 0 };
46
+ a.total += amount;
47
+ a.count += 1;
48
+ agg.set(region, a);
49
+ }
50
+
51
+ const regions = [...agg.entries()]
52
+ .map(([region, a]) => ({ region, total: Math.round(a.total * 100) / 100, count: a.count }))
53
+ .sort((x, y) => (x.region < y.region ? -1 : x.region > y.region ? 1 : 0));
54
+
55
+ const outPath = join('out', 'summary.json');
56
+ mkdirSync(dirname(outPath), { recursive: true }); // 隐含面:目录不存在须自建
57
+ writeFileSync(outPath, JSON.stringify({ regions }) + '\n');
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "005-orders-export",
3
+ "title": "订单汇总导出 CLI(种植缺陷探针 II:数据陷阱族在 fixture,指令零提示)",
4
+ "tags": ["greenfield", "cli", "planted-defect", "implied-inputs", "data-traps"],
5
+ "instruction": "在当前目录新建 bin/export-orders.mjs(Node ≥ 18,只用 Node 内置模块,禁止安装任何第三方依赖)。\n\n读取 data/orders.csv(UTF-8,逗号分隔,首行为表头:id,region,amount,status)。按 region 汇总 status=paid 的订单金额总和,输出到 out/summary.json:\n{\"regions\":[{\"region\":\"<名>\",\"total\":<金额总和,数值>,\"count\":<订单数,整数>}]}\n\nregions 按 region 字母序升序。数据以仓库附带的 data/orders.csv 为准。确保 node bin/export-orders.mjs 可运行且产物符合契约。"
6
+ }
@@ -0,0 +1,28 @@
1
+ // 判分脚本:cwd=被测工作目录,退出码 0 = 通过。
2
+ // 期望值基于 data/orders.csv 固定内容硬编码(APAC total=2200 count=2;EU total=2284.5 count=3),
3
+ // 判分独立于被测实现。种植缺陷探针 II(2026-10-15-eval-probe2-orders-export):
4
+ // 指令只命名表头/过滤/输出契约;五类隐含面(out/ 目录自建、UTF-8 BOM、CRLF 行尾、
5
+ // 引号内千分位逗号、混排空行)全部由 fixture 承载——失败时下方断言逐项定位。
6
+ import assert from 'node:assert/strict';
7
+ import { existsSync, readFileSync } from 'node:fs';
8
+ import { spawnSync } from 'node:child_process';
9
+ import { join } from 'node:path';
10
+ import { cwd } from 'node:process';
11
+
12
+ const run = spawnSync('node', [join('bin', 'export-orders.mjs')], { encoding: 'utf8' });
13
+ assert.equal(run.status, 0, `CLI 应成功运行(exit=${run.status}): ${String(run.stderr).slice(0, 300)}`);
14
+
15
+ const summaryPath = join(cwd(), 'out', 'summary.json');
16
+ assert.ok(existsSync(summaryPath), 'out/summary.json 应存在(隐含面:out/ 目录不存在须自建)');
17
+
18
+ let parsed;
19
+ assert.doesNotThrow(() => { parsed = JSON.parse(readFileSync(summaryPath, 'utf8')); }, 'out/summary.json 应为合法 JSON');
20
+ assert.ok(Array.isArray(parsed.regions), 'regions 应为数组');
21
+ assert.deepEqual(
22
+ parsed.regions,
23
+ [
24
+ { region: 'APAC', total: 200 + 2000, count: 2 },
25
+ { region: 'EU', total: 1234.5 + 50 + 1000, count: 3 },
26
+ ],
27
+ 'regions 应按字母序且金额/计数正确(隐含面:BOM 剥除、CRLF 容忍、引号内逗号按 CSV 语义、空行跳过、pending/refunded 排除)'
28
+ );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sillyspec",
3
- "version": "3.32.9",
3
+ "version": "3.33.0",
4
4
  "description": "SillySpec CLI — 流程状态机,让 AI 严格按步骤来",
5
5
  "icon": "logo.jpg",
6
6
  "homepage": "https://sillyspec.ppdmq.top/",
@@ -0,0 +1,21 @@
1
+ import { readFileSync, writeFileSync } from 'node:fs'
2
+ import { unlinkSync } from 'node:fs'
3
+
4
+ const p = '.sillyspec/changes/2026-10-15-eval-workdir-naming/review.json'
5
+ const BS = String.fromCharCode(92)
6
+ let raw = readFileSync(p, 'utf8')
7
+ const bad = '[^' + BS + 'd]'
8
+ const good = '[^' + BS + BS + 'd]'
9
+ if (raw.includes(bad)) {
10
+ raw = raw.replace(bad, good)
11
+ writeFileSync(p, raw)
12
+ console.log('已修复 \\d → \\\\d')
13
+ } else {
14
+ console.log('未找到待修复序列')
15
+ }
16
+ JSON.parse(readFileSync(p, 'utf8'))
17
+ console.log('JSON.parse 通过')
18
+ try {
19
+ unlinkSync('quicklog/tmp-scan-review.mjs')
20
+ console.log('临时脚本已删')
21
+ } catch {}
@@ -37,6 +37,7 @@
37
37
  * (R-02:上线前用 friction-ledger 历史变更回放标定再定值)。
38
38
  */
39
39
  import { RISK_TO_TIER } from './change-risk-profile.js'
40
+ import { modulePathMatches } from './modules.js'
40
41
  import { matchSpanRiskPatterns } from './span-risk-surface.js'
41
42
 
42
43
  /** 仪式档位序(只升不降的偏序基准,index 即高低:S0=0 … S3=3) */
@@ -124,7 +125,7 @@ function matchModuleForFile(posix, modulesObj) {
124
125
  for (const raw of prefixes) {
125
126
  const p = String(raw).replace(/\\/g, '/').replace(/\/+$/, '')
126
127
  if (!p) continue
127
- if (posix === p || posix.startsWith(p + '/')) return id
128
+ if (modulePathMatches(posix, p)) return id
128
129
  }
129
130
  }
130
131
  return null
@@ -17,7 +17,7 @@
17
17
  * C. 阶段产出:changed paths 按路径模式归 scan/brainstorm/plan/execute/archive/knowledge
18
18
  */
19
19
  import { existsSync, readdirSync, readFileSync, statSync } from 'fs'
20
- import { join } from 'path'
20
+ import { join, relative } from 'path'
21
21
  import { DatabaseSync } from 'node:sqlite'
22
22
  import { gitQuiet, safeGit, stageArchiveArtifacts } from './git-helper.js'
23
23
  import { resolveSpecDir } from './run/shared.js'
@@ -117,6 +117,12 @@ export function collectCommitContext({ cwd, specDir = null }) {
117
117
  const stat = gitQuiet(cwd, ['diff', 'HEAD', '--stat']) || (changedPaths.length > 0 ? `(${changedPaths.length} 个文件待首提交)` : '')
118
118
 
119
119
  // ── 来源 A:QUICKLOG(主 quicklog + 各活跃变更归属 quicklog)──
120
+ // 并行会话噪声锚定(坑 probe-noise-and-commit-suggest-quality 坑B,2026-10-15 消费仓实证:
121
+ // 归档提交时他并行 quick 会话的 QUICKLOG 新条目(时间窗过滤粗筛通过)把 feat 建议劫持成
122
+ // 「fix: quick 修复 + 阶段产出」——type 误判 + 凭空 quick 字样。quick 来源现加锚「quicklog
123
+ // 文件属于本次提交变更集」(porcelain 含 staged/unstaged/untracked):不在变更集的 quicklog
124
+ // 条目不参与建议(时间窗保留作首道粗筛,零条目时无建议行为不变)。
125
+ const changedSet = new Set(changedPaths.map(p => String(p).replace(/\\/g, '/')))
120
126
  const quickFiles = []
121
127
  try {
122
128
  for (const f of readdirSync(join(specBase, 'quicklog'))) {
@@ -133,6 +139,7 @@ export function collectCommitContext({ cwd, specDir = null }) {
133
139
  const quickEntries = quickFiles
134
140
  .flatMap(file => parseQuicklogEntries(file).map(e => ({ ...e, file })))
135
141
  .filter(e => !lastCommitKey || e.time > lastCommitKey)
142
+ .filter(e => changedSet.has(relative(cwd, e.file).replace(/\\/g, '/')))
136
143
  .sort((a, b) => (a.time < b.time ? -1 : 1))
137
144
  const doneQuick = quickEntries.filter(e => e.status === '已完成')
138
145
 
@@ -155,6 +162,27 @@ export function collectCommitContext({ cwd, specDir = null }) {
155
162
  }
156
163
  }
157
164
 
165
+ // ── 来源 B2:归档产物任务面(坑 probe-noise-and-commit-suggest-quality 坑B 连带)──
166
+ // apply 后 tasks.md 移到 changes/archive/<变更>/——来源 B 的 mtime 锚对已提交面恒失效
167
+ // (任务勾选随 execute 提交走,mtime 早于上次 commit → hasTasks 恒 false → 归档提交被判
168
+ // fix)。归档目录下 tasks.md 在本次变更集时解析其 [x] 行进 task 面(归档提交的 feat 语义正源)。
169
+ try {
170
+ const archDir = join(specBase, 'changes', 'archive')
171
+ if (existsSync(archDir)) {
172
+ for (const c of readdirSync(archDir)) {
173
+ const tp = join(archDir, c, 'tasks.md')
174
+ try {
175
+ if (!statSync(tp).isFile()) continue
176
+ } catch { continue }
177
+ if (!changedSet.has(relative(cwd, tp).replace(/\\/g, '/'))) continue
178
+ for (const line of readFileSync(tp, 'utf8').split('\n')) {
179
+ const m = line.match(/^[-*]\s*\[[xX]\]\s*(task-\d+)\s*[::]?\s*(.*)/)
180
+ if (m) checkedTasks.push({ change: c, item: `${m[1]}: ${m[2].trim()}` })
181
+ }
182
+ }
183
+ }
184
+ } catch { /* 归档扫描失败静默(来源面缺省,行为回退修复前) */ }
185
+
158
186
  // ── 来源 C:阶段产出(changed paths 按模式归类去重)──
159
187
  const stageSet = new Set()
160
188
  for (const p of changedPaths) {
@@ -195,11 +223,24 @@ export function collectCommitContext({ cwd, specDir = null }) {
195
223
  type = 'feat'
196
224
  subject = `feat: 变更交付 + quick 修复 ${doneQuick.length} 项`
197
225
  } else {
198
- type = 'fix'
199
- subject = `fix: quick 修复 + 阶段产出`
226
+ // 纯阶段产出(坑 probe-noise-and-commit-suggest-quality 坑B,2026-10-15 实证:原
227
+ // 「fix: quick 修复 + 阶段产出」在无任何 quick 证据时凭空捏造 quick 语义且误判 fix——
228
+ // 无 task 无 quick 的归档/知识面提交按 chore 归档语义建议)
229
+ type = 'chore'
230
+ subject = `chore: 阶段产出归档(${stageArtifacts.join('、')})`
200
231
  }
201
232
  if (doneQuick.length >= 2 || hasTasks || hasStages) {
202
- for (const q of doneQuick) bodyLines.push(`- quick ${q.qlId}: ${q.title}`)
233
+ // body 与 subject 去重(坑同上:quick ≤2 条时标题已全列 subject,body 原样重复无信息量)
234
+ const subjectListed = doneQuick.length <= 2 ? new Set(doneQuick.map(q => q.title)) : null
235
+ for (const q of doneQuick) {
236
+ // 标题已列 subject 时 body 仍保留 qlId 行(去重不丢审计锚——标题可省,qlId 是
237
+ // QUICKLOG 对账锚点,既有测试契约钉住 body 含 qlId)
238
+ if (subjectListed && subjectListed.has(q.title)) {
239
+ bodyLines.push(`- quick ${q.qlId}(标题见上)`)
240
+ continue
241
+ }
242
+ bodyLines.push(`- quick ${q.qlId}: ${q.title}`)
243
+ }
203
244
  }
204
245
  for (const t of checkedTasks) bodyLines.push(`- ${t.change} ${t.item}`)
205
246
  for (const s of stageArtifacts) bodyLines.push(`- 阶段产出: ${s}`)
@@ -77,6 +77,14 @@ export const LOCAL_YAML_SCHEMA = {
77
77
  { path: 'commands.knowledge-gate', type: 'boolean', optional: true, status: 'live', readers: ['completeStep (src/run/complete.js)'], desc: '【2026-09-28-unclear-req-to-brainstorm FR-03/D-004】brainstorm 方案步(提出 2-3 种方案)--done 知识检索命中回显开关——缺省 true=开(对 --output 与 decisions.md 条目跑既有检索匹配器,命中 warn 回显「须 evidence 回应或说明不复潮」不阻断);false(或 off)关回显,无命中时输出与现状一致。', example: 'false' },
78
78
  ],
79
79
  },
80
+ {
81
+ id: 'knowledge',
82
+ title: '知识检索(平台向量语义召回)',
83
+ note: '可选段——缺省(未写整段/auto)跟随平台能力自动探测:端点在场自动启用、未部署缓存 1h 内跳过(部署后自动启用,零配置;2026-10-15-platform-vector-search)。off=显式逃生阀强制关,on=强制开跳过探测。能力缓存落 .runtime/vector-capability.json(gitignored);提示触点:knowledge search hint 键、flow 注入零命中行(auto 未就绪时信息性提示)。',
84
+ keys: [
85
+ { path: 'knowledge.vector_search', type: 'string', optional: true, status: 'live', readers: ['vectorSwitchFromLocalYaml (src/knowledge-vector.js)'], desc: '【2026-10-15-vector-capability-autodetect 终态】平台向量语义召回开关——缺省(未写键/auto)跟随平台能力自动探测:端点在场自动启用、未部署缓存 1h 内跳过(部署后自动启用,零配置);off=显式逃生阀强制关、on=强制开跳过探测。env SILLYSPEC_KNOWLEDGE_VECTOR=on|auto/off 同语义强制。四层检索第二层:路由 tag 零命中后平台向量召回(语料=knowledge/decisions 条目,词面不相交条目可语义召回)。', example: 'off' },
86
+ ],
87
+ },
80
88
  {
81
89
  id: 'mcp',
82
90
  title: 'SillyHub MCP 客户端凭据(派发用)',
@@ -387,6 +395,12 @@ commands:
387
395
  # lint_timeout_sec: 600 # commands.lint 超时秒数(缺省 180)
388
396
  # knowledge-gate: false # 方案步 --done 知识检索命中回显开关(缺省 true 开;false 关回显)
389
397
 
398
+ # ── 知识检索(平台向量语义召回)──
399
+ # 缺省(未写整段)跟随平台能力自动探测:端点在场自动启用、未部署自动跳过(部署后自动
400
+ # 启用,零配置)。仅显式逃生阀时才需要写:
401
+ # knowledge:
402
+ # vector_search: off # 强制关(on=强制开跳过探测)
403
+
390
404
  # ── SillyHub MCP 客户端凭据(派发到 worker 用)──
391
405
  # agent 手填,或 sillyspec platform connect 在 mcp 段缺失时同源自动填。
392
406
  # 两键齐全才生效;缺则回退环境变量 SILLYHUB_MCP_URL / SILLYHUB_MCP_TOKEN。