@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,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
|
+
}
|