@haiyangbg/buildbeat 2.0.0-beta.2 → 2.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -6
- package/SKILL.md +76 -2
- package/bin/buildbeat-v2.js +13 -1
- package/docs/CLI-PILOT-2026-08-23.md +1 -1
- package/docs/CLI.md +1 -1
- package/docs/EXECUTION-PLAN.md +2 -2
- package/docs/PHASE2-PILOT-PREFLIGHT-2026-08-25.md +2 -2
- package/docs/PHASE4-V1.20-PILOT-2026-08-25.md +2 -2
- package/docs/RELEASING.md +1 -1
- package/docs/V2-D2-DECISION-CARD.md +2 -2
- package/docs/V2-DECISIONS.md +2 -2
- package/docs/V2-ITERATION-01.md +13 -13
- package/docs/V2-ITERATION-06.md +2 -2
- package/docs/V2-ITERATION-08.md +62 -0
- package/docs/V2-PLAN.md +6 -6
- package/docs/V2-PROPOSAL.md +2 -2
- package/docs/V2.0.0-BETA.1-RELEASE-EVIDENCE-2026-08-28.md +1 -1
- package/docs/V2.0.0-BETA.2-RELEASE-EVIDENCE-2026-08-28.md +8 -0
- package/docs/V2.0.0-BETA.3-RELEASE-EVIDENCE-2026-09-01.md +8 -0
- package/docs/v2/M4-EXTERNAL-PILOT-2026-08-28.md +11 -11
- package/docs/v2/{M4-CHICKAI-PILOT-2026-08-28.md → M4-PILOT-APP-2026-08-28.md} +4 -4
- package/docs/v2/M4-SELFHOST-2026-08-28.md +1 -1
- package/docs/v2/RFC-0001-product-definition.md +2 -2
- package/docs/v2/SPEC-0001-events-v1.md +2 -2
- package/docs/v2/guide/00-how-to-talk.md +57 -0
- package/docs/v2/guide/01-quickstart.md +6 -0
- package/docs/v2/guide/02-workflow-guide.md +31 -0
- package/docs/v2/guide/04-adapter-guide.md +4 -0
- package/docs/v2/guide/05-worker-contract.md +10 -0
- package/docs/v2/guide/06-evidence-guide.md +10 -0
- package/docs/v2/guide/07-approval-guide.md +50 -0
- package/docs/v2/guide/10-recovery.md +25 -2
- package/docs/v2/guide/README.md +3 -0
- package/example/.buildbeat/manifest.json +1 -1
- package/lessons.md +12 -0
- package/package.json +1 -1
- package/src/v2/adapters/shell.js +87 -14
- package/src/v2/cli/run.js +602 -30
- package/src/v2/domain/event-registry.js +24 -0
- package/src/v2/engine/reducer.js +5 -0
- package/src/v2/engine/workflow.js +8 -1
- package/src/v2/evidence/collector.js +14 -3
- package/src/v2/observe/observe.js +9 -2
- package/src/v2/presets/release-readback.yaml +36 -0
- package/src/v2/presets/risk/release.yaml +21 -0
- package/src/v2/presets/software-delivery.yaml +5 -0
- package/src/v2/runtime/cache.js +124 -0
- package/src/v2/runtime/decisions.js +6 -4
- package/src/v2/runtime/env-contract.js +135 -0
- package/src/v2/runtime/envelope.js +183 -0
- package/src/v2/runtime/findings.js +158 -0
- package/src/v2/runtime/gc.js +182 -0
- package/src/v2/runtime/liveness.js +193 -0
- package/src/v2/runtime/metrics.js +8 -0
- package/src/v2/runtime/notify.js +223 -0
- package/src/v2/runtime/orchestrator.js +252 -24
- package/src/v2/runtime/overview.js +264 -0
- package/src/v2/runtime/repo-ref.js +38 -0
- package/src/v2/runtime/run-record.js +18 -3
- package/src/v2/workspace/workspace-manager.js +4 -1
- package/templates/v2/AGENTS.md +72 -0
- package/templates/v2//346/214/207/346/214/245/345/217/260.md +36 -0
package/src/v2/adapters/shell.js
CHANGED
|
@@ -2,8 +2,19 @@
|
|
|
2
2
|
// step's workspace. This is the vendor-neutral path to any CLI agent
|
|
3
3
|
// (claude -p, codex exec, plain scripts). Adapters never touch kernel state;
|
|
4
4
|
// they only return what actually happened — the orchestrator writes events.
|
|
5
|
+
//
|
|
6
|
+
// Live output (iteration 08): when the orchestrator hands over a liveDir the
|
|
7
|
+
// child's stdout/stderr stream straight into files there while it runs, and a
|
|
8
|
+
// live.json marker names the command and its start time. `status` reads
|
|
9
|
+
// those to answer "is it still doing something?" — the question the owner
|
|
10
|
+
// asked a dozen times during the deploy campaign while a buffered spawnSync
|
|
11
|
+
// showed nothing until the step ended. The marker and the streams are
|
|
12
|
+
// removed once the step returns; the evidence log is still written by the
|
|
13
|
+
// collector from what was read back.
|
|
5
14
|
|
|
6
15
|
import { spawnSync } from "node:child_process";
|
|
16
|
+
import { closeSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
7
18
|
|
|
8
19
|
export class AdapterConfigError extends Error {
|
|
9
20
|
constructor(message) {
|
|
@@ -13,10 +24,15 @@ export class AdapterConfigError extends Error {
|
|
|
13
24
|
}
|
|
14
25
|
|
|
15
26
|
function fillTemplate(text, context) {
|
|
16
|
-
|
|
27
|
+
let out = text
|
|
17
28
|
.replaceAll("{workspace}", context.workspacePath)
|
|
18
29
|
.replaceAll("{step}", context.step)
|
|
19
|
-
.replaceAll("{worker}", context.worker)
|
|
30
|
+
.replaceAll("{worker}", context.worker)
|
|
31
|
+
.replaceAll("{prompt}", context.promptPath ?? "");
|
|
32
|
+
for (const [key, value] of Object.entries(context.vars ?? {})) {
|
|
33
|
+
out = out.replaceAll(`{vars.${key}}`, String(value));
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
20
36
|
}
|
|
21
37
|
|
|
22
38
|
// Workers get a minimal environment by default: host credentials living in
|
|
@@ -24,6 +40,24 @@ function fillTemplate(text, context) {
|
|
|
24
40
|
// in with inheritEnv (which doctor reports as ADVISORY-only isolation).
|
|
25
41
|
const DEFAULT_ENV_KEYS = ["PATH", "HOME", "LANG", "LC_ALL", "TMPDIR", "TERM", "USER", "SHELL"];
|
|
26
42
|
|
|
43
|
+
export const LIVE_MARKER = "live.json";
|
|
44
|
+
|
|
45
|
+
export function liveStreamPaths(liveDir, step, attempt) {
|
|
46
|
+
return {
|
|
47
|
+
marker: join(liveDir, LIVE_MARKER),
|
|
48
|
+
stdout: join(liveDir, `${step}-${attempt}.stdout.live`),
|
|
49
|
+
stderr: join(liveDir, `${step}-${attempt}.stderr.live`),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function readLive(path) {
|
|
54
|
+
try {
|
|
55
|
+
return readFileSync(path, "utf8");
|
|
56
|
+
} catch {
|
|
57
|
+
return "";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
27
61
|
export function createShellAdapter(config) {
|
|
28
62
|
if (!config || typeof config.command !== "string" || config.command.length === 0) {
|
|
29
63
|
throw new AdapterConfigError("shell adapter requires a command");
|
|
@@ -33,9 +67,10 @@ export function createShellAdapter(config) {
|
|
|
33
67
|
return {
|
|
34
68
|
name,
|
|
35
69
|
envMode,
|
|
36
|
-
execute({ step, worker, workspacePath, input, timeoutMs, outputPath }) {
|
|
37
|
-
const context = { step, worker, workspacePath };
|
|
70
|
+
execute({ step, worker, workspacePath, input, timeoutMs, outputPath, liveDir, promptPath, vars }) {
|
|
71
|
+
const context = { step, worker, workspacePath, promptPath, vars };
|
|
38
72
|
const args = (config.args ?? []).map((arg) => fillTemplate(String(arg), context));
|
|
73
|
+
const command = [config.command, ...args].join(" ");
|
|
39
74
|
const startedAt = new Date().toISOString();
|
|
40
75
|
let env;
|
|
41
76
|
if (config.inheritEnv === true) {
|
|
@@ -53,21 +88,59 @@ export function createShellAdapter(config) {
|
|
|
53
88
|
if (outputPath) {
|
|
54
89
|
env.BUILDBEAT_OUTPUT = outputPath;
|
|
55
90
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
91
|
+
if (promptPath) {
|
|
92
|
+
env.BUILDBEAT_PROMPT = promptPath;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let live = null;
|
|
96
|
+
let stdio = "pipe";
|
|
97
|
+
if (liveDir) {
|
|
98
|
+
mkdirSync(liveDir, { recursive: true });
|
|
99
|
+
const paths = liveStreamPaths(liveDir, step, input?.attempt ?? 0);
|
|
100
|
+
const outFd = openSync(paths.stdout, "w");
|
|
101
|
+
const errFd = openSync(paths.stderr, "w");
|
|
102
|
+
writeFileSync(
|
|
103
|
+
paths.marker,
|
|
104
|
+
`${JSON.stringify({ step, attempt: input?.attempt ?? null, worker, command, startedAt, stdout: paths.stdout, stderr: paths.stderr })}\n`,
|
|
105
|
+
"utf8",
|
|
106
|
+
);
|
|
107
|
+
live = { paths, outFd, errFd };
|
|
108
|
+
stdio = ["ignore", outFd, errFd];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let result;
|
|
112
|
+
try {
|
|
113
|
+
result = spawnSync(config.command, args, {
|
|
114
|
+
cwd: workspacePath,
|
|
115
|
+
encoding: "utf8",
|
|
116
|
+
timeout: timeoutMs ?? config.timeoutMs,
|
|
117
|
+
env,
|
|
118
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
119
|
+
stdio,
|
|
120
|
+
});
|
|
121
|
+
} finally {
|
|
122
|
+
if (live) {
|
|
123
|
+
closeSync(live.outFd);
|
|
124
|
+
closeSync(live.errFd);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
63
127
|
const finishedAt = new Date().toISOString();
|
|
128
|
+
let stdout = result.stdout ?? "";
|
|
129
|
+
let stderr = result.stderr ?? "";
|
|
130
|
+
if (live) {
|
|
131
|
+
stdout = readLive(live.paths.stdout);
|
|
132
|
+
stderr = readLive(live.paths.stderr);
|
|
133
|
+
rmSync(live.paths.marker, { force: true });
|
|
134
|
+
rmSync(live.paths.stdout, { force: true });
|
|
135
|
+
rmSync(live.paths.stderr, { force: true });
|
|
136
|
+
}
|
|
64
137
|
return {
|
|
65
138
|
adapter: name,
|
|
66
|
-
command
|
|
139
|
+
command,
|
|
67
140
|
exitCode: result.status,
|
|
68
141
|
signal: result.signal ?? null,
|
|
69
|
-
stdout
|
|
70
|
-
stderr
|
|
142
|
+
stdout,
|
|
143
|
+
stderr,
|
|
71
144
|
timedOut: result.error?.code === "ETIMEDOUT",
|
|
72
145
|
spawnError: result.error && result.error.code !== "ETIMEDOUT" ? result.error.message : null,
|
|
73
146
|
startedAt,
|