@ak--47/dungeon-master 1.5.4 → 1.6.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 +21 -12
- package/.claude/skills/create-dungeon/SKILL.md +65 -5
- package/.claude/skills/create-project/SKILL.md +1 -1
- package/.claude/skills/create-project/provision.mjs +57 -10
- package/.claude/skills/powertools/SKILL.md +75 -0
- package/.claude/skills/powertools/pt.mjs +91 -0
- package/.claude/skills/powertools/snapshot-project.mjs +124 -0
- package/.claude/skills/verify-dungeon/SKILL.md +56 -25
- package/.claude/skills/verify-dungeon/references/counting-semantics.md +26 -6
- package/.claude/skills/verify-dungeon/references/report-format.md +14 -8
- package/.claude/skills/write-hooks/SKILL.md +156 -18
- package/CHANGELOG.md +224 -0
- package/HOOKS.md +566 -112
- package/README.md +30 -9
- package/docs/guides/1.3.0-upgrade-guide.md +262 -0
- package/docs/guides/1.3.1-upgrade-guide.md +328 -0
- package/docs/guides/1.4.0-upgrade-guide.md +130 -0
- package/docs/guides/1.4.1-upgrade-guide.md +125 -0
- package/docs/guides/1.4.3-upgrade-guide.md +200 -0
- package/docs/guides/1.4.4-upgrade-guide.md +95 -0
- package/docs/guides/1.5.0-upgrade-guide.md +426 -0
- package/docs/guides/1.5.1-upgrade-guide.md +178 -0
- package/docs/guides/1.5.3-upgrade-guide.md +117 -0
- package/docs/guides/1.6.0-upgrade-guide.md +204 -0
- package/dungeons/technical/stories-verify.js +152 -0
- package/dungeons/vertical/README.md +154 -0
- package/dungeons/vertical/ai-platform/ai-platform.js +1424 -0
- package/dungeons/vertical/ai-platform/ai-platform.sql +184 -0
- package/dungeons/vertical/ai-platform/ai-platform.verify.mjs +71 -0
- package/dungeons/vertical/community/community.js +1589 -0
- package/dungeons/vertical/community/community.sql +216 -0
- package/dungeons/vertical/community/community.verify.mjs +72 -0
- package/dungeons/vertical/crypto/crypto.js +1483 -0
- package/dungeons/vertical/crypto/crypto.sql +194 -0
- package/dungeons/vertical/crypto/crypto.verify.mjs +78 -0
- package/dungeons/vertical/dating/dating.js +1540 -0
- package/dungeons/vertical/dating/dating.sql +288 -0
- package/dungeons/vertical/dating/dating.verify.mjs +72 -0
- package/dungeons/vertical/devtools/devtools.js +1524 -0
- package/dungeons/vertical/devtools/devtools.sql +161 -0
- package/dungeons/vertical/devtools/devtools.verify.mjs +73 -0
- package/dungeons/vertical/ecommerce/ecommerce.js +1480 -0
- package/dungeons/vertical/ecommerce/ecommerce.sql +218 -0
- package/dungeons/vertical/ecommerce/ecommerce.verify.mjs +70 -0
- package/dungeons/vertical/education/education.js +1763 -0
- package/dungeons/vertical/education/education.sql +243 -0
- package/dungeons/vertical/education/education.verify.mjs +75 -0
- package/dungeons/vertical/fintech/fintech.js +1410 -0
- package/dungeons/vertical/fintech/fintech.sql +175 -0
- package/dungeons/vertical/fintech/fintech.verify.mjs +70 -0
- package/dungeons/vertical/fitness/fitness.js +1495 -0
- package/dungeons/vertical/fitness/fitness.sql +206 -0
- package/dungeons/vertical/fitness/fitness.verify.mjs +71 -0
- package/dungeons/vertical/food-delivery/food-delivery.js +1641 -0
- package/dungeons/vertical/food-delivery/food-delivery.sql +183 -0
- package/dungeons/vertical/food-delivery/food-delivery.verify.mjs +75 -0
- package/dungeons/vertical/gaming/gaming.js +1741 -0
- package/dungeons/vertical/gaming/gaming.sql +249 -0
- package/dungeons/vertical/gaming/gaming.verify.mjs +70 -0
- package/dungeons/vertical/healthcare/healthcare.js +1488 -0
- package/dungeons/vertical/healthcare/healthcare.sql +193 -0
- package/dungeons/vertical/healthcare/healthcare.verify.mjs +72 -0
- package/dungeons/vertical/insurance-application/insurance-application.js +1852 -0
- package/dungeons/vertical/insurance-application/insurance-application.sql +174 -0
- package/dungeons/vertical/insurance-application/insurance-application.verify.mjs +74 -0
- package/dungeons/vertical/logistics/logistics.js +1541 -0
- package/dungeons/vertical/logistics/logistics.sql +205 -0
- package/dungeons/vertical/logistics/logistics.verify.mjs +73 -0
- package/dungeons/vertical/marketplace/marketplace.js +1438 -0
- package/dungeons/vertical/marketplace/marketplace.sql +148 -0
- package/dungeons/vertical/marketplace/marketplace.verify.mjs +74 -0
- package/dungeons/vertical/{media.js → media/media.js} +557 -58
- package/dungeons/vertical/media/media.sql +173 -0
- package/dungeons/vertical/media/media.verify.mjs +74 -0
- package/dungeons/vertical/real-estate/real-estate.js +1629 -0
- package/dungeons/vertical/real-estate/real-estate.sql +167 -0
- package/dungeons/vertical/real-estate/real-estate.verify.mjs +75 -0
- package/dungeons/vertical/sass/sass.js +1733 -0
- package/dungeons/vertical/sass/sass.sql +173 -0
- package/dungeons/vertical/sass/sass.verify.mjs +74 -0
- package/dungeons/vertical/social/social.js +1456 -0
- package/dungeons/vertical/social/social.sql +160 -0
- package/dungeons/vertical/social/social.verify.mjs +76 -0
- package/dungeons/vertical/streaming/streaming.js +677 -0
- package/dungeons/vertical/streaming/streaming.sql +101 -0
- package/dungeons/vertical/streaming/streaming.verify.mjs +75 -0
- package/dungeons/vertical/support-desk/support-desk.js +741 -0
- package/dungeons/vertical/support-desk/support-desk.sql +137 -0
- package/dungeons/vertical/support-desk/support-desk.verify.mjs +75 -0
- package/dungeons/vertical/travel/travel.js +1603 -0
- package/dungeons/vertical/travel/travel.sql +236 -0
- package/dungeons/vertical/travel/travel.verify.mjs +72 -0
- package/index.js +7 -3
- package/lib/core/config-validator.js +37 -2
- package/lib/core/context.js +4 -2
- package/lib/core/dungeon-loader.js +20 -15
- package/lib/core/dungeon-to-json.js +22 -4
- package/lib/core/storage.js +3 -3
- package/lib/generators/funnels.js +36 -9
- package/lib/hook-helpers/cohort.js +53 -0
- package/lib/hook-helpers/index.js +8 -0
- package/lib/hook-helpers/mutate.js +1 -1
- package/lib/hook-helpers/shape.js +319 -0
- package/lib/hook-patterns/aggregate-per-user-by-bin.js +13 -5
- package/lib/hook-patterns/attributed-by-source.js +60 -58
- package/lib/hook-patterns/frequency-by-frequency.js +14 -4
- package/lib/hook-patterns/funnel-frequency-breakdown.js +24 -12
- package/lib/hook-patterns/index.js +1 -1
- package/lib/hook-patterns/time-to-convert-by-segment.js +75 -13
- package/lib/orchestrators/user-loop.js +41 -0
- package/lib/templates/story-spec.schema.json +175 -0
- package/lib/utils/json-evaluator.js +1 -1
- package/lib/utils/utils.js +151 -59
- package/lib/verify/coerce.js +186 -0
- package/lib/verify/counting.js +270 -32
- package/lib/verify/emulate-breakdown.js +1285 -203
- package/lib/verify/first-time.js +89 -0
- package/lib/verify/flows.js +679 -0
- package/lib/verify/formula.js +259 -0
- package/lib/verify/funnel-engine.js +697 -113
- package/lib/verify/index.js +32 -4
- package/lib/verify/sessionize.js +275 -0
- package/lib/verify/story-runner.js +483 -0
- package/lib/verify/verify-dungeon.js +59 -37
- package/package.json +2 -1
- package/scripts/extract-dungeon-schema.mjs +2 -2
- package/scripts/run-many.mjs +2 -2
- package/scripts/verify-runner.mjs +2 -2
- package/scripts/verify-stories.mjs +244 -0
- package/types.d.ts +298 -30
- package/dungeons/vertical/ai-platform.js +0 -818
- package/dungeons/vertical/community.js +0 -966
- package/dungeons/vertical/crypto.js +0 -895
- package/dungeons/vertical/dating.js +0 -830
- package/dungeons/vertical/devtools.js +0 -1050
- package/dungeons/vertical/ecommerce.js +0 -811
- package/dungeons/vertical/education.js +0 -964
- package/dungeons/vertical/fintech.js +0 -946
- package/dungeons/vertical/fitness.js +0 -935
- package/dungeons/vertical/food-delivery.js +0 -934
- package/dungeons/vertical/gaming.js +0 -1120
- package/dungeons/vertical/healthcare.js +0 -945
- package/dungeons/vertical/insurance-application.js +0 -1039
- package/dungeons/vertical/logistics.js +0 -986
- package/dungeons/vertical/marketplace.js +0 -1019
- package/dungeons/vertical/real-estate.js +0 -872
- package/dungeons/vertical/sass.js +0 -995
- package/dungeons/vertical/social.js +0 -904
- package/dungeons/vertical/travel.js +0 -930
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
-- ============================================================
|
|
2
|
+
-- social.js — hook inspection queries (v1.6.0 stories rebuild)
|
|
3
|
+
--
|
|
4
|
+
-- The verification CONTRACT lives in the `stories` export of
|
|
5
|
+
-- social.js (run via social.verify.mjs). These queries are for
|
|
6
|
+
-- manual inspection of the same reads.
|
|
7
|
+
--
|
|
8
|
+
-- Derivation notes (measured at 2K reduced scale + organic
|
|
9
|
+
-- counterfactual run — same seed, identity hook):
|
|
10
|
+
-- - H1's old "10+ post created" cohort read is dead: 85% of
|
|
11
|
+
-- users clear 10 FINAL posts (clones inflate counts). The
|
|
12
|
+
-- live read is concentration: top-3% engagement share 0.62
|
|
13
|
+
-- vs organic 0.06.
|
|
14
|
+
-- - H2's 1.5x posts ratio decomposes into 1.24x activity
|
|
15
|
+
-- confound x 1.21x true hook increment.
|
|
16
|
+
-- - Organic DOW is FLAT (soup weights >= 1.0 accept-always);
|
|
17
|
+
-- H8's 1.25x weekend ratio is pure hook. The v1.5 "soup
|
|
18
|
+
-- baseline ~0.55" claim was wrong.
|
|
19
|
+
-- - H9 must be read by FINAL post counts (the analyst view) —
|
|
20
|
+
-- the hook buckets on pre-injection counts, so the final
|
|
21
|
+
-- 3-7 bucket is dominated by boosted 0-2 dodgers: sweet
|
|
22
|
+
-- avg ~250, low ~200, over ~145.
|
|
23
|
+
-- - H10's funnel-post gap scaling is NOT visible to
|
|
24
|
+
-- MIN(step)-join SQL; use the emulator via social.verify.mjs.
|
|
25
|
+
-- The MIN-join below shows direction only.
|
|
26
|
+
--
|
|
27
|
+
-- Replace data/verify-social with your run prefix.
|
|
28
|
+
-- ============================================================
|
|
29
|
+
|
|
30
|
+
-- H1: VIRAL CONTENT — top-3% of users' share of engagement events
|
|
31
|
+
-- Expected: ~0.62 (organic counterfactual 0.057)
|
|
32
|
+
WITH pu AS (
|
|
33
|
+
SELECT user_id::VARCHAR AS uid,
|
|
34
|
+
COUNT(*) FILTER (WHERE event IN ('post viewed', 'post liked', 'post shared')) AS eng
|
|
35
|
+
FROM read_json_auto('data/verify-social-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
36
|
+
WHERE user_id IS NOT NULL GROUP BY 1
|
|
37
|
+
), ranked AS (
|
|
38
|
+
SELECT eng, ROW_NUMBER() OVER (ORDER BY eng DESC) AS rn,
|
|
39
|
+
COUNT(*) OVER () AS n, SUM(eng) OVER () AS tot
|
|
40
|
+
FROM pu
|
|
41
|
+
)
|
|
42
|
+
SELECT ROUND(SUM(eng)::DOUBLE / MAX(tot), 4) AS top3_share, MAX(n) AS users
|
|
43
|
+
FROM ranked WHERE rn <= CEIL(n * 0.03);
|
|
44
|
+
|
|
45
|
+
-- H2: FOLLOW-BACK SNOWBALL — 5+ user-followed → more posts
|
|
46
|
+
-- Expected: big/small posts-per-user ~1.50 (42.3 vs 28.2 at 2K)
|
|
47
|
+
WITH pu AS (
|
|
48
|
+
SELECT user_id::VARCHAR AS uid,
|
|
49
|
+
COUNT(*) FILTER (WHERE event = 'user followed') AS fc,
|
|
50
|
+
COUNT(*) FILTER (WHERE event = 'post created') AS pc
|
|
51
|
+
FROM read_json_auto('data/verify-social-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
52
|
+
WHERE user_id IS NOT NULL GROUP BY 1
|
|
53
|
+
)
|
|
54
|
+
SELECT (fc >= 5) AS big_followers, COUNT(*) AS users, ROUND(AVG(pc), 2) AS avg_posts
|
|
55
|
+
FROM pu GROUP BY 1 ORDER BY 1;
|
|
56
|
+
|
|
57
|
+
-- H3 + H5: SOURCE MIX by window (algorithm change @d45, notifications @d30)
|
|
58
|
+
-- Expected: pre_d30 feed ~0.78, notif ~0.03; d30_45 feed ~0.54, notif ~0.32;
|
|
59
|
+
-- post_d45 explore ~0.54, notif ~0.32, feed ~0.05
|
|
60
|
+
SELECT
|
|
61
|
+
CASE WHEN time::TIMESTAMP < TIMESTAMP '2026-01-31 00:00:00' THEN 'pre_d30'
|
|
62
|
+
WHEN time::TIMESTAMP < TIMESTAMP '2026-02-15 00:00:00' THEN 'd30_45'
|
|
63
|
+
ELSE 'post_d45' END AS win,
|
|
64
|
+
COUNT(*) AS n,
|
|
65
|
+
ROUND(AVG((source = 'feed')::INT), 4) AS feed,
|
|
66
|
+
ROUND(AVG((source = 'explore')::INT), 4) AS explore,
|
|
67
|
+
ROUND(AVG((source = 'notification')::INT), 4) AS notif,
|
|
68
|
+
ROUND(AVG((source = 'profile')::INT), 4) AS prof
|
|
69
|
+
FROM read_json_auto('data/verify-social-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
70
|
+
WHERE event = 'post viewed'
|
|
71
|
+
GROUP BY 1 ORDER BY 1;
|
|
72
|
+
|
|
73
|
+
-- H4: ENGAGEMENT BAIT — share of post-viewed events with crushed (<=5s) duration
|
|
74
|
+
-- Expected: ~0.20 (organic 0.000 — generator floors durations above 5s)
|
|
75
|
+
SELECT COUNT(*) AS views,
|
|
76
|
+
ROUND(AVG((view_duration_sec <= 5)::INT), 4) AS crushed_share,
|
|
77
|
+
ROUND(AVG(view_duration_sec) FILTER (WHERE view_duration_sec <= 5), 2) AS avg_crushed_dur
|
|
78
|
+
FROM read_json_auto('data/verify-social-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
79
|
+
WHERE event = 'post viewed' AND view_duration_sec IS NOT NULL;
|
|
80
|
+
|
|
81
|
+
-- H6: CREATOR MONETIZATION — subscribers (~78% of users) vs non
|
|
82
|
+
-- Expected: posts/user ratio ~3.32; stories/user ~3.82 (purer read —
|
|
83
|
+
-- H2 dupes lift non-sub posts but never stories)
|
|
84
|
+
WITH pu AS (
|
|
85
|
+
SELECT user_id::VARCHAR AS uid,
|
|
86
|
+
BOOL_OR(event = 'creator subscription started') AS sub,
|
|
87
|
+
COUNT(*) FILTER (WHERE event = 'post created') AS pc,
|
|
88
|
+
COUNT(*) FILTER (WHERE event = 'story created') AS sc
|
|
89
|
+
FROM read_json_auto('data/verify-social-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
90
|
+
WHERE user_id IS NOT NULL GROUP BY 1
|
|
91
|
+
)
|
|
92
|
+
SELECT sub, COUNT(*) AS users, ROUND(AVG(pc), 2) AS avg_posts, ROUND(AVG(sc), 2) AS avg_stories
|
|
93
|
+
FROM pu GROUP BY 1 ORDER BY 1;
|
|
94
|
+
|
|
95
|
+
-- H7: TOXICITY CHURN — per-user post/pre-d30 event ratio, reporters vs normal
|
|
96
|
+
-- Expected: reporters ~1.27 vs normal ~3.21 → contrast ~0.40 (60% drop knob;
|
|
97
|
+
-- growth shape makes the raw post/pre ~3.2 for everyone)
|
|
98
|
+
WITH pu AS (
|
|
99
|
+
SELECT user_id::VARCHAR AS uid,
|
|
100
|
+
COUNT(*) FILTER (WHERE event = 'report submitted') AS rep,
|
|
101
|
+
COUNT(*) FILTER (WHERE time::TIMESTAMP <= TIMESTAMP '2026-01-31 00:00:00') AS pre,
|
|
102
|
+
COUNT(*) FILTER (WHERE time::TIMESTAMP > TIMESTAMP '2026-01-31 00:00:00') AS post
|
|
103
|
+
FROM read_json_auto('data/verify-social-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
104
|
+
WHERE user_id IS NOT NULL GROUP BY 1
|
|
105
|
+
)
|
|
106
|
+
SELECT (rep >= 2) AS reporter, COUNT(*) AS users, ROUND(AVG(post::DOUBLE / pre), 3) AS post_pre_ratio
|
|
107
|
+
FROM pu WHERE pre > 0 GROUP BY 1 ORDER BY 1;
|
|
108
|
+
|
|
109
|
+
-- H8: WEEKEND SURGE — weekend vs weekday daily creation rate
|
|
110
|
+
-- Expected: ~1.25x (organic counterfactual 0.98 — flat DOW baseline)
|
|
111
|
+
WITH d AS (
|
|
112
|
+
SELECT time::DATE AS dt, COUNT(*) AS n
|
|
113
|
+
FROM read_json_auto('data/verify-social-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
114
|
+
WHERE event IN ('post created', 'story created') GROUP BY 1
|
|
115
|
+
)
|
|
116
|
+
SELECT
|
|
117
|
+
ROUND(AVG(n) FILTER (WHERE EXTRACT(DOW FROM dt) IN (0, 6)), 1) AS weekend_per_day,
|
|
118
|
+
ROUND(AVG(n) FILTER (WHERE EXTRACT(DOW FROM dt) NOT IN (0, 6)), 1) AS weekday_per_day,
|
|
119
|
+
ROUND(AVG(n) FILTER (WHERE EXTRACT(DOW FROM dt) IN (0, 6))
|
|
120
|
+
/ AVG(n) FILTER (WHERE EXTRACT(DOW FROM dt) NOT IN (0, 6)), 3) AS ratio
|
|
121
|
+
FROM d;
|
|
122
|
+
|
|
123
|
+
-- H9: MAGIC NUMBER — comment_length by FINAL post-count bucket
|
|
124
|
+
-- Expected: sweet(3-7) ~250, low(0-2) ~200, over(8+) ~145; sweet/over ~1.72
|
|
125
|
+
WITH pu AS (
|
|
126
|
+
SELECT user_id::VARCHAR AS uid,
|
|
127
|
+
COUNT(*) FILTER (WHERE event = 'post created') AS pc
|
|
128
|
+
FROM read_json_auto('data/verify-social-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
129
|
+
WHERE user_id IS NOT NULL GROUP BY 1
|
|
130
|
+
)
|
|
131
|
+
SELECT CASE WHEN pu.pc >= 8 THEN 'over' WHEN pu.pc >= 3 THEN 'sweet' ELSE 'low' END AS bucket,
|
|
132
|
+
COUNT(DISTINCT pu.uid) AS users, COUNT(*) AS n_comments, ROUND(AVG(e.comment_length), 1) AS avg_len
|
|
133
|
+
FROM read_json_auto('data/verify-social-EVENTS*.json', sample_size=-1, union_by_name=true) e
|
|
134
|
+
JOIN pu ON e.user_id::VARCHAR = pu.uid
|
|
135
|
+
WHERE e.event = 'comment posted' AND e.comment_length IS NOT NULL
|
|
136
|
+
GROUP BY 1 ORDER BY 1;
|
|
137
|
+
|
|
138
|
+
-- H10: ONBOARDING TTC by account_type — DIRECTION ONLY via MIN-join
|
|
139
|
+
-- (funnel-post gap scaling needs per-instance greedy matching; the story
|
|
140
|
+
-- asserts fast/personal ~0.71 through emulateBreakdown @6h window)
|
|
141
|
+
SELECT u.account_type, COUNT(*) AS converters,
|
|
142
|
+
ROUND(MEDIAN(EXTRACT(EPOCH FROM (pc.t - ac.t)) / 60), 1) AS median_ttc_min
|
|
143
|
+
FROM
|
|
144
|
+
(SELECT user_id, MIN(time::TIMESTAMP) AS t
|
|
145
|
+
FROM read_json_auto('data/verify-social-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
146
|
+
WHERE event = 'account created' GROUP BY user_id) ac
|
|
147
|
+
JOIN
|
|
148
|
+
(SELECT user_id, MIN(time::TIMESTAMP) AS t
|
|
149
|
+
FROM read_json_auto('data/verify-social-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
150
|
+
WHERE event = 'post created' GROUP BY user_id) pc USING (user_id)
|
|
151
|
+
JOIN read_json_auto('data/verify-social-USERS*.json', sample_size=-1, union_by_name=true) u
|
|
152
|
+
ON ac.user_id = u.distinct_id
|
|
153
|
+
WHERE pc.t > ac.t
|
|
154
|
+
GROUP BY u.account_type ORDER BY u.account_type;
|
|
155
|
+
|
|
156
|
+
-- Identity invariants — expected uid_share 1.0, device_share >= 0.99, devices/user ~2
|
|
157
|
+
SELECT ROUND(AVG((user_id IS NOT NULL)::INT), 4) AS uid_share,
|
|
158
|
+
ROUND(AVG((device_id IS NOT NULL)::INT), 4) AS device_share,
|
|
159
|
+
ROUND(COUNT(DISTINCT device_id)::DOUBLE / COUNT(DISTINCT user_id), 2) AS devices_per_user
|
|
160
|
+
FROM read_json_auto('data/verify-social-EVENTS*.json', sample_size=-1, union_by_name=true);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* social.verify.mjs — thin wrapper around the story runner.
|
|
4
|
+
*
|
|
5
|
+
* All verification logic lives in the `stories` export of ./social.js;
|
|
6
|
+
* this script just streams the shards and delegates. It is equivalent to:
|
|
7
|
+
*
|
|
8
|
+
* node scripts/verify-stories.mjs dungeons/vertical/social/social.js --data-prefix verify-social
|
|
9
|
+
*
|
|
10
|
+
* Generate first:
|
|
11
|
+
* node scripts/verify-runner.mjs dungeons/vertical/social/social.js verify-social
|
|
12
|
+
* Run:
|
|
13
|
+
* node dungeons/vertical/social/social.verify.mjs [prefix] # default verify-social
|
|
14
|
+
*/
|
|
15
|
+
import fs from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import readline from 'node:readline';
|
|
18
|
+
import { execFile } from 'node:child_process';
|
|
19
|
+
import { promisify } from 'node:util';
|
|
20
|
+
import { buildIdentityMap, evaluateStories, VERDICT_RANK } from '@ak--47/dungeon-master/verify';
|
|
21
|
+
import config, { stories } from './social.js';
|
|
22
|
+
|
|
23
|
+
const PREFIX = process.argv[2] || 'verify-social';
|
|
24
|
+
const prefixPath = path.join('data', PREFIX);
|
|
25
|
+
|
|
26
|
+
async function loadShards(suffix) {
|
|
27
|
+
// streaming load: full-fidelity EVENTS shards exceed the readFileSync cap
|
|
28
|
+
const dir = path.dirname(prefixPath), base = path.basename(prefixPath);
|
|
29
|
+
const out = [];
|
|
30
|
+
if (!fs.existsSync(dir)) return out;
|
|
31
|
+
for (const f of fs.readdirSync(dir).filter(f => f.startsWith(`${base}-${suffix}`) && f.endsWith('.json')).sort()) {
|
|
32
|
+
const rl = readline.createInterface({ input: fs.createReadStream(path.join(dir, f)), crlfDelay: Infinity });
|
|
33
|
+
for await (const line of rl) {
|
|
34
|
+
if (line.trim()) out.push(JSON.parse(line));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const events = await loadShards('EVENTS');
|
|
41
|
+
const profiles = await loadShards('USERS');
|
|
42
|
+
if (!events.length) {
|
|
43
|
+
console.error(`no shards at ${prefixPath}-EVENTS*.json — run: node scripts/verify-runner.mjs dungeons/vertical/social/social.js ${PREFIX}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
console.log(`social — events=${events.length} users=${profiles.length} (${prefixPath})`);
|
|
47
|
+
|
|
48
|
+
const execFileP = promisify(execFile);
|
|
49
|
+
const runSql = async (sql) => {
|
|
50
|
+
const { stdout } = await execFileP('duckdb', ['-json', '-c', sql.replaceAll('{{PREFIX}}', prefixPath)], { maxBuffer: 64 * 1024 * 1024 });
|
|
51
|
+
return stdout.trim() ? JSON.parse(stdout) : [];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Nine stories are DuckDB-backed (concentration shares, behavioral cohorts,
|
|
55
|
+
// temporal source-mix windows, per-user post/pre retention ratios, DOW daily
|
|
56
|
+
// rates, and final-count frequency buckets). H10 dispatches to
|
|
57
|
+
// emulateBreakdown's timeToConvert at a 6-HOUR window — funnel-post gap
|
|
58
|
+
// scaling is invisible to cross-event SQL, and the onboarding funnel's
|
|
59
|
+
// unique first step (`account created`, isFirstEvent) anchors the greedy
|
|
60
|
+
// evaluator to the exact instance the hook touched, so the read is stable
|
|
61
|
+
// across 1h-24h windows.
|
|
62
|
+
const results = await evaluateStories(stories, events, {
|
|
63
|
+
profiles,
|
|
64
|
+
funnels: config.funnels,
|
|
65
|
+
identityMap: buildIdentityMap(profiles),
|
|
66
|
+
runSql,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
let worst = 'NAILED';
|
|
70
|
+
for (const r of results) {
|
|
71
|
+
console.log(`${r.verdict.padEnd(7)} ${r.id}`);
|
|
72
|
+
for (const a of r.assertions) console.log(` ${a.verdict.padEnd(7)} ${a.detail}`);
|
|
73
|
+
if (VERDICT_RANK[r.verdict] < VERDICT_RANK[worst]) worst = r.verdict;
|
|
74
|
+
}
|
|
75
|
+
console.log(`\nworst verdict: ${worst}`);
|
|
76
|
+
process.exit(VERDICT_RANK[worst] >= VERDICT_RANK.STRONG ? 0 : 1);
|