@girardmedia/bootspring 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +255 -0
  3. package/agents/README.md +93 -0
  4. package/agents/api-expert/context.md +416 -0
  5. package/agents/architecture-expert/context.md +454 -0
  6. package/agents/backend-expert/context.md +483 -0
  7. package/agents/code-review-expert/context.md +365 -0
  8. package/agents/database-expert/context.md +250 -0
  9. package/agents/devops-expert/context.md +446 -0
  10. package/agents/frontend-expert/context.md +364 -0
  11. package/agents/index.js +140 -0
  12. package/agents/performance-expert/context.md +377 -0
  13. package/agents/security-expert/context.md +343 -0
  14. package/agents/testing-expert/context.md +414 -0
  15. package/agents/ui-ux-expert/context.md +448 -0
  16. package/agents/vercel-expert/context.md +426 -0
  17. package/bin/bootspring.js +310 -0
  18. package/cli/agent.js +337 -0
  19. package/cli/context.js +194 -0
  20. package/cli/dashboard.js +150 -0
  21. package/cli/generate.js +294 -0
  22. package/cli/init.js +410 -0
  23. package/cli/loop.js +421 -0
  24. package/cli/mcp.js +241 -0
  25. package/cli/memory.js +303 -0
  26. package/cli/orchestrator.js +400 -0
  27. package/cli/plugin.js +451 -0
  28. package/cli/quality.js +332 -0
  29. package/cli/skill.js +369 -0
  30. package/cli/task.js +628 -0
  31. package/cli/telemetry.js +114 -0
  32. package/cli/todo.js +614 -0
  33. package/cli/update.js +312 -0
  34. package/core/config.js +245 -0
  35. package/core/context.js +329 -0
  36. package/core/entitlements.js +209 -0
  37. package/core/index.js +43 -0
  38. package/core/policies.js +68 -0
  39. package/core/telemetry.js +247 -0
  40. package/core/utils.js +380 -0
  41. package/dashboard/server.js +818 -0
  42. package/docs/integrations/claude-code.md +42 -0
  43. package/docs/integrations/codex.md +42 -0
  44. package/docs/mcp-api-platform.md +102 -0
  45. package/generators/generate.js +598 -0
  46. package/generators/index.js +18 -0
  47. package/hooks/context-detector.js +177 -0
  48. package/hooks/index.js +35 -0
  49. package/hooks/prompt-enhancer.js +289 -0
  50. package/intelligence/git-memory.js +551 -0
  51. package/intelligence/index.js +59 -0
  52. package/intelligence/orchestrator.js +964 -0
  53. package/intelligence/prd.js +447 -0
  54. package/intelligence/recommendation-weights.json +18 -0
  55. package/intelligence/recommendations.js +234 -0
  56. package/mcp/capabilities.js +71 -0
  57. package/mcp/contracts/mcp-contract.v1.json +497 -0
  58. package/mcp/registry.js +213 -0
  59. package/mcp/response-formatter.js +462 -0
  60. package/mcp/server.js +99 -0
  61. package/mcp/tools/agent-tool.js +137 -0
  62. package/mcp/tools/capabilities-tool.js +54 -0
  63. package/mcp/tools/context-tool.js +49 -0
  64. package/mcp/tools/dashboard-tool.js +58 -0
  65. package/mcp/tools/generate-tool.js +46 -0
  66. package/mcp/tools/loop-tool.js +134 -0
  67. package/mcp/tools/memory-tool.js +180 -0
  68. package/mcp/tools/orchestrator-tool.js +232 -0
  69. package/mcp/tools/plugin-tool.js +76 -0
  70. package/mcp/tools/quality-tool.js +47 -0
  71. package/mcp/tools/skill-tool.js +233 -0
  72. package/mcp/tools/telemetry-tool.js +95 -0
  73. package/mcp/tools/todo-tool.js +133 -0
  74. package/package.json +98 -0
  75. package/plugins/index.js +141 -0
  76. package/quality/index.js +380 -0
  77. package/quality/lint-budgets.json +19 -0
  78. package/skills/index.js +787 -0
  79. package/skills/patterns/README.md +163 -0
  80. package/skills/patterns/api/route-handler.md +217 -0
  81. package/skills/patterns/api/server-action.md +249 -0
  82. package/skills/patterns/auth/clerk.md +132 -0
  83. package/skills/patterns/database/prisma.md +180 -0
  84. package/skills/patterns/payments/stripe.md +272 -0
  85. package/skills/patterns/security/validation.md +268 -0
  86. package/skills/patterns/testing/vitest.md +307 -0
  87. package/templates/bootspring.config.js +83 -0
  88. package/templates/mcp.json +9 -0
@@ -0,0 +1,329 @@
1
+ /**
2
+ * Bootspring Context Manager
3
+ * Handles project context for AI assistants
4
+ *
5
+ * @package bootspring
6
+ * @module core/context
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const config = require('./config');
12
+ const utils = require('./utils');
13
+
14
+ /**
15
+ * Get current project context
16
+ * @param {object} [options] - Options
17
+ * @returns {object} Context object
18
+ */
19
+ function get(options = {}) {
20
+ const cfg = options.config || config.load();
21
+ const projectRoot = cfg._projectRoot;
22
+
23
+ const context = {
24
+ project: cfg.project,
25
+ stack: cfg.stack,
26
+ plugins: getEnabledPlugins(cfg),
27
+ files: getProjectFiles(projectRoot),
28
+ git: getGitInfo(projectRoot),
29
+ state: getProjectState(projectRoot, cfg),
30
+ timestamp: new Date().toISOString()
31
+ };
32
+
33
+ return context;
34
+ }
35
+
36
+ /**
37
+ * Get enabled plugins from config
38
+ * @param {object} cfg - Configuration
39
+ * @returns {object} Enabled plugins
40
+ */
41
+ function getEnabledPlugins(cfg) {
42
+ const enabled = {};
43
+
44
+ for (const [name, plugin] of Object.entries(cfg.plugins || {})) {
45
+ if (plugin.enabled !== false) {
46
+ enabled[name] = {
47
+ provider: plugin.provider || 'default',
48
+ features: plugin.features || []
49
+ };
50
+ }
51
+ }
52
+
53
+ return enabled;
54
+ }
55
+
56
+ /**
57
+ * Get project file structure summary
58
+ * @param {string} projectRoot - Project root
59
+ * @returns {object} File info
60
+ */
61
+ function getProjectFiles(projectRoot) {
62
+ const files = {
63
+ hasPackageJson: utils.fileExists(path.join(projectRoot, 'package.json')),
64
+ hasTsConfig: utils.fileExists(path.join(projectRoot, 'tsconfig.json')),
65
+ hasClaudeMd: utils.fileExists(path.join(projectRoot, 'CLAUDE.md')),
66
+ hasBootspringConfig: utils.fileExists(path.join(projectRoot, 'bootspring.config.js')),
67
+ hasTodoMd: utils.fileExists(path.join(projectRoot, 'todo.md')),
68
+ hasGit: utils.fileExists(path.join(projectRoot, '.git')),
69
+ hasSrcDir: utils.fileExists(path.join(projectRoot, 'src')),
70
+ hasAppDir: utils.fileExists(path.join(projectRoot, 'app')),
71
+ hasPagesDir: utils.fileExists(path.join(projectRoot, 'pages'))
72
+ };
73
+
74
+ // Detect framework structure
75
+ if (files.hasAppDir) {
76
+ files.structure = 'app-router';
77
+ } else if (files.hasPagesDir) {
78
+ files.structure = 'pages-router';
79
+ } else if (files.hasSrcDir) {
80
+ files.structure = 'src-based';
81
+ } else {
82
+ files.structure = 'flat';
83
+ }
84
+
85
+ return files;
86
+ }
87
+
88
+ /**
89
+ * Get git information
90
+ * @param {string} projectRoot - Project root
91
+ * @returns {object} Git info
92
+ */
93
+ function getGitInfo(projectRoot) {
94
+ const gitDir = path.join(projectRoot, '.git');
95
+
96
+ if (!utils.fileExists(gitDir)) {
97
+ return { initialized: false };
98
+ }
99
+
100
+ const info = { initialized: true };
101
+
102
+ // Get current branch
103
+ const headPath = path.join(gitDir, 'HEAD');
104
+ if (utils.fileExists(headPath)) {
105
+ const head = utils.readFile(headPath).trim();
106
+ if (head.startsWith('ref: refs/heads/')) {
107
+ info.branch = head.replace('ref: refs/heads/', '');
108
+ }
109
+ }
110
+
111
+ // Check for remote
112
+ const configPath = path.join(gitDir, 'config');
113
+ if (utils.fileExists(configPath)) {
114
+ const gitConfig = utils.readFile(configPath);
115
+ info.hasRemote = gitConfig.includes('[remote "origin"]');
116
+ }
117
+
118
+ return info;
119
+ }
120
+
121
+ /**
122
+ * Get project state
123
+ * @param {string} projectRoot - Project root
124
+ * @param {object} cfg - Configuration
125
+ * @returns {object} State info
126
+ */
127
+ function getProjectState(projectRoot, cfg) {
128
+ const state = {
129
+ phase: 'unknown',
130
+ health: 'unknown',
131
+ todos: 0,
132
+ lastGenerated: null
133
+ };
134
+
135
+ // Count todos
136
+ const todoPath = path.join(projectRoot, cfg.paths?.todo || 'todo.md');
137
+ if (utils.fileExists(todoPath)) {
138
+ const content = utils.readFile(todoPath);
139
+ const todoMatches = content.match(/- \[ \]/g);
140
+ state.todos = todoMatches ? todoMatches.length : 0;
141
+ }
142
+
143
+ // Check CLAUDE.md generation time
144
+ const claudePath = path.join(projectRoot, cfg.paths?.context || 'CLAUDE.md');
145
+ if (utils.fileExists(claudePath)) {
146
+ state.lastGenerated = utils.getFileTime(claudePath);
147
+ }
148
+
149
+ // Determine phase
150
+ if (!cfg._configPath) {
151
+ state.phase = 'uninitialized';
152
+ } else if (!state.lastGenerated) {
153
+ state.phase = 'initialized';
154
+ } else {
155
+ state.phase = 'active';
156
+ }
157
+
158
+ // Determine health
159
+ const issues = [];
160
+ if (!utils.fileExists(path.join(projectRoot, 'package.json'))) {
161
+ issues.push('missing-package-json');
162
+ }
163
+ if (!cfg._configPath) {
164
+ issues.push('missing-config');
165
+ }
166
+ if (!state.lastGenerated) {
167
+ issues.push('missing-context');
168
+ }
169
+
170
+ if (issues.length === 0) {
171
+ state.health = 'good';
172
+ } else if (issues.length <= 2) {
173
+ state.health = 'fair';
174
+ } else {
175
+ state.health = 'needs-attention';
176
+ }
177
+
178
+ state.issues = issues;
179
+
180
+ return state;
181
+ }
182
+
183
+ /**
184
+ * Validate project context
185
+ * @param {object} [options] - Options
186
+ * @returns {object} Validation result
187
+ */
188
+ function validate(options = {}) {
189
+ const cfg = options.config || config.load();
190
+ const projectRoot = cfg._projectRoot;
191
+
192
+ const checks = [];
193
+ let score = 0;
194
+ const maxScore = 10;
195
+
196
+ // Check 1: Config exists
197
+ if (cfg._configPath) {
198
+ checks.push({ name: 'Configuration', status: 'pass', message: 'bootspring.config.js found' });
199
+ score += 2;
200
+ } else {
201
+ checks.push({ name: 'Configuration', status: 'fail', message: 'bootspring.config.js missing' });
202
+ }
203
+
204
+ // Check 2: CLAUDE.md exists
205
+ const claudePath = path.join(projectRoot, cfg.paths?.context || 'CLAUDE.md');
206
+ if (utils.fileExists(claudePath)) {
207
+ checks.push({ name: 'AI Context', status: 'pass', message: 'CLAUDE.md exists' });
208
+ score += 2;
209
+ } else {
210
+ checks.push({ name: 'AI Context', status: 'fail', message: 'CLAUDE.md missing - run bootspring generate' });
211
+ }
212
+
213
+ // Check 3: package.json exists
214
+ if (utils.fileExists(path.join(projectRoot, 'package.json'))) {
215
+ checks.push({ name: 'Package', status: 'pass', message: 'package.json found' });
216
+ score += 1;
217
+ } else {
218
+ checks.push({ name: 'Package', status: 'warn', message: 'package.json missing' });
219
+ }
220
+
221
+ // Check 4: Git initialized
222
+ if (utils.fileExists(path.join(projectRoot, '.git'))) {
223
+ checks.push({ name: 'Git', status: 'pass', message: 'Git repository initialized' });
224
+ score += 1;
225
+ } else {
226
+ checks.push({ name: 'Git', status: 'warn', message: 'Git not initialized' });
227
+ }
228
+
229
+ // Check 5: TypeScript config (if using TS)
230
+ if (cfg.stack?.language === 'typescript') {
231
+ if (utils.fileExists(path.join(projectRoot, 'tsconfig.json'))) {
232
+ checks.push({ name: 'TypeScript', status: 'pass', message: 'tsconfig.json found' });
233
+ score += 1;
234
+ } else {
235
+ checks.push({ name: 'TypeScript', status: 'fail', message: 'tsconfig.json missing for TypeScript project' });
236
+ }
237
+ } else {
238
+ score += 1;
239
+ }
240
+
241
+ // Check 6: Config validation
242
+ const configValidation = config.validate(cfg);
243
+ if (configValidation.valid) {
244
+ checks.push({ name: 'Config Validation', status: 'pass', message: 'Configuration is valid' });
245
+ score += 2;
246
+ } else {
247
+ checks.push({ name: 'Config Validation', status: 'fail', message: configValidation.errors.join(', ') });
248
+ }
249
+
250
+ // Check 7: Todo file
251
+ if (utils.fileExists(path.join(projectRoot, cfg.paths?.todo || 'todo.md'))) {
252
+ checks.push({ name: 'Todo Tracking', status: 'pass', message: 'todo.md exists' });
253
+ score += 1;
254
+ } else {
255
+ checks.push({ name: 'Todo Tracking', status: 'info', message: 'todo.md not found - optional' });
256
+ score += 0.5;
257
+ }
258
+
259
+ return {
260
+ valid: score >= maxScore * 0.6,
261
+ score,
262
+ maxScore,
263
+ percentage: Math.round((score / maxScore) * 100),
264
+ checks
265
+ };
266
+ }
267
+
268
+ /**
269
+ * Generate context summary for AI
270
+ * @param {object} [options] - Options
271
+ * @returns {string} Context summary markdown
272
+ */
273
+ function generateSummary(options = {}) {
274
+ const ctx = get(options);
275
+
276
+ const lines = [
277
+ `# Project Context`,
278
+ ``,
279
+ `**Project**: ${ctx.project.name}`,
280
+ `**Generated**: ${ctx.timestamp}`,
281
+ ``,
282
+ `## Stack`,
283
+ `- Framework: ${ctx.stack.framework}`,
284
+ `- Language: ${ctx.stack.language}`,
285
+ `- Database: ${ctx.stack.database}`,
286
+ `- Hosting: ${ctx.stack.hosting}`,
287
+ ``
288
+ ];
289
+
290
+ // Plugins
291
+ const enabledPlugins = Object.keys(ctx.plugins);
292
+ if (enabledPlugins.length > 0) {
293
+ lines.push(`## Enabled Plugins`);
294
+ for (const [name, plugin] of Object.entries(ctx.plugins)) {
295
+ lines.push(`- **${name}**: ${plugin.provider}`);
296
+ }
297
+ lines.push(``);
298
+ }
299
+
300
+ // State
301
+ lines.push(`## Project State`);
302
+ lines.push(`- Phase: ${ctx.state.phase}`);
303
+ lines.push(`- Health: ${ctx.state.health}`);
304
+ lines.push(`- Open Todos: ${ctx.state.todos}`);
305
+ if (ctx.state.lastGenerated) {
306
+ lines.push(`- Context Last Generated: ${utils.formatRelativeTime(ctx.state.lastGenerated)}`);
307
+ }
308
+ lines.push(``);
309
+
310
+ // Git
311
+ if (ctx.git.initialized) {
312
+ lines.push(`## Git`);
313
+ lines.push(`- Branch: ${ctx.git.branch || 'unknown'}`);
314
+ lines.push(`- Remote: ${ctx.git.hasRemote ? 'configured' : 'not configured'}`);
315
+ lines.push(``);
316
+ }
317
+
318
+ return lines.join('\n');
319
+ }
320
+
321
+ module.exports = {
322
+ get,
323
+ validate,
324
+ generateSummary,
325
+ getEnabledPlugins,
326
+ getProjectFiles,
327
+ getGitInfo,
328
+ getProjectState
329
+ };
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Bootspring Entitlements
3
+ * Shared access policy for gated capabilities.
4
+ */
5
+
6
+ const LOCAL_MODE = 'local';
7
+ const SERVER_MODE = 'server';
8
+ const PRO_TIERS = new Set(['pro', 'team', 'enterprise']);
9
+ const policies = require('./policies');
10
+
11
+ function parseBoolean(value) {
12
+ if (typeof value === 'boolean') return value;
13
+ if (value === null || value === undefined) return false;
14
+ const normalized = String(value).trim().toLowerCase();
15
+ return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
16
+ }
17
+
18
+ function normalizeMode(value) {
19
+ const mode = String(value || '').trim().toLowerCase();
20
+ if (mode === SERVER_MODE) {
21
+ return SERVER_MODE;
22
+ }
23
+ return LOCAL_MODE;
24
+ }
25
+
26
+ function resolveAccessContext(options = {}) {
27
+ const envMode = process.env.BOOTSPRING_SKILL_ACCESS_MODE;
28
+ const envTier = process.env.BOOTSPRING_USER_TIER;
29
+ const envEntitled = process.env.BOOTSPRING_SKILLS_ENTITLED;
30
+
31
+ return {
32
+ mode: normalizeMode(options.mode || envMode),
33
+ tier: String(options.tier || envTier || 'free').trim().toLowerCase(),
34
+ entitled: parseBoolean(options.entitled ?? envEntitled),
35
+ policyProfile: policies.resolvePolicyProfile(options)
36
+ };
37
+ }
38
+
39
+ function resolveWorkflowAccessContext(options = {}) {
40
+ const envMode = process.env.BOOTSPRING_WORKFLOW_ACCESS_MODE || process.env.BOOTSPRING_SKILL_ACCESS_MODE;
41
+ const envTier = process.env.BOOTSPRING_USER_TIER;
42
+ const envEntitled = process.env.BOOTSPRING_WORKFLOWS_ENTITLED ?? process.env.BOOTSPRING_SKILLS_ENTITLED;
43
+
44
+ return {
45
+ mode: normalizeMode(options.mode || envMode),
46
+ tier: String(options.tier || envTier || 'free').trim().toLowerCase(),
47
+ entitled: parseBoolean(options.entitled ?? envEntitled),
48
+ policyProfile: policies.resolvePolicyProfile(options)
49
+ };
50
+ }
51
+
52
+ function isExternalSkill(skillId) {
53
+ return String(skillId || '').startsWith('external/');
54
+ }
55
+
56
+ function checkSkillAccess(skillId, options = {}) {
57
+ const context = resolveAccessContext(options);
58
+
59
+ if (!isExternalSkill(skillId)) {
60
+ return {
61
+ allowed: true,
62
+ code: 'built_in',
63
+ reason: 'Built-in skills are always available.',
64
+ context
65
+ };
66
+ }
67
+
68
+ const policy = policies.getPolicyProfile(context.policyProfile, options);
69
+ if (!policy.allowExternalSkills) {
70
+ return {
71
+ allowed: false,
72
+ code: 'external_policy_blocked',
73
+ reason: `External skills are blocked by ${policy.id} policy profile.`,
74
+ context
75
+ };
76
+ }
77
+
78
+ if (context.mode !== SERVER_MODE) {
79
+ return {
80
+ allowed: true,
81
+ code: 'external_local_mode',
82
+ reason: 'External skills are enabled in local mode.',
83
+ context
84
+ };
85
+ }
86
+
87
+ if (context.entitled || PRO_TIERS.has(context.tier)) {
88
+ return {
89
+ allowed: true,
90
+ code: 'external_entitled',
91
+ reason: 'External skill access granted.',
92
+ context
93
+ };
94
+ }
95
+
96
+ return {
97
+ allowed: false,
98
+ code: 'external_subscription_required',
99
+ reason: 'External skills require entitlement in server mode. Set BOOTSPRING_SKILLS_ENTITLED=true or use tier=pro/team/enterprise.',
100
+ context
101
+ };
102
+ }
103
+
104
+ function filterAccessibleSkills(skillIds, options = {}) {
105
+ const allowed = [];
106
+ const denied = [];
107
+
108
+ for (const skillId of skillIds || []) {
109
+ const decision = checkSkillAccess(skillId, options);
110
+ if (decision.allowed) {
111
+ allowed.push(skillId);
112
+ } else {
113
+ denied.push({
114
+ skillId,
115
+ code: decision.code,
116
+ reason: decision.reason
117
+ });
118
+ }
119
+ }
120
+
121
+ return { allowed, denied };
122
+ }
123
+
124
+ function isPremiumWorkflow(workflow) {
125
+ const tier = String(workflow?.tier || 'free').toLowerCase();
126
+ return tier !== 'free';
127
+ }
128
+
129
+ function checkWorkflowAccess(workflow, options = {}) {
130
+ const context = resolveWorkflowAccessContext(options);
131
+ const policy = policies.getPolicyProfile(context.policyProfile, options);
132
+
133
+ if (policies.isWorkflowBlocked(workflow, policy)) {
134
+ return {
135
+ allowed: false,
136
+ code: 'workflow_policy_blocked',
137
+ reason: `Workflow ${workflow?.key || workflow?.name || 'unknown'} is blocked by ${policy.id} policy profile.`,
138
+ context
139
+ };
140
+ }
141
+
142
+ if (!isPremiumWorkflow(workflow)) {
143
+ return {
144
+ allowed: true,
145
+ code: 'workflow_free',
146
+ reason: 'Workflow is available on free tier.',
147
+ context
148
+ };
149
+ }
150
+
151
+ if (context.mode !== SERVER_MODE) {
152
+ return {
153
+ allowed: true,
154
+ code: 'workflow_local_mode',
155
+ reason: 'Premium workflows are enabled in local mode.',
156
+ context
157
+ };
158
+ }
159
+
160
+ if (context.entitled || PRO_TIERS.has(context.tier)) {
161
+ return {
162
+ allowed: true,
163
+ code: 'workflow_entitled',
164
+ reason: 'Premium workflow access granted.',
165
+ context
166
+ };
167
+ }
168
+
169
+ return {
170
+ allowed: false,
171
+ code: 'workflow_subscription_required',
172
+ reason: 'Premium workflows require entitlement in server mode. Set BOOTSPRING_WORKFLOWS_ENTITLED=true or use tier=pro/team/enterprise.',
173
+ context
174
+ };
175
+ }
176
+
177
+ function filterAccessibleWorkflows(workflows, options = {}) {
178
+ const allowed = [];
179
+ const denied = [];
180
+
181
+ for (const workflow of workflows || []) {
182
+ const decision = checkWorkflowAccess(workflow, options);
183
+ if (decision.allowed) {
184
+ allowed.push(workflow);
185
+ } else {
186
+ denied.push({
187
+ key: workflow?.key,
188
+ name: workflow?.name,
189
+ code: decision.code,
190
+ reason: decision.reason
191
+ });
192
+ }
193
+ }
194
+
195
+ return { allowed, denied };
196
+ }
197
+
198
+ module.exports = {
199
+ LOCAL_MODE,
200
+ SERVER_MODE,
201
+ resolveAccessContext,
202
+ resolveWorkflowAccessContext,
203
+ isExternalSkill,
204
+ checkSkillAccess,
205
+ filterAccessibleSkills,
206
+ isPremiumWorkflow,
207
+ checkWorkflowAccess,
208
+ filterAccessibleWorkflows
209
+ };
package/core/index.js ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Bootspring Core
3
+ * Main exports for the Bootspring package
4
+ *
5
+ * @package bootspring
6
+ * @module core
7
+ */
8
+
9
+ const config = require('./config');
10
+ const context = require('./context');
11
+ const utils = require('./utils');
12
+ const policies = require('./policies');
13
+ const entitlements = require('./entitlements');
14
+ const telemetry = require('./telemetry');
15
+ const packageJson = require('../package.json');
16
+
17
+ module.exports = {
18
+ // Version
19
+ VERSION: packageJson.version,
20
+
21
+ // Core modules
22
+ config,
23
+ context,
24
+ utils,
25
+ policies,
26
+ entitlements,
27
+ telemetry,
28
+
29
+ // Convenience exports
30
+ loadConfig: config.load,
31
+ getContext: context.get,
32
+ validateContext: context.validate,
33
+ checkSkillAccess: entitlements.checkSkillAccess,
34
+ checkWorkflowAccess: entitlements.checkWorkflowAccess,
35
+
36
+ // Brand info
37
+ BRAND: {
38
+ name: 'Bootspring',
39
+ tagline: 'Development scaffolding with intelligence',
40
+ website: 'https://bootspring.com',
41
+ docs: 'https://bootspring.com/docs'
42
+ }
43
+ };
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Bootspring policy profiles
3
+ * Team-level controls for capability gating.
4
+ */
5
+
6
+ const DEFAULT_POLICY_PROFILE = 'startup';
7
+
8
+ const POLICY_PROFILES = {
9
+ startup: {
10
+ id: 'startup',
11
+ name: 'Startup',
12
+ allowExternalSkills: true,
13
+ blockedWorkflows: []
14
+ },
15
+ regulated: {
16
+ id: 'regulated',
17
+ name: 'Regulated',
18
+ allowExternalSkills: false,
19
+ blockedWorkflows: ['growth-pack']
20
+ },
21
+ enterprise: {
22
+ id: 'enterprise',
23
+ name: 'Enterprise',
24
+ allowExternalSkills: true,
25
+ blockedWorkflows: []
26
+ }
27
+ };
28
+
29
+ function normalizeProfile(profile) {
30
+ const key = String(profile || DEFAULT_POLICY_PROFILE).trim().toLowerCase();
31
+ return POLICY_PROFILES[key] ? key : DEFAULT_POLICY_PROFILE;
32
+ }
33
+
34
+ function parseCsvList(value) {
35
+ if (!value) return [];
36
+ return String(value)
37
+ .split(',')
38
+ .map(item => item.trim())
39
+ .filter(Boolean);
40
+ }
41
+
42
+ function resolvePolicyProfile(options = {}) {
43
+ return normalizeProfile(options.policyProfile || process.env.BOOTSPRING_POLICY_PROFILE);
44
+ }
45
+
46
+ function getPolicyProfile(profile, options = {}) {
47
+ const key = normalizeProfile(profile);
48
+ const base = POLICY_PROFILES[key];
49
+ const blockedFromEnv = parseCsvList(options.blockedWorkflows || process.env.BOOTSPRING_POLICY_BLOCKED_WORKFLOWS);
50
+ return {
51
+ ...base,
52
+ blockedWorkflows: Array.from(new Set([...(base.blockedWorkflows || []), ...blockedFromEnv]))
53
+ };
54
+ }
55
+
56
+ function isWorkflowBlocked(workflow, profile) {
57
+ const workflowKey = String(workflow?.key || '').trim();
58
+ if (!workflowKey) return false;
59
+ return (profile.blockedWorkflows || []).includes(workflowKey);
60
+ }
61
+
62
+ module.exports = {
63
+ DEFAULT_POLICY_PROFILE,
64
+ POLICY_PROFILES,
65
+ resolvePolicyProfile,
66
+ getPolicyProfile,
67
+ isWorkflowBlocked
68
+ };