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.
@@ -0,0 +1,5 @@
1
+ # GitHub Sponsors
2
+ # Free for devs. Teams and enterprise contact us.
3
+
4
+ github: wolfejam
5
+ custom: ['https://faf.one/enterprise']
@@ -0,0 +1,29 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ name: Bun Tests
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Setup Bun
18
+ uses: oven-sh/setup-bun@v2
19
+ with:
20
+ bun-version: latest
21
+
22
+ - name: Install dependencies
23
+ run: bun install
24
+
25
+ - name: Run tests
26
+ run: bun test
27
+
28
+ - name: Type check
29
+ run: bun run tsc --noEmit
@@ -0,0 +1,36 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ name: Publish to npm
12
+
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Setup Bun
17
+ uses: oven-sh/setup-bun@v2
18
+ with:
19
+ bun-version: latest
20
+
21
+ - name: Install dependencies
22
+ run: bun install
23
+
24
+ - name: Run tests
25
+ run: bun test
26
+
27
+ - name: Setup Node for npm publish
28
+ uses: actions/setup-node@v4
29
+ with:
30
+ node-version: '20'
31
+ registry-url: 'https://registry.npmjs.org'
32
+
33
+ - name: Publish
34
+ run: npm publish
35
+ env:
36
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/CLAUDE.md ADDED
@@ -0,0 +1,81 @@
1
+ # Bun Sticky
2
+
3
+ Fastest bun under the sum. Bun-native .faf CLI.
4
+
5
+ ## Quick Start
6
+ ```bash
7
+ bunx bun-sticky score # Score current project
8
+ bunx bun-sticky init app # Create project.faf
9
+ bunx bun-sticky help # Show commands
10
+ ```
11
+
12
+ ## Architecture
13
+ ```
14
+ bun-sticky/
15
+ ├── index.ts # CLI + ASCII banner
16
+ ├── lib/
17
+ │ ├── parser.ts # Zero-dep YAML parser
18
+ │ ├── scorer.ts # Wolfejam slot-based scoring
19
+ │ └── tier.ts # 9-tier system
20
+ ├── tests/
21
+ │ └── sticky.test.ts # 177 tests, WJTTC championship
22
+ └── package.json # Zero dependencies
23
+ ```
24
+
25
+ ## Core Concepts
26
+
27
+ **Wolfejam Slot-Based Scoring** (NOT Elon weights)
28
+ - 21 total slots across 5 categories
29
+ - Type-aware: CLI=9 slots, Fullstack=21 slots
30
+ - Score = Filled Slots / Applicable Slots × 100
31
+
32
+ **Tier System**
33
+ | Score | Tier | Emoji |
34
+ |-------|------|-------|
35
+ | 100% | Trophy | 🏆 |
36
+ | 99%+ | Gold | 🥇 |
37
+ | 95%+ | Silver | 🥈 |
38
+ | 85%+ | Bronze | 🥉 |
39
+ | 70%+ | Green | 🟢 |
40
+ | 55%+ | Yellow | 🟡 |
41
+ | <55% | Red | 🔴 |
42
+
43
+ ## Key Files
44
+ | File | Line | What |
45
+ |------|------|------|
46
+ | `lib/scorer.ts` | 16 | SLOTS definition (21 slots) |
47
+ | `lib/scorer.ts` | 68 | TYPE_CATEGORIES mapping |
48
+ | `lib/scorer.ts` | 173 | calculateScore() |
49
+ | `lib/tier.ts` | 22 | getTier() |
50
+ | `lib/parser.ts` | 1 | parseYaml() zero-dep |
51
+
52
+ ## Design Principles
53
+ 1. **Zero Dependencies** - Pure Bun APIs
54
+ 2. **TypeScript Native** - No build step
55
+ 3. **Speed First** - Sub-50ms cold start
56
+ 4. **WJTTC Testing** - Championship-grade test suite
57
+
58
+ ## Commands
59
+ | Command | Description |
60
+ |---------|-------------|
61
+ | `score` | Show FAF score + tier |
62
+ | `init <name>` | Create project.faf |
63
+ | `sync` | Sync project.faf → CLAUDE.md |
64
+ | `version` | Show version |
65
+ | `help` | Show help |
66
+
67
+ ## Testing
68
+ ```bash
69
+ bun test
70
+ # 177 tests, 1254 assertions
71
+ # Full Bun API: test.each, mock, spyOn, snapshots
72
+ # WJTTC Championship Grade
73
+ ```
74
+
75
+ ## Part of FAF Ecosystem
76
+ - **faf-cli** - Full Node.js CLI (15,000+ downloads)
77
+ - **bun-sticky** - Bun-native lite CLI (you are here)
78
+ - **xai-faf-zig** - Ultra-fast Zig implementation
79
+
80
+ ---
81
+ *Zero dependencies. Pure Bun. Wolfejam slot-based scoring.*
@@ -0,0 +1,63 @@
1
+ # Bun Sticky - Publish Protocol
2
+
3
+ ## Pre-Publish Checklist
4
+
5
+ ### 1. Code Quality
6
+ - [ ] All 45 tests passing (`bun test`)
7
+ - [ ] Zero TypeScript errors
8
+ - [ ] Zero runtime dependencies
9
+ - [ ] Pure Bun APIs only
10
+
11
+ ### 2. Version Bump
12
+ ```bash
13
+ # Update version in:
14
+ # - package.json
15
+ # - index.ts (VERSION constant)
16
+ ```
17
+
18
+ ### 3. Test Suite
19
+ ```bash
20
+ bun test
21
+ # Expect: 45 pass, 0 fail
22
+ ```
23
+
24
+ ### 4. Manual Verification
25
+ ```bash
26
+ bun run index.ts --version
27
+ bun run index.ts help
28
+ bun run index.ts score
29
+ ```
30
+
31
+ ## Publish Commands
32
+
33
+ ### npm Registry
34
+ ```bash
35
+ # Login (first time)
36
+ npm login
37
+
38
+ # Publish
39
+ npm publish
40
+ ```
41
+
42
+ ### Bun Package Manager (future)
43
+ ```bash
44
+ # When Bun has its own registry
45
+ bun publish
46
+ ```
47
+
48
+ ## Post-Publish
49
+
50
+ 1. Tag release in git
51
+ 2. Update ZIG-n-RUST.md benchmarks
52
+ 3. Announce in faf-cli ecosystem
53
+
54
+ ## Version History
55
+
56
+ | Version | Date | Notes |
57
+ |---------|------|-------|
58
+ | 1.0.0 | TBD | Initial release - Wolfejam slot-based scoring |
59
+
60
+ ---
61
+
62
+ *Fastest bun under the sum.*
63
+ *Zero dependencies. Pure Bun.*
package/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # bun-sticky
2
+
3
+ Fastest bun under the sum. Zero dependencies. Pure Bun.
4
+
5
+ ```bash
6
+ bunx bun-sticky score
7
+ ```
8
+
9
+ ```
10
+ ────────────────────────────────────────────────
11
+
12
+ ▄▄ ▄▀▀▀ ▀█▀ █ ▄▀▀ █▄▀ █ █
13
+ ████ ▀▀█▄ █ █ █ █▀▄ █
14
+ ██████ ▄▄▄▀ █ █ ▀▀▀ █ █ █
15
+ ████████
16
+ ████████ █▀▄ █ █ █▀▄
17
+ ██████ ██▀ █ █ █ █
18
+ ████ █▄▀ ▀▄▀ █ █
19
+ ▀▀
20
+
21
+ 🥐 Bun Sticky v1.0.0 .faf CLI
22
+ Fastest bun under the sum.
23
+
24
+ ────────────────────────────────────────────────
25
+
26
+ Project: my-app
27
+ Type: cli
28
+
29
+ Project ████████████ 3/3
30
+ Human ████████░░░░ 4/6
31
+
32
+ 🥉 78% Bronze
33
+ Filled: 7/9 slots
34
+ ```
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ bun add -g bun-sticky
40
+ ```
41
+
42
+ Or run directly:
43
+
44
+ ```bash
45
+ bunx bun-sticky score
46
+ ```
47
+
48
+ ## Commands
49
+
50
+ ```bash
51
+ bun-sticky score # Show FAF score + tier
52
+ bun-sticky init myapp # Create project.faf
53
+ bun-sticky sync # Sync to CLAUDE.md
54
+ bun-sticky help # Show commands
55
+ ```
56
+
57
+ ## What is FAF?
58
+
59
+ **FAF** (Foundational AI-context Format) is project DNA for AI assistants. A `project.faf` file tells Claude, Cursor, Copilot, and other AI tools what your project is about.
60
+
61
+ **bun-sticky** scores your project's AI-readiness using the Wolfejam slot-based system.
62
+
63
+ ## Scoring
64
+
65
+ 21 slots across 5 categories. Type-aware scoring:
66
+
67
+ | Type | Slots | Categories |
68
+ |------|-------|------------|
69
+ | CLI | 9 | project + human |
70
+ | Library | 9 | project + human |
71
+ | API | 17 | project + backend + universal + human |
72
+ | Webapp | 16 | project + frontend + universal + human |
73
+ | Fullstack | 21 | all |
74
+
75
+ **Score = Filled Slots / Applicable Slots × 100**
76
+
77
+ ## Tiers
78
+
79
+ | Score | Tier |
80
+ |-------|------|
81
+ | 100% | 🏆 Trophy |
82
+ | 99%+ | 🥇 Gold |
83
+ | 95%+ | 🥈 Silver |
84
+ | 85%+ | 🥉 Bronze |
85
+ | 70%+ | 🟢 Green |
86
+ | 55%+ | 🟡 Yellow |
87
+ | <55% | 🔴 Red |
88
+
89
+ ## Speed
90
+
91
+ Built for Bun's speed:
92
+
93
+ - **Cold start**: <50ms
94
+ - **Score command**: <100ms
95
+ - **Zero dependencies**: Pure Bun APIs
96
+ - **TypeScript native**: No build step
97
+
98
+ ## Testing
99
+
100
+ 177 tests. Championship-grade WJTTC test suite.
101
+
102
+ ```bash
103
+ bun test
104
+ ```
105
+
106
+ Full Bun test API coverage: `test.each`, `mock`, `spyOn`, `snapshots`, custom matchers.
107
+
108
+ ## FAF Ecosystem
109
+
110
+ | Package | Runtime | Downloads |
111
+ |---------|---------|-----------|
112
+ | [faf-cli](https://npmjs.com/package/faf-cli) | Node.js | 15,000+ |
113
+ | **bun-sticky** | Bun | you are here |
114
+ | xai-faf-zig | Zig | ultra-fast |
115
+ | xai-faf-rust | Rust | WASM |
116
+
117
+ ## Philosophy
118
+
119
+ Built the Anthropic way:
120
+
121
+ - First principles
122
+ - Zero dependencies
123
+ - Native Bun APIs
124
+ - TypeScript native
125
+
126
+ Wolfejam slot-based scoring. Not Elon weights.
127
+
128
+ ## License
129
+
130
+ MIT
131
+
132
+ ---
133
+
134
+ *Part of the FAF ecosystem. Made for Claude Code.*
package/index.ts ADDED
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * 🥐 Bun Sticky - Fastest bun under the sum.
4
+ *
5
+ * Built the Anthropic way:
6
+ * - First principles
7
+ * - Zero dependencies
8
+ * - Native Bun APIs
9
+ * - TypeScript native
10
+ *
11
+ * Wolfejam slot-based scoring (NOT Elon weights).
12
+ * For Claude Codesters.
13
+ */
14
+
15
+ import { parseYaml, getNestedValue } from "./lib/parser.ts";
16
+ import { calculateScore, FafScore } from "./lib/scorer.ts";
17
+ import { getTier } from "./lib/tier.ts";
18
+
19
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
20
+ // CONSTANTS
21
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
22
+
23
+ const VERSION = "1.0.0";
24
+
25
+ // Bun Brand Colors (ANSI 256)
26
+ const BUN_BLUE = "\x1b[38;5;39m"; // #00a6e1
27
+ const BUN_PINK = "\x1b[38;5;205m"; // #ec4899
28
+ const BUN_ORANGE = "\x1b[38;5;215m"; // #f89b4b (medium - croissant)
29
+ const BUN_ORANGE_DARK = "\x1b[38;5;208m"; // dark orange (BUN)
30
+ const BUN_YELLOW = "\x1b[38;5;220m"; // #febc2e
31
+ const BUN_CREAM = "\x1b[38;5;223m"; // Croissant color
32
+ const WHITE = "\x1b[97m"; // bright white (STICKY)
33
+
34
+ // Standard colors
35
+ const CYAN = "\x1b[36m";
36
+ const GREEN = "\x1b[32m";
37
+ const YELLOW = "\x1b[33m";
38
+ const RED = "\x1b[31m";
39
+ const BOLD = "\x1b[1m";
40
+ const DIM = "\x1b[2m";
41
+ const RESET = "\x1b[0m";
42
+
43
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
44
+ // ASCII ART BANNER
45
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
46
+
47
+ const BANNER = `
48
+ ${BUN_BLUE}────────────────────────────────────────────────${RESET}
49
+
50
+ ${BUN_ORANGE}▄▄${RESET} ${WHITE}▄▀▀▀ ▀█▀ █ ▄▀▀ █▄▀ █ █${RESET}
51
+ ${BUN_ORANGE}████${RESET} ${WHITE}▀▀█▄ █ █ █ █▀▄ █${RESET}
52
+ ${BUN_ORANGE}██████${RESET} ${WHITE}▄▄▄▀ █ █ ▀▀▀ █ █ █${RESET}
53
+ ${BUN_ORANGE}████████${RESET}
54
+ ${BUN_ORANGE}████████${RESET} ${BUN_ORANGE_DARK}█▀▄ █ █ █▀▄${RESET}
55
+ ${BUN_ORANGE}██████${RESET} ${BUN_ORANGE_DARK}██▀ █ █ █ █${RESET}
56
+ ${BUN_ORANGE}████${RESET} ${BUN_ORANGE_DARK}█▄▀ ▀▄▀ █ █${RESET}
57
+ ${BUN_ORANGE}▀▀${RESET}
58
+
59
+ ${WHITE}🥐 Bun Sticky v${VERSION} .faf CLI${RESET}
60
+ ${BUN_ORANGE_DARK}Fastest bun under the sum.${RESET}
61
+
62
+ ${BUN_BLUE}────────────────────────────────────────────────${RESET}
63
+ `;
64
+
65
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
66
+ // COMMANDS
67
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
68
+
69
+ async function cmdScore(): Promise<void> {
70
+ const file = Bun.file("project.faf");
71
+
72
+ if (!(await file.exists())) {
73
+ console.log(`${RED}No project.faf found${RESET}`);
74
+ console.log(`${DIM}Run: bun-sticky init <name>${RESET}`);
75
+ process.exit(1);
76
+ }
77
+
78
+ const content = await file.text();
79
+ const faf = parseYaml(content);
80
+ const result = calculateScore(faf);
81
+ const tier = getTier(result.score);
82
+
83
+ console.log(BANNER);
84
+
85
+ // Project name & type
86
+ const name = (getNestedValue(faf, "project.name") as string) || "Unknown";
87
+ console.log(` Project: ${BOLD}${name}${RESET}`);
88
+ console.log(` Type: ${DIM}${result.projectType}${RESET}`);
89
+ console.log();
90
+
91
+ // Section breakdown (only show applicable sections)
92
+ const { sections } = result;
93
+
94
+ if (sections.project.total > 0) {
95
+ console.log(` ${DIM}Project${RESET} ${formatBar(sections.project.percentage)} ${sections.project.filled}/${sections.project.total}`);
96
+ }
97
+ if (sections.frontend.total > 0) {
98
+ console.log(` ${DIM}Frontend${RESET} ${formatBar(sections.frontend.percentage)} ${sections.frontend.filled}/${sections.frontend.total}`);
99
+ }
100
+ if (sections.backend.total > 0) {
101
+ console.log(` ${DIM}Backend${RESET} ${formatBar(sections.backend.percentage)} ${sections.backend.filled}/${sections.backend.total}`);
102
+ }
103
+ if (sections.universal.total > 0) {
104
+ console.log(` ${DIM}Universal${RESET} ${formatBar(sections.universal.percentage)} ${sections.universal.filled}/${sections.universal.total}`);
105
+ }
106
+ if (sections.human.total > 0) {
107
+ console.log(` ${DIM}Human${RESET} ${formatBar(sections.human.percentage)} ${sections.human.filled}/${sections.human.total}`);
108
+ }
109
+ console.log();
110
+
111
+ // Total
112
+ console.log(` ${tier.color}${tier.emoji} ${BOLD}${result.score}%${RESET} ${tier.color}${tier.name}${RESET}`);
113
+ console.log(` ${DIM}Filled: ${result.filled}/${result.total} slots${RESET}`);
114
+ console.log();
115
+ }
116
+
117
+ function formatBar(percent: number): string {
118
+ const width = 12;
119
+ const filled = Math.round((percent / 100) * width);
120
+ const empty = width - filled;
121
+ const bar = "█".repeat(filled) + "░".repeat(empty);
122
+
123
+ if (percent >= 85) return `${GREEN}${bar}${RESET}`;
124
+ if (percent >= 70) return `${GREEN}${bar}${RESET}`;
125
+ if (percent >= 55) return `${YELLOW}${bar}${RESET}`;
126
+ return `${RED}${bar}${RESET}`;
127
+ }
128
+
129
+ async function cmdInit(name: string): Promise<void> {
130
+ const file = Bun.file("project.faf");
131
+
132
+ if (await file.exists()) {
133
+ console.log(`${YELLOW}project.faf already exists${RESET}`);
134
+ process.exit(1);
135
+ }
136
+
137
+ const template = `# ${name} - Project DNA
138
+ # Generated by Bun Sticky
139
+
140
+ faf_version: 2.5.0
141
+
142
+ project:
143
+ name: ${name}
144
+ goal: Define your project goal here
145
+ main_language: TypeScript
146
+ type: cli
147
+ version: 0.1.0
148
+
149
+ human_context:
150
+ who: Your target users
151
+ what: What this project does
152
+ why: Why it exists
153
+ where: Where it runs
154
+ when: When to use it
155
+ how: How to get started
156
+
157
+ stack:
158
+ runtime: Bun
159
+ build: bun build
160
+ `;
161
+
162
+ await Bun.write("project.faf", template);
163
+ console.log(BANNER);
164
+ console.log(` ${GREEN}Created${RESET} project.faf`);
165
+ console.log(` ${DIM}Run: bun-sticky score${RESET}`);
166
+ console.log();
167
+ }
168
+
169
+ async function cmdSync(): Promise<void> {
170
+ const fafFile = Bun.file("project.faf");
171
+
172
+ if (!(await fafFile.exists())) {
173
+ console.log(`${RED}No project.faf found${RESET}`);
174
+ process.exit(1);
175
+ }
176
+
177
+ const content = await fafFile.text();
178
+ const faf = parseYaml(content);
179
+ const name = (getNestedValue(faf, "project.name") as string) || "Project";
180
+ const goal = (getNestedValue(faf, "project.goal") as string) || "";
181
+ const result = calculateScore(faf);
182
+ const tier = getTier(result.score);
183
+
184
+ // Generate CLAUDE.md
185
+ const claudeMd = `# ${name}
186
+
187
+ ${goal}
188
+
189
+ ## Score: ${tier.emoji} ${result.score}%
190
+
191
+ Filled: ${result.filled}/${result.total} slots
192
+
193
+ ---
194
+ *Synced by Bun Sticky*
195
+ `;
196
+
197
+ await Bun.write("CLAUDE.md", claudeMd);
198
+ console.log(BANNER);
199
+ console.log(` ${GREEN}Synced${RESET} project.faf → CLAUDE.md`);
200
+ console.log();
201
+ }
202
+
203
+ function cmdHelp(): void {
204
+ console.log(BANNER);
205
+ console.log(` ${BOLD}Commands${RESET}`);
206
+ console.log();
207
+ console.log(` score Show FAF score + tier`);
208
+ console.log(` init <n> Create project.faf`);
209
+ console.log(` sync Sync to CLAUDE.md`);
210
+ console.log(` version Show version`);
211
+ console.log(` help Show this help`);
212
+ console.log();
213
+ console.log(` ${DIM}Zero dependencies. Pure Bun.${RESET}`);
214
+ console.log(` ${DIM}Wolfejam slot-based scoring.${RESET}`);
215
+ console.log();
216
+ }
217
+
218
+ function cmdVersion(): void {
219
+ console.log(`bun-sticky v${VERSION}`);
220
+ }
221
+
222
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
223
+ // MAIN
224
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
225
+
226
+ const args = process.argv.slice(2);
227
+ const cmd = args[0] || "help";
228
+
229
+ switch (cmd) {
230
+ case "score":
231
+ await cmdScore();
232
+ break;
233
+ case "init":
234
+ const name = args[1];
235
+ if (!name) {
236
+ console.log(`${RED}Usage: bun-sticky init <name>${RESET}`);
237
+ process.exit(1);
238
+ }
239
+ await cmdInit(name);
240
+ break;
241
+ case "sync":
242
+ await cmdSync();
243
+ break;
244
+ case "version":
245
+ case "-v":
246
+ case "--version":
247
+ cmdVersion();
248
+ break;
249
+ case "help":
250
+ case "-h":
251
+ case "--help":
252
+ default:
253
+ cmdHelp();
254
+ break;
255
+ }
package/lib/parser.ts ADDED
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Minimal YAML Parser for .faf files
3
+ *
4
+ * Zero dependencies. Pure Bun.
5
+ * Handles: scalars, nested objects, arrays
6
+ */
7
+
8
+ export function parseYaml(content: string): Record<string, unknown> {
9
+ const result: Record<string, unknown> = {};
10
+ const lines = content.split("\n");
11
+ const stack: { indent: number; obj: Record<string, unknown> }[] = [
12
+ { indent: -1, obj: result },
13
+ ];
14
+
15
+ for (const line of lines) {
16
+ // Skip comments and empty lines
17
+ if (line.trim().startsWith("#") || line.trim() === "") continue;
18
+
19
+ const indent = line.search(/\S/);
20
+ const trimmed = line.trim();
21
+
22
+ // Handle key: value pairs
23
+ const colonIndex = trimmed.indexOf(":");
24
+ if (colonIndex === -1) continue;
25
+
26
+ const key = trimmed.slice(0, colonIndex).trim();
27
+ const value = trimmed.slice(colonIndex + 1).trim();
28
+
29
+ // Pop stack to find parent
30
+ while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
31
+ stack.pop();
32
+ }
33
+
34
+ const parent = stack[stack.length - 1].obj;
35
+
36
+ if (value === "" || value === "|" || value === ">") {
37
+ // Nested object or multiline
38
+ const newObj: Record<string, unknown> = {};
39
+ parent[key] = newObj;
40
+ stack.push({ indent, obj: newObj });
41
+ } else if (value.startsWith("[") && value.endsWith("]")) {
42
+ // Inline array
43
+ parent[key] = value
44
+ .slice(1, -1)
45
+ .split(",")
46
+ .map((s) => s.trim().replace(/^["']|["']$/g, ""));
47
+ } else if (value.startsWith("- ")) {
48
+ // Array item (simple case)
49
+ parent[key] = [value.slice(2).trim()];
50
+ } else {
51
+ // Scalar value
52
+ parent[key] = value.replace(/^["']|["']$/g, "");
53
+ }
54
+ }
55
+
56
+ return result;
57
+ }
58
+
59
+ export function getNestedValue(obj: Record<string, unknown>, path: string): unknown {
60
+ const parts = path.split(".");
61
+ let current: unknown = obj;
62
+ for (const part of parts) {
63
+ if (current === null || current === undefined) return undefined;
64
+ if (typeof current !== "object") return undefined;
65
+ current = (current as Record<string, unknown>)[part];
66
+ }
67
+ return current;
68
+ }
69
+
70
+ export function hasValue(obj: Record<string, unknown>, path: string): boolean {
71
+ const value = getNestedValue(obj, path);
72
+ if (value === undefined || value === null) return false;
73
+ if (typeof value === "string" && value.trim() === "") return false;
74
+ return true;
75
+ }