@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,137 @@
1
+ -- ============================================================
2
+ -- support-desk.js — v1.6.0 Hook Inspection Queries (DuckDB)
3
+ -- Score: see README — machine contract in support-desk.js `stories`
4
+ -- Run after: node scripts/verify-runner.mjs dungeons/vertical/support-desk/support-desk.js verify-support-desk
5
+ -- ============================================================
6
+ --
7
+ -- v1.6.0 derivation notes (2K reduced run vs organic counterfactual):
8
+ -- 1. This dungeon is the FLOWS + SESSIONS SHOWCASE: applySessionShape
9
+ -- retimes every user's stream into role-shaped sessions (agents ~5
10
+ -- long 45-min sessions/week, requesters ~1 short 10-min session/week
11
+ -- of ~28 events), then applyPathBias injects a one-touch resolution
12
+ -- (H1 band, 5-20s gaps — in-session) and a cross-session escalation
13
+ -- chain (H2 band, 1-4h gaps — resolved lands at session ordinal +3).
14
+ -- 2. hashFloat bands are not expressible in SQL, but `role` is pinned on
15
+ -- every event, so per-role sessionization (LAG > 30 min) is exact.
16
+ -- Session-count conversion windows and topPaths semantics live in the
17
+ -- stories export (emulator); the queries below eyeball the same
18
+ -- signals through time-gap and session aggregates.
19
+ -- 3. H2's signature in raw SQL: the session-ordinal span from a user's
20
+ -- first 'ticket created' to their first subsequent 'ticket resolved'
21
+ -- is 1 for the H1 band (one-touch) and 4 for the H2 band (the
22
+ -- injected chain's three cross-session steps) — the histogram in
23
+ -- query 2 shows both spikes; organic data ramps smoothly.
24
+ -- ============================================================
25
+
26
+
27
+ -- Hook 1: ONE-TOUCH RESOLUTION — ~45% of users get reply+resolved
28
+ -- injected 5-20s after their first ticket created.
29
+ -- Expect: a large spike of first-created → first-resolved gaps under
30
+ -- 30 seconds (2K hooked: ~0.45 of ticket creators; organic: none under
31
+ -- a minute — organic resolution flows through the F2 funnel over hours).
32
+ WITH firsts AS (
33
+ SELECT user_id,
34
+ MIN(CASE WHEN event = 'ticket created' THEN time::TIMESTAMP END) AS first_created,
35
+ MIN(CASE WHEN event = 'ticket resolved' THEN time::TIMESTAMP END) AS first_resolved
36
+ FROM read_json_auto('data/verify-support-desk-EVENTS*.json', sample_size=-1, union_by_name=true)
37
+ WHERE user_id IS NOT NULL
38
+ GROUP BY 1
39
+ )
40
+ SELECT
41
+ CASE
42
+ WHEN gap_s < 30 THEN 'a. <30s (H1 one-touch)'
43
+ WHEN gap_s < 1800 THEN 'b. 30s-30min'
44
+ WHEN gap_s < 14400 THEN 'c. 30min-4h (H2 chain)'
45
+ ELSE 'd. >4h (organic tail)'
46
+ END AS first_resolution_gap,
47
+ COUNT(*) AS users
48
+ FROM (
49
+ SELECT EXTRACT(EPOCH FROM first_resolved - first_created)::DOUBLE AS gap_s
50
+ FROM firsts
51
+ WHERE first_created IS NOT NULL AND first_resolved IS NOT NULL AND first_resolved > first_created
52
+ )
53
+ GROUP BY 1 ORDER BY 1;
54
+
55
+
56
+ -- Hook 2: CROSS-SESSION ESCALATION — the H2 band's organic resolutions
57
+ -- are dropped; the injected escalated→reply→resolved chain (1-4h gaps)
58
+ -- resolves at session ordinal +3.
59
+ -- Expect: bimodal session-ordinal span from first created to first
60
+ -- resolved — a spike at 1 (H1 one-touch, ~63% of resolvers) and a spike
61
+ -- at 4 (H2 chain, ~35%); organic data spreads across many ordinals.
62
+ WITH e AS (
63
+ SELECT user_id AS uid, event, time::TIMESTAMP AS t,
64
+ CASE WHEN LAG(time::TIMESTAMP) OVER (PARTITION BY user_id ORDER BY time::TIMESTAMP) IS NULL
65
+ OR time::TIMESTAMP - LAG(time::TIMESTAMP) OVER (PARTITION BY user_id ORDER BY time::TIMESTAMP) > INTERVAL 30 MINUTE
66
+ THEN 1 ELSE 0 END AS is_start
67
+ FROM read_json_auto('data/verify-support-desk-EVENTS*.json', sample_size=-1, union_by_name=true)
68
+ WHERE user_id IS NOT NULL
69
+ ), s AS (
70
+ SELECT uid, event, t, SUM(is_start) OVER (PARTITION BY uid ORDER BY t) AS sid FROM e
71
+ ), spans AS (
72
+ SELECT c.uid,
73
+ (SELECT MIN(r.sid) FROM s r WHERE r.uid = c.uid AND r.event = 'ticket resolved' AND r.t > c.t0)
74
+ - c.sid0 + 1 AS ordinal_span
75
+ FROM (
76
+ SELECT uid, MIN(t) AS t0, ARG_MIN(sid, t) AS sid0
77
+ FROM s WHERE event = 'ticket created' GROUP BY uid
78
+ ) c
79
+ )
80
+ SELECT ordinal_span, COUNT(*) AS users
81
+ FROM spans WHERE ordinal_span IS NOT NULL
82
+ GROUP BY 1 ORDER BY 1 LIMIT 12;
83
+
84
+
85
+ -- Hook 3a: SESSION SHAPE — duration bimodality. applySessionShape gives
86
+ -- requesters 10-min sessions and agents 45-min sessions.
87
+ -- Expect: requester median ~10.0 min, agent median ~45.0 min (both
88
+ -- near-deterministic); organic sessions are ~1.2-event singletons.
89
+ WITH e AS (
90
+ SELECT user_id AS uid, role, time::TIMESTAMP AS t,
91
+ CASE WHEN LAG(time::TIMESTAMP) OVER (PARTITION BY user_id ORDER BY time::TIMESTAMP) IS NULL
92
+ OR time::TIMESTAMP - LAG(time::TIMESTAMP) OVER (PARTITION BY user_id ORDER BY time::TIMESTAMP) > INTERVAL 30 MINUTE
93
+ THEN 1 ELSE 0 END AS is_start
94
+ FROM read_json_auto('data/verify-support-desk-EVENTS*.json', sample_size=-1, union_by_name=true)
95
+ WHERE user_id IS NOT NULL
96
+ ), s AS (
97
+ SELECT uid, role, t, SUM(is_start) OVER (PARTITION BY uid ORDER BY t) AS sid FROM e
98
+ ), sess AS (
99
+ SELECT uid, ANY_VALUE(role) AS role, sid,
100
+ COUNT(*) AS events_in_session,
101
+ EXTRACT(EPOCH FROM MAX(t) - MIN(t))::DOUBLE / 60.0 AS dur_min
102
+ FROM s GROUP BY uid, sid
103
+ )
104
+ SELECT role,
105
+ COUNT(*) AS sessions,
106
+ ROUND(MEDIAN(dur_min), 1) AS median_min,
107
+ ROUND(QUANTILE_CONT(dur_min, 0.9), 1) AS p90_min,
108
+ ROUND(MEDIAN(events_in_session), 1) AS median_events
109
+ FROM sess GROUP BY 1 ORDER BY 1;
110
+
111
+
112
+ -- Hook 3b: SESSION CADENCE — agents ~5 sessions/week, requesters ~1.
113
+ -- Expect (2K hooked): agent median ~4.6/wk, requester ~1.0/wk (ratio
114
+ -- ~4.6x); organic ~23/wk for BOTH roles (ratio ~1.0x).
115
+ WITH e AS (
116
+ SELECT user_id AS uid, role, time::TIMESTAMP AS t,
117
+ CASE WHEN LAG(time::TIMESTAMP) OVER (PARTITION BY user_id ORDER BY time::TIMESTAMP) IS NULL
118
+ OR time::TIMESTAMP - LAG(time::TIMESTAMP) OVER (PARTITION BY user_id ORDER BY time::TIMESTAMP) > INTERVAL 30 MINUTE
119
+ THEN 1 ELSE 0 END AS is_start
120
+ FROM read_json_auto('data/verify-support-desk-EVENTS*.json', sample_size=-1, union_by_name=true)
121
+ WHERE user_id IS NOT NULL
122
+ ), s AS (
123
+ SELECT uid, role, SUM(is_start) AS sessions FROM e GROUP BY uid, role
124
+ )
125
+ SELECT role,
126
+ COUNT(*) AS users,
127
+ ROUND(MEDIAN(sessions / 12.0), 2) AS median_sessions_per_week
128
+ FROM s GROUP BY 1 ORDER BY 1;
129
+
130
+
131
+ -- Identity invariants: every event carries user_id; avgDevicePerUser: 2.
132
+ -- Expect: uid_share 1.0, device_share ~0.999, devices/user ~2.05.
133
+ SELECT COUNT(*) AS n,
134
+ AVG((user_id IS NOT NULL)::INT) AS uid_share,
135
+ AVG((device_id IS NOT NULL)::INT) AS device_share,
136
+ COUNT(DISTINCT device_id)::DOUBLE / COUNT(DISTINCT user_id) AS dpu
137
+ FROM read_json_auto('data/verify-support-desk-EVENTS*.json', sample_size=-1, union_by_name=true);
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * support-desk.verify.mjs — thin wrapper around the story runner.
4
+ *
5
+ * All verification logic lives in the `stories` export of ./support-desk.js;
6
+ * this script just streams the shards and delegates. It is equivalent to:
7
+ *
8
+ * node scripts/verify-stories.mjs dungeons/vertical/support-desk/support-desk.js --data-prefix verify-support-desk
9
+ *
10
+ * Generate first:
11
+ * node scripts/verify-runner.mjs dungeons/vertical/support-desk/support-desk.js verify-support-desk
12
+ * Run:
13
+ * node dungeons/vertical/support-desk/support-desk.verify.mjs [prefix] # default verify-support-desk
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 './support-desk.js';
22
+
23
+ const PREFIX = process.argv[2] || 'verify-support-desk';
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/support-desk/support-desk.js ${PREFIX}`);
44
+ process.exit(1);
45
+ }
46
+ console.log(`support-desk — 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
+ // Stories are emulator-backed where Mixpanel-equivalent semantics matter
55
+ // (topPaths for the engineered path shares, funnelFrequency with session
56
+ // conversion windows for the 1-vs-4 session contrast, sessionMetrics and
57
+ // uniques-sessions for the role cadence split). The per-role session-cadence
58
+ // cross-check and the identity invariants are DuckDB-backed: `role` is
59
+ // pinned on every event by the hook, so SQL can sessionize per user (LAG
60
+ // > 30 min) and split by role without needing hashFloat.
61
+ const results = await evaluateStories(stories, events, {
62
+ profiles,
63
+ funnels: config.funnels,
64
+ identityMap: buildIdentityMap(profiles),
65
+ runSql,
66
+ });
67
+
68
+ let worst = 'NAILED';
69
+ for (const r of results) {
70
+ console.log(`${r.verdict.padEnd(7)} ${r.id}`);
71
+ for (const a of r.assertions) console.log(` ${a.verdict.padEnd(7)} ${a.detail}`);
72
+ if (VERDICT_RANK[r.verdict] < VERDICT_RANK[worst]) worst = r.verdict;
73
+ }
74
+ console.log(`\nworst verdict: ${worst}`);
75
+ process.exit(VERDICT_RANK[worst] >= VERDICT_RANK.STRONG ? 0 : 1);