@girardmedia/bootspring 3.1.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 +448 -92
- package/dist/core/index.d.ts +1 -1
- package/dist/core.js +3 -3
- package/dist/mcp-server.js +89 -6
- package/package.json +1 -1
|
@@ -21,6 +21,8 @@ When user invokes `/build` or says "build next", "build loop", "build done", use
|
|
|
21
21
|
- `/build list` → `bootspring_build` with action: "list"
|
|
22
22
|
- `/build current` → `bootspring_build` with action: "current"
|
|
23
23
|
|
|
24
|
+
After `action: "next"` or `action: "current"`, surface the returned `visibility` contract: show a visible checklist in the host UI when available and keep `planning/BUILD_PROGRESS.md` updated after inspection, edits, verification, and completion.
|
|
25
|
+
|
|
24
26
|
## Important
|
|
25
27
|
|
|
26
28
|
- "build" in Bootspring context means **task execution**, not `npm run build`
|
package/dist/cli/index.js
CHANGED
|
@@ -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
|
});
|
|
@@ -56437,6 +56437,15 @@ init_cjs_shims();
|
|
|
56437
56437
|
var fs51 = __toESM(require("fs"));
|
|
56438
56438
|
var path52 = __toESM(require("path"));
|
|
56439
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
|
+
];
|
|
56440
56449
|
function getPlanningSurfaceStatus() {
|
|
56441
56450
|
const planningDir = path52.join(process.cwd(), "planning");
|
|
56442
56451
|
return {
|
|
@@ -56768,6 +56777,97 @@ function getTaskEntriesFromState(state) {
|
|
|
56768
56777
|
estimatedTurns: task.estimatedTurns
|
|
56769
56778
|
}));
|
|
56770
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
|
+
}
|
|
56771
56871
|
function extractSupplementaryContent(queueContent) {
|
|
56772
56872
|
const lines = queueContent.split("\n");
|
|
56773
56873
|
const sections = [];
|
|
@@ -56941,7 +57041,8 @@ function registerBuildCommand(program3) {
|
|
|
56941
57041
|
build.command("status").description("Check build progress").option("--json", "Output as JSON").action((opts) => {
|
|
56942
57042
|
const state = loadBuildStateWithAutoSync(opts.json);
|
|
56943
57043
|
const planningTasks = loadPlanningTasks();
|
|
56944
|
-
const
|
|
57044
|
+
const stateTasks = getTaskEntriesFromState(state);
|
|
57045
|
+
const tasks = stateTasks.length > 0 ? stateTasks : planningTasks.tasks;
|
|
56945
57046
|
if (!state) {
|
|
56946
57047
|
if (opts.json) {
|
|
56947
57048
|
console.log(JSON.stringify({ error: "no_build_state", tasks: [] }));
|
|
@@ -56960,14 +57061,8 @@ function registerBuildCommand(program3) {
|
|
|
56960
57061
|
}
|
|
56961
57062
|
return;
|
|
56962
57063
|
}
|
|
56963
|
-
const completed = tasks
|
|
56964
|
-
|
|
56965
|
-
return s === "completed" || s === "done";
|
|
56966
|
-
}).length;
|
|
56967
|
-
const pending = tasks.filter((t) => t.status.toLowerCase() === "pending").length;
|
|
56968
|
-
const inProgress = tasks.filter((t) => t.status.toLowerCase() === "in_progress").length;
|
|
56969
|
-
const total = tasks.length;
|
|
56970
|
-
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 });
|
|
56971
57066
|
const iteration = state.loopSession?.currentIteration ?? 0;
|
|
56972
57067
|
const maxIter = state.loopSession?.maxIterations ?? 50;
|
|
56973
57068
|
const sessionId = state.loopSession?.sessionId ?? "N/A";
|
|
@@ -56975,12 +57070,15 @@ function registerBuildCommand(program3) {
|
|
|
56975
57070
|
const currentTask2 = tasks.find((t) => t.status.toLowerCase() === "in_progress");
|
|
56976
57071
|
console.log(JSON.stringify({
|
|
56977
57072
|
project: state.projectName ?? "Unknown",
|
|
56978
|
-
status:
|
|
57073
|
+
status: derivedStatus,
|
|
56979
57074
|
phase: formatPhaseName(state.currentPhase ?? getCurrentPhase(state.implementationQueue)),
|
|
56980
57075
|
planningSource: formatPlanningSourceLabel(planningTasks.source ?? (state ? "BUILD_STATE.json" : null)),
|
|
56981
57076
|
completed,
|
|
57077
|
+
skipped,
|
|
57078
|
+
blocked,
|
|
56982
57079
|
pending,
|
|
56983
57080
|
inProgress,
|
|
57081
|
+
closed,
|
|
56984
57082
|
total,
|
|
56985
57083
|
pct: pct2,
|
|
56986
57084
|
iteration,
|
|
@@ -56993,10 +57091,12 @@ function registerBuildCommand(program3) {
|
|
|
56993
57091
|
print.header("Build Status");
|
|
56994
57092
|
printLegacyQueueWarningIfNeeded();
|
|
56995
57093
|
print.info(`Project: ${state.projectName ?? "Unknown"}`);
|
|
56996
|
-
print.info(`Status: ${
|
|
57094
|
+
print.info(`Status: ${derivedStatus}`);
|
|
56997
57095
|
print.info(`Phase: ${formatPhaseName(state.currentPhase ?? getCurrentPhase(state.implementationQueue))}`);
|
|
56998
57096
|
print.info(`Planning Source: ${formatPlanningSourceLabel(planningTasks.source ?? (state ? "BUILD_STATE.json" : null))}`);
|
|
56999
|
-
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}`);
|
|
57000
57100
|
print.info(`Pending: ${pending}`);
|
|
57001
57101
|
if (inProgress > 0) print.info(`In Progress: ${inProgress}`);
|
|
57002
57102
|
if (iteration > maxIter) {
|
|
@@ -57013,6 +57113,40 @@ function registerBuildCommand(program3) {
|
|
|
57013
57113
|
const filled = Math.round(pct2 / 100 * barWidth);
|
|
57014
57114
|
const bar = "#".repeat(filled) + ".".repeat(barWidth - filled);
|
|
57015
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"));
|
|
57016
57150
|
});
|
|
57017
57151
|
build.command("task").description("Show the current task").action(() => {
|
|
57018
57152
|
printLegacyQueueWarningIfNeeded();
|
|
@@ -57096,6 +57230,8 @@ function registerBuildCommand(program3) {
|
|
|
57096
57230
|
} else {
|
|
57097
57231
|
print.success("No more pending tasks!");
|
|
57098
57232
|
}
|
|
57233
|
+
const artifactPath = writeBuildProgressArtifact(state, nextTask ?? queue[idx], nextTask ? "task_completed_next_available" : "task_completed_all_done");
|
|
57234
|
+
printVisibleWorkNotice(artifactPath);
|
|
57099
57235
|
});
|
|
57100
57236
|
build.command("plan").description("View the full build plan").action(() => {
|
|
57101
57237
|
printLegacyQueueWarningIfNeeded();
|
|
@@ -57167,6 +57303,8 @@ function registerBuildCommand(program3) {
|
|
|
57167
57303
|
const inProgress = queue.find((t2) => t2.status === "in_progress");
|
|
57168
57304
|
if (inProgress) {
|
|
57169
57305
|
print.warning(`Task ${inProgress.id} is already in progress: ${inProgress.title}`);
|
|
57306
|
+
const artifactPath2 = writeBuildProgressArtifact(state, inProgress, "already_in_progress");
|
|
57307
|
+
printVisibleWorkNotice(artifactPath2);
|
|
57170
57308
|
return;
|
|
57171
57309
|
}
|
|
57172
57310
|
const idx = queue.findIndex((t2) => t2.status === "pending");
|
|
@@ -57194,6 +57332,8 @@ function registerBuildCommand(program3) {
|
|
|
57194
57332
|
console.log(` [ ] ${ac}`);
|
|
57195
57333
|
}
|
|
57196
57334
|
}
|
|
57335
|
+
const artifactPath = writeBuildProgressArtifact(state, t, "task_started");
|
|
57336
|
+
printVisibleWorkNotice(artifactPath);
|
|
57197
57337
|
});
|
|
57198
57338
|
build.command("pause").description("Pause the build loop").action(() => {
|
|
57199
57339
|
const state = loadBuildState();
|
|
@@ -61870,7 +62010,7 @@ function generateDocTemplate(docType, projectName) {
|
|
|
61870
62010
|
`;
|
|
61871
62011
|
}
|
|
61872
62012
|
function registerGoCommand(program3) {
|
|
61873
|
-
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) => {
|
|
61874
62014
|
console.log("");
|
|
61875
62015
|
console.log(`${COLORS.cyan}${COLORS.bold}\u26A1 Bootspring Go${COLORS.reset}`);
|
|
61876
62016
|
console.log(`${COLORS.dim}Setting up your project in one shot...${COLORS.reset}`);
|
|
@@ -61890,26 +62030,56 @@ function registerGoCommand(program3) {
|
|
|
61890
62030
|
print.dim(" No codebase detected (starting fresh)");
|
|
61891
62031
|
}
|
|
61892
62032
|
console.log("");
|
|
61893
|
-
|
|
61894
|
-
|
|
61895
|
-
|
|
61896
|
-
|
|
61897
|
-
|
|
61898
|
-
|
|
61899
|
-
|
|
61900
|
-
|
|
61901
|
-
|
|
61902
|
-
|
|
61903
|
-
|
|
61904
|
-
if (fs64.existsSync(filePath) && !opts.force) {
|
|
61905
|
-
skipped++;
|
|
61906
|
-
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;
|
|
61907
62044
|
}
|
|
61908
|
-
|
|
61909
|
-
|
|
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);
|
|
61910
62082
|
}
|
|
61911
|
-
if (generated > 0) print.success(` Created ${generated} template(s) in ${CONTEXT_DIR}/`);
|
|
61912
|
-
if (skipped > 0) print.dim(` Skipped ${skipped} (already exist)`);
|
|
61913
62083
|
console.log("");
|
|
61914
62084
|
if (opts.editFirst) {
|
|
61915
62085
|
print.info("Templates created. Edit them, then run:");
|
|
@@ -61962,13 +62132,39 @@ function registerGoCommand(program3) {
|
|
|
61962
62132
|
console.log("");
|
|
61963
62133
|
console.log(`${COLORS.green}${COLORS.bold}Setup complete!${COLORS.reset}`);
|
|
61964
62134
|
console.log("");
|
|
61965
|
-
|
|
61966
|
-
|
|
61967
|
-
|
|
61968
|
-
|
|
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
|
+
}
|
|
61969
62146
|
console.log("");
|
|
61970
62147
|
});
|
|
61971
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
|
+
}
|
|
61972
62168
|
|
|
61973
62169
|
// src/commands/seed.ts
|
|
61974
62170
|
var CONTEXT_DOCS2 = {
|
|
@@ -62478,7 +62674,7 @@ ${COLORS.bold}Detected Features:${COLORS.reset}`);
|
|
|
62478
62674
|
console.log("");
|
|
62479
62675
|
print.info(`${generated} generated, ${skipped} skipped in ${CONTEXT_DIR2}/`);
|
|
62480
62676
|
});
|
|
62481
|
-
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) => {
|
|
62482
62678
|
print.header("Seed Merge");
|
|
62483
62679
|
const cwd = process.cwd();
|
|
62484
62680
|
const contextDir = getContextDir();
|
|
@@ -62744,10 +62940,122 @@ ${COLORS.bold}Detected Features:${COLORS.reset}`);
|
|
|
62744
62940
|
print.success("Document looks good!");
|
|
62745
62941
|
}
|
|
62746
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
|
+
});
|
|
62747
63052
|
seed.action(() => {
|
|
62748
63053
|
seed.outputHelp();
|
|
62749
63054
|
console.log("");
|
|
62750
|
-
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}`);
|
|
62751
63059
|
console.log(" bootspring seed init # Create .bootspring/context/ + templates");
|
|
62752
63060
|
console.log(" (edit/drop files) # Add your project docs");
|
|
62753
63061
|
console.log(" bootspring seed merge # Combine into planning/SEED.md");
|
|
@@ -73569,6 +73877,29 @@ ${COLORS.green}Task submitted:${COLORS.reset} ${res.id}`);
|
|
|
73569
73877
|
handleError(error50);
|
|
73570
73878
|
}
|
|
73571
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
|
+
});
|
|
73572
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) => {
|
|
73573
73904
|
if (!requireAuth4()) return;
|
|
73574
73905
|
try {
|
|
@@ -78042,40 +78373,45 @@ var import_child_process18 = require("child_process");
|
|
|
78042
78373
|
init_src2();
|
|
78043
78374
|
var HOOK_REGISTRY = {
|
|
78044
78375
|
SessionStart: [
|
|
78045
|
-
{
|
|
78046
|
-
{
|
|
78047
|
-
{
|
|
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" }
|
|
78048
78379
|
],
|
|
78049
78380
|
UserPromptSubmit: [
|
|
78050
|
-
{
|
|
78051
|
-
{
|
|
78052
|
-
{
|
|
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" }
|
|
78053
78384
|
],
|
|
78054
78385
|
PreToolUse: [
|
|
78055
|
-
{
|
|
78056
|
-
{
|
|
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" }
|
|
78057
78388
|
],
|
|
78058
78389
|
PostToolUse: [
|
|
78059
|
-
{
|
|
78060
|
-
{
|
|
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" }
|
|
78061
78392
|
],
|
|
78062
78393
|
PreCompact: [
|
|
78063
|
-
{
|
|
78394
|
+
{ feature: "stats", script: "packages/session-intelligence/src/features/stats/hooks/pre-compact.ts" }
|
|
78064
78395
|
],
|
|
78065
78396
|
SubagentStart: [
|
|
78066
|
-
{
|
|
78397
|
+
{ feature: "stats", script: "packages/session-intelligence/src/features/stats/hooks/subagent-start.ts" }
|
|
78067
78398
|
],
|
|
78068
78399
|
SubagentStop: [
|
|
78069
|
-
{
|
|
78400
|
+
{ feature: "stats", script: "packages/session-intelligence/src/features/stats/hooks/subagent-stop.ts" }
|
|
78070
78401
|
],
|
|
78071
78402
|
SessionEnd: [
|
|
78072
|
-
{
|
|
78073
|
-
{
|
|
78074
|
-
{
|
|
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" }
|
|
78075
78406
|
]
|
|
78076
78407
|
};
|
|
78077
78408
|
var ALL_EVENTS = Object.keys(HOOK_REGISTRY);
|
|
78078
|
-
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
|
+
];
|
|
78079
78415
|
function findMonorepoRoot(cwd) {
|
|
78080
78416
|
let dir = cwd;
|
|
78081
78417
|
for (let i = 0; i < 10; i++) {
|
|
@@ -78086,19 +78422,27 @@ function findMonorepoRoot(cwd) {
|
|
|
78086
78422
|
}
|
|
78087
78423
|
return null;
|
|
78088
78424
|
}
|
|
78089
|
-
function
|
|
78090
|
-
const
|
|
78091
|
-
if (!fs106.existsSync(
|
|
78425
|
+
function readJsonFile(cwd, relPath) {
|
|
78426
|
+
const filePath = path107.join(cwd, relPath);
|
|
78427
|
+
if (!fs106.existsSync(filePath)) return {};
|
|
78092
78428
|
try {
|
|
78093
|
-
return JSON.parse(fs106.readFileSync(
|
|
78429
|
+
return JSON.parse(fs106.readFileSync(filePath, "utf8"));
|
|
78094
78430
|
} catch {
|
|
78095
78431
|
return {};
|
|
78096
78432
|
}
|
|
78097
78433
|
}
|
|
78098
|
-
function
|
|
78099
|
-
const dir = path107.join(cwd,
|
|
78434
|
+
function writeJsonFile(cwd, relPath, data) {
|
|
78435
|
+
const dir = path107.dirname(path107.join(cwd, relPath));
|
|
78100
78436
|
if (!fs106.existsSync(dir)) fs106.mkdirSync(dir, { recursive: true });
|
|
78101
|
-
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");
|
|
78102
78446
|
}
|
|
78103
78447
|
function buildDispatcherCommand(monorepoRoot) {
|
|
78104
78448
|
const cliEntry = path107.join(monorepoRoot, "monorepo/apps/cli/bin/bootspring.js");
|
|
@@ -78152,7 +78496,7 @@ async function dispatchEvent(event) {
|
|
|
78152
78496
|
});
|
|
78153
78497
|
} catch (err) {
|
|
78154
78498
|
const msg = err.stderr?.toString?.().slice(0, 200) || err.message || "unknown error";
|
|
78155
|
-
process.stderr.write(`[hook-dispatcher] ${handler.
|
|
78499
|
+
process.stderr.write(`[hook-dispatcher] ${handler.feature}/${path107.basename(handler.script)} failed: ${msg}
|
|
78156
78500
|
`);
|
|
78157
78501
|
}
|
|
78158
78502
|
}
|
|
@@ -78181,8 +78525,8 @@ function readStdinPayload() {
|
|
|
78181
78525
|
});
|
|
78182
78526
|
}
|
|
78183
78527
|
function registerHookCommand(program3) {
|
|
78184
|
-
const hook = program3.command("hook").description("Manage Claude Code
|
|
78185
|
-
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) => {
|
|
78186
78530
|
const cwd = process.cwd();
|
|
78187
78531
|
const monorepoRoot = findMonorepoRoot(cwd);
|
|
78188
78532
|
if (!monorepoRoot) {
|
|
@@ -78196,17 +78540,19 @@ function registerHookCommand(program3) {
|
|
|
78196
78540
|
console.log(`
|
|
78197
78541
|
${COLORS.bold}Hook config (dry run):${COLORS.reset}
|
|
78198
78542
|
`);
|
|
78199
|
-
console.log(JSON.stringify(
|
|
78543
|
+
console.log(JSON.stringify(Object.fromEntries(
|
|
78544
|
+
HOOK_TARGETS.map((target) => [target.file, { hooks: hooksConfig }])
|
|
78545
|
+
), null, 2));
|
|
78200
78546
|
console.log(`
|
|
78201
|
-
${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}
|
|
78202
78548
|
`);
|
|
78203
78549
|
return;
|
|
78204
78550
|
}
|
|
78205
|
-
const
|
|
78206
|
-
|
|
78207
|
-
|
|
78208
|
-
print.success(`Wrote ${ALL_EVENTS.length} hook events (${totalHandlers} handlers) to ${
|
|
78209
|
-
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}`);
|
|
78210
78556
|
console.log(` ${COLORS.dim}Error isolation: each handler runs in its own subprocess${COLORS.reset}`);
|
|
78211
78557
|
console.log(` ${COLORS.dim}Events: ${ALL_EVENTS.join(", ")}${COLORS.reset}
|
|
78212
78558
|
`);
|
|
@@ -78214,45 +78560,54 @@ ${COLORS.dim}${ALL_EVENTS.length} events, ${totalHandlers} handlers, ordering: o
|
|
|
78214
78560
|
hook.command("status").description("Show installed hook configuration").action(() => {
|
|
78215
78561
|
const cwd = process.cwd();
|
|
78216
78562
|
const monorepoRoot = findMonorepoRoot(cwd) || cwd;
|
|
78217
|
-
const
|
|
78218
|
-
|
|
78563
|
+
const targetStates = HOOK_TARGETS.map((target) => ({
|
|
78564
|
+
...target,
|
|
78565
|
+
hooks: readJsonFile(monorepoRoot, target.file).hooks || {}
|
|
78566
|
+
}));
|
|
78219
78567
|
console.log(`
|
|
78220
78568
|
${COLORS.bold}Hook Status${COLORS.reset}
|
|
78221
78569
|
`);
|
|
78222
|
-
if (Object.keys(hooks).length === 0) {
|
|
78223
|
-
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 ")}`);
|
|
78224
78572
|
print.dim('Run "bootspring hook install" to set up the deterministic dispatcher.');
|
|
78225
78573
|
console.log();
|
|
78226
78574
|
return;
|
|
78227
78575
|
}
|
|
78228
|
-
const isDispatcher = JSON.stringify(hooks).includes("hook dispatch");
|
|
78229
78576
|
for (const event of ALL_EVENTS) {
|
|
78230
|
-
const
|
|
78577
|
+
const installedTargets = targetStates.filter((target) => hookEntriesUseDispatcher(target.hooks[event]));
|
|
78231
78578
|
const registry4 = HOOK_REGISTRY[event];
|
|
78232
|
-
const mark =
|
|
78233
|
-
const handlers = registry4.map((h) => `${COLORS.cyan}${h.
|
|
78234
|
-
|
|
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}`);
|
|
78235
78583
|
}
|
|
78236
78584
|
console.log();
|
|
78237
|
-
|
|
78238
|
-
|
|
78239
|
-
|
|
78240
|
-
|
|
78241
|
-
|
|
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
|
+
}
|
|
78242
78592
|
}
|
|
78243
78593
|
console.log();
|
|
78244
78594
|
});
|
|
78245
|
-
hook.command("uninstall").description("Remove all hook entries from
|
|
78595
|
+
hook.command("uninstall").description("Remove all hook entries from Claude and Codex settings").action(() => {
|
|
78246
78596
|
const cwd = process.cwd();
|
|
78247
78597
|
const monorepoRoot = findMonorepoRoot(cwd) || cwd;
|
|
78248
|
-
|
|
78249
|
-
|
|
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) {
|
|
78250
78607
|
print.info("No hooks configured \u2014 nothing to remove.");
|
|
78251
78608
|
return;
|
|
78252
78609
|
}
|
|
78253
|
-
|
|
78254
|
-
writeSettings(monorepoRoot, settings);
|
|
78255
|
-
print.success("Removed all hook entries from .claude/settings.local.json");
|
|
78610
|
+
print.success(`Removed hook entries from ${removed} assistant config file(s).`);
|
|
78256
78611
|
});
|
|
78257
78612
|
hook.command("dispatch <event>").description("(internal) Dispatch a hook event to handlers in order").action(async (event) => {
|
|
78258
78613
|
await dispatchEvent(event);
|
|
@@ -78272,6 +78627,7 @@ var SUBCOMMANDS = {
|
|
|
78272
78627
|
auth: ["login", "logout", "whoami", "status", "clear", "register", "switch", "reauth"],
|
|
78273
78628
|
build: [
|
|
78274
78629
|
"status",
|
|
78630
|
+
"progress",
|
|
78275
78631
|
"task",
|
|
78276
78632
|
"done",
|
|
78277
78633
|
"plan",
|
package/dist/core/index.d.ts
CHANGED
|
@@ -477,7 +477,7 @@ interface InstallContext {
|
|
|
477
477
|
scriptPath: string;
|
|
478
478
|
}
|
|
479
479
|
declare const PACKAGE_NAME = "@girardmedia/bootspring";
|
|
480
|
-
declare const CURRENT_VERSION = "3.
|
|
480
|
+
declare const CURRENT_VERSION = "3.2.0";
|
|
481
481
|
declare const DEFAULT_INTERVAL_MS: number;
|
|
482
482
|
declare const STATE_PATH: string;
|
|
483
483
|
declare function compareVersions(a: string, b: string): number;
|
package/dist/core.js
CHANGED
|
@@ -379,7 +379,7 @@ var init_release = __esm({
|
|
|
379
379
|
"../../packages/shared/src/release.ts"() {
|
|
380
380
|
"use strict";
|
|
381
381
|
init_cjs_shims();
|
|
382
|
-
BOOTSPRING_VERSION = "3.
|
|
382
|
+
BOOTSPRING_VERSION = "3.2.0";
|
|
383
383
|
BOOTSPRING_PACKAGE_NAME = "@girardmedia/bootspring";
|
|
384
384
|
}
|
|
385
385
|
});
|
|
@@ -21643,7 +21643,7 @@ ${COLORS2.dim}Run "bootspring mcp" for server options${COLORS2.reset}
|
|
|
21643
21643
|
console.log(`${COLORS2.dim}Run "bootspring mcp" for setup instructions.${COLORS2.reset}
|
|
21644
21644
|
`);
|
|
21645
21645
|
}
|
|
21646
|
-
var BOOTSPRING_VERSION2 = "3.
|
|
21646
|
+
var BOOTSPRING_VERSION2 = "3.2.0";
|
|
21647
21647
|
var BOOTSPRING_PACKAGE_NAME2 = "@girardmedia/bootspring";
|
|
21648
21648
|
var REDACTED2 = "[REDACTED]";
|
|
21649
21649
|
var SENSITIVE_KEY_PATTERN2 = /(?:^|[_-])(api[_-]?key|token|refresh[_-]?token|authorization|x[_-]?api[_-]?key|project[_-]?id)$/i;
|
|
@@ -21691,7 +21691,7 @@ var require_package = __commonJS({
|
|
|
21691
21691
|
"../../../package.json"(exports2, module2) {
|
|
21692
21692
|
module2.exports = {
|
|
21693
21693
|
name: "bootspring-workspace",
|
|
21694
|
-
version: "3.
|
|
21694
|
+
version: "3.2.0",
|
|
21695
21695
|
private: true,
|
|
21696
21696
|
description: "Workspace tooling for the Bootspring monorepo",
|
|
21697
21697
|
keywords: [
|
package/dist/mcp-server.js
CHANGED
|
@@ -31464,7 +31464,7 @@ var init_release = __esm({
|
|
|
31464
31464
|
"../../packages/shared/src/release.ts"() {
|
|
31465
31465
|
"use strict";
|
|
31466
31466
|
init_cjs_shims();
|
|
31467
|
-
BOOTSPRING_VERSION = "3.
|
|
31467
|
+
BOOTSPRING_VERSION = "3.2.0";
|
|
31468
31468
|
BOOTSPRING_PACKAGE_NAME = "@girardmedia/bootspring";
|
|
31469
31469
|
}
|
|
31470
31470
|
});
|
|
@@ -52365,7 +52365,7 @@ var require_package = __commonJS({
|
|
|
52365
52365
|
"../../../package.json"(exports2, module2) {
|
|
52366
52366
|
module2.exports = {
|
|
52367
52367
|
name: "bootspring-workspace",
|
|
52368
|
-
version: "3.
|
|
52368
|
+
version: "3.2.0",
|
|
52369
52369
|
private: true,
|
|
52370
52370
|
description: "Workspace tooling for the Bootspring monorepo",
|
|
52371
52371
|
keywords: [
|
|
@@ -52503,6 +52503,15 @@ var core = require_dist2();
|
|
|
52503
52503
|
var api2 = core.api;
|
|
52504
52504
|
var auth2 = core.auth;
|
|
52505
52505
|
var VERSION = require_package().version;
|
|
52506
|
+
var BUILD_PROGRESS_ARTIFACT = "planning/BUILD_PROGRESS.md";
|
|
52507
|
+
var VISIBLE_WORK_CHECKPOINTS = [
|
|
52508
|
+
"Announce the active task and intended checkpoints in the chat or terminal.",
|
|
52509
|
+
"Inspect the relevant files and summarize what changed before editing.",
|
|
52510
|
+
"Patch the implementation and keep unrelated files untouched.",
|
|
52511
|
+
"Add or update focused tests when the task changes behavior.",
|
|
52512
|
+
"Run the relevant verification commands and report pass/fail output.",
|
|
52513
|
+
"Update the build state with action=done only after verification."
|
|
52514
|
+
];
|
|
52506
52515
|
function getProjectRoot() {
|
|
52507
52516
|
return process.cwd();
|
|
52508
52517
|
}
|
|
@@ -52579,6 +52588,70 @@ function formatBuildTask(task) {
|
|
|
52579
52588
|
dependencies: task.dependencies || []
|
|
52580
52589
|
};
|
|
52581
52590
|
}
|
|
52591
|
+
function renderVisibleProgressBar(percent, width = 30) {
|
|
52592
|
+
const clamped = Math.max(0, Math.min(100, Math.round(percent || 0)));
|
|
52593
|
+
const filled = Math.round(clamped / 100 * width);
|
|
52594
|
+
return `[${"#".repeat(filled)}${".".repeat(width - filled)}] ${clamped}%`;
|
|
52595
|
+
}
|
|
52596
|
+
function writeVisibleProgressArtifact(task, stats, event) {
|
|
52597
|
+
const fs3 = require("fs");
|
|
52598
|
+
const path3 = require("path");
|
|
52599
|
+
const artifactPath = path3.join(getProjectRoot(), BUILD_PROGRESS_ARTIFACT);
|
|
52600
|
+
const progress = {
|
|
52601
|
+
completed: stats?.completed || 0,
|
|
52602
|
+
pending: stats?.pending || 0,
|
|
52603
|
+
inProgress: stats?.inProgress || 0,
|
|
52604
|
+
total: stats?.total || 0,
|
|
52605
|
+
percent: stats?.percent || 0
|
|
52606
|
+
};
|
|
52607
|
+
const taskData = formatBuildTask(task);
|
|
52608
|
+
fs3.mkdirSync(path3.dirname(artifactPath), { recursive: true });
|
|
52609
|
+
fs3.writeFileSync(artifactPath, [
|
|
52610
|
+
"# Build Progress",
|
|
52611
|
+
"",
|
|
52612
|
+
`Updated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
52613
|
+
`Event: ${event}`,
|
|
52614
|
+
`Overall: ${progress.completed}/${progress.total} ${renderVisibleProgressBar(progress.percent)}`,
|
|
52615
|
+
"",
|
|
52616
|
+
"## Active Task",
|
|
52617
|
+
"",
|
|
52618
|
+
taskData ? [
|
|
52619
|
+
`- ID: ${taskData.id}`,
|
|
52620
|
+
`- Title: ${taskData.title}`,
|
|
52621
|
+
`- Phase: ${taskData.phase || "Unknown"}`,
|
|
52622
|
+
taskData.description ? `- Description: ${taskData.description}` : null,
|
|
52623
|
+
taskData.dependencies?.length ? `- Dependencies: ${taskData.dependencies.join(", ")}` : null
|
|
52624
|
+
].filter(Boolean).join("\n") : "No active or pending task.",
|
|
52625
|
+
"",
|
|
52626
|
+
"## Acceptance Criteria",
|
|
52627
|
+
"",
|
|
52628
|
+
taskData?.acceptanceCriteria?.length ? taskData.acceptanceCriteria.map((item) => `- [ ] ${item}`).join("\n") : "- [ ] No task-specific acceptance criteria recorded.",
|
|
52629
|
+
"",
|
|
52630
|
+
"## Visible Work Contract",
|
|
52631
|
+
"",
|
|
52632
|
+
VISIBLE_WORK_CHECKPOINTS.map((item) => `- [ ] ${item}`).join("\n"),
|
|
52633
|
+
"",
|
|
52634
|
+
"## Host Notes",
|
|
52635
|
+
"",
|
|
52636
|
+
"- Claude Code and Codex CLI can stream terminal output directly.",
|
|
52637
|
+
"- Codex Desktop, Claude Desktop, and other MCP hosts should keep a visible plan/checklist updated while the terminal is hidden.",
|
|
52638
|
+
"- Update this file after inspection, edits, verification, and completion so the workspace has a durable progress surface.",
|
|
52639
|
+
""
|
|
52640
|
+
].join("\n"));
|
|
52641
|
+
return BUILD_PROGRESS_ARTIFACT;
|
|
52642
|
+
}
|
|
52643
|
+
function buildVisibilityContract(task, stats, event) {
|
|
52644
|
+
const artifact = writeVisibleProgressArtifact(task, stats, event);
|
|
52645
|
+
return {
|
|
52646
|
+
artifact,
|
|
52647
|
+
checkpoints: VISIBLE_WORK_CHECKPOINTS,
|
|
52648
|
+
hostInstructions: [
|
|
52649
|
+
"Immediately show the active task and a short visible checklist to the user.",
|
|
52650
|
+
"Post visible updates after inspection, implementation, verification, and completion.",
|
|
52651
|
+
`Keep ${artifact} current when the host hides terminal output.`
|
|
52652
|
+
]
|
|
52653
|
+
};
|
|
52654
|
+
}
|
|
52582
52655
|
function autoCommitTask(taskId, taskTitle) {
|
|
52583
52656
|
try {
|
|
52584
52657
|
const { execSync } = require("child_process");
|
|
@@ -52685,6 +52758,7 @@ async function executeLocalBuild(args) {
|
|
|
52685
52758
|
status: state.status,
|
|
52686
52759
|
progress: { completed: stats.completed, pending: stats.pending, inProgress: stats.inProgress, total: stats.total, percent: stats.percent },
|
|
52687
52760
|
currentTask: currentTask ? { id: currentTask.id, title: currentTask.title } : null,
|
|
52761
|
+
visibility: buildVisibilityContract(currentTask, stats, "status"),
|
|
52688
52762
|
nextAction: currentTask ? "Complete the current task, then use action=done" : "Use action=next to get the next task"
|
|
52689
52763
|
}, null, 2) }] };
|
|
52690
52764
|
}
|
|
@@ -52698,6 +52772,7 @@ async function executeLocalBuild(args) {
|
|
|
52698
52772
|
message: "Task already in progress",
|
|
52699
52773
|
state: "in_progress",
|
|
52700
52774
|
task: formatBuildTask(inProgress),
|
|
52775
|
+
visibility: buildVisibilityContract(inProgress, buildGetStats(state), "already_in_progress"),
|
|
52701
52776
|
hint: "Complete this task with action=done, or use action=skip to skip it"
|
|
52702
52777
|
}, null, 2) }] };
|
|
52703
52778
|
}
|
|
@@ -52714,6 +52789,7 @@ async function executeLocalBuild(args) {
|
|
|
52714
52789
|
state: "task_ready",
|
|
52715
52790
|
file: "planning/TODO.md",
|
|
52716
52791
|
progress: { completed: stats.completed, total: stats.total, percent: stats.percent },
|
|
52792
|
+
visibility: buildVisibilityContract(nextTask, stats, "task_ready"),
|
|
52717
52793
|
instructions: [
|
|
52718
52794
|
`Find ${nextTask.id} in planning/TODO.md for full details`,
|
|
52719
52795
|
"Implement the task in the codebase",
|
|
@@ -52734,6 +52810,7 @@ async function executeLocalBuild(args) {
|
|
|
52734
52810
|
}
|
|
52735
52811
|
return { content: [{ type: "text", text: JSON.stringify({
|
|
52736
52812
|
task: formatBuildTask(currentTask),
|
|
52813
|
+
visibility: buildVisibilityContract(currentTask, buildGetStats(state), "current"),
|
|
52737
52814
|
instructions: ["Implement this task in the codebase", "Run quality checks (lint, test)", "Commit your changes", "Use action=done to mark complete"]
|
|
52738
52815
|
}, null, 2) }] };
|
|
52739
52816
|
}
|
|
@@ -52762,6 +52839,7 @@ async function executeLocalBuild(args) {
|
|
|
52762
52839
|
nextTask: nextTask ? { ...formatBuildTask(nextTask), file: "planning/TODO.md" } : null,
|
|
52763
52840
|
allComplete: !nextTask,
|
|
52764
52841
|
state: nextTask ? "advanced" : "all_complete",
|
|
52842
|
+
visibility: buildVisibilityContract(nextTask, stats, nextTask ? "advanced" : "all_complete"),
|
|
52765
52843
|
message: nextTask ? `Done! Committed ${inProgress.id}. Next: ${nextTask.id} \u2014 ${nextTask.title}` : "Build complete! All tasks finished."
|
|
52766
52844
|
}, null, 2) }] };
|
|
52767
52845
|
}
|
|
@@ -52782,6 +52860,7 @@ async function executeLocalBuild(args) {
|
|
|
52782
52860
|
return { content: [{ type: "text", text: JSON.stringify({
|
|
52783
52861
|
skipped: { id: inProgress.id, title: inProgress.title, reason: args?.reason || "Skipped" },
|
|
52784
52862
|
nextTask: nextTask ? formatBuildTask(nextTask) : null,
|
|
52863
|
+
visibility: buildVisibilityContract(nextTask, buildGetStats(state), nextTask ? "skipped_next_ready" : "skipped_all_done"),
|
|
52785
52864
|
message: nextTask ? `Skipped. Next: ${nextTask.title}` : "No more tasks available"
|
|
52786
52865
|
}, null, 2) }] };
|
|
52787
52866
|
}
|
|
@@ -52807,13 +52886,14 @@ async function executeLocalBuild(args) {
|
|
|
52807
52886
|
const nextTask2 = buildGetNextTask(state);
|
|
52808
52887
|
if (nextTask2) buildUpdateTaskStatus(state, nextTask2.id, "in_progress");
|
|
52809
52888
|
saveBuildState(state);
|
|
52810
|
-
const
|
|
52889
|
+
const stats2 = buildGetStats(state);
|
|
52811
52890
|
return { content: [{ type: "text", text: JSON.stringify({
|
|
52812
52891
|
state: nextTask2 ? "advanced" : "all_complete",
|
|
52813
52892
|
autoCompleted: true,
|
|
52814
52893
|
completed: { id: activeTask.id, title: activeTask.title },
|
|
52815
52894
|
nextTask: nextTask2 ? formatBuildTask(nextTask2) : null,
|
|
52816
|
-
progress: { completed:
|
|
52895
|
+
progress: { completed: stats2.completed, total: stats2.total, percent: stats2.percent },
|
|
52896
|
+
visibility: buildVisibilityContract(nextTask2, stats2, nextTask2 ? "advanced" : "all_complete")
|
|
52817
52897
|
}, null, 2) }] };
|
|
52818
52898
|
}
|
|
52819
52899
|
} catch {
|
|
@@ -52822,20 +52902,23 @@ async function executeLocalBuild(args) {
|
|
|
52822
52902
|
return { content: [{ type: "text", text: JSON.stringify({
|
|
52823
52903
|
state: "in_progress",
|
|
52824
52904
|
task: formatBuildTask(activeTask),
|
|
52905
|
+
visibility: buildVisibilityContract(activeTask, buildGetStats(state), "in_progress"),
|
|
52825
52906
|
hint: "Finish implementation and use action=done, or call action=advance with autoDone=true after commit"
|
|
52826
52907
|
}, null, 2) }] };
|
|
52827
52908
|
}
|
|
52828
52909
|
const nextTask = buildGetNextTask(state);
|
|
52829
52910
|
if (!nextTask) {
|
|
52830
|
-
const
|
|
52831
|
-
return { content: [{ type: "text", text: JSON.stringify({ state: "all_complete", message: "All tasks complete!", progress: { completed:
|
|
52911
|
+
const stats2 = buildGetStats(state);
|
|
52912
|
+
return { content: [{ type: "text", text: JSON.stringify({ state: "all_complete", message: "All tasks complete!", progress: { completed: stats2.completed, total: stats2.total, percent: stats2.percent }, visibility: buildVisibilityContract(null, stats2, "all_complete") }, null, 2) }] };
|
|
52832
52913
|
}
|
|
52833
52914
|
buildUpdateTaskStatus(state, nextTask.id, "in_progress");
|
|
52834
52915
|
saveBuildState(state);
|
|
52916
|
+
const stats = buildGetStats(state);
|
|
52835
52917
|
return { content: [{ type: "text", text: JSON.stringify({
|
|
52836
52918
|
state: "task_ready",
|
|
52837
52919
|
task: formatBuildTask(nextTask),
|
|
52838
52920
|
file: "planning/TODO.md",
|
|
52921
|
+
visibility: buildVisibilityContract(nextTask, stats, "task_ready"),
|
|
52839
52922
|
hint: "Implement task, then call action=advance (or action=done) to continue loop"
|
|
52840
52923
|
}, null, 2) }] };
|
|
52841
52924
|
}
|