@ak--47/dungeon-master 1.5.3 → 1.6.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.
Files changed (146) hide show
  1. package/.claude/skills/analyze-soup/SKILL.md +21 -12
  2. package/.claude/skills/create-dungeon/SKILL.md +65 -5
  3. package/.claude/skills/create-project/SKILL.md +96 -0
  4. package/.claude/skills/create-project/provision.mjs +336 -0
  5. package/.claude/skills/verify-dungeon/SKILL.md +56 -25
  6. package/.claude/skills/verify-dungeon/references/counting-semantics.md +26 -6
  7. package/.claude/skills/verify-dungeon/references/report-format.md +14 -8
  8. package/.claude/skills/write-hooks/SKILL.md +156 -18
  9. package/CHANGELOG.md +211 -0
  10. package/HOOKS.md +566 -112
  11. package/README.md +28 -9
  12. package/docs/guides/1.3.0-upgrade-guide.md +262 -0
  13. package/docs/guides/1.3.1-upgrade-guide.md +328 -0
  14. package/docs/guides/1.4.0-upgrade-guide.md +130 -0
  15. package/docs/guides/1.4.1-upgrade-guide.md +125 -0
  16. package/docs/guides/1.4.3-upgrade-guide.md +200 -0
  17. package/docs/guides/1.4.4-upgrade-guide.md +95 -0
  18. package/docs/guides/1.5.0-upgrade-guide.md +426 -0
  19. package/docs/guides/1.5.1-upgrade-guide.md +178 -0
  20. package/docs/guides/1.5.3-upgrade-guide.md +117 -0
  21. package/docs/guides/1.6.0-upgrade-guide.md +204 -0
  22. package/dungeons/technical/stories-verify.js +152 -0
  23. package/dungeons/vertical/README.md +154 -0
  24. package/dungeons/vertical/ai-platform/ai-platform.js +1424 -0
  25. package/dungeons/vertical/ai-platform/ai-platform.sql +184 -0
  26. package/dungeons/vertical/ai-platform/ai-platform.verify.mjs +71 -0
  27. package/dungeons/vertical/community/community.js +1589 -0
  28. package/dungeons/vertical/community/community.sql +216 -0
  29. package/dungeons/vertical/community/community.verify.mjs +72 -0
  30. package/dungeons/vertical/crypto/crypto.js +1483 -0
  31. package/dungeons/vertical/crypto/crypto.sql +194 -0
  32. package/dungeons/vertical/crypto/crypto.verify.mjs +78 -0
  33. package/dungeons/vertical/dating/dating.js +1540 -0
  34. package/dungeons/vertical/dating/dating.sql +288 -0
  35. package/dungeons/vertical/dating/dating.verify.mjs +72 -0
  36. package/dungeons/vertical/devtools/devtools.js +1524 -0
  37. package/dungeons/vertical/devtools/devtools.sql +161 -0
  38. package/dungeons/vertical/devtools/devtools.verify.mjs +73 -0
  39. package/dungeons/vertical/ecommerce/ecommerce.js +1480 -0
  40. package/dungeons/vertical/ecommerce/ecommerce.sql +218 -0
  41. package/dungeons/vertical/ecommerce/ecommerce.verify.mjs +70 -0
  42. package/dungeons/vertical/education/education.js +1763 -0
  43. package/dungeons/vertical/education/education.sql +243 -0
  44. package/dungeons/vertical/education/education.verify.mjs +75 -0
  45. package/dungeons/vertical/fintech/fintech.js +1410 -0
  46. package/dungeons/vertical/fintech/fintech.sql +175 -0
  47. package/dungeons/vertical/fintech/fintech.verify.mjs +70 -0
  48. package/dungeons/vertical/fitness/fitness.js +1495 -0
  49. package/dungeons/vertical/fitness/fitness.sql +206 -0
  50. package/dungeons/vertical/fitness/fitness.verify.mjs +71 -0
  51. package/dungeons/vertical/food-delivery/food-delivery.js +1641 -0
  52. package/dungeons/vertical/food-delivery/food-delivery.sql +183 -0
  53. package/dungeons/vertical/food-delivery/food-delivery.verify.mjs +75 -0
  54. package/dungeons/vertical/gaming/gaming.js +1741 -0
  55. package/dungeons/vertical/gaming/gaming.sql +249 -0
  56. package/dungeons/vertical/gaming/gaming.verify.mjs +70 -0
  57. package/dungeons/vertical/healthcare/healthcare.js +1488 -0
  58. package/dungeons/vertical/healthcare/healthcare.sql +193 -0
  59. package/dungeons/vertical/healthcare/healthcare.verify.mjs +72 -0
  60. package/dungeons/vertical/insurance-application/insurance-application.js +1852 -0
  61. package/dungeons/vertical/insurance-application/insurance-application.sql +174 -0
  62. package/dungeons/vertical/insurance-application/insurance-application.verify.mjs +74 -0
  63. package/dungeons/vertical/logistics/logistics.js +1541 -0
  64. package/dungeons/vertical/logistics/logistics.sql +205 -0
  65. package/dungeons/vertical/logistics/logistics.verify.mjs +73 -0
  66. package/dungeons/vertical/marketplace/marketplace.js +1438 -0
  67. package/dungeons/vertical/marketplace/marketplace.sql +148 -0
  68. package/dungeons/vertical/marketplace/marketplace.verify.mjs +74 -0
  69. package/dungeons/vertical/{media.js → media/media.js} +557 -58
  70. package/dungeons/vertical/media/media.sql +173 -0
  71. package/dungeons/vertical/media/media.verify.mjs +74 -0
  72. package/dungeons/vertical/real-estate/real-estate.js +1629 -0
  73. package/dungeons/vertical/real-estate/real-estate.sql +167 -0
  74. package/dungeons/vertical/real-estate/real-estate.verify.mjs +75 -0
  75. package/dungeons/vertical/sass/sass.js +1733 -0
  76. package/dungeons/vertical/sass/sass.sql +173 -0
  77. package/dungeons/vertical/sass/sass.verify.mjs +74 -0
  78. package/dungeons/vertical/social/social.js +1456 -0
  79. package/dungeons/vertical/social/social.sql +160 -0
  80. package/dungeons/vertical/social/social.verify.mjs +76 -0
  81. package/dungeons/vertical/streaming/streaming.js +677 -0
  82. package/dungeons/vertical/streaming/streaming.sql +101 -0
  83. package/dungeons/vertical/streaming/streaming.verify.mjs +75 -0
  84. package/dungeons/vertical/support-desk/support-desk.js +741 -0
  85. package/dungeons/vertical/support-desk/support-desk.sql +137 -0
  86. package/dungeons/vertical/support-desk/support-desk.verify.mjs +75 -0
  87. package/dungeons/vertical/travel/travel.js +1603 -0
  88. package/dungeons/vertical/travel/travel.sql +236 -0
  89. package/dungeons/vertical/travel/travel.verify.mjs +72 -0
  90. package/index.js +2 -2
  91. package/lib/core/config-validator.js +37 -2
  92. package/lib/core/context.js +4 -2
  93. package/lib/core/dungeon-loader.js +20 -15
  94. package/lib/core/dungeon-to-json.js +22 -4
  95. package/lib/core/storage.js +3 -3
  96. package/lib/generators/funnels.js +36 -9
  97. package/lib/hook-helpers/cohort.js +53 -0
  98. package/lib/hook-helpers/index.js +8 -0
  99. package/lib/hook-helpers/mutate.js +1 -1
  100. package/lib/hook-helpers/shape.js +319 -0
  101. package/lib/hook-patterns/aggregate-per-user-by-bin.js +13 -5
  102. package/lib/hook-patterns/attributed-by-source.js +60 -58
  103. package/lib/hook-patterns/frequency-by-frequency.js +14 -4
  104. package/lib/hook-patterns/funnel-frequency-breakdown.js +24 -12
  105. package/lib/hook-patterns/index.js +1 -1
  106. package/lib/hook-patterns/time-to-convert-by-segment.js +75 -13
  107. package/lib/orchestrators/user-loop.js +41 -0
  108. package/lib/templates/story-spec.schema.json +175 -0
  109. package/lib/utils/json-evaluator.js +1 -1
  110. package/lib/utils/utils.js +5 -4
  111. package/lib/verify/coerce.js +186 -0
  112. package/lib/verify/counting.js +270 -32
  113. package/lib/verify/emulate-breakdown.js +1285 -203
  114. package/lib/verify/first-time.js +89 -0
  115. package/lib/verify/flows.js +679 -0
  116. package/lib/verify/formula.js +259 -0
  117. package/lib/verify/funnel-engine.js +697 -113
  118. package/lib/verify/index.js +32 -4
  119. package/lib/verify/sessionize.js +275 -0
  120. package/lib/verify/story-runner.js +483 -0
  121. package/lib/verify/verify-dungeon.js +59 -37
  122. package/package.json +3 -2
  123. package/scripts/extract-dungeon-schema.mjs +2 -2
  124. package/scripts/run-many.mjs +2 -2
  125. package/scripts/verify-runner.mjs +2 -2
  126. package/scripts/verify-stories.mjs +244 -0
  127. package/types.d.ts +296 -30
  128. package/dungeons/vertical/ai-platform.js +0 -818
  129. package/dungeons/vertical/community.js +0 -966
  130. package/dungeons/vertical/crypto.js +0 -895
  131. package/dungeons/vertical/dating.js +0 -830
  132. package/dungeons/vertical/devtools.js +0 -1050
  133. package/dungeons/vertical/ecommerce.js +0 -811
  134. package/dungeons/vertical/education.js +0 -964
  135. package/dungeons/vertical/fintech.js +0 -946
  136. package/dungeons/vertical/fitness.js +0 -935
  137. package/dungeons/vertical/food-delivery.js +0 -934
  138. package/dungeons/vertical/gaming.js +0 -1120
  139. package/dungeons/vertical/healthcare.js +0 -945
  140. package/dungeons/vertical/insurance-application.js +0 -1039
  141. package/dungeons/vertical/logistics.js +0 -986
  142. package/dungeons/vertical/marketplace.js +0 -1019
  143. package/dungeons/vertical/real-estate.js +0 -872
  144. package/dungeons/vertical/sass.js +0 -995
  145. package/dungeons/vertical/social.js +0 -904
  146. package/dungeons/vertical/travel.js +0 -930
@@ -0,0 +1,161 @@
1
+ -- ============================================================
2
+ -- devtools.sql — human-eyeball inspection queries (v1.6)
3
+ -- ============================================================
4
+ -- Mirrors the story reads in devtools.js `stories`. Run after:
5
+ -- node scripts/verify-runner.mjs dungeons/vertical/devtools/devtools.js verify-devtools
6
+ --
7
+ -- Derivation notes:
8
+ -- - H1 uses MEDIAN: the extreme-value anomaly (10x build_duration_sec
9
+ -- at 0.3%) fattens means; medians isolate the clean 2x mechanism.
10
+ -- Bot events carry null build_status and fall out of status groups.
11
+ -- - H2/H9 exclude days 43-49: H6 recovery clones carry status
12
+ -- success/rolled_back only (never failed) and inflate deploy counts,
13
+ -- polluting both the failure-share and deploys-per-build reads.
14
+ -- - H3's cohort is the user_id hash (first GUID char in {2,3,4,d,e,f}),
15
+ -- NOT ai_assist — the copilot_integration feature (launchDay 30)
16
+ -- also flips ai_assist for feature adopters.
17
+ -- - H5/H9 normalize per-event / per-build: shares cancel persona event
18
+ -- multipliers, and the over/sweet pair cancels organic deploys-per-
19
+ -- build entirely (0.6/1.5 = 0.40 exact).
20
+ -- - H10 is NOT visible here: funnel-post scales gaps within funnel
21
+ -- instances; cross-event MIN→MIN SQL flattens it. See the emulator
22
+ -- timeToConvert assertion in devtools.js stories.
23
+
24
+ -- Hook 1: BUILD FAILURE CASCADE — failed builds 2x duration (median)
25
+ SELECT build_status, COUNT(*) AS n,
26
+ ROUND(MEDIAN(TRY_CAST(build_duration_sec AS DOUBLE)), 0) AS med_dur,
27
+ ROUND(AVG(TRY_CAST(build_duration_sec AS DOUBLE)), 0) AS avg_dur
28
+ FROM read_json_auto('data/verify-devtools-EVENTS*.json', sample_size=-1, union_by_name=true)
29
+ WHERE event = 'build completed'
30
+ GROUP BY build_status ORDER BY med_dur DESC;
31
+
32
+ -- Hook 2: NIGHT DEPLOY RISK — ~52% night vs ~21% day failure share
33
+ -- (recovery window days 43-49 excluded; H6 clones dilute it)
34
+ WITH ev AS (
35
+ SELECT deploy_status, hour(time::TIMESTAMP) AS hr,
36
+ date_diff('day', TIMESTAMP '2026-01-01 00:00:00', time::TIMESTAMP) AS day_idx
37
+ FROM read_json_auto('data/verify-devtools-EVENTS*.json', sample_size=-1, union_by_name=true)
38
+ WHERE event = 'deployment completed'
39
+ )
40
+ SELECT CASE WHEN hr >= 22 OR hr < 6 THEN 'night' ELSE 'day' END AS bucket,
41
+ COUNT(*) AS deploys,
42
+ ROUND(AVG((deploy_status = 'failed')::INT), 4) AS failure_share
43
+ FROM ev WHERE day_idx NOT BETWEEN 43 AND 49
44
+ GROUP BY (CASE WHEN hr >= 22 OR hr < 6 THEN 'night' ELSE 'day' END);
45
+
46
+ -- Hook 3: COPILOT PR VELOCITY — hash cohort ~1.5x PRs/user
47
+ WITH pu AS (
48
+ SELECT user_id::VARCHAR AS uid,
49
+ (ascii(substr(user_id::VARCHAR, 1, 1)) % 10 < 3) AS copilot,
50
+ COUNT(*) FILTER (WHERE event = 'pull request created') AS prs
51
+ FROM read_json_auto('data/verify-devtools-EVENTS*.json', sample_size=-1, union_by_name=true)
52
+ GROUP BY 1, 2
53
+ )
54
+ SELECT copilot, COUNT(*) AS users,
55
+ ROUND(SUM(prs)::DOUBLE / COUNT(*), 2) AS prs_per_user
56
+ FROM pu GROUP BY copilot ORDER BY copilot;
57
+
58
+ -- Hook 4: ON-CALL FATIGUE — >20 alerts → ~2.6x mean response time
59
+ WITH pu AS (
60
+ SELECT user_id::VARCHAR AS uid,
61
+ COUNT(*) FILTER (WHERE event = 'alert triggered') AS alerts,
62
+ AVG(TRY_CAST(response_time_minutes AS DOUBLE)) FILTER (WHERE event IN ('incident created', 'incident resolved')) AS rt,
63
+ COUNT(*) FILTER (WHERE event IN ('incident created', 'incident resolved')) AS incidents
64
+ FROM read_json_auto('data/verify-devtools-EVENTS*.json', sample_size=-1, union_by_name=true)
65
+ GROUP BY 1
66
+ )
67
+ SELECT (alerts > 20) AS fatigued, COUNT(*) AS users,
68
+ ROUND(AVG(rt), 1) AS mean_response_min,
69
+ ROUND(AVG(1 + LEAST(alerts / 20.0, 3)) FILTER (WHERE alerts > 20), 2) AS mean_engineered_mult
70
+ FROM pu WHERE incidents > 0 GROUP BY (alerts > 20) ORDER BY 1;
71
+
72
+ -- Hook 5: OSS POWER USAGE — active oss build share ~1.3x active non-oss
73
+ WITH us AS (
74
+ SELECT distinct_id::VARCHAR AS uid, segment
75
+ FROM read_json_auto('data/verify-devtools-USERS*.json', sample_size=-1, union_by_name=true)
76
+ ), pu AS (
77
+ SELECT user_id::VARCHAR AS uid, COUNT(*) AS n_ev,
78
+ COUNT(*) FILTER (WHERE event = 'build completed') AS builds,
79
+ COUNT(*) FILTER (WHERE event = 'deployment completed') AS deploys
80
+ FROM read_json_auto('data/verify-devtools-EVENTS*.json', sample_size=-1, union_by_name=true)
81
+ GROUP BY 1
82
+ )
83
+ SELECT (u.segment = 'oss_user') AS is_oss, COUNT(*) AS users,
84
+ ROUND(SUM(builds)::DOUBLE / SUM(n_ev), 4) AS build_share,
85
+ ROUND(SUM(deploys)::DOUBLE / SUM(n_ev), 4) AS deploy_share
86
+ FROM pu p JOIN us u USING (uid)
87
+ WHERE n_ev > 25
88
+ GROUP BY (u.segment = 'oss_user') ORDER BY is_oss;
89
+
90
+ -- Hook 6: POST-OUTAGE RECOVERY — deploys ~3.7x on days 44-47
91
+ -- (ratio-of-ratios vs builds cancels the growth ramp)
92
+ WITH ev AS (
93
+ SELECT event, date_diff('day', TIMESTAMP '2026-01-01 00:00:00', time::TIMESTAMP) AS day_idx
94
+ FROM read_json_auto('data/verify-devtools-EVENTS*.json', sample_size=-1, union_by_name=true)
95
+ WHERE event IN ('deployment completed', 'build completed')
96
+ )
97
+ SELECT CASE WHEN day_idx BETWEEN 44 AND 47 THEN 'recovery' ELSE 'baseline' END AS zone,
98
+ COUNT(*) FILTER (WHERE event = 'deployment completed') AS deploys,
99
+ COUNT(*) FILTER (WHERE event = 'build completed') AS builds,
100
+ ROUND(COUNT(*) FILTER (WHERE event = 'deployment completed')::DOUBLE
101
+ / COUNT(*) FILTER (WHERE event = 'build completed'), 3) AS deploys_per_build
102
+ FROM ev
103
+ WHERE day_idx BETWEEN 35 AND 41 OR day_idx BETWEEN 44 AND 47 OR day_idx BETWEEN 49 AND 55
104
+ GROUP BY (CASE WHEN day_idx BETWEEN 44 AND 47 THEN 'recovery' ELSE 'baseline' END);
105
+
106
+ -- Hook 7: DEVOPS PROFILE ENRICHMENT — repos_connected is the crisp
107
+ -- signal (default [0]); team_size contrast is devops vs junior
108
+ -- (full_stack/oss default pool mean is ~24, NOT ~10)
109
+ SELECT segment, COUNT(*) AS users,
110
+ ROUND(AVG(TRY_CAST(team_size AS DOUBLE)), 1) AS avg_team,
111
+ ROUND(AVG(TRY_CAST(repos_connected AS DOUBLE)), 1) AS avg_repos,
112
+ MODE(experience_level) AS mode_exp
113
+ FROM read_json_auto('data/verify-devtools-USERS*.json', sample_size=-1, union_by_name=true)
114
+ GROUP BY segment ORDER BY avg_repos DESC;
115
+
116
+ -- Hook 8: ENTERPRISE FUNNEL LIFT — free/team mv-per-deploy ~0.63x paid
117
+ -- (per-deploy normalization cancels H6/H9 deploy inflation)
118
+ WITH pu AS (
119
+ SELECT user_id::VARCHAR AS uid, ANY_VALUE(subscription_tier) AS tier,
120
+ COUNT(*) FILTER (WHERE event = 'monitoring dashboard viewed') AS mv,
121
+ COUNT(*) FILTER (WHERE event = 'deployment completed') AS dep
122
+ FROM read_json_auto('data/verify-devtools-EVENTS*.json', sample_size=-1, union_by_name=true)
123
+ WHERE event IN ('monitoring dashboard viewed', 'deployment completed')
124
+ GROUP BY 1
125
+ )
126
+ SELECT CASE WHEN tier IN ('enterprise', 'business') THEN 'paid' ELSE 'free_team' END AS grp,
127
+ COUNT(*) AS users,
128
+ ROUND(SUM(mv)::DOUBLE / NULLIF(SUM(dep), 0), 4) AS mv_per_deploy
129
+ FROM pu WHERE tier IS NOT NULL
130
+ GROUP BY (CASE WHEN tier IN ('enterprise', 'business') THEN 'paid' ELSE 'free_team' END);
131
+
132
+ -- Hook 9: BUILD-COUNT MAGIC NUMBER — deploys-per-build by bucket
133
+ -- (full_stack only; recovery-window deploys excluded; over/sweet ≈ 0.40)
134
+ WITH us AS (
135
+ SELECT distinct_id::VARCHAR AS uid, segment
136
+ FROM read_json_auto('data/verify-devtools-USERS*.json', sample_size=-1, union_by_name=true)
137
+ ), pu AS (
138
+ SELECT user_id::VARCHAR AS uid,
139
+ COUNT(*) FILTER (WHERE event = 'build completed') AS builds,
140
+ COUNT(*) FILTER (WHERE event = 'deployment completed'
141
+ AND date_diff('day', TIMESTAMP '2026-01-01 00:00:00', time::TIMESTAMP) NOT BETWEEN 43 AND 49) AS deploys
142
+ FROM read_json_auto('data/verify-devtools-EVENTS*.json', sample_size=-1, union_by_name=true)
143
+ GROUP BY 1
144
+ )
145
+ SELECT CASE WHEN builds BETWEEN 15 AND 30 THEN 'sweet'
146
+ WHEN builds >= 31 THEN 'over' ELSE 'base' END AS bucket,
147
+ COUNT(*) AS users,
148
+ ROUND(SUM(deploys)::DOUBLE / SUM(builds), 4) AS deploys_per_build
149
+ FROM pu p JOIN us u USING (uid)
150
+ WHERE u.segment = 'full_stack' AND builds >= 1
151
+ GROUP BY (CASE WHEN builds BETWEEN 15 AND 30 THEN 'sweet' WHEN builds >= 31 THEN 'over' ELSE 'base' END)
152
+ ORDER BY bucket;
153
+
154
+ -- Hook 10: BUILD-DEPLOY TTC BY TIER — not visible in cross-event SQL.
155
+ -- funnel-post rewrites step timestamps within funnel instances; the
156
+ -- emulator's timeToConvert (see devtools.js stories) is the honest
157
+ -- read. This query only confirms the tier populations exist.
158
+ SELECT subscription_tier, COUNT(DISTINCT user_id) AS users
159
+ FROM read_json_auto('data/verify-devtools-EVENTS*.json', sample_size=-1, union_by_name=true)
160
+ WHERE event IN ('build completed', 'deployment completed') AND subscription_tier IS NOT NULL
161
+ GROUP BY subscription_tier ORDER BY users DESC;
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * devtools.verify.mjs — thin wrapper around the story runner.
4
+ *
5
+ * All verification logic lives in the `stories` export of
6
+ * ./devtools.js; this script just streams the shards and
7
+ * delegates. It is equivalent to:
8
+ *
9
+ * node scripts/verify-stories.mjs dungeons/vertical/devtools/devtools.js --data-prefix verify-devtools
10
+ *
11
+ * Generate first:
12
+ * node scripts/verify-runner.mjs dungeons/vertical/devtools/devtools.js verify-devtools
13
+ * Run:
14
+ * node dungeons/vertical/devtools/devtools.verify.mjs [prefix] # default verify-devtools
15
+ */
16
+ import fs from 'node:fs';
17
+ import path from 'node:path';
18
+ import readline from 'node:readline';
19
+ import { execFile } from 'node:child_process';
20
+ import { promisify } from 'node:util';
21
+ import { buildIdentityMap, evaluateStories, VERDICT_RANK } from '@ak--47/dungeon-master/verify';
22
+ import config, { stories } from './devtools.js';
23
+
24
+ const PREFIX = process.argv[2] || 'verify-devtools';
25
+ const prefixPath = path.join('data', PREFIX);
26
+
27
+ async function loadShards(suffix) {
28
+ // streaming load: full-fidelity EVENTS shards exceed the readFileSync cap
29
+ const dir = path.dirname(prefixPath), base = path.basename(prefixPath);
30
+ const out = [];
31
+ if (!fs.existsSync(dir)) return out;
32
+ for (const f of fs.readdirSync(dir).filter(f => f.startsWith(`${base}-${suffix}`) && f.endsWith('.json')).sort()) {
33
+ const rl = readline.createInterface({ input: fs.createReadStream(path.join(dir, f)), crlfDelay: Infinity });
34
+ for await (const line of rl) {
35
+ if (line.trim()) out.push(JSON.parse(line));
36
+ }
37
+ }
38
+ return out;
39
+ }
40
+
41
+ const events = await loadShards('EVENTS');
42
+ const profiles = await loadShards('USERS');
43
+ if (!events.length) {
44
+ console.error(`no shards at ${prefixPath}-EVENTS*.json — run: node scripts/verify-runner.mjs dungeons/vertical/devtools/devtools.js ${PREFIX}`);
45
+ process.exit(1);
46
+ }
47
+ console.log(`devtools — events=${events.length} users=${profiles.length} (${prefixPath})`);
48
+
49
+ const execFileP = promisify(execFile);
50
+ const runSql = async (sql) => {
51
+ const { stdout } = await execFileP('duckdb', ['-json', '-c', sql.replaceAll('{{PREFIX}}', prefixPath)], { maxBuffer: 64 * 1024 * 1024 });
52
+ return stdout.trim() ? JSON.parse(stdout) : [];
53
+ };
54
+
55
+ // Nine stories are DuckDB-backed (behavioral cohorts, ratio-of-ratios reads
56
+ // that cancel soup/persona confounds); H10 dispatches to emulateBreakdown's
57
+ // timeToConvert — funnel-post gap scaling is only visible to funnel-instance
58
+ // semantics, not cross-event SQL (greedy MIN→MIN picks flatten it).
59
+ const results = await evaluateStories(stories, events, {
60
+ profiles,
61
+ funnels: config.funnels,
62
+ identityMap: buildIdentityMap(profiles),
63
+ runSql,
64
+ });
65
+
66
+ let worst = 'NAILED';
67
+ for (const r of results) {
68
+ console.log(`${r.verdict.padEnd(7)} ${r.id}`);
69
+ for (const a of r.assertions) console.log(` ${a.verdict.padEnd(7)} ${a.detail}`);
70
+ if (VERDICT_RANK[r.verdict] < VERDICT_RANK[worst]) worst = r.verdict;
71
+ }
72
+ console.log(`\nworst verdict: ${worst}`);
73
+ process.exit(VERDICT_RANK[worst] >= VERDICT_RANK.STRONG ? 0 : 1);