@haiyangbg/buildbeat 3.3.1 → 4.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.
- package/CHANGELOG.md +26 -0
- package/README.en.md +20 -124
- package/README.md +20 -124
- package/SKILL.md +73 -84
- package/docs/CAPABILITY-MATRIX.md +30 -39
- package/docs/MIGRATION.md +93 -0
- package/docs/README.md +8 -37
- package/docs/RELEASING.md +4 -3
- package/docs/v2/RFC-0001-product-definition.md +3 -0
- package/docs/v2/RFC-0002-domain-model.md +3 -0
- package/docs/v2/RFC-0003-workflow-policy.md +3 -0
- package/docs/v2/SPEC-0001-events-v1.md +3 -0
- package/docs/v2/guide/00-how-to-talk.en.md +12 -56
- package/docs/v2/guide/00-how-to-talk.md +12 -56
- package/docs/v2/guide/01-quickstart.en.md +42 -111
- package/docs/v2/guide/01-quickstart.md +42 -111
- package/docs/v2/guide/02-workflow-guide.en.md +13 -123
- package/docs/v2/guide/02-workflow-guide.md +14 -122
- package/docs/v2/guide/03-policy-guide.en.md +6 -50
- package/docs/v2/guide/03-policy-guide.md +6 -50
- package/docs/v2/guide/04-adapter-guide.en.md +7 -60
- package/docs/v2/guide/04-adapter-guide.md +7 -60
- package/docs/v2/guide/05-worker-contract.en.md +6 -53
- package/docs/v2/guide/05-worker-contract.md +7 -52
- package/docs/v2/guide/06-evidence-guide.en.md +8 -44
- package/docs/v2/guide/06-evidence-guide.md +9 -43
- package/docs/v2/guide/07-approval-guide.en.md +8 -110
- package/docs/v2/guide/07-approval-guide.md +8 -121
- package/docs/v2/guide/09-security-boundaries.en.md +13 -37
- package/docs/v2/guide/09-security-boundaries.md +13 -37
- package/docs/v2/guide/10-recovery.en.md +12 -96
- package/docs/v2/guide/10-recovery.md +12 -96
- package/docs/v2/guide/11-session-handoff.en.md +10 -75
- package/docs/v2/guide/11-session-handoff.md +10 -75
- package/docs/v2/guide/12-without-runtime.en.md +46 -0
- package/docs/v2/guide/12-without-runtime.md +46 -0
- package/docs/v2/guide/README.en.md +16 -36
- package/docs/v2/guide/README.md +16 -36
- package/example/AGENTS.md +15 -69
- package/example/README.md +8 -23
- package/example/delivery/envelope/prompts/builder.md +3 -1
- package/example/delivery/envelope/prompts/fixer.md +3 -1
- package/example/delivery/envelope/prompts/reviewer.md +3 -1
- package/example/delivery/work/WORK-EXPORT-DATE-FILTER/run-config.yaml +1 -1
- package/example//346/214/207/346/214/245/345/217/260.md +8 -40
- package/package.json +2 -1
- package/src/v2/adapters/notification-payload.js +40 -0
- package/src/v2/adapters/shell.js +3 -1
- package/src/v2/cli/run-config-check.js +129 -45
- package/src/v2/cli/run.js +853 -254
- package/src/v2/engine/reducer.js +2 -0
- package/src/v2/engine/risk-preset.js +14 -47
- package/src/v2/engine/workflow.js +177 -185
- package/src/v2/evidence/collector.js +12 -1
- package/src/v2/evidence/image.js +346 -0
- package/src/v2/policy/policy.js +219 -286
- package/src/v2/presets/software-delivery.yaml +1 -16
- package/src/v2/runtime/cache.js +22 -1
- package/src/v2/runtime/decisions.js +185 -31
- package/src/v2/runtime/findings.js +4 -1
- package/src/v2/runtime/notify.js +42 -36
- package/src/v2/runtime/orchestrator.js +811 -297
- package/src/v2/runtime/overview-repos.js +79 -0
- package/src/v2/runtime/overview.js +279 -47
- package/src/v2/runtime/release.js +233 -0
- package/src/v2/runtime/run-record.js +1 -0
- package/src/v2/workspace/workspace-manager.js +8 -0
- package/templates/v2/AGENTS.md +20 -73
- package/templates/v2/BUILDBEAT.md +2 -13
- package/templates/v2/envelope/prompts/builder.md +3 -1
- package/templates/v2/envelope/prompts/fixer.md +3 -1
- package/templates/v2/envelope/prompts/reviewer.md +3 -1
- package/templates/v2/run-config.example.yaml +9 -7
- package/templates/v2/work.example.md +16 -0
- package/templates/v2//346/214/207/346/214/245/345/217/260.md +8 -40
- package/docs/v2/skill/01-principles.md +0 -26
- package/docs/v2/skill/02-project-layout.md +0 -31
- package/docs/v2/skill/03-collaboration-rules.md +0 -45
- package/docs/v2/skill/04-rhythm-and-rituals.md +0 -104
- package/docs/v2/skill/05-red-lines.md +0 -13
- package/docs/v2/skill/06-bootstrap-and-takeover.md +0 -84
- package/docs/v2/skill/07-templates-and-lessons.md +0 -24
- package/example/.buildbeat/observe.yaml +0 -31
- package/src/v2/observe/observe-config.js +0 -194
- package/src/v2/observe/observe-reducer.js +0 -117
- package/src/v2/observe/observe.js +0 -427
- package/src/v2/presets/observe.yaml +0 -45
- package/src/v2/presets/policies/ui-render-gate.yaml +0 -13
- package/src/v2/presets/release-readback.yaml +0 -36
- package/src/v2/presets/risk/controlled.yaml +0 -39
- package/src/v2/presets/risk/fast.yaml +0 -19
- package/src/v2/presets/risk/release.yaml +0 -21
- package/src/v2/presets/risk/standard.yaml +0 -28
- package/templates/ARCHITECTURE.md +0 -50
- package/templates/contracts/PROTOCOL.md +0 -28
- package/templates/pm/adr/ADR-0000-template.md +0 -25
- package/templates/pm/adr/README.md +0 -15
- package/templates/pm/decisions.md +0 -11
- package/templates/standards/CODE.md +0 -23
- package/templates/standards/DESIGN.md +0 -36
- package/templates/standards/REVIEW.md +0 -20
- package/templates/standards/STACK.md +0 -31
|
@@ -28,9 +28,19 @@ const TOP_KEYS = [
|
|
|
28
28
|
"supersede",
|
|
29
29
|
"stallAfterMs",
|
|
30
30
|
"parallel",
|
|
31
|
+
"maxReviewSeverity",
|
|
32
|
+
"requireScreenshot",
|
|
33
|
+
"release",
|
|
34
|
+
];
|
|
35
|
+
const REQUIRED = ["repo", "work", "run"];
|
|
36
|
+
const NULL_REPORTED = [
|
|
37
|
+
"workflow",
|
|
38
|
+
"base",
|
|
39
|
+
"entry",
|
|
40
|
+
"riskPreset",
|
|
41
|
+
"stepTimeoutMs",
|
|
42
|
+
"maxAttemptsPerStep",
|
|
31
43
|
];
|
|
32
|
-
const REQUIRED = ["repo", "work", "run", "workflow"];
|
|
33
|
-
const NULL_REPORTED = ["base", "entry", "riskPreset", "stepTimeoutMs", "maxAttemptsPerStep"];
|
|
34
44
|
const WORKER_KEYS = ["command", "args", "timeoutMs", "inheritEnv", "env"];
|
|
35
45
|
const ENVELOPE_KEYS = ["prompts", "vars", "pin"];
|
|
36
46
|
const LISTS = ["stopAt", "allowedPaths", "policies", "redact", "requires"];
|
|
@@ -38,7 +48,9 @@ const ID = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
|
38
48
|
|
|
39
49
|
export class RunConfigError extends Error {
|
|
40
50
|
constructor(label, problems) {
|
|
41
|
-
super(
|
|
51
|
+
super(
|
|
52
|
+
`run config ${label} has ${problems.length} problem(s):\n${problems.map((problem) => ` - ${problem}`).join("\n")}`,
|
|
53
|
+
);
|
|
42
54
|
this.name = "RunConfigError";
|
|
43
55
|
this.problems = problems;
|
|
44
56
|
}
|
|
@@ -55,7 +67,11 @@ function distance(a, b) {
|
|
|
55
67
|
row[0] = i;
|
|
56
68
|
for (let j = 1; j <= b.length; j += 1) {
|
|
57
69
|
const kept = row[j];
|
|
58
|
-
row[j] = Math.min(
|
|
70
|
+
row[j] = Math.min(
|
|
71
|
+
row[j] + 1,
|
|
72
|
+
row[j - 1] + 1,
|
|
73
|
+
previous + (a[i - 1] === b[j - 1] ? 0 : 1),
|
|
74
|
+
);
|
|
59
75
|
previous = kept;
|
|
60
76
|
}
|
|
61
77
|
}
|
|
@@ -99,7 +115,9 @@ function nonEmptyString(value) {
|
|
|
99
115
|
// resolution.
|
|
100
116
|
export function checkRunConfigShape(config) {
|
|
101
117
|
if (!isMap(config)) {
|
|
102
|
-
return [
|
|
118
|
+
return [
|
|
119
|
+
"the file must be a map of keys (repo, work, run, workflow, workers, ...)",
|
|
120
|
+
];
|
|
103
121
|
}
|
|
104
122
|
const problems = [];
|
|
105
123
|
for (const key of Object.keys(config)) {
|
|
@@ -117,13 +135,17 @@ export function checkRunConfigShape(config) {
|
|
|
117
135
|
// (cache: null has always meant no cache; lists must be lists).
|
|
118
136
|
for (const key of NULL_REPORTED) {
|
|
119
137
|
if (Object.hasOwn(config, key) && config[key] === null) {
|
|
120
|
-
problems.push(
|
|
138
|
+
problems.push(
|
|
139
|
+
`${key}: has no value; remove the line to use the default, or give it a value`,
|
|
140
|
+
);
|
|
121
141
|
}
|
|
122
142
|
}
|
|
123
143
|
for (const key of ["repo", "workflow", "riskPreset", "base", "entry"]) {
|
|
124
144
|
const value = config[key];
|
|
125
145
|
if (value !== undefined && value !== null && !nonEmptyString(value)) {
|
|
126
|
-
problems.push(
|
|
146
|
+
problems.push(
|
|
147
|
+
`${key}: must be a non-empty string, got ${JSON.stringify(value)}`,
|
|
148
|
+
);
|
|
127
149
|
}
|
|
128
150
|
}
|
|
129
151
|
for (const key of ["work", "run"]) {
|
|
@@ -132,28 +154,51 @@ export function checkRunConfigShape(config) {
|
|
|
132
154
|
continue;
|
|
133
155
|
}
|
|
134
156
|
if (typeof value !== "string") {
|
|
135
|
-
problems.push(
|
|
157
|
+
problems.push(
|
|
158
|
+
`${key}: must be a string, got ${JSON.stringify(value)} (quote it, e.g. ${key}: "${value}")`,
|
|
159
|
+
);
|
|
136
160
|
} else if (!ID.test(value) || value.includes("..") || value.length > 100) {
|
|
137
|
-
problems.push(
|
|
161
|
+
problems.push(
|
|
162
|
+
`${key}: "${value}" may only use letters, digits, ".", "_" and "-", start with a letter or digit, contain no "..", and be at most 100 characters (it becomes part of paths and branch names)`,
|
|
163
|
+
);
|
|
138
164
|
}
|
|
139
165
|
}
|
|
166
|
+
if (
|
|
167
|
+
config.maxReviewSeverity !== undefined &&
|
|
168
|
+
!["P2", "P3"].includes(config.maxReviewSeverity)
|
|
169
|
+
) {
|
|
170
|
+
problems.push("maxReviewSeverity: must be P2 or P3");
|
|
171
|
+
}
|
|
172
|
+
if (Array.isArray(config.policies) && config.policies.length) {
|
|
173
|
+
problems.push(
|
|
174
|
+
"policies: custom policy files are retired; migrate safeguards explicitly (docs/MIGRATION.md)",
|
|
175
|
+
);
|
|
176
|
+
}
|
|
140
177
|
if (config.parallel !== undefined && typeof config.parallel !== "boolean") {
|
|
141
|
-
problems.push(
|
|
178
|
+
problems.push(
|
|
179
|
+
`parallel: must be true or false, got ${JSON.stringify(config.parallel)}`,
|
|
180
|
+
);
|
|
142
181
|
}
|
|
143
182
|
for (const key of ["stepTimeoutMs", "maxAttemptsPerStep"]) {
|
|
144
183
|
if (config[key] !== undefined && !positiveInteger(config[key])) {
|
|
145
|
-
problems.push(
|
|
184
|
+
problems.push(
|
|
185
|
+
`${key}: must be a positive integer, got ${JSON.stringify(config[key])}`,
|
|
186
|
+
);
|
|
146
187
|
}
|
|
147
188
|
}
|
|
148
189
|
for (const key of LISTS) {
|
|
149
190
|
if (config[key] !== undefined && !Array.isArray(config[key])) {
|
|
150
|
-
problems.push(
|
|
191
|
+
problems.push(
|
|
192
|
+
`${key}: must be a list (one "- item" per line), got ${JSON.stringify(config[key])}`,
|
|
193
|
+
);
|
|
151
194
|
}
|
|
152
195
|
}
|
|
153
196
|
if (Array.isArray(config.allowedPaths)) {
|
|
154
197
|
config.allowedPaths.forEach((entry, index) => {
|
|
155
198
|
if (!nonEmptyString(entry)) {
|
|
156
|
-
problems.push(
|
|
199
|
+
problems.push(
|
|
200
|
+
`allowedPaths[${index}]: must be a non-empty path, got ${JSON.stringify(entry)}`,
|
|
201
|
+
);
|
|
157
202
|
}
|
|
158
203
|
});
|
|
159
204
|
}
|
|
@@ -169,38 +214,65 @@ export function checkRunConfigShape(config) {
|
|
|
169
214
|
}
|
|
170
215
|
}
|
|
171
216
|
if (config.workers === undefined || config.workers === null) {
|
|
172
|
-
problems.push(
|
|
217
|
+
problems.push(
|
|
218
|
+
"workers: required and missing (at least the verifier; see templates/v2/run-config.example.yaml)",
|
|
219
|
+
);
|
|
173
220
|
} else if (!isMap(config.workers)) {
|
|
174
|
-
problems.push(
|
|
221
|
+
problems.push(
|
|
222
|
+
"workers: must be a map of worker name -> { command, args, ... }",
|
|
223
|
+
);
|
|
175
224
|
} else {
|
|
176
225
|
for (const [name, spec] of Object.entries(config.workers)) {
|
|
177
|
-
|
|
178
|
-
if (!isMap(spec)) {
|
|
179
|
-
problems.push(`${where}: must be a map with at least a command`);
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
for (const key of Object.keys(spec)) {
|
|
183
|
-
if (!WORKER_KEYS.includes(key)) {
|
|
184
|
-
problems.push(unknownKey(`${where}.`, key, WORKER_KEYS));
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
if (!nonEmptyString(spec.command)) {
|
|
188
|
-
problems.push(`${where}.command: required, a non-empty string`);
|
|
189
|
-
}
|
|
190
|
-
if (spec.args !== undefined && !Array.isArray(spec.args)) {
|
|
191
|
-
problems.push(`${where}.args: must be a list (one "- arg" per line)`);
|
|
192
|
-
}
|
|
193
|
-
if (spec.timeoutMs !== undefined && !(typeof spec.timeoutMs === "number" && spec.timeoutMs > 0)) {
|
|
194
|
-
problems.push(`${where}.timeoutMs: must be a positive number, got ${JSON.stringify(spec.timeoutMs)}`);
|
|
195
|
-
}
|
|
196
|
-
if (spec.inheritEnv !== undefined && typeof spec.inheritEnv !== "boolean") {
|
|
197
|
-
problems.push(`${where}.inheritEnv: must be true or false, got ${JSON.stringify(spec.inheritEnv)} (anything else used to mean false silently)`);
|
|
198
|
-
}
|
|
199
|
-
if (spec.env !== undefined && !isMap(spec.env)) {
|
|
200
|
-
problems.push(`${where}.env: must be a map of NAME -> value`);
|
|
201
|
-
}
|
|
226
|
+
problems.push(...checkCommandSpec(`workers.${name}`, spec));
|
|
202
227
|
}
|
|
203
228
|
}
|
|
229
|
+
if (
|
|
230
|
+
config.requireScreenshot !== undefined &&
|
|
231
|
+
typeof config.requireScreenshot !== "boolean"
|
|
232
|
+
) {
|
|
233
|
+
problems.push(
|
|
234
|
+
`requireScreenshot: must be true or false, got ${JSON.stringify(config.requireScreenshot)}`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
// The release readback command has the shape of a worker.
|
|
238
|
+
if (config.release !== undefined) {
|
|
239
|
+
problems.push(...checkCommandSpec("release", config.release));
|
|
240
|
+
}
|
|
241
|
+
return problems;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function checkCommandSpec(where, spec) {
|
|
245
|
+
const problems = [];
|
|
246
|
+
if (!isMap(spec)) {
|
|
247
|
+
return [`${where}: must be a map with at least a command`];
|
|
248
|
+
}
|
|
249
|
+
for (const key of Object.keys(spec)) {
|
|
250
|
+
if (!WORKER_KEYS.includes(key)) {
|
|
251
|
+
problems.push(unknownKey(`${where}.`, key, WORKER_KEYS));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (!nonEmptyString(spec.command)) {
|
|
255
|
+
problems.push(`${where}.command: required, a non-empty string`);
|
|
256
|
+
}
|
|
257
|
+
if (spec.args !== undefined && !Array.isArray(spec.args)) {
|
|
258
|
+
problems.push(`${where}.args: must be a list (one "- arg" per line)`);
|
|
259
|
+
}
|
|
260
|
+
if (
|
|
261
|
+
spec.timeoutMs !== undefined &&
|
|
262
|
+
!(typeof spec.timeoutMs === "number" && spec.timeoutMs > 0)
|
|
263
|
+
) {
|
|
264
|
+
problems.push(
|
|
265
|
+
`${where}.timeoutMs: must be a positive number, got ${JSON.stringify(spec.timeoutMs)}`,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
if (spec.inheritEnv !== undefined && typeof spec.inheritEnv !== "boolean") {
|
|
269
|
+
problems.push(
|
|
270
|
+
`${where}.inheritEnv: must be true or false, got ${JSON.stringify(spec.inheritEnv)} (anything else used to mean false silently)`,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
if (spec.env !== undefined && !isMap(spec.env)) {
|
|
274
|
+
problems.push(`${where}.env: must be a map of NAME -> value`);
|
|
275
|
+
}
|
|
204
276
|
return problems;
|
|
205
277
|
}
|
|
206
278
|
|
|
@@ -208,22 +280,34 @@ export function checkRunConfigShape(config) {
|
|
|
208
280
|
export function checkRunConfigAgainstWorkflow(config, workflow) {
|
|
209
281
|
const problems = [];
|
|
210
282
|
const steps = [...workflow.stepIds];
|
|
211
|
-
const workers = [
|
|
283
|
+
const workers = [
|
|
284
|
+
...new Set(workflow.steps.map((step) => step.worker).filter(Boolean)),
|
|
285
|
+
];
|
|
212
286
|
for (const name of Object.keys(isMap(config.workers) ? config.workers : {})) {
|
|
213
287
|
if (!workers.includes(name)) {
|
|
214
288
|
const guess = suggest(name, workers);
|
|
215
|
-
problems.push(
|
|
289
|
+
problems.push(
|
|
290
|
+
`workers.${name}: no step of the workflow uses this worker${guess ? ` (did you mean ${guess}?)` : ""}; workers in this workflow: ${workers.join(", ")}`,
|
|
291
|
+
);
|
|
216
292
|
}
|
|
217
293
|
}
|
|
218
294
|
for (const step of Array.isArray(config.stopAt) ? config.stopAt : []) {
|
|
219
295
|
if (!workflow.stepIds.has(step)) {
|
|
220
296
|
const guess = suggest(step, steps);
|
|
221
|
-
problems.push(
|
|
297
|
+
problems.push(
|
|
298
|
+
`stopAt: "${step}" is not a step of the workflow${guess ? ` (did you mean ${guess}?)` : ""}; steps: ${steps.join(", ")}`,
|
|
299
|
+
);
|
|
222
300
|
}
|
|
223
301
|
}
|
|
224
|
-
if (
|
|
302
|
+
if (
|
|
303
|
+
config.entry !== undefined &&
|
|
304
|
+
config.entry !== null &&
|
|
305
|
+
!workflow.stepIds.has(config.entry)
|
|
306
|
+
) {
|
|
225
307
|
const guess = suggest(config.entry, steps);
|
|
226
|
-
problems.push(
|
|
308
|
+
problems.push(
|
|
309
|
+
`entry: "${config.entry}" is not a step of the workflow${guess ? ` (did you mean ${guess}?)` : ""}; steps: ${steps.join(", ")}`,
|
|
310
|
+
);
|
|
227
311
|
}
|
|
228
312
|
return problems;
|
|
229
313
|
}
|