@ak--47/dungeon-master 1.4.5 → 1.5.1

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 (78) hide show
  1. package/.claude/skills/analyze-soup/SKILL.md +158 -0
  2. package/.claude/skills/create-dungeon/SKILL.md +464 -0
  3. package/.claude/skills/verify-dungeon/SKILL.md +157 -0
  4. package/.claude/skills/verify-dungeon/references/counting-semantics.md +161 -0
  5. package/.claude/skills/verify-dungeon/references/report-format.md +216 -0
  6. package/.claude/skills/verify-dungeon/references/sql-recipes.md +857 -0
  7. package/.claude/skills/write-hooks/SKILL.md +468 -0
  8. package/CHANGELOG.md +182 -0
  9. package/HOOKS.md +1256 -597
  10. package/README.md +140 -5
  11. package/dungeons/technical/ad-spend.js +41 -49
  12. package/dungeons/technical/anonymous-users.js +38 -36
  13. package/dungeons/technical/array-of-object-lookup.js +136 -153
  14. package/dungeons/technical/datagen-v15-verify.js +87 -0
  15. package/dungeons/technical/experiments.js +42 -40
  16. package/dungeons/technical/foobar.js +114 -118
  17. package/dungeons/technical/group-analytics.js +42 -40
  18. package/dungeons/technical/hook-helpers-verify.js +69 -50
  19. package/dungeons/technical/identity-model-verify.js +22 -12
  20. package/dungeons/technical/mirror-strategies.js +37 -39
  21. package/dungeons/technical/nested-objects.js +119 -118
  22. package/dungeons/technical/pattern-aggregate-by-bin.js +21 -8
  23. package/dungeons/technical/pattern-attributed-by-source.js +23 -9
  24. package/dungeons/technical/pattern-frequency-by-frequency.js +21 -8
  25. package/dungeons/technical/pattern-funnel-frequency.js +30 -15
  26. package/dungeons/technical/pattern-ttc-by-segment.js +21 -8
  27. package/dungeons/technical/retention-cadence.js +115 -112
  28. package/dungeons/technical/sanity.js +86 -80
  29. package/dungeons/technical/scale-test.js +34 -38
  30. package/dungeons/technical/scd.js +111 -128
  31. package/dungeons/technical/simple.js +134 -141
  32. package/dungeons/technical/simplest.js +111 -65
  33. package/dungeons/technical/text-generation.js +110 -146
  34. package/dungeons/vertical/ai-platform.js +300 -333
  35. package/dungeons/vertical/community.js +290 -255
  36. package/dungeons/vertical/crypto.js +400 -391
  37. package/dungeons/vertical/dating.js +421 -375
  38. package/dungeons/vertical/devtools.js +346 -298
  39. package/dungeons/vertical/ecommerce.js +322 -394
  40. package/dungeons/vertical/education.js +380 -325
  41. package/dungeons/vertical/fintech.js +371 -325
  42. package/dungeons/vertical/fitness.js +345 -291
  43. package/dungeons/vertical/food-delivery.js +352 -307
  44. package/dungeons/vertical/gaming.js +490 -444
  45. package/dungeons/vertical/healthcare.js +311 -262
  46. package/dungeons/vertical/insurance-application.js +437 -409
  47. package/dungeons/vertical/logistics.js +278 -252
  48. package/dungeons/vertical/marketplace.js +340 -323
  49. package/dungeons/vertical/media.js +390 -335
  50. package/dungeons/vertical/real-estate.js +402 -347
  51. package/dungeons/vertical/sass.js +331 -333
  52. package/dungeons/vertical/social.js +377 -316
  53. package/dungeons/vertical/travel.js +302 -295
  54. package/index.js +64 -7
  55. package/lib/core/config-validator.js +378 -17
  56. package/lib/core/dungeon-loader.js +2 -5
  57. package/lib/generators/events.js +12 -13
  58. package/lib/generators/funnels.js +76 -2
  59. package/lib/hook-helpers/index.js +1 -0
  60. package/lib/hook-helpers/inject.js +95 -0
  61. package/lib/orchestrators/mixpanel-sender.js +7 -0
  62. package/lib/orchestrators/user-loop.js +598 -48
  63. package/lib/templates/defaults.js +59 -59
  64. package/lib/templates/macro-presets.js +53 -11
  65. package/lib/utils/dataset-context.js +103 -0
  66. package/lib/utils/retention-curve.js +140 -0
  67. package/lib/utils/utils.js +157 -109
  68. package/lib/verify/counting.js +360 -0
  69. package/lib/verify/emulate-breakdown.js +531 -108
  70. package/lib/verify/funnel-engine.js +539 -0
  71. package/lib/verify/identity.js +78 -0
  72. package/lib/verify/index.js +20 -0
  73. package/lib/verify/schema-validator.js +3 -1
  74. package/lib/verify/verify-dungeon.js +58 -0
  75. package/package.json +14 -3
  76. package/scripts/run-dungeon.mjs +12 -1
  77. package/types.d.ts +353 -4
  78. package/scripts/smoke-test-all.mjs +0 -162
@@ -1,162 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Smoke-test runner — runs every dungeon at tiny scale in parallel to verify it
4
- * loads, generates events, and writes output without crashing.
5
- *
6
- * Default scale: 100 users, 1000 events per dungeon. NOT for verification —
7
- * use scripts/verify-runner.mjs at full fidelity for that.
8
- *
9
- * Usage:
10
- * node scripts/smoke-test-all.mjs
11
- * node scripts/smoke-test-all.mjs --dir dungeons/vertical # default
12
- * node scripts/smoke-test-all.mjs --dir dungeons/technical
13
- * node scripts/smoke-test-all.mjs --dir dungeons # both subdirs
14
- * node scripts/smoke-test-all.mjs --concurrency 4 # default: cpu count
15
- * node scripts/smoke-test-all.mjs --users 500 --events 5000 # override scale
16
- *
17
- * Output: per-dungeon PASS/FAIL line + a final summary table. Spawns each
18
- * dungeon as a child node process so a crash in one doesn't abort the run.
19
- */
20
- import { spawn } from 'child_process';
21
- import { readdirSync, statSync, rmSync, existsSync, mkdirSync } from 'fs';
22
- import path from 'path';
23
- import os from 'os';
24
-
25
- const args = process.argv.slice(2);
26
- function arg(name, fallback) {
27
- const i = args.indexOf(`--${name}`);
28
- return i === -1 ? fallback : args[i + 1];
29
- }
30
-
31
- const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..');
32
- const dirArg = arg('dir', 'dungeons/vertical');
33
- const numUsers = parseInt(arg('users', '100'), 10);
34
- const numEvents = parseInt(arg('events', '1000'), 10);
35
- const concurrency = parseInt(arg('concurrency', String(Math.max(2, os.cpus().length))), 10);
36
- const keep = args.includes('--keep');
37
-
38
- function findDungeons(dir) {
39
- const abs = path.resolve(ROOT, dir);
40
- if (!existsSync(abs)) return [];
41
- const stat = statSync(abs);
42
- if (stat.isFile()) return [abs];
43
- const out = [];
44
- for (const entry of readdirSync(abs)) {
45
- const full = path.join(abs, entry);
46
- const s = statSync(full);
47
- if (s.isDirectory()) out.push(...findDungeons(full));
48
- else if (entry.endsWith('.js')) out.push(full);
49
- }
50
- return out;
51
- }
52
-
53
- const dungeons = findDungeons(dirArg).sort();
54
- if (dungeons.length === 0) {
55
- console.error(`No dungeons found under ${dirArg}`);
56
- process.exit(1);
57
- }
58
-
59
- const dataDir = path.join(ROOT, 'data');
60
- mkdirSync(dataDir, { recursive: true });
61
-
62
- const RUNNER = `
63
- import generate from '${path.join(ROOT, 'index.js')}';
64
- // With node -e, process.argv = [nodePath, ...userArgs] — no script slot.
65
- const [dungeonPath, name, numUsers, numEvents] = process.argv.slice(1);
66
- const r = await generate(dungeonPath, {
67
- numUsers: parseInt(numUsers, 10),
68
- numEvents: parseInt(numEvents, 10),
69
- avgEventsPerUserPerDay: undefined, // force numEvents path
70
- writeToDisk: true,
71
- name,
72
- format: 'json',
73
- verbose: false,
74
- concurrency: 1,
75
- token: '',
76
- serviceAccount: 'fake', serviceSecret: 'fake', projectId: '1',
77
- });
78
- console.log(JSON.stringify({ eventCount: r.eventCount, userCount: r.userCount }));
79
- `;
80
-
81
- function runOne(dungeonPath) {
82
- const base = path.basename(dungeonPath, '.js');
83
- const namePrefix = `smoke-${base}`;
84
- return new Promise(resolve => {
85
- const start = Date.now();
86
- const child = spawn(
87
- process.execPath,
88
- ['--input-type=module', '-e', RUNNER, dungeonPath, namePrefix, String(numUsers), String(numEvents)],
89
- { cwd: ROOT, stdio: ['ignore', 'pipe', 'pipe'] }
90
- );
91
- let out = '';
92
- let err = '';
93
- child.stdout.on('data', d => { out += d.toString(); });
94
- child.stderr.on('data', d => { err += d.toString(); });
95
- child.on('close', code => {
96
- const ms = Date.now() - start;
97
- let result;
98
- try {
99
- const lastLine = out.trim().split('\n').filter(Boolean).pop() || '{}';
100
- result = JSON.parse(lastLine);
101
- } catch {
102
- result = {};
103
- }
104
- const ok = code === 0 && result.eventCount > 0;
105
- if (!keep) {
106
- try {
107
- for (const f of readdirSync(dataDir)) {
108
- if (f.startsWith(`${namePrefix}-`)) rmSync(path.join(dataDir, f));
109
- }
110
- } catch {}
111
- }
112
- resolve({
113
- dungeon: path.relative(ROOT, dungeonPath),
114
- ok,
115
- code,
116
- ms,
117
- eventCount: result.eventCount || 0,
118
- userCount: result.userCount || 0,
119
- err: ok ? '' : (err.trim().split('\n').slice(-3).join(' | ') || `exit ${code}`),
120
- });
121
- });
122
- });
123
- }
124
-
125
- console.log(`Smoke test: ${dungeons.length} dungeons, ${numUsers} users / ${numEvents} events each, concurrency=${concurrency}`);
126
- const queue = [...dungeons];
127
- const results = [];
128
- const workers = Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
129
- while (queue.length) {
130
- const d = queue.shift();
131
- const res = await runOne(d);
132
- const tag = res.ok ? 'PASS' : 'FAIL';
133
- console.log(` [${tag}] ${path.basename(res.dungeon)} — ${res.eventCount} events, ${res.userCount} users, ${(res.ms / 1000).toFixed(2)}s${res.ok ? '' : ` — ${res.err}`}`);
134
- results.push(res);
135
- }
136
- });
137
- await Promise.all(workers);
138
-
139
- results.sort((a, b) => a.dungeon.localeCompare(b.dungeon));
140
- const fails = results.filter(r => !r.ok);
141
-
142
- console.log('\n┌─────────────────────────────────────────────────┬───────┬────────┬───────┬──────┐');
143
- console.log('│ Dungeon │ State │ Events │ Users │ ms │');
144
- console.log('├─────────────────────────────────────────────────┼───────┼────────┼───────┼──────┤');
145
- for (const r of results) {
146
- const name = r.dungeon.padEnd(47).slice(0, 47);
147
- const state = (r.ok ? 'PASS' : 'FAIL').padEnd(5);
148
- const ev = String(r.eventCount).padStart(6);
149
- const u = String(r.userCount).padStart(5);
150
- const ms = String(r.ms).padStart(4);
151
- console.log(`│ ${name} │ ${state} │ ${ev} │ ${u} │ ${ms} │`);
152
- }
153
- console.log('└─────────────────────────────────────────────────┴───────┴────────┴───────┴──────┘');
154
-
155
- console.log(`\n${results.length - fails.length}/${results.length} passed`);
156
- if (fails.length > 0) {
157
- console.log('\nFailures:');
158
- for (const f of fails) {
159
- console.log(` ✗ ${f.dungeon}: ${f.err}`);
160
- }
161
- process.exit(1);
162
- }