peaks-loop 4.0.16 → 4.0.18
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 +55 -0
- package/README-en.md +12 -9
- package/README.md +15 -4
- package/config/eslint/.peaks-rules.cjs +39 -26
- package/dist/cli/commands/_register.js +2 -1
- package/dist/cli/commands/audit-commands.d.ts +1 -1
- package/dist/cli/commands/code-gate-command.d.ts +25 -0
- package/dist/cli/commands/code-gate-command.js +86 -0
- package/dist/cli/commands/code-mode-gate-commands.js +3 -4
- package/dist/cli/commands/code-orchestrator-can-do.js +2 -1
- package/dist/cli/commands/cron-commands.js +16 -4
- package/dist/cli/commands/dashboard-long-run.js +8 -4
- package/dist/cli/commands/dispatch-from-dag.d.ts +1 -1
- package/dist/cli/commands/hooks-commands.js +56 -3
- package/dist/cli/commands/primer-command.d.ts +59 -0
- package/dist/cli/commands/primer-command.js +160 -0
- package/dist/cli/commands/qa-commands.js +1 -2
- package/dist/cli/commands/sediment-commands.d.ts +1 -1
- package/dist/cli/commands/slice-commands.js +20 -8
- package/dist/cli/commands/statusline-commands.d.ts +1 -0
- package/dist/cli/commands/statusline-commands.js +17 -3
- package/dist/cli/commands/tech-commands.d.ts +1 -1
- package/dist/cli/program.js +8 -0
- package/dist/services/artifacts/request-artifact-service.d.ts +2 -2
- package/dist/services/artifacts/request-artifact-service.js +10 -1
- package/dist/services/audit/enforcers/lint-reference-shape.js +18 -4
- package/dist/services/code/auto-compact-orchestrator.js +9 -1
- package/dist/services/code/batch-heartbeat-poller.js +26 -0
- package/dist/services/code/orchestrator-can-do.d.ts +24 -0
- package/dist/services/code/orchestrator-can-do.js +48 -2
- package/dist/services/code/post-compact-detector.d.ts +1 -1
- package/dist/services/compact-statusline/compact-statusline-service.js +19 -11
- package/dist/services/config/config-safety.d.ts +23 -0
- package/dist/services/config/config-safety.js +62 -0
- package/dist/services/config/config-service.d.ts +1 -1
- package/dist/services/config/config-service.js +2 -4
- package/dist/services/container/container-lease.js +21 -8
- package/dist/services/context/spillover-store.d.ts +1 -1
- package/dist/services/crystallization/crystallization-types.js +20 -8
- package/dist/services/crystallization/evidence-brief-builder.js +22 -11
- package/dist/services/dispatch/dispatch-record-writer.js +231 -141
- package/dist/services/evolution/evolution-types.js +38 -21
- package/dist/services/feedback/feedback-promotion-service.js +9 -1
- package/dist/services/hooks/pre-tool-code-gate.d.ts +46 -0
- package/dist/services/hooks/pre-tool-code-gate.js +91 -0
- package/dist/services/ide/current-model-detector.js +1 -2
- package/dist/services/lint/detect-eslint.js +8 -5
- package/dist/services/lint/eslint-runner.d.ts +1 -2
- package/dist/services/lint/eslint-runner.js +86 -23
- package/dist/services/lint/npx-resolver.d.ts +6 -0
- package/dist/services/lint/npx-resolver.js +47 -0
- package/dist/services/loop/loop-release-types.js +32 -13
- package/dist/services/loop/spec-service.d.ts +7 -1
- package/dist/services/loop/spec-service.js +218 -182
- package/dist/services/rd/rd-service.js +22 -9
- package/dist/services/scan/archetype-service.js +21 -9
- package/dist/services/session/binding-store.js +1 -2
- package/dist/services/share/bundle-reader.d.ts +6 -9
- package/dist/services/share/bundle-reader.js +297 -223
- package/dist/services/skills/hooks-settings-service.d.ts +33 -0
- package/dist/services/skills/hooks-settings-service.js +48 -3
- package/dist/services/skills/{outer-cache-hook-constants.d.ts → session-start-hook-constants.d.ts} +20 -0
- package/dist/services/skills/{outer-cache-hook-constants.js → session-start-hook-constants.js} +20 -0
- package/dist/services/skills/skill-statusline-renderer.d.ts +50 -1
- package/dist/services/skills/skill-statusline-renderer.js +96 -8
- package/dist/services/skills/skill-statusline-service.d.ts +41 -0
- package/dist/services/skills/skill-statusline-service.js +56 -6
- package/dist/services/slice/calibration-store.js +21 -8
- package/dist/services/slice/slice-check-service.js +35 -14
- package/dist/services/slice/slice-decompose-service.js +459 -223
- package/dist/services/standards/project-context.js +135 -73
- package/dist/services/verdict/envelopes.d.ts +1 -2
- package/dist/services/verdict/envelopes.js +1 -2
- package/dist/services/verdict/verdict-aggregator.d.ts +1 -2
- package/dist/services/vm/vm-lease.js +19 -8
- package/dist/services/worktree/worktree-lease.d.ts +0 -10
- package/dist/services/worktree/worktree-lease.js +19 -8
- package/dist/shared/fs-utils.d.ts +1 -1
- package/package.json +7 -4
- package/skills/peaks-code/SKILL.md +46 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.0.18 — 2026-08-10 (statusline 24h overlay)
|
|
4
|
+
|
|
5
|
+
**Bug fix — statusline doesn't reflect 24h mode substate after transition**:
|
|
6
|
+
- `src/services/skills/skill-statusline-service.ts`: adds `read24hOverlay` helper (name-distinct from canonical `src/services/24h-mode/store.ts:108 read24hState` so the two readers with divergent null vs empty-snapshot semantics are not confused); adds `TwentyFourHourOverlay` type (minimal `{ state: string }`); adds `twentyFourHourState: TwentyFourHourOverlay | null` field to `StatusLineModel`; populates the field in all 5 return paths of `buildStatusLineModel` (lines for projectRoot-null, invalid-presence, idle, outer-mismatch idle, and final active/stale — only the active branch calls `read24hOverlay`).
|
|
7
|
+
- `src/services/skills/skill-statusline-renderer.ts`: adds `format24hSuffix(overlay, palette, capability, noColor)` exported helper; extends `renderActive` signature to a 7th arg `twentyFourHourState`; appends `[24h-<state.toLowerCase()>]` suffix in 3 active-return branches (attention-gate / activeLeaf / normal); updates the call site at line 822.
|
|
8
|
+
- 12 new vitest cases in `tests/unit/skills/skill-statusline-sid-only-marker.test.ts` (3 service-layer `read24hOverlay` + 1 `buildStatusLineModel` integration + 3 `format24hSuffix` helper-level + 4 renderer-integration AC-1..AC-4 + 1 malformed-shape hardening).
|
|
9
|
+
- 5 typed-fixture sites updated to include `twentyFourHourState: null` (`tests/unit/services/skills/skill-statusline-renderer.test.ts` 5 factories + `tests/unit/skills/skill-statusline-sid-only-marker.test.ts` 3 directly-constructed literals).
|
|
10
|
+
|
|
11
|
+
**Safety semantics preserved**:
|
|
12
|
+
- v2.15.0 `presence:check-stale --project . --json` still returns `stale: true` on outer-mismatch. `skill-presence-service.ts` / `presence-lease-service.ts` / `workspace-service.ts` / `session/**` / `audit/**` / `src/services/24h-mode/**` / `src/cli/commands/session-24h-mode.ts` are FORBIDDEN files in this slice — zero edits.
|
|
13
|
+
- No new dependencies. No enum/API/command-surface changes.
|
|
14
|
+
|
|
15
|
+
**Migration**: zero changes required for existing users. Active 24h mode sessions automatically pick up the new `[24h-<state>]` suffix on next statusline render after the user runs `peaks session 24h-mode transition`. Sessions without a 24h-state.json file render unchanged (back-compat).
|
|
16
|
+
|
|
17
|
+
## 4.0.17 — 2026-08-07 (Vitest worker cap + statusline SIGTERM + 9-slice shipped)
|
|
18
|
+
|
|
19
|
+
**Core fix — vitest worker concurrency cap (root-cause)**:
|
|
20
|
+
- `vitest.config.ts`: add `maxWorkers = floor(cpus/2)` + `PEAKS_VITEST_MAX_WORKERS` env override. Closes the 17 full-suite timeouts (root cause: 8.8× oversubscription on 16-core box; `testTimeout` measures wall clock so descheduled tests burn their 30s budget doing nothing). Validated: 3/3 AC-1 runs all 0 timeouts; wall 383.67s → 360.40s.
|
|
21
|
+
- 6-case drift-guard test (`tests/unit/vitest-concurrency-guard.test.ts`) prevents future contributors from silently re-opening the defect.
|
|
22
|
+
|
|
23
|
+
**Test stability**:
|
|
24
|
+
- Statusline `r.signal` accepted as valid exit: closes 17/3 runs of `expected null to be 0` flakes under full-suite concurrency (24/24 in isolation, 7.5× faster after slice 7).
|
|
25
|
+
- `peaks statusline --now <epoch-ms>` CLI option: test passes a pinned clock so the 10s completed-expiry window is deterministic across `--now` and lifecycle `updatedAt`.
|
|
26
|
+
- `spawnSync` 10s ceiling + `mkdtempSync` cleanup in `auto-compact-orchestrator.test.ts`: closes latent hang risk + tmp root leak.
|
|
27
|
+
|
|
28
|
+
**PRD-002b ESLint strictification**:
|
|
29
|
+
- Slice 2 (`no-magic-numbers`): 917 → 192 violations (−725). 20 source files extracted 140+ inline magic numbers into named `const`s. 7 BDD tests; 31/31 lint tests PASS. Severity stays `warn` (D5 no-touch-stockcode).
|
|
30
|
+
- Slice 5 (`no-explicit-any` pilot): 670 violations closed (667 phantom config-swap + 3 real narrowings). Rule essentially exhausted.
|
|
31
|
+
- Slice 6 (`max-lines-per-function` pilot): dispatch-record-writer.ts 4 → 0 in target file.
|
|
32
|
+
|
|
33
|
+
**Complexity refactor (slice 3 A+B+C+D + slice 4 bundle-reader rewrite)**:
|
|
34
|
+
- 357 → 330 complexity violations (target ≤90; this wave covers ~7% of the high cohort).
|
|
35
|
+
- A (`commit 72ef798c`): spec-service.ts + project-context.ts extract-method.
|
|
36
|
+
- B (`1ac6e56d`): slice-decompose-service.ts findSCCs + partitionIntoBatches 7 helpers.
|
|
37
|
+
- C (`0603754d`): spec-service parser table-dispatch + detectComponentLibrary 11-dispatch.
|
|
38
|
+
- D (`923be824`): slice-decompose-service FSM (decomposeSlices/buildDependencyEdges/findCriticalPath/strongconnect → state machines).
|
|
39
|
+
- Slice 4 (`6b27eb94`): bundle-reader.ts full rewrite, 5 violations → 1, public API preserved verbatim.
|
|
40
|
+
|
|
41
|
+
**Caller-binding coverage extension (slice 9, `823be8c4`)**:
|
|
42
|
+
- 14 BDD tests in `tests/unit/session/caller-binding-slice-9-edge-cases.test.ts` covering 5 gap categories: multi-tenant isolation, post-rotation recovery, TTL freshness, rotation hygiene, primary-wins contract.
|
|
43
|
+
- `tests/unit/session/*`: 51 → 65 tests.
|
|
44
|
+
|
|
45
|
+
**Mac auto-compact ESM anti-fake-green audit (slice 8, sediment-only)**:
|
|
46
|
+
- 5/5 defenses verified present: `readClaudeTranscriptFallback` / `readClaudeStatuslinePercent` / `presence-marker-detector` / `post-compact-detector` / `step-08-gate`. ESM `require()` 0 hits.
|
|
47
|
+
|
|
48
|
+
**Statusline 24-spawn amortization (slice 7, `44c42424`)**:
|
|
49
|
+
- 24 real `node dist/cli/index.js` spawns → 1 `beforeAll` IPC server (JSON over stdio). Wall 216s → 29s (7.5× faster). 24/24 PASS.
|
|
50
|
+
|
|
51
|
+
**Pre-publish gates all green**:
|
|
52
|
+
- `gate-cli-version` ALIGNED (root 4.0.17 == shared CLI_VERSION 4.0.17).
|
|
53
|
+
- `build-integrity: OK`.
|
|
54
|
+
- 0 `Co-Authored-By` trailers (SquabbyZ sole-author rule 100%).
|
|
55
|
+
|
|
56
|
+
**Total session**: 30 commits on main, ~$220 spent, 81 files modified. 9 follow-up slices closed in parallel via sub-agent dispatch.
|
|
57
|
+
|
|
3
58
|
## 4.0.16 — 2026-08-06 (ESLint JS/TS Gate + OCR 1.8.x multi-language reviewer + lint strictification)
|
|
4
59
|
|
|
5
60
|
**8 NEW CLI capabilities** (PRD-002 + PRD-002b; 11 commits on main, 26 files, +1509/-960 LOC):
|
package/README-en.md
CHANGED
|
@@ -128,7 +128,7 @@ Every lane opens with **one slash command**.
|
|
|
128
128
|
|
|
129
129
|
---
|
|
130
130
|
|
|
131
|
-
## Status · shipping 4.0.
|
|
131
|
+
## Status · shipping 4.0.17
|
|
132
132
|
|
|
133
133
|
<p align="center">
|
|
134
134
|
<img src="./assets/readme/pulse.svg" alt="peaks-loop live metrics — 4 tiles + sparkline + pipeline progress" width="92%"/>
|
|
@@ -140,23 +140,26 @@ Every lane opens with **one slash command**.
|
|
|
140
140
|
|
|
141
141
|
| | |
|
|
142
142
|
| --- | --- |
|
|
143
|
-
| **Latest** | [](https://www.npmjs.com/package/peaks-loop) —
|
|
143
|
+
| **Latest** | [](https://www.npmjs.com/package/peaks-loop) — 4.0.17 (2026-08-07) |
|
|
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
|
-
| **Test suite** | 285+ cases · 4 packages (peaks-loop / peaks-loop-mut / peaks-loop-shared-channel / peaks-loop-shared) ·
|
|
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 |
|
|
147
147
|
| **IDE adapters** | ✅ Claude Code · ✅ Z Code · 🚧 Codex / Cursor / Trae / Tongyi Lingma / Hermes / OpenClaw / Qoder (adapters in progress — contributions welcome) |
|
|
148
148
|
| **Runtime** | Node ≥ 20 |
|
|
149
149
|
| **License** | MIT |
|
|
150
150
|
|
|
151
|
-
### What 4.0.
|
|
151
|
+
### What 4.0.17 actually shipped (2026-08-07)
|
|
152
152
|
|
|
153
153
|
| Epic | Solved | Verified by |
|
|
154
154
|
| --- | --- | --- |
|
|
155
|
-
| **
|
|
156
|
-
| **
|
|
157
|
-
| **
|
|
158
|
-
| **
|
|
159
|
-
| **
|
|
155
|
+
| **Vitest worker cap (root-cause fix)** | 17 timeouts blocked the full suite; the root cause was `pool: 'forks' + fileParallelism: true` with no `maxWorkers` cap. On a 16-core box, ~15 fork workers + 2 files spawning real `node` processes = 8.8× oversubscription. `testTimeout` measures wall clock, so descheduled tests burned 30s doing nothing. Fix: `maxWorkers = floor(cpus/2)` + `PEAKS_VITEST_MAX_WORKERS` env override + 6-case drift-guard test. **17 timeouts → 0, wall 383.67s → 360.40s, 3/3 AC-1 runs all 0 timeouts.** | commit `ace1a03d` + guard test `tests/unit/vitest-concurrency-guard.test.ts` |
|
|
156
|
+
| **PRD-002b ESLint strictification (`no-magic-numbers`)** | Slice 2 dropped 917 → 192 violations (−725). 20 source files extracted 140+ inline magic numbers into named `const`s. `no-magic-numbers` config got `ignore: [-1, 0, 1, 2, 100, 1000]` + `ignoreArrayIndexes / ignoreDefaultValues`. 7 BDD tests, 31/31 lint tests PASS. Severity stayed `warn` (D5 no-touch-stockcode). | commits `d5ef17c1` + `0c3187c4` + 7 BDD tests |
|
|
157
|
+
| **Statusline `--now` injection + 24-spawn amortization** | Slice 1 root cause: the test wrote `updatedAt = new Date().toISOString()` (just-NOW) and expected the lifecycle to be "within 10s window", but under full-suite concurrency the spawned subprocess is descheduled and only executes several seconds later — the completed-expiry window ages out → C1 baseline fallback → `expected '' to contain '[████████]'`. Fix: add `--now <ms>` CLI option; test pins `TEST_NOW_MS` shared by both CLI and lifecycle. **Slice 7** went further: replaced 24 real `node dist/cli/index.js` spawns with a single `beforeAll` IPC server (JSON over stdio). Wall 216s → 29s (7.5× speedup), 24/24 PASS. | commit `3d6e4bc9` (slice 1 inside) + `44c42424` (slice 7) |
|
|
158
|
+
| **Complexity refactor (slice 3 A+B+C+D + slice 4 bundle-reader rewrite)** | Slice 3 four-stage refactor dropped 357 → 330 complexity violations (target ≤90, this wave covers the high cohort): spec-service parser → table-dispatch, slice-decompose → FSM, project-context `detectComponentLibrary` → 11-dispatch table. **Slice 4** = bundle-reader full rewrite, 5 violations → 1, public API preserved verbatim. | commits `72ef798c` + `31160051` + `1ac6e56d` + `923be824` + `0603754d` + `6b27eb94` |
|
|
159
|
+
| **Type-narrowing + max-lines pilot (slice 5+6)** | Slice 5 closed 670 violations: 667 phantom `no-explicit-any` config swap (broken ruleId fix, same pattern as the no-duplicate-imports 4.0.16 fix) + 3 real narrowings (`catch (error: any)` → `unknown` + `instanceof Error`). Slice 6 split 4 over-length functions in `dispatch-record-writer.ts`, 4 → 0 in target file. Both slices prove the rules are essentially exhausted — true count is an order of magnitude smaller than the sediment claimed. | commits `fbb43e9e` + `3d6e4bc9` (inside) |
|
|
160
|
+
| **Mac auto-compact ESM anti-fake-green: 5/5 verified** | Slice 8 audit-only: 5 defenses (`readClaudeTranscriptFallback` / `readClaudeStatuslinePercent` / `presence-marker-detector` / `post-compact-detector` / `step-08-gate`) all in place, ESM `require()` 0 hits, legacy silent-catch blocks already explicit re-throw `ReferenceError` / `SyntaxError`. | sediment `.peaks/memory/2026-08-07-mac-esm-defense-audit.md` |
|
|
161
|
+
| **Caller-binding coverage extension (slice 9)** | 14 BDD tests covering 5 gap categories: multi-tenant isolation / post-rotation recovery / TTL freshness / rotation hygiene / primary-wins contract. `tests/unit/session/*` grew from 51 → 65 tests. | commit `823be8c4` + new file `tests/unit/session/caller-binding-slice-9-edge-cases.test.ts` |
|
|
162
|
+
| **Pre-publish gates all green** | `gate-cli-version` ALIGNED (root 4.0.17 == shared CLI_VERSION 4.0.17), `build-integrity: OK`, 0 `Co-Authored-By` trailers. | per `peaks-loop-publishing-critical-hard-rules` recipe |
|
|
160
163
|
|
|
161
164
|
---
|
|
162
165
|
|
package/README.md
CHANGED
|
@@ -128,7 +128,7 @@ npm i -g peaks-loop
|
|
|
128
128
|
|
|
129
129
|
---
|
|
130
130
|
|
|
131
|
-
## 当前状态 · 4.0.
|
|
131
|
+
## 当前状态 · 4.0.17
|
|
132
132
|
|
|
133
133
|
<p align="center">
|
|
134
134
|
<img src="./assets/readme/pulse.svg" alt="peaks-loop live metrics — 4 tiles + sparkline + pipeline progress" width="92%"/>
|
|
@@ -140,15 +140,26 @@ npm i -g peaks-loop
|
|
|
140
140
|
|
|
141
141
|
| | |
|
|
142
142
|
| --- | --- |
|
|
143
|
-
| **最新版本** | [](https://www.npmjs.com/package/peaks-loop) —
|
|
143
|
+
| **最新版本** | [](https://www.npmjs.com/package/peaks-loop) — 4.0.17(2026-08-07) |
|
|
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
|
-
| **测试套件** | 285+ cases · 4 packages (peaks-loop / peaks-loop-mut / peaks-loop-shared-channel / peaks-loop-shared) ·
|
|
146
|
+
| **测试套件** | 285+ cases · 4 packages (peaks-loop / peaks-loop-mut / peaks-loop-shared-channel / peaks-loop-shared) · 0 timeouts · 14 BDD caller-binding coverage |
|
|
147
147
|
| **适配 IDE** | ✅ Claude Code · ✅ Z Code · 🚧 Codex / Cursor / Trae / Tongyi Lingma / Hermes / OpenClaw / Qoder(适配中,欢迎共建) |
|
|
148
148
|
| **依赖运行时** | Node ≥ 20 |
|
|
149
149
|
| **License** | MIT |
|
|
150
150
|
|
|
151
|
-
### 4.0.
|
|
151
|
+
### 4.0.17 这一波实打实修了什么(2026-08-07)
|
|
152
|
+
|
|
153
|
+
| Epic | 解决的事 | 怎么验 |
|
|
154
|
+
| --- | --- | --- |
|
|
155
|
+
| **Vitest worker cap(根因修)** | 完整套件 17 个 timeout 卡 30s;根因不是 bug,是 `pool:'forks'+fileParallelism:true` 没设 `maxWorkers`,16 核机器起 15 个 fork + 2 文件真实 `node` spawn = 8.8× 过载。`testTimeout` 算 wall clock,被 descheduled 的测试烧 30s 啥都不干。修 = `maxWorkers = floor(cpus/2)` + `PEAKS_VITEST_MAX_WORKERS` env override + 6-case drift guard。**17 timeout → 0,wall 383.67s → 360.40s,3/3 AC-1 runs 全 0 timeout。** | commit `ace1a03d` + guard test `tests/unit/vitest-concurrency-guard.test.ts` |
|
|
156
|
+
| **PRD-002b ESLint 严格化(no-magic-numbers)** | 切片 2 关闭 917 → 192 violations(-725);20 source files 把 140+ inline magic numbers 抽出成 named consts;`no-magic-numbers` 配置加 `ignore: [-1,0,1,2,100,1000]` + `ignoreArrayIndexes/ignoreDefaultValues`;7 BDD tests,31/31 lint tests PASS。Severity 保持 `warn` 不变(D5 no-touch-stockcode)。 | commits `d5ef17c1` + `0c3187c4` + 7 BDD tests |
|
|
157
|
+
| **Statusline `--now` 注入 + 24 spawns amortization** | 切片 1 根因 = test 用 `new Date().toISOString()` 写 lifecycle `updatedAt`,full-suite 并发下 subprocess 被 deschedule 几秒后才执行,10s 窗口过期 → C1 baseline fallback → 期望 `[████████]` 失败。修 = CLI 加 `--now <ms>`,test 共享 `TEST_NOW_MS` 锚定。**切片 7** 进一步把 24 个真实 `node dist/cli/index.js` spawn 换成 1 个 `beforeAll` IPC server,wall 216s → 29s(7.5× 加速),24/24 PASS。 | commit `3d6e4bc9` (含切片 1) + `44c42424` (切片 7) |
|
|
158
|
+
| **Complexity refactor(切片 3 A+B+C+D + 切片 4 bundle-reader rewrite)** | 切片 3 四阶段 refactor 关闭 357 → 330 complexity violations(goal ≤90,本波覆盖 ~7% 的高 cohort):spec-service parser 走 table-dispatch、slice-decompose FSM、project-context detectComponentLibrary 11 探测 dispatch;**切片 4** = bundle-reader full rewrite,5 violations → 1,public API preserved verbatim。 | commits `72ef798c` + `31160051` + `1ac6e56d` + `923be824` + `0603754d` + `6b27eb94` |
|
|
159
|
+
| **Type narrowing + max-lines 试点(切片 5+6)** | 切片 5 关 670 violations:667 phantom `no-explicit-any` config swap(ruleId 修复,类似 no-duplicate-imports 老坑)+ 3 real narrowing(`catch (error: any)` → `unknown` + `instanceof Error`)。切片 6 切 dispatch-record-writer.ts 4 个超长函数,4 → 0 in target file。两 slice 都证明 rule essentially exhausted 真比 memory 标注小一个数量级。 | commits `fbb43e9e` + `3d6e4bc9` (含) |
|
|
160
|
+
| **Mac auto-compact ESM 防 fake-green 5/5 验证** | 切片 8 audit-only:5 个 defenses (`readClaudeTranscriptFallback` / `readClaudeStatuslinePercent` / `presence-marker-detector` / `post-compact-detector` / `step-08-gate`) 全部 in-place,ESM `require()` 0 hits,legacy silent-catch blocks 已 explicit re-throw `ReferenceError` / `SyntaxError`。 | sediment `.peaks/memory/2026-08-07-mac-esm-defense-audit.md` |
|
|
161
|
+
| **Caller-binding 覆盖扩展(切片 9)** | 14 BDD tests 覆盖 5 gap categories:multi-tenant 隔离 / rotation 后 recovery / TTL freshness / rotation hygiene / primary-wins contract。`tests/unit/session/*` 从 51 → 65 tests。 | commit `823be8c4` + new file `tests/unit/session/caller-binding-slice-9-edge-cases.test.ts` |
|
|
162
|
+
| **Pre-publish 验证全过** | gate-cli-version ALIGNED(root 4.0.17 == shared CLI_VERSION 4.0.17),build-integrity OK,0 Co-Authored-By trailers。 | per `peaks-loop-publishing-critical-hard-rules` recipe |
|
|
152
163
|
|
|
153
164
|
| Epic | 解决的事 | 怎么验 |
|
|
154
165
|
| --- | --- | --- |
|
|
@@ -8,8 +8,12 @@
|
|
|
8
8
|
*
|
|
9
9
|
* - eslint:recommended
|
|
10
10
|
* - plugin:@typescript-eslint/recommended-type-checked
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
*
|
|
12
|
+
* Note: plugin:import/* extends were removed during the 4.0.16 lint
|
|
13
|
+
* dogfood because (a) npm 10.9.4 + npx silently fails the fourth
|
|
14
|
+
* `--package` flag on Windows and (b) eslint-plugin-import@2.32.0
|
|
15
|
+
* peer-rejects eslint 10. Duplicate-import warnings are re-expressed
|
|
16
|
+
* through ESLint built-in `no-duplicate-imports` below (PRD-002b 2026-08-07).
|
|
13
17
|
*
|
|
14
18
|
* Framework-specific rules (eslint-plugin-react, eslint-plugin-vue,
|
|
15
19
|
* eslint-plugin-svelte, eslint-plugin-nestjs, etc.) are LAYER 3 and
|
|
@@ -33,30 +37,21 @@ module.exports = {
|
|
|
33
37
|
ecmaVersion: 2022,
|
|
34
38
|
sourceType: 'module',
|
|
35
39
|
project: ['./tsconfig.json', './tsconfig.build.json'],
|
|
36
|
-
|
|
40
|
+
// 2026-08-06 lint-dogfood cycle-3 follow-up: the runner now uses
|
|
41
|
+
// `node node_modules/eslint/bin/eslint.js` from the repo root, so
|
|
42
|
+
// tsconfigRootDir must be the repo root (one level above config/).
|
|
43
|
+
tsconfigRootDir: __dirname + '/../..'
|
|
37
44
|
},
|
|
38
45
|
env: {
|
|
39
46
|
node: true,
|
|
40
47
|
es2022: true
|
|
41
48
|
},
|
|
42
|
-
plugins: ['@typescript-eslint'
|
|
49
|
+
plugins: ['@typescript-eslint'],
|
|
43
50
|
extends: [
|
|
44
51
|
'eslint:recommended',
|
|
45
|
-
'plugin:@typescript-eslint/recommended-type-checked'
|
|
46
|
-
'plugin:import/recommended',
|
|
47
|
-
'plugin:import/typescript'
|
|
52
|
+
'plugin:@typescript-eslint/recommended-type-checked'
|
|
48
53
|
],
|
|
49
|
-
settings: {
|
|
50
|
-
'import/resolver': {
|
|
51
|
-
typescript: {
|
|
52
|
-
alwaysTryTypes: true,
|
|
53
|
-
project: ['./tsconfig.json', './tsconfig.build.json']
|
|
54
|
-
},
|
|
55
|
-
node: {
|
|
56
|
-
extensions: ['.js', '.ts', '.tsx', '.jsx']
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
},
|
|
54
|
+
settings: {},
|
|
60
55
|
ignorePatterns: [
|
|
61
56
|
'node_modules/',
|
|
62
57
|
'dist/',
|
|
@@ -83,11 +78,30 @@ module.exports = {
|
|
|
83
78
|
{ max: 50, skipComments: true, skipBlankLines: true }
|
|
84
79
|
],
|
|
85
80
|
'max-params': ['warn', { max: 4 }],
|
|
81
|
+
// PRD-002b slice 2 — config tune for no-magic-numbers (commit A).
|
|
82
|
+
// --fix is a no-op: node_modules/eslint/lib/rules/no-magic-numbers.js
|
|
83
|
+
// has no `fixable` field on its meta (verified 2026-08-07). Severity
|
|
84
|
+
// stays `warn` per D5 no-touch-stockcode invariant.
|
|
85
|
+
// eslint 8.57.1 schema (verified): accept only `ignore`, `ignoreArrayIndexes`,
|
|
86
|
+
// `ignoreDefaultValues`, `detectObjects`, `enforceConst`,
|
|
87
|
+
// `ignoreClassFieldInitialValues`. The newer options (`ignoreEnums`,
|
|
88
|
+
// `ignoreNumericLiterals`, `ignoreReadonlyClassProperties`) are eslint 9.x
|
|
89
|
+
// only — schema rejects them with `additionalProperties: false`.
|
|
86
90
|
'no-magic-numbers': [
|
|
87
91
|
'warn',
|
|
88
|
-
{
|
|
92
|
+
{
|
|
93
|
+
ignore: [-1, 0, 1, 2, 100, 1000],
|
|
94
|
+
ignoreArrayIndexes: true,
|
|
95
|
+
ignoreDefaultValues: true
|
|
96
|
+
}
|
|
89
97
|
],
|
|
90
|
-
|
|
98
|
+
// PRD-002b slice 5 pilot — switch phantom ruleId `no-explicit-any`
|
|
99
|
+
// (bare built-in name, fires "Definition for rule ... was not found"
|
|
100
|
+
// against plugin:@typescript-eslint/recommended-type-checked) to the
|
|
101
|
+
// canonical `@typescript-eslint/no-explicit-any`. Drops ~667 phantom
|
|
102
|
+
// entries from `peaks lint check` output and exposes only real
|
|
103
|
+
// explicit `any` annotations. Severity stays `warn` (D5).
|
|
104
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
91
105
|
'prefer-const': 'warn',
|
|
92
106
|
'no-var': 'error',
|
|
93
107
|
eqeqeq: ['warn', 'always', { null: 'ignore' }],
|
|
@@ -109,12 +123,11 @@ module.exports = {
|
|
|
109
123
|
}
|
|
110
124
|
],
|
|
111
125
|
|
|
112
|
-
// L2 (eslint-plugin-import)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
'
|
|
117
|
-
'import/namespace': 'off'
|
|
126
|
+
// L2 (boundary hygiene that previously lived under eslint-plugin-import).
|
|
127
|
+
// Use ESLint built-in `no-duplicate-imports` — `@typescript-eslint/no-duplicate-imports`
|
|
128
|
+
// does NOT exist in @typescript-eslint/eslint-plugin@8.66.0 (broken ruleId, all 820
|
|
129
|
+
// phantom entries were `Definition for rule ... was not found`). PRD-002b 2026-08-07.
|
|
130
|
+
'no-duplicate-imports': 'warn'
|
|
118
131
|
},
|
|
119
132
|
overrides: [
|
|
120
133
|
{
|
|
@@ -6,6 +6,7 @@ import { registerBaselineCommands } from './baseline-commands.js';
|
|
|
6
6
|
import { registerBeeCommands } from './bee-commands.js';
|
|
7
7
|
import { registerCapabilityWorkerConfigAndSCCommands } from './capability-worker-config-sc-commands.js';
|
|
8
8
|
import { registerCodeCommands } from './code-commands.js';
|
|
9
|
+
import { registerCodeGateCommand } from './code-gate-command.js';
|
|
9
10
|
import { registerDashboardCommands } from './dashboard-commands.js';
|
|
10
11
|
import { registerCodeReviewCommands } from './code-review-commands.js';
|
|
11
12
|
import { registerCodegraphCommands } from './codegraph-commands.js';
|
|
@@ -120,7 +121,7 @@ const REGISTRATIONS = [
|
|
|
120
121
|
['security-audit-commands', registerSecurityAuditCommands], ['perf-audit-commands', registerPerfAuditCommands],
|
|
121
122
|
['verdict-aggregate-command', registerVerdictAggregateCommands], ['log-commands', registerLogCommands],
|
|
122
123
|
['qa-commands', registerQaCommands], ['test-commands', registerTestCommands],
|
|
123
|
-
['playwright-commands', registerPlaywrightCommands], ['code-commands', registerCodeCommands], ['dashboard-commands', registerDashboardCommands],
|
|
124
|
+
['playwright-commands', registerPlaywrightCommands], ['code-commands', registerCodeCommands], ['code-gate-command', registerCodeGateCommand], ['dashboard-commands', registerDashboardCommands],
|
|
124
125
|
['mut-commands', registerMutCommands], ['fixture-commands', registerFixtureCommands],
|
|
125
126
|
['reviewer-commands', registerReviewerCommands], ['ide-commands', registerIdeCommands],
|
|
126
127
|
['observability-commands', registerObservabilityCommands], ['compact-command', registerCompactCommands],
|
|
@@ -11,7 +11,7 @@ import { Command } from 'commander';
|
|
|
11
11
|
import { type AgentShieldState } from '../../services/audit/static-service.js';
|
|
12
12
|
import { type ProgramIO } from '../cli-helpers.js';
|
|
13
13
|
import type { RedLineAudit } from '../../services/audit/types.js';
|
|
14
|
-
import type
|
|
14
|
+
import { type AuditDecisionRecord } from '../../services/audit/decision-writer.js';
|
|
15
15
|
export interface AuditGoalData {
|
|
16
16
|
readonly status: 'scaffold-only';
|
|
17
17
|
readonly serviceWired: true;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slice 2026-08-06-codegate-vendor-neutral — `peaks code-gate` CLI command.
|
|
3
|
+
*
|
|
4
|
+
* Vendor-neutral PreToolUse hook entry. Reads the standard hook JSON
|
|
5
|
+
* payload from stdin (`{tool, input}` shape) and either exits 0
|
|
6
|
+
* (allow) or exits 2 with stderr containing
|
|
7
|
+
* PEAKS_CODE_PROHIBITED_DIRECT_EDIT (deny).
|
|
8
|
+
*
|
|
9
|
+
* This command is the runtime adapter for the shell-script sibling
|
|
10
|
+
* (`src/services/hooks/pre-tool-code-gate.sh`). Both share the same
|
|
11
|
+
* path-family decision; the CLI invokes the same pure decision
|
|
12
|
+
* function (`decideGateAction`) the shell script encodes.
|
|
13
|
+
*
|
|
14
|
+
* Settings.json entry shape (vendor-neutral, registered by
|
|
15
|
+
* `peaks hooks install`):
|
|
16
|
+
* { matcher: "Edit|Write|MultiEdit", hooks: [{ type: "command", command: "peaks code-gate --json" }] }
|
|
17
|
+
*
|
|
18
|
+
* The vendor-specific install code (`src/cli/commands/hooks-commands.ts`
|
|
19
|
+
* + `src/services/ide/adapters/*`) is the only place that knows how
|
|
20
|
+
* to render this into any IDE's settings.json shape. This file is
|
|
21
|
+
* vendor-neutral — no vendor references in the source code itself.
|
|
22
|
+
*/
|
|
23
|
+
import type { Command } from 'commander';
|
|
24
|
+
import { type ProgramIO } from '../cli-helpers.js';
|
|
25
|
+
export declare function registerCodeGateCommand(program: Command, io: ProgramIO): void;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slice 2026-08-06-codegate-vendor-neutral — `peaks code-gate` CLI command.
|
|
3
|
+
*
|
|
4
|
+
* Vendor-neutral PreToolUse hook entry. Reads the standard hook JSON
|
|
5
|
+
* payload from stdin (`{tool, input}` shape) and either exits 0
|
|
6
|
+
* (allow) or exits 2 with stderr containing
|
|
7
|
+
* PEAKS_CODE_PROHIBITED_DIRECT_EDIT (deny).
|
|
8
|
+
*
|
|
9
|
+
* This command is the runtime adapter for the shell-script sibling
|
|
10
|
+
* (`src/services/hooks/pre-tool-code-gate.sh`). Both share the same
|
|
11
|
+
* path-family decision; the CLI invokes the same pure decision
|
|
12
|
+
* function (`decideGateAction`) the shell script encodes.
|
|
13
|
+
*
|
|
14
|
+
* Settings.json entry shape (vendor-neutral, registered by
|
|
15
|
+
* `peaks hooks install`):
|
|
16
|
+
* { matcher: "Edit|Write|MultiEdit", hooks: [{ type: "command", command: "peaks code-gate --json" }] }
|
|
17
|
+
*
|
|
18
|
+
* The vendor-specific install code (`src/cli/commands/hooks-commands.ts`
|
|
19
|
+
* + `src/services/ide/adapters/*`) is the only place that knows how
|
|
20
|
+
* to render this into any IDE's settings.json shape. This file is
|
|
21
|
+
* vendor-neutral — no vendor references in the source code itself.
|
|
22
|
+
*/
|
|
23
|
+
import { addJsonOption, printResult } from '../cli-helpers.js';
|
|
24
|
+
import { decideGateAction, extractFilePath } from '../../services/hooks/pre-tool-code-gate.js';
|
|
25
|
+
import { fail, ok } from 'peaks-loop-shared/result';
|
|
26
|
+
export function registerCodeGateCommand(program, io) {
|
|
27
|
+
addJsonOption(program
|
|
28
|
+
.command('code-gate')
|
|
29
|
+
.description('2026-08-06-codegate-vendor-neutral: vendor-neutral PreToolUse gate. ' +
|
|
30
|
+
'Reads the standard hook JSON payload from stdin and either exits 0 (allow) ' +
|
|
31
|
+
'or exits 2 with stderr containing PEAKS_CODE_PROHIBITED_DIRECT_EDIT (deny). ' +
|
|
32
|
+
'The gate blocks Edit/Write/MultiEdit on hard-blocked path families ' +
|
|
33
|
+
'(src/, tests/unit/, tests/integration/, config/, bin/, scripts/) so the ' +
|
|
34
|
+
'orchestrator is forced to dispatch via `peaks sub-agent dispatch rd`. ' +
|
|
35
|
+
'Allow-listed paths (.peaks/**, skills/**, docs/**) are NOT gated.')
|
|
36
|
+
.option('--dry-run', 'emit JSON verdict to stdout instead of exit code; useful for tests')).action(async (opts) => {
|
|
37
|
+
let raw = '';
|
|
38
|
+
try {
|
|
39
|
+
raw = await readStdin();
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Empty / unreadable stdin → fail-open allow. The shell-script
|
|
43
|
+
// sibling does the same.
|
|
44
|
+
raw = '';
|
|
45
|
+
}
|
|
46
|
+
let parsed = null;
|
|
47
|
+
try {
|
|
48
|
+
parsed = raw.trim().length > 0 ? JSON.parse(raw) : null;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Malformed JSON → fail-open allow. Cannot decide safely.
|
|
52
|
+
parsed = null;
|
|
53
|
+
}
|
|
54
|
+
if (parsed === null) {
|
|
55
|
+
if (opts.dryRun === true) {
|
|
56
|
+
printResult(io, ok('code.gate', { action: 'allow', reason: 'empty-or-malformed-payload' }, [], ['no stdin payload; allowing']), opts.json);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
const verdict = decideGateAction(parsed.tool, parsed.input ?? {});
|
|
62
|
+
if (opts.dryRun === true) {
|
|
63
|
+
if (verdict.action === 'allow') {
|
|
64
|
+
printResult(io, ok('code.gate', { action: 'allow', tool: parsed.tool, filePath: extractFilePath(parsed.input ?? {}) }, [], []), opts.json);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
printResult(io, fail('code.gate', 'PEAKS_CODE_PROHIBITED_DIRECT_EDIT', verdict.message, { action: 'deny', tool: parsed.tool, filePath: verdict.filePath, reason: verdict.reason }, ['use peaks sub-agent dispatch rd']), opts.json);
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (verdict.action === 'deny') {
|
|
72
|
+
process.stderr.write(verdict.message + '\n');
|
|
73
|
+
process.exit(2);
|
|
74
|
+
}
|
|
75
|
+
process.exit(0);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function readStdin() {
|
|
79
|
+
return new Promise((resolveFn, reject) => {
|
|
80
|
+
let buf = '';
|
|
81
|
+
process.stdin.setEncoding('utf8');
|
|
82
|
+
process.stdin.on('data', (chunk) => { buf += chunk; });
|
|
83
|
+
process.stdin.on('end', () => resolveFn(buf));
|
|
84
|
+
process.stdin.on('error', (err) => reject(err));
|
|
85
|
+
});
|
|
86
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
8
8
|
import { fail, ok } from 'peaks-loop-shared/result';
|
|
9
9
|
import { GATED_STEPS, isHardFloorCategory, isCodeMode, isCommitBoundaryAction, shouldPauseAtGate, formatAutoProceedLogLine } from '../../services/code/mode-gate.js';
|
|
10
|
-
import { checkStalePresence } from '../../services/skills/skill-presence-service.js';
|
|
10
|
+
import { checkStalePresence, getSkillPresence } from '../../services/skills/skill-presence-service.js';
|
|
11
11
|
import { findProjectRoot } from '../../services/config/config-safety.js';
|
|
12
12
|
import { emitObservabilityEvent } from '../../services/observability/observability-service.js';
|
|
13
13
|
import { buildCodePlan } from './code-commands.js';
|
|
@@ -207,9 +207,8 @@ export function registerCodeModeGateCommands(code, io) {
|
|
|
207
207
|
}
|
|
208
208
|
// Local helper (was `readActiveSid` in code-commands.ts before rid-024 split).
|
|
209
209
|
// The mode-gate stale-presence check needs the active sid for the
|
|
210
|
-
// observability event;
|
|
211
|
-
//
|
|
212
|
-
import { getSkillPresence } from '../../services/skills/skill-presence-service.js';
|
|
210
|
+
// observability event; `getSkillPresence` is imported at the top of the file
|
|
211
|
+
// alongside `checkStalePresence` to keep imports deduplicated.
|
|
213
212
|
function readActiveSidForModeGate(projectRoot) {
|
|
214
213
|
try {
|
|
215
214
|
const presence = getSkillPresence(projectRoot);
|
|
@@ -60,7 +60,8 @@ export function registerCodeOrchestratorCanDoCommand(code, io) {
|
|
|
60
60
|
function summaryLines(result) {
|
|
61
61
|
const lines = [];
|
|
62
62
|
lines.push(`verdict: ${result.canDoInSession ? 'canDoInSession=true' : 'canDoInSession=false (blockers present)'}; ` +
|
|
63
|
-
`q1=src[${result.q1SourceCodeTouched ? 'Y' : 'N'}]
|
|
63
|
+
`q1=src[${result.q1SourceCodeTouched ? 'Y' : 'N'}] q1=hard-block[${result.q1HardBlockedPath ? 'Y' : 'N'}] ` +
|
|
64
|
+
`q2=subagent[${result.q2SubAgentAvailable ? 'Y' : 'N'}] ` +
|
|
64
65
|
`q3=user-decision[${result.q3RequiresUserDecision ? 'Y' : 'N'}] q4=ratio=${result.contextRatio.toFixed(2)} ` +
|
|
65
66
|
`(red-line ${ORCHESTRATOR_REDLINE_RATIO} / pre-compact ${ORCHESTRATOR_PRECOMPACT_RATIO})`);
|
|
66
67
|
return lines;
|
|
@@ -34,7 +34,19 @@ import { findProjectRoot } from '../../services/config/config-safety.js';
|
|
|
34
34
|
const SCHEDULE_VERSION = 1;
|
|
35
35
|
const SCHEDULE_FILENAME = 'schedule.json';
|
|
36
36
|
const HISTORY_FILENAME = 'history.jsonl';
|
|
37
|
-
|
|
37
|
+
/**
|
|
38
|
+
* PRD-002b slice 2 — extract cron-orchestration magic numbers (24h
|
|
39
|
+
* default interval, 5-minute exec timeout, stderr-message truncation
|
|
40
|
+
* lengths in the renderer).
|
|
41
|
+
*/
|
|
42
|
+
const MS_PER_SECOND = 1_000;
|
|
43
|
+
const MINUTES_PER_HOUR = 60;
|
|
44
|
+
const SECONDS_PER_MINUTE = 60;
|
|
45
|
+
const HOURS_PER_DAY = 24;
|
|
46
|
+
const DEFAULT_INTERVAL_MS = HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MS_PER_SECOND;
|
|
47
|
+
const EXEC_TIMEOUT_MS = 5 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MS_PER_SECOND;
|
|
48
|
+
const STDERR_RECORD_TRUNCATE_BYTES = 2000;
|
|
49
|
+
const STDERR_NEXT_ACTIONS_TRUNCATE_CHARS = 200;
|
|
38
50
|
function cronDir(projectRoot) {
|
|
39
51
|
return join(projectRoot, '.peaks', 'cron');
|
|
40
52
|
}
|
|
@@ -136,13 +148,13 @@ export function runTask(projectRoot, task) {
|
|
|
136
148
|
cwd: projectRoot,
|
|
137
149
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
138
150
|
encoding: 'utf8',
|
|
139
|
-
timeout:
|
|
151
|
+
timeout: EXEC_TIMEOUT_MS
|
|
140
152
|
});
|
|
141
153
|
}
|
|
142
154
|
catch (err) {
|
|
143
155
|
const e = err;
|
|
144
156
|
exitCode = typeof e.status === 'number' ? e.status : 1;
|
|
145
|
-
stderr = (e.stderr ?? getErrorMessage(err)).slice(0,
|
|
157
|
+
stderr = (e.stderr ?? getErrorMessage(err)).slice(0, STDERR_RECORD_TRUNCATE_BYTES);
|
|
146
158
|
}
|
|
147
159
|
const record = {
|
|
148
160
|
id,
|
|
@@ -243,7 +255,7 @@ export function registerCronCommand(program, io) {
|
|
|
243
255
|
updatedEntries.push({ ...task, lastRunAt: record.finishedAt });
|
|
244
256
|
}
|
|
245
257
|
writeSchedule(projectRoot, { version: SCHEDULE_VERSION, entries: updatedEntries });
|
|
246
|
-
printResult(io, ok('cron.run', { projectRoot, ran: records.length, records }, records.filter((r) => r.exitCode !== 0).map((r) => `Task ${r.taskId} failed (exit=${r.exitCode}): ${r.stderr.slice(0,
|
|
258
|
+
printResult(io, ok('cron.run', { projectRoot, ran: records.length, records }, records.filter((r) => r.exitCode !== 0).map((r) => `Task ${r.taskId} failed (exit=${r.exitCode}): ${r.stderr.slice(0, STDERR_NEXT_ACTIONS_TRUNCATE_CHARS)}`), [`Ran ${records.length} task(s); ${records.filter((r) => r.exitCode === 0).length} succeeded.`]), options.json);
|
|
247
259
|
if (records.some((r) => r.exitCode !== 0))
|
|
248
260
|
process.exitCode = 1;
|
|
249
261
|
}
|
|
@@ -5,7 +5,11 @@ import { read24hState } from '../../services/24h-mode/index.js';
|
|
|
5
5
|
import { getErrorMessage } from '../cli-helpers.js';
|
|
6
6
|
const SINCE_PATTERN = /^(\d+)([smhd])$/i;
|
|
7
7
|
const WINDOW_CAP_HOURS = 24;
|
|
8
|
-
const
|
|
8
|
+
const MS_PER_SECOND = 1000;
|
|
9
|
+
const MS_PER_MINUTE = 60 * MS_PER_SECOND;
|
|
10
|
+
const MS_PER_HOUR = 60 * MS_PER_MINUTE;
|
|
11
|
+
const MS_PER_DAY = 24 * MS_PER_HOUR;
|
|
12
|
+
const WINDOW_CAP_MS = WINDOW_CAP_HOURS * MS_PER_HOUR;
|
|
9
13
|
function parseSince(raw) {
|
|
10
14
|
if (!raw)
|
|
11
15
|
return { ok: false, error: '--since is required (e.g. 24h, 8h, 30m)' };
|
|
@@ -16,7 +20,7 @@ function parseSince(raw) {
|
|
|
16
20
|
if (!Number.isFinite(value) || value < 0)
|
|
17
21
|
return { ok: false, error: `--since must be a non-negative integer (got ${raw})` };
|
|
18
22
|
const unit = (match[2] ?? 'h').toLowerCase();
|
|
19
|
-
const multiplier = unit === 'd' ?
|
|
23
|
+
const multiplier = unit === 'd' ? MS_PER_DAY : unit === 'h' ? MS_PER_HOUR : unit === 'm' ? MS_PER_MINUTE : MS_PER_SECOND;
|
|
20
24
|
return { ok: true, ms: Math.min(WINDOW_CAP_MS, value * multiplier) };
|
|
21
25
|
}
|
|
22
26
|
function readSlices(projectRoot, sessionId) {
|
|
@@ -45,7 +49,7 @@ function boundaryLabel(raw) {
|
|
|
45
49
|
return { key: 'valid', message: 'unparseable' };
|
|
46
50
|
const value = Number(match[1]);
|
|
47
51
|
const unit = (match[2] ?? 'h').toLowerCase();
|
|
48
|
-
const multiplier = unit === 'd' ?
|
|
52
|
+
const multiplier = unit === 'd' ? MS_PER_DAY : unit === 'h' ? MS_PER_HOUR : unit === 'm' ? MS_PER_MINUTE : MS_PER_SECOND;
|
|
49
53
|
if (value * multiplier > WINDOW_CAP_MS) {
|
|
50
54
|
return { key: 'cap', message: 'since>24h is capped to the 24h window' };
|
|
51
55
|
}
|
|
@@ -86,7 +90,7 @@ export function registerDashboardLongRunCommand(dashboard, io) {
|
|
|
86
90
|
const sliceCount = readSlices(projectRoot, sid);
|
|
87
91
|
const checkpointAgeMs = readLastCheckpointAgeMs(snapshot);
|
|
88
92
|
const checkpointFrequency = snapshot.checkpoints > 0 && snapshot.enteredAt
|
|
89
|
-
? Math.max(1, Math.round((Date.now() - Date.parse(snapshot.enteredAt)) / snapshot.checkpoints /
|
|
93
|
+
? Math.max(1, Math.round((Date.now() - Date.parse(snapshot.enteredAt)) / snapshot.checkpoints / MS_PER_MINUTE))
|
|
90
94
|
: null;
|
|
91
95
|
const indicators = {
|
|
92
96
|
dispatchCount: sliceCount,
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ProgramIO } from '../cli-helpers.js';
|
|
2
2
|
import { type DispatchOptions } from './sub-agent-shared.js';
|
|
3
3
|
export declare function runDispatchFromDag(role: string, options: DispatchOptions, asJson: boolean, io: ProgramIO): Promise<void>;
|