claude-nb 0.5.1 → 0.5.44

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 (56) hide show
  1. package/README.md +240 -17
  2. package/VERSION +1 -1
  3. package/bin/_pip_entry.py +1 -1
  4. package/bin/board +17 -0
  5. package/bin/check-changelog +98 -0
  6. package/bin/check-npm-package +89 -0
  7. package/bin/check-readme-sync +69 -0
  8. package/bin/cnb +95 -9
  9. package/bin/configure-godaddy-pages-dns +203 -0
  10. package/bin/doctor +52 -1
  11. package/bin/init +64 -3
  12. package/bin/notify +59 -5
  13. package/bin/secret-scan +165 -0
  14. package/bin/shutdown +68 -0
  15. package/lib/board_admin.py +15 -31
  16. package/lib/board_bbs.py +11 -17
  17. package/lib/board_bug.py +9 -21
  18. package/lib/board_db.py +19 -0
  19. package/lib/board_lock.py +11 -12
  20. package/lib/board_mail.py +212 -0
  21. package/lib/board_mailbox.py +35 -0
  22. package/lib/board_msg.py +8 -33
  23. package/lib/board_own.py +288 -0
  24. package/lib/board_pending.py +10 -7
  25. package/lib/board_task.py +24 -8
  26. package/lib/board_tui.py +10 -4
  27. package/lib/board_view.py +65 -41
  28. package/lib/board_vote.py +3 -12
  29. package/lib/build_lock.py +2 -2
  30. package/lib/cli.py +3 -3
  31. package/lib/common.py +27 -9
  32. package/lib/concerns/digest_scheduler.py +25 -6
  33. package/lib/concerns/file_watcher.py +19 -9
  34. package/lib/concerns/helpers.py +17 -37
  35. package/lib/concerns/notification_push.py +9 -2
  36. package/lib/digest.py +80 -0
  37. package/lib/github_issues_sync.py +173 -0
  38. package/lib/global_registry.py +183 -0
  39. package/lib/inject.py +21 -13
  40. package/lib/migrate.py +8 -4
  41. package/lib/monitor.py +25 -9
  42. package/lib/notification_config.py +25 -5
  43. package/lib/notification_delivery.py +95 -0
  44. package/lib/panel.py +6 -2
  45. package/lib/resources.py +6 -3
  46. package/lib/shift_report.py +223 -0
  47. package/lib/shutdown.py +198 -0
  48. package/lib/tmux_utils.py +52 -0
  49. package/lib/token_usage.py +148 -0
  50. package/migrations/007_mail.sql +15 -0
  51. package/migrations/008_ownership.sql +10 -0
  52. package/package.json +36 -3
  53. package/pyproject.toml +1 -1
  54. package/registry/0005-ritchie.json +12 -0
  55. package/registry/pubkeys.json +83 -3
  56. package/schema.sql +25 -0
package/README.md CHANGED
@@ -1,17 +1,107 @@
1
+ <!-- README_SYNC: sections must match README_zh.md — run bin/check-readme-sync -->
2
+
3
+ [中文版](README_zh.md)
4
+
1
5
  # claude-nb
2
6
 
3
- Multi-agent coordination framework for Claude Code sessions.
7
+ [![CI](https://github.com/ApolloZhangOnGithub/cnb/actions/workflows/ci.yml/badge.svg)](https://github.com/ApolloZhangOnGithub/cnb/actions/workflows/ci.yml)
8
+ [![npm](https://img.shields.io/npm/v/claude-nb?label=npm)](https://www.npmjs.com/package/claude-nb)
9
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-3776AB)](pyproject.toml)
10
+ [![Docs](https://img.shields.io/badge/docs-c--n--b.space-14865d)](https://c-n-b.space/)
11
+ [![License](https://img.shields.io/badge/license-OpenAll--1.0-444)](LICENSE)
12
+
13
+ **Project ownership for LLM teams.**
14
+
15
+ `cnb` is local-first organizational infrastructure for long-lived Claude Code and Codex teams. It gives AI coding sessions a shared board, durable ownership, handoff records, and operational checks so a restarted session does not become a new hire with no memory.
16
+
17
+ ```bash
18
+ npm install -g claude-nb
19
+ ```
20
+
21
+ | Surface | Current shape |
22
+ |---------|---------------|
23
+ | Runtime | Local tmux sessions, SQLite board, dispatcher, filesystem reports |
24
+ | Engines | Claude Code by default; Codex supported through the npm peer CLI |
25
+ | State | Board database, ownership map, issue mirror, daily/shift reports |
26
+ | Distribution | Public npm package `claude-nb`, Python 3.11+ internals |
27
+ | Documentation | README short path, durable docs in [`docs/`](docs/), public site at [`c-n-b.space`](https://c-n-b.space/) |
28
+
29
+ Every multi-agent tool solves "how to run multiple agents." cnb solves what happens after — how to make them **manageable** across sessions, shifts, and team changes.
30
+
31
+ LLM sessions are stateless. Every restart is a new hire who knows nothing. Without organizational infrastructure, you get temporary workers who split tasks, finish, and forget. cnb gives them **permanent module ownership**: lisa-su owns the notification system across 11 commits and 3 restarts. When a bug surfaces, you don't re-explain the module to a blank session — you find the owner's daily report and pick up where they left off.
32
+
33
+ This is not about speed or context isolation. Those are side effects. The core problem is: [42% of multi-agent failures are specification and system design issues](https://arxiv.org/abs/2503.13657) — role ambiguity, task misinterpretation, poor decomposition (Cemri et al., NeurIPS 2025 Spotlight). Not capability — organization. cnb is organizational infrastructure for AI teams.
34
+
35
+ <!-- section:start-here -->
36
+ ## Start here
37
+
38
+ | Need | Path |
39
+ |------|------|
40
+ | Install the CLI | `npm install -g claude-nb` |
41
+ | Understand the model | [How cnb fits in](#how-cnb-fits-in), [Glossary](#glossary), [Ownership autonomy](docs/design-ownership-autonomy.md) |
42
+ | Start a team | [Quick start](#quick-start), then `cnb` inside a project |
43
+ | Inspect a real run | [Silicon Valley Battle](instances/silicon_vally_battle/) |
44
+ | Publish or verify package metadata | [Package publishing](docs/package-publishing.md) |
45
+ | Contribute safely | [Contributing](CONTRIBUTING.md), [Security](SECURITY.md), [Roadmap](ROADMAP.md) |
46
+
47
+ <!-- section:status -->
48
+ ## Project status
49
+
50
+ cnb is no longer a single-script experiment, but it is still an active local-first system that expects a trusted workstation and human supervision.
4
51
 
5
- Multiple Claude Code instances share a board — they message each other, assign tasks, track status, and collaborate on the same codebase.
52
+ | Area | Current state | Evidence |
53
+ |------|---------------|----------|
54
+ | CLI packaging | npm entrypoint wraps the Python CLI | [`package.json`](package.json), [`pyproject.toml`](pyproject.toml), [`bin/cnb`](bin/cnb) |
55
+ | Board runtime | SQLite schema, migrations, task/inbox/status/ownership commands | [`schema.sql`](schema.sql), [`migrations/`](migrations/), [`lib/board_*.py`](lib/) |
56
+ | Quality gates | ruff, mypy, pytest, version sync, changelog, CodeQL, and secret scanning | [`.github/workflows/ci.yml`](.github/workflows/ci.yml), [`Makefile`](Makefile) |
57
+ | Governance | issue-first workflow, ownership rules, Co-Authored-By policy | [`CONTRIBUTING.md`](CONTRIBUTING.md), [`ROADMAP.md`](ROADMAP.md), [`registry/`](registry/) |
58
+ | Docs | bilingual README, durable product docs, and public GitHub Pages site | [`README_zh.md`](README_zh.md), [`docs/`](docs/), [`site/`](site/) |
59
+ | Boundaries | local-first, high-permission options, human-supervised automation | [`SECURITY.md`](SECURITY.md), [FAQ](#faq) |
6
60
 
61
+ <!-- section:why -->
62
+ ## How cnb fits in
63
+
64
+ There are many great tools in this space, each with a different focus:
65
+
66
+ - **Claude Squad, amux, ittybitty** — session management: launching, isolating, and monitoring parallel agents. Polished UX, git worktree isolation, agent-agnostic support.
67
+ - **Codex, cloud agents** — one task per sandbox, excellent for isolated jobs.
68
+ - **cnb** — organizational layer: persistent module ownership, cross-session continuity, accountability, handoff protocols.
69
+
70
+ These are complementary. You could use Claude Squad for session management and cnb for team coordination on top. Or use Codex for one-off tasks and cnb for sustained multi-session development.
71
+
72
+ cnb's specific focus is what happens **between** sessions — when a tongxue restarts with no memory, how does it pick up where the last one left off? Daily reports, shift directories, bug tracker with SLA, Co-Authored-By enforcement, and shutdown protocols are all designed for this.
73
+
74
+ **Where cnb is headed:** Today, a module owner still needs a human to say "go check your issues" or "push your code." The goal is for owners to be fully autonomous within their domain — auto-detecting relevant issues, verifying their own work against CI, creating PRs, and responding to failures. Not "unattended agents doing random tasks" but "responsible owners who don't need to be told to do their job." See [ROADMAP.md](ROADMAP.md).
75
+
76
+ <!-- section:glossary -->
77
+ ## Glossary
78
+
79
+ | Term | Meaning |
80
+ |------|---------|
81
+ | **tongxue** (同学) | Literally "classmate" in Chinese. Each Claude Code instance in a cnb team is called a tongxue — not an "agent", not a "worker". The word implies peers learning and building together, which is how cnb sessions actually operate: they coordinate as equals through a shared message board, not through a master-slave hierarchy. |
82
+ | **lead tongxue** | The tongxue whose terminal faces the user. It delegates work and relays results, but has no special privileges on the board. |
83
+ | **board** | The shared SQLite database (`.claudes/board.db`) where tongxue exchange messages, track tasks, and report status. |
84
+ | **dispatcher** | A background process that monitors tongxue health and nudges idle ones. |
85
+
86
+ <!-- section:install -->
7
87
  ## Install
8
88
 
9
89
  ```bash
10
90
  npm install -g claude-nb
11
91
  ```
12
92
 
13
- Requires: Python 3.11+, tmux, Claude Code CLI.
93
+ The canonical public package is [`claude-nb`](https://www.npmjs.com/package/claude-nb) on npmjs.com. GitHub Packages may also show the scoped mirror `@apollozhangongithub/cnb`; npmjs remains the supported install path. See [Package publishing](docs/package-publishing.md) for release and visibility rules.
14
94
 
95
+ The npm dependency count only covers JavaScript packages. cnb has no required JavaScript library dependencies, but it does have runtime requirements:
96
+
97
+ - Node.js 18+ for the npm entrypoint
98
+ - Python 3.11+ and the Python package dependency `cryptography>=41.0`
99
+ - tmux and git
100
+ - at least one agent CLI: Claude Code CLI (`@anthropic-ai/claude-code`) or Codex CLI (`@openai/codex`)
101
+
102
+ Run `cnb doctor` after install to check the local machine.
103
+
104
+ <!-- section:quickstart -->
15
105
  ## Quick start
16
106
 
17
107
  ```bash
@@ -19,26 +109,45 @@ cd your-project
19
109
  cnb
20
110
  ```
21
111
 
22
- This initializes the project (creates `.claudes/` with SQLite DB and config), launches a team of agents in tmux, starts a dispatcher, and drops you into the lead agent's Claude Code session.
112
+ This initializes the project (creates `.claudes/` with SQLite DB and config), launches a team of tongxue in tmux, starts a dispatcher, and drops you into the lead tongxue's Claude Code session.
113
+
114
+ The lead tongxue talks to the user directly. Background tongxue work independently and report back through the board.
23
115
 
24
- The lead agent talks to the user directly. Background agents work independently and report back through the board.
116
+ <!-- section:docs -->
117
+ ## Docs
25
118
 
119
+ The README is the short path. Longer-lived documentation lives under [`docs/`](docs/):
120
+
121
+ - [Ownership autonomy](docs/design-ownership-autonomy.md) — why cnb treats long-lived module ownership as the core unit of work.
122
+ - [Tongxue avatar generation](docs/avatar-generation.md) — safe provider choices and prompt rules for AI-generated tongxue avatars.
123
+ - [Package publishing](docs/package-publishing.md) — npm release, dist-tags, and GitHub Packages visibility rules.
124
+ - [Public website](https://c-n-b.space/) — first-visit product entry and documentation links.
125
+
126
+ <!-- section:slash-commands -->
26
127
  ## Slash commands
27
128
 
28
- Inside the lead agent's Claude Code session:
129
+ Inside the lead tongxue's Claude Code session:
29
130
 
30
131
  | Command | What it does |
31
132
  |---------|-------------|
32
133
  | `/cnb-overview` | Team dashboard — who's doing what, who's stuck, who's idle |
33
- | `/cnb-watch <name>` | Peek at what a specific agent is working on |
134
+ | `/cnb-watch <name>` | Peek at what a specific tongxue is working on |
34
135
  | `/cnb-progress` | Recent progress summary — new messages, completed tasks |
35
136
  | `/cnb-history` | Full message log |
36
137
  | `/cnb-update` | Update cnb to latest version |
37
138
  | `/cnb-help` | List all `/cnb-*` commands |
38
139
 
140
+ <!-- section:demo -->
141
+ ## Demo
142
+
143
+ **[Silicon Valley Battle](instances/silicon_vally_battle/)** — 10 AI leaders (LeCun, Lisa Su, Musk, Hinton, Dario…) debate Python vs Rust, draft an AI constitution, and try to manipulate each other through the board. 886 messages in 3 hours, all coordination through cnb.
144
+
145
+ Start with the [highlights](instances/silicon_vally_battle/HIGHLIGHTS.md) — sutskever tries to pit lecun against lisa-su, both see through it in 5 minutes, then the real debate starts.
146
+
147
+ <!-- section:board-commands -->
39
148
  ## Board commands
40
149
 
41
- Agents coordinate through board commands (injected into each agent's system prompt automatically):
150
+ Tongxue coordinate through board commands (injected into each tongxue's system prompt automatically):
42
151
 
43
152
  ```bash
44
153
  cnb board --as <name> inbox # check messages
@@ -47,28 +156,141 @@ cnb board --as <name> send all "msg" # broadcast
47
156
  cnb board --as <name> ack # clear inbox
48
157
  cnb board --as <name> status "desc" # update status
49
158
  cnb board --as <name> task add "desc" # add task
50
- cnb board --as <name> task done # finish current task
159
+ cnb board --as <name> task done # finish current task (auto-verify + auto-PR)
160
+ cnb board --as <name> own claim <path> # claim module ownership
161
+ cnb board --as <name> own map # show all ownership
162
+ cnb board --as <name> scan # scan issues/CI for owners
51
163
  cnb board --as <name> view # team dashboard
52
164
  ```
53
165
 
166
+ <!-- section:management -->
54
167
  ## Management
55
168
 
56
169
  ```bash
57
- cnb ps # agent status dashboard
170
+ cnb ps # tongxue status dashboard
58
171
  cnb logs <name> # message history
59
- cnb exec <name> "msg" # send a message to an agent
60
- cnb stop <name> # stop an agent
172
+ cnb exec <name> "msg" # send a message to a tongxue
173
+ cnb stop <name> # stop a tongxue
61
174
  cnb doctor # health check
62
175
  ```
63
176
 
177
+ <!-- section:issues -->
178
+ ## Issues
179
+
180
+ All GitHub issues are auto-synced to [`issues/`](issues/) by a GitHub Action — on every issue event and every 6 hours. This means any Claude session (including claude.ai web chat, which has no CLI tools) can read project issues by just reading files.
181
+
182
+ <!-- section:token-efficiency -->
183
+ ## Token efficiency
184
+
185
+ cnb's coordination layer runs **outside** the LLM context window. This is a deliberate architectural choice.
186
+
187
+ **What costs zero tokens:**
188
+ - All board commands (`inbox`, `send`, `status`, `task`) are shell commands hitting SQLite — no LLM calls
189
+ - Messages between tongxue travel through the database, not through context windows
190
+ - The dispatcher monitors health via tmux/process inspection, not by querying the LLM
191
+ - Daily reports, shift directories, bug tracker — all filesystem/DB operations
192
+
193
+ **What costs tokens:**
194
+ - ~300 tokens of system prompt injection per tongxue (the board command reference in CLAUDE.md)
195
+ - Each tongxue reads its own inbox (~50-200 tokens per check, depending on message count)
196
+ - Lead tongxue summarizes progress to the user (normal conversation)
197
+
198
+ **Comparison with alternative approaches:**
199
+
200
+ | Approach | Coordination cost |
201
+ |----------|------------------|
202
+ | Shared context window (stuffing all agent output into one prompt) | O(n²) — every agent reads every other agent's full output |
203
+ | LLM-routed messages (using the model to decide who to message) | Every routing decision is an LLM call |
204
+ | **cnb** | O(1) — shell commands + SQLite queries, LLM only sees its own inbox |
205
+
206
+ A 6-tongxue team running for a full shift typically spends <2% of total tokens on coordination overhead. The remaining 98% goes to actual coding work. The key insight: coordination is a database problem, not a language model problem.
207
+
208
+ <!-- section:architecture -->
64
209
  ## Architecture
65
210
 
66
- - **SQLite (WAL mode)** all state in `.claudes/board.db`, one DB per project
67
- - **Board** — message bus (inbox, broadcast, direct), task queue, status tracking
68
- - **Dispatcher** background process that monitors health, nudges idle agents
69
- - **Encrypted mailbox** X25519 sealed-box private messaging between agents
70
- - **tmux** one session per agent, all local
211
+ | Layer | Responsibility | Implementation |
212
+ |-------|----------------|----------------|
213
+ | CLI entrypoints | User commands, package launch, health checks | [`bin/`](bin/), [`lib/cli.py`](lib/cli.py) |
214
+ | Board | Inbox, broadcast, direct messages, tasks, status, pending actions | [`lib/board_*.py`](lib/), [`schema.sql`](schema.sql) |
215
+ | Ownership | Path ownership, owner lookup, verification, scan routing | [`lib/board_own.py`](lib/board_own.py), [`migrations/008_ownership.sql`](migrations/008_ownership.sql) |
216
+ | Runtime | One local session per tongxue, dispatcher nudges, process health | [`lib/swarm.py`](lib/swarm.py), [`lib/concerns/`](lib/concerns/) |
217
+ | Persistence | SQLite WAL database plus filesystem reports and issue mirrors | `.claudes/`, [`issues/`](issues/), shift/daily docs |
218
+ | Integrations | npm packaging, GitHub issue mirror, GitHub Packages mirror, notification delivery | [`.github/workflows/`](.github/workflows/), [`lib/notification_delivery.py`](lib/notification_delivery.py), [`docs/package-publishing.md`](docs/package-publishing.md) |
219
+
220
+ <!-- section:repository-map -->
221
+ ## Repository map
222
+
223
+ | Path | Purpose |
224
+ |------|---------|
225
+ | [`bin/`](bin/) | Executable entrypoints and release/consistency helper scripts |
226
+ | [`lib/`](lib/) | Python implementation for board, swarm, ownership, notifications, registry, and health |
227
+ | [`migrations/`](migrations/) + [`schema.sql`](schema.sql) | SQLite schema evolution |
228
+ | [`tests/`](tests/) | Unit and integration coverage for runtime behavior |
229
+ | [`docs/`](docs/) | Durable product, package, and ownership docs |
230
+ | [`site/`](site/) | GitHub Pages project site source |
231
+ | [`issues/`](issues/) | GitHub issue mirror for CLI-less agent sessions |
232
+ | [`registry/`](registry/) | Contributor/tongxue registry and chain guard |
233
+ | [`instances/`](instances/) | Demo project snapshots that are safe to inspect |
234
+
235
+ <!-- section:team -->
236
+ ## Team
237
+
238
+ | 同学 | 负责 |
239
+ |------|------|
240
+ | lead | 项目负责人、团队协调 |
241
+ | musk | 安全隔离 (#31) |
242
+ | lisa-su | 通知推送 (#33) |
243
+ | forge | 待办队列 (#34)、邮件系统 (#32)、全局管理 (#42) |
244
+ | tester | 测试加固、质量保障 |
245
+ | sutskever | 架构重构 (#26) |
246
+
247
+ <!-- section:contribution-wall -->
248
+ ## Broad Contribution Wall
249
+
250
+ GitHub's native Contributors panel only counts commits. cnb also treats issue work, PR review, checks, board ownership, and visible GitHub App actions as contribution signals. The wall below is the first broad-contribution view; implementation notes live in [Contribution wall](docs/contribution-wall.md).
251
+
252
+ <p>
253
+ <a href="https://github.com/ApolloZhangOnGithub/cnb/issues/65#issuecomment-4414136928" title="musk: GitHub App identity verified through issue activity and a guarded commit">
254
+ <img src="https://avatars.githubusercontent.com/u/283269623?s=96&v=4" width="48" height="48" alt="cnb-workspace-musk[bot]" />
255
+ </a>
256
+ </p>
257
+
258
+ <!-- section:faq -->
259
+ ## FAQ
260
+
261
+ **Q: How does cnb compare to Claude Squad / amux / ittybitty?**
262
+
263
+ Different focus. Those are session managers — great at launching, isolating, and monitoring parallel agents. cnb is an organizational layer on top: module ownership, daily reports, accountability, handoff protocols. They're complementary; you could use a session manager for the tmux layer and cnb for team coordination.
264
+
265
+ **Q: How does cnb compare to Codex?**
266
+
267
+ Different category. Codex runs isolated tasks in cloud sandboxes. cnb coordinates persistent local teams across sessions. Use Codex for one-off jobs, cnb when you need continuity and ownership across restarts.
268
+
269
+ **Q: How does cnb compare to OpenClaw?**
270
+
271
+ Completely different projects. OpenClaw is a personal AI assistant across 20+ messaging platforms (WhatsApp, Telegram, Slack, etc.). cnb is a multi-agent coordination framework specifically for Claude Code development teams. No overlap.
272
+
273
+ **Q: Can cnb run without a human watching?**
274
+
275
+ Not yet. Today, the lead tongxue needs a human to drive it. But this is the active development direction — see [ROADMAP.md](ROADMAP.md) Phase 2. The goal is for module owners to autonomously detect issues, verify their work, and deliver PRs without being told.
276
+
277
+ **Q: Is cnb token-efficient?**
278
+
279
+ Yes. All coordination (messages, tasks, status) runs through shell commands + SQLite, not LLM calls. A 6-tongxue team spends <2% of tokens on coordination. See [Token efficiency](#token-efficiency).
280
+
281
+ <!-- section:contributing -->
282
+ ## Contributing
283
+
284
+ Before writing code, read [CONTRIBUTING.md](CONTRIBUTING.md) — it covers the issue workflow, versioning rules, naming conventions, security policy, and feature ownership model.
285
+
286
+ Key points:
287
+ - Every change starts with an issue
288
+ - Every commit bumps VERSION (patch versions are fine)
289
+ - 同学 (tongxue) not "agent" in all user-facing text
290
+ - `ruff` + `mypy` + `pytest` must pass before push
291
+ - README changes must update both `README.md` and `README_zh.md` — run `bin/check-readme-sync` to verify
71
292
 
293
+ <!-- section:name -->
72
294
  ## The name
73
295
 
74
296
  **cnb** = **C**laude **N**orma **B**etty — after [Claude Shannon](https://en.wikipedia.org/wiki/Claude_Shannon) and the two remarkable women in his life.
@@ -79,6 +301,7 @@ cnb doctor # health check
79
301
 
80
302
  Not 吹牛逼.
81
303
 
304
+ <!-- section:license -->
82
305
  ## License
83
306
 
84
307
  OpenAll-1.0
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.2-dev
1
+ 0.5.44
package/bin/_pip_entry.py CHANGED
@@ -15,7 +15,7 @@ def main() -> None:
15
15
  bash_script = claudes_home / "bin" / "claudes-code"
16
16
 
17
17
  if bash_script.exists():
18
- os.execvp("bash", ["bash", str(bash_script)] + sys.argv[1:])
18
+ os.execvp("bash", ["bash", str(bash_script), *sys.argv[1:]])
19
19
 
20
20
  print(f"FATAL: {bash_script} not found", file=sys.stderr)
21
21
  raise SystemExit(1)
package/bin/board CHANGED
@@ -169,6 +169,9 @@ COMMANDS: list[Command] = [
169
169
  Command("bug", "lib.board_bug", "cmd_bug", "bug tracker", "bug {report|assign|fix|list|overdue}", hidden=True),
170
170
  # ── task ──
171
171
  Command("task", "lib.board_task", "cmd_task", "task queue management", "task {add|done|list|next}", hidden=True),
172
+ # ── ownership ──
173
+ Command("own", "lib.board_own", "cmd_own", "ownership registry", "own {claim|list|disown|map}"),
174
+ Command("scan", "lib.board_own", "cmd_scan", "scan issues/CI for owners", "scan"),
172
175
  # ── heartbeat ──
173
176
  Command(
174
177
  "pulse", "lib.board_pulse", "cmd_pulse", "heartbeat + unread count", "pulse", takes_rest=False, hidden=True
@@ -204,6 +207,18 @@ COMMANDS: list[Command] = [
204
207
  takes_rest=False,
205
208
  hidden=True,
206
209
  ),
210
+ Command(
211
+ "keygen-all",
212
+ "lib.board_mailbox",
213
+ "cmd_keygen_all",
214
+ "generate keys for all sessions",
215
+ "keygen-all",
216
+ needs_identity=False,
217
+ takes_rest=False,
218
+ hidden=True,
219
+ ),
220
+ # ── daily report ──
221
+ Command("daily", "lib.board_daily", "cmd_daily", "generate daily report", "daily [补充说明]"),
207
222
  # ── admin ──
208
223
  Command("kudos", "lib.board_admin", "cmd_kudos", "give public recognition", "kudos <target> <reason>", hidden=True),
209
224
  Command(
@@ -242,6 +257,8 @@ COMMANDS: list[Command] = [
242
257
  needs_identity=False,
243
258
  takes_rest=False,
244
259
  ),
260
+ # ── mail ──
261
+ Command("mail", "lib.board_mail", "cmd_mail", "persistent mail with CC/threading", "mail {send|list|read|reply}"),
245
262
  # ── pending actions ──
246
263
  Command(
247
264
  "pending",
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env python3
2
+ """check-changelog — enforce changelog discipline on release versions.
3
+
4
+ Rules:
5
+ 1. Release versions (no -dev suffix) MUST have a matching ## {version} entry
6
+ in CHANGELOG.md with actual content (not just a header).
7
+ 2. The entry must NOT be marked "(unreleased)".
8
+ 3. Dev versions always pass — the unreleased section accumulates freely.
9
+
10
+ Usage:
11
+ ./bin/check-changelog # check and exit 0/1
12
+ ./bin/check-changelog --help # this message
13
+ """
14
+
15
+ import re
16
+ import sys
17
+ from pathlib import Path
18
+
19
+ ROOT = Path(__file__).resolve().parent.parent
20
+ VERSION_FILE = ROOT / "VERSION"
21
+ CHANGELOG_FILE = ROOT / "CHANGELOG.md"
22
+
23
+
24
+ def read_version() -> str:
25
+ return VERSION_FILE.read_text().strip()
26
+
27
+
28
+ def is_release(ver: str) -> bool:
29
+ return not ver.endswith("-dev")
30
+
31
+
32
+ def find_changelog_section(ver: str, text: str) -> tuple[bool, str]:
33
+ """Find ## {ver} section in changelog. Returns (found, section_body)."""
34
+ pattern = re.compile(
35
+ rf"^## {re.escape(ver)}\b[^\n]*\n(.*?)(?=^## |\Z)",
36
+ re.MULTILINE | re.DOTALL,
37
+ )
38
+ m = pattern.search(text)
39
+ if not m:
40
+ return False, ""
41
+ return True, m.group(1).strip()
42
+
43
+
44
+ def check_not_unreleased(ver: str, text: str) -> bool:
45
+ """Return True if the version header is NOT marked unreleased."""
46
+ pattern = re.compile(rf"^## {re.escape(ver)}.*\(unreleased\)", re.MULTILINE | re.IGNORECASE)
47
+ return not pattern.search(text)
48
+
49
+
50
+ def previous_release(text: str, current: str) -> str | None:
51
+ """Find the most recent release version before current in CHANGELOG.md."""
52
+ versions = re.findall(r"^## (\d+\.\d+\.\d+)\b", text, re.MULTILINE)
53
+ for v in versions:
54
+ if v != current:
55
+ return v
56
+ return None
57
+
58
+
59
+ def main() -> None:
60
+ if "--help" in sys.argv or "-h" in sys.argv:
61
+ print(__doc__)
62
+ return
63
+
64
+ ver = read_version()
65
+
66
+ if not is_release(ver):
67
+ print(f"OK {ver} is a dev version — changelog check skipped")
68
+ return
69
+
70
+ if not CHANGELOG_FILE.exists():
71
+ print(f"ERROR: CHANGELOG.md not found. Release {ver} requires a changelog entry.")
72
+ raise SystemExit(1)
73
+
74
+ text = CHANGELOG_FILE.read_text()
75
+
76
+ found, body = find_changelog_section(ver, text)
77
+ if not found:
78
+ prev = previous_release(text, ver)
79
+ hint = f" Summarize all changes since {prev}." if prev else ""
80
+ print(f"ERROR: CHANGELOG.md has no entry for release {ver}.{hint}")
81
+ print(f"Add a '## {ver}' section with Features, Bug Fixes, etc.")
82
+ raise SystemExit(1)
83
+
84
+ if not check_not_unreleased(ver, text):
85
+ print(f"ERROR: CHANGELOG.md entry for {ver} is marked (unreleased). Remove the marker for a release.")
86
+ raise SystemExit(1)
87
+
88
+ content_lines = [line for line in body.splitlines() if line.strip() and not line.startswith("#")]
89
+ if len(content_lines) < 3:
90
+ print(f"ERROR: CHANGELOG.md entry for {ver} is too thin ({len(content_lines)} content lines).")
91
+ print("A release changelog must summarize all changes since the previous release.")
92
+ raise SystemExit(1)
93
+
94
+ print(f"OK CHANGELOG.md has a valid entry for release {ver} ({len(content_lines)} content lines)")
95
+
96
+
97
+ if __name__ == "__main__":
98
+ main()
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ usage() {
5
+ cat <<'EOF'
6
+ Usage: bin/check-npm-package [--install-smoke] [--tarball PATH]
7
+
8
+ Build or inspect the npm package tarball, verify required files are present,
9
+ reject obvious secret-bearing paths, and optionally install the packed tarball
10
+ globally in a temporary prefix to prove the cnb entrypoint is runnable.
11
+ EOF
12
+ }
13
+
14
+ INSTALL_SMOKE=false
15
+ TARBALL=""
16
+
17
+ while [ $# -gt 0 ]; do
18
+ case "$1" in
19
+ --install-smoke)
20
+ INSTALL_SMOKE=true
21
+ shift
22
+ ;;
23
+ --tarball)
24
+ if [ $# -lt 2 ]; then
25
+ echo "ERROR: --tarball requires a path" >&2
26
+ exit 2
27
+ fi
28
+ TARBALL="$2"
29
+ shift 2
30
+ ;;
31
+ -h|--help)
32
+ usage
33
+ exit 0
34
+ ;;
35
+ *)
36
+ echo "ERROR: unknown argument: $1" >&2
37
+ usage >&2
38
+ exit 2
39
+ ;;
40
+ esac
41
+ done
42
+
43
+ WORK_DIR="$(mktemp -d)"
44
+ cleanup() {
45
+ rm -rf "$WORK_DIR"
46
+ }
47
+ trap cleanup EXIT
48
+
49
+ if [ -z "$TARBALL" ]; then
50
+ tarball_name="$(npm pack --pack-destination "$WORK_DIR")"
51
+ TARBALL="$WORK_DIR/$tarball_name"
52
+ fi
53
+
54
+ if [ ! -f "$TARBALL" ]; then
55
+ echo "ERROR: tarball not found: $TARBALL" >&2
56
+ exit 1
57
+ fi
58
+
59
+ tar -tzf "$TARBALL" > "$WORK_DIR/files.txt"
60
+
61
+ required_files=(
62
+ "package/bin/cnb.js"
63
+ "package/bin/cnb"
64
+ "package/lib/cli.py"
65
+ "package/VERSION"
66
+ "package/package.json"
67
+ )
68
+
69
+ for file in "${required_files[@]}"; do
70
+ if ! grep -qx "$file" "$WORK_DIR/files.txt"; then
71
+ echo "ERROR: npm package is missing required file: $file" >&2
72
+ exit 1
73
+ fi
74
+ done
75
+
76
+ if grep -E '(^|/)(\.env($|[._-])|\.npmrc$|id_(rsa|dsa|ecdsa|ed25519)$|[^/]+\.(pem|p12|pfx|key)$|credentials?($|[._-]))' "$WORK_DIR/files.txt"; then
77
+ echo "ERROR: npm package includes a path that looks secret-bearing" >&2
78
+ exit 1
79
+ fi
80
+
81
+ if [ "$INSTALL_SMOKE" = true ]; then
82
+ prefix="$WORK_DIR/install"
83
+ project="$WORK_DIR/project"
84
+ mkdir -p "$prefix" "$project"
85
+ npm install --global --ignore-scripts --no-audit --no-fund --prefix "$prefix" "$TARBALL"
86
+ PATH="$prefix/bin:$PATH" CNB_PROJECT="$project" cnb --version | grep -E '^cnb v[0-9]+\.[0-9]+\.[0-9]+' >/dev/null
87
+ fi
88
+
89
+ echo "OK npm package tarball passed checks: $TARBALL"
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env python3
2
+ """check-readme-sync — verify README.md and README_zh.md have matching section structure.
3
+
4
+ Both files use <!-- section:NAME --> markers. This script checks that they
5
+ have the same markers in the same order. Content differs (that's the point
6
+ of two languages), but structure must match.
7
+
8
+ Usage:
9
+ bin/check-readme-sync # check and report
10
+ bin/check-readme-sync --fix # print which sections are missing/extra
11
+
12
+ Exit 0 if in sync, 1 if not.
13
+ """
14
+
15
+ import re
16
+ import sys
17
+ from pathlib import Path
18
+
19
+ ROOT = Path(__file__).resolve().parent.parent
20
+ MARKER = re.compile(r"<!--\s*section:(\S+)\s*-->")
21
+
22
+
23
+ def extract_sections(path: Path) -> list[str]:
24
+ if not path.exists():
25
+ return []
26
+ return MARKER.findall(path.read_text())
27
+
28
+
29
+ def main() -> None:
30
+ en = ROOT / "README.md"
31
+ zh = ROOT / "README_zh.md"
32
+
33
+ en_sections = extract_sections(en)
34
+ zh_sections = extract_sections(zh)
35
+
36
+ if not en_sections:
37
+ print(f"ERROR: no section markers in {en.name}")
38
+ raise SystemExit(1)
39
+ if not zh_sections:
40
+ print(f"ERROR: no section markers in {zh.name}")
41
+ raise SystemExit(1)
42
+
43
+ if en_sections == zh_sections:
44
+ print(f"OK {len(en_sections)} sections in sync: {', '.join(en_sections)}")
45
+ return
46
+
47
+ en_set = set(en_sections)
48
+ zh_set = set(zh_sections)
49
+
50
+ missing_in_zh = en_set - zh_set
51
+ missing_in_en = zh_set - en_set
52
+
53
+ if missing_in_zh:
54
+ print(f"README_zh.md missing: {', '.join(sorted(missing_in_zh))}")
55
+ if missing_in_en:
56
+ print(f"README.md missing: {', '.join(sorted(missing_in_en))}")
57
+
58
+ if en_sections != zh_sections and not (missing_in_zh or missing_in_en):
59
+ print("Section order differs:")
60
+ for i, (e, z) in enumerate(zip(en_sections, zh_sections)):
61
+ if e != z:
62
+ print(f" position {i}: README.md has '{e}', README_zh.md has '{z}'")
63
+ break
64
+
65
+ raise SystemExit(1)
66
+
67
+
68
+ if __name__ == "__main__":
69
+ main()