@haiyangbg/buildbeat 0.0.0 → 1.20.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 (79) hide show
  1. package/CHANGELOG.md +296 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +288 -0
  4. package/README.md +283 -4
  5. package/SKILL.md +303 -0
  6. package/bin/buildbeat.js +5 -0
  7. package/bin/solobaton.js +6 -0
  8. package/docs/CAPABILITY-MATRIX.md +50 -0
  9. package/docs/CHECKS.md +326 -0
  10. package/docs/CLI-PILOT-2026-08-23.md +25 -0
  11. package/docs/CLI-STRATEGY-2026-08.md +55 -0
  12. package/docs/CLI.md +233 -0
  13. package/docs/EXECUTION-PLAN.md +487 -0
  14. package/docs/LEGACY-V1.16-MIGRATION.md +54 -0
  15. package/docs/PHASE1-PILOT-2026-08-24.md +32 -0
  16. package/docs/PHASE2-BUILDBEAT-PILOT-2026-08-25.md +75 -0
  17. package/docs/PHASE2-PILOT-2026-08-25.md +88 -0
  18. package/docs/PHASE2-PILOT-PREFLIGHT-2026-08-25.md +42 -0
  19. package/docs/PHASE4-STABILITY-AUDIT-2026-08-25.md +33 -0
  20. package/docs/PHASE4-V1.20-PILOT-2026-08-25.md +52 -0
  21. package/docs/RELEASING.md +117 -0
  22. package/docs/ROADMAP.md +873 -0
  23. package/example/.buildbeat/manifest.json +45 -0
  24. package/example/AGENTS.md +19 -0
  25. package/example/ARCHITECTURE.md +39 -0
  26. package/example/BUILDBEAT.md +17 -0
  27. package/example/CLAUDE.md +7 -0
  28. package/example/README.md +53 -0
  29. package/example/contracts/PROTOCOL.md +38 -0
  30. package/example/pm/NOW.md +22 -0
  31. package/example/pm/adr/ADR-0001-local-first-sqlite.md +25 -0
  32. package/example/pm/adr/README.md +7 -0
  33. package/example/pm/archive//344/270/200/346/234/237/evidence/gate1.md +5 -0
  34. package/example/pm/archive//344/270/200/346/234/237/evidence/gate2.md +5 -0
  35. package/example/pm/archive//344/270/200/346/234/237/evidence/gate3.md +5 -0
  36. package/example/pm/archive//344/270/200/346/234/237/evidence/gate4.md +5 -0
  37. package/example/pm/archive//344/270/200/346/234/237/evidence/implementation.md +5 -0
  38. package/example/pm/decisions.md +20 -0
  39. package/example/pm/status//344/272/247/345/223/201.md +20 -0
  40. package/example/pm/status//345/205/250/346/240/210.md +15 -0
  41. package/example/pm/status//346/265/213/350/257/225.md +15 -0
  42. package/example/pm//344/270/200/346/234/237-/347/234/213/346/235/277.md +97 -0
  43. package/example/standards/CODE.md +18 -0
  44. package/example/standards/DESIGN.md +34 -0
  45. package/example/standards/REVIEW.md +16 -0
  46. package/example/standards/STACK.md +31 -0
  47. package/lessons.md +119 -0
  48. package/package.json +48 -7
  49. package/src/cli.js +323 -0
  50. package/src/constants.js +199 -0
  51. package/src/doctor.js +267 -0
  52. package/src/planner.js +251 -0
  53. package/src/project.js +839 -0
  54. package/src/upgrader.js +1249 -0
  55. package/src/writer.js +534 -0
  56. package/templates/.claude/agents/reviewer.md +62 -0
  57. package/templates/AGENTS.md +64 -0
  58. package/templates/ARCHITECTURE.md +50 -0
  59. package/templates/BUILDBEAT.md +13 -0
  60. package/templates/CLAUDE.md +7 -0
  61. package/templates/contracts/PROTOCOL.md +32 -0
  62. package/templates/gitignore.template +19 -0
  63. package/templates/pm/NOW.md +26 -0
  64. package/templates/pm/adr/ADR-0000-template.md +25 -0
  65. package/templates/pm/adr/README.md +15 -0
  66. package/templates/pm/changes/README.md +44 -0
  67. package/templates/pm/decisions.md +12 -0
  68. package/templates/pm/status/README.md +32 -0
  69. package/templates/pm//345/275/223/346/234/237/347/234/213/346/235/277.md +62 -0
  70. package/templates/scripts/bus-check.sh +1850 -0
  71. package/templates/scripts/design-preview.sh +44 -0
  72. package/templates/scripts/drift-check.sh +112 -0
  73. package/templates/scripts/pre-commit.sh +74 -0
  74. package/templates/scripts/verify-status.sh +105 -0
  75. package/templates/standards/CODE.md +23 -0
  76. package/templates/standards/DESIGN.md +36 -0
  77. package/templates/standards/REVIEW.md +20 -0
  78. package/templates/standards/STACK.md +37 -0
  79. package/templates//346/214/207/346/214/245/345/217/260.md +35 -0
package/src/planner.js ADDED
@@ -0,0 +1,251 @@
1
+ import {
2
+ CLI_VERSION,
3
+ MANIFEST_PATH,
4
+ OUTPUT_SCHEMA_VERSION,
5
+ SCAFFOLD_VERSION,
6
+ } from "./constants.js";
7
+ import { inspectProject, plannedFiles } from "./project.js";
8
+ import { prepareScaffold } from "./writer.js";
9
+
10
+ export function buildPlan({ mode, target, layout, preview = true, now = new Date() }) {
11
+ const inspection = inspectProject(target, {
12
+ collisionLayout: layout,
13
+ includeDependencies: false,
14
+ });
15
+ const rendered = prepareScaffold({
16
+ layout,
17
+ projectName: inspection.projectName.value,
18
+ now,
19
+ });
20
+ const operations = plannedFiles(layout).map((item) => ({
21
+ action: item.policy === "merge-only" ? "merge" : "seed",
22
+ source: `templates/${item.template}`,
23
+ target: item.target,
24
+ policy: item.policy,
25
+ collision: inspection.collisions.includes(item.target),
26
+ }));
27
+ const blockers = [];
28
+ const warnings = [];
29
+
30
+ if (inspection.installation.state === "installed") {
31
+ blockers.push({
32
+ code: "install.already_present",
33
+ message: "BuildBeat or a legacy Solobaton scaffold is already installed; a future upgrade/adoption path must own this transition.",
34
+ });
35
+ } else if (inspection.installation.state === "mixed") {
36
+ blockers.push({
37
+ code: "install.mixed_layout",
38
+ message: "Multiple BuildBeat/legacy markers or layout signals are present; reconcile ownership before any lifecycle write.",
39
+ });
40
+ } else if (inspection.installation.state === "partial") {
41
+ blockers.push({
42
+ code: "install.partial",
43
+ message: "A partial installation exists; the CLI will not guess which files are user-owned.",
44
+ });
45
+ }
46
+
47
+ if (inspection.manifest.state !== "missing") {
48
+ blockers.push({
49
+ code: "manifest.already_present",
50
+ message: "A lifecycle manifest already exists or is unreadable; reconcile it before scaffolding.",
51
+ });
52
+ }
53
+
54
+ if (mode === "adopt" && !inspection.exists) {
55
+ blockers.push({
56
+ code: "target.not_found",
57
+ message: "Brownfield adoption requires an existing project directory.",
58
+ });
59
+ }
60
+
61
+ if (mode === "init" && inspection.exists && inspection.signals.nonEmpty) {
62
+ warnings.push({
63
+ code: "target.non_empty",
64
+ message: "The target is not empty; confirm whether the brownfield adopt flow is more appropriate.",
65
+ });
66
+ }
67
+ if (mode === "adopt" && inspection.exists && !inspection.signals.nonEmpty) {
68
+ warnings.push({
69
+ code: "target.empty",
70
+ message: "The target is empty; the new-project init flow is probably more appropriate.",
71
+ });
72
+ }
73
+ if (mode === "adopt" && layout === "default") {
74
+ warnings.push({
75
+ code: "layout.default_for_adopt",
76
+ message: "Brownfield adoption normally uses compact layout; default layout needs an explicit collision review.",
77
+ });
78
+ }
79
+ if (inspection.collisions.length > 0) {
80
+ blockers.push({
81
+ code: "files.collide",
82
+ message: `${inspection.collisions.length} planned target path(s) collide; Wave 1 never overwrites project files.`,
83
+ });
84
+ }
85
+ if (inspection.gitWorktree.state === "dirty") {
86
+ blockers.push({
87
+ code: "git.dirty",
88
+ message: `The target-root Git worktree has ${inspection.gitWorktree.changes} visible change(s); commit or otherwise clean it before writing.`,
89
+ });
90
+ } else if (inspection.gitWorktree.state === "unavailable") {
91
+ blockers.push({
92
+ code: "git.status_unavailable",
93
+ message: "A root .git entry exists, but the fixed read-only Git status check failed.",
94
+ });
95
+ }
96
+ if (inspection.gitignore.state === "unsafe") {
97
+ blockers.push({
98
+ code: "integration.gitignore_unsafe",
99
+ message: ".gitignore exists but is not a readable regular file.",
100
+ });
101
+ } else if (inspection.gitignore.beginMarkers > 0 || inspection.gitignore.endMarkers > 0) {
102
+ blockers.push({
103
+ code: "integration.gitignore_fragment_present",
104
+ message: "BuildBeat or legacy Solobaton fragment markers already exist without schema 2 ownership metadata.",
105
+ });
106
+ }
107
+ if (inspection.scan.truncated) {
108
+ warnings.push({
109
+ code: "scan.truncated",
110
+ message: "The project scan hit its safety limit; inspect the omitted area before Gate 1.",
111
+ });
112
+ }
113
+ if (inspection.scan.symlinks.length > 0) {
114
+ warnings.push({
115
+ code: "scan.symlinks_skipped",
116
+ message: `${inspection.scan.symlinks.length} symbolic link(s) were skipped and require separate inspection.`,
117
+ });
118
+ }
119
+ if (inspection.scan.warnings.length > 0) {
120
+ warnings.push({
121
+ code: "scan.unreadable",
122
+ message: `${inspection.scan.warnings.length} path(s) could not be inspected.`,
123
+ });
124
+ }
125
+
126
+ const questions = [
127
+ "Is this project expected to finish in a few days, or will it be maintained long term?",
128
+ "Will anyone else open AI coding sessions for this project?",
129
+ "Keep the default Product / Fullstack / Testing sessions, or change them?",
130
+ ];
131
+ if (inspection.signals.hasUi) {
132
+ questions.push("Who decides the UI result: an existing design source, or your rendered review?");
133
+ }
134
+
135
+ const steps = mode === "adopt"
136
+ ? [
137
+ "Review the detected repositories, deployment markers, tests, and high-risk boundaries.",
138
+ "Approve the old/new strangler boundary and establish a minimum L3 verification suite.",
139
+ "Seed the compact coordination layout without replacing the project's existing scripts directory.",
140
+ "Render project facts, record the first decision package, then run bus-check.",
141
+ "Install hooks only after detecting and preserving any existing hook chain.",
142
+ ]
143
+ : [
144
+ "Confirm the detected project facts and the small remaining question set.",
145
+ "Seed the selected coordination layout and render every canonical placeholder.",
146
+ "Merge ignore rules instead of replacing .gitignore.",
147
+ "Record the first decision package, configure verification, then run bus-check.",
148
+ "Install hooks only after detecting and preserving any existing hook chain.",
149
+ ];
150
+
151
+ return {
152
+ schemaVersion: OUTPUT_SCHEMA_VERSION,
153
+ command: mode,
154
+ cliVersion: CLI_VERSION,
155
+ scaffoldVersion: SCAFFOLD_VERSION,
156
+ preview,
157
+ writesPerformed: false,
158
+ target: inspection.target,
159
+ targetExists: inspection.exists,
160
+ layout,
161
+ detected: {
162
+ projectName: inspection.projectName,
163
+ rootGit: inspection.signals.hasGit,
164
+ repositories: inspection.repositories,
165
+ deploymentMarkers: inspection.deploymentMarkers,
166
+ hasTests: inspection.signals.hasTests,
167
+ hasUi: inspection.signals.hasUi,
168
+ symlinksSkipped: inspection.scan.symlinks.length,
169
+ scanTruncated: inspection.scan.truncated,
170
+ },
171
+ operations,
172
+ collisions: inspection.collisions,
173
+ writtenPaths: [],
174
+ renderedPlaceholders: rendered.renderedPlaceholders,
175
+ pendingPlaceholders: rendered.pendingPlaceholders,
176
+ manifestPath: MANIFEST_PATH,
177
+ nextAction: mode === "adopt"
178
+ ? "Continue with SKILL.md §8.5 to verify the brownfield boundary and render project-owned facts."
179
+ : "Continue with SKILL.md §8 to render project-owned facts and complete Bootstrap.",
180
+ blockers,
181
+ warnings,
182
+ questions,
183
+ steps,
184
+ ready: blockers.length === 0,
185
+ };
186
+ }
187
+
188
+ export function formatPlan(plan) {
189
+ if (plan.writesPerformed) {
190
+ const lines = [
191
+ `BuildBeat ${plan.command} complete`,
192
+ `Target: ${plan.target}`,
193
+ `Layout: ${plan.layout}`,
194
+ `Written paths: ${plan.writtenPaths.length}`,
195
+ `Manifest: ${plan.manifestPath}`,
196
+ `Pending placeholder files: ${plan.pendingPlaceholders.length}`,
197
+ ];
198
+ if (plan.pendingPlaceholders.length > 0) {
199
+ lines.push("", "Pending placeholders:");
200
+ plan.pendingPlaceholders.forEach((item) => {
201
+ lines.push(`- ${item.path}: ${item.tokens.join(", ")}`);
202
+ });
203
+ }
204
+ lines.push("", plan.nextAction);
205
+ return lines.join("\n");
206
+ }
207
+ const lines = [
208
+ `BuildBeat ${plan.command} ${plan.preview ? "dry run" : "write plan"}`,
209
+ `Target: ${plan.target}${plan.targetExists ? "" : plan.preview ? " (does not exist)" : " (will be created)"}`,
210
+ `Layout: ${plan.layout}`,
211
+ `Detected project: ${plan.detected.projectName.value} (${plan.detected.projectName.source})`,
212
+ `Repositories: ${plan.detected.repositories.length}; deployment markers: ${plan.detected.deploymentMarkers.length}; tests: ${plan.detected.hasTests ? "detected" : "not detected"}; UI: ${plan.detected.hasUi ? "detected" : "not detected"}`,
213
+ `Planned paths: ${plan.operations.length}; collisions: ${plan.collisions.length}`,
214
+ "",
215
+ ];
216
+ for (const blocker of plan.blockers) {
217
+ lines.push(`BLOCKER ${blocker.code}: ${blocker.message}`);
218
+ }
219
+ for (const warning of plan.warnings) {
220
+ lines.push(`WARNING ${warning.code}: ${warning.message}`);
221
+ }
222
+ if (plan.blockers.length > 0 || plan.warnings.length > 0) {
223
+ lines.push("");
224
+ }
225
+ lines.push("Planned operations:");
226
+ plan.operations.forEach((operation, index) => {
227
+ lines.push(
228
+ `${index + 1}. ${operation.action} ${operation.target} (${operation.policy})${operation.collision ? " [collision]" : ""}`,
229
+ );
230
+ });
231
+ lines.push("", `Deterministic replacements: ${plan.renderedPlaceholders.length}`);
232
+ plan.renderedPlaceholders.forEach((item) => {
233
+ lines.push(`- ${item.path}: ${item.token} -> ${item.value}`);
234
+ });
235
+ lines.push("", `Pending placeholder files: ${plan.pendingPlaceholders.length}`);
236
+ plan.pendingPlaceholders.forEach((item) => {
237
+ lines.push(`- ${item.path}: ${item.tokens.join(", ")}`);
238
+ });
239
+ lines.push("");
240
+ lines.push("Remaining human questions:");
241
+ plan.questions.forEach((question, index) => lines.push(`${index + 1}. ${question}`));
242
+ lines.push("", "Proposed sequence:");
243
+ plan.steps.forEach((step, index) => lines.push(`${index + 1}. ${step}`));
244
+ lines.push(
245
+ "",
246
+ plan.preview
247
+ ? "No files changed. This was a dry run."
248
+ : "No files changed yet. Apply only after the confirmation prompt.",
249
+ );
250
+ return lines.join("\n");
251
+ }