@ak--47/dungeon-master 1.5.4 → 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 +1 -1
  4. package/.claude/skills/create-project/provision.mjs +57 -10
  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 +183 -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 +2 -1
  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,184 @@
1
+ -- ============================================================
2
+ -- ai-platform.js — v1.6 human-inspection queries (DuckDB)
3
+ --
4
+ -- Every query is keyed to a story id in ai-platform.js's `stories` export;
5
+ -- the machine-checked verdicts come from:
6
+ -- node scripts/verify-stories.mjs dungeons/vertical/ai-platform/ai-platform.js --data-prefix verify-ai-platform
7
+ -- Generate first:
8
+ -- node scripts/verify-runner.mjs dungeons/vertical/ai-platform/ai-platform.js verify-ai-platform
9
+ -- Run this file:
10
+ -- duckdb -c ".read dungeons/vertical/ai-platform/ai-platform.sql"
11
+ -- ============================================================
12
+
13
+ -- ── identity-resolution prelude ─────────────────────────────
14
+ -- avgDevicePerUser: 2 + organization created is both isAuthEvent and
15
+ -- isFirstEvent, so born users auth on their first event; the device-pool
16
+ -- resolve is belt-and-braces for any device-only edge.
17
+ CREATE OR REPLACE VIEW users AS
18
+ SELECT * FROM read_json_auto('data/verify-ai-platform-USERS*.json', sample_size=-1, union_by_name=true);
19
+
20
+ CREATE OR REPLACE VIEW device_map AS
21
+ -- profiles store the device pool under the legacy "anonymousIds" key
22
+ SELECT unnest("anonymousIds") AS device_id, distinct_id FROM users;
23
+
24
+ CREATE OR REPLACE VIEW ev AS
25
+ -- ::VARCHAR casts — user_id sniffs as UUID, device_id as VARCHAR; DuckDB
26
+ -- refuses to coalesce mixed types
27
+ SELECT coalesce(m.distinct_id::VARCHAR, e.user_id::VARCHAR, e.device_id::VARCHAR) AS uid,
28
+ e.time::TIMESTAMP AS t,
29
+ e.*
30
+ FROM read_json_auto('data/verify-ai-platform-EVENTS*.json', sample_size=-1, union_by_name=true) e
31
+ LEFT JOIN device_map m ON e.device_id = m.device_id;
32
+
33
+ -- H3/H7 four-cell cohort: agentic = 3+ tool use calls AND 3+ multi-turn
34
+ -- api calls; batch = any batch job submitted. Filters (H4/H8) run before
35
+ -- the cohort mutators, so output-side classification reproduces the
36
+ -- hook's cohorts 1:1.
37
+ CREATE OR REPLACE VIEW cells AS
38
+ WITH coh AS (SELECT e.uid,
39
+ (count(*) FILTER (WHERE e.event = 'tool use call') >= 3
40
+ AND count(*) FILTER (WHERE e.event = 'api call' AND e.multi_turn = true) >= 3) AS agentic,
41
+ bool_or(e.event = 'batch job submitted') AS batch
42
+ FROM ev e GROUP BY 1)
43
+ SELECT uid, CASE WHEN agentic AND batch THEN 'both' WHEN agentic THEN 'agentic'
44
+ WHEN batch THEN 'batch' ELSE 'neither' END AS cell FROM coh;
45
+
46
+
47
+ -- ── H1-prompt-caching ───────────────────────────────────────
48
+ -- ~25% of users flip cache_enabled=true ~30% into their stream; cached
49
+ -- api calls carry cost_usd × 0.3.
50
+ SELECT CASE WHEN cache_enabled = true THEN 'cached' ELSE 'uncached' END AS grp,
51
+ count(*) AS calls, round(avg(cost_usd), 4) AS avg_cost
52
+ FROM ev WHERE event = 'api call' GROUP BY 1 ORDER BY 1;
53
+
54
+
55
+ -- ── H2-model-migration ──────────────────────────────────────
56
+ -- opus-4-7 releases day 60 (2026-03-02): 35% of post-release
57
+ -- Build/Enterprise api calls migrate at 1.5x tokens. Purity is exact —
58
+ -- the hook scrubs engine-sampled opus-4-7 back to the pre-release mix.
59
+ SELECT
60
+ count(*) FILTER (WHERE model = 'opus-4-7' AND (t < TIMESTAMP '2026-03-02' OR api_tier = 'Free' OR event <> 'api call')) AS impure_rows,
61
+ count(*) FILTER (WHERE model = 'opus-4-7') AS opus_calls,
62
+ round(count(*) FILTER (WHERE model = 'opus-4-7' AND event = 'api call' AND api_tier IN ('Build', 'Enterprise') AND t >= TIMESTAMP '2026-03-02')::DOUBLE
63
+ / nullif(count(*) FILTER (WHERE event = 'api call' AND api_tier IN ('Build', 'Enterprise') AND t >= TIMESTAMP '2026-03-02'), 0), 4) AS post_paid_share
64
+ FROM ev WHERE model IS NOT NULL;
65
+
66
+ -- tokens 1.5x on neither-cell users (H3's 8x / H7's 2x excluded by cell)
67
+ SELECT CASE WHEN e.model = 'opus-4-7' THEN 'opus' ELSE 'other' END AS grp,
68
+ count(*) AS calls, round(avg(e.tokens_used), 0) AS avg_tokens
69
+ FROM ev e JOIN cells c ON c.uid = e.uid AND c.cell = 'neither'
70
+ WHERE e.event = 'api call' AND e.api_tier IN ('Build', 'Enterprise')
71
+ AND e.t >= TIMESTAMP '2026-03-02'
72
+ GROUP BY 1 ORDER BY 1;
73
+
74
+
75
+ -- ── H3-agentic-power-users / H7-batch-discount ──────────────
76
+ -- Four-cell token design: neither 1x / agentic 8x / batch 2x / both 16x.
77
+ -- Agentic clones stamp multi_turn=true → agentic mt_share ≈ 0.75
78
+ -- ((0.25n + 2n) / 3n against the declared 1-in-4 mix).
79
+ SELECT c.cell, count(DISTINCT e.uid) AS users, count(*) AS calls,
80
+ round(avg(e.tokens_used), 0) AS avg_tokens,
81
+ round(count(*) FILTER (WHERE e.multi_turn = true)::DOUBLE / count(*), 4) AS mt_share
82
+ FROM cells c JOIN ev e ON e.uid = c.uid
83
+ WHERE e.event = 'api call' GROUP BY 1 ORDER BY avg_tokens;
84
+
85
+ -- H7: cost_per_token × 0.5 for batch users (no other hook touches it)
86
+ SELECT CASE WHEN c.cell IN ('batch', 'both') THEN 'batch' ELSE 'rest' END AS grp,
87
+ count(*) AS calls, round(avg(e.cost_per_token), 6) AS avg_cpt
88
+ FROM cells c JOIN ev e ON e.uid = c.uid
89
+ WHERE e.event = 'api call' GROUP BY 1 ORDER BY 1;
90
+
91
+
92
+ -- ── H4-rate-limit-churn ─────────────────────────────────────
93
+ -- 60% of users with 2+ rate limit errors in week 1 lose ALL post-week-1
94
+ -- events (per-user cliff). Signal: zero-post-week-1 share, flagged vs
95
+ -- rest — the DIFFERENCE cancels the natural-quiet baseline. Eligibility:
96
+ -- t0 ≤ datasetEnd − 21d (≥14d of post-week-1 runway).
97
+ WITH t0 AS (SELECT uid, min(t) AS t0 FROM ev GROUP BY 1),
98
+ rl AS (SELECT e.uid FROM ev e JOIN t0 ON t0.uid = e.uid
99
+ WHERE e.event = 'rate limit error' AND e.t < t0.t0 + INTERVAL 7 DAY
100
+ GROUP BY 1 HAVING count(*) >= 2),
101
+ per AS (SELECT t0.uid, (t0.uid IN (SELECT uid FROM rl)) AS flagged,
102
+ count(*) FILTER (WHERE e.t > t0.t0 + INTERVAL 7 DAY) AS post_ct
103
+ FROM t0 JOIN ev e ON e.uid = t0.uid
104
+ WHERE t0.t0 <= TIMESTAMP '2026-04-10 23:59:59' GROUP BY 1, 2)
105
+ SELECT CASE WHEN flagged THEN 'flagged' ELSE 'rest' END AS grp,
106
+ count(*) AS users,
107
+ round(count(*) FILTER (WHERE post_ct = 0)::DOUBLE / count(*), 4) AS zero_post_share
108
+ FROM per GROUP BY 1 ORDER BY 1;
109
+
110
+
111
+ -- ── H5-tier-context-window ──────────────────────────────────
112
+ -- input_tokens × 1/2/4 by tier; context_window pinned to the tier
113
+ -- constant (200K / 1M / 2M) on every api call — min = max proves it.
114
+ SELECT api_tier, count(*) AS calls,
115
+ round(avg(input_tokens), 0) AS avg_input,
116
+ min(context_window) AS min_cw, max(context_window) AS max_cw
117
+ FROM ev WHERE event = 'api call' GROUP BY 1 ORDER BY avg_input;
118
+
119
+
120
+ -- ── H6-outage-day ───────────────────────────────────────────
121
+ -- days 40-41 (2026-02-10 → 2026-02-12): 40% of api calls flagged
122
+ -- is_error + 3x latency. is_error declares [false] → zero errors outside
123
+ -- the window by schema.
124
+ SELECT
125
+ CASE WHEN t >= TIMESTAMP '2026-02-10' AND t < TIMESTAMP '2026-02-12' THEN 'outage' ELSE 'normal' END AS bucket,
126
+ count(*) AS calls,
127
+ round(count(*) FILTER (WHERE is_error = true)::DOUBLE / count(*), 4) AS error_share,
128
+ round(avg(latency_ms), 0) AS avg_latency
129
+ FROM ev WHERE event = 'api call' GROUP BY 1 ORDER BY 1;
130
+
131
+
132
+ -- ── H8-eval-retention ───────────────────────────────────────
133
+ -- users without an eval job in week 1 keep 25% of post-day-30 events.
134
+ -- Ratio-of-ratios (noneval post/pre vs eval post/pre) cancels window
135
+ -- lengths and the growth soup.
136
+ WITH t0 AS (SELECT uid, min(t) AS t0 FROM ev GROUP BY 1),
137
+ ev_users AS (SELECT e.uid FROM ev e JOIN t0 ON t0.uid = e.uid
138
+ WHERE e.event = 'eval job' AND e.t < t0.t0 + INTERVAL 7 DAY GROUP BY 1),
139
+ per AS (SELECT t0.uid, (t0.uid IN (SELECT uid FROM ev_users)) AS eval_user,
140
+ count(*) FILTER (WHERE e.t <= t0.t0 + INTERVAL 30 DAY) AS pre_ct,
141
+ count(*) FILTER (WHERE e.t > t0.t0 + INTERVAL 30 DAY) AS post_ct
142
+ FROM t0 JOIN ev e ON e.uid = t0.uid GROUP BY 1, 2)
143
+ SELECT CASE WHEN eval_user THEN 'eval' ELSE 'noneval' END AS grp,
144
+ count(*) AS users, round(avg(pre_ct), 1) AS avg_pre, round(avg(post_ct), 1) AS avg_post,
145
+ round(avg(post_ct) / nullif(avg(pre_ct), 0), 4) AS post_pre
146
+ FROM per GROUP BY 1 ORDER BY 1;
147
+
148
+
149
+ -- ── H9-api-to-eval-ttc ──────────────────────────────────────
150
+ -- funnel-post scales API-to-Eval step gaps: Enterprise × 0.5, Free × 2.0.
151
+ -- CAUTION: this nearest-preceding-pair SQL is shown for inspection only —
152
+ -- it is censored by its lookback window (stretched Free pairs fall out and
153
+ -- organic events intercept), which can INVERT the direction. The story
154
+ -- asserts through the Mixpanel-aligned funnel emulator at a 336h window
155
+ -- (= 2.0 × the funnel's 168h generative window, covering the stretched
156
+ -- support); trust the story verdict, not this query's ratio.
157
+ WITH ej AS (SELECT uid, t, api_tier FROM ev WHERE event = 'eval job'),
158
+ gap AS (SELECT ej.uid, ej.api_tier, epoch(ej.t - max(tc.t)) / 3600.0 AS gap_h
159
+ FROM ej JOIN ev tc ON tc.uid = ej.uid AND tc.event = 'tool use call'
160
+ AND tc.t < ej.t AND tc.t >= ej.t - INTERVAL 336 HOUR
161
+ GROUP BY ej.uid, ej.api_tier, ej.t)
162
+ SELECT api_tier, count(*) AS pairs, count(DISTINCT uid) AS users,
163
+ round(median(gap_h), 1) AS med_gap_h
164
+ FROM gap GROUP BY 1 ORDER BY med_gap_h;
165
+
166
+
167
+ -- ── H10-docs-magic-number ───────────────────────────────────
168
+ -- docs searched strictly between earliest org-created and earliest
169
+ -- billing payment: 1-2 (sweet) → amount_usd × 1.35 on ALL billing
170
+ -- payments; 3+ (over) → × 0.75. Both branches amount-only (iid draw,
171
+ -- selection-free); median ratios vs the untouched zero-docs cohort read
172
+ -- the knobs directly.
173
+ WITH org AS (SELECT uid, min(t) AS org_t FROM ev WHERE event = 'organization created' GROUP BY 1),
174
+ bill AS (SELECT uid, min(t) AS bill_t FROM ev WHERE event = 'billing payment' GROUP BY 1),
175
+ docs AS (SELECT o.uid, count(e.uid) AS docs_ct
176
+ FROM org o JOIN bill b ON b.uid = o.uid
177
+ LEFT JOIN ev e ON e.uid = o.uid AND e.event = 'docs searched' AND e.t > o.org_t AND e.t < b.bill_t
178
+ GROUP BY 1, o.org_t, b.bill_t),
179
+ dcoh AS (SELECT uid, CASE WHEN docs_ct BETWEEN 1 AND 2 THEN 'sweet'
180
+ WHEN docs_ct >= 3 THEN 'over' ELSE 'zero' END AS grp FROM docs)
181
+ SELECT d.grp, count(DISTINCT d.uid) AS users, count(*) AS payments,
182
+ round(median(e.amount_usd), 0) AS med_amount
183
+ FROM dcoh d JOIN ev e ON e.uid = d.uid AND e.event = 'billing payment'
184
+ GROUP BY 1 ORDER BY med_amount;
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ai-platform.verify.mjs — thin wrapper around the story runner.
4
+ *
5
+ * All verification logic lives in the `stories` export of ./ai-platform.js;
6
+ * this script just streams the shards and delegates. It is equivalent to:
7
+ *
8
+ * node scripts/verify-stories.mjs dungeons/vertical/ai-platform/ai-platform.js --data-prefix verify-ai-platform
9
+ *
10
+ * Generate first:
11
+ * node scripts/verify-runner.mjs dungeons/vertical/ai-platform/ai-platform.js verify-ai-platform
12
+ * Run:
13
+ * node dungeons/vertical/ai-platform/ai-platform.verify.mjs [prefix] # default verify-ai-platform
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 './ai-platform.js';
22
+
23
+ const PREFIX = process.argv[2] || 'verify-ai-platform';
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/ai-platform/ai-platform.js ${PREFIX}`);
44
+ process.exit(1);
45
+ }
46
+ console.log(`ai-platform — 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
+ // funnels passed raw (unvalidated) — the H9 story carries its own explicit
55
+ // 336-hour conversion window (2x the generative window, covering the
56
+ // stretched support), so no funnel-default threading is needed
57
+ const results = await evaluateStories(stories, events, {
58
+ profiles,
59
+ funnels: config.funnels,
60
+ identityMap: buildIdentityMap(profiles),
61
+ runSql,
62
+ });
63
+
64
+ let worst = 'NAILED';
65
+ for (const r of results) {
66
+ console.log(`${r.verdict.padEnd(7)} ${r.id}`);
67
+ for (const a of r.assertions) console.log(` ${a.verdict.padEnd(7)} ${a.detail}`);
68
+ if (VERDICT_RANK[r.verdict] < VERDICT_RANK[worst]) worst = r.verdict;
69
+ }
70
+ console.log(`\nworst verdict: ${worst}`);
71
+ process.exit(VERDICT_RANK[worst] >= VERDICT_RANK.STRONG ? 0 : 1);