@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.
@@ -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 todoFile = path9.join(process.cwd(), "planning", "TODO.md");
7718
- const queueFile = path9.join(process.cwd(), "planning", "TASK_QUEUE.md");
7719
- const files = [];
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.implementationQueue.map((t) => ({
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 tasks = loadTasks();
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 ?? "N/A"}`);
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
- print.info(`Iteration: ${iteration}/${maxIter}`);
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 TASK_QUEUE.md to BUILD_STATE.json").action(() => {
7960
- const queueFile = path9.join(process.cwd(), "planning", "TASK_QUEUE.md");
7961
- if (!fs8.existsSync(queueFile)) {
7962
- print.warning("No planning/TASK_QUEUE.md found");
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 tasks = loadTasks();
7971
- const existingIds = new Set((state.implementationQueue ?? []).map((t) => t.id));
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
- for (const task of tasks) {
8305
+ let updated = 0;
8306
+ for (const task of planningTasks.tasks) {
7974
8307
  if (!existingIds.has(task.id)) {
7975
- state.implementationQueue = state.implementationQueue ?? [];
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 or planning/TASK_QUEUE.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 to enriched TODO.md format").action(() => {
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
- if (!fs8.existsSync(queueFile)) {
8082
- print.warning("No planning/TASK_QUEUE.md found to migrate");
8083
- return;
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 content = fs8.readFileSync(queueFile, "utf-8");
8091
- const tasks = [];
8092
- for (const line of content.split("\n")) {
8093
- const match = line.match(/\|\s*\d+\s*\|\s*(bs-\d+)\s*\|\s*(.+?)\s*\|\s*(\w+)\s*\|\s*(\w+)\s*\|\s*(\w+)\s*\|/);
8094
- if (match) {
8095
- tasks.push({
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 phases = [...new Set(tasks.map((t) => t.phase))];
8109
- let todoContent = "# Build TODO\n\n";
8110
- for (const phase of phases) {
8111
- todoContent += `## ${phase}
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
- const phaseTasks = tasks.filter((t) => t.phase === phase);
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 += "\n";
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
- "**Generated by**: Bootspring v2.0.0",
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 or planning/TASK_QUEUE.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
- var OUTPUT_DIR = "planning";
13670
- function getTemplates(projectName, date) {
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 = getTemplates(projectName, date);
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}/BUSINESS_PLAN.md`);
13884
- console.log(" 2. Run `bootspring agent invoke business-strategy-expert`");
13885
- console.log(" 3. Run `bootspring business status` to check progress");
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 = getTemplates(projectName, date);
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 = getTemplates(projectName, date);
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 = getTemplates(projectName, date);
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 = getTemplates(projectName, date);
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 = getTemplates(projectName, date);
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("2.0.0");
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);