@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.
- package/bin/bootspring.js +5 -0
- package/cli/org.js +474 -0
- package/cli/preseed/index.js +16 -0
- package/cli/preseed/interactive.js +143 -0
- package/cli/preseed/templates.js +227 -0
- package/cli/preseed.js +9 -301
- package/cli/seed/builders/ai-context-builder.js +85 -0
- package/cli/seed/builders/index.js +13 -0
- package/cli/seed/builders/seed-builder.js +272 -0
- package/cli/seed/extractors/content-extractors.js +383 -0
- package/cli/seed/extractors/index.js +47 -0
- package/cli/seed/extractors/metadata-extractors.js +167 -0
- package/cli/seed/extractors/section-extractor.js +54 -0
- package/cli/seed/extractors/stack-extractors.js +228 -0
- package/cli/seed/index.js +18 -0
- package/cli/seed/utils/folder-structure.js +84 -0
- package/cli/seed/utils/index.js +11 -0
- package/cli/seed.js +23 -1074
- package/core/api-client.js +77 -0
- package/core/entitlements.js +36 -0
- package/core/organizations.js +223 -0
- package/core/policies.js +51 -6
- package/core/policy-matrix.js +303 -0
- package/core/project-context.js +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +3220 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/context-McpJQa_2.d.ts +5710 -0
- package/dist/core/index.d.ts +635 -0
- package/dist/core/index.js +2593 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index-QqbeEiDm.d.ts +857 -0
- package/dist/index-UiYCgwiH.d.ts +174 -0
- package/dist/index.d.ts +453 -0
- package/dist/index.js +44228 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +41173 -0
- package/dist/mcp/index.js.map +1 -0
- package/generators/index.ts +82 -0
- package/intelligence/orchestrator/config/failure-signatures.js +48 -0
- package/intelligence/orchestrator/config/index.js +23 -0
- package/intelligence/orchestrator/config/pack-lifecycle.js +262 -0
- package/intelligence/orchestrator/config/phases.js +111 -0
- package/intelligence/orchestrator/config/remediation.js +150 -0
- package/intelligence/orchestrator/config/workflows.js +168 -0
- package/intelligence/orchestrator/core/index.js +16 -0
- package/intelligence/orchestrator/core/state-manager.js +88 -0
- package/intelligence/orchestrator/core/telemetry.js +24 -0
- package/intelligence/orchestrator/index.js +17 -0
- package/intelligence/orchestrator.js +17 -512
- package/mcp/contracts/mcp-contract.v1.json +1 -1
- package/package.json +16 -3
- package/src/cli/agent.ts +703 -0
- package/src/cli/analyze.ts +640 -0
- package/src/cli/audit.ts +707 -0
- package/src/cli/auth.ts +930 -0
- package/src/cli/billing.ts +364 -0
- package/src/cli/build.ts +1089 -0
- package/src/cli/business.ts +508 -0
- package/src/cli/checkpoint-utils.ts +236 -0
- package/src/cli/checkpoint.ts +757 -0
- package/src/cli/cloud-sync.ts +534 -0
- package/src/cli/content.ts +273 -0
- package/src/cli/context.ts +667 -0
- package/src/cli/dashboard.ts +133 -0
- package/src/cli/deploy.ts +704 -0
- package/src/cli/doctor.ts +480 -0
- package/src/cli/fundraise.ts +494 -0
- package/src/cli/generate.ts +346 -0
- package/src/cli/github-cmd.ts +566 -0
- package/src/cli/health.ts +599 -0
- package/src/cli/index.ts +113 -0
- package/src/cli/init.ts +838 -0
- package/src/cli/legal.ts +495 -0
- package/src/cli/log.ts +316 -0
- package/src/cli/loop.ts +1660 -0
- package/src/cli/manager.ts +878 -0
- package/src/cli/mcp.ts +275 -0
- package/src/cli/memory.ts +346 -0
- package/src/cli/metrics.ts +590 -0
- package/src/cli/monitor.ts +960 -0
- package/src/cli/mvp.ts +662 -0
- package/src/cli/onboard.ts +663 -0
- package/src/cli/orchestrator.ts +622 -0
- package/src/cli/plugin.ts +483 -0
- package/src/cli/prd.ts +671 -0
- package/src/cli/preseed-start.ts +1633 -0
- package/src/cli/preseed.ts +2434 -0
- package/src/cli/project.ts +526 -0
- package/src/cli/quality.ts +885 -0
- package/src/cli/security.ts +1079 -0
- package/src/cli/seed.ts +1224 -0
- package/src/cli/skill.ts +537 -0
- package/src/cli/suggest.ts +1225 -0
- package/src/cli/switch.ts +518 -0
- package/src/cli/task.ts +780 -0
- package/src/cli/telemetry.ts +172 -0
- package/src/cli/todo.ts +627 -0
- package/src/cli/types.ts +15 -0
- package/src/cli/update.ts +334 -0
- package/src/cli/visualize.ts +609 -0
- package/src/cli/watch.ts +895 -0
- package/src/cli/workspace.ts +709 -0
- package/src/core/action-recorder.ts +673 -0
- package/src/core/analyze-workflow.ts +1453 -0
- package/src/core/api-client.ts +1120 -0
- package/src/core/audit-workflow.ts +1681 -0
- package/src/core/auth.ts +471 -0
- package/src/core/build-orchestrator.ts +509 -0
- package/src/core/build-state.ts +621 -0
- package/src/core/checkpoint-engine.ts +482 -0
- package/src/core/config.ts +1285 -0
- package/src/core/context-loader.ts +694 -0
- package/src/core/context.ts +410 -0
- package/src/core/deploy-workflow.ts +1085 -0
- package/src/core/entitlements.ts +322 -0
- package/src/core/github-sync.ts +720 -0
- package/src/core/index.ts +981 -0
- package/src/core/ingest.ts +1186 -0
- package/src/core/metrics-engine.ts +886 -0
- package/src/core/mvp.ts +847 -0
- package/src/core/onboard-workflow.ts +1293 -0
- package/src/core/policies.ts +81 -0
- package/src/core/preseed-workflow.ts +1163 -0
- package/src/core/preseed.ts +1826 -0
- package/src/core/project-context.ts +380 -0
- package/src/core/project-state.ts +699 -0
- package/src/core/r2-sync.ts +691 -0
- package/src/core/scaffold.ts +1715 -0
- package/src/core/session.ts +286 -0
- package/src/core/task-extractor.ts +799 -0
- package/src/core/telemetry.ts +371 -0
- package/src/core/tier-enforcement.ts +737 -0
- package/src/core/utils.ts +437 -0
- package/src/index.ts +29 -0
- package/src/intelligence/agent-collab.ts +2376 -0
- package/src/intelligence/auto-suggest.ts +713 -0
- package/src/intelligence/content-gen.ts +1351 -0
- package/src/intelligence/cross-project.ts +1692 -0
- package/src/intelligence/git-memory.ts +529 -0
- package/src/intelligence/index.ts +318 -0
- package/src/intelligence/orchestrator.ts +534 -0
- package/src/intelligence/prd.ts +466 -0
- package/src/intelligence/recommendations.ts +982 -0
- package/src/intelligence/workflow-composer.ts +1472 -0
- package/src/mcp/capabilities.ts +233 -0
- package/src/mcp/index.ts +37 -0
- package/src/mcp/registry.ts +1268 -0
- package/src/mcp/response-formatter.ts +797 -0
- package/src/mcp/server.ts +240 -0
- package/src/types/agent.ts +69 -0
- package/src/types/config.ts +86 -0
- package/src/types/context.ts +77 -0
- package/src/types/index.ts +53 -0
- package/src/types/mcp.ts +91 -0
- package/src/types/skills.ts +47 -0
- package/src/types/workflow.ts +155 -0
- 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
|