fraim-framework 2.0.26 ā 2.0.30
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/.github/workflows/deploy-fraim.yml +1 -1
- package/dist/registry/scripts/build-scripts-generator.js +205 -0
- package/dist/registry/scripts/cleanup-branch.js +258 -0
- package/dist/registry/scripts/evaluate-code-quality.js +66 -0
- package/dist/registry/scripts/exec-with-timeout.js +142 -0
- package/dist/registry/scripts/fraim-config.js +61 -0
- package/dist/registry/scripts/generate-engagement-emails.js +630 -0
- package/dist/registry/scripts/generic-issues-api.js +100 -0
- package/dist/registry/scripts/newsletter-helpers.js +731 -0
- package/dist/registry/scripts/openapi-generator.js +664 -0
- package/dist/registry/scripts/performance/profile-server.js +390 -0
- package/dist/registry/scripts/run-thank-you-workflow.js +92 -0
- package/dist/registry/scripts/send-newsletter-simple.js +85 -0
- package/dist/registry/scripts/send-thank-you-emails.js +54 -0
- package/dist/registry/scripts/validate-openapi-limits.js +311 -0
- package/dist/registry/scripts/validate-test-coverage.js +262 -0
- package/dist/registry/scripts/verify-test-coverage.js +66 -0
- package/dist/src/cli/commands/init.js +14 -12
- package/dist/src/cli/commands/sync.js +19 -2
- package/dist/src/cli/fraim.js +24 -22
- package/dist/src/cli/setup/first-run.js +13 -6
- package/dist/src/fraim/config-loader.js +0 -8
- package/dist/src/fraim/db-service.js +26 -15
- package/dist/src/fraim/issues.js +67 -0
- package/dist/src/fraim/setup-wizard.js +1 -69
- package/dist/src/fraim/types.js +0 -11
- package/dist/src/fraim-mcp-server.js +272 -18
- package/dist/src/utils/git-utils.js +1 -1
- package/dist/src/utils/version-utils.js +32 -0
- package/dist/tests/debug-tools.js +79 -0
- package/dist/tests/esm-compat.js +11 -0
- package/dist/tests/test-chalk-esm-issue.js +159 -0
- package/dist/tests/test-chalk-real-world.js +265 -0
- package/dist/tests/test-chalk-regression.js +327 -0
- package/dist/tests/test-chalk-resolution-issue.js +304 -0
- package/dist/tests/test-cli.js +0 -2
- package/dist/tests/test-fraim-install-chalk-issue.js +254 -0
- package/dist/tests/test-fraim-issues.js +59 -0
- package/dist/tests/test-genericization.js +1 -3
- package/dist/tests/test-mcp-connection.js +166 -0
- package/dist/tests/test-mcp-issue-integration.js +144 -0
- package/dist/tests/test-mcp-lifecycle-methods.js +312 -0
- package/dist/tests/test-node-compatibility.js +71 -0
- package/dist/tests/test-npm-install.js +66 -0
- package/dist/tests/test-npm-resolution-diagnostic.js +140 -0
- package/dist/tests/test-session-rehydration.js +145 -0
- package/dist/tests/test-standalone.js +2 -8
- package/dist/tests/test-sync-version-update.js +93 -0
- package/dist/tests/test-telemetry.js +190 -0
- package/package.json +10 -8
- package/registry/agent-guardrails.md +62 -54
- package/registry/rules/agent-success-criteria.md +52 -0
- package/registry/rules/agent-testing-guidelines.md +502 -502
- package/registry/rules/communication.md +121 -121
- package/registry/rules/continuous-learning.md +54 -54
- package/registry/rules/ephemeral-execution.md +10 -5
- package/registry/rules/hitl-ppe-record-analysis.md +302 -302
- package/registry/rules/local-development.md +251 -251
- package/registry/rules/software-development-lifecycle.md +104 -104
- package/registry/rules/successful-debugging-patterns.md +482 -478
- package/registry/rules/telemetry.md +67 -0
- package/registry/scripts/build-scripts-generator.ts +216 -215
- package/registry/scripts/cleanup-branch.ts +303 -284
- package/registry/scripts/code-quality-check.sh +559 -559
- package/registry/scripts/detect-tautological-tests.sh +38 -38
- package/registry/scripts/evaluate-code-quality.ts +1 -1
- package/registry/scripts/generate-engagement-emails.ts +744 -744
- package/registry/scripts/generic-issues-api.ts +110 -150
- package/registry/scripts/newsletter-helpers.ts +874 -874
- package/registry/scripts/openapi-generator.ts +695 -693
- package/registry/scripts/performance/profile-server.ts +5 -3
- package/registry/scripts/prep-issue.sh +468 -455
- package/registry/scripts/validate-openapi-limits.ts +366 -365
- package/registry/scripts/validate-test-coverage.ts +280 -280
- package/registry/scripts/verify-pr-comments.sh +70 -70
- package/registry/scripts/verify-test-coverage.ts +1 -1
- package/registry/templates/bootstrap/ARCHITECTURE-TEMPLATE.md +53 -53
- package/registry/templates/evidence/Implementation-BugEvidence.md +85 -85
- package/registry/templates/evidence/Implementation-FeatureEvidence.md +120 -120
- package/registry/templates/marketing/HBR-ARTICLE-TEMPLATE.md +66 -0
- package/registry/workflows/bootstrap/create-architecture.md +2 -2
- package/registry/workflows/bootstrap/evaluate-code-quality.md +3 -3
- package/registry/workflows/bootstrap/verify-test-coverage.md +2 -2
- package/registry/workflows/customer-development/insight-analysis.md +156 -156
- package/registry/workflows/customer-development/interview-preparation.md +421 -421
- package/registry/workflows/customer-development/strategic-brainstorming.md +146 -146
- package/registry/workflows/customer-development/thank-customers.md +193 -191
- package/registry/workflows/customer-development/weekly-newsletter.md +362 -352
- package/registry/workflows/improve-fraim/contribute.md +32 -0
- package/registry/workflows/improve-fraim/file-issue.md +32 -0
- package/registry/workflows/marketing/hbr-article.md +73 -0
- package/registry/workflows/performance/analyze-performance.md +63 -59
- package/registry/workflows/product-building/design.md +3 -2
- package/registry/workflows/product-building/implement.md +4 -3
- package/registry/workflows/product-building/prep-issue.md +28 -17
- package/registry/workflows/product-building/resolve.md +3 -2
- package/registry/workflows/product-building/retrospect.md +3 -2
- package/registry/workflows/product-building/spec.md +5 -4
- package/registry/workflows/product-building/test.md +3 -2
- package/registry/workflows/quality-assurance/iterative-improvement-cycle.md +562 -562
- package/registry/workflows/replicate/website-discovery-analysis.md +3 -3
- package/registry/workflows/reviewer/review-implementation-vs-design-spec.md +632 -632
- package/registry/workflows/reviewer/review-implementation-vs-feature-spec.md +669 -669
- package/tsconfig.json +2 -1
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const child_process_1 = require("child_process");
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const fraim_config_1 = require("../fraim-config");
|
|
41
|
+
const azureConfig = fraim_config_1.fraimConfig.azure;
|
|
42
|
+
const CONFIGS = {
|
|
43
|
+
prod: {
|
|
44
|
+
env: 'prod',
|
|
45
|
+
appName: azureConfig.prodAppName,
|
|
46
|
+
resourceGroup: azureConfig.prodResourceGroup
|
|
47
|
+
},
|
|
48
|
+
preprod: {
|
|
49
|
+
env: 'preprod',
|
|
50
|
+
appName: azureConfig.preprodAppName,
|
|
51
|
+
resourceGroup: azureConfig.preprodResourceGroup
|
|
52
|
+
},
|
|
53
|
+
local: {
|
|
54
|
+
env: 'local',
|
|
55
|
+
appName: azureConfig.localAppName,
|
|
56
|
+
resourceGroup: azureConfig.localResourceGroup
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
async function main() {
|
|
60
|
+
const args = process.argv.slice(2);
|
|
61
|
+
let config = CONFIGS.local;
|
|
62
|
+
const includeLogs = args.includes('--logs');
|
|
63
|
+
if (args.includes('--prod'))
|
|
64
|
+
config = CONFIGS.prod;
|
|
65
|
+
else if (args.includes('--preprod'))
|
|
66
|
+
config = CONFIGS.preprod;
|
|
67
|
+
else if (args.includes('--local')) {
|
|
68
|
+
console.log('š Running in local mode (log analysis only)');
|
|
69
|
+
config = CONFIGS.local;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
console.log('ā ļø No environment specified.');
|
|
73
|
+
console.log('Usage: npx tsx <this-script> [--prod|--preprod|--local] [--logs]');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
console.log(`\nš Starting Profiling for Environment: ${config.env.toUpperCase()}`);
|
|
77
|
+
// 1. Infrastructure (Step 1)
|
|
78
|
+
if (config.env !== 'local') {
|
|
79
|
+
try {
|
|
80
|
+
console.log('\n--- āļø Step 1: Server Resource Analysis (Plan Level) ---');
|
|
81
|
+
// Get App Service Plan ID
|
|
82
|
+
const planId = (0, child_process_1.execSync)(`az webapp show --name ${config.appName} --resource-group ${config.resourceGroup} --query "appServicePlanId" -o tsv`).toString().trim();
|
|
83
|
+
console.log(`Plan ID: ${planId.split('/').pop()}`);
|
|
84
|
+
// 1a. Overall Metric (Plan Level)
|
|
85
|
+
// Get last 5 minutes of CPU/Memory
|
|
86
|
+
console.log(`> Fetching Plan Metrics (Last 5m)...`);
|
|
87
|
+
const metricCmd = `az monitor metrics list --resource "${planId}" --metrics CpuPercentage MemoryPercentage --interval PT1M --query "value[].{name:name.value, average:timeseries[0].data[-1].average}" -o json`;
|
|
88
|
+
try {
|
|
89
|
+
const metrics = JSON.parse((0, child_process_1.execSync)(metricCmd).toString());
|
|
90
|
+
metrics.forEach((m) => {
|
|
91
|
+
console.log(`${m.name}: ${m.average != null ? m.average.toFixed(2) + '%' : 'N/A'}`);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
console.log(`ā ļø Could not fetch plan metrics: ${e.message.split('\n')[0]}`);
|
|
96
|
+
}
|
|
97
|
+
// Prepare for API calls
|
|
98
|
+
// Get Access Token explicitly
|
|
99
|
+
const token = (0, child_process_1.execSync)('az account get-access-token --resource https://management.azure.com/ --query accessToken -o tsv').toString().trim();
|
|
100
|
+
const axios = (await Promise.resolve().then(() => __importStar(require('axios')))).default;
|
|
101
|
+
// --- 1.5: App Level Metrics (CpuTime) ---
|
|
102
|
+
try {
|
|
103
|
+
console.log(`\n> Fetching App Metrics (Last 5m) for ${config.appName}...`);
|
|
104
|
+
const subscriptionId = (0, child_process_1.execSync)(`az account show --query id -o tsv`).toString().trim();
|
|
105
|
+
const siteResourceId = `/subscriptions/${subscriptionId}/resourceGroups/${config.resourceGroup}/providers/Microsoft.Web/sites/${config.appName}`;
|
|
106
|
+
const appResponse = await axios.get(`https://management.azure.com${siteResourceId}/providers/microsoft.insights/metrics?api-version=2018-01-01&metricnames=CpuTime×pan=PT5M&aggregation=Total`, { headers: { Authorization: `Bearer ${token}` } });
|
|
107
|
+
if (appResponse.data && appResponse.data.value) {
|
|
108
|
+
appResponse.data.value.forEach((m) => {
|
|
109
|
+
const val = (m.timeseries[0]?.data[0]?.total);
|
|
110
|
+
const displayVal = val !== undefined ? val.toFixed(2) : 'N/A';
|
|
111
|
+
console.log(`${m.name.value}: ${displayVal}s (Total over 5m)`);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
console.log(`ā ļø Could not fetch app metrics: ${e.message}`);
|
|
117
|
+
}
|
|
118
|
+
// 2. Process Metrics (Step 2)
|
|
119
|
+
console.log(`\n--- āļø Step 2: Per-Process Resource Analysis (Kudu) ---`);
|
|
120
|
+
try {
|
|
121
|
+
// Token and axios already fetched above
|
|
122
|
+
const response = await axios.get(`https://${config.appName}.scm.azurewebsites.net/api/processes`, {
|
|
123
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
124
|
+
});
|
|
125
|
+
const processes = response.data;
|
|
126
|
+
// Shared diagnostics state
|
|
127
|
+
let memInfoOutput = '';
|
|
128
|
+
let psOutput = '';
|
|
129
|
+
let psLines = [];
|
|
130
|
+
// Use Kudu Command API to run 'top' remotely
|
|
131
|
+
try {
|
|
132
|
+
const topCmd = {
|
|
133
|
+
command: "top -b -d 3 -n 2",
|
|
134
|
+
dir: "/home"
|
|
135
|
+
};
|
|
136
|
+
const cmdRes = await axios.post(`https://${config.appName}.scm.azurewebsites.net/api/command`, topCmd, {
|
|
137
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
138
|
+
timeout: 25000
|
|
139
|
+
});
|
|
140
|
+
if (cmdRes.data && cmdRes.data.Output) {
|
|
141
|
+
const lines = cmdRes.data.Output.split('\n');
|
|
142
|
+
// --- Trace System Summary ---
|
|
143
|
+
console.log('System Summary (from top):');
|
|
144
|
+
let summaryLinesCount = 0;
|
|
145
|
+
for (const line of lines) {
|
|
146
|
+
if (line.trim() && summaryLinesCount < 5) {
|
|
147
|
+
console.log(` ${line.trim()}`);
|
|
148
|
+
summaryLinesCount++;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// --- Deep Dive: Missing Memory Investigation ---
|
|
152
|
+
console.log(`\n> Investigating "Invisible" Memory Usage...`);
|
|
153
|
+
try {
|
|
154
|
+
const dfCmd = { command: "df -h | grep -E 'tmpfs|shm'", dir: "/home" };
|
|
155
|
+
const dfRes = await axios.post(`https://${config.appName}.scm.azurewebsites.net/api/command`, dfCmd, { headers: { Authorization: `Bearer ${token}` } });
|
|
156
|
+
console.log(' [Tmpfs/Shm Usage]:');
|
|
157
|
+
console.log(dfRes.data.Output ? ' ' + dfRes.data.Output.replace(/\n/g, '\n ').trim() : ' (No output)');
|
|
158
|
+
const memInfoCmd = { command: "cat /proc/meminfo | grep -E 'MemTotal|MemFree|MemAvailable|SwapTotal|SwapFree|Shmem|Slab|SUnreclaim|Buffers|Cached|AnonPages|Committed_AS'", dir: "/home" };
|
|
159
|
+
const memRes = await axios.post(`https://${config.appName}.scm.azurewebsites.net/api/command`, memInfoCmd, { headers: { Authorization: `Bearer ${token}` } });
|
|
160
|
+
memInfoOutput = memRes.data.Output || '';
|
|
161
|
+
console.log(' [Detailed MemInfo]:');
|
|
162
|
+
console.log(memInfoOutput ? ' ' + memInfoOutput.replace(/\n/g, '\n ').trim() : ' (No output)');
|
|
163
|
+
console.log('\n [Searching for Swap/VM Hogs in /proc...]:');
|
|
164
|
+
const swapHogCmd = {
|
|
165
|
+
command: "for f in /proc/[0-9]*/status; do grep -E '^(Name|Pid|VmSwap|VmSize)' $f 2>/dev/null | tr '\n' ' ' && echo ''; done | sort -k6 -rn | head -n 10",
|
|
166
|
+
dir: "/home"
|
|
167
|
+
};
|
|
168
|
+
const swapRes = await axios.post(`https://${config.appName}.scm.azurewebsites.net/api/command`, swapHogCmd, { headers: { Authorization: `Bearer ${token}` } });
|
|
169
|
+
console.log(' (Format: Name, Pid, VmSize, VmSwap)');
|
|
170
|
+
console.log(swapRes.data.Output ? ' ' + swapRes.data.Output.replace(/\n/g, '\n ').trim() : ' (No output)');
|
|
171
|
+
}
|
|
172
|
+
catch (e) {
|
|
173
|
+
console.log(` Could not inspect storage/meminfo: ${e.message}`);
|
|
174
|
+
}
|
|
175
|
+
let currentSnapshot = [];
|
|
176
|
+
let foundHeader = false;
|
|
177
|
+
let cpuColIndex = -1;
|
|
178
|
+
let memColIndex = -1;
|
|
179
|
+
let cmdColIndex = -1;
|
|
180
|
+
for (const line of lines) {
|
|
181
|
+
const trimmed = line.trim();
|
|
182
|
+
if (!trimmed)
|
|
183
|
+
continue;
|
|
184
|
+
if (trimmed.includes('PID') && trimmed.includes('COMMAND')) {
|
|
185
|
+
foundHeader = true;
|
|
186
|
+
currentSnapshot = [];
|
|
187
|
+
const headers = trimmed.split(/\s+/);
|
|
188
|
+
cpuColIndex = headers.indexOf('%CPU');
|
|
189
|
+
if (cpuColIndex === -1)
|
|
190
|
+
cpuColIndex = headers.indexOf('CPU%');
|
|
191
|
+
memColIndex = headers.indexOf('%MEM');
|
|
192
|
+
if (memColIndex === -1)
|
|
193
|
+
memColIndex = headers.indexOf('MEM%');
|
|
194
|
+
cmdColIndex = headers.indexOf('COMMAND');
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (foundHeader && cpuColIndex >= 0) {
|
|
198
|
+
const parts = trimmed.split(/\s+/);
|
|
199
|
+
if (parts.length >= 9) {
|
|
200
|
+
const pid = parts[0];
|
|
201
|
+
const cpuStr = parts[cpuColIndex];
|
|
202
|
+
const memStr = parts[memColIndex];
|
|
203
|
+
const cmd = parts.slice(cmdColIndex).join(' ');
|
|
204
|
+
const state = parts[7] || '?';
|
|
205
|
+
const time = parts[10] || '?';
|
|
206
|
+
const cpuVal = parseFloat(cpuStr);
|
|
207
|
+
const memVal = parseFloat(memStr);
|
|
208
|
+
if (!isNaN(parseInt(pid))) {
|
|
209
|
+
currentSnapshot.push({ pid, cmd, cpuStr, memStr, cpuVal, memVal, state, time });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (currentSnapshot.length === 0) {
|
|
215
|
+
console.log('(No processes found or parsing failed)');
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
console.log(`\n--- Cross-Referencing Memory Hogs (ps aux) ---`);
|
|
219
|
+
try {
|
|
220
|
+
const psRes = await axios.post(`https://${config.appName}.scm.azurewebsites.net/api/command`, { command: "ps aux", dir: "/home" }, { headers: { Authorization: `Bearer ${token}` } });
|
|
221
|
+
psOutput = psRes.data.Output || '';
|
|
222
|
+
console.log(`PID\tVSZ\tRSS\tCOMMAND`);
|
|
223
|
+
console.log(`---\t---\t---\t-------`);
|
|
224
|
+
psLines = psOutput.split('\n').filter((l) => l.includes(' '));
|
|
225
|
+
psLines.slice(1, 10).forEach((l) => {
|
|
226
|
+
const p = l.trim().split(/\s+/);
|
|
227
|
+
if (p.length > 10) {
|
|
228
|
+
console.log(`${p[1]}\t${(parseInt(p[4]) / 1024).toFixed(0)}M\t${(parseInt(p[5]) / 1024).toFixed(0)}M\t${p.slice(10).join(' ').padEnd(30).slice(0, 30)}`);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
catch (e) {
|
|
233
|
+
console.log(` Could not fetch ps aux: ${e.message}`);
|
|
234
|
+
}
|
|
235
|
+
console.log(`\n--- Top Processes by CPU (top delta) ---`);
|
|
236
|
+
console.log(`PID\tSTATE\tTIME+\t%CPU\t%MEM\tCOMMAND`);
|
|
237
|
+
console.log(`---\t-----\t-----\t----\t----\t-------`);
|
|
238
|
+
currentSnapshot.sort((a, b) => b.cpuVal - a.cpuVal);
|
|
239
|
+
currentSnapshot.slice(0, 5).forEach(p => {
|
|
240
|
+
console.log(`${p.pid}\t${p.state}\t${p.time}\t${p.cpuStr}%\t${p.memStr}%\t${p.cmd.padEnd(20).slice(0, 20)}`);
|
|
241
|
+
});
|
|
242
|
+
console.log(`\n--- š§ Step 4: Automated Diagnosis ---`);
|
|
243
|
+
try {
|
|
244
|
+
let healthy = true;
|
|
245
|
+
if (memInfoOutput.includes('SwapTotal')) {
|
|
246
|
+
const swapTotal = parseInt(memInfoOutput.match(/SwapTotal:\s+(\d+)/)?.[1] || '0');
|
|
247
|
+
const swapFree = parseInt(memInfoOutput.match(/SwapFree:\s+(\d+)/)?.[1] || '0');
|
|
248
|
+
if (swapTotal > 0) {
|
|
249
|
+
const swapUsedPct = ((swapTotal - swapFree) / swapTotal) * 100;
|
|
250
|
+
if (swapUsedPct > 95) {
|
|
251
|
+
console.log(`ā [CRITICAL] Swap Death detected (${swapUsedPct.toFixed(1)}% used).`);
|
|
252
|
+
healthy = false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const hasApp = psOutput.includes('server.js') || psOutput.includes('orchestrator.ts');
|
|
257
|
+
if (!hasApp) {
|
|
258
|
+
console.log(`ā [CRITICAL] Application process 'server.js' NOT FOUND in ps aux.`);
|
|
259
|
+
healthy = false;
|
|
260
|
+
}
|
|
261
|
+
const kuduHog = currentSnapshot.find(p => p.pid === '851' && p.memVal > 20);
|
|
262
|
+
if (kuduHog) {
|
|
263
|
+
console.log(`ā ļø [WARNING] Kudu (Azure Agent) is consuming ${kuduHog.memStr}% of physical RAM.`);
|
|
264
|
+
healthy = false;
|
|
265
|
+
}
|
|
266
|
+
const pm2Hogs = psLines.filter((l) => l.includes('God Daemon'));
|
|
267
|
+
if (pm2Hogs.length > 1) {
|
|
268
|
+
console.log(`ā ļø [WARNING] Multiple PM2 God Daemons detected (${pm2Hogs.length}). Potential resource conflict.`);
|
|
269
|
+
healthy = false;
|
|
270
|
+
}
|
|
271
|
+
if (healthy) {
|
|
272
|
+
console.log(`ā
No obvious infrastructure bottlenecks detected.`);
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
console.log(`\nRECOMMENDED ACTIONS:`);
|
|
276
|
+
console.log(`1. Restart the Web App to clear Swap and kill rogue agents.`);
|
|
277
|
+
console.log(`2. Consider upgrading the App Service Plan (B1 -> P1v3) for more RAM.`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch (e) {
|
|
281
|
+
console.log(` Could not perform automated diagnosis: ${e.message}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
console.log('Debug: Unexpected response format:', cmdRes.data);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
console.log(`ā ļø 'top' failed via Kudu: ${err.message}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
catch (e) {
|
|
294
|
+
console.log(`ā ļø Could not fetch process metrics: ${e.message}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
console.error('ā Failed to fetch infrastructure metrics:', error.message);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// 3. Log Analysis (Step 3)
|
|
302
|
+
if (includeLogs || config.env === 'local') {
|
|
303
|
+
console.log('\n--- š Step 3: Log Analysis ---');
|
|
304
|
+
let logContent = '';
|
|
305
|
+
if (config.env !== 'local') {
|
|
306
|
+
try {
|
|
307
|
+
console.log('> Downloading recent logs from Azure...');
|
|
308
|
+
const fraimTmp = path.join(os.homedir(), '.fraim', 'tmp');
|
|
309
|
+
if (!fs.existsSync(fraimTmp))
|
|
310
|
+
fs.mkdirSync(fraimTmp, { recursive: true });
|
|
311
|
+
const tempDir = fs.mkdtempSync(path.join(fraimTmp, `${fraim_config_1.fraimConfig.personaName.toLowerCase()}-logs-`));
|
|
312
|
+
const zipPath = path.join(tempDir, 'logs.zip');
|
|
313
|
+
(0, child_process_1.execSync)(`az webapp log download --name ${config.appName} --resource-group ${config.resourceGroup} --log-file "${zipPath}"`);
|
|
314
|
+
const originalCwd = process.cwd();
|
|
315
|
+
try {
|
|
316
|
+
process.chdir(tempDir);
|
|
317
|
+
try {
|
|
318
|
+
(0, child_process_1.execSync)(`unzip -o logs.zip`);
|
|
319
|
+
}
|
|
320
|
+
catch (e) {
|
|
321
|
+
try {
|
|
322
|
+
(0, child_process_1.execSync)(`powershell -command "Expand-Archive -Path 'logs.zip' -DestinationPath '.' -Force"`);
|
|
323
|
+
}
|
|
324
|
+
catch (p) { }
|
|
325
|
+
}
|
|
326
|
+
process.chdir(originalCwd);
|
|
327
|
+
}
|
|
328
|
+
catch (e) {
|
|
329
|
+
process.chdir(originalCwd);
|
|
330
|
+
}
|
|
331
|
+
const findLogFile = (dir) => {
|
|
332
|
+
const files = fs.readdirSync(dir);
|
|
333
|
+
for (const file of files) {
|
|
334
|
+
const fullPath = path.join(dir, file);
|
|
335
|
+
const stat = fs.statSync(fullPath);
|
|
336
|
+
if (stat.isDirectory()) {
|
|
337
|
+
const found = findLogFile(fullPath);
|
|
338
|
+
if (found)
|
|
339
|
+
return found;
|
|
340
|
+
}
|
|
341
|
+
else if (file.endsWith('_default_docker.log')) {
|
|
342
|
+
return fullPath;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return null;
|
|
346
|
+
};
|
|
347
|
+
const dockerLog = findLogFile(tempDir);
|
|
348
|
+
if (dockerLog)
|
|
349
|
+
logContent = fs.readFileSync(dockerLog, 'utf-8');
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
console.error('ā Failed to download logs:', error.message);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
const localLogPath = path.join(process.cwd(), 'test.log');
|
|
357
|
+
if (fs.existsSync(localLogPath))
|
|
358
|
+
logContent = fs.readFileSync(localLogPath, 'utf-8');
|
|
359
|
+
}
|
|
360
|
+
if (logContent) {
|
|
361
|
+
analyzeLogs(logContent);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function analyzeLogs(content) {
|
|
366
|
+
const lines = content.split('\n');
|
|
367
|
+
let indexChecks = 0, decryptions = 0, dbConnections = 0, rssMemory = 0;
|
|
368
|
+
const indexPattern = /Ensured unique index/;
|
|
369
|
+
const decryptPattern = /Token decrypted/;
|
|
370
|
+
const dbConnPattern = /Database connection successful/;
|
|
371
|
+
const memoryPattern = /RSS: (\d+) MB/;
|
|
372
|
+
for (const line of lines) {
|
|
373
|
+
if (indexPattern.test(line))
|
|
374
|
+
indexChecks++;
|
|
375
|
+
if (decryptPattern.test(line))
|
|
376
|
+
decryptions++;
|
|
377
|
+
if (dbConnPattern.test(line))
|
|
378
|
+
dbConnections++;
|
|
379
|
+
const memMatch = line.match(memoryPattern);
|
|
380
|
+
if (memMatch)
|
|
381
|
+
rssMemory = Math.max(rssMemory, parseInt(memMatch[1]));
|
|
382
|
+
}
|
|
383
|
+
console.log('\n--- š Operation Frequency ---');
|
|
384
|
+
console.log(`Ensured Unique Index: ${indexChecks}`);
|
|
385
|
+
console.log(`Token Decryptions: ${decryptions}`);
|
|
386
|
+
console.log(`DB Connections: ${dbConnections}`);
|
|
387
|
+
if (rssMemory > 0)
|
|
388
|
+
console.log(`Max RSS Detected: ${rssMemory} MB`);
|
|
389
|
+
}
|
|
390
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Runner script for thank-you email workflow
|
|
5
|
+
* Called by AI agents to generate thank-you emails
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
const generate_engagement_emails_1 = require("./generate-engagement-emails");
|
|
9
|
+
const fs_1 = require("fs");
|
|
10
|
+
const path_1 = require("path");
|
|
11
|
+
async function main() {
|
|
12
|
+
console.log('šÆ Starting thank-you email generation workflow...\n');
|
|
13
|
+
// Step 1: Get resolved issues
|
|
14
|
+
console.log('š Step 1: Retrieving resolved issues...');
|
|
15
|
+
const issues = await (0, generate_engagement_emails_1.getResolvedIssues)();
|
|
16
|
+
console.log(`\nā
Found ${issues.length} resolved issues\n`);
|
|
17
|
+
// Step 2: Extract customer information
|
|
18
|
+
console.log('š§ Step 2: Extracting customer information...\n');
|
|
19
|
+
const customerMap = new Map();
|
|
20
|
+
for (const issue of issues) {
|
|
21
|
+
// Extract email and name from issue body or title
|
|
22
|
+
let customerEmail = null;
|
|
23
|
+
let customerName = null;
|
|
24
|
+
// Try to extract from body first
|
|
25
|
+
const bodyEmailMatch = issue.body?.match(/\*\*Reported by:\*\*\s+(.+?)\s+\((.+?)\)/);
|
|
26
|
+
if (bodyEmailMatch) {
|
|
27
|
+
customerName = bodyEmailMatch[1].trim();
|
|
28
|
+
customerEmail = bodyEmailMatch[2].trim();
|
|
29
|
+
}
|
|
30
|
+
// Try to extract from title (format: "email: summary")
|
|
31
|
+
if (!customerEmail) {
|
|
32
|
+
const titleEmailMatch = issue.title.match(/^([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}):/);
|
|
33
|
+
if (titleEmailMatch) {
|
|
34
|
+
customerEmail = titleEmailMatch[1];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!customerEmail) {
|
|
38
|
+
console.log(`ā ļø Issue #${issue.number}: Could not extract customer email, skipping`);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
// Normalize email to lowercase for consistent matching
|
|
42
|
+
const normalizedEmail = customerEmail.toLowerCase();
|
|
43
|
+
// Add or update customer data
|
|
44
|
+
if (!customerMap.has(normalizedEmail)) {
|
|
45
|
+
customerMap.set(normalizedEmail, {
|
|
46
|
+
email: normalizedEmail,
|
|
47
|
+
name: customerName || 'Valued Customer',
|
|
48
|
+
issues: []
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const customerData = customerMap.get(normalizedEmail);
|
|
52
|
+
customerData.issues.push({
|
|
53
|
+
number: issue.number,
|
|
54
|
+
title: issue.title,
|
|
55
|
+
body: issue.body || ''
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
console.log(`š Found ${customerMap.size} unique customers\n`);
|
|
59
|
+
// Step 3: Lookup executive data for each customer
|
|
60
|
+
console.log('š Step 3: Looking up executive data...\n');
|
|
61
|
+
const entries = Array.from(customerMap.entries());
|
|
62
|
+
for (const [email, customerData] of entries) {
|
|
63
|
+
try {
|
|
64
|
+
const executive = await (0, generate_engagement_emails_1.findExecutiveByEmail)(email);
|
|
65
|
+
if (executive) {
|
|
66
|
+
customerData.executiveId = executive.id;
|
|
67
|
+
customerData.executiveName = executive.name;
|
|
68
|
+
if (executive.id) {
|
|
69
|
+
const personaEmail = await (0, generate_engagement_emails_1.getPersonaEmailForExecutive)(executive.id);
|
|
70
|
+
customerData.personaEmail = personaEmail;
|
|
71
|
+
console.log(`ā
${email}: Found executive ${executive.name} (${executive.id}), Persona email: ${personaEmail}`);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
console.log(`ā ļø ${email}: Found executive ${executive.name} but no ID`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
console.log(`ā ļø ${email}: No executive record found in database`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.error(`ā ${email}: Error looking up executive:`, error);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
console.log('\nš Customer Data Summary:\n');
|
|
86
|
+
console.log(JSON.stringify(Array.from(customerMap.values()), null, 2));
|
|
87
|
+
// Save to temp file for AI agent to process
|
|
88
|
+
const outputPath = (0, path_1.join)(process.cwd(), 'temp-customer-data.json');
|
|
89
|
+
(0, fs_1.writeFileSync)(outputPath, JSON.stringify(Array.from(customerMap.values()), null, 2));
|
|
90
|
+
console.log(`\nš¾ Customer data saved to: ${outputPath}`);
|
|
91
|
+
}
|
|
92
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Send Newsletter - Deterministic Script
|
|
5
|
+
*
|
|
6
|
+
* Simple script that users run after approving newsletter JSON.
|
|
7
|
+
* No decisions, just sends the newsletter.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* npm run send-newsletter # Send to all executives and potential customers
|
|
11
|
+
* npm run send-newsletter -- --showonly # List recipients without sending
|
|
12
|
+
* npm run send-newsletter -- --no-potential-customers # Send only to active executives
|
|
13
|
+
* npm run send-newsletter -- --only-potential-customers # Send only to potential customers
|
|
14
|
+
* npm run send-newsletter -- --email=user@example.com # Send to specific email
|
|
15
|
+
* npm run send-newsletter -- exec-XXX exec-YYY # Send to specific executive IDs
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
const newsletter_helpers_1 = require("./newsletter-helpers");
|
|
19
|
+
const fs_1 = require("fs");
|
|
20
|
+
const path_1 = require("path");
|
|
21
|
+
async function main() {
|
|
22
|
+
console.log('š§ Weekly Newsletter Sender\n');
|
|
23
|
+
// Parse command line arguments
|
|
24
|
+
const args = process.argv.slice(2);
|
|
25
|
+
const showOnly = args.includes('--showonly');
|
|
26
|
+
const excludePotentialCustomers = args.includes('--no-potential-customers');
|
|
27
|
+
const onlyPotentialCustomers = args.includes('--only-potential-customers');
|
|
28
|
+
const filterEmails = [];
|
|
29
|
+
const filterExecIds = [];
|
|
30
|
+
// Parse email filters (--email=address)
|
|
31
|
+
args.forEach(arg => {
|
|
32
|
+
if (arg.startsWith('--email=')) {
|
|
33
|
+
filterEmails.push(arg.substring(8));
|
|
34
|
+
}
|
|
35
|
+
else if (arg.startsWith('exec-')) {
|
|
36
|
+
filterExecIds.push(arg);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
// Find the most recent newsletter
|
|
40
|
+
const newslettersDir = (0, path_1.join)(process.cwd(), 'docs', 'customer-development', 'newsletters');
|
|
41
|
+
if (!(0, fs_1.existsSync)(newslettersDir)) {
|
|
42
|
+
console.error('ā Error: No newsletters directory found');
|
|
43
|
+
console.error(' Expected: docs/customer-development/newsletters/');
|
|
44
|
+
console.error(' Generate a newsletter first using the AI agent workflow');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
const files = (0, fs_1.readdirSync)(newslettersDir)
|
|
48
|
+
.filter(f => f.startsWith('newsletter-') && f.endsWith('.json'))
|
|
49
|
+
.sort()
|
|
50
|
+
.reverse(); // Most recent first
|
|
51
|
+
if (files.length === 0) {
|
|
52
|
+
console.error('ā Error: No newsletter files found');
|
|
53
|
+
console.error(' Generate a newsletter first using the AI agent workflow');
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
const latestNewsletter = files[0];
|
|
57
|
+
const newsletterPath = (0, path_1.join)(newslettersDir, latestNewsletter);
|
|
58
|
+
console.log(`š Found newsletter: ${latestNewsletter}`);
|
|
59
|
+
if (showOnly) {
|
|
60
|
+
console.log(`š Listing recipients (--showonly mode - no emails will be sent)...\n`);
|
|
61
|
+
}
|
|
62
|
+
else if (filterEmails.length > 0 || filterExecIds.length > 0) {
|
|
63
|
+
console.log(`š§ Sending to filtered recipients...\n`);
|
|
64
|
+
console.log(`ā ļø This will send real emails. Make sure the newsletter is approved!`);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
if (onlyPotentialCustomers) {
|
|
68
|
+
console.log(`š§ Sending to potential customers only...\n`);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.log(`š§ Sending to all active executives${excludePotentialCustomers ? '' : ' and potential customers'}...\n`);
|
|
72
|
+
}
|
|
73
|
+
console.log(`ā ļø This will send real emails. Make sure the newsletter is approved!`);
|
|
74
|
+
}
|
|
75
|
+
console.log(` Preview: ${newsletterPath.replace('.json', '.html')}\n`);
|
|
76
|
+
// Determine what to include based on flags
|
|
77
|
+
const includePotentialCustomers = onlyPotentialCustomers || !excludePotentialCustomers;
|
|
78
|
+
const includeExecutives = !onlyPotentialCustomers;
|
|
79
|
+
// Send newsletter (or show only)
|
|
80
|
+
await (0, newsletter_helpers_1.sendNewsletterToExecutives)(newsletterPath, filterEmails.length > 0 ? filterEmails : undefined, filterExecIds.length > 0 ? filterExecIds : undefined, includePotentialCustomers, showOnly, includeExecutives);
|
|
81
|
+
}
|
|
82
|
+
main().catch(error => {
|
|
83
|
+
console.error('ā Error:', error.message);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Send Thank-You Emails from Candidates JSON File
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npm run send-thank-you -- <filename> [exec-id-1] [exec-id-2] ...
|
|
8
|
+
*
|
|
9
|
+
* Examples:
|
|
10
|
+
* npm run send-thank-you -- docs/customer-development/thank-you-notes/thank-you-candidates-2025-10-29.json
|
|
11
|
+
* npm run send-thank-you -- docs/customer-development/thank-you-notes/thank-you-candidates-2025-10-29.json exec-123 exec-456
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
const generate_engagement_emails_1 = require("./generate-engagement-emails");
|
|
15
|
+
async function main() {
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
if (args.length === 0) {
|
|
18
|
+
console.error('ā Error: Missing required argument: filename');
|
|
19
|
+
console.error('');
|
|
20
|
+
console.error('Usage: npm run send-thank-you -- <filename> [exec-id-1] [exec-id-2] ...');
|
|
21
|
+
console.error('');
|
|
22
|
+
console.error('Examples:');
|
|
23
|
+
console.error(' npm run send-thank-you -- docs/customer-development/thank-you-notes/thank-you-candidates-2025-10-29.json');
|
|
24
|
+
console.error(' npm run send-thank-you -- docs/customer-development/thank-you-notes/thank-you-candidates-2025-10-29.json exec-1760627251980-31qmjy5y4');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
const filename = args[0];
|
|
28
|
+
const execIds = args.slice(1).filter(arg => arg.startsWith('exec-'));
|
|
29
|
+
if (execIds.length === 0) {
|
|
30
|
+
// Send to all pending candidates
|
|
31
|
+
console.log(`š§ Sending emails from: ${filename}`);
|
|
32
|
+
console.log(`šÆ Sending to ALL pending candidates`);
|
|
33
|
+
await (0, generate_engagement_emails_1.sendCustomerMail)(filename);
|
|
34
|
+
}
|
|
35
|
+
else if (execIds.length === 1) {
|
|
36
|
+
// Send to specific executive
|
|
37
|
+
console.log(`š§ Sending emails from: ${filename}`);
|
|
38
|
+
console.log(`šÆ Sending to executive: ${execIds[0]}`);
|
|
39
|
+
await (0, generate_engagement_emails_1.sendCustomerMail)(filename, execIds[0]);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// Multiple executives - send to each one
|
|
43
|
+
console.log(`š§ Sending emails from: ${filename}`);
|
|
44
|
+
console.log(`šÆ Sending to ${execIds.length} executives: ${execIds.join(', ')}`);
|
|
45
|
+
for (const execId of execIds) {
|
|
46
|
+
console.log(`\nš§ Processing executive: ${execId}`);
|
|
47
|
+
await (0, generate_engagement_emails_1.sendCustomerMail)(filename, execId);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
main().catch(error => {
|
|
52
|
+
console.error('ā Error:', error);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
});
|