sillyspec 3.28.2 → 3.28.4
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 +16 -55
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +1 -1
- package/.claude/skills/sillyspec-continue/SKILL.md +8 -38
- package/.claude/skills/sillyspec-execute/SKILL.md +10 -2
- package/.claude/skills/sillyspec-knowledge/SKILL.md +19 -249
- package/.claude/skills/sillyspec-plan/SKILL.md +1 -1
- package/.claude/skills/sillyspec-propose/SKILL.md +6 -77
- package/.claude/skills/sillyspec-resume/SKILL.md +8 -57
- package/.claude/skills/sillyspec-state/SKILL.md +7 -46
- package/.claude/skills/sillyspec-workspace/SKILL.md +14 -156
- package/AGENTS.md +24 -0
- package/INSTRUCTIONS.md +12 -0
- package/package.json +1 -1
- package/src/archive-delta.js +127 -2
- package/src/change-delete.js +2 -1
- package/src/change-list.js +7 -2
- package/src/change-risk-profile.js +77 -6
- package/src/config-cat.js +12 -1
- package/src/constants.js +9 -1
- package/src/design-facts.js +64 -0
- package/src/dispatch/backends/sillyhub-mcp.js +17 -5
- package/src/docs-check.js +153 -27
- package/src/docs-migrate.js +124 -0
- package/src/doctor-diagnostics.js +441 -5
- package/src/foreign-declared.js +53 -2
- package/src/index.js +353 -28
- package/src/module-impact.js +77 -12
- package/src/plan-adopt-waves.js +13 -5
- package/src/progress/change-registry.js +35 -0
- package/src/progress.js +64 -1
- package/src/quick-recommend.js +6 -1
- package/src/quicklog.js +10 -2
- package/src/run/command.js +159 -13
- package/src/run/complete-handlers.js +162 -9
- package/src/run/complete.js +104 -5
- package/src/run/gates.js +116 -8
- package/src/run/progress-confirm.js +66 -0
- package/src/run/prompt.js +235 -36
- package/src/run/quick-audit.js +12 -5
- package/src/run/scan-profile.js +20 -2
- package/src/run/shared.js +84 -11
- package/src/run/stage.js +42 -0
- package/src/runtime-hygiene.js +84 -0
- package/src/scan-postcheck.js +5 -1
- package/src/spec-sync.js +63 -8
- package/src/stage-contract.js +83 -3
- package/src/stage-review.js +3 -0
- package/src/stages/archive.js +4 -4
- package/src/stages/brainstorm-auto.js +2 -1
- package/src/stages/brainstorm.js +11 -25
- package/src/stages/doctor.js +41 -515
- package/src/stages/execute.js +10 -14
- package/src/stages/plan-postcheck.js +191 -7
- package/src/stages/plan.js +7 -27
- package/src/stages/quick.js +15 -13
- package/src/stages/verify.js +23 -21
- package/src/sync-noise.js +158 -0
- package/src/sync.js +42 -17
- package/src/task-review.js +225 -1
- package/src/verify-facts-schema.js +141 -0
- package/src/verify-postcheck.js +421 -49
- package/src/verify-probes.js +142 -13
- package/src/workflow.js +6 -0
- package/src/worktree-apply.js +51 -13
- package/src/worktree.js +5 -1
- package/templates/agents-instruction.md +1 -1
- package/.claude/skills/verify-per-user/SKILL.md +0 -6
|
@@ -3,62 +3,23 @@ name: sillyspec:state
|
|
|
3
3
|
description: 查看当前工作状态 — 显示 SillySpec 进度
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
你现在是 SillySpec 的状态查看器。
|
|
7
|
-
|
|
8
6
|
## 流程
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
```bash
|
|
13
|
-
sillyspec progress show
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
### 2. 如果有活跃变更
|
|
17
|
-
|
|
18
|
-
格式化展示当前状态:
|
|
19
|
-
|
|
20
|
-
> 📊 当前状态
|
|
21
|
-
>
|
|
22
|
-
> **变更**:<名称>
|
|
23
|
-
> **阶段**:<当前阶段>
|
|
24
|
-
> **进度**:<具体进度>
|
|
25
|
-
> **下一步**:<命令>
|
|
26
|
-
>
|
|
27
|
-
> **阶段进度**(大模块):
|
|
28
|
-
> (显示各阶段状态表)
|
|
29
|
-
>
|
|
30
|
-
> **关键决策**:
|
|
31
|
-
> - xxx
|
|
32
|
-
>
|
|
33
|
-
> **阻塞项**:
|
|
34
|
-
> - xxx(如无则省略)
|
|
35
|
-
|
|
36
|
-
### 3. 如果没有活跃变更
|
|
37
|
-
|
|
38
|
-
提示用户项目还没有开始:
|
|
8
|
+
跑 `sillyspec progress show [--change <名>]`,把 CLI 输出**原样转述**给用户即可——输出本身已是成品格式(变更/阶段/最近活跃/未决冲突/滞留信号/下一步建议),勿再按自备模板重排。
|
|
39
9
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
> 开始使用:
|
|
43
|
-
> - 新项目:`/sillyspec:init`
|
|
44
|
-
> - 已有项目:`/sillyspec:scan`
|
|
45
|
-
> - 恢复中断的工作:`/sillyspec:resume`
|
|
46
|
-
>
|
|
47
|
-
> 进度数据会在 `sillyspec init` 时自动创建到 SQLite 数据库中。
|
|
10
|
+
- 多活跃变更时输出为汇总表;需要单变更细节加 `--change <名>`。
|
|
11
|
+
- 输出「没有活跃的变更」时,提示用户:新项目 `/sillyspec:init`、已有项目 `/sillyspec:scan`、恢复中断 `/sillyspec:resume`。
|
|
48
12
|
|
|
49
|
-
## progress
|
|
13
|
+
## progress 只读子命令(诊断用)
|
|
50
14
|
|
|
51
15
|
```bash
|
|
52
|
-
sillyspec progress show [--change <名>] # 当前工作状态(本 skill 主命令)
|
|
53
16
|
sillyspec progress check # 状态一致性检查(只报告,不修复)
|
|
54
17
|
sillyspec progress repair # 修复状态元数据(dry-run,加 --apply 才真改)
|
|
55
18
|
sillyspec progress validate # 校验并修复
|
|
56
19
|
sillyspec progress reset [--stage <阶段>] # 重置进度(破坏性,慎用)
|
|
57
20
|
```
|
|
58
21
|
|
|
59
|
-
|
|
22
|
+
- `/sillyspec:status` 看项目整体进度(change 文件级别);`/sillyspec:state` 看当前工作状态(阶段/步骤级别)——status 看"有什么",state 看"在做什么"。
|
|
60
23
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
- `/sillyspec:state` 查看当前工作状态(阶段/步骤级别)
|
|
64
|
-
- 两者互补:status 看"有什么",state 看"在做什么"
|
|
24
|
+
## 用户指令
|
|
25
|
+
$ARGUMENTS
|
|
@@ -5,171 +5,29 @@ description: 工作区管理 — 初始化、管理多项目工作区,查看
|
|
|
5
5
|
|
|
6
6
|
## 交互规范
|
|
7
7
|
|
|
8
|
-
**当需要用户从多个选项中做出选择时,必须使用 Claude Code 内置的 AskUserQuestion 工具,将选项以参数传入。**
|
|
8
|
+
**当需要用户从多个选项中做出选择时,必须使用 Claude Code 内置的 AskUserQuestion 工具,将选项以参数传入。** 不要用编号列表让用户手动输入数字。需要自由输入时在选项中加入"Other(自定义输入)"。
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
如果需要自由输入,在 AskUserQuestion 的选项中加入"Other(自定义输入)"。
|
|
10
|
+
## CLI 命令(一切读写都走命令,勿手写 yaml / 勿 bash 解析)
|
|
12
11
|
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## CLI 边界(重要)
|
|
16
|
-
|
|
17
|
-
`sillyspec workspace` 命令已存在(2026-08-21 落地):
|
|
18
12
|
```bash
|
|
19
|
-
sillyspec workspace add <名> <相对路径> [--role <角色>] [--repo <仓库地址>] # 登记子项目(外科写入 yaml
|
|
13
|
+
sillyspec workspace add <名> <相对路径> [--role <角色>] [--repo <仓库地址>] # 登记子项目(外科写入 yaml,已有字段保留;路径须存在)
|
|
20
14
|
sillyspec workspace remove <名> # 删除登记(项目本体不动)
|
|
21
|
-
sillyspec workspace status #
|
|
22
|
-
```
|
|
23
|
-
优先用命令;命令异常时才按下文手工兜底(yaml 字段口径:init 写 name/path/status 三字段,role/repo 由 add 追加)。
|
|
24
|
-
|
|
25
|
-
## 相关 CLI 命令
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
sillyspec scan # 扫描子项目生成文档
|
|
29
|
-
sillyspec init [--tool <名>] # 安装命令模板
|
|
30
|
-
sillyspec progress show # 查看进度
|
|
15
|
+
sillyspec workspace status # 三态探测表(未初始化/已初始化/已扫描+文档数)
|
|
31
16
|
```
|
|
32
17
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
## 用户指令
|
|
36
|
-
$ARGUMENTS
|
|
18
|
+
无参运行任一子命令可见用法。`status` 的输出即成品状态表,直接转述给用户即可,勿再自行 for-loop 探测。
|
|
37
19
|
|
|
38
20
|
## 核心流程
|
|
39
21
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
ls .sillyspec/projects/*.yaml 2>/dev/null | grep -q .
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
如果不存在 → 这是一个尚未配置工作区的项目。询问用户:
|
|
47
|
-
1. 初始化工作区
|
|
48
|
-
2. 跳过
|
|
49
|
-
|
|
50
|
-
### Step 2: 解析指令
|
|
51
|
-
|
|
52
|
-
根据 `$ARGUMENTS` 或默认行为:
|
|
53
|
-
|
|
54
|
-
- **无参数 / `status`** → 显示工作区状态
|
|
55
|
-
- **`add`** → 添加子项目
|
|
56
|
-
- **`remove`** → 移除子项目
|
|
57
|
-
- **`info`** → 显示某个子项目详情
|
|
58
|
-
- 无参数且 projects/*.yaml 不存在 → 初始化工作区
|
|
59
|
-
|
|
60
|
-
### Step 3: 执行对应操作
|
|
61
|
-
|
|
62
|
-
#### 3a. 初始化工作区(projects/ 不存在时)
|
|
63
|
-
|
|
64
|
-
1. 询问工作区名称(默认用当前目录名)
|
|
65
|
-
2. 逐个添加子项目:
|
|
66
|
-
- 询问子项目名称(如 `frontend`、`backend`)
|
|
67
|
-
- 询问子项目路径(相对于工作区根目录,如 `./frontend`)
|
|
68
|
-
- 询问子项目角色描述(如 `前端 - Vue3 + TypeScript`)
|
|
69
|
-
- 询问仓库地址(可选)
|
|
70
|
-
- 验证路径存在
|
|
71
|
-
添加完后询问:
|
|
72
|
-
> 1. 继续添加子项目
|
|
73
|
-
> 2. 完成,配置共享规范
|
|
74
|
-
3. 询问共享规范文件
|
|
75
|
-
4. 为每个子项目创建 `.sillyspec/projects/<name>.yaml`:
|
|
76
|
-
```yaml
|
|
77
|
-
name: <name>
|
|
78
|
-
path: <relative-path>
|
|
79
|
-
status: active
|
|
80
|
-
```
|
|
81
|
-
> ⚠️ `sillyspec init` 只会写入 `name`、`path`、`status` 三个字段。
|
|
82
|
-
> `role` 和 `repo` 由 `/sillyspec:workspace add` 追加。
|
|
83
|
-
5. 创建 `.sillyspec/shared/` 目录
|
|
84
|
-
|
|
85
|
-
#### 3b. 添加子项目(`add`)
|
|
86
|
-
|
|
87
|
-
1. 询问名称、路径、角色
|
|
88
|
-
2. 验证路径存在
|
|
89
|
-
3. 创建/更新 `.sillyspec/projects/<name>.yaml`
|
|
90
|
-
4. Git 提交
|
|
91
|
-
|
|
92
|
-
#### 3c. 移除子项目(`remove`)
|
|
93
|
-
|
|
94
|
-
1. 显示当前所有子项目列表
|
|
95
|
-
2. 询问要移除哪个:
|
|
96
|
-
> 1. (列出子项目名称)
|
|
97
|
-
> 2. 取消
|
|
98
|
-
3. 删除 `.sillyspec/projects/<name>.yaml`
|
|
99
|
-
4. Git 提交
|
|
100
|
-
|
|
101
|
-
#### 3d. 工作区状态(`status`,默认)
|
|
102
|
-
|
|
103
|
-
读取所有 `projects/*.yaml`,对每个子项目检查:
|
|
104
|
-
|
|
105
|
-
```bash
|
|
106
|
-
# 读取子项目列表
|
|
107
|
-
for f in .sillyspec/projects/*.yaml; do
|
|
108
|
-
[ -f "$f" ] || continue
|
|
109
|
-
proj_name=$(basename "$f" .yaml)
|
|
110
|
-
proj_path=$(grep '^path:' "$f" | head -1 | sed 's/^path:[[:space:]]*//')
|
|
111
|
-
proj_role=$(grep '^role:' "$f" | head -1 | sed 's/^role:[[:space:]]*//')
|
|
112
|
-
# 检查子项目状态
|
|
113
|
-
cd "$proj_path"
|
|
114
|
-
ls .sillyspec/ 2>/dev/null
|
|
115
|
-
cat .sillyspec/PROJECT.md 2>/dev/null
|
|
116
|
-
ls .sillyspec/docs/${proj_name}/scan/ 2>/dev/null | wc -l
|
|
117
|
-
cd -
|
|
118
|
-
done
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
输出格式:
|
|
122
|
-
|
|
123
|
-
```
|
|
124
|
-
🏢 工作区:<workspace-name>
|
|
125
|
-
|
|
126
|
-
📦 子项目(N 个):
|
|
127
|
-
✅ frontend ./frontend 前端 - Vue3 + TypeScript 已扫描(7 份文档)
|
|
128
|
-
⚠️ backend ./backend 后端 - Node.js 已初始化(未扫描)
|
|
129
|
-
❌ miniprogram ./miniprogram 小程序 - Taro 未初始化
|
|
130
|
-
|
|
131
|
-
📄 共享规范:2 份
|
|
132
|
-
- api-contract.md
|
|
133
|
-
- data-models.md
|
|
134
|
-
|
|
135
|
-
💡 操作:
|
|
136
|
-
/sillyspec:workspace add — 添加子项目
|
|
137
|
-
/sillyspec:workspace remove — 移除子项目
|
|
138
|
-
/sillyspec:init — 初始化子项目
|
|
139
|
-
/sillyspec:scan — 扫描子项目
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
#### 3e. 子项目详情(`info <name>`)
|
|
143
|
-
|
|
144
|
-
显示指定子项目的详细信息,包括 PROJECT.md、REQUIREMENTS.md 内容摘要、扫描文档列表。
|
|
145
|
-
|
|
146
|
-
### Step 4: projects/*.yaml 格式
|
|
147
|
-
|
|
148
|
-
每个子项目对应 `.sillyspec/projects/<name>.yaml`:
|
|
149
|
-
|
|
150
|
-
**sillyspec init 创建的格式(3 字段):**
|
|
151
|
-
```yaml
|
|
152
|
-
name: frontend
|
|
153
|
-
path: ./frontend
|
|
154
|
-
status: active
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
**workspace add 扩展的格式(最多 5 字段):**
|
|
158
|
-
```yaml
|
|
159
|
-
name: frontend
|
|
160
|
-
path: ./frontend
|
|
161
|
-
status: active
|
|
162
|
-
role: 前端 - Vue3 + TypeScript
|
|
163
|
-
repo: https://github.com/xxx/frontend.git # 可选
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### 最后说:
|
|
167
|
-
|
|
168
|
-
> 工作区已更新。
|
|
169
|
-
> 下一步:对子项目运行 `/sillyspec:init` 或 `/sillyspec:scan`。
|
|
22
|
+
1. **解析指令**:无参数 / `status` → 跑 `workspace status` 转述;`add` / `remove` → 交互收集参数后跑命令;`info <名>` → 读该子项目 `PROJECT.md` / `REQUIREMENTS.md` 摘要转述。
|
|
23
|
+
2. **初始化工作区**(`projects/` 尚无任何 yaml 且用户同意时):用 AskUserQuestion 逐个收集子项目名称/相对路径/角色(可选 repo),每收集一个跑一次 `workspace add`;添加完询问是否继续或完成。共享规范目录 `.sillyspec/shared/` 需要时手动创建。
|
|
24
|
+
3. **收尾**:add/remove 后建议 git 提交(CLI 只写 yaml 不提交);提示下一步对子项目跑 `/sillyspec:init` 或 `/sillyspec:scan`。
|
|
170
25
|
|
|
171
26
|
## 绝对规则
|
|
27
|
+
|
|
172
28
|
- 不修改子项目目录下的任何文件
|
|
173
|
-
-
|
|
174
|
-
-
|
|
175
|
-
|
|
29
|
+
- 子项目路径必须是相对于工作区根目录的相对路径,且真实存在(`workspace add` 会校验)
|
|
30
|
+
- yaml 读写一律交给 CLI——字段口径(name/path/status/role/repo)由命令维护,不手工拼
|
|
31
|
+
|
|
32
|
+
## 用户指令
|
|
33
|
+
$ARGUMENTS
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<!-- SillySpec v3.28.3 — 由 sillyspec init 生成,可自由编辑;重跑 init 同版本不更新 -->
|
|
2
|
+
# Agent 指引
|
|
3
|
+
|
|
4
|
+
## 项目说明
|
|
5
|
+
本项目使用 **SillySpec** 管理变更,采用文档驱动开发。SillySpec 是给 Agent 调用的 CLI 流程控制器,不是给人类直接使用的产品,也不是处理业务逻辑的工具——你(Agent)通过 CLI 告诉它"我在哪",它告诉你"下一步做什么";你执行步骤,它校验产出、推进状态,人类只在关键决策点介入审批。要考虑多 agent 同时操作代码,代码随时可能变化。所有变更以稳定、可用、可维护为目标,按生产级标准处理。
|
|
6
|
+
|
|
7
|
+
## 核心规则
|
|
8
|
+
1. **禁止绕过本文件规则和 SillySpec 流程**。所有变更走 sillyspec 流程,不裸改裸提交。
|
|
9
|
+
2. **改代码前必须先说明依据**——依据的文档路径(design.md / 模块文档 / file-lifecycle.md)或现有代码依据,无依据不改。
|
|
10
|
+
3. **新功能 / 大改动走完整流程**:`brainstorm → plan → execute → verify → archive`。
|
|
11
|
+
4. **小修复 / 小调整走 quick**:`sillyspec run quick`。
|
|
12
|
+
5. **执行顺序**:文档 → 读代码 → 写测试 → 写实现 → 跑测试 → 验收 → 更新文档。
|
|
13
|
+
6. **判规模选档**:≤3 文件、范围明确走 `quick`;多阶段 / 架构级走完整流程。
|
|
14
|
+
7. **代码先行不补流程(倒推 B 模式)**:代码若已先写好,**不回头补 brainstorm/plan 装样子**——用 `quick --done` 收尾 + 补 quicklog 条目,把已落盘改动如实登记进进度库。
|
|
15
|
+
8. **实证核验再 `--done`**:触及 `src`/`test` 的改动,CLI 会在 `quick --done` 时**亲自实测** `.sillyspec/local.yaml` 的 `commands.test` / `commands.lint`(门禁:实测失败阻断 --done 回 pending;纯 doc/配置与未配置命令自动跳过;倒推 B 模式按 --files 声明边界兜底判定)。agent 侧预跑**可选**——想省「--done 被拦→修复→重跑」一轮时才预跑;纯 doc/配置改动无需跑。以落盘文件与测试结果为准,不信口头"已完成"。
|
|
16
|
+
9. **中途停下不靠额外命令存进度**——进度已由上一次 `--done` 自动落盘;恢复时用 `sillyspec progress show` 查看进度,再用 `sillyspec run <stage>` 续跑,不直接 commit 半成品。
|
|
17
|
+
10. **实现完成后对照文档验收**(design.md / 模块文档),并检查是否影响已有测试。
|
|
18
|
+
11. **非测试逻辑本身有误时,禁止改测试来"通过"**——修逻辑,不修测试。
|
|
19
|
+
12. **git hook 拦截提交时禁止跳过**(如 `.husky/pre-push`),修复问题后再提交。
|
|
20
|
+
13. **代码必须兼容 Windows / Linux / macOS**(路径 / 换行 / 并发都要顾)。
|
|
21
|
+
14. **任务记录隔离**:永不重置 / reset / 清零已存在的 change;多个活跃 change 各自 `--change <名>` 隔离不重叠;quick 同一 QUICKLOG 按 ql-ID 条目追加,不冲突。
|
|
22
|
+
15. **quicklog 结构化落盘**:末步 `--done` 用四参数 `--req/--cause/--solution/--result`(CLI 合成结构化 output 并自动提取标题/四段分行);文件括注用 `--file-notes "path::注 || path2"`。骨架由 CLI 接管,`--done` 后按需核对即可,勿手拼模板。
|
|
23
|
+
16. **代码可能随时在修改**(多 agent 并行),Edit 前重跑 + 查最新态;破坏性 git op 前先备份。
|
|
24
|
+
17. **不奉承用户**,禁止"你说得对"类话术,直接给结论、依据、方案。
|
package/INSTRUCTIONS.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
## SillySpec — 规范驱动开发
|
|
2
|
+
|
|
3
|
+
在执行开发任务时,遵循以下规范:
|
|
4
|
+
|
|
5
|
+
### 代码规范
|
|
6
|
+
- 写代码前先读取 `.sillyspec/docs/<project>/scan/CONVENTIONS.md`(代码风格)和 `.sillyspec/docs/<project>/scan/ARCHITECTURE.md`(架构)
|
|
7
|
+
- 调用已有方法前,用 grep 确认方法存在,不许编造
|
|
8
|
+
- 遵循 `.sillyspec/docs/<project>/scan/CONVENTIONS.md` 中的代码风格
|
|
9
|
+
|
|
10
|
+
### 工作流程
|
|
11
|
+
- 读取 sillyspec.db 确认当前阶段(使用 `sillyspec progress show`)
|
|
12
|
+
- 各阶段产出文件位于 `.sillyspec/changes/<变更名>/` 下
|
package/package.json
CHANGED
package/src/archive-delta.js
CHANGED
|
@@ -28,12 +28,13 @@
|
|
|
28
28
|
* 依赖方向(单向防环):archive-delta → design-facts / modules / endpoint-baseline /
|
|
29
29
|
* endpoint-extractor,被依赖者均不反向 import 本模块。
|
|
30
30
|
*/
|
|
31
|
-
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
31
|
+
import { existsSync, readFileSync, readdirSync, writeFileSync, mkdirSync } from 'node:fs'
|
|
32
32
|
import { basename, dirname, join } from 'node:path'
|
|
33
33
|
import { parseDecisionDomains, loadModuleMap, deriveActualModules } from './design-facts.js'
|
|
34
34
|
import { parseModuleMapSimple } from './modules.js'
|
|
35
35
|
import { scanBackendEndpoints } from './endpoint-extractor.js'
|
|
36
36
|
import { diffEndpointSets } from './endpoint-baseline.js'
|
|
37
|
+
import { resolveVerifyChangedFiles } from './verify-postcheck.js'
|
|
37
38
|
|
|
38
39
|
// ---------------------------------------------------------------------------
|
|
39
40
|
// 小工具(全部 fail-soft:读不到/解析失败 → null / [],不抛出)
|
|
@@ -201,7 +202,7 @@ export function collectDeltaSources({ changeDir, specRoot, project, runtimeRoot,
|
|
|
201
202
|
* ISO(缺省当前时刻,测试可注入)
|
|
202
203
|
* @returns {string} delta.md 全文(LF 行尾,单一尾换行)
|
|
203
204
|
*/
|
|
204
|
-
export function buildDeltaReport({ changeDir, specRoot, project, runtimeRoot, cwd, now } = {}) {
|
|
205
|
+
export function buildDeltaReport({ changeDir, specRoot, project, runtimeRoot, cwd, now, withSummary = false } = {}) {
|
|
205
206
|
const change = changeDir ? basename(changeDir) : '<变更名>'
|
|
206
207
|
const {
|
|
207
208
|
reconcile, verifyFacts, moduleMap, decisions, deliverables,
|
|
@@ -415,5 +416,129 @@ export function buildDeltaReport({ changeDir, specRoot, project, runtimeRoot, cw
|
|
|
415
416
|
L.push('')
|
|
416
417
|
}
|
|
417
418
|
|
|
419
|
+
// IR 回灌(2026-09-07-ir-hardening D-006@v1):withSummary=true 返回结构化对象——
|
|
420
|
+
// affectedFiles/affectedModules 复用函数内既有推导(matched+undeclared/deliverables 兜底、
|
|
421
|
+
// module-map 归属),sidecar 写入消费同一份(单一真相源,Grill P2-③采纳)。默认 false 返回
|
|
422
|
+
// 纯字符串(既有调用方零变化)。
|
|
423
|
+
if (withSummary) {
|
|
424
|
+
return {
|
|
425
|
+
markdown: L.join('\n'),
|
|
426
|
+
change,
|
|
427
|
+
affectedFiles: [...affectedFiles],
|
|
428
|
+
affectedModules: affectedModules ? [...affectedModules] : [],
|
|
429
|
+
}
|
|
430
|
+
}
|
|
418
431
|
return L.join('\n')
|
|
419
432
|
}
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* writeLastDeltaSidecar —— delta 增量回灌 sidecar(change: 2026-09-07-ir-hardening,D-006@v1,FR-03)。
|
|
437
|
+
*
|
|
438
|
+
* 落 `.runtime/last-delta.json`(无主快照,幂等覆盖):schema
|
|
439
|
+
* `{ schemaVersion: 1, change, affectedModules: string[], affectedFiles: string[], updatedAt: string(ISO) }`。
|
|
440
|
+
* 消费方:scan 断点续扫步(executeScanResumeCheck)读它打「本轮 scan 优先核对模块」advisory(14 天窗口)。
|
|
441
|
+
* fail-soft:写失败只 console.error 留痕,不阻断 delta.md 生成(advisory 缺席即静默,零影响)。
|
|
442
|
+
*
|
|
443
|
+
* @param {string} runtimeRoot
|
|
444
|
+
* @param {{ change: string, affectedFiles: string[], affectedModules: string[] }} summary - buildDeltaReport({withSummary:true}) 的返回
|
|
445
|
+
*/
|
|
446
|
+
export function writeLastDeltaSidecar(runtimeRoot, summary) {
|
|
447
|
+
try {
|
|
448
|
+
if (!runtimeRoot || !summary || !summary.change) return { ok: false, reason: '参数缺失' }
|
|
449
|
+
const payload = {
|
|
450
|
+
schemaVersion: 1,
|
|
451
|
+
change: summary.change,
|
|
452
|
+
affectedModules: Array.isArray(summary.affectedModules) ? summary.affectedModules : [],
|
|
453
|
+
affectedFiles: Array.isArray(summary.affectedFiles) ? summary.affectedFiles : [],
|
|
454
|
+
updatedAt: new Date().toISOString(),
|
|
455
|
+
}
|
|
456
|
+
mkdirSync(runtimeRoot, { recursive: true })
|
|
457
|
+
writeFileSync(join(runtimeRoot, 'last-delta.json'), JSON.stringify(payload, null, 2) + '\n')
|
|
458
|
+
return { ok: true, path: join(runtimeRoot, 'last-delta.json') }
|
|
459
|
+
} catch (e) {
|
|
460
|
+
console.error(`[sillyspec] last-delta sidecar 写入失败(advisory 将缺席,不阻断 delta 生成):${e && e.message ? e.message : e}`)
|
|
461
|
+
return { ok: false, reason: e && e.message ? e.message : String(e) }
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* archive 三重核对(2026-09-09 ql-20260909-004,轮次经济学 §3.1 archive 收口机械化):
|
|
466
|
+
* module-impact.md 矩阵记录 vs 真实变更文件 vs module-map 归属——机械比对 CLI 代算,
|
|
467
|
+
* agent 只裁决不一致项。只读,供 archive extract-module-impact 步 prompt 注入/命令消费。
|
|
468
|
+
* @returns {{ ok: boolean, mismatches: string[], impactFiles: string[], actualFiles: string[], summary: string }}
|
|
469
|
+
*/
|
|
470
|
+
export function auditModuleImpactAgainstDiff({ cwd, changeName, specDir = null }) {
|
|
471
|
+
const mismatches = []
|
|
472
|
+
const specBase = specDir || join(cwd, '.sillyspec')
|
|
473
|
+
const impactPath = join(specBase, 'changes', changeName, 'module-impact.md')
|
|
474
|
+
let impactText = null
|
|
475
|
+
try { impactText = readFileSync(impactPath, 'utf8') } catch { /* 缺失由降级路径处理 */ }
|
|
476
|
+
const actualFiles = (resolveVerifyChangedFiles(cwd, changeName, null, { includeWorkingTree: true, specBase: specDir || join(cwd, '.sillyspec') }) || [])
|
|
477
|
+
.map(f => String(f).replace(/\\/g, '/'))
|
|
478
|
+
.filter(f => !f.startsWith('.sillyspec/changes/'))
|
|
479
|
+
if (!impactText) {
|
|
480
|
+
return { ok: false, mismatches: ['module-impact.md 不存在(走 --init 降级路径)'], impactFiles: [], actualFiles, summary: `真实变更 ${actualFiles.length} 文件;无 module-impact 可核` }
|
|
481
|
+
}
|
|
482
|
+
// 矩阵反引号路径收集(启发式——advisory 注入用,非硬门真相源:无反引号表格格会漏,外部审核 P3-3)
|
|
483
|
+
const impactFiles = [...impactText.matchAll(/`([^`]+[.](?:js|mjs|ts|py|java|md|yaml))`/g)].map(m => m[1].split(String.fromCharCode(92)).join('/'))
|
|
484
|
+
const impactSet = new Set(impactFiles)
|
|
485
|
+
const actualSet = new Set(actualFiles)
|
|
486
|
+
const inDiffNotDoc = [...actualSet].filter(f => !impactSet.has(f))
|
|
487
|
+
const inDocNotDiff = [...impactSet].filter(f => !actualSet.has(f))
|
|
488
|
+
if (inDiffNotDoc.length > 0) mismatches.push(`diff 有而 module-impact 未列(${inDiffNotDoc.length}):${inDiffNotDoc.slice(0, 5).join('、')}${inDiffNotDoc.length > 5 ? ' …' : ''}`)
|
|
489
|
+
if (inDocNotDiff.length > 0) mismatches.push(`module-impact 列而 diff 无(${inDocNotDiff.length}):${inDocNotDiff.slice(0, 5).join('、')}${inDocNotDiff.length > 5 ? ' …' : ''}`)
|
|
490
|
+
// 第三重:module-map 归属一致性(2026-09-09 外部审核 P2-1——「三重核对」此前名实不符:
|
|
491
|
+
// 矩阵行的模块列 vs map 前缀推导——误标模块点名;未命中 map 的归 unmatched 提示不判红)
|
|
492
|
+
const moduleMisattrib = auditImpactModuleAttribution(impactText, actualFiles, specBase)
|
|
493
|
+
mismatches.push(...moduleMisattrib)
|
|
494
|
+
return {
|
|
495
|
+
ok: mismatches.length === 0,
|
|
496
|
+
mismatches,
|
|
497
|
+
impactFiles,
|
|
498
|
+
actualFiles,
|
|
499
|
+
summary: `module-impact ${impactFiles.length} 文件 × 真实 diff ${actualFiles.length} 文件 × map 归属:${mismatches.length === 0 ? '一致 ✓' : mismatches.length + ' 类不一致'}`,
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* 第三重核对(P2-1):矩阵行「| <模块> | `文件` |」的模块列 vs _module-map.yaml 前缀推导。
|
|
505
|
+
* 只读 fail-soft:map 不可得 → 返回 [](前两重仍有效,注入文案会带 map 跳过注记)。
|
|
506
|
+
*/
|
|
507
|
+
function auditImpactModuleAttribution(impactText, actualFiles, specBase) {
|
|
508
|
+
try {
|
|
509
|
+
// 找 _module-map.yaml(docs/<p>/modules/ 扫描——detectModuleDocHealth 同款)
|
|
510
|
+
let mapText = null
|
|
511
|
+
const docsDir = join(specBase, 'docs')
|
|
512
|
+
if (existsSync(docsDir)) {
|
|
513
|
+
for (const d of readdirSync(docsDir)) {
|
|
514
|
+
const cand = join(docsDir, d, 'modules', '_module-map.yaml')
|
|
515
|
+
if (existsSync(cand)) { mapText = readFileSync(cand, 'utf8'); break }
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (!mapText) return []
|
|
519
|
+
const { parseModuleMapPaths } = require_module_impact()
|
|
520
|
+
const map = parseModuleMapPaths(mapText)
|
|
521
|
+
const deriveMod = (posixPath) => {
|
|
522
|
+
for (const [id, paths] of map) {
|
|
523
|
+
for (const pp of paths) {
|
|
524
|
+
if (pp.endsWith('/') ? posixPath.startsWith(pp) : (posixPath === pp || posixPath.startsWith(pp + '/'))) return id
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return null
|
|
528
|
+
}
|
|
529
|
+
const out = []
|
|
530
|
+
for (const m of impactText.matchAll(/^\|\s*([^|]+?)\s*\|[^|]*`([^`]+)`/gm)) {
|
|
531
|
+
const recorded = m[1].trim()
|
|
532
|
+
const file = m[2].split(String.fromCharCode(92)).join('/')
|
|
533
|
+
if (recorded.startsWith('---') || recorded.startsWith('模块')) continue
|
|
534
|
+
const derived = deriveMod(file)
|
|
535
|
+
if (derived && recorded !== derived && recorded !== 'unmapped') {
|
|
536
|
+
out.push(`map 归属不一致:${file} 矩阵记「${recorded}」但 map 推导为「${derived}」——修正矩阵模块列或跑 modules rebuild`)
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return out
|
|
540
|
+
} catch { return [] }
|
|
541
|
+
}
|
|
542
|
+
// 延迟引用(防环:module-impact 不反向依赖本模块)
|
|
543
|
+
import * as _moduleImpactMod from './module-impact.js'
|
|
544
|
+
function require_module_impact() { return _moduleImpactMod }
|
package/src/change-delete.js
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* - DB:status='deleted'(与 archived 语义分离,DB 可直接审计区分,行保留不物理删)
|
|
10
10
|
* - 目录:changes/<name>/ 物理移除(git tracked 文件删除后照常 commit,历史仍可回溯)
|
|
11
11
|
* - worktree:复用 archiveWorktreeCleanup(有未 apply 变更时保留 worktree 只警告——
|
|
12
|
-
* 不因删变更丢用户唯一副本的代码;顺带清 execute/stage-review runId marker
|
|
12
|
+
* 不因删变更丢用户唯一副本的代码;顺带清 execute/stage-review runId marker
|
|
13
|
+
* + 该变更的 runtime 取证(pruneArchivedChangeRuntime))
|
|
13
14
|
* - git:safeGit add -A 暂存目录删除(best-effort,对齐 archive CLI 下沉 git add)
|
|
14
15
|
* - 平台:triggerSync 推终态(sync.js 对 status='deleted' 上行墓碑,平台收敛软删)
|
|
15
16
|
*
|
package/src/change-list.js
CHANGED
|
@@ -74,8 +74,13 @@ export function globMatch(str, pattern) {
|
|
|
74
74
|
* @returns {boolean}
|
|
75
75
|
*/
|
|
76
76
|
export function pathMatches(a, b) {
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
// 坑 platform-sync-progress-rollback-and-db-corruption 坑3:NEW: 前缀(待建文件标记)在
|
|
78
|
+
// 「比对」语义下剥除对齐——design 清单带 NEW: 的待建文件 vs task allowed_paths 裸路径 /
|
|
79
|
+
// 主仓脏文件,比的是目标文件而非「新建意图」,剥后两道门禁(brainstorm 幻觉核验要求加
|
|
80
|
+
// NEW: / plan 覆盖对账按裸路径匹配)不再互斥。仅在比对处剥:存在性核验(design-facts
|
|
81
|
+
// 的 NEW: 豁免)不走本函数、仍见原文前缀;target_files 解析器(plan-postcheck)本就剥。
|
|
82
|
+
a = normalizePath(a).replace(/^NEW:/, '')
|
|
83
|
+
b = normalizePath(b).replace(/^NEW:/, '')
|
|
79
84
|
if (!a || !b) return false
|
|
80
85
|
if (a === b) return true
|
|
81
86
|
if (a.startsWith(b + '/') || b.startsWith(a + '/')) return true
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* 判定 5 级风险(doc-only / unit-sufficient / contract-required /
|
|
6
6
|
* integration-critical / deployment-critical),产出门控验证需求。
|
|
7
7
|
*/
|
|
8
|
+
import { readFileSync, statSync } from 'fs'
|
|
9
|
+
import { join } from 'path'
|
|
8
10
|
|
|
9
11
|
// ============ 向后兼容:旧的 INTEGRATION_CRITICAL_PATTERNS ============
|
|
10
12
|
|
|
@@ -292,10 +294,40 @@ export function checkIntegrationEvidence(verifyContent, requiredVerification, op
|
|
|
292
294
|
const warnings = []
|
|
293
295
|
const lower = (String(verifyContent || '') + '\n' + String(opts.extraEvidenceText || '')).toLowerCase()
|
|
294
296
|
|
|
295
|
-
//
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
//
|
|
297
|
+
// ── v2 回执槽优先(2026-09-08-ir-verify-facts FR-04,D-003@v1):「集成验证回执」槽在场时
|
|
298
|
+
// 走结构化一致性校验(绿判据:logPath 存在 × mtime ∈ verify 窗口 × 日志尾 200 行失败签名
|
|
299
|
+
// 扫描(噪声剔除)× exitCode===0),literals 降 legacy 回退(存量兼容)。
|
|
300
|
+
// extraEvidenceText(verify-services 回执注入)保留合并为补充候选文本。CLI 不代跑集成进程。──
|
|
301
|
+
if (Array.isArray(opts.runtimeEvidence) && opts.runtimeEvidence.length > 0) {
|
|
302
|
+
const greenReceipts = []
|
|
303
|
+
const receiptAudit = []
|
|
304
|
+
for (const r of opts.runtimeEvidence) {
|
|
305
|
+
const audit = auditRuntimeReceipt(r, opts)
|
|
306
|
+
receiptAudit.push(audit)
|
|
307
|
+
if (audit.green) greenReceipts.push(r)
|
|
308
|
+
}
|
|
309
|
+
const needsIntegration = requiredVerification.includes('real_daemon_backend_integration')
|
|
310
|
+
const needsLogEvidence = requiredVerification.includes('runtime_log_evidence')
|
|
311
|
+
const needsRealStartup = requiredVerification.includes('real_startup_once')
|
|
312
|
+
// 任一绿回执 = 结构化在场证据(claim 语义由 agent 声明,一致性由 CLI 校验)
|
|
313
|
+
const hasGreen = greenReceipts.length > 0
|
|
314
|
+
if (needsIntegration && !hasGreen) {
|
|
315
|
+
const bad = receiptAudit.filter(a => !a.green)
|
|
316
|
+
errors.push(`integration-critical 变更无绿回执(${bad.length} 条回执校验不过:${bad.map(a => a.reason).join(';') || '无有效回执'})——绿判据:log 存在 × mtime ∈ verify 窗口 × 日志无失败签名 × exit 0`)
|
|
317
|
+
}
|
|
318
|
+
if (needsLogEvidence && !hasGreen) {
|
|
319
|
+
errors.push('integration-critical 变更的回执未通过一致性校验(Runtime Evidence 等价物)')
|
|
320
|
+
}
|
|
321
|
+
if (needsRealStartup && !hasGreen) {
|
|
322
|
+
errors.push('deployment-critical 变更无绿回执(真实启动验证)')
|
|
323
|
+
}
|
|
324
|
+
if (receiptAudit.some(a => !a.green && a.failSignatures > 0)) {
|
|
325
|
+
warnings.push('回执日志含失败签名(error/exception/traceback/fatal 行首命中,已剔除「0 errors」类良性行)——确认是否预期内失败')
|
|
326
|
+
}
|
|
327
|
+
return { ok: errors.length === 0, errors, warnings, receiptAudit, structured: true }
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ── legacy literals 回退(无槽存量格式)──
|
|
299
331
|
const hasEvidence = (k) => {
|
|
300
332
|
const n = VERIFICATION_NEEDS[k]
|
|
301
333
|
if (!n || !n.literals || n.literals.length === 0) return true
|
|
@@ -335,5 +367,44 @@ export function checkIntegrationEvidence(verifyContent, requiredVerification, op
|
|
|
335
367
|
}
|
|
336
368
|
}
|
|
337
369
|
|
|
338
|
-
return { ok: errors.length === 0, errors, warnings }
|
|
339
|
-
}
|
|
370
|
+
return { ok: errors.length === 0, errors, warnings, structured: false }
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* 单条回执一致性校验(FR-04 绿判据)。噪声剔除:签名行首匹配 + 剔除「0 errors」类良性行
|
|
375
|
+
* (本仓 verify-postcheck.js 测试输出解析先例同款)。fail-soft:校验依赖缺失(opts 无
|
|
376
|
+
* cwd/verifyStartAt)时只核 exitCode 与日志可读性,不假装跑了文件校验。
|
|
377
|
+
*/
|
|
378
|
+
function auditRuntimeReceipt(r, opts) {
|
|
379
|
+
const out = { claim: r && r.claim, logPath: r && r.logPath, green: false, logExists: null, mtimeInWindow: null, failSignatures: 0, exitCode: r && typeof r.exitCode === 'number' ? r.exitCode : null, reason: '' }
|
|
380
|
+
if (!r || !r.logPath) { out.reason = '回执缺 logPath'; return out }
|
|
381
|
+
let content = null
|
|
382
|
+
try {
|
|
383
|
+
const abs = opts.cwd ? join(opts.cwd, r.logPath) : r.logPath
|
|
384
|
+
content = readFileSync(abs, 'utf8')
|
|
385
|
+
out.logExists = true
|
|
386
|
+
} catch {
|
|
387
|
+
out.logExists = false
|
|
388
|
+
out.reason = `日志不存在:${r.logPath}`
|
|
389
|
+
return out
|
|
390
|
+
}
|
|
391
|
+
if (opts.verifyStartAt) {
|
|
392
|
+
try {
|
|
393
|
+
const startAt = new Date(opts.verifyStartAt).getTime()
|
|
394
|
+
out.mtimeInWindow = statSync(opts.cwd ? join(opts.cwd, r.logPath) : r.logPath).mtimeMs >= startAt - 60_000
|
|
395
|
+
if (!out.mtimeInWindow) { out.reason = `日志 mtime 早于 verifyStartAt(${r.logPath})`; return out }
|
|
396
|
+
} catch { out.mtimeInWindow = null }
|
|
397
|
+
}
|
|
398
|
+
// 失败签名扫描(日志尾 200 行;行首匹配 + 良性计数行剔除)
|
|
399
|
+
const tail = content.split('\n').slice(-200)
|
|
400
|
+
const SIGNATURE_RE = /^(?:error|exception|traceback|fatal)\b/i
|
|
401
|
+
const BENIGN_RE = /\b0\s+(?:errors?|exceptions?|failures?)\b|no\s+(?:errors?|exceptions?)/i
|
|
402
|
+
for (const line of tail) {
|
|
403
|
+
if (SIGNATURE_RE.test(line.trim()) && !BENIGN_RE.test(line)) out.failSignatures++
|
|
404
|
+
}
|
|
405
|
+
if (out.failSignatures > 0) { out.reason = `日志含 ${out.failSignatures} 行失败签名(行首 error/exception/traceback/fatal,已剔除良性计数行)`; return out }
|
|
406
|
+
if (out.exitCode !== 0) { out.reason = `exitCode=${out.exitCode}(非 0 不可用作在场证据)`; return out }
|
|
407
|
+
out.green = true
|
|
408
|
+
out.reason = '绿回执(log 存在 × mtime 窗口 × 无失败签名 × exit 0)'
|
|
409
|
+
return out
|
|
410
|
+
}
|
package/src/config-cat.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { homedir } from 'os'
|
|
2
|
+
import { sep } from 'path'
|
|
1
3
|
/**
|
|
2
4
|
* config-cat.js — local.yaml 实际值读取的权威路径解析器(`sillyspec config cat` 数据源)。
|
|
3
5
|
*
|
|
@@ -39,9 +41,18 @@ export function resolveLocalYaml(dir, opts = {}) {
|
|
|
39
41
|
candidates.push({ path: key, source })
|
|
40
42
|
}
|
|
41
43
|
|
|
44
|
+
// home 拒绝守卫(e4f2855 resolveSpecDir 同款,2026-09-09 补到 local.yaml 候选链):
|
|
45
|
+
// 起点在 home 子树内(如 Temp 下的测试 fixture)时,祖先链走到 home 层不得命中
|
|
46
|
+
// ~/.sillyspec/local.yaml——那是用户全局配置,不是本项目配置;register-repo 写侧
|
|
47
|
+
// 经 resolveLocalYamlWriteTarget 同链解析,误命中会把项目注册静默写进全局配置
|
|
48
|
+
// (local-register.test.mjs 实证)。
|
|
49
|
+
const home = homedir()
|
|
50
|
+
const originBelowHome = start !== home && start.startsWith(home + sep)
|
|
42
51
|
if (opts.specBase) push(join(opts.specBase, 'local.yaml'), 'spec 根(--spec-dir/平台 pointer/最近 .sillyspec)')
|
|
43
52
|
for (let d = start; ; d = dirname(d)) {
|
|
44
|
-
|
|
53
|
+
if (!(originBelowHome && d === home)) {
|
|
54
|
+
push(join(d, '.sillyspec', 'local.yaml'), 'cwd 祖先链(worktree 内向上命中主仓;home 层受守卫跳过)')
|
|
55
|
+
}
|
|
45
56
|
const parent = dirname(d)
|
|
46
57
|
if (parent === d) break
|
|
47
58
|
}
|
package/src/constants.js
CHANGED
|
@@ -93,5 +93,13 @@ export const AUXILIARY_STAGES = Object.freeze([
|
|
|
93
93
|
// 供 command.js 判断 status/doctor 等查询型辅助阶段执行只读短路(FR-04 / D-005@v2)。
|
|
94
94
|
export const READONLY_AUXILIARY_STAGES = Object.freeze([
|
|
95
95
|
'status',
|
|
96
|
-
|
|
96
|
+
// doctor 移出(2026-09-09-doctor-noai D-001@v1):阶段形态走状态机(noAI 诊断步 +
|
|
97
|
+
// agent 修复步——只读短路使 _cliAction 不可达);顶层 sillyspec doctor 命令的只读
|
|
98
|
+
// 语义由 index.js case 头部拦截保真(--json/--status 先行分流)。
|
|
97
99
|
])
|
|
100
|
+
|
|
101
|
+
// ── IR 严格模式闸门(change: 2026-09-07-ir-hardening,D-001@v1)──
|
|
102
|
+
// created_at ≥ 此值的变更进入严格模式(P3b 探针子节缺失 / P3a 主仓卡全零声明 → ERROR);
|
|
103
|
+
// 早于此值的存量变更保持既有豁免语义零变化。写入口径:changes.created_at 恒为
|
|
104
|
+
// new Date().toISOString()(change-registry 全部写点同源),ISO 等长字符串字典序比较即确定。
|
|
105
|
+
export const IR_STRICT_SINCE = '2026-09-07'
|