bun-sticky 1.0.0

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/lib/scorer.ts ADDED
@@ -0,0 +1,225 @@
1
+ /**
2
+ * 🥐 Bun Sticky Scorer - Wolfejam Slot-Based Scoring
3
+ *
4
+ * Score = (Filled slots / Applicable slots) × 100
5
+ *
6
+ * 21 total slots, type-aware scoring.
7
+ * Zero dependencies. Pure Bun.
8
+ */
9
+
10
+ import { hasValue, getNestedValue } from "./parser.ts";
11
+
12
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
13
+ // SLOT DEFINITIONS - 21 Slots Total
14
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
15
+
16
+ export const SLOTS = {
17
+ // Project slots (3)
18
+ project: [
19
+ "project.name",
20
+ "project.goal",
21
+ "project.main_language",
22
+ ],
23
+ // Frontend slots (4)
24
+ frontend: [
25
+ "stack.frontend",
26
+ "stack.css_framework",
27
+ "stack.ui_library",
28
+ "stack.state_management",
29
+ ],
30
+ // Backend slots (5)
31
+ backend: [
32
+ "stack.backend",
33
+ "stack.api_type",
34
+ "stack.runtime",
35
+ "stack.database",
36
+ "stack.connection",
37
+ ],
38
+ // Universal slots (3)
39
+ universal: [
40
+ "stack.hosting",
41
+ "stack.build",
42
+ "stack.cicd",
43
+ ],
44
+ // Human context slots (6)
45
+ human: [
46
+ "human_context.who",
47
+ "human_context.what",
48
+ "human_context.why",
49
+ "human_context.where",
50
+ "human_context.when",
51
+ "human_context.how",
52
+ ],
53
+ } as const;
54
+
55
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
56
+ // TYPE DEFINITIONS - Which slots apply to each type
57
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
58
+
59
+ export type ProjectType =
60
+ | "cli"
61
+ | "library"
62
+ | "api"
63
+ | "webapp"
64
+ | "fullstack"
65
+ | "mobile"
66
+ | "unknown";
67
+
68
+ export const TYPE_CATEGORIES: Record<ProjectType, (keyof typeof SLOTS)[]> = {
69
+ // CLI/Tool: 9 slots (project + human)
70
+ cli: ["project", "human"],
71
+
72
+ // Library/Package: 9 slots (project + human)
73
+ library: ["project", "human"],
74
+
75
+ // API/Backend: 17 slots (project + backend + universal + human)
76
+ api: ["project", "backend", "universal", "human"],
77
+
78
+ // Web App: 16 slots (project + frontend + universal + human)
79
+ webapp: ["project", "frontend", "universal", "human"],
80
+
81
+ // Fullstack: 21 slots (all)
82
+ fullstack: ["project", "frontend", "backend", "universal", "human"],
83
+
84
+ // Mobile: 9 slots (project + human) - simplified
85
+ mobile: ["project", "human"],
86
+
87
+ // Unknown: 9 slots (project + human) - safe default
88
+ unknown: ["project", "human"],
89
+ };
90
+
91
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
92
+ // SCORE INTERFACE
93
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
94
+
95
+ export interface SlotSection {
96
+ filled: number;
97
+ total: number;
98
+ percentage: number;
99
+ }
100
+
101
+ export interface FafScore {
102
+ projectType: ProjectType;
103
+ sections: {
104
+ project: SlotSection;
105
+ frontend: SlotSection;
106
+ backend: SlotSection;
107
+ universal: SlotSection;
108
+ human: SlotSection;
109
+ };
110
+ filled: number;
111
+ total: number;
112
+ score: number;
113
+ }
114
+
115
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
116
+ // SCORING FUNCTIONS
117
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
118
+
119
+ /**
120
+ * Detect project type from .faf content
121
+ */
122
+ export function detectProjectType(faf: Record<string, unknown>): ProjectType {
123
+ const type = getNestedValue(faf, "project.type") as string;
124
+
125
+ if (type) {
126
+ const typeLower = type.toLowerCase();
127
+ if (typeLower.includes("cli")) return "cli";
128
+ if (typeLower.includes("lib") || typeLower.includes("package")) return "library";
129
+ if (typeLower.includes("api") || typeLower.includes("backend")) return "api";
130
+ if (typeLower.includes("web") || typeLower.includes("frontend")) return "webapp";
131
+ if (typeLower.includes("full")) return "fullstack";
132
+ if (typeLower.includes("mobile") || typeLower.includes("app")) return "mobile";
133
+ }
134
+
135
+ // Infer from stack
136
+ const hasFrontend = hasValue(faf, "stack.frontend");
137
+ const hasBackend = hasValue(faf, "stack.backend") || hasValue(faf, "stack.database");
138
+
139
+ if (hasFrontend && hasBackend) return "fullstack";
140
+ if (hasFrontend) return "webapp";
141
+ if (hasBackend) return "api";
142
+
143
+ return "unknown";
144
+ }
145
+
146
+ /**
147
+ * Count filled slots in a section
148
+ */
149
+ function countSection(
150
+ faf: Record<string, unknown>,
151
+ slots: readonly string[],
152
+ applies: boolean
153
+ ): SlotSection {
154
+ if (!applies) {
155
+ return { filled: 0, total: 0, percentage: 0 };
156
+ }
157
+
158
+ let filled = 0;
159
+ for (const slot of slots) {
160
+ if (hasValue(faf, slot)) filled++;
161
+ }
162
+
163
+ const total = slots.length;
164
+ const percentage = total > 0 ? Math.round((filled / total) * 100) : 0;
165
+
166
+ return { filled, total, percentage };
167
+ }
168
+
169
+ /**
170
+ * Calculate score using wolfejam slot-based system
171
+ * Score = (Filled slots / Applicable slots) × 100
172
+ */
173
+ export function calculateScore(faf: Record<string, unknown>): FafScore {
174
+ const projectType = detectProjectType(faf);
175
+ const applicableCategories = TYPE_CATEGORIES[projectType];
176
+
177
+ // Count each section
178
+ const sections = {
179
+ project: countSection(
180
+ faf,
181
+ SLOTS.project,
182
+ applicableCategories.includes("project")
183
+ ),
184
+ frontend: countSection(
185
+ faf,
186
+ SLOTS.frontend,
187
+ applicableCategories.includes("frontend")
188
+ ),
189
+ backend: countSection(
190
+ faf,
191
+ SLOTS.backend,
192
+ applicableCategories.includes("backend")
193
+ ),
194
+ universal: countSection(
195
+ faf,
196
+ SLOTS.universal,
197
+ applicableCategories.includes("universal")
198
+ ),
199
+ human: countSection(
200
+ faf,
201
+ SLOTS.human,
202
+ applicableCategories.includes("human")
203
+ ),
204
+ };
205
+
206
+ // Sum totals
207
+ let filled = 0;
208
+ let total = 0;
209
+
210
+ for (const section of Object.values(sections)) {
211
+ filled += section.filled;
212
+ total += section.total;
213
+ }
214
+
215
+ // Calculate final score
216
+ const score = total > 0 ? Math.round((filled / total) * 100) : 0;
217
+
218
+ return {
219
+ projectType,
220
+ sections,
221
+ filled,
222
+ total,
223
+ score,
224
+ };
225
+ }
package/lib/tier.ts ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Tier System
3
+ *
4
+ * The medal hierarchy for FAF scores.
5
+ * Zero dependencies. Pure Bun.
6
+ */
7
+
8
+ export interface Tier {
9
+ emoji: string;
10
+ name: string;
11
+ color: string;
12
+ }
13
+
14
+ // ANSI colors
15
+ const YELLOW = "\x1b[33m";
16
+ const GREEN = "\x1b[32m";
17
+ const RED = "\x1b[31m";
18
+ const DIM = "\x1b[2m";
19
+ const ORANGE = "\x1b[38;5;208m";
20
+ const WHITE = "\x1b[37m";
21
+
22
+ export function getTier(score: number): Tier {
23
+ if (score >= 105) return { emoji: "🍊", name: "Big Orange", color: ORANGE };
24
+ if (score >= 100) return { emoji: "🏆", name: "Trophy", color: YELLOW };
25
+ if (score >= 99) return { emoji: "🥇", name: "Gold", color: YELLOW };
26
+ if (score >= 95) return { emoji: "🥈", name: "Silver", color: WHITE };
27
+ if (score >= 85) return { emoji: "🥉", name: "Bronze", color: ORANGE };
28
+ if (score >= 70) return { emoji: "🟢", name: "Green", color: GREEN };
29
+ if (score >= 55) return { emoji: "🟡", name: "Yellow", color: YELLOW };
30
+ if (score > 0) return { emoji: "🔴", name: "Red", color: RED };
31
+ return { emoji: "⚪", name: "Empty", color: DIM };
32
+ }
33
+
34
+ export function isLaunchReady(score: number): boolean {
35
+ return score >= 85;
36
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "bun-sticky",
3
+ "version": "1.0.0",
4
+ "description": "Fastest bun under the sum. FAF scoring CLI for Bun.",
5
+ "main": "index.ts",
6
+ "type": "module",
7
+ "bin": {
8
+ "bun-sticky": "./index.ts"
9
+ },
10
+ "scripts": {
11
+ "start": "bun run index.ts",
12
+ "score": "bun run index.ts score",
13
+ "test": "bun test"
14
+ },
15
+ "keywords": [
16
+ "faf",
17
+ "bun",
18
+ "cli",
19
+ "scoring",
20
+ "wolfejam",
21
+ "ai-context"
22
+ ],
23
+ "author": "wolfejam",
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/Wolfe-Jam/bun-sticky.git"
28
+ },
29
+ "homepage": "https://github.com/Wolfe-Jam/bun-sticky",
30
+ "engines": {
31
+ "bun": ">=1.0.0"
32
+ },
33
+ "dependencies": {},
34
+ "devDependencies": {}
35
+ }
package/project.faf ADDED
@@ -0,0 +1,23 @@
1
+ # Bun Sticky - Project DNA
2
+ # Fastest bun under the sum
3
+
4
+ faf_version: 2.5.0
5
+
6
+ project:
7
+ name: bun-sticky
8
+ goal: Bun-native FAF scoring CLI with wolfejam slot-based system
9
+ main_language: TypeScript
10
+ type: cli
11
+ version: 1.0.0
12
+
13
+ human_context:
14
+ who: Developers using Bun for fast TypeScript CLIs
15
+ what: FAF scoring tool that rates project AI-readiness using slot-based scoring
16
+ why: Speed matters - 10x faster than Node, zero dependencies
17
+ where: Terminal, CI/CD pipelines, development workflows
18
+ when: Before commits, during reviews, in automation
19
+ how: bunx bun-sticky score
20
+
21
+ stack:
22
+ runtime: Bun
23
+ build: none (TypeScript native)
@@ -0,0 +1,225 @@
1
+ // Bun Snapshot v1, https://bun.sh/docs/test/snapshots
2
+
3
+ exports[`Tier 9: Snapshot Testing T9.01 - SLOTS structure snapshot 1`] = `
4
+ {
5
+ "backend": [
6
+ "stack.backend",
7
+ "stack.api_type",
8
+ "stack.runtime",
9
+ "stack.database",
10
+ "stack.connection",
11
+ ],
12
+ "frontend": [
13
+ "stack.frontend",
14
+ "stack.css_framework",
15
+ "stack.ui_library",
16
+ "stack.state_management",
17
+ ],
18
+ "human": [
19
+ "human_context.who",
20
+ "human_context.what",
21
+ "human_context.why",
22
+ "human_context.where",
23
+ "human_context.when",
24
+ "human_context.how",
25
+ ],
26
+ "project": [
27
+ "project.name",
28
+ "project.goal",
29
+ "project.main_language",
30
+ ],
31
+ "universal": [
32
+ "stack.hosting",
33
+ "stack.build",
34
+ "stack.cicd",
35
+ ],
36
+ }
37
+ `;
38
+
39
+ exports[`Tier 9: Snapshot Testing T9.02 - TYPE_CATEGORIES snapshot 1`] = `
40
+ {
41
+ "api": [
42
+ "project",
43
+ "backend",
44
+ "universal",
45
+ "human",
46
+ ],
47
+ "cli": [
48
+ "project",
49
+ "human",
50
+ ],
51
+ "fullstack": [
52
+ "project",
53
+ "frontend",
54
+ "backend",
55
+ "universal",
56
+ "human",
57
+ ],
58
+ "library": [
59
+ "project",
60
+ "human",
61
+ ],
62
+ "mobile": [
63
+ "project",
64
+ "human",
65
+ ],
66
+ "unknown": [
67
+ "project",
68
+ "human",
69
+ ],
70
+ "webapp": [
71
+ "project",
72
+ "frontend",
73
+ "universal",
74
+ "human",
75
+ ],
76
+ }
77
+ `;
78
+
79
+ exports[`Tier 9: Snapshot Testing T9.03 - Full CLI score result snapshot 1`] = `
80
+ {
81
+ "filled": 9,
82
+ "projectType": "cli",
83
+ "score": 100,
84
+ "sections": {
85
+ "backend": {
86
+ "filled": 0,
87
+ "percentage": 0,
88
+ "total": 0,
89
+ },
90
+ "frontend": {
91
+ "filled": 0,
92
+ "percentage": 0,
93
+ "total": 0,
94
+ },
95
+ "human": {
96
+ "filled": 6,
97
+ "percentage": 100,
98
+ "total": 6,
99
+ },
100
+ "project": {
101
+ "filled": 3,
102
+ "percentage": 100,
103
+ "total": 3,
104
+ },
105
+ "universal": {
106
+ "filled": 0,
107
+ "percentage": 0,
108
+ "total": 0,
109
+ },
110
+ },
111
+ "total": 9,
112
+ }
113
+ `;
114
+
115
+ exports[`Tier 2: Scoring Engine T2.07 - SLOTS structure snapshot 1`] = `
116
+ {
117
+ "backend": [
118
+ "stack.backend",
119
+ "stack.api_type",
120
+ "stack.runtime",
121
+ "stack.database",
122
+ "stack.connection",
123
+ ],
124
+ "frontend": [
125
+ "stack.frontend",
126
+ "stack.css_framework",
127
+ "stack.ui_library",
128
+ "stack.state_management",
129
+ ],
130
+ "human": [
131
+ "human_context.who",
132
+ "human_context.what",
133
+ "human_context.why",
134
+ "human_context.where",
135
+ "human_context.when",
136
+ "human_context.how",
137
+ ],
138
+ "project": [
139
+ "project.name",
140
+ "project.goal",
141
+ "project.main_language",
142
+ ],
143
+ "universal": [
144
+ "stack.hosting",
145
+ "stack.build",
146
+ "stack.cicd",
147
+ ],
148
+ }
149
+ `;
150
+
151
+ exports[`Tier 2: Scoring Engine T2.14 - Full CLI result snapshot 1`] = `
152
+ {
153
+ "filled": 9,
154
+ "projectType": "cli",
155
+ "score": 100,
156
+ "sections": {
157
+ "backend": {
158
+ "filled": 0,
159
+ "percentage": 0,
160
+ "total": 0,
161
+ },
162
+ "frontend": {
163
+ "filled": 0,
164
+ "percentage": 0,
165
+ "total": 0,
166
+ },
167
+ "human": {
168
+ "filled": 6,
169
+ "percentage": 100,
170
+ "total": 6,
171
+ },
172
+ "project": {
173
+ "filled": 3,
174
+ "percentage": 100,
175
+ "total": 3,
176
+ },
177
+ "universal": {
178
+ "filled": 0,
179
+ "percentage": 0,
180
+ "total": 0,
181
+ },
182
+ },
183
+ "total": 9,
184
+ }
185
+ `;
186
+
187
+ exports[`Tier 2: Scoring Engine T2.15 - TYPE_CATEGORIES snapshot 1`] = `
188
+ {
189
+ "api": [
190
+ "project",
191
+ "backend",
192
+ "universal",
193
+ "human",
194
+ ],
195
+ "cli": [
196
+ "project",
197
+ "human",
198
+ ],
199
+ "fullstack": [
200
+ "project",
201
+ "frontend",
202
+ "backend",
203
+ "universal",
204
+ "human",
205
+ ],
206
+ "library": [
207
+ "project",
208
+ "human",
209
+ ],
210
+ "mobile": [
211
+ "project",
212
+ "human",
213
+ ],
214
+ "unknown": [
215
+ "project",
216
+ "human",
217
+ ],
218
+ "webapp": [
219
+ "project",
220
+ "frontend",
221
+ "universal",
222
+ "human",
223
+ ],
224
+ }
225
+ `;