@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.
- package/cli/audit/ast-visitors.js +243 -0
- package/cli/audit/discussion-store.js +39 -0
- package/cli/audit/history.d.ts +69 -0
- package/cli/audit/history.js +397 -0
- package/cli/audit/metrics.js +179 -0
- package/cli/audit/prompts.js +174 -0
- package/cli/audit/reporter-ascii.d.ts +4 -0
- package/cli/audit/reporter-ascii.js +99 -0
- package/cli/audit/reporter-grades.js +275 -0
- package/cli/audit/reporter-markdown.js +137 -0
- package/cli/audit/reporter-utils.js +125 -0
- package/cli/audit/reporter.js +497 -0
- package/cli/audit/rules-helpers.js +109 -0
- package/cli/audit/rules-registry.js +97 -0
- package/cli/audit/rules.js +123 -0
- package/cli/audit/social-constants.js +4 -0
- package/cli/audit/social-gh.js +126 -0
- package/cli/audit/social-git.js +214 -0
- package/cli/audit/social-http.js +222 -0
- package/cli/audit/social-publisher.js +193 -0
- package/cli/audit/social.d.ts +69 -0
- package/cli/audit/social.js +303 -0
- package/cli/audit/types.d.ts +140 -0
- package/cli/audit.d.ts +3 -24
- package/cli/audit.js +207 -210
- package/cli/help.d.ts +1 -0
- package/cli/help.js +136 -0
- package/cli/index.js +128 -566
- package/cli/installer-templates.js +91 -0
- package/cli/installer.d.ts +21 -0
- package/cli/installer.js +104 -0
- package/cli/license.js +320 -0
- package/cli/navigator-actions.js +274 -0
- package/cli/navigator-banner.js +98 -0
- package/cli/navigator-conversion.js +100 -0
- package/cli/navigator-grades.js +87 -0
- package/cli/navigator-menu.js +127 -0
- package/cli/navigator-paged.js +86 -0
- package/cli/navigator-share.js +129 -0
- package/cli/navigator.d.ts +43 -0
- package/cli/navigator.js +126 -0
- package/cli/scaffold.js +148 -0
- package/cli/terminal.js +126 -0
- package/docs/CHANGELOG.md +101 -0
- 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
|
+
};
|