fraim-framework 2.0.35 → 2.0.37
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/dist/registry/scripts/cleanup-branch.js +62 -33
- package/dist/registry/scripts/generate-engagement-emails.js +119 -44
- package/dist/registry/scripts/newsletter-helpers.js +208 -268
- package/dist/registry/scripts/profile-server.js +387 -0
- package/dist/scripts/build-stub-registry.js +108 -0
- package/dist/src/cli/commands/doctor.js +5 -5
- package/dist/src/cli/commands/sync.js +33 -19
- package/dist/tests/test-client-scripts-validation.js +133 -0
- package/dist/tests/test-package-size.js +88 -0
- package/dist/tests/test-script-location-independence.js +76 -28
- package/dist/tests/test-stub-registry.js +120 -0
- package/dist/tests/test-sync-stubs.js +143 -0
- package/package.json +7 -9
- package/registry/scripts/cleanup-branch.ts +341 -0
- package/registry/scripts/generate-engagement-emails.ts +830 -0
- package/registry/scripts/markdown-to-pdf.js +7 -3
- package/registry/scripts/newsletter-helpers.ts +777 -0
- package/registry/scripts/profile-server.ts +424 -0
- package/registry/scripts/run-thank-you-workflow.ts +122 -0
- package/registry/scripts/send-newsletter-simple.ts +102 -0
- package/registry/scripts/send-thank-you-emails.ts +57 -0
- package/registry/stubs/workflows/bootstrap/create-architecture.md +11 -0
- package/registry/stubs/workflows/bootstrap/detect-broken-windows.md +11 -0
- package/registry/stubs/workflows/bootstrap/evaluate-code-quality.md +11 -0
- package/registry/stubs/workflows/bootstrap/verify-test-coverage.md +11 -0
- package/registry/stubs/workflows/business-development/create-business-plan.md +11 -0
- package/registry/stubs/workflows/business-development/ideate-business-opportunity.md +11 -0
- package/registry/stubs/workflows/business-development/price-product.md +18 -0
- package/registry/stubs/workflows/convert-to-pdf.md +11 -0
- package/registry/stubs/workflows/customer-development/insight-analysis.md +11 -0
- package/registry/stubs/workflows/customer-development/insight-triage.md +11 -0
- package/registry/stubs/workflows/customer-development/interview-preparation.md +11 -0
- package/registry/stubs/workflows/customer-development/linkedin-outreach.md +11 -0
- package/registry/stubs/workflows/customer-development/strategic-brainstorming.md +11 -0
- package/registry/stubs/workflows/customer-development/thank-customers.md +11 -0
- package/registry/stubs/workflows/customer-development/weekly-newsletter.md +11 -0
- package/registry/stubs/workflows/deploy/cloud-deployment.md +11 -0
- package/registry/stubs/workflows/improve-fraim/contribute.md +11 -0
- package/registry/stubs/workflows/improve-fraim/file-issue.md +11 -0
- package/registry/stubs/workflows/marketing/content-creation.md +11 -0
- package/registry/stubs/workflows/marketing/hbr-article.md +11 -0
- package/registry/stubs/workflows/marketing/launch-checklist.md +11 -0
- package/registry/stubs/workflows/marketing/marketing-strategy.md +11 -0
- package/registry/stubs/workflows/marketing/storytelling.md +11 -0
- package/registry/stubs/workflows/performance/analyze-performance.md +11 -0
- package/registry/stubs/workflows/product-building/design.md +11 -0
- package/registry/stubs/workflows/product-building/implement.md +12 -0
- package/registry/stubs/workflows/product-building/iterate-on-pr-comments.md +11 -0
- package/registry/stubs/workflows/product-building/prep-issue.md +11 -0
- package/registry/stubs/workflows/product-building/prototype.md +11 -0
- package/registry/stubs/workflows/product-building/resolve.md +11 -0
- package/registry/stubs/workflows/product-building/retrospect.md +11 -0
- package/registry/stubs/workflows/product-building/spec.md +11 -0
- package/registry/stubs/workflows/product-building/test.md +11 -0
- package/registry/stubs/workflows/quality-assurance/browser-validation.md +11 -0
- package/registry/stubs/workflows/quality-assurance/iterative-improvement-cycle.md +11 -0
- package/registry/stubs/workflows/replicate/replicate-discovery.md +11 -0
- package/registry/stubs/workflows/replicate/replicate-to-issues.md +11 -0
- package/registry/stubs/workflows/reviewer/review-implementation-vs-design-spec.md +11 -0
- package/registry/stubs/workflows/reviewer/review-implementation-vs-feature-spec.md +11 -0
- package/registry/stubs/workflows/startup-credits/aws-activate-application.md +11 -0
- package/registry/stubs/workflows/startup-credits/google-cloud-application.md +11 -0
- package/registry/stubs/workflows/startup-credits/microsoft-azure-application.md +11 -0
- package/.github/workflows/ci.yml +0 -65
- package/.github/workflows/deploy-fraim.yml +0 -87
- package/.github/workflows/phase-change.yml +0 -251
- package/.github/workflows/status-change.yml +0 -68
- package/.github/workflows/sync-on-pr-review.yml +0 -66
- package/examples/simple-webapp/TESTING.md +0 -62
- package/examples/simple-webapp/example-test.ts +0 -186
- package/registry/github/workflows/ci.yml +0 -51
- package/registry/github/workflows/phase-change.yml +0 -251
- package/registry/github/workflows/status-change.yml +0 -68
- package/registry/github/workflows/sync-on-pr-review.yml +0 -66
- package/registry/mcp-template.jsonc +0 -29
- package/registry/rules/agent-success-criteria.md +0 -52
- package/registry/rules/agent-testing-guidelines.md +0 -502
- package/registry/rules/architecture.md +0 -52
- package/registry/rules/communication.md +0 -122
- package/registry/rules/continuous-learning.md +0 -55
- package/registry/rules/debugging-multitenancy-issues.md +0 -85
- package/registry/rules/ephemeral-execution.md +0 -57
- package/registry/rules/git-safe-commands.md +0 -34
- package/registry/rules/hitl-ppe-record-analysis.md +0 -302
- package/registry/rules/integrity-and-test-ethics.md +0 -275
- package/registry/rules/local-development.md +0 -254
- package/registry/rules/merge-requirements.md +0 -231
- package/registry/rules/simplicity.md +0 -118
- package/registry/rules/software-development-lifecycle.md +0 -105
- package/registry/rules/spike-first-development.md +0 -205
- package/registry/rules/successful-debugging-patterns.md +0 -491
- package/registry/rules/telemetry.md +0 -67
- package/registry/templates/bootstrap/ARCHITECTURE-TEMPLATE.md +0 -53
- package/registry/templates/bootstrap/CODE-QUALITY-REPORT-TEMPLATE.md +0 -37
- package/registry/templates/bootstrap/TEST-COVERAGE-REPORT-TEMPLATE.md +0 -35
- package/registry/templates/business-development/IDEATION-REPORT-TEMPLATE.md +0 -29
- package/registry/templates/business-development/PRICING-STRATEGY-TEMPLATE.md +0 -126
- package/registry/templates/customer-development/customer-interview-template.md +0 -99
- package/registry/templates/customer-development/follow-up-email-templates.md +0 -132
- package/registry/templates/customer-development/insight-analysis-template.md +0 -74
- package/registry/templates/customer-development/strategic-recommendations-template.md +0 -53
- package/registry/templates/customer-development/thank-you-email-template.html +0 -124
- package/registry/templates/customer-development/thank-you-note-template.md +0 -16
- package/registry/templates/customer-development/triage-log-template.md +0 -278
- package/registry/templates/customer-development/weekly-newsletter-template.html +0 -204
- package/registry/templates/evidence/Design-Evidence.md +0 -30
- package/registry/templates/evidence/Implementation-BugEvidence.md +0 -86
- package/registry/templates/evidence/Implementation-FeatureEvidence.md +0 -121
- package/registry/templates/evidence/Spec-Evidence.md +0 -19
- package/registry/templates/help/HelpNeeded.md +0 -14
- package/registry/templates/marketing/HBR-ARTICLE-TEMPLATE.md +0 -66
- package/registry/templates/marketing/STORYTELLING-TEMPLATE.md +0 -130
- package/registry/templates/replicate/implementation-checklist.md +0 -39
- package/registry/templates/replicate/use-cases-template.md +0 -88
- package/registry/templates/retrospective/RETROSPECTIVE-TEMPLATE.md +0 -55
- package/registry/templates/specs/BUGSPEC-TEMPLATE.md +0 -37
- package/registry/templates/specs/FEATURESPEC-TEMPLATE.md +0 -29
- package/registry/templates/specs/TECHSPEC-TEMPLATE.md +0 -39
- package/registry/workflows/bootstrap/create-architecture.md +0 -38
- package/registry/workflows/bootstrap/evaluate-code-quality.md +0 -36
- package/registry/workflows/bootstrap/verify-test-coverage.md +0 -37
- package/registry/workflows/business-development/create-business-plan.md +0 -737
- package/registry/workflows/business-development/ideate-business-opportunity.md +0 -55
- package/registry/workflows/business-development/price-product.md +0 -325
- package/registry/workflows/convert-to-pdf.md +0 -235
- package/registry/workflows/customer-development/insight-analysis.md +0 -156
- package/registry/workflows/customer-development/insight-triage.md +0 -933
- package/registry/workflows/customer-development/interview-preparation.md +0 -421
- package/registry/workflows/customer-development/linkedin-outreach.md +0 -593
- package/registry/workflows/customer-development/strategic-brainstorming.md +0 -146
- package/registry/workflows/customer-development/thank-customers.md +0 -203
- package/registry/workflows/customer-development/weekly-newsletter.md +0 -366
- package/registry/workflows/deploy/cloud-deployment.md +0 -310
- package/registry/workflows/improve-fraim/contribute.md +0 -32
- package/registry/workflows/improve-fraim/file-issue.md +0 -32
- package/registry/workflows/marketing/content-creation.md +0 -37
- package/registry/workflows/marketing/hbr-article.md +0 -73
- package/registry/workflows/marketing/launch-checklist.md +0 -37
- package/registry/workflows/marketing/marketing-strategy.md +0 -45
- package/registry/workflows/marketing/storytelling.md +0 -65
- package/registry/workflows/performance/analyze-performance.md +0 -65
- package/registry/workflows/product-building/design.md +0 -130
- package/registry/workflows/product-building/implement.md +0 -315
- package/registry/workflows/product-building/iterate-on-pr-comments.md +0 -70
- package/registry/workflows/product-building/prep-issue.md +0 -43
- package/registry/workflows/product-building/prototype.md +0 -60
- package/registry/workflows/product-building/resolve.md +0 -164
- package/registry/workflows/product-building/retrospect.md +0 -86
- package/registry/workflows/product-building/spec.md +0 -117
- package/registry/workflows/product-building/test.md +0 -120
- package/registry/workflows/quality-assurance/browser-validation.md +0 -221
- package/registry/workflows/quality-assurance/iterative-improvement-cycle.md +0 -562
- package/registry/workflows/replicate/replicate-discovery.md +0 -336
- package/registry/workflows/replicate/replicate-to-issues.md +0 -319
- package/registry/workflows/reviewer/review-implementation-vs-design-spec.md +0 -632
- package/registry/workflows/reviewer/review-implementation-vs-feature-spec.md +0 -669
- package/registry/workflows/startup-credits/aws-activate-application.md +0 -535
- package/registry/workflows/startup-credits/google-cloud-application.md +0 -647
- package/registry/workflows/startup-credits/microsoft-azure-application.md +0 -538
|
@@ -5,9 +5,39 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
const child_process_1 = require("child_process");
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const path_1 = require("path");
|
|
8
10
|
const dotenv_1 = __importDefault(require("dotenv"));
|
|
9
11
|
// Load environment variables
|
|
10
12
|
dotenv_1.default.config({ override: true });
|
|
13
|
+
// Self-contained utility functions (no FRAIM internal imports)
|
|
14
|
+
function extractIssueNumber(branchName) {
|
|
15
|
+
const match = branchName.match(/(\d+)/);
|
|
16
|
+
return match ? match[1] : '';
|
|
17
|
+
}
|
|
18
|
+
function getCurrentGitBranch() {
|
|
19
|
+
try {
|
|
20
|
+
return (0, child_process_1.execSync)('git branch --show-current', { encoding: 'utf8' }).trim();
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return 'unknown';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function loadClientConfig() {
|
|
27
|
+
const configPath = (0, path_1.join)(process.cwd(), '.fraim', 'config.json');
|
|
28
|
+
if (!(0, fs_1.existsSync)(configPath)) {
|
|
29
|
+
throw new Error('.fraim/config.json not found. Run fraim init first.');
|
|
30
|
+
}
|
|
31
|
+
return JSON.parse((0, fs_1.readFileSync)(configPath, 'utf-8'));
|
|
32
|
+
}
|
|
33
|
+
function getEnvOr(keys, fallback) {
|
|
34
|
+
for (const key of keys) {
|
|
35
|
+
const value = process.env[key];
|
|
36
|
+
if (value && value.length)
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
return fallback;
|
|
40
|
+
}
|
|
11
41
|
class BranchCleanup {
|
|
12
42
|
constructor(options = {}) {
|
|
13
43
|
this.options = {
|
|
@@ -18,7 +48,6 @@ class BranchCleanup {
|
|
|
18
48
|
};
|
|
19
49
|
}
|
|
20
50
|
async insertYourCodeHere(branchName) {
|
|
21
|
-
const { extractIssueNumber } = require('../../src/utils/git-utils');
|
|
22
51
|
const branchPattern = "branch_" + extractIssueNumber(branchName) + "%";
|
|
23
52
|
this.log(`Attempting to fetch and run DB cleanup for pattern: ${branchPattern}`);
|
|
24
53
|
// Historically this called: npx tsx scripts/database/cleanup-mongo-schemas.ts
|
|
@@ -217,38 +246,38 @@ for (let i = 0; i < args.length; i++) {
|
|
|
217
246
|
break;
|
|
218
247
|
case '--help':
|
|
219
248
|
case '-h':
|
|
220
|
-
console.log(`
|
|
221
|
-
🧹 Branch Cleanup Script
|
|
222
|
-
|
|
223
|
-
Usage:
|
|
224
|
-
npx tsx scripts/cleanup-branch.ts [options]
|
|
225
|
-
|
|
226
|
-
Options:
|
|
227
|
-
--branch, -b <name> Specific branch name to cleanup (default: current branch)
|
|
228
|
-
--force, -f Continue even if some operations fail
|
|
229
|
-
--skip-project-cleanup Skip project-specific cleanup
|
|
230
|
-
--skip-git Skip Git branch deletion
|
|
231
|
-
--help, -h Show this help message
|
|
232
|
-
|
|
233
|
-
Examples:
|
|
234
|
-
# Cleanup current branch with default settings
|
|
235
|
-
npx tsx <cleanup-script-path>
|
|
236
|
-
|
|
237
|
-
# Cleanup specific branch
|
|
238
|
-
npx tsx <cleanup-script-path> --branch feature/123
|
|
239
|
-
|
|
240
|
-
# Force cleanup even if some operations fail
|
|
241
|
-
npx tsx <cleanup-script-path> --force
|
|
242
|
-
|
|
243
|
-
# Skip project-specific cleanup (only do Git cleanup)
|
|
244
|
-
npx tsx <cleanup-script-path> --skip-project-cleanup
|
|
245
|
-
|
|
246
|
-
This script performs the following cleanup operations:
|
|
247
|
-
1. 🗄️ Project-specific cleanup (calls configured cleanup script)
|
|
248
|
-
2. 🌿 Git branch deletion (remote and local)
|
|
249
|
-
3. ✅ Verification of cleanup completion
|
|
250
|
-
|
|
251
|
-
⚠️ WARNING: This script will permanently delete branches and data!
|
|
249
|
+
console.log(`
|
|
250
|
+
🧹 Branch Cleanup Script
|
|
251
|
+
|
|
252
|
+
Usage:
|
|
253
|
+
npx tsx scripts/cleanup-branch.ts [options]
|
|
254
|
+
|
|
255
|
+
Options:
|
|
256
|
+
--branch, -b <name> Specific branch name to cleanup (default: current branch)
|
|
257
|
+
--force, -f Continue even if some operations fail
|
|
258
|
+
--skip-project-cleanup Skip project-specific cleanup
|
|
259
|
+
--skip-git Skip Git branch deletion
|
|
260
|
+
--help, -h Show this help message
|
|
261
|
+
|
|
262
|
+
Examples:
|
|
263
|
+
# Cleanup current branch with default settings
|
|
264
|
+
npx tsx <cleanup-script-path>
|
|
265
|
+
|
|
266
|
+
# Cleanup specific branch
|
|
267
|
+
npx tsx <cleanup-script-path> --branch feature/123
|
|
268
|
+
|
|
269
|
+
# Force cleanup even if some operations fail
|
|
270
|
+
npx tsx <cleanup-script-path> --force
|
|
271
|
+
|
|
272
|
+
# Skip project-specific cleanup (only do Git cleanup)
|
|
273
|
+
npx tsx <cleanup-script-path> --skip-project-cleanup
|
|
274
|
+
|
|
275
|
+
This script performs the following cleanup operations:
|
|
276
|
+
1. 🗄️ Project-specific cleanup (calls configured cleanup script)
|
|
277
|
+
2. 🌿 Git branch deletion (remote and local)
|
|
278
|
+
3. ✅ Verification of cleanup completion
|
|
279
|
+
|
|
280
|
+
⚠️ WARNING: This script will permanently delete branches and data!
|
|
252
281
|
`);
|
|
253
282
|
process.exit(0);
|
|
254
283
|
}
|
|
@@ -49,8 +49,83 @@ const child_process_1 = require("child_process");
|
|
|
49
49
|
const fs_1 = require("fs");
|
|
50
50
|
const mongodb_1 = require("mongodb");
|
|
51
51
|
const path_1 = require("path");
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
// Self-contained utility functions (no FRAIM internal imports)
|
|
53
|
+
function determineDatabaseName() {
|
|
54
|
+
const env = process.env.NODE_ENV || process.env.ENVIRONMENT;
|
|
55
|
+
// If explicitly set to ppe/staging, use that; otherwise default to prod
|
|
56
|
+
if (env === 'ppe' || env === 'staging') {
|
|
57
|
+
return 'fraim_ppe';
|
|
58
|
+
}
|
|
59
|
+
// Default to production
|
|
60
|
+
return process.env.MONGO_DB_NAME || 'fraim_prod';
|
|
61
|
+
}
|
|
62
|
+
function determineSchema(branchName) {
|
|
63
|
+
if (branchName.includes('ppe') || branchName.includes('staging'))
|
|
64
|
+
return 'ppe';
|
|
65
|
+
return 'prod';
|
|
66
|
+
}
|
|
67
|
+
function getCurrentGitBranch() {
|
|
68
|
+
try {
|
|
69
|
+
return (0, child_process_1.execSync)('git branch --show-current', { encoding: 'utf8' }).trim();
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
return 'master';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function loadClientConfig() {
|
|
76
|
+
const configPath = (0, path_1.join)(process.cwd(), '.fraim', 'config.json');
|
|
77
|
+
if (!(0, fs_1.existsSync)(configPath)) {
|
|
78
|
+
throw new Error('.fraim/config.json not found. Run fraim init first.');
|
|
79
|
+
}
|
|
80
|
+
return JSON.parse((0, fs_1.readFileSync)(configPath, 'utf-8'));
|
|
81
|
+
}
|
|
82
|
+
function getEnvOr(keys, fallback) {
|
|
83
|
+
for (const key of keys) {
|
|
84
|
+
const value = process.env[key];
|
|
85
|
+
if (value && value.length)
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
return fallback;
|
|
89
|
+
}
|
|
90
|
+
// Load configuration
|
|
91
|
+
const config = loadClientConfig();
|
|
92
|
+
const personaName = config.persona.name;
|
|
93
|
+
const personaDisplayNameDefault = config.persona.displayNamePattern.replace('{executiveName}', 'Your');
|
|
94
|
+
const fraimConfig = {
|
|
95
|
+
repoOwner: config.git.repoOwner || 'mathursrus',
|
|
96
|
+
repoName: config.git.repoName || 'fraim-repo',
|
|
97
|
+
projectName: 'FRAIM',
|
|
98
|
+
personaName,
|
|
99
|
+
personaPronouns: getEnvOr(['FRAIM_PERSONA_PRONOUNS'], 'they/them'),
|
|
100
|
+
personaDisplayName: getEnvOr(['FRAIM_PERSONA_DISPLAY_NAME'], personaDisplayNameDefault),
|
|
101
|
+
personaDisplayNamePattern: config.persona.displayNamePattern,
|
|
102
|
+
personaThankYouSignature: config.persona.emailSignature,
|
|
103
|
+
defaultEmail: getEnvOr(['FRAIM_DEFAULT_EMAIL'], 'agent@example.com'),
|
|
104
|
+
prodDefaultEmail: getEnvOr(['PROD_FRAIM_DEFAULT_EMAIL'], 'agent@example.com'),
|
|
105
|
+
defaultAccessToken: getEnvOr(['FRAIM_DEFAULT_ACCESS_TOKEN'], ''),
|
|
106
|
+
defaultRefreshToken: getEnvOr(['FRAIM_DEFAULT_REFRESH_TOKEN'], ''),
|
|
107
|
+
prodAccessToken: getEnvOr(['PROD_FRAIM_DEFAULT_ACCESS_TOKEN'], ''),
|
|
108
|
+
prodRefreshToken: getEnvOr(['PROD_FRAIM_DEFAULT_REFRESH_TOKEN'], ''),
|
|
109
|
+
defaultOAuthClientId: getEnvOr(['FRAIM_DEFAULT_OAUTH_CLIENT_ID'], ''),
|
|
110
|
+
defaultOAuthClientSecret: getEnvOr(['FRAIM_DEFAULT_OAUTH_CLIENT_SECRET'], ''),
|
|
111
|
+
prodOAuthClientId: getEnvOr(['PROD_FRAIM_DEFAULT_OAUTH_CLIENT_ID'], ''),
|
|
112
|
+
prodOAuthClientSecret: getEnvOr(['PROD_FRAIM_DEFAULT_OAUTH_CLIENT_SECRET'], ''),
|
|
113
|
+
identityCollection: config.database?.identityCollection || 'Identity',
|
|
114
|
+
executiveCollection: config.database?.executiveCollection || 'Executive',
|
|
115
|
+
newsletterTitle: 'Weekly Update',
|
|
116
|
+
newsletterCtaText: 'Learn More',
|
|
117
|
+
newsletterUrl: getEnvOr(['FRAIM_NEWSLETTER_URL'], ''),
|
|
118
|
+
issueRepoUrl: `https://github.com/${config.git.repoOwner}/${config.git.repoName}.git`,
|
|
119
|
+
identityTokensCollection: 'Tokens',
|
|
120
|
+
webAppUrl: config.marketing?.websiteUrl || getEnvOr(['FRAIM_WEB_APP_URL'], 'http://localhost:3000'),
|
|
121
|
+
chatUrl: config.marketing?.chatUrl || getEnvOr(['FRAIM_CHAT_URL'], ''),
|
|
122
|
+
};
|
|
123
|
+
function formatExecutiveDisplayName(executiveName) {
|
|
124
|
+
return config.persona.displayNamePattern.replace('{executiveName}', executiveName);
|
|
125
|
+
}
|
|
126
|
+
function formatPersonaSignature() {
|
|
127
|
+
return `With gratitude,\n${fraimConfig.personaThankYouSignature}\n${personaName} - Your AI Executive Assistant\n\n`;
|
|
128
|
+
}
|
|
54
129
|
// Get template path (relative to script location)
|
|
55
130
|
function getTemplatePath() {
|
|
56
131
|
// Script is at <this-path>
|
|
@@ -123,7 +198,7 @@ async function getResolvedIssues(date) {
|
|
|
123
198
|
}
|
|
124
199
|
// Use ">YYYY-MM-DD" format to get issues updated AFTER the date (not on the date)
|
|
125
200
|
// GitHub CLI requires quotes around the comparison operator
|
|
126
|
-
const command = `gh search issues --repo=${
|
|
201
|
+
const command = `gh search issues --repo=${fraimConfig.repoOwner}/${fraimConfig.repoName} --state=closed --updated=">${date}" --label=user-reported --json number,title,body,labels,closedAt,author`;
|
|
127
202
|
const output = (0, child_process_1.execSync)(command, { encoding: 'utf-8' });
|
|
128
203
|
return JSON.parse(output);
|
|
129
204
|
}
|
|
@@ -141,7 +216,7 @@ function getDatabaseName() {
|
|
|
141
216
|
const env = process.env.NODE_ENV || process.env.ENVIRONMENT;
|
|
142
217
|
// If explicitly set to ppe/staging, use that; otherwise default to prod
|
|
143
218
|
if (env === 'ppe' || env === 'staging') {
|
|
144
|
-
return
|
|
219
|
+
return determineDatabaseName();
|
|
145
220
|
}
|
|
146
221
|
// Default to production
|
|
147
222
|
return process.env.MONGO_DB_NAME || 'fraim_prod';
|
|
@@ -153,7 +228,7 @@ function getCollectionName(baseName) {
|
|
|
153
228
|
const env = process.env.NODE_ENV || process.env.ENVIRONMENT;
|
|
154
229
|
// If explicitly set to ppe/staging, use that schema; otherwise default to prod
|
|
155
230
|
if (env === 'ppe' || env === 'staging') {
|
|
156
|
-
const schema =
|
|
231
|
+
const schema = determineSchema(getCurrentGitBranch());
|
|
157
232
|
return `${schema}_${baseName}`;
|
|
158
233
|
}
|
|
159
234
|
// Default to prod schema
|
|
@@ -184,15 +259,15 @@ async function getPersonaEmailForExecutive(executiveId) {
|
|
|
184
259
|
const { MongoClient } = await Promise.resolve().then(() => __importStar(require('mongodb')));
|
|
185
260
|
const mongoUrl = process.env.PROD_MONGO_DATABASE_URL || process.env.MONGO_DATABASE_URL;
|
|
186
261
|
if (!mongoUrl) {
|
|
187
|
-
console.warn(`⚠️ PROD_MONGO_DATABASE_URL not set, using default ${
|
|
188
|
-
return
|
|
262
|
+
console.warn(`⚠️ PROD_MONGO_DATABASE_URL not set, using default ${fraimConfig.personaName} email`);
|
|
263
|
+
return fraimConfig.defaultEmail;
|
|
189
264
|
}
|
|
190
265
|
const client = new MongoClient(mongoUrl);
|
|
191
266
|
try {
|
|
192
267
|
await client.connect();
|
|
193
268
|
const dbName = getDatabaseName();
|
|
194
269
|
const db = client.db(dbName);
|
|
195
|
-
const collectionName = getCollectionName(
|
|
270
|
+
const collectionName = getCollectionName(fraimConfig.identityCollection);
|
|
196
271
|
// Query the identity collection (defaults to prod)
|
|
197
272
|
let identity = await db.collection(collectionName).findOne({
|
|
198
273
|
executive_id: executiveId,
|
|
@@ -206,11 +281,11 @@ async function getPersonaEmailForExecutive(executiveId) {
|
|
|
206
281
|
if (identity && identity.email) {
|
|
207
282
|
return identity.email;
|
|
208
283
|
}
|
|
209
|
-
return
|
|
284
|
+
return fraimConfig.defaultEmail;
|
|
210
285
|
}
|
|
211
286
|
catch (error) {
|
|
212
|
-
console.warn(`⚠️ Could not get ${
|
|
213
|
-
return
|
|
287
|
+
console.warn(`⚠️ Could not get ${fraimConfig.personaName} email for executive ${executiveId}:`, error);
|
|
288
|
+
return fraimConfig.defaultEmail;
|
|
214
289
|
}
|
|
215
290
|
finally {
|
|
216
291
|
await client.close();
|
|
@@ -268,7 +343,7 @@ async function sendCustomerMail(candidatesFilePath, executiveId) {
|
|
|
268
343
|
await client.connect();
|
|
269
344
|
const dbName = getDatabaseName();
|
|
270
345
|
const db = client.db(dbName);
|
|
271
|
-
const collectionName = getCollectionName(
|
|
346
|
+
const collectionName = getCollectionName(fraimConfig.identityCollection);
|
|
272
347
|
const identity = await db.collection(collectionName).findOne({
|
|
273
348
|
executive_id: candidate.executive.id,
|
|
274
349
|
status: 'active'
|
|
@@ -280,10 +355,10 @@ async function sendCustomerMail(candidatesFilePath, executiveId) {
|
|
|
280
355
|
access_token: identity.access_token,
|
|
281
356
|
refresh_token: identity.refresh_token
|
|
282
357
|
};
|
|
283
|
-
console.log(`Found ${
|
|
358
|
+
console.log(`Found ${fraimConfig.personaName} tokens in ${dbName} database for ${candidate.from.email}`);
|
|
284
359
|
}
|
|
285
360
|
else {
|
|
286
|
-
console.warn(`No ${
|
|
361
|
+
console.warn(`No ${fraimConfig.personaName} tokens found in ${dbName} database for executive ${candidate.executive.id}`);
|
|
287
362
|
}
|
|
288
363
|
}
|
|
289
364
|
finally {
|
|
@@ -291,7 +366,7 @@ async function sendCustomerMail(candidatesFilePath, executiveId) {
|
|
|
291
366
|
}
|
|
292
367
|
}
|
|
293
368
|
catch (error) {
|
|
294
|
-
console.warn(`Could not get ${
|
|
369
|
+
console.warn(`Could not get ${fraimConfig.personaName} tokens from database for executive ${candidate.executive.id}:`, error);
|
|
295
370
|
}
|
|
296
371
|
}
|
|
297
372
|
// Generate plain text body for fallback (from structured format if available)
|
|
@@ -335,7 +410,7 @@ async function sendSingleEmail(params, executive, personaTokens) {
|
|
|
335
410
|
let refreshToken;
|
|
336
411
|
if (personaTokens?.access_token && personaTokens?.refresh_token) {
|
|
337
412
|
// Use persona identity tokens (preferred - ensures correct From address)
|
|
338
|
-
console.log(`🔑 Using ${
|
|
413
|
+
console.log(`🔑 Using ${fraimConfig.personaName} identity tokens for ${fromEmail}`);
|
|
339
414
|
accessToken = personaTokens.access_token;
|
|
340
415
|
refreshToken = personaTokens.refresh_token;
|
|
341
416
|
}
|
|
@@ -347,11 +422,11 @@ async function sendSingleEmail(params, executive, personaTokens) {
|
|
|
347
422
|
}
|
|
348
423
|
else {
|
|
349
424
|
// Use default persona tokens as last resort
|
|
350
|
-
console.log(`🔑 Using default ${
|
|
351
|
-
accessToken =
|
|
352
|
-
refreshToken =
|
|
425
|
+
console.log(`🔑 Using default ${fraimConfig.personaName} tokens as fallback`);
|
|
426
|
+
accessToken = fraimConfig.defaultAccessToken;
|
|
427
|
+
refreshToken = fraimConfig.defaultRefreshToken;
|
|
353
428
|
if (!accessToken || !refreshToken) {
|
|
354
|
-
throw new Error(`${
|
|
429
|
+
throw new Error(`${fraimConfig.personaName} Gmail tokens not found. Need either identity tokens, executive tokens, or default tokens in environment variables.`);
|
|
355
430
|
}
|
|
356
431
|
}
|
|
357
432
|
// Verify the From email matches the authenticated account (Gmail requirement)
|
|
@@ -416,8 +491,8 @@ async function sendSingleEmail(params, executive, personaTokens) {
|
|
|
416
491
|
if (response.status === 401) {
|
|
417
492
|
console.log('🔄 Access token expired, refreshing...');
|
|
418
493
|
// Refresh the access token
|
|
419
|
-
const clientId =
|
|
420
|
-
const clientSecret =
|
|
494
|
+
const clientId = fraimConfig.defaultOAuthClientId || process.env.FRAIM_DEFAULT_OAUTH_CLIENT_ID || process.env.PERSONA_DEFAULT_OAUTH_CLIENT_ID;
|
|
495
|
+
const clientSecret = fraimConfig.defaultOAuthClientSecret || process.env.FRAIM_DEFAULT_OAUTH_CLIENT_SECRET || process.env.PERSONA_DEFAULT_OAUTH_CLIENT_SECRET;
|
|
421
496
|
if (!clientId || !clientSecret) {
|
|
422
497
|
throw new Error('FRAIM_DEFAULT_OAUTH_CLIENT_ID and FRAIM_DEFAULT_OAUTH_CLIENT_SECRET environment variables are required for token refresh');
|
|
423
498
|
}
|
|
@@ -476,9 +551,9 @@ function generateHtmlEmail(params) {
|
|
|
476
551
|
// Replace simple template variables
|
|
477
552
|
template = template.replace(/\{\{displayName\}\}/g, escapeHtml(params.displayName));
|
|
478
553
|
template = template.replace(/\{\{executiveName\}\}/g, escapeHtml(params.executiveName));
|
|
479
|
-
template = template.replace(/\{\{personaName\}\}/g, escapeHtml(
|
|
480
|
-
template = template.replace(/\{\{chatUrl\}\}/g, escapeHtml(
|
|
481
|
-
template = template.replace(/\{\{webAppUrl\}\}/g, escapeHtml(
|
|
554
|
+
template = template.replace(/\{\{personaName\}\}/g, escapeHtml(fraimConfig.personaName));
|
|
555
|
+
template = template.replace(/\{\{chatUrl\}\}/g, escapeHtml(fraimConfig.chatUrl || '#'));
|
|
556
|
+
template = template.replace(/\{\{webAppUrl\}\}/g, escapeHtml(fraimConfig.webAppUrl || '#'));
|
|
482
557
|
template = template.replace(/\{\{fromEmail\}\}/g, escapeHtml(params.fromEmail));
|
|
483
558
|
template = template.replace(/\{\{greeting\}\}/g, escapeHtml(params.greeting));
|
|
484
559
|
template = template.replace(/\{\{opening\}\}/g, params.opening);
|
|
@@ -495,16 +570,16 @@ function generateHtmlEmail(params) {
|
|
|
495
570
|
html += `</div>`;
|
|
496
571
|
return html;
|
|
497
572
|
}).join('');
|
|
498
|
-
const improvementsSection = `
|
|
499
|
-
<tr>
|
|
500
|
-
<td style="padding: 0 30px 20px 30px;">
|
|
501
|
-
<div style="background-color: #f8f9fa; border-left: 4px solid #667eea; border-radius: 6px; padding: 20px; margin: 20px 0;">
|
|
502
|
-
<div style="font-size: 18px; font-weight: 600; color: #333333; margin-bottom: 16px;">
|
|
503
|
-
✨ What's Fixed
|
|
504
|
-
</div>
|
|
505
|
-
${improvementsHtml}
|
|
506
|
-
</div>
|
|
507
|
-
</td>
|
|
573
|
+
const improvementsSection = `
|
|
574
|
+
<tr>
|
|
575
|
+
<td style="padding: 0 30px 20px 30px;">
|
|
576
|
+
<div style="background-color: #f8f9fa; border-left: 4px solid #667eea; border-radius: 6px; padding: 20px; margin: 20px 0;">
|
|
577
|
+
<div style="font-size: 18px; font-weight: 600; color: #333333; margin-bottom: 16px;">
|
|
578
|
+
✨ What's Fixed
|
|
579
|
+
</div>
|
|
580
|
+
${improvementsHtml}
|
|
581
|
+
</div>
|
|
582
|
+
</td>
|
|
508
583
|
</tr>`;
|
|
509
584
|
template = template.replace(/\{\{#if hasImprovements\}\}[\s\S]*?\{\{\/if\}\}/, improvementsSection);
|
|
510
585
|
}
|
|
@@ -514,13 +589,13 @@ function generateHtmlEmail(params) {
|
|
|
514
589
|
}
|
|
515
590
|
// Replace closing paragraph
|
|
516
591
|
if (params.closing) {
|
|
517
|
-
template = template.replace(/\{\{#if closing\}\}[\s\S]*?\{\{\/if\}\}/, `
|
|
518
|
-
<tr>
|
|
519
|
-
<td style="padding: 0 30px 20px 30px;">
|
|
520
|
-
<div style="font-size: 16px; color: #555555; line-height: 1.8;">
|
|
521
|
-
${convertTextToHtml(params.closing)}
|
|
522
|
-
</div>
|
|
523
|
-
</td>
|
|
592
|
+
template = template.replace(/\{\{#if closing\}\}[\s\S]*?\{\{\/if\}\}/, `
|
|
593
|
+
<tr>
|
|
594
|
+
<td style="padding: 0 30px 20px 30px;">
|
|
595
|
+
<div style="font-size: 16px; color: #555555; line-height: 1.8;">
|
|
596
|
+
${convertTextToHtml(params.closing)}
|
|
597
|
+
</div>
|
|
598
|
+
</td>
|
|
524
599
|
</tr>`);
|
|
525
600
|
}
|
|
526
601
|
else {
|
|
@@ -548,7 +623,7 @@ function generatePlainTextBody(candidate) {
|
|
|
548
623
|
if (candidate.closing) {
|
|
549
624
|
body += `${candidate.closing}\n\n`;
|
|
550
625
|
}
|
|
551
|
-
body +=
|
|
626
|
+
body += formatPersonaSignature();
|
|
552
627
|
return body;
|
|
553
628
|
}
|
|
554
629
|
// Fallback to legacy body format
|
|
@@ -576,7 +651,7 @@ function extractFirstName(text) {
|
|
|
576
651
|
function extractExecutiveName(displayName) {
|
|
577
652
|
// "{Persona} - [Name]'s AI Executive Assistant"
|
|
578
653
|
// We need to match based on the configured pattern
|
|
579
|
-
const pattern =
|
|
654
|
+
const pattern = fraimConfig.personaDisplayNamePattern.replace('{executiveName}', '(.+?)');
|
|
580
655
|
try {
|
|
581
656
|
// If pattern contains regex characters, they need expanding or escaping?
|
|
582
657
|
// This is simple pattern matching.
|