@haiyangbg/buildbeat 3.0.0 → 3.1.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 (50) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.en.md +4 -4
  3. package/README.md +1 -1
  4. package/SKILL.md +8 -5
  5. package/docs/README.md +3 -2
  6. package/docs/RELEASING.md +3 -1
  7. package/docs/v2/guide/01-quickstart.en.md +165 -0
  8. package/docs/v2/guide/01-quickstart.md +7 -3
  9. package/docs/v2/guide/02-workflow-guide.md +11 -0
  10. package/docs/v2/guide/06-evidence-guide.en.md +51 -0
  11. package/docs/v2/guide/06-evidence-guide.md +2 -0
  12. package/docs/v2/guide/07-approval-guide.en.md +117 -0
  13. package/docs/v2/guide/07-approval-guide.md +16 -2
  14. package/docs/v2/guide/10-recovery.en.md +99 -0
  15. package/docs/v2/guide/10-recovery.md +22 -4
  16. package/docs/v2/guide/README.md +4 -4
  17. package/example/.buildbeat/notify.yaml +13 -0
  18. package/example/.buildbeat/observe.yaml +31 -0
  19. package/example/AGENTS.md +73 -0
  20. package/example/BUILDBEAT.md +14 -0
  21. package/example/CLAUDE.md +7 -0
  22. package/example/README.md +25 -0
  23. package/example/delivery/envelope/prompts/builder.md +10 -0
  24. package/example/delivery/envelope/prompts/fixer.md +10 -0
  25. package/example/delivery/envelope/prompts/reviewer.md +13 -0
  26. package/example/delivery/envelope/worker.sh +70 -0
  27. package/example/delivery/work/WORK-EXPORT-DATE-FILTER/decisions.jsonl +3 -0
  28. package/example/delivery/work/WORK-EXPORT-DATE-FILTER/intent.md +24 -0
  29. package/example/delivery/work/WORK-EXPORT-DATE-FILTER/plan.md +20 -0
  30. package/example/delivery/work/WORK-EXPORT-DATE-FILTER/run-config.yaml +66 -0
  31. package/example/delivery/work/WORK-EXPORT-DATE-FILTER/runs/RUN-EXPORT-01/run-record.json +108 -0
  32. package/example/delivery/work/WORK-EXPORT-DATE-FILTER/workflow.yaml +44 -0
  33. package/example/gitignore.template +20 -0
  34. package/example/package.json +13 -0
  35. package/example/pm/decisions.md +8 -0
  36. package/example/src/export.js +25 -0
  37. package/example/src/ledger.js +16 -0
  38. package/example/tests/export.test.js +35 -0
  39. package/example//346/214/207/346/214/245/345/217/260.md +40 -0
  40. package/package.json +2 -1
  41. package/src/v2/cli/run-config-check.js +223 -0
  42. package/src/v2/cli/run.js +74 -14
  43. package/src/v2/engine/reducer.js +6 -0
  44. package/src/v2/engine/yaml-subset.js +44 -9
  45. package/src/v2/runtime/decisions.js +32 -30
  46. package/src/v2/runtime/gc.js +28 -3
  47. package/src/v2/runtime/orchestrator.js +223 -96
  48. package/src/v2/storage/event-ledger.js +22 -3
  49. package/src/v2/workspace/workspace-manager.js +182 -13
  50. package/templates/v2/run-config.example.yaml +3 -1
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env bash
2
+ # BuildBeat v2 worker 包装:把任意 CLI Agent 接进 Runner 的信封合同。
3
+ #
4
+ # bash delivery/envelope/worker.sh <role> -- <tool command...>
5
+ # role: builder | fixer | planner(写入步) reviewer(只读步)
6
+ #
7
+ # 做的事(每一条都对应 docs/v2/guide/05-worker-contract.md 的一条合同):
8
+ # 1. 工具不在 PATH → exit 75(EX_TEMPFAIL):内核判基础设施故障停人,不派 fixer、不扣预算;
9
+ # 2. 有 $BUILDBEAT_PROMPT 就把 prompt 文本作为最后一个参数追加给工具(codex exec / claude -p 都吃位置参数);
10
+ # 3. 写入步:工具返回后若工作树有改动,机械地 git add -A && git commit(提交动作不交给模型);
11
+ # 4. 只读步:工具的 stdout 落到 $BUILDBEAT_OUTPUT(工具自己写了该文件则不覆盖);不 commit;
12
+ # 5. 工具退出码原样透传;本脚本自己不判断候选好坏——那是 verify 与 review 的事。
13
+ # cwd 已由内核设为该步的隔离 worktree;不要 cd。
14
+ set -uo pipefail
15
+
16
+ role="${1:-}"
17
+ shift || true
18
+ if [ "${1:-}" = "--" ]; then
19
+ shift
20
+ fi
21
+ if [ -z "$role" ] || [ "$#" -eq 0 ]; then
22
+ echo "usage: worker.sh <builder|fixer|planner|reviewer> -- <tool command...>" >&2
23
+ exit 64
24
+ fi
25
+
26
+ tool="$1"
27
+ if ! command -v "$tool" >/dev/null 2>&1; then
28
+ echo "worker.sh: tool not found in PATH: $tool (exit 75 = infrastructure, not a candidate defect)" >&2
29
+ exit 75
30
+ fi
31
+
32
+ input="${BUILDBEAT_INPUT:-{}}"
33
+ run_id="$(printf '%s' "$input" | sed -n 's/.*"runId":"\([^"]*\)".*/\1/p')"
34
+ attempt="$(printf '%s' "$input" | sed -n 's/.*"attempt":\([0-9]*\).*/\1/p')"
35
+
36
+ args=("$@")
37
+ if [ -n "${BUILDBEAT_PROMPT:-}" ] && [ -f "$BUILDBEAT_PROMPT" ]; then
38
+ args+=("$(cat "$BUILDBEAT_PROMPT")")
39
+ fi
40
+
41
+ case "$role" in
42
+ builder|fixer|planner)
43
+ "${args[@]}"
44
+ status=$?
45
+ if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
46
+ git add -A
47
+ git -c user.name="${GIT_AUTHOR_NAME:-buildbeat-worker}" -c user.email="${GIT_AUTHOR_EMAIL:-worker@buildbeat.local}" \
48
+ commit -q -m "${role}: ${run_id:-run} attempt ${attempt:-?}" || status=$?
49
+ fi
50
+ exit "$status"
51
+ ;;
52
+ reviewer)
53
+ if [ -n "${BUILDBEAT_OUTPUT:-}" ]; then
54
+ captured="$(mktemp "${TMPDIR:-/tmp}/bb-review.XXXXXX")"
55
+ "${args[@]}" | tee "$captured"
56
+ status=${PIPESTATUS[0]}
57
+ if [ ! -s "$BUILDBEAT_OUTPUT" ]; then
58
+ cp "$captured" "$BUILDBEAT_OUTPUT"
59
+ fi
60
+ rm -f "$captured"
61
+ exit "$status"
62
+ fi
63
+ "${args[@]}"
64
+ exit $?
65
+ ;;
66
+ *)
67
+ echo "worker.sh: unknown role: $role" >&2
68
+ exit 64
69
+ ;;
70
+ esac
@@ -0,0 +1,3 @@
1
+ {"ts":"2026-09-09T15:09:50.301Z","decisionRef":"A-WORK-EXPORT-DATE-FILTER-1","decision":"approved","transition":"accept-intent","subject":{"artifact":"intent","digest":"sha256:4e0d96de3ff7c5a1fffc8af268ce91784a515ebcc92e6d5ea035d67ef118da92"},"by":"小周"}
2
+ {"ts":"2026-09-09T15:09:50.350Z","decisionRef":"A-WORK-EXPORT-DATE-FILTER-2","decision":"approved","transition":"accept-plan","subject":{"artifact":"plan","digest":"sha256:4f770dd7b20f14c3e64371ec475ea18f0b82e052fba1f3b8649b04d3ac7ec07a"},"by":"小周"}
3
+ {"ts":"2026-09-09T15:09:51.628Z","run":"RUN-EXPORT-01","decisionRef":"D-RUN-EXPORT-01-1","decision":"approved","transition":"enter-wait-merge","subject":{"candidate":"be7e4f9c4d3d5dcd26132f98f660f9e217c47a97","planDigest":"sha256:4f770dd7b20f14c3e64371ec475ea18f0b82e052fba1f3b8649b04d3ac7ec07a","evidenceDigest":"sha256:7c00d8d23d7d1b3f56006f4686a99bd8b3cc0839f105b524cabadd50dfa04f67"},"by":"小周"}
@@ -0,0 +1,24 @@
1
+ # Intent:导出加日期筛选(WORK-EXPORT-DATE-FILTER)
2
+
3
+ ## 为什么做
4
+
5
+ 月底对账只想看当月流水,现在的 CSV 导出是全量的,用户得在表格里再筛一遍。
6
+
7
+ ## 目标
8
+
9
+ `exportCsv(ledger, { from, to })` 支持含端点的日期区间,两端都可以省略;导出格式(列序、金额两位小数)不变。
10
+
11
+ ## 非目标
12
+
13
+ - 不动 `src/ledger.js`(老地盘);
14
+ - 不做界面,没有 UI 交付;
15
+ - 不改 CSV 的列和编码。
16
+
17
+ ## 止损线
18
+
19
+ 最多 2 个 Run、3 轮 review、总计 1 小时 worker 时间;越线先问所有者"继续还是砍"。
20
+
21
+ ## 验收条件
22
+
23
+ - `npm test` 全绿,且新增用例覆盖:无边界、单边界、双边界、区间外为空、坏格式、from>to;
24
+ - review 无 P0/P1。
@@ -0,0 +1,20 @@
1
+ # Plan:导出加日期筛选(WORK-EXPORT-DATE-FILTER)
2
+
3
+ ## 修改范围
4
+
5
+ `src/export.js`、`tests/export.test.js`(allowedPaths:`src`、`tests`)。
6
+
7
+ ## 实现顺序
8
+
9
+ 1. `exportCsv` 增加第二个参数 `options = {}`,读 `from` / `to`;
10
+ 2. 两个边界都按 `YYYY-MM-DD` 校验,坏格式抛 `bad date bound`,`from > to` 抛 `is after`(【EXPORT-1】拍板:不静默);
11
+ 3. 用字符串比较做含端点筛选(ISO 日期字符串可直接比大小);
12
+ 4. `tests/export.test.js` 补:无边界、单边界、双边界、区间外为空、坏格式、from>to。
13
+
14
+ ## 测试方法
15
+
16
+ verify 步跑 `npm test`(`node --test tests/*.test.js`),退出码回读为证据。
17
+
18
+ ## 风险与回滚
19
+
20
+ 只加参数、默认行为不变,老调用方不受影响;候选不合并即无影响,Run 的工作树可整体 `gc`。
@@ -0,0 +1,66 @@
1
+ # 简账 · WORK-EXPORT-DATE-FILTER 的 run 配置。路径相对本文件解析。
2
+ # 起跑前:buildbeat doctor --config <本文件>;起跑:buildbeat start --config <本文件> --attempt new
3
+ repo: ../../..
4
+ work: WORK-EXPORT-DATE-FILTER
5
+ run: RUN-EXPORT
6
+ # 从 $(npm root -g)/@haiyangbg/buildbeat/src/v2/presets/software-delivery.yaml 复制到本目录
7
+ workflow: workflow.yaml
8
+ riskPreset: standard
9
+ entry: build
10
+ allowedPaths:
11
+ - src
12
+ - tests
13
+ reviewTriage: required
14
+ # 对应 intent 的止损线:3 轮 review
15
+ budgets:
16
+ maxAttempts:
17
+ review: 2
18
+ reviewRoundsPerWork: 3
19
+ cache:
20
+ verify: tree
21
+ envelope:
22
+ prompts: ../../envelope/prompts
23
+ vars:
24
+ component: export
25
+ redact:
26
+ - "(token|secret|password|TOKEN|SECRET|PASSWORD)=\\S+"
27
+ requires:
28
+ - command: node
29
+ min: "20"
30
+ # 换工具只改 -- 后面的命令
31
+ workers:
32
+ builder:
33
+ command: bash
34
+ args:
35
+ - delivery/envelope/worker.sh
36
+ - builder
37
+ - --
38
+ - codex
39
+ - exec
40
+ - -s
41
+ - workspace-write
42
+ verifier:
43
+ command: bash
44
+ args:
45
+ - -lc
46
+ - npm test
47
+ reviewer:
48
+ command: bash
49
+ args:
50
+ - delivery/envelope/worker.sh
51
+ - reviewer
52
+ - --
53
+ - codex
54
+ - exec
55
+ - -s
56
+ - read-only
57
+ fixer:
58
+ command: bash
59
+ args:
60
+ - delivery/envelope/worker.sh
61
+ - fixer
62
+ - --
63
+ - codex
64
+ - exec
65
+ - -s
66
+ - workspace-write
@@ -0,0 +1,108 @@
1
+ {
2
+ "run": "RUN-EXPORT-01",
3
+ "work": "WORK-EXPORT-DATE-FILTER",
4
+ "workflow": "software-delivery",
5
+ "terminal": {
6
+ "status": "SUCCEEDED",
7
+ "reason": "final decision approved; the external action (merge) stays manual"
8
+ },
9
+ "events": {
10
+ "from": 1,
11
+ "to": 31,
12
+ "lastDigest": "sha256:fb0aad2e3f356dd8cdd3d12c70421536c9033b6bfdac80808023a49df4509dc8"
13
+ },
14
+ "startedAt": "2026-09-09T15:09:50.530Z",
15
+ "finishedAt": "2026-09-09T15:09:51.628Z",
16
+ "attempts": {
17
+ "build": 1,
18
+ "verify": 1,
19
+ "review": 1
20
+ },
21
+ "budgets": {
22
+ "attempts": {
23
+ "consumed": 3,
24
+ "remaining": 1
25
+ }
26
+ },
27
+ "cost": {
28
+ "reviewRounds": 1,
29
+ "humanWaits": 1,
30
+ "infraFailures": 0,
31
+ "workerMs": 853
32
+ },
33
+ "workspaces": {
34
+ "RUN-EXPORT-01": {
35
+ "repo": ".",
36
+ "branch": "run/RUN-EXPORT-01",
37
+ "worktreePath": ".buildbeat/worktrees/RUN-EXPORT-01",
38
+ "base": "8f4ef5d4a4bd75c7765fbec860c3f5cbf1e0d02f",
39
+ "candidate": "be7e4f9c4d3d5dcd26132f98f660f9e217c47a97"
40
+ }
41
+ },
42
+ "evidence": [
43
+ {
44
+ "ref": ".buildbeat/runtime/runs/RUN-EXPORT-01/logs/build-1.log",
45
+ "kind": "command",
46
+ "subject": "be7e4f9c4d3d5dcd26132f98f660f9e217c47a97",
47
+ "digest": "sha256:1192f1c2a9a09f23c69d2c3935d2f7a0b3945f2b8aab979f811dd508183755c2",
48
+ "status": "passed",
49
+ "grade": "L2"
50
+ },
51
+ {
52
+ "ref": ".buildbeat/runtime/runs/RUN-EXPORT-01/logs/verify-1.log",
53
+ "kind": "command",
54
+ "subject": "be7e4f9c4d3d5dcd26132f98f660f9e217c47a97",
55
+ "digest": "sha256:603a7dd74be408af1575dca2c5a79bc5b211b9de3366812fb70603aa6121fc99",
56
+ "status": "passed",
57
+ "grade": "L2",
58
+ "cacheKey": "sha256:4425cad04706cc2c7f45e5306c573bf756d39d7aa0815d46406810331592e0ea"
59
+ },
60
+ {
61
+ "ref": ".buildbeat/runtime/runs/RUN-EXPORT-01/logs/review-1.log",
62
+ "kind": "command",
63
+ "subject": "be7e4f9c4d3d5dcd26132f98f660f9e217c47a97",
64
+ "digest": "sha256:65b9474c69d677a43341ee0af9f6865ca66a6c620d0ca71e7c1d61e6b806913e",
65
+ "status": "passed",
66
+ "grade": "L2"
67
+ },
68
+ {
69
+ "ref": ".buildbeat/runtime/runs/RUN-EXPORT-01/outputs/review-1.json",
70
+ "kind": "review",
71
+ "subject": "be7e4f9c4d3d5dcd26132f98f660f9e217c47a97",
72
+ "digest": "sha256:7c00d8d23d7d1b3f56006f4686a99bd8b3cc0839f105b524cabadd50dfa04f67",
73
+ "status": "passed",
74
+ "grade": "L2",
75
+ "findings": [
76
+ {
77
+ "severity": "P2",
78
+ "summary": "日期区间用字符串比较依赖 YYYY-MM-DD 格式,已有校验兜底,但可以在 export.js 顶部注释里写明这个前提"
79
+ }
80
+ ]
81
+ }
82
+ ],
83
+ "decisions": [
84
+ {
85
+ "decision": "approved",
86
+ "transition": "enter-wait-merge",
87
+ "subject": {
88
+ "candidate": "be7e4f9c4d3d5dcd26132f98f660f9e217c47a97",
89
+ "planDigest": "sha256:4f770dd7b20f14c3e64371ec475ea18f0b82e052fba1f3b8649b04d3ac7ec07a",
90
+ "evidenceDigest": "sha256:7c00d8d23d7d1b3f56006f4686a99bd8b3cc0839f105b524cabadd50dfa04f67"
91
+ },
92
+ "decisionRef": "D-RUN-EXPORT-01-1"
93
+ }
94
+ ],
95
+ "approvals": [
96
+ {
97
+ "decisionRef": "D-RUN-EXPORT-01-1",
98
+ "transition": "enter-wait-merge",
99
+ "subject": {
100
+ "candidate": "be7e4f9c4d3d5dcd26132f98f660f9e217c47a97",
101
+ "planDigest": "sha256:4f770dd7b20f14c3e64371ec475ea18f0b82e052fba1f3b8649b04d3ac7ec07a",
102
+ "evidenceDigest": "sha256:7c00d8d23d7d1b3f56006f4686a99bd8b3cc0839f105b524cabadd50dfa04f67"
103
+ },
104
+ "stale": false
105
+ }
106
+ ],
107
+ "unverified": []
108
+ }
@@ -0,0 +1,44 @@
1
+ # Official software-delivery preset per docs/v2/RFC-0003-workflow-policy.md §2.
2
+ # Step order defines the happy path (each step chains to the next on
3
+ # "succeeded"); fix sits off-path at the end and is reached only via the
4
+ # explicit failure/findings transitions.
5
+ kind: workflow
6
+ version: 1
7
+ name: software-delivery
8
+ entry: intent
9
+ steps:
10
+ - id: intent
11
+ worker: planner
12
+ - id: spec
13
+ worker: planner
14
+ optional: true
15
+ requiredWhen: ui-delivery
16
+ - id: plan
17
+ worker: planner
18
+ - id: build
19
+ worker: builder
20
+ - id: verify
21
+ worker: verifier
22
+ - id: review
23
+ worker: reviewer
24
+ readonly: true
25
+ - id: wait-merge
26
+ - id: fix
27
+ worker: fixer
28
+ transitions:
29
+ - from: verify
30
+ on: failed
31
+ to: fix
32
+ - from: fix
33
+ on: succeeded
34
+ to: verify
35
+ - from: review
36
+ on: findings-blocking
37
+ to: fix
38
+ terminal:
39
+ - wait-merge
40
+ # Review rounds are capped by default (deploy-campaign charter: two review
41
+ # rounds per run, then a human). Override per project via budgets.maxAttempts.
42
+ budgets:
43
+ maxAttempts:
44
+ review: 2
@@ -0,0 +1,20 @@
1
+ # 拷入新项目根后改名为 .gitignore(模板文件名不带点,避开 cp 通配符丢点文件的坑)
2
+
3
+ # macOS
4
+ .DS_Store
5
+
6
+ # 凭据 / 本地环境(红线①:凭据不入 git)
7
+ *.env
8
+ .env
9
+ .env.*
10
+ *.local
11
+ *.key
12
+ *.pem
13
+
14
+ # BuildBeat v2 运行时面与隔离工作树(可随时整删重建;不入 git)
15
+ # 同时让 rg / 尊重 .gitignore 的工具不再走进旧工作树;vitest / jest 等要另配 exclude,见 docs/v2/guide/02-workflow-guide.md
16
+ .buildbeat/runtime/
17
+ .buildbeat/worktrees/
18
+
19
+ # 单仓项目:没有子仓
20
+ node_modules/
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "jianzhang",
3
+ "version": "0.3.0",
4
+ "private": true,
5
+ "description": "简账:一个虚构的个人记账小工具(BuildBeat 示例项目)",
6
+ "type": "module",
7
+ "scripts": {
8
+ "test": "node --test tests/*.test.js"
9
+ },
10
+ "engines": {
11
+ "node": ">=20"
12
+ }
13
+ }
@@ -0,0 +1,8 @@
1
+ # decisions — 拍板台账(全工作区唯一决策单点)
2
+
3
+ > **规则(AGENTS ⑨ 单点事实)**:一个真实决策包收敛后,**第一动作 = 在此追加一行**(倒序),然后才去回写受影响的 SSOT(intent/plan/设计稿);「回写」列登记落点,没回写完 = 欠账可见。Run 级批准不进这里,它们由内核落在各 Work 的 `decisions.jsonl`。
4
+
5
+ | 日期 | 拍板人 | 决策包 | 回写(落点 → 状态) |
6
+ |---|---|---|---|
7
+ | 2026-09-09 | 小周 | 【EXPORT-1】导出日期筛选:from/to 含端点;两端都可省;格式只认 `YYYY-MM-DD`,坏格式和 from>to 直接报错不静默;金额列保持两位小数、不加币种 | `delivery/work/WORK-EXPORT-DATE-FILTER/plan.md` → ✅ |
8
+ | 2026-09-09 | 小周 | 【BOOTSTRAP】接入 BuildBeat:单仓、长期做;一个 Builder 端到端;`src/ledger.js` 为老地盘只维护;无 UI;worker 用 `codex exec`;通知先不接(等待只在 inbox 里);`standards/` 不生成 | `AGENTS.md` §1 边界、`delivery/envelope/prompts/*`、`run-config.yaml` → ✅ |
@@ -0,0 +1,25 @@
1
+ // CSV 导出(新地盘)。WORK-EXPORT-DATE-FILTER 给它加了 from / to 日期筛选。
2
+
3
+ const HEADER = "date,category,amount,note";
4
+
5
+ function escapeCell(value) {
6
+ const text = String(value ?? "");
7
+ return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
8
+ }
9
+
10
+ // options.from / options.to 都是含端点的 "YYYY-MM-DD";省略一端表示不限。
11
+ export function exportCsv(ledger, options = {}) {
12
+ const { from, to } = options;
13
+ for (const bound of [from, to]) {
14
+ if (bound !== undefined && !/^\d{4}-\d{2}-\d{2}$/.test(bound)) {
15
+ throw new Error(`bad date bound: ${bound}`);
16
+ }
17
+ }
18
+ if (from && to && from > to) {
19
+ throw new Error(`from ${from} is after to ${to}`);
20
+ }
21
+ const rows = ledger
22
+ .filter((entry) => (from === undefined || entry.date >= from) && (to === undefined || entry.date <= to))
23
+ .map((entry) => [entry.date, entry.category, (entry.amount / 100).toFixed(2), entry.note].map(escapeCell).join(","));
24
+ return [HEADER, ...rows].join("\n") + "\n";
25
+ }
@@ -0,0 +1,16 @@
1
+ // 记账核心(老地盘:只维护,改动一律先问人)。
2
+ // 一条流水 = { date: "YYYY-MM-DD", category, amount, note };金额以分为单位的整数。
3
+
4
+ export function addEntry(ledger, entry) {
5
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(entry.date)) {
6
+ throw new Error(`bad date: ${entry.date}`);
7
+ }
8
+ if (!Number.isInteger(entry.amount)) {
9
+ throw new Error("amount must be an integer number of cents");
10
+ }
11
+ return [...ledger, { note: "", ...entry }];
12
+ }
13
+
14
+ export function total(ledger) {
15
+ return ledger.reduce((sum, entry) => sum + entry.amount, 0);
16
+ }
@@ -0,0 +1,35 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { exportCsv } from "../src/export.js";
5
+ import { addEntry } from "../src/ledger.js";
6
+
7
+ const ledger = [
8
+ { date: "2026-08-30", category: "餐饮", amount: 3550, note: "" },
9
+ { date: "2026-09-01", category: "交通", amount: 1200, note: "地铁, 两程" },
10
+ { date: "2026-09-15", category: "房租", amount: 250000, note: "" },
11
+ ];
12
+
13
+ test("exports every row with a header when no bounds are given", () => {
14
+ const csv = exportCsv(ledger);
15
+ const lines = csv.trimEnd().split("\n");
16
+ assert.equal(lines[0], "date,category,amount,note");
17
+ assert.equal(lines.length, 4);
18
+ assert.equal(lines[2], '2026-09-01,交通,12.00,"地铁, 两程"');
19
+ });
20
+
21
+ test("from / to bounds are inclusive and each side is optional", () => {
22
+ assert.equal(exportCsv(ledger, { from: "2026-09-01" }).trimEnd().split("\n").length, 3);
23
+ assert.equal(exportCsv(ledger, { to: "2026-09-01" }).trimEnd().split("\n").length, 3);
24
+ assert.equal(exportCsv(ledger, { from: "2026-09-01", to: "2026-09-01" }).trimEnd().split("\n").length, 2);
25
+ assert.equal(exportCsv(ledger, { from: "2026-10-01" }), "date,category,amount,note\n");
26
+ });
27
+
28
+ test("malformed or inverted bounds are rejected, not silently ignored", () => {
29
+ assert.throws(() => exportCsv(ledger, { from: "2026/09/01" }), /bad date bound/);
30
+ assert.throws(() => exportCsv(ledger, { from: "2026-09-02", to: "2026-09-01" }), /is after/);
31
+ });
32
+
33
+ test("ledger entries still validate their own date", () => {
34
+ assert.throws(() => addEntry([], { date: "bad", category: "x", amount: 1 }), /bad date/);
35
+ });
@@ -0,0 +1,40 @@
1
+ # 指挥台 — 简账:Builder 怎么驱动工作(忘了怎么开场就看这页)
2
+
3
+ > 本仓运行 BuildBeat。会话按所用工具的方式装载 `AGENTS.md`(多数工具自动读根目录 `AGENTS.md` / `CLAUDE.md`;不自动读的,开场把它贴给会话);活动工作在 `delivery/`。你基本只需说下面这几句话,命令由会话调、不用你手敲。
4
+
5
+ ## 日常六句话
6
+
7
+ | 你想干什么 | 说什么 | 会话背后调什么 |
8
+ |---|---|---|
9
+ | 看现在到哪了、该谁动 | 「当前进度」/「待办是什么」 | `buildbeat overview --repo .`(每个 Work 的阶段 + 下一步)+ `observe status` |
10
+ | 看有什么等我批 | 「有什么要我拍板」 | `buildbeat inbox --repo .`(每条附可复制的下一句) |
11
+ | 立一件新事 | 「开个 Work:〔一句话目标〕」→ 看完说「接受」 | 建 `delivery/work/<ID>/intent.md + plan.md` → `accept`(digest 绑定) |
12
+ | 让它干活 | 「开工」/「再来一轮」 | 先 `buildbeat doctor --config …`(配置、intent/plan 接受状态、env 姿态、预算),再 `start --config … --attempt new`(自动编号、自动作废旧等待,停在合并决定) |
13
+ | 它是不是卡了 | 「怎么样了」/「卡住了吗」 | `buildbeat status --repo . --run <RUN>`(耗时、历史中位数、最后输出、STALLED) |
14
+ | 拍板 | 「批准 / 拒绝〔RUN-ID〕」;分诊时「这条接受,那条不算」 | `approve` / `reject` / `findings adjudicate`;会话说清批的是哪一步(放行 fixer / 再跑一次 / 合并决定),非终态批准后 `resume` 续跑;合并决定只表示候选够格合并,合并/push/部署逐项另说 |
15
+
16
+ 其他:生产报警看 `delivery/observe/intents/` 草稿 → 「fix_now / schedule / dismiss」;上线用 `release-readback` 预设开一个 Run,「我做完了」就是批准 `enter-apply-readback`;「打扫卫生」= `gc --repo .`(先看计划再 `--apply true`)。
17
+
18
+ ## 新开一个 Work 时会话要动的文件
19
+
20
+ `delivery/work/<WORK-ID>/`:`intent.md`(为什么 + 止损线)、`plan.md`(怎么做)、`run-config.yaml`(从 `templates/v2/run-config.example.yaml` 改 work / run / allowedPaths / workers)、`workflow.yaml`(从 `$(npm root -g)/@haiyangbg/buildbeat/src/v2/presets/software-delivery.yaml` 复制)。信封与 worker 包装在仓级 `delivery/envelope/`,不用每个 Work 重写。
21
+
22
+ ## 视角开场白
23
+
24
+ | AI 视角 | 在哪开(cwd) | 你的一句话 |
25
+ |---|---|---|
26
+ | **产品** | 工作区根 | 「把当前目标收成一个 Work」/「验收」——定 intent/plan、维护决策收件箱、按决策包记 `pm/decisions.md` |
27
+ | **全栈** | `<代码仓>/` | 「〔WORK-ID〕该你了,开工」——授权范围内实现,Run 停在合并决定回人批 |
28
+ | **测试** | 工作区根 | 「验〔RUN-ID〕候选」——对精确 candidate 独立核验,报告落所属 Work 目录 |
29
+
30
+ ## 域回复怎么写
31
+
32
+ 面向你收口统一「已做 → 未做 → 下一步」,各一句;证据紧跟事项。下一步只写一条:下一棒是谁、需要你确认什么、或「我继续做,暂不交棒」。
33
+
34
+ ## 按项目阶段的完整问法
35
+
36
+ 未开始 → 立项定方案 → 准备执行 → 执行推进 → 验收合并 → 上线 → 完结换期复盘,每阶段说什么、会话做什么、你得到什么:见 BuildBeat 指南第 0 篇 `docs/v2/guide/00-how-to-talk.md`(Skill 装载目录下同路径)。
37
+
38
+ ## 红线、写边界、审批三级、可见命名进决策卡
39
+
40
+ 全在 `AGENTS.md`,本页不复制。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haiyangbg/buildbeat",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "BuildBeat: Git-based AI delivery across models, tools, sessions, and people, with context in project files, build-test-review-fix loops, and human decisions.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,6 +22,7 @@
22
22
  "!docs/PHASE*.md",
23
23
  "!docs/CLI-STRATEGY-2026-08.md",
24
24
  "!docs/CLI-PILOT-2026-08-23.md",
25
+ "example/",
25
26
  "src/",
26
27
  "templates/",
27
28
  "CHANGELOG.md",