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.
- package/README.md +240 -17
- package/VERSION +1 -1
- package/bin/_pip_entry.py +1 -1
- package/bin/board +17 -0
- package/bin/check-changelog +98 -0
- package/bin/check-npm-package +89 -0
- package/bin/check-readme-sync +69 -0
- package/bin/cnb +95 -9
- package/bin/configure-godaddy-pages-dns +203 -0
- package/bin/doctor +52 -1
- package/bin/init +64 -3
- package/bin/notify +59 -5
- package/bin/secret-scan +165 -0
- package/bin/shutdown +68 -0
- package/lib/board_admin.py +15 -31
- package/lib/board_bbs.py +11 -17
- package/lib/board_bug.py +9 -21
- package/lib/board_db.py +19 -0
- package/lib/board_lock.py +11 -12
- package/lib/board_mail.py +212 -0
- package/lib/board_mailbox.py +35 -0
- package/lib/board_msg.py +8 -33
- package/lib/board_own.py +288 -0
- package/lib/board_pending.py +10 -7
- package/lib/board_task.py +24 -8
- package/lib/board_tui.py +10 -4
- package/lib/board_view.py +65 -41
- package/lib/board_vote.py +3 -12
- package/lib/build_lock.py +2 -2
- package/lib/cli.py +3 -3
- package/lib/common.py +27 -9
- package/lib/concerns/digest_scheduler.py +25 -6
- package/lib/concerns/file_watcher.py +19 -9
- package/lib/concerns/helpers.py +17 -37
- package/lib/concerns/notification_push.py +9 -2
- package/lib/digest.py +80 -0
- package/lib/github_issues_sync.py +173 -0
- package/lib/global_registry.py +183 -0
- package/lib/inject.py +21 -13
- package/lib/migrate.py +8 -4
- package/lib/monitor.py +25 -9
- package/lib/notification_config.py +25 -5
- package/lib/notification_delivery.py +95 -0
- package/lib/panel.py +6 -2
- package/lib/resources.py +6 -3
- package/lib/shift_report.py +223 -0
- package/lib/shutdown.py +198 -0
- package/lib/tmux_utils.py +52 -0
- package/lib/token_usage.py +148 -0
- package/migrations/007_mail.sql +15 -0
- package/migrations/008_ownership.sql +10 -0
- package/package.json +36 -3
- package/pyproject.toml +1 -1
- package/registry/0005-ritchie.json +12 -0
- package/registry/pubkeys.json +83 -3
- 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
|
-
|
|
7
|
+
[](https://github.com/ApolloZhangOnGithub/cnb/actions/workflows/ci.yml)
|
|
8
|
+
[](https://www.npmjs.com/package/claude-nb)
|
|
9
|
+
[](pyproject.toml)
|
|
10
|
+
[](https://c-n-b.space/)
|
|
11
|
+
[](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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 #
|
|
170
|
+
cnb ps # tongxue status dashboard
|
|
58
171
|
cnb logs <name> # message history
|
|
59
|
-
cnb exec <name> "msg" # send a message to
|
|
60
|
-
cnb stop <name> # stop
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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.
|
|
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)
|
|
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()
|