@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.
- package/.claude/skills/analyze-soup/SKILL.md +158 -0
- package/.claude/skills/create-dungeon/SKILL.md +464 -0
- package/.claude/skills/verify-dungeon/SKILL.md +157 -0
- package/.claude/skills/verify-dungeon/references/counting-semantics.md +161 -0
- package/.claude/skills/verify-dungeon/references/report-format.md +216 -0
- package/.claude/skills/verify-dungeon/references/sql-recipes.md +857 -0
- package/.claude/skills/write-hooks/SKILL.md +468 -0
- package/CHANGELOG.md +182 -0
- package/HOOKS.md +1256 -597
- package/README.md +140 -5
- package/dungeons/technical/ad-spend.js +41 -49
- package/dungeons/technical/anonymous-users.js +38 -36
- package/dungeons/technical/array-of-object-lookup.js +136 -153
- package/dungeons/technical/datagen-v15-verify.js +87 -0
- package/dungeons/technical/experiments.js +42 -40
- package/dungeons/technical/foobar.js +114 -118
- package/dungeons/technical/group-analytics.js +42 -40
- package/dungeons/technical/hook-helpers-verify.js +69 -50
- package/dungeons/technical/identity-model-verify.js +22 -12
- package/dungeons/technical/mirror-strategies.js +37 -39
- package/dungeons/technical/nested-objects.js +119 -118
- package/dungeons/technical/pattern-aggregate-by-bin.js +21 -8
- package/dungeons/technical/pattern-attributed-by-source.js +23 -9
- package/dungeons/technical/pattern-frequency-by-frequency.js +21 -8
- package/dungeons/technical/pattern-funnel-frequency.js +30 -15
- package/dungeons/technical/pattern-ttc-by-segment.js +21 -8
- package/dungeons/technical/retention-cadence.js +115 -112
- package/dungeons/technical/sanity.js +86 -80
- package/dungeons/technical/scale-test.js +34 -38
- package/dungeons/technical/scd.js +111 -128
- package/dungeons/technical/simple.js +134 -141
- package/dungeons/technical/simplest.js +111 -65
- package/dungeons/technical/text-generation.js +110 -146
- package/dungeons/vertical/ai-platform.js +300 -333
- package/dungeons/vertical/community.js +290 -255
- package/dungeons/vertical/crypto.js +400 -391
- package/dungeons/vertical/dating.js +421 -375
- package/dungeons/vertical/devtools.js +346 -298
- package/dungeons/vertical/ecommerce.js +322 -394
- package/dungeons/vertical/education.js +380 -325
- package/dungeons/vertical/fintech.js +371 -325
- package/dungeons/vertical/fitness.js +345 -291
- package/dungeons/vertical/food-delivery.js +352 -307
- package/dungeons/vertical/gaming.js +490 -444
- package/dungeons/vertical/healthcare.js +311 -262
- package/dungeons/vertical/insurance-application.js +437 -409
- package/dungeons/vertical/logistics.js +278 -252
- package/dungeons/vertical/marketplace.js +340 -323
- package/dungeons/vertical/media.js +390 -335
- package/dungeons/vertical/real-estate.js +402 -347
- package/dungeons/vertical/sass.js +331 -333
- package/dungeons/vertical/social.js +377 -316
- package/dungeons/vertical/travel.js +302 -295
- package/index.js +64 -7
- package/lib/core/config-validator.js +378 -17
- package/lib/core/dungeon-loader.js +2 -5
- package/lib/generators/events.js +12 -13
- package/lib/generators/funnels.js +76 -2
- package/lib/hook-helpers/index.js +1 -0
- package/lib/hook-helpers/inject.js +95 -0
- package/lib/orchestrators/mixpanel-sender.js +7 -0
- package/lib/orchestrators/user-loop.js +598 -48
- package/lib/templates/defaults.js +59 -59
- package/lib/templates/macro-presets.js +53 -11
- package/lib/utils/dataset-context.js +103 -0
- package/lib/utils/retention-curve.js +140 -0
- package/lib/utils/utils.js +157 -109
- package/lib/verify/counting.js +360 -0
- package/lib/verify/emulate-breakdown.js +531 -108
- package/lib/verify/funnel-engine.js +539 -0
- package/lib/verify/identity.js +78 -0
- package/lib/verify/index.js +20 -0
- package/lib/verify/schema-validator.js +3 -1
- package/lib/verify/verify-dungeon.js +58 -0
- package/package.json +14 -3
- package/scripts/run-dungeon.mjs +12 -1
- package/types.d.ts +353 -4
- 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
|
-
}
|