sillyspec 3.32.1 → 3.32.2
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-explore/SKILL.md +6 -1
- package/package.json +2 -2
- package/src/doctor-diagnostics.js +84 -4
- package/src/flow-review.js +4 -0
- package/src/flow.js +7 -2
- package/src/init.js +5 -1
- package/src/knowledge-graph.js +918 -0
- package/src/knowledge-match.js +11 -6
- package/src/knowledge-vector.js +17 -3
- package/src/run/complete.js +16 -2
- package/src/sentinel-assertions.js +27 -2
- package/src/stages/explore.js +5 -1
- package/src/stages/knowledge.js +11 -1
- package/src/watcher.js +6 -2
package/src/knowledge-match.js
CHANGED
|
@@ -62,7 +62,7 @@ function isDecisionRoute(entry) {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
const DECISION_HEADER_RE = /^##\s+(D-\d+@v\d+)\s*(.*)$/
|
|
65
|
-
const DECISION_FIELD_RE = /^(
|
|
65
|
+
const DECISION_FIELD_RE = /^(状态|文件|理由|否决理由|复潮条件|supersedes)\s*[::]\s*(.*)$/
|
|
66
66
|
// 「锚点:」单独标签精确匹配读入(仿 docs-check.js parseDecisionEntries 先例)——绝不并入
|
|
67
67
|
// DECISION_FIELD_RE:其 else-if 链末尾的 reason 回填(else if (!cur.reason))会把锚点值
|
|
68
68
|
// 误吞进 reason(Grill X-002 隐性回归)。
|
|
@@ -87,15 +87,19 @@ const ANCHOR_FILE_TOKEN_RE = /[\w./-]+\.(?:mjs|cjs|jsx|tsx|js|ts|py|go|java|rs)/
|
|
|
87
87
|
* 锚点值 → 代码文件路径集(条目无「文件:」字段时的兜底命中源)。
|
|
88
88
|
* 真实先例:D-905 形态「锚点:src/quicklog.js:493」→ 提取 src/quicklog.js
|
|
89
89
|
* (token 字符类不含 : 故止于行号前,再防御性剥 :line/:line-line/:符号 后缀)。
|
|
90
|
-
* 「未记录」/无路径形态 token → []
|
|
90
|
+
* 「未记录」/无路径形态 token → []。导出供 knowledge-graph.js 采集 anchors 边复用。
|
|
91
91
|
*/
|
|
92
|
-
function anchorFilePaths(anchor) {
|
|
92
|
+
export function anchorFilePaths(anchor) {
|
|
93
93
|
const a = String(anchor || '').trim().replace(/\\/g, '/')
|
|
94
94
|
if (!a || a === '未记录') return []
|
|
95
95
|
const out = new Set()
|
|
96
96
|
for (const token of a.match(ANCHOR_FILE_TOKEN_RE) || []) {
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
// 前导斜杠剥除(2026-10-09-graph-docrefs-noise):repo:// URL 被正则吃成 //sillyhub/... 碎片,
|
|
98
|
+
// 剥后 sillyhub/ 顶级由图侧按跨仓口径处理;纯数字/裸扩展段丢弃(解析噪声)
|
|
99
|
+
const p = token
|
|
100
|
+
.replace(/:(?:\d+(?:-\d+)?|[A-Za-z_$][A-Za-z0-9_$]*)$/, '')
|
|
101
|
+
.replace(/^\/+/, '')
|
|
102
|
+
if (p && !/^\d+$/.test(p)) out.add(p)
|
|
99
103
|
}
|
|
100
104
|
return [...out]
|
|
101
105
|
}
|
|
@@ -129,7 +133,7 @@ function parseDecisionFile(filePath, file) {
|
|
|
129
133
|
const h = line.match(DECISION_HEADER_RE)
|
|
130
134
|
if (h) {
|
|
131
135
|
flush()
|
|
132
|
-
cur = { file, id: h[1], title: h[2].trim(), status: '', reason: '', revisitWhen: '', files: [], anchor: '', change: '' }
|
|
136
|
+
cur = { file, id: h[1], title: h[2].trim(), status: '', reason: '', revisitWhen: '', files: [], anchor: '', change: '', supersedesText: '' }
|
|
133
137
|
continue
|
|
134
138
|
}
|
|
135
139
|
if (!cur) continue
|
|
@@ -145,6 +149,7 @@ function parseDecisionFile(filePath, file) {
|
|
|
145
149
|
const value = f[2].trim()
|
|
146
150
|
if (f[1] === '状态') cur.status = value.toLowerCase()
|
|
147
151
|
else if (f[1] === '文件') cur.files = splitDecisionFiles(value)
|
|
152
|
+
else if (f[1] === 'supersedes') cur.supersedesText = value
|
|
148
153
|
else if (f[1] === '否决理由') cur.reason = value
|
|
149
154
|
else if (f[1] === '复潮条件') cur.revisitWhen = value
|
|
150
155
|
else if (!cur.reason) cur.reason = value // 理由:仅作 implemented(或否决理由缺失)时的回填
|
package/src/knowledge-vector.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* knowledge-vector.js — 平台向量召回层(CLI 侧,2026-09-29-knowledge-vector-recall)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* 检索四层(2026-10-08-knowledge-graph 起):路由 tag 命中 →(零命中)平台向量召回(本模块,
|
|
5
|
+
* 已连接平台时)→ scope 遍历召回(knowledge-graph.js,scopeFiles 在场时)→ 本地词片复现窗口
|
|
6
|
+
* (knowledge-match.js fallbackByQueryShingles)→ 空。
|
|
6
7
|
*
|
|
7
8
|
* 原则(用户裁决 2026-09-29):平台只做语义召回——返回「哪个文件哪个条目」的候选
|
|
8
9
|
* (spec_path+anchor+score);条目 status/deathPath/回显资格等全部策略面在本地解析
|
|
@@ -143,8 +144,11 @@ function buildResultFromPlatform(indexDir, pvResults) {
|
|
|
143
144
|
|
|
144
145
|
/**
|
|
145
146
|
* 异步三层检索入口(四消费方使用:flow 注入段/complete 门/prompt {DECISION_HITS}/
|
|
146
|
-
* knowledge search CLI):路由 → 平台向量(已连接且开关开)→ 本地词片 → 空。
|
|
147
|
+
* knowledge search CLI):路由 → 平台向量(已连接且开关开)→ scope 遍历 → 本地词片 → 空。
|
|
147
148
|
* opts.cwd = 仓根(平台配置与 local.yaml 定位用);缺省由 indexDir 上推两级。
|
|
149
|
+
* opts.scopeFiles(2026-10-08-knowledge-graph,FR-04 承接 FR-cli-entry-234):变更触碰文件集
|
|
150
|
+
* ——机器算的图查询键(D-004:flow 注入段传 touched、complete 门传 decisions.md 锚点提取)。
|
|
151
|
+
* 缺省/空数组 → 遍历层整体跳过,行为与不携带该参数的旧行为逐字段一致(回归钉死)。
|
|
148
152
|
*/
|
|
149
153
|
export async function matchKnowledgeHybrid(indexDir, taskContext, opts = {}) {
|
|
150
154
|
const r = matchByRouting(indexDir, taskContext)
|
|
@@ -155,6 +159,16 @@ export async function matchKnowledgeHybrid(indexDir, taskContext, opts = {}) {
|
|
|
155
159
|
const built = pv ? buildResultFromPlatform(indexDir, pv.results) : null
|
|
156
160
|
if (built) return built
|
|
157
161
|
}
|
|
162
|
+
// scope 遍历层(第三层):只在 scope 非空且向量零命中后接管;fail-soft(图构建异常静默降级词片)
|
|
163
|
+
const scopeFiles = Array.isArray(opts.scopeFiles) ? opts.scopeFiles.filter(Boolean) : []
|
|
164
|
+
if (scopeFiles.length > 0) {
|
|
165
|
+
try {
|
|
166
|
+
const { buildKnowledgeGraph, scopeRecall, buildScopeRecallResult } = await import('./knowledge-graph.js')
|
|
167
|
+
const specRoot = String(indexDir).replace(/[\\/]knowledge[\\/]?$/, '')
|
|
168
|
+
const built = buildScopeRecallResult(scopeRecall(buildKnowledgeGraph(specRoot), scopeFiles))
|
|
169
|
+
if (built) return built
|
|
170
|
+
} catch { /* 图构建 fail-soft——降级词片层 */ }
|
|
171
|
+
}
|
|
158
172
|
const fb = fallbackByQueryShingles(indexDir, taskContext)
|
|
159
173
|
return fb || r
|
|
160
174
|
}
|
package/src/run/complete.js
CHANGED
|
@@ -479,10 +479,23 @@ export async function completeStep(pm, progress, stageName, cwd, outputText, inp
|
|
|
479
479
|
_kgQuery = _kgQuery.slice(0, 4000)
|
|
480
480
|
if (_kgQuery.trim()) {
|
|
481
481
|
const { matchKnowledgeHybrid } = await import('../knowledge-vector.js')
|
|
482
|
-
const
|
|
482
|
+
const { scopeFromDecisionsMd } = await import('../knowledge-graph.js')
|
|
483
|
+
// scope 键=机器算(D-004 需求期入口):decisions.md「锚点:」字段路径提取——方案步的
|
|
484
|
+
// 结构化声明面,不解析自由文本。词面零命中时沿强边保底防复潮(FR-04)。
|
|
485
|
+
const km = await matchKnowledgeHybrid(join(specBase, 'knowledge'), _kgQuery, { cwd, scopeFiles: scopeFromDecisionsMd(_dm) })
|
|
483
486
|
// 零分不弹(2026-09-28-knowledge-gate-denoise):score 零且非死路的 rejected 与查询零主题
|
|
484
487
|
// 重叠——空标题条目靠状态蹭进回显是行为实测三例的噪音源;死路条目不受限(防复潮先验)。
|
|
485
488
|
const _hits = (km.decisionHits || []).filter((h) => h.deathPath || (h.status === 'rejected' && h.score > 0))
|
|
489
|
+
// impact 防复潮可达集(2026-10-09-brainstorm-impact-antirevival):锚点+模块域双结构键
|
|
490
|
+
// → graphImpact 闭包内 rejected/死路保底进场——方案步锚点常空(confirmed 才必填),
|
|
491
|
+
// 模块域是生成规范文件步硬校验的在场键;viaImpact 条目绕过 score 门槛(结构可达即
|
|
492
|
+
// 防复潮先验,与死路同待遇)。图构建 fail-soft(失败不阻断门)。
|
|
493
|
+
try {
|
|
494
|
+
const { buildKnowledgeGraph, impactFromDecisionsMd } = await import('../knowledge-graph.js')
|
|
495
|
+
const _viaImpact = impactFromDecisionsMd(buildKnowledgeGraph(specBase), _dm)
|
|
496
|
+
.filter((h) => !_hits.some((x) => x.file === h.file && x.id === h.id && (x.change || '') === (h.change || '')))
|
|
497
|
+
_hits.push(..._viaImpact)
|
|
498
|
+
} catch { /* 图构建/键解析 fail-soft——词面命中面不受影响 */ }
|
|
486
499
|
// 已回应不重弹:decisions.md 正文已含「命中 id+域文件名」共现(小白鼠回应形态
|
|
487
500
|
// 「unmapped.md D-001@v1」)→ 视为已甄别过,静默;同轮查询串仍含其词面属预期。
|
|
488
501
|
const _answered = _dm
|
|
@@ -500,7 +513,8 @@ export async function completeStep(pm, progress, stageName, cwd, outputText, inp
|
|
|
500
513
|
const _note = h.status === 'rejected'
|
|
501
514
|
? ` 否决理由:${h.reason || '(未记录)'}`
|
|
502
515
|
: h.deathPath ? ` ⚰️死路注记:${deathPathNote(h.reason)}` : ''
|
|
503
|
-
|
|
516
|
+
const _via = h.viaImpact ? ` 🧭impact 可达(键:${h.impactKey})` : ''
|
|
517
|
+
console.warn(` - ${h.id} ${h.title}(${h.file})status=${h.status || '?'}${_note}${_via}`)
|
|
504
518
|
}
|
|
505
519
|
for (const e of _entries.slice(0, 3)) {
|
|
506
520
|
const _base = e.anchor ? `${e.file}#${e.anchor}` : e.file
|
|
@@ -25,6 +25,24 @@ function taskTokenRe(id) {
|
|
|
25
25
|
return new RegExp(`${id}(?!\\d)`);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* 提交消息连写 token 组展开(坑 flow-done-sentinel-token-split,2026-10-08 实证):
|
|
30
|
+
* `task-01/02/03`、`task-1、2,03` 等自然语言连写组只含首个完整 token——字面匹配判
|
|
31
|
+
* 其余为零完成证据误拒收。展开为独立规范 token(补零到两位,与判集行锚同形)后再判;
|
|
32
|
+
* 尾数不跟数字(防 task-013 版本串误切)。纯函数,供哨兵判定与 autopilot 勾选提取共用。
|
|
33
|
+
*/
|
|
34
|
+
export function expandTaskShorthand(messages) {
|
|
35
|
+
return (Array.isArray(messages) ? messages : []).map((msg) =>
|
|
36
|
+
String(msg ?? '').replace(
|
|
37
|
+
/task-(\d{1,2})((?:\s*[\/、,,;;]\s*\d{1,2}(?!\d))+)/gi,
|
|
38
|
+
(full, first, rest) =>
|
|
39
|
+
' ' + [first, ...(rest.match(/\d{1,2}/g) || [])]
|
|
40
|
+
.map((n) => `task-${String(Number(n)).padStart(2, '0')}`)
|
|
41
|
+
.join(' ') + ' '
|
|
42
|
+
)
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
28
46
|
/**
|
|
29
47
|
* review.json 在场证据清单(execute-runs 两级遍历;异常/缺失 → [],commit 证据照判)。
|
|
30
48
|
*/
|
|
@@ -84,11 +102,13 @@ export function detectFakeCheckCompletion({ changeDir, tasksMd, commits, opts =
|
|
|
84
102
|
return { status: 'none', claimTotal, checked, missing: [] };
|
|
85
103
|
}
|
|
86
104
|
const reviews = listReviewEvidence(changeDir, opts);
|
|
105
|
+
// 连写组先展开(坑 flow-done-sentinel-token-split):task-01/02/03 → 独立 token 再字面判
|
|
106
|
+
const expanded = expandTaskShorthand(messages);
|
|
87
107
|
const missing = entries
|
|
88
108
|
.filter((e) => {
|
|
89
109
|
if (!e.checked) return false;
|
|
90
110
|
const re = taskTokenRe(e.id);
|
|
91
|
-
const byCommit =
|
|
111
|
+
const byCommit = expanded.some((msg) => re.test(msg));
|
|
92
112
|
const byReview = reviews.some((p) => p.includes(`/tasks/${e.id}/`));
|
|
93
113
|
return !(byCommit || byReview);
|
|
94
114
|
})
|
|
@@ -109,7 +129,12 @@ export function detectFakeCheckCompletion({ changeDir, tasksMd, commits, opts =
|
|
|
109
129
|
export function detectBatchCheckCadence(events) {
|
|
110
130
|
const taskEvents = [];
|
|
111
131
|
for (const e of events || []) {
|
|
112
|
-
if (e
|
|
132
|
+
if (!e || e.kind !== 'task-done') continue;
|
|
133
|
+
// 节奏域过滤(2026-10-08-batch-tick-false-positive):只认任务队列面(stage='tasks',缺省
|
|
134
|
+
// 视同 tasks 兼容 legacy 流)。其他阶段的 task-done(如 design.md 自审预勾的首现计数)不是
|
|
135
|
+
// tick 信号——混入会把逐格勾选误判成单拍大跳(实证:design 0→8 拒收六次独立 tick)。
|
|
136
|
+
if (e.stage != null && e.stage !== 'tasks') continue;
|
|
137
|
+
taskEvents.push(e);
|
|
113
138
|
}
|
|
114
139
|
const parse = (e) => {
|
|
115
140
|
const m = /^checked (\d+)→(\d+)$/.exec(String(e.detail || ''));
|
package/src/stages/explore.js
CHANGED
|
@@ -15,7 +15,11 @@ export const definition = {
|
|
|
15
15
|
- \`.sillyspec/docs/<project>/scan/ARCHITECTURE.md\`
|
|
16
16
|
- \`.sillyspec/docs/<project>/scan/CONVENTIONS.md\`
|
|
17
17
|
- \`.sillyspec/changes/<change-name>/design.md\`
|
|
18
|
-
3.
|
|
18
|
+
3. 调查兵器按话题选(话题→兵器映射,全只读):
|
|
19
|
+
- 词面考古("这段代码在哪/怎么写的")→ \`rg\` / \`ls\` / \`cat\`
|
|
20
|
+
- 坑史检索("这话题以前踩过什么坑")→ \`sillyspec knowledge search --query "<关键词>"\`(四层召回)
|
|
21
|
+
- 关系面(影响面/历史决策/需求谱系)→ \`sillyspec knowledge graph impact <文件|模块|变更名>\` 多跳闭包与防复潮可达的已否决决策、\`neighbors <锚点>\` / \`path <A> <B>\` 推理链、\`summary\` 知识面健康度——比较方案前先看 impact 的 rejected/⚰️死路条目,已否决的路不重新兜售;全部支持 \`--json\`
|
|
22
|
+
- 在途面(出方向建议前)→ \`sillyspec status\` 活跃变更一览——结论别跟在途工作撞车
|
|
19
23
|
4. 输出 2-3 个有价值方向、关键风险和下一步建议
|
|
20
24
|
5. 如果用户要求保存结论,先明确保存位置,再写入对应文档
|
|
21
25
|
6. 讨论 UI/交互方案且用户明确要求原型时,按下方「HTML 原型生成」执行
|
package/src/stages/knowledge.js
CHANGED
|
@@ -609,6 +609,16 @@ export async function cmdKnowledge(args, dir, opts = {}) {
|
|
|
609
609
|
return cmdRefresh(dir, args.slice(1), opts)
|
|
610
610
|
case 'propose':
|
|
611
611
|
return cmdPropose(dir, args.slice(1), opts)
|
|
612
|
+
case 'graph': {
|
|
613
|
+
// 知识图谱查询面(2026-10-08-knowledge-graph):neighbors/path/impact/orphans/dangling,
|
|
614
|
+
// --edges 边型筛选;全 JSON 出口(与 search/validate 同族)。
|
|
615
|
+
const mod = await tryImportSubcommandImpl('../knowledge-graph.js', 'knowledge-graph.js')
|
|
616
|
+
if (!mod || typeof mod.cmdKnowledgeGraph !== 'function') {
|
|
617
|
+
output(false, {}, { code: 'not_implemented', subcommand: 'graph' })
|
|
618
|
+
return
|
|
619
|
+
}
|
|
620
|
+
return mod.cmdKnowledgeGraph(dir, args.slice(1), opts)
|
|
621
|
+
}
|
|
612
622
|
case 'classify': {
|
|
613
623
|
const mod = await tryImportSubcommandImpl('../knowledge-classify.js', 'knowledge-classify.js')
|
|
614
624
|
if (!mod || typeof mod.cmdKnowledgeClassify !== 'function') {
|
|
@@ -665,7 +675,7 @@ export async function cmdKnowledge(args, dir, opts = {}) {
|
|
|
665
675
|
output(false, {}, {
|
|
666
676
|
code: 'unknown_subcommand',
|
|
667
677
|
subcommand: subCommand,
|
|
668
|
-
available: ['search', 'inspect', 'validate', 'refresh', 'propose', 'classify', 'inbox', 'stats', 'digest'],
|
|
678
|
+
available: ['search', 'inspect', 'validate', 'refresh', 'propose', 'classify', 'inbox', 'stats', 'digest', 'graph'], // dump 随 graph 路由面(2026-10-09-graph-dump-layout)
|
|
669
679
|
})
|
|
670
680
|
}
|
|
671
681
|
}
|
package/src/watcher.js
CHANGED
|
@@ -339,8 +339,12 @@ export function inferEvents(prev, next) {
|
|
|
339
339
|
const p = prev.files[key];
|
|
340
340
|
const n = next.files[key];
|
|
341
341
|
if (!p && n) {
|
|
342
|
-
events.push(mk('file', n.stage, `${key} 出现`))
|
|
343
|
-
|
|
342
|
+
events.push(mk('file', n.stage, `${key} 出现`))
|
|
343
|
+
// 勾选节奏域=任务队列文件(tasks.md / tasks/*.md,stage='tasks')。其余阶段文件首现的
|
|
344
|
+
// 勾选计数不是 tick 信号:design.md 自审清单天然预勾(整体写盘的已完成断言)——发了就是
|
|
345
|
+
// 幻影单拍跳(2026-10-08-batch-tick-false-positive 实证:design 首现 0→8 被节奏门误拒
|
|
346
|
+
// 六次独立 tick);tasks.md 首现带已勾仍发(保住「整卡预勾创建」的真实捕捉面)。
|
|
347
|
+
if (n.checked > 0 && n.stage === 'tasks') events.push(mk('task-done', n.stage, `checked 0→${n.checked}`))
|
|
344
348
|
} else if (p && n && p.hash !== n.hash) {
|
|
345
349
|
events.push(mk('file-update', n.stage, `${key} 内容变更`));
|
|
346
350
|
}
|