@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,89 @@
1
+ /**
2
+ * "First time ever" event filter.
3
+ *
4
+ * In Mixpanel this is a two-query API rewrite, not an engine primitive
5
+ * (`backend/util/behaviors/event_selector.py:59-149`;
6
+ * `backend/util/arb_selector.py` `extract_nth_time_filter` :1874-1936):
7
+ *
8
+ * 1. PRE-filters define the event universe the "first" is picked from —
9
+ * a per-user `first_event_time` aggregation runs over
10
+ * (event + pre_filters) (event_selector.py:125-142,
11
+ * `aggregation_operator: "first_event_time"`).
12
+ * 2. The query is then rewritten to
13
+ * `properties["$time"] == <first_event_time>` AND'ed with the
14
+ * POST-filters (event_selector.py:126, :143-145). Post-filters test
15
+ * only the picked event; if the user's first-ever match fails them,
16
+ * the user contributes nothing — NOT their first post-matching event
17
+ * (event_selector.py:59-63: "we want to find the first time a user
18
+ * viewed an insights report and then if that event happens to be
19
+ * from a chrome browser").
20
+ *
21
+ * Filter ORDER in the UI decides pre vs post: filters listed before the
22
+ * nth-time marker are pre, after are post (arb_selector.py:1935-1936).
23
+ *
24
+ * Faithful consequence of the rewrite: the second query scans events by
25
+ * name + timestamp only — pre-filters are NOT re-applied. If two
26
+ * same-name events share the user's first-event timestamp exactly, BOTH
27
+ * pass (even one that fails the pre-filter).
28
+ *
29
+ * "Ever" here = the full dataset. Mixpanel bounds the lookback at ~5
30
+ * years (`date_range.py:744-781` via `get_first_time_filter_date_range`),
31
+ * which always covers a generated dungeon's window.
32
+ *
33
+ * This is the `$nth_time_event` machinery that powers Lifecycle's "New"
34
+ * class (see P1.8).
35
+ */
36
+
37
+ import { toMs } from '../hook-helpers/_internal.js';
38
+ import { resolveUserId } from './identity.js';
39
+ import { matchesWhere } from './coerce.js';
40
+
41
+ /**
42
+ * Return the subset of `events` that are each user's FIRST-EVER match of
43
+ * (event + preWhere), post-filtered by `postWhere`. Input order preserved.
44
+ *
45
+ * @param {Object[]} events
46
+ * @param {Object} options
47
+ * @param {string} [options.event] - Event name; omit to match any event name.
48
+ * @param {Object<string, *>} [options.preWhere] - Defines the universe the
49
+ * first is picked from (`{ prop: value | { op, value } }`, see coerce.js).
50
+ * @param {Object<string, *>} [options.postWhere] - Tests only the picked
51
+ * event(s).
52
+ * @param {Map<string, string>} [options.identityMap] - device_id → canonical
53
+ * user id map (see identity.js).
54
+ * @returns {Object[]}
55
+ */
56
+ export function filterFirstTimeEver(events, options = {}) {
57
+ if (!Array.isArray(events)) throw new Error('filterFirstTimeEver: events must be an array');
58
+ const { event, preWhere, postWhere, identityMap } = options;
59
+
60
+ // Pass 1 — per-user first_event_time over (event + preWhere).
61
+ const firstTimeByUser = new Map(); // uid → ms
62
+ for (const e of events) {
63
+ if (!e || (event && e.event !== event)) continue;
64
+ if (!matchesWhere(e, preWhere)) continue;
65
+ const uid = resolveUserId(e, identityMap);
66
+ if (!uid) continue;
67
+ const ms = toMs(e.time);
68
+ if (!Number.isFinite(ms)) continue;
69
+ const prev = firstTimeByUser.get(uid);
70
+ if (prev === undefined || ms < prev) firstTimeByUser.set(uid, ms);
71
+ }
72
+ if (!firstTimeByUser.size) return [];
73
+
74
+ // Pass 2 — the rewritten query: event name + $time == first_event_time
75
+ // + postWhere. preWhere intentionally NOT re-applied (see header).
76
+ const out = [];
77
+ for (const e of events) {
78
+ if (!e || (event && e.event !== event)) continue;
79
+ const uid = resolveUserId(e, identityMap);
80
+ if (!uid) continue;
81
+ const first = firstTimeByUser.get(uid);
82
+ if (first === undefined) continue;
83
+ const ms = toMs(e.time);
84
+ if (ms !== first) continue;
85
+ if (!matchesWhere(e, postWhere)) continue;
86
+ out.push(e);
87
+ }
88
+ return out;
89
+ }