sillyspec 3.33.4 → 3.33.6
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 +70 -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/command-cards.js +1 -1
- package/src/config-schema.js +50 -0
- package/src/config-validate.js +180 -0
- package/src/deleted-ledger.js +3 -1
- package/src/flow-parity.js +62 -2
- package/src/flow-preflight.js +67 -0
- package/src/flow-review.js +19 -3
- package/src/flow.js +318 -70
- package/src/git-helper.js +22 -6
- package/src/index.js +48 -5
- package/src/knowledge-scope.js +82 -0
- package/src/progress/change-registry.js +15 -1
- package/src/push.js +934 -0
- package/src/review-dispatch.js +99 -0
- package/src/run/complete-handlers.js +49 -14
- package/src/run/quick-audit.js +4 -2
- package/src/run/shared.js +1 -1
- package/src/stages/knowledge.js +44 -1
- package/src/verify-postcheck.js +177 -10
- package/templates/agents-instruction.md +2 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sillyspec:config
|
|
3
|
+
description: 用于 SillySpec local.yaml 配置引导。适合用户说"配置下 sillyspec、连接平台、配 MCP、lint 不跑、测试太慢、加豁免"或 agent 需要写 .sillyspec/local.yaml 时。核心原则:默认零配置可用,只写真需要的键,写完必须 config validate 通过。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## 何时使用
|
|
7
|
+
|
|
8
|
+
- 用户要求配置 SillySpec(平台凭据/MCP/测试/lint/调优键)
|
|
9
|
+
- agent 判断需要写 `.sillyspec/local.yaml`(任何场景——本技能是唯一引导面)
|
|
10
|
+
|
|
11
|
+
## 第一原则:默认零配置可用(先读这段再动手)
|
|
12
|
+
|
|
13
|
+
**local.yaml 一个键都不写,SillySpec 就是完整可用的。** 全部 ~70 个注册键中只有平台/MCP 凭据族(4 键)真必填,且仅当用户要用平台功能。测试面按变更动态推断、lint 未配置自动跳过归 pre-push、FR 回归层缺省不跑归 CI——都是零配置最优缺省(2026-10-23 起)。
|
|
14
|
+
|
|
15
|
+
**不要「预防性配置」**:不确定要不要写的键就是不写。写了多余键的唯一效果是偏离缺省、增加审计面。
|
|
16
|
+
|
|
17
|
+
## 必填场景决策表(只有这些场景才动配置)
|
|
18
|
+
|
|
19
|
+
| 场景 | 症状/触发 | 要写的键 | 怎么获得值 |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| 平台同步/看板 | 用户要用 SillyHub 平台 | `platform.url` + `platform.token` | **不手填**:跑 `sillyspec platform connect <url> --token <t>` 自动写 |
|
|
22
|
+
| MCP 派发 | 评审平台单调用/独立审查派发 | `mcp.url` + `mcp.token` | 同上(connect 成对签发) |
|
|
23
|
+
| 评审单调用 | 用户要评审收口一次完成(省 missing→PASS 往返) | `review_inline_wait: 180` | 依赖 mcp 凭据;平台不可用自动回退任务书面零损失 |
|
|
24
|
+
| dirty 门残渣 | 收口反复被已知残渣文件拦(--accept-dirty-gap 成常态) | `dirty_gate_ignore: <前缀1>, <前缀2>` | 看收口输出点名的文件,取其稳定目录前缀 |
|
|
25
|
+
| lint 收口实测 | 用户要 lint 在收口时跑(缺省跳过归 pre-push) | `commands.lint: <命令>` | 从 package.json scripts / Makefile 抄 |
|
|
26
|
+
| 慢套件超时 | 实测被 600s 默认帽判超时失败 | `commands.test_timeout_sec: 2400` | 收口输出会提示 |
|
|
27
|
+
| 全量测试道 | 用户明确要本地跑全量(罕见) | `test_strategy: full` | 仅显式全量时;缺省动态子集已是最优 |
|
|
28
|
+
| 项目特有评审必查面 | 团队有自己的评审红线 | `review_probes:` 段 | 见 `sillyspec config schema` review_probes 节 |
|
|
29
|
+
|
|
30
|
+
调优键全清单与语义:`sillyspec config schema`(单一真相;example 列可照抄值)。
|
|
31
|
+
|
|
32
|
+
## 操作流程
|
|
33
|
+
|
|
34
|
+
1. **看现状**:`sillyspec config cat`(权威路径链解析——worktree 内也能拿到主仓真实文件;不存在=零配置形态,正常)。
|
|
35
|
+
2. **对决策表**:确认触发场景真实存在;不在表内的场景**不写任何键**,向用户说明缺省行为已覆盖。
|
|
36
|
+
3. **获得值**:凭据族走 `sillyspec platform connect`(禁止手填 token 到 yaml);其余键从仓库结构(package.json/pyproject/Makefile)或收口输出提示取值。
|
|
37
|
+
4. **写文件**:编辑 `.sillyspec/local.yaml`(config cat 显示的路径;整文件 YAML,注意缩进用空格、保存为 UTF-8 无 BOM——PS 下用 Set-Content -Encoding utf8NoBOM 或让 CLI 命令代写)。**只写决策表需要的键,逐键照抄 schema 的 example 形态。**
|
|
38
|
+
5. **硬门验证(必做,不许跳过)**:跑 `sillyspec config validate`
|
|
39
|
+
- **exit 0 才算配置完成**;exit 1 时按输出的错误行逐条修复后重跑。
|
|
40
|
+
- 有「未知顶层键」警告=键名/层级写错,回 schema 核对。
|
|
41
|
+
- 凭据段报缺键=两键要成对(或整段删除回退)。
|
|
42
|
+
- **禁止**在 validate 未通过时向用户宣称配置完成。
|
|
43
|
+
|
|
44
|
+
## 知识注入作用域(非 local.yaml——写在知识文件标记行,不改配置)
|
|
45
|
+
|
|
46
|
+
> 这是「配置」之外唯一需要写的声明:让某条知识每次变更都被注入(规约/仓级约束),而不是等关键词命中。
|
|
47
|
+
|
|
48
|
+
在 `knowledge/<文件>.md` 某个 section 的 heading 下方写一行 HTML 注释(无标记=关键词命中现状):
|
|
49
|
+
|
|
50
|
+
```markdown
|
|
51
|
+
## 工具中立性
|
|
52
|
+
<!-- scope: always -->
|
|
53
|
+
不锁死开发语言/框架……
|
|
54
|
+
|
|
55
|
+
## 本仓发布纪律
|
|
56
|
+
<!-- scope: repo -->
|
|
57
|
+
发布前必须跑锚套件……
|
|
58
|
+
|
|
59
|
+
## 模块路由规则
|
|
60
|
+
<!-- scope: module:cli-entry,core-engine -->
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- `always`=每次变更恒注入(跨仓通用规约);`repo`=本仓恒注入;`module:<域>[,<域>]`=触达该知识域时注入(域 id 与 FR 域同源);无标记=关键词档不恒注入。
|
|
64
|
+
- 查看现状/语法自查:`sillyspec knowledge scope`(纯读;空态会提示「仅关键词档在用」);flow start 注入块以 📌 行呈现。
|
|
65
|
+
|
|
66
|
+
## 边界
|
|
67
|
+
|
|
68
|
+
- 禁止写 `.sillyspec/` 之外的 local.yaml(hook 拦截);项目根没有这个文件。
|
|
69
|
+
- 禁止把 token 写进 local.yaml.example 或任何可提交文件(真实 local.yaml 是 gitignored)。
|
|
70
|
+
- known_failures 豁免键不用手改 yaml:`sillyspec config known-failures add|remove`(行级手术保注释)。
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sillyspec:push
|
|
3
|
+
description: 推送前压扁本地未推送提交并快进推送。适合用户说“推送 / push / 推到远端 / 发布 / 同步 origin / 把这堆中间提交整理一下”。默认按连续变更段压扁(by-change),可选 --mode one 合并为一笔;只改写本地未推送段,禁止改写已推送历史、禁止 force-push、禁止直接 git push。
|
|
4
|
+
argument-hint: "[--mode by-change|one|none] [--change <名>] [--dry-run] [--no-push] [--undo] [--remote <名>] [--branch <名>] [--json]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 交互规范
|
|
8
|
+
当需要用户从多个选项中做出选择时,必须使用 Claude Code 内置的 AskUserQuestion 工具,将选项以参数传入。真实推送前必须先展示 `--dry-run` 计划并取得用户确认。
|
|
9
|
+
|
|
10
|
+
## 何时使用
|
|
11
|
+
- `flow done` / `archive` 收口、提交完成后,准备推送到远端。
|
|
12
|
+
- 用户说“推送 / push / 推到 origin / 发布 / 同步远端 / 把中间提交压扁”。
|
|
13
|
+
- 本地有多个 `task-NN`、wip、fix、归档提交,需要整理成可审阅的历史。
|
|
14
|
+
- 不适用:改写已推送历史、他人分支未授权改写、平台进度同步(那是 `sillyspec platform sync`,不是 git push)。
|
|
15
|
+
|
|
16
|
+
## 用法
|
|
17
|
+
- `sillyspec push` — 默认 `--mode by-change`:连续且归属同一变更的提交压成一笔,其余原样保留。
|
|
18
|
+
- `sillyspec push --mode one --change <名>` — 所有未推送提交合并为一笔(有活跃变更时拒绝)。
|
|
19
|
+
- `sillyspec push --dry-run` — 只输出计划,不改写 ref/index/工作区/远端。
|
|
20
|
+
- `sillyspec push --no-push` — 只压扁,不推送。
|
|
21
|
+
- `sillyspec push --undo` — 推送前恢复最近一次压扁;远端已更新时拒绝。
|
|
22
|
+
- `sillyspec push --remote origin --branch <分支> --json` — 显式远端/分支/机器输出。
|
|
23
|
+
|
|
24
|
+
## 流程
|
|
25
|
+
### 1. 预检(只读)
|
|
26
|
+
```bash
|
|
27
|
+
git status -sb
|
|
28
|
+
git log --oneline --decorate @{u}..HEAD
|
|
29
|
+
sillyspec push --dry-run
|
|
30
|
+
```
|
|
31
|
+
- 无 upstream、detached HEAD、merge commit、staged 改动、cwd 在 worktree 内 → 先停下说明原因。
|
|
32
|
+
- 展示:目标远端/分支、base、每个分段(变更名、旧范围、新 subject)、保留的无关提交、是否快进、pre-push 会跑什么。
|
|
33
|
+
|
|
34
|
+
### 2. 用户确认
|
|
35
|
+
AskUserQuestion:确认执行 / 改为 `--mode one --change <名>` / 取消。
|
|
36
|
+
用户未明确同意,不执行真实推送。
|
|
37
|
+
|
|
38
|
+
### 3. 执行
|
|
39
|
+
```bash
|
|
40
|
+
sillyspec push
|
|
41
|
+
# 或
|
|
42
|
+
sillyspec push --mode one --change <名>
|
|
43
|
+
```
|
|
44
|
+
- CLI 自动:文件锁、pre/post 备份 ref、`commit-tree`、树等价校验、CAS `update-ref`、普通快进 push。
|
|
45
|
+
- pre-push hook 失败:修复 lint/docs gate 后重跑;禁止 `--no-verify`。
|
|
46
|
+
|
|
47
|
+
### 4. 汇报
|
|
48
|
+
- 旧 → 新 SHA、远端 ref、备份 ref、压扁段数、`--undo` 命令。
|
|
49
|
+
- 若 push 失败:本地压扁保留,按提示重试或 `--undo`;不要手工 `reset`。
|
|
50
|
+
|
|
51
|
+
## 模式与守卫
|
|
52
|
+
- `by-change`:只合并连续同归属段;subject 必须保留 `(<变更名>)`,body 保留全部 `task-NN` 证据。
|
|
53
|
+
- `one`:必须 `--change`;区间含其他活跃变更归属时拒绝。
|
|
54
|
+
- 拒绝:base 非 HEAD 祖先、区间含已推送提交、merge commit、staged 改动、active worktree、CAS 并发失败、远端分叉。
|
|
55
|
+
- 活锚点:scan `source_commit`、活跃 `baseline_commit`、change-patch、worktree meta 会自动抬升 effectiveBase;不要手工越过它们。
|
|
56
|
+
|
|
57
|
+
## 绝对规则
|
|
58
|
+
- ❌ 不要直接 `git push` / `git push --force` / `git push --force-with-lease`。
|
|
59
|
+
- ❌ 不要手工 `git reset --soft` + `git commit` 压扁(会卷入共享暂存区)。
|
|
60
|
+
- ❌ 不要改写已推送历史;base 非祖先一律停下让用户先 pull/rebase。
|
|
61
|
+
- ❌ 不要 `--no-verify`,不要跳过 pre-push hook。
|
|
62
|
+
- ❌ 不要在 staged 改动未处置时运行;不要动其他会话的未提交文件。
|
|
63
|
+
- ❌ 不要在 SillySpec worktree cwd 内压扁;先 `worktree apply` 回主仓再推送。
|
|
64
|
+
- ✅ 不确定时先 `--dry-run`;失败用 `--undo` 恢复,不手改 ref。
|
|
65
|
+
|
|
66
|
+
## 多会话与并发
|
|
67
|
+
- 在主仓根运行;每条命令带 `SILLYSPEC_SESSION_ID=<agent名+任务名>`。
|
|
68
|
+
- CLI 用 `<git-common-dir>/sillyspec-push.lock` 串行化,分支用 CAS 更新;锁只约束走 `sillyspec push` 的进程。
|
|
69
|
+
- 共享暂存区可能有其他会话的 staged 文件:检测到即拒绝,不要替别人提交或清理。
|
|
70
|
+
|
|
71
|
+
## 故障排查
|
|
72
|
+
- 无 upstream → 先 `git push -u origin <branch>` 一次,再重跑。
|
|
73
|
+
- base 非祖先 / 远端领先 → 人工 fetch + rebase/merge,不自动 force。
|
|
74
|
+
- pre-push hook 失败 → 修 lint/docs gate,不跳过。
|
|
75
|
+
- CAS/锁失败 → 重跑 `--dry-run` 重新计划;确认无并发 push 后再执行。
|
|
76
|
+
- 已推送后想整理 → 不在本命令范围;需要团队明确同意后人工 force-with-lease。
|
|
77
|
+
|
|
78
|
+
## 用户指令
|
|
79
|
+
$ARGUMENTS
|
package/AGENTS.md
CHANGED
|
@@ -43,5 +43,6 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句 10-30 字中文
|
|
|
43
43
|
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 文件警告(只警告不阻断)。
|
|
44
44
|
12. **不奉承用户**,禁止"你说得对"类话术,直接给结论、依据、方案。
|
|
45
45
|
13. **边干边勾(任务进度唯一源=变更的 tasks.md)**:完成一个任务单元(实现到位 + 相关测试跑绿)当场勾对应格——`sillyspec task tick --change <名> --task task-NN`(即时回显进度与下一任务)或直接 Edit 翻格 `- [ ]`→`- [x]`,勿攒到收口一把勾。harness 自带的 TodoWrite 类工具是会话内便利面,不替代 tasks.md——平台进度投影、收口哨兵、断点恢复只读 tasks.md。
|
|
46
|
+
14. **推送前压扁**:本地未推送提交统一走 `sillyspec push`(默认 by-change;`--mode one` 需明确归属),禁止直接 `git push`、`git push --force` 或手工 `git reset --soft` 压扁;已推送历史不改写,并行会话的 staged 文件/Dirty 文件不替人提交或清理。
|
|
46
47
|
|
|
47
48
|
00. **不要枚举定义开放世界 以及 不要限制死 开发语言和框架**
|
package/README.md
CHANGED
|
@@ -116,6 +116,7 @@ SillySpec 进度库使用 SQLite 持久化(基于 Node.js 内置的 `node:sqli
|
|
|
116
116
|
| `/sillyspec:resume` | 恢复工作(支持大模块阶段进度) |
|
|
117
117
|
| `/sillyspec:doctor` | 项目自检与状态修复 |
|
|
118
118
|
| `/sillyspec:commit` | 智能提交 |
|
|
119
|
+
| `/sillyspec:push` | 推送前压扁本地未推送提交并快进推送 |
|
|
119
120
|
| `/sillyspec:workspace` | 多项目工作区管理 |
|
|
120
121
|
| `/sillyspec:export` | 导出成功方案为可复用模板 |
|
|
121
122
|
|
|
@@ -126,6 +127,7 @@ sillyspec run <stage> 执行阶段(auto/brainstorm/plan/execute/ver
|
|
|
126
127
|
sillyspec run <stage> --done 完成当前步骤并推进到下一步
|
|
127
128
|
sillyspec run <stage> --status 查看阶段进度
|
|
128
129
|
sillyspec progress show 显示当前项目状态
|
|
130
|
+
sillyspec push 推送前压扁本地未推送提交并快进推送(默认 by-change)
|
|
129
131
|
sillyspec init 初始化(零交互,自动检测工具)
|
|
130
132
|
sillyspec setup 安装推荐 MCP 工具(交互式)
|
|
131
133
|
sillyspec setup --list 查看已安装 MCP 状态
|
package/SKILL.md
CHANGED
|
@@ -54,6 +54,7 @@ Claude Code / Cursor / Codex / OpenCode / OpenClaw / Gemini 通用。
|
|
|
54
54
|
| `/sillyspec:resume` | 恢复工作 |
|
|
55
55
|
| `/sillyspec:doctor` | 项目自检 |
|
|
56
56
|
| `/sillyspec:commit` | 智能提交 |
|
|
57
|
+
| `/sillyspec:push` | 推送前压扁本地未推送提交并快进推送 |
|
|
57
58
|
| `/sillyspec:knowledge` | 知识库检索 / 沉淀 |
|
|
58
59
|
| `/sillyspec:export` | 导出成功方案为可复用模板 |
|
|
59
60
|
| `/sillyspec:workspace` | 多项目工作区管理 |
|
|
@@ -69,6 +70,7 @@ sillyspec run execute 执行开发阶段(子代理并行 + worktree
|
|
|
69
70
|
sillyspec run verify 执行验证阶段
|
|
70
71
|
sillyspec run archive 执行归档阶段
|
|
71
72
|
sillyspec flow start/done/status 轻量变更(默认快道,2 次调用收口)
|
|
73
|
+
sillyspec push 推送前压扁本地未推送提交并快进推送(默认 by-change)
|
|
72
74
|
sillyspec run explore 自由探索
|
|
73
75
|
sillyspec progress show 显示当前项目状态
|
|
74
76
|
sillyspec setup 安装推荐 MCP 工具
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sillyspec:push
|
|
3
|
+
description: 推送前压扁本地未推送提交并快进推送(默认 by-change;拒绝改写已推送历史与 force-push)
|
|
4
|
+
---
|
|
5
|
+
# 何时用
|
|
6
|
+
|
|
7
|
+
- `flow done` / `archive` 收口、提交完成后准备推送到远端。
|
|
8
|
+
- 用户说“推送 / push / 推到 origin / 发布 / 整理中间提交”。
|
|
9
|
+
- 不适用:已推送历史改写、他人分支未授权改写、平台进度同步(用 `platform sync`)。
|
|
10
|
+
|
|
11
|
+
# 生命周期速查
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
export SILLYSPEC_SESSION_ID=<agent名+任务名> # 多会话所有权判定依赖
|
|
15
|
+
sillyspec push --dry-run # 先看计划,零写盘
|
|
16
|
+
sillyspec push # 默认 by-change:连续同变更段压扁 + 快进推送
|
|
17
|
+
sillyspec push --mode one --change <名> # 全部未推送提交合并一笔(守卫通过才可)
|
|
18
|
+
sillyspec push --no-push # 只压扁不推送
|
|
19
|
+
sillyspec push --undo # 推送前恢复最近一次压扁
|
|
20
|
+
# pre-push hook 会跑 lint/docs gate;失败必须修复,禁止 --no-verify
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
# 防坑清单
|
|
24
|
+
|
|
25
|
+
- 不要直接 `git push`、`git push --force`,也不要手工 `git reset --soft` 压扁。
|
|
26
|
+
- base 非 HEAD 祖先 / upstream 领先 → 拒绝;先人工 fetch/rebase,本命令不 force。
|
|
27
|
+
- index 有 staged 改动 → 拒绝;不要替其他会话提交或清理暂存区。
|
|
28
|
+
- 在 SillySpec worktree 内 → 拒绝;先 `worktree apply` 回主仓再 push。
|
|
29
|
+
- 区间含 merge commit / 已推送提交 → 默认拒绝;不要试图展平他人已推送历史。
|
|
30
|
+
- by-change 只合并连续段;裸提交与其他变更提交原样保留,不跨段重排。
|
|
31
|
+
- one 模式必须 `--change`;有活跃变更或其他归属提交时拒绝。
|
|
32
|
+
- 活锚点(scan `source_commit` / 活跃 baseline / change-patch / worktree meta)自动抬升 base;不要手工越过。
|
|
33
|
+
- 备份 ref 不自动清理;`--undo` 只允许推送前,已推送后拒绝。
|
|
34
|
+
|
|
35
|
+
# 边界声明
|
|
36
|
+
|
|
37
|
+
本卡不含实现细节——计划、分段、锚点抬升、CAS 与推送由 `sillyspec push` 按当前仓状态实时计算。
|
|
38
|
+
本卡不替代 commit:先用 `sillyspec commit` 或显式 pathspec 提交,再 push。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sillyspec",
|
|
3
|
-
"version": "3.33.
|
|
3
|
+
"version": "3.33.6",
|
|
4
4
|
"description": "SillySpec CLI — 流程状态机,让 AI 严格按步骤来",
|
|
5
5
|
"icon": "logo.jpg",
|
|
6
6
|
"homepage": "https://sillyspec.ppdmq.top/",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
],
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"scripts": {
|
|
27
|
-
"test:core": "node --test test/preview-migration.test.mjs test/preview-progress.test.mjs test/preview-outlet.test.mjs test/preview-gc.test.mjs test/preview-gate-isolation.test.mjs test/db-engine.test.mjs test/db-concurrency.test.mjs test/db-atomic-write.test.mjs test/platform-sync-schema.test.mjs test/platform-sync-serialization.test.mjs test/change-ownership-guards.test.mjs test/watcher.test.mjs test/watcher-alerts.test.mjs test/watcher-timeline.test.mjs test/sentinel-rules.test.mjs test/handoff.test.mjs test/execution-mode-render.test.mjs test/knowledge-inbox.test.mjs test/knowledge-baseline.test.mjs test/quick-laststep-fourfields-preview.test.mjs test/doctor-align-execute-progress.test.mjs test/test-ledger.test.mjs test/test-bindings.test.mjs test/flow-draft-binding-extract.test.mjs test/gate-snapshot-lifecycle.test.mjs test/gate-snapshot-cleanup.test.mjs test/verify-trace-residual.test.mjs test/platform-creds-fallback.test.mjs test/archive-timeline-bake.test.mjs test/doc-ref-check.test.mjs test/flow-status-json.test.mjs test/flow-status-title.test.mjs test/flow-resume-title.test.mjs test/datetime-timeago.test.mjs test/resume-domain-flip.test.mjs test/fr-regress-cap-drop.test.mjs test/fr-priority-overlap.test.mjs test/archive-commit-suggest-race.test.mjs test/flow-done-fail-brief.test.mjs test/verify-probes-refresh-backup.test.mjs test/taskcard-yaml-triage-validate.test.mjs test/gate-last-reader.test.mjs test/status-multi-active-list.test.mjs test/gate-docs-only.test.mjs test/probe7-fr-prefill.test.mjs test/plan-fill-batch-config.test.mjs test/step-guide-prune.test.mjs test/gate-stage-review-hint.test.mjs test/design-ref-suggest.test.mjs test/flow-friction-batch3.test.mjs test/wave-auto-adopt.test.mjs test/test-incremental-rerun.test.mjs test/knowledge-graph.test.mjs test/explore-graph-guidance.test.mjs",
|
|
27
|
+
"test:core": "node --test test/preview-migration.test.mjs test/preview-progress.test.mjs test/preview-outlet.test.mjs test/preview-gc.test.mjs test/preview-gate-isolation.test.mjs test/db-engine.test.mjs test/db-concurrency.test.mjs test/db-atomic-write.test.mjs test/platform-sync-schema.test.mjs test/platform-sync-serialization.test.mjs test/change-ownership-guards.test.mjs test/watcher.test.mjs test/watcher-alerts.test.mjs test/watcher-timeline.test.mjs test/sentinel-rules.test.mjs test/handoff.test.mjs test/execution-mode-render.test.mjs test/knowledge-inbox.test.mjs test/knowledge-baseline.test.mjs test/quick-laststep-fourfields-preview.test.mjs test/doctor-align-execute-progress.test.mjs test/test-ledger.test.mjs test/test-bindings.test.mjs test/flow-draft-binding-extract.test.mjs test/gate-snapshot-lifecycle.test.mjs test/gate-snapshot-cleanup.test.mjs test/verify-trace-residual.test.mjs test/platform-creds-fallback.test.mjs test/archive-timeline-bake.test.mjs test/doc-ref-check.test.mjs test/flow-status-json.test.mjs test/flow-status-title.test.mjs test/flow-resume-title.test.mjs test/datetime-timeago.test.mjs test/resume-domain-flip.test.mjs test/fr-regress-cap-drop.test.mjs test/fr-priority-overlap.test.mjs test/archive-commit-suggest-race.test.mjs test/flow-done-fail-brief.test.mjs test/verify-probes-refresh-backup.test.mjs test/taskcard-yaml-triage-validate.test.mjs test/gate-last-reader.test.mjs test/status-multi-active-list.test.mjs test/gate-docs-only.test.mjs test/probe7-fr-prefill.test.mjs test/plan-fill-batch-config.test.mjs test/step-guide-prune.test.mjs test/gate-stage-review-hint.test.mjs test/design-ref-suggest.test.mjs test/flow-friction-batch3.test.mjs test/wave-auto-adopt.test.mjs test/test-incremental-rerun.test.mjs test/knowledge-graph.test.mjs test/explore-graph-guidance.test.mjs test/git-helper-input.test.mjs test/push-plan.test.mjs test/push-cli.test.mjs test/push-skill.test.mjs test/flow-guidance.test.mjs",
|
|
28
28
|
"test": "node test/run-tests.mjs",
|
|
29
29
|
"lint": "node test/check-syntax.mjs",
|
|
30
30
|
"eval:dry": "node eval/run.mjs --dry-run",
|
package/src/command-cards.js
CHANGED
|
@@ -10,7 +10,7 @@ import { writeAtomicSync } from './fs-atomic.js'
|
|
|
10
10
|
|
|
11
11
|
export const COMMAND_CARD_TARGETS = { zcode: '.zcode/commands/sillyspec', claude: '.claude/commands/sillyspec' }
|
|
12
12
|
// flow=轻量变更默认道入口卡(2026-09-25-quick-channel-retire 配套:quick 退役后快道首次有卡)
|
|
13
|
-
export const COMMAND_CARD_NAMES = ['flow', 'run-brainstorm', 'run-plan', 'run-execute', 'run-verify', 'run-archive', 'status']
|
|
13
|
+
export const COMMAND_CARD_NAMES = ['flow', 'run-brainstorm', 'run-plan', 'run-execute', 'run-verify', 'run-archive', 'status', 'push']
|
|
14
14
|
|
|
15
15
|
const ANCHOR_TAIL_RE = /\n?<!-- sillyspec-card: v(\S+) sha256=([0-9a-f]{64}) -->\s*$/
|
|
16
16
|
const sha256 = (s) => createHash('sha256').update(s, 'utf8').digest('hex')
|
package/src/config-schema.js
CHANGED
|
@@ -75,6 +75,7 @@ export const LOCAL_YAML_SCHEMA = {
|
|
|
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
77
|
{ path: 'commands.knowledge-gate', type: 'boolean', optional: true, status: 'live', readers: ['completeStep (src/run/complete.js)'], desc: '【2026-09-28-unclear-req-to-brainstorm FR-03/D-004】brainstorm 方案步(提出 2-3 种方案)--done 知识检索命中回显开关——缺省 true=开(对 --output 与 decisions.md 条目跑既有检索匹配器,命中 warn 回显「须 evidence 回应或说明不复潮」不阻断);false(或 off)关回显,无命中时输出与现状一致。', example: 'false' },
|
|
78
|
+
{ path: 'commands.scan:check', type: 'string', optional: true, status: 'live', readers: ['scan 漂移检查(scripts/scan-drift-check.py 经 scan 流程调用)'], desc: '【2026-10-23-schema-registration-debt 平台仓 dogfood 补登】scan 产物漂移自检命令。', example: 'python scripts/scan-drift-check.py' },
|
|
78
79
|
],
|
|
79
80
|
},
|
|
80
81
|
{
|
|
@@ -122,6 +123,16 @@ export const LOCAL_YAML_SCHEMA = {
|
|
|
122
123
|
keys: [
|
|
123
124
|
{ path: 'modules.<name>.path', type: 'string', optional: true, status: 'live', readers: ['extractModules (src/verify-postcheck.js)', 'parseLocalYamlModules (src/plan-postcheck.js)', 'extractModulePaths (src/worktree-deps.js)'], desc: '子模块目录路径(相对仓库根)。', example: 'frontend/' },
|
|
124
125
|
{ path: 'modules.<name>.test', type: 'string', optional: true, status: 'deprecated', readers: ['extractModules (src/verify-postcheck.js,退役提示用)'], desc: '【2026-09-26-dynamic-test-inference 退役,不再消费】测试面按变更动态推断、runner 自项目结构(pyproject/uv.lock/package.json 就近祖先)推断。模块级 flaky 处置迁 known_failures。块在场时 CLI 打印退役指引。', example: 'cd frontend && pnpm test' },
|
|
126
|
+
{ path: 'module_paths', type: 'object', optional: true, status: 'live', readers: ['module_paths 旧字段(部分工具仍用——平台仓 dogfood 2026-10-23 核实在用)'], desc: '【旧字段】模块名 → 目录路径映射(如 backend: backend/)。部分工具仍消费;新配置勿再写(模块归属走 docs/<project>/modules/_module-map.yaml)。', example: '{ backend: backend/, frontend: frontend/ }' },
|
|
127
|
+
{ path: 'fr_unmapped_baseline', type: 'integer', optional: true, status: 'live', readers: ['unmapped 大池消音(src/fr-index.js)'], desc: 'unmapped 域 FR 条数消音基线(跨仓历史堆积已清算时设当前值;增量 >10 才警告,缺省 0=无基线恒警告行为)。', example: '720' },
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: 'repos',
|
|
132
|
+
title: '跨仓注册表',
|
|
133
|
+
note: '仓键 → 本地工作目录。注册后参与跨仓冻结面/对账/未标注交付探测与跨仓 worktree 落位;条目两种形态:字符串路径或 {path, worktree} 对象(worktree=跨仓 worktree 落位根,优先级见 worktree.crossPlacement)。local.yaml 本就 gitignored,真实本地路径不入库。',
|
|
134
|
+
keys: [
|
|
135
|
+
{ path: 'repos.<repo键>', type: 'string', optional: true, status: 'live', readers: ['跨仓冻结/对账(src/cross-repo-reconcile.js)、注册探测(src/local-register.js)、跨仓 worktree(src/worktree-cross.js)'], desc: '仓键 → 该仓本地工作目录绝对路径(或 {path, worktree} 对象形态)。', example: 'shared-lib: C:/path/to/shared-lib' },
|
|
125
136
|
],
|
|
126
137
|
},
|
|
127
138
|
{
|
|
@@ -131,6 +142,10 @@ export const LOCAL_YAML_SCHEMA = {
|
|
|
131
142
|
keys: [
|
|
132
143
|
{ 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 }' },
|
|
133
144
|
{ 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' },
|
|
145
|
+
{ path: 'runner_flags', type: 'object', optional: true, status: 'live', readers: ['readRunnerFlags (src/verify-postcheck.js)'], desc: '【2026-10-23-runner-flags-generic,语言中立】推断批尾随 flags 映射:批类别(CLI 自有 runner 分类 py|js|jsx|…,未知类别前向兼容)→ flags 串。flags 置于文件参数前(node/go 类参数解析在首个位置参数处截断);hits 路径自带 flags 不注入。缺省不写=现状。', example: 'py: "-q --no-cov -n auto" / jsx: "--pool=forks"' },
|
|
146
|
+
{ path: 'py_runner_flags', type: 'string', optional: true, status: 'deprecated', readers: ['readRunnerFlags 兼容别名 (src/verify-postcheck.js)'], desc: '【已泛化】runner_flags.py 的单键旧别名(2026-10-23-py-runner-flags 遗留);新配置写 runner_flags 映射。', example: '-q --no-cov -n auto' },
|
|
147
|
+
{ path: 'deps_regress_cap', type: 'string', optional: true, status: 'live', readers: ['readDepsRegressCap (src/verify-postcheck.js)'], desc: '【2026-10-23-runner-flags-generic,语言中立】import 依赖面档位:N(1-2000)=变更自身测试优先、依赖只取前 N;off=只跑变更自身测试(关联回归归 pre-push/CI 门);不写=全跑。own=变更触碰文件∩依赖面,纯路径集合运算不涉语言。', example: 'off' },
|
|
148
|
+
{ path: 'fr_regress_cap', type: 'string', optional: true, status: 'live', readers: ['readFrRegressCap (src/verify-postcheck.js)'], desc: '【2026-10-23-fr-regress-default-off 缺省翻转】FR 关联回归层本地实测开关:缺省(不写)=不跑(归 pre-push/CI 门,收口披露行留痕);N(1-2000)=前 N 个绑定文件进实测面;all=全跑旧行为显式恢复;off=同缺省。', example: '40' },
|
|
134
149
|
],
|
|
135
150
|
},
|
|
136
151
|
{
|
|
@@ -188,6 +203,12 @@ export const LOCAL_YAML_SCHEMA = {
|
|
|
188
203
|
{ path: 'flow.mode', type: 'enum', values: ['thin', 'legacy'], optional: true, status: 'live', readers: ['readFlowConfig (src/flow.js)'], desc: '2-调用轻量协议开关:thin(缺省——2026-09-25-thin-default-flip 翻转,入口归一:常规变更默认轻量跑道,需求不清晰 CLI 拦下指路头脑风暴预段,实测失败自动升厚)= flow start/done 轻量跑道;legacy = 显式回旧道(run <stage> 全族行为逐字不动,flow start 拒跑并指路)。切回一行 yaml。', example: 'thin' },
|
|
189
204
|
{ path: 'flow.edit_ratio_threshold', type: 'number', optional: true, status: 'live', readers: ['readFlowConfig (src/flow.js)'], desc: '机器稿改写比例路由阈值(amend 通道计算的行级 editRatio 超阈 → 厚档提示 route_hint)。缺省 0.5。', example: '0.5' },
|
|
190
205
|
{ path: 'flow.edit_ratio_enforcement', type: 'enum', values: ['advisory', 'block'], optional: true, status: 'live', readers: ['readFlowConfig (src/flow.js)'], desc: 'editRatio 超阈的执行档:advisory(缺省——测绿可轻量档过,只提示+遥测)| block(超阈阻断 flow done)。用户裁定 advisory 定案,dogfood 后可按遥测翻 block。', example: 'advisory' },
|
|
206
|
+
// 以下四键为根级标量(无点 path——2026-10-23-config-validate-skill 补登:此前散落
|
|
207
|
+
// 各 reader 内联正则、schema 发现面缺失;校验器按注册面跟随)
|
|
208
|
+
{ path: 'turbo', type: 'enum', values: ['docs-auto'], optional: true, status: 'live', readers: ['readFlowConfig (src/flow.js)'], desc: '【2026-10-23-turbo-docs-auto】纯文档面常设极速授权档:docs-auto=CLI 按提交∪脏面自动判纯文档(代码分类器同源)降档 turbo(实测/评审/UI 探针跳过,授权源留痕);不写=关(--turbo 旗标档不受影响,agent 仍不可自启)。', example: 'docs-auto' },
|
|
209
|
+
{ path: 'review_inline_wait', type: 'integer', optional: true, status: 'live', readers: ['readFlowConfig (src/flow.js)'], desc: '【2026-10-23-review-inline-dispatch】薄道评审平台单调用通道等待预算秒数(1-600,钳 300s 帽):>0 时 flow done 评审缺席分支经 SillyHub 派发 readOnly worker 有界轮询回收、同调用登记(missing→PASS 往返消灭);任何失败回退任务书面零损失;不写/坏值=关。', example: '180' },
|
|
210
|
+
{ path: 'dirty_gate_ignore', type: 'string', optional: true, status: 'live', readers: ['readDirtyGateIgnore (src/flow-parity.js)'], desc: '【2026-10-23-dirty-gate-ignore】dirty 冻结缺口门的已知残渣前缀忽略清单(单行逗号分隔、posix 前缀匹配,module_gate_ignore 同款):命中面从缺口剔除并每轮 ℹ️ 留痕;三态 fail-soft 未配置=现状。', example: 'quicklog, test/.sillyspec' },
|
|
211
|
+
{ path: 'module_gate_ignore', type: 'string', optional: true, status: 'live', readers: ['readFlowConfig (src/flow.js)'], desc: '【2026-10-12-thin-module-gate-and-feed】模块图未登记目录软门的固定忽略清单(单行逗号、目录前缀、posix)——合法未登记面(根级脚本/配置等)免登记签收。', example: 'scripts, .github' },
|
|
191
212
|
],
|
|
192
213
|
},
|
|
193
214
|
{
|
|
@@ -283,6 +304,15 @@ export const LOCAL_YAML_SCHEMA = {
|
|
|
283
304
|
{ path: 'change-ownership.heartbeat_minutes', type: 'integer', optional: true, status: 'live', readers: ['assertChangeOwnership (src/progress/change-registry.js — 本变更 task-02 接线消费)'], desc: '所有者活跃心跳窗口(分钟):owner 非本会话且 last_active 距今在窗口内 → 拒绝接管类操作(--takeover 显式接管);窗口外 → 放行并自动接管。缺省 15。', example: '15' },
|
|
284
305
|
],
|
|
285
306
|
},
|
|
307
|
+
{
|
|
308
|
+
id: 'ui_prototype',
|
|
309
|
+
title: 'UI 原型形态契约(每仓)',
|
|
310
|
+
note: '【2026-10-15-ui-proto-form-config】frontend 触达的 UI 变更定稿原型形态:real-code=真码走仓内原型管线(推荐);single-html=单 HTML 例外(须 ruling 三键留痕:by/at/reason)。缺省未配置=flow start 强提醒登记。',
|
|
311
|
+
keys: [
|
|
312
|
+
{ path: 'ui_prototype.form', type: 'enum', values: ['real-code', 'single-html'], optional: true, status: 'live', readers: ['readUiPrototypeForm (src/ui-visual.js)'], desc: '本仓原型形态契约:real-code(真码管线)/ single-html(单 HTML 例外,须 ruling 裁决留痕)。', example: 'real-code' },
|
|
313
|
+
{ path: 'ui_prototype.<key>', type: 'string', optional: true, status: 'live', readers: ['readUiPrototypeForm (src/ui-visual.js)——豁免裁决 ruling{by,at,reason} 等扩展子键'], desc: '扩展子键占位(豁免裁决 ruling 族等)——子键名开放,校验不枚举。', example: 'ruling_by' },
|
|
314
|
+
],
|
|
315
|
+
},
|
|
286
316
|
{
|
|
287
317
|
id: 'ceremony',
|
|
288
318
|
title: '评审仪式档位(ceremony tier)',
|
|
@@ -426,6 +456,26 @@ dispatch:
|
|
|
426
456
|
# mode: thin # thin=flow start/done 轻量跑道(协议必需交互=2)| legacy=既有 run <stage> 全族(回滚一行)
|
|
427
457
|
# edit_ratio_threshold: 0.5 # 机器稿改写比例路由阈值(超阈提示厚档;缺省 0.5)
|
|
428
458
|
# edit_ratio_enforcement: advisory # advisory=测绿可轻量过只提示(缺省)| block=超阈阻断
|
|
459
|
+
#
|
|
460
|
+
# ── 收口调优(根级键,缺省零配置即用——注释态=缺省已最优,确要调优再解注释)──
|
|
461
|
+
# turbo: docs-auto # 纯文档面自动极速档(实测/评审/UI 探针跳过,授权源留痕);不写=关
|
|
462
|
+
# review_inline_wait: 180 # 评审平台单调用等待秒(1-600,需 SillyHub);不写=评审任务书面模式
|
|
463
|
+
# dirty_gate_ignore: quicklog, test/.sillyspec # dirty 门已知残渣前缀(单行逗号、posix 前缀)
|
|
464
|
+
# module_gate_ignore: scripts # 模块图软门固定忽略目录(单行逗号、前缀匹配)
|
|
465
|
+
# commands:
|
|
466
|
+
# scan:check: python scripts/scan-drift-check.py # scan 漂移自检命令
|
|
467
|
+
# module_paths: { backend: backend/, frontend: frontend/ } # 旧字段(部分工具仍用;新配置勿写)
|
|
468
|
+
# ui_prototype:
|
|
469
|
+
# form: real-code # UI 原型形态契约(real-code / single-html+ruling 豁免)
|
|
470
|
+
# fr_regress_cap: 40 # FR 回归层本地开关:缺省不跑归 pre-push/CI 门;N=前 N 个;all=全跑旧行为
|
|
471
|
+
# fr_unmapped_baseline: 720 # unmapped 域 FR 消音基线(跨仓历史堆积已清算时设)
|
|
472
|
+
# runner_flags: # 推断批尾随 flags(按批类别 py|js|jsx|…,未知类别前向兼容)
|
|
473
|
+
# py: "-q --no-cov -n auto"
|
|
474
|
+
# deps_regress_cap: off # import 依赖面:off=只跑变更自身测试;N=前 N 个依赖文件;不写=全跑
|
|
475
|
+
#
|
|
476
|
+
# ── 跨仓注册表(注册后参与跨仓冻结/对账;local.yaml gitignored 故真实路径不入库)──
|
|
477
|
+
# repos:
|
|
478
|
+
# shared-lib: C:/path/to/shared-lib # 或对象形态 {path: …, worktree: <跨仓落位根>}
|
|
429
479
|
|
|
430
480
|
# ── 阶段说明书下发与会话墙(R16 减负批次)──
|
|
431
481
|
# stage:
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config-validate.js — local.yaml 逐键校验器(2026-10-23-config-validate-skill)
|
|
3
|
+
*
|
|
4
|
+
* 用户配置哲学(2026-10-23 裁决原话):「能不要配置的就别让用户配置,默认都集成好;实在
|
|
5
|
+
* 不好配置的要生成一个技能去引导 agent 怎么填写,并且 agent 写完要 CLI 验证通过才行」。
|
|
6
|
+
* 本模块是那句「CLI 验证」:sillyspec config validate 经 config-cat 权威路径链解析真实
|
|
7
|
+
* local.yaml(worktree 感知),按 LOCAL_YAML_SCHEMA 单一源逐键校验——校验只消费不生产
|
|
8
|
+
* (schema 注册面新增键照常跟随,无需改本模块)。
|
|
9
|
+
*
|
|
10
|
+
* 判级:错误(exit 1)=YAML 解析失败/顶层非映射/段非映射/枚举越界/结构性类型错(标量位
|
|
11
|
+
* 收到映射或数组)/不可强转标量(number/integer 键收 "abc" 类纯文本、boolean 键收非
|
|
12
|
+
* true|false 串)/段内必填键缺失(凭据族两键齐全才生效)。警告(exit 0)=未知键(可能
|
|
13
|
+
* 无效果)。info(exit 0)=deprecated 键在场/空文件。标量强转语义(评审 P1 修正):数字
|
|
14
|
+
* 串与数字同义("2400"≡2400——YAML 弱型等价),但不可强转的错拼类必拦——防「宽容」变成
|
|
15
|
+
* 放过 FR 承诺要拦的坏值。缺文件=零配置可用 info exit 0(69 键仅 4 凭据键真必填且仅平台
|
|
16
|
+
* 用户——零配置契约锚)。
|
|
17
|
+
*/
|
|
18
|
+
import yaml from 'js-yaml'
|
|
19
|
+
import { readFileSync } from 'fs'
|
|
20
|
+
import { LOCAL_YAML_SCHEMA } from './config-schema.js'
|
|
21
|
+
import { resolveLocalYaml } from './config-cat.js'
|
|
22
|
+
|
|
23
|
+
/** schema 注册面索引:path→def、已知顶层键(段 id ∪ 无点 path)、含占位段的段(免未知键误报)。 */
|
|
24
|
+
function buildRegistry() {
|
|
25
|
+
const byPath = new Map()
|
|
26
|
+
const sectionIds = new Set()
|
|
27
|
+
const rootScalars = new Set()
|
|
28
|
+
const placeholderSections = new Set()
|
|
29
|
+
for (const sec of LOCAL_YAML_SCHEMA.sections || []) {
|
|
30
|
+
sectionIds.add(sec.id)
|
|
31
|
+
for (const k of sec.keys || []) {
|
|
32
|
+
byPath.set(k.path, k)
|
|
33
|
+
if (!k.path.includes('.')) rootScalars.add(k.path)
|
|
34
|
+
if (/<[^>]+>/.test(k.path)) placeholderSections.add(sec.id)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { byPath, sectionIds, knownTop: new Set([...sectionIds, ...rootScalars]), placeholderSections }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const kindOf = (v) => (v === null || v === undefined ? 'null' : Array.isArray(v) ? 'array' : typeof v)
|
|
41
|
+
|
|
42
|
+
/** 类型检查(评审 P1 修正口径:可强转宽容、不可强转判错)——YAML 标量弱型下 24 与 "24"
|
|
43
|
+
* 同义(number/integer 收数字串合法),但 "abc" 这类不可强转错拼必须拦(FR-01 场景);
|
|
44
|
+
* boolean 收非 true/false 串判错;string 收任意标量;结构错配(标量位收映射/数组)恒错。 */
|
|
45
|
+
function checkType(def, v, path, errors) {
|
|
46
|
+
const kind = kindOf(v)
|
|
47
|
+
if (kind === 'null') return
|
|
48
|
+
const scalarSpot = ['string', 'number', 'integer', 'enum', 'boolean', 'json'].includes(def.type)
|
|
49
|
+
if (scalarSpot && (kind === 'object' || kind === 'array')) {
|
|
50
|
+
errors.push(`「${path}」期望${def.type === 'enum' ? `枚举(${(def.values || []).join('|')})` : def.type}标量,实得 ${kind}——嵌套层级写错(该键是叶子值不是段)`)
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
if (def.type === 'object' && kind !== 'object') {
|
|
54
|
+
errors.push(`「${path}」期望键值映射,实得 ${kind}`)
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
if (def.type === 'array' && kind !== 'array') {
|
|
58
|
+
errors.push(`「${path}」期望列表,实得 ${kind}`)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
if (def.type === 'enum' && !(def.values || []).map(String).includes(String(v))) {
|
|
62
|
+
errors.push(`「${path}」取值越界:${JSON.stringify(v)}(合法:${(def.values || []).join('|')})`)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
if ((def.type === 'number' || def.type === 'integer') && kind === 'string' && !/^-?\d+(\.\d+)?$/.test(v.trim())) {
|
|
66
|
+
errors.push(`「${path}」期望${def.type === 'integer' ? '整数' : '数字'},实得字符串 ${JSON.stringify(v)}(不可强转——数字串可,纯文本不可)`)
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
if (def.type === 'integer' && kind === 'string' && !/^-?\d+$/.test(v.trim())) {
|
|
70
|
+
errors.push(`「${path}」期望整数,实得 ${JSON.stringify(v)}`)
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
if (def.type === 'boolean' && kind === 'string' && !/^(true|false)$/i.test(v.trim())) {
|
|
74
|
+
errors.push(`「${path}」期望布尔(true/false),实得 ${JSON.stringify(v)}`)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 校验 local.yaml 文本(纯函数)。@returns {{errors:string[], warnings:string[], infos:string[], keysChecked:number}}
|
|
80
|
+
*/
|
|
81
|
+
export function validateLocalYamlContent(text) {
|
|
82
|
+
const { byPath, knownTop, placeholderSections } = buildRegistry()
|
|
83
|
+
const errors = [], warnings = [], infos = []
|
|
84
|
+
let doc
|
|
85
|
+
try { doc = yaml.load(text) } catch (e) {
|
|
86
|
+
return { errors: [`YAML 解析失败:${(e && e.message) || e}——agent 手改易犯缩进/Tab/编码错(doctor 的 local_yaml_integrity 维度查编码)`], warnings, infos, keysChecked: 0 }
|
|
87
|
+
}
|
|
88
|
+
if (doc == null) doc = {}
|
|
89
|
+
const topKind = kindOf(doc)
|
|
90
|
+
if (topKind !== 'object') {
|
|
91
|
+
return { errors: [`local.yaml 顶层必须是键值映射(实得 ${topKind})`], warnings, infos, keysChecked: 0 }
|
|
92
|
+
}
|
|
93
|
+
const sectionIds = new Set((LOCAL_YAML_SCHEMA.sections || []).map((s) => s.id))
|
|
94
|
+
let checked = 0
|
|
95
|
+
const checkDef = (def, v, path) => {
|
|
96
|
+
checked++
|
|
97
|
+
checkType(def, v, path, errors)
|
|
98
|
+
if (def.status === 'deprecated') infos.push(`「${path}」已退役(${String(def.desc || '').slice(0, 80)}…)——新配置勿再写`)
|
|
99
|
+
}
|
|
100
|
+
for (const [k, v] of Object.entries(doc)) {
|
|
101
|
+
if (!knownTop.has(k)) {
|
|
102
|
+
warnings.push(`未知顶层键「${k}」——sillyspec config schema 可查全部已知键(未知键大概率无效果;确认拼写与层级)`)
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
105
|
+
if (sectionIds.has(k) && byPath.has(k) === false) {
|
|
106
|
+
// 纯段(如 project/commands):校验段内键。两种真实形态:键值映射(commands.lint)
|
|
107
|
+
// 与列表-of-对象(review_probes 列表项字段建模为 sec.field 点路径——本仓 dogfood 实证)。
|
|
108
|
+
if (v == null) continue
|
|
109
|
+
const vKind = kindOf(v)
|
|
110
|
+
if (vKind !== 'object' && vKind !== 'array') { errors.push(`段「${k}」必须是键值映射或列表(实得 ${vKind})`); continue }
|
|
111
|
+
const entries = vKind === 'array'
|
|
112
|
+
? (v.every((it) => kindOf(it) === 'object') ? v.flatMap((it) => Object.entries(it)) : Object.entries(v[0] || {}))
|
|
113
|
+
: Object.entries(v)
|
|
114
|
+
for (const [ik, iv] of entries) {
|
|
115
|
+
const path = `${k}.${ik}`
|
|
116
|
+
const def = byPath.get(path)
|
|
117
|
+
if (!def) {
|
|
118
|
+
// 占位段(modules.<name>.path 族):子键名是用户自定义 id,注册面无法穷举——
|
|
119
|
+
// 未知子键不警告(占位形态本身合法),具体叶子键校验交给占位 path 前缀匹配不到即跳过
|
|
120
|
+
if (!placeholderSections.has(k)) warnings.push(`「${path}」不在注册键表(可能拼错/层级错,或新版键未同步 schema——sillyspec config schema 核对)`)
|
|
121
|
+
continue
|
|
122
|
+
}
|
|
123
|
+
checkDef(def, iv, path)
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
// 根级标量键(test_strategy/module_gate_ignore/…)
|
|
127
|
+
const def = byPath.get(k)
|
|
128
|
+
if (!def) { warnings.push(`键「${k}」不在注册键表`); continue }
|
|
129
|
+
checkDef(def, v, k)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// 段内必填闭合(required:true 键所在段在场则须齐全——mcp/platform 凭据族两键成对才生效)
|
|
133
|
+
for (const sec of LOCAL_YAML_SCHEMA.sections || []) {
|
|
134
|
+
if (!(sec.id in doc) || doc[sec.id] == null || kindOf(doc[sec.id]) !== 'object') continue
|
|
135
|
+
for (const kd of sec.keys || []) {
|
|
136
|
+
if (kd.required !== true || !kd.path.startsWith(`${sec.id}.`)) continue
|
|
137
|
+
const leaf = kd.path.slice(sec.id.length + 1)
|
|
138
|
+
if (!(leaf in doc[sec.id])) {
|
|
139
|
+
errors.push(`段「${sec.id}」缺必填键「${leaf}」——${sec.id === 'mcp' || sec.id === 'platform' ? '凭据两键齐全才生效(缺半整段回退缺省/env);补齐或整段删除' : '必填键'}`)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (checked === 0 && errors.length === 0) infos.push('local.yaml 在场但无任何已注册键(空段/仅注释)——等于零配置形态')
|
|
144
|
+
return { errors, warnings, infos, keysChecked: checked }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* config validate 子命令体:路径链解析(config-cat 同源,worktree 感知)→ 校验 → 人类/--json
|
|
149
|
+
* 双态渲染。退出码:0=通过(可含警告/info),1=有错误;缺文件=零配置 info exit 0。
|
|
150
|
+
*/
|
|
151
|
+
export function cmdConfigValidate({ dir, specBase = null, json = false } = {}) {
|
|
152
|
+
const r = resolveLocalYaml(dir, { specBase })
|
|
153
|
+
if (!r.exists) {
|
|
154
|
+
const payload = { file: null, exists: false, passed: true, errors: [], warnings: [], infos: ['local.yaml 不存在——零配置可用(全部键走缺省;仅平台/MCP 凭据族真必填)'], keysChecked: 0 }
|
|
155
|
+
if (json) console.log(JSON.stringify(payload, null, 2))
|
|
156
|
+
else console.log('✅ local.yaml 不存在——零配置可用(全部键走缺省;仅平台/MCP 凭据族真必填,需要时 sillyspec platform connect 一键写入)。\n 需要调优时:sillyspec config schema 查键 + sillyspec-config 技能引导;写完跑 sillyspec config validate 验证。')
|
|
157
|
+
return 0
|
|
158
|
+
}
|
|
159
|
+
let v
|
|
160
|
+
try { v = validateLocalYamlContent(readFileSync(r.path, 'utf8')) } catch (e) {
|
|
161
|
+
// 读盘竞态兜底(评审 P3 清偿):resolve 与 read 之间文件消失/被锁——优雅报错不裸崩
|
|
162
|
+
if (json) console.log(JSON.stringify({ file: r.path, exists: true, passed: false, errors: [`local.yaml 读取失败:${(e && e.message) || e}(可能刚被移动/删除——重跑)`], warnings: [], infos: [], keysChecked: 0 }, null, 2))
|
|
163
|
+
else console.error(`❌ local.yaml 读取失败:${(e && e.message) || e}(可能刚被移动/删除——重跑 sillyspec config validate)`)
|
|
164
|
+
return 1
|
|
165
|
+
}
|
|
166
|
+
const passed = v.errors.length === 0
|
|
167
|
+
if (json) {
|
|
168
|
+
console.log(JSON.stringify({ file: r.path, exists: true, passed, ...v }, null, 2))
|
|
169
|
+
} else {
|
|
170
|
+
console.log(`${passed ? '✅' : '❌'} local.yaml 校验${passed ? '通过' : '未通过'}(${r.path}${r.source ? `,来源 ${r.source}` : ''};已查 ${v.keysChecked} 个注册键)`)
|
|
171
|
+
for (const e of v.errors) console.error(` 错误:${e}`)
|
|
172
|
+
for (const w of v.warnings) console.warn(` 警告:${w}`)
|
|
173
|
+
for (const i of v.infos) console.log(` ℹ️ ${i}`)
|
|
174
|
+
if (passed && v.warnings.length === 0 && v.errors.length === 0) console.log(' 无警告无未知键。')
|
|
175
|
+
if (!passed) console.error(' 修复后重跑:sillyspec config validate(exit 0 才算配置完成——sillyspec-config 技能硬门)')
|
|
176
|
+
}
|
|
177
|
+
return passed ? 0 : 1
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export default { validateLocalYamlContent, cmdConfigValidate }
|
package/src/deleted-ledger.js
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* DB 行被外部手段物理清除后(rm + 裸 SQL,任何 sillyspec 官方路径都不物理删行),后续
|
|
7
7
|
* platform sync --change 一律「变更无进度数据」空转,平台行永挂 in_progress/active。
|
|
8
8
|
* 台账让「存在过且已达终态」这一事实脱离 DB 行存活而可查:deleteChange/unregisterChange
|
|
9
|
-
* 在事务提交后追加一行;
|
|
9
|
+
* 在事务提交后追加一行;renameChange 对旧名追加 deleted 行(平台按 change_key 建行,
|
|
10
|
+
* rename 只改本地,旧平台名行的唯一终态凭证——2026-10-23-rename-platform-tombstone);
|
|
11
|
+
* sync() 的行缺失分支据此合成墓碑补推(见 sync.js 台账分支)。
|
|
10
12
|
*
|
|
11
13
|
* 形态契约:`.sillyspec/.runtime/deleted-changes.jsonl`,每行一个 JSON 对象
|
|
12
14
|
* { name, status: 'deleted'|'archived', at: ISO };追加-only(读取侧 latest-wins),
|