@boopkit/cli 0.6.12 → 0.6.13
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 +26 -5
- package/dist/capability-versions.json +1 -1
- package/dist/cli.js +2 -1
- package/dist/commands/add.js +49 -2
- package/dist/commands/init.d.ts +17 -1
- package/dist/commands/init.js +149 -25
- package/dist/commands/loop-merge.d.ts +1 -0
- package/dist/commands/loop-merge.js +4 -2
- package/dist/commands/loop.d.ts +21 -2
- package/dist/commands/loop.js +105 -16
- package/dist/commands/loop.js.orig-pre-maxbuffer +1455 -0
- package/dist/lib/scaffold.d.ts +18 -7
- package/dist/lib/scaffold.js +31 -14
- package/docs/init.md +132 -86
- package/docs/loop.md +16 -0
- package/package.json +3 -2
- package/profiles/base/profile.md +105 -0
- package/profiles/base/templates/.github/copilot-instructions.md +3 -0
- package/profiles/base/templates/.gitignore.hbs +6 -0
- package/profiles/base/templates/AGENTS.md.hbs +50 -0
- package/profiles/base/templates/CLAUDE.md.hbs +3 -0
- package/profiles/base/templates/README.md.hbs +15 -0
- package/profiles/base/templates/docs/README.md +12 -0
- package/profiles/base/templates/package.json.hbs +7 -0
- package/profiles/base/templates/reference/README.md +16 -0
- package/profiles/base/templates/specs/README.md +12 -0
- package/profiles/development/profile.md +8 -1
package/dist/commands/loop.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.RESUME_PROMPT = exports.DEFAULT_BUDGET_USD = void 0;
|
|
36
|
+
exports.RESUME_PROMPT = exports.DEFAULT_BUDGET_USD = exports.BD_READ_MAX_BUFFER = void 0;
|
|
37
37
|
exports.assertDoltServerMode = assertDoltServerMode;
|
|
38
38
|
exports.discoverDoltServer = discoverDoltServer;
|
|
39
39
|
exports.resolveDefaultBranch = resolveDefaultBranch;
|
|
@@ -57,6 +57,71 @@ const child_process_1 = require("child_process");
|
|
|
57
57
|
const fs = __importStar(require("fs"));
|
|
58
58
|
const path = __importStar(require("path"));
|
|
59
59
|
const crypto_1 = require("crypto");
|
|
60
|
+
/**
|
|
61
|
+
* Output cap for every `bd … --json` read the loop makes.
|
|
62
|
+
*
|
|
63
|
+
* Node's execSync defaults to 1 MiB and throws ENOBUFS past it. `bd ready --json`
|
|
64
|
+
* returns each issue WITH its full description, and `bd show --children --json`
|
|
65
|
+
* on a large container is the same trap — a mature backlog crosses 1 MiB and the
|
|
66
|
+
* read dies. The ready-queue read also projects its output (see
|
|
67
|
+
* READY_QUEUE_CMD); this cap is the backstop for the reads that cannot (boop-fl22).
|
|
68
|
+
*/
|
|
69
|
+
exports.BD_READ_MAX_BUFFER = 256 * 1024 * 1024;
|
|
70
|
+
/**
|
|
71
|
+
* The ready-queue read, projected to the only three fields the filters below use.
|
|
72
|
+
*
|
|
73
|
+
* Unprojected this returns every ready issue with its full description: measured
|
|
74
|
+
* in boop at 274,529 bytes for 112 issues, 69.7% of it description that nothing
|
|
75
|
+
* here reads. The projection takes that to 5,941 bytes. `-n 0` = unlimited, which
|
|
76
|
+
* is itself the fix for boop-pra; unlimited fetch is only safe once the per-record
|
|
77
|
+
* payload is bounded (boop-fl22).
|
|
78
|
+
*/
|
|
79
|
+
const READY_QUEUE_CMD = `bd ready --json -n 0 2>/dev/null | jq -c '[.[] | {id, issue_type, parent}]'`;
|
|
80
|
+
/**
|
|
81
|
+
* The same read without the projection, for hosts that have no `jq`.
|
|
82
|
+
*
|
|
83
|
+
* jq ships with neither stock macOS nor stock Debian, and the projection is an
|
|
84
|
+
* optimisation rather than a requirement — BD_READ_MAX_BUFFER already makes the
|
|
85
|
+
* unprojected read safe. So a missing jq degrades the read instead of breaking
|
|
86
|
+
* the loop (boop-fl22).
|
|
87
|
+
*/
|
|
88
|
+
const READY_QUEUE_CMD_UNPROJECTED = 'bd ready --json -n 0 2>/dev/null';
|
|
89
|
+
/** Warn once per process, not once per loop iteration. Never gates control flow. */
|
|
90
|
+
let warnedAboutProjection = false;
|
|
91
|
+
function readReadyQueue(execSyncFn) {
|
|
92
|
+
const opts = { encoding: 'utf8', maxBuffer: exports.BD_READ_MAX_BUFFER };
|
|
93
|
+
// Run one read and insist it produced something. A healthy read always emits
|
|
94
|
+
// at least `[]`, so empty output means the command failed — and `sh` has no
|
|
95
|
+
// pipefail, so a pipeline hides the exit status of `bd` itself. This is the
|
|
96
|
+
// only signal that the left side of the pipe died. The command is carried in
|
|
97
|
+
// the message so the diagnostic names the read that actually failed.
|
|
98
|
+
const read = (cmd) => {
|
|
99
|
+
let raw;
|
|
100
|
+
try {
|
|
101
|
+
raw = execSyncFn(cmd, opts);
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
throw new Error(`${e.message.split('\n')[0]} (command: ${cmd})`);
|
|
105
|
+
}
|
|
106
|
+
if (!raw.trim()) {
|
|
107
|
+
throw new Error(`bd produced no output — a healthy read emits at least [] (command: ${cmd})`);
|
|
108
|
+
}
|
|
109
|
+
return raw;
|
|
110
|
+
};
|
|
111
|
+
// Always attempt the projection: a failed `jq` lookup is microseconds, and
|
|
112
|
+
// caching the outcome would make this function depend on call history.
|
|
113
|
+
try {
|
|
114
|
+
return read(READY_QUEUE_CMD);
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
if (!warnedAboutProjection) {
|
|
118
|
+
warnedAboutProjection = true;
|
|
119
|
+
console.warn(`\x1b[33m[bd-loop]\x1b[0m Projected ready-queue read failed (${e.message}); ` +
|
|
120
|
+
`falling back to the unprojected read. Install \`jq\` to keep the queue read small.`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return read(READY_QUEUE_CMD_UNPROJECTED);
|
|
124
|
+
}
|
|
60
125
|
/**
|
|
61
126
|
* Hard requirement: beads must run in Dolt server mode so concurrent worktrees
|
|
62
127
|
* share one issue DB over TCP. Reads `.beads/metadata.json` and throws an
|
|
@@ -490,8 +555,9 @@ async function planWork(opts) {
|
|
|
490
555
|
// Fallback: query bd list for recently created features
|
|
491
556
|
console.log('\x1b[33m[bd-loop]\x1b[0m No FEATURE_ID in output, trying fallback query...');
|
|
492
557
|
try {
|
|
493
|
-
const execFn = opts.execSyncFn ||
|
|
494
|
-
|
|
558
|
+
const execFn = opts.execSyncFn ||
|
|
559
|
+
((cmd, o) => (0, child_process_1.execSync)(cmd, o));
|
|
560
|
+
const raw = execFn('bd list --json --type feature 2>/dev/null', { encoding: 'utf8', maxBuffer: exports.BD_READ_MAX_BUFFER });
|
|
495
561
|
const features = JSON.parse(raw);
|
|
496
562
|
const fallbackId = findFallbackFeature(features, workContent, opts.workPath);
|
|
497
563
|
if (fallbackId) {
|
|
@@ -615,7 +681,7 @@ function findNextIssue(prefix, includeFeatures) {
|
|
|
615
681
|
}
|
|
616
682
|
function getIssueParentId(id) {
|
|
617
683
|
try {
|
|
618
|
-
const raw = (0, child_process_1.execSync)(`bd show ${id} --json 2>/dev/null`, { encoding: 'utf8' });
|
|
684
|
+
const raw = (0, child_process_1.execSync)(`bd show ${id} --json 2>/dev/null`, { encoding: 'utf8', maxBuffer: exports.BD_READ_MAX_BUFFER });
|
|
619
685
|
const data = JSON.parse(raw);
|
|
620
686
|
const issue = data[0] || data;
|
|
621
687
|
return issue.parent || null;
|
|
@@ -678,7 +744,7 @@ function shouldCloseFeatureRoot(feature, children) {
|
|
|
678
744
|
}
|
|
679
745
|
function getIssueChildren(id) {
|
|
680
746
|
try {
|
|
681
|
-
const raw = (0, child_process_1.execSync)(`bd show ${id} --children --json 2>/dev/null`, { encoding: 'utf8' });
|
|
747
|
+
const raw = (0, child_process_1.execSync)(`bd show ${id} --children --json 2>/dev/null`, { encoding: 'utf8', maxBuffer: exports.BD_READ_MAX_BUFFER });
|
|
682
748
|
const data = JSON.parse(raw);
|
|
683
749
|
const children = data[id];
|
|
684
750
|
return Array.isArray(children) ? children : [];
|
|
@@ -714,7 +780,7 @@ function closeCompletedFeatureRoot(featureId) {
|
|
|
714
780
|
}
|
|
715
781
|
function getIssueInfoWithStatus(id) {
|
|
716
782
|
try {
|
|
717
|
-
const raw = (0, child_process_1.execSync)(`bd show ${id} --json 2>/dev/null`, { encoding: 'utf8' });
|
|
783
|
+
const raw = (0, child_process_1.execSync)(`bd show ${id} --json 2>/dev/null`, { encoding: 'utf8', maxBuffer: exports.BD_READ_MAX_BUFFER });
|
|
718
784
|
const data = JSON.parse(raw);
|
|
719
785
|
const issue = data[0] || data;
|
|
720
786
|
return { status: issue.status || 'unknown', issue_type: issue.issue_type || 'task' };
|
|
@@ -723,15 +789,18 @@ function getIssueInfoWithStatus(id) {
|
|
|
723
789
|
return null;
|
|
724
790
|
}
|
|
725
791
|
}
|
|
792
|
+
/**
|
|
793
|
+
* Read the ready queue, filtered to what this loop may claim.
|
|
794
|
+
*
|
|
795
|
+
* THROWS on a failed read. It must never answer a broken read with an empty
|
|
796
|
+
* array: the caller treats "no issues" as a clean finish, so a swallowed
|
|
797
|
+
* failure makes the loop exit 0 with work undone (boop-fl22).
|
|
798
|
+
*/
|
|
726
799
|
function getMatchingReadyIssues(prefix, includeFeatures, deps) {
|
|
727
800
|
const execSyncFn = deps?.execSyncFn ||
|
|
728
801
|
((cmd, opts) => (0, child_process_1.execSync)(cmd, opts));
|
|
729
802
|
try {
|
|
730
|
-
|
|
731
|
-
// filter below: with more than 20 ready issues globally, in-scope issues
|
|
732
|
-
// sorting outside the window are never seen and the loop exits with group
|
|
733
|
-
// work undone (boop-pra).
|
|
734
|
-
const raw = execSyncFn('bd ready --json -n 0 2>/dev/null', { encoding: 'utf8' });
|
|
803
|
+
const raw = readReadyQueue(execSyncFn);
|
|
735
804
|
const issues = JSON.parse(raw);
|
|
736
805
|
const typeFilter = includeFeatures
|
|
737
806
|
? (i) => i.issue_type !== 'epic'
|
|
@@ -763,13 +832,13 @@ function getMatchingReadyIssues(prefix, includeFeatures, deps) {
|
|
|
763
832
|
.filter((issue) => isIssueInScope(issue, prefix, getParentById))
|
|
764
833
|
.filter((issue) => isClaimableReadyIssue(issue, hasChildren));
|
|
765
834
|
}
|
|
766
|
-
catch {
|
|
767
|
-
|
|
835
|
+
catch (e) {
|
|
836
|
+
throw new Error(`Could not read the ready queue: ${e.message}`);
|
|
768
837
|
}
|
|
769
838
|
}
|
|
770
839
|
function getIssueInfo(id) {
|
|
771
840
|
try {
|
|
772
|
-
const raw = (0, child_process_1.execSync)(`bd show ${id} --json 2>/dev/null`, { encoding: 'utf8' });
|
|
841
|
+
const raw = (0, child_process_1.execSync)(`bd show ${id} --json 2>/dev/null`, { encoding: 'utf8', maxBuffer: exports.BD_READ_MAX_BUFFER });
|
|
773
842
|
const data = JSON.parse(raw);
|
|
774
843
|
const issue = data[0] || data;
|
|
775
844
|
return {
|
|
@@ -786,7 +855,7 @@ function getIssueInfo(id) {
|
|
|
786
855
|
}
|
|
787
856
|
function isIssueClosed(id) {
|
|
788
857
|
try {
|
|
789
|
-
const raw = (0, child_process_1.execSync)(`bd show ${id} --json 2>/dev/null`, { encoding: 'utf8' });
|
|
858
|
+
const raw = (0, child_process_1.execSync)(`bd show ${id} --json 2>/dev/null`, { encoding: 'utf8', maxBuffer: exports.BD_READ_MAX_BUFFER });
|
|
790
859
|
const data = JSON.parse(raw);
|
|
791
860
|
const status = (data[0] || data).status || 'unknown';
|
|
792
861
|
return status === 'closed';
|
|
@@ -1300,7 +1369,27 @@ async function runLoop(options) {
|
|
|
1300
1369
|
// rotation). Actual rotations are everything after the first — see the summary.
|
|
1301
1370
|
const sessionStarts = [];
|
|
1302
1371
|
while (true) {
|
|
1303
|
-
|
|
1372
|
+
let nextId = null;
|
|
1373
|
+
if (prefix) {
|
|
1374
|
+
try {
|
|
1375
|
+
nextId = findNextIssue(prefix, includeFeatures);
|
|
1376
|
+
}
|
|
1377
|
+
catch (e) {
|
|
1378
|
+
// A failed read is not a drained queue. Treating the two alike is the
|
|
1379
|
+
// boop-fl22 bug: the loop printed "No more ready issues" and exited 0
|
|
1380
|
+
// while `bd ready` was still listing claimable work.
|
|
1381
|
+
console.error(`\x1b[31m[bd-loop]\x1b[0m ${e.message}`);
|
|
1382
|
+
if (poll) {
|
|
1383
|
+
console.error(`\x1b[31m[bd-loop]\x1b[0m Ready-queue read FAILED (not an empty queue); retrying in ${pollInterval}s.`);
|
|
1384
|
+
await sleep(pollInterval * 1000);
|
|
1385
|
+
continue;
|
|
1386
|
+
}
|
|
1387
|
+
console.error(`\x1b[31m[bd-loop]\x1b[0m Stopping: the ready queue could not be read, so work may be outstanding. ` +
|
|
1388
|
+
`This is NOT a clean finish.`);
|
|
1389
|
+
process.exitCode = 1;
|
|
1390
|
+
break;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1304
1393
|
if (!nextId) {
|
|
1305
1394
|
// Spec-driven safety net: the filter removes the workflow-owned feature
|
|
1306
1395
|
// root from claimable work, so when its chain completes nobody may have
|