@girardmedia/bootspring 1.2.0 → 2.0.3
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/README.md +107 -14
- package/bin/bootspring.js +166 -27
- package/cli/agent.js +189 -17
- package/cli/analyze.js +499 -0
- package/cli/audit.js +557 -0
- package/cli/auth.js +495 -38
- package/cli/billing.js +302 -0
- package/cli/build.js +695 -0
- package/cli/business.js +109 -26
- package/cli/checkpoint-utils.js +168 -0
- package/cli/checkpoint.js +639 -0
- package/cli/cloud-sync.js +447 -0
- package/cli/content.js +198 -0
- package/cli/context.js +1 -1
- package/cli/deploy.js +543 -0
- package/cli/fundraise.js +112 -50
- package/cli/github-cmd.js +435 -0
- package/cli/health.js +477 -0
- package/cli/init.js +84 -13
- package/cli/legal.js +107 -95
- package/cli/log.js +2 -2
- package/cli/loop.js +976 -73
- package/cli/manager.js +711 -0
- package/cli/metrics.js +480 -0
- package/cli/monitor.js +812 -0
- package/cli/onboard.js +521 -0
- package/cli/orchestrator.js +12 -24
- package/cli/prd.js +594 -0
- package/cli/preseed-start.js +1483 -0
- package/cli/preseed.js +2302 -0
- package/cli/project.js +436 -0
- package/cli/quality.js +233 -0
- package/cli/security.js +913 -0
- package/cli/seed.js +1441 -5
- package/cli/skill.js +273 -211
- package/cli/suggest.js +989 -0
- package/cli/switch.js +453 -0
- package/cli/visualize.js +527 -0
- package/cli/watch.js +769 -0
- package/cli/workspace.js +607 -0
- package/core/analyze-workflow.js +1134 -0
- package/core/api-client.js +535 -22
- package/core/audit-workflow.js +1350 -0
- package/core/build-orchestrator.js +480 -0
- package/core/build-state.js +577 -0
- package/core/checkpoint-engine.js +408 -0
- package/core/config.js +1109 -26
- package/core/context-loader.js +21 -1
- package/core/deploy-workflow.js +836 -0
- package/core/entitlements.js +93 -22
- package/core/github-sync.js +610 -0
- package/core/index.js +8 -1
- package/core/ingest.js +1111 -0
- package/core/metrics-engine.js +768 -0
- package/core/onboard-workflow.js +1007 -0
- package/core/preseed-workflow.js +934 -0
- package/core/preseed.js +1617 -0
- package/core/project-context.js +325 -0
- package/core/project-state.js +694 -0
- package/core/r2-sync.js +583 -0
- package/core/scaffold.js +525 -7
- package/core/session.js +258 -0
- package/core/task-extractor.js +758 -0
- package/core/telemetry.js +28 -6
- package/core/tier-enforcement.js +737 -0
- package/core/utils.js +38 -14
- package/generators/questionnaire.js +15 -12
- package/generators/sections/ai.js +7 -7
- package/generators/sections/content.js +300 -0
- package/generators/sections/index.js +3 -0
- package/generators/sections/plugins.js +7 -6
- package/generators/templates/build-planning.template.js +596 -0
- package/generators/templates/content.template.js +819 -0
- package/generators/templates/index.js +2 -1
- package/hooks/git-autopilot.js +1250 -0
- package/hooks/index.js +9 -0
- package/intelligence/agent-collab.js +2057 -0
- package/intelligence/auto-suggest.js +634 -0
- package/intelligence/content-gen.js +1589 -0
- package/intelligence/cross-project.js +1647 -0
- package/intelligence/index.js +184 -0
- package/intelligence/learning/insights.json +517 -7
- package/intelligence/learning/pattern-learner.js +1008 -14
- package/intelligence/memory/decision-tracker.js +1431 -31
- package/intelligence/memory/decisions.jsonl +0 -0
- package/intelligence/orchestrator.js +2896 -1
- package/intelligence/prd.js +92 -1
- package/intelligence/recommendation-weights.json +14 -2
- package/intelligence/recommendations.js +463 -9
- package/intelligence/workflow-composer.js +1451 -0
- package/marketplace/index.d.ts +324 -0
- package/marketplace/index.js +1921 -0
- package/mcp/contracts/mcp-contract.v1.json +342 -4
- package/mcp/registry.js +680 -3
- package/mcp/response-formatter.js +23 -0
- package/mcp/tools/assist-tool.js +78 -4
- package/mcp/tools/autopilot-tool.js +408 -0
- package/mcp/tools/content-tool.js +571 -0
- package/mcp/tools/dashboard-tool.js +251 -5
- package/mcp/tools/mvp-tool.js +344 -0
- package/mcp/tools/plugin-tool.js +23 -1
- package/mcp/tools/prd-tool.js +579 -0
- package/mcp/tools/seed-tool.js +447 -0
- package/mcp/tools/skill-tool.js +43 -14
- package/mcp/tools/suggest-tool.js +147 -0
- package/package.json +15 -6
- package/agents/README.md +0 -93
- package/agents/ai-integration-expert/context.md +0 -386
- package/agents/api-expert/context.md +0 -416
- package/agents/architecture-expert/context.md +0 -454
- package/agents/auth-expert/context.md +0 -399
- package/agents/backend-expert/context.md +0 -483
- package/agents/business-strategy-expert/context.md +0 -180
- package/agents/code-review-expert/context.md +0 -365
- package/agents/competitive-analysis-expert/context.md +0 -239
- package/agents/data-modeling-expert/context.md +0 -352
- package/agents/database-expert/context.md +0 -250
- package/agents/devops-expert/context.md +0 -446
- package/agents/email-expert/context.md +0 -379
- package/agents/financial-expert/context.md +0 -213
- package/agents/frontend-expert/context.md +0 -364
- package/agents/fundraising-expert/context.md +0 -257
- package/agents/growth-expert/context.md +0 -249
- package/agents/index.js +0 -140
- package/agents/investor-relations-expert/context.md +0 -266
- package/agents/legal-expert/context.md +0 -284
- package/agents/marketing-expert/context.md +0 -236
- package/agents/monitoring-expert/context.md +0 -362
- package/agents/operations-expert/context.md +0 -279
- package/agents/partnerships-expert/context.md +0 -286
- package/agents/payment-expert/context.md +0 -340
- package/agents/performance-expert/context.md +0 -377
- package/agents/private-equity-expert/context.md +0 -246
- package/agents/railway-expert/context.md +0 -284
- package/agents/research-expert/context.md +0 -245
- package/agents/sales-expert/context.md +0 -241
- package/agents/security-expert/context.md +0 -343
- package/agents/testing-expert/context.md +0 -414
- package/agents/ui-ux-expert/context.md +0 -448
- package/agents/vercel-expert/context.md +0 -426
- package/skills/index.js +0 -787
- package/skills/patterns/README.md +0 -163
- package/skills/patterns/ai/agents.md +0 -281
- package/skills/patterns/ai/claude.md +0 -138
- package/skills/patterns/ai/embeddings.md +0 -150
- package/skills/patterns/ai/rag.md +0 -266
- package/skills/patterns/ai/streaming.md +0 -170
- package/skills/patterns/ai/structured-output.md +0 -162
- package/skills/patterns/ai/tools.md +0 -154
- package/skills/patterns/analytics/tracking.md +0 -220
- package/skills/patterns/api/errors.md +0 -296
- package/skills/patterns/api/graphql.md +0 -440
- package/skills/patterns/api/middleware.md +0 -279
- package/skills/patterns/api/openapi.md +0 -285
- package/skills/patterns/api/rate-limiting.md +0 -231
- package/skills/patterns/api/route-handler.md +0 -217
- package/skills/patterns/api/server-action.md +0 -249
- package/skills/patterns/api/versioning.md +0 -443
- package/skills/patterns/api/webhooks.md +0 -247
- package/skills/patterns/auth/clerk.md +0 -132
- package/skills/patterns/auth/mfa.md +0 -313
- package/skills/patterns/auth/nextauth.md +0 -140
- package/skills/patterns/auth/oauth.md +0 -237
- package/skills/patterns/auth/rbac.md +0 -152
- package/skills/patterns/auth/session-management.md +0 -367
- package/skills/patterns/auth/session.md +0 -120
- package/skills/patterns/database/audit.md +0 -177
- package/skills/patterns/database/migrations.md +0 -177
- package/skills/patterns/database/pagination.md +0 -230
- package/skills/patterns/database/pooling.md +0 -357
- package/skills/patterns/database/prisma.md +0 -180
- package/skills/patterns/database/relations.md +0 -187
- package/skills/patterns/database/seeding.md +0 -246
- package/skills/patterns/database/soft-delete.md +0 -153
- package/skills/patterns/database/transactions.md +0 -162
- package/skills/patterns/deployment/ci-cd.md +0 -231
- package/skills/patterns/deployment/docker.md +0 -188
- package/skills/patterns/deployment/monitoring.md +0 -387
- package/skills/patterns/deployment/vercel.md +0 -160
- package/skills/patterns/email/resend.md +0 -143
- package/skills/patterns/email/templates.md +0 -245
- package/skills/patterns/email/transactional.md +0 -503
- package/skills/patterns/email/verification.md +0 -176
- package/skills/patterns/files/download.md +0 -243
- package/skills/patterns/files/upload.md +0 -239
- package/skills/patterns/i18n/nextintl.md +0 -188
- package/skills/patterns/logging/structured.md +0 -292
- package/skills/patterns/notifications/email-queue.md +0 -248
- package/skills/patterns/notifications/push.md +0 -279
- package/skills/patterns/payments/checkout.md +0 -303
- package/skills/patterns/payments/invoices.md +0 -287
- package/skills/patterns/payments/portal.md +0 -245
- package/skills/patterns/payments/stripe.md +0 -272
- package/skills/patterns/payments/subscriptions.md +0 -300
- package/skills/patterns/payments/usage.md +0 -279
- package/skills/patterns/performance/caching.md +0 -276
- package/skills/patterns/performance/code-splitting.md +0 -233
- package/skills/patterns/performance/edge.md +0 -254
- package/skills/patterns/performance/isr.md +0 -266
- package/skills/patterns/performance/lazy-loading.md +0 -281
- package/skills/patterns/realtime/sse.md +0 -327
- package/skills/patterns/realtime/websockets.md +0 -336
- package/skills/patterns/search/filtering.md +0 -329
- package/skills/patterns/search/fulltext.md +0 -260
- package/skills/patterns/security/audit-logging.md +0 -444
- package/skills/patterns/security/csrf.md +0 -234
- package/skills/patterns/security/headers.md +0 -252
- package/skills/patterns/security/sanitization.md +0 -258
- package/skills/patterns/security/secrets.md +0 -261
- package/skills/patterns/security/validation.md +0 -268
- package/skills/patterns/security/xss.md +0 -229
- package/skills/patterns/seo/metadata.md +0 -252
- package/skills/patterns/state/context.md +0 -349
- package/skills/patterns/state/react-query.md +0 -313
- package/skills/patterns/state/url-state.md +0 -482
- package/skills/patterns/state/zustand.md +0 -262
- package/skills/patterns/testing/api.md +0 -259
- package/skills/patterns/testing/component.md +0 -233
- package/skills/patterns/testing/coverage.md +0 -207
- package/skills/patterns/testing/fixtures.md +0 -225
- package/skills/patterns/testing/integration.md +0 -436
- package/skills/patterns/testing/mocking.md +0 -177
- package/skills/patterns/testing/playwright.md +0 -162
- package/skills/patterns/testing/snapshot.md +0 -175
- package/skills/patterns/testing/vitest.md +0 -307
- package/skills/patterns/ui/accordions.md +0 -395
- package/skills/patterns/ui/cards.md +0 -299
- package/skills/patterns/ui/dropdowns.md +0 -476
- package/skills/patterns/ui/empty-states.md +0 -320
- package/skills/patterns/ui/forms.md +0 -405
- package/skills/patterns/ui/inputs.md +0 -319
- package/skills/patterns/ui/layouts.md +0 -282
- package/skills/patterns/ui/loading.md +0 -291
- package/skills/patterns/ui/modals.md +0 -338
- package/skills/patterns/ui/navigation.md +0 -374
- package/skills/patterns/ui/tables.md +0 -407
- package/skills/patterns/ui/toasts.md +0 -300
- package/skills/patterns/ui/tooltips.md +0 -396
- package/skills/patterns/utils/dates.md +0 -435
- package/skills/patterns/utils/errors.md +0 -451
- package/skills/patterns/utils/formatting.md +0 -345
- package/skills/patterns/utils/validation.md +0 -434
- package/templates/bootspring.config.js +0 -83
- package/templates/business/business-model-canvas.md +0 -246
- package/templates/business/business-plan.md +0 -266
- package/templates/business/competitive-analysis.md +0 -312
- package/templates/fundraising/data-room-checklist.md +0 -300
- package/templates/fundraising/investor-research.md +0 -243
- package/templates/fundraising/pitch-deck-outline.md +0 -253
- package/templates/legal/gdpr-checklist.md +0 -339
- package/templates/legal/privacy-policy.md +0 -285
- package/templates/legal/terms-of-service.md +0 -222
- package/templates/mcp.json +0 -9
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring GitHub Sync
|
|
3
|
+
* Fetch GitHub repository data using gh CLI
|
|
4
|
+
*
|
|
5
|
+
* @package bootspring
|
|
6
|
+
* @module core/github-sync
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { execSync, exec } = require('child_process');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const utils = require('./utils');
|
|
13
|
+
const projectState = require('./project-state');
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// gh CLI Utilities
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Check if gh CLI is installed
|
|
21
|
+
* @returns {boolean} True if gh is available
|
|
22
|
+
*/
|
|
23
|
+
function isGhInstalled() {
|
|
24
|
+
try {
|
|
25
|
+
execSync('gh --version', { stdio: 'pipe' });
|
|
26
|
+
return true;
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Check if gh CLI is authenticated
|
|
34
|
+
* @returns {boolean} True if authenticated
|
|
35
|
+
*/
|
|
36
|
+
function isGhAuthenticated() {
|
|
37
|
+
try {
|
|
38
|
+
const result = execSync('gh auth status', { stdio: 'pipe', encoding: 'utf-8' });
|
|
39
|
+
return !result.includes('not logged');
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Execute gh command and return parsed JSON
|
|
47
|
+
* @param {string} command - gh command (without 'gh' prefix)
|
|
48
|
+
* @param {object} options - Options
|
|
49
|
+
* @returns {object|null} Parsed result or null on error
|
|
50
|
+
*/
|
|
51
|
+
function ghCommand(command, options = {}) {
|
|
52
|
+
try {
|
|
53
|
+
const result = execSync(`gh ${command}`, {
|
|
54
|
+
cwd: options.cwd || process.cwd(),
|
|
55
|
+
encoding: 'utf-8',
|
|
56
|
+
timeout: options.timeout || 30000,
|
|
57
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
58
|
+
});
|
|
59
|
+
return options.parseJson !== false ? JSON.parse(result) : result.trim();
|
|
60
|
+
} catch (error) {
|
|
61
|
+
utils.print.debug(`gh command failed: ${error.message}`);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Execute gh command asynchronously
|
|
68
|
+
* @param {string} command - gh command
|
|
69
|
+
* @param {object} options - Options
|
|
70
|
+
* @returns {Promise<object|null>} Parsed result or null on error
|
|
71
|
+
*/
|
|
72
|
+
function ghCommandAsync(command, options = {}) {
|
|
73
|
+
return new Promise((resolve) => {
|
|
74
|
+
exec(`gh ${command}`, {
|
|
75
|
+
cwd: options.cwd || process.cwd(),
|
|
76
|
+
timeout: options.timeout || 30000
|
|
77
|
+
}, (error, stdout) => {
|
|
78
|
+
if (error) {
|
|
79
|
+
utils.print.debug(`gh command failed: ${error.message}`);
|
|
80
|
+
resolve(null);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
resolve(options.parseJson !== false ? JSON.parse(stdout) : stdout.trim());
|
|
85
|
+
} catch {
|
|
86
|
+
resolve(null);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// Repository Detection
|
|
94
|
+
// ============================================================================
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Detect GitHub repository from git remote
|
|
98
|
+
* @param {string} projectRoot - Project root directory
|
|
99
|
+
* @returns {object|null} Repository info { owner, repo, url } or null
|
|
100
|
+
*/
|
|
101
|
+
function detectRepository(projectRoot) {
|
|
102
|
+
try {
|
|
103
|
+
const result = execSync('git remote get-url origin', {
|
|
104
|
+
cwd: projectRoot,
|
|
105
|
+
encoding: 'utf-8',
|
|
106
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
107
|
+
}).trim();
|
|
108
|
+
|
|
109
|
+
// Parse GitHub URL (SSH or HTTPS)
|
|
110
|
+
// HTTPS: https://github.com/owner/repo.git
|
|
111
|
+
// SSH: git@github.com:owner/repo.git
|
|
112
|
+
let match;
|
|
113
|
+
|
|
114
|
+
if (result.startsWith('https://')) {
|
|
115
|
+
match = result.match(/github\.com\/([^/]+)\/([^/.]+)/);
|
|
116
|
+
} else if (result.includes('github.com')) {
|
|
117
|
+
match = result.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (match) {
|
|
121
|
+
return {
|
|
122
|
+
owner: match[1],
|
|
123
|
+
repo: match[2].replace('.git', ''),
|
|
124
|
+
url: `https://github.com/${match[1]}/${match[2].replace('.git', '')}`
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
// Not a git repo or no remote
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get default branch name
|
|
136
|
+
* @param {string} projectRoot - Project root directory
|
|
137
|
+
* @returns {string} Default branch name
|
|
138
|
+
*/
|
|
139
|
+
function getDefaultBranch(projectRoot) {
|
|
140
|
+
try {
|
|
141
|
+
// Try to get from remote
|
|
142
|
+
const result = execSync('git remote show origin | grep "HEAD branch"', {
|
|
143
|
+
cwd: projectRoot,
|
|
144
|
+
encoding: 'utf-8',
|
|
145
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
146
|
+
});
|
|
147
|
+
const match = result.match(/HEAD branch:\s*(\S+)/);
|
|
148
|
+
if (match) {
|
|
149
|
+
return match[1];
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
// Fall back to checking common branch names
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Check if main or master exists
|
|
156
|
+
try {
|
|
157
|
+
execSync('git rev-parse --verify main', { cwd: projectRoot, stdio: 'pipe' });
|
|
158
|
+
return 'main';
|
|
159
|
+
} catch {
|
|
160
|
+
try {
|
|
161
|
+
execSync('git rev-parse --verify master', { cwd: projectRoot, stdio: 'pipe' });
|
|
162
|
+
return 'master';
|
|
163
|
+
} catch {
|
|
164
|
+
return 'main';
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ============================================================================
|
|
170
|
+
// Repository Data Fetching
|
|
171
|
+
// ============================================================================
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get repository metadata
|
|
175
|
+
* @param {string} owner - Repository owner
|
|
176
|
+
* @param {string} repo - Repository name
|
|
177
|
+
* @returns {object|null} Repository metadata
|
|
178
|
+
*/
|
|
179
|
+
function getRepositoryInfo(owner, repo) {
|
|
180
|
+
return ghCommand(`repo view ${owner}/${repo} --json name,description,defaultBranchRef,url,createdAt,updatedAt,stargazerCount,forkCount,isPrivate`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get commit statistics
|
|
185
|
+
* @param {string} owner - Repository owner
|
|
186
|
+
* @param {string} repo - Repository name
|
|
187
|
+
* @param {object} options - Options
|
|
188
|
+
* @returns {object} Commit stats
|
|
189
|
+
*/
|
|
190
|
+
function getCommitStats(owner, repo, options = {}) {
|
|
191
|
+
const limit = options.limit || 100;
|
|
192
|
+
|
|
193
|
+
// Get recent commits
|
|
194
|
+
const commits = ghCommand(`api repos/${owner}/${repo}/commits?per_page=${limit}`, { parseJson: true });
|
|
195
|
+
|
|
196
|
+
if (!commits) {
|
|
197
|
+
return {
|
|
198
|
+
totalCommits: 0,
|
|
199
|
+
lastCommit: null,
|
|
200
|
+
lastCommitMessage: null,
|
|
201
|
+
lastCommitAuthor: null
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const latest = commits[0];
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
totalCommits: commits.length, // Note: This is limited to what we fetch
|
|
209
|
+
lastCommit: latest?.commit?.committer?.date || null,
|
|
210
|
+
lastCommitMessage: latest?.commit?.message?.split('\n')[0] || null,
|
|
211
|
+
lastCommitAuthor: latest?.commit?.author?.name || null,
|
|
212
|
+
lastCommitSha: latest?.sha?.substring(0, 7) || null
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get pull request statistics
|
|
218
|
+
* @param {string} owner - Repository owner
|
|
219
|
+
* @param {string} repo - Repository name
|
|
220
|
+
* @returns {object} PR stats
|
|
221
|
+
*/
|
|
222
|
+
function getPRStats(owner, repo) {
|
|
223
|
+
// Get open PRs
|
|
224
|
+
const openPRs = ghCommand(`pr list --repo ${owner}/${repo} --state open --json number`, { parseJson: true });
|
|
225
|
+
|
|
226
|
+
// Get recently closed PRs
|
|
227
|
+
const closedPRs = ghCommand(`pr list --repo ${owner}/${repo} --state closed --limit 50 --json number`, { parseJson: true });
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
openPRs: openPRs?.length || 0,
|
|
231
|
+
closedPRs: closedPRs?.length || 0
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Get contributor count
|
|
237
|
+
* @param {string} owner - Repository owner
|
|
238
|
+
* @param {string} repo - Repository name
|
|
239
|
+
* @returns {number} Contributor count
|
|
240
|
+
*/
|
|
241
|
+
function getContributorCount(owner, repo) {
|
|
242
|
+
const contributors = ghCommand(`api repos/${owner}/${repo}/contributors?per_page=100`, { parseJson: true });
|
|
243
|
+
return contributors?.length || 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Get recent activity (commits + PRs)
|
|
248
|
+
* @param {string} owner - Repository owner
|
|
249
|
+
* @param {string} repo - Repository name
|
|
250
|
+
* @param {number} limit - Number of items
|
|
251
|
+
* @returns {Array} Activity items
|
|
252
|
+
*/
|
|
253
|
+
function getRecentActivity(owner, repo, limit = 10) {
|
|
254
|
+
const activity = [];
|
|
255
|
+
|
|
256
|
+
// Get recent commits
|
|
257
|
+
const commits = ghCommand(`api repos/${owner}/${repo}/commits?per_page=${limit}`, { parseJson: true });
|
|
258
|
+
if (commits) {
|
|
259
|
+
for (const commit of commits.slice(0, 5)) {
|
|
260
|
+
activity.push({
|
|
261
|
+
type: 'commit',
|
|
262
|
+
message: commit.commit?.message?.split('\n')[0],
|
|
263
|
+
author: commit.commit?.author?.name,
|
|
264
|
+
date: commit.commit?.committer?.date,
|
|
265
|
+
sha: commit.sha?.substring(0, 7)
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Get recent PRs
|
|
271
|
+
const prs = ghCommand(`pr list --repo ${owner}/${repo} --state all --limit 5 --json number,title,author,state,createdAt`, { parseJson: true });
|
|
272
|
+
if (prs) {
|
|
273
|
+
for (const pr of prs) {
|
|
274
|
+
activity.push({
|
|
275
|
+
type: 'pr',
|
|
276
|
+
number: pr.number,
|
|
277
|
+
title: pr.title,
|
|
278
|
+
author: pr.author?.login,
|
|
279
|
+
state: pr.state,
|
|
280
|
+
date: pr.createdAt
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Sort by date
|
|
286
|
+
activity.sort((a, b) => new Date(b.date) - new Date(a.date));
|
|
287
|
+
|
|
288
|
+
return activity.slice(0, limit);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ============================================================================
|
|
292
|
+
// Sync Functions
|
|
293
|
+
// ============================================================================
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Full sync of GitHub data to project state
|
|
297
|
+
* @param {string} projectRoot - Project root directory
|
|
298
|
+
* @param {object} options - Options
|
|
299
|
+
* @returns {object} Sync result
|
|
300
|
+
*/
|
|
301
|
+
function syncGitHubData(projectRoot, options = {}) {
|
|
302
|
+
const state = projectState.loadState(projectRoot);
|
|
303
|
+
|
|
304
|
+
if (!state?.github?.connected) {
|
|
305
|
+
return {
|
|
306
|
+
success: false,
|
|
307
|
+
error: 'GitHub not connected'
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const { owner, repo } = state.github;
|
|
312
|
+
|
|
313
|
+
// Check gh CLI
|
|
314
|
+
if (!isGhInstalled()) {
|
|
315
|
+
return {
|
|
316
|
+
success: false,
|
|
317
|
+
error: 'gh CLI not installed'
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (!isGhAuthenticated()) {
|
|
322
|
+
return {
|
|
323
|
+
success: false,
|
|
324
|
+
error: 'gh CLI not authenticated'
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Fetch data
|
|
329
|
+
const spinner = options.spinner;
|
|
330
|
+
if (spinner) spinner.start();
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
// Get commit stats
|
|
334
|
+
const commitStats = getCommitStats(owner, repo);
|
|
335
|
+
|
|
336
|
+
// Get PR stats
|
|
337
|
+
const prStats = getPRStats(owner, repo);
|
|
338
|
+
|
|
339
|
+
// Get contributor count
|
|
340
|
+
const contributors = getContributorCount(owner, repo);
|
|
341
|
+
|
|
342
|
+
// Update state
|
|
343
|
+
const updatedState = projectState.updateGitHubState(projectRoot, {
|
|
344
|
+
connected: true,
|
|
345
|
+
repositoryUrl: state.github.repositoryUrl,
|
|
346
|
+
owner,
|
|
347
|
+
repo,
|
|
348
|
+
defaultBranch: state.github.defaultBranch,
|
|
349
|
+
stats: {
|
|
350
|
+
totalCommits: commitStats.totalCommits,
|
|
351
|
+
openPRs: prStats.openPRs,
|
|
352
|
+
closedPRs: prStats.closedPRs,
|
|
353
|
+
contributors,
|
|
354
|
+
lastCommit: commitStats.lastCommit,
|
|
355
|
+
lastCommitMessage: commitStats.lastCommitMessage,
|
|
356
|
+
lastCommitSha: commitStats.lastCommitSha
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// Save sync metadata
|
|
361
|
+
saveGitHubSyncMetadata(projectRoot, {
|
|
362
|
+
lastSync: new Date().toISOString(),
|
|
363
|
+
commitStats,
|
|
364
|
+
prStats,
|
|
365
|
+
contributors
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
success: true,
|
|
370
|
+
stats: updatedState.github.stats
|
|
371
|
+
};
|
|
372
|
+
} catch (error) {
|
|
373
|
+
return {
|
|
374
|
+
success: false,
|
|
375
|
+
error: error.message
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Connect to a GitHub repository
|
|
382
|
+
* @param {string} projectRoot - Project root directory
|
|
383
|
+
* @param {object} options - Options
|
|
384
|
+
* @returns {object} Connection result
|
|
385
|
+
*/
|
|
386
|
+
function connectRepository(projectRoot, options = {}) {
|
|
387
|
+
// Check gh CLI
|
|
388
|
+
if (!isGhInstalled()) {
|
|
389
|
+
return {
|
|
390
|
+
success: false,
|
|
391
|
+
error: 'gh CLI not installed. Install from https://cli.github.com/'
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (!isGhAuthenticated()) {
|
|
396
|
+
return {
|
|
397
|
+
success: false,
|
|
398
|
+
error: 'gh CLI not authenticated. Run: gh auth login'
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Get repository info
|
|
403
|
+
let owner, repo, url;
|
|
404
|
+
|
|
405
|
+
if (options.url) {
|
|
406
|
+
// Parse provided URL
|
|
407
|
+
const match = options.url.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
408
|
+
if (!match) {
|
|
409
|
+
return {
|
|
410
|
+
success: false,
|
|
411
|
+
error: 'Invalid GitHub URL'
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
owner = match[1];
|
|
415
|
+
repo = match[2].replace('.git', '').replace(/\/$/, '');
|
|
416
|
+
url = `https://github.com/${owner}/${repo}`;
|
|
417
|
+
} else {
|
|
418
|
+
// Auto-detect from git remote
|
|
419
|
+
const detected = detectRepository(projectRoot);
|
|
420
|
+
if (!detected) {
|
|
421
|
+
return {
|
|
422
|
+
success: false,
|
|
423
|
+
error: 'Could not detect GitHub repository. Use --url to specify manually.'
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
owner = detected.owner;
|
|
427
|
+
repo = detected.repo;
|
|
428
|
+
url = detected.url;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Verify repository access
|
|
432
|
+
const repoInfo = getRepositoryInfo(owner, repo);
|
|
433
|
+
if (!repoInfo) {
|
|
434
|
+
return {
|
|
435
|
+
success: false,
|
|
436
|
+
error: `Could not access repository: ${owner}/${repo}`
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Get default branch
|
|
441
|
+
const defaultBranch = repoInfo.defaultBranchRef?.name || getDefaultBranch(projectRoot);
|
|
442
|
+
|
|
443
|
+
// Update project state
|
|
444
|
+
const state = projectState.getOrCreateState(projectRoot);
|
|
445
|
+
|
|
446
|
+
projectState.updateGitHubState(projectRoot, {
|
|
447
|
+
connected: true,
|
|
448
|
+
repositoryUrl: url,
|
|
449
|
+
owner,
|
|
450
|
+
repo,
|
|
451
|
+
defaultBranch,
|
|
452
|
+
stats: {
|
|
453
|
+
totalCommits: 0,
|
|
454
|
+
openPRs: 0,
|
|
455
|
+
closedPRs: 0,
|
|
456
|
+
contributors: 0,
|
|
457
|
+
lastCommit: null,
|
|
458
|
+
lastCommitMessage: null
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// Initial sync
|
|
463
|
+
const syncResult = syncGitHubData(projectRoot);
|
|
464
|
+
|
|
465
|
+
return {
|
|
466
|
+
success: true,
|
|
467
|
+
owner,
|
|
468
|
+
repo,
|
|
469
|
+
url,
|
|
470
|
+
defaultBranch,
|
|
471
|
+
description: repoInfo.description,
|
|
472
|
+
isPrivate: repoInfo.isPrivate,
|
|
473
|
+
stats: syncResult.stats
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Disconnect from GitHub repository
|
|
479
|
+
* @param {string} projectRoot - Project root directory
|
|
480
|
+
* @returns {object} Disconnection result
|
|
481
|
+
*/
|
|
482
|
+
function disconnectRepository(projectRoot) {
|
|
483
|
+
const state = projectState.loadState(projectRoot);
|
|
484
|
+
|
|
485
|
+
if (!state?.github?.connected) {
|
|
486
|
+
return {
|
|
487
|
+
success: false,
|
|
488
|
+
error: 'GitHub not connected'
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
projectState.clearGitHubState(projectRoot);
|
|
493
|
+
|
|
494
|
+
return {
|
|
495
|
+
success: true
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// ============================================================================
|
|
500
|
+
// Sync Metadata
|
|
501
|
+
// ============================================================================
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Save GitHub sync metadata
|
|
505
|
+
* @param {string} projectRoot - Project root directory
|
|
506
|
+
* @param {object} metadata - Metadata to save
|
|
507
|
+
*/
|
|
508
|
+
function saveGitHubSyncMetadata(projectRoot, metadata) {
|
|
509
|
+
const syncFile = projectState.getGitHubSyncFilePath(projectRoot);
|
|
510
|
+
const planningDir = projectState.getPlanningDir(projectRoot);
|
|
511
|
+
|
|
512
|
+
try {
|
|
513
|
+
if (!fs.existsSync(planningDir)) {
|
|
514
|
+
fs.mkdirSync(planningDir, { recursive: true });
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
fs.writeFileSync(syncFile, JSON.stringify(metadata, null, 2), 'utf-8');
|
|
518
|
+
} catch (error) {
|
|
519
|
+
utils.print.debug(`Error saving sync metadata: ${error.message}`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Load GitHub sync metadata
|
|
525
|
+
* @param {string} projectRoot - Project root directory
|
|
526
|
+
* @returns {object|null} Metadata or null
|
|
527
|
+
*/
|
|
528
|
+
function loadGitHubSyncMetadata(projectRoot) {
|
|
529
|
+
const syncFile = projectState.getGitHubSyncFilePath(projectRoot);
|
|
530
|
+
|
|
531
|
+
if (!fs.existsSync(syncFile)) {
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
try {
|
|
536
|
+
return JSON.parse(fs.readFileSync(syncFile, 'utf-8'));
|
|
537
|
+
} catch {
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// ============================================================================
|
|
543
|
+
// Status Functions
|
|
544
|
+
// ============================================================================
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Get GitHub connection status
|
|
548
|
+
* @param {string} projectRoot - Project root directory
|
|
549
|
+
* @returns {object} Status object
|
|
550
|
+
*/
|
|
551
|
+
function getStatus(projectRoot) {
|
|
552
|
+
const state = projectState.loadState(projectRoot);
|
|
553
|
+
|
|
554
|
+
if (!state?.github?.connected) {
|
|
555
|
+
return {
|
|
556
|
+
connected: false,
|
|
557
|
+
ghInstalled: isGhInstalled(),
|
|
558
|
+
ghAuthenticated: isGhInstalled() && isGhAuthenticated(),
|
|
559
|
+
detected: detectRepository(projectRoot)
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const syncMetadata = loadGitHubSyncMetadata(projectRoot);
|
|
564
|
+
|
|
565
|
+
return {
|
|
566
|
+
connected: true,
|
|
567
|
+
repositoryUrl: state.github.repositoryUrl,
|
|
568
|
+
owner: state.github.owner,
|
|
569
|
+
repo: state.github.repo,
|
|
570
|
+
defaultBranch: state.github.defaultBranch,
|
|
571
|
+
lastSync: state.github.lastSync,
|
|
572
|
+
stats: state.github.stats,
|
|
573
|
+
syncMetadata
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// ============================================================================
|
|
578
|
+
// Exports
|
|
579
|
+
// ============================================================================
|
|
580
|
+
|
|
581
|
+
module.exports = {
|
|
582
|
+
// gh CLI utilities
|
|
583
|
+
isGhInstalled,
|
|
584
|
+
isGhAuthenticated,
|
|
585
|
+
ghCommand,
|
|
586
|
+
ghCommandAsync,
|
|
587
|
+
|
|
588
|
+
// Repository detection
|
|
589
|
+
detectRepository,
|
|
590
|
+
getDefaultBranch,
|
|
591
|
+
|
|
592
|
+
// Data fetching
|
|
593
|
+
getRepositoryInfo,
|
|
594
|
+
getCommitStats,
|
|
595
|
+
getPRStats,
|
|
596
|
+
getContributorCount,
|
|
597
|
+
getRecentActivity,
|
|
598
|
+
|
|
599
|
+
// Sync operations
|
|
600
|
+
syncGitHubData,
|
|
601
|
+
connectRepository,
|
|
602
|
+
disconnectRepository,
|
|
603
|
+
|
|
604
|
+
// Metadata
|
|
605
|
+
saveGitHubSyncMetadata,
|
|
606
|
+
loadGitHubSyncMetadata,
|
|
607
|
+
|
|
608
|
+
// Status
|
|
609
|
+
getStatus
|
|
610
|
+
};
|
package/core/index.js
CHANGED
|
@@ -11,6 +11,7 @@ const context = require('./context');
|
|
|
11
11
|
const utils = require('./utils');
|
|
12
12
|
const policies = require('./policies');
|
|
13
13
|
const entitlements = require('./entitlements');
|
|
14
|
+
const tierEnforcement = require('./tier-enforcement');
|
|
14
15
|
const telemetry = require('./telemetry');
|
|
15
16
|
const auth = require('./auth');
|
|
16
17
|
const api = require('./api-client');
|
|
@@ -26,6 +27,7 @@ module.exports = {
|
|
|
26
27
|
utils,
|
|
27
28
|
policies,
|
|
28
29
|
entitlements,
|
|
30
|
+
tierEnforcement,
|
|
29
31
|
telemetry,
|
|
30
32
|
auth,
|
|
31
33
|
api,
|
|
@@ -37,10 +39,15 @@ module.exports = {
|
|
|
37
39
|
checkSkillAccess: entitlements.checkSkillAccess,
|
|
38
40
|
checkWorkflowAccess: entitlements.checkWorkflowAccess,
|
|
39
41
|
|
|
42
|
+
// Tier enforcement convenience exports
|
|
43
|
+
getTier: tierEnforcement.getTier,
|
|
44
|
+
checkAgentAccess: tierEnforcement.checkAgentAccess,
|
|
45
|
+
hasFeature: tierEnforcement.hasFeature,
|
|
46
|
+
meetsTierRequirement: tierEnforcement.meetsTierRequirement,
|
|
47
|
+
|
|
40
48
|
// Auth convenience exports
|
|
41
49
|
isAuthenticated: auth.isAuthenticated,
|
|
42
50
|
getUser: auth.getUser,
|
|
43
|
-
getTier: auth.getTier,
|
|
44
51
|
|
|
45
52
|
// Brand info
|
|
46
53
|
BRAND: {
|