sillyspec 3.33.5 → 3.33.7
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-config/SKILL.md +22 -0
- package/.claude/skills/sillyspec-push/SKILL.md +79 -0
- package/AGENTS.md +1 -0
- package/README.md +2 -0
- package/SKILL.md +2 -0
- package/assets/command-cards/push.md +38 -0
- package/package.json +2 -2
- package/src/agent-session-log.js +4 -1
- package/src/command-cards.js +1 -1
- package/src/config-schema.js +22 -0
- package/src/deleted-ledger.js +3 -1
- package/src/flow-parity.js +40 -0
- package/src/flow-review.js +19 -3
- package/src/flow.js +70 -19
- package/src/git-helper.js +22 -6
- package/src/index.js +34 -3
- package/src/knowledge-hits.js +80 -1
- package/src/knowledge-scope.js +82 -0
- package/src/progress/change-registry.js +15 -1
- package/src/push.js +934 -0
- package/src/run/complete-handlers.js +30 -29
- package/src/run/shared.js +1 -1
- package/src/stages/knowledge.js +44 -1
- package/src/verify-postcheck.js +142 -3
- package/templates/agents-instruction.md +2 -1
|
@@ -854,40 +854,13 @@ export async function bakeArchiveTimeline({ cwd, specBase, changeName, destDir,
|
|
|
854
854
|
}
|
|
855
855
|
}
|
|
856
856
|
|
|
857
|
-
/**
|
|
858
|
-
* runArchiveChain —— 归档执行链(R7 切片二 task-02 从 archiveChangeDirectory 抽出,纯搬运)。
|
|
859
|
-
*
|
|
860
|
-
* 链面:未 apply 交付面检查(--skip-apply 留痕旁路)→ plan.md 硬校验(skipPlanCheck 旁路:
|
|
861
|
-
* flow thin 轻量工件面无 plan.md,task-03 flow done 归档子步传 true;既有 archive 流程缺省
|
|
862
|
-
* false 行为零变化)→ module-impact pending 死信校验 → 目标目录检查 → 目录搬移(rename 重试)
|
|
863
|
-
* → 时间线烤制(fail-open,2026-09-28-archive-timeline-bake)→ unregisterChange 终态一致化
|
|
864
|
-
* → archiveNarrowedGitAdd 窄化暂存 → 他者半归档残留探测。
|
|
865
|
-
* 消费方:archiveChangeDirectory(既有 archive 流程)与 flow done 归档子步(R7)。
|
|
866
|
-
*
|
|
867
|
-
* @returns {Promise<void>} 失败面沿链内既有 process.exit 语义(纯搬运不改)。
|
|
868
|
-
*/
|
|
869
|
-
/**
|
|
870
|
-
* 归档提交 pathspec 构成(2026-10-06-archive-cmd-race-and-brief:共享暂存区竞态加固)。
|
|
871
|
-
*
|
|
872
|
-
* 背景:旧实现照抄 `git diff --cached --name-status` 单元格——多 agent 共享仓下,兄弟会话/
|
|
873
|
-
* 后台同步可在「打印→执行」窗口瞬改暂存区(实测 flow-status-json 收尾:建议命令含转瞬即逝
|
|
874
|
-
* 的源侧 A 条目,照抄执行报 pathspec did not match;单会话镜像复盘命令干净=竞态即现即逝)。
|
|
875
|
-
*
|
|
876
|
-
* 构成按稳定性分级:
|
|
877
|
-
* - src 侧(changes/<名>/ 非归档):仅取 HEAD 树在册路径——HEAD 不可变,不受暂存区竞态
|
|
878
|
-
* 影响;rename src / 删除 D 恒在册,转瞬即逝的源侧 A 条目恒不在册(天然滤幽灵)。
|
|
879
|
-
* - dst 侧:本变更专属归档目录单条 pathspec(archive/<名>/ 与窄化 add 同一专属前提),
|
|
880
|
-
* 目录在场即匹配,条目级抖动无感。
|
|
881
|
-
* - knowledge/docs 共享面:HEAD 在册(M 形态)或工作区在场(蒸馏新产 A 形态)才入列。
|
|
882
|
-
* - 其余丢弃并回报(dropped),由调用方显式提示。
|
|
883
|
-
*
|
|
884
|
-
* 纯函数(git 读数由调用方喂入),单测直接钉构成口径。
|
|
885
|
-
* @param {{changeName:string, stagedNameStatus:string, headTreeFiles:string, existsFn?:(p:string)=>boolean}} p
|
|
886
857
|
/**
|
|
887
858
|
* 归档落库判定(2026-10-23-flow-done-shared-repo-multi-session-frictions 缺陷③):HEAD 树中
|
|
888
859
|
* 是否已含本变更的归档目录条目(.sillyspec/changes/archive/<changeName>/ 下一层首段)。
|
|
889
860
|
* 目录名口径:archiveDestDirName 现行=裸变更名;`-<changeName>` 后缀兼容历史日期前缀形态。
|
|
890
861
|
* 纯函数(headTreeFiles 由调用方 git ls-tree 取好传入,测试零 git 依赖)。
|
|
862
|
+
* @param {{headTreeFiles:string, changeName:string}} p
|
|
863
|
+
* @returns {boolean}
|
|
891
864
|
*/
|
|
892
865
|
export function archiveLandedInHeadTree({ headTreeFiles, changeName }) {
|
|
893
866
|
const prefix = '.sillyspec/changes/archive/'
|
|
@@ -903,6 +876,22 @@ export function archiveLandedInHeadTree({ headTreeFiles, changeName }) {
|
|
|
903
876
|
}
|
|
904
877
|
|
|
905
878
|
/**
|
|
879
|
+
* 归档提交 pathspec 构成(2026-10-06-archive-cmd-race-and-brief:共享暂存区竞态加固)。
|
|
880
|
+
*
|
|
881
|
+
* 背景:旧实现照抄 `git diff --cached --name-status` 单元格——多 agent 共享仓下,兄弟会话/
|
|
882
|
+
* 后台同步可在「打印→执行」窗口瞬改暂存区(实测 flow-status-json 收尾:建议命令含转瞬即逝
|
|
883
|
+
* 的源侧 A 条目,照抄执行报 pathspec did not match;单会话镜像复盘命令干净=竞态即现即逝)。
|
|
884
|
+
*
|
|
885
|
+
* 构成按稳定性分级:
|
|
886
|
+
* - src 侧(changes/<名>/ 非归档):仅取 HEAD 树在册路径——HEAD 不可变,不受暂存区竞态
|
|
887
|
+
* 影响;rename src / 删除 D 恒在册,转瞬即逝的源侧 A 条目恒不在册(天然滤幽灵)。
|
|
888
|
+
* - dst 侧:本变更专属归档目录单条 pathspec(archive/<名>/ 与窄化 add 同一专属前提),
|
|
889
|
+
* 目录在场即匹配,条目级抖动无感。
|
|
890
|
+
* - knowledge/docs 共享面:HEAD 在册(M 形态)或工作区在场(蒸馏新产 A 形态)才入列。
|
|
891
|
+
* - 其余丢弃并回报(dropped),由调用方显式提示。
|
|
892
|
+
*
|
|
893
|
+
* 纯函数(git 读数由调用方喂入),单测直接钉构成口径。
|
|
894
|
+
* @param {{changeName:string, stagedNameStatus:string, headTreeFiles:string, existsFn?:(p:string)=>boolean}} p
|
|
906
895
|
* @returns {{pathspecs:string[], dropped:string[]}}
|
|
907
896
|
*/
|
|
908
897
|
export function resolveArchiveCommitPathspecs({ changeName, stagedNameStatus, headTreeFiles, existsFn }) {
|
|
@@ -940,6 +929,18 @@ export function resolveArchiveCommitPathspecs({ changeName, stagedNameStatus, he
|
|
|
940
929
|
return { pathspecs, dropped }
|
|
941
930
|
}
|
|
942
931
|
|
|
932
|
+
/**
|
|
933
|
+
* runArchiveChain —— 归档执行链(R7 切片二 task-02 从 archiveChangeDirectory 抽出,纯搬运)。
|
|
934
|
+
*
|
|
935
|
+
* 链面:未 apply 交付面检查(--skip-apply 留痕旁路)→ plan.md 硬校验(skipPlanCheck 旁路:
|
|
936
|
+
* flow thin 轻量工件面无 plan.md,task-03 flow done 归档子步传 true;既有 archive 流程缺省
|
|
937
|
+
* false 行为零变化)→ module-impact pending 死信校验 → 目标目录检查 → 目录搬移(rename 重试)
|
|
938
|
+
* → 时间线烤制(fail-open,2026-09-28-archive-timeline-bake)→ unregisterChange 终态一致化
|
|
939
|
+
* → archiveNarrowedGitAdd 窄化暂存 → 他者半归档残留探测。
|
|
940
|
+
* 消费方:archiveChangeDirectory(既有 archive 流程)与 flow done 归档子步(R7)。
|
|
941
|
+
*
|
|
942
|
+
* @returns {Promise<void>} 失败面沿链内既有 process.exit 语义(纯搬运不改)。
|
|
943
|
+
*/
|
|
943
944
|
export async function runArchiveChain({ pm, cwd, specBase, changeName, srcDir, destDir, skipApply = false, skipPlanCheck = false, deliverableFiles = null, docsIncrement = null, extraDocs = null } = {}) {
|
|
944
945
|
const archiveDir = dirname(destDir) // 抽取补:原闭包变量(changes/archive),从 destDir 推导等价
|
|
945
946
|
// ── 模块文档认领 advisory(资产三小件③,2026-09-23)──
|
package/src/run/shared.js
CHANGED
|
@@ -390,7 +390,7 @@ export function detectCwdInsideWorktree(dir) {
|
|
|
390
390
|
export const UNINIT_CWD_GATE_EXEMPT = new Set([
|
|
391
391
|
'init', 'scan', 'doctor', 'status', 'progress', 'next', 'workspace',
|
|
392
392
|
'setup', 'knowledge', 'local', 'config', 'mcp', 'dashboard', 'platform',
|
|
393
|
-
'wt-commit', 'agent-log',
|
|
393
|
+
'wt-commit', 'push', 'agent-log',
|
|
394
394
|
])
|
|
395
395
|
|
|
396
396
|
/**
|
package/src/stages/knowledge.js
CHANGED
|
@@ -627,6 +627,7 @@ export async function cmdKnowledge(args, dir, opts = {}) {
|
|
|
627
627
|
' inspect <文件> 查看单个知识文件条目结构',
|
|
628
628
|
' validate 校验知识库完整性(INDEX 锚点/注册/幽灵条目——纯读)',
|
|
629
629
|
' digest 治理信号卡(rot/伪域/坏绑定——纯读)',
|
|
630
|
+
' scope 知识注入作用域现状与标记语法(always/repo/module/关键词;纯读)',
|
|
630
631
|
' stats 覆盖率/死条目统计(纯读)',
|
|
631
632
|
' refresh 重扫 scan 文档重写 generated/ 区(**写盘**——先 validate 再用)',
|
|
632
633
|
' propose / classify / inbox / graph / stats / renumber',
|
|
@@ -636,7 +637,7 @@ export async function cmdKnowledge(args, dir, opts = {}) {
|
|
|
636
637
|
'写盘面子命令(refresh/propose/classify)先跑同名只读命令确认现状;探索用法先看本帮助。',
|
|
637
638
|
]
|
|
638
639
|
for (const l of lines) console.log(l)
|
|
639
|
-
output(true, { help: true, subcommands: ['search', 'inspect', 'validate', 'digest', 'refresh', 'propose', 'classify', 'inbox', 'graph', 'stats', 'redomain-unmapped'] })
|
|
640
|
+
output(true, { help: true, subcommands: ['search', 'inspect', 'validate', 'digest', 'refresh', 'propose', 'classify', 'inbox', 'graph', 'stats', 'scope', 'redomain-unmapped'] })
|
|
640
641
|
return
|
|
641
642
|
}
|
|
642
643
|
const subCommand = args[0] || ''
|
|
@@ -706,6 +707,48 @@ export async function cmdKnowledge(args, dir, opts = {}) {
|
|
|
706
707
|
}
|
|
707
708
|
return mod.cmdKnowledgeStats(dir, args.slice(1), opts)
|
|
708
709
|
}
|
|
710
|
+
case 'scope': {
|
|
711
|
+
// 知识注入作用域发现面(2026-10-23-knowledge-scope-discoverability):机制 2026-10-23-
|
|
712
|
+
// knowledge-scope-inject 落地但零发现面(用户追问「在哪引导配置」)——本命令列四档现状+
|
|
713
|
+
// 标记语法教学(示例与 collectScopedKnowledge 解析同源),空态明示仅关键词档在用。
|
|
714
|
+
// 纯读零写盘;--json 单对象供程序化消费。
|
|
715
|
+
try {
|
|
716
|
+
const { collectScopedKnowledge } = await import('../knowledge-scope.js')
|
|
717
|
+
const knowledgeDir = resolveKnowledgeDir(dir, opts.specDir)
|
|
718
|
+
const scoped = collectScopedKnowledge(knowledgeDir)
|
|
719
|
+
const moduleEntries = Object.entries(scoped.modules)
|
|
720
|
+
const total = new Set([...scoped.always, ...scoped.repo, ...moduleEntries.flatMap(([, v]) => v)].map((e) => e.id)).size
|
|
721
|
+
const guide = [
|
|
722
|
+
'作用域标记(HTML 注释,写在 top-level knowledge/<文件>.md 的 section heading 之下;无标记=关键词命中现状):',
|
|
723
|
+
' <!-- scope: always --> 每次变更恒注入(跨仓通用规约)',
|
|
724
|
+
' <!-- scope: repo --> 本仓每次变更恒注入(仓级约束)',
|
|
725
|
+
' <!-- scope: module:<域>[,<域>] --> 仅当触达该知识域时注入(域 id 与 FR 域同源,逗号多域)',
|
|
726
|
+
'查看现状:sillyspec knowledge scope;flow start 注入块中以 📌 行呈现;机制变更:2026-10-23-knowledge-scope-inject',
|
|
727
|
+
]
|
|
728
|
+
if (opts.json || args.slice(1).includes('--json')) {
|
|
729
|
+
output(true, { subcommand: 'scope', ...scoped, total, guide })
|
|
730
|
+
} else {
|
|
731
|
+
console.log(`🧭 知识注入作用域现状(${total} 条 scoped;关键词档始终生效)`)
|
|
732
|
+
if (scoped.always.length > 0) {
|
|
733
|
+
console.log(' always(每次变更恒注入):')
|
|
734
|
+
for (const e of scoped.always) console.log(` - ${e.id}:${e.summary}`)
|
|
735
|
+
}
|
|
736
|
+
if (scoped.repo.length > 0) {
|
|
737
|
+
console.log(' repo(本仓恒注入):')
|
|
738
|
+
for (const e of scoped.repo) console.log(` - ${e.id}:${e.summary}`)
|
|
739
|
+
}
|
|
740
|
+
for (const [d, list] of moduleEntries) {
|
|
741
|
+
console.log(` module:${d}(触达该域时注入):`)
|
|
742
|
+
for (const e of list) console.log(` - ${e.id}:${e.summary}`)
|
|
743
|
+
}
|
|
744
|
+
if (total === 0) console.log(' 当前仅关键词档在用(无任何 scoped 标记)——要恒注入的规约/仓级约束按下述标记声明:')
|
|
745
|
+
for (const l of guide) console.log(` ${l}`)
|
|
746
|
+
}
|
|
747
|
+
} catch (e) {
|
|
748
|
+
output(false, {}, { code: 'scope_failed', message: String((e && e.message) || e) })
|
|
749
|
+
}
|
|
750
|
+
return
|
|
751
|
+
}
|
|
709
752
|
case 'digest': {
|
|
710
753
|
// 治理信号摘要(2026-09-27-knowledge-digest):四类沉睡信号(rot 待复核/收件箱积压/
|
|
711
754
|
// 伪域落库/绑定解析失败)超阈才见人——安静即健康态。--json 供平台 RPC 消费(信号卡)。
|
package/src/verify-postcheck.js
CHANGED
|
@@ -3011,6 +3011,16 @@ function buildDepsBatches({ deps, changedFiles = [], hits = [], cwd = null, prio
|
|
|
3011
3011
|
}
|
|
3012
3012
|
pyGroups.get(key).files.push(f)
|
|
3013
3013
|
}
|
|
3014
|
+
// runner_flags(2026-10-23-runner-flags-generic,语言中立):推断路径按批类别注入声明
|
|
3015
|
+
// flags——批类别=CLI 自有 runner 分类(py|js|jsx|…,未知类别前向兼容,不枚举语言)。
|
|
3016
|
+
// hits 路径的模块命令自带 flags(extractRunnerTrailingFlags),此处不双加;flags 置于
|
|
3017
|
+
// 文件参数之前(node/go 类参数解析在首个位置参数处截断);缺省空=零回归。
|
|
3018
|
+
let runnerFlags = {}
|
|
3019
|
+
try { runnerFlags = readRunnerFlags(specBase) } catch { /* fail-soft 空映射 */ }
|
|
3020
|
+
const flagsSuffix = (kind) => (runnerFlags[kind] ? `${runnerFlags[kind]} ` : '')
|
|
3021
|
+
if (!pyRunner && pyGroups.size > 0 && runnerFlags.py) {
|
|
3022
|
+
for (const grp of pyGroups.values()) grp.command = `${grp.command} ${runnerFlags.py}`
|
|
3023
|
+
}
|
|
3014
3024
|
if (!pyRunner && pyGroups.size > 0) pyRunner = pyGroups.values().next().value.command
|
|
3015
3025
|
// JSX 运行器推断(2026-09-26-residual-runner-parity 起 + 结构推断源):.tsx/.jsx 是 node
|
|
3016
3026
|
// 原生 type stripping 跑不了的(node --test 直跑恒败——R18 verify 段 ~11 次尝试的坑),
|
|
@@ -3077,7 +3087,7 @@ function buildDepsBatches({ deps, changedFiles = [], hits = [], cwd = null, prio
|
|
|
3077
3087
|
}
|
|
3078
3088
|
}
|
|
3079
3089
|
if (jsNative.length > 0) {
|
|
3080
|
-
const jsPrefix = 'node --test --test-reporter=spec --test-reporter-destination=stderr --test-reporter=tap --test-reporter-destination=stdout '
|
|
3090
|
+
const jsPrefix = 'node --test --test-reporter=spec --test-reporter-destination=stderr --test-reporter=tap --test-reporter-destination=stdout ' + flagsSuffix('js')
|
|
3081
3091
|
const jsChunks = chunkByCommandLength(jsPrefix, jsNative)
|
|
3082
3092
|
jsChunks.forEach((chunk, i) => {
|
|
3083
3093
|
const isMulti = jsChunks.length > 1
|
|
@@ -3115,7 +3125,7 @@ function buildDepsBatches({ deps, changedFiles = [], hits = [], cwd = null, prio
|
|
|
3115
3125
|
const multiGroup = jsxGroups.size > 1
|
|
3116
3126
|
for (const [command, grp] of jsxGroups) {
|
|
3117
3127
|
const isVitest = /vitest/.test(command)
|
|
3118
|
-
const jsxPrefix = `${command} ${isVitest ? 'run ' : ''}`
|
|
3128
|
+
const jsxPrefix = `${command} ${isVitest ? 'run ' : ''}${jsxRunnerFromHits ? '' : flagsSuffix('jsx')}`
|
|
3119
3129
|
const grpDir = grp.dirKey || /(?:^|\s)cd\s+(\S+)\s*&&/.exec(command)?.[1] || null
|
|
3120
3130
|
const rebaseJsx = (f) => (grpDir && f.startsWith(grpDir + '/')) ? f.slice(grpDir.length + 1) : f
|
|
3121
3131
|
const jsxArgs = grp.files.map(rebaseJsx).map(quoteWinPath)
|
|
@@ -3371,6 +3381,68 @@ export function applyTraceResidual({ mainResult, action, cwd, specBase, changeNa
|
|
|
3371
3381
|
* export 供直测(buildDepsBatches 同款先例——fixture 需真跑测试文件,不宜经
|
|
3372
3382
|
* runVerifyTestCheck 全链只为断言组卷接线)。
|
|
3373
3383
|
*/
|
|
3384
|
+
/**
|
|
3385
|
+
* runner_flags 读取(2026-10-23-runner-flags-generic,语言中立):local.yaml 的批类别 →
|
|
3386
|
+
* 尾随 flags 映射(批类别 = CLI 自有 runner 分类 py|js|jsx|…,未知类别保留、前向兼容——
|
|
3387
|
+
* 不枚举语言/框架,对齐 AGENTS 规约 00)。嵌套块行式解析(引号/注释/CRLF 容错,块结束于
|
|
3388
|
+
* 去缩进行);py_runner_flags 单键保留为兼容别名(映射到 py)。缺省/坏值/不可读 → 空映射
|
|
3389
|
+
* (零回归)。flags 注入置于文件参数之前(node/go 类参数解析在首个位置参数处截断)。
|
|
3390
|
+
*/
|
|
3391
|
+
export function readRunnerFlags(specBase) {
|
|
3392
|
+
const out = {}
|
|
3393
|
+
try {
|
|
3394
|
+
const raw = normalizeLineEndings(readFileSync(join(specBase, 'local.yaml'), 'utf8'))
|
|
3395
|
+
const legacy = raw.match(/^[ ]*py_runner_flags[ ]*:[ ]*([^#\n]+?)[ ]*(?:#.*)?$/m)
|
|
3396
|
+
if (legacy) {
|
|
3397
|
+
const v = legacy[1].trim().replace(/^["']|["']$/g, '').trim()
|
|
3398
|
+
if (v) out.py = v
|
|
3399
|
+
}
|
|
3400
|
+
const lines = raw.split(String.fromCharCode(10))
|
|
3401
|
+
let inBlock = false
|
|
3402
|
+
let indent = -1
|
|
3403
|
+
for (const line of lines) {
|
|
3404
|
+
if (!inBlock) {
|
|
3405
|
+
if (/^[ ]*runner_flags[ ]*:[ ]*(?:#.*)?$/.test(line)) { inBlock = true; indent = -1 }
|
|
3406
|
+
continue
|
|
3407
|
+
}
|
|
3408
|
+
if (!line.trim() || /^[ ]*#/.test(line)) continue
|
|
3409
|
+
const m = /^([ ]+)([A-Za-z0-9_.:+/-]+)[ ]*:[ ]*([^#\n]+?)[ ]*(?:#.*)?$/.exec(line)
|
|
3410
|
+
if (!m) { if (!/^[ ]/.test(line)) break; continue }
|
|
3411
|
+
if (indent === -1) indent = m[1].length
|
|
3412
|
+
if (m[1].length < indent) { break }
|
|
3413
|
+
const v = m[3].trim().replace(/^["']|["']$/g, '').trim()
|
|
3414
|
+
if (v) out[m[2]] = v
|
|
3415
|
+
}
|
|
3416
|
+
} catch { /* fail-soft 空映射 */ }
|
|
3417
|
+
return out
|
|
3418
|
+
}
|
|
3419
|
+
|
|
3420
|
+
/** py_runner_flags 兼容别名(2026-10-23-py-runner-flags;泛化后保留读口——等价 readRunnerFlags().py)。 */
|
|
3421
|
+
export function readVerifyPyFlags(specBase) {
|
|
3422
|
+
return readRunnerFlags(specBase).py || ''
|
|
3423
|
+
}
|
|
3424
|
+
|
|
3425
|
+
/**
|
|
3426
|
+
* deps_regress_cap 读取(2026-10-23-runner-flags-generic,语言中立):import 依赖面档位——
|
|
3427
|
+
* N(1-2000)= 变更自身测试优先整跑、import 依赖只取前 N 个;off = 只跑变更自身测试(其余
|
|
3428
|
+
* 归 pre-push/CI 门);缺省/坏值/不可读 → null(全跑=现状零回归)。own 判定=变更触碰文件集
|
|
3429
|
+
* ∩ 依赖面(测试文件),与语言/框架无关。
|
|
3430
|
+
*/
|
|
3431
|
+
export function readDepsRegressCap(specBase) {
|
|
3432
|
+
try {
|
|
3433
|
+
const raw = normalizeLineEndings(readFileSync(join(specBase, 'local.yaml'), 'utf8'))
|
|
3434
|
+
const m = raw.match(/^[ ]*deps_regress_cap[ ]*:[ ]*([^#\n]+?)[ ]*(?:#.*)?$/m)
|
|
3435
|
+
if (!m) return null
|
|
3436
|
+
const v = m[1].trim().replace(/^["']|["']$/g, '')
|
|
3437
|
+
if (/^off$/i.test(v)) return 0
|
|
3438
|
+
if (/^\d+$/.test(v)) {
|
|
3439
|
+
const n = Number(v)
|
|
3440
|
+
if (n >= 1 && n <= 2000) return n
|
|
3441
|
+
}
|
|
3442
|
+
return null
|
|
3443
|
+
} catch { return null }
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3374
3446
|
/**
|
|
3375
3447
|
* fr_regress_cap 读取 v2(2026-10-23-fr-regress-default-off,缺省翻转:用户裁决「默认就不跑,
|
|
3376
3448
|
* 配置可以开启」):缺省(无键)/坏值/不可读 → 0(off——FR 关联回归层本地不跑,归 pre-push/CI
|
|
@@ -3414,7 +3486,29 @@ export function runModuleSubset({ cwd, specBase, changeName, hits, knownFailures
|
|
|
3414
3486
|
// 依赖测试伪模块(module-test-face-rot,2026-09-16 本会话实证):硬编码模块测试清单不含
|
|
3415
3487
|
// 变更 src 文件的直接断言测试 → module 收窄漏全量断言。附加执行「import 变更 src 的测试 ∪
|
|
3416
3488
|
// 变更的 test 文件」中未被命中模块命令覆盖的部分;无额外文件零行为(不新增失败面)。
|
|
3417
|
-
|
|
3489
|
+
let deps = discoverModuleDependentTests({ cwd, changedFiles, coveredCommands: hits.map(h => h.test) })
|
|
3490
|
+
// deps_regress_cap(2026-10-23-runner-flags-generic,语言中立):import 依赖面档位——
|
|
3491
|
+
// own(变更触碰文件集∩依赖面)优先整跑,其余按 N 截断/off 全弃(归 pre-push/CI 门)。
|
|
3492
|
+
// 缺省 null=全跑(现状零回归)。own 判定与语言/框架无关(纯路径集合运算)。
|
|
3493
|
+
let depsCapApplied = null
|
|
3494
|
+
try {
|
|
3495
|
+
const depCap = readDepsRegressCap(specBase)
|
|
3496
|
+
if (depCap !== null && deps.length > 0) {
|
|
3497
|
+
const ownSet = new Set((changedFiles || []).map((f) => String(f).split(String.fromCharCode(92)).join('/')))
|
|
3498
|
+
const own = deps.filter((f) => ownSet.has(f))
|
|
3499
|
+
const rest = deps.filter((f) => !ownSet.has(f))
|
|
3500
|
+
if (depCap === 0) {
|
|
3501
|
+
deps = own
|
|
3502
|
+
depsCapApplied = { kept: own.length, total: own.length + rest.length }
|
|
3503
|
+
} else if (rest.length > depCap) {
|
|
3504
|
+
deps = [...own, ...rest.slice(0, depCap)]
|
|
3505
|
+
depsCapApplied = { kept: deps.length, total: own.length + rest.length }
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
} catch { /* 键读取 fail-soft=全跑 */ }
|
|
3509
|
+
if (depsCapApplied) {
|
|
3510
|
+
console.log(`ℹ️ deps_regress_cap:实测依赖面 ${depsCapApplied.kept}/${depsCapApplied.total} 进实测面,其余 ${depsCapApplied.total - depsCapApplied.kept} 个归 pre-push/CI 门(可开启:local.yaml deps_regress_cap: <N|off>——N=前 N 个依赖 / off=只跑变更自身测试;缺省全跑)`)
|
|
3511
|
+
}
|
|
3418
3512
|
// FR 关联回归源(2026-09-26-dynamic-test-inference 三源之二):active FR 覆盖面(来源变更
|
|
3419
3513
|
// patch ∪ 绑定)∩ 本次触碰文件 ≠ ∅ 者,其绑定 tests 解析仓根相对后并入实测面——「需求→
|
|
3420
3514
|
// 用例」映射即测试需求。frPre 在场用调用方预计算(runVerifyTestCheck 决策已扫过,免二次
|
|
@@ -4712,6 +4806,51 @@ export function reconcileTargetFiles({ cwd, specBase = null, changeName = null,
|
|
|
4712
4806
|
if (rescuedCount > 0) {
|
|
4713
4807
|
notes.push(`对账救赎:${rescuedCount} 个声明文件经近窗提交触及面命中(isolated worktree per-task commit 形态,porcelain 恒空——R9/R11 四连拦根治)`)
|
|
4714
4808
|
}
|
|
4809
|
+
// 主仓治理文档救赎(坑 target-files-worktree-docs-mismatch,2026-10-26-session-stage-progress
|
|
4810
|
+
// verify 实证):模块卡等主仓 .sillyspec/ 文档按 CLI 铁律「一律写主仓,绝不写 worktree 副本」
|
|
4811
|
+
// ——worktree 形态 actual(wt diff+porcelain)结构性看不见主仓侧改动,声明主仓治理路径恒落
|
|
4812
|
+
// ②类假红(「写 worktree=产物蒸发,写主仓=对账 missing」两规则打架)。救赎面=主仓 .sillyspec/
|
|
4813
|
+
// 的 status(未提交)∪ baseline..HEAD diff(已提交),仅救赎「声明而 missing」的主仓治理路径
|
|
4814
|
+
// (不扩 undeclared 面——与 R9/R11 窗口救赎同语义)。fail-open:主仓读取失败走既有 missing 判定。
|
|
4815
|
+
if (actual.form === 'worktree') {
|
|
4816
|
+
const govDeclared = declaredPaths.filter((p) => {
|
|
4817
|
+
const clean = String(p).replace(/\\/g, '/').replace(/^NEW:/, '')
|
|
4818
|
+
return clean.startsWith('.sillyspec/')
|
|
4819
|
+
})
|
|
4820
|
+
if (govDeclared.length > 0) {
|
|
4821
|
+
let mainDocsRescued = 0
|
|
4822
|
+
try {
|
|
4823
|
+
const mainFace = new Set()
|
|
4824
|
+
// -uall:未跟踪目录缺省折叠成目录条目(?? .sillyspec/docs/<proj>/ 整目录一行)——
|
|
4825
|
+
// 模块卡常为新建文件,折叠形态对不上文件级声明,逐文件展开(scope-audit 同款口径)
|
|
4826
|
+
const st = gitQuiet(cwd, ['status', '--porcelain', '--untracked-files=all', '--', '.sillyspec/'], { timeout: 30000, trim: false })
|
|
4827
|
+
for (const line of String(st || '').split('\n')) {
|
|
4828
|
+
if (!line || line.length < 4) continue
|
|
4829
|
+
const p = line.slice(3).trim().replace(/^"|"$/g, '')
|
|
4830
|
+
const arrow = p.indexOf(' -> ')
|
|
4831
|
+
const f = (arrow !== -1 ? p.slice(arrow + 4) : p).replace(/\\/g, '/')
|
|
4832
|
+
if (f) mainFace.add(pathKey(f))
|
|
4833
|
+
}
|
|
4834
|
+
if (actual.baseAnchor) {
|
|
4835
|
+
const df = gitQuiet(cwd, ['diff', '--name-only', `${actual.baseAnchor}..HEAD`, '--', '.sillyspec/'], { timeout: 30000 })
|
|
4836
|
+
for (const f of String(df || '').split('\n')) {
|
|
4837
|
+
const t = f.trim().replace(/\\/g, '/')
|
|
4838
|
+
if (t) mainFace.add(pathKey(t))
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
for (const p of govDeclared) {
|
|
4842
|
+
const clean = String(p).replace(/\\/g, '/').replace(/^NEW:/, '')
|
|
4843
|
+
if (!actualKeySet.has(pathKey(p)) && mainFace.has(pathKey(clean))) {
|
|
4844
|
+
actualKeySet.add(pathKey(p))
|
|
4845
|
+
mainDocsRescued++
|
|
4846
|
+
}
|
|
4847
|
+
}
|
|
4848
|
+
} catch { /* 主仓治理面读取失败 → 走既有 missing 判定(fail-open 不新造阻断) */ }
|
|
4849
|
+
if (mainDocsRescued > 0) {
|
|
4850
|
+
notes.push(`主仓治理文档救赎:${mainDocsRescued} 个 .sillyspec/ 声明经主仓实际改动面命中(worktree 对账面看不见主仓侧模块卡等治理文档——「治理文档一律写主仓」铁律与 worktree actual 的结构性错配,2026-10-26 实证)`)
|
|
4851
|
+
}
|
|
4852
|
+
}
|
|
4853
|
+
}
|
|
4715
4854
|
// ①交集(计数进 evidence):唯一路径口径(多卡声明同文件只计一次)
|
|
4716
4855
|
for (const path of declaredPaths) {
|
|
4717
4856
|
if (actualKeySet.has(pathKey(path))) matched.push(path)
|
|
@@ -26,7 +26,7 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句 10-30 字中文
|
|
|
26
26
|
## 恢复与查看
|
|
27
27
|
- 轻量变更恢复:重跑 `sillyspec flow start --change <名>`(输出恢复简报)或 `flow status --change <名>`;完整流程恢复:`run <stage> --change <名>` 续跑。不直接 commit 半成品
|
|
28
28
|
- 跨会话交接:`sillyspec handoff --change <名>`;变更列表:`sillyspec status`;自检修复:`sillyspec doctor`
|
|
29
|
-
- 知识库:`sillyspec knowledge search "<关键词>"`(命中知识 CLI 会自动注入 prompt
|
|
29
|
+
- 知识库:`sillyspec knowledge search "<关键词>"`(命中知识 CLI 会自动注入 prompt,勿自行重复检索);知识条目可声明注入作用域(`<!-- scope: always|repo|module:<域> -->`,无标记=关键词档)——现状与语法:`sillyspec knowledge scope`
|
|
30
30
|
|
|
31
31
|
## 核心规则
|
|
32
32
|
1. **禁止绕过本文件规则和 SillySpec 流程**。所有变更走 sillyspec 流程,不裸改裸提交。
|
|
@@ -42,3 +42,4 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句 10-30 字中文
|
|
|
42
42
|
11. **禁止目录级 git add / git add -A**(多会话共享仓):目录级暂存会夹带并行会话的进行中文件甚至误删已提交文件。提交一律用显式 pathspec,且 **add 与 commit 用同一份清单**:`git commit -m "..." -- 文件1 文件2`——带 pathspec 的 commit 只提交这些路径,他侧已 staged 的条目既不被带走、也原样留给对方;裸 `git commit` 提交的是整个共享暂存区,他会话 add 过的文件会被一并扫入。核对暂存面固定用 `git diff --cached --name-only` 全量读取,禁用过滤式核对(过滤正是致盲原因);核对与提交分开两条命令执行,不得链在同一命令里(链行没有拦截点)。安装了 commit-guard hook 的仓会对超声明面的 staged 文件警告(只警告不阻断)。
|
|
43
43
|
12. **不奉承用户**,禁止"你说得对"类话术,直接给结论、依据、方案。
|
|
44
44
|
13. **边干边勾(任务进度唯一源=变更的 tasks.md)**:完成一个任务单元(实现到位 + 相关测试跑绿)当场勾对应格——`sillyspec task tick --change <名> --task task-NN`(即时回显进度与下一任务)或直接 Edit 翻格 `- [ ]`→`- [x]`,勿攒到收口一把勾。harness 自带的 TodoWrite 类工具是会话内便利面,不替代 tasks.md——平台进度投影、收口哨兵、断点恢复只读 tasks.md。
|
|
45
|
+
14. **推送前压扁**:本地未推送提交统一走 `sillyspec push`(默认 by-change;`--mode one` 需明确归属),禁止直接 `git push`、`git push --force` 或手工 `git reset --soft` 压扁;已推送历史不改写,并行会话的 staged 文件/Dirty 文件不替人提交或清理。
|