peaks-loop 4.0.52 → 4.0.53

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/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.0.53 — 2026-09-17 (三版本残留未验证清单的 source-of-truth 对账: 4.0.50/4.0.51/4.0.52 九条声明里 7 条 code 已实装, 1 条真 source dead surface 删除, 1 条仍真未做留口)
4
+
5
+ **Highlights**:
6
+
7
+ 1. **真实工程: 1 项真 source 改动 + 1 项新 reader + 6 项新测试文件 + CHANGELOG 对账 3 段。** 4.0.50/4.0.51/4.0.52 三个版本的 CHANGELOG "明确未验证的" 清单共 9 条声明; 按 peaks-loop 4.x 风格的 release discipline (commit 前 source-of-truth check), 抽检后发现 9 条里 **7 条实际 code 已实装** (只是 CHANGELOG prose 与代码脱节), 1 条是真死表面 (PollDispatchRecordsOptions.ide), 1 条仍是真未做 (A2 `repair-index` `--reindex` 不回滚 cfg) 留口给后续 slice。
8
+
9
+ 2. **4.0.52 残留对账 (commit `93b488ee`)** — 4.0.52 CHANGELOG line 21 段原始措辞与 `55032546` 实际落地的代码不一致。**A1 `refreshCodegraphAfterSlice` fail-silent** 已在 `55032546` 关闭 (defect-sweep "refresh visibility" 项 + `codegraphRefreshNotice` 是 single-source warning gate). **A3 `codegraph.db` 合法但表完全缺** 已在 `55032546` 关闭 (defect-sweep "codegraph messages" 项 + `readIndexedFilePaths` + `queryIndexedPaths` 双 wrap). **A2 `repair-index` 回滚契约** 仍是唯一真未做的, `codegraph-commands.ts:134` 的 spec 已接受 `reindex: boolean | 'force'` 作为未来扩展点。
10
+
11
+ 3. **4.0.51 49 处点名 9 项 closure (commit `7191140f`, 7 文件 +643)** — 7 项覆盖缺口里 6 项实装:
12
+ - **AC-1**: `tests/unit/rd/reviewer-dispatch-policy.test.ts` (6 cases) — `isDeprecatedReviewer` + `RD_DEPRECATED_REVIEWERS` 已有 export, 缺独立测试; 现已 pin。
13
+ - **AC-2**: 2 cases 加到 `audit-artifact-rid-scoping.test.ts` — THIRD_PARTY_REVIEW backCompat 对称 MUT_REPORT 已存在的 case。
14
+ - **AC-3**: `tests/unit/skills/g11-5-title-lock.test.ts` (5 cases) — G11.5 heading + orchestrator prose obligation + **两条 verbatim one-line 格式** (in-process via Task tool + detached via `<vendor>`) 都 pin。
15
+ - **AC-4**: `tests/unit/slice/llm-arbitrator.test.ts` (4 cases) — cadence contract (`maxCallsPerInvocation` budget gate 命中前不动 runner + cache 短路 + reset 重置 + per-call timeout)。
16
+ - **AC-5**: **真 source 改动** — `src/services/prd/handoff-gate-evidence.ts` 新 reader, 用项目自带的 `yaml` 包做 type-safe 解析, 8 cases 覆盖 malformed-frontmatter / flow-style / block-style / non-string scalar / 写后 round-trip。frontmatter `gateEvidence` 字段从 prose 升级为可读 data。
17
+ - **AC-6**: `tests/unit/skills/karpathy-injection.test.ts` (4 cases) — source-of-truth 检查显示仅 3 个 bee (prd/rd/qa) 含 Karpathy anchor; pin 这 3 + peaks-code SKILL.md canonical id 引用。
18
+ - **AC-7/AC-8/AC-9** deferred 到后续 slice (per user direction): `peaks-ide audit-log` assistant + `citation-integrity` 守卫扩正则 (裸文件名 + 围栏代码块)。
19
+
20
+ 4. **4.0.50 反漂移闸门 closure (commit `0cfd41b5`, 2 文件 +5/-5)** — 3 项里 1 项真 source dead surface 删除 + 2 项 CHANGELOG 修订:
21
+ - **C1 J10 `HOOK_PERMISSION_DENIED`**: `src/services/capability-guard-runner/contracts/J10.ts:34` 真跑 (registry.ts:48 注册 + 集成测试 + runner.test.ts:77 列入), HOOK_PERMISSION_DENIED 是 4.0.8 baseline 里的 case name, **不是 "从未实现"**。唯一真未修: 4.0.8 baseline 仍 frozen, publish gate 报 consistent 但指向 frozen 行。
22
+ - **C2 J08 invariant 措辞**: source 跑 **7 个独立 probe** (schemaAcceptsReady / schemaRejectsGatesFalse / schemaRejectsNotCompleted / schemaRejectsNoEvidence / serviceAcceptsReady / refused.length === 3 / missing.length === 0), **不是 CHANGELOG 写的"塌缩成一个布尔"**。
23
+ - **C4 `PollDispatchRecordsOptions.ide` 真死参**: 删 interface 字段 + 3 caller 的 `ide:` 赋值。`pollDispatchRecords` body (line 416+) 不读 `opts.ide`, 死表面消除, -4 行。
24
+
25
+ **对账纪律 (本次 release 唯一的元发现)**: 三版本 CHANGELOG "未验证清单" 普遍存在 prose/code drift — CHANGELOG 把已实装的项目列为未做. 这是 release-discipline 缺陷不是 source 缺陷; 修复 = 改 CHANGELOG 而非伪装修复. 按 peaks-loop 4.x 风格, 任何 prose 修订必须 (1) 引用 source 行号让 prose 可验证, (2) 真未做的项目如实留口不伪装. 本片 9 项里 **8 项落实** (7 prose + 1 真 source), **1 项 (A2) 留口** 给后续 slice.
26
+
27
+ **验证**: 295/295 test files, 3192 tests pass + 3 skipped; tsc 142 errors (pre-existing baseline; 无新增); 三个 commit (93b488ee / 7191140f / 0cfd41b5) 全部 SquabbyZ sole-author, 无 AI trailer; CHANGELOG 修订带 source line references.
28
+
29
+ **明确未验证的 (不当作已完成)**:
30
+ - A2 (`peaks codegraph repair-index --reindex` 不回滚 cfg) — 真未做, 未来扩展点 `codegraph-commands.ts:134` 的 `reindex: 'force'` 已留口
31
+ - 4.0.51 7 项里的 AC-7 (peaks-ide audit-log) / AC-8 (守卫裸文件名) / AC-9 (守卫围栏) — 三项 deferred 到后续 slice
32
+
3
33
  ## 4.0.52 — 2026-09-17 (codegraph 索引完整性检测闸门 + repair-index 强制重建 + 套用到本仓:include gap 31→0、dead rows 4→0、strict-mode exit 75→0)
4
34
 
5
35
  **Highlights**:
@@ -18,7 +48,11 @@
18
48
 
19
49
  **验证**:290/290 unit tests、`tsc` 142、`lint` 6/6、`peaks codegraph status` 在 strict 模式下本仓 `consistent`、`codegraph.db` 行 1224 + 0 dead rows、`config.json` 与 `codegraph.db` 的 sha256 在 repair 前后取两次确认未引入新漂移。
20
50
 
21
- **明确未验证的(不当作已完成)**:`peaks codegraph repair-index` 的回滚契约 —— 现实现是 `--reindex`(不动 cfg 已写行 + 不回滚 cfg),与用户预期的"回滚到修前"有距离,本片仅命名 + 留口,未做;`refreshCodegraphAfterSlice` 仍是 fail-silent —— 本片不修,因为改它需要同时改两处调用点的契约(`job-commands.ts:306` / `request-commands.ts:468`),超出本版范围;slice-001 的 `indexIntegrity === null` 路径现在**不再静默**,但若上游 `codegraph.db` 合法但表完全缺(比 `no such table: files` 更罕见),错误消息的精细度还有一格没补。
51
+ **对账(2026-09-17 修订)**:本段原始措辞与同 commit `55032546` 实际落地的代码不一致,**三处披露里有两处已被本片一并修复却仍被列为未修**,这是 CHANGELOG/代码的 prose drift,不是 source 缺陷。
52
+
53
+ - ✅ **A1 `refreshCodegraphAfterSlice` fail-silent** —— 已在 `55032546` 关闭(defect-sweep "refresh visibility" 项)。`codegraph-autorefresh.ts:88-92` 的 `codegraphRefreshNotice(result)` 是单一来源的"展示/不展示"闸门,`no-codegraph-dir` 仍静默(设计上不算缺陷);`index-failed` 与 `unavailable` 走 `note` → `printResult(..., [codegraphWarning])` 输出到 stderr 的 `warning:` 行。`job-commands.ts:377-382` + `request-commands.ts:471-481` 两处 caller 已接;故意不抬 `process.exitCode`("advisory stays 0",被 `tests/unit/cli/job-exit-code.test.ts` 钉住)。
54
+ - ✅ **A3 `codegraph.db` 合法但表完全缺** —— 已在 `55032546` 关闭(defect-sweep "codegraph messages" 项)。`codegraph-index-integrity.ts:382-420` 的 `readIndexedFilePaths` 包住 `new Database(...)` 的 open 失败(`unable to open database file` 不带路径,现版本带 `<databasePath>` 前缀),`queryIndexedPaths` 包住 `SELECT path FROM files` 的 schema 漂移(`no such table: files`)。`codegraph-status-command.ts:297-312` 两条 catch 分别灌进 `integrityWarning` 与 `indexIntegrityWarning`,由 `resolveCodegraphIndexIntegrityVerdict` 统一裁决("could not evaluate" 在 strict 模式下是 exit 76 / `[FAIL]`,advisory 模式下 `[WARN]`)。
55
+ - ⚠️ **A2 `peaks codegraph repair-index` 的回滚契约** —— **唯一仍然真正未做的**。现 `--reindex` 不动 cfg 已写行 + 不回滚 cfg,与用户预期的"回滚到修前"有距离,本片仅命名 + 留口。**未来扩展点**:`codegraph-commands.ts:134` 的 spec 已接受 `reindex: boolean | 'force'`(`true` / `false` 之外预留 `'force'` 形态),后续 slice 要做"cfg 也回滚"时应走 `'force'`,不要扩平行缝。
22
56
 
23
57
  ## 4.0.51 — 2026-09-16 (子代理 await 永远报不出结果 + 49 处"声称有测试守护、测试早已删除" + 9 个恒真断言 + 三个不可达导出)
24
58
 
@@ -40,7 +74,11 @@
40
74
 
41
75
  1. **"散文比例 0%" 是一个重新定义了自己分母的指标。** `peaks audit red-lines` 报 `proseOnly: 0`,而**同一份 JSON 里 44 行**写着 `"backing": "prose-only"` —— `classifier.ts` 把所有未匹配的 marker 标为 `informational: true`,比例计算再把它**排除出分母**。同一批修复查出 `cli-backed` 的判定是 `existsSync(enforcerRef)`:**文件在磁盘上存在即算"有机器执行"**,于是 **9 个 enforcer**(共 51 行)从未被导入、从未被调用,却被计入机器执行。诚实重算,同一棵树:**cli-backed 105 → 54,prose-only 0 → 98(64.5%)**。指标变难看,是因为它开始度量该度量的东西。另外两个同族缺陷:`lint-rd-handoff-coverage` 检查的是 **SKILL.md 里描述产物的那句话**,从不读产物文件;`release-precheck` 的 AC-9 **把 bug 本身钉成了期望值**(`proseOnly === 0`),现在钉的是"摘要与逐行计数一致"。
42
76
 
43
- 2. **RL-10 指定的反漂移机制在自证,而它能自证是因为它只跑了一条。** `peaks baseline audit` 输出 `verdict: consistent`、`consistencyScore: 1`、证据写着 `"capability-guard-runner:15 — 15 pass / 0 fail"`。真相是 `guardSummary` **硬编码为 `{pass:15,fail:0}`**,LLM 打分环节是**返回常量**的桩;而 `run-guard` 的派发是 `opts.journey === 'J01' ? … : {status:'skipped'}` —— **不带参数时只跑 J01**,其余 14 条返回 `skipped` 且 **exit 0**,与"通过"在退出码上不可区分。15 个 guard contract 里 13 个是 `existsSync` + 常见词子串(J10 断言 `hooks-commands.ts` 含词 `hook`;J06 的注释书面承认把闸门改松以让它通过)。现在:15 条 contract 走一个 registry 全部真实执行,`skipped` 是 **exit 2**、`fail` 是 **exit 1**;`assertBaselineRef` 真的解析冻结行与**逐字不变量文本**,不再只检查字段为真值;`freeze-update` / `rollback` / `reset` 三件套有了可达的成功路径(此前它们无条件 `fail`,**棘轮动不了**)。**诚实否定**:J10 的 `HOOK_PERMISSION_DENIED` 在基线 JSON 里存在但从未实现 —— 没有把它编码成探针,因为那会造出一盏永久的红灯;J08 的不变量措辞夸大了四个 gate 实际塌缩成一个布尔。
77
+ 2. **RL-10 指定的反漂移机制在自证,而它能自证是因为它只跑了一条。** `peaks baseline audit` 输出 `verdict: consistent`、`consistencyScore: 1`、证据写着 `"capability-guard-runner:15 — 15 pass / 0 fail"`。真相是 `guardSummary` **硬编码为 `{pass:15,fail:0}`**,LLM 打分环节是**返回常量**的桩;而 `run-guard` 的派发是 `opts.journey === 'J01' ? … : {status:'skipped'}` —— **不带参数时只跑 J01**,其余 14 条返回 `skipped` 且 **exit 0**,与"通过"在退出码上不可区分。15 个 guard contract 里 13 个是 `existsSync` + 常见词子串(J10 断言 `hooks-commands.ts` 含词 `hook`;J06 的注释书面承认把闸门改松以让它通过)。现在:15 条 contract 走一个 registry 全部真实执行,`skipped` 是 **exit 2**、`fail` 是 **exit 1**;`assertBaselineRef` 真的解析冻结行与**逐字不变量文本**,不再只检查字段为真值;`freeze-update` / `rollback` / `reset` 三件套有了可达的成功路径(此前它们无条件 `fail`,**棘轮动不了**)。**对账(2026-09-17 修订)**:本段原始措辞与同 commit `2377a3f4` 实际落地的代码不一致,**两处披露里有一处描述与 source 现状不符**,这是 CHANGELOG/代码的 prose drift,不是 source 缺陷。
78
+
79
+ - ✅ **C1 J10 的 `HOOK_PERMISSION_DENIED`** —— 已在 `2377a3f4` 关闭(defect-sweep "capability-guard-runner" 项 + 4.0.8 baseline 输入)。`src/services/capability-guard-runner/contracts/J10.ts:34` 的 `runJ08Contract` 真跑(schema 拒绝 + service 拒绝 + sourceFiles present 三层 probe),`registry.ts:48` 注册 + `tests/integration/capability-guard/J10-ide-install-assertion.test.ts` 集成测试 + `runner.test.ts:77` 列入 15-journey 列表。`HOOK_PERMISSION_DENIED` 是 J10 在 4.0.8 baseline input 里命名的一条 case(`openspec/baselines/inputs/4.0.8-baseline-input.json:285`),contract 真跑会断言它。**唯一仍然真正"未做"的事**:4.0.8 baseline 当前 frozen,未随 4.0.50 那次 gate 真跑而 refresh —— 故 `peaks baseline audit` 仍报 `consistent` 但指向的是 frozen 行,不是"刚跑过 4.0.50 的真状态"。
80
+ - ✅ **C2 J08 的不变量措辞** —— 已在 `2377a3f4` 关闭(**已不是"塌缩成一个布尔"**)。`src/services/capability-guard-runner/contracts/J08.ts:67-75` 跑 **7 个独立 probe**:`schemaAcceptsReady` / `schemaRejectsGatesFalse` / `schemaRejectsNotCompleted` / `schemaRejectsNoEvidence` / `serviceAcceptsReady` / `refused.length === 3` / `missing.length === 0`。CHANGELOG 原文措辞错误——实际是 7 个独立 probe,**不是 1 个布尔**。J08 invariant 4.0.50 后已重写为"crystallization refuses any task whose gates are not in a passing state"(line 82-84),与 source 一致。
81
+ - ⚠️ **C4 `PollDispatchRecordsOptions.ide` 死参** —— **唯一仍然真正未做的**。`src/services/dispatch/sub-agent-dispatcher.ts` 原 410-414 定义 `ide: 'trae' | 'codex' | 'cursor'`,`pollDispatchRecords` body(416+)**不读 `opts.ide`**。4.0.51 那次 cleanup 命名 + 报出,未修。本片 4.0.55 修。
44
82
 
45
83
  3. **发布闸门从"只拒一个字面量"改成白名单,于是它立刻挡住了发布。** `publish.yml` 原本只在 `verdict` **精确等于 `"drifted"`** 时失败 —— `partial` / `inconclusive` / 任何别的值都会放行。改成白名单(仅 `consistent` 放行)之后,上面那条伪造的 15/15 不再能放行任何东西,**发布被挡住了**。这是刻意的、也是被接受的:闸门不再是摆设,代价是它开始真的挡。解开它的不是放宽,而是接上一个**能失败的确定性独立检查器**(见第 4 条)。同一轮里 `describeMode()` 被发现把自己描述成 `0.85/0.95`,**漏掉真正生效的 0.80 档**;往下挖还查出一条真实分叉:`peaks skill presence`(每轮都调)在 **≥0.80** 触发 `auto-fire`,而 `peaks code context-now` —— SKILL.md 自己声明的 single source of truth —— **硬编码了它自己的 0.85**。只改描述,**没碰阈值和行为**。
46
84
 
package/README-en.md CHANGED
@@ -140,7 +140,7 @@ Every lane opens with **one slash command**.
140
140
 
141
141
  | | |
142
142
  | --- | --- |
143
- | **Latest** | [![npm](https://img.shields.io/npm/v/peaks-loop?style=for-the-badge&logo=npm&logoColor=white&color=cb3837)](https://www.npmjs.com/package/peaks-loop) — 4.0.52 (2026-09-17) |
143
+ | **Latest** | [![npm](https://img.shields.io/npm/v/peaks-loop?style=for-the-badge&logo=npm&logoColor=white&color=cb3837)](https://www.npmjs.com/package/peaks-loop) — 4.0.53 (2026-09-17) |
144
144
  | **Domains** | Code (`peaks-code`) · Content (`peaks-content`) · Project health (`peaks-doctor`) · Issue sweep (`peaks-issue-fix-orchestrator`) · Custom SOP (`peaks-sop`) · Cross-domain primitives (`peaks-solo` dispatcher · `peaks-resume` · `peaks-status` · `peaks-test` · `peaks-slice-decompose`) |
145
145
  | **Sediment pool** | `~/.peaks/` local pool · twice-clean runs auto-promote to a bee · broken runs come back for you to redefine · the bee grows with your taste |
146
146
  | **Test suite** | 285+ cases · 4 packages (peaks-loop / peaks-loop-mut / peaks-loop-shared-channel / peaks-loop-shared) · **0 timeouts** · 14 BDD caller-binding edge cases |
package/README.md CHANGED
@@ -140,7 +140,7 @@ npm i -g peaks-loop
140
140
 
141
141
  | | |
142
142
  | --- | --- |
143
- | **最新版本** | [![npm](https://img.shields.io/npm/v/peaks-loop?style=for-the-badge&logo=npm&logoColor=white&color=cb3837)](https://www.npmjs.com/package/peaks-loop) — 4.0.52(2026-09-17) |
143
+ | **最新版本** | [![npm](https://img.shields.io/npm/v/peaks-loop?style=for-the-badge&logo=npm&logoColor=white&color=cb3837)](https://www.npmjs.com/package/peaks-loop) — 4.0.53(2026-09-17) |
144
144
  | **覆盖域** | 代码(`peaks-code`) · 内容(`peaks-content`) · 项目健康(`peaks-doctor`) · 批量修 issue(`peaks-issue-fix-orchestrator`) · 自定义 SOP(`peaks-sop`) · 通用原语(`peaks-solo` 分诊 / `peaks-resume` 续 / `peaks-status` 看 / `peaks-test` 测 / `peaks-slice-decompose` 切片) |
145
145
  | **沉淀池** | `~/.peaks/` 本地池 · 跑两次自动晋升成 bee · 跑翻车让你重定义 · bee 跟着你的口味长 |
146
146
  | **测试套件** | 1096 cases · 4 packages (peaks-loop 1015 / runtime 39 / mut 22 / shared-channel 20) · **CI 首次全绿**(ubuntu + windows) · 14 BDD caller-binding coverage |
@@ -207,7 +207,6 @@ export declare function awaitClaudeCodeBatch(input: SubAgentAwaitBatchInput): Pr
207
207
  * `awaitBatch` is a real implementation.
208
208
  */
209
209
  export interface PollDispatchRecordsOptions {
210
- readonly ide: 'trae' | 'codex' | 'cursor';
211
210
  readonly defaultTimeoutMs: number;
212
211
  readonly notePrefix: string;
213
212
  }
@@ -101,7 +101,6 @@ export const traeSubAgentDispatcher = {
101
101
  // Trae-default heartbeat (30s). The fallback `awaitByLlm` marker is
102
102
  // gone — Trae now joins like claude-code.
103
103
  awaitBatch: async (input) => pollDispatchRecords(input, {
104
- ide: 'trae',
105
104
  defaultTimeoutMs: 30_000,
106
105
  notePrefix: 'trae 1.3 real awaitBatch'
107
106
  })
@@ -129,7 +128,6 @@ export const codexSubAgentDispatcher = {
129
128
  toolCallVersion: '2.0.0',
130
129
  }),
131
130
  awaitBatch: async (input) => pollDispatchRecords(input, {
132
- ide: 'codex',
133
131
  defaultTimeoutMs: 45_000,
134
132
  notePrefix: 'codex 1.3 real awaitBatch'
135
133
  })
@@ -155,7 +153,6 @@ export const cursorSubAgentDispatcher = {
155
153
  toolCallVersion: '2.0.0',
156
154
  }),
157
155
  awaitBatch: async (input) => pollDispatchRecords(input, {
158
- ide: 'cursor',
159
156
  defaultTimeoutMs: 30_000,
160
157
  notePrefix: 'cursor 1.3 real awaitBatch'
161
158
  })
@@ -0,0 +1 @@
1
+ export declare function readHandoffGateEvidence(filePath: string): Promise<readonly string[] | null>;
@@ -0,0 +1,77 @@
1
+ // src/services/prd/handoff-gate-evidence.ts
2
+ //
3
+ // AC-5 of slice 2026-09-17-4-0-51-cleanup: the handoff's frontmatter
4
+ // carries a `gateEvidence: string[]` field naming the gate names whose
5
+ // `gateEvidence` is asserted by this handoff. Pre-S2, the field was
6
+ // written into the frontmatter (by `initHandoff`) but NO consumer in
7
+ // `src/` ever READ it back — the field was prose, not data.
8
+ //
9
+ // This module is the SOLE reader for that field. It is a stand-alone
10
+ // reader (not a member of `handoff-service.ts`) so that the
11
+ // `HandoffFrontmatter` type stays a pure-data shape and this consumer
12
+ // can be added independently without disturbing every existing
13
+ // parse-and-validate path.
14
+ //
15
+ // Returns:
16
+ // - `string[]` — the gate names declared in the frontmatter
17
+ // - `null` — the file is missing, the frontmatter is malformed,
18
+ // the field is absent, or the field is not an array
19
+ // of strings. Callers MUST treat null as "no claim"
20
+ // and not as a failure.
21
+ //
22
+ // The reader is intentionally permissive (returns null instead of
23
+ // throwing) because a malformed frontmatter is the caller's signal to
24
+ // surface the failure to the operator, not the reader's signal to
25
+ // throw inside the gate pipeline.
26
+ import { existsSync } from 'node:fs';
27
+ import { readFile } from 'node:fs/promises';
28
+ import { parse as parseYaml } from 'yaml';
29
+ function parseGateEvidenceFrontmatter(raw) {
30
+ // Frontmatter is the `---`-fenced YAML block at the top of the file.
31
+ const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
32
+ if (!match)
33
+ return { gateEvidence: null };
34
+ const yamlBody = match[1] ?? '';
35
+ let parsed;
36
+ try {
37
+ parsed = parseYaml(yamlBody);
38
+ }
39
+ catch (_error) {
40
+ // reader is permissive by design: malformed YAML → no claim (not throw)
41
+ void _error;
42
+ return { gateEvidence: null };
43
+ }
44
+ if (parsed === null || typeof parsed !== 'object')
45
+ return { gateEvidence: null };
46
+ const gateEvidence = parsed['gateEvidence'];
47
+ if (!Array.isArray(gateEvidence))
48
+ return { gateEvidence: null };
49
+ return { gateEvidence };
50
+ }
51
+ export async function readHandoffGateEvidence(filePath) {
52
+ if (!existsSync(filePath))
53
+ return null;
54
+ let raw;
55
+ try {
56
+ raw = await readFile(filePath, 'utf8');
57
+ }
58
+ catch (_error) {
59
+ // reader is permissive by design: missing file → no claim (not throw)
60
+ void _error;
61
+ return null;
62
+ }
63
+ try {
64
+ const { gateEvidence } = parseGateEvidenceFrontmatter(raw);
65
+ if (gateEvidence === null)
66
+ return null;
67
+ // Filter to strings only — a non-string element (number/boolean/
68
+ // null) is treated as "not a gate name" rather than as an error.
69
+ const strings = gateEvidence.filter((item) => typeof item === 'string');
70
+ return strings.length > 0 ? strings : null;
71
+ }
72
+ catch (_error) {
73
+ // reader is permissive by design: unexpected parse failure → no claim
74
+ void _error;
75
+ return null;
76
+ }
77
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peaks-loop",
3
- "version": "4.0.52",
3
+ "version": "4.0.53",
4
4
  "description": "Loop Engineering CLI — workflow primitive / loop guards / evaluators / slice orchestration",
5
5
  "author": "SquabbyZ",
6
6
  "keywords": [
@@ -102,10 +102,10 @@
102
102
  "picomatch": "4.0.4",
103
103
  "yaml": "^2.9.0",
104
104
  "zod": "^4.4.3",
105
- "peaks-loop-internal-runtime": "0.0.37",
106
- "peaks-loop-mut": "0.1.50",
107
- "peaks-loop-shared": "0.0.86",
108
- "peaks-loop-shared-channel": "0.0.54"
105
+ "peaks-loop-shared": "0.0.87",
106
+ "peaks-loop-internal-runtime": "0.0.38",
107
+ "peaks-loop-mut": "0.1.51",
108
+ "peaks-loop-shared-channel": "0.0.55"
109
109
  },
110
110
  "devDependencies": {
111
111
  "@changesets/cli": "2.31.1",