@chemx/starter-kit 26.9.9-786 → 26.9.11-481

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/cli/audit/ast-visitors.js +243 -0
  2. package/cli/audit/discussion-store.js +39 -0
  3. package/cli/audit/history.d.ts +69 -0
  4. package/cli/audit/history.js +397 -0
  5. package/cli/audit/metrics.js +179 -0
  6. package/cli/audit/prompts.js +174 -0
  7. package/cli/audit/reporter-ascii.d.ts +4 -0
  8. package/cli/audit/reporter-ascii.js +99 -0
  9. package/cli/audit/reporter-grades.js +275 -0
  10. package/cli/audit/reporter-markdown.js +137 -0
  11. package/cli/audit/reporter-utils.js +125 -0
  12. package/cli/audit/reporter.js +497 -0
  13. package/cli/audit/rules-helpers.js +109 -0
  14. package/cli/audit/rules-registry.js +97 -0
  15. package/cli/audit/rules.js +123 -0
  16. package/cli/audit/social-constants.js +4 -0
  17. package/cli/audit/social-gh.js +126 -0
  18. package/cli/audit/social-git.js +214 -0
  19. package/cli/audit/social-http.js +222 -0
  20. package/cli/audit/social-publisher.js +193 -0
  21. package/cli/audit/social.d.ts +69 -0
  22. package/cli/audit/social.js +303 -0
  23. package/cli/audit/types.d.ts +140 -0
  24. package/cli/audit.d.ts +3 -24
  25. package/cli/audit.js +207 -210
  26. package/cli/help.d.ts +1 -0
  27. package/cli/help.js +136 -0
  28. package/cli/index.js +128 -566
  29. package/cli/installer-templates.js +91 -0
  30. package/cli/installer.d.ts +21 -0
  31. package/cli/installer.js +104 -0
  32. package/cli/license.js +320 -0
  33. package/cli/navigator-actions.js +274 -0
  34. package/cli/navigator-banner.js +98 -0
  35. package/cli/navigator-conversion.js +100 -0
  36. package/cli/navigator-grades.js +87 -0
  37. package/cli/navigator-menu.js +127 -0
  38. package/cli/navigator-paged.js +86 -0
  39. package/cli/navigator-share.js +129 -0
  40. package/cli/navigator.d.ts +43 -0
  41. package/cli/navigator.js +126 -0
  42. package/cli/scaffold.js +148 -0
  43. package/cli/terminal.js +126 -0
  44. package/docs/CHANGELOG.md +101 -0
  45. package/package.json +11 -10
@@ -0,0 +1,274 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import {
4
+ hasGum,
5
+ gumChoose,
6
+ gumInput,
7
+ promptQuestion
8
+ } from "./terminal.js";
9
+ import {
10
+ formatTerminalReport,
11
+ generateMarkdownReport,
12
+ groupViolationsBySeverity,
13
+ formatGradeFSection,
14
+ formatGradeDSection,
15
+ formatGradeCSection,
16
+ formatGradeBSection,
17
+ formatGradeASection,
18
+ getAuditHistory,
19
+ getAuditBaseline,
20
+ createSnapshotFromReport,
21
+ formatTransformationTerminal,
22
+ formatHistoryTimelineTerminal,
23
+ buildGradeFPrompt,
24
+ buildGradeDPrompt,
25
+ buildGradeCPrompt,
26
+ buildGradeBPrompt,
27
+ buildMasterPrompt,
28
+ copyToClipboard
29
+ } from "./audit.js";
30
+ import { resolveGradeBadge } from "./navigator-grades.js";
31
+ import { showPagedContent } from "./navigator-paged.js";
32
+ import { showConversionMenu } from "./navigator-conversion.js";
33
+ import { handleShareToDiscussions } from "./navigator-share.js";
34
+ import { runInstallWizard } from "./installer.js";
35
+ import { formatButtonTag } from "./navigator-menu.js";
36
+
37
+ const isExtremeMonolith = (h) => h.lineCount >= 2000;
38
+ const isSevereMonolith = (h) => h.lineCount >= 1000 && h.lineCount < 2000;
39
+ const isWarningMonolith = (h) => h.lineCount >= 500 && h.lineCount < 1000;
40
+ const isPassedPillar = (p) => p.status === "PASSED";
41
+ const hasGradeItems = (g) => g.count > 0;
42
+
43
+ export const buildActiveGrades = (report) => {
44
+ const { hotspots, violations, pillars } = report;
45
+ const { critical, high, medium, low } = groupViolationsBySeverity(violations);
46
+
47
+ const passedPillarsCount = Object.values(pillars || {}).filter(isPassedPillar).length;
48
+
49
+ const extremeMonoliths = hotspots.filter(isExtremeMonolith);
50
+ const severeMonoliths = hotspots.filter(isSevereMonolith);
51
+ const warningMonoliths = hotspots.filter(isWarningMonolith);
52
+
53
+ const gradeFCount = critical.length + extremeMonoliths.length;
54
+ const gradeDCount = high.length + severeMonoliths.length;
55
+ const gradeCCount = medium.length + warningMonoliths.length;
56
+ const gradeBCount = low.length;
57
+ const gradeACount = passedPillarsCount;
58
+
59
+ const handleGradeAAction = async () => {
60
+ await showPagedContent(formatGradeASection(report));
61
+ };
62
+ const handleGradeBAction = async () => {
63
+ await showPagedContent(formatGradeBSection(report), buildGradeBPrompt(report));
64
+ };
65
+ const handleGradeCAction = async () => {
66
+ await showPagedContent(formatGradeCSection(report), buildGradeCPrompt(report));
67
+ };
68
+ const handleGradeDAction = async () => {
69
+ await showPagedContent(formatGradeDSection(report), buildGradeDPrompt(report));
70
+ };
71
+ const handleGradeFAction = async () => {
72
+ await showPagedContent(formatGradeFSection(report), buildGradeFPrompt(report));
73
+ };
74
+
75
+ const gradeTiers = [
76
+ {
77
+ grade: "A",
78
+ count: gradeACount,
79
+ icon: "✅",
80
+ description: "Compliant Checks & Passing Pillars",
81
+ action: handleGradeAAction
82
+ },
83
+ {
84
+ grade: "B",
85
+ count: gradeBCount,
86
+ icon: "💣",
87
+ description: "Low Hygiene Issues & Minor Debts",
88
+ action: handleGradeBAction
89
+ },
90
+ {
91
+ grade: "C",
92
+ count: gradeCCount,
93
+ icon: "⚡",
94
+ description: "Medium Severity Debts & Monolith Drift",
95
+ action: handleGradeCAction
96
+ },
97
+ {
98
+ grade: "D",
99
+ count: gradeDCount,
100
+ icon: "🔥",
101
+ description: "High Severity Debts & Severe Monoliths",
102
+ action: handleGradeDAction
103
+ },
104
+ {
105
+ grade: "F",
106
+ count: gradeFCount,
107
+ icon: "💥",
108
+ description: "Critical Hazards & Extreme Monoliths",
109
+ action: handleGradeFAction
110
+ }
111
+ ];
112
+
113
+ const createActiveGradeItem = (g) => ({
114
+ key: `grade_${g.grade.toLowerCase()}`,
115
+ grade: g.grade.toLowerCase(),
116
+ tag: resolveGradeBadge(g.grade, 11),
117
+ label: `${g.icon} Grade ${g.grade}: ${g.description} (${g.count} ${g.grade === "A" ? "clean" : "items"})`,
118
+ action: g.action
119
+ });
120
+
121
+ return gradeTiers.filter(hasGradeItems).map(createActiveGradeItem);
122
+ };
123
+
124
+ export const buildDashboardActionGroups = ({ report, onScaffold = null }) => {
125
+ const handleInstallAction = async () => {
126
+ await runInstallWizard(process.cwd());
127
+ if (hasGum()) {
128
+ gumChoose(["<-- Back to Audit Dashboard"]);
129
+ }
130
+ };
131
+
132
+ const handleUpgradeAction = async () => {
133
+ await showConversionMenu(onScaffold);
134
+ };
135
+
136
+ const handleReportAction = async () => {
137
+ await showPagedContent(formatTerminalReport(report), buildMasterPrompt(report));
138
+ };
139
+
140
+ const handleShareAction = async () => {
141
+ await handleShareToDiscussions(report);
142
+ };
143
+
144
+ const handleProgressAction = async () => {
145
+ const baseline = getAuditBaseline();
146
+ const history = getAuditHistory();
147
+ const currentSnapshot = createSnapshotFromReport(report);
148
+
149
+ let output = "";
150
+ if (baseline) {
151
+ output += formatTransformationTerminal(baseline, currentSnapshot);
152
+ } else {
153
+ output +=
154
+ "\n\x1b[33mNo baseline audit found. Current audit established as baseline.\x1b[0m\n";
155
+ }
156
+
157
+ if (history.length > 0) {
158
+ output += "\n\n" + formatHistoryTimelineTerminal(history);
159
+ }
160
+
161
+ await showPagedContent(output);
162
+ };
163
+
164
+ const handleExportAction = async () => {
165
+ const outName = hasGum()
166
+ ? gumInput("Export file path:", "AUDIT_REPORT.md") || "AUDIT_REPORT.md"
167
+ : (await promptQuestion("Export file path [AUDIT_REPORT.md]: ")) || "AUDIT_REPORT.md";
168
+ const md = generateMarkdownReport(report);
169
+ fs.writeFileSync(path.resolve(process.cwd(), outName), md, "utf-8");
170
+ process.stdout.write(`\x1b[32m✔ Exported markdown audit report to ${outName}\x1b[0m\n\n`);
171
+ if (hasGum()) {
172
+ gumChoose(["<-- Back to Audit Dashboard"]);
173
+ }
174
+ };
175
+
176
+ const handleCopyPromptAction = async () => {
177
+ const masterPrompt = buildMasterPrompt(report);
178
+ if (!masterPrompt) {
179
+ process.stdout.write("\n\x1b[32m✔ Codebase is pristine (Grade A+). No refactoring prompt needed!\x1b[0m\n\n");
180
+ if (hasGum()) {
181
+ gumChoose(["<-- Back to Audit Dashboard"]);
182
+ }
183
+ return;
184
+ }
185
+ const success = copyToClipboard(masterPrompt);
186
+ if (success) {
187
+ process.stdout.write(
188
+ "\n\x1b[1m\x1b[32m✔ AI Agent refactoring prompt copied to clipboard!\x1b[0m\n" +
189
+ "\x1b[36mPaste directly into Cursor, Claude, or Windsurf to resolve architectural hazards.\x1b[0m\n\n"
190
+ );
191
+ } else {
192
+ process.stdout.write("\n\x1b[33m⚠ Could not access system clipboard.\x1b[0m\n\n");
193
+ }
194
+ if (hasGum()) {
195
+ gumChoose(["<-- Back to Audit Dashboard"]);
196
+ }
197
+ };
198
+
199
+ const handleExitAction = async () => {};
200
+
201
+ const installAction = {
202
+ key: "install",
203
+ tag: formatButtonTag("Install", "\x1b[32m"),
204
+ label: "🪝 Install Pre-Commit Hook & GitHub CI Workflow",
205
+ action: handleInstallAction
206
+ };
207
+
208
+ const upgradeAction = {
209
+ key: "upgrade",
210
+ tag: formatButtonTag("Upgrade", "\x1b[33m"),
211
+ label: "💎 Unlock Full Molecular Rules & Scaffolding (Chemical X: Team Power Puff)",
212
+ action: handleUpgradeAction
213
+ };
214
+
215
+ const reportAction = {
216
+ key: "report",
217
+ tag: formatButtonTag("Full Report", "\x1b[36m"),
218
+ label: "📋 Show Full Report (All Sections at Once)",
219
+ action: handleReportAction
220
+ };
221
+
222
+ const shareAction = {
223
+ key: "share",
224
+ tag: formatButtonTag("Share", "\x1b[35m"),
225
+ label: "🕵 Plug your site and Post to GitHub Discussions (npx chemx audit)",
226
+ action: handleShareAction
227
+ };
228
+
229
+ const progressAction = {
230
+ key: "progress",
231
+ tag: formatButtonTag("Progress", "\x1b[32m"),
232
+ label: "📈 View Before & After Transformation Progress",
233
+ action: handleProgressAction
234
+ };
235
+
236
+ const exportAction = {
237
+ key: "export",
238
+ tag: formatButtonTag("Export", "\x1b[36m"),
239
+ label: "💾 Export Markdown Report to File",
240
+ action: handleExportAction
241
+ };
242
+
243
+ const copyPromptAction = {
244
+ key: "prompt",
245
+ tag: formatButtonTag("Prompt", "\x1b[38;2;98;201;255m"),
246
+ label: "📋 Copy AI Prompt Fix to Clipboard",
247
+ action: handleCopyPromptAction
248
+ };
249
+
250
+ const exitAction = {
251
+ key: "exit",
252
+ tag: formatButtonTag("Exit", "\x1b[90m"),
253
+ label: "🚪 Exit",
254
+ action: handleExitAction
255
+ };
256
+
257
+ const masterPrompt = buildMasterPrompt(report);
258
+ const isGradeAPlus = report?.health?.grade === "A+" || report?.metrics?.grade === "A+";
259
+ const hasPromptToCopy = Boolean(masterPrompt && masterPrompt.trim().length > 0);
260
+ const isCleanAPlus = isGradeAPlus && !hasPromptToCopy;
261
+ const shouldShowPromptAction = hasPromptToCopy && !isCleanAPlus;
262
+
263
+ return {
264
+ installAction,
265
+ upgradeAction,
266
+ reportAction,
267
+ shareAction,
268
+ progressAction,
269
+ exportAction,
270
+ copyPromptAction,
271
+ exitAction,
272
+ shouldShowPromptAction
273
+ };
274
+ };
@@ -0,0 +1,98 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { getChemicalXAsciiBanner } from "./audit.js";
4
+ import { resolveGradeColor, resolveHealthHearts } from "./navigator-grades.js";
5
+
6
+ const visualWidth = (s) => {
7
+ const stripped = s.replace(/\x1b\[[0-9;]*m/g, "");
8
+ let w = 0;
9
+ for (const seg of new Intl.Segmenter().segment(stripped)) {
10
+ const char = seg.segment;
11
+ const isDoubleWidth = char === "❤️" || char === "🖤" || char.codePointAt(0) > 0x1f000;
12
+ w += isDoubleWidth ? 2 : char.length;
13
+ }
14
+ return w;
15
+ };
16
+
17
+ const resolveProjectName = () => {
18
+ try {
19
+ const pkgPath = path.resolve(process.cwd(), "package.json");
20
+ if (fs.existsSync(pkgPath)) return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).name || path.basename(process.cwd());
21
+ } catch {}
22
+ return path.basename(process.cwd()) || "Project";
23
+ };
24
+
25
+ const truncatePath = (p, maxLen) => (p.length <= maxLen ? p : `…${p.slice(p.length - maxLen + 1)}`);
26
+
27
+ export const renderDashboardBanner = (
28
+ health,
29
+ metrics,
30
+ violations,
31
+ critical,
32
+ highMediumCount,
33
+ low,
34
+ contextAnalysis = null
35
+ ) => {
36
+ if (process.stdout.isTTY) console.clear();
37
+ process.stdout.write(getChemicalXAsciiBanner(health.grade));
38
+
39
+ const gColor = resolveGradeColor(health.grade);
40
+ const critColor = critical.length > 0 ? "\x1b[31;1m" : "\x1b[32m";
41
+ const highMedColor = highMediumCount > 0 ? "\x1b[38;5;208;1m" : "\x1b[32m";
42
+ const lowColor = low.length > 0 ? "\x1b[33;1m" : "\x1b[32m";
43
+
44
+ const hearts = resolveHealthHearts(health.grade, health.score);
45
+ const scoreBadge = `${health.score}/100 [Grade: ${health.grade}]`;
46
+ const labelStr = health.label ? ` (${health.label})` : "";
47
+ const healthStr = `${hearts} ${gColor}${scoreBadge}\x1b[0m${gColor}${labelStr}\x1b[0m`;
48
+
49
+ const filesStr = `${metrics.scannedFiles} Files`;
50
+ const locStr = `${(metrics.totalLoc || 0).toLocaleString()}`;
51
+ const hazardsStr = `💥 ${critColor}${critical.length} Critical\x1b[0m | 🔥 ${highMedColor}${highMediumCount} High/Med\x1b[0m | 💣 ${lowColor}${low.length} Low\x1b[0m`;
52
+
53
+ const tokens = contextAnalysis?.estimatedTokens || 0;
54
+ const pct = contextAnalysis?.potentialSavingsPct || 0;
55
+ const costPass = contextAnalysis?.excessCostPerPass !== undefined
56
+ ? contextAnalysis.excessCostPerPass
57
+ : Number((((contextAnalysis?.estimatedExcessTokens || 0) / 1000000) * 3.0).toFixed(3));
58
+ const costMonth = contextAnalysis?.monthlyWastePerDev !== undefined
59
+ ? contextAnalysis.monthlyWastePerDev
60
+ : Number((costPass * 20 * 5 * 4).toFixed(2));
61
+
62
+ const tokensStr = `~${tokens.toLocaleString()} tokens` + (pct > 0 ? ` (${pct}% cut)` : "");
63
+ const costStr = costPass > 0
64
+ ? `\x1b[38;5;208;1m$${costPass.toFixed(3)}\x1b[0m\x1b[2m/turn\x1b[0m | \x1b[31;1m$${costMonth.toFixed(2)}\x1b[0m\x1b[2m/mo\x1b[0m`
65
+ : `\x1b[32;1m$0.000\x1b[0m\x1b[2m/turn\x1b[0m | \x1b[32;1m$0.00\x1b[0m\x1b[2m/mo\x1b[0m`;
66
+
67
+ const col1 = [
68
+ "\x1b[36;1m-FILES-\x1b[0m", `\x1b[36m${filesStr}\x1b[0m`,
69
+ "\x1b[36;1m-LINES OF CODE-\x1b[0m", `\x1b[36m${locStr}\x1b[0m`,
70
+ "\x1b[36;1m-TOKEN EST-\x1b[0m", `\x1b[36m${tokensStr}\x1b[0m`
71
+ ];
72
+ const col2 = [
73
+ "\x1b[31;1m-HEALTH-\x1b[0m", healthStr,
74
+ "\x1b[33;1m-HAZARDS-\x1b[0m", hazardsStr,
75
+ "\x1b[32;1m-CURRENCY-\x1b[0m", costStr
76
+ ];
77
+
78
+ const col1Pad = 32;
79
+ const rows = [];
80
+ let maxW = 0;
81
+ for (let i = 0; i < col1.length; i++) {
82
+ const pad = " ".repeat(Math.max(2, col1Pad - visualWidth(col1[i])));
83
+ const content = col1[i] + pad + col2[i];
84
+ const w = visualWidth(content);
85
+ if (w > maxW) maxW = w;
86
+ rows.push({ content, width: w });
87
+ }
88
+
89
+ const projectName = resolveProjectName();
90
+ const innerWidth = Math.max(maxW, visualWidth(projectName) + 4);
91
+ const bColor = "\x1b[1;38;5;81m";
92
+ const topBorder = `${bColor}╭── \x1b[1;37m${projectName}${bColor} ${"─".repeat(Math.max(2, innerWidth - visualWidth(projectName)))}╮\x1b[0m`;
93
+ const displayPwd = truncatePath(process.cwd(), Math.max(10, innerWidth - 4));
94
+ const botBorder = `${bColor}╰── \x1b[36m${displayPwd}${bColor} ${"─".repeat(Math.max(2, innerWidth - visualWidth(displayPwd)))}╯\x1b[0m`;
95
+ const bodyLines = rows.map((r) => `${bColor}│\x1b[0m ${r.content}${" ".repeat(innerWidth - r.width)} ${bColor}│\x1b[0m`);
96
+
97
+ process.stdout.write(`\n${[topBorder, ...bodyLines, botBorder].join("\n")}\n\n`);
98
+ };
@@ -0,0 +1,100 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import {
3
+ hasGum,
4
+ gumChoose,
5
+ promptQuestion,
6
+ openBrowser
7
+ } from './terminal.js';
8
+ import {
9
+ URL_LEARN,
10
+ URL_SPONSOR,
11
+ URL_STANDARD,
12
+ URL_MASTER
13
+ } from './license.js';
14
+
15
+ export const showConversionMenu = async (onScaffold = null) => {
16
+ while (true) {
17
+ if (hasGum()) {
18
+ spawnSync('gum', [
19
+ 'style', '--border=rounded', '--border-foreground=81', '--padding=0 1', '--bold',
20
+ 'Chemical X: The Secret Sauce to Vibe Coding\nStop letting AI agents scour 2,000-line monoliths and hallucinate.\n25+ Years XP | Codified by Principal Systems Architect Xopher Pollard\nTarget File Budget: Max 500 lines/file (<100 lines/molecule) | 85% Token Burn Cut'
21
+ ], { stdio: 'inherit' });
22
+
23
+ const choice = gumChoose([
24
+ '1. Sponsor on GitHub: $9/mo Solo / $49/mo Team Unlimited -> Launch Sponsors',
25
+ '2. Buy Standard Vault: Single Dev License ($27) -> Launch Checkout',
26
+ '3. Buy Team Power Puff: Unlimited Lifetime ($97) -> Launch Checkout',
27
+ '4. Enter License Key to Scaffold (Sponsors & License Holders)',
28
+ '5. Visit chemicalx.xophz.com to learn more',
29
+ '6. Back to Navigator'
30
+ ], 'Unlock Chemical X: Team Power Puff & Scaffolding:');
31
+
32
+ if (choice.startsWith('1.')) {
33
+ process.stdout.write(`\n\x1b[36mOpening GitHub Sponsors in browser:\x1b[0m ${URL_SPONSOR}\n\n`);
34
+ openBrowser(URL_SPONSOR);
35
+ continue;
36
+ }
37
+ if (choice.startsWith('2.')) {
38
+ process.stdout.write(`\n\x1b[36mOpening Standard Vault checkout (Single Dev License):\x1b[0m ${URL_STANDARD}\n\n`);
39
+ openBrowser(URL_STANDARD);
40
+ continue;
41
+ }
42
+ if (choice.startsWith('3.')) {
43
+ process.stdout.write(`\n\x1b[36mOpening Team Power Puff checkout (Unlimited Lifetime):\x1b[0m ${URL_MASTER}\n\n`);
44
+ openBrowser(URL_MASTER);
45
+ continue;
46
+ }
47
+ if (choice.startsWith('4.')) {
48
+ if (onScaffold) await onScaffold();
49
+ break;
50
+ }
51
+ if (choice.startsWith('5.')) {
52
+ process.stdout.write(`\n\x1b[36mOpening Chemical X Portal in browser:\x1b[0m ${URL_LEARN}\n\n`);
53
+ openBrowser(URL_LEARN);
54
+ continue;
55
+ }
56
+ break;
57
+ }
58
+
59
+ process.stdout.write(
60
+ '\n\x1b[1m\x1b[38;2;98;201;255mChemical X: The Secret Sauce to Vibe Coding\x1b[0m\n' +
61
+ 'Stop letting AI agents scour 2,000-line monoliths and hallucinate breaking changes.\n' +
62
+ '25+ Years XP | Codified by Principal Systems Architect Xopher Pollard\n' +
63
+ 'Target File Budget: Max 500 lines/file (<100 lines per molecule capsule).\n\n'
64
+ );
65
+ process.stdout.write(` [1] Sponsor on GitHub ($9/mo Solo / $49/mo Team Unlimited) - ${URL_SPONSOR}\n Includes: Instant GitHub org access to private starter-kit, continuous model prompt updates\n`);
66
+ process.stdout.write(` [2] Buy Standard Vault: Single Dev License ($27) - ${URL_STANDARD}\n Includes: Kindle/Print PDF eBook, 7 Molecular Architecture Chapters, Universal AGENTS.md\n`);
67
+ process.stdout.write(` [3] Buy Team Power Puff: Unlimited Lifetime ($97) - ${URL_MASTER}\n Includes: Unlimited Team Seats, Private Starter-Kit Repo, Pre-Commit Line Budget Hooks, VIP Discord\n`);
68
+ process.stdout.write(' [4] Enter License Key to Scaffold (Sponsors & License Holders)\n');
69
+ process.stdout.write(` [5] Visit chemicalx.xophz.com to learn more - ${URL_LEARN}\n`);
70
+ process.stdout.write(' [6] Back to Navigator\n\n');
71
+
72
+ const selection = await promptQuestion('Select option [1-6] (default: 6): ');
73
+ const effectiveSelection = selection.trim() || '6';
74
+ if (effectiveSelection === '1') {
75
+ process.stdout.write(`\nOpening: ${URL_SPONSOR}\n\n`);
76
+ openBrowser(URL_SPONSOR);
77
+ continue;
78
+ }
79
+ if (effectiveSelection === '2') {
80
+ process.stdout.write(`\nOpening: ${URL_STANDARD}\n\n`);
81
+ openBrowser(URL_STANDARD);
82
+ continue;
83
+ }
84
+ if (effectiveSelection === '3') {
85
+ process.stdout.write(`\nOpening: ${URL_MASTER}\n\n`);
86
+ openBrowser(URL_MASTER);
87
+ continue;
88
+ }
89
+ if (effectiveSelection === '4') {
90
+ if (onScaffold) await onScaffold();
91
+ break;
92
+ }
93
+ if (effectiveSelection === '5') {
94
+ process.stdout.write(`\nOpening: ${URL_LEARN}\n\n`);
95
+ openBrowser(URL_LEARN);
96
+ continue;
97
+ }
98
+ break;
99
+ }
100
+ };
@@ -0,0 +1,87 @@
1
+ export const resolveGradeColor = (grade) => {
2
+ if (grade === 'A+' || grade === 'A') return '\x1b[32;1m';
3
+ if (grade === 'B') return '\x1b[33;1m';
4
+ if (grade === 'C') return '\x1b[33;1m';
5
+ if (grade === 'D') return '\x1b[38;5;208;1m';
6
+ return '\x1b[31;1m';
7
+ };
8
+
9
+ export const resolveGradeBadge = (grade, width = 11) => {
10
+ const color = resolveGradeColor(grade);
11
+ const text = `Grade: ${grade}`;
12
+ const totalPadding = Math.max(0, width - text.length);
13
+ const leftPad = Math.floor(totalPadding / 2);
14
+ const rightPad = totalPadding - leftPad;
15
+ const padded = `${' '.repeat(leftPad)}${text}${' '.repeat(rightPad)}`;
16
+ return `${color}[${padded}]\x1b[0m`;
17
+ };
18
+
19
+ export const resolveHealthHearts = (grade, score = 0) => {
20
+ const g = String(grade || '').toUpperCase();
21
+ let filled = 1;
22
+ if (g.startsWith('A') || score >= 90) filled = 5;
23
+ else if (g.startsWith('B') || score >= 80) filled = 4;
24
+ else if (g.startsWith('C') || score >= 70) filled = 3;
25
+ else if (g.startsWith('D') || score >= 60) filled = 2;
26
+ else filled = score === 0 ? 0 : 1;
27
+
28
+ const empty = 5 - filled;
29
+ return '❤️'.repeat(filled) + '🖤'.repeat(empty);
30
+ };
31
+
32
+ export const resolveCriticalGrade = (count) => {
33
+ if (count === 0) return 'A+';
34
+ if (count === 1) return 'D';
35
+ return 'F';
36
+ };
37
+
38
+ export const resolveHighMedGrade = (count) => {
39
+ if (count === 0) return 'A+';
40
+ if (count <= 3) return 'B';
41
+ if (count <= 8) return 'C';
42
+ if (count <= 15) return 'D';
43
+ return 'F';
44
+ };
45
+
46
+ export const resolveLowGrade = (count) => {
47
+ if (count === 0) return 'A+';
48
+ if (count <= 5) return 'B';
49
+ if (count <= 15) return 'C';
50
+ return 'D';
51
+ };
52
+
53
+ const isFailedPillar = (p) => p.status === 'FAILED';
54
+ const isWarnPillar = (p) => p.status === 'WARN';
55
+
56
+ export const resolvePillarGrade = (pillars) => {
57
+ const values = Object.values(pillars || {});
58
+ const failed = values.filter(isFailedPillar).length;
59
+ const warn = values.filter(isWarnPillar).length;
60
+ if (failed === 0 && warn === 0) return 'A+';
61
+ if (failed === 0) return 'B';
62
+ if (failed <= 2) return 'C';
63
+ if (failed <= 4) return 'D';
64
+ return 'F';
65
+ };
66
+
67
+ const isExtremeFile = (h) => h.lineCount >= 2000;
68
+ const isSevereFile = (h) => h.lineCount >= 1000;
69
+ const isWarningFile = (h) => h.lineCount > 500;
70
+
71
+ export const resolveHotspotGrade = (hotspots) => {
72
+ if (!hotspots || hotspots.length === 0) return 'A+';
73
+ const hasExtreme = hotspots.some(isExtremeFile);
74
+ if (hasExtreme) return 'F';
75
+ const hasSevere = hotspots.some(isSevereFile);
76
+ if (hasSevere) return 'D';
77
+ const hasWarning = hotspots.some(isWarningFile);
78
+ if (hasWarning) return 'C';
79
+ return 'B';
80
+ };
81
+
82
+ export const resolveContextGrade = (riskTier) => {
83
+ if (riskTier === 'LOW') return 'A';
84
+ if (riskTier === 'MEDIUM') return 'B';
85
+ if (riskTier === 'HIGH') return 'D';
86
+ return 'F';
87
+ };
@@ -0,0 +1,127 @@
1
+ export const formatButtonTag = (label, color = "", width = 11) => {
2
+ const totalPadding = Math.max(0, width - label.length);
3
+ const leftPad = Math.floor(totalPadding / 2);
4
+ const rightPad = totalPadding - leftPad;
5
+ const inner = `${" ".repeat(leftPad)}${label}${" ".repeat(rightPad)}`;
6
+ return color ? `${color}[${inner}]\x1b[0m` : `[${inner}]`;
7
+ };
8
+
9
+ const isNonEmptyGroup = (group) => group.length > 0;
10
+
11
+ export const buildNavigatorMenu = (topActions, activeGrades, midActions, bottomActions) => {
12
+ const groups = [topActions, activeGrades, midActions, bottomActions].filter(isNonEmptyGroup);
13
+ const menuItems = groups.flat();
14
+ const menuOptions = [];
15
+ let currentIndex = 1;
16
+ const divider = "──────────────────────────────────────────────────────────────────────";
17
+
18
+ groups.forEach((group, idx) => {
19
+ for (const item of group) {
20
+ item.index = currentIndex;
21
+ const pad = String(currentIndex).padStart(2, " ");
22
+ menuOptions.push(` ${pad}. ${item.tag} ${item.label}`);
23
+ currentIndex++;
24
+ }
25
+ if (idx < groups.length - 1) {
26
+ menuOptions.push(divider);
27
+ }
28
+ });
29
+
30
+ return { menuItems, menuOptions };
31
+ };
32
+
33
+ const isChoiceMatchingItem = (cleanChoice) => (item) => {
34
+ const prefix = `${item.index}.`;
35
+ if (cleanChoice.startsWith(prefix) || cleanChoice === String(item.index)) return true;
36
+ if (
37
+ item.key === "exit" &&
38
+ (cleanChoice.includes("Exit") || cleanChoice.toLowerCase() === "exit")
39
+ ) {
40
+ return true;
41
+ }
42
+ if (
43
+ item.key === "prompt" &&
44
+ (cleanChoice.includes("Prompt") || cleanChoice.includes("Clipboard"))
45
+ ) {
46
+ return true;
47
+ }
48
+ if (
49
+ item.key === "report" &&
50
+ (cleanChoice.includes("Report") || cleanChoice.includes("Full Report"))
51
+ ) {
52
+ return true;
53
+ }
54
+ if (item.key === "progress" && cleanChoice.includes("Progress")) return true;
55
+ if (
56
+ item.key === "share" &&
57
+ (cleanChoice.includes("Share") ||
58
+ cleanChoice.includes("Discussions") ||
59
+ cleanChoice.includes("Plug your"))
60
+ ) {
61
+ return true;
62
+ }
63
+ if (item.key === "export" && cleanChoice.includes("Export")) return true;
64
+ if (
65
+ item.key === "install" &&
66
+ (cleanChoice.includes("Install") ||
67
+ cleanChoice.includes("Pre-Commit") ||
68
+ cleanChoice.includes("Workflow"))
69
+ ) {
70
+ return true;
71
+ }
72
+ if (
73
+ item.key === "upgrade" &&
74
+ (cleanChoice.includes("Upgrade") ||
75
+ cleanChoice.includes("Power Puff") ||
76
+ cleanChoice.includes("Team Power Puff") ||
77
+ cleanChoice.includes("Molecular Rules"))
78
+ ) {
79
+ return true;
80
+ }
81
+ if (
82
+ item.grade &&
83
+ (cleanChoice.toLowerCase().includes(`grade ${item.grade}`) ||
84
+ cleanChoice.toLowerCase().includes(`grade: ${item.grade}`))
85
+ ) {
86
+ return true;
87
+ }
88
+ return false;
89
+ };
90
+
91
+ export const matchGumChoice = (cleanChoice, menuItems) => {
92
+ const matcher = isChoiceMatchingItem(cleanChoice);
93
+ return menuItems.find(matcher);
94
+ };
95
+
96
+ const isEffectiveMatchingItem = (effective, exitIndex) => (item) => {
97
+ if (effective === String(item.index)) return true;
98
+ if (item.key === "exit" && (effective === "exit" || effective === String(exitIndex))) {
99
+ return true;
100
+ }
101
+ if (
102
+ item.key === "prompt" &&
103
+ (effective === "prompt" ||
104
+ effective === "copy" ||
105
+ effective === "c" ||
106
+ effective === "clipboard")
107
+ ) {
108
+ return true;
109
+ }
110
+ if (
111
+ item.key === "report" &&
112
+ (effective === "report" || effective === "full report" || effective === "full")
113
+ ) {
114
+ return true;
115
+ }
116
+ if (item.key === "share" && (effective === "share" || effective === "post")) return true;
117
+ if (item.key === effective) return true;
118
+ if (item.grade && (effective === item.grade || effective === `grade ${item.grade}`)) {
119
+ return true;
120
+ }
121
+ return false;
122
+ };
123
+
124
+ export const matchFallbackChoice = (effective, menuItems, exitIndex) => {
125
+ const matcher = isEffectiveMatchingItem(effective, exitIndex);
126
+ return menuItems.find(matcher);
127
+ };