@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,148 @@
|
|
|
1
|
+
-- ============================================================
|
|
2
|
+
-- marketplace.sql — human-eyeball inspection queries (v1.6)
|
|
3
|
+
-- ============================================================
|
|
4
|
+
-- Mirrors the story reads in marketplace.js `stories`. Run after:
|
|
5
|
+
-- node scripts/verify-runner.mjs dungeons/vertical/marketplace/marketplace.js verify-marketplace
|
|
6
|
+
--
|
|
7
|
+
-- Derivation notes:
|
|
8
|
+
-- - H1 uses the hook's exact cutoff (time > 2026-02-15T00:00:00Z), not
|
|
9
|
+
-- date_diff day bucketing — day-45 events straddle the boundary.
|
|
10
|
+
-- - H3/H8 read purchases-per-add-to-cart ratio-of-ratios: normalizing by
|
|
11
|
+
-- carts cancels the persona activity gap (power 5.0x vs casual 1.5x
|
|
12
|
+
-- eventMultiplier); the residual is the engineered clone lift (H3
|
|
13
|
+
-- x1.65) or retention edge (H8 4/3).
|
|
14
|
+
-- - H4's clean read is per-user purchases: raw category counts carry the
|
|
15
|
+
-- engine's seeded favored-index popularity skew.
|
|
16
|
+
-- - H5's fast/slow split replicates the hook's charCode hash:
|
|
17
|
+
-- (ascii(first char) + ascii(last char)) % 5 < 2.
|
|
18
|
+
-- - H9 is NOT visible in cross-event SQL — funnel-post scales gaps within
|
|
19
|
+
-- one Browse-to-Purchase instance per user, invisible to MIN→MIN
|
|
20
|
+
-- queries over the full event history. See the emulator timeToConvert
|
|
21
|
+
-- assertion in marketplace.js stories (48-HOUR window, matching the
|
|
22
|
+
-- funnel's timeToConvert; longer windows admit cross-instance chains).
|
|
23
|
+
-- - H10's cohort is total message-sent count — exactly reproducible from
|
|
24
|
+
-- output because H10 runs last and nothing after it drops messages.
|
|
25
|
+
|
|
26
|
+
-- Hook 1: FEE CHANGE IMPACT — avg listing_fee steps ~x1.26 after day 45
|
|
27
|
+
-- (x1.3 knob, Math.floor shaves ~4%)
|
|
28
|
+
SELECT (time::TIMESTAMP > TIMESTAMP '2026-02-15 00:00:00') AS post_d45,
|
|
29
|
+
COUNT(*) AS n, ROUND(AVG(listing_fee), 2) AS avg_fee
|
|
30
|
+
FROM read_json_auto('data/verify-marketplace-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
31
|
+
WHERE event = 'listing created' AND listing_fee IS NOT NULL
|
|
32
|
+
GROUP BY 1 ORDER BY post_d45;
|
|
33
|
+
|
|
34
|
+
-- Hook 2: WEEKEND SHOPPING SURGE — Sat/Sun avg total_amount ~1.21x weekday
|
|
35
|
+
SELECT
|
|
36
|
+
CASE WHEN dayofweek(time::TIMESTAMP) IN (0, 6) THEN 'weekend' ELSE 'weekday' END AS bucket,
|
|
37
|
+
COUNT(*) AS n, ROUND(AVG(total_amount), 1) AS avg_amount
|
|
38
|
+
FROM read_json_auto('data/verify-marketplace-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
39
|
+
WHERE event = 'purchase completed' AND total_amount IS NOT NULL
|
|
40
|
+
GROUP BY (CASE WHEN dayofweek(time::TIMESTAMP) IN (0, 6) THEN 'weekend' ELSE 'weekday' END);
|
|
41
|
+
|
|
42
|
+
-- Hook 3 + Hook 8: purchases-per-cart by segment. Power/casual RoR ~1.67
|
|
43
|
+
-- (H3 x1.65 clones); frequent/casual RoR ~1.33 (H8 4/3 retention edge).
|
|
44
|
+
-- Raw purchases/user power/casual ~6x = clone lift x activity gap.
|
|
45
|
+
WITH pu AS (
|
|
46
|
+
SELECT e.user_id::VARCHAR AS uid, ANY_VALUE(u.segment) AS segment,
|
|
47
|
+
COUNT(*) FILTER (WHERE e.event = 'purchase completed') AS purch,
|
|
48
|
+
COUNT(*) FILTER (WHERE e.event = 'add to cart') AS carts
|
|
49
|
+
FROM read_json_auto('data/verify-marketplace-EVENTS*.json', sample_size=-1, union_by_name=true) e
|
|
50
|
+
JOIN read_json_auto('data/verify-marketplace-USERS*.json', sample_size=-1, union_by_name=true) u
|
|
51
|
+
ON e.user_id::VARCHAR = u.distinct_id::VARCHAR
|
|
52
|
+
GROUP BY 1
|
|
53
|
+
)
|
|
54
|
+
SELECT segment, COUNT(*) AS users,
|
|
55
|
+
ROUND(AVG(purch), 3) AS purch_pu,
|
|
56
|
+
ROUND(SUM(purch)::DOUBLE / NULLIF(SUM(carts), 0), 4) AS purch_per_cart
|
|
57
|
+
FROM pu GROUP BY segment ORDER BY purch_per_cart DESC;
|
|
58
|
+
|
|
59
|
+
-- Hook 4: ELECTRONICS CATEGORY LIFT — elec-searchers ~1.22x purchases/user
|
|
60
|
+
WITH pu AS (
|
|
61
|
+
SELECT user_id::VARCHAR AS uid,
|
|
62
|
+
BOOL_OR(event = 'item searched' AND category = 'electronics') AS elec,
|
|
63
|
+
COUNT(*) FILTER (WHERE event = 'purchase completed') AS purch
|
|
64
|
+
FROM read_json_auto('data/verify-marketplace-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
65
|
+
GROUP BY 1
|
|
66
|
+
)
|
|
67
|
+
SELECT elec, COUNT(*) AS users, ROUND(AVG(purch), 3) AS purch_pu
|
|
68
|
+
FROM pu GROUP BY elec ORDER BY elec;
|
|
69
|
+
|
|
70
|
+
-- Hook 5: RESPONSE TIME → CONVERSION — fast hash cohort ~10x accepts/user;
|
|
71
|
+
-- avg response_time_hours ~2.25h vs ~22h on message sent
|
|
72
|
+
WITH pu AS (
|
|
73
|
+
SELECT user_id::VARCHAR AS uid,
|
|
74
|
+
(ascii(substr(user_id::VARCHAR, 1, 1)) + ascii(substr(user_id::VARCHAR, -1, 1))) % 5 < 2 AS is_fast,
|
|
75
|
+
BOOL_OR(event = 'message sent') AS has_msg,
|
|
76
|
+
COUNT(*) FILTER (WHERE event = 'offer accepted') AS accepts
|
|
77
|
+
FROM read_json_auto('data/verify-marketplace-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
78
|
+
GROUP BY 1
|
|
79
|
+
)
|
|
80
|
+
SELECT is_fast, COUNT(*) AS users, ROUND(AVG(accepts), 3) AS accepts_pu
|
|
81
|
+
FROM pu WHERE has_msg GROUP BY is_fast ORDER BY is_fast;
|
|
82
|
+
|
|
83
|
+
SELECT
|
|
84
|
+
(ascii(substr(user_id::VARCHAR, 1, 1)) + ascii(substr(user_id::VARCHAR, -1, 1))) % 5 < 2 AS is_fast,
|
|
85
|
+
COUNT(*) AS n, ROUND(AVG(response_time_hours), 2) AS avg_rt
|
|
86
|
+
FROM read_json_auto('data/verify-marketplace-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
87
|
+
WHERE event = 'message sent' AND response_time_hours IS NOT NULL
|
|
88
|
+
GROUP BY 1 ORDER BY is_fast;
|
|
89
|
+
|
|
90
|
+
-- Hook 6: NEW SELLER CHURN — new_seller post/pre(+14d) event ratio ~0.5x rest
|
|
91
|
+
-- (50% drop stacked on the persona's 28-day activeWindow)
|
|
92
|
+
WITH pu AS (
|
|
93
|
+
SELECT e.user_id::VARCHAR AS uid, ANY_VALUE(u.segment) AS segment, MIN(e.time::TIMESTAMP) AS t0
|
|
94
|
+
FROM read_json_auto('data/verify-marketplace-EVENTS*.json', sample_size=-1, union_by_name=true) e
|
|
95
|
+
JOIN read_json_auto('data/verify-marketplace-USERS*.json', sample_size=-1, union_by_name=true) u
|
|
96
|
+
ON e.user_id::VARCHAR = u.distinct_id::VARCHAR
|
|
97
|
+
GROUP BY 1
|
|
98
|
+
), flags AS (
|
|
99
|
+
SELECT p.uid, p.segment,
|
|
100
|
+
COUNT(*) FILTER (WHERE e.time::TIMESTAMP <= p.t0 + INTERVAL 14 DAY) AS pre,
|
|
101
|
+
COUNT(*) FILTER (WHERE e.time::TIMESTAMP > p.t0 + INTERVAL 14 DAY) AS post
|
|
102
|
+
FROM read_json_auto('data/verify-marketplace-EVENTS*.json', sample_size=-1, union_by_name=true) e
|
|
103
|
+
JOIN pu p ON e.user_id::VARCHAR = p.uid
|
|
104
|
+
GROUP BY 1, 2
|
|
105
|
+
)
|
|
106
|
+
SELECT (segment = 'new_seller') AS is_new_seller, COUNT(*) AS users,
|
|
107
|
+
ROUND(AVG(post::DOUBLE / NULLIF(pre, 0)), 4) AS avg_post_pre
|
|
108
|
+
FROM flags WHERE pre > 0
|
|
109
|
+
GROUP BY (segment = 'new_seller') ORDER BY is_new_seller;
|
|
110
|
+
|
|
111
|
+
-- Hook 7: POWER SELLER PROFILES — tx means 300 / 27.5 / 1.5; ratings
|
|
112
|
+
-- 4.75 / 3.75 / 0; buyers keep the [0] default tx
|
|
113
|
+
SELECT segment, COUNT(*) AS users,
|
|
114
|
+
ROUND(AVG(total_transactions), 1) AS avg_tx,
|
|
115
|
+
ROUND(AVG(seller_rating), 2) AS avg_rating
|
|
116
|
+
FROM read_json_auto('data/verify-marketplace-USERS*.json', sample_size=-1, union_by_name=true)
|
|
117
|
+
GROUP BY segment ORDER BY avg_tx DESC;
|
|
118
|
+
|
|
119
|
+
-- Hook 9: BROWSE-TO-PURCHASE TTC — not visible here; emulator-only read.
|
|
120
|
+
-- See derivation notes above and the timeToConvert assertion in
|
|
121
|
+
-- marketplace.js stories (48h window: frequent/casual ~0.61,
|
|
122
|
+
-- window/casual ~1.12, power/casual ~0.64).
|
|
123
|
+
|
|
124
|
+
-- Hook 10: MESSAGE-COUNT MAGIC NUMBER — per-user avg offer_amount by
|
|
125
|
+
-- total-message cohort: sweet(2-5)/low(0-1) ~1.30x, over(6+)/low ~0.83x
|
|
126
|
+
WITH pu AS (
|
|
127
|
+
SELECT user_id::VARCHAR AS uid,
|
|
128
|
+
COUNT(*) FILTER (WHERE event = 'message sent') AS msgs,
|
|
129
|
+
AVG(offer_amount) FILTER (WHERE event = 'offer received') AS avg_offer
|
|
130
|
+
FROM read_json_auto('data/verify-marketplace-EVENTS*.json', sample_size=-1, union_by_name=true)
|
|
131
|
+
GROUP BY 1
|
|
132
|
+
)
|
|
133
|
+
SELECT
|
|
134
|
+
CASE WHEN msgs BETWEEN 2 AND 5 THEN 'sweet' WHEN msgs >= 6 THEN 'over' ELSE 'low' END AS bucket,
|
|
135
|
+
COUNT(*) FILTER (WHERE avg_offer IS NOT NULL) AS users,
|
|
136
|
+
ROUND(AVG(avg_offer), 1) AS avg_offer
|
|
137
|
+
FROM pu
|
|
138
|
+
GROUP BY (CASE WHEN msgs BETWEEN 2 AND 5 THEN 'sweet' WHEN msgs >= 6 THEN 'over' ELSE 'low' END)
|
|
139
|
+
ORDER BY bucket;
|
|
140
|
+
|
|
141
|
+
-- Identity invariants: uid coverage 1.0 (auth event is Buyer Onboarding
|
|
142
|
+
-- step 1 — no device-only prefix), device coverage ~0.999, devices/user
|
|
143
|
+
-- ~2.06 (avgDevicePerUser: 2)
|
|
144
|
+
SELECT
|
|
145
|
+
ROUND(AVG((user_id IS NOT NULL)::INT), 4) AS uid_share,
|
|
146
|
+
ROUND(AVG((device_id IS NOT NULL)::INT), 4) AS device_share,
|
|
147
|
+
ROUND(COUNT(DISTINCT device_id)::DOUBLE / COUNT(DISTINCT user_id), 2) AS devices_per_user
|
|
148
|
+
FROM read_json_auto('data/verify-marketplace-EVENTS*.json', sample_size=-1, union_by_name=true);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* marketplace.verify.mjs — thin wrapper around the story runner.
|
|
4
|
+
*
|
|
5
|
+
* All verification logic lives in the `stories` export of
|
|
6
|
+
* ./marketplace.js; this script just streams the shards and
|
|
7
|
+
* delegates. It is equivalent to:
|
|
8
|
+
*
|
|
9
|
+
* node scripts/verify-stories.mjs dungeons/vertical/marketplace/marketplace.js --data-prefix verify-marketplace
|
|
10
|
+
*
|
|
11
|
+
* Generate first:
|
|
12
|
+
* node scripts/verify-runner.mjs dungeons/vertical/marketplace/marketplace.js verify-marketplace
|
|
13
|
+
* Run:
|
|
14
|
+
* node dungeons/vertical/marketplace/marketplace.verify.mjs [prefix] # default verify-marketplace
|
|
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 './marketplace.js';
|
|
23
|
+
|
|
24
|
+
const PREFIX = process.argv[2] || 'verify-marketplace';
|
|
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/marketplace/marketplace.js ${PREFIX}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
console.log(`marketplace — 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 + purchases-per-cart
|
|
56
|
+
// ratio-of-ratios reads that cancel persona activity confounds); H9
|
|
57
|
+
// dispatches to emulateBreakdown's timeToConvert at a 48-HOUR window —
|
|
58
|
+
// funnel-post gap scaling is invisible to cross-event SQL, and longer
|
|
59
|
+
// windows admit cross-instance chains that contaminate the fast tail.
|
|
60
|
+
const results = await evaluateStories(stories, events, {
|
|
61
|
+
profiles,
|
|
62
|
+
funnels: config.funnels,
|
|
63
|
+
identityMap: buildIdentityMap(profiles),
|
|
64
|
+
runSql,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
let worst = 'NAILED';
|
|
68
|
+
for (const r of results) {
|
|
69
|
+
console.log(`${r.verdict.padEnd(7)} ${r.id}`);
|
|
70
|
+
for (const a of r.assertions) console.log(` ${a.verdict.padEnd(7)} ${a.detail}`);
|
|
71
|
+
if (VERDICT_RANK[r.verdict] < VERDICT_RANK[worst]) worst = r.verdict;
|
|
72
|
+
}
|
|
73
|
+
console.log(`\nworst verdict: ${worst}`);
|
|
74
|
+
process.exit(VERDICT_RANK[worst] >= VERDICT_RANK.STRONG ? 0 : 1);
|