sillyspec 3.33.0 → 3.33.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/sillyspec-flow/SKILL.md +18 -10
- package/.github/workflows/ci.yml +11 -3
- package/AGENTS.md +3 -3
- package/README.md +2 -2
- package/assets/command-cards/flow.md +9 -5
- package/package.json +1 -1
- package/src/archive-delta.js +4 -3
- package/src/change-delete.js +6 -5
- package/src/contract-matrix.js +2 -1
- package/src/cross-placement.js +5 -1
- package/src/cross-repo-reconcile.js +4 -1
- package/src/db.js +8 -2
- package/src/decision-distill.js +22 -8
- package/src/decision-values.js +34 -0
- package/src/decisions-io.js +5 -2
- package/src/deleted-ledger.js +2 -1
- package/src/doctor-diagnostics.js +8 -3
- package/src/flow-draft.js +17 -10
- package/src/flow-parity.js +93 -5
- package/src/flow-review-pack.js +183 -0
- package/src/flow-review.js +45 -3
- package/src/flow.js +365 -109
- package/src/friction-ledger.js +2 -1
- package/src/friction-tally.js +8 -4
- package/src/handoff.js +2 -1
- package/src/hooks/worktree-guard.js +5 -5
- package/src/index.js +62 -21
- package/src/init.js +7 -30
- package/src/knowledge-hits.js +2 -1
- package/src/knowledge-vector.js +2 -1
- package/src/progress/consistency-doctor.js +5 -2
- package/src/progress/stage-machine.js +6 -2
- package/src/progress.js +19 -8
- package/src/quicklog.js +194 -9
- package/src/review-dispatch.js +4 -3
- package/src/review-tier.js +2 -1
- package/src/route-hindsight.js +4 -3
- package/src/run/bg-sync.js +10 -3
- package/src/run/command.js +14 -12
- package/src/run/complete-handlers.js +40 -37
- package/src/run/complete.js +12 -8
- package/src/run/gate-snapshot-ledger.js +2 -1
- package/src/run/gate-snapshot.js +102 -23
- package/src/run/gates.js +32 -8
- package/src/run/prompt.js +6 -5
- package/src/run/scan-profile.js +2 -1
- package/src/run/shared.js +109 -5
- package/src/run/stage.js +5 -4
- package/src/run/test-ledger.js +2 -1
- package/src/run/verify-quality-scan.js +11 -5
- package/src/runtime-paths.js +189 -0
- package/src/scan-refresh.js +4 -3
- package/src/scope-audit.js +13 -7
- package/src/sentinel-assertions.js +37 -0
- package/src/spec-sync.js +38 -22
- package/src/stage-contract.js +24 -2
- package/src/stage-review.js +3 -2
- package/src/stage-templates.js +3 -2
- package/src/stages/brainstorm.js +6 -5
- package/src/sync-noise.js +3 -2
- package/src/sync.js +71 -15
- package/src/task-done.js +2 -1
- package/src/task-review.js +7 -5
- package/src/task-tick.js +45 -1
- package/src/validate-artifacts.js +2 -1
- package/src/verify-draft.js +2 -1
- package/src/verify-postcheck.js +11 -10
- package/src/verify-probes.js +157 -52
- package/src/watcher.js +15 -9
- package/src/worktree-apply.js +2 -1
- package/src/write-audit.js +2 -1
- package/templates/agents-instruction.md +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sillyspec:flow
|
|
3
|
-
description:
|
|
3
|
+
description: 轻量变更(默认快道)——需求明确的小/中改动。适合用户说"直接改、快速修、顺手调整、改个文案、修个小 bug、更新一个文件、需求很明确、开干吧、把这个改了"。协议链:flow start → 补 spec 面(FR 绑定+design 四节)→ 批准(flow approve / 用户显式 --autopilot)→ 干活(改代码写测试)→ flow done(实测门+测试绑定+patch 留档)。顺序纪律:先答 spec 面、批准后才动码(读代码探究不算动码)。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
## 何时使用
|
|
@@ -11,12 +11,14 @@ description: 轻量变更(默认快道)——需求明确的小/中改动两
|
|
|
11
11
|
- 需求不清晰 → CLI 会在 flow start 处拦下给两选一(头脑风暴预段 / 确认输入已含决策);或直接先走 `/sillyspec:brainstorm`,完成后 `flow start` 收编续跑(design 以头脑风暴版为准)
|
|
12
12
|
- 大改动(跨模块取舍 / 需要 Wave 计划编排 / 设计期人机对抗)→ 不走本 skill,走完整五阶段流程
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## 协议链与顺序纪律
|
|
15
|
+
|
|
16
|
+
顺序正典:flow start 后**先答 spec 面 → 断点① 批准 → 才动码**(读代码探究不算动码)。批准是动码前置义务,不是收口补救动作。
|
|
15
17
|
|
|
16
18
|
### ① flow start(立项 + 锁基线)
|
|
17
19
|
|
|
18
20
|
```bash
|
|
19
|
-
sillyspec flow start --change <变更名> --input "
|
|
21
|
+
sillyspec flow start --change <变更名> --input "<一句 10-30 字中文概括(首行即变更标题,硬区间 10-30 必须、建议 ~20 字);随后动机与背景
|
|
20
22
|
|
|
21
23
|
成功标准:
|
|
22
24
|
- <可验证标准>"
|
|
@@ -35,16 +37,22 @@ sillyspec flow start --change <变更名> --input "<一句中文概括(首行
|
|
|
35
37
|
- 新增单测覆盖窗口翻转边界,npm test 全绿
|
|
36
38
|
```
|
|
37
39
|
|
|
38
|
-
### ②
|
|
39
|
-
|
|
40
|
-
改代码、写测试。治理工件(proposal/requirements/design)CLI 机器起草,你只需要:
|
|
40
|
+
### ② 补 spec 面(动码前)
|
|
41
41
|
|
|
42
|
-
-
|
|
42
|
+
- requirements:每条 FR 行为句(带强度词 必须/禁止/SHOULD)+ 测试绑定行(无测试面写「不适用:理由」)
|
|
43
|
+
- design.md 四节 AGENT 槽作答(做法概述/接口契约/边界并发四问/风险与死路)——**动码前完成**,每节至少一行,写「不适用:<理由>」也算答,空节 flow done 拒收;列改动文件用独立「## 文件变更清单」节
|
|
43
44
|
- 变更目录若已有收割生成的 decisions.md(frontmatter `author: flow-machine-draft`),它是底稿不是终稿:槽4 作答含「放弃方案」的,收口前把每个放弃方案拆成独立 rejected 条目(问题=决策专属问句、否决理由=放弃理由、复潮条件=重提前提),风险与对策留在主条目——rejected 条目才走蒸馏防复潮通道,埋在散文里的否决理由进不了通道;无实质放弃方案则不动
|
|
44
|
-
-
|
|
45
|
-
|
|
45
|
+
- tasks.md 改写为真实工作分解(机器种子按实现路径改写,保持 `- [ ] task-NN:` 行形态)
|
|
46
|
+
|
|
47
|
+
### ③ 断点①:批准后才动码
|
|
48
|
+
|
|
49
|
+
把 FR 条目+design 四节摘要给用户看 → 用户运行 `sillyspec flow approve --change <名>`(agent 代跑如实声明通道 `--user-confirmed` / `--self-approved`,无 flag 按 unattested 留痕);用户明确说「直接跑完/不用问我」→ 重跑 `flow start --autopilot` 声明豁免(用户显式授权,非 agent 自选补救)。未批准且未声明 --autopilot 时 flow done 拒收。
|
|
50
|
+
|
|
51
|
+
### ④ 干活(已批准)
|
|
52
|
+
|
|
53
|
+
写测试、改代码(按层适用——纯逻辑先写测试、行为契约断言用户可见行为、视觉样式与纯配置不写测试)。以 tasks.md 为进度源:做一件 → 勾一格(`- [ ]` → `- [x]`)→ 继续下一条,勿攒一把勾(勾选是收口哨兵的证据面——逐 task 核提交 token/review.json);`flow status --change <名>` 为自愿查看/恢复面(②阶段给下一任务指针与进度,非协议必需——D-007);交付代码用显式 pathspec 提交(`git commit -m "..." -- 文件1 文件2`;patch 冻结面=baseline..HEAD 提交面,未提交的代码不进审计件)
|
|
46
54
|
|
|
47
|
-
###
|
|
55
|
+
### ⑤ flow done(收口)
|
|
48
56
|
|
|
49
57
|
```bash
|
|
50
58
|
sillyspec flow done --change <变更名>
|
package/.github/workflows/ci.yml
CHANGED
|
@@ -20,7 +20,8 @@ jobs:
|
|
|
20
20
|
- uses: actions/checkout@v4
|
|
21
21
|
- uses: actions/setup-node@v4
|
|
22
22
|
with:
|
|
23
|
-
node-version: '
|
|
23
|
+
node-version: '24'
|
|
24
|
+
# mock.module 内置模块 named exports 枚举在 node 22 不全(appendFileSync 等缺失实证 38859877373)——统一 24 与本地开发环一致
|
|
24
25
|
- name: lint(check-syntax 全文件)
|
|
25
26
|
run: npm run lint --silent
|
|
26
27
|
|
|
@@ -32,12 +33,18 @@ jobs:
|
|
|
32
33
|
- uses: actions/checkout@v4
|
|
33
34
|
- uses: actions/setup-node@v4
|
|
34
35
|
with:
|
|
35
|
-
node-version: '
|
|
36
|
+
node-version: '24'
|
|
37
|
+
# mock.module 内置模块 named exports 枚举在 node 22 不全(appendFileSync 等缺失实证 38859877373)——统一 24 与本地开发环一致
|
|
36
38
|
# 依赖必须装:测试框架零三方(node:sqlite/test 内置)但被测 CLI 本体 import 7 个
|
|
37
39
|
# 运行时依赖(js-yaml/chalk/@inquirer/prompts 等,src 下 10+ 文件)——CI 首跑
|
|
38
40
|
# (121a33ee)无 install 步实证两 job 全灭(Cannot find package 'js-yaml')
|
|
39
41
|
- name: install(npm ci 锁定 package-lock)
|
|
40
42
|
run: npm ci --no-audit --no-fund
|
|
43
|
+
- name: 诊断:node/dash 可用性(run-tests 子进程 shell 语义面)
|
|
44
|
+
run: |
|
|
45
|
+
which node
|
|
46
|
+
node -e "0" && echo "sh 直跑 node -e OK"
|
|
47
|
+
/bin/sh -c 'node -e "0"' && echo "sh -c 内嵌引号 OK"
|
|
41
48
|
- name: test(run-tests 并发池 + TEMP/HOME 套件隔离)
|
|
42
49
|
run: npm test --silent
|
|
43
50
|
|
|
@@ -48,7 +55,8 @@ jobs:
|
|
|
48
55
|
- uses: actions/checkout@v4
|
|
49
56
|
- uses: actions/setup-node@v4
|
|
50
57
|
with:
|
|
51
|
-
node-version: '
|
|
58
|
+
node-version: '24'
|
|
59
|
+
# mock.module 内置模块 named exports 枚举在 node 22 不全(appendFileSync 等缺失实证 38859877373)——统一 24 与本地开发环一致
|
|
52
60
|
# docs-check.js 同样 import js-yaml——无 install 首跑 7s 即灭(同 test job 根因)
|
|
53
61
|
- name: install(npm ci 锁定 package-lock)
|
|
54
62
|
run: npm ci --no-audit --no-fund
|
package/AGENTS.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
## 选道(动手前先对号入座)
|
|
8
8
|
| 需求形态 | 走法 |
|
|
9
9
|
|---|---|
|
|
10
|
-
| 需求已含决策(改什么、成功标准说得清——成功标准须来自**用户原话或既有文档转写**;要靠你推断拟出的标准=需求不清晰) | **轻量变更(默认快道)**:`flow start` →
|
|
10
|
+
| 需求已含决策(改什么、成功标准说得清——成功标准须来自**用户原话或既有文档转写**;要靠你推断拟出的标准=需求不清晰) | **轻量变更(默认快道)**:`flow start` → 补 spec 面(FR 绑定+design 四节)→ 批准(`flow approve` / 用户显式 `--autopilot`)→ 干活 → `flow done`(协议调用仍 2 次,批准由用户运行;操作细节见 `/sillyspec:flow` skill 与 flow start 输出) |
|
|
11
11
|
| 用户显式要求极速(「极速/别验证/只要 spec 和代码」类原话)且需求已含决策 | **轻量变更极速版**:同上行加 `--turbo`(`flow start --change <名> --input "<...>" --turbo`)——**仅用户显式触发,禁止你自行启用**;收口跳过实测与重仪式门(hunk/dirty/ui 探针/评审),spec 结构校验/tasks 全勾/ledger/patch 冻结/distill/归档照常(跳的是验证不是证据);升厚即失效恢复全量仪式;收口输出显式标注「不含测试客观核验」。预段组合极速:头脑风暴预段走轻量跑判定+单轮合并问询+burst 一次 --done,收编时按用户要求带 --turbo |
|
|
12
12
|
| 需求不清晰 / 需要方案探索(含「成功标准要你替用户拟」的形态——自己拟标准再自答是把默认共识伪装成共识) | 头脑风暴预段:`run brainstorm --change <名>` → 完成后 `flow start --change <名>` 收编续跑(design 以头脑风暴版为准)。上个轻量变更被 route-hindsight 标记时全新 start 会被同意门拦下要求显式选道:确认已含决策带 `--no-prestage` 声明留痕,否则先走本行预段 |
|
|
13
13
|
| 大改动:跨模块取舍 / Wave 计划编排 / 多阶段治理 / 设计期人机对抗 | 完整流程五阶段:`run brainstorm → plan → execute → verify → archive`(每阶段一次渲染 + 一次 --done 收口;细节见各阶段 skill 与 CLI 输出) |
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
`--input` 过门格式(轻量道首次调用即过,勿靠报错学;引号内换行合法,下例可照抄):
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
sillyspec flow start --change <YYYY-MM-DD-名> --input "
|
|
19
|
+
sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句 10-30 字中文概括(首行即变更标题,必须落在 10-30 字硬区间、建议 ~20 字——不合区间 flow start 拒启);随后动机与背景
|
|
20
20
|
|
|
21
21
|
成功标准:
|
|
22
22
|
- <可验证标准>"
|
|
@@ -32,7 +32,7 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句中文概括(
|
|
|
32
32
|
## 核心规则
|
|
33
33
|
1. **禁止绕过本文件规则和 SillySpec 流程**。所有变更走 sillyspec 流程,不裸改裸提交。
|
|
34
34
|
2. **改代码前必须先说明依据**——依据的文档路径(design.md / 模块文档 / file-lifecycle.md(如有))或现有代码依据,无依据不改。
|
|
35
|
-
3. **执行顺序**:文档 → 读代码 → 写测试 → 写实现 → 跑测试 → 验收 →
|
|
35
|
+
3. **执行顺序**:文档 → 读代码 → 写测试 → 写实现 → 跑测试 → 验收 → 更新文档(轻量道即:FR 绑定+design 四节+用户批准先于动码,读代码探究不算动码)。测试按层适用:纯逻辑先写测试,行为契约断言用户可见行为,视觉样式与纯配置/文档不写测试(记「不适用:理由」,验收靠规范与人工)。
|
|
36
36
|
4. **实证核验再收口**:触及 `src`/`test` 的改动,CLI 会在收口时亲自实测(实测失败阻断收口;纯 doc/配置自动跳过)。测试面按变更动态推断(本变更测试 ∪ FR 关联回归 ∪ import 依赖,runner 自项目结构推断——无需配置);lint 走 `.sillyspec/local.yaml` 的 `commands.lint`。以落盘文件与测试结果为准,不信口头"已完成"。
|
|
37
37
|
5. **非测试逻辑本身有误时,禁止改测试来"通过"**——修逻辑,不修测试。
|
|
38
38
|
6. **git hook 拦截提交时禁止跳过**(如 `.husky/pre-push`),修复问题后再提交。
|
package/README.md
CHANGED
|
@@ -46,9 +46,9 @@ npx sillyspec init
|
|
|
46
46
|
| Claude Code(推荐) | `claude` | `AGENTS.md`(完整指引)+ `CLAUDE.md`(`@AGENTS.md` 指针)+ `.claude/skills/sillyspec-*/` |
|
|
47
47
|
| Cursor | `cursor` | `.cursor/skills/sillyspec-*/` |
|
|
48
48
|
| OpenAI Codex | `codex` | `AGENTS.md`(完整指引) |
|
|
49
|
-
| OpenCode | `opencode` | `INSTRUCTIONS.md` |
|
|
50
49
|
| OpenClaw | `openclaw` | `.openclaw/skills/sillyspec-*/` |
|
|
51
|
-
|
|
|
50
|
+
| OpenCode | `opencode` | `AGENTS.md`(完整指引)+ `.opencode/skills/sillyspec-*/` |
|
|
51
|
+
| Gemini | `gemini` | `AGENTS.md`(完整指引) |
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
54
|
npx sillyspec init --tool claude # 指定工具
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sillyspec:flow
|
|
3
|
-
description:
|
|
3
|
+
description: 轻量变更(默认快道):需求明确的小/中改动——flow start → 补 spec 面 → 批准 → 干活 → flow done
|
|
4
4
|
---
|
|
5
5
|
# 何时用
|
|
6
6
|
|
|
@@ -11,19 +11,23 @@ description: 轻量变更(默认快道):需求明确的小/中改动两步
|
|
|
11
11
|
```bash
|
|
12
12
|
export SILLYSPEC_SESSION_ID=<agent名+任务名> # 每条命令都带(shell 状态不持久;多会话并行的所有权判定依赖它)
|
|
13
13
|
# ① 立项 + 锁基线(--input 引号内换行合法,可照抄;缺「成功标准」条目会被清晰度门拦下):
|
|
14
|
-
sillyspec flow start --change <变更名> --input "
|
|
14
|
+
sillyspec flow start --change <变更名> --input "<一句 10-30 字中文概括(首行即变更标题,硬区间 10-30 必须、建议 ~20 字);随后动机与背景
|
|
15
15
|
|
|
16
16
|
成功标准:
|
|
17
17
|
- <可验证标准>"
|
|
18
18
|
# 极速变体:用户显式说「极速/别验证/只要 spec 和代码」类原话时才加 --turbo(仅用户显式触发,
|
|
19
19
|
# 禁止你自行启用)——flow-state 留痕,收口跳过实测与重仪式门(hunk/dirty/ui 探针/评审),
|
|
20
20
|
# spec 结构校验、tasks 全勾、ledger、patch 冻结、distill、归档照常;升厚即失效恢复全量仪式。
|
|
21
|
-
# ②
|
|
22
|
-
#
|
|
21
|
+
# ② 补 spec 面(动码前):requirements FR 行为句(强度词)+ 测试绑定行、design.md 四节作答
|
|
22
|
+
# (空节 flow done 拒收;列改动文件用独立「## 文件变更清单」节);tasks.md 改写为工作队列
|
|
23
|
+
# ——机器种子改写为实现步骤(每行做什么+怎么验证,保持 `- [ ] task-NN:` 行形态)。
|
|
24
|
+
# 然后过断点①:摘要给用户看 → 用户运行 sillyspec flow approve --change <名>(批准留痕;
|
|
25
|
+
# 用户显式说「直接跑完」→ 重跑 start --autopilot 豁免)。批准是动码前置义务,不是收口补救。
|
|
26
|
+
# ③ 干活(已批准):写测试、改代码(按层适用——纯逻辑先写、行为契约断用户所见、样式/纯配置不写);
|
|
23
27
|
# 执行循环:Working on task N/M → 做一件 → 测试绿后当场勾一格(sillyspec task tick --change <名> --task task-NN
|
|
24
28
|
# 即时回显进度与下一任务)→ 下一行;每格勾选要有对应提交(消息带 task-NN),一把勾收口被拒。
|
|
25
29
|
# 交付代码显式 pathspec 提交(patch 冻结面=baseline..HEAD 提交面,未提交不进审计件)
|
|
26
|
-
#
|
|
30
|
+
# ④ 收口(CLI 亲自实测 test/lint + 独立评审定档 + patch 留档;--turbo 变更自动跳过实测与重仪式门):
|
|
27
31
|
sillyspec flow done --change <变更名>
|
|
28
32
|
# 断点恢复 / 随时查进度:
|
|
29
33
|
sillyspec flow status --change <变更名>
|
package/package.json
CHANGED
package/src/archive-delta.js
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
* endpoint-extractor,被依赖者均不反向 import 本模块。
|
|
30
30
|
*/
|
|
31
31
|
import { existsSync, readFileSync, readdirSync, writeFileSync, mkdirSync } from 'node:fs'
|
|
32
|
+
import { runtimeStatePath } from './runtime-paths.js'
|
|
32
33
|
import { basename, dirname, join } from 'node:path'
|
|
33
34
|
import { parseDecisionDomains, loadModuleMap, deriveActualModules } from './design-facts.js'
|
|
34
35
|
import { parseModuleMapSimple } from './modules.js'
|
|
@@ -153,7 +154,7 @@ export function collectDeltaSources({ changeDir, specRoot, project, runtimeRoot,
|
|
|
153
154
|
// 兜底(Gap-2/D-003):全无 reconcile → apply-pathspec-<change>.txt 逐行路径作交付清单
|
|
154
155
|
let deliverables = []
|
|
155
156
|
if (!reconcile && runtimeRoot && change) {
|
|
156
|
-
const text = readTextSafe(
|
|
157
|
+
const text = readTextSafe(runtimeStatePath(runtimeRoot, `apply-pathspec-${change}.txt`))
|
|
157
158
|
if (text !== null) {
|
|
158
159
|
deliverables = text.split(/\r?\n/).map(s => s.trim()).filter(Boolean)
|
|
159
160
|
}
|
|
@@ -454,8 +455,8 @@ export function writeLastDeltaSidecar(runtimeRoot, summary) {
|
|
|
454
455
|
updatedAt: new Date().toISOString(),
|
|
455
456
|
}
|
|
456
457
|
mkdirSync(runtimeRoot, { recursive: true })
|
|
457
|
-
writeFileSync(
|
|
458
|
-
return { ok: true, path:
|
|
458
|
+
writeFileSync(runtimeStatePath(runtimeRoot, 'last-delta.json'), JSON.stringify(payload, null, 2) + '\n')
|
|
459
|
+
return { ok: true, path: runtimeStatePath(runtimeRoot, 'last-delta.json') }
|
|
459
460
|
} catch (e) {
|
|
460
461
|
console.error(`[sillyspec] last-delta sidecar 写入失败(advisory 将缺席,不阻断 delta 生成):${e && e.message ? e.message : e}`)
|
|
461
462
|
return { ok: false, reason: e && e.message ? e.message : String(e) }
|
package/src/change-delete.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 不因删变更丢用户唯一副本的代码;顺带清 execute/stage-review runId marker
|
|
13
13
|
* + 该变更的 runtime 取证(pruneArchivedChangeRuntime))
|
|
14
14
|
* - git:safeGit add -A 暂存目录删除(best-effort,对齐 archive CLI 下沉 git add)
|
|
15
|
-
* - 平台:
|
|
15
|
+
* - 平台:triggerSyncTerminalStrict 推终态(sync.js 对 status='deleted' 上行墓碑,平台收敛软删;文件树推送失败显性化)
|
|
16
16
|
*
|
|
17
17
|
* 两段式安全(对齐 --cleanup-remnant / --cleanup-ghosts / --align-execute-progress 家族):
|
|
18
18
|
* 默认 dry-run 只报告将发生什么,--confirm 才执行。
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
import { existsSync, readdirSync, rmSync, statSync } from 'fs';
|
|
24
24
|
import { join, relative } from 'path';
|
|
25
25
|
import { safeGit } from './git-helper.js';
|
|
26
|
-
import { assertSafeChangeName,
|
|
26
|
+
import { assertSafeChangeName, triggerSyncTerminalStrict } from './run/shared.js';
|
|
27
27
|
import { archiveWorktreeCleanup } from './run/complete-handlers.js';
|
|
28
28
|
|
|
29
29
|
function countFiles(dir) {
|
|
@@ -156,10 +156,11 @@ export async function deleteChange({ cwd, specDir = null, changeName, confirm =
|
|
|
156
156
|
safeGit(cwd, ['add', '-A', '--', relDir]);
|
|
157
157
|
}
|
|
158
158
|
} catch { /* 非 git 仓等场景:删除在 git status 可见,暂存非必需 */ }
|
|
159
|
-
// 5.
|
|
160
|
-
//
|
|
159
|
+
// 5. 平台终态/墓碑上行(终态强校验:内联有界同步,文件树推送失败显性化——🚨 告警 +
|
|
160
|
+
// 自救命令 + exit 1;未连接静默。change-delete 是终态一次性命令,await 保证墓碑
|
|
161
|
+
// 发出后再退出,输出顺序确定)
|
|
161
162
|
try {
|
|
162
|
-
await
|
|
163
|
+
await triggerSyncTerminalStrict(cwd, changeName, { specRoot: specBase });
|
|
163
164
|
} catch (e) {
|
|
164
165
|
warnings.push(`平台终态同步失败(不阻断删除,下次常规同步重试):${e.message}`);
|
|
165
166
|
}
|
package/src/contract-matrix.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'fs'
|
|
12
|
+
import { runtimeStatePath } from './runtime-paths.js'
|
|
12
13
|
import { join, resolve, basename, dirname, relative, isAbsolute } from 'path'
|
|
13
14
|
import { gitQuiet } from './git-helper.js'
|
|
14
15
|
import { splitOwnVsForeignDiffFiles } from './foreign-declared.js'
|
|
@@ -440,7 +441,7 @@ function _resolveDiffFilesForParity(specBase, scanRoot, changeName, runtimeRoot)
|
|
|
440
441
|
// 主仓 diff 空(apply 后已 commit / cleanup 完毕)→ apply-pathspec 兜底:apply 成功时落盘的
|
|
441
442
|
// 本变更精确文件清单(与 gates 归因提示②同源)。仍拿不到才回 null(调用方退全仓)。
|
|
442
443
|
try {
|
|
443
|
-
const pathspecFile =
|
|
444
|
+
const pathspecFile = runtimeStatePath(runtimeRoot || join(specBase, '.runtime'), `apply-pathspec-${changeName}.txt`)
|
|
444
445
|
if (existsSync(pathspecFile)) {
|
|
445
446
|
const fromPathspec = readFileSync(pathspecFile, 'utf8').split('\n').map(l => l.trim()).filter(Boolean)
|
|
446
447
|
if (fromPathspec.length > 0) {
|
package/src/cross-placement.js
CHANGED
|
@@ -48,9 +48,13 @@ export function crossWorktreePath(specBase, changeName, repoKey, placementRoot =
|
|
|
48
48
|
* @param {string} mainRoot 主仓根(相对路径解析基准)
|
|
49
49
|
* @returns {string} 绝对落位根
|
|
50
50
|
*/
|
|
51
|
+
// Windows 盘符形态判定(坑跨平台 CI:POSIX isAbsolute('C:/x')=false → 带引号的盘符路径
|
|
52
|
+
// 被相对主仓根拼接成 test/C:/quoted 伪路径)。跨仓落位本就面向 Windows 盘符场景设计,
|
|
53
|
+
// 语义上盘符路径恒绝对(与 win32 path.isAbsolute 同判,跨平台自洽)。
|
|
54
|
+
const WIN_DRIVE_ABS_RE = /^[A-Za-z]:[/\\]/;
|
|
51
55
|
export function resolvePlacementRoot(raw, mainRoot) {
|
|
52
56
|
const trimmed = String(raw || '').trim().replace(/^['"]|['"]$/g, '');
|
|
53
|
-
if (isAbsolute(trimmed)) return resolvePath(trimmed);
|
|
57
|
+
if (isAbsolute(trimmed) || WIN_DRIVE_ABS_RE.test(trimmed)) return resolvePath(trimmed);
|
|
54
58
|
return resolvePath(mainRoot, trimmed);
|
|
55
59
|
}
|
|
56
60
|
|
|
@@ -215,8 +215,11 @@ export function collectRepoActual({ repoKey, specBase, cwd, runtimeRoot = null,
|
|
|
215
215
|
// 2) 仓根(isAbsolute/resolve 兼容 Windows 盘符绝对注册与相对 cwd 注册)
|
|
216
216
|
const repoPath = isAbsolute(rawPath) ? rawPath : resolve(cwd, rawPath)
|
|
217
217
|
if (!existsSync(repoPath)) {
|
|
218
|
+
// degradedReason 不嵌绝对路径(2026-10-19-cross-repo-face-tool-filter,平台评审 P2):
|
|
219
|
+
// 该文案会随 change-patch.json 冻结并透出到平台资产卡,本机布局属环境信息不外泄;
|
|
220
|
+
// repoPath 仍留在返回值供本机诊断使用。
|
|
218
221
|
return { repo: repoKey, repoPath, anchor: degradedAnchor(), files: [],
|
|
219
|
-
degradedReason:
|
|
222
|
+
degradedReason: `注册路径不可达(local.yaml repos.${repoKey} 的注册值在本机不存在)——请人工到对应仓核对` }
|
|
220
223
|
}
|
|
221
224
|
|
|
222
225
|
// 3) A 档 reviews-range:runtimeRoot 与 changeName 双提供才尝试(缺省跳过 = 向后兼容)
|
package/src/db.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { openDatabase, applyPragmas, runTransaction } from './db-engine.js';
|
|
2
|
+
import { runtimeTempPath } from './runtime-paths.js'
|
|
2
3
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'fs';
|
|
3
4
|
import { dirname } from 'path';
|
|
4
5
|
|
|
@@ -102,7 +103,8 @@ export class DB {
|
|
|
102
103
|
* 全新 node:sqlite 项目不产生主 .bak)。
|
|
103
104
|
*/
|
|
104
105
|
_openWithFallback() {
|
|
105
|
-
|
|
106
|
+
// .bak 2026-10-18 布局分层后归 temp/;根路径保留兜底(未迁移旧仓/混版本窗口)
|
|
107
|
+
const bakPath = existsSync(`${this.dbPath}.bak`) ? `${this.dbPath}.bak` : runtimeTempPath(dirname(this.dbPath), 'sillyspec.db.bak');
|
|
106
108
|
// readValid: null=文件不存在;'empty'=存在但 0 字节;'ok'=有内容
|
|
107
109
|
const readValid = (p) => {
|
|
108
110
|
if (!existsSync(p)) return null;
|
|
@@ -156,7 +158,11 @@ export class DB {
|
|
|
156
158
|
// 覆盖前救援快照(2026-08-21 审查 BUG-7):主库有内容但打不开("损坏")时,其中可能
|
|
157
159
|
// 含比 .bak 更新的数据——静默覆盖即静默丢。best-effort 留一份 .corrupt-<ts> 副本供人工恢复。
|
|
158
160
|
if (primaryRaw === 'ok') {
|
|
159
|
-
try {
|
|
161
|
+
try {
|
|
162
|
+
const corruptPath = runtimeTempPath(dirname(this.dbPath), `sillyspec.db.corrupt-${Date.now()}`);
|
|
163
|
+
try { mkdirSync(dirname(corruptPath), { recursive: true }); } catch { /* fail-open */ }
|
|
164
|
+
copyFileSync(this.dbPath, corruptPath);
|
|
165
|
+
} catch { /* 救援失败不阻断恢复 */ }
|
|
160
166
|
}
|
|
161
167
|
// node:sqlite 绑定路径,无 _save:把 .bak 内容 copy 回主库,统一用 dbPath。
|
|
162
168
|
// 体检 BUG-18:主库可能正被其他进程(daemon/另一 CLI)打开——Windows 覆盖他人
|
package/src/decision-distill.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 蒸馏异态判定(2026-10-20-distill-abnormal-upgrade-fix):needsWait 非空是唯一异态信号——
|
|
3
|
+
* 与 needsWait 产出(distillIntoKnowledge)单一源同处,返回值契约变化时两处同文件可见不漂移。
|
|
4
|
+
* 历史:flow done 曾内联 `res.rejected > 0 || res.needsWait > 0`——前半引用返回值不存在的字段
|
|
5
|
+
* (幽灵引用恒 undefined),后半字符串与 >0 比较恒 false,「异态升厚留人工裁决」从未生效。
|
|
6
|
+
* @param {{ written?: unknown[], skipped?: string|null, needsWait?: string|null }} res distillIntoKnowledge 返回值(容错 null/undefined)
|
|
7
|
+
* @returns {boolean} needsWait 为非空字符串时 true
|
|
8
|
+
*/
|
|
9
|
+
export function isDistillAbnormal(res) {
|
|
10
|
+
return Boolean(res && typeof res.needsWait === 'string' && res.needsWait.length > 0)
|
|
11
|
+
}
|
|
12
|
+
|
|
1
13
|
/**
|
|
2
14
|
* decision-distill — 决策提炼纯函数(change: 2026-08-23-adopt-harness-practices,FR-02/03/04,D-007@v1)
|
|
3
15
|
*
|
|
@@ -24,14 +36,16 @@
|
|
|
24
36
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
|
|
25
37
|
import { modulePathMatches } from './modules.js'
|
|
26
38
|
import { basename, dirname, join } from 'node:path'
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
39
|
+
import { KNOWLEDGE_STATUSES, KNOWLEDGE_TYPES } from './decision-values.js'
|
|
40
|
+
|
|
41
|
+
// 入选集=正典单一源派生(2026-10-20-decision-caliber-unify)。历史:五类集漏选 8 条
|
|
42
|
+
// feasibility+accepted 决策(2026-09-11 驾驭第十二批,2026-08-08-concurrent-write-preflight
|
|
43
|
+
// 实证)后扩为 7 值字面集;scope 排除是刻意设计(范围裁决不入库)。现改 KNOWLEDGE_TYPES 派生
|
|
44
|
+
// (恰等于历史 7 值零漂移)。状态集纳入 implemented:知识库入选输出值本就是「状态:implemented」,
|
|
45
|
+
// 输入侧声明 implemented 却永不入选是白名单后加未同步蒸馏的遗留缺陷。
|
|
46
|
+
const IMPLEMENTED_TYPES = new Set(KNOWLEDGE_TYPES)
|
|
47
|
+
/** FR-02:implemented 入选的 status(proposed/draft/superseded 等其余状态不入选) */
|
|
48
|
+
const IMPLEMENTED_STATUSES = new Set(KNOWLEDGE_STATUSES)
|
|
35
49
|
/** 三级兜底终点域(仍不中归此;域文件由本模块自管) */
|
|
36
50
|
const UNMAPPED_DOMAIN = 'unmapped'
|
|
37
51
|
/** 旧格式容错占位值(FR-01:缺锚点/缺 headHash 不阻断) */
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* decision-values.js — 决策 type/status 正典值集单一源(2026-10-20-decision-caliber-unify)。
|
|
3
|
+
*
|
|
4
|
+
* 背景:值集此前三套口径并存——decisions-io 写入白名单(type 6 值缺 feasibility/consistency)、
|
|
5
|
+
* decision-distill 蒸馏入选集(类型 7 值含 scope 排除;状态仅 confirmed/accepted,implemented
|
|
6
|
+
* 写入合法却永不入选)、brainstorm 提示面两处清单(term/premise/risk/unresolved 等机器永不
|
|
7
|
+
* 收录的值)。写入者拒绝蒸馏想收的值、提示面教 agent 写机器不认的值——口径漂移是事故温床
|
|
8
|
+
* (八条 feasibility 决策因五类集漏选入库事故,见 decision-distill.js 头注)。
|
|
9
|
+
*
|
|
10
|
+
* 单一消费契约:
|
|
11
|
+
* - decisions-io 写入白名单 = 正典全集(fail-fast 拦正典外值)
|
|
12
|
+
* - decision-distill 入选裁决 = KNOWLEDGE_* 派生集(scope 刻意不入知识库——范围裁决,
|
|
13
|
+
* 文档明载;KNOWLEDGE_TYPES 恒等于正典去 scope)
|
|
14
|
+
* - brainstorm 决策记录提示清单 = 正典全集(渲染产物 docs/prompt/ 经 _extract 链同步)
|
|
15
|
+
*
|
|
16
|
+
* 开放世界边界:手写 decisions.md 的历史散值(40+ 种 type)不在正典集,由 parseDecisions
|
|
17
|
+
* 宽容解析保留、蒸馏不入选——存量行为零变化,本集只约束机器面(写入校验/入选裁决/提示文案)。
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** 正典 type:写入白名单全集(8 值)。 */
|
|
21
|
+
export const DECISION_TYPES = Object.freeze([
|
|
22
|
+
'architecture', 'compatibility', 'boundary', 'definition', 'process', 'scope', 'feasibility', 'consistency',
|
|
23
|
+
])
|
|
24
|
+
|
|
25
|
+
/** 正典 status:写入白名单全集(7 值)。 */
|
|
26
|
+
export const DECISION_STATUSES = Object.freeze([
|
|
27
|
+
'confirmed', 'accepted', 'implemented', 'rejected', 'proposed', 'superseded', 'draft',
|
|
28
|
+
])
|
|
29
|
+
|
|
30
|
+
/** 蒸馏入选 type:正典去 scope(范围裁决刻意不入知识库,保持既有 7 值口径)。 */
|
|
31
|
+
export const KNOWLEDGE_TYPES = Object.freeze(DECISION_TYPES.filter((t) => t !== 'scope'))
|
|
32
|
+
|
|
33
|
+
/** 蒸馏入选 status:定案/接受/已实现三值(rejected 走独立防复潮通道,其余状态不入选)。 */
|
|
34
|
+
export const KNOWLEDGE_STATUSES = Object.freeze(['confirmed', 'accepted', 'implemented'])
|
package/src/decisions-io.js
CHANGED
|
@@ -17,9 +17,12 @@
|
|
|
17
17
|
import { existsSync, readFileSync } from 'node:fs'
|
|
18
18
|
import { join } from 'node:path'
|
|
19
19
|
import { writeAtomicSync } from './fs-atomic.js'
|
|
20
|
+
import { DECISION_STATUSES, DECISION_TYPES } from './decision-values.js'
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
// 白名单=正典全集单一源派生(2026-10-20-decision-caliber-unify):此前字面集与蒸馏入选集、
|
|
23
|
+
// brainstorm 提示清单三套口径漂移——feasibility/consistency 被写入者拒绝而蒸馏想收。
|
|
24
|
+
const STATUS_VALUES = new Set(DECISION_STATUSES)
|
|
25
|
+
const TYPE_VALUES = new Set(DECISION_TYPES)
|
|
23
26
|
|
|
24
27
|
/**
|
|
25
28
|
* canonical 条目渲染(正文散文行原样保留在 body 数组)。
|
package/src/deleted-ledger.js
CHANGED
|
@@ -14,12 +14,13 @@
|
|
|
14
14
|
* 坏行容错:单行 JSON 解析失败跳过该行(外部编辑/半写截断不拖垮读取)。
|
|
15
15
|
*/
|
|
16
16
|
import { appendFileSync, existsSync, readFileSync } from 'node:fs'
|
|
17
|
+
import { runtimeStatePath } from './runtime-paths.js'
|
|
17
18
|
import { join } from 'node:path'
|
|
18
19
|
|
|
19
20
|
const LEDGER_FILENAME = 'deleted-changes.jsonl'
|
|
20
21
|
|
|
21
22
|
function ledgerPath(specDir) {
|
|
22
|
-
return join(specDir, '.runtime', LEDGER_FILENAME)
|
|
23
|
+
return runtimeStatePath(join(specDir, '.runtime'), LEDGER_FILENAME)
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* writer 落盘到 <authoritySpecDir>/.runtime/。
|
|
24
24
|
*/
|
|
25
25
|
import { openDatabase, pluckGet, pluckAll } from './db-engine.js';
|
|
26
|
+
import { runtimeTempPath } from './runtime-paths.js'
|
|
26
27
|
import { safeGit } from './git-helper.js';
|
|
27
28
|
import { createHash } from 'crypto';
|
|
28
29
|
import { existsSync, statSync, readFileSync, readdirSync, mkdirSync, writeFileSync, unlinkSync, rmSync } from 'fs';
|
|
@@ -1109,12 +1110,15 @@ export function detectSelfMaintenanceTax(cwd, authoritySpecDir) {
|
|
|
1109
1110
|
const warnings = [];
|
|
1110
1111
|
|
|
1111
1112
|
// ① 活跃非零 tally 列示(events:{type:{count,lastAt}},X-10 结构)
|
|
1113
|
+
// 2026-10-18 布局分层:tally 落 state/;根目录扫描保留旧布局兜底(入口迁移收敛后恒空)
|
|
1112
1114
|
try {
|
|
1113
|
-
|
|
1115
|
+
const tallyDirs = [join(runtimeRoot, 'state'), runtimeRoot]
|
|
1116
|
+
for (const tallyDir of tallyDirs) {
|
|
1117
|
+
for (const f of (() => { try { return readdirSync(tallyDir) } catch { return [] } })()) {
|
|
1114
1118
|
const m = f.match(/^friction-tally-(.+)\.json$/);
|
|
1115
1119
|
if (!m) continue;
|
|
1116
1120
|
let tally = null;
|
|
1117
|
-
try { tally = JSON.parse(readFileSync(join(
|
|
1121
|
+
try { tally = JSON.parse(readFileSync(join(tallyDir, f), 'utf8')); } catch { continue; }
|
|
1118
1122
|
const events = tally && typeof tally === 'object' && tally.events && typeof tally.events === 'object' && !Array.isArray(tally.events) ? tally.events : {};
|
|
1119
1123
|
const segs = [];
|
|
1120
1124
|
let total = 0;
|
|
@@ -1131,6 +1135,7 @@ export function detectSelfMaintenanceTax(cwd, authoritySpecDir) {
|
|
|
1131
1135
|
findings.push(`活跃摩擦:${m[1]}(${segs.join('、')}${lastAt ? `,最近 ${lastAt}` : ''})`);
|
|
1132
1136
|
if (total >= TAX_HEAVY_TOTAL) warnings.push(`税重——评估该机制群简化/退役(对照其 decisions 退役判据):${m[1]}(活跃 total ${total})`);
|
|
1133
1137
|
}
|
|
1138
|
+
}
|
|
1134
1139
|
} catch { /* runtimeRoot 不可读 → 无活跃数据,不误报 */ }
|
|
1135
1140
|
|
|
1136
1141
|
// ② 台账聚合(近 90 天 top5 + 累计总量);④ 缺失渲染提示不告警
|
|
@@ -1748,7 +1753,7 @@ export function writeDoctorDiagnosis(json, specDir) {
|
|
|
1748
1753
|
try {
|
|
1749
1754
|
const runtimeDir = join(specDir, '.runtime');
|
|
1750
1755
|
mkdirSync(runtimeDir, { recursive: true });
|
|
1751
|
-
const outPath =
|
|
1756
|
+
const outPath = runtimeTempPath(runtimeDir, 'doctor-diagnosis.json');
|
|
1752
1757
|
writeFileSync(outPath, JSON.stringify(json, null, 2) + '\n');
|
|
1753
1758
|
return outPath;
|
|
1754
1759
|
} catch (e) {
|
package/src/flow-draft.js
CHANGED
|
@@ -23,6 +23,7 @@ import { wrapSection, verifyMarkers, reanchorText, bodyHash, parseMarkerBlocks }
|
|
|
23
23
|
// 占位句常量真同源(2026-10-03-fr-skeleton-gate 评审 P3 清偿):起草端 Then 兜底与骨架判据
|
|
24
24
|
// (fr-index.isThinSkeletonBodies)共用同一字面量——两处独立字符串会被评审实证为「镜像非同源」。
|
|
25
25
|
import { SKELETON_THEN_PLACEHOLDER } from './fr-index.js'
|
|
26
|
+
import { runtimeStatePath } from './runtime-paths.js'
|
|
26
27
|
|
|
27
28
|
/** 读文件并归一化 CRLF→LF(2026-09-25-feedback-fixes④:Python/编辑器写盘 CRLF 会破坏
|
|
28
29
|
锚定的槽位识别 regex)。 */
|
|
@@ -88,7 +89,7 @@ const QUESTION_REPAIR_SIM_THRESHOLD = 0.5
|
|
|
88
89
|
|
|
89
90
|
/** sidecar 台账路径(.runtime 下,verify-draft sidecar 同族;命名空间=文件:段键)。 */
|
|
90
91
|
export function draftLedgerPath(runtimeRoot, changeName) {
|
|
91
|
-
return
|
|
92
|
+
return runtimeStatePath(runtimeRoot, `draft-ledger-${changeName}.json`)
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
const AGENT_SLOT = (n, hint) => `<!--AGENT:槽${n} ${hint}——例外裁决书写面(机器段之外合法) -->`
|
|
@@ -326,7 +327,7 @@ function draftTasksV2({ change, criteria, withTasks }) {
|
|
|
326
327
|
* 零 advisory——工作分解归 agent,2026-10-07-thin-tasks-v3)。
|
|
327
328
|
* adopted brainstorm 变更(opts.skipDesign)豁免 design 四节门(设计承诺以 brainstorm design 为准)。
|
|
328
329
|
*/
|
|
329
|
-
export function verifyThinDocsV2({ changeDir, ledger, skipDesign = false }) {
|
|
330
|
+
export function verifyThinDocsV2({ changeDir, ledger, skipDesign = false, repair = true }) {
|
|
330
331
|
const violations = []
|
|
331
332
|
const advisories = []
|
|
332
333
|
const repairs = []
|
|
@@ -391,12 +392,18 @@ export function verifyThinDocsV2({ changeDir, ledger, skipDesign = false }) {
|
|
|
391
392
|
}
|
|
392
393
|
}
|
|
393
394
|
if (repairEntries.length > 0) {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
for (const e of repairEntries)
|
|
398
|
-
}
|
|
399
|
-
|
|
395
|
+
// repair:false 只读轨(2026-10-19-approve-gate-tick-nudge):approve 批准面不做自动
|
|
396
|
+
// 回填(写盘修复属 flow done 面)——违规照报,design.md 字节不动。
|
|
397
|
+
if (repair === false) {
|
|
398
|
+
for (const e of repairEntries) violations.push(`design「${e.heading}」问题文本被改写——原文:${e.q}(批准校验只读不自动回填,手工恢复此行后重试)`)
|
|
399
|
+
} else {
|
|
400
|
+
try {
|
|
401
|
+
writeAtomicSync(join(changeDir, 'design.md'), repairedLines.join('\n'))
|
|
402
|
+
dLines = repairedLines
|
|
403
|
+
for (const e of repairEntries) repairs.push(`design「${e.heading}」问题行曾被改写——已按锚回填原文(agent 作答保留):${e.q}`)
|
|
404
|
+
} catch {
|
|
405
|
+
for (const e of repairEntries) violations.push(`design「${e.heading}」问题文本被改写——原文:${e.q}(机器回填写盘失败,请手工恢复此行,答案写在下方)`)
|
|
406
|
+
}
|
|
400
407
|
}
|
|
401
408
|
}
|
|
402
409
|
for (const sec of anchorSections) {
|
|
@@ -1056,13 +1063,13 @@ export function amendFlowDraft({ changeDir, change, runtimeRoot }) {
|
|
|
1056
1063
|
* schemaVersion===2 → verifyThinDocsV2(文档↔锚对比:问题/节/FR/绑定拒收 + 漂移 advisory);
|
|
1057
1064
|
* v1(缺省)→ verifyMarkers 指纹三态(存量在途变更零影响)。
|
|
1058
1065
|
* opts.skipDesign:adopted brainstorm 变更豁免 design 四节门(flow.js 判定后透传)。 */
|
|
1059
|
-
export function verifyFlowDrafts({ changeDir, change, runtimeRoot, skipDesign = false }) {
|
|
1066
|
+
export function verifyFlowDrafts({ changeDir, change, runtimeRoot, skipDesign = false, repair = true }) {
|
|
1060
1067
|
const ledgerPath = draftLedgerPath(runtimeRoot, change)
|
|
1061
1068
|
if (!existsSync(ledgerPath)) return { applicable: false, violations: [] }
|
|
1062
1069
|
let ledger
|
|
1063
1070
|
try { ledger = JSON.parse(readFileSync(ledgerPath, 'utf8')) } catch { return { applicable: false, violations: [] } }
|
|
1064
1071
|
if (ledger.schemaVersion === DRAFT_SCHEMA_V2) {
|
|
1065
|
-
const r = verifyThinDocsV2({ changeDir, ledger, skipDesign })
|
|
1072
|
+
const r = verifyThinDocsV2({ changeDir, ledger, skipDesign, repair })
|
|
1066
1073
|
return { applicable: true, schema: DRAFT_SCHEMA_V2, ...r }
|
|
1067
1074
|
}
|
|
1068
1075
|
const violations = []
|