@girardmedia/bootspring 2.4.0 → 2.5.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/dist/cli/index.cjs +737 -122
- package/dist/core/index.d.ts +46 -2
- package/dist/core.js +9 -6
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp-server.js +9 -6
- package/generators/templates/build-planning.template.js +15 -11
- package/package.json +9 -6
package/dist/cli/index.cjs
CHANGED
|
@@ -3214,13 +3214,15 @@ function isMCPContext() {
|
|
|
3214
3214
|
function deepClone(obj) {
|
|
3215
3215
|
return JSON.parse(JSON.stringify(obj));
|
|
3216
3216
|
}
|
|
3217
|
-
var import_fs, import_path, REDACTED, SENSITIVE_KEY_PATTERN, COLORS, print;
|
|
3217
|
+
var import_fs, import_path, BOOTSPRING_VERSION, BOOTSPRING_PACKAGE_NAME, REDACTED, SENSITIVE_KEY_PATTERN, COLORS, print;
|
|
3218
3218
|
var init_dist = __esm({
|
|
3219
3219
|
"../../packages/shared/dist/index.mjs"() {
|
|
3220
3220
|
"use strict";
|
|
3221
3221
|
init_cjs_shims();
|
|
3222
3222
|
import_fs = __toESM(require("fs"), 1);
|
|
3223
3223
|
import_path = __toESM(require("path"), 1);
|
|
3224
|
+
BOOTSPRING_VERSION = "2.5.0";
|
|
3225
|
+
BOOTSPRING_PACKAGE_NAME = "@girardmedia/bootspring";
|
|
3224
3226
|
REDACTED = "[REDACTED]";
|
|
3225
3227
|
SENSITIVE_KEY_PATTERN = /(?:^|[_-])(api[_-]?key|token|refresh[_-]?token|authorization|x[_-]?api[_-]?key|project[_-]?id)$/i;
|
|
3226
3228
|
COLORS = {
|
|
@@ -3269,6 +3271,8 @@ __export(dist_exports, {
|
|
|
3269
3271
|
API_BASE: () => API_BASE,
|
|
3270
3272
|
API_VERSION: () => API_VERSION,
|
|
3271
3273
|
BOOTSPRING_DIR: () => BOOTSPRING_DIR,
|
|
3274
|
+
BOOTSPRING_PACKAGE_NAME: () => BOOTSPRING_PACKAGE_NAME,
|
|
3275
|
+
BOOTSPRING_VERSION: () => BOOTSPRING_VERSION,
|
|
3272
3276
|
COLORS: () => COLORS,
|
|
3273
3277
|
CONFIG_FILE: () => CONFIG_FILE,
|
|
3274
3278
|
CREDENTIALS_FILE: () => CREDENTIALS_FILE,
|
|
@@ -4681,6 +4685,7 @@ var {
|
|
|
4681
4685
|
|
|
4682
4686
|
// src/index.ts
|
|
4683
4687
|
init_dist2();
|
|
4688
|
+
init_dist();
|
|
4684
4689
|
|
|
4685
4690
|
// src/middleware.ts
|
|
4686
4691
|
init_cjs_shims();
|
|
@@ -7698,6 +7703,367 @@ init_cjs_shims();
|
|
|
7698
7703
|
var fs8 = __toESM(require("fs"), 1);
|
|
7699
7704
|
var path9 = __toESM(require("path"), 1);
|
|
7700
7705
|
init_dist();
|
|
7706
|
+
function getPlanningSurfaceStatus() {
|
|
7707
|
+
const planningDir = path9.join(process.cwd(), "planning");
|
|
7708
|
+
return {
|
|
7709
|
+
hasTodo: fs8.existsSync(path9.join(planningDir, "TODO.md")),
|
|
7710
|
+
hasQueue: fs8.existsSync(path9.join(planningDir, "TASK_QUEUE.md"))
|
|
7711
|
+
};
|
|
7712
|
+
}
|
|
7713
|
+
function printLegacyQueueWarningIfNeeded() {
|
|
7714
|
+
const { hasTodo, hasQueue } = getPlanningSurfaceStatus();
|
|
7715
|
+
if (hasTodo && hasQueue) {
|
|
7716
|
+
print.warning("Legacy planning conflict detected: planning/TODO.md is canonical, but planning/TASK_QUEUE.md is still present.");
|
|
7717
|
+
print.info("Run `bootspring build migrate-todo` to archive the lingering queue file.");
|
|
7718
|
+
}
|
|
7719
|
+
}
|
|
7720
|
+
function formatPlanningSourceLabel(source) {
|
|
7721
|
+
if (source === "TODO.md") return "planning/TODO.md";
|
|
7722
|
+
if (source === "TASK_QUEUE.md") return "planning/TASK_QUEUE.md (legacy fallback)";
|
|
7723
|
+
if (source === "BUILD_STATE.json") return "planning/BUILD_STATE.json";
|
|
7724
|
+
return "not detected";
|
|
7725
|
+
}
|
|
7726
|
+
function normalizeStatus(value) {
|
|
7727
|
+
const normalized = String(value || "pending").trim().toLowerCase().replace(/[\s-]+/g, "_");
|
|
7728
|
+
if (normalized === "done" || normalized === "complete" || normalized === "completed") {
|
|
7729
|
+
return "completed";
|
|
7730
|
+
}
|
|
7731
|
+
if (normalized === "inprogress" || normalized === "in_progress") {
|
|
7732
|
+
return "in_progress";
|
|
7733
|
+
}
|
|
7734
|
+
if (normalized === "blocked" || normalized === "skipped") {
|
|
7735
|
+
return normalized;
|
|
7736
|
+
}
|
|
7737
|
+
return normalized === "pending" ? "pending" : "pending";
|
|
7738
|
+
}
|
|
7739
|
+
function normalizePhase(value) {
|
|
7740
|
+
const normalized = String(value || "mvp").trim().toLowerCase().replace(/[\s-]+/g, "_");
|
|
7741
|
+
return ["foundation", "mvp", "launch"].includes(normalized) ? normalized : "mvp";
|
|
7742
|
+
}
|
|
7743
|
+
function formatPhaseName(phase) {
|
|
7744
|
+
if (!phase) return "Unknown";
|
|
7745
|
+
if (phase.toLowerCase() === "mvp") return "MVP";
|
|
7746
|
+
const normalized = normalizePhase(phase);
|
|
7747
|
+
return normalized.charAt(0).toUpperCase() + normalized.slice(1);
|
|
7748
|
+
}
|
|
7749
|
+
function parseDependencyIds(rawText) {
|
|
7750
|
+
const matches = rawText.match(/[a-z0-9][a-z0-9_-]*/gi) || [];
|
|
7751
|
+
const ids = [];
|
|
7752
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7753
|
+
for (const match of matches) {
|
|
7754
|
+
const token = match.trim();
|
|
7755
|
+
if (!token.includes("-") || !/\d/.test(token) || seen.has(token)) continue;
|
|
7756
|
+
seen.add(token);
|
|
7757
|
+
ids.push(token);
|
|
7758
|
+
}
|
|
7759
|
+
return ids;
|
|
7760
|
+
}
|
|
7761
|
+
function parseSourceMetadata(rawSource) {
|
|
7762
|
+
const trimmed = rawSource.trim();
|
|
7763
|
+
const match = trimmed.match(/^(.+?)\s+\((.+)\)$/);
|
|
7764
|
+
if (match?.[1] && match?.[2]) {
|
|
7765
|
+
return {
|
|
7766
|
+
source: match[1].trim(),
|
|
7767
|
+
sourceSection: match[2].trim()
|
|
7768
|
+
};
|
|
7769
|
+
}
|
|
7770
|
+
return { source: trimmed };
|
|
7771
|
+
}
|
|
7772
|
+
function parseTodoTasks(content) {
|
|
7773
|
+
const tasks = [];
|
|
7774
|
+
const lines = content.split("\n");
|
|
7775
|
+
let currentPhase = "mvp";
|
|
7776
|
+
let currentTask = null;
|
|
7777
|
+
const pushTask = () => {
|
|
7778
|
+
if (!currentTask) return;
|
|
7779
|
+
tasks.push(currentTask);
|
|
7780
|
+
currentTask = null;
|
|
7781
|
+
};
|
|
7782
|
+
for (const line of lines) {
|
|
7783
|
+
const phaseMatch = line.match(/^##\s+(Foundation|MVP|Launch)\b/i);
|
|
7784
|
+
if (phaseMatch?.[1]) {
|
|
7785
|
+
pushTask();
|
|
7786
|
+
currentPhase = normalizePhase(phaseMatch[1]);
|
|
7787
|
+
continue;
|
|
7788
|
+
}
|
|
7789
|
+
const taskMatch = line.match(/^-\s+\[([ xX])\]\s+`([^`]+)`\s+(.+?)(?:\s+\(`?([\w-]+)`?\))?\s*$/);
|
|
7790
|
+
if (taskMatch?.[2] && taskMatch?.[3]) {
|
|
7791
|
+
pushTask();
|
|
7792
|
+
const checked = taskMatch[1]?.toLowerCase() === "x";
|
|
7793
|
+
currentTask = {
|
|
7794
|
+
id: taskMatch[2].trim(),
|
|
7795
|
+
title: taskMatch[3].trim(),
|
|
7796
|
+
phase: currentPhase,
|
|
7797
|
+
status: normalizeStatus(taskMatch[4] || (checked ? "completed" : "pending")),
|
|
7798
|
+
acceptanceCriteria: [],
|
|
7799
|
+
dependencies: []
|
|
7800
|
+
};
|
|
7801
|
+
continue;
|
|
7802
|
+
}
|
|
7803
|
+
if (!currentTask) {
|
|
7804
|
+
continue;
|
|
7805
|
+
}
|
|
7806
|
+
const sourceMatch = line.match(/^\s{2,}-\s+\*\*Source:\*\*\s+(.+)\s*$/);
|
|
7807
|
+
if (sourceMatch?.[1]) {
|
|
7808
|
+
const sourceMetadata = parseSourceMetadata(sourceMatch[1]);
|
|
7809
|
+
currentTask.source = sourceMetadata.source;
|
|
7810
|
+
currentTask.sourceSection = sourceMetadata.sourceSection;
|
|
7811
|
+
continue;
|
|
7812
|
+
}
|
|
7813
|
+
const descriptionMatch = line.match(/^\s{2,}-\s+\*\*Description:\*\*\s+(.+)\s*$/);
|
|
7814
|
+
if (descriptionMatch?.[1]) {
|
|
7815
|
+
currentTask.description = descriptionMatch[1].trim();
|
|
7816
|
+
continue;
|
|
7817
|
+
}
|
|
7818
|
+
const complexityMatch = line.match(/^\s{2,}-\s+\*\*Complexity:\*\*\s+(.+)\s*$/);
|
|
7819
|
+
if (complexityMatch?.[1]) {
|
|
7820
|
+
currentTask.complexity = complexityMatch[1].trim().toLowerCase();
|
|
7821
|
+
continue;
|
|
7822
|
+
}
|
|
7823
|
+
const dependenciesMatch = line.match(/^\s{2,}-\s+\*\*Dependencies:\*\*\s+(.+)\s*$/);
|
|
7824
|
+
if (dependenciesMatch?.[1]) {
|
|
7825
|
+
currentTask.dependencies = parseDependencyIds(dependenciesMatch[1]);
|
|
7826
|
+
continue;
|
|
7827
|
+
}
|
|
7828
|
+
const criteriaMatch = line.match(/^\s{2,}-\s+\[[ xX]\]\s+(.+)\s*$/);
|
|
7829
|
+
if (criteriaMatch?.[1]) {
|
|
7830
|
+
currentTask.acceptanceCriteria?.push(criteriaMatch[1].trim());
|
|
7831
|
+
}
|
|
7832
|
+
}
|
|
7833
|
+
pushTask();
|
|
7834
|
+
return tasks;
|
|
7835
|
+
}
|
|
7836
|
+
function parseQueueTasks(content) {
|
|
7837
|
+
const tasks = [];
|
|
7838
|
+
const tableMatches = content.matchAll(/^\|\s*([^|]+)\s*\|\s*([a-z0-9][a-z0-9_-]*)\s*\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|$/gmi);
|
|
7839
|
+
for (const match of tableMatches) {
|
|
7840
|
+
const id = match[2]?.trim() ?? "";
|
|
7841
|
+
const title = match[3]?.trim() ?? "";
|
|
7842
|
+
if (id.toLowerCase() === "id" || title.toLowerCase() === "task" || id.includes("---") || title.includes("---")) {
|
|
7843
|
+
continue;
|
|
7844
|
+
}
|
|
7845
|
+
tasks.push({
|
|
7846
|
+
id,
|
|
7847
|
+
title,
|
|
7848
|
+
phase: normalizePhase(match[4]?.trim()),
|
|
7849
|
+
complexity: String(match[5] || "medium").trim().toLowerCase(),
|
|
7850
|
+
status: normalizeStatus(match[6]?.trim()),
|
|
7851
|
+
acceptanceCriteria: [],
|
|
7852
|
+
dependencies: []
|
|
7853
|
+
});
|
|
7854
|
+
}
|
|
7855
|
+
const detailMatches = content.matchAll(/^###\s+([a-z0-9][a-z0-9_-]*):\s*(.+)$/gmi);
|
|
7856
|
+
for (const match of detailMatches) {
|
|
7857
|
+
const id = match[1]?.trim() ?? "";
|
|
7858
|
+
const sectionStart = (match.index ?? 0) + match[0].length;
|
|
7859
|
+
const nextSectionOffset = content.slice(sectionStart).search(/^###\s+/m);
|
|
7860
|
+
const sectionContent = nextSectionOffset === -1 ? content.slice(sectionStart) : content.slice(sectionStart, sectionStart + nextSectionOffset);
|
|
7861
|
+
const existing = tasks.find((task) => task.id === id);
|
|
7862
|
+
const acceptanceCriteria = [];
|
|
7863
|
+
const criteriaBlock = sectionContent.match(/\*{0,2}Acceptance\s+Criteria:\*{0,2}\s*([\s\S]*?)(?=\n\*{0,2}[A-Z]|\n---|\n###|$)/i);
|
|
7864
|
+
if (criteriaBlock?.[1]) {
|
|
7865
|
+
for (const criteriaMatch of criteriaBlock[1].matchAll(/^\s*[-*]\s+(?:\[[ xX]\]\s*)?(.+)$/gm)) {
|
|
7866
|
+
if (criteriaMatch[1]) {
|
|
7867
|
+
acceptanceCriteria.push(criteriaMatch[1].trim());
|
|
7868
|
+
}
|
|
7869
|
+
}
|
|
7870
|
+
}
|
|
7871
|
+
const dependencies = (() => {
|
|
7872
|
+
const dependencyMatch = sectionContent.match(/\*{0,2}(?:Dependencies|Depends on):\*{0,2}\s*([\s\S]*?)(?=\n\*{0,2}[A-Z]|\n---|\n###|$)/i);
|
|
7873
|
+
return dependencyMatch?.[1] ? parseDependencyIds(dependencyMatch[1]) : [];
|
|
7874
|
+
})();
|
|
7875
|
+
if (existing) {
|
|
7876
|
+
existing.acceptanceCriteria = acceptanceCriteria;
|
|
7877
|
+
existing.dependencies = dependencies;
|
|
7878
|
+
}
|
|
7879
|
+
}
|
|
7880
|
+
return tasks;
|
|
7881
|
+
}
|
|
7882
|
+
function loadPlanningTasks() {
|
|
7883
|
+
const todoFile = path9.join(process.cwd(), "planning", "TODO.md");
|
|
7884
|
+
const queueFile = path9.join(process.cwd(), "planning", "TASK_QUEUE.md");
|
|
7885
|
+
if (fs8.existsSync(todoFile)) {
|
|
7886
|
+
try {
|
|
7887
|
+
const tasks = parseTodoTasks(fs8.readFileSync(todoFile, "utf-8"));
|
|
7888
|
+
if (tasks.length > 0) {
|
|
7889
|
+
return { tasks, source: "TODO.md" };
|
|
7890
|
+
}
|
|
7891
|
+
} catch {
|
|
7892
|
+
}
|
|
7893
|
+
}
|
|
7894
|
+
if (fs8.existsSync(queueFile)) {
|
|
7895
|
+
try {
|
|
7896
|
+
const tasks = parseQueueTasks(fs8.readFileSync(queueFile, "utf-8"));
|
|
7897
|
+
if (tasks.length > 0) {
|
|
7898
|
+
return { tasks, source: "TASK_QUEUE.md" };
|
|
7899
|
+
}
|
|
7900
|
+
} catch {
|
|
7901
|
+
}
|
|
7902
|
+
}
|
|
7903
|
+
return { tasks: [], source: null };
|
|
7904
|
+
}
|
|
7905
|
+
function renderTodo(tasks, projectName) {
|
|
7906
|
+
const byPhase = /* @__PURE__ */ new Map();
|
|
7907
|
+
for (const task of tasks) {
|
|
7908
|
+
const phase = normalizePhase(task.phase);
|
|
7909
|
+
const phaseTasks = byPhase.get(phase) ?? [];
|
|
7910
|
+
phaseTasks.push({ ...task, phase });
|
|
7911
|
+
byPhase.set(phase, phaseTasks);
|
|
7912
|
+
}
|
|
7913
|
+
const orderedPhases = ["foundation", "mvp", "launch", ...Array.from(byPhase.keys()).filter((phase) => !["foundation", "mvp", "launch"].includes(phase))];
|
|
7914
|
+
const total = tasks.length;
|
|
7915
|
+
const completed = tasks.filter((task) => normalizeStatus(task.status) === "completed").length;
|
|
7916
|
+
const pending = tasks.filter((task) => normalizeStatus(task.status) === "pending").length;
|
|
7917
|
+
const inProgress = tasks.filter((task) => normalizeStatus(task.status) === "in_progress").length;
|
|
7918
|
+
let content = `# ${projectName} - Build Todo
|
|
7919
|
+
|
|
7920
|
+
> Single source of truth for autonomous build execution
|
|
7921
|
+
> Updated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
|
|
7922
|
+
|
|
7923
|
+
---
|
|
7924
|
+
|
|
7925
|
+
## Program Status
|
|
7926
|
+
|
|
7927
|
+
| Metric | Value |
|
|
7928
|
+
|---|---:|
|
|
7929
|
+
| Total Tasks | ${total} |
|
|
7930
|
+
| Completed | ${completed} |
|
|
7931
|
+
| Remaining | ${pending} |
|
|
7932
|
+
| In Progress | ${inProgress} |
|
|
7933
|
+
|
|
7934
|
+
---
|
|
7935
|
+
|
|
7936
|
+
`;
|
|
7937
|
+
for (const phase of orderedPhases) {
|
|
7938
|
+
const phaseTasks = byPhase.get(phase);
|
|
7939
|
+
if (!phaseTasks || phaseTasks.length === 0) continue;
|
|
7940
|
+
const phaseCompleted = phaseTasks.filter((task) => normalizeStatus(task.status) === "completed").length;
|
|
7941
|
+
const percent = phaseTasks.length > 0 ? Math.round(phaseCompleted / phaseTasks.length * 100) : 0;
|
|
7942
|
+
content += `## ${formatPhaseName(phase)} (${phaseCompleted}/${phaseTasks.length} \u2014 ${percent}%)
|
|
7943
|
+
|
|
7944
|
+
`;
|
|
7945
|
+
for (const task of phaseTasks) {
|
|
7946
|
+
const status = normalizeStatus(task.status);
|
|
7947
|
+
const checkbox = status === "completed" ? "[x]" : "[ ]";
|
|
7948
|
+
const statusTag = status === "pending" ? "" : ` (\`${status}\`)`;
|
|
7949
|
+
content += `- ${checkbox} \`${task.id}\` ${task.title}${statusTag}
|
|
7950
|
+
`;
|
|
7951
|
+
if (task.source) {
|
|
7952
|
+
const sourceLabel = task.sourceSection ? `${task.source} (${task.sourceSection})` : task.source;
|
|
7953
|
+
content += ` - **Source:** ${sourceLabel}
|
|
7954
|
+
`;
|
|
7955
|
+
}
|
|
7956
|
+
if (task.description) {
|
|
7957
|
+
content += ` - **Description:** ${task.description}
|
|
7958
|
+
`;
|
|
7959
|
+
}
|
|
7960
|
+
if (task.complexity && task.complexity !== "medium") {
|
|
7961
|
+
content += ` - **Complexity:** ${task.complexity}
|
|
7962
|
+
`;
|
|
7963
|
+
}
|
|
7964
|
+
if (task.dependencies && task.dependencies.length > 0) {
|
|
7965
|
+
content += ` - **Dependencies:** ${task.dependencies.join(", ")}
|
|
7966
|
+
`;
|
|
7967
|
+
}
|
|
7968
|
+
if (task.acceptanceCriteria && task.acceptanceCriteria.length > 0) {
|
|
7969
|
+
const criteriaCheckbox = status === "completed" ? "[x]" : "[ ]";
|
|
7970
|
+
for (const criterion of task.acceptanceCriteria) {
|
|
7971
|
+
content += ` - ${criteriaCheckbox} ${criterion}
|
|
7972
|
+
`;
|
|
7973
|
+
}
|
|
7974
|
+
}
|
|
7975
|
+
}
|
|
7976
|
+
content += "\n---\n\n";
|
|
7977
|
+
}
|
|
7978
|
+
content += `*Updated: ${(/* @__PURE__ */ new Date()).toISOString()}*
|
|
7979
|
+
`;
|
|
7980
|
+
return content;
|
|
7981
|
+
}
|
|
7982
|
+
function getCurrentPhase(queue) {
|
|
7983
|
+
const active = queue?.find((task) => normalizeStatus(task.status) === "in_progress") ?? queue?.find((task) => normalizeStatus(task.status) === "pending");
|
|
7984
|
+
return active?.phase ? formatPhaseName(active.phase) : "N/A";
|
|
7985
|
+
}
|
|
7986
|
+
function getTaskEntriesFromState(state) {
|
|
7987
|
+
return (state?.implementationQueue ?? []).map((task) => ({
|
|
7988
|
+
id: task.id,
|
|
7989
|
+
title: task.title,
|
|
7990
|
+
phase: task.phase,
|
|
7991
|
+
complexity: task.estimatedComplexity,
|
|
7992
|
+
status: normalizeStatus(task.status),
|
|
7993
|
+
description: task.description,
|
|
7994
|
+
source: task.source,
|
|
7995
|
+
sourceSection: task.sourceSection,
|
|
7996
|
+
acceptanceCriteria: task.acceptanceCriteria ?? [],
|
|
7997
|
+
dependencies: task.dependencies ?? []
|
|
7998
|
+
}));
|
|
7999
|
+
}
|
|
8000
|
+
function extractSupplementaryContent(queueContent) {
|
|
8001
|
+
const lines = queueContent.split("\n");
|
|
8002
|
+
const sections = [];
|
|
8003
|
+
let currentSection = null;
|
|
8004
|
+
let inTaskTable = false;
|
|
8005
|
+
let inTaskDetail = false;
|
|
8006
|
+
for (const rawLine of lines) {
|
|
8007
|
+
const line = rawLine ?? "";
|
|
8008
|
+
if (/^\|\s*(Priority|Position|#)\s*\|/i.test(line)) {
|
|
8009
|
+
inTaskTable = true;
|
|
8010
|
+
continue;
|
|
8011
|
+
}
|
|
8012
|
+
if (inTaskTable && /^\|/.test(line)) continue;
|
|
8013
|
+
if (inTaskTable && !/^\|/.test(line)) inTaskTable = false;
|
|
8014
|
+
if (/^###\s+([a-z0-9][a-z0-9_-]*)\s*:/.test(line)) {
|
|
8015
|
+
inTaskDetail = true;
|
|
8016
|
+
continue;
|
|
8017
|
+
}
|
|
8018
|
+
if (inTaskDetail && /^#{2,3}\s+/.test(line) && !/^###\s+([a-z0-9][a-z0-9_-]*)\s*:/.test(line)) {
|
|
8019
|
+
inTaskDetail = false;
|
|
8020
|
+
}
|
|
8021
|
+
if (inTaskDetail) continue;
|
|
8022
|
+
if (/^#\s+.*(?:Implementation Queue|Task Queue)\b/i.test(line)) continue;
|
|
8023
|
+
if (/^>\s*(Ordered task queue|Last Updated|Current Version)/i.test(line)) continue;
|
|
8024
|
+
if (/^##\s+(Queue Status|Task Details)\b/i.test(line)) {
|
|
8025
|
+
if (/^##\s+Queue Status\b/i.test(line)) inTaskTable = true;
|
|
8026
|
+
if (/^##\s+Task Details\b/i.test(line)) inTaskDetail = true;
|
|
8027
|
+
continue;
|
|
8028
|
+
}
|
|
8029
|
+
if (/^\*Generated by/i.test(line)) continue;
|
|
8030
|
+
if (/^---$/.test(line.trim())) continue;
|
|
8031
|
+
if (/^##\s+/.test(line)) {
|
|
8032
|
+
if (currentSection) sections.push(currentSection);
|
|
8033
|
+
currentSection = { heading: line, lines: [] };
|
|
8034
|
+
continue;
|
|
8035
|
+
}
|
|
8036
|
+
if (currentSection) {
|
|
8037
|
+
currentSection.lines.push(line);
|
|
8038
|
+
}
|
|
8039
|
+
}
|
|
8040
|
+
if (currentSection) {
|
|
8041
|
+
sections.push(currentSection);
|
|
8042
|
+
}
|
|
8043
|
+
return sections.filter((section) => section.lines.some((line) => line.trim().length > 0)).map((section) => `${section.heading}
|
|
8044
|
+
|
|
8045
|
+
${section.lines.join("\n").trim()}`).join("\n\n---\n\n").trim();
|
|
8046
|
+
}
|
|
8047
|
+
function getAvailableArchivePath(filePath) {
|
|
8048
|
+
const baseArchivePath = `${filePath}.bak`;
|
|
8049
|
+
if (!fs8.existsSync(baseArchivePath)) {
|
|
8050
|
+
return baseArchivePath;
|
|
8051
|
+
}
|
|
8052
|
+
let counter = 1;
|
|
8053
|
+
while (fs8.existsSync(`${baseArchivePath}.${counter}`)) {
|
|
8054
|
+
counter += 1;
|
|
8055
|
+
}
|
|
8056
|
+
return `${baseArchivePath}.${counter}`;
|
|
8057
|
+
}
|
|
8058
|
+
function syncTodoFromState(state) {
|
|
8059
|
+
const queue = state.implementationQueue ?? [];
|
|
8060
|
+
if (queue.length === 0) return;
|
|
8061
|
+
const dir = path9.join(process.cwd(), "planning");
|
|
8062
|
+
if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
|
|
8063
|
+
const todoPath = path9.join(dir, "TODO.md");
|
|
8064
|
+
const tasks = getTaskEntriesFromState(state);
|
|
8065
|
+
fs8.writeFileSync(todoPath, renderTodo(tasks, state.projectName ?? path9.basename(process.cwd())));
|
|
8066
|
+
}
|
|
7701
8067
|
function loadBuildState() {
|
|
7702
8068
|
const stateFile = path9.join(process.cwd(), "planning", "BUILD_STATE.json");
|
|
7703
8069
|
if (!fs8.existsSync(stateFile)) return null;
|
|
@@ -7710,67 +8076,21 @@ function loadBuildState() {
|
|
|
7710
8076
|
function saveBuildState(state) {
|
|
7711
8077
|
const dir = path9.join(process.cwd(), "planning");
|
|
7712
8078
|
if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
|
|
8079
|
+
if (state.implementationQueue?.length) {
|
|
8080
|
+
state.currentPhase = getCurrentPhase(state.implementationQueue);
|
|
8081
|
+
}
|
|
7713
8082
|
if (state.metadata) state.metadata.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7714
8083
|
fs8.writeFileSync(path9.join(dir, "BUILD_STATE.json"), JSON.stringify(state, null, 2));
|
|
8084
|
+
syncTodoFromState(state);
|
|
7715
8085
|
}
|
|
7716
8086
|
function loadTasks() {
|
|
7717
|
-
const
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
if (fs8.existsSync(todoFile)) files.push(todoFile);
|
|
7721
|
-
if (fs8.existsSync(queueFile)) files.push(queueFile);
|
|
7722
|
-
for (const file of files) {
|
|
7723
|
-
try {
|
|
7724
|
-
const content = fs8.readFileSync(file, "utf-8");
|
|
7725
|
-
const tasks = [];
|
|
7726
|
-
if (file.endsWith("TODO.md")) {
|
|
7727
|
-
let currentPhase = "Unknown";
|
|
7728
|
-
for (const line of content.split("\n")) {
|
|
7729
|
-
const phaseMatch = line.match(/^##\s+(Foundation|MVP|Launch)\b/i);
|
|
7730
|
-
if (phaseMatch) {
|
|
7731
|
-
currentPhase = phaseMatch[1];
|
|
7732
|
-
continue;
|
|
7733
|
-
}
|
|
7734
|
-
const todoMatch = line.match(/^-\s+\[([ xX])\]\s+`(bs-\d+)`\s+(.+?)(?:\s+\(`?(\w+)`?\))?$/);
|
|
7735
|
-
if (todoMatch) {
|
|
7736
|
-
const checked = todoMatch[1].toLowerCase() === "x";
|
|
7737
|
-
const explicitStatus = todoMatch[4]?.toLowerCase();
|
|
7738
|
-
tasks.push({
|
|
7739
|
-
id: todoMatch[2],
|
|
7740
|
-
title: todoMatch[3].trim(),
|
|
7741
|
-
phase: currentPhase,
|
|
7742
|
-
status: explicitStatus || (checked ? "completed" : "pending")
|
|
7743
|
-
});
|
|
7744
|
-
}
|
|
7745
|
-
}
|
|
7746
|
-
if (tasks.length > 0) return tasks;
|
|
7747
|
-
}
|
|
7748
|
-
for (const line of content.split("\n")) {
|
|
7749
|
-
const match = line.match(/\|\s*\d+\s*\|\s*(bs-\d+)\s*\|\s*(.+?)\s*\|\s*(\w+)\s*\|\s*(\w+)\s*\|\s*(\w+)\s*\|/);
|
|
7750
|
-
if (match) {
|
|
7751
|
-
tasks.push({
|
|
7752
|
-
id: match[1],
|
|
7753
|
-
title: match[2].trim(),
|
|
7754
|
-
phase: match[3],
|
|
7755
|
-
complexity: match[4],
|
|
7756
|
-
status: match[5]
|
|
7757
|
-
});
|
|
7758
|
-
}
|
|
7759
|
-
}
|
|
7760
|
-
if (tasks.length > 0) return tasks;
|
|
7761
|
-
} catch {
|
|
7762
|
-
continue;
|
|
7763
|
-
}
|
|
8087
|
+
const planningTasks = loadPlanningTasks();
|
|
8088
|
+
if (planningTasks.tasks.length > 0) {
|
|
8089
|
+
return planningTasks.tasks;
|
|
7764
8090
|
}
|
|
7765
8091
|
const state = loadBuildState();
|
|
7766
8092
|
if (state?.implementationQueue?.length) {
|
|
7767
|
-
return state
|
|
7768
|
-
id: t.id,
|
|
7769
|
-
title: t.title,
|
|
7770
|
-
phase: t.phase,
|
|
7771
|
-
complexity: t.estimatedComplexity,
|
|
7772
|
-
status: t.status
|
|
7773
|
-
}));
|
|
8093
|
+
return getTaskEntriesFromState(state);
|
|
7774
8094
|
}
|
|
7775
8095
|
return [];
|
|
7776
8096
|
}
|
|
@@ -7778,8 +8098,10 @@ function registerBuildCommand(program3) {
|
|
|
7778
8098
|
const build = program3.command("build").description("Manage the build loop");
|
|
7779
8099
|
build.command("status").description("Check build progress").action(() => {
|
|
7780
8100
|
const state = loadBuildState();
|
|
7781
|
-
const
|
|
8101
|
+
const planningTasks = loadPlanningTasks();
|
|
8102
|
+
const tasks = planningTasks.tasks.length > 0 ? planningTasks.tasks : getTaskEntriesFromState(state);
|
|
7782
8103
|
print.header("Build Status");
|
|
8104
|
+
printLegacyQueueWarningIfNeeded();
|
|
7783
8105
|
if (!state) {
|
|
7784
8106
|
print.warning("No build state found. Run `bootspring build start` to begin.");
|
|
7785
8107
|
return;
|
|
@@ -7797,11 +8119,16 @@ function registerBuildCommand(program3) {
|
|
|
7797
8119
|
const sessionId = state.loopSession?.sessionId ?? "N/A";
|
|
7798
8120
|
print.info(`Project: ${state.projectName ?? "Unknown"}`);
|
|
7799
8121
|
print.info(`Status: ${state.status}`);
|
|
7800
|
-
print.info(`Phase: ${state.currentPhase ??
|
|
8122
|
+
print.info(`Phase: ${formatPhaseName(state.currentPhase ?? getCurrentPhase(state.implementationQueue))}`);
|
|
8123
|
+
print.info(`Planning Source: ${formatPlanningSourceLabel(planningTasks.source ?? (state ? "BUILD_STATE.json" : null))}`);
|
|
7801
8124
|
print.info(`Progress: ${completed}/${total} (${pct}%)`);
|
|
7802
8125
|
print.info(`Pending: ${pending}`);
|
|
7803
8126
|
if (inProgress > 0) print.info(`In Progress: ${inProgress}`);
|
|
7804
|
-
|
|
8127
|
+
if (iteration > maxIter) {
|
|
8128
|
+
print.info(`Iteration: ${iteration} (session max ${maxIter})`);
|
|
8129
|
+
} else {
|
|
8130
|
+
print.info(`Iteration: ${iteration}/${maxIter}`);
|
|
8131
|
+
}
|
|
7805
8132
|
print.info(`Session: ${sessionId}`);
|
|
7806
8133
|
const currentTask = tasks.find((t) => t.status.toLowerCase() === "in_progress");
|
|
7807
8134
|
if (currentTask) {
|
|
@@ -7813,6 +8140,7 @@ function registerBuildCommand(program3) {
|
|
|
7813
8140
|
console.log(` [${bar}] ${pct}%`);
|
|
7814
8141
|
});
|
|
7815
8142
|
build.command("task").description("Show the current task").action(() => {
|
|
8143
|
+
printLegacyQueueWarningIfNeeded();
|
|
7816
8144
|
const tasks = loadTasks();
|
|
7817
8145
|
const current = tasks.find((t) => t.status.toLowerCase() === "in_progress");
|
|
7818
8146
|
const next = current ?? tasks.find((t) => t.status.toLowerCase() === "pending");
|
|
@@ -7857,6 +8185,7 @@ function registerBuildCommand(program3) {
|
|
|
7857
8185
|
}
|
|
7858
8186
|
});
|
|
7859
8187
|
build.command("plan").description("View the full build plan").action(() => {
|
|
8188
|
+
printLegacyQueueWarningIfNeeded();
|
|
7860
8189
|
const tasks = loadTasks();
|
|
7861
8190
|
if (tasks.length === 0) {
|
|
7862
8191
|
print.warning("No tasks found in planning/TODO.md or planning/TASK_QUEUE.md");
|
|
@@ -7956,10 +8285,11 @@ function registerBuildCommand(program3) {
|
|
|
7956
8285
|
saveBuildState(state);
|
|
7957
8286
|
print.info("Build resumed");
|
|
7958
8287
|
});
|
|
7959
|
-
build.command("sync").description("Sync tasks from
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
8288
|
+
build.command("sync").description("Sync planning tasks from TODO.md into BUILD_STATE.json").option("--replace", "Replace runtime queue instead of merging").action((opts) => {
|
|
8289
|
+
printLegacyQueueWarningIfNeeded();
|
|
8290
|
+
const planningTasks = loadPlanningTasks();
|
|
8291
|
+
if (planningTasks.tasks.length === 0) {
|
|
8292
|
+
print.warning("No planning/TODO.md found (legacy fallback: planning/TASK_QUEUE.md)");
|
|
7963
8293
|
return;
|
|
7964
8294
|
}
|
|
7965
8295
|
const state = loadBuildState();
|
|
@@ -7967,24 +8297,62 @@ function registerBuildCommand(program3) {
|
|
|
7967
8297
|
print.error("No build state found");
|
|
7968
8298
|
return;
|
|
7969
8299
|
}
|
|
7970
|
-
const
|
|
7971
|
-
const
|
|
8300
|
+
const replace = Boolean(opts.replace);
|
|
8301
|
+
const planningIds = new Set(planningTasks.tasks.map((task) => task.id));
|
|
8302
|
+
const existingQueue = replace ? [] : state.implementationQueue ?? [];
|
|
8303
|
+
const existingIds = new Set(existingQueue.map((t) => t.id));
|
|
7972
8304
|
let added = 0;
|
|
7973
|
-
|
|
8305
|
+
let updated = 0;
|
|
8306
|
+
for (const task of planningTasks.tasks) {
|
|
7974
8307
|
if (!existingIds.has(task.id)) {
|
|
7975
|
-
|
|
7976
|
-
state.implementationQueue.push({
|
|
8308
|
+
existingQueue.push({
|
|
7977
8309
|
id: task.id,
|
|
7978
8310
|
title: task.title,
|
|
7979
8311
|
phase: task.phase,
|
|
7980
8312
|
status: task.status,
|
|
7981
|
-
estimatedComplexity: task.complexity
|
|
8313
|
+
estimatedComplexity: task.complexity,
|
|
8314
|
+
description: task.description,
|
|
8315
|
+
source: task.source,
|
|
8316
|
+
sourceSection: task.sourceSection,
|
|
8317
|
+
acceptanceCriteria: task.acceptanceCriteria,
|
|
8318
|
+
dependencies: task.dependencies
|
|
7982
8319
|
});
|
|
7983
8320
|
added++;
|
|
8321
|
+
continue;
|
|
7984
8322
|
}
|
|
8323
|
+
const index = existingQueue.findIndex((existing) => existing.id === task.id);
|
|
8324
|
+
if (index >= 0) {
|
|
8325
|
+
const previous = existingQueue[index];
|
|
8326
|
+
const nextTask = {
|
|
8327
|
+
...previous,
|
|
8328
|
+
id: task.id,
|
|
8329
|
+
title: task.title,
|
|
8330
|
+
phase: task.phase,
|
|
8331
|
+
status: task.status,
|
|
8332
|
+
estimatedComplexity: task.complexity,
|
|
8333
|
+
description: task.description,
|
|
8334
|
+
source: task.source,
|
|
8335
|
+
sourceSection: task.sourceSection,
|
|
8336
|
+
acceptanceCriteria: task.acceptanceCriteria,
|
|
8337
|
+
dependencies: task.dependencies
|
|
8338
|
+
};
|
|
8339
|
+
if (JSON.stringify(previous) !== JSON.stringify(nextTask)) {
|
|
8340
|
+
existingQueue[index] = nextTask;
|
|
8341
|
+
updated++;
|
|
8342
|
+
}
|
|
8343
|
+
}
|
|
8344
|
+
}
|
|
8345
|
+
const orderedQueue = [
|
|
8346
|
+
...planningTasks.tasks.map((task) => existingQueue.find((existing) => existing.id === task.id)).filter(Boolean),
|
|
8347
|
+
...existingQueue.filter((task) => !planningIds.has(task.id))
|
|
8348
|
+
];
|
|
8349
|
+
state.implementationQueue = orderedQueue;
|
|
8350
|
+
if (!state.metadata) {
|
|
8351
|
+
state.metadata = { updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
7985
8352
|
}
|
|
7986
8353
|
saveBuildState(state);
|
|
7987
|
-
print.success(`Synced ${added} new tasks (${state.implementationQueue?.length ?? 0} total)`);
|
|
8354
|
+
print.success(`Synced ${added} new tasks and updated ${updated} tasks (${state.implementationQueue?.length ?? 0} total)`);
|
|
8355
|
+
print.info(`Planning source: ${planningTasks.source ?? "unknown"}`);
|
|
7988
8356
|
});
|
|
7989
8357
|
build.command("stop").description("Graceful stop the build loop").action(() => {
|
|
7990
8358
|
const state = loadBuildState();
|
|
@@ -8040,6 +8408,7 @@ function registerBuildCommand(program3) {
|
|
|
8040
8408
|
print.success(`Started: ${first.id} \u2014 ${first.title}`);
|
|
8041
8409
|
});
|
|
8042
8410
|
build.command("start").description("Initialize build from seed/planning documents").action(() => {
|
|
8411
|
+
printLegacyQueueWarningIfNeeded();
|
|
8043
8412
|
const state = loadBuildState();
|
|
8044
8413
|
if (state) {
|
|
8045
8414
|
print.warning("Build already initialized. Use `bootspring build status` to check progress.");
|
|
@@ -8047,7 +8416,7 @@ function registerBuildCommand(program3) {
|
|
|
8047
8416
|
}
|
|
8048
8417
|
const tasks = loadTasks();
|
|
8049
8418
|
if (tasks.length === 0) {
|
|
8050
|
-
print.warning("No tasks found in planning/TODO.md
|
|
8419
|
+
print.warning("No tasks found in planning/TODO.md (legacy fallback: planning/TASK_QUEUE.md)");
|
|
8051
8420
|
print.info("Run `bootspring preseed` or `bootspring prd create` first to generate tasks.");
|
|
8052
8421
|
return;
|
|
8053
8422
|
}
|
|
@@ -8075,54 +8444,56 @@ function registerBuildCommand(program3) {
|
|
|
8075
8444
|
print.success(`Build initialized with ${tasks.length} tasks`);
|
|
8076
8445
|
print.info("Run `bootspring build next` to start the first task");
|
|
8077
8446
|
});
|
|
8078
|
-
build.command("migrate-todo").alias("migrate").description("Convert TASK_QUEUE.md
|
|
8447
|
+
build.command("migrate-todo").alias("migrate").description("Convert legacy TASK_QUEUE.md into canonical TODO.md format and archive the queue by default").option("--keep-queue", "Keep legacy planning/TASK_QUEUE.md instead of archiving it").action((options) => {
|
|
8079
8448
|
const queueFile = path9.join(process.cwd(), "planning", "TASK_QUEUE.md");
|
|
8080
8449
|
const todoFile = path9.join(process.cwd(), "planning", "TODO.md");
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
if (fs8.existsSync(todoFile)) {
|
|
8086
|
-
print.warning("planning/TODO.md already exists. Delete it first to re-migrate.");
|
|
8087
|
-
return;
|
|
8088
|
-
}
|
|
8450
|
+
const state = loadBuildState();
|
|
8451
|
+
const keepQueue = Boolean(options.keepQueue);
|
|
8452
|
+
const hasTodo = fs8.existsSync(todoFile);
|
|
8453
|
+
const hasQueue = fs8.existsSync(queueFile);
|
|
8089
8454
|
try {
|
|
8090
|
-
const
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
id: match[1],
|
|
8097
|
-
title: match[2].trim(),
|
|
8098
|
-
phase: match[3],
|
|
8099
|
-
complexity: match[4],
|
|
8100
|
-
status: match[5]
|
|
8101
|
-
});
|
|
8102
|
-
}
|
|
8455
|
+
const planningTasks = loadPlanningTasks();
|
|
8456
|
+
let tasks = planningTasks.tasks;
|
|
8457
|
+
let source = planningTasks.source;
|
|
8458
|
+
if (tasks.length === 0) {
|
|
8459
|
+
tasks = getTaskEntriesFromState(state);
|
|
8460
|
+
source = tasks.length > 0 ? "BUILD_STATE.json" : null;
|
|
8103
8461
|
}
|
|
8104
8462
|
if (tasks.length === 0) {
|
|
8105
|
-
print.warning("No tasks found in TASK_QUEUE.md");
|
|
8463
|
+
print.warning("No tasks found in planning/TODO.md, planning/TASK_QUEUE.md, or planning/BUILD_STATE.json");
|
|
8106
8464
|
return;
|
|
8107
8465
|
}
|
|
8108
|
-
const
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
8466
|
+
const dir = path9.dirname(todoFile);
|
|
8467
|
+
if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
|
|
8468
|
+
const projectName = state?.projectName ?? path9.basename(process.cwd());
|
|
8469
|
+
if (!hasTodo || source !== "TODO.md") {
|
|
8470
|
+
let todoContent = renderTodo(tasks, projectName);
|
|
8471
|
+
if (hasQueue && source === "TASK_QUEUE.md") {
|
|
8472
|
+
const supplementaryContent = extractSupplementaryContent(fs8.readFileSync(queueFile, "utf-8"));
|
|
8473
|
+
if (supplementaryContent) {
|
|
8474
|
+
todoContent += `
|
|
8475
|
+
---
|
|
8112
8476
|
|
|
8477
|
+
## Reference (from TASK_QUEUE.md)
|
|
8478
|
+
|
|
8479
|
+
${supplementaryContent}
|
|
8113
8480
|
`;
|
|
8114
|
-
|
|
8115
|
-
for (const task of phaseTasks) {
|
|
8116
|
-
const checked = task.status.toLowerCase() === "completed" || task.status.toLowerCase() === "done";
|
|
8117
|
-
todoContent += `- [${checked ? "x" : " "}] \`${task.id}\` ${task.title}
|
|
8118
|
-
`;
|
|
8481
|
+
}
|
|
8119
8482
|
}
|
|
8120
|
-
todoContent
|
|
8483
|
+
fs8.writeFileSync(todoFile, todoContent);
|
|
8484
|
+
print.success(`Wrote planning/TODO.md from ${source ?? "planning data"} (${tasks.length} tasks)`);
|
|
8485
|
+
} else {
|
|
8486
|
+
print.info("Retained existing planning/TODO.md as the canonical task file");
|
|
8487
|
+
}
|
|
8488
|
+
if (hasQueue && !keepQueue) {
|
|
8489
|
+
const archivePath = getAvailableArchivePath(queueFile);
|
|
8490
|
+
fs8.renameSync(queueFile, archivePath);
|
|
8491
|
+
print.success(`Archived legacy TASK_QUEUE.md to planning/${path9.basename(archivePath)}`);
|
|
8492
|
+
} else if (hasQueue && keepQueue) {
|
|
8493
|
+
print.info("Retained legacy TASK_QUEUE.md for compatibility (--keep-queue)");
|
|
8494
|
+
} else {
|
|
8495
|
+
print.info("No planning/TASK_QUEUE.md present; nothing to archive");
|
|
8121
8496
|
}
|
|
8122
|
-
const dir = path9.dirname(todoFile);
|
|
8123
|
-
if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
|
|
8124
|
-
fs8.writeFileSync(todoFile, todoContent);
|
|
8125
|
-
print.success(`Migrated ${tasks.length} tasks to planning/TODO.md`);
|
|
8126
8497
|
} catch (err) {
|
|
8127
8498
|
print.error(`Migration failed: ${err.message}`);
|
|
8128
8499
|
}
|
|
@@ -8130,6 +8501,7 @@ function registerBuildCommand(program3) {
|
|
|
8130
8501
|
build.command("backfill").aliases(["recover", "hydrate", "bootstrap"]).description("Recover build artifacts from existing codebase").action(() => {
|
|
8131
8502
|
const planningDir = path9.join(process.cwd(), "planning");
|
|
8132
8503
|
print.header("Build Artifact Recovery");
|
|
8504
|
+
printLegacyQueueWarningIfNeeded();
|
|
8133
8505
|
const hasState = fs8.existsSync(path9.join(planningDir, "BUILD_STATE.json"));
|
|
8134
8506
|
const hasTodo = fs8.existsSync(path9.join(planningDir, "TODO.md"));
|
|
8135
8507
|
const hasQueue = fs8.existsSync(path9.join(planningDir, "TASK_QUEUE.md"));
|
|
@@ -8420,7 +8792,7 @@ function registerGenerateCommand(program3) {
|
|
|
8420
8792
|
const sections = [
|
|
8421
8793
|
`# ${projectName} - AI Context`,
|
|
8422
8794
|
"",
|
|
8423
|
-
|
|
8795
|
+
`**Generated by**: Bootspring v${BOOTSPRING_VERSION}`,
|
|
8424
8796
|
`**Last Updated**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`,
|
|
8425
8797
|
"",
|
|
8426
8798
|
"---",
|
|
@@ -9325,7 +9697,7 @@ function registerLoopCommand(program3) {
|
|
|
9325
9697
|
}
|
|
9326
9698
|
const tasks = loadTasks2();
|
|
9327
9699
|
if (tasks.length === 0) {
|
|
9328
|
-
print.warning("No tasks found in planning/TODO.md
|
|
9700
|
+
print.warning("No tasks found in planning/TODO.md (legacy fallback: planning/TASK_QUEUE.md)");
|
|
9329
9701
|
return;
|
|
9330
9702
|
}
|
|
9331
9703
|
const pending = tasks.filter((t) => t.status.toLowerCase() === "pending");
|
|
@@ -13666,9 +14038,227 @@ init_cjs_shims();
|
|
|
13666
14038
|
var fs30 = __toESM(require("fs"), 1);
|
|
13667
14039
|
var path31 = __toESM(require("path"), 1);
|
|
13668
14040
|
init_dist();
|
|
13669
|
-
|
|
13670
|
-
|
|
14041
|
+
|
|
14042
|
+
// src/commands/business-templates.ts
|
|
14043
|
+
init_cjs_shims();
|
|
14044
|
+
|
|
14045
|
+
// src/commands/business-strategy-modules.ts
|
|
14046
|
+
init_cjs_shims();
|
|
14047
|
+
var STRATEGY_MODULES = [
|
|
14048
|
+
{
|
|
14049
|
+
title: "Precision ICP Builder",
|
|
14050
|
+
useWhen: "You need a buyer definition that changes product, sales, and messaging decisions immediately.",
|
|
14051
|
+
prompt: ["You are a market research strategist for early-stage software and services.", "Build a decision-ready ideal customer profile for the project."],
|
|
14052
|
+
returns: ["buyer filters that materially affect purchase behavior", "identity, professional self-image, status fears, and desired reputation", "urgent pains versus tolerable pains", "active buying triggers and decision style", "exact customer language, reachable communities, objections, first-buyer profile, and one opening sentence"],
|
|
14053
|
+
inputs: ["Offer: <describe what you sell>", "Current customer guess: <who you think buys today>"]
|
|
14054
|
+
},
|
|
14055
|
+
{
|
|
14056
|
+
title: "Distinctive Positioning Builder",
|
|
14057
|
+
useWhen: "You need messaging that sounds like you, not a generic competitor.",
|
|
14058
|
+
prompt: ["You are a positioning strategist.", "Write positioning for the project that survives a specificity test."],
|
|
14059
|
+
returns: ["one sharp positioning statement", "a rewrite pass that removes vague phrases", "a competitor difference matrix on the 4 factors buyers actually care about", "variations for cold outreach, homepage copy, and spoken pitch", "a short validation question to ask live prospects"],
|
|
14060
|
+
inputs: ["Product: <what you do>", "Target customer: <who buys this>", "Main alternative: <what buyers do instead>", "Real differentiator: <what is true and hard to copy>"]
|
|
14061
|
+
},
|
|
14062
|
+
{
|
|
14063
|
+
title: "Pricing Architecture Builder",
|
|
14064
|
+
useWhen: "You want pricing to steer buyers toward the right plan instead of just listing options.",
|
|
14065
|
+
prompt: ["You are a monetization strategist.", "Design a 3-tier pricing system for the project with strong anchoring."],
|
|
14066
|
+
returns: ["entry, core, and premium tier structure", "why each tier exists and what should stay out of it", "recommended monthly and annual price points", "upgrade triggers between tiers", "page-ordering, trial logic, and the pricing mistake most likely to hurt us"],
|
|
14067
|
+
inputs: ["Offer: <describe what you sell>", "Pricing instinct: <what you were planning to charge>", "Target customer: <who buys>"]
|
|
14068
|
+
},
|
|
14069
|
+
{
|
|
14070
|
+
title: "90-Day GTM Sprint Planner",
|
|
14071
|
+
useWhen: "You need a week-by-week launch plan with zero fluff and no paid ads.",
|
|
14072
|
+
prompt: ["You are a zero-to-one go-to-market operator.", "Create a 90-day GTM plan for the project."],
|
|
14073
|
+
returns: ["one-line ICP restatement", "top 3 launch channels ranked by likely first-90-day ROI", "week-by-week actions for validation, first closes, system building, and momentum", "measurable weekly targets", "2 daily non-negotiables for month 1", "one signal that proves the market is responding"],
|
|
14074
|
+
inputs: ["Product: <describe the product or service>", "Ideal buyer: <specific role and context>", "Available channels: <email, LinkedIn, communities, network, etc.>"]
|
|
14075
|
+
},
|
|
14076
|
+
{
|
|
14077
|
+
title: "Partnership Lever Scanner",
|
|
14078
|
+
useWhen: "You want distribution through other people\u2019s audiences instead of only direct outreach.",
|
|
14079
|
+
prompt: ["You are a partnership strategist.", "Find partnership and co-marketing opportunities for the project."],
|
|
14080
|
+
returns: ["complementary product partners", "audience owners and the co-marketing format that fits each", "referral partners and incentive logic", "integration partners that place us near the moment of need", "first outreach message and top 3 fastest opportunities"],
|
|
14081
|
+
inputs: ["Product: <what you sell>", "ICP: <who you want to reach>", "Category: <your niche or market>"]
|
|
14082
|
+
},
|
|
14083
|
+
{
|
|
14084
|
+
title: "Competitive Gap and Threat Review",
|
|
14085
|
+
useWhen: "You need actionable white space, not a generic list of rivals.",
|
|
14086
|
+
prompt: ["You are a competitive intelligence analyst.", "Map the competitive field for the project and show where we can win."],
|
|
14087
|
+
returns: ["the 5 most relevant competitors ranked by directness", "each rival\u2019s model, strengths, recurring weaknesses, and ignored segment", "the open category gap buyers would pay for", "the competitor most likely to copy us and how to raise the bar before they do", "3 quick wins to take buyers from a named rival this month"],
|
|
14088
|
+
inputs: ["Product: <what you sell>", "Known competitors: <names or descriptions>"]
|
|
14089
|
+
},
|
|
14090
|
+
{
|
|
14091
|
+
title: "Moat and Defensibility Planner",
|
|
14092
|
+
useWhen: "You need a credible path to defensibility before scale makes copying easier.",
|
|
14093
|
+
prompt: ["You are a startup strategy advisor.", "Evaluate defensibility for the project."],
|
|
14094
|
+
returns: ["current moat score across network effects, switching costs, cost advantage, intangible assets, and efficient scale", "the 1 or 2 moat types most realistic right now", "90-day actions that start compounding defensibility", "an incumbent attack scenario and how to reduce vulnerability", "a 2-sentence investor explanation of the moat"],
|
|
14095
|
+
inputs: ["Business model: <describe product and monetization>", "Current unfair advantage: <what is hard to copy today>"]
|
|
14096
|
+
},
|
|
14097
|
+
{
|
|
14098
|
+
title: "Revenue Model Stress Test",
|
|
14099
|
+
useWhen: "Your model looks fine on paper but you need to find the weak assumptions early.",
|
|
14100
|
+
prompt: ["You are a financial model reviewer.", "Pressure-test the revenue model for the project."],
|
|
14101
|
+
returns: ["unit economics summary", "break-even math and runway implications", "churn and concentration risks", "pricing-pressure scenario analysis", "customer growth math to reach key revenue milestones", "the single assumption that could break the model", "the one improvement lever with the highest 90-day impact"],
|
|
14102
|
+
inputs: ["Revenue model: <how money is made>", "Current pricing: <what you charge>", "Cost structure: <rough CAC and cost to serve>"]
|
|
14103
|
+
},
|
|
14104
|
+
{
|
|
14105
|
+
title: "Investor One-Pager Draft Builder",
|
|
14106
|
+
useWhen: "You need a fundraising artifact that is short, specific, and credible.",
|
|
14107
|
+
prompt: ["You are a fundraising narrative advisor.", "Write a one-page investor brief for the project."],
|
|
14108
|
+
returns: ["clear problem, solution, market framing, model, traction, team, and ask", "a competition section that differentiates without posturing", "a one-page layout with suggested word count per section", "the closing line that makes a meeting feel like the next obvious step"],
|
|
14109
|
+
inputs: ["Product: <what is being built>", "Traction: <revenue, pilots, waitlist, design partners, etc.>", "Team: <who is building it>"]
|
|
14110
|
+
},
|
|
14111
|
+
{
|
|
14112
|
+
title: "Startup Launch Brief",
|
|
14113
|
+
useWhen: "You want one document that turns strategy into immediate execution.",
|
|
14114
|
+
prompt: ["You are a founding-team operator.", "Synthesize the current plan for the project into a one-page execution brief."],
|
|
14115
|
+
returns: ["business in one line", "ICP in one line", "positioning in one line", "pricing summary", "30-day action plan", "first 5 customers with outreach angle", "top 3 risks and the mitigation for each", "the single highest-leverage action for the next 7 days", "the measurable 90-day success definition"],
|
|
14116
|
+
inputs: ["Idea context: <product, market, stage, founder context>", "Biggest uncertainty: <what feels most risky>", "90-day goal: <what success looks like>"]
|
|
14117
|
+
},
|
|
14118
|
+
{
|
|
14119
|
+
title: "Customer Interview Guide",
|
|
14120
|
+
useWhen: "You want interviews that produce decisions, not polite anecdotes.",
|
|
14121
|
+
prompt: ["You are a customer interview coach.", "Create a buyer interview guide for the project."],
|
|
14122
|
+
returns: ["12 interview questions in the right order", "follow-up probes for pain, urgency, alternatives, and budget", "signals that indicate real buying intent", "the 3 mistakes that bias the conversation"],
|
|
14123
|
+
inputs: ["Who you want to interview: <specific buyer type>", "Hypothesis to test: <belief you want validated or disproved>"]
|
|
14124
|
+
},
|
|
14125
|
+
{
|
|
14126
|
+
title: "Offer Packaging Designer",
|
|
14127
|
+
useWhen: "Your product is clear but the way it is sold still feels muddy.",
|
|
14128
|
+
prompt: ["You are an offer designer.", "Turn the project into a clear, high-conversion offer."],
|
|
14129
|
+
returns: ["core deliverable and boundary definition", "what is included, excluded, guaranteed, and time-bound", "add-ons, bonuses, urgency devices, and risk reversal", "the offer version best suited for first customers"],
|
|
14130
|
+
inputs: ["Product: <what you sell>", "Buyer: <who buys>", "Delivery model: <software, service, hybrid, one-time, recurring>"]
|
|
14131
|
+
},
|
|
14132
|
+
{
|
|
14133
|
+
title: "Homepage Messaging Brief",
|
|
14134
|
+
useWhen: "The product is real but the homepage does not yet explain why anyone should care.",
|
|
14135
|
+
prompt: ["You are a conversion copy strategist.", "Draft the messaging architecture for the homepage."],
|
|
14136
|
+
returns: ["hero headline and subhead", "proof strip, problem framing, value explanation, objection handling, and CTA logic", "section order for a high-intent homepage", "copy notes for designers and implementers"],
|
|
14137
|
+
inputs: ["ICP: <who lands on the page>", "Offer: <what you sell>", "Proof available: <logos, results, founder story, testimonials>"]
|
|
14138
|
+
},
|
|
14139
|
+
{
|
|
14140
|
+
title: "Outbound Sequence Builder",
|
|
14141
|
+
useWhen: "You need a repeatable outbound message system rooted in real buyer pain.",
|
|
14142
|
+
prompt: ["You are an outbound operator.", "Write a short outbound sequence for the project."],
|
|
14143
|
+
returns: ["first-touch email or DM", "3 follow-ups with different angles", "subject lines or hooks", "reply handling for curiosity, no timing, wrong fit, and no response"],
|
|
14144
|
+
inputs: ["Target role: <who you are contacting>", "Trigger event: <what happened that makes them timely>", "Offer angle: <why they should care now>"]
|
|
14145
|
+
},
|
|
14146
|
+
{
|
|
14147
|
+
title: "Sales Discovery Blueprint",
|
|
14148
|
+
useWhen: "You need a better structure for live calls and demos.",
|
|
14149
|
+
prompt: ["You are a sales discovery coach.", "Design a discovery and demo flow for the project."],
|
|
14150
|
+
returns: ["call agenda", "key diagnostic questions", "what to show in the product and in what order", "deal qualification criteria", "close question and next-step framing"],
|
|
14151
|
+
inputs: ["Buyer type: <role and company>", "Typical use case: <what they want done>", "Common objection: <what usually stalls the sale>"]
|
|
14152
|
+
},
|
|
14153
|
+
{
|
|
14154
|
+
title: "Activation Friction Audit",
|
|
14155
|
+
useWhen: "People sign up or book demos but stall before reaching value.",
|
|
14156
|
+
prompt: ["You are a product activation strategist.", "Audit the first-use journey for the project."],
|
|
14157
|
+
returns: ["the first-value milestone", "the top friction points before that milestone", "manual assists, product changes, and lifecycle messaging fixes", "one metric that best measures activation progress"],
|
|
14158
|
+
inputs: ["Onboarding flow: <how new users start>", "Desired outcome: <what first value looks like>", "Current drop-off: <where people stall today>"]
|
|
14159
|
+
},
|
|
14160
|
+
{
|
|
14161
|
+
title: "Referral Engine Designer",
|
|
14162
|
+
useWhen: "You have some happy users but no deliberate referral motion.",
|
|
14163
|
+
prompt: ["You are a referral growth strategist.", "Design a referral loop for the project."],
|
|
14164
|
+
returns: ["the ideal moment to ask for a referral", "what the referrer and referred person each get", "the message and CTA to use", "how to track quality, not just volume"],
|
|
14165
|
+
inputs: ["Best-fit customer: <who is happiest today>", "Product outcome: <what result they got>", "Incentive limits: <what you can realistically offer>"]
|
|
14166
|
+
},
|
|
14167
|
+
{
|
|
14168
|
+
title: "Case Study Capture Kit",
|
|
14169
|
+
useWhen: "You are getting wins but not turning them into reusable proof.",
|
|
14170
|
+
prompt: ["You are a case-study editor.", "Create a case-study capture system for the project."],
|
|
14171
|
+
returns: ["interview questions for customers", "the before/during/after story arc", "proof formats for website, outbound, investor updates, and sales calls", "release and approval checklist"],
|
|
14172
|
+
inputs: ["Customer win: <what changed for them>", "Available evidence: <quotes, screenshots, metrics, call notes>"]
|
|
14173
|
+
},
|
|
14174
|
+
{
|
|
14175
|
+
title: "Founder Narrative Builder",
|
|
14176
|
+
useWhen: "You need a tighter founder story for customers, hires, and investors.",
|
|
14177
|
+
prompt: ["You are a founder narrative strategist.", "Shape the founder story for the project."],
|
|
14178
|
+
returns: ["short founder story for homepage and deck", "longer origin story for meetings and recruiting", "why-now logic", "credibility anchors that feel earned rather than inflated"],
|
|
14179
|
+
inputs: ["Founder background: <relevant experience>", "Why this matters: <personal reason or market insight>", "What gives credibility: <evidence you can execute>"]
|
|
14180
|
+
},
|
|
14181
|
+
{
|
|
14182
|
+
title: "Channel Experiment Scorecard",
|
|
14183
|
+
useWhen: "You have too many growth ideas and need to prioritize the next 30 days.",
|
|
14184
|
+
prompt: ["You are a growth experiment lead.", "Build a channel experiment scorecard for the project."],
|
|
14185
|
+
returns: ["10 experiments scored by impact, speed, confidence, and cost", "one experiment to run this week", "kill criteria, success metric, and owner for each", "the reporting cadence for a 30-day sprint"],
|
|
14186
|
+
inputs: ["Current channels: <where you can reach buyers today>", "Team capacity: <how much time or headcount you have>", "Revenue goal: <what outcome matters most next>"]
|
|
14187
|
+
}
|
|
14188
|
+
];
|
|
14189
|
+
|
|
14190
|
+
// src/commands/business-templates.ts
|
|
14191
|
+
function renderList(items) {
|
|
14192
|
+
return items.map((item) => `- ${item}`).join("\n");
|
|
14193
|
+
}
|
|
14194
|
+
function renderStrategyModule(module2, index) {
|
|
14195
|
+
return `### ${index}. ${module2.title}
|
|
14196
|
+
|
|
14197
|
+
**Use when**: ${module2.useWhen}
|
|
14198
|
+
|
|
14199
|
+
**Prompt**
|
|
14200
|
+
${module2.prompt.join("\n")}
|
|
14201
|
+
|
|
14202
|
+
Return:
|
|
14203
|
+
${renderList(module2.returns)}
|
|
14204
|
+
|
|
14205
|
+
Inputs:
|
|
14206
|
+
${renderList(module2.inputs)}`;
|
|
14207
|
+
}
|
|
14208
|
+
function buildFounderStrategyPack(projectName, date) {
|
|
14209
|
+
const coreModules = STRATEGY_MODULES.slice(0, 10).map((module2, index) => renderStrategyModule(module2, index + 1)).join("\n\n");
|
|
14210
|
+
const expansionModules = STRATEGY_MODULES.slice(10).map((module2, index) => renderStrategyModule(module2, index + 11)).join("\n\n");
|
|
14211
|
+
return `# Founder Strategy Pack: ${projectName}
|
|
14212
|
+
|
|
14213
|
+
> **Version**: 1.0 | **Created**: ${date} | **Purpose**: Founder strategy prompts for customer research, positioning, pricing, GTM, fundraising, and growth
|
|
14214
|
+
|
|
14215
|
+
---
|
|
14216
|
+
|
|
14217
|
+
## How to Use This Pack
|
|
14218
|
+
|
|
14219
|
+
- Start with modules 1 through 10 to sharpen customer, market, and business fundamentals.
|
|
14220
|
+
- Use modules 11 through 20 to tighten execution, messaging, activation, and growth loops.
|
|
14221
|
+
- Feed the outputs back into \`AUDIENCE.md\`, \`BUSINESS_MODEL.md\`, \`COMPETITORS.md\`, \`ROADMAP.md\`, and your launch docs.
|
|
14222
|
+
- Replace the angle-bracket inputs before pasting a prompt into your preferred assistant or Bootspring workflow.
|
|
14223
|
+
|
|
14224
|
+
---
|
|
14225
|
+
|
|
14226
|
+
## Core Strategy Modules
|
|
14227
|
+
|
|
14228
|
+
${coreModules}
|
|
14229
|
+
|
|
14230
|
+
---
|
|
14231
|
+
|
|
14232
|
+
## Execution Expansion Modules
|
|
14233
|
+
|
|
14234
|
+
${expansionModules}
|
|
14235
|
+
|
|
14236
|
+
---
|
|
14237
|
+
|
|
14238
|
+
## Recommended Order
|
|
14239
|
+
|
|
14240
|
+
1. Precision ICP Builder
|
|
14241
|
+
2. Distinctive Positioning Builder
|
|
14242
|
+
3. Pricing Architecture Builder
|
|
14243
|
+
4. 90-Day GTM Sprint Planner
|
|
14244
|
+
5. Competitive Gap and Threat Review
|
|
14245
|
+
6. Startup Launch Brief
|
|
14246
|
+
|
|
14247
|
+
Then use the expansion modules based on the bottleneck you hit first.
|
|
14248
|
+
|
|
14249
|
+
---
|
|
14250
|
+
|
|
14251
|
+
*Generated with Bootspring*
|
|
14252
|
+
`;
|
|
14253
|
+
}
|
|
14254
|
+
function getBusinessTemplates(projectName, date) {
|
|
13671
14255
|
return {
|
|
14256
|
+
strategy: {
|
|
14257
|
+
name: "Founder Strategy Pack",
|
|
14258
|
+
output: "FOUNDER_STRATEGY_PACK.md",
|
|
14259
|
+
description: "20 founder strategy prompts for ICP, pricing, GTM, fundraising, and growth",
|
|
14260
|
+
content: buildFounderStrategyPack(projectName, date)
|
|
14261
|
+
},
|
|
13672
14262
|
plan: {
|
|
13673
14263
|
name: "Business Plan",
|
|
13674
14264
|
output: "BUSINESS_PLAN.md",
|
|
@@ -13841,6 +14431,9 @@ For [target customer] who [need], [product] is a [category] that [key benefit].
|
|
|
13841
14431
|
}
|
|
13842
14432
|
};
|
|
13843
14433
|
}
|
|
14434
|
+
|
|
14435
|
+
// src/commands/business.ts
|
|
14436
|
+
var OUTPUT_DIR = "planning";
|
|
13844
14437
|
function registerBusinessCommand(program3) {
|
|
13845
14438
|
const business = program3.command("business").description("Business planning and strategy tools");
|
|
13846
14439
|
business.command("init").description("Initialize all business planning documents").action(async () => {
|
|
@@ -13853,7 +14446,7 @@ function registerBusinessCommand(program3) {
|
|
|
13853
14446
|
if (!fs30.existsSync(outputDir)) {
|
|
13854
14447
|
fs30.mkdirSync(outputDir, { recursive: true });
|
|
13855
14448
|
}
|
|
13856
|
-
const templates =
|
|
14449
|
+
const templates = getBusinessTemplates(projectName, date);
|
|
13857
14450
|
const created = [];
|
|
13858
14451
|
const skipped = [];
|
|
13859
14452
|
const spinner = createSpinner("Creating business documents...").start();
|
|
@@ -13880,15 +14473,37 @@ function registerBusinessCommand(program3) {
|
|
|
13880
14473
|
}
|
|
13881
14474
|
}
|
|
13882
14475
|
console.log("\nNext Steps:");
|
|
13883
|
-
console.log(` 1. Edit ${OUTPUT_DIR}/
|
|
13884
|
-
console.log(
|
|
13885
|
-
console.log(" 3. Run `bootspring business
|
|
14476
|
+
console.log(` 1. Edit ${OUTPUT_DIR}/FOUNDER_STRATEGY_PACK.md`);
|
|
14477
|
+
console.log(` 2. Edit ${OUTPUT_DIR}/BUSINESS_PLAN.md`);
|
|
14478
|
+
console.log(" 3. Run `bootspring agent invoke business-strategy-expert`");
|
|
14479
|
+
console.log(" 4. Run `bootspring business status` to check progress");
|
|
14480
|
+
});
|
|
14481
|
+
business.command("strategy").description("Create founder strategy pack").action(async () => {
|
|
14482
|
+
const projectRoot = process.cwd();
|
|
14483
|
+
const projectName = path31.basename(projectRoot);
|
|
14484
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
14485
|
+
const templates = getBusinessTemplates(projectName, date);
|
|
14486
|
+
const template = templates.strategy;
|
|
14487
|
+
print.header(template.name);
|
|
14488
|
+
const outputDir = path31.join(projectRoot, OUTPUT_DIR);
|
|
14489
|
+
const outputPath = path31.join(outputDir, template.output);
|
|
14490
|
+
if (fs30.existsSync(outputPath)) {
|
|
14491
|
+
print.warning(`File already exists: ${OUTPUT_DIR}/${template.output}`);
|
|
14492
|
+
return;
|
|
14493
|
+
}
|
|
14494
|
+
if (!fs30.existsSync(outputDir)) {
|
|
14495
|
+
fs30.mkdirSync(outputDir, { recursive: true });
|
|
14496
|
+
}
|
|
14497
|
+
fs30.writeFileSync(outputPath, template.content);
|
|
14498
|
+
print.success(`Created ${template.name}`);
|
|
14499
|
+
console.log(`
|
|
14500
|
+
File: ${OUTPUT_DIR}/${template.output}`);
|
|
13886
14501
|
});
|
|
13887
14502
|
business.command("plan").description("Create business plan").action(async () => {
|
|
13888
14503
|
const projectRoot = process.cwd();
|
|
13889
14504
|
const projectName = path31.basename(projectRoot);
|
|
13890
14505
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
13891
|
-
const templates =
|
|
14506
|
+
const templates = getBusinessTemplates(projectName, date);
|
|
13892
14507
|
const template = templates.plan;
|
|
13893
14508
|
print.header(template.name);
|
|
13894
14509
|
const outputDir = path31.join(projectRoot, OUTPUT_DIR);
|
|
@@ -13909,7 +14524,7 @@ File: ${OUTPUT_DIR}/${template.output}`);
|
|
|
13909
14524
|
const projectRoot = process.cwd();
|
|
13910
14525
|
const projectName = path31.basename(projectRoot);
|
|
13911
14526
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
13912
|
-
const templates =
|
|
14527
|
+
const templates = getBusinessTemplates(projectName, date);
|
|
13913
14528
|
const template = templates.model;
|
|
13914
14529
|
print.header(template.name);
|
|
13915
14530
|
const outputDir = path31.join(projectRoot, OUTPUT_DIR);
|
|
@@ -13930,7 +14545,7 @@ File: ${OUTPUT_DIR}/${template.output}`);
|
|
|
13930
14545
|
const projectRoot = process.cwd();
|
|
13931
14546
|
const projectName = path31.basename(projectRoot);
|
|
13932
14547
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
13933
|
-
const templates =
|
|
14548
|
+
const templates = getBusinessTemplates(projectName, date);
|
|
13934
14549
|
const template = templates.competitors;
|
|
13935
14550
|
print.header(template.name);
|
|
13936
14551
|
const outputDir = path31.join(projectRoot, OUTPUT_DIR);
|
|
@@ -13953,7 +14568,7 @@ File: ${OUTPUT_DIR}/${template.output}`);
|
|
|
13953
14568
|
const projectName = path31.basename(projectRoot);
|
|
13954
14569
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
13955
14570
|
const outputDir = path31.join(projectRoot, OUTPUT_DIR);
|
|
13956
|
-
const templates =
|
|
14571
|
+
const templates = getBusinessTemplates(projectName, date);
|
|
13957
14572
|
let found = 0;
|
|
13958
14573
|
const total = Object.keys(templates).length;
|
|
13959
14574
|
for (const [key, template] of Object.entries(templates)) {
|
|
@@ -13978,11 +14593,11 @@ Progress: ${found}/${total} documents created`);
|
|
|
13978
14593
|
console.log(" bootspring business init - Create all documents");
|
|
13979
14594
|
}
|
|
13980
14595
|
});
|
|
13981
|
-
business.command("show").description("Preview a business document").argument("<type>", "Document type (plan, model, competitors)").action((type) => {
|
|
14596
|
+
business.command("show").description("Preview a business document").argument("<type>", "Document type (strategy, plan, model, competitors)").action((type) => {
|
|
13982
14597
|
const projectRoot = process.cwd();
|
|
13983
14598
|
const projectName = path31.basename(projectRoot);
|
|
13984
14599
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
13985
|
-
const templates =
|
|
14600
|
+
const templates = getBusinessTemplates(projectName, date);
|
|
13986
14601
|
const template = templates[type];
|
|
13987
14602
|
if (!template) {
|
|
13988
14603
|
print.error(`Unknown template: ${type}`);
|
|
@@ -20083,7 +20698,7 @@ ${COLORS.green}Done.${COLORS.reset} Restart assistant clients to load new MCP se
|
|
|
20083
20698
|
|
|
20084
20699
|
// src/index.ts
|
|
20085
20700
|
var program2 = new Command();
|
|
20086
|
-
program2.name("bootspring").description("Development scaffolding with intelligence - CLI for AI-assisted workflows").version(
|
|
20701
|
+
program2.name("bootspring").description("Development scaffolding with intelligence - CLI for AI-assisted workflows").version(BOOTSPRING_VERSION);
|
|
20087
20702
|
registerAuthCommand(program2);
|
|
20088
20703
|
registerProjectCommand(program2);
|
|
20089
20704
|
registerSwitchCommand(program2);
|