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.
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -114
- package/.claude/skills/sillyspec-execute/SKILL.md +222 -222
- package/.claude/skills/sillyspec-explore/SKILL.md +107 -107
- package/.claude/skills/sillyspec-flow/SKILL.md +1 -1
- package/.claude/skills/sillyspec-plan/SKILL.md +156 -156
- package/.github/workflows/ci.yml +58 -0
- package/.husky/pre-push +7 -6
- package/AGENTS.md +1 -1
- package/assets/command-cards/flow.md +1 -1
- package/eval/eval.config.json +3 -3
- package/eval/lib/config.mjs +6 -3
- package/eval/lib/util.mjs +16 -3
- package/eval/lib/workdir.mjs +4 -1
- package/eval/results/eval.db +0 -0
- package/eval/results/logs/2026-10-15T12-37-22-986Z-004-phase-stats-armA/prompt.txt +10 -0
- package/eval/results/logs/2026-10-15T12-37-22-986Z-004-phase-stats-armA/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T12-37-22-986Z-004-phase-stats-armA/stdout.txt +1 -0
- package/eval/results/logs/2026-10-15T12-45-37-124Z-004-phase-stats-armB/prompt.txt +10 -0
- package/eval/results/logs/2026-10-15T12-45-37-124Z-004-phase-stats-armB/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T12-45-37-124Z-004-phase-stats-armB/stdout.txt +1 -0
- package/eval/results/logs/2026-10-15T12-56-19-259Z-004-phase-stats-armA/prompt.txt +10 -0
- package/eval/results/logs/2026-10-15T12-56-19-259Z-004-phase-stats-armA/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T12-56-19-259Z-004-phase-stats-armA/stdout.txt +1 -0
- package/eval/results/logs/2026-10-15T13-00-05-625Z-004-phase-stats-armB/prompt.txt +10 -0
- package/eval/results/logs/2026-10-15T13-04-51-003Z-004-phase-stats-armA/prompt.txt +10 -0
- package/eval/results/logs/2026-10-15T13-04-51-003Z-004-phase-stats-armA/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T13-04-51-003Z-004-phase-stats-armA/stdout.txt +1 -0
- package/eval/results/logs/2026-10-15T13-06-25-271Z-004-phase-stats-armB/prompt.txt +10 -0
- package/eval/results/logs/2026-10-15T13-06-25-271Z-004-phase-stats-armB/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T13-06-25-271Z-004-phase-stats-armB/stdout.txt +1 -0
- package/eval/results/logs/2026-10-15T13-24-38-308Z-004-phase-stats-armB/prompt.txt +10 -0
- package/eval/results/logs/2026-10-15T13-24-38-308Z-004-phase-stats-armB/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T13-24-38-308Z-004-phase-stats-armB/stdout.txt +1 -0
- package/eval/results/logs/2026-10-15T14-50-01-950Z-005-orders-export-armA/prompt.txt +8 -0
- package/eval/results/logs/2026-10-15T14-50-01-950Z-005-orders-export-armA/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T14-50-01-950Z-005-orders-export-armA/stdout.txt +1 -0
- package/eval/results/logs/2026-10-15T14-51-56-316Z-005-orders-export-armB/prompt.txt +8 -0
- package/eval/results/logs/2026-10-15T14-51-56-316Z-005-orders-export-armB/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T14-51-56-316Z-005-orders-export-armB/stdout.txt +1 -0
- package/eval/results/workdirs/001-slugify__armA/package.json +6 -0
- package/eval/results/workdirs/001-slugify__armA/src/slugify.mjs +26 -0
- package/eval/results/workdirs/001-slugify__armB/package.json +6 -0
- package/eval/results/workdirs/001-slugify__armB/src/slugify.mjs +26 -0
- package/eval/results/workdirs/002-each-day__armA/package.json +6 -0
- package/eval/results/workdirs/002-each-day__armA/src/dates.mjs +30 -0
- package/eval/results/workdirs/002-each-day__armB/DRY_RUN_WRONG_ANSWER.mjs +1 -0
- package/eval/results/workdirs/002-each-day__armB/package.json +6 -0
- package/eval/results/workdirs/002-each-day__armB/src/dates.mjs +4 -0
- package/eval/results/workdirs/003-word-report__armA/bin/report.mjs +36 -0
- package/eval/results/workdirs/003-word-report__armA/data/notes.txt +5 -0
- package/eval/results/workdirs/003-word-report__armA/package.json +6 -0
- package/eval/results/workdirs/003-word-report__armB/bin/report.mjs +36 -0
- package/eval/results/workdirs/003-word-report__armB/data/notes.txt +5 -0
- package/eval/results/workdirs/003-word-report__armB/package.json +6 -0
- package/eval/results/workdirs/004-phase-stats__armA/data/events.log +16 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/flow.md +44 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-archive.md +27 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-brainstorm.md +34 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-execute.md +30 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-plan.md +28 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-verify.md +30 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/status.md +28 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-archive/SKILL.md +87 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-auto/SKILL.md +54 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-commit/SKILL.md +67 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-continue/SKILL.md +17 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-execute/SKILL.md +222 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-explore/SKILL.md +107 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-export/SKILL.md +57 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-flow/SKILL.md +62 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-init/SKILL.md +177 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-knowledge/SKILL.md +40 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-plan/SKILL.md +156 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-propose/SKILL.md +16 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-quick/SKILL.md +116 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-resume/SKILL.md +21 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-scan/SKILL.md +79 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-state/SKILL.md +25 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-status/SKILL.md +54 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-verify/SKILL.md +87 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-workspace/SKILL.md +33 -0
- package/eval/results/workdirs/004-phase-stats__armB/AGENTS.md +45 -0
- package/eval/results/workdirs/004-phase-stats__armB/CLAUDE.md +3 -0
- package/eval/results/workdirs/004-phase-stats__armB/data/events.log +16 -0
- package/eval/results/workdirs/run-001-slugify__armA/package.json +6 -0
- package/eval/results/workdirs/run-001-slugify__armA/src/slugify.mjs +26 -0
- package/eval/results/workdirs/run-001-slugify__armB/package.json +6 -0
- package/eval/results/workdirs/run-001-slugify__armB/src/slugify.mjs +26 -0
- package/eval/results/workdirs/run-002-each-day__armA/package.json +6 -0
- package/eval/results/workdirs/run-002-each-day__armA/src/dates.mjs +30 -0
- package/eval/results/workdirs/run-002-each-day__armB/DRY_RUN_WRONG_ANSWER.mjs +1 -0
- package/eval/results/workdirs/run-002-each-day__armB/package.json +6 -0
- package/eval/results/workdirs/run-002-each-day__armB/src/dates.mjs +4 -0
- package/eval/results/workdirs/run-003-word-report__armA/bin/report.mjs +36 -0
- package/eval/results/workdirs/run-003-word-report__armA/data/notes.txt +5 -0
- package/eval/results/workdirs/run-003-word-report__armA/package.json +6 -0
- package/eval/results/workdirs/run-003-word-report__armB/bin/report.mjs +36 -0
- package/eval/results/workdirs/run-003-word-report__armB/data/notes.txt +5 -0
- package/eval/results/workdirs/run-003-word-report__armB/package.json +6 -0
- package/eval/results/workdirs/run-004-phase-stats__armA/bin/phase-stats.mjs +37 -0
- package/eval/results/workdirs/run-004-phase-stats__armA/data/events.log +16 -0
- package/eval/results/workdirs/run-004-phase-stats__armB/DRY_RUN_WRONG_ANSWER.mjs +1 -0
- package/eval/results/workdirs/run-004-phase-stats__armB/data/events.log +16 -0
- package/eval/results/workdirs/run-005-orders-export__armA/bin/export-orders.mjs +57 -0
- package/eval/results/workdirs/run-005-orders-export__armA/data/.gitattributes +1 -0
- package/eval/results/workdirs/run-005-orders-export__armA/data/orders.csv +9 -0
- package/eval/results/workdirs/run-005-orders-export__armA/out/summary.json +1 -0
- package/eval/results/workdirs/run-005-orders-export__armB/bin/export-orders.mjs +57 -0
- package/eval/results/workdirs/run-005-orders-export__armB/data/.gitattributes +1 -0
- package/eval/results/workdirs/run-005-orders-export__armB/data/orders.csv +9 -0
- package/eval/results/workdirs/run-005-orders-export__armB/out/summary.json +1 -0
- package/eval/tasks/004-phase-stats/files/data/events.log +16 -0
- package/eval/tasks/004-phase-stats/solution/bin/phase-stats.mjs +37 -0
- package/eval/tasks/004-phase-stats/task.json +6 -0
- package/eval/tasks/004-phase-stats/verify.mjs +38 -0
- package/eval/tasks/005-orders-export/files/data/.gitattributes +1 -0
- package/eval/tasks/005-orders-export/files/data/orders.csv +9 -0
- package/eval/tasks/005-orders-export/solution/bin/export-orders.mjs +57 -0
- package/eval/tasks/005-orders-export/task.json +6 -0
- package/eval/tasks/005-orders-export/verify.mjs +28 -0
- package/package.json +1 -1
- package/quicklog/tmp-fix-review.mjs +21 -0
- package/src/ceremony-tier.js +2 -1
- package/src/commit-suggest.js +45 -4
- package/src/config-schema.js +14 -0
- package/src/decision-distill.js +106 -24
- package/src/decision-redomain.js +148 -0
- package/src/decision-renumber.js +157 -0
- package/src/design-facts.js +3 -3
- package/src/docs-check.js +30 -15
- package/src/docs-debt.js +6 -5
- package/src/doctor-diagnostics.js +3 -1
- package/src/flow-parity.js +28 -4
- package/src/flow.js +76 -13
- package/src/forensics.js +236 -0
- package/src/fr-index.js +17 -2
- package/src/hooks/worktree-guard.js +3 -3
- package/src/index.js +41 -6
- package/src/knowledge-digest.js +23 -6
- package/src/knowledge-graph.js +95 -18
- package/src/knowledge-match.js +34 -9
- package/src/knowledge-stats.js +12 -13
- package/src/knowledge-vector.js +98 -7
- package/src/machine-interface.js +26 -0
- package/src/module-impact.js +2 -5
- package/src/modules.js +31 -0
- package/src/quicklog.js +6 -1
- package/src/run/archive-distill.js +3 -0
- package/src/run/command.js +3 -3
- package/src/run/complete-handlers.js +8 -3
- package/src/run/complete.js +113 -44
- package/src/run/gates.js +100 -103
- package/src/run/lint-ownership.js +37 -0
- package/src/run/prompt.js +43 -28
- package/src/run/stage.js +1 -1
- package/src/run/verify-quality-scan.js +10 -1
- package/src/sentinel-assertions.js +43 -1
- package/src/spec-sync.js +81 -2
- package/src/stage-contract.js +26 -3
- package/src/stage-review.js +46 -12
- package/src/stage-templates.js +2 -0
- package/src/stages/brainstorm.js +76 -21
- package/src/stages/knowledge.js +56 -5
- package/src/stages/plan-postcheck.js +13 -2
- package/src/stages/plan.js +19 -0
- package/src/sync.js +28 -2
- package/src/task-tick.js +12 -0
- package/src/test-bindings.js +32 -3
- package/src/timeline.js +60 -19
- package/src/ui-visual.js +119 -4
- package/src/verify-postcheck.js +166 -18
- package/src/verify-probes.js +61 -18
- package/src/worktree-apply.js +26 -2
- package/templates/agents-instruction.md +1 -1
package/src/forensics.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* forensics.js — 会话取证证据包装配 + 取证任务书渲染(2026-10-15-session-forensics)
|
|
3
|
+
*
|
|
4
|
+
* 定位:`sillyspec diagnose --change <名>` 的只读装配面。对照 obra/superpowers
|
|
5
|
+
* diagnosing-superpowers 的取证形态做 SillySpec 原生版——分工沿本仓哲学:
|
|
6
|
+
* CLI 装配机械事实(证据包六面),agent 做语义判断(按任务书读 transcript、出
|
|
7
|
+
* path:line 证据报告)。全程零写入:证据源全是事后读取的静态文件,单源缺失/
|
|
8
|
+
* 损坏 fail-soft 空清单+注记,不抛错不阻断(取证要容忍现场变动)。
|
|
9
|
+
*
|
|
10
|
+
* 数据源(全部既有,零新格式):
|
|
11
|
+
* - 变更目录 / 归档目录(flow-state.yaml 身份面、review 轮次文件)
|
|
12
|
+
* - <runtimeRoot>/agent-session-log.json(run 命令登记的会话日志路径,
|
|
13
|
+
* 按 change_key 精确相等过滤——登记面唯一键,无子串误命中面)
|
|
14
|
+
* - <runtimeRoot>/friction-ledger.json(readFrictionLedger 既有 API)
|
|
15
|
+
* - <runtimeRoot>/verify-runs/<时间戳目录>/test-result.json(mtime 近序取前 N)
|
|
16
|
+
*
|
|
17
|
+
* 红线:零内容枚举——任务书六维度是结构骨架(CLI 自有任务书面集合,同
|
|
18
|
+
* review_probes scope 判例),不是内容词表;CLI 不解析 transcript 本体
|
|
19
|
+
* (三 harness 格式 × 演化泥潭,解读交给 agent)。
|
|
20
|
+
*/
|
|
21
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
|
|
22
|
+
import { join } from 'node:path'
|
|
23
|
+
import yaml from 'js-yaml'
|
|
24
|
+
import { readFrictionLedger } from './friction-ledger.js'
|
|
25
|
+
|
|
26
|
+
/** verify-runs 证据默认截断数(近序 N 条,防大仓 runtime 灌爆输出;模块内常量——导出即死码债) */
|
|
27
|
+
const VERIFY_RUNS_LIMIT = 3
|
|
28
|
+
|
|
29
|
+
/** 读 JSON 文件,失败/不存在返回 null(fail-soft 取证立场)。 */
|
|
30
|
+
function readJsonSafe(path) {
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(readFileSync(path, 'utf8'))
|
|
33
|
+
} catch {
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 读 YAML 文件,失败/不存在返回 null。 */
|
|
39
|
+
function readYamlSafe(path) {
|
|
40
|
+
try {
|
|
41
|
+
return yaml.load(readFileSync(path, 'utf8'))
|
|
42
|
+
} catch {
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 装配证据包六面。纯读函数,零写入。
|
|
49
|
+
* @param {{ specBase: string, runtimeRoot: string, changeName: string, verifyRunsLimit?: number }} p
|
|
50
|
+
* @returns {{ identity: object, agentLogs: object[], friction: object[], verifyRuns: object[], reviews: object[], warnings: string[] }}
|
|
51
|
+
*/
|
|
52
|
+
export function assembleForensicsPack({ specBase, runtimeRoot, changeName, verifyRunsLimit = VERIFY_RUNS_LIMIT }) {
|
|
53
|
+
const warnings = []
|
|
54
|
+
const changeDir = join(specBase, 'changes', changeName)
|
|
55
|
+
const archivedDir = join(specBase, 'changes', 'archive', changeName)
|
|
56
|
+
|
|
57
|
+
// ① 变更身份(active / archived / missing + flow-state 态)
|
|
58
|
+
const identity = { changeName, status: 'missing', changeDir: null, archivedDir: null, tier: null, substeps: null }
|
|
59
|
+
if (existsSync(changeDir)) {
|
|
60
|
+
identity.status = 'active'
|
|
61
|
+
identity.changeDir = changeDir
|
|
62
|
+
} else if (existsSync(archivedDir)) {
|
|
63
|
+
identity.status = 'archived'
|
|
64
|
+
identity.changeDir = archivedDir
|
|
65
|
+
identity.archivedDir = archivedDir
|
|
66
|
+
warnings.push('变更已归档——证据包从归档目录读取,transcript 仍按登记路径取')
|
|
67
|
+
}
|
|
68
|
+
const flowStatePath = identity.changeDir ? join(identity.changeDir, 'flow-state.yaml') : null
|
|
69
|
+
if (flowStatePath && existsSync(flowStatePath)) {
|
|
70
|
+
const fs_ = readYamlSafe(flowStatePath)
|
|
71
|
+
if (fs_ && typeof fs_ === 'object') {
|
|
72
|
+
identity.tier = fs_.tier ?? null
|
|
73
|
+
identity.substeps = fs_.substeps && typeof fs_.substeps === 'object' ? fs_.substeps : null
|
|
74
|
+
} else {
|
|
75
|
+
warnings.push(`flow-state.yaml 解析失败(${flowStatePath})——身份面降级`)
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
warnings.push('无 flow 态(archive 件或无 flow-state.yaml)')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ② agent 会话日志(change_key 精确相等——登记面唯一键)
|
|
82
|
+
const registryPath = join(runtimeRoot, 'agent-session-log.json')
|
|
83
|
+
const registry = readJsonSafe(registryPath)
|
|
84
|
+
let agentLogs = []
|
|
85
|
+
if (registry && Array.isArray(registry.entries)) {
|
|
86
|
+
agentLogs = registry.entries.filter((e) => e && typeof e === 'object' && e.change_key === changeName)
|
|
87
|
+
} else {
|
|
88
|
+
warnings.push(`会话日志登记缺失或不可解析(${registryPath})——本机未跑过 run 命令或登记面损坏`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ③ 摩擦账本(readFrictionLedger 容错:缺文件/坏 JSON → [])
|
|
92
|
+
const friction = readFrictionLedger(runtimeRoot).filter((e) => e.change === changeName)
|
|
93
|
+
|
|
94
|
+
// ④ verify-runs(mtime 近序,同 mtime 路径字典序破平;截断前 N)
|
|
95
|
+
// fail-soft:runsRoot 为普通文件/权限损坏形态时 readdirSync 抛 ENOTDIR——捕获降级,不阻断取证。
|
|
96
|
+
const runsRoot = join(runtimeRoot, 'verify-runs')
|
|
97
|
+
const verifyRuns = []
|
|
98
|
+
if (existsSync(runsRoot)) {
|
|
99
|
+
let runEntries = []
|
|
100
|
+
try {
|
|
101
|
+
runEntries = readdirSync(runsRoot, { withFileTypes: true })
|
|
102
|
+
} catch {
|
|
103
|
+
warnings.push(`verify-runs 不可读(非目录或损坏形态,${runsRoot})——降级为空`)
|
|
104
|
+
}
|
|
105
|
+
const dirs = runEntries
|
|
106
|
+
.filter((d) => d.isDirectory())
|
|
107
|
+
.map((d) => join(runsRoot, d.name))
|
|
108
|
+
const withTime = dirs
|
|
109
|
+
.map((dir) => {
|
|
110
|
+
try {
|
|
111
|
+
return { dir, mtimeMs: statSync(dir).mtimeMs }
|
|
112
|
+
} catch {
|
|
113
|
+
return null
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
.filter(Boolean)
|
|
117
|
+
.sort((a, b) => (b.mtimeMs - a.mtimeMs) || (a.dir < b.dir ? -1 : a.dir > b.dir ? 1 : 0))
|
|
118
|
+
for (const { dir, mtimeMs } of withTime.slice(0, Math.max(1, verifyRunsLimit))) {
|
|
119
|
+
const resultPath = join(dir, 'test-result.json')
|
|
120
|
+
verifyRuns.push({
|
|
121
|
+
dir,
|
|
122
|
+
resultPath: existsSync(resultPath) ? resultPath : null,
|
|
123
|
+
mtimeIso: new Date(mtimeMs).toISOString(),
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
warnings.push(`无 verify-runs 目录(${runsRoot})——本变更未跑过收口实测或 runtime 已清理`)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ⑤ 评审轮次(review.json + superseded-*,mtime 近序;目录损坏形态同上降级)
|
|
131
|
+
const reviews = []
|
|
132
|
+
if (identity.changeDir && existsSync(identity.changeDir)) {
|
|
133
|
+
let reviewFiles = []
|
|
134
|
+
try {
|
|
135
|
+
reviewFiles = readdirSync(identity.changeDir).filter((f) => /^review\.json(\.superseded-.+)?$/.test(f))
|
|
136
|
+
} catch {
|
|
137
|
+
warnings.push(`变更目录不可读(${identity.changeDir})——评审轮次面降级为空`)
|
|
138
|
+
}
|
|
139
|
+
for (const f of reviewFiles) {
|
|
140
|
+
const p = join(identity.changeDir, f)
|
|
141
|
+
let mtimeIso = null
|
|
142
|
+
try {
|
|
143
|
+
mtimeIso = new Date(statSync(p).mtimeMs).toISOString()
|
|
144
|
+
} catch { /* 文件刚被删——列名不留 mtime */ }
|
|
145
|
+
reviews.push({ file: p, superseded: f !== 'review.json', mtimeIso })
|
|
146
|
+
}
|
|
147
|
+
reviews.sort((a, b) => (b.mtimeIso || '').localeCompare(a.mtimeIso || ''))
|
|
148
|
+
} else {
|
|
149
|
+
warnings.push('变更目录不存在——评审轮次面为空')
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { identity, agentLogs, friction, verifyRuns, reviews, warnings }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 渲染取证任务书(markdown)。六维度是结构骨架不是封闭清单;四纪律钉死
|
|
157
|
+
* diagnosing-superpowers 的核心铁律(path:line 锚、唯一可写产物、先读本体、时间盒)。
|
|
158
|
+
* @param {{ identity: object, agentLogs: object[], friction: object[], verifyRuns: object[], reviews: object[], warnings: string[] }} pack
|
|
159
|
+
* @returns {string}
|
|
160
|
+
*/
|
|
161
|
+
export function renderForensicsTaskbook(pack) {
|
|
162
|
+
const { identity, agentLogs, friction, verifyRuns, reviews, warnings } = pack
|
|
163
|
+
const lines = []
|
|
164
|
+
lines.push(`# 会话取证任务书 — ${identity.changeName}`)
|
|
165
|
+
lines.push('')
|
|
166
|
+
lines.push(`【角色】你是取证分析员(干净上下文)——还原这个会话里实际发生了什么,拿证据说话,不替流程辩护也不替人辩护。报告读者是「要决定下一步怎么办的人」。`)
|
|
167
|
+
lines.push('')
|
|
168
|
+
lines.push('## 证据包(CLI 装配的机械事实——先读这些文件本体,再下结论)')
|
|
169
|
+
lines.push('')
|
|
170
|
+
lines.push(`- 变更身份:status=${identity.status}|tier=${identity.tier ?? '无'}|目录=${identity.changeDir ?? '(不存在)'}`)
|
|
171
|
+
lines.push(`- agent 会话日志(${agentLogs.length} 条,change_key=${identity.changeName} 精确过滤):`)
|
|
172
|
+
for (const e of agentLogs) {
|
|
173
|
+
lines.push(` - [${e.harness}/${e.format ?? '?'}] ${e.log_path}(session=${e.session_id ?? '?'}${e.exists === false ? ',⚠️ 文件不在场' : ''})`)
|
|
174
|
+
}
|
|
175
|
+
if (agentLogs.length === 0) lines.push(' - (零命中——本机未登记过本变更的会话日志)')
|
|
176
|
+
lines.push(`- 摩擦账本(${friction.length} 条):${friction.map((e) => JSON.stringify(e.counts ?? {})).join(' / ') || '(无)'}`)
|
|
177
|
+
lines.push(`- verify-runs(近 ${verifyRuns.length} 次):`)
|
|
178
|
+
for (const r of verifyRuns) lines.push(` - ${r.resultPath ?? r.dir + '(无 test-result.json)'} @ ${r.mtimeIso}`)
|
|
179
|
+
lines.push(`- 评审轮次(${reviews.length} 份,含 superseded):`)
|
|
180
|
+
for (const r of reviews) lines.push(` - ${r.file}${r.superseded ? '(superseded——前轮评审结论,对比两轮可还原修复轨迹)' : ''}`)
|
|
181
|
+
if (warnings.length) {
|
|
182
|
+
lines.push(`- 降级注记:${warnings.join(';')}`)
|
|
183
|
+
}
|
|
184
|
+
lines.push('')
|
|
185
|
+
lines.push('## 分析维度(结构骨架——常见失事面,不是封闭清单:证据指向维度外时照报,报告允许增设章节)')
|
|
186
|
+
lines.push('')
|
|
187
|
+
lines.push('1. **时间线重建**:这个会话按时间实际做了什么(步骤推进/等待/回滚/中断点)。')
|
|
188
|
+
lines.push('2. **计划遵从**:做的是计划内的事吗?偏离处有没有留痕(决策/账本/提交消息)?')
|
|
189
|
+
lines.push('3. **重复工作**:有没有同一件事做两遍(重派/重审/重跑)?根因是什么?')
|
|
190
|
+
lines.push('4. **停等与重试**:哪里停等了用户/子代理?重试了几次?是否有可避免的空转?')
|
|
191
|
+
lines.push('5. **成本与轮次**:轮次/token 花在哪了(读文件长尾?全量重验?)?')
|
|
192
|
+
lines.push('6. **门禁摩擦**:gate 回滚/评审打回/实测失败的完整链条与最终清偿方式。')
|
|
193
|
+
lines.push('')
|
|
194
|
+
lines.push('## 取证纪律')
|
|
195
|
+
lines.push('')
|
|
196
|
+
lines.push('1. **每条 finding 必须带 `path:line` 锚,无锚不成立**——锚指向证据包列出的文件或其内容(行号取自你实际读到的文件)。')
|
|
197
|
+
lines.push('2. **唯一可写产物是 `diagnosis-report.md`**(写在变更目录)——禁止修改任何其他文件、禁止 git 操作。固定章节:`## 问题陈述` / `## 时间线` / `## Findings`(分维度)/ `## 无法证实面`(证据不足的疑点如实列,不脑补)。')
|
|
198
|
+
lines.push('3. **先读证据包列出的文件本体再下结论,禁止全量扫读仓**——矛盾经常藏在交叉处,定向读那一处。')
|
|
199
|
+
lines.push('4. **时间盒收敛**:结论存疑时定向补证;连续扩面仍零结论 = 基于已读材料收敛,把疑点写进「无法证实面」。')
|
|
200
|
+
lines.push('')
|
|
201
|
+
lines.push('【无子代理通道时】主会话按本任务书自行分析即可,如实标注执行方式;有子代理通道时按维度分派并行分析、汇总时逐条核对锚点。')
|
|
202
|
+
lines.push('')
|
|
203
|
+
return lines.join('\n')
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 命令入口:sillyspec diagnose --change <名> [--detect] [--json]
|
|
208
|
+
* 只读零写入;--detect 先透传 agent-session-log 现场探测(不落盘语义随既有 cmdAgentLog)。
|
|
209
|
+
* @param {string[]} args - filteredArgs.slice(1)('diagnose' 之后)
|
|
210
|
+
* @param {{ json?: boolean, cwd?: string, specDir?: string|null }} opts
|
|
211
|
+
*/
|
|
212
|
+
export async function cmdForensics(args, { json = false, cwd = process.cwd(), specDir = null } = {}) {
|
|
213
|
+
const changeIdx = args.indexOf('--change')
|
|
214
|
+
const changeName = changeIdx >= 0 && args[changeIdx + 1] && !String(args[changeIdx + 1]).startsWith('--') ? args[changeIdx + 1] : null
|
|
215
|
+
if (!changeName) {
|
|
216
|
+
console.error('用法: sillyspec diagnose --change <名> [--detect] [--json] [--spec-dir <路径>]')
|
|
217
|
+
console.error(' 只读装配会话取证证据包 + 取证任务书(path:line 铁律;唯一可写产物是变更目录的 diagnosis-report.md,由执行任务书的 agent 产出)')
|
|
218
|
+
process.exit(2)
|
|
219
|
+
}
|
|
220
|
+
const specBase = specDir || join(cwd, '.sillyspec')
|
|
221
|
+
const runtimeRoot = join(specBase, '.runtime')
|
|
222
|
+
|
|
223
|
+
if (args.includes('--detect')) {
|
|
224
|
+
const { cmdAgentLog } = await import('./agent-session-log.js')
|
|
225
|
+
await cmdAgentLog(['--detect'], { json: false, cwd, specDir })
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const pack = assembleForensicsPack({ specBase, runtimeRoot, changeName })
|
|
229
|
+
const taskbook = renderForensicsTaskbook(pack)
|
|
230
|
+
|
|
231
|
+
if (json || args.includes('--json')) {
|
|
232
|
+
console.log(JSON.stringify({ ...pack, taskbook }))
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
console.log(taskbook.trimEnd())
|
|
236
|
+
}
|
package/src/fr-index.js
CHANGED
|
@@ -18,9 +18,11 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { readChangeTrace, upsertFrBindings, applySupersededToEntryLines, testAnchorFile, resolveTestFileRel } from './test-bindings.js'
|
|
21
|
+
import { modulePathMatches } from './modules.js'
|
|
21
22
|
import { suggestDomainFromFiles, snapDomainToDictionary } from './knowledge-digest.js'
|
|
22
23
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'fs';
|
|
23
24
|
import { basename, dirname, join } from 'path';
|
|
25
|
+
import { execFileSync } from 'node:child_process';
|
|
24
26
|
import { writeAtomicSync } from './fs-atomic.js';
|
|
25
27
|
import {
|
|
26
28
|
splitKnowledgeSections,
|
|
@@ -73,6 +75,19 @@ export function frCoverageFiles({ archiveRoot, changeName }) {
|
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
77
|
} catch { /* 源2 fail-soft */ }
|
|
78
|
+
try {
|
|
79
|
+
// 源3(2026-10-15-rot-coverage-git-source):thin 归档常无交付表无 patch 文件清单——覆盖面
|
|
80
|
+
// 从 git 历史恢复:提交主题含变更名的提交的 name-only 文件集(--grep 子串匹配,变更名含
|
|
81
|
+
// 日期前缀足够特异;剔除 .sillyspec/ 自身工件面)。只读确定性;调用侧 covCache 每运行缓存,
|
|
82
|
+
// 非 git 仓/无记录 fail-soft=源缺(六顽固化 unknown 来源实测全部可恢复 1~23 文件)。
|
|
83
|
+
const gitOut = execFileSync('git', ['log', '--all', '--name-only', '--pretty=format:COMMIT', '--grep', String(changeName || '')],
|
|
84
|
+
{ cwd: archiveRoot, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
|
|
85
|
+
for (const f of String(gitOut).split(/\r?\n/)) {
|
|
86
|
+
const v = f.trim().replace(/\\/g, '/');
|
|
87
|
+
if (!v || v.startsWith('COMMIT') || v.startsWith('.sillyspec/')) continue;
|
|
88
|
+
out.add(v);
|
|
89
|
+
}
|
|
90
|
+
} catch { /* 源3 fail-soft(非 git 仓/无记录) */ }
|
|
76
91
|
return [...out].filter((p) => !p.startsWith('.sillyspec/'));
|
|
77
92
|
}
|
|
78
93
|
|
|
@@ -236,7 +251,7 @@ export function matchedModuleIds(moduleIndex, filePath) {
|
|
|
236
251
|
const hits = [];
|
|
237
252
|
for (const [modId, mod] of Object.entries(moduleIndex)) {
|
|
238
253
|
const paths = (mod && Array.isArray(mod.paths)) ? mod.paths : [];
|
|
239
|
-
if (paths.some((pp) => filePath
|
|
254
|
+
if (paths.some((pp) => modulePathMatches(filePath, pp))) {
|
|
240
255
|
hits.push(modId);
|
|
241
256
|
}
|
|
242
257
|
}
|
|
@@ -873,7 +888,7 @@ export function markSkeletonThin(knowledgeRoot) {
|
|
|
873
888
|
* active FR × 触碰文件 → hits(覆盖相交)/ skip(覆盖可判无交集)/ unknown(覆盖源缺失)。
|
|
874
889
|
* 判据逐字不变——三消费方(rot/测试门/注入排序)共用同一实现,防口径漂移。
|
|
875
890
|
* covCache 供调用方跨查询复用归档读取。 */
|
|
876
|
-
function partitionActiveByCoverage({ archiveRoot, frs, changed, covCache = new Map() }) {
|
|
891
|
+
export function partitionActiveByCoverage({ archiveRoot, frs, changed, covCache = new Map() }) {
|
|
877
892
|
const hits = []
|
|
878
893
|
const unknownSources = new Set()
|
|
879
894
|
let skip = 0
|
|
@@ -61,7 +61,7 @@ const STAGE_HINTS = {
|
|
|
61
61
|
'你需要先启动一个任务流程才能修改源码(调用对应的 sillyspec skill):',
|
|
62
62
|
'',
|
|
63
63
|
' 轻量变更/BUG修复(默认快道,skill sillyspec-flow)——--input 引号内换行合法,下例可照抄:',
|
|
64
|
-
' sillyspec flow start --change <YYYY-MM-DD-名> --input "
|
|
64
|
+
' sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句中文概括(首行即变更标题);随后动机与背景',
|
|
65
65
|
'',
|
|
66
66
|
' 成功标准:',
|
|
67
67
|
' - <可验证标准>"',
|
|
@@ -80,7 +80,7 @@ const STAGE_HINTS = {
|
|
|
80
80
|
'verify': [
|
|
81
81
|
'当前在 verify(验证)阶段,只做代码审查和测试验证,不修改源码。',
|
|
82
82
|
'如需修改,请先回到 execute 阶段,或收口后开轻量变更(--input 引号内换行合法,可照抄):',
|
|
83
|
-
' sillyspec flow start --change <YYYY-MM-DD-名> --input "
|
|
83
|
+
' sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句中文概括(首行即变更标题);随后动机与背景',
|
|
84
84
|
'',
|
|
85
85
|
' 成功标准:',
|
|
86
86
|
' - <可验证标准>"',
|
|
@@ -88,7 +88,7 @@ const STAGE_HINTS = {
|
|
|
88
88
|
'archive': [
|
|
89
89
|
'当前在 archive(归档)阶段,不修改源码。',
|
|
90
90
|
'如需修改,请开启新变更(--input 引号内换行合法,可照抄):',
|
|
91
|
-
' sillyspec flow start --change <YYYY-MM-DD-名> --input "
|
|
91
|
+
' sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句中文概括(首行即变更标题);随后动机与背景',
|
|
92
92
|
'',
|
|
93
93
|
' 成功标准:',
|
|
94
94
|
' - <可验证标准>"',
|
package/src/index.js
CHANGED
|
@@ -100,6 +100,8 @@ SillySpec CLI — 规范驱动开发工具包
|
|
|
100
100
|
变更合成时间线(事件流 × tasks.md × git 提交锚三源真实留痕渲染;活跃/归档变更均可)
|
|
101
101
|
sillyspec dispatch <probe | hint> SillyHub 派发能力探测 + 策略生成(agent 调用桥,仅渲染不执行 tool)
|
|
102
102
|
sillyspec agent-log [--detect] [--json] 本地 agent 会话日志查询/现场探测(run 命令自动上报平台 POST /api/agent-logs 并本地留底)
|
|
103
|
+
sillyspec diagnose --change <名> [--detect] [--json]
|
|
104
|
+
会话取证:只读装配证据包(flow 态/会话日志/摩擦账本/verify-runs/评审轮次)+ 取证任务书(path:line 铁律;报告由执行任务书的 agent 产出)
|
|
103
105
|
|
|
104
106
|
sillyspec workflow check <name> [--project <p>] [--change <c>] [--json] [--save]
|
|
105
107
|
全局 workflow 校验(--save 归档到 .sillyspec/.runtime/workflow-runs/)
|
|
@@ -171,7 +173,7 @@ SillySpec CLI — 规范驱动开发工具包
|
|
|
171
173
|
示例:
|
|
172
174
|
sillyspec init
|
|
173
175
|
sillyspec run brainstorm --change 2026-07-03-add-login
|
|
174
|
-
sillyspec flow start --change 2026-07-03-my-fix --input "
|
|
176
|
+
sillyspec flow start --change 2026-07-03-my-fix --input "<一句中文概括(首行即变更标题)+动机与背景详述+独立一行『成功标准:』+每行一条『- 可验证标准』>" # 轻量变更(默认快道,2 次调用;变更名须 YYYY-MM-DD-<简短描述> 日期前缀)
|
|
175
177
|
sillyspec run verify --done --output "验证通过,测试全绿"
|
|
176
178
|
sillyspec run archive --done --confirm --output "归档完成"
|
|
177
179
|
sillyspec run plan --reopen --from-step 2 # 修订 plan,从第 2 步重做
|
|
@@ -226,7 +228,7 @@ function refuseRetiredQuickFreshStart(stageArgs) {
|
|
|
226
228
|
console.error('❌ quick 通道已退役,不再接受新会话(v3.30.0 起)。')
|
|
227
229
|
// 可照抄实例(2026-10-05-input-teach-copyable):引号内换行合法,行级 trim 容忍缩进
|
|
228
230
|
console.error(' 新工作请走轻量变更(--input 引号内换行合法,下例可照抄):')
|
|
229
|
-
console.error(' sillyspec flow start --change <YYYY-MM-DD-名> --input "
|
|
231
|
+
console.error(' sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句中文概括(首行即变更标题);随后动机与背景')
|
|
230
232
|
console.error('')
|
|
231
233
|
console.error(' 成功标准:')
|
|
232
234
|
console.error(' - <可验证标准>"')
|
|
@@ -3390,11 +3392,34 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
3390
3392
|
// --by-change(坑 fr-domain-suggest-typo-and-no-split-migration 缺陷2):unmapped 等
|
|
3391
3393
|
// 混合池按「变更:<名>」字段分批迁移——整域迁移会错置大部分条目,来源变更才是分批键。
|
|
3392
3394
|
const byChangeD = flag('--by-change')
|
|
3393
|
-
if (!fromD || !toD) { fail('用法: sillyspec tests --redomain --from <源域> --to <目标域> [--anchor <FR-id>] [--by-change <变更名>] [--write]\n
|
|
3395
|
+
if (!fromD || !toD) { fail('用法: sillyspec tests --redomain --from <源域> --to <目标域> [--anchor <FR-id>] [--by-change <变更名>] [--decisions] [--write]\n --decisions 切 decisions/ 面(默认 fr/ 面);域名可带前缀与 .md 后缀(fr/auto-backend ≍ auto-backend.md ≍ auto-backend)'); break }
|
|
3394
3396
|
// 多 --anchor 收集
|
|
3395
3397
|
const anchorList = []
|
|
3396
3398
|
for (let i = 0; i < filteredArgs.length - 1; i++) if (filteredArgs[i] === '--anchor') anchorList.push(filteredArgs[i + 1])
|
|
3399
|
+
// --decisions(2026-10-15-decision-redomain):切 decisions/ 面的孪生迁移实现——
|
|
3400
|
+
// 同 flag 族(--from/--to/--anchor/--by-change/--write),段字节整体搬运、迁域不换号、
|
|
3401
|
+
// 跨变更同号不冲突(双键防);平台仓 unmapped 442 条存量清偿的机器通道。
|
|
3402
|
+
const useDecisions = has('--decisions')
|
|
3397
3403
|
try {
|
|
3404
|
+
if (useDecisions) {
|
|
3405
|
+
const { planDecisionRedomain, redomainDecisionEntries } = await import('./decision-redomain.js')
|
|
3406
|
+
if (!has('--write')) {
|
|
3407
|
+
const plan = planDecisionRedomain({ knowledgeRoot, from: fromD, to: toD, anchors: anchorList.length ? anchorList : null, byChange: byChangeD || null })
|
|
3408
|
+
console.log(`🔧 决策域迁移预览(--write 落盘):decisions/${fromD}.md → decisions/${toD}.md,${plan.entries.length} 条${byChangeD ? `(仅「变更:${byChangeD}」)` : ''}`)
|
|
3409
|
+
for (const e of plan.entries.slice(0, 10)) console.log(` ${e.id} ${e.title}`)
|
|
3410
|
+
if (plan.entries.length > 10) console.log(` … 共 ${plan.entries.length} 条`)
|
|
3411
|
+
console.log(` 目标域文件${plan.targetExists ? '在场' : '将新建'};源域${plan.sourceWillDelete ? '将删空壳' : '保留余条'};ID 不变(身份保持)`)
|
|
3412
|
+
break
|
|
3413
|
+
}
|
|
3414
|
+
const r = redomainDecisionEntries({ knowledgeRoot, from: fromD, to: toD, anchors: anchorList.length ? anchorList : null, byChange: byChangeD || null })
|
|
3415
|
+
console.log(`✅ 决策域迁移完成:${r.moved.length} 条 decisions/${fromD}.md → decisions/${toD}.md(ID 不变${byChangeD ? `,仅「变更:${byChangeD}」` : ''})`)
|
|
3416
|
+
for (const m of r.moved.slice(0, 10)) console.log(` ${m.id} ${m.title}`)
|
|
3417
|
+
if (r.skippedConflicts.length > 0) console.log(` ⚠️ 目标域同 id 同变更段冲突跳过 ${r.skippedConflicts.length} 条(${r.skippedConflicts.slice(0, 5).join('、')})`)
|
|
3418
|
+
if (r.sourceDeleted) console.log(` 源域空壳已删:decisions/${fromD}.md`)
|
|
3419
|
+
if (r.targetCreated) console.log(` 目标域文件新建:decisions/${toD}.md`)
|
|
3420
|
+
if (r.indexLineAdded) console.log(` INDEX 路由行已补:${toD}`)
|
|
3421
|
+
break
|
|
3422
|
+
}
|
|
3398
3423
|
if (!has('--write')) {
|
|
3399
3424
|
// 预览与落盘同口径透传 byChange(坑 redomain-plan-preview-by-change-ignored,
|
|
3400
3425
|
// 2026-10-02 实证:76338b4d 收编在途工作时此行漏透传——预览列整域、落盘只迁
|
|
@@ -4107,6 +4132,14 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
4107
4132
|
await cmdAgentLog(filteredArgs.slice(1), { json, cwd: dir, specDir });
|
|
4108
4133
|
break;
|
|
4109
4134
|
}
|
|
4135
|
+
case 'diagnose': {
|
|
4136
|
+
// 会话取证(2026-10-15-session-forensics):只读装配证据包六面(flow 态/会话日志/摩擦账本/
|
|
4137
|
+
// verify-runs/评审轮次/任务书)+ 取证任务书(path:line 铁律,唯一可写产物 diagnosis-report.md
|
|
4138
|
+
// 由执行任务书的 agent 产出)。零状态写入,不进 run 状态机——诊断是随时可调用的查询动作。
|
|
4139
|
+
const { cmdForensics } = await import('./forensics.js');
|
|
4140
|
+
await cmdForensics(filteredArgs.slice(1), { json, cwd: dir, specDir });
|
|
4141
|
+
break;
|
|
4142
|
+
}
|
|
4110
4143
|
case 'patch-reanchor': {
|
|
4111
4144
|
// 收口留痕锚点回写(2026-10-12-close-trace-freeze-integrity FR-03):worktree 内收口的
|
|
4112
4145
|
// change-patch.json 记 worktree 本地 SHA,apply 重放回主仓后悬空——按提交主题归属推导
|
|
@@ -5499,7 +5532,7 @@ SillySpec pull — 拉取服务器 spec 快照到本地(X2 / FR-07)
|
|
|
5499
5532
|
console.error(` 事件流路径:${join(tRuntimeRoot, `watcher-events-${tChange}.jsonl`)};活跃变更跑任意 flow/run 命令会拉起 watcher`);
|
|
5500
5533
|
process.exit(2);
|
|
5501
5534
|
}
|
|
5502
|
-
const { changeDir, tasksMd } = loadChangeTasks(tSpecBase, tChange);
|
|
5535
|
+
const { changeDir, tasksMd, tickLog } = loadChangeTasks(tSpecBase, tChange);
|
|
5503
5536
|
const tasks = tasksMd ? parseTaskLines(tasksMd) : null;
|
|
5504
5537
|
let tier = null;
|
|
5505
5538
|
if (changeDir) {
|
|
@@ -5509,6 +5542,8 @@ SillySpec pull — 拉取服务器 spec 快照到本地(X2 / FR-07)
|
|
|
5509
5542
|
} catch { /* 缺 flow-state(头脑风暴预段等)按未知 */ }
|
|
5510
5543
|
}
|
|
5511
5544
|
const { gitQuiet } = await import('./git-helper.js');
|
|
5545
|
+
// 锚收敛(2026-10-14-timeline-ticktime-anchor-scope):change 参进锚解析——消息不含本变更
|
|
5546
|
+
// 名的提交不参与任务行锚定(跨变更 token 撞车防线);事件时间轴不受影响。
|
|
5512
5547
|
const anchors = resolveCommitAnchors(tRes.events, (hash) => {
|
|
5513
5548
|
const out = gitQuiet(dir, ['log', '-1', '--format=%h%x1f%aI%x1f%B', hash]);
|
|
5514
5549
|
if (typeof out !== 'string' || !out.trim()) return null;
|
|
@@ -5516,8 +5551,8 @@ SillySpec pull — 拉取服务器 spec 快照到本地(X2 / FR-07)
|
|
|
5516
5551
|
const rest = out.slice(sep + 1);
|
|
5517
5552
|
const sep2 = rest.indexOf('\x1f');
|
|
5518
5553
|
return { hash: out.slice(0, sep), dateISO: rest.slice(0, sep2), message: rest.slice(sep2 + 1) };
|
|
5519
|
-
});
|
|
5520
|
-
console.log(renderTimeline({ change: tChange, events: tRes.events, tasks, anchors, birthTs: readBirthTs(changeDir), tier }));
|
|
5554
|
+
}, tChange);
|
|
5555
|
+
console.log(renderTimeline({ change: tChange, events: tRes.events, tasks, anchors, birthTs: readBirthTs(changeDir), tier, tickLog }));
|
|
5521
5556
|
if (tSourceNote) console.log(tSourceNote);
|
|
5522
5557
|
break;
|
|
5523
5558
|
}
|
package/src/knowledge-digest.js
CHANGED
|
@@ -152,6 +152,14 @@ export function collectKnowledgeDigest({ specBase, projectRoot }) {
|
|
|
152
152
|
mk('inbox', 'warn', `知识收件箱积压(${totals.inbox} 条 > 20)`, totals.inbox,
|
|
153
153
|
inboxTitles.slice(0, 5).join(';') + (inboxTitles.length > 5 ? ' 等' : ''),
|
|
154
154
|
'sillyspec knowledge inbox 逐条 classify 清账')
|
|
155
|
+
} else if (totals.inbox > 0) {
|
|
156
|
+
// 低量收件箱可见性(2026-10-15-classify-surface):>20 才报警导致 1~20 条常态化静默——
|
|
157
|
+
// classify 遥测零消费实证(工具在、入口隐身)。info 级提示给可执行命令(classify 零使用
|
|
158
|
+
// 的接入点);不进 healthy 判定否决——info 信号 healthy 语义见下(healthy=signals 全空,
|
|
159
|
+
// info 在场仍算 healthy=false?否——info 级降噪:healthy 只看 warn 级)。
|
|
160
|
+
mk('inbox', 'info', `知识收件箱 ${totals.inbox} 条待归类`, totals.inbox,
|
|
161
|
+
inboxTitles.slice(0, 5).join(';') + (inboxTitles.length > 5 ? ' 等' : ''),
|
|
162
|
+
'sillyspec knowledge inbox 看清单 → knowledge classify --ql <id> --file <known-issues.md|patterns.md|conventions.md> [--dry-run] 逐条归类')
|
|
155
163
|
}
|
|
156
164
|
if (totals.pseudo > 0) {
|
|
157
165
|
mk('pseudo-domain', 'warn', `伪域在库(auto-*/unmapped 共 ${totals.pseudo} 条)`, totals.pseudo,
|
|
@@ -166,7 +174,9 @@ export function collectKnowledgeDigest({ specBase, projectRoot }) {
|
|
|
166
174
|
|
|
167
175
|
return {
|
|
168
176
|
generated_at: new Date().toISOString(),
|
|
169
|
-
healthy
|
|
177
|
+
// healthy 只看 warn 级(2026-10-15-classify-surface):info 是提示不是超阈——
|
|
178
|
+
// 低量收件箱提示不该把治理卡打成人人自危的告警面;平台 governance 消费同语义。
|
|
179
|
+
healthy: !signals.some((s) => s.level === 'warn'),
|
|
170
180
|
signals,
|
|
171
181
|
totals,
|
|
172
182
|
}
|
|
@@ -176,15 +186,22 @@ export function collectKnowledgeDigest({ specBase, projectRoot }) {
|
|
|
176
186
|
export function renderKnowledgeDigestText(d) {
|
|
177
187
|
const lines = []
|
|
178
188
|
lines.push(`📬 知识资产治理摘要(${d.generated_at.slice(0, 16).replace('T', ' ')})`)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
lines.push(`⚠️ ${
|
|
183
|
-
for (const s of
|
|
189
|
+
const warns = d.signals.filter((s) => s.level === 'warn')
|
|
190
|
+
const infos = d.signals.filter((s) => s.level === 'info')
|
|
191
|
+
if (warns.length > 0) {
|
|
192
|
+
lines.push(`⚠️ ${warns.length} 类信号超阈:`)
|
|
193
|
+
for (const s of warns) {
|
|
184
194
|
lines.push(` [${s.kind}] ${s.title}`)
|
|
185
195
|
if (s.detail) lines.push(` 明细:${s.detail}`)
|
|
186
196
|
if (s.suggestion) lines.push(` 处置:${s.suggestion}`)
|
|
187
197
|
}
|
|
198
|
+
} else {
|
|
199
|
+
lines.push('✅ 超阈信号零(inbox/伪域/坏绑定)——健康态')
|
|
200
|
+
}
|
|
201
|
+
for (const s of infos) {
|
|
202
|
+
lines.push(`ℹ️ [${s.kind}] ${s.title}`)
|
|
203
|
+
if (s.detail) lines.push(` 明细:${s.detail}`)
|
|
204
|
+
if (s.suggestion) lines.push(` 处置:${s.suggestion}`)
|
|
188
205
|
}
|
|
189
206
|
lines.push(`底数:收件箱 ${d.totals.inbox}|伪域条目 ${d.totals.pseudo}|坏绑定 ${d.totals.unresolvedBindings}`)
|
|
190
207
|
return lines.join('\n')
|