@girardmedia/bootspring 2.0.21 → 2.0.23

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.
Files changed (159) hide show
  1. package/bin/bootspring.js +5 -0
  2. package/cli/org.js +474 -0
  3. package/cli/preseed/index.js +16 -0
  4. package/cli/preseed/interactive.js +143 -0
  5. package/cli/preseed/templates.js +227 -0
  6. package/cli/preseed.js +9 -301
  7. package/cli/seed/builders/ai-context-builder.js +85 -0
  8. package/cli/seed/builders/index.js +13 -0
  9. package/cli/seed/builders/seed-builder.js +272 -0
  10. package/cli/seed/extractors/content-extractors.js +383 -0
  11. package/cli/seed/extractors/index.js +47 -0
  12. package/cli/seed/extractors/metadata-extractors.js +167 -0
  13. package/cli/seed/extractors/section-extractor.js +54 -0
  14. package/cli/seed/extractors/stack-extractors.js +228 -0
  15. package/cli/seed/index.js +18 -0
  16. package/cli/seed/utils/folder-structure.js +84 -0
  17. package/cli/seed/utils/index.js +11 -0
  18. package/cli/seed.js +23 -1074
  19. package/core/api-client.js +77 -0
  20. package/core/entitlements.js +36 -0
  21. package/core/organizations.js +223 -0
  22. package/core/policies.js +51 -6
  23. package/core/policy-matrix.js +303 -0
  24. package/core/project-context.js +1 -0
  25. package/dist/cli/index.d.ts +3 -0
  26. package/dist/cli/index.js +3220 -0
  27. package/dist/cli/index.js.map +1 -0
  28. package/dist/context-McpJQa_2.d.ts +5710 -0
  29. package/dist/core/index.d.ts +635 -0
  30. package/dist/core/index.js +2593 -0
  31. package/dist/core/index.js.map +1 -0
  32. package/dist/index-QqbeEiDm.d.ts +857 -0
  33. package/dist/index-UiYCgwiH.d.ts +174 -0
  34. package/dist/index.d.ts +453 -0
  35. package/dist/index.js +44228 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/mcp/index.d.ts +1 -0
  38. package/dist/mcp/index.js +41173 -0
  39. package/dist/mcp/index.js.map +1 -0
  40. package/generators/index.ts +82 -0
  41. package/intelligence/orchestrator/config/failure-signatures.js +48 -0
  42. package/intelligence/orchestrator/config/index.js +23 -0
  43. package/intelligence/orchestrator/config/pack-lifecycle.js +262 -0
  44. package/intelligence/orchestrator/config/phases.js +111 -0
  45. package/intelligence/orchestrator/config/remediation.js +150 -0
  46. package/intelligence/orchestrator/config/workflows.js +168 -0
  47. package/intelligence/orchestrator/core/index.js +16 -0
  48. package/intelligence/orchestrator/core/state-manager.js +88 -0
  49. package/intelligence/orchestrator/core/telemetry.js +24 -0
  50. package/intelligence/orchestrator/index.js +17 -0
  51. package/intelligence/orchestrator.js +17 -512
  52. package/mcp/contracts/mcp-contract.v1.json +1 -1
  53. package/package.json +16 -3
  54. package/src/cli/agent.ts +703 -0
  55. package/src/cli/analyze.ts +640 -0
  56. package/src/cli/audit.ts +707 -0
  57. package/src/cli/auth.ts +930 -0
  58. package/src/cli/billing.ts +364 -0
  59. package/src/cli/build.ts +1089 -0
  60. package/src/cli/business.ts +508 -0
  61. package/src/cli/checkpoint-utils.ts +236 -0
  62. package/src/cli/checkpoint.ts +757 -0
  63. package/src/cli/cloud-sync.ts +534 -0
  64. package/src/cli/content.ts +273 -0
  65. package/src/cli/context.ts +667 -0
  66. package/src/cli/dashboard.ts +133 -0
  67. package/src/cli/deploy.ts +704 -0
  68. package/src/cli/doctor.ts +480 -0
  69. package/src/cli/fundraise.ts +494 -0
  70. package/src/cli/generate.ts +346 -0
  71. package/src/cli/github-cmd.ts +566 -0
  72. package/src/cli/health.ts +599 -0
  73. package/src/cli/index.ts +113 -0
  74. package/src/cli/init.ts +838 -0
  75. package/src/cli/legal.ts +495 -0
  76. package/src/cli/log.ts +316 -0
  77. package/src/cli/loop.ts +1660 -0
  78. package/src/cli/manager.ts +878 -0
  79. package/src/cli/mcp.ts +275 -0
  80. package/src/cli/memory.ts +346 -0
  81. package/src/cli/metrics.ts +590 -0
  82. package/src/cli/monitor.ts +960 -0
  83. package/src/cli/mvp.ts +662 -0
  84. package/src/cli/onboard.ts +663 -0
  85. package/src/cli/orchestrator.ts +622 -0
  86. package/src/cli/plugin.ts +483 -0
  87. package/src/cli/prd.ts +671 -0
  88. package/src/cli/preseed-start.ts +1633 -0
  89. package/src/cli/preseed.ts +2434 -0
  90. package/src/cli/project.ts +526 -0
  91. package/src/cli/quality.ts +885 -0
  92. package/src/cli/security.ts +1079 -0
  93. package/src/cli/seed.ts +1224 -0
  94. package/src/cli/skill.ts +537 -0
  95. package/src/cli/suggest.ts +1225 -0
  96. package/src/cli/switch.ts +518 -0
  97. package/src/cli/task.ts +780 -0
  98. package/src/cli/telemetry.ts +172 -0
  99. package/src/cli/todo.ts +627 -0
  100. package/src/cli/types.ts +15 -0
  101. package/src/cli/update.ts +334 -0
  102. package/src/cli/visualize.ts +609 -0
  103. package/src/cli/watch.ts +895 -0
  104. package/src/cli/workspace.ts +709 -0
  105. package/src/core/action-recorder.ts +673 -0
  106. package/src/core/analyze-workflow.ts +1453 -0
  107. package/src/core/api-client.ts +1120 -0
  108. package/src/core/audit-workflow.ts +1681 -0
  109. package/src/core/auth.ts +471 -0
  110. package/src/core/build-orchestrator.ts +509 -0
  111. package/src/core/build-state.ts +621 -0
  112. package/src/core/checkpoint-engine.ts +482 -0
  113. package/src/core/config.ts +1285 -0
  114. package/src/core/context-loader.ts +694 -0
  115. package/src/core/context.ts +410 -0
  116. package/src/core/deploy-workflow.ts +1085 -0
  117. package/src/core/entitlements.ts +322 -0
  118. package/src/core/github-sync.ts +720 -0
  119. package/src/core/index.ts +981 -0
  120. package/src/core/ingest.ts +1186 -0
  121. package/src/core/metrics-engine.ts +886 -0
  122. package/src/core/mvp.ts +847 -0
  123. package/src/core/onboard-workflow.ts +1293 -0
  124. package/src/core/policies.ts +81 -0
  125. package/src/core/preseed-workflow.ts +1163 -0
  126. package/src/core/preseed.ts +1826 -0
  127. package/src/core/project-context.ts +380 -0
  128. package/src/core/project-state.ts +699 -0
  129. package/src/core/r2-sync.ts +691 -0
  130. package/src/core/scaffold.ts +1715 -0
  131. package/src/core/session.ts +286 -0
  132. package/src/core/task-extractor.ts +799 -0
  133. package/src/core/telemetry.ts +371 -0
  134. package/src/core/tier-enforcement.ts +737 -0
  135. package/src/core/utils.ts +437 -0
  136. package/src/index.ts +29 -0
  137. package/src/intelligence/agent-collab.ts +2376 -0
  138. package/src/intelligence/auto-suggest.ts +713 -0
  139. package/src/intelligence/content-gen.ts +1351 -0
  140. package/src/intelligence/cross-project.ts +1692 -0
  141. package/src/intelligence/git-memory.ts +529 -0
  142. package/src/intelligence/index.ts +318 -0
  143. package/src/intelligence/orchestrator.ts +534 -0
  144. package/src/intelligence/prd.ts +466 -0
  145. package/src/intelligence/recommendations.ts +982 -0
  146. package/src/intelligence/workflow-composer.ts +1472 -0
  147. package/src/mcp/capabilities.ts +233 -0
  148. package/src/mcp/index.ts +37 -0
  149. package/src/mcp/registry.ts +1268 -0
  150. package/src/mcp/response-formatter.ts +797 -0
  151. package/src/mcp/server.ts +240 -0
  152. package/src/types/agent.ts +69 -0
  153. package/src/types/config.ts +86 -0
  154. package/src/types/context.ts +77 -0
  155. package/src/types/index.ts +53 -0
  156. package/src/types/mcp.ts +91 -0
  157. package/src/types/skills.ts +47 -0
  158. package/src/types/workflow.ts +155 -0
  159. package/generators/index.js +0 -18
@@ -0,0 +1,3220 @@
1
+ 'use strict';
2
+ const __create = Object.create;
3
+ const __defProp = Object.defineProperty;
4
+ const __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ const __getOwnPropNames = Object.getOwnPropertyNames;
6
+ const __getProtoOf = Object.getPrototypeOf;
7
+ const __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ const __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ const __commonJS = (cb, mod) => function __require() {
12
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
13
+ };
14
+ const __export = (target, all) => {
15
+ for (const name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ const __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === 'object' || typeof from === 'function') {
20
+ for (const key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ const __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
27
+ // If the importer is in node compatibility mode or this is not an ESM
28
+ // file that has been converted to a CommonJS file using a Babel-
29
+ // compatible transform (i.e. "__esModule" has not been set), then set
30
+ // "default" to the CommonJS "module.exports" for node compatibility.
31
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, 'default', { value: mod, enumerable: true }) : target,
32
+ mod
33
+ ));
34
+ const __toCommonJS = (mod) => __copyProps(__defProp({}, '__esModule', { value: true }), mod);
35
+
36
+ // node_modules/tsup/assets/cjs_shims.js
37
+ const init_cjs_shims = __esm({
38
+ 'node_modules/tsup/assets/cjs_shims.js'() {
39
+ 'use strict';
40
+ }
41
+ });
42
+
43
+ // package.json
44
+ const require_package = __commonJS({
45
+ 'package.json'(exports2, module2) {
46
+ module2.exports = {
47
+ name: '@girardmedia/bootspring',
48
+ version: '2.0.21',
49
+ description: 'Development scaffolding with intelligence - AI-powered context, agents, and workflows for any MCP-compatible assistant',
50
+ keywords: [
51
+ 'ai',
52
+ 'development',
53
+ 'scaffolding',
54
+ 'mcp',
55
+ 'claude',
56
+ 'agents',
57
+ 'context',
58
+ 'workflow',
59
+ 'devtools'
60
+ ],
61
+ author: 'Bootspring',
62
+ license: 'MIT',
63
+ repository: {
64
+ type: 'git',
65
+ url: 'https://github.com/bootspring/bootspring.git'
66
+ },
67
+ homepage: 'https://bootspring.com',
68
+ bugs: {
69
+ url: 'https://github.com/bootspring/bootspring/issues'
70
+ },
71
+ bin: {
72
+ bootspring: './bin/bootspring.js'
73
+ },
74
+ main: './core/index.js',
75
+ types: './core/index.d.ts',
76
+ exports: {
77
+ '.': {
78
+ types: './core/index.d.ts',
79
+ default: './core/index.js'
80
+ },
81
+ './mcp': {
82
+ types: './mcp/server.d.ts',
83
+ default: './mcp/server.js'
84
+ },
85
+ './auth': {
86
+ types: './core/auth.d.ts',
87
+ default: './core/auth.js'
88
+ },
89
+ './api': {
90
+ types: './core/api-client.d.ts',
91
+ default: './core/api-client.js'
92
+ },
93
+ './marketplace': {
94
+ types: './marketplace/index.d.ts',
95
+ default: './marketplace/index.js'
96
+ }
97
+ },
98
+ files: [
99
+ 'bin/',
100
+ 'core/',
101
+ 'cli/',
102
+ 'mcp/',
103
+ 'intelligence/',
104
+ 'quality/',
105
+ 'generators/',
106
+ 'hooks/',
107
+ 'plugins/',
108
+ 'marketplace/',
109
+ 'src/',
110
+ 'dist/'
111
+ ],
112
+ scripts: {
113
+ start: 'node bin/bootspring.js',
114
+ dashboard: 'node bin/bootspring.js dashboard',
115
+ mcp: 'node mcp/server.js',
116
+ test: 'jest',
117
+ 'test:watch': 'jest --watch',
118
+ 'test:coverage': 'jest --coverage',
119
+ lint: 'eslint .',
120
+ 'lint:fix': 'eslint . --fix',
121
+ typecheck: 'tsc --noEmit',
122
+ build: 'tsup',
123
+ 'build:watch': 'tsup --watch',
124
+ dev: 'tsx watch src/index.ts',
125
+ 'verify:lint-budget': 'node scripts/check-lint-budgets.js',
126
+ 'build:mcp-contract': 'node scripts/export-mcp-contract.js',
127
+ 'verify:mcp-contract': 'node scripts/export-mcp-contract.js --check',
128
+ 'verify:package': 'node scripts/check-package-boundaries.js',
129
+ 'db:sync': 'node shared/db/sync.js',
130
+ 'db:sync:check': 'node shared/db/sync.js --check',
131
+ prepublishOnly: 'npm test && npm run lint --if-present && npm run verify:package && npm run verify:mcp-contract'
132
+ },
133
+ devDependencies: {
134
+ '@eslint/js': '^9.39.2',
135
+ '@types/node': '^25.3.0',
136
+ eslint: '^9.39.2',
137
+ globals: '^17.3.0',
138
+ jest: '^29.7.0',
139
+ tsup: '^8.5.1',
140
+ tsx: '^4.21.0',
141
+ typescript: '^5.9.3'
142
+ },
143
+ dependencies: {
144
+ '@modelcontextprotocol/sdk': '^1.0.0',
145
+ ws: '^8.18.0',
146
+ yaml: '^2.8.0',
147
+ zod: '^3.25.0'
148
+ },
149
+ engines: {
150
+ node: '>=18.0.0'
151
+ },
152
+ overrides: {
153
+ table: {
154
+ ajv: '^8.12.0'
155
+ },
156
+ minimatch: '^10.2.1'
157
+ }
158
+ };
159
+ }
160
+ });
161
+
162
+ // src/core/auth.ts
163
+ const auth_exports = {};
164
+ __export(auth_exports, {
165
+ BOOTSPRING_DIR: () => BOOTSPRING_DIR,
166
+ CONFIG_FILE: () => CONFIG_FILE,
167
+ CREDENTIALS_FILE: () => CREDENTIALS_FILE,
168
+ DEVICE_FILE: () => DEVICE_FILE,
169
+ clearCredentials: () => clearCredentials,
170
+ clearDeviceInfo: () => clearDeviceInfo,
171
+ ensureDir: () => ensureDir2,
172
+ generateDeviceFingerprint: () => generateDeviceFingerprint,
173
+ getApiKey: () => getApiKey,
174
+ getConfig: () => getConfig,
175
+ getCredentials: () => getCredentials,
176
+ getCredentialsPath: () => getCredentialsPath,
177
+ getDeviceContext: () => getDeviceContext,
178
+ getDeviceId: () => getDeviceId,
179
+ getDeviceInfo: () => getDeviceInfo,
180
+ getRefreshToken: () => getRefreshToken,
181
+ getTier: () => getTier,
182
+ getToken: () => getToken,
183
+ getUser: () => getUser,
184
+ isApiKeyAuth: () => isApiKeyAuth,
185
+ isAuthenticated: () => isAuthenticated,
186
+ login: () => login,
187
+ loginWithApiKey: () => loginWithApiKey,
188
+ logout: () => logout,
189
+ saveConfig: () => saveConfig,
190
+ saveCredentials: () => saveCredentials,
191
+ updateTokens: () => updateTokens
192
+ });
193
+ function getEncryptionKey() {
194
+ const machineId = os.hostname() + os.userInfo().username;
195
+ return crypto.createHash('sha256').update(machineId).digest();
196
+ }
197
+ function ensureDir2() {
198
+ if (!fs2.existsSync(BOOTSPRING_DIR)) {
199
+ fs2.mkdirSync(BOOTSPRING_DIR, { recursive: true, mode: 448 });
200
+ }
201
+ }
202
+ function encrypt(data) {
203
+ try {
204
+ const key = getEncryptionKey();
205
+ const iv = crypto.randomBytes(16);
206
+ const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
207
+ let encrypted = cipher.update(JSON.stringify(data), 'utf8', 'hex');
208
+ encrypted += cipher.final('hex');
209
+ return { iv: iv.toString('hex'), data: encrypted };
210
+ } catch {
211
+ return data;
212
+ }
213
+ }
214
+ function decrypt(encrypted) {
215
+ try {
216
+ if ('iv' in encrypted && 'data' in encrypted && typeof encrypted.iv === 'string') {
217
+ const key = getEncryptionKey();
218
+ const iv = Buffer.from(encrypted.iv, 'hex');
219
+ const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
220
+ let decrypted = decipher.update(encrypted.data, 'hex', 'utf8');
221
+ decrypted += decipher.final('utf8');
222
+ return JSON.parse(decrypted);
223
+ }
224
+ return encrypted;
225
+ } catch {
226
+ return encrypted;
227
+ }
228
+ }
229
+ function getCredentials() {
230
+ try {
231
+ if (fs2.existsSync(CREDENTIALS_FILE)) {
232
+ const raw = JSON.parse(fs2.readFileSync(CREDENTIALS_FILE, 'utf-8'));
233
+ return decrypt(raw);
234
+ }
235
+ } catch {
236
+ }
237
+ return null;
238
+ }
239
+ function saveCredentials(credentials) {
240
+ ensureDir2();
241
+ const encrypted = encrypt(credentials);
242
+ fs2.writeFileSync(
243
+ CREDENTIALS_FILE,
244
+ JSON.stringify(encrypted, null, 2),
245
+ { mode: 384 }
246
+ );
247
+ }
248
+ function clearCredentials() {
249
+ try {
250
+ if (fs2.existsSync(CREDENTIALS_FILE)) {
251
+ fs2.unlinkSync(CREDENTIALS_FILE);
252
+ }
253
+ } catch {
254
+ }
255
+ }
256
+ function getToken() {
257
+ const creds = getCredentials();
258
+ if (!creds) return null;
259
+ if (creds.apiKey) return null;
260
+ if (creds.expiresAt && new Date(creds.expiresAt) < /* @__PURE__ */ new Date()) {
261
+ return null;
262
+ }
263
+ return creds.token || null;
264
+ }
265
+ function getApiKey() {
266
+ const creds = getCredentials();
267
+ return creds?.apiKey || null;
268
+ }
269
+ function isApiKeyAuth() {
270
+ const creds = getCredentials();
271
+ return !!creds?.apiKey;
272
+ }
273
+ function getRefreshToken() {
274
+ const creds = getCredentials();
275
+ return creds?.refreshToken || null;
276
+ }
277
+ function isAuthenticated() {
278
+ return !!getToken() || !!getApiKey();
279
+ }
280
+ function getUser() {
281
+ const creds = getCredentials();
282
+ return creds?.user || null;
283
+ }
284
+ function getTier() {
285
+ const user = getUser();
286
+ return user?.tier || 'free';
287
+ }
288
+ function parseExpiry(expiry) {
289
+ const match = expiry.match(/^(\d+)([mhd])$/);
290
+ if (!match || !match[1] || !match[2]) return 15 * 60 * 1e3;
291
+ const value = parseInt(match[1]);
292
+ const unit = match[2];
293
+ switch (unit) {
294
+ case 'm':
295
+ return value * 60 * 1e3;
296
+ case 'h':
297
+ return value * 60 * 60 * 1e3;
298
+ case 'd':
299
+ return value * 24 * 60 * 60 * 1e3;
300
+ default:
301
+ return 15 * 60 * 1e3;
302
+ }
303
+ }
304
+ function login(response) {
305
+ const expiresIn = response.expiresIn ?? '15m';
306
+ const expiresMs = parseExpiry(expiresIn);
307
+ const expiresAt = new Date(Date.now() + expiresMs).toISOString();
308
+ const credentials = {
309
+ token: response.token,
310
+ expiresAt
311
+ };
312
+ if (response.refreshToken !== void 0) {
313
+ credentials.refreshToken = response.refreshToken;
314
+ }
315
+ if (response.user !== void 0) {
316
+ credentials.user = response.user;
317
+ }
318
+ saveCredentials(credentials);
319
+ }
320
+ function loginWithApiKey(apiKey, user) {
321
+ const credentials = { apiKey };
322
+ if (user !== void 0) {
323
+ credentials.user = user;
324
+ }
325
+ saveCredentials(credentials);
326
+ }
327
+ function updateTokens(response) {
328
+ const creds = getCredentials() || {};
329
+ const expiresIn = response.expiresIn ?? '15m';
330
+ const expiresMs = parseExpiry(expiresIn);
331
+ const expiresAt = new Date(Date.now() + expiresMs).toISOString();
332
+ const credentials = {
333
+ ...creds,
334
+ token: response.token,
335
+ expiresAt
336
+ };
337
+ if (response.refreshToken !== void 0) {
338
+ credentials.refreshToken = response.refreshToken;
339
+ }
340
+ saveCredentials(credentials);
341
+ }
342
+ function logout() {
343
+ clearCredentials();
344
+ }
345
+ function getConfig() {
346
+ try {
347
+ if (fs2.existsSync(CONFIG_FILE)) {
348
+ return JSON.parse(fs2.readFileSync(CONFIG_FILE, 'utf-8'));
349
+ }
350
+ } catch {
351
+ }
352
+ return {};
353
+ }
354
+ function saveConfig(config) {
355
+ ensureDir2();
356
+ fs2.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
357
+ }
358
+ function getCredentialsPath() {
359
+ return CREDENTIALS_FILE;
360
+ }
361
+ function generateDeviceFingerprint() {
362
+ const networkInterfaces2 = os.networkInterfaces();
363
+ const macAddresses = Object.values(networkInterfaces2).flat().filter(
364
+ (iface) => iface !== void 0 && !iface.internal && iface.mac !== '00:00:00:00:00:00'
365
+ ).map((iface) => iface.mac).sort().join(',');
366
+ const components = [
367
+ os.hostname(),
368
+ os.userInfo().username,
369
+ os.platform(),
370
+ os.arch(),
371
+ os.cpus()[0]?.model || 'unknown-cpu',
372
+ os.homedir(),
373
+ macAddresses
374
+ ];
375
+ return crypto.createHash('sha256').update(components.join('|')).digest('hex');
376
+ }
377
+ function getDeviceInfo() {
378
+ ensureDir2();
379
+ try {
380
+ if (fs2.existsSync(DEVICE_FILE)) {
381
+ const stored = JSON.parse(fs2.readFileSync(DEVICE_FILE, 'utf-8'));
382
+ const currentFingerprint = generateDeviceFingerprint();
383
+ if (stored.fingerprint === currentFingerprint) {
384
+ return stored;
385
+ }
386
+ }
387
+ } catch {
388
+ }
389
+ const deviceInfo = {
390
+ deviceId: crypto.randomUUID(),
391
+ fingerprint: generateDeviceFingerprint(),
392
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
393
+ platform: os.platform(),
394
+ arch: os.arch(),
395
+ hostname: os.hostname()
396
+ };
397
+ fs2.writeFileSync(DEVICE_FILE, JSON.stringify(deviceInfo, null, 2), { mode: 384 });
398
+ return deviceInfo;
399
+ }
400
+ function getDeviceId() {
401
+ return getDeviceInfo().deviceId;
402
+ }
403
+ function getDeviceContext() {
404
+ const info = getDeviceInfo();
405
+ let cliVersion = 'unknown';
406
+ try {
407
+ const pkg = require_package();
408
+ cliVersion = pkg.version;
409
+ } catch {
410
+ }
411
+ return {
412
+ deviceId: info.deviceId,
413
+ platform: info.platform,
414
+ arch: info.arch,
415
+ hostname: info.hostname,
416
+ cliVersion,
417
+ nodeVersion: process.version,
418
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
419
+ };
420
+ }
421
+ function clearDeviceInfo() {
422
+ try {
423
+ if (fs2.existsSync(DEVICE_FILE)) {
424
+ fs2.unlinkSync(DEVICE_FILE);
425
+ }
426
+ } catch {
427
+ }
428
+ }
429
+ let fs2, path2, os, crypto, BOOTSPRING_DIR, CREDENTIALS_FILE, CONFIG_FILE, DEVICE_FILE;
430
+ const init_auth = __esm({
431
+ 'src/core/auth.ts'() {
432
+ 'use strict';
433
+ init_cjs_shims();
434
+ fs2 = __toESM(require('fs'));
435
+ path2 = __toESM(require('path'));
436
+ os = __toESM(require('os'));
437
+ crypto = __toESM(require('crypto'));
438
+ BOOTSPRING_DIR = path2.join(os.homedir(), '.bootspring');
439
+ CREDENTIALS_FILE = path2.join(BOOTSPRING_DIR, 'credentials.json');
440
+ CONFIG_FILE = path2.join(BOOTSPRING_DIR, 'config.json');
441
+ DEVICE_FILE = path2.join(BOOTSPRING_DIR, 'device.json');
442
+ }
443
+ });
444
+
445
+ // src/core/session.ts
446
+ const session_exports = {};
447
+ __export(session_exports, {
448
+ BOOTSPRING_DIR: () => BOOTSPRING_DIR2,
449
+ LOCAL_CONFIG_NAME: () => LOCAL_CONFIG_NAME,
450
+ SESSION_FILE: () => SESSION_FILE,
451
+ addRecentProject: () => addRecentProject,
452
+ clearSession: () => clearSession,
453
+ createLocalConfig: () => createLocalConfig,
454
+ ensureDir: () => ensureDir3,
455
+ findLocalConfig: () => findLocalConfig,
456
+ getCurrentProject: () => getCurrentProject,
457
+ getEffectiveProject: () => getEffectiveProject,
458
+ getRecentProjects: () => getRecentProjects,
459
+ getSession: () => getSession,
460
+ getSessionState: () => getSessionState,
461
+ saveSession: () => saveSession,
462
+ setCurrentProject: () => setCurrentProject
463
+ });
464
+ function ensureDir3() {
465
+ if (!fs3.existsSync(BOOTSPRING_DIR2)) {
466
+ fs3.mkdirSync(BOOTSPRING_DIR2, { recursive: true, mode: 448 });
467
+ }
468
+ }
469
+ function getSession() {
470
+ try {
471
+ if (fs3.existsSync(SESSION_FILE)) {
472
+ return JSON.parse(fs3.readFileSync(SESSION_FILE, 'utf-8'));
473
+ }
474
+ } catch {
475
+ }
476
+ return null;
477
+ }
478
+ function saveSession(session) {
479
+ ensureDir3();
480
+ const data = {
481
+ ...session,
482
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
483
+ };
484
+ fs3.writeFileSync(SESSION_FILE, JSON.stringify(data, null, 2));
485
+ }
486
+ function clearSession() {
487
+ try {
488
+ if (fs3.existsSync(SESSION_FILE)) {
489
+ fs3.unlinkSync(SESSION_FILE);
490
+ }
491
+ } catch {
492
+ }
493
+ }
494
+ function getCurrentProject() {
495
+ const session = getSession();
496
+ return session?.project || null;
497
+ }
498
+ function setCurrentProject(project) {
499
+ const session = getSession() || {};
500
+ session.project = project;
501
+ saveSession(session);
502
+ }
503
+ function getRecentProjects() {
504
+ const session = getSession();
505
+ return session?.recentProjects || [];
506
+ }
507
+ function addRecentProject(project) {
508
+ const session = getSession() || {};
509
+ const recent = session.recentProjects || [];
510
+ const filtered = recent.filter((p) => p.id !== project.id);
511
+ filtered.unshift({
512
+ id: project.id,
513
+ name: project.name,
514
+ slug: project.slug,
515
+ lastUsed: (/* @__PURE__ */ new Date()).toISOString()
516
+ });
517
+ session.recentProjects = filtered.slice(0, 10);
518
+ saveSession(session);
519
+ }
520
+ function findLocalConfig(startDir) {
521
+ let dir = startDir || process.cwd();
522
+ for (let i = 0; i < 10; i++) {
523
+ const jsonConfigPath = path3.join(dir, LOCAL_CONFIG_NAME);
524
+ if (fs3.existsSync(jsonConfigPath)) {
525
+ try {
526
+ const config = JSON.parse(fs3.readFileSync(jsonConfigPath, 'utf-8'));
527
+ return {
528
+ ...config,
529
+ _path: jsonConfigPath,
530
+ _dir: dir
531
+ };
532
+ } catch {
533
+ }
534
+ }
535
+ const jsConfigPath = path3.join(dir, 'bootspring.config.js');
536
+ if (fs3.existsSync(jsConfigPath)) {
537
+ try {
538
+ delete require.cache[require.resolve(jsConfigPath)];
539
+ const config = require(jsConfigPath);
540
+ if (config.projectId) {
541
+ const project = config.project;
542
+ return {
543
+ projectId: config.projectId,
544
+ projectName: project?.name || config.name || 'Unknown',
545
+ projectSlug: project?.slug,
546
+ _path: jsConfigPath,
547
+ _dir: dir,
548
+ _fromJsConfig: true
549
+ };
550
+ }
551
+ } catch {
552
+ }
553
+ }
554
+ const parent = path3.dirname(dir);
555
+ if (parent === dir) break;
556
+ dir = parent;
557
+ }
558
+ return null;
559
+ }
560
+ function getEffectiveProject() {
561
+ const local = findLocalConfig();
562
+ if (local?.projectId) {
563
+ return {
564
+ id: local.projectId,
565
+ name: local.projectName || 'Unknown',
566
+ slug: local.projectSlug,
567
+ source: 'local',
568
+ _localConfig: local
569
+ };
570
+ }
571
+ const sessionProject = getCurrentProject();
572
+ if (sessionProject) {
573
+ return {
574
+ ...sessionProject,
575
+ source: 'session'
576
+ };
577
+ }
578
+ return null;
579
+ }
580
+ function createLocalConfig(dir, project) {
581
+ const configPath = path3.join(dir, LOCAL_CONFIG_NAME);
582
+ const config = {
583
+ projectId: project.id,
584
+ projectName: project.name,
585
+ projectSlug: project.slug,
586
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
587
+ };
588
+ fs3.writeFileSync(configPath, JSON.stringify(config, null, 2));
589
+ return configPath;
590
+ }
591
+ function getSessionState() {
592
+ const session = getSession();
593
+ const effective = getEffectiveProject();
594
+ const local = findLocalConfig();
595
+ return {
596
+ hasSession: !!session,
597
+ project: effective,
598
+ source: effective?.source || null,
599
+ hasLocalConfig: !!local,
600
+ localConfigPath: local?._path,
601
+ recentProjects: session?.recentProjects || [],
602
+ lastUpdated: session?.updatedAt || null
603
+ };
604
+ }
605
+ let fs3, path3, os2, BOOTSPRING_DIR2, SESSION_FILE, LOCAL_CONFIG_NAME;
606
+ const init_session = __esm({
607
+ 'src/core/session.ts'() {
608
+ 'use strict';
609
+ init_cjs_shims();
610
+ fs3 = __toESM(require('fs'));
611
+ path3 = __toESM(require('path'));
612
+ os2 = __toESM(require('os'));
613
+ BOOTSPRING_DIR2 = path3.join(os2.homedir(), '.bootspring');
614
+ SESSION_FILE = path3.join(BOOTSPRING_DIR2, 'session.json');
615
+ LOCAL_CONFIG_NAME = '.bootspring.json';
616
+ }
617
+ });
618
+
619
+ // intelligence/git-memory.js
620
+ const require_git_memory = __commonJS({
621
+ 'intelligence/git-memory.js'(exports2, module2) {
622
+ 'use strict';
623
+ init_cjs_shims();
624
+ const { execSync } = require('child_process');
625
+ const MEMORY_CATEGORIES = {
626
+ DECISION: {
627
+ patterns: [
628
+ /decided to/i,
629
+ /chose to/i,
630
+ /switched to/i,
631
+ /migrated to/i,
632
+ /now using/i,
633
+ /replaced .+ with/i,
634
+ /prefer/i
635
+ ],
636
+ icon: '\u{1F3AF}',
637
+ label: 'Decisions'
638
+ },
639
+ BUGFIX: {
640
+ patterns: [
641
+ /fix(ed|es)?:/i,
642
+ /bug(fix)?:/i,
643
+ /resolved/i,
644
+ /patched/i,
645
+ /was causing/i,
646
+ /issue with/i,
647
+ /broke/i,
648
+ /fixed by/i
649
+ ],
650
+ icon: '\u{1F41B}',
651
+ label: 'Bug Fixes & Gotchas'
652
+ },
653
+ PATTERN: {
654
+ patterns: [
655
+ /pattern/i,
656
+ /refactor/i,
657
+ /abstracted/i,
658
+ /extracted/i,
659
+ /standardized/i,
660
+ /convention/i,
661
+ /approach/i
662
+ ],
663
+ icon: '\u{1F527}',
664
+ label: 'Patterns & Conventions'
665
+ },
666
+ ARCHITECTURE: {
667
+ patterns: [
668
+ /architect/i,
669
+ /structure/i,
670
+ /reorganiz/i,
671
+ /module/i,
672
+ /split/i,
673
+ /merged/i,
674
+ /moved .+ to/i,
675
+ /renamed/i
676
+ ],
677
+ icon: '\u{1F3D7}\uFE0F',
678
+ label: 'Architecture'
679
+ },
680
+ DEPENDENCY: {
681
+ patterns: [
682
+ /added .+ dependency/i,
683
+ /upgraded/i,
684
+ /downgraded/i,
685
+ /pinned/i,
686
+ /removed .+ package/i,
687
+ /npm/i,
688
+ /yarn/i
689
+ ],
690
+ icon: '\u{1F4E6}',
691
+ label: 'Dependencies'
692
+ },
693
+ PERFORMANCE: {
694
+ patterns: [
695
+ /performance/i,
696
+ /optimiz/i,
697
+ /faster/i,
698
+ /slower/i,
699
+ /memory/i,
700
+ /cache/i,
701
+ /lazy/i,
702
+ /async/i
703
+ ],
704
+ icon: '\u26A1',
705
+ label: 'Performance'
706
+ },
707
+ SECURITY: {
708
+ patterns: [
709
+ /security/i,
710
+ /vulnerab/i,
711
+ /auth/i,
712
+ /permission/i,
713
+ /sanitiz/i,
714
+ /validat/i,
715
+ /escape/i,
716
+ /inject/i
717
+ ],
718
+ icon: '\u{1F512}',
719
+ label: 'Security'
720
+ },
721
+ CONFIG: {
722
+ patterns: [
723
+ /config/i,
724
+ /environment/i,
725
+ /\.env/i,
726
+ /settings/i,
727
+ /setup/i,
728
+ /initialize/i
729
+ ],
730
+ icon: '\u2699\uFE0F',
731
+ label: 'Configuration'
732
+ }
733
+ };
734
+ function git(command, options = {}) {
735
+ try {
736
+ const cwd = options.cwd || process.cwd();
737
+ return execSync(`git ${command}`, {
738
+ cwd,
739
+ encoding: 'utf-8',
740
+ stdio: ['pipe', 'pipe', 'pipe']
741
+ }).trim();
742
+ } catch (e) {
743
+ if (options.throwOnError) throw e;
744
+ return '';
745
+ }
746
+ }
747
+ function isGitRepo(cwd = process.cwd()) {
748
+ try {
749
+ git('rev-parse --git-dir', { cwd, throwOnError: true });
750
+ return true;
751
+ } catch {
752
+ return false;
753
+ }
754
+ }
755
+ function getCommits(options = {}) {
756
+ const {
757
+ limit = 100,
758
+ since = null,
759
+ until = null,
760
+ author = null,
761
+ path: filePath = null,
762
+ cwd = process.cwd()
763
+ } = options;
764
+ let command = `log --pretty=format:"%H|||%ai|||%an|||%s%x00" -n ${limit}`;
765
+ if (since) command += ` --since="${since}"`;
766
+ if (until) command += ` --until="${until}"`;
767
+ if (author) command += ` --author="${author}"`;
768
+ if (filePath) command += ` -- "${filePath}"`;
769
+ const output = git(command, { cwd });
770
+ if (!output) return [];
771
+ return output.split('\0').filter((record) => record.trim()).map((record) => {
772
+ const [hash, date, author2, subject] = record.trim().split('|||');
773
+ return {
774
+ hash: hash?.slice(0, 8),
775
+ date,
776
+ author: author2,
777
+ subject,
778
+ body: '',
779
+ // Body extraction removed for simplicity - subject is enough for categorization
780
+ fullMessage: subject || ''
781
+ };
782
+ }).filter((c) => c.hash && c.hash.length === 8);
783
+ }
784
+ function categorizeCommit(commit) {
785
+ const categories = [];
786
+ for (const [category, config] of Object.entries(MEMORY_CATEGORIES)) {
787
+ for (const pattern of config.patterns) {
788
+ if (pattern.test(commit.fullMessage)) {
789
+ categories.push(category);
790
+ break;
791
+ }
792
+ }
793
+ }
794
+ if (categories.length === 0) {
795
+ categories.push('GENERAL');
796
+ }
797
+ return categories;
798
+ }
799
+ function getCommitFiles(hash, cwd = process.cwd()) {
800
+ const output = git(`show --name-only --pretty=format:"" ${hash}`, { cwd });
801
+ return output.split('\n').filter((f) => f.trim());
802
+ }
803
+ function extractLearnings(options = {}) {
804
+ const {
805
+ limit = 50,
806
+ since = '3 months ago',
807
+ cwd = process.cwd(),
808
+ includeFiles = false
809
+ } = options;
810
+ if (!isGitRepo(cwd)) {
811
+ return { error: 'Not a git repository', learnings: [] };
812
+ }
813
+ const commits = getCommits({ limit, since, cwd });
814
+ const learnings = [];
815
+ for (const commit of commits) {
816
+ const categories = categorizeCommit(commit);
817
+ if (isMundaneCommit(commit)) continue;
818
+ const learning = {
819
+ hash: commit.hash,
820
+ date: commit.date,
821
+ categories,
822
+ summary: commit.subject,
823
+ details: commit.body || null,
824
+ importance: calculateImportance(commit, categories)
825
+ };
826
+ if (includeFiles) {
827
+ learning.files = getCommitFiles(commit.hash, cwd);
828
+ }
829
+ learnings.push(learning);
830
+ }
831
+ learnings.sort((a, b) => b.importance - a.importance);
832
+ return { learnings, total: commits.length };
833
+ }
834
+ function isMundaneCommit(commit) {
835
+ const mundanePatterns = [
836
+ /^merge/i,
837
+ /^wip/i,
838
+ /^temp/i,
839
+ /^bump version/i,
840
+ /^update changelog/i,
841
+ /^lint/i,
842
+ /^format/i,
843
+ /^typo/i,
844
+ /^minor/i,
845
+ /^small/i
846
+ ];
847
+ return mundanePatterns.some((p) => p.test(commit.subject));
848
+ }
849
+ function calculateImportance(commit, categories) {
850
+ let score = 0;
851
+ const weights = {
852
+ DECISION: 10,
853
+ ARCHITECTURE: 9,
854
+ SECURITY: 8,
855
+ BUGFIX: 7,
856
+ PATTERN: 6,
857
+ PERFORMANCE: 5,
858
+ DEPENDENCY: 4,
859
+ CONFIG: 3,
860
+ GENERAL: 1
861
+ };
862
+ for (const cat of categories) {
863
+ score += weights[cat] || 1;
864
+ }
865
+ if (commit.body && commit.body.length > 50) {
866
+ score += 2;
867
+ }
868
+ if (/^(feat|fix|refactor|perf|security):/i.test(commit.subject)) {
869
+ score += 2;
870
+ }
871
+ return score;
872
+ }
873
+ function groupByCategory(learnings) {
874
+ const grouped = {};
875
+ for (const learning of learnings) {
876
+ for (const category of learning.categories) {
877
+ if (!grouped[category]) {
878
+ grouped[category] = [];
879
+ }
880
+ grouped[category].push(learning);
881
+ }
882
+ }
883
+ return grouped;
884
+ }
885
+ function toMarkdown(learnings, options = {}) {
886
+ const { title = 'Project Learnings', maxPerCategory = 5 } = options;
887
+ const grouped = groupByCategory(learnings);
888
+ let md = `## ${title}
889
+
890
+ `;
891
+ md += `> Extracted from ${learnings.length} significant commits
892
+
893
+ `;
894
+ for (const [category, items] of Object.entries(grouped)) {
895
+ const config = MEMORY_CATEGORIES[category] || { icon: '\u{1F4DD}', label: category };
896
+ const topItems = items.slice(0, maxPerCategory);
897
+ md += `### ${config.icon} ${config.label}
898
+
899
+ `;
900
+ for (const item of topItems) {
901
+ md += `- **${item.summary}**`;
902
+ if (item.details) {
903
+ const firstLine = item.details.split('\n')[0].trim();
904
+ if (firstLine && firstLine.length < 100) {
905
+ md += ` - ${firstLine}`;
906
+ }
907
+ }
908
+ md += ` _(${item.hash})_
909
+ `;
910
+ }
911
+ md += '\n';
912
+ }
913
+ return md;
914
+ }
915
+ function toCompactSummary(learnings, options = {}) {
916
+ const { maxItems = 15 } = options;
917
+ const topLearnings = learnings.slice(0, maxItems);
918
+ let summary = '## Recent Learnings\n\n';
919
+ summary += 'Key decisions and patterns from recent development:\n\n';
920
+ for (const learning of topLearnings) {
921
+ const categoryIcons = learning.categories.map((c) => MEMORY_CATEGORIES[c]?.icon || '\u{1F4DD}').join('');
922
+ summary += `- ${categoryIcons} ${learning.summary}
923
+ `;
924
+ }
925
+ return summary;
926
+ }
927
+ function searchLearnings(learnings, query) {
928
+ const queryLower = query.toLowerCase();
929
+ return learnings.filter((l) => {
930
+ return l.summary.toLowerCase().includes(queryLower) || l.details && l.details.toLowerCase().includes(queryLower) || l.categories.some((c) => c.toLowerCase().includes(queryLower));
931
+ });
932
+ }
933
+ function getLearningsForFiles(files, options = {}) {
934
+ const { cwd = process.cwd(), limit = 20 } = options;
935
+ const learnings = [];
936
+ for (const file of files) {
937
+ const commits = getCommits({ limit: 10, path: file, cwd });
938
+ for (const commit of commits) {
939
+ if (!isMundaneCommit(commit)) {
940
+ const categories = categorizeCommit(commit);
941
+ learnings.push({
942
+ hash: commit.hash,
943
+ date: commit.date,
944
+ categories,
945
+ summary: commit.subject,
946
+ details: commit.body,
947
+ file,
948
+ importance: calculateImportance(commit, categories)
949
+ });
950
+ }
951
+ }
952
+ }
953
+ const seen = /* @__PURE__ */ new Set();
954
+ return learnings.filter((l) => {
955
+ if (seen.has(l.hash)) return false;
956
+ seen.add(l.hash);
957
+ return true;
958
+ }).sort((a, b) => b.importance - a.importance).slice(0, limit);
959
+ }
960
+ function getRepoStats(cwd = process.cwd()) {
961
+ if (!isGitRepo(cwd)) {
962
+ return null;
963
+ }
964
+ const totalCommits = parseInt(git('rev-list --count HEAD', { cwd }) || '0');
965
+ const firstCommit = git('log --reverse --pretty=format:"%ai" | head -1', { cwd });
966
+ const contributors = git('shortlog -sn --no-merges', { cwd }).split('\n').filter((l) => l.trim()).length;
967
+ const recentActivity = git('log --since="1 week ago" --oneline', { cwd }).split('\n').filter((l) => l.trim()).length;
968
+ return {
969
+ totalCommits,
970
+ firstCommit,
971
+ contributors,
972
+ recentActivity,
973
+ weeklyPace: recentActivity
974
+ };
975
+ }
976
+ if (require.main === module2) {
977
+ const args = process.argv.slice(2);
978
+ const command = args[0] || 'summary';
979
+ switch (command) {
980
+ case 'extract': {
981
+ const result = extractLearnings({
982
+ limit: parseInt(args[1]) || 50,
983
+ includeFiles: args.includes('--files')
984
+ });
985
+ if (result.error) {
986
+ console.error(result.error);
987
+ process.exit(1);
988
+ }
989
+ console.log(JSON.stringify(result.learnings, null, 2));
990
+ break;
991
+ }
992
+ case 'summary': {
993
+ const result = extractLearnings({ limit: 50 });
994
+ if (result.error) {
995
+ console.error(result.error);
996
+ process.exit(1);
997
+ }
998
+ console.log(toMarkdown(result.learnings));
999
+ break;
1000
+ }
1001
+ case 'compact': {
1002
+ const result = extractLearnings({ limit: 30 });
1003
+ if (result.error) {
1004
+ console.error(result.error);
1005
+ process.exit(1);
1006
+ }
1007
+ console.log(toCompactSummary(result.learnings));
1008
+ break;
1009
+ }
1010
+ case 'search': {
1011
+ const query = args.slice(1).join(' ');
1012
+ if (!query) {
1013
+ console.error('Usage: git-memory.js search <query>');
1014
+ process.exit(1);
1015
+ }
1016
+ const result = extractLearnings({ limit: 100 });
1017
+ const matches = searchLearnings(result.learnings, query);
1018
+ console.log(`Found ${matches.length} matches for "${query}":
1019
+ `);
1020
+ for (const match of matches.slice(0, 10)) {
1021
+ console.log(` [${match.hash}] ${match.summary}`);
1022
+ }
1023
+ break;
1024
+ }
1025
+ case 'stats': {
1026
+ const stats = getRepoStats();
1027
+ if (!stats) {
1028
+ console.error('Not a git repository');
1029
+ process.exit(1);
1030
+ }
1031
+ console.log(JSON.stringify(stats, null, 2));
1032
+ break;
1033
+ }
1034
+ case 'categories': {
1035
+ console.log('\nAvailable categories:\n');
1036
+ for (const [key, config] of Object.entries(MEMORY_CATEGORIES)) {
1037
+ console.log(` ${config.icon} ${key.padEnd(15)} ${config.label}`);
1038
+ }
1039
+ console.log();
1040
+ break;
1041
+ }
1042
+ default:
1043
+ console.log(`
1044
+ Bootspring Git Memory
1045
+
1046
+ Usage:
1047
+ git-memory.js extract [limit] [--files] Extract learnings as JSON
1048
+ git-memory.js summary Generate markdown summary
1049
+ git-memory.js compact Compact summary for CLAUDE.md
1050
+ git-memory.js search <query> Search learnings
1051
+ git-memory.js stats Repository statistics
1052
+ git-memory.js categories List memory categories
1053
+ `);
1054
+ }
1055
+ }
1056
+ module2.exports = {
1057
+ isGitRepo,
1058
+ getCommits,
1059
+ extractLearnings,
1060
+ groupByCategory,
1061
+ toMarkdown,
1062
+ toCompactSummary,
1063
+ searchLearnings,
1064
+ getLearningsForFiles,
1065
+ getRepoStats,
1066
+ MEMORY_CATEGORIES
1067
+ };
1068
+ }
1069
+ });
1070
+
1071
+ // generators/templates/agents.template.js
1072
+ const require_agents_template = __commonJS({
1073
+ 'generators/templates/agents.template.js'(exports2, module2) {
1074
+ 'use strict';
1075
+ init_cjs_shims();
1076
+ function generate(config) {
1077
+ const sections = [];
1078
+ sections.push(`# ${config.project.name}`);
1079
+ sections.push('');
1080
+ sections.push(`You are a senior full-stack developer working on ${config.project.name}, a ${formatFramework(config.stack.framework)} application with ${config.stack.language}.`);
1081
+ sections.push('');
1082
+ sections.push('## Commands');
1083
+ sections.push('');
1084
+ sections.push('```bash');
1085
+ sections.push('# Development');
1086
+ sections.push('npm run dev');
1087
+ sections.push('');
1088
+ sections.push('# Quality checks (run before committing)');
1089
+ sections.push('npm run lint');
1090
+ sections.push('npm run test');
1091
+ sections.push('npm run typecheck');
1092
+ sections.push('');
1093
+ sections.push('# Build tasks');
1094
+ sections.push('bootspring build next # Get next task');
1095
+ sections.push('bootspring build done # Mark task complete');
1096
+ sections.push('bootspring build status # Check progress');
1097
+ sections.push('```');
1098
+ sections.push('');
1099
+ sections.push('## Build Loop');
1100
+ sections.push('');
1101
+ sections.push('**Start building:**');
1102
+ sections.push('```');
1103
+ sections.push('bootspring build next');
1104
+ sections.push('```');
1105
+ sections.push('');
1106
+ sections.push('This loops through all tasks:');
1107
+ sections.push('1. Find in_progress task in `planning/TASK_QUEUE.md`');
1108
+ sections.push('2. Implement following acceptance criteria');
1109
+ sections.push('3. Run `bootspring build done`');
1110
+ sections.push('4. Continue to next task (auto-queued)');
1111
+ sections.push('');
1112
+ sections.push('**Keep looping until MVP complete.**');
1113
+ sections.push('');
1114
+ sections.push('## Stack');
1115
+ sections.push('');
1116
+ sections.push(`- **Framework**: ${formatFramework(config.stack.framework)}`);
1117
+ sections.push(`- **Language**: ${config.stack.language}`);
1118
+ if (config.stack.database !== 'none') {
1119
+ sections.push(`- **Database**: ${config.stack.database}`);
1120
+ if (config.stack.orm) {
1121
+ sections.push(`- **ORM**: ${config.stack.orm}`);
1122
+ }
1123
+ }
1124
+ sections.push('');
1125
+ sections.push('## Code Style');
1126
+ sections.push('');
1127
+ if (config.stack.framework === 'nextjs') {
1128
+ sections.push('- Use Server Components by default');
1129
+ sections.push('- Use Server Actions for mutations (not API routes)');
1130
+ }
1131
+ sections.push('- Use Zod for input validation');
1132
+ sections.push('- Keep files under 300 lines');
1133
+ sections.push('- Use meaningful variable and function names');
1134
+ sections.push('- Follow existing patterns in the codebase');
1135
+ sections.push('');
1136
+ sections.push('## Git Workflow');
1137
+ sections.push('');
1138
+ sections.push('- Conventional commits: `feat:`, `fix:`, `docs:`, `refactor:`');
1139
+ sections.push('- Atomic commits - one logical change per commit');
1140
+ sections.push('- Run quality checks before committing');
1141
+ sections.push('- Never force push to main');
1142
+ sections.push('- **Never add Co-Authored-By or AI attribution to commits**');
1143
+ sections.push('');
1144
+ sections.push('## Boundaries');
1145
+ sections.push('');
1146
+ sections.push('**Never:**');
1147
+ sections.push('- Commit secrets, API keys, or credentials');
1148
+ sections.push('- Modify `.env` files with real values');
1149
+ sections.push('- Delete or overwrite migration files');
1150
+ sections.push('- Skip tests to make code compile');
1151
+ sections.push('- Introduce breaking changes without discussion');
1152
+ sections.push('');
1153
+ sections.push('**Always:**');
1154
+ sections.push('- Read existing code before modifying');
1155
+ sections.push('- Run tests after changes');
1156
+ sections.push('- Check for TypeScript errors');
1157
+ sections.push('- Follow the acceptance criteria exactly');
1158
+ sections.push('');
1159
+ sections.push('## Testing');
1160
+ sections.push('');
1161
+ sections.push('- Write tests for new features');
1162
+ sections.push('- Ensure existing tests still pass');
1163
+ sections.push('- Test edge cases and error conditions');
1164
+ sections.push('');
1165
+ return sections.join('\n');
1166
+ }
1167
+ function generatePlanningAgents(_config) {
1168
+ const sections = [];
1169
+ sections.push('# Planning Directory');
1170
+ sections.push('');
1171
+ sections.push('You are working on build tasks for the MVP. This directory contains build planning files.');
1172
+ sections.push('');
1173
+ sections.push('## Current Task');
1174
+ sections.push('');
1175
+ sections.push('Find the task with `status: in_progress` in `TASK_QUEUE.md`.');
1176
+ sections.push('');
1177
+ sections.push('## Commands');
1178
+ sections.push('');
1179
+ sections.push('```bash');
1180
+ sections.push('bootspring build done # Mark task complete, get next');
1181
+ sections.push('bootspring build skip # Skip task, get next');
1182
+ sections.push('bootspring build status # View progress');
1183
+ sections.push('```');
1184
+ sections.push('');
1185
+ sections.push('## Files');
1186
+ sections.push('');
1187
+ sections.push('| File | Purpose |');
1188
+ sections.push('|------|---------|');
1189
+ sections.push('| `TASK_QUEUE.md` | All tasks - find in_progress task |');
1190
+ sections.push('| `TODO.md` | Full task checklist |');
1191
+ sections.push('| `BUILD_STATE.json` | Build state (do not edit) |');
1192
+ sections.push('| `TASK_QUEUE.md` | Ordered task queue |');
1193
+ sections.push('| `CONTEXT.md` | Build context summary |');
1194
+ sections.push('');
1195
+ sections.push('## Workflow');
1196
+ sections.push('');
1197
+ sections.push('1. Find in_progress task in `TASK_QUEUE.md`');
1198
+ sections.push('2. Implement in the main codebase (not here)');
1199
+ sections.push('3. Ensure acceptance criteria met');
1200
+ sections.push('4. Run `npm run lint && npm run test`');
1201
+ sections.push('5. Commit changes');
1202
+ sections.push('6. Run `bootspring build done`');
1203
+ sections.push('');
1204
+ sections.push('## Boundaries');
1205
+ sections.push('');
1206
+ sections.push('- Do NOT edit `BUILD_STATE.json` directly');
1207
+ sections.push('- Do NOT skip quality checks');
1208
+ sections.push('- Do NOT work on multiple tasks at once');
1209
+ sections.push('');
1210
+ return sections.join('\n');
1211
+ }
1212
+ function formatFramework(framework) {
1213
+ const names = {
1214
+ 'nextjs': 'Next.js 14+ (App Router)',
1215
+ 'remix': 'Remix',
1216
+ 'nuxt': 'Nuxt 3',
1217
+ 'sveltekit': 'SvelteKit',
1218
+ 'express': 'Express.js',
1219
+ 'fastify': 'Fastify',
1220
+ 'hono': 'Hono'
1221
+ };
1222
+ return names[framework] || framework;
1223
+ }
1224
+ module2.exports = {
1225
+ generate,
1226
+ generatePlanningAgents
1227
+ };
1228
+ }
1229
+ });
1230
+
1231
+ // src/cli/index.ts
1232
+ const cli_exports = {};
1233
+ __export(cli_exports, {
1234
+ dashboard: () => dashboard_exports,
1235
+ generate: () => generate_exports,
1236
+ generateClaudeMd: () => generateClaudeMd,
1237
+ runDashboard: () => run2,
1238
+ runGenerate: () => run3,
1239
+ runTelemetry: () => run,
1240
+ runTodo: () => run4,
1241
+ telemetry: () => telemetry_exports,
1242
+ todo: () => todo_exports
1243
+ });
1244
+ module.exports = __toCommonJS(cli_exports);
1245
+ init_cjs_shims();
1246
+
1247
+ // src/cli/telemetry.ts
1248
+ var telemetry_exports = {};
1249
+ __export(telemetry_exports, {
1250
+ run: () => run
1251
+ });
1252
+ init_cjs_shims();
1253
+
1254
+ // src/core/utils.ts
1255
+ init_cjs_shims();
1256
+ const fs = __toESM(require('fs'));
1257
+ const path = __toESM(require('path'));
1258
+ const COLORS = {
1259
+ reset: '\x1B[0m',
1260
+ bold: '\x1B[1m',
1261
+ dim: '\x1B[2m',
1262
+ italic: '\x1B[3m',
1263
+ underline: '\x1B[4m',
1264
+ // Foreground
1265
+ black: '\x1B[30m',
1266
+ red: '\x1B[31m',
1267
+ green: '\x1B[32m',
1268
+ yellow: '\x1B[33m',
1269
+ blue: '\x1B[34m',
1270
+ magenta: '\x1B[35m',
1271
+ cyan: '\x1B[36m',
1272
+ white: '\x1B[37m',
1273
+ // Background
1274
+ bgBlack: '\x1B[40m',
1275
+ bgRed: '\x1B[41m',
1276
+ bgGreen: '\x1B[42m',
1277
+ bgYellow: '\x1B[43m',
1278
+ bgBlue: '\x1B[44m',
1279
+ bgMagenta: '\x1B[45m',
1280
+ bgCyan: '\x1B[46m',
1281
+ bgWhite: '\x1B[47m'
1282
+ };
1283
+ const print = {
1284
+ info: (msg) => console.log(`${COLORS.cyan}\u2139${COLORS.reset} ${msg}`),
1285
+ success: (msg) => console.log(`${COLORS.green}\u2713${COLORS.reset} ${msg}`),
1286
+ warning: (msg) => console.log(`${COLORS.yellow}\u26A0${COLORS.reset} ${msg}`),
1287
+ error: (msg) => console.log(`${COLORS.red}\u2717${COLORS.reset} ${msg}`),
1288
+ debug: (msg) => {
1289
+ if (process.env.DEBUG) {
1290
+ console.log(`${COLORS.dim}\u22EF ${msg}${COLORS.reset}`);
1291
+ }
1292
+ },
1293
+ header: (msg) => console.log(`
1294
+ ${COLORS.bold}${COLORS.cyan}${msg}${COLORS.reset}
1295
+ `),
1296
+ dim: (msg) => console.log(`${COLORS.dim}${msg}${COLORS.reset}`),
1297
+ brand: (msg) => console.log(`${COLORS.cyan}\u26A1${COLORS.reset} ${msg}`)
1298
+ };
1299
+ function createSpinner(message) {
1300
+ const frames = ['\u280B', '\u2819', '\u2839', '\u2838', '\u283C', '\u2834', '\u2826', '\u2827', '\u2807', '\u280F'];
1301
+ let frameIndex = 0;
1302
+ let interval = null;
1303
+ const isTTY = process.stdout.isTTY;
1304
+ const clearLine = () => {
1305
+ if (isTTY && process.stdout.clearLine) {
1306
+ process.stdout.clearLine(0);
1307
+ process.stdout.cursorTo(0);
1308
+ }
1309
+ };
1310
+ return {
1311
+ start() {
1312
+ if (isTTY) {
1313
+ process.stdout.write(`${COLORS.cyan}${frames[0]}${COLORS.reset} ${message}`);
1314
+ interval = setInterval(() => {
1315
+ frameIndex = (frameIndex + 1) % frames.length;
1316
+ clearLine();
1317
+ process.stdout.write(`${COLORS.cyan}${frames[frameIndex]}${COLORS.reset} ${message}`);
1318
+ }, 80);
1319
+ }
1320
+ return this;
1321
+ },
1322
+ succeed(text = message) {
1323
+ if (interval) clearInterval(interval);
1324
+ clearLine();
1325
+ console.log(`${COLORS.green}\u2713${COLORS.reset} ${text}`);
1326
+ return this;
1327
+ },
1328
+ fail(text = message) {
1329
+ if (interval) clearInterval(interval);
1330
+ clearLine();
1331
+ console.log(`${COLORS.red}\u2717${COLORS.reset} ${text}`);
1332
+ return this;
1333
+ },
1334
+ warn(text = message) {
1335
+ if (interval) clearInterval(interval);
1336
+ clearLine();
1337
+ console.log(`${COLORS.yellow}\u26A0${COLORS.reset} ${text}`);
1338
+ return this;
1339
+ },
1340
+ info(text = message) {
1341
+ if (interval) clearInterval(interval);
1342
+ clearLine();
1343
+ console.log(`${COLORS.cyan}\u2139${COLORS.reset} ${text}`);
1344
+ return this;
1345
+ },
1346
+ stop() {
1347
+ if (interval) clearInterval(interval);
1348
+ clearLine();
1349
+ return this;
1350
+ }
1351
+ };
1352
+ }
1353
+ function ensureDir(dirPath) {
1354
+ try {
1355
+ if (!fs.existsSync(dirPath)) {
1356
+ fs.mkdirSync(dirPath, { recursive: true });
1357
+ }
1358
+ return true;
1359
+ } catch {
1360
+ return false;
1361
+ }
1362
+ }
1363
+ function readFile(filepath, defaultValue = null) {
1364
+ try {
1365
+ return fs.readFileSync(filepath, 'utf-8');
1366
+ } catch {
1367
+ return defaultValue;
1368
+ }
1369
+ }
1370
+ function writeFile(filepath, content) {
1371
+ try {
1372
+ const dir = path.dirname(filepath);
1373
+ ensureDir(dir);
1374
+ fs.writeFileSync(filepath, content, 'utf-8');
1375
+ return true;
1376
+ } catch {
1377
+ return false;
1378
+ }
1379
+ }
1380
+ function fileExists(filepath) {
1381
+ try {
1382
+ return fs.existsSync(filepath);
1383
+ } catch {
1384
+ return false;
1385
+ }
1386
+ }
1387
+ function getFileTime(filepath) {
1388
+ try {
1389
+ const stats = fs.statSync(filepath);
1390
+ return stats.mtime;
1391
+ } catch {
1392
+ return null;
1393
+ }
1394
+ }
1395
+ function formatDate(date = /* @__PURE__ */ new Date()) {
1396
+ const parts = date.toISOString().split('T');
1397
+ return parts[0] ?? date.toISOString().substring(0, 10);
1398
+ }
1399
+ function parseArgs(args) {
1400
+ const result = { _: [] };
1401
+ for (let i = 0; i < args.length; i++) {
1402
+ const arg = args[i];
1403
+ if (!arg) continue;
1404
+ if (arg.startsWith('--')) {
1405
+ const key = arg.slice(2);
1406
+ const nextArg = args[i + 1];
1407
+ if (key.includes('=')) {
1408
+ const parts = key.split('=');
1409
+ const k = parts[0];
1410
+ const v = parts.slice(1).join('=');
1411
+ if (k) result[k] = v;
1412
+ } else if (nextArg && !nextArg.startsWith('-')) {
1413
+ result[key] = nextArg;
1414
+ i++;
1415
+ } else {
1416
+ result[key] = true;
1417
+ }
1418
+ } else if (arg.startsWith('-') && arg.length === 2) {
1419
+ const key = arg.slice(1);
1420
+ const nextArg = args[i + 1];
1421
+ if (nextArg && !nextArg.startsWith('-')) {
1422
+ result[key] = nextArg;
1423
+ i++;
1424
+ } else {
1425
+ result[key] = true;
1426
+ }
1427
+ } else if (arg.startsWith('-')) {
1428
+ const keys = arg.slice(1).split('');
1429
+ for (const key of keys) {
1430
+ if (key) result[key] = true;
1431
+ }
1432
+ } else {
1433
+ result._.push(arg);
1434
+ }
1435
+ }
1436
+ return result;
1437
+ }
1438
+
1439
+ // src/core/telemetry.ts
1440
+ init_cjs_shims();
1441
+ const fs4 = __toESM(require('fs'));
1442
+ const path4 = __toESM(require('path'));
1443
+ const crypto2 = __toESM(require('crypto'));
1444
+ const MAX_EVENTS_LIMIT = 1e4;
1445
+ function getTelemetryDir(projectRoot = process.cwd()) {
1446
+ return path4.join(projectRoot, '.bootspring', 'telemetry');
1447
+ }
1448
+ function getTelemetryFile(projectRoot = process.cwd()) {
1449
+ return path4.join(getTelemetryDir(projectRoot), 'events.jsonl');
1450
+ }
1451
+ function parseEventLine(line) {
1452
+ try {
1453
+ return JSON.parse(line);
1454
+ } catch {
1455
+ return null;
1456
+ }
1457
+ }
1458
+ function listEvents(options = {}) {
1459
+ const projectRoot = options.projectRoot || process.cwd();
1460
+ const file = getTelemetryFile(projectRoot);
1461
+ if (!fs4.existsSync(file)) return [];
1462
+ const eventFilter = String(options.event || '').trim();
1463
+ const from = options.from ? new Date(options.from).getTime() : null;
1464
+ const to = options.to ? new Date(options.to).getTime() : null;
1465
+ const limit = Number(options.limit);
1466
+ const lines = fs4.readFileSync(file, 'utf-8').split('\n').filter(Boolean);
1467
+ let records = lines.map(parseEventLine).filter((record) => record !== null).filter((record) => {
1468
+ if (eventFilter && record.event !== eventFilter) return false;
1469
+ const ts = new Date(record.timestamp).getTime();
1470
+ if (Number.isFinite(from) && from !== null && ts < from) return false;
1471
+ if (Number.isFinite(to) && to !== null && ts > to) return false;
1472
+ return true;
1473
+ });
1474
+ const effectiveLimit = Number.isFinite(limit) && limit > 0 ? Math.min(limit, MAX_EVENTS_LIMIT) : MAX_EVENTS_LIMIT;
1475
+ records = records.slice(-effectiveLimit);
1476
+ return records;
1477
+ }
1478
+ function clearEvents(options = {}) {
1479
+ const projectRoot = options.projectRoot || process.cwd();
1480
+ const file = getTelemetryFile(projectRoot);
1481
+ const records = listEvents({ projectRoot });
1482
+ if (fs4.existsSync(file)) {
1483
+ fs4.writeFileSync(file, '', 'utf-8');
1484
+ }
1485
+ return {
1486
+ cleared: records.length,
1487
+ file
1488
+ };
1489
+ }
1490
+ function getStatus(options = {}) {
1491
+ const projectRoot = options.projectRoot || process.cwd();
1492
+ const file = getTelemetryFile(projectRoot);
1493
+ const records = listEvents({ projectRoot });
1494
+ const lastRecord = records.length > 0 ? records[records.length - 1] : null;
1495
+ return {
1496
+ file,
1497
+ exists: fs4.existsSync(file),
1498
+ count: records.length,
1499
+ lastEventAt: lastRecord?.timestamp ?? null
1500
+ };
1501
+ }
1502
+ function sleep(ms) {
1503
+ return new Promise((resolve) => setTimeout(resolve, ms));
1504
+ }
1505
+ function chunkArray(items, size) {
1506
+ const chunks = [];
1507
+ for (let i = 0; i < items.length; i += size) {
1508
+ chunks.push(items.slice(i, i + size));
1509
+ }
1510
+ return chunks;
1511
+ }
1512
+ async function postBatchWithRetry(endpoint, body, headers, options = {}) {
1513
+ const maxRetries = Number(options.maxRetries);
1514
+ const retryDelayMs = Number(options.retryDelayMs);
1515
+ const retries = Number.isFinite(maxRetries) && maxRetries >= 0 ? maxRetries : 2;
1516
+ const delayBase = Number.isFinite(retryDelayMs) && retryDelayMs > 0 ? retryDelayMs : 300;
1517
+ let attempt = 0;
1518
+ while (true) {
1519
+ try {
1520
+ const response = await fetch(endpoint, {
1521
+ method: 'POST',
1522
+ headers,
1523
+ body: JSON.stringify(body)
1524
+ });
1525
+ if (!response.ok) {
1526
+ throw new Error(`HTTP ${response.status}`);
1527
+ }
1528
+ return { success: true, attempts: attempt + 1 };
1529
+ } catch (error) {
1530
+ if (attempt >= retries) {
1531
+ return {
1532
+ success: false,
1533
+ attempts: attempt + 1,
1534
+ error: error instanceof Error ? error.message : String(error)
1535
+ };
1536
+ }
1537
+ attempt += 1;
1538
+ const delay = delayBase * 2 ** (attempt - 1);
1539
+ await sleep(delay);
1540
+ }
1541
+ }
1542
+ }
1543
+ async function uploadEvents(options = {}) {
1544
+ const projectRoot = options.projectRoot || process.cwd();
1545
+ const API_BASE = process.env.BOOTSPRING_API_URL || 'https://www.bootspring.com';
1546
+ const defaultEndpoint = `${API_BASE}/api/v1/events/batch`;
1547
+ const endpoint = options.endpoint || process.env.BOOTSPRING_TELEMETRY_ENDPOINT || defaultEndpoint;
1548
+ const token = options.token || process.env.BOOTSPRING_TELEMETRY_TOKEN;
1549
+ const event = options.event;
1550
+ const limit = Number(options.limit) || void 0;
1551
+ const batchSizeOption = Number(options.batchSize || process.env.BOOTSPRING_TELEMETRY_BATCH_SIZE);
1552
+ const batchSize = Number.isFinite(batchSizeOption) && batchSizeOption > 0 ? batchSizeOption : 100;
1553
+ const clearOnSuccess = options.clearOnSuccess === true;
1554
+ const records = listEvents({ projectRoot, event, limit });
1555
+ if (records.length === 0) {
1556
+ return {
1557
+ uploaded: 0,
1558
+ remaining: 0,
1559
+ endpoint
1560
+ };
1561
+ }
1562
+ let apiKey = null;
1563
+ let projectId = null;
1564
+ try {
1565
+ const auth = await Promise.resolve().then(() => (init_auth(), auth_exports));
1566
+ const session = await Promise.resolve().then(() => (init_session(), session_exports));
1567
+ apiKey = auth.getApiKey();
1568
+ const project = session.getEffectiveProject();
1569
+ projectId = project?.id ?? null;
1570
+ } catch {
1571
+ }
1572
+ let version = 'unknown';
1573
+ try {
1574
+ const pkg = require_package();
1575
+ version = pkg.version;
1576
+ } catch {
1577
+ }
1578
+ const headers = {
1579
+ 'content-type': 'application/json',
1580
+ accept: 'application/json',
1581
+ 'user-agent': `bootspring-cli/${version}`
1582
+ };
1583
+ if (apiKey) {
1584
+ headers['x-api-key'] = apiKey;
1585
+ } else if (token) {
1586
+ headers.authorization = `Bearer ${token}`;
1587
+ }
1588
+ if (projectId) {
1589
+ headers['x-project-id'] = projectId;
1590
+ }
1591
+ const batches = chunkArray(records, batchSize);
1592
+ let uploaded = 0;
1593
+ let totalAttempts = 0;
1594
+ const failedBatches = [];
1595
+ for (let i = 0; i < batches.length; i++) {
1596
+ const events = batches[i];
1597
+ if (!events) continue;
1598
+ const batchId = crypto2.createHash('sha1').update(JSON.stringify(events)).digest('hex');
1599
+ const result = await postBatchWithRetry(
1600
+ endpoint,
1601
+ {
1602
+ source: 'bootspring',
1603
+ batch: {
1604
+ index: i,
1605
+ total: batches.length,
1606
+ id: batchId
1607
+ },
1608
+ events
1609
+ },
1610
+ {
1611
+ ...headers,
1612
+ 'x-bootspring-batch-id': batchId
1613
+ },
1614
+ options
1615
+ );
1616
+ totalAttempts += result.attempts ?? 1;
1617
+ if (!result.success) {
1618
+ failedBatches.push({
1619
+ index: i,
1620
+ count: events.length,
1621
+ error: result.error ?? 'upload_failed'
1622
+ });
1623
+ continue;
1624
+ }
1625
+ uploaded += events.length;
1626
+ }
1627
+ if (failedBatches.length > 0) {
1628
+ throw new Error(`Telemetry upload failed for ${failedBatches.length}/${batches.length} batches`);
1629
+ }
1630
+ if (clearOnSuccess) {
1631
+ clearEvents({ projectRoot });
1632
+ }
1633
+ const remaining = clearOnSuccess ? 0 : listEvents({ projectRoot }).length;
1634
+ return {
1635
+ uploaded,
1636
+ attempted: records.length,
1637
+ batches: batches.length,
1638
+ attempts: totalAttempts,
1639
+ remaining,
1640
+ endpoint,
1641
+ failedBatches
1642
+ };
1643
+ }
1644
+
1645
+ // src/cli/telemetry.ts
1646
+ function showStatus() {
1647
+ const status = getStatus();
1648
+ console.log(`
1649
+ ${COLORS.cyan}${COLORS.bold}Telemetry Status${COLORS.reset}
1650
+ ${COLORS.dim}File: ${status.file}${COLORS.reset}
1651
+ ${COLORS.dim}Events: ${status.count}${COLORS.reset}
1652
+ ${COLORS.dim}Last event: ${status.lastEventAt ?? 'none'}${COLORS.reset}
1653
+ `);
1654
+ }
1655
+ function listEvents2(options = {}) {
1656
+ const listOptions = {
1657
+ event: options.event,
1658
+ limit: options.limit
1659
+ };
1660
+ const records = listEvents(listOptions);
1661
+ if (records.length === 0) {
1662
+ print.warning('No telemetry events found');
1663
+ return;
1664
+ }
1665
+ records.forEach((record) => {
1666
+ console.log(`${COLORS.cyan}${record.timestamp}${COLORS.reset} ${record.event}`);
1667
+ });
1668
+ console.log(`
1669
+ ${COLORS.dim}${records.length} event(s)${COLORS.reset}`);
1670
+ }
1671
+ async function upload(options = {}) {
1672
+ const spinner = createSpinner('Uploading telemetry events').start();
1673
+ try {
1674
+ const uploadOptions = {
1675
+ endpoint: options.endpoint,
1676
+ token: options.token,
1677
+ event: options.event,
1678
+ limit: options.limit,
1679
+ clearOnSuccess: options.clear
1680
+ };
1681
+ const result = await uploadEvents(uploadOptions);
1682
+ spinner.succeed(`Uploaded ${result.uploaded} event(s)`);
1683
+ print.dim(`Endpoint: ${result.endpoint}`);
1684
+ print.dim(`Remaining local events: ${result.remaining}`);
1685
+ } catch (error) {
1686
+ spinner.fail(`Upload failed: ${error instanceof Error ? error.message : String(error)}`);
1687
+ }
1688
+ }
1689
+ function clear() {
1690
+ const result = clearEvents();
1691
+ print.success(`Cleared ${result.cleared} event(s)`);
1692
+ }
1693
+ function help() {
1694
+ console.log(`
1695
+ ${COLORS.cyan}${COLORS.bold}Bootspring Telemetry${COLORS.reset}
1696
+
1697
+ ${COLORS.cyan}Usage:${COLORS.reset}
1698
+ bootspring telemetry <command> [options]
1699
+
1700
+ ${COLORS.cyan}Commands:${COLORS.reset}
1701
+ ${COLORS.cyan}status${COLORS.reset} Show telemetry status
1702
+ ${COLORS.cyan}list${COLORS.reset} List telemetry events
1703
+ ${COLORS.cyan}upload${COLORS.reset} Upload events to endpoint
1704
+ ${COLORS.cyan}clear${COLORS.reset} Clear local event log
1705
+
1706
+ ${COLORS.cyan}Options:${COLORS.reset}
1707
+ ${COLORS.cyan}--event <name>${COLORS.reset} Filter by event name
1708
+ ${COLORS.cyan}--limit <n>${COLORS.reset} Limit listed/uploaded events
1709
+ ${COLORS.cyan}--endpoint <url>${COLORS.reset} Upload endpoint override
1710
+ ${COLORS.cyan}--token <value>${COLORS.reset} Upload bearer token
1711
+ ${COLORS.cyan}--clear${COLORS.reset} Clear local events on successful upload
1712
+ `);
1713
+ }
1714
+ async function run(args) {
1715
+ const parsed = parseArgs(args);
1716
+ const command = parsed._[0] ?? 'status';
1717
+ switch (command) {
1718
+ case 'status':
1719
+ showStatus();
1720
+ break;
1721
+ case 'list':
1722
+ listEvents2({
1723
+ event: parsed.event,
1724
+ limit: parsed.limit ? Number(parsed.limit) : void 0
1725
+ });
1726
+ break;
1727
+ case 'upload':
1728
+ await upload({
1729
+ endpoint: parsed.endpoint,
1730
+ token: parsed.token,
1731
+ event: parsed.event,
1732
+ limit: parsed.limit ? Number(parsed.limit) : void 0,
1733
+ clear: Boolean(parsed.clear)
1734
+ });
1735
+ break;
1736
+ case 'clear':
1737
+ clear();
1738
+ break;
1739
+ case 'help':
1740
+ case '--help':
1741
+ case '-h':
1742
+ help();
1743
+ break;
1744
+ default:
1745
+ print.error(`Unknown telemetry command: ${command}`);
1746
+ help();
1747
+ }
1748
+ }
1749
+
1750
+ // src/cli/dashboard.ts
1751
+ var dashboard_exports = {};
1752
+ __export(dashboard_exports, {
1753
+ run: () => run2
1754
+ });
1755
+ init_cjs_shims();
1756
+ const import_child_process = require('child_process');
1757
+ init_auth();
1758
+ const DASHBOARD_URL = 'https://bootspring.com/dashboard';
1759
+ const colors = {
1760
+ reset: '\x1B[0m',
1761
+ bold: '\x1B[1m',
1762
+ dim: '\x1B[2m',
1763
+ green: '\x1B[32m',
1764
+ yellow: '\x1B[33m',
1765
+ cyan: '\x1B[36m'
1766
+ };
1767
+ function openBrowser(url) {
1768
+ const platform2 = process.platform;
1769
+ let command;
1770
+ if (platform2 === 'darwin') {
1771
+ command = `open "${url}"`;
1772
+ } else if (platform2 === 'win32') {
1773
+ command = `start "" "${url}"`;
1774
+ } else {
1775
+ command = `xdg-open "${url}"`;
1776
+ }
1777
+ (0, import_child_process.exec)(command, (error) => {
1778
+ if (error) {
1779
+ console.log(`${colors.dim}Could not open browser automatically.${colors.reset}`);
1780
+ console.log(`${colors.dim}Please visit: ${url}${colors.reset}`);
1781
+ }
1782
+ });
1783
+ }
1784
+ function openDashboard() {
1785
+ console.log(`
1786
+ ${colors.cyan}${colors.bold}\u26A1 Bootspring Dashboard${colors.reset}`);
1787
+ if (!isAuthenticated()) {
1788
+ console.log(`
1789
+ ${colors.yellow}You are not logged in.${colors.reset}`);
1790
+ console.log(`${colors.dim}Run 'bootspring auth login' first to access the dashboard.${colors.reset}
1791
+ `);
1792
+ console.log(`${colors.dim}Opening login page...${colors.reset}`);
1793
+ openBrowser('https://bootspring.com/login');
1794
+ return;
1795
+ }
1796
+ const user = getUser();
1797
+ console.log(`
1798
+ ${colors.dim}Opening dashboard for ${user?.email ?? 'unknown'}...${colors.reset}`);
1799
+ const token = getToken();
1800
+ let url = DASHBOARD_URL;
1801
+ if (token) {
1802
+ url = `${DASHBOARD_URL}?token=${encodeURIComponent(token)}`;
1803
+ }
1804
+ openBrowser(url);
1805
+ console.log(`
1806
+ ${colors.green}\u2713 Dashboard opened in browser${colors.reset}`);
1807
+ console.log(`${colors.dim}URL: ${DASHBOARD_URL}${colors.reset}
1808
+ `);
1809
+ }
1810
+ function showHelp() {
1811
+ console.log(`
1812
+ ${colors.cyan}${colors.bold}\u26A1 Bootspring Dashboard${colors.reset}
1813
+ ${colors.dim}Cloud-based project dashboard${colors.reset}
1814
+
1815
+ ${colors.bold}Usage:${colors.reset}
1816
+ bootspring dashboard
1817
+
1818
+ ${colors.bold}Description:${colors.reset}
1819
+ Opens the Bootspring cloud dashboard in your browser.
1820
+
1821
+ The dashboard provides:
1822
+ - Real-time project context sync
1823
+ - Usage analytics and billing
1824
+ - Team management (Team tier)
1825
+ - API key management
1826
+
1827
+ ${colors.bold}Notes:${colors.reset}
1828
+ - You must be logged in (bootspring auth login)
1829
+ - Dashboard is available at https://bootspring.com/dashboard
1830
+ - Pro tier required for full dashboard features
1831
+ `);
1832
+ }
1833
+ async function run2(args) {
1834
+ const subcommand = args[0];
1835
+ if (subcommand === 'help' || subcommand === '-h' || subcommand === '--help') {
1836
+ showHelp();
1837
+ return;
1838
+ }
1839
+ openDashboard();
1840
+ }
1841
+
1842
+ // src/cli/generate.ts
1843
+ var generate_exports = {};
1844
+ __export(generate_exports, {
1845
+ generateClaudeMd: () => generateClaudeMd,
1846
+ run: () => run3
1847
+ });
1848
+ init_cjs_shims();
1849
+ const path7 = __toESM(require('path'));
1850
+
1851
+ // src/core/config.ts
1852
+ init_cjs_shims();
1853
+ const fs5 = __toESM(require('fs'));
1854
+ const path5 = __toESM(require('path'));
1855
+ const import_zod = require('zod');
1856
+ const BasePluginSchema = import_zod.z.object({
1857
+ enabled: import_zod.z.boolean().optional().default(false),
1858
+ provider: import_zod.z.string().optional(),
1859
+ features: import_zod.z.array(import_zod.z.string()).optional().default([])
1860
+ }).passthrough();
1861
+ const AuthPluginSchema = import_zod.z.object({
1862
+ enabled: import_zod.z.boolean().optional().default(false),
1863
+ provider: import_zod.z.enum(['clerk', 'nextauth', 'auth0', 'supabase', 'jwt', 'custom']).optional(),
1864
+ features: import_zod.z.array(import_zod.z.enum([
1865
+ 'social_login',
1866
+ 'email_password',
1867
+ 'magic_link',
1868
+ 'sso',
1869
+ 'mfa',
1870
+ 'rbac',
1871
+ 'passwordless'
1872
+ ])).optional().default([])
1873
+ }).passthrough();
1874
+ const PaymentsPluginSchema = import_zod.z.object({
1875
+ enabled: import_zod.z.boolean().optional().default(false),
1876
+ provider: import_zod.z.enum(['stripe', 'paddle', 'lemonsqueezy', 'paypal', 'custom']).optional(),
1877
+ features: import_zod.z.array(import_zod.z.enum([
1878
+ 'checkout',
1879
+ 'subscriptions',
1880
+ 'invoices',
1881
+ 'usage_billing',
1882
+ 'trials',
1883
+ 'coupons'
1884
+ ])).optional().default([])
1885
+ }).passthrough();
1886
+ const DatabasePluginSchema = import_zod.z.object({
1887
+ enabled: import_zod.z.boolean().optional().default(true),
1888
+ provider: import_zod.z.enum(['prisma', 'drizzle', 'typeorm', 'kysely', 'custom']).optional(),
1889
+ features: import_zod.z.array(import_zod.z.enum([
1890
+ 'migrations',
1891
+ 'transactions',
1892
+ 'seeding',
1893
+ 'multi_tenant',
1894
+ 'full_text_search',
1895
+ 'soft_delete'
1896
+ ])).optional().default([])
1897
+ }).passthrough();
1898
+ const TestingPluginSchema = import_zod.z.object({
1899
+ enabled: import_zod.z.boolean().optional().default(true),
1900
+ provider: import_zod.z.enum(['vitest', 'jest', 'playwright', 'cypress', 'custom']).optional(),
1901
+ features: import_zod.z.array(import_zod.z.enum([
1902
+ 'unit',
1903
+ 'integration',
1904
+ 'e2e',
1905
+ 'coverage',
1906
+ 'snapshot',
1907
+ 'mocking'
1908
+ ])).optional().default([])
1909
+ }).passthrough();
1910
+ const SecurityPluginSchema = import_zod.z.object({
1911
+ enabled: import_zod.z.boolean().optional().default(true),
1912
+ provider: import_zod.z.string().optional(),
1913
+ features: import_zod.z.array(import_zod.z.enum([
1914
+ 'input_validation',
1915
+ 'rate_limiting',
1916
+ 'csrf',
1917
+ 'xss',
1918
+ 'sql_injection',
1919
+ 'audit',
1920
+ 'rbac',
1921
+ 'encryption',
1922
+ 'secrets_management'
1923
+ ])).optional().default([])
1924
+ }).passthrough();
1925
+ const AIPluginSchema = import_zod.z.object({
1926
+ enabled: import_zod.z.boolean().optional().default(false),
1927
+ provider: import_zod.z.enum(['anthropic', 'openai', 'google', 'cohere', 'custom']).optional(),
1928
+ features: import_zod.z.array(import_zod.z.enum([
1929
+ 'streaming',
1930
+ 'tool_use',
1931
+ 'embeddings',
1932
+ 'rag',
1933
+ 'agents',
1934
+ 'vision'
1935
+ ])).optional().default([])
1936
+ }).passthrough();
1937
+ const EmailPluginSchema = import_zod.z.object({
1938
+ enabled: import_zod.z.boolean().optional().default(false),
1939
+ provider: import_zod.z.enum(['resend', 'sendgrid', 'postmark', 'ses', 'mailgun', 'custom']).optional(),
1940
+ features: import_zod.z.array(import_zod.z.enum([
1941
+ 'transactional',
1942
+ 'marketing',
1943
+ 'templates',
1944
+ 'tracking'
1945
+ ])).optional().default([])
1946
+ }).passthrough();
1947
+ const AnalyticsPluginSchema = import_zod.z.object({
1948
+ enabled: import_zod.z.boolean().optional().default(false),
1949
+ provider: import_zod.z.enum(['posthog', 'amplitude', 'mixpanel', 'google_analytics', 'custom']).optional(),
1950
+ features: import_zod.z.array(import_zod.z.enum([
1951
+ 'page_views',
1952
+ 'events',
1953
+ 'user_tracking',
1954
+ 'funnels',
1955
+ 'experiments'
1956
+ ])).optional().default([])
1957
+ }).passthrough();
1958
+ const MonitoringPluginSchema = import_zod.z.object({
1959
+ enabled: import_zod.z.boolean().optional().default(false),
1960
+ provider: import_zod.z.enum(['sentry', 'datadog', 'newrelic', 'logrocket', 'custom']).optional(),
1961
+ features: import_zod.z.array(import_zod.z.enum([
1962
+ 'error_tracking',
1963
+ 'performance',
1964
+ 'logs',
1965
+ 'alerts',
1966
+ 'apm'
1967
+ ])).optional().default([])
1968
+ }).passthrough();
1969
+ const PluginsSchema = import_zod.z.object({
1970
+ auth: AuthPluginSchema.optional(),
1971
+ payments: PaymentsPluginSchema.optional(),
1972
+ database: DatabasePluginSchema.optional(),
1973
+ testing: TestingPluginSchema.optional(),
1974
+ security: SecurityPluginSchema.optional(),
1975
+ ai: AIPluginSchema.optional(),
1976
+ email: EmailPluginSchema.optional(),
1977
+ analytics: AnalyticsPluginSchema.optional(),
1978
+ monitoring: MonitoringPluginSchema.optional()
1979
+ }).catchall(BasePluginSchema);
1980
+ const AgentConfigSchema = import_zod.z.object({
1981
+ enabled: import_zod.z.boolean().optional().default(true),
1982
+ expertise: import_zod.z.array(import_zod.z.string()).optional(),
1983
+ customInstructions: import_zod.z.string().optional(),
1984
+ priority: import_zod.z.enum(['high', 'medium', 'low']).optional().default('medium')
1985
+ }).passthrough();
1986
+ const AgentsConfigSchema = import_zod.z.object({
1987
+ enabled: import_zod.z.record(import_zod.z.string(), import_zod.z.boolean()).optional(),
1988
+ custom: import_zod.z.record(import_zod.z.string(), AgentConfigSchema).optional(),
1989
+ defaults: import_zod.z.array(import_zod.z.string()).optional(),
1990
+ settings: import_zod.z.object({
1991
+ maxConcurrent: import_zod.z.number().int().min(1).max(10).optional().default(3),
1992
+ timeout: import_zod.z.number().int().min(1e3).max(3e5).optional().default(6e4),
1993
+ verbose: import_zod.z.boolean().optional().default(false)
1994
+ }).optional()
1995
+ }).passthrough();
1996
+ const SkillConfigSchema = import_zod.z.object({
1997
+ enabled: import_zod.z.boolean().optional().default(true),
1998
+ tier: import_zod.z.enum(['free', 'pro', 'premium']).optional().default('free'),
1999
+ category: import_zod.z.string().optional(),
2000
+ maxChars: import_zod.z.number().int().min(100).optional()
2001
+ }).passthrough();
2002
+ const SkillsConfigSchema = import_zod.z.object({
2003
+ categories: import_zod.z.record(import_zod.z.string(), import_zod.z.boolean()).optional(),
2004
+ custom: import_zod.z.record(import_zod.z.string(), SkillConfigSchema).optional(),
2005
+ external: import_zod.z.object({
2006
+ enabled: import_zod.z.boolean().optional().default(false),
2007
+ manifestUrl: import_zod.z.string().url().optional(),
2008
+ cacheDir: import_zod.z.string().optional(),
2009
+ requireSignature: import_zod.z.boolean().optional().default(false)
2010
+ }).optional(),
2011
+ settings: import_zod.z.object({
2012
+ defaultMaxChars: import_zod.z.number().int().min(100).optional().default(5e4),
2013
+ summaryByDefault: import_zod.z.boolean().optional().default(false),
2014
+ includeExternal: import_zod.z.boolean().optional().default(false)
2015
+ }).optional()
2016
+ }).passthrough();
2017
+ const WorkflowPhaseSchema = import_zod.z.object({
2018
+ name: import_zod.z.string().min(1, 'Phase name is required'),
2019
+ agents: import_zod.z.array(import_zod.z.string()).min(1, 'At least one agent is required'),
2020
+ duration: import_zod.z.string().optional(),
2021
+ parallel: import_zod.z.boolean().optional().default(false),
2022
+ description: import_zod.z.string().optional()
2023
+ }).passthrough();
2024
+ const WorkflowConfigSchema = import_zod.z.object({
2025
+ name: import_zod.z.string().min(1, 'Workflow name is required'),
2026
+ description: import_zod.z.string().optional(),
2027
+ tier: import_zod.z.enum(['free', 'pro']).optional().default('free'),
2028
+ pack: import_zod.z.string().optional(),
2029
+ outcomes: import_zod.z.array(import_zod.z.string()).optional(),
2030
+ completionSignals: import_zod.z.array(import_zod.z.string()).optional(),
2031
+ phases: import_zod.z.array(WorkflowPhaseSchema).min(1, 'At least one phase is required')
2032
+ }).passthrough();
2033
+ const WorkflowsConfigSchema = import_zod.z.object({
2034
+ enabled: import_zod.z.record(import_zod.z.string(), import_zod.z.boolean()).optional(),
2035
+ custom: import_zod.z.record(import_zod.z.string(), WorkflowConfigSchema).optional(),
2036
+ default: import_zod.z.string().optional(),
2037
+ settings: import_zod.z.object({
2038
+ autoAdvance: import_zod.z.boolean().optional().default(true),
2039
+ pauseBetweenPhases: import_zod.z.boolean().optional().default(false),
2040
+ trackSignals: import_zod.z.boolean().optional().default(true),
2041
+ emitTelemetry: import_zod.z.boolean().optional().default(true)
2042
+ }).optional()
2043
+ }).passthrough();
2044
+ const QualityCheckSchema = import_zod.z.union([
2045
+ import_zod.z.boolean(),
2046
+ import_zod.z.object({
2047
+ enabled: import_zod.z.boolean().optional().default(true),
2048
+ checks: import_zod.z.array(import_zod.z.enum([
2049
+ 'lint',
2050
+ 'typecheck',
2051
+ 'test',
2052
+ 'build',
2053
+ 'security',
2054
+ 'coverage',
2055
+ 'format'
2056
+ ])).optional()
2057
+ })
2058
+ ]);
2059
+ const QualityConfigSchema = import_zod.z.object({
2060
+ preCommit: QualityCheckSchema.optional().default(true),
2061
+ prePush: QualityCheckSchema.optional().default(false),
2062
+ preDeploy: QualityCheckSchema.optional(),
2063
+ strictMode: import_zod.z.boolean().optional().default(false),
2064
+ coverage: import_zod.z.object({
2065
+ statements: import_zod.z.number().min(0).max(100).optional(),
2066
+ branches: import_zod.z.number().min(0).max(100).optional(),
2067
+ functions: import_zod.z.number().min(0).max(100).optional(),
2068
+ lines: import_zod.z.number().min(0).max(100).optional()
2069
+ }).optional()
2070
+ }).passthrough();
2071
+ const ContextConfigSchema = import_zod.z.object({
2072
+ includeEnvVars: import_zod.z.boolean().optional().default(true),
2073
+ includeTechStack: import_zod.z.boolean().optional().default(true),
2074
+ includePlugins: import_zod.z.boolean().optional().default(true),
2075
+ includeGitInfo: import_zod.z.boolean().optional().default(true),
2076
+ includeTodos: import_zod.z.boolean().optional().default(true),
2077
+ includeLearnings: import_zod.z.boolean().optional().default(true),
2078
+ customSections: import_zod.z.array(import_zod.z.object({
2079
+ title: import_zod.z.string(),
2080
+ content: import_zod.z.string()
2081
+ })).optional().default([]),
2082
+ maxSize: import_zod.z.number().int().min(1e3).optional()
2083
+ }).passthrough();
2084
+ const PathsConfigSchema = import_zod.z.object({
2085
+ context: import_zod.z.string().optional().default('CLAUDE.md'),
2086
+ config: import_zod.z.string().optional().default('bootspring.config.js'),
2087
+ todo: import_zod.z.string().optional().default('todo.md'),
2088
+ roadmap: import_zod.z.string().optional().default('ROADMAP.md'),
2089
+ changelog: import_zod.z.string().optional().default('CHANGELOG.md'),
2090
+ state: import_zod.z.string().optional().default('.bootspring')
2091
+ }).passthrough();
2092
+ const DashboardConfigSchema = import_zod.z.object({
2093
+ port: import_zod.z.number().int().min(1024).max(65535).optional().default(3456),
2094
+ autoOpen: import_zod.z.boolean().optional().default(false),
2095
+ host: import_zod.z.string().optional().default('localhost'),
2096
+ theme: import_zod.z.enum(['light', 'dark', 'system']).optional().default('system')
2097
+ }).passthrough();
2098
+ const ProjectConfigSchema = import_zod.z.object({
2099
+ name: import_zod.z.string().min(1, 'Project name is required'),
2100
+ description: import_zod.z.string().optional().default(''),
2101
+ version: import_zod.z.string().regex(/^\d+\.\d+\.\d+/, 'Version must be semver format').optional().default('1.0.0'),
2102
+ author: import_zod.z.string().optional(),
2103
+ license: import_zod.z.string().optional(),
2104
+ repository: import_zod.z.string().url().optional()
2105
+ }).passthrough();
2106
+ const StackConfigSchema = import_zod.z.object({
2107
+ framework: import_zod.z.enum([
2108
+ 'nextjs',
2109
+ 'remix',
2110
+ 'nuxt',
2111
+ 'sveltekit',
2112
+ 'astro',
2113
+ 'express',
2114
+ 'fastify',
2115
+ 'hono',
2116
+ 'custom'
2117
+ ]).optional(),
2118
+ language: import_zod.z.enum(['typescript', 'javascript']).optional(),
2119
+ database: import_zod.z.enum(['postgresql', 'mysql', 'mongodb', 'sqlite', 'supabase', 'planetscale', 'none']).optional(),
2120
+ hosting: import_zod.z.enum([
2121
+ 'vercel',
2122
+ 'railway',
2123
+ 'render',
2124
+ 'fly',
2125
+ 'aws',
2126
+ 'gcp',
2127
+ 'azure',
2128
+ 'cloudflare',
2129
+ 'self-hosted',
2130
+ 'custom'
2131
+ ]).optional()
2132
+ }).passthrough();
2133
+ const ConfigSchema = import_zod.z.object({
2134
+ project: ProjectConfigSchema.optional(),
2135
+ stack: StackConfigSchema.optional(),
2136
+ plugins: PluginsSchema.optional(),
2137
+ agents: AgentsConfigSchema.optional(),
2138
+ skills: SkillsConfigSchema.optional(),
2139
+ workflows: WorkflowsConfigSchema.optional(),
2140
+ dashboard: DashboardConfigSchema.optional(),
2141
+ quality: QualityConfigSchema.optional(),
2142
+ context: ContextConfigSchema.optional(),
2143
+ paths: PathsConfigSchema.optional(),
2144
+ mcp: import_zod.z.object({
2145
+ enabled: import_zod.z.boolean().optional().default(false),
2146
+ servers: import_zod.z.record(import_zod.z.string(), import_zod.z.object({
2147
+ command: import_zod.z.string(),
2148
+ args: import_zod.z.array(import_zod.z.string()).optional(),
2149
+ env: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional()
2150
+ })).optional()
2151
+ }).optional()
2152
+ }).passthrough();
2153
+ function formatValidationErrors(zodError) {
2154
+ return zodError.issues.map((issue) => {
2155
+ const path9 = issue.path.join('.');
2156
+ const prefix = path9 ? `${path9}: ` : '';
2157
+ switch (issue.code) {
2158
+ case 'invalid_type':
2159
+ return `${prefix}Expected ${issue.expected}, received ${issue.received}`;
2160
+ case 'invalid_enum_value':
2161
+ return `${prefix}Invalid value "${issue.received}". Expected one of: ${issue.options.join(', ')}`;
2162
+ case 'too_small':
2163
+ if (issue.type === 'string') {
2164
+ return `${prefix}String must contain at least ${issue.minimum} character(s)`;
2165
+ }
2166
+ if (issue.type === 'array') {
2167
+ return `${prefix}Array must contain at least ${issue.minimum} element(s)`;
2168
+ }
2169
+ return `${prefix}Value must be greater than or equal to ${issue.minimum}`;
2170
+ case 'too_big':
2171
+ return `${prefix}Value must be less than or equal to ${issue.maximum}`;
2172
+ case 'invalid_string':
2173
+ if (issue.validation === 'url') {
2174
+ return `${prefix}Invalid URL format`;
2175
+ }
2176
+ if (issue.validation === 'regex') {
2177
+ return `${prefix}Invalid format`;
2178
+ }
2179
+ return `${prefix}${issue.message}`;
2180
+ default:
2181
+ return `${prefix}${issue.message}`;
2182
+ }
2183
+ });
2184
+ }
2185
+ const sectionSchemas = {
2186
+ project: ProjectConfigSchema,
2187
+ stack: StackConfigSchema,
2188
+ plugins: PluginsSchema,
2189
+ agents: AgentsConfigSchema,
2190
+ skills: SkillsConfigSchema,
2191
+ workflows: WorkflowsConfigSchema,
2192
+ dashboard: DashboardConfigSchema,
2193
+ quality: QualityConfigSchema,
2194
+ context: ContextConfigSchema,
2195
+ paths: PathsConfigSchema
2196
+ };
2197
+ function validateSection(section, data) {
2198
+ const schema = sectionSchemas[section];
2199
+ if (!schema) {
2200
+ return {
2201
+ valid: false,
2202
+ errors: [`Unknown configuration section: ${section}`],
2203
+ data: null
2204
+ };
2205
+ }
2206
+ const result = schema.safeParse(data);
2207
+ if (result.success) {
2208
+ return {
2209
+ valid: true,
2210
+ errors: [],
2211
+ data: result.data
2212
+ };
2213
+ }
2214
+ return {
2215
+ valid: false,
2216
+ errors: formatValidationErrors(result.error),
2217
+ data: null
2218
+ };
2219
+ }
2220
+ const DEFAULT_CONFIG = {
2221
+ project: {
2222
+ name: 'My Project',
2223
+ description: '',
2224
+ version: '1.0.0'
2225
+ },
2226
+ stack: {
2227
+ framework: 'nextjs',
2228
+ language: 'typescript',
2229
+ database: 'postgresql',
2230
+ hosting: 'vercel'
2231
+ },
2232
+ plugins: {
2233
+ auth: { enabled: false, provider: 'clerk' },
2234
+ payments: { enabled: false, provider: 'stripe' },
2235
+ database: { enabled: true, provider: 'prisma' },
2236
+ testing: { enabled: true, provider: 'vitest' },
2237
+ security: { enabled: true },
2238
+ ai: { enabled: false }
2239
+ },
2240
+ dashboard: {
2241
+ port: 3456,
2242
+ autoOpen: false
2243
+ },
2244
+ quality: {
2245
+ preCommit: true,
2246
+ prePush: false,
2247
+ strictMode: false
2248
+ },
2249
+ paths: {
2250
+ context: 'CLAUDE.md',
2251
+ config: 'bootspring.config.js',
2252
+ todo: 'todo.md'
2253
+ }
2254
+ };
2255
+ const CONFIG_FILES = [
2256
+ 'bootspring.config.js',
2257
+ 'bootspring.config.mjs',
2258
+ 'bootspring.config.json',
2259
+ '.bootspringrc',
2260
+ '.bootspringrc.js',
2261
+ '.bootspringrc.json'
2262
+ ];
2263
+ function deepMerge(target, source) {
2264
+ const result = { ...target };
2265
+ for (const key of Object.keys(source)) {
2266
+ const sourceValue = source[key];
2267
+ const targetValue = target[key];
2268
+ if (Array.isArray(sourceValue)) {
2269
+ result[key] = [...sourceValue];
2270
+ } else if (sourceValue !== null && typeof sourceValue === 'object' && !Array.isArray(sourceValue) && targetValue !== null && typeof targetValue === 'object' && !Array.isArray(targetValue)) {
2271
+ result[key] = deepMerge(
2272
+ targetValue,
2273
+ sourceValue
2274
+ );
2275
+ } else if (sourceValue !== void 0) {
2276
+ result[key] = sourceValue;
2277
+ }
2278
+ }
2279
+ return result;
2280
+ }
2281
+ function findProjectRoot() {
2282
+ let dir = process.cwd();
2283
+ const root = path5.parse(dir).root;
2284
+ while (dir !== root) {
2285
+ if (fs5.existsSync(path5.join(dir, 'package.json')) || fs5.existsSync(path5.join(dir, 'bootspring.config.js')) || fs5.existsSync(path5.join(dir, '.git'))) {
2286
+ return dir;
2287
+ }
2288
+ dir = path5.dirname(dir);
2289
+ }
2290
+ return process.cwd();
2291
+ }
2292
+ function findConfigFile(projectRoot) {
2293
+ for (const filename of CONFIG_FILES) {
2294
+ const filepath = path5.join(projectRoot, filename);
2295
+ if (fs5.existsSync(filepath)) {
2296
+ return filepath;
2297
+ }
2298
+ }
2299
+ return null;
2300
+ }
2301
+ function load(projectRoot = null) {
2302
+ const root = projectRoot ?? findProjectRoot();
2303
+ const configPath = findConfigFile(root);
2304
+ let userConfig = {};
2305
+ if (configPath) {
2306
+ try {
2307
+ if (configPath.endsWith('.json') || configPath.endsWith('.bootspringrc')) {
2308
+ const content = fs5.readFileSync(configPath, 'utf-8');
2309
+ userConfig = JSON.parse(content);
2310
+ } else {
2311
+ delete require.cache[require.resolve(configPath)];
2312
+ userConfig = require(configPath);
2313
+ }
2314
+ } catch (error) {
2315
+ const message = error instanceof Error ? error.message : String(error);
2316
+ console.warn(`Warning: Could not load config from ${configPath}: ${message}`);
2317
+ }
2318
+ }
2319
+ const config = deepMerge(DEFAULT_CONFIG, userConfig);
2320
+ config._projectRoot = root;
2321
+ config._configPath = configPath;
2322
+ config._bootspringDir = path5.join(root, '.bootspring');
2323
+ return config;
2324
+ }
2325
+ function validate(config, options = {}) {
2326
+ const configToValidate = { ...config };
2327
+ delete configToValidate._projectRoot;
2328
+ delete configToValidate._configPath;
2329
+ delete configToValidate._bootspringDir;
2330
+ delete configToValidate._validation;
2331
+ const errors = [];
2332
+ const warnings = [];
2333
+ if (options.sections && options.sections.length > 0) {
2334
+ let allValid = true;
2335
+ const validatedData = {};
2336
+ for (const section of options.sections) {
2337
+ const sectionValue = configToValidate[section];
2338
+ if (sectionValue !== void 0) {
2339
+ const result2 = validateSection(section, sectionValue);
2340
+ if (!result2.valid) {
2341
+ allValid = false;
2342
+ errors.push(...result2.errors);
2343
+ } else {
2344
+ validatedData[section] = result2.data;
2345
+ }
2346
+ }
2347
+ }
2348
+ return {
2349
+ valid: allValid,
2350
+ errors,
2351
+ warnings,
2352
+ data: allValid ? validatedData : null
2353
+ };
2354
+ }
2355
+ const result = ConfigSchema.safeParse(configToValidate);
2356
+ if (result.success) {
2357
+ return {
2358
+ valid: true,
2359
+ errors: [],
2360
+ warnings,
2361
+ data: result.data
2362
+ };
2363
+ }
2364
+ return {
2365
+ valid: false,
2366
+ errors: formatValidationErrors(result.error),
2367
+ warnings,
2368
+ data: null
2369
+ };
2370
+ }
2371
+
2372
+ // src/core/context.ts
2373
+ init_cjs_shims();
2374
+ const path6 = __toESM(require('path'));
2375
+ function get(options = {}) {
2376
+ const cfg = options.config ?? load();
2377
+ const projectRoot = cfg._projectRoot ?? process.cwd();
2378
+ const context = {
2379
+ project: cfg.project,
2380
+ stack: cfg.stack,
2381
+ plugins: getEnabledPlugins(cfg),
2382
+ files: getProjectFiles(projectRoot),
2383
+ git: getGitInfo(projectRoot),
2384
+ state: getProjectState(projectRoot, cfg),
2385
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2386
+ };
2387
+ return context;
2388
+ }
2389
+ function getEnabledPlugins(cfg) {
2390
+ const enabled = {};
2391
+ const plugins = cfg.plugins ?? {};
2392
+ for (const [name, plugin] of Object.entries(plugins)) {
2393
+ if (plugin && plugin.enabled !== false) {
2394
+ enabled[name] = {
2395
+ provider: plugin.provider ?? 'default',
2396
+ features: plugin.features ?? []
2397
+ };
2398
+ }
2399
+ }
2400
+ return enabled;
2401
+ }
2402
+ function getProjectFiles(projectRoot) {
2403
+ const files = {
2404
+ hasPackageJson: fileExists(path6.join(projectRoot, 'package.json')),
2405
+ hasTsConfig: fileExists(path6.join(projectRoot, 'tsconfig.json')),
2406
+ hasClaudeMd: fileExists(path6.join(projectRoot, 'CLAUDE.md')),
2407
+ hasBootspringConfig: fileExists(path6.join(projectRoot, 'bootspring.config.js')),
2408
+ hasTodoMd: fileExists(path6.join(projectRoot, 'todo.md')),
2409
+ hasGit: fileExists(path6.join(projectRoot, '.git')),
2410
+ hasSrcDir: fileExists(path6.join(projectRoot, 'src')),
2411
+ hasAppDir: fileExists(path6.join(projectRoot, 'app')),
2412
+ hasPagesDir: fileExists(path6.join(projectRoot, 'pages')),
2413
+ structure: 'flat'
2414
+ };
2415
+ if (files.hasAppDir) {
2416
+ files.structure = 'app-router';
2417
+ } else if (files.hasPagesDir) {
2418
+ files.structure = 'pages-router';
2419
+ } else if (files.hasSrcDir) {
2420
+ files.structure = 'src-based';
2421
+ }
2422
+ return files;
2423
+ }
2424
+ function getGitInfo(projectRoot) {
2425
+ const gitDir = path6.join(projectRoot, '.git');
2426
+ if (!fileExists(gitDir)) {
2427
+ return { initialized: false };
2428
+ }
2429
+ const info = { initialized: true };
2430
+ const headPath = path6.join(gitDir, 'HEAD');
2431
+ if (fileExists(headPath)) {
2432
+ const head = readFile(headPath);
2433
+ if (head) {
2434
+ const trimmedHead = head.trim();
2435
+ if (trimmedHead.startsWith('ref: refs/heads/')) {
2436
+ info.branch = trimmedHead.replace('ref: refs/heads/', '');
2437
+ }
2438
+ }
2439
+ }
2440
+ const configPath = path6.join(gitDir, 'config');
2441
+ if (fileExists(configPath)) {
2442
+ const gitConfig = readFile(configPath);
2443
+ if (gitConfig) {
2444
+ info.hasRemote = gitConfig.includes('[remote "origin"]');
2445
+ }
2446
+ }
2447
+ return info;
2448
+ }
2449
+ function getProjectState(projectRoot, cfg) {
2450
+ const state = {
2451
+ phase: 'unknown',
2452
+ health: 'unknown',
2453
+ todos: 0,
2454
+ lastGenerated: null
2455
+ };
2456
+ const todoPath = path6.join(projectRoot, cfg.paths?.todo ?? 'todo.md');
2457
+ if (fileExists(todoPath)) {
2458
+ const content = readFile(todoPath);
2459
+ if (content) {
2460
+ const todoMatches = content.match(/- \[ \]/g);
2461
+ state.todos = todoMatches ? todoMatches.length : 0;
2462
+ }
2463
+ }
2464
+ const claudePath = path6.join(projectRoot, cfg.paths?.context ?? 'CLAUDE.md');
2465
+ if (fileExists(claudePath)) {
2466
+ state.lastGenerated = getFileTime(claudePath);
2467
+ }
2468
+ const loadedCfg = cfg;
2469
+ if (!loadedCfg._configPath) {
2470
+ state.phase = 'uninitialized';
2471
+ } else if (!state.lastGenerated) {
2472
+ state.phase = 'initialized';
2473
+ } else {
2474
+ state.phase = 'active';
2475
+ }
2476
+ const issues = [];
2477
+ if (!fileExists(path6.join(projectRoot, 'package.json'))) {
2478
+ issues.push('missing-package-json');
2479
+ }
2480
+ if (!loadedCfg._configPath) {
2481
+ issues.push('missing-config');
2482
+ }
2483
+ if (!state.lastGenerated) {
2484
+ issues.push('missing-context');
2485
+ }
2486
+ if (issues.length === 0) {
2487
+ state.health = 'good';
2488
+ } else if (issues.length <= 2) {
2489
+ state.health = 'fair';
2490
+ } else {
2491
+ state.health = 'needs-attention';
2492
+ }
2493
+ state.issues = issues;
2494
+ return state;
2495
+ }
2496
+ function validate2(options = {}) {
2497
+ const cfg = options.config ?? load();
2498
+ const projectRoot = cfg._projectRoot ?? process.cwd();
2499
+ const checks = [];
2500
+ let score = 0;
2501
+ const maxScore = 10;
2502
+ if (cfg._configPath) {
2503
+ checks.push({ name: 'Configuration', status: 'pass', message: 'bootspring.config.js found' });
2504
+ score += 2;
2505
+ } else {
2506
+ checks.push({ name: 'Configuration', status: 'fail', message: 'bootspring.config.js missing' });
2507
+ }
2508
+ const claudePath = path6.join(projectRoot, cfg.paths?.context ?? 'CLAUDE.md');
2509
+ if (fileExists(claudePath)) {
2510
+ checks.push({ name: 'AI Context', status: 'pass', message: 'CLAUDE.md exists' });
2511
+ score += 2;
2512
+ } else {
2513
+ checks.push({ name: 'AI Context', status: 'fail', message: 'CLAUDE.md missing - run bootspring generate' });
2514
+ }
2515
+ if (fileExists(path6.join(projectRoot, 'package.json'))) {
2516
+ checks.push({ name: 'Package', status: 'pass', message: 'package.json found' });
2517
+ score += 1;
2518
+ } else {
2519
+ checks.push({ name: 'Package', status: 'warn', message: 'package.json missing' });
2520
+ }
2521
+ if (fileExists(path6.join(projectRoot, '.git'))) {
2522
+ checks.push({ name: 'Git', status: 'pass', message: 'Git repository initialized' });
2523
+ score += 1;
2524
+ } else {
2525
+ checks.push({ name: 'Git', status: 'warn', message: 'Git not initialized' });
2526
+ }
2527
+ if (cfg.stack?.language === 'typescript') {
2528
+ if (fileExists(path6.join(projectRoot, 'tsconfig.json'))) {
2529
+ checks.push({ name: 'TypeScript', status: 'pass', message: 'tsconfig.json found' });
2530
+ score += 1;
2531
+ } else {
2532
+ checks.push({ name: 'TypeScript', status: 'fail', message: 'tsconfig.json missing for TypeScript project' });
2533
+ }
2534
+ } else {
2535
+ score += 1;
2536
+ }
2537
+ const configValidation = validate(cfg);
2538
+ if (configValidation.valid) {
2539
+ checks.push({ name: 'Config Validation', status: 'pass', message: 'Configuration is valid' });
2540
+ score += 2;
2541
+ } else {
2542
+ checks.push({ name: 'Config Validation', status: 'fail', message: configValidation.errors.join(', ') });
2543
+ }
2544
+ if (fileExists(path6.join(projectRoot, cfg.paths?.todo ?? 'todo.md'))) {
2545
+ checks.push({ name: 'Todo Tracking', status: 'pass', message: 'todo.md exists' });
2546
+ score += 1;
2547
+ } else {
2548
+ checks.push({ name: 'Todo Tracking', status: 'fail', message: 'todo.md not found' });
2549
+ }
2550
+ return {
2551
+ valid: score >= maxScore * 0.6,
2552
+ score,
2553
+ maxScore,
2554
+ percentage: Math.round(score / maxScore * 100),
2555
+ checks
2556
+ };
2557
+ }
2558
+
2559
+ // src/cli/generate.ts
2560
+ function generateClaudeMd(cfg, ctx) {
2561
+ const date = formatDate();
2562
+ const sections = [];
2563
+ sections.push(`# ${cfg.project?.name ?? 'Project'} - AI Context
2564
+
2565
+ **Generated by**: Bootspring v1.0.0
2566
+ **Last Updated**: ${date}
2567
+ **Status**: ${ctx.state.phase}
2568
+ **Health**: ${ctx.state.health}
2569
+
2570
+ ---`);
2571
+ sections.push(`## Project Overview
2572
+
2573
+ ${cfg.project?.description ?? 'A modern web application built with Bootspring.'}
2574
+
2575
+ ---`);
2576
+ sections.push(`## Tech Stack
2577
+
2578
+ | Component | Technology |
2579
+ |-----------|------------|
2580
+ | Framework | ${cfg.stack?.framework ?? 'unknown'} |
2581
+ | Language | ${cfg.stack?.language ?? 'unknown'} |
2582
+ | Database | ${cfg.stack?.database ?? 'unknown'} |
2583
+ | Hosting | ${cfg.stack?.hosting ?? 'unknown'} |
2584
+
2585
+ ---`);
2586
+ const plugins = cfg.plugins ?? {};
2587
+ const enabledPlugins = Object.entries(plugins).filter(([_name, p]) => p && p.enabled !== false);
2588
+ if (enabledPlugins.length > 0) {
2589
+ const pluginSections = enabledPlugins.map(([name, plugin]) => {
2590
+ const pluginData = plugin;
2591
+ return `### ${name.charAt(0).toUpperCase() + name.slice(1)}
2592
+ - **Provider**: ${pluginData.provider ?? 'default'}
2593
+ - **Features**: ${(pluginData.features ?? []).join(', ') || 'default'}`;
2594
+ });
2595
+ sections.push(`## Enabled Plugins
2596
+
2597
+ ${pluginSections.join('\n\n')}
2598
+
2599
+ ---`);
2600
+ }
2601
+ sections.push(`## Bootspring Commands
2602
+
2603
+ Quick commands for your AI assistant:
2604
+
2605
+ ### Todo Management
2606
+ \`\`\`bash
2607
+ bootspring todo add "task" # Add a new todo
2608
+ bootspring todo list # List all todos
2609
+ bootspring todo done <id> # Mark as complete
2610
+ bootspring todo clear # Clear completed
2611
+ \`\`\`
2612
+
2613
+ ### Agents & Skills
2614
+ \`\`\`bash
2615
+ bootspring agent list # List available agents
2616
+ bootspring agent invoke <n> # Get specialized help
2617
+ bootspring skill search <q> # Find code patterns
2618
+ bootspring skill show <name> # View a skill
2619
+ \`\`\`
2620
+
2621
+ ### Project Tools
2622
+ \`\`\`bash
2623
+ bootspring dashboard # Start real-time dashboard
2624
+ bootspring generate # Regenerate this context
2625
+ bootspring quality pre-commit # Run quality checks
2626
+ bootspring context validate # Validate project setup
2627
+ \`\`\`
2628
+
2629
+ ---`);
2630
+ const issuesText = ctx.state.issues && ctx.state.issues.length > 0 ? `
2631
+ - **Issues**: ${ctx.state.issues.join(', ')}` : '';
2632
+ sections.push(`## Current State
2633
+
2634
+ - **Phase**: ${ctx.state.phase}
2635
+ - **Health**: ${ctx.state.health}
2636
+ - **Open Todos**: ${ctx.state.todos}${issuesText}
2637
+
2638
+ ---`);
2639
+ if (ctx.git.initialized) {
2640
+ sections.push(`## Git Repository
2641
+
2642
+ - **Branch**: ${ctx.git.branch ?? 'unknown'}
2643
+ - **Remote**: ${ctx.git.hasRemote ? 'configured' : 'not configured'}
2644
+
2645
+ ---`);
2646
+ try {
2647
+ const gitMemory = require_git_memory();
2648
+ const loadedCfg = cfg;
2649
+ const { learnings } = gitMemory.extractLearnings({
2650
+ limit: 30,
2651
+ since: '2 months ago',
2652
+ cwd: loadedCfg._projectRoot ?? process.cwd()
2653
+ });
2654
+ if (learnings && learnings.length > 0) {
2655
+ const learningsSection = gitMemory.toCompactSummary(learnings, { maxItems: 12 });
2656
+ sections.push(learningsSection + '\n---');
2657
+ }
2658
+ } catch {
2659
+ }
2660
+ }
2661
+ sections.push(`## Project Structure
2662
+
2663
+ - **Structure Type**: ${ctx.files.structure}
2664
+ - **Has TypeScript**: ${ctx.files.hasTsConfig ? 'yes' : 'no'}
2665
+ - **Has Package.json**: ${ctx.files.hasPackageJson ? 'yes' : 'no'}
2666
+
2667
+ ---`);
2668
+ const language = cfg.stack?.language === 'typescript' ? 'TypeScript' : 'JavaScript';
2669
+ sections.push(`## Development Guidelines
2670
+
2671
+ ### Code Style
2672
+ - Use ${language} for all new code
2673
+ - Follow existing naming conventions in the codebase
2674
+ - Keep files focused and under 300 lines when possible
2675
+
2676
+ ### Best Practices
2677
+ - Use Server Components by default (if Next.js)
2678
+ - Prefer Server Actions over API routes for mutations
2679
+ - Use Zod for all input validation
2680
+ - Never expose API keys to client-side code
2681
+ - Write tests for new features
2682
+
2683
+ ### Git Commits
2684
+ - Use conventional commit format: \`feat:\`, \`fix:\`, \`docs:\`, \`refactor:\`
2685
+ - Keep commits focused and atomic
2686
+ - **Never add Co-Authored-By or AI attribution to commits**
2687
+ - Never commit sensitive data or API keys
2688
+
2689
+ ---`);
2690
+ if (cfg.customInstructions) {
2691
+ sections.push(`## Custom Instructions
2692
+
2693
+ ${cfg.customInstructions}
2694
+
2695
+ ---`);
2696
+ }
2697
+ sections.push(`---
2698
+
2699
+ *Generated by [Bootspring](https://bootspring.com) - Development scaffolding with intelligence*
2700
+ `);
2701
+ return sections.join('\n\n');
2702
+ }
2703
+ async function run3(args) {
2704
+ const parsedArgs = parseArgs(args);
2705
+ const full = parsedArgs.full || parsedArgs.f;
2706
+ console.log(`
2707
+ ${COLORS.cyan}${COLORS.bold}\u26A1 Bootspring Generate${COLORS.reset}
2708
+ ${COLORS.dim}Regenerating AI context...${COLORS.reset}
2709
+ `);
2710
+ const cfg = load();
2711
+ if (!cfg._configPath) {
2712
+ print.error('No bootspring.config.js found');
2713
+ print.dim('Run "bootspring init" first to initialize your project');
2714
+ return;
2715
+ }
2716
+ const ctx = get({ config: cfg });
2717
+ const claudePath = path7.join(cfg._projectRoot ?? process.cwd(), cfg.paths?.context ?? 'CLAUDE.md');
2718
+ const spinner = createSpinner('Generating CLAUDE.md').start();
2719
+ try {
2720
+ const content = generateClaudeMd(cfg, ctx);
2721
+ writeFile(claudePath, content);
2722
+ spinner.succeed('Generated CLAUDE.md');
2723
+ } catch (error) {
2724
+ spinner.fail(`Failed to generate CLAUDE.md: ${error instanceof Error ? error.message : String(error)}`);
2725
+ return;
2726
+ }
2727
+ const todoPath = path7.join(cfg._projectRoot ?? process.cwd(), cfg.paths?.todo ?? 'todo.md');
2728
+ if (!fileExists(todoPath)) {
2729
+ const todoSpinner = createSpinner('Generating todo.md').start();
2730
+ const todoContent = `# ${cfg.project?.name ?? 'Project'} - Todo List
2731
+
2732
+ > Last updated: ${formatDate()}
2733
+
2734
+ ## In Progress
2735
+
2736
+ ## Pending
2737
+
2738
+ - [ ] Review generated context
2739
+ - [ ] Configure environment variables
2740
+
2741
+ ## Completed
2742
+
2743
+ `;
2744
+ writeFile(todoPath, todoContent);
2745
+ todoSpinner.succeed('Generated todo.md');
2746
+ }
2747
+ const validation = validate2({ config: cfg });
2748
+ console.log(`
2749
+ ${COLORS.bold}Context Validation${COLORS.reset}
2750
+ `);
2751
+ for (const check of validation.checks) {
2752
+ const icon = check.status === 'pass' ? COLORS.green + '\u2713' : check.status === 'fail' ? COLORS.red + '\u2717' : check.status === 'warn' ? COLORS.yellow + '\u26A0' : COLORS.dim + '\u2139';
2753
+ console.log(` ${icon}${COLORS.reset} ${check.name}: ${COLORS.dim}${check.message}${COLORS.reset}`);
2754
+ }
2755
+ console.log(`
2756
+ ${COLORS.bold}Score:${COLORS.reset} ${validation.score}/${validation.maxScore} (${validation.percentage}%)
2757
+ `);
2758
+ if (validation.valid) {
2759
+ print.success('Context is valid and ready for AI assistants');
2760
+ } else {
2761
+ print.warning('Context has some issues - review the checks above');
2762
+ }
2763
+ if (full) {
2764
+ console.log(`
2765
+ ${COLORS.bold}Full Generation${COLORS.reset}
2766
+ `);
2767
+ const mcpPath = path7.join(cfg._projectRoot ?? process.cwd(), '.mcp.json');
2768
+ if (!fileExists(mcpPath)) {
2769
+ const mcpSpinner = createSpinner('Generating .mcp.json').start();
2770
+ const mcpConfig = {
2771
+ mcpServers: {
2772
+ bootspring: {
2773
+ command: 'npx',
2774
+ args: ['bootspring', 'mcp'],
2775
+ env: {}
2776
+ }
2777
+ }
2778
+ };
2779
+ writeFile(mcpPath, JSON.stringify(mcpConfig, null, 2));
2780
+ mcpSpinner.succeed('Generated .mcp.json');
2781
+ }
2782
+ const agentsPath = path7.join(cfg._projectRoot ?? process.cwd(), 'AGENTS.md');
2783
+ const agentsSpinner = createSpinner('Generating AGENTS.md').start();
2784
+ try {
2785
+ const agentsTemplate = require_agents_template();
2786
+ const agentsContent = agentsTemplate.generate(cfg);
2787
+ writeFile(agentsPath, agentsContent);
2788
+ agentsSpinner.succeed('Generated AGENTS.md (Codex, Cursor, Amp, Kilo)');
2789
+ } catch (error) {
2790
+ agentsSpinner.fail(`Failed to generate AGENTS.md: ${error instanceof Error ? error.message : String(error)}`);
2791
+ }
2792
+ }
2793
+ console.log(`
2794
+ ${COLORS.dim}Tip: Run 'bootspring generate --full' to regenerate all files${COLORS.reset}
2795
+ `);
2796
+ }
2797
+
2798
+ // src/cli/todo.ts
2799
+ var todo_exports = {};
2800
+ __export(todo_exports, {
2801
+ addTodo: () => addTodo,
2802
+ clearCompleted: () => clearCompleted,
2803
+ clearTodos: () => clearTodos,
2804
+ doneTodo: () => doneTodo,
2805
+ listTodos: () => listTodos,
2806
+ markDone: () => markDone,
2807
+ markUndone: () => markUndone,
2808
+ parseTodoFile: () => parseTodoFile,
2809
+ parseTodos: () => parseTodos,
2810
+ removeTodo: () => removeTodo,
2811
+ run: () => run4,
2812
+ undoTodo: () => undoTodo,
2813
+ writeTodoFile: () => writeTodoFile
2814
+ });
2815
+ init_cjs_shims();
2816
+ const fs6 = __toESM(require('fs'));
2817
+ const path8 = __toESM(require('path'));
2818
+ function parseTodoFile(filePath) {
2819
+ try {
2820
+ const content = fs6.readFileSync(filePath, 'utf-8');
2821
+ const todos = [];
2822
+ const lines = content.split('\n');
2823
+ for (const line of lines) {
2824
+ const match = line.match(/^(\s*)-\s*\[([ xX])\]\s*(.+)$/);
2825
+ if (match && match[2] && match[3]) {
2826
+ todos.push({
2827
+ done: match[2].toLowerCase() === 'x',
2828
+ text: match[3].trim()
2829
+ });
2830
+ }
2831
+ }
2832
+ return todos;
2833
+ } catch {
2834
+ return [];
2835
+ }
2836
+ }
2837
+ function writeTodoFile(filePath, items) {
2838
+ const lines = ['# Todo', ''];
2839
+ for (const item of items) {
2840
+ const checkbox = item.done ? '[x]' : '[ ]';
2841
+ lines.push(`- ${checkbox} ${item.text}`);
2842
+ }
2843
+ lines.push('');
2844
+ fs6.writeFileSync(filePath, lines.join('\n'), 'utf-8');
2845
+ }
2846
+ function addTodoUtil(filePath, text) {
2847
+ const todos = parseTodoFile(filePath);
2848
+ todos.push({ done: false, text });
2849
+ writeTodoFile(filePath, todos);
2850
+ }
2851
+ function markDone(filePath, index) {
2852
+ const todos = parseTodoFile(filePath);
2853
+ if (index >= 0 && index < todos.length) {
2854
+ const todo = todos[index];
2855
+ if (todo) {
2856
+ todo.done = true;
2857
+ writeTodoFile(filePath, todos);
2858
+ }
2859
+ }
2860
+ }
2861
+ function markUndone(filePath, index) {
2862
+ const todos = parseTodoFile(filePath);
2863
+ if (index >= 0 && index < todos.length) {
2864
+ const todo = todos[index];
2865
+ if (todo) {
2866
+ todo.done = false;
2867
+ writeTodoFile(filePath, todos);
2868
+ }
2869
+ }
2870
+ }
2871
+ function removeTodoUtil(filePath, index) {
2872
+ const todos = parseTodoFile(filePath);
2873
+ if (index >= 0 && index < todos.length) {
2874
+ todos.splice(index, 1);
2875
+ writeTodoFile(filePath, todos);
2876
+ }
2877
+ }
2878
+ function clearTodos(filePath, type = 'completed') {
2879
+ const todos = parseTodoFile(filePath);
2880
+ if (type === 'all') {
2881
+ writeTodoFile(filePath, []);
2882
+ } else if (type === 'completed') {
2883
+ const remaining = todos.filter((t) => !t.done);
2884
+ writeTodoFile(filePath, remaining);
2885
+ }
2886
+ }
2887
+ function listTodosUtil(filePath) {
2888
+ return parseTodoFile(filePath);
2889
+ }
2890
+ function parseTodos(content) {
2891
+ const todos = [];
2892
+ const lines = content.split('\n');
2893
+ for (let i = 0; i < lines.length; i++) {
2894
+ const line = lines[i];
2895
+ if (!line) continue;
2896
+ const match = line.match(/^(\s*)-\s*\[([ xX])\]\s*(.+)$/);
2897
+ if (match && match[1] !== void 0 && match[2] && match[3]) {
2898
+ todos.push({
2899
+ index: todos.length + 1,
2900
+ indent: match[1].length,
2901
+ completed: match[2].toLowerCase() === 'x',
2902
+ text: match[3].trim(),
2903
+ line: i
2904
+ });
2905
+ }
2906
+ }
2907
+ return todos;
2908
+ }
2909
+ function displayTodos(todos, options = {}) {
2910
+ const { showCompleted = true, showIndex = true } = options;
2911
+ const pending = todos.filter((t) => !t.completed);
2912
+ const completed = todos.filter((t) => t.completed);
2913
+ if (pending.length === 0 && completed.length === 0) {
2914
+ print.dim('No todos found. Add one with: bootspring todo add "Your task"');
2915
+ return;
2916
+ }
2917
+ if (pending.length > 0) {
2918
+ console.log(`
2919
+ ${COLORS.bold}Pending (${pending.length})${COLORS.reset}
2920
+ `);
2921
+ for (const todo of pending) {
2922
+ const index = showIndex ? `${COLORS.dim}${String(todo.index).padStart(2)}${COLORS.reset} ` : '';
2923
+ const indent = ' '.repeat(todo.indent);
2924
+ console.log(`${index}${indent}${COLORS.yellow}\u25CB${COLORS.reset} ${todo.text}`);
2925
+ }
2926
+ }
2927
+ if (showCompleted && completed.length > 0) {
2928
+ console.log(`
2929
+ ${COLORS.bold}Completed (${completed.length})${COLORS.reset}
2930
+ `);
2931
+ for (const todo of completed) {
2932
+ const index = showIndex ? `${COLORS.dim}${String(todo.index).padStart(2)}${COLORS.reset} ` : '';
2933
+ const indent = ' '.repeat(todo.indent);
2934
+ console.log(`${index}${indent}${COLORS.green}\u25CF${COLORS.reset} ${COLORS.dim}${todo.text}${COLORS.reset}`);
2935
+ }
2936
+ }
2937
+ console.log();
2938
+ }
2939
+ function getTodoPath() {
2940
+ const cfg = load();
2941
+ return path8.join(cfg._projectRoot ?? process.cwd(), cfg.paths?.todo ?? 'todo.md');
2942
+ }
2943
+ function readTodoFile() {
2944
+ const todoPath = getTodoPath();
2945
+ if (!fileExists(todoPath)) {
2946
+ const defaultContent = '# Todo List\n\n## Pending\n\n## Completed\n';
2947
+ writeFile(todoPath, defaultContent);
2948
+ return defaultContent;
2949
+ }
2950
+ return readFile(todoPath) ?? '';
2951
+ }
2952
+ function saveTodoContent(content) {
2953
+ const todoPath = getTodoPath();
2954
+ writeFile(todoPath, content);
2955
+ }
2956
+ function listTodosCli(args) {
2957
+ const parsedArgs = parseArgs(args);
2958
+ const content = readTodoFile();
2959
+ const todos = parseTodos(content);
2960
+ console.log(`${COLORS.cyan}${COLORS.bold}\u26A1 Bootspring Todo${COLORS.reset}`);
2961
+ displayTodos(todos, {
2962
+ showCompleted: !parsedArgs.pending,
2963
+ showIndex: true
2964
+ });
2965
+ const pending = todos.filter((t) => !t.completed).length;
2966
+ const completed = todos.filter((t) => t.completed).length;
2967
+ print.dim(`${pending} pending, ${completed} completed`);
2968
+ }
2969
+ function addTodoCli(args) {
2970
+ const text = args.join(' ').trim();
2971
+ if (!text) {
2972
+ print.error('Please provide a todo text');
2973
+ print.dim('Usage: bootspring todo add "Your task"');
2974
+ return;
2975
+ }
2976
+ const content = readTodoFile();
2977
+ const lines = content.split('\n');
2978
+ let insertIndex = -1;
2979
+ for (let i = 0; i < lines.length; i++) {
2980
+ const line = lines[i];
2981
+ if (line && line.match(/^##?\s*(Pending|In Progress|Todo)/i)) {
2982
+ insertIndex = i + 1;
2983
+ while (insertIndex < lines.length && lines[insertIndex]?.trim() === '') {
2984
+ insertIndex++;
2985
+ }
2986
+ break;
2987
+ }
2988
+ }
2989
+ if (insertIndex === -1) {
2990
+ for (let i = 0; i < lines.length; i++) {
2991
+ if (lines[i]?.startsWith('#')) {
2992
+ insertIndex = i + 1;
2993
+ break;
2994
+ }
2995
+ }
2996
+ }
2997
+ if (insertIndex === -1) {
2998
+ insertIndex = lines.length;
2999
+ }
3000
+ const newTodo = `- [ ] ${text}`;
3001
+ lines.splice(insertIndex, 0, newTodo);
3002
+ saveTodoContent(lines.join('\n'));
3003
+ print.success(`Added: ${text}`);
3004
+ const todos = parseTodos(lines.join('\n'));
3005
+ const pending = todos.filter((t) => !t.completed).length;
3006
+ print.dim(`${pending} pending todos`);
3007
+ }
3008
+ function doneTodo(args) {
3009
+ const indexStr = args[0];
3010
+ if (!indexStr) {
3011
+ print.error('Please provide a todo number');
3012
+ print.dim('Usage: bootspring todo done <number>');
3013
+ return;
3014
+ }
3015
+ const index = parseInt(indexStr, 10);
3016
+ if (isNaN(index) || index < 1) {
3017
+ print.error('Invalid todo number');
3018
+ return;
3019
+ }
3020
+ const content = readTodoFile();
3021
+ const todos = parseTodos(content);
3022
+ const todo = todos.find((t) => t.index === index);
3023
+ if (!todo) {
3024
+ print.error(`Todo #${index} not found`);
3025
+ return;
3026
+ }
3027
+ if (todo.completed) {
3028
+ print.warning(`Todo #${index} is already completed`);
3029
+ return;
3030
+ }
3031
+ const lines = content.split('\n');
3032
+ const originalLine = lines[todo.line];
3033
+ if (originalLine) {
3034
+ lines[todo.line] = originalLine.replace('[ ]', '[x]');
3035
+ }
3036
+ saveTodoContent(lines.join('\n'));
3037
+ print.success(`Completed: ${todo.text}`);
3038
+ const remaining = todos.filter((t) => !t.completed && t.index !== index).length;
3039
+ print.dim(`${remaining} pending todos remaining`);
3040
+ }
3041
+ function undoTodo(args) {
3042
+ const indexStr = args[0];
3043
+ if (!indexStr) {
3044
+ print.error('Please provide a todo number');
3045
+ print.dim('Usage: bootspring todo undo <number>');
3046
+ return;
3047
+ }
3048
+ const index = parseInt(indexStr, 10);
3049
+ if (isNaN(index) || index < 1) {
3050
+ print.error('Invalid todo number');
3051
+ return;
3052
+ }
3053
+ const content = readTodoFile();
3054
+ const todos = parseTodos(content);
3055
+ const todo = todos.find((t) => t.index === index);
3056
+ if (!todo) {
3057
+ print.error(`Todo #${index} not found`);
3058
+ return;
3059
+ }
3060
+ if (!todo.completed) {
3061
+ print.warning(`Todo #${index} is not completed`);
3062
+ return;
3063
+ }
3064
+ const lines = content.split('\n');
3065
+ const originalLine = lines[todo.line];
3066
+ if (originalLine) {
3067
+ lines[todo.line] = originalLine.replace(/\[[xX]\]/, '[ ]');
3068
+ }
3069
+ saveTodoContent(lines.join('\n'));
3070
+ print.success(`Reopened: ${todo.text}`);
3071
+ }
3072
+ function removeTodoCli(args) {
3073
+ const indexStr = args[0];
3074
+ if (!indexStr) {
3075
+ print.error('Please provide a todo number');
3076
+ print.dim('Usage: bootspring todo remove <number>');
3077
+ return;
3078
+ }
3079
+ const index = parseInt(indexStr, 10);
3080
+ if (isNaN(index) || index < 1) {
3081
+ print.error('Invalid todo number');
3082
+ return;
3083
+ }
3084
+ const content = readTodoFile();
3085
+ const todos = parseTodos(content);
3086
+ const todo = todos.find((t) => t.index === index);
3087
+ if (!todo) {
3088
+ print.error(`Todo #${index} not found`);
3089
+ return;
3090
+ }
3091
+ const lines = content.split('\n');
3092
+ lines.splice(todo.line, 1);
3093
+ saveTodoContent(lines.join('\n'));
3094
+ print.success(`Removed: ${todo.text}`);
3095
+ }
3096
+ function clearCompleted() {
3097
+ const content = readTodoFile();
3098
+ const todos = parseTodos(content);
3099
+ const completedTodos = todos.filter((t) => t.completed);
3100
+ if (completedTodos.length === 0) {
3101
+ print.info('No completed todos to clear');
3102
+ return;
3103
+ }
3104
+ const lines = content.split('\n');
3105
+ const linesToRemove = completedTodos.map((t) => t.line).sort((a, b) => b - a);
3106
+ for (const lineIndex of linesToRemove) {
3107
+ lines.splice(lineIndex, 1);
3108
+ }
3109
+ saveTodoContent(lines.join('\n'));
3110
+ print.success(`Cleared ${completedTodos.length} completed todos`);
3111
+ }
3112
+ function showHelp2() {
3113
+ console.log(`
3114
+ ${COLORS.cyan}${COLORS.bold}\u26A1 Bootspring Todo${COLORS.reset}
3115
+ ${COLORS.dim}Simple and powerful todo management${COLORS.reset}
3116
+
3117
+ ${COLORS.bold}Usage:${COLORS.reset}
3118
+ bootspring todo <command> [args]
3119
+
3120
+ ${COLORS.bold}Commands:${COLORS.reset}
3121
+ ${COLORS.cyan}list${COLORS.reset} List all todos
3122
+ ${COLORS.cyan}add${COLORS.reset} <text> Add a new todo
3123
+ ${COLORS.cyan}done${COLORS.reset} <number> Mark todo as completed
3124
+ ${COLORS.cyan}undo${COLORS.reset} <number> Reopen a completed todo
3125
+ ${COLORS.cyan}remove${COLORS.reset} <number> Remove a todo
3126
+ ${COLORS.cyan}clear${COLORS.reset} Clear all completed todos
3127
+
3128
+ ${COLORS.bold}Examples:${COLORS.reset}
3129
+ bootspring todo add "Implement user auth"
3130
+ bootspring todo done 1
3131
+ bootspring todo list --pending
3132
+ bootspring todo clear
3133
+ `);
3134
+ }
3135
+ async function run4(args) {
3136
+ const subcommand = args[0] ?? 'list';
3137
+ const subargs = args.slice(1);
3138
+ switch (subcommand) {
3139
+ case 'list':
3140
+ case 'ls':
3141
+ listTodosCli(subargs);
3142
+ break;
3143
+ case 'add':
3144
+ case 'a':
3145
+ case 'new':
3146
+ addTodoCli(subargs);
3147
+ break;
3148
+ case 'done':
3149
+ case 'd':
3150
+ case 'complete':
3151
+ case 'check':
3152
+ doneTodo(subargs);
3153
+ break;
3154
+ case 'undo':
3155
+ case 'u':
3156
+ case 'reopen':
3157
+ undoTodo(subargs);
3158
+ break;
3159
+ case 'remove':
3160
+ case 'rm':
3161
+ case 'delete':
3162
+ removeTodoCli(subargs);
3163
+ break;
3164
+ case 'clear':
3165
+ clearCompleted();
3166
+ break;
3167
+ case 'help':
3168
+ case '-h':
3169
+ case '--help':
3170
+ showHelp2();
3171
+ break;
3172
+ default:
3173
+ if (subcommand && !subcommand.startsWith('-')) {
3174
+ addTodoCli([subcommand, ...subargs]);
3175
+ } else {
3176
+ print.error(`Unknown subcommand: ${subcommand}`);
3177
+ showHelp2();
3178
+ }
3179
+ }
3180
+ }
3181
+ function addTodo(firstArg, secondArg) {
3182
+ if (typeof firstArg === 'string' && typeof secondArg === 'string') {
3183
+ addTodoUtil(firstArg, secondArg);
3184
+ } else if (Array.isArray(firstArg)) {
3185
+ addTodoCli(firstArg);
3186
+ } else {
3187
+ addTodoCli([firstArg, secondArg].filter((x) => Boolean(x)));
3188
+ }
3189
+ }
3190
+ function removeTodo(firstArg, secondArg) {
3191
+ if (typeof firstArg === 'string' && typeof secondArg === 'number') {
3192
+ removeTodoUtil(firstArg, secondArg);
3193
+ } else if (Array.isArray(firstArg)) {
3194
+ removeTodoCli(firstArg);
3195
+ } else {
3196
+ removeTodoCli([firstArg]);
3197
+ }
3198
+ }
3199
+ function listTodos(firstArg) {
3200
+ if (typeof firstArg === 'string' && (firstArg.includes('/') || firstArg.includes('\\'))) {
3201
+ return listTodosUtil(firstArg);
3202
+ } else if (Array.isArray(firstArg)) {
3203
+ listTodosCli(firstArg);
3204
+ } else {
3205
+ listTodosCli(firstArg ? [firstArg] : []);
3206
+ }
3207
+ }
3208
+ // Annotate the CommonJS export names for ESM import in node:
3209
+ 0 && (module.exports = {
3210
+ dashboard,
3211
+ generate,
3212
+ generateClaudeMd,
3213
+ runDashboard,
3214
+ runGenerate,
3215
+ runTelemetry,
3216
+ runTodo,
3217
+ telemetry,
3218
+ todo
3219
+ });
3220
+ //# sourceMappingURL=index.js.map