@logicseek/hello-cc 0.1.8 → 1.0.0

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.
Files changed (108) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/README.md +26 -4
  3. package/README.zh-CN.md +22 -3
  4. package/bin/hcc.mjs +3 -5622
  5. package/docs/README.md +8 -0
  6. package/docs/README.zh-CN.md +6 -0
  7. package/docs/architecture.md +29 -18
  8. package/docs/architecture.zh-CN.md +20 -10
  9. package/docs/commands.md +16 -5
  10. package/docs/commands.zh-CN.md +16 -5
  11. package/docs/current-state-review-2026-08.md +341 -0
  12. package/docs/defect-review-2026-07.md +382 -0
  13. package/docs/guide.md +53 -5
  14. package/docs/guide.zh-CN.md +46 -3
  15. package/docs/superpowers/plans/2026-08-03-runtime-safety-plan.md +294 -0
  16. package/docs/superpowers/plans/2026-08-03-v1-data-identity-plan.md +400 -0
  17. package/docs/superpowers/plans/2026-08-03-v1-release-verification-plan.md +284 -0
  18. package/docs/superpowers/plans/2026-08-03-web-api-hardening-plan.md +303 -0
  19. package/docs/superpowers/plans/2026-08-04-runtime-buffer-gc-two-phase.md +71 -0
  20. package/docs/superpowers/specs/2026-08-03-runtime-safety-design.md +117 -0
  21. package/docs/superpowers/specs/2026-08-03-v1-data-identity-design.md +120 -0
  22. package/docs/superpowers/specs/2026-08-03-v1-release-verification-design.md +117 -0
  23. package/docs/superpowers/specs/2026-08-03-web-api-hardening-design.md +104 -0
  24. package/lib/cli/app.mjs +642 -0
  25. package/lib/cli/commands/coordination.mjs +247 -0
  26. package/lib/cli/commands/doctor.mjs +81 -0
  27. package/lib/cli/commands/gc.mjs +852 -0
  28. package/lib/cli/commands/hook.mjs +205 -0
  29. package/lib/cli/commands/install.mjs +294 -0
  30. package/lib/cli/commands/lifecycle.mjs +101 -0
  31. package/lib/cli/commands/lock.mjs +197 -0
  32. package/lib/cli/commands/misc.mjs +61 -0
  33. package/lib/cli/commands/msg.mjs +152 -0
  34. package/lib/cli/commands/peer.mjs +233 -0
  35. package/lib/cli/commands/query.mjs +96 -0
  36. package/lib/cli/commands/run.mjs +463 -0
  37. package/lib/cli/commands/task.mjs +405 -0
  38. package/lib/cli/commands/team.mjs +131 -0
  39. package/lib/cli/commands/tmux.mjs +838 -0
  40. package/lib/cli/commands/up.mjs +135 -0
  41. package/lib/cli-args.mjs +16 -2
  42. package/lib/coordination-state.mjs +112 -51
  43. package/lib/core/coordination/automation.mjs +5 -8
  44. package/lib/core/coordination/clock-safety.mjs +323 -0
  45. package/lib/core/coordination/event-retention.mjs +17 -0
  46. package/lib/core/coordination/gc-plan.mjs +614 -0
  47. package/lib/core/coordination/lease-renewal.mjs +102 -0
  48. package/lib/core/coordination/lock-evidence.mjs +77 -0
  49. package/lib/core/coordination/optimistic-evidence.mjs +26 -0
  50. package/lib/core/coordination/tasks.mjs +156 -36
  51. package/lib/core/peers/evidence-runtime.mjs +312 -0
  52. package/lib/core/peers/evidence.mjs +103 -0
  53. package/lib/core/peers/hook-owner.mjs +32 -0
  54. package/lib/core/peers/liveness.mjs +44 -4
  55. package/lib/core/peers/peer-helpers.mjs +121 -0
  56. package/lib/core/peers/session.mjs +8 -2
  57. package/lib/core/peers/tmux-safety.mjs +290 -0
  58. package/lib/core/sessions/launch.mjs +15 -1
  59. package/lib/db/connection.mjs +104 -0
  60. package/lib/db/events.mjs +34 -0
  61. package/lib/db/migration-backup.mjs +225 -0
  62. package/lib/db/schema.mjs +102 -5
  63. package/lib/db/stores/peers.mjs +34 -0
  64. package/lib/integrations/peers/identity.mjs +62 -16
  65. package/lib/integrations/shims/script.mjs +139 -15
  66. package/lib/process/identity.mjs +192 -0
  67. package/lib/process/pty-lifecycle.mjs +149 -0
  68. package/lib/runtime/buffer-directory-lease.mjs +47 -0
  69. package/lib/runtime/buffer-evidence.mjs +336 -0
  70. package/lib/runtime/buffer-gc-protocol.mjs +167 -0
  71. package/lib/runtime/buffer-gc.mjs +270 -0
  72. package/lib/runtime/client.mjs +61 -8
  73. package/lib/runtime/fatal-shutdown.mjs +54 -0
  74. package/lib/runtime/project-path.mjs +139 -0
  75. package/lib/runtime/projects.mjs +320 -40
  76. package/lib/runtime/state.mjs +232 -29
  77. package/lib/shared/cleanup-error.mjs +54 -0
  78. package/lib/shared/clock-grace.mjs +106 -0
  79. package/lib/shared/errors.mjs +9 -0
  80. package/lib/shared/file-lock.mjs +370 -0
  81. package/lib/shared/json-file.mjs +6 -2
  82. package/lib/shared/redact.mjs +60 -0
  83. package/lib/shared/socket-lock-worker.mjs +201 -0
  84. package/lib/terminal/tmux.mjs +78 -4
  85. package/lib/tmux.mjs +4 -0
  86. package/lib/ui/help.mjs +53 -15
  87. package/lib/web/api-version.mjs +42 -0
  88. package/lib/web/auto-attach.mjs +185 -0
  89. package/lib/web/buffer-gc-runtime.mjs +175 -0
  90. package/lib/web/cookie-auth.mjs +119 -0
  91. package/lib/web/external-sessions.mjs +254 -0
  92. package/lib/web/http-routes.mjs +542 -0
  93. package/lib/web/http.mjs +143 -7
  94. package/lib/web/liveness-reaper.mjs +145 -0
  95. package/lib/web/peer-actions.mjs +132 -23
  96. package/lib/web/project-contexts.mjs +178 -0
  97. package/lib/web/pty-sessions.mjs +203 -0
  98. package/lib/web/runtime-main.mjs +714 -0
  99. package/lib/web/runtime.mjs +91 -36
  100. package/lib/web/session-serialize.mjs +0 -0
  101. package/lib/web/startup.mjs +333 -0
  102. package/lib/web/tls.mjs +365 -0
  103. package/lib/web/tmux-sessions.mjs +1361 -0
  104. package/lib/web/tmux-stream.mjs +166 -0
  105. package/lib/web/ui-template.mjs +152 -20
  106. package/package.json +5 -2
  107. package/scripts/check-free-identifiers.mjs +290 -0
  108. package/scripts/regression.mjs +5646 -262
@@ -3,6 +3,37 @@
3
3
  这份文档说明安装、启动和日常使用流程。紧凑命令清单见
4
4
  [命令参考](commands.zh-CN.md)。
5
5
 
6
+ ## v1 升级契约
7
+
8
+ 1.0.0 会在生成并校验迁移前备份后升级到 schema v7,且不支持降级。provider
9
+ peer ID 已变更,不会映射旧 ID;受保护接口要求 Runtime API v2。存活判断以进程
10
+ 证据为先,只有 unknown 证据获得 120 秒宽限。历史 GC 必须显式使用
11
+ `--history`。需要加密时使用 `--tls`,或
12
+ `--trust-proxy --proxy-origin ORIGIN`。可信内网明文模式和已认证浏览器选择任意
13
+ 已存在服务器目录,仍是明确接受的风险。
14
+
15
+ ### 从迁移前备份恢复
16
+
17
+ hello-cc 为每个数据库最多保留 5 份经校验的迁移备份。如果必须回退一次 v1
18
+ 迁移,先停止 Web runtime,以及所有仍可能访问该项目的 hello-cc/provider 进程。
19
+ 校验选定备份,把失败数据库及其 WAL/SHM 文件改名保留,再发布备份的私有副本:
20
+
21
+ ```bash
22
+ db=/path/to/project/.hello-cc/mesh.db
23
+ backup=/path/to/project/.hello-cc/mesh.db.pre-v6-to-v7.<timestamp>.<suffix>.bak
24
+ sqlite3 "$backup" 'PRAGMA quick_check;'
25
+ cp -p "$backup" "$db.restore"
26
+ chmod 600 "$db.restore"
27
+ mv "$db" "$db.failed-v7.<recovery-id>"
28
+ test ! -e "$db-wal" || mv "$db-wal" "$db-wal.failed-v7.<recovery-id>"
29
+ test ! -e "$db-shm" || mv "$db-shm" "$db-shm.failed-v7.<recovery-id>"
30
+ mv "$db.restore" "$db"
31
+ ```
32
+
33
+ 校验结果必须是 `ok`。确认恢复后的项目正常前,不要删除这些改名文件。再次启动
34
+ v1 会把恢复的数据库重新迁移到 schema v7;用旧版本 hello-cc 打开恢复出的 pre-v1
35
+ 数据库不属于受支持的降级路径。
36
+
6
37
  ## 安装、更新和卸载
7
38
 
8
39
  全局安装 hello-cc:
@@ -95,6 +126,9 @@ codex resume <session-id>
95
126
 
96
127
  - 本地终端会 attach 到同一个 tmux 会话,仍然可以像普通 Claude/Codex 一样交互。
97
128
  - Web 控制台看到并操作的是同一个 tmux pane,不是浏览器里临时创建的假终端。
129
+ - shim 只使用当前项目的 `.hello-cc/runtime.json`。全局 Web runtime 不会让任意
130
+ 目录自动进入管理;如果当前项目没有运行过 `hcc web`,`claude` 和 `codex`
131
+ 会回退到真实 provider CLI,不会创建 `.hello-cc/mesh.db`。
98
132
  - `hcc down` 只停止 Web runtime,不会杀掉这些 tmux 会话。
99
133
  - 重新运行 `hcc web` 后,只要 tmux pane 还活着,Web 会重新接管。
100
134
 
@@ -108,10 +142,14 @@ hcc up
108
142
  ```
109
143
 
110
144
  普通用户优先使用 `hcc web`。
145
+ `hcc web --local` 仍然是 Web 模式,只是把 Web runtime 限制为监听
146
+ `127.0.0.1`。
111
147
 
112
148
  ## 项目边界
113
149
 
114
- 默认项目边界就是当前工作目录。如果当前目录没有 `.hello-cc/mesh.db`,hello-cc 会在首次使用时创建:
150
+ 默认项目边界就是当前工作目录。显式运行 `hcc web`、`hcc up`、`hcc task` 或
151
+ `hcc peer start` 这类 `hcc` 命令时,如果当前目录没有 `.hello-cc/mesh.db`,
152
+ hello-cc 会在首次使用时创建:
115
153
 
116
154
  ```text
117
155
  /repo-a/.hello-cc/mesh.db
@@ -120,6 +158,10 @@ hcc up
120
158
 
121
159
  这两个目录默认是两个不同项目,不会互相看到任务和消息。想让多个终端自然通信,就在同一个项目目录启动它们。
122
160
 
161
+ provider shim 更严格:直接运行 `claude` 和 `codex` 时,只有当前项目已经通过
162
+ `hcc web` 生成本地 `.hello-cc/runtime.json` 才会加入 hello-cc。它们不会使用
163
+ `~/.hello-cc/runtime.json` 去自动注册无关目录,也不会为这些目录创建数据库。
164
+
123
165
  如果确实要让多个路径或 worktree 共用一个总线,需要显式指定:
124
166
 
125
167
  ```bash
@@ -199,7 +241,7 @@ debug 日志里应该出现 `Hook UserPromptSubmit ... provided additionalContex
199
241
  hcc web
200
242
  ```
201
243
 
202
- 裸 `hcc web` 会保存自动生成的 token,并在重启后复用。需要替换保存值时可显式设置:
244
+ 裸 `hcc web` 会为本次 runtime 自动生成 token。需要稳定的运维 token 时可显式设置:
203
245
 
204
246
  ```bash
205
247
  HCC_WEB_TOKEN='choose-a-long-token' hcc web --port 8787
@@ -320,7 +362,8 @@ hcc lock release --resource vllm/router
320
362
  | `HCC_PEER` | 默认 peer 身份 |
321
363
  | `HCC_ROOT` | 覆盖项目根目录 |
322
364
  | `HCC_DB` | 覆盖数据库路径 |
323
- | `HCC_WEB_TOKEN` | 替换并保存固定 Web 访问令牌 |
365
+ | `HCC_WEB_TOKEN` | 设置显式 Web 访问令牌 |
366
+ | `HCC_RUNTIME_CA` | `HCC_RUNTIME_URL=https` 端点的 CA 文件 |
324
367
  | `HCC_RUNTIME_URL` | 直接指定 runtime URL |
325
368
  | `HCC_NO_AUTO_INSTALL_TMUX=1` | 禁用 tmux 自动安装,主要用于测试 |
326
369
 
@@ -0,0 +1,294 @@
1
+ # Runtime Safety, Clock, GC, and Registry Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Make clock, takeover, garbage collection, and project-registry decisions safe under sleep, PID reuse, runtime failure, and concurrent writers.
6
+
7
+ **Architecture:** A clock-safety module owns persistent observation and grace. A buffer-GC planner is shared by CLI and runtime, while an authenticated runtime endpoint serializes deletion with active sessions. Registry updates use an owner-identified lock directory.
8
+
9
+ **Tech Stack:** Node.js 24 ESM, `node:test`, `node:sqlite`, filesystem atomics, HTTP runtime API, tmux integration.
10
+
11
+ ---
12
+
13
+ ## File Map
14
+
15
+ - Create `lib/core/coordination/clock-safety.mjs` and `test/clock-safety.test.mjs`.
16
+ - Modify `lib/shared/clock-grace.mjs` to make grace extension atomic and observable.
17
+ - Create `lib/runtime/buffer-gc.mjs` and `test/buffer-gc.test.mjs`.
18
+ - Create `lib/shared/file-lock.mjs` and `test/file-lock.test.mjs`.
19
+ - Modify `bin/hcc.mjs` for clock observation, runtime GC endpoint, safe CLI GC, and history flags.
20
+ - Modify `lib/runtime/projects.mjs` for lock-protected registry merging.
21
+ - Modify `lib/runtime/client.mjs` for the internal buffer-GC request.
22
+ - Modify `lib/coordination-state.mjs`, task/lock paths, and `scripts/regression.mjs` for integration behavior.
23
+
24
+ ### Task 1: Make grace extension atomic and classify observation decisions
25
+
26
+ **Files:**
27
+ - Create: `lib/core/coordination/clock-safety.mjs`
28
+ - Create: `test/clock-safety.test.mjs`
29
+ - Modify: `lib/shared/clock-grace.mjs:1-46`
30
+
31
+ - [ ] **Step 1: Write failing pure-decision tests**
32
+
33
+ ```js
34
+ import test from 'node:test';
35
+ import assert from 'node:assert/strict';
36
+ import { decideClockSafety } from '../lib/core/coordination/clock-safety.mjs';
37
+
38
+ test('live evidence crosses expiry without grace and requests renewal', () => {
39
+ assert.deepEqual(decideClockSafety({ previous: 100, current: 1000, operation: 'ownership', candidates: [{ boundary: 500, evidence: 'live' }] }), {
40
+ enterGrace: false,
41
+ renewOwners: true,
42
+ reason: 'verified-live'
43
+ });
44
+ });
45
+
46
+ test('unknown evidence crossing expiry enters grace', () => {
47
+ assert.equal(decideClockSafety({ previous: 100, current: 1000, operation: 'ownership', candidates: [{ boundary: 500, evidence: 'unknown' }] }).enterGrace, true);
48
+ });
49
+
50
+ test('GC crossing an age cutoff enters grace without owner candidates', () => {
51
+ assert.equal(decideClockSafety({ previous: 100, current: 1000, operation: 'gc', gcCutoffs: [500] }).enterGrace, true);
52
+ });
53
+
54
+ test('verified dead evidence does not delay ownership', () => {
55
+ assert.equal(decideClockSafety({ previous: 100, current: 1000, operation: 'ownership', candidates: [{ boundary: 500, evidence: 'dead' }] }).enterGrace, false);
56
+ });
57
+ ```
58
+
59
+ - [ ] **Step 2: Run to verify RED**
60
+
61
+ Run: `node --test test/clock-safety.test.mjs`
62
+
63
+ Expected: FAIL because the decision module is missing.
64
+
65
+ - [ ] **Step 3: Implement the pure decision and atomic SQL extension**
66
+
67
+ `decideClockSafety` must return deterministic `{ enterGrace, renewOwners, reason }` from the supplied observation. Backward movement beyond five seconds always enters grace for unknown and GC age decisions. Long forward gaps enter grace only when they cross the relevant boundary.
68
+
69
+ Replace grace overwrite SQL with:
70
+
71
+ ```sql
72
+ INSERT INTO meta(key, value) VALUES ('clock_grace_until', ?)
73
+ ON CONFLICT(key) DO UPDATE SET value = CAST(
74
+ MAX(CAST(meta.value AS INTEGER), CAST(excluded.value AS INTEGER)) AS TEXT
75
+ )
76
+ ```
77
+
78
+ `writeClockGraceUntil` returns the persisted deadline and throws on write failure; it must not swallow exceptions.
79
+
80
+ - [ ] **Step 4: Verify GREEN**
81
+
82
+ Run: `node --test test/clock-safety.test.mjs`.
83
+
84
+ Add a real in-memory SQLite test with two shorter/longer writes and assert the final value is the maximum. Run again and require PASS.
85
+
86
+ - [ ] **Step 5: Commit**
87
+
88
+ ```bash
89
+ git add lib/core/coordination/clock-safety.mjs lib/shared/clock-grace.mjs test/clock-safety.test.mjs
90
+ git commit -m "fix: make clock grace monotonic"
91
+ ```
92
+
93
+ ### Task 2: Observe clock safety before destructive ownership decisions
94
+
95
+ **Files:**
96
+ - Modify: `bin/hcc.mjs:413-471,728-751,1053-1120,1752-1810,2926-3005,5709-5725,6426-6538`
97
+ - Modify: `lib/core/coordination/clock-safety.mjs`
98
+ - Modify: `lib/coordination-state.mjs:37-105,191-220`
99
+ - Modify: `lib/core/coordination/tasks.mjs:73-90`
100
+ - Modify: `scripts/regression.mjs:2616-2820`
101
+
102
+ - [ ] **Step 1: Add the CLI-only failing integration fixture**
103
+
104
+ Create an isolated project with no local or global runtime pointer. Register a peer using the regression process PID and stored fingerprint, acquire a lock, move only wall-clock DB timestamps one day into the past, remove `clock_grace_until`, and attempt acquisition from another peer.
105
+
106
+ Write the final expected assertion from the start:
107
+
108
+ ```js
109
+ if (attempt.status === 0 || !attempt.stderr.includes('LOCK_HELD')) {
110
+ fail(`live owner was not protected after a CLI-only clock gap:\n${attempt.stderr}`);
111
+ }
112
+ ```
113
+
114
+ From the same initial test, require an unchanged owner, renewed `expires_at`, and
115
+ no blanket grace for the verified live owner. Add unknown-evidence and
116
+ verified-dead variants before running RED.
117
+
118
+ - [ ] **Step 2: Run to verify RED**
119
+
120
+ Run: `npm run test:regression`.
121
+
122
+ Expected: live-owner fixture demonstrates takeover/replacement under the current code.
123
+
124
+ - [ ] **Step 3: Add the observation transaction**
125
+
126
+ Export `observeClockSafety(db, { operation, candidates, gcCutoffs, nowSec })`. In one immediate transaction, read `clock_last_observed_at`, call `decideClockSafety`, extend grace to `nowSec + 120` only when `enterGrace` is true, and write the new watermark. Persist the maximum of the existing and proposed deadline. Return the decision and persisted grace. Verified-live evidence renews immediately without grace, verified-dead evidence proceeds without grace, and only unknown evidence receives the 120-second delay.
127
+
128
+ Call it before lock acquire, task takeover, heartbeat/hook recovery, reaper, and GC. Do not call it from pure status/list/doctor paths. When it reports verified live owners, renew retained locks from `ttl_sec`. When grace persistence throws, abort the destructive command with `CLOCK_SAFETY_UNAVAILABLE`.
129
+
130
+ The Web poller passes its wall/monotonic jump signal into the same observer and no longer maintains a separate grace algorithm.
131
+
132
+ - [ ] **Step 4: Verify GREEN and nearby behavior**
133
+
134
+ Run: `node --test test/clock-safety.test.mjs`.
135
+
136
+ Run: `npm run test:regression`.
137
+
138
+ Expected: live evidence renews immediately, dead evidence expires, unknown evidence enters grace, status/doctor remain read-only.
139
+
140
+ - [ ] **Step 5: Commit**
141
+
142
+ ```bash
143
+ git add bin/hcc.mjs lib/core/coordination/clock-safety.mjs lib/coordination-state.mjs lib/core/coordination/tasks.mjs scripts/regression.mjs
144
+ git commit -m "fix: protect CLI-only ownership after clock gaps"
145
+ ```
146
+
147
+ ### Task 3: Extract a buffer-GC planner and runtime arbitration endpoint
148
+
149
+ **Files:**
150
+ - Create: `lib/runtime/buffer-gc.mjs`
151
+ - Create: `test/buffer-gc.test.mjs`
152
+ - Modify: `bin/hcc.mjs:3005-3063,6426-6538,4675-5050`
153
+ - Modify: `lib/runtime/client.mjs:7-35`
154
+ - Modify: `scripts/regression.mjs:1841-1880,2866-3300,7019-7070`
155
+
156
+ - [ ] **Step 1: Write failing planner tests with real files**
157
+
158
+ In a temporary `bufs` directory create old protected, old dead, old unknown, and new files. Call:
159
+
160
+ ```js
161
+ const result = pruneBufferFiles({
162
+ directories: [bufsDir],
163
+ cutoffMs,
164
+ protectedPaths: new Set([protectedFile]),
165
+ unknownPaths: new Set([unknownFile]),
166
+ dryRun: false
167
+ });
168
+ ```
169
+
170
+ Assert protected/unknown/new remain, dead old is deleted, and counts are `{ deleted: 1, protected: 1, deferred: 1 }`.
171
+
172
+ - [ ] **Step 2: Run to verify RED**
173
+
174
+ Run: `node --test test/buffer-gc.test.mjs`.
175
+
176
+ Expected: missing-module failure.
177
+
178
+ - [ ] **Step 3: Implement the planner and endpoint**
179
+
180
+ Implement `pruneBufferFiles` with absolute resolved paths, `lstat` regular-file/FIFO checks, one supplied cutoff, and identical dry-run/apply planning. It must not follow symlinks.
181
+
182
+ Add authenticated `POST /api/runtime/gc-buffers`. The handler snapshots actual `outFile`, `inFile`, `resizeFile`, `pipeFile`, and external meta paths from every running session, groups them by actual directory, and calls the synchronous planner before returning counts.
183
+
184
+ Manual CLI GC fixes `cutoffMs` before the request. If a runtime pointer exists and the endpoint returns unreachable/404/426, mark eligible buffers deferred and do not fall back to blind local deletion. With no runtime pointer, build live/unknown protection from external metadata fingerprints and verified tmux panes.
185
+
186
+ - [ ] **Step 4: Verify RED-GREEN at the real boundary**
187
+
188
+ Run planner tests. Then add runtime integration cases for active external files, active tmux FIFO, sibling-project actual directories, unreachable runtime, legacy metadata, and orphan old files.
189
+
190
+ Run: `npm run test:regression`.
191
+
192
+ Expected: active/unknown files remain, only proven old orphans are removed, and runtime failure reports deferred counts.
193
+
194
+ - [ ] **Step 5: Commit**
195
+
196
+ ```bash
197
+ git add lib/runtime/buffer-gc.mjs test/buffer-gc.test.mjs lib/runtime/client.mjs bin/hcc.mjs scripts/regression.mjs
198
+ git commit -m "fix: arbitrate buffer cleanup through runtime"
199
+ ```
200
+
201
+ ### Task 4: Make GC retention explicit and clock-safe
202
+
203
+ **Files:**
204
+ - Modify: `bin/hcc.mjs:6420-6557`
205
+ - Modify: `lib/ui/help.mjs:40-50,285-305`
206
+ - Modify: `scripts/regression.mjs:3689-3776,7019-7070`
207
+
208
+ - [ ] **Step 1: Add failing CLI cases**
209
+
210
+ Add fixtures proving:
211
+
212
+ ```text
213
+ hcc gc --older-than -1 --yes -> BAD_ARGS, no row/file changes
214
+ hcc gc --older-than 0 --yes -> technical cleanup only
215
+ hcc gc --older-than 0 --history -> dry-run only
216
+ hcc gc --older-than 0 --history --yes -> eligible history removed
217
+ clock grace + any form -> every age-based category deferred
218
+ ```
219
+
220
+ Keep an open-task handoff and assert it survives even with `--history --yes`.
221
+
222
+ - [ ] **Step 2: Run to verify RED**
223
+
224
+ Run: `npm run test:regression`.
225
+
226
+ Expected: negative retention mutates/plans broadly, and ordinary `--yes` currently removes history.
227
+
228
+ - [ ] **Step 3: Implement the command contract**
229
+
230
+ Reject `olderThanDays < 0` before `connect()`. Parse a `history` boolean. Split the planner into technical and history SQL. During grace, produce deferred counts for all age-based categories and execute no age-based delete. Always preserve handoffs linked to tasks outside `done`/`abandoned`.
231
+
232
+ Keep WAL checkpoint only after an applied safe plan. Update text and JSON output with `deleted`, `protected`, and `deferred` category counts.
233
+
234
+ - [ ] **Step 4: Verify GREEN**
235
+
236
+ Run: `npm run test:regression`.
237
+
238
+ Run: `node ./bin/hcc.mjs gc --help`.
239
+
240
+ Expected: every CLI contract case passes and help documents `--history`.
241
+
242
+ - [ ] **Step 5: Commit**
243
+
244
+ ```bash
245
+ git add bin/hcc.mjs lib/ui/help.mjs scripts/regression.mjs
246
+ git commit -m "fix: make GC retention explicit and safe"
247
+ ```
248
+
249
+ ### Task 5: Serialize project-registry writers
250
+
251
+ **Files:**
252
+ - Create: `lib/shared/file-lock.mjs`
253
+ - Create: `test/file-lock.test.mjs`
254
+ - Modify: `lib/runtime/projects.mjs:1-86`
255
+ - Modify: `scripts/regression.mjs:1813-1840,4500-4700`
256
+
257
+ - [ ] **Step 1: Write failing real-filesystem tests**
258
+
259
+ Spawn two Node child processes that wait on a barrier file, then concurrently call `registerProject` for different roots under one isolated `HOME`. Assert both records remain. Add a same-root/different-DB update inside 60 seconds and assert the DB changes immediately.
260
+
261
+ Add a stale-lock fixture with a dead owner fingerprint and an unknown-owner fixture. Dead lock must be reclaimed; unknown lock must time out without deletion.
262
+
263
+ - [ ] **Step 2: Run to verify RED**
264
+
265
+ Run: `node --test test/file-lock.test.mjs`.
266
+
267
+ Expected: missing-module failure, then the concurrent registry assertion fails before integration.
268
+
269
+ - [ ] **Step 3: Implement atomic lock-directory ownership**
270
+
271
+ Implement:
272
+
273
+ ```js
274
+ export function withFileLock(target, fn, { timeoutMs = 5000, retryMs = 25 } = {})
275
+ ```
276
+
277
+ Acquire `${target}.lock` with `mkdirSync`. Write owner metadata atomically with the current process fingerprint. On `EEXIST`, inspect the owner: reclaim only `dead`, retry `live`, and time out `unknown`. Always remove only the lock whose owner token still matches the current holder.
278
+
279
+ Wrap the entire registry re-read, merge, throttle decision, and atomic write. Compare both canonical root and resolved DB before throttling.
280
+
281
+ - [ ] **Step 4: Verify GREEN**
282
+
283
+ Run: `node --test test/file-lock.test.mjs`.
284
+
285
+ Run: `npm run test:regression`.
286
+
287
+ Expected: concurrent writers retain both records; changed DB updates immediately; unknown locks are not stolen.
288
+
289
+ - [ ] **Step 5: Commit**
290
+
291
+ ```bash
292
+ git add lib/shared/file-lock.mjs test/file-lock.test.mjs lib/runtime/projects.mjs scripts/regression.mjs
293
+ git commit -m "fix: serialize project registry updates"
294
+ ```