sillyspec 3.30.0 → 3.32.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-auto/SKILL.md +54 -54
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +2 -2
- package/.claude/skills/sillyspec-commit/SKILL.md +1 -1
- package/.claude/skills/sillyspec-flow/SKILL.md +10 -6
- package/.claude/skills/sillyspec-quick/SKILL.md +116 -18
- package/.zcodeignore +66 -0
- package/AGENTS.md +15 -15
- package/assets/command-cards/flow.md +9 -3
- package/assets/command-cards/run-quick.md +4 -1
- package/package.json +2 -2
- package/src/agent-session-log.js +27 -5
- package/src/config-schema.js +21 -0
- package/src/datetime.js +51 -0
- package/src/db.js +10 -3
- package/src/decision-distill.js +21 -12
- package/src/design-facts.js +46 -6
- package/src/flow-draft.js +468 -95
- package/src/flow-parity.js +62 -4
- package/src/flow-review.js +136 -6
- package/src/flow.js +729 -125
- package/src/foreign-declared.js +71 -3
- package/src/fr-index.js +148 -162
- package/src/git-helper.js +16 -3
- package/src/hooks/worktree-guard.js +16 -5
- package/src/hunk-attribution.js +28 -5
- package/src/index.js +184 -17
- package/src/knowledge-classify.js +3 -1
- package/src/knowledge-digest.js +55 -26
- package/src/knowledge-match.js +135 -7
- package/src/knowledge-stats.js +66 -4
- package/src/knowledge-vector.js +160 -0
- package/src/machine-interface.js +90 -2
- package/src/module-impact.js +22 -1
- package/src/modules.js +9 -0
- package/src/progress/change-registry.js +20 -0
- package/src/progress/stage-machine.js +4 -7
- package/src/progress.js +8 -1
- package/src/quicklog.js +20 -0
- package/src/redomain.js +23 -7
- package/src/route-hindsight.js +276 -0
- package/src/run/archive-distill.js +3 -1
- package/src/run/command.js +65 -12
- package/src/run/complete-handlers.js +219 -5
- package/src/run/complete.js +77 -11
- package/src/run/gate-snapshot.js +15 -0
- package/src/run/gates.js +60 -4
- package/src/run/prompt.js +159 -31
- package/src/run/quick-audit.js +66 -7
- package/src/run/shared.js +6 -11
- package/src/run/stage.js +5 -1
- package/src/run/verify-quality-scan.js +84 -0
- package/src/scan-facts.js +2 -1
- package/src/scope-audit.js +9 -2
- package/src/sentinel-assertions.js +67 -16
- package/src/stage-contract-engine.js +4 -1
- package/src/stage-contract-spec.js +103 -4
- package/src/stage-contract.js +55 -7
- package/src/stages/brainstorm.js +27 -14
- package/src/stages/knowledge.js +4 -3
- package/src/stages/plan-postcheck.js +98 -3
- package/src/stages/plan.js +22 -2
- package/src/stages/verify.js +2 -1
- package/src/task-review.js +1 -1
- package/src/task-tick.js +121 -0
- package/src/taskcard-frontmatter.js +31 -0
- package/src/ui-visual.js +43 -8
- package/src/verify-postcheck.js +509 -37
- package/src/verify-probes.js +216 -5
- package/src/watcher.js +1067 -1031
- package/src/worktree-apply.js +9 -1
- package/src/worktree.js +21 -6
- package/templates/agents-instruction.md +15 -15
- package/templates/prompts/taskcard-rules.md +1 -0
package/src/config-schema.js
CHANGED
|
@@ -74,6 +74,7 @@ export const LOCAL_YAML_SCHEMA = {
|
|
|
74
74
|
{ path: 'commands.lint_timeout_sec', type: 'number', optional: true, status: 'live', readers: ['runVerifyLintCheck (src/verify-postcheck.js)'], desc: 'commands.lint 超时秒数——优先级 显式调用参 > 本键 > env SILLYSPEC_LINT_TIMEOUT_MS > 缺省 180(快照内 junction I/O 慢,快照路径调用侧显式传 300)。', example: '600' },
|
|
75
75
|
{ path: 'commands.smoke', type: 'string', optional: true, status: 'live', readers: ['executeVerifyQualityScan (src/run/verify-quality-scan.js)'], desc: '接口冒烟命令——verify 阶段 CLI 亲跑(脚本自理服务生命周期:后台起服+轮询就绪+finally 杀),300s 超时帽,失败/超时只记失败态不阻断本步、触发 PASS 封顶(D-002);指纹含此键,未变不重跑。', example: 'node scripts/smoke.mjs' },
|
|
76
76
|
{ path: 'commands.install', type: 'string', optional: true, status: 'live', readers: ['extractInstallCommand (src/worktree-deps.js)'], desc: '依赖安装命令(worktree 依赖 provisioning 用)。', example: 'npm install' },
|
|
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' },
|
|
77
78
|
],
|
|
78
79
|
},
|
|
79
80
|
{
|
|
@@ -120,6 +121,7 @@ export const LOCAL_YAML_SCHEMA = {
|
|
|
120
121
|
title: '测试策略',
|
|
121
122
|
note: 'verify 阶段 CLI 对账的收窄策略(D-005@v2:skip 接线兑现 + evidence-auto 新增,full/module 语义不变)。',
|
|
122
123
|
keys: [
|
|
124
|
+
{ path: 'verify.test_rerun', type: 'enum', values: ['incremental', 'full'], optional: true, status: 'live', readers: ['readTestRerunConfig (src/verify-postcheck.js — dynamic-subset 分支增量档)'], desc: '实测门失败后重跑档:incremental(缺省)= 前轮失败后,下轮只跑「失败批测试文件 ∪ 自失败基线以来变更文件」的三源推断面(未触碰绿面复用,返工轮分钟级);full = 恒全子集重跑(2026-10-07 前现状,保守项目用)。env SILLYSPEC_TEST_RERUN 优先。仅作用 dynamic-subset 档(显式 test_strategy: full 不受影响)。', example: 'verify: { test_rerun: full }' },
|
|
123
125
|
{ path: 'test_strategy', type: 'enum', values: ['full', 'module', 'skip', 'evidence-auto'], optional: true, status: 'live', readers: ['extractTestStrategy (src/verify-postcheck.js)', 'resolveTestStrategy (src/verify-postcheck.js)'], desc: '【2026-09-26-dynamic-test-inference 起】full=全量(commands.test 在场生效,否则结构推断);module=已退役折算动态子集(超集覆盖:变更文件面收窄 + FR 关联回归 + import 依赖);skip=真跳过(R-07 不变);evidence-auto=按 module-impact.md 推荐(行为→动态子集、文档/门禁→skip)。缺省=动态子集:三源并集非空实测、空则不硬跑全量(防超时/预存失败面)。', example: 'full' },
|
|
124
126
|
],
|
|
125
127
|
},
|
|
@@ -188,6 +190,15 @@ export const LOCAL_YAML_SCHEMA = {
|
|
|
188
190
|
{ path: 'stage.wall', type: 'enum', values: ['advisory', 'hard'], optional: true, status: 'live', readers: ['readStageWall (src/run/shared.js) + runStage 入口墙 (src/run/stage.js)'], desc: '阶段边界会话墙:advisory(缺省)= 阶段完成时提示可 handoff 换会话,不阻断;hard = 同一会话(SILLYSPEC_SESSION_ID 相同)刚收口前驱阶段后硬续墙后阶段(execute/verify)→ run <stage> 拒启动(exit 1),指引 sillyspec handoff 生成交接块换新会话续跑;--same-session 显式逃生口(应急/平台编排)。墙后阶段 = execute/verify(上下文最贵两段:R15 实测 execute 均轮 263K,断崖拆会话后回落 120-170K)。stage-session ledger 缺失/会话标识不可判定 → 放行(fail-open,与账本 best-effort 一致)。', example: 'hard' },
|
|
189
191
|
],
|
|
190
192
|
},
|
|
193
|
+
{
|
|
194
|
+
id: 'plan',
|
|
195
|
+
title: '计划阶段派发',
|
|
196
|
+
note: 'plan 阶段 TaskCard 填充派发策略。默认主 agent 直填(2026-09-20 对撞实验:6 个填卡子代理 6.7M token 纯誊写,独立性价值为零);task 总数超阈值且跨模块才 batch 派子代理——阈值按项目体系可调(厚流程实证 11 任务 3 子代理 430 万 token 模板化产出后引入)。',
|
|
197
|
+
keys: [
|
|
198
|
+
{ path: 'plan.fill_batch_min_tasks', type: 'integer', optional: true, status: 'live', readers: ['buildCoordinatorStep (src/stages/plan.js — 协调器 prompt 阈值插值)'], desc: '填卡 batch 派发阈值:task 总数 ≤N(或单仓单模块)→ 主 agent 直填不派子代理;>N 且跨模块/跨仓才按 batch 分派。≥1 整数,缺省 8(内置);非法值回退 8(fail-safe)。调大 = 更少派子代理省 token;调小 = 大变更更早并行省墙钟。', example: '12' },
|
|
199
|
+
{ path: 'plan.auto_adopt_waves', type: 'boolean', optional: true, status: 'live', readers: ['executePlanPostcheck 自动重排门 (src/stages/plan-postcheck.js — tryAutoAdoptWaves)'], desc: 'Wave 形态错误自动重排:plan postcheck 发现「同 Wave 冲突/伪并行串行链」且无其他类错误时,自动跑 plan-adopt-waves 按 depends_on 拓扑重排并整体复跑校验(预算 1 次防环)。缺省 true;置 false 关闭(报错指路手工跑,2026-10-07 前现状)。方向违规自动修复(既有)不受本键影响。', example: 'true' },
|
|
200
|
+
],
|
|
201
|
+
},
|
|
191
202
|
{
|
|
192
203
|
id: 'auto_mode',
|
|
193
204
|
title: '变更规模自动分类',
|
|
@@ -362,6 +373,7 @@ commands:
|
|
|
362
373
|
# install: npm install # worktree 依赖安装命令
|
|
363
374
|
# test_timeout_sec: 2400 # commands.test 超时秒数(慢仓提帽;缺省 600,env SILLYSPEC_TEST_TIMEOUT_MS 同效)
|
|
364
375
|
# lint_timeout_sec: 600 # commands.lint 超时秒数(缺省 180)
|
|
376
|
+
# knowledge-gate: false # 方案步 --done 知识检索命中回显开关(缺省 true 开;false 关回显)
|
|
365
377
|
|
|
366
378
|
# ── SillyHub MCP 客户端凭据(派发到 worker 用)──
|
|
367
379
|
# agent 手填,或 sillyspec platform connect 在 mcp 段缺失时同源自动填。
|
|
@@ -409,6 +421,8 @@ dispatch:
|
|
|
409
421
|
# skip=真跳过测试(不回退全量,verify 输出显式标注留审计痕迹)
|
|
410
422
|
# evidence-auto=按变更 module-impact.md 影响面推荐(行为→动态子集、文档→docs-check、门禁→gate)
|
|
411
423
|
# test_strategy: full # 缺省注释态=动态子集(评审 P3-5 清偿:复制即全量道的自相矛盾陷阱——缺省注释才与上方「缺省=动态子集」一致);确要全量再解注释
|
|
424
|
+
# verify:
|
|
425
|
+
# test_rerun: incremental # 实测门失败后重跑档:incremental(缺省,下轮只跑失败批∪修复面)| full(恒全子集重跑,保守项目用)
|
|
412
426
|
|
|
413
427
|
# ── 证据门档位(2026-09-27-ui-visual-guidance / 2026-09-27-hunk-attribution-gate;注释态=缺省档位生效)──
|
|
414
428
|
# ui_visual_gate: warn # UI 视觉证据分级门:warn(缺省,缺 visual-evidence.md 仅警告)| error(缺证据即阻断)| off(探针整体关闭;视觉降级无用户裁决留痕时无论档位恒阻断)
|
|
@@ -503,6 +517,13 @@ docs-check:
|
|
|
503
517
|
# change-ownership:
|
|
504
518
|
# heartbeat_minutes: 15 # 所有者活跃心跳窗口(分钟)
|
|
505
519
|
|
|
520
|
+
# ── 计划阶段派发(plan:TaskCard 填充派发策略)──
|
|
521
|
+
# 默认主 agent 直填(对撞实验:6 个填卡子代理 6.7M token 纯誊写,独立性价值为零);
|
|
522
|
+
# 仅 task 总数超阈值且跨模块/跨仓才按 batch 派子代理(并行省墙钟有真实收益)。
|
|
523
|
+
# plan:
|
|
524
|
+
# fill_batch_min_tasks: 12 # 填卡 batch 派发阈值(内置 8;调大=更少派子代理省 token,调小=大变更更早并行)
|
|
525
|
+
# auto_adopt_waves: true # Wave 形态错误自动拓扑重排(内置 true;false=报错指路手工跑)
|
|
526
|
+
|
|
506
527
|
# ── 评审仪式档位(ceremony tier:S0~S3 由 CLI 按 blast/span/friction 三轴风险客观定价)──
|
|
507
528
|
# blast 轴输入=项目声明危险面(_module-map.yaml 顶层 blast 段);完成门声明追赶重定价(无摩擦随声明、有摩擦地板不退)。
|
|
508
529
|
# 项目化定价(每个项目体系不一样,起点与阈值可配——只升不降纪律不开放配置):
|
package/src/datetime.js
CHANGED
|
@@ -18,3 +18,54 @@ export function nowWallClock(d = new Date()) {
|
|
|
18
18
|
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} `
|
|
19
19
|
+ `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
|
20
20
|
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 输入 → Date 的私有强转(toWallClock/timeAgo 共用):Date 实例 / epoch 毫秒数 /
|
|
24
|
+
* 时间字符串(解析委托 Date 构造器——开放解析面归语言规范,不建格式枚举白名单)。
|
|
25
|
+
* 无效输入(非三类之一,或解析产物为 NaN 时刻)抛 TypeError,message 含输入的字符串形式。
|
|
26
|
+
*/
|
|
27
|
+
function coerceClock(input, fnName) {
|
|
28
|
+
const d = input instanceof Date ? input
|
|
29
|
+
: (typeof input === 'number' || typeof input === 'string') ? new Date(input)
|
|
30
|
+
: null;
|
|
31
|
+
if (!d || Number.isNaN(d.getTime())) {
|
|
32
|
+
throw new TypeError(`${fnName}: 无法解析的时间输入:${String(input)}`);
|
|
33
|
+
}
|
|
34
|
+
return d;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 任意时刻输入 → 本地墙钟串(YYYY-MM-DD HH:mm:ss):Date 实例 / epoch 毫秒数 /
|
|
39
|
+
* 时间字符串。无效输入抛 TypeError,message 含输入的字符串形式。
|
|
40
|
+
* @param {Date|number|string} input
|
|
41
|
+
* @returns {string}
|
|
42
|
+
*/
|
|
43
|
+
export function toWallClock(input) {
|
|
44
|
+
return nowWallClock(coerceClock(input, 'toWallClock'));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 任意时刻输入 → 人读相对时间串,档位与进度面板(stage-machine._timeAgo)逐字同构:
|
|
49
|
+
* 差值折算分钟 floor 后 <1(含负差/未来时间)→「刚刚」;<60 →「N 分钟前」;折算小时
|
|
50
|
+
* 后 <24 →「N 小时前」;否则「N 天前」。人读串硬编码不依赖 locale/ICU(与 nowWallClock
|
|
51
|
+
* 同理:Node small-icu 构建下 toLocaleString 不可靠)。
|
|
52
|
+
* @param {Date|number|string} input
|
|
53
|
+
* @param {Date|number} [now] 可注入「当前时刻」(测试钉档位边界用),缺省当前时间;
|
|
54
|
+
* 仅接受 Date 实例或 epoch 毫秒,非法值抛 TypeError
|
|
55
|
+
* @returns {string}
|
|
56
|
+
*/
|
|
57
|
+
export function timeAgo(input, now = new Date()) {
|
|
58
|
+
const ts = coerceClock(input, 'timeAgo').getTime();
|
|
59
|
+
const nowTs = now instanceof Date ? now.getTime()
|
|
60
|
+
: typeof now === 'number' ? now
|
|
61
|
+
: NaN;
|
|
62
|
+
if (!Number.isFinite(nowTs)) {
|
|
63
|
+
throw new TypeError(`timeAgo: 无法解析的当前时刻:${String(now)}`);
|
|
64
|
+
}
|
|
65
|
+
const minutes = Math.floor((nowTs - ts) / 60000);
|
|
66
|
+
if (minutes < 1) return '刚刚';
|
|
67
|
+
if (minutes < 60) return `${minutes} 分钟前`;
|
|
68
|
+
const hours = Math.floor(minutes / 60);
|
|
69
|
+
if (hours < 24) return `${hours} 小时前`;
|
|
70
|
+
return `${Math.floor(hours / 24)} 天前`;
|
|
71
|
+
}
|
package/src/db.js
CHANGED
|
@@ -71,7 +71,10 @@ export class DB {
|
|
|
71
71
|
// 4. 创建表结构(仅当 schema 版本戳不匹配时——W4-H):
|
|
72
72
|
// schema 已最新则跳过建表,省 DDL 开销。node:sqlite 打开即持久化,
|
|
73
73
|
// _createSchema 内 DDL 直接落盘,无需 _save。
|
|
74
|
-
//
|
|
74
|
+
// 戳只对它伴生的库文件作证:库文件被删而戳残留(孤儿戳)时戳匹配是假信号——
|
|
75
|
+
// _openWithFallback 的全新建库分支以 _freshCreate 标记(2026-10-06-status-multi-active-list
|
|
76
|
+
// 实测:空库无表首个 SELECT 即 no such table 崩溃)。戳在但库有内容的缺表属
|
|
77
|
+
// "手动改 db"边角(SillySpec 不支持),仍不在本层兜底。
|
|
75
78
|
const versionPath = `${this.dbPath}.schema-version`;
|
|
76
79
|
let schemaCurrent = false;
|
|
77
80
|
try {
|
|
@@ -79,7 +82,7 @@ export class DB {
|
|
|
79
82
|
&& readFileSync(versionPath, 'utf8').trim() === String(DB_SCHEMA_VERSION);
|
|
80
83
|
} catch { /* 戳读取失败保守重跑 schema */ }
|
|
81
84
|
|
|
82
|
-
if (!schemaCurrent) {
|
|
85
|
+
if (!schemaCurrent || this._freshCreate) {
|
|
83
86
|
this._createSchema();
|
|
84
87
|
try { writeFileSync(versionPath, String(DB_SCHEMA_VERSION)); } catch { /* 戳写入失败不阻断,下次重跑 */ }
|
|
85
88
|
}
|
|
@@ -183,7 +186,11 @@ export class DB {
|
|
|
183
186
|
|
|
184
187
|
// 3. 都不可用
|
|
185
188
|
if (readValid(this.dbPath) === null) {
|
|
186
|
-
// 主库与备份都不存在 →
|
|
189
|
+
// 主库与备份都不存在 → 全新项目,建空库。
|
|
190
|
+
// 全新标记(2026-10-06-status-multi-active-list):schema 戳只对它伴生的库文件作证——
|
|
191
|
+
// 库文件被删而戳残留时,init() 步骤4 的戳匹配会让全新空库跳过建表(孤儿戳空库,
|
|
192
|
+
// 首个 SELECT 即 no such table 崩溃,实测)。全新空库必须无视残留戳重跑 _createSchema。
|
|
193
|
+
this._freshCreate = true;
|
|
187
194
|
return openDatabase(this.dbPath);
|
|
188
195
|
}
|
|
189
196
|
// 主库曾存在(空/损坏)但无法恢复 → 数据丢失,必须 fail-loud(不静默建空库吞进度)
|
package/src/decision-distill.js
CHANGED
|
@@ -195,20 +195,29 @@ export function parseModulePathsSubset(content) {
|
|
|
195
195
|
let cur = null
|
|
196
196
|
let key = null
|
|
197
197
|
for (const line of content.replace(/\r\n/g, '\n').split('\n')) {
|
|
198
|
+
// 空行/注释行:保状态(列表块内允许空行与注释穿插)
|
|
199
|
+
if (line.trim() === '' || line.trim().startsWith('#')) continue
|
|
198
200
|
const mm = line.match(/^ ([a-zA-Z0-9_-]+):$/)
|
|
199
201
|
if (mm) { cur = mm[1]; modules[cur] = { paths: [] }; key = null; continue }
|
|
200
202
|
if (!cur) continue
|
|
203
|
+
const item = line.match(/^ - (.+)$/)
|
|
204
|
+
if (item && key) { modules[cur][key].push(item[1].trim().replace(/^['"]|['"]$/g, '')); continue }
|
|
201
205
|
const inline = line.match(/^ (paths|core_files): \[(.*)\]$/)
|
|
202
206
|
if (inline) {
|
|
203
207
|
modules[cur][inline[1]] = inline[2].split(',').map(s => s.trim().replace(/^['"]|['"]$/g, '')).filter(Boolean)
|
|
204
208
|
key = null; continue
|
|
205
209
|
}
|
|
206
|
-
const block = line.match(/^ (paths|core_files)
|
|
210
|
+
const block = line.match(/^ (paths|core_files):\s*$/)
|
|
207
211
|
if (block) { key = block[1]; modules[cur][key] = modules[cur][key] || []; continue }
|
|
208
212
|
const doc = line.match(/^ doc: (.+)$/)
|
|
209
213
|
if (doc) { modules[cur].doc = doc[1].trim().replace(/^['"]|['"]$/g, ''); key = null; continue }
|
|
210
|
-
|
|
211
|
-
|
|
214
|
+
// 结构守卫(坑 module-map-list-leak,2026-10-06-wallclock-entry 实测发现):list 收集只在
|
|
215
|
+
// 「6 缩进项行 / 空行 / 注释」上存活,其余任何行——字段头(字段名开放集不枚举)、顶层段
|
|
216
|
+
// (span_risk:/blast:)、异缩进项(2 缩进 "- prefixes:")——终结收集。此前 paths: 块后的
|
|
217
|
+
// tags/aliases/entrypoints/depends_on 字段项与顶层 blast prefixes 全部漏进 paths(8 项目
|
|
218
|
+
// map 500 实声明 vs 1375 泄漏项;散文斜杠词 git/DB/JSON 前缀命中 tag 'git' 误路由触达域、
|
|
219
|
+
// bin 模块双重归属 src/db.js)。
|
|
220
|
+
key = null
|
|
212
221
|
}
|
|
213
222
|
return modules
|
|
214
223
|
}
|
|
@@ -518,17 +527,17 @@ export function distillIntoKnowledge(changeDir, knowledgeRoot, headHash, moduleI
|
|
|
518
527
|
const waitParts = []
|
|
519
528
|
const distillable = []
|
|
520
529
|
for (const e of selected) {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
530
|
+
// 标题必填(2026-09-29-decision-route-vocab:裸号条目检索面不可见——路由词形派生与回显
|
|
531
|
+
// 排序都吃标题,缺标题=知识入库即隐身);rejected 缺否决理由/复潮条件 → 不写盘(其余照常)
|
|
532
|
+
const missing = []
|
|
533
|
+
if (!e.title) missing.push('标题(## D-xxx@vN 后接短标题——检索词形来源,裸号条目入库即隐身)')
|
|
534
|
+
if (e.selected === 'rejected' && !e.rejectReason) missing.push('否决理由')
|
|
535
|
+
if (e.selected === 'rejected' && !e.revisitWhen) missing.push('复潮条件')
|
|
536
|
+
if (missing.length > 0) waitParts.push(`${e.id} 缺${missing.join('、')}`)
|
|
537
|
+
else distillable.push(e)
|
|
529
538
|
}
|
|
530
539
|
const needsWait = waitParts.length > 0
|
|
531
|
-
? `${waitParts.join(';')}
|
|
540
|
+
? `${waitParts.join(';')}——条目必填字段缺失,补齐后重跑提炼`
|
|
532
541
|
: null
|
|
533
542
|
if (distillable.length === 0) return { written: [], skipped: null, needsWait }
|
|
534
543
|
|
package/src/design-facts.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* decision_module_check_skipped(skipped 原因字符串,接线层包 code)。
|
|
23
23
|
*/
|
|
24
24
|
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
25
|
-
import { join } from 'node:path'
|
|
25
|
+
import { join, relative } from 'node:path'
|
|
26
26
|
import { parseDecisions } from './decision-distill.js'
|
|
27
27
|
import { parseFileChangeListDetailed } from './change-list.js'
|
|
28
28
|
import { parseDesignCoverageByRepo, parseRepoRegistry } from './stages/plan-postcheck.js'
|
|
@@ -295,7 +295,9 @@ export function generateDesignSkeleton({ changeName, decisionsText, author, now
|
|
|
295
295
|
// P2-e provenance 戳(noai-ir-roadmap §5):CLI 骨架出品的可审计标记——铁律 8 收窄为
|
|
296
296
|
//「骨架优先,仅手写补文档才手填元数据」的依据(validateMetadata 未来可据此只认 CLI 戳)
|
|
297
297
|
lines.push(`generated_by: sillyspec-design-init`)
|
|
298
|
-
|
|
298
|
+
// scale 不预填(2026-09-29-brainstorm-exit-thin-default):此前预填 large 是静默厚默认——
|
|
299
|
+
// 不主动改就是五阶段;现由 brainstorm Step 8 规模评估按复杂度/上下文轴落值,拿不准默认 small
|
|
300
|
+
lines.push('scale: "" # TODO:Step 8 规模评估落值——单上下文可吞吐=small(flow start 收编);需 Wave 编排/上下文分片/多阶段治理=large(run plan);拿不准 small')
|
|
299
301
|
lines.push('---')
|
|
300
302
|
lines.push('')
|
|
301
303
|
lines.push(`# 设计文档(Design)— ${change}`)
|
|
@@ -339,7 +341,8 @@ export function generateDesignSkeleton({ changeName, decisionsText, author, now
|
|
|
339
341
|
|
|
340
342
|
lines.push('## 接口定义')
|
|
341
343
|
lines.push('')
|
|
342
|
-
lines.push('<!-- TODO
|
|
344
|
+
lines.push('<!-- TODO(代码类任务必填):方法签名、数据结构。本变更无接口改动时,把下面这行声明粘贴到本节正文(探针矩阵认该声明行,同义式「无接口变更/不涉及接口/零端点」亦可;留在本注释内不生效——2026-10-06-verify-friction-fix): -->')
|
|
345
|
+
lines.push('<!-- 本变更接口面:0 端点(无接口变更) -->')
|
|
343
346
|
lines.push('')
|
|
344
347
|
|
|
345
348
|
lines.push('## 生命周期契约表')
|
|
@@ -415,14 +418,47 @@ function stripPathPlaceholders(p) {
|
|
|
415
418
|
* @param {{ changeDir: string, cwd: string }} opts
|
|
416
419
|
* @returns {{ ok: boolean, errors: Array<{path, message}>, warnings: string[] }}
|
|
417
420
|
*/
|
|
421
|
+
/**
|
|
422
|
+
* 相近既有路径建议(2026-10-07-flow-friction-batch3 task-02):design 清单幻觉路径的
|
|
423
|
+
* basename did-you-mean。受控 BFS:跳过 node_modules/.git/dist/build/venv 类重目录与
|
|
424
|
+
* 隐藏目录,扫描量上限 8000 文件(超限即止——建议是锦上添花不是承诺);只收 basename
|
|
425
|
+
* 全等命中,至多 3 条按路径长度升序(最短=最像根级缺前缀的正确答案)。纯读无副作用。
|
|
426
|
+
* @param {string} root 仓根(或跨仓根)
|
|
427
|
+
* @param {string} normalized 失效路径(已剥占位符)
|
|
428
|
+
* @returns {string[]} 仓根相对 posix 路径建议(≤3 条,零命中返回 [])
|
|
429
|
+
*/
|
|
430
|
+
function suggestClosePaths(root, normalized) {
|
|
431
|
+
const base = normalized.split('/').pop().toLowerCase()
|
|
432
|
+
if (!base || base.length < 3) return []
|
|
433
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', 'out', 'venv', '.venv', '__pycache__', '.runtime', 'coverage', 'target', '.sillyspec'])
|
|
434
|
+
const hits = []
|
|
435
|
+
let scanned = 0
|
|
436
|
+
const queue = [root]
|
|
437
|
+
while (queue.length > 0 && hits.length < 3 && scanned < 8000) {
|
|
438
|
+
const dir = queue.shift()
|
|
439
|
+
let entries
|
|
440
|
+
try { entries = readdirSync(dir, { withFileTypes: true }) } catch { continue }
|
|
441
|
+
for (const e of entries) {
|
|
442
|
+
if (e.name.startsWith('.') || SKIP_DIRS.has(e.name)) continue
|
|
443
|
+
const abs = join(dir, e.name)
|
|
444
|
+
if (e.isDirectory()) { queue.push(abs); continue }
|
|
445
|
+
scanned++
|
|
446
|
+
if (e.name.toLowerCase() === base) {
|
|
447
|
+
hits.push(relative(root, abs).split('\\').join('/'))
|
|
448
|
+
if (hits.length >= 3) break
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return hits.sort((a, b) => a.length - b.length)
|
|
453
|
+
}
|
|
454
|
+
|
|
418
455
|
export function validateDesignFileList({ changeDir, cwd } = {}) {
|
|
419
456
|
const errors = []
|
|
420
457
|
const warnings = []
|
|
421
458
|
try {
|
|
422
459
|
if (!changeDir || !cwd) {
|
|
423
460
|
return { ok: true, errors, warnings: ['design 清单核验跳过:缺少 changeDir/cwd'] }
|
|
424
|
-
}
|
|
425
|
-
const designPath = join(changeDir, 'design.md')
|
|
461
|
+
} const designPath = join(changeDir, 'design.md')
|
|
426
462
|
if (!existsSync(designPath)) {
|
|
427
463
|
return { ok: true, errors, warnings: ['design 清单核验跳过:design.md 不存在'] }
|
|
428
464
|
}
|
|
@@ -440,9 +476,13 @@ export function validateDesignFileList({ changeDir, cwd } = {}) {
|
|
|
440
476
|
const normalized = stripPathPlaceholders(rawPath)
|
|
441
477
|
if (!normalized || normalized === '.') continue
|
|
442
478
|
if (!existsSync(join(root, normalized))) {
|
|
479
|
+
// 相近路径指路(2026-10-07-flow-friction-batch3 task-02):幻觉路径多为缺目录前缀
|
|
480
|
+
// (如漏 backend/)或近似书写——basename did-you-mean 把「三轮试错」压成「一眼改对」;
|
|
481
|
+
// 无相近命中不附建议段(不加噪)
|
|
482
|
+
const close = suggestClosePaths(root, normalized)
|
|
443
483
|
errors.push({
|
|
444
484
|
path: rawPath,
|
|
445
|
-
message: `design_file_ref_invalid:文件变更清单条目「${rawPath}」在${repoLabel}(${root})下不存在且无 NEW: 前缀(幻觉路径/书写错误)——修正路径,或计划新建的文件改为 NEW:${rawPath}
|
|
485
|
+
message: `design_file_ref_invalid:文件变更清单条目「${rawPath}」在${repoLabel}(${root})下不存在且无 NEW: 前缀(幻觉路径/书写错误)——修正路径,或计划新建的文件改为 NEW:${rawPath} 前缀${close.length > 0 ? `;相近既有路径:${close.join('、')}` : ''}`,
|
|
446
486
|
})
|
|
447
487
|
}
|
|
448
488
|
}
|