@girardmedia/bootspring 3.0.0 → 3.2.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/assets/claude-commands/build.md +2 -0
- package/dist/cli/index.js +691 -144
- package/dist/core/index.d.ts +1 -1
- package/dist/core.js +10 -8
- package/dist/mcp-server.js +409 -65
- package/package.json +1 -1
- package/scripts/postinstall.cjs +7 -1
package/dist/cli/index.js
CHANGED
|
@@ -33,9 +33,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
33
33
|
));
|
|
34
34
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
35
35
|
|
|
36
|
-
// ../../node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.
|
|
36
|
+
// ../../node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.14_tsx@4.21.0_typescript@5.9.3_yaml@2.8.3/node_modules/tsup/assets/cjs_shims.js
|
|
37
37
|
var init_cjs_shims = __esm({
|
|
38
|
-
"../../node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.
|
|
38
|
+
"../../node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.14_tsx@4.21.0_typescript@5.9.3_yaml@2.8.3/node_modules/tsup/assets/cjs_shims.js"() {
|
|
39
39
|
"use strict";
|
|
40
40
|
}
|
|
41
41
|
});
|
|
@@ -3386,7 +3386,7 @@ var init_release = __esm({
|
|
|
3386
3386
|
"../../packages/shared/src/release.ts"() {
|
|
3387
3387
|
"use strict";
|
|
3388
3388
|
init_cjs_shims();
|
|
3389
|
-
BOOTSPRING_VERSION = "3.
|
|
3389
|
+
BOOTSPRING_VERSION = "3.2.0";
|
|
3390
3390
|
BOOTSPRING_PACKAGE_NAME = "@girardmedia/bootspring";
|
|
3391
3391
|
}
|
|
3392
3392
|
});
|
|
@@ -32899,13 +32899,16 @@ var require_data = __commonJS({
|
|
|
32899
32899
|
}
|
|
32900
32900
|
});
|
|
32901
32901
|
|
|
32902
|
-
// ../../node_modules/.pnpm/fast-uri@3.1.
|
|
32902
|
+
// ../../node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/lib/utils.js
|
|
32903
32903
|
var require_utils = __commonJS({
|
|
32904
|
-
"../../node_modules/.pnpm/fast-uri@3.1.
|
|
32904
|
+
"../../node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/lib/utils.js"(exports2, module2) {
|
|
32905
32905
|
"use strict";
|
|
32906
32906
|
init_cjs_shims();
|
|
32907
32907
|
var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
|
|
32908
32908
|
var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
|
|
32909
|
+
var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
|
|
32910
|
+
var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
|
|
32911
|
+
var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
|
|
32909
32912
|
function stringArrayToHexStripped(input) {
|
|
32910
32913
|
let acc = "";
|
|
32911
32914
|
let code = 0;
|
|
@@ -33098,27 +33101,77 @@ var require_utils = __commonJS({
|
|
|
33098
33101
|
}
|
|
33099
33102
|
return output.join("");
|
|
33100
33103
|
}
|
|
33101
|
-
|
|
33102
|
-
|
|
33103
|
-
|
|
33104
|
-
|
|
33105
|
-
|
|
33106
|
-
|
|
33107
|
-
|
|
33108
|
-
|
|
33109
|
-
|
|
33110
|
-
|
|
33104
|
+
var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
|
|
33105
|
+
var HOST_DELIM_RE = /[@/?#:]/g;
|
|
33106
|
+
var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
|
|
33107
|
+
function reescapeHostDelimiters(host, isIP) {
|
|
33108
|
+
const re = isIP ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
|
|
33109
|
+
re.lastIndex = 0;
|
|
33110
|
+
return host.replace(re, (ch) => HOST_DELIMS[ch]);
|
|
33111
|
+
}
|
|
33112
|
+
function normalizePercentEncoding(input, decodeUnreserved = false) {
|
|
33113
|
+
if (input.indexOf("%") === -1) {
|
|
33114
|
+
return input;
|
|
33111
33115
|
}
|
|
33112
|
-
|
|
33113
|
-
|
|
33116
|
+
let output = "";
|
|
33117
|
+
for (let i = 0; i < input.length; i++) {
|
|
33118
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
33119
|
+
const hex3 = input.slice(i + 1, i + 3);
|
|
33120
|
+
if (isHexPair(hex3)) {
|
|
33121
|
+
const normalizedHex = hex3.toUpperCase();
|
|
33122
|
+
const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
|
|
33123
|
+
if (decodeUnreserved && isUnreserved(decoded)) {
|
|
33124
|
+
output += decoded;
|
|
33125
|
+
} else {
|
|
33126
|
+
output += "%" + normalizedHex;
|
|
33127
|
+
}
|
|
33128
|
+
i += 2;
|
|
33129
|
+
continue;
|
|
33130
|
+
}
|
|
33131
|
+
}
|
|
33132
|
+
output += input[i];
|
|
33114
33133
|
}
|
|
33115
|
-
|
|
33116
|
-
|
|
33134
|
+
return output;
|
|
33135
|
+
}
|
|
33136
|
+
function normalizePathEncoding(input) {
|
|
33137
|
+
let output = "";
|
|
33138
|
+
for (let i = 0; i < input.length; i++) {
|
|
33139
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
33140
|
+
const hex3 = input.slice(i + 1, i + 3);
|
|
33141
|
+
if (isHexPair(hex3)) {
|
|
33142
|
+
const normalizedHex = hex3.toUpperCase();
|
|
33143
|
+
const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
|
|
33144
|
+
if (decoded !== "." && isUnreserved(decoded)) {
|
|
33145
|
+
output += decoded;
|
|
33146
|
+
} else {
|
|
33147
|
+
output += "%" + normalizedHex;
|
|
33148
|
+
}
|
|
33149
|
+
i += 2;
|
|
33150
|
+
continue;
|
|
33151
|
+
}
|
|
33152
|
+
}
|
|
33153
|
+
if (isPathCharacter(input[i])) {
|
|
33154
|
+
output += input[i];
|
|
33155
|
+
} else {
|
|
33156
|
+
output += escape(input[i]);
|
|
33157
|
+
}
|
|
33117
33158
|
}
|
|
33118
|
-
|
|
33119
|
-
|
|
33159
|
+
return output;
|
|
33160
|
+
}
|
|
33161
|
+
function escapePreservingEscapes(input) {
|
|
33162
|
+
let output = "";
|
|
33163
|
+
for (let i = 0; i < input.length; i++) {
|
|
33164
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
33165
|
+
const hex3 = input.slice(i + 1, i + 3);
|
|
33166
|
+
if (isHexPair(hex3)) {
|
|
33167
|
+
output += "%" + hex3.toUpperCase();
|
|
33168
|
+
i += 2;
|
|
33169
|
+
continue;
|
|
33170
|
+
}
|
|
33171
|
+
}
|
|
33172
|
+
output += escape(input[i]);
|
|
33120
33173
|
}
|
|
33121
|
-
return
|
|
33174
|
+
return output;
|
|
33122
33175
|
}
|
|
33123
33176
|
function recomposeAuthority(component) {
|
|
33124
33177
|
const uriTokens = [];
|
|
@@ -33133,7 +33186,7 @@ var require_utils = __commonJS({
|
|
|
33133
33186
|
if (ipV6res.isIPV6 === true) {
|
|
33134
33187
|
host = `[${ipV6res.escapedHost}]`;
|
|
33135
33188
|
} else {
|
|
33136
|
-
host =
|
|
33189
|
+
host = reescapeHostDelimiters(host, false);
|
|
33137
33190
|
}
|
|
33138
33191
|
}
|
|
33139
33192
|
uriTokens.push(host);
|
|
@@ -33147,7 +33200,10 @@ var require_utils = __commonJS({
|
|
|
33147
33200
|
module2.exports = {
|
|
33148
33201
|
nonSimpleDomain,
|
|
33149
33202
|
recomposeAuthority,
|
|
33150
|
-
|
|
33203
|
+
reescapeHostDelimiters,
|
|
33204
|
+
normalizePercentEncoding,
|
|
33205
|
+
normalizePathEncoding,
|
|
33206
|
+
escapePreservingEscapes,
|
|
33151
33207
|
removeDotSegments,
|
|
33152
33208
|
isIPv4,
|
|
33153
33209
|
isUUID,
|
|
@@ -33157,9 +33213,9 @@ var require_utils = __commonJS({
|
|
|
33157
33213
|
}
|
|
33158
33214
|
});
|
|
33159
33215
|
|
|
33160
|
-
// ../../node_modules/.pnpm/fast-uri@3.1.
|
|
33216
|
+
// ../../node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/lib/schemes.js
|
|
33161
33217
|
var require_schemes = __commonJS({
|
|
33162
|
-
"../../node_modules/.pnpm/fast-uri@3.1.
|
|
33218
|
+
"../../node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/lib/schemes.js"(exports2, module2) {
|
|
33163
33219
|
"use strict";
|
|
33164
33220
|
init_cjs_shims();
|
|
33165
33221
|
var { isUUID } = require_utils();
|
|
@@ -33368,17 +33424,17 @@ var require_schemes = __commonJS({
|
|
|
33368
33424
|
}
|
|
33369
33425
|
});
|
|
33370
33426
|
|
|
33371
|
-
// ../../node_modules/.pnpm/fast-uri@3.1.
|
|
33427
|
+
// ../../node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/index.js
|
|
33372
33428
|
var require_fast_uri = __commonJS({
|
|
33373
|
-
"../../node_modules/.pnpm/fast-uri@3.1.
|
|
33429
|
+
"../../node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/index.js"(exports2, module2) {
|
|
33374
33430
|
"use strict";
|
|
33375
33431
|
init_cjs_shims();
|
|
33376
|
-
var { normalizeIPv6, removeDotSegments, recomposeAuthority,
|
|
33432
|
+
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
33377
33433
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
33378
33434
|
function normalize2(uri, options) {
|
|
33379
33435
|
if (typeof uri === "string") {
|
|
33380
33436
|
uri = /** @type {T} */
|
|
33381
|
-
|
|
33437
|
+
normalizeString(uri, options);
|
|
33382
33438
|
} else if (typeof uri === "object") {
|
|
33383
33439
|
uri = /** @type {T} */
|
|
33384
33440
|
parse7(serialize(uri, options), options);
|
|
@@ -33445,19 +33501,9 @@ var require_fast_uri = __commonJS({
|
|
|
33445
33501
|
return target;
|
|
33446
33502
|
}
|
|
33447
33503
|
function equal(uriA, uriB, options) {
|
|
33448
|
-
|
|
33449
|
-
|
|
33450
|
-
|
|
33451
|
-
} else if (typeof uriA === "object") {
|
|
33452
|
-
uriA = serialize(normalizeComponentEncoding(uriA, true), { ...options, skipEscape: true });
|
|
33453
|
-
}
|
|
33454
|
-
if (typeof uriB === "string") {
|
|
33455
|
-
uriB = unescape(uriB);
|
|
33456
|
-
uriB = serialize(normalizeComponentEncoding(parse7(uriB, options), true), { ...options, skipEscape: true });
|
|
33457
|
-
} else if (typeof uriB === "object") {
|
|
33458
|
-
uriB = serialize(normalizeComponentEncoding(uriB, true), { ...options, skipEscape: true });
|
|
33459
|
-
}
|
|
33460
|
-
return uriA.toLowerCase() === uriB.toLowerCase();
|
|
33504
|
+
const normalizedA = normalizeComparableURI(uriA, options);
|
|
33505
|
+
const normalizedB = normalizeComparableURI(uriB, options);
|
|
33506
|
+
return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA.toLowerCase() === normalizedB.toLowerCase();
|
|
33461
33507
|
}
|
|
33462
33508
|
function serialize(cmpts, opts) {
|
|
33463
33509
|
const component = {
|
|
@@ -33482,12 +33528,12 @@ var require_fast_uri = __commonJS({
|
|
|
33482
33528
|
if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
|
|
33483
33529
|
if (component.path !== void 0) {
|
|
33484
33530
|
if (!options.skipEscape) {
|
|
33485
|
-
component.path =
|
|
33531
|
+
component.path = escapePreservingEscapes(component.path);
|
|
33486
33532
|
if (component.scheme !== void 0) {
|
|
33487
33533
|
component.path = component.path.split("%3A").join(":");
|
|
33488
33534
|
}
|
|
33489
33535
|
} else {
|
|
33490
|
-
component.path =
|
|
33536
|
+
component.path = normalizePercentEncoding(component.path);
|
|
33491
33537
|
}
|
|
33492
33538
|
}
|
|
33493
33539
|
if (options.reference !== "suffix" && component.scheme) {
|
|
@@ -33522,7 +33568,16 @@ var require_fast_uri = __commonJS({
|
|
|
33522
33568
|
return uriTokens.join("");
|
|
33523
33569
|
}
|
|
33524
33570
|
var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
|
|
33525
|
-
function
|
|
33571
|
+
function getParseError(parsed, matches) {
|
|
33572
|
+
if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
|
|
33573
|
+
return 'URI path must start with "/" when authority is present.';
|
|
33574
|
+
}
|
|
33575
|
+
if (typeof parsed.port === "number" && (parsed.port < 0 || parsed.port > 65535)) {
|
|
33576
|
+
return "URI port is malformed.";
|
|
33577
|
+
}
|
|
33578
|
+
return void 0;
|
|
33579
|
+
}
|
|
33580
|
+
function parseWithStatus(uri, opts) {
|
|
33526
33581
|
const options = Object.assign({}, opts);
|
|
33527
33582
|
const parsed = {
|
|
33528
33583
|
scheme: void 0,
|
|
@@ -33533,6 +33588,7 @@ var require_fast_uri = __commonJS({
|
|
|
33533
33588
|
query: void 0,
|
|
33534
33589
|
fragment: void 0
|
|
33535
33590
|
};
|
|
33591
|
+
let malformedAuthorityOrPort = false;
|
|
33536
33592
|
let isIP = false;
|
|
33537
33593
|
if (options.reference === "suffix") {
|
|
33538
33594
|
if (options.scheme) {
|
|
@@ -33553,6 +33609,11 @@ var require_fast_uri = __commonJS({
|
|
|
33553
33609
|
if (isNaN(parsed.port)) {
|
|
33554
33610
|
parsed.port = matches[5];
|
|
33555
33611
|
}
|
|
33612
|
+
const parseError = getParseError(parsed, matches);
|
|
33613
|
+
if (parseError !== void 0) {
|
|
33614
|
+
parsed.error = parsed.error || parseError;
|
|
33615
|
+
malformedAuthorityOrPort = true;
|
|
33616
|
+
}
|
|
33556
33617
|
if (parsed.host) {
|
|
33557
33618
|
const ipv4result = isIPv4(parsed.host);
|
|
33558
33619
|
if (ipv4result === false) {
|
|
@@ -33591,14 +33652,18 @@ var require_fast_uri = __commonJS({
|
|
|
33591
33652
|
parsed.scheme = unescape(parsed.scheme);
|
|
33592
33653
|
}
|
|
33593
33654
|
if (parsed.host !== void 0) {
|
|
33594
|
-
parsed.host = unescape(parsed.host);
|
|
33655
|
+
parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP);
|
|
33595
33656
|
}
|
|
33596
33657
|
}
|
|
33597
33658
|
if (parsed.path) {
|
|
33598
|
-
parsed.path =
|
|
33659
|
+
parsed.path = normalizePathEncoding(parsed.path);
|
|
33599
33660
|
}
|
|
33600
33661
|
if (parsed.fragment) {
|
|
33601
|
-
|
|
33662
|
+
try {
|
|
33663
|
+
parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
|
|
33664
|
+
} catch {
|
|
33665
|
+
parsed.error = parsed.error || "URI malformed";
|
|
33666
|
+
}
|
|
33602
33667
|
}
|
|
33603
33668
|
}
|
|
33604
33669
|
if (schemeHandler && schemeHandler.parse) {
|
|
@@ -33607,7 +33672,29 @@ var require_fast_uri = __commonJS({
|
|
|
33607
33672
|
} else {
|
|
33608
33673
|
parsed.error = parsed.error || "URI can not be parsed.";
|
|
33609
33674
|
}
|
|
33610
|
-
return parsed;
|
|
33675
|
+
return { parsed, malformedAuthorityOrPort };
|
|
33676
|
+
}
|
|
33677
|
+
function parse7(uri, opts) {
|
|
33678
|
+
return parseWithStatus(uri, opts).parsed;
|
|
33679
|
+
}
|
|
33680
|
+
function normalizeString(uri, opts) {
|
|
33681
|
+
return normalizeStringWithStatus(uri, opts).normalized;
|
|
33682
|
+
}
|
|
33683
|
+
function normalizeStringWithStatus(uri, opts) {
|
|
33684
|
+
const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
|
|
33685
|
+
return {
|
|
33686
|
+
normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
|
|
33687
|
+
malformedAuthorityOrPort
|
|
33688
|
+
};
|
|
33689
|
+
}
|
|
33690
|
+
function normalizeComparableURI(uri, opts) {
|
|
33691
|
+
if (typeof uri === "string") {
|
|
33692
|
+
const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
|
|
33693
|
+
return malformedAuthorityOrPort ? void 0 : normalized;
|
|
33694
|
+
}
|
|
33695
|
+
if (typeof uri === "object") {
|
|
33696
|
+
return serialize(uri, opts);
|
|
33697
|
+
}
|
|
33611
33698
|
}
|
|
33612
33699
|
var fastUri = {
|
|
33613
33700
|
SCHEMES,
|
|
@@ -56350,6 +56437,15 @@ init_cjs_shims();
|
|
|
56350
56437
|
var fs51 = __toESM(require("fs"));
|
|
56351
56438
|
var path52 = __toESM(require("path"));
|
|
56352
56439
|
init_src();
|
|
56440
|
+
var BUILD_PROGRESS_ARTIFACT = path52.join("planning", "BUILD_PROGRESS.md");
|
|
56441
|
+
var VISIBLE_WORK_CHECKPOINTS = [
|
|
56442
|
+
"Announce the active task and intended checkpoints in the chat or terminal.",
|
|
56443
|
+
"Inspect the relevant files and summarize what changed before editing.",
|
|
56444
|
+
"Patch the implementation and keep unrelated files untouched.",
|
|
56445
|
+
"Add or update focused tests when the task changes behavior.",
|
|
56446
|
+
"Run the relevant verification commands and report pass/fail output.",
|
|
56447
|
+
"Update the build state with `bootspring build done` only after verification."
|
|
56448
|
+
];
|
|
56353
56449
|
function getPlanningSurfaceStatus() {
|
|
56354
56450
|
const planningDir = path52.join(process.cwd(), "planning");
|
|
56355
56451
|
return {
|
|
@@ -56681,6 +56777,97 @@ function getTaskEntriesFromState(state) {
|
|
|
56681
56777
|
estimatedTurns: task.estimatedTurns
|
|
56682
56778
|
}));
|
|
56683
56779
|
}
|
|
56780
|
+
function getStatusCounts(tasks) {
|
|
56781
|
+
const completed = tasks.filter((t) => normalizeStatus(t.status) === "completed").length;
|
|
56782
|
+
const skipped = tasks.filter((t) => normalizeStatus(t.status) === "skipped").length;
|
|
56783
|
+
const blocked = tasks.filter((t) => normalizeStatus(t.status) === "blocked").length;
|
|
56784
|
+
const pending = tasks.filter((t) => normalizeStatus(t.status) === "pending").length;
|
|
56785
|
+
const inProgress = tasks.filter((t) => normalizeStatus(t.status) === "in_progress").length;
|
|
56786
|
+
const total = tasks.length;
|
|
56787
|
+
const closed = completed + skipped;
|
|
56788
|
+
const pct2 = total > 0 ? Math.round(closed / total * 100) : 0;
|
|
56789
|
+
return { completed, skipped, blocked, pending, inProgress, total, closed, pct: pct2 };
|
|
56790
|
+
}
|
|
56791
|
+
function renderProgressBar(pct2, width = 30) {
|
|
56792
|
+
const clamped = Math.max(0, Math.min(100, pct2));
|
|
56793
|
+
const filled = Math.round(clamped / 100 * width);
|
|
56794
|
+
return `[${"#".repeat(filled)}${".".repeat(width - filled)}] ${clamped}%`;
|
|
56795
|
+
}
|
|
56796
|
+
function getVisibleTask(task) {
|
|
56797
|
+
if (!task?.id || !task?.title) return null;
|
|
56798
|
+
return {
|
|
56799
|
+
id: task.id,
|
|
56800
|
+
title: task.title,
|
|
56801
|
+
phase: task.phase,
|
|
56802
|
+
complexity: task.complexity,
|
|
56803
|
+
status: normalizeStatus(task.status),
|
|
56804
|
+
description: task.description,
|
|
56805
|
+
source: task.source,
|
|
56806
|
+
sourceSection: task.sourceSection,
|
|
56807
|
+
acceptanceCriteria: task.acceptanceCriteria ?? [],
|
|
56808
|
+
dependencies: task.dependencies ?? [],
|
|
56809
|
+
estimatedTokens: task.estimatedTokens,
|
|
56810
|
+
estimatedTurns: task.estimatedTurns
|
|
56811
|
+
};
|
|
56812
|
+
}
|
|
56813
|
+
function writeBuildProgressArtifact(state, task, event) {
|
|
56814
|
+
const dir = path52.join(process.cwd(), "planning");
|
|
56815
|
+
if (!fs51.existsSync(dir)) fs51.mkdirSync(dir, { recursive: true });
|
|
56816
|
+
const tasks = getTaskEntriesFromState(state);
|
|
56817
|
+
const counts = getStatusCounts(tasks);
|
|
56818
|
+
const visibleTask = getVisibleTask(task) ?? tasks.find((t) => normalizeStatus(t.status) === "in_progress") ?? tasks.find((t) => normalizeStatus(t.status) === "pending") ?? null;
|
|
56819
|
+
const progressPath = path52.join(process.cwd(), BUILD_PROGRESS_ARTIFACT);
|
|
56820
|
+
const updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
56821
|
+
const artifact = [
|
|
56822
|
+
"# Build Progress",
|
|
56823
|
+
"",
|
|
56824
|
+
`Updated: ${updated}`,
|
|
56825
|
+
`Event: ${event}`,
|
|
56826
|
+
`Project: ${state.projectName ?? path52.basename(process.cwd())}`,
|
|
56827
|
+
`Status: ${deriveBuildStatus(state.status, counts)}`,
|
|
56828
|
+
`Overall: ${counts.closed}/${counts.total} ${renderProgressBar(counts.pct)}`,
|
|
56829
|
+
"",
|
|
56830
|
+
"## Active Task",
|
|
56831
|
+
"",
|
|
56832
|
+
visibleTask ? [
|
|
56833
|
+
`- ID: ${visibleTask.id}`,
|
|
56834
|
+
`- Title: ${visibleTask.title}`,
|
|
56835
|
+
`- Status: ${normalizeStatus(visibleTask.status)}`,
|
|
56836
|
+
`- Phase: ${visibleTask.phase ?? "Unknown"}`,
|
|
56837
|
+
visibleTask.description ? `- Description: ${visibleTask.description}` : null,
|
|
56838
|
+
visibleTask.dependencies?.length ? `- Dependencies: ${visibleTask.dependencies.join(", ")}` : null
|
|
56839
|
+
].filter(Boolean).join("\n") : "No active or pending task.",
|
|
56840
|
+
"",
|
|
56841
|
+
"## Acceptance Criteria",
|
|
56842
|
+
"",
|
|
56843
|
+
visibleTask?.acceptanceCriteria?.length ? visibleTask.acceptanceCriteria.map((item) => `- [ ] ${item}`).join("\n") : "- [ ] No task-specific acceptance criteria recorded.",
|
|
56844
|
+
"",
|
|
56845
|
+
"## Visible Work Contract",
|
|
56846
|
+
"",
|
|
56847
|
+
VISIBLE_WORK_CHECKPOINTS.map((item) => `- [ ] ${item}`).join("\n"),
|
|
56848
|
+
"",
|
|
56849
|
+
"## Host Notes",
|
|
56850
|
+
"",
|
|
56851
|
+
"- Claude Code and Codex CLI can stream terminal output directly.",
|
|
56852
|
+
"- Codex Desktop, Claude Desktop, and other MCP hosts should keep a visible plan/checklist updated while the terminal is hidden.",
|
|
56853
|
+
"- Update this file after inspection, edits, verification, and completion so the workspace has a durable progress surface.",
|
|
56854
|
+
""
|
|
56855
|
+
].join("\n");
|
|
56856
|
+
fs51.writeFileSync(progressPath, artifact);
|
|
56857
|
+
return BUILD_PROGRESS_ARTIFACT;
|
|
56858
|
+
}
|
|
56859
|
+
function printVisibleWorkNotice(artifactPath) {
|
|
56860
|
+
print.header("Visible Work");
|
|
56861
|
+
print.info(`Progress artifact: ${artifactPath}`);
|
|
56862
|
+
print.info("Desktop agents should keep a visible plan/checklist updated while they inspect, edit, verify, and mark the task done.");
|
|
56863
|
+
print.info("This mirrors Claude Code/Codex CLI-style streaming even when the host hides terminal output.");
|
|
56864
|
+
}
|
|
56865
|
+
function deriveBuildStatus(stateStatus, counts) {
|
|
56866
|
+
if (counts.total > 0 && counts.pending === 0 && counts.inProgress === 0) {
|
|
56867
|
+
return counts.blocked > 0 ? "blocked" : "completed";
|
|
56868
|
+
}
|
|
56869
|
+
return stateStatus ?? "unknown";
|
|
56870
|
+
}
|
|
56684
56871
|
function extractSupplementaryContent(queueContent) {
|
|
56685
56872
|
const lines = queueContent.split("\n");
|
|
56686
56873
|
const sections = [];
|
|
@@ -56854,7 +57041,8 @@ function registerBuildCommand(program3) {
|
|
|
56854
57041
|
build.command("status").description("Check build progress").option("--json", "Output as JSON").action((opts) => {
|
|
56855
57042
|
const state = loadBuildStateWithAutoSync(opts.json);
|
|
56856
57043
|
const planningTasks = loadPlanningTasks();
|
|
56857
|
-
const
|
|
57044
|
+
const stateTasks = getTaskEntriesFromState(state);
|
|
57045
|
+
const tasks = stateTasks.length > 0 ? stateTasks : planningTasks.tasks;
|
|
56858
57046
|
if (!state) {
|
|
56859
57047
|
if (opts.json) {
|
|
56860
57048
|
console.log(JSON.stringify({ error: "no_build_state", tasks: [] }));
|
|
@@ -56873,14 +57061,8 @@ function registerBuildCommand(program3) {
|
|
|
56873
57061
|
}
|
|
56874
57062
|
return;
|
|
56875
57063
|
}
|
|
56876
|
-
const completed = tasks
|
|
56877
|
-
|
|
56878
|
-
return s === "completed" || s === "done";
|
|
56879
|
-
}).length;
|
|
56880
|
-
const pending = tasks.filter((t) => t.status.toLowerCase() === "pending").length;
|
|
56881
|
-
const inProgress = tasks.filter((t) => t.status.toLowerCase() === "in_progress").length;
|
|
56882
|
-
const total = tasks.length;
|
|
56883
|
-
const pct2 = total > 0 ? Math.round(completed / total * 100) : 0;
|
|
57064
|
+
const { completed, skipped, blocked, pending, inProgress, total, closed, pct: pct2 } = getStatusCounts(tasks);
|
|
57065
|
+
const derivedStatus = deriveBuildStatus(state.status, { completed, skipped, blocked, pending, inProgress, total, closed, pct: pct2 });
|
|
56884
57066
|
const iteration = state.loopSession?.currentIteration ?? 0;
|
|
56885
57067
|
const maxIter = state.loopSession?.maxIterations ?? 50;
|
|
56886
57068
|
const sessionId = state.loopSession?.sessionId ?? "N/A";
|
|
@@ -56888,12 +57070,15 @@ function registerBuildCommand(program3) {
|
|
|
56888
57070
|
const currentTask2 = tasks.find((t) => t.status.toLowerCase() === "in_progress");
|
|
56889
57071
|
console.log(JSON.stringify({
|
|
56890
57072
|
project: state.projectName ?? "Unknown",
|
|
56891
|
-
status:
|
|
57073
|
+
status: derivedStatus,
|
|
56892
57074
|
phase: formatPhaseName(state.currentPhase ?? getCurrentPhase(state.implementationQueue)),
|
|
56893
57075
|
planningSource: formatPlanningSourceLabel(planningTasks.source ?? (state ? "BUILD_STATE.json" : null)),
|
|
56894
57076
|
completed,
|
|
57077
|
+
skipped,
|
|
57078
|
+
blocked,
|
|
56895
57079
|
pending,
|
|
56896
57080
|
inProgress,
|
|
57081
|
+
closed,
|
|
56897
57082
|
total,
|
|
56898
57083
|
pct: pct2,
|
|
56899
57084
|
iteration,
|
|
@@ -56906,10 +57091,12 @@ function registerBuildCommand(program3) {
|
|
|
56906
57091
|
print.header("Build Status");
|
|
56907
57092
|
printLegacyQueueWarningIfNeeded();
|
|
56908
57093
|
print.info(`Project: ${state.projectName ?? "Unknown"}`);
|
|
56909
|
-
print.info(`Status: ${
|
|
57094
|
+
print.info(`Status: ${derivedStatus}`);
|
|
56910
57095
|
print.info(`Phase: ${formatPhaseName(state.currentPhase ?? getCurrentPhase(state.implementationQueue))}`);
|
|
56911
57096
|
print.info(`Planning Source: ${formatPlanningSourceLabel(planningTasks.source ?? (state ? "BUILD_STATE.json" : null))}`);
|
|
56912
|
-
print.info(`Progress: ${
|
|
57097
|
+
print.info(`Progress: ${closed}/${total} (${pct2}%)`);
|
|
57098
|
+
if (skipped > 0) print.info(`Skipped: ${skipped}`);
|
|
57099
|
+
if (blocked > 0) print.info(`Blocked: ${blocked}`);
|
|
56913
57100
|
print.info(`Pending: ${pending}`);
|
|
56914
57101
|
if (inProgress > 0) print.info(`In Progress: ${inProgress}`);
|
|
56915
57102
|
if (iteration > maxIter) {
|
|
@@ -56926,6 +57113,40 @@ function registerBuildCommand(program3) {
|
|
|
56926
57113
|
const filled = Math.round(pct2 / 100 * barWidth);
|
|
56927
57114
|
const bar = "#".repeat(filled) + ".".repeat(barWidth - filled);
|
|
56928
57115
|
console.log(` [${bar}] ${pct2}%`);
|
|
57116
|
+
const artifactPath = writeBuildProgressArtifact(state, currentTask, "status");
|
|
57117
|
+
print.info(`Progress artifact: ${artifactPath}`);
|
|
57118
|
+
});
|
|
57119
|
+
build.command("progress").description("Show the visible build progress artifact for desktop/agent hosts").option("--json", "Output artifact metadata as JSON").action((opts) => {
|
|
57120
|
+
const state = loadBuildStateWithAutoSync(opts.json);
|
|
57121
|
+
if (!state) {
|
|
57122
|
+
if (opts.json) {
|
|
57123
|
+
console.log(JSON.stringify({ error: "no_build_state", artifact: null }));
|
|
57124
|
+
} else {
|
|
57125
|
+
print.error("No build state found");
|
|
57126
|
+
}
|
|
57127
|
+
return;
|
|
57128
|
+
}
|
|
57129
|
+
const tasks = getTaskEntriesFromState(state);
|
|
57130
|
+
const currentTask = tasks.find((t) => normalizeStatus(t.status) === "in_progress") ?? tasks.find((t) => normalizeStatus(t.status) === "pending") ?? null;
|
|
57131
|
+
const artifactPath = writeBuildProgressArtifact(state, currentTask, "progress");
|
|
57132
|
+
if (opts.json) {
|
|
57133
|
+
const counts = getStatusCounts(tasks);
|
|
57134
|
+
console.log(JSON.stringify({
|
|
57135
|
+
artifact: artifactPath,
|
|
57136
|
+
progress: {
|
|
57137
|
+
completed: counts.completed,
|
|
57138
|
+
pending: counts.pending,
|
|
57139
|
+
inProgress: counts.inProgress,
|
|
57140
|
+
total: counts.total,
|
|
57141
|
+
percent: counts.pct
|
|
57142
|
+
},
|
|
57143
|
+
currentTask: currentTask ? { id: currentTask.id, title: currentTask.title, status: currentTask.status } : null,
|
|
57144
|
+
visibleWorkCheckpoints: VISIBLE_WORK_CHECKPOINTS
|
|
57145
|
+
}, null, 2));
|
|
57146
|
+
return;
|
|
57147
|
+
}
|
|
57148
|
+
print.header("Build Progress");
|
|
57149
|
+
console.log(fs51.readFileSync(path52.join(process.cwd(), artifactPath), "utf-8"));
|
|
56929
57150
|
});
|
|
56930
57151
|
build.command("task").description("Show the current task").action(() => {
|
|
56931
57152
|
printLegacyQueueWarningIfNeeded();
|
|
@@ -56944,7 +57165,7 @@ function registerBuildCommand(program3) {
|
|
|
56944
57165
|
print.info(`Complexity: ${next.complexity ?? "Unknown"}`);
|
|
56945
57166
|
if (current) print.info(`Status: ${next.status}`);
|
|
56946
57167
|
});
|
|
56947
|
-
build.command("done").alias("complete").description("Mark the current task as done
|
|
57168
|
+
build.command("done").alias("complete").description("Mark the current task as done, auto-commit, and show next task").option("--no-commit", "Skip auto-commit").action((opts) => {
|
|
56948
57169
|
const state = loadBuildStateWithAutoSync();
|
|
56949
57170
|
if (!state) {
|
|
56950
57171
|
print.error("No build state found");
|
|
@@ -56958,18 +57179,59 @@ function registerBuildCommand(program3) {
|
|
|
56958
57179
|
return;
|
|
56959
57180
|
}
|
|
56960
57181
|
const currentId = queue[idx].id;
|
|
57182
|
+
const currentTitle = queue[idx].title;
|
|
57183
|
+
if (opts.commit !== false) {
|
|
57184
|
+
try {
|
|
57185
|
+
const { execSync: execSync15 } = require("child_process");
|
|
57186
|
+
const cwd = process.cwd();
|
|
57187
|
+
const status = execSync15("git status --porcelain", { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
57188
|
+
if (status) {
|
|
57189
|
+
execSync15("git add -A", { cwd, stdio: ["ignore", "ignore", "ignore"] });
|
|
57190
|
+
const msg = `feat(${currentId}): ${currentTitle}`;
|
|
57191
|
+
execSync15(`git commit -m ${JSON.stringify(msg)}`, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
57192
|
+
print.success(`Committed: ${msg}`);
|
|
57193
|
+
}
|
|
57194
|
+
} catch {
|
|
57195
|
+
}
|
|
57196
|
+
}
|
|
56961
57197
|
queue[idx].status = "completed";
|
|
56962
57198
|
if (state.loopSession) {
|
|
56963
57199
|
state.loopSession.currentIteration = (state.loopSession.currentIteration ?? 0) + 1;
|
|
56964
57200
|
}
|
|
56965
57201
|
saveBuildState(state);
|
|
56966
|
-
const nextTask =
|
|
57202
|
+
const nextTask = (() => {
|
|
57203
|
+
for (const task of queue) {
|
|
57204
|
+
if (task.status !== "pending") continue;
|
|
57205
|
+
if (task.dependencies?.length) {
|
|
57206
|
+
const allDone = task.dependencies.every((depId) => {
|
|
57207
|
+
const dep = queue.find((t) => t.id === depId);
|
|
57208
|
+
return dep && dep.status === "completed";
|
|
57209
|
+
});
|
|
57210
|
+
if (!allDone) continue;
|
|
57211
|
+
}
|
|
57212
|
+
return task;
|
|
57213
|
+
}
|
|
57214
|
+
return null;
|
|
57215
|
+
})();
|
|
56967
57216
|
print.success(`Task ${currentId} marked as done`);
|
|
56968
57217
|
if (nextTask) {
|
|
56969
|
-
|
|
57218
|
+
console.log("");
|
|
57219
|
+
print.header("Next Task");
|
|
57220
|
+
print.info(`ID: ${nextTask.id}`);
|
|
57221
|
+
print.info(`Title: ${nextTask.title}`);
|
|
57222
|
+
if (nextTask.phase) print.info(`Phase: ${nextTask.phase}`);
|
|
57223
|
+
if (nextTask.description) print.info(`Description: ${nextTask.description}`);
|
|
57224
|
+
if (nextTask.acceptanceCriteria && nextTask.acceptanceCriteria.length > 0) {
|
|
57225
|
+
print.info("Acceptance criteria:");
|
|
57226
|
+
for (const ac of nextTask.acceptanceCriteria) {
|
|
57227
|
+
console.log(` [ ] ${ac}`);
|
|
57228
|
+
}
|
|
57229
|
+
}
|
|
56970
57230
|
} else {
|
|
56971
57231
|
print.success("No more pending tasks!");
|
|
56972
57232
|
}
|
|
57233
|
+
const artifactPath = writeBuildProgressArtifact(state, nextTask ?? queue[idx], nextTask ? "task_completed_next_available" : "task_completed_all_done");
|
|
57234
|
+
printVisibleWorkNotice(artifactPath);
|
|
56973
57235
|
});
|
|
56974
57236
|
build.command("plan").description("View the full build plan").action(() => {
|
|
56975
57237
|
printLegacyQueueWarningIfNeeded();
|
|
@@ -57041,6 +57303,8 @@ function registerBuildCommand(program3) {
|
|
|
57041
57303
|
const inProgress = queue.find((t2) => t2.status === "in_progress");
|
|
57042
57304
|
if (inProgress) {
|
|
57043
57305
|
print.warning(`Task ${inProgress.id} is already in progress: ${inProgress.title}`);
|
|
57306
|
+
const artifactPath2 = writeBuildProgressArtifact(state, inProgress, "already_in_progress");
|
|
57307
|
+
printVisibleWorkNotice(artifactPath2);
|
|
57044
57308
|
return;
|
|
57045
57309
|
}
|
|
57046
57310
|
const idx = queue.findIndex((t2) => t2.status === "pending");
|
|
@@ -57068,6 +57332,8 @@ function registerBuildCommand(program3) {
|
|
|
57068
57332
|
console.log(` [ ] ${ac}`);
|
|
57069
57333
|
}
|
|
57070
57334
|
}
|
|
57335
|
+
const artifactPath = writeBuildProgressArtifact(state, t, "task_started");
|
|
57336
|
+
printVisibleWorkNotice(artifactPath);
|
|
57071
57337
|
});
|
|
57072
57338
|
build.command("pause").description("Pause the build loop").action(() => {
|
|
57073
57339
|
const state = loadBuildState();
|
|
@@ -57852,6 +58118,71 @@ ${supplementaryContent}
|
|
|
57852
58118
|
print.dim(`${snapshot.completedCount} completed, ${snapshot.pendingCount} pending`);
|
|
57853
58119
|
}
|
|
57854
58120
|
});
|
|
58121
|
+
build.command("handoff").description("Save or load session handoff state for cross-session continuity").option("--save", "Save current session state").option("--notes <notes>", "Notes for the next session").option("--json", "Output as JSON").action((opts) => {
|
|
58122
|
+
const handoffPath = path52.join(process.cwd(), "planning", ".handoff.json");
|
|
58123
|
+
if (opts.save) {
|
|
58124
|
+
const state = loadBuildStateWithAutoSync(true);
|
|
58125
|
+
const queue = state?.implementationQueue ?? [];
|
|
58126
|
+
const inProgress = queue.find((t) => t.status === "in_progress");
|
|
58127
|
+
const lastCompleted = [...queue].reverse().find((t) => t.status === "completed");
|
|
58128
|
+
let filesModified = [];
|
|
58129
|
+
try {
|
|
58130
|
+
const { execSync: execSync15 } = require("child_process");
|
|
58131
|
+
const gitDiff = execSync15("git diff --name-only HEAD~1 2>/dev/null || true", { encoding: "utf8" }).trim();
|
|
58132
|
+
if (gitDiff) filesModified = gitDiff.split("\n").filter(Boolean);
|
|
58133
|
+
} catch {
|
|
58134
|
+
}
|
|
58135
|
+
const handoff = {
|
|
58136
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
58137
|
+
lastTaskId: lastCompleted?.id ?? null,
|
|
58138
|
+
inProgressTask: inProgress?.id ?? null,
|
|
58139
|
+
inProgressTitle: inProgress?.title ?? null,
|
|
58140
|
+
filesModified,
|
|
58141
|
+
notes: opts.notes ?? "",
|
|
58142
|
+
blockers: [],
|
|
58143
|
+
progress: {
|
|
58144
|
+
completed: queue.filter((t) => t.status === "completed").length,
|
|
58145
|
+
pending: queue.filter((t) => t.status === "pending").length,
|
|
58146
|
+
total: queue.length
|
|
58147
|
+
}
|
|
58148
|
+
};
|
|
58149
|
+
const dir = path52.dirname(handoffPath);
|
|
58150
|
+
if (!fs51.existsSync(dir)) fs51.mkdirSync(dir, { recursive: true });
|
|
58151
|
+
fs51.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
|
|
58152
|
+
if (opts.json) {
|
|
58153
|
+
console.log(JSON.stringify({ saved: true, handoff }));
|
|
58154
|
+
} else {
|
|
58155
|
+
print.success("Session handoff saved");
|
|
58156
|
+
if (inProgress) print.info(`In-progress: ${inProgress.id} \u2014 ${inProgress.title}`);
|
|
58157
|
+
if (opts.notes) print.info(`Notes: ${opts.notes}`);
|
|
58158
|
+
}
|
|
58159
|
+
return;
|
|
58160
|
+
}
|
|
58161
|
+
if (!fs51.existsSync(handoffPath)) {
|
|
58162
|
+
if (opts.json) {
|
|
58163
|
+
console.log(JSON.stringify({ hasHandoff: false }));
|
|
58164
|
+
} else {
|
|
58165
|
+
print.info("No previous session handoff found.");
|
|
58166
|
+
}
|
|
58167
|
+
return;
|
|
58168
|
+
}
|
|
58169
|
+
try {
|
|
58170
|
+
const handoff = JSON.parse(fs51.readFileSync(handoffPath, "utf-8"));
|
|
58171
|
+
if (opts.json) {
|
|
58172
|
+
console.log(JSON.stringify({ hasHandoff: true, handoff }));
|
|
58173
|
+
} else {
|
|
58174
|
+
print.header("Session Handoff");
|
|
58175
|
+
print.info(`Saved: ${handoff.timestamp}`);
|
|
58176
|
+
if (handoff.inProgressTask) print.info(`In-progress: ${handoff.inProgressTask} \u2014 ${handoff.inProgressTitle ?? ""}`);
|
|
58177
|
+
if (handoff.lastTaskId) print.info(`Last completed: ${handoff.lastTaskId}`);
|
|
58178
|
+
if (handoff.notes) print.info(`Notes: ${handoff.notes}`);
|
|
58179
|
+
if (handoff.progress) print.info(`Progress: ${handoff.progress.completed}/${handoff.progress.total}`);
|
|
58180
|
+
if (handoff.filesModified?.length) print.dim(`Files: ${handoff.filesModified.slice(0, 5).join(", ")}${handoff.filesModified.length > 5 ? ` +${handoff.filesModified.length - 5} more` : ""}`);
|
|
58181
|
+
}
|
|
58182
|
+
} catch {
|
|
58183
|
+
print.error("Failed to read handoff file");
|
|
58184
|
+
}
|
|
58185
|
+
});
|
|
57855
58186
|
build.action(() => {
|
|
57856
58187
|
build.outputHelp();
|
|
57857
58188
|
});
|
|
@@ -61679,7 +62010,7 @@ function generateDocTemplate(docType, projectName) {
|
|
|
61679
62010
|
`;
|
|
61680
62011
|
}
|
|
61681
62012
|
function registerGoCommand(program3) {
|
|
61682
|
-
program3.command("go").description("One-command setup: detect \u2192 seed \u2192 generate \u2192 build init").option("--preset <preset>", "Document set (essential, startup, full, technical)", "startup").option("--force", "Overwrite existing files").option("--edit-first", "Stop after creating templates for manual editing").action((opts) => {
|
|
62013
|
+
program3.command("go").description("One-command setup: detect \u2192 seed \u2192 generate \u2192 build init").argument("[pitch]", "Optional project pitch \u2014 uses AI to fill all context docs").option("--preset <preset>", "Document set (essential, startup, full, technical)", "startup").option("--provider <provider>", "AI provider: anthropic, openai, or gemini", "anthropic").option("--force", "Overwrite existing files").option("--edit-first", "Stop after creating templates for manual editing").action(async (pitch, opts) => {
|
|
61683
62014
|
console.log("");
|
|
61684
62015
|
console.log(`${COLORS.cyan}${COLORS.bold}\u26A1 Bootspring Go${COLORS.reset}`);
|
|
61685
62016
|
console.log(`${COLORS.dim}Setting up your project in one shot...${COLORS.reset}`);
|
|
@@ -61699,26 +62030,56 @@ function registerGoCommand(program3) {
|
|
|
61699
62030
|
print.dim(" No codebase detected (starting fresh)");
|
|
61700
62031
|
}
|
|
61701
62032
|
console.log("");
|
|
61702
|
-
|
|
61703
|
-
|
|
61704
|
-
|
|
61705
|
-
|
|
61706
|
-
|
|
61707
|
-
|
|
61708
|
-
|
|
61709
|
-
|
|
61710
|
-
|
|
61711
|
-
|
|
61712
|
-
|
|
61713
|
-
if (fs64.existsSync(filePath) && !opts.force) {
|
|
61714
|
-
skipped++;
|
|
61715
|
-
continue;
|
|
62033
|
+
if (pitch) {
|
|
62034
|
+
console.log(`${COLORS.bold}[2/5] AI Synthesize${COLORS.reset}`);
|
|
62035
|
+
print.info(` Pitch: "${pitch}"`);
|
|
62036
|
+
let api;
|
|
62037
|
+
try {
|
|
62038
|
+
const core = await Promise.resolve().then(() => (init_src2(), src_exports2));
|
|
62039
|
+
api = core.api;
|
|
62040
|
+
} catch {
|
|
62041
|
+
print.error(" Failed to load API client. Run `bootspring auth login` first.");
|
|
62042
|
+
print.info(" Falling back to empty templates...");
|
|
62043
|
+
api = null;
|
|
61716
62044
|
}
|
|
61717
|
-
|
|
61718
|
-
|
|
62045
|
+
if (api) {
|
|
62046
|
+
try {
|
|
62047
|
+
const result = await api.request("POST", "/seed/synthesize", {
|
|
62048
|
+
pitch,
|
|
62049
|
+
projectName,
|
|
62050
|
+
stack,
|
|
62051
|
+
features,
|
|
62052
|
+
preset: opts.preset,
|
|
62053
|
+
provider: opts.provider
|
|
62054
|
+
}, { timeout: 12e4 });
|
|
62055
|
+
if (result?.documents && Object.keys(result.documents).length > 0) {
|
|
62056
|
+
if (!fs64.existsSync(contextDir)) fs64.mkdirSync(contextDir, { recursive: true });
|
|
62057
|
+
let written = 0;
|
|
62058
|
+
for (const [key, content] of Object.entries(result.documents)) {
|
|
62059
|
+
if (typeof content !== "string") continue;
|
|
62060
|
+
const filePath = path65.join(contextDir, `${key}.md`);
|
|
62061
|
+
if (fs64.existsSync(filePath) && !opts.force) continue;
|
|
62062
|
+
fs64.writeFileSync(filePath, content);
|
|
62063
|
+
written++;
|
|
62064
|
+
}
|
|
62065
|
+
print.success(` Generated ${written} context doc(s) with AI (${result.metadata?.durationMs || "?"}ms)`);
|
|
62066
|
+
} else {
|
|
62067
|
+
print.warning(" AI returned no documents \u2014 falling back to templates");
|
|
62068
|
+
generateFallbackTemplates(contextDir, opts.preset, projectName, opts.force);
|
|
62069
|
+
}
|
|
62070
|
+
} catch (err) {
|
|
62071
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
62072
|
+
print.warning(` AI generation failed: ${msg}`);
|
|
62073
|
+
print.info(" Falling back to templates...");
|
|
62074
|
+
generateFallbackTemplates(contextDir, opts.preset, projectName, opts.force);
|
|
62075
|
+
}
|
|
62076
|
+
} else {
|
|
62077
|
+
generateFallbackTemplates(contextDir, opts.preset, projectName, opts.force);
|
|
62078
|
+
}
|
|
62079
|
+
} else {
|
|
62080
|
+
console.log(`${COLORS.bold}[2/5] Seed templates${COLORS.reset}`);
|
|
62081
|
+
generateFallbackTemplates(contextDir, opts.preset, projectName, opts.force);
|
|
61719
62082
|
}
|
|
61720
|
-
if (generated > 0) print.success(` Created ${generated} template(s) in ${CONTEXT_DIR}/`);
|
|
61721
|
-
if (skipped > 0) print.dim(` Skipped ${skipped} (already exist)`);
|
|
61722
62083
|
console.log("");
|
|
61723
62084
|
if (opts.editFirst) {
|
|
61724
62085
|
print.info("Templates created. Edit them, then run:");
|
|
@@ -61771,13 +62132,39 @@ function registerGoCommand(program3) {
|
|
|
61771
62132
|
console.log("");
|
|
61772
62133
|
console.log(`${COLORS.green}${COLORS.bold}Setup complete!${COLORS.reset}`);
|
|
61773
62134
|
console.log("");
|
|
61774
|
-
|
|
61775
|
-
|
|
61776
|
-
|
|
61777
|
-
|
|
62135
|
+
if (pitch) {
|
|
62136
|
+
console.log(`${COLORS.bold}Next:${COLORS.reset}`);
|
|
62137
|
+
console.log(" 1. Review .bootspring/context/ \u2014 tweak anything the AI got wrong");
|
|
62138
|
+
console.log(" 2. Edit planning/TODO.md with your real tasks");
|
|
62139
|
+
console.log(" 3. bootspring build next # Start the first task");
|
|
62140
|
+
} else {
|
|
62141
|
+
console.log(`${COLORS.bold}Next:${COLORS.reset}`);
|
|
62142
|
+
console.log(' 1. Edit the docs in .bootspring/context/ (or run: bootspring seed synthesize "your idea")');
|
|
62143
|
+
console.log(" 2. bootspring seed merge # Re-merge after edits");
|
|
62144
|
+
console.log(" 3. bootspring build next # Start the first task");
|
|
62145
|
+
}
|
|
61778
62146
|
console.log("");
|
|
61779
62147
|
});
|
|
61780
62148
|
}
|
|
62149
|
+
function generateFallbackTemplates(contextDir, preset, projectName, force) {
|
|
62150
|
+
if (!fs64.existsSync(contextDir)) fs64.mkdirSync(contextDir, { recursive: true });
|
|
62151
|
+
const docs = PRESETS[preset] || PRESETS.startup;
|
|
62152
|
+
let generated = 0;
|
|
62153
|
+
let skipped = 0;
|
|
62154
|
+
for (const docType of docs) {
|
|
62155
|
+
const meta3 = CONTEXT_DOCS[docType];
|
|
62156
|
+
if (!meta3) continue;
|
|
62157
|
+
const filePath = path65.join(contextDir, meta3.name);
|
|
62158
|
+
if (fs64.existsSync(filePath) && !force) {
|
|
62159
|
+
skipped++;
|
|
62160
|
+
continue;
|
|
62161
|
+
}
|
|
62162
|
+
fs64.writeFileSync(filePath, generateDocTemplate(docType, projectName));
|
|
62163
|
+
generated++;
|
|
62164
|
+
}
|
|
62165
|
+
if (generated > 0) print.success(` Created ${generated} template(s) in .bootspring/context/`);
|
|
62166
|
+
if (skipped > 0) print.dim(` Skipped ${skipped} (already exist)`);
|
|
62167
|
+
}
|
|
61781
62168
|
|
|
61782
62169
|
// src/commands/seed.ts
|
|
61783
62170
|
var CONTEXT_DOCS2 = {
|
|
@@ -62287,7 +62674,7 @@ ${COLORS.bold}Detected Features:${COLORS.reset}`);
|
|
|
62287
62674
|
console.log("");
|
|
62288
62675
|
print.info(`${generated} generated, ${skipped} skipped in ${CONTEXT_DIR2}/`);
|
|
62289
62676
|
});
|
|
62290
|
-
seed.command("merge").
|
|
62677
|
+
seed.command("merge").description("Merge context documents into planning/SEED.md").option("--output <path>", "Output file", "planning/SEED.md").option("--force", "Overwrite existing seed file").action((opts) => {
|
|
62291
62678
|
print.header("Seed Merge");
|
|
62292
62679
|
const cwd = process.cwd();
|
|
62293
62680
|
const contextDir = getContextDir();
|
|
@@ -62553,10 +62940,122 @@ ${COLORS.bold}Detected Features:${COLORS.reset}`);
|
|
|
62553
62940
|
print.success("Document looks good!");
|
|
62554
62941
|
}
|
|
62555
62942
|
});
|
|
62943
|
+
seed.command("synthesize").alias("ai").description("Generate all context docs from a one-liner pitch using AI").argument("<pitch>", "Your project idea in one sentence").option("--preset <preset>", "Document set (essential, startup, full, technical)", "startup").option("--provider <provider>", "AI provider: anthropic, openai, or gemini", "anthropic").option("--force", "Overwrite existing context docs").option("--json", "Output as JSON").action(async (pitch, opts) => {
|
|
62944
|
+
let api;
|
|
62945
|
+
try {
|
|
62946
|
+
const core = await Promise.resolve().then(() => (init_src2(), src_exports2));
|
|
62947
|
+
api = core.api;
|
|
62948
|
+
} catch {
|
|
62949
|
+
print.error("Failed to load API client. Run `bootspring auth login` first.");
|
|
62950
|
+
return;
|
|
62951
|
+
}
|
|
62952
|
+
const cwd = process.cwd();
|
|
62953
|
+
const contextDir = path66.join(cwd, CONTEXT_DIR2);
|
|
62954
|
+
const { name: projectName, stack, features } = detectStack2(cwd);
|
|
62955
|
+
if (!opts.json) {
|
|
62956
|
+
print.header("Seed Synthesize");
|
|
62957
|
+
print.info(`Pitch: "${pitch}"`);
|
|
62958
|
+
if (stack.length > 0) print.dim(`Stack: ${stack.join(", ")}`);
|
|
62959
|
+
console.log("");
|
|
62960
|
+
}
|
|
62961
|
+
const spinner = opts.json ? null : createSpinner("Generating context docs with AI...").start();
|
|
62962
|
+
try {
|
|
62963
|
+
const result = await api.request("POST", "/seed/synthesize", {
|
|
62964
|
+
pitch,
|
|
62965
|
+
projectName,
|
|
62966
|
+
stack,
|
|
62967
|
+
features,
|
|
62968
|
+
preset: opts.preset,
|
|
62969
|
+
provider: opts.provider
|
|
62970
|
+
}, { timeout: 12e4 });
|
|
62971
|
+
if (!result?.documents || Object.keys(result.documents).length === 0) {
|
|
62972
|
+
spinner?.fail("AI returned no documents");
|
|
62973
|
+
print.error("No documents were generated. Try rephrasing your pitch.");
|
|
62974
|
+
return;
|
|
62975
|
+
}
|
|
62976
|
+
if (!fs65.existsSync(contextDir)) {
|
|
62977
|
+
fs65.mkdirSync(contextDir, { recursive: true });
|
|
62978
|
+
}
|
|
62979
|
+
let written = 0;
|
|
62980
|
+
const writtenFiles = [];
|
|
62981
|
+
for (const [key, content] of Object.entries(result.documents)) {
|
|
62982
|
+
if (typeof content !== "string") continue;
|
|
62983
|
+
const fileName = `${key}.md`;
|
|
62984
|
+
const filePath = path66.join(contextDir, fileName);
|
|
62985
|
+
if (fs65.existsSync(filePath) && !opts.force) {
|
|
62986
|
+
if (!opts.json) print.dim(` Skipped ${fileName} (exists, use --force)`);
|
|
62987
|
+
continue;
|
|
62988
|
+
}
|
|
62989
|
+
fs65.writeFileSync(filePath, content);
|
|
62990
|
+
written++;
|
|
62991
|
+
writtenFiles.push(fileName);
|
|
62992
|
+
}
|
|
62993
|
+
spinner?.succeed(`Generated ${written} document(s) in ${result.metadata?.durationMs || "?"}ms`);
|
|
62994
|
+
if (opts.json) {
|
|
62995
|
+
console.log(JSON.stringify({
|
|
62996
|
+
documents: result.documents,
|
|
62997
|
+
files: writtenFiles,
|
|
62998
|
+
metadata: result.metadata
|
|
62999
|
+
}, null, 2));
|
|
63000
|
+
return;
|
|
63001
|
+
}
|
|
63002
|
+
console.log("");
|
|
63003
|
+
for (const f of writtenFiles) {
|
|
63004
|
+
print.success(` ${CONTEXT_DIR2}/${f}`);
|
|
63005
|
+
}
|
|
63006
|
+
if (written > 0) {
|
|
63007
|
+
console.log("");
|
|
63008
|
+
print.info("Merging into planning/SEED.md...");
|
|
63009
|
+
const validDocs = Object.values(CONTEXT_DOCS2).map((d) => d.name);
|
|
63010
|
+
const files = fs65.readdirSync(contextDir).filter((f) => f.endsWith(".md") && f !== "README.md").sort((a, b) => {
|
|
63011
|
+
const ai = validDocs.indexOf(a);
|
|
63012
|
+
const bi = validDocs.indexOf(b);
|
|
63013
|
+
return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi);
|
|
63014
|
+
});
|
|
63015
|
+
if (files.length > 0) {
|
|
63016
|
+
const sections = files.map((f) => fs65.readFileSync(path66.join(contextDir, f), "utf-8"));
|
|
63017
|
+
const planningDir = path66.join(cwd, "planning");
|
|
63018
|
+
if (!fs65.existsSync(planningDir)) fs65.mkdirSync(planningDir, { recursive: true });
|
|
63019
|
+
fs65.writeFileSync(path66.join(planningDir, "SEED.md"), sections.join("\n\n---\n\n"));
|
|
63020
|
+
print.success(` planning/SEED.md (${files.length} docs merged)`);
|
|
63021
|
+
}
|
|
63022
|
+
const genFiles = runGenerate(cwd, { force: false, full: true });
|
|
63023
|
+
if (genFiles.length > 0) {
|
|
63024
|
+
for (const f of genFiles) {
|
|
63025
|
+
print.success(` ${f}`);
|
|
63026
|
+
}
|
|
63027
|
+
}
|
|
63028
|
+
const buildResult = initBuildFromSeed(cwd);
|
|
63029
|
+
if (buildResult.initialized) {
|
|
63030
|
+
print.success(` Build initialized with ${buildResult.taskCount} tasks`);
|
|
63031
|
+
}
|
|
63032
|
+
}
|
|
63033
|
+
console.log("");
|
|
63034
|
+
console.log(`${COLORS.green}${COLORS.bold}Done!${COLORS.reset} Your project context is ready.`);
|
|
63035
|
+
console.log("");
|
|
63036
|
+
console.log(`${COLORS.bold}Next:${COLORS.reset}`);
|
|
63037
|
+
console.log(" 1. Review the docs in .bootspring/context/");
|
|
63038
|
+
console.log(" 2. Edit planning/TODO.md with your real tasks");
|
|
63039
|
+
console.log(" 3. bootspring build next # Start building");
|
|
63040
|
+
} catch (err) {
|
|
63041
|
+
spinner?.fail("AI generation failed");
|
|
63042
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
63043
|
+
if (message.includes("Cannot connect") || message.includes("ECONNREFUSED")) {
|
|
63044
|
+
print.error("Cannot reach Bootspring API. Check your internet connection.");
|
|
63045
|
+
} else if (message.includes("Authentication") || message.includes("401")) {
|
|
63046
|
+
print.error("Not authenticated. Run `bootspring auth login` first.");
|
|
63047
|
+
} else {
|
|
63048
|
+
print.error(message);
|
|
63049
|
+
}
|
|
63050
|
+
}
|
|
63051
|
+
});
|
|
62556
63052
|
seed.action(() => {
|
|
62557
63053
|
seed.outputHelp();
|
|
62558
63054
|
console.log("");
|
|
62559
|
-
console.log(`${COLORS.bold}
|
|
63055
|
+
console.log(`${COLORS.bold}Quick start (AI-powered):${COLORS.reset}`);
|
|
63056
|
+
console.log(' bootspring seed synthesize "your project idea" # AI fills all context docs');
|
|
63057
|
+
console.log("");
|
|
63058
|
+
console.log(`${COLORS.bold}Manual workflow:${COLORS.reset}`);
|
|
62560
63059
|
console.log(" bootspring seed init # Create .bootspring/context/ + templates");
|
|
62561
63060
|
console.log(" (edit/drop files) # Add your project docs");
|
|
62562
63061
|
console.log(" bootspring seed merge # Combine into planning/SEED.md");
|
|
@@ -73378,6 +73877,29 @@ ${COLORS.green}Task submitted:${COLORS.reset} ${res.id}`);
|
|
|
73378
73877
|
handleError(error50);
|
|
73379
73878
|
}
|
|
73380
73879
|
});
|
|
73880
|
+
cmd.command("priority <taskId> <level>").description("Update queued swarm task priority").option("--json", "Output as JSON").action(async (taskId, level, opts) => {
|
|
73881
|
+
if (!requireAuth4()) return;
|
|
73882
|
+
const priority = level.toLowerCase();
|
|
73883
|
+
const valid = ["critical", "high", "normal", "low"];
|
|
73884
|
+
if (!valid.includes(priority)) {
|
|
73885
|
+
print(`${COLORS.red}Invalid priority:${COLORS.reset} ${level}. Must be one of: ${valid.join(", ")}`);
|
|
73886
|
+
return;
|
|
73887
|
+
}
|
|
73888
|
+
try {
|
|
73889
|
+
const res = await api_client_exports.request("PATCH", `/swarm/tasks/${taskId}/priority`, { priority });
|
|
73890
|
+
if (opts.json) {
|
|
73891
|
+
print(JSON.stringify(res, null, 2));
|
|
73892
|
+
return;
|
|
73893
|
+
}
|
|
73894
|
+
print(`
|
|
73895
|
+
${COLORS.green}Task priority updated:${COLORS.reset} ${taskId}`);
|
|
73896
|
+
print(` Priority: ${priority}`);
|
|
73897
|
+
print(` Status: ${res.task?.status ?? "queued"}
|
|
73898
|
+
`);
|
|
73899
|
+
} catch (error50) {
|
|
73900
|
+
handleError(error50);
|
|
73901
|
+
}
|
|
73902
|
+
});
|
|
73381
73903
|
cmd.command("plan <goal>").description("Generate a GOAP execution plan from a goal").option("--parallel", "Enable parallel task groups").option("--json", "Output as JSON").action(async (goal, opts) => {
|
|
73382
73904
|
if (!requireAuth4()) return;
|
|
73383
73905
|
try {
|
|
@@ -77851,40 +78373,45 @@ var import_child_process18 = require("child_process");
|
|
|
77851
78373
|
init_src2();
|
|
77852
78374
|
var HOOK_REGISTRY = {
|
|
77853
78375
|
SessionStart: [
|
|
77854
|
-
{
|
|
77855
|
-
{
|
|
77856
|
-
{
|
|
78376
|
+
{ feature: "stats", script: "packages/session-intelligence/src/features/stats/hooks/session-start.ts" },
|
|
78377
|
+
{ feature: "suggest", script: "packages/session-intelligence/src/features/suggest/hooks/session-start.ts" },
|
|
78378
|
+
{ feature: "recall", script: "packages/session-intelligence/src/features/recall/hooks/session-start.ts" }
|
|
77857
78379
|
],
|
|
77858
78380
|
UserPromptSubmit: [
|
|
77859
|
-
{
|
|
77860
|
-
{
|
|
77861
|
-
{
|
|
78381
|
+
{ feature: "stats", script: "packages/session-intelligence/src/features/stats/hooks/user-prompt-submit.ts" },
|
|
78382
|
+
{ feature: "suggest", script: "packages/session-intelligence/src/features/suggest/hooks/user-prompt-submit.ts" },
|
|
78383
|
+
{ feature: "recall", script: "packages/session-intelligence/src/features/recall/hooks/prompt-submit.ts" }
|
|
77862
78384
|
],
|
|
77863
78385
|
PreToolUse: [
|
|
77864
|
-
{
|
|
77865
|
-
{
|
|
78386
|
+
{ feature: "suggest", script: "packages/session-intelligence/src/features/suggest/hooks/pre-tool-use.ts" },
|
|
78387
|
+
{ feature: "recall", script: "packages/session-intelligence/src/features/recall/hooks/pre-tool-use.ts" }
|
|
77866
78388
|
],
|
|
77867
78389
|
PostToolUse: [
|
|
77868
|
-
{
|
|
77869
|
-
{
|
|
78390
|
+
{ feature: "stats", script: "packages/session-intelligence/src/features/stats/hooks/post-tool-use.ts" },
|
|
78391
|
+
{ feature: "suggest", script: "packages/session-intelligence/src/features/suggest/hooks/post-tool-use.ts" }
|
|
77870
78392
|
],
|
|
77871
78393
|
PreCompact: [
|
|
77872
|
-
{
|
|
78394
|
+
{ feature: "stats", script: "packages/session-intelligence/src/features/stats/hooks/pre-compact.ts" }
|
|
77873
78395
|
],
|
|
77874
78396
|
SubagentStart: [
|
|
77875
|
-
{
|
|
78397
|
+
{ feature: "stats", script: "packages/session-intelligence/src/features/stats/hooks/subagent-start.ts" }
|
|
77876
78398
|
],
|
|
77877
78399
|
SubagentStop: [
|
|
77878
|
-
{
|
|
78400
|
+
{ feature: "stats", script: "packages/session-intelligence/src/features/stats/hooks/subagent-stop.ts" }
|
|
77879
78401
|
],
|
|
77880
78402
|
SessionEnd: [
|
|
77881
|
-
{
|
|
77882
|
-
{
|
|
77883
|
-
{
|
|
78403
|
+
{ feature: "stats", script: "packages/session-intelligence/src/features/stats/hooks/session-end.ts" },
|
|
78404
|
+
{ feature: "suggest", script: "packages/session-intelligence/src/features/suggest/hooks/session-end.ts" },
|
|
78405
|
+
{ feature: "recall", script: "packages/session-intelligence/src/features/recall/hooks/stop.ts" }
|
|
77884
78406
|
]
|
|
77885
78407
|
};
|
|
77886
78408
|
var ALL_EVENTS = Object.keys(HOOK_REGISTRY);
|
|
77887
|
-
var
|
|
78409
|
+
var CLAUDE_SETTINGS_FILE = ".claude/settings.local.json";
|
|
78410
|
+
var CODEX_HOOKS_FILE = ".codex/hooks.json";
|
|
78411
|
+
var HOOK_TARGETS = [
|
|
78412
|
+
{ id: "claude", label: "Claude", file: CLAUDE_SETTINGS_FILE },
|
|
78413
|
+
{ id: "codex", label: "Codex", file: CODEX_HOOKS_FILE }
|
|
78414
|
+
];
|
|
77888
78415
|
function findMonorepoRoot(cwd) {
|
|
77889
78416
|
let dir = cwd;
|
|
77890
78417
|
for (let i = 0; i < 10; i++) {
|
|
@@ -77895,19 +78422,27 @@ function findMonorepoRoot(cwd) {
|
|
|
77895
78422
|
}
|
|
77896
78423
|
return null;
|
|
77897
78424
|
}
|
|
77898
|
-
function
|
|
77899
|
-
const
|
|
77900
|
-
if (!fs106.existsSync(
|
|
78425
|
+
function readJsonFile(cwd, relPath) {
|
|
78426
|
+
const filePath = path107.join(cwd, relPath);
|
|
78427
|
+
if (!fs106.existsSync(filePath)) return {};
|
|
77901
78428
|
try {
|
|
77902
|
-
return JSON.parse(fs106.readFileSync(
|
|
78429
|
+
return JSON.parse(fs106.readFileSync(filePath, "utf8"));
|
|
77903
78430
|
} catch {
|
|
77904
78431
|
return {};
|
|
77905
78432
|
}
|
|
77906
78433
|
}
|
|
77907
|
-
function
|
|
77908
|
-
const dir = path107.join(cwd,
|
|
78434
|
+
function writeJsonFile(cwd, relPath, data) {
|
|
78435
|
+
const dir = path107.dirname(path107.join(cwd, relPath));
|
|
77909
78436
|
if (!fs106.existsSync(dir)) fs106.mkdirSync(dir, { recursive: true });
|
|
77910
|
-
fs106.writeFileSync(path107.join(cwd,
|
|
78437
|
+
fs106.writeFileSync(path107.join(cwd, relPath), JSON.stringify(data, null, 2) + "\n");
|
|
78438
|
+
}
|
|
78439
|
+
function installHooksForTarget(cwd, relPath, hooksConfig) {
|
|
78440
|
+
const settings = readJsonFile(cwd, relPath);
|
|
78441
|
+
settings.hooks = hooksConfig;
|
|
78442
|
+
writeJsonFile(cwd, relPath, settings);
|
|
78443
|
+
}
|
|
78444
|
+
function hookEntriesUseDispatcher(entries) {
|
|
78445
|
+
return JSON.stringify(entries || "").includes("hook dispatch");
|
|
77911
78446
|
}
|
|
77912
78447
|
function buildDispatcherCommand(monorepoRoot) {
|
|
77913
78448
|
const cliEntry = path107.join(monorepoRoot, "monorepo/apps/cli/bin/bootspring.js");
|
|
@@ -77961,7 +78496,7 @@ async function dispatchEvent(event) {
|
|
|
77961
78496
|
});
|
|
77962
78497
|
} catch (err) {
|
|
77963
78498
|
const msg = err.stderr?.toString?.().slice(0, 200) || err.message || "unknown error";
|
|
77964
|
-
process.stderr.write(`[hook-dispatcher] ${handler.
|
|
78499
|
+
process.stderr.write(`[hook-dispatcher] ${handler.feature}/${path107.basename(handler.script)} failed: ${msg}
|
|
77965
78500
|
`);
|
|
77966
78501
|
}
|
|
77967
78502
|
}
|
|
@@ -77990,8 +78525,8 @@ function readStdinPayload() {
|
|
|
77990
78525
|
});
|
|
77991
78526
|
}
|
|
77992
78527
|
function registerHookCommand(program3) {
|
|
77993
|
-
const hook = program3.command("hook").description("Manage Claude Code
|
|
77994
|
-
hook.command("install").description("Write deterministic hook config to
|
|
78528
|
+
const hook = program3.command("hook").description("Manage Bootspring hook dispatcher for Claude Code and Codex (stats \u2192 suggest \u2192 recall)");
|
|
78529
|
+
hook.command("install").description("Write deterministic hook config to Claude and Codex settings").option("--dry-run", "Show what would be written without modifying files").action((opts) => {
|
|
77995
78530
|
const cwd = process.cwd();
|
|
77996
78531
|
const monorepoRoot = findMonorepoRoot(cwd);
|
|
77997
78532
|
if (!monorepoRoot) {
|
|
@@ -78005,17 +78540,19 @@ function registerHookCommand(program3) {
|
|
|
78005
78540
|
console.log(`
|
|
78006
78541
|
${COLORS.bold}Hook config (dry run):${COLORS.reset}
|
|
78007
78542
|
`);
|
|
78008
|
-
console.log(JSON.stringify(
|
|
78543
|
+
console.log(JSON.stringify(Object.fromEntries(
|
|
78544
|
+
HOOK_TARGETS.map((target) => [target.file, { hooks: hooksConfig }])
|
|
78545
|
+
), null, 2));
|
|
78009
78546
|
console.log(`
|
|
78010
|
-
${COLORS.dim}${ALL_EVENTS.length} events, ${totalHandlers} handlers, ordering:
|
|
78547
|
+
${COLORS.dim}${ALL_EVENTS.length} events, ${totalHandlers} handlers, ordering: stats \u2192 suggest \u2192 recall${COLORS.reset}
|
|
78011
78548
|
`);
|
|
78012
78549
|
return;
|
|
78013
78550
|
}
|
|
78014
|
-
const
|
|
78015
|
-
|
|
78016
|
-
|
|
78017
|
-
print.success(`Wrote ${ALL_EVENTS.length} hook events (${totalHandlers} handlers) to ${
|
|
78018
|
-
console.log(` ${COLORS.dim}Ordering:
|
|
78551
|
+
for (const target of HOOK_TARGETS) {
|
|
78552
|
+
installHooksForTarget(monorepoRoot, target.file, hooksConfig);
|
|
78553
|
+
}
|
|
78554
|
+
print.success(`Wrote ${ALL_EVENTS.length} hook events (${totalHandlers} handlers) to ${HOOK_TARGETS.map((t) => t.file).join(" and ")}`);
|
|
78555
|
+
console.log(` ${COLORS.dim}Ordering: stats \u2192 suggest \u2192 recall (deterministic)${COLORS.reset}`);
|
|
78019
78556
|
console.log(` ${COLORS.dim}Error isolation: each handler runs in its own subprocess${COLORS.reset}`);
|
|
78020
78557
|
console.log(` ${COLORS.dim}Events: ${ALL_EVENTS.join(", ")}${COLORS.reset}
|
|
78021
78558
|
`);
|
|
@@ -78023,45 +78560,54 @@ ${COLORS.dim}${ALL_EVENTS.length} events, ${totalHandlers} handlers, ordering: o
|
|
|
78023
78560
|
hook.command("status").description("Show installed hook configuration").action(() => {
|
|
78024
78561
|
const cwd = process.cwd();
|
|
78025
78562
|
const monorepoRoot = findMonorepoRoot(cwd) || cwd;
|
|
78026
|
-
const
|
|
78027
|
-
|
|
78563
|
+
const targetStates = HOOK_TARGETS.map((target) => ({
|
|
78564
|
+
...target,
|
|
78565
|
+
hooks: readJsonFile(monorepoRoot, target.file).hooks || {}
|
|
78566
|
+
}));
|
|
78028
78567
|
console.log(`
|
|
78029
78568
|
${COLORS.bold}Hook Status${COLORS.reset}
|
|
78030
78569
|
`);
|
|
78031
|
-
if (Object.keys(hooks).length === 0) {
|
|
78032
|
-
print.warning(
|
|
78570
|
+
if (targetStates.every((target) => Object.keys(target.hooks).length === 0)) {
|
|
78571
|
+
print.warning(`No hooks configured in ${HOOK_TARGETS.map((t) => t.file).join(" or ")}`);
|
|
78033
78572
|
print.dim('Run "bootspring hook install" to set up the deterministic dispatcher.');
|
|
78034
78573
|
console.log();
|
|
78035
78574
|
return;
|
|
78036
78575
|
}
|
|
78037
|
-
const isDispatcher = JSON.stringify(hooks).includes("hook dispatch");
|
|
78038
78576
|
for (const event of ALL_EVENTS) {
|
|
78039
|
-
const
|
|
78577
|
+
const installedTargets = targetStates.filter((target) => hookEntriesUseDispatcher(target.hooks[event]));
|
|
78040
78578
|
const registry4 = HOOK_REGISTRY[event];
|
|
78041
|
-
const mark =
|
|
78042
|
-
const handlers = registry4.map((h) => `${COLORS.cyan}${h.
|
|
78043
|
-
|
|
78579
|
+
const mark = installedTargets.length === HOOK_TARGETS.length ? `${COLORS.green}\u2713${COLORS.reset}` : `${COLORS.yellow}!${COLORS.reset}`;
|
|
78580
|
+
const handlers = registry4.map((h) => `${COLORS.cyan}${h.feature}${COLORS.reset}`).join(" \u2192 ");
|
|
78581
|
+
const targetLabels = installedTargets.map((t) => t.label).join(", ") || "none";
|
|
78582
|
+
console.log(` ${mark} ${COLORS.bold}${event.padEnd(20)}${COLORS.reset} ${handlers} ${COLORS.dim}(${targetLabels})${COLORS.reset}`);
|
|
78044
78583
|
}
|
|
78045
78584
|
console.log();
|
|
78046
|
-
|
|
78047
|
-
|
|
78048
|
-
|
|
78049
|
-
|
|
78050
|
-
|
|
78585
|
+
for (const target of targetStates) {
|
|
78586
|
+
const isDispatcher = ALL_EVENTS.every((event) => hookEntriesUseDispatcher(target.hooks[event]));
|
|
78587
|
+
if (isDispatcher) {
|
|
78588
|
+
print.success(`${target.label}: using centralized dispatcher (${target.file}).`);
|
|
78589
|
+
} else {
|
|
78590
|
+
print.warning(`${target.label}: hooks are missing or not using the centralized dispatcher (${target.file}).`);
|
|
78591
|
+
}
|
|
78051
78592
|
}
|
|
78052
78593
|
console.log();
|
|
78053
78594
|
});
|
|
78054
|
-
hook.command("uninstall").description("Remove all hook entries from
|
|
78595
|
+
hook.command("uninstall").description("Remove all hook entries from Claude and Codex settings").action(() => {
|
|
78055
78596
|
const cwd = process.cwd();
|
|
78056
78597
|
const monorepoRoot = findMonorepoRoot(cwd) || cwd;
|
|
78057
|
-
|
|
78058
|
-
|
|
78598
|
+
let removed = 0;
|
|
78599
|
+
for (const target of HOOK_TARGETS) {
|
|
78600
|
+
const settings = readJsonFile(monorepoRoot, target.file);
|
|
78601
|
+
if (!settings.hooks) continue;
|
|
78602
|
+
delete settings.hooks;
|
|
78603
|
+
writeJsonFile(monorepoRoot, target.file, settings);
|
|
78604
|
+
removed++;
|
|
78605
|
+
}
|
|
78606
|
+
if (removed === 0) {
|
|
78059
78607
|
print.info("No hooks configured \u2014 nothing to remove.");
|
|
78060
78608
|
return;
|
|
78061
78609
|
}
|
|
78062
|
-
|
|
78063
|
-
writeSettings(monorepoRoot, settings);
|
|
78064
|
-
print.success("Removed all hook entries from .claude/settings.local.json");
|
|
78610
|
+
print.success(`Removed hook entries from ${removed} assistant config file(s).`);
|
|
78065
78611
|
});
|
|
78066
78612
|
hook.command("dispatch <event>").description("(internal) Dispatch a hook event to handlers in order").action(async (event) => {
|
|
78067
78613
|
await dispatchEvent(event);
|
|
@@ -78081,6 +78627,7 @@ var SUBCOMMANDS = {
|
|
|
78081
78627
|
auth: ["login", "logout", "whoami", "status", "clear", "register", "switch", "reauth"],
|
|
78082
78628
|
build: [
|
|
78083
78629
|
"status",
|
|
78630
|
+
"progress",
|
|
78084
78631
|
"task",
|
|
78085
78632
|
"done",
|
|
78086
78633
|
"plan",
|