@ak--47/dungeon-master 1.7.0 → 1.8.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 +30 -11
- package/.claude/skills/create-dungeon/SKILL.md +84 -44
- package/.claude/skills/create-project/SKILL.md +28 -3
- package/.claude/skills/create-project/context.mjs +89 -0
- package/.claude/skills/create-project/provision.mjs +1 -60
- package/.claude/skills/headless-build/SKILL.md +39 -12
- package/.claude/skills/powertools/SKILL.md +26 -3
- package/.claude/skills/release-check/SKILL.md +124 -0
- package/.claude/skills/verify-dungeon/SKILL.md +103 -29
- package/.claude/skills/verify-dungeon/references/alignment-contract.md +84 -0
- package/.claude/skills/verify-dungeon/references/counting-semantics.md +41 -16
- package/.claude/skills/verify-dungeon/references/report-format.md +41 -10
- package/.claude/skills/verify-dungeon/references/sql-recipes.md +171 -226
- package/.claude/skills/warehouse-metrics/GAPS-template.md +34 -0
- package/.claude/skills/warehouse-metrics/SKILL.md +111 -0
- package/.claude/skills/warehouse-metrics/deploy.mjs +651 -0
- package/.claude/skills/write-hooks/SKILL.md +94 -51
- package/CHANGELOG.md +183 -0
- package/HOOKS.md +165 -18
- package/README.md +265 -1
- package/docs/guides/1.8.0-upgrade-guide.md +151 -0
- package/docs/guides/1.8.1-upgrade-guide.md +153 -0
- package/dungeons/technical/warehouse.js +187 -0
- package/index.js +116 -2
- package/lib/core/config-validator.js +21 -0
- package/lib/core/dungeon-loader.js +1 -1
- package/lib/core/storage.js +51 -3
- package/lib/generators/events.js +6 -0
- package/lib/generators/funnels.js +15 -0
- package/lib/generators/standalone.js +248 -0
- package/lib/generators/warehouse.js +828 -0
- package/lib/hook-helpers/shape.js +73 -17
- package/lib/orchestrators/mixpanel-sender.js +27 -2
- package/lib/orchestrators/user-loop.js +83 -15
- package/lib/templates/story-spec.schema.json +41 -16
- package/lib/utils/utils.js +37 -12
- package/lib/verify/funnel-engine.js +66 -26
- package/lib/verify/index.js +1 -0
- package/lib/verify/story-runner.js +71 -8
- package/lib/verify/warehouse.js +683 -0
- package/package.json +4 -2
- package/scripts/verify-stories.mjs +150 -44
- package/types.d.ts +312 -9
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: write-hooks
|
|
3
3
|
description: Use when an existing dungeon needs engineered story trends or "magic number" patterns — writes the `hook` function using atom helpers and high-level patterns. Adds no new event flags; never mutates the schema.
|
|
4
|
-
argument-hint: [path/to/dungeon.js] [free-text story / trend description]
|
|
4
|
+
argument-hint: '[path/to/dungeon.js] [free-text story / trend description]'
|
|
5
5
|
model: claude-opus-4-6
|
|
6
6
|
effort: max
|
|
7
7
|
---
|
|
@@ -28,8 +28,8 @@ In scope:
|
|
|
28
28
|
|
|
29
29
|
Out of scope:
|
|
30
30
|
- Schema changes (events, properties, funnels, superProps, userProps).
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
Send missing fields or enumeration changes back to `/create-dungeon` for an
|
|
32
|
+
explicitly authorized schema edit before continuing. This skill never changes schema.
|
|
33
33
|
- New top-level config knobs.
|
|
34
34
|
- Removing the `hook: function...` body to start over with a new schema.
|
|
35
35
|
|
|
@@ -39,14 +39,15 @@ off, mix shift drags the blended rate) are better architected as initial
|
|
|
39
39
|
conditions — duplicate funnels with swapped steps/props/`conversionRate`/
|
|
40
40
|
`timeToConvert`/`weight` (see the "Structural trend engineering" section in
|
|
41
41
|
`create-dungeon`). If a story reduces to structure, recommend the funnel
|
|
42
|
-
change back to the schema instead of writing a hook to fight the engine
|
|
43
|
-
the
|
|
44
|
-
|
|
42
|
+
change back to the schema instead of writing a hook to fight the engine.
|
|
43
|
+
Derive the expected effect from the knob, then verify the report against a neutral
|
|
44
|
+
control; finite budgets and competing histories can change the measured effect. Hooks are for
|
|
45
45
|
within-cohort behavior: segments doing more/less of something over time,
|
|
46
46
|
property values that differ by cohort, injected bursts, lifecycle waves.
|
|
47
47
|
|
|
48
48
|
## Reference reading
|
|
49
49
|
|
|
50
|
+
- [1.8.1 verification contract](../verify-dungeon/references/alignment-contract.md) - independent reports, neutral controls, counting boundaries, and evidence sufficiency.
|
|
50
51
|
- `lib/hook-helpers/index.js` — atoms (cohort, mutate, timing, inject,
|
|
51
52
|
identity). One file per group; full JSDoc on each atom.
|
|
52
53
|
- `lib/hook-patterns/index.js` — high-level recipes (one per Mixpanel
|
|
@@ -68,15 +69,15 @@ property values that differ by cohort, injected bursts, lifecycle waves.
|
|
|
68
69
|
Hooks fire in this order per user (see `CLAUDE.md` for the canonical reference):
|
|
69
70
|
|
|
70
71
|
1. `"user"` — profile created. Mutate in place; return ignored.
|
|
71
|
-
2. `"scd-pre"` — SCD entries created. Mutate in place
|
|
72
|
+
2. `"scd-pre"` — SCD entries created. Mutate in place; return ignored.
|
|
72
73
|
3. For each funnel: `"funnel-pre"` → `"event"` (per step) → `"funnel-post"`.
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
the
|
|
77
|
-
|
|
78
|
-
`props
|
|
79
|
-
4. `"event"` — for non-funnel
|
|
75
|
+
**Usage anchors do not accumulate previous funnel TTC.** Each usage run samples
|
|
76
|
+
from its eligible window or active day after onboarding. `meta.firstEventTime`
|
|
77
|
+
is the supplied anchor, not necessarily the first emitted event time. Persona
|
|
78
|
+
and world-event modifiers apply before `funnel-pre`; the hook can mutate
|
|
79
|
+
`conversionRate`, `timeToConvert`, and `props` within engine constraints.
|
|
80
|
+
4. `"event"` — for non-funnel user events from `events[]`. Return value REPLACES the event.
|
|
80
81
|
5. `"everything"` — array of ALL the user's events. Return array to replace.
|
|
81
82
|
|
|
82
83
|
**Most engineered trends belong in `everything`.** It sees the full user stream,
|
|
@@ -86,6 +87,36 @@ and you can mutate freely.
|
|
|
86
87
|
Storage-only hooks (`ad-spend`, `group`, `mirror`, `lookup`) fire later in the
|
|
87
88
|
pipeline and don't see the same `meta` shape.
|
|
88
89
|
|
|
90
|
+
### Cadence streams and warehouse rows (v1.8.0)
|
|
91
|
+
|
|
92
|
+
These hooks sit outside the per-user sequence and never enter `everything`.
|
|
93
|
+
Neither has person metadata (`meta.profile`, auth state, sessions, or SCDs).
|
|
94
|
+
|
|
95
|
+
- `standaloneEvents`: `type === 'standalone'` fires on storage push before the
|
|
96
|
+
user loop. Read `meta.spec` and `meta.config` to identify the stream. Return
|
|
97
|
+
the record object or an array of records. Returning `undefined` drops it.
|
|
98
|
+
Mutating without returning is therefore insufficient. Preserve required keys
|
|
99
|
+
and use fresh `insert_id` values for clones. Its synthetic `distinct_id` is a
|
|
100
|
+
series identifier, never a person or a retention cohort.
|
|
101
|
+
- `warehouseMetrics`: `type === 'warehouse'` fires after user generation, once
|
|
102
|
+
per materialized row. Mutate the row in place; its return value is ignored.
|
|
103
|
+
Keep the time column and group keys stable. Modify only the value column and
|
|
104
|
+
declared extra columns. Meta includes `spec`, `config`, `metricName`,
|
|
105
|
+
`bucketIndex`, `bucketCount`, `grain`, `seriesKey`, `isBackfill`, and `raw`.
|
|
106
|
+
`raw` describes plus/minus source aggregates before scaling, noise, and carry.
|
|
107
|
+
|
|
108
|
+
Do not add either schema here. Send missing `standaloneEvents` properties or
|
|
109
|
+
warehouse `columns` back to `/create-dungeon`. Warehouse sources consume user
|
|
110
|
+
`events[]` only, including both plus and minus legs; they cannot consume cadence
|
|
111
|
+
streams. Standalone value functions use tick context, warehouse column functions
|
|
112
|
+
use bucket context; neither supplies a user profile.
|
|
113
|
+
|
|
114
|
+
Verify standalone stories with disk-backed `duckdb` assertions against
|
|
115
|
+
`{{PREFIX}}-STANDALONE*.json`. The user-event emulator and `--in-memory` CLI mode
|
|
116
|
+
do not evaluate this stream. Warehouse stories can use `warehouse` assertions
|
|
117
|
+
or `warehouse-stats` assertions; automatic warehouse audits also run without
|
|
118
|
+
stories. Hand off to `/verify-dungeon` with an explicit artifact prefix.
|
|
119
|
+
|
|
89
120
|
## Hook meta — identity context
|
|
90
121
|
|
|
91
122
|
Inside `funnel-pre` and `funnel-post`:
|
|
@@ -156,7 +187,7 @@ if (type === 'funnel-post' && meta.experiment) {
|
|
|
156
187
|
| cohort | `hashCohort(id, pct)` | Deterministic pct% cohort (0–100 scale). **Use this first for hidden cohorts** — replaces ad-hoc `charCodeAt % N`. When one dungeon needs several NON-overlapping cohorts, gate on disjoint `hashFloat(uid)` bands instead (e.g. `[0, 0.45)`, `[0.45, 0.70)`) |
|
|
157
188
|
| shape | `applyLifecycleWave(events, uid, opts)` | Dormancy window + resurrection burst for Lifecycle reports. When-to-use: the story is "users go quiet, then come back". Gap discipline: ONE stray value moment inside the window destroys the Resurrected read — size `dormantDays` to cover ≥2 whole lifecycle periods, keep the window inside the user's lifespan |
|
|
158
189
|
| shape | `applyPathBias(events, uid, opts)` | Inject a Flows path after the user's FIRST anchor occurrence. When-to-use: the story is "X% of users take this route". `share` is a 0–1 FRACTION (not `hashCohort`'s pct scale); needs ~≥0.20–0.25 to survive Sankey top-3-per-level pruning; per-step gaps clamped ≥1s so ordering survives |
|
|
159
|
-
| shape | `applySessionShape(events, uid, opts)` | Retime the
|
|
190
|
+
| shape | `applySessionShape(events, uid, opts)` | Retime the same records for session-duration/cadence stories. Pass known dataset bounds; explicit-bound mode rejects insufficient capacity before mutation. Legacy unbounded overfull layouts can merge sessions or cross dataset end. Call BEFORE `applyPathBias` so injected paths keep their own tight gaps. |
|
|
160
191
|
|
|
161
192
|
### Hook anti-patterns
|
|
162
193
|
|
|
@@ -206,8 +237,9 @@ writing a custom hook:
|
|
|
206
237
|
No special hook needed; engineer cohort behavior via `engagementDecay`,
|
|
207
238
|
`dropEventsWhere`, or per-user filtering in `everything`.
|
|
208
239
|
- **Session metrics** ("avg session has 6 events, lasts 4 minutes") — verify
|
|
209
|
-
with `emulateBreakdown({ type: 'sessionMetrics' })`.
|
|
210
|
-
`session_id
|
|
240
|
+
with `emulateBreakdown({ type: 'sessionMetrics' })`. Derive sessions from
|
|
241
|
+
timestamps; stamped `session_id` is diagnostic or explicit legacy mode.
|
|
242
|
+
Engineer with `applySessionShape`, passing known dataset bounds.
|
|
211
243
|
- **Reentry funnels** ("power users complete the funnel 3+ times") — set
|
|
212
244
|
`Funnel.reentry: true` (verifier hint). Engineer multiple completions via
|
|
213
245
|
`funnel-post` injecting cloned funnel sequences for that cohort.
|
|
@@ -224,9 +256,10 @@ writing a custom hook:
|
|
|
224
256
|
breakdown with `timeBucket: 'week'`. Engineer via temporal-windowed hooks
|
|
225
257
|
using `DATASET_START.add(N, 'days')`.
|
|
226
258
|
- **Identity-model dungeons** — when `identity.avgDevicePerUser > 0`
|
|
227
|
-
(or the deprecated `hasAnonIds: true`),
|
|
228
|
-
|
|
229
|
-
events
|
|
259
|
+
(or the deprecated `hasAnonIds: true`), pass `profiles` for profile segments.
|
|
260
|
+
For identity proof, pass an explicit `identityMap` derived from emitted valid
|
|
261
|
+
both-ID events. Profile pools alone establish no link; the compatibility helper
|
|
262
|
+
can merge users without emitted stitch evidence. See the shared contract.
|
|
230
263
|
|
|
231
264
|
**Schema-first reminder:** exclusion events must be declared in `events[]`
|
|
232
265
|
before referencing them as `Funnel.exclusionEvents` — the validator throws
|
|
@@ -240,9 +273,9 @@ emulator can re-derive.
|
|
|
240
273
|
| Pattern | Mixpanel analysis | Hook type | Caveat (HOOKS.md) |
|
|
241
274
|
|---------|-------------------|-----------|-------------------|
|
|
242
275
|
| `applyFrequencyByFrequency` | Insights — count(A) by per-user count(B) | everything | `binBy` defaults to `'distinctDays'` (v1.6) — bins match Mixpanel's per-user distinct-day counting, not raw event totals |
|
|
243
|
-
| `applyFunnelFrequencyBreakdown` | Funnels
|
|
276
|
+
| `applyFunnelFrequencyBreakdown` | Funnels - completion by per-user count(X) | funnel-post | Restrict scaling to the target funnel. Completed histories can combine competing instances; verify explicit report options and paired lift instead of assuming the per-run factor equals the report ratio. |
|
|
244
277
|
| `applyAggregateByBin` | Insights — avg(prop X) by per-user count(B) | everything | Same `binBy: 'distinctDays'` default as above |
|
|
245
|
-
| `applyTTCBySegmentV2` | Funnel TTC
|
|
278
|
+
| `applyTTCBySegmentV2` | Funnel TTC by user-property segment | everything | v1 (`applyTTCBySegment`, funnel-post) is deprecated. V2 scales `findFirstSequence`; verify the report's completed histories separately, including restart, grace, windows, and reentry. Neither helper guarantees the requested report ratio. |
|
|
246
279
|
| `applyAttributedBySource` | Conversions by Source (first/last touch) | everything | OVERWRITES the engine-stamped touch the chosen model reads; never stamps UTMs onto unstamped events (would blow the `maxTouchpointsPerUser` cap and land outside the last-10 lookback) |
|
|
247
280
|
|
|
248
281
|
Use a pattern when the trend matches its analysis 1:1. Drop down to atoms when
|
|
@@ -268,13 +301,17 @@ non-negotiable design rule learned the hard way:
|
|
|
268
301
|
visualization even though the data is there. Label-only path reads can
|
|
269
302
|
INVERT when a busier cohort glues extra visible events between path steps —
|
|
270
303
|
assert the share on the cohort you engineered, not globally.
|
|
304
|
+
The helper is append-only: `share` selects injection recipients, not exact
|
|
305
|
+
visible branch share. Keep competing events and measure paired branch lift.
|
|
271
306
|
- **`session-shape`** (`applySessionShape`) — 30-MIN STRADDLING + MIDNIGHT
|
|
272
|
-
RULE.
|
|
273
|
-
UTC midnight. Engineered cadences must keep intra-session gaps clearly
|
|
307
|
+
RULE. The local default derives sessions with a 30-min idle timeout and splits
|
|
308
|
+
at UTC midnight, with a 24-hour maximum. Engineered cadences must keep intra-session gaps clearly
|
|
274
309
|
UNDER 30min and inter-session gaps clearly OVER it — a gap that straddles
|
|
275
310
|
the timeout makes session counts jitter across runs. Never let an
|
|
276
311
|
engineered session cross UTC midnight (the day split cuts it in two). The
|
|
277
|
-
|
|
312
|
+
bounded helper checks placement capacity before mutation. Legacy unbounded
|
|
313
|
+
overfull requests can merge sessions. Pass known bounds and measure derived
|
|
314
|
+
sessions; do not infer dataset end from the last observed event.
|
|
278
315
|
- **`composition-drift`** — the breakdown's SHARE of a segment moves over
|
|
279
316
|
time while totals stay flat (e.g. plan-mix shifts toward premium). Engineer
|
|
280
317
|
by flipping an existing property value on a date-gated cohort, never by
|
|
@@ -309,9 +346,9 @@ events.push(clone); // ✅ inject from template
|
|
|
309
346
|
return events.filter(e => !shouldDrop(e)); // ✅ filter inside `everything`
|
|
310
347
|
```
|
|
311
348
|
|
|
312
|
-
If a trend
|
|
313
|
-
|
|
314
|
-
|
|
349
|
+
If a trend needs an undeclared property, stop that pattern and hand the schema
|
|
350
|
+
request to `/create-dungeon`. Resume only after an authorized schema edit declares
|
|
351
|
+
the default. Do not add the field or change its enumeration in this skill.
|
|
315
352
|
|
|
316
353
|
## Identity-aware hook patterns
|
|
317
354
|
|
|
@@ -433,8 +470,9 @@ Rules:
|
|
|
433
470
|
- `floor` must itself be derived (e.g. `target * 0.8`) — never hand-tuned to
|
|
434
471
|
a run. A missed assertion means fixing the hook or the derivation, never
|
|
435
472
|
relaxing the number to match output.
|
|
436
|
-
- Set `minCohort` from the
|
|
437
|
-
|
|
473
|
+
- Set `minCohort` from the planned eligible population, then inspect the actual
|
|
474
|
+
selected-row population fields and independent users/converters. The guard
|
|
475
|
+
does not prove every denominator. Report insufficient evidence separately.
|
|
438
476
|
- One story per pattern; story `hook` matches the doc-block numbering (`H3`).
|
|
439
477
|
- The `assert` function escape hatch is discouraged — each use needs a
|
|
440
478
|
comment saying why the declarative `expect` grammar can't express it.
|
|
@@ -444,12 +482,12 @@ Rules:
|
|
|
444
482
|
|
|
445
483
|
The order of operations inside the everything hook matters when hooks interact:
|
|
446
484
|
|
|
447
|
-
1. **
|
|
485
|
+
1. **Profile projection** - prefer schema-declared `stickyEventProps`; preserve projected values in clones
|
|
448
486
|
2. **Temporal value mutations that DON'T need cloned events** — e.g., version stamping
|
|
449
487
|
3. **Behavioral detection + event cloning** — agentic detection, KYC clones, pro clones, magic number clones
|
|
450
488
|
4. **Event filtering** — churn, retention, rate-limit drops
|
|
451
|
-
5. **Temporal value mutations that NEED cloned events**
|
|
452
|
-
6. **
|
|
489
|
+
5. **Temporal value mutations that NEED cloned events** - e.g., spring price boost, gas spike, outage errors (last mutation)
|
|
490
|
+
6. **Return** - the engine auto-sorts after `everything` by default; no manual output sort
|
|
453
491
|
|
|
454
492
|
**Why:** If a temporal mutation runs before cloning, cloned events that land in
|
|
455
493
|
the temporal window miss the mutation. Moving temporal value mutations to the
|
|
@@ -459,7 +497,8 @@ end ensures ALL events in the window — original and cloned — receive the eff
|
|
|
459
497
|
|
|
460
498
|
When a dungeon relied on deprecated config blocks (`subscription`, `attribution`,
|
|
461
499
|
`features`, `geo`, `anomalies`) for properties that hooks depend on, those
|
|
462
|
-
properties no longer appear in the data.
|
|
500
|
+
properties no longer appear in the data. Request an authorized schema migration
|
|
501
|
+
through `/create-dungeon` first:
|
|
463
502
|
|
|
464
503
|
1. Add the property to `superProps` and `userProps` with default values
|
|
465
504
|
2. Assign meaningful values in the `user` hook (based on hash, persona, or profile)
|
|
@@ -476,8 +515,8 @@ control group, but not so broad they catch everyone:
|
|
|
476
515
|
| Detection | Problem | Fix |
|
|
477
516
|
|-----------|---------|-----|
|
|
478
517
|
| `events.some(e => e.event === X)` with common X | 90%+ of users qualify | Require 3+ events: `events.filter(...).length >= 3` |
|
|
479
|
-
| `charCodeAt(0) % 50 === 0` |
|
|
480
|
-
| `profile.tier === "premium"` | Fixed by config distribution |
|
|
518
|
+
| `charCodeAt(0) % 50 === 0` | First-character distributions are biased; modulo does not imply 2% | Use `hashCohort(uid, 2)` and measure the realized population |
|
|
519
|
+
| `profile.tier === "premium"` | Fixed by config distribution | Request a schema distribution change if cohort is too small |
|
|
481
520
|
| `earlyEvents.length >= 5` for a low-weight event | 0% qualify (impossible threshold) | Check actual distribution first, set at ~80th percentile |
|
|
482
521
|
|
|
483
522
|
Target: 10-30% of users in the affected cohort for clean signal at 10K users.
|
|
@@ -516,11 +555,12 @@ Reserve drops for a single churn/retention effect per dungeon.
|
|
|
516
555
|
|
|
517
556
|
Apply these BEFORE handing off to `/verify-dungeon`. See HOOKS.md §9 for full recipes.
|
|
518
557
|
|
|
519
|
-
###
|
|
558
|
+
### Opt out of strict events only when standalone occurrences are required
|
|
520
559
|
|
|
521
560
|
If your hook reads `event === 'X'` and `X` is also a funnel-step event, the
|
|
522
561
|
validator auto-promotes it to `isStrictEvent: true` and the engine
|
|
523
|
-
won't emit standalone occurrences.
|
|
562
|
+
won't emit standalone occurrences. Funnel-generated occurrences remain readable;
|
|
563
|
+
only a cohort that depends on additional standalone traffic needs the opt-out.
|
|
524
564
|
|
|
525
565
|
```js
|
|
526
566
|
// BAD — login is a funnel step + read by hook
|
|
@@ -529,16 +569,15 @@ events: [{ event: 'login', weight: 4, properties: {...} }]
|
|
|
529
569
|
events: [{ event: 'login', weight: 4, isStrictEvent: false, properties: {...} }]
|
|
530
570
|
```
|
|
531
571
|
|
|
532
|
-
Audit
|
|
533
|
-
|
|
572
|
+
Audit whether the cohort needs standalone occurrences. Request any required
|
|
573
|
+
`isStrictEvent: false` schema edit through `/create-dungeon`; do not change it here.
|
|
534
574
|
|
|
535
575
|
### Reentry on per-instance loops
|
|
536
576
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
funnel.
|
|
577
|
+
**`reentry` is verifier-only.** Usage volume, funnel selection, and the event
|
|
578
|
+
budget control generated repetitions. Set `reentry: true` for a report that
|
|
579
|
+
counts repeated histories; it does not generate loops. Local totals also
|
|
580
|
+
default to `reentry: false`, so preserve the report's explicit counting options.
|
|
542
581
|
|
|
543
582
|
### Hash-based cohorts produce textbook signals
|
|
544
583
|
|
|
@@ -547,12 +586,13 @@ deterministically:
|
|
|
547
586
|
|
|
548
587
|
```js
|
|
549
588
|
// 2% whales with 50x trade amount → long-tail Insights distribution
|
|
550
|
-
const isWhale = uid
|
|
589
|
+
const isWhale = hashCohort(uid, 2);
|
|
551
590
|
if (isWhale && e.event === 'swap') e.trade_amount_usd *= 50;
|
|
552
591
|
```
|
|
553
592
|
|
|
554
593
|
Use a large multiplier (≥10x, ideally 50x) so the signal beats soup noise.
|
|
555
|
-
Use
|
|
594
|
+
Use `hashCohort(uid, 2)`, `hashCohort(uid, 4)`, or `hashCohort(uid, 10)` for
|
|
595
|
+
those target percentages, then check realized eligible cohort sizes.
|
|
556
596
|
|
|
557
597
|
### Hook ordering inside `everything`
|
|
558
598
|
|
|
@@ -574,13 +614,13 @@ counts even after reduction. Either:
|
|
|
574
614
|
```js
|
|
575
615
|
// BAD — profile.level isn't in userProps; resolves to undefined
|
|
576
616
|
if (meta.profile.level >= 50) e.gold_earned *= 3;
|
|
577
|
-
//
|
|
617
|
+
// Request a schema declaration before targeting this profile segment.
|
|
578
618
|
```
|
|
579
619
|
|
|
580
620
|
When the hook references a missing profile field, you can still get the
|
|
581
621
|
data spread you want (gold range), but the cohort can't be analytically
|
|
582
|
-
recovered.
|
|
583
|
-
cohort.
|
|
622
|
+
recovered. Request the missing userProp through `/create-dungeon`, or rewrite
|
|
623
|
+
the hook to use a hash cohort without changing schema.
|
|
584
624
|
|
|
585
625
|
## Workflow
|
|
586
626
|
|
|
@@ -618,11 +658,14 @@ cohort.
|
|
|
618
658
|
If verify-dungeon returns WEAK, NONE, or INVERSE on any pattern at full
|
|
619
659
|
fidelity, return to step 4 and refine — fix the hook or the derivation,
|
|
620
660
|
never relax a threshold to match output. Iterate until all patterns score
|
|
621
|
-
|
|
661
|
+
STRONG or NAILED with report semantics, neutral controls, and sufficient
|
|
662
|
+
independent populations verified. Insufficient evidence requires more evidence,
|
|
663
|
+
not an automatic hook rewrite or a weaker target.
|
|
622
664
|
|
|
623
665
|
## Stopping condition
|
|
624
666
|
|
|
625
|
-
Stop after `/verify-dungeon` reports all engineered patterns as STRONG or NAILED
|
|
667
|
+
Stop after `/verify-dungeon` reports all engineered patterns as STRONG or NAILED
|
|
668
|
+
and the shared proof contract is satisfied,
|
|
626
669
|
OR after three iterations without convergence — at that point, document what's
|
|
627
670
|
still off in the dungeon's overview comment and report the gap to the user.
|
|
628
671
|
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,189 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@ak--47/dungeon-master`.
|
|
4
4
|
|
|
5
|
+
## 1.8.1 — 2026-09-12
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Align funnel counting with source-derived Mixpanel contracts: shared first/last
|
|
10
|
+
steps finalize correctly with or without reentry, completion grace consumes
|
|
11
|
+
events through the inclusive two-second boundary, and first/last-touch
|
|
12
|
+
breakdowns merge properties across reached steps.
|
|
13
|
+
- Derive session boundaries from the full event stream before hold-property-constant
|
|
14
|
+
partitioning, so unrelated activity can keep a session open.
|
|
15
|
+
- Keep onboarding after user creation and usage after onboarding. Preserve retry
|
|
16
|
+
entries when strict event-count capacity allows. Short windows retain partial
|
|
17
|
+
output and report capacity limits through existing warnings.
|
|
18
|
+
- Reconcile engine-generated identity against surviving auth events, including
|
|
19
|
+
engine-created duplicates and amplification clones. Preserve explicit hook
|
|
20
|
+
overrides and the synthetic experiment identity exception.
|
|
21
|
+
- Select the earliest chronological anchor for append-only path injection.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- Optional `datasetStart` and `datasetEnd` bounds for `applySessionShape`, preventing
|
|
26
|
+
retimed events from being clipped outside a known dataset window. Explicit bounds
|
|
27
|
+
reject impossible session layouts before mutation; calls without bounds retain
|
|
28
|
+
legacy full-day placement.
|
|
29
|
+
- An offline `tests/alignment` family with mixed-dungeon trend checks, neutral
|
|
30
|
+
controls, multiple seeds, sample-size guards, counting contracts, and a diagnostic
|
|
31
|
+
sweep with a hard ten-minute deadline. The runner denies network access at the OS
|
|
32
|
+
level on macOS and fails closed elsewhere.
|
|
33
|
+
- A control inventory, failure-first repair evidence, and measured operating ranges.
|
|
34
|
+
Release validation passed 175 alignment tests and 2,022 unit/integration tests, with
|
|
35
|
+
one existing skip. The 297-cell sweep generated 17.08 million events cumulatively:
|
|
36
|
+
125 cells met their evidence thresholds and 172 had insufficient evidence.
|
|
37
|
+
|
|
38
|
+
### Compatibility
|
|
39
|
+
|
|
40
|
+
Existing call forms and defaults remain supported. Session bounds are additive,
|
|
41
|
+
not required. Corrected timestamps, identities, and counts can change generated
|
|
42
|
+
output relative to 1.8.0. Totals counting still requires explicit `reentry: true`
|
|
43
|
+
for repeated histories. These tests use local Mixpanel source-derived contracts,
|
|
44
|
+
not live engine execution, and do not establish complete coverage of every knob.
|
|
45
|
+
|
|
46
|
+
## 1.8.0 — 2026-09-10
|
|
47
|
+
|
|
48
|
+
### Added — `standaloneEvents`: identity-less metric snapshots
|
|
49
|
+
|
|
50
|
+
A new top-level config key that generates records describing a **system, not a
|
|
51
|
+
person**. They carry no `user_id` and no `device_id`. Before 1.8.0 the only
|
|
52
|
+
identity-less stream the engine could produce was `$ad_spend` via `hasAdSpend`,
|
|
53
|
+
which is hard-coded to one shape, one cadence, and a Mixpanel reserved event
|
|
54
|
+
name. `standaloneEvents` is the general form.
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
standaloneEvents: [{
|
|
58
|
+
event: 'cdn_egress',
|
|
59
|
+
cadence: 'day', // 'hour' | 'day' | 'week', default 'day'
|
|
60
|
+
dimensions: { region: ['us-east', 'us-west', 'eu'] }, // cross-producted
|
|
61
|
+
distinctIdFrom: 'region', // synthetic id, never a person
|
|
62
|
+
properties: {
|
|
63
|
+
gb_out: (ctx) => 400 + ctx.tickIndex * 3,
|
|
64
|
+
cost_usd: (ctx) => (400 + ctx.tickIndex * 3) * 0.085,
|
|
65
|
+
p95_ms: [120, 140, 160],
|
|
66
|
+
},
|
|
67
|
+
}]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- One record per cadence tick per dimension cross-product row.
|
|
71
|
+
- Ticks start at the dataset start and step by the cadence. The last tick is the
|
|
72
|
+
final one at or before the dataset end, so nothing lands in the future.
|
|
73
|
+
- Each record carries `event`, `time`, `insert_id`, `distinct_id`, every
|
|
74
|
+
dimension as a flat property, and every resolved entry in `properties`.
|
|
75
|
+
- `distinct_id` is the value of the dimension named by `distinctIdFrom`, else the
|
|
76
|
+
event name. It exists so Mixpanel accepts the record; it never maps to a person.
|
|
77
|
+
- Property value functions receive a `StandaloneValueContext`:
|
|
78
|
+
`{ time, config, dimensions, tickIndex, tickCount, cadence, event }`.
|
|
79
|
+
`tickIndex / (tickCount - 1)` is window progress — use it to shape a trend.
|
|
80
|
+
- New hook type `standalone` (storage-only). Return the record or an array of
|
|
81
|
+
records; returning nothing drops the record. `meta.spec` carries the resolved
|
|
82
|
+
stream config. The `warehouse` hook instead mutates its row and ignores returns.
|
|
83
|
+
- Lands in `result.standaloneEventData`, writes to a `-STANDALONE` file shard,
|
|
84
|
+
and imports to Mixpanel as its own event stream.
|
|
85
|
+
- Validation **throws** on a malformed entry rather than skipping it. A silent
|
|
86
|
+
skip would drop a whole data stream without the author noticing.
|
|
87
|
+
|
|
88
|
+
New types: `StandaloneEventConfig`, `ResolvedStandaloneEventConfig`,
|
|
89
|
+
`StandaloneValueContext`, `HookMetaStandalone`. `WritePaths` gains
|
|
90
|
+
`standaloneFiles`; `Result` gains `standaloneEventData`; `hookTypes` gains
|
|
91
|
+
`"standalone"`.
|
|
92
|
+
|
|
93
|
+
**Output compatibility.** Additive only. A config without `standaloneEvents` is
|
|
94
|
+
byte-identical to 1.7.0 — the generation pass is gated on the key being present,
|
|
95
|
+
so the seeded RNG stream is untouched. `config.standaloneEvents` normalizes to
|
|
96
|
+
`[]` when absent. Event determinism comparisons exclude the fresh `insert_id`.
|
|
97
|
+
|
|
98
|
+
New tests: `tests/unit/standalone-events.test.js` (25),
|
|
99
|
+
`tests/integration/standalone-events.test.js` (15).
|
|
100
|
+
|
|
101
|
+
### Added — `warehouseMetrics`: manifest-driven warehouse source tables
|
|
102
|
+
|
|
103
|
+
A new top-level config key that materializes warehouse-ready tables from the
|
|
104
|
+
run's own events after generation completes. This is the local source-table side
|
|
105
|
+
of a warehouse metric demo: bookings rollups, active subscription levels, ARR
|
|
106
|
+
snapshots, and other time-series tables that should read like a real warehouse.
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
warehouseMetrics: [{
|
|
110
|
+
name: 'daily_new_bookings',
|
|
111
|
+
source: { event: 'new_booking', measure: 'sum', property: 'booking_value' },
|
|
112
|
+
valueColumn: 'bookings',
|
|
113
|
+
}]
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- Supports additive and point-in-time metrics.
|
|
117
|
+
- Grain: `day`, `week`, `month`.
|
|
118
|
+
- Supports subtractive `minus` legs, `groupBy` on up to two declared keys,
|
|
119
|
+
optional `history` backfill, sparse point-in-time emission, seeded `noise`,
|
|
120
|
+
`scale`, and derived `columns`.
|
|
121
|
+
- Lands in `result.warehouseMetricData` keyed by metric name and emits
|
|
122
|
+
`result.warehouseManifest` with table schemas, SQL, and recommended
|
|
123
|
+
aggregation.
|
|
124
|
+
- Writes `<name>-WAREHOUSE-<table>.csv|json` plus
|
|
125
|
+
`<name>-WAREHOUSE-MANIFEST.json` when `writeToDisk` is enabled.
|
|
126
|
+
- Never imports through `token`. Warehouse deploy is a separate flow.
|
|
127
|
+
- New hook type `warehouse` fires once per materialized row with
|
|
128
|
+
`metricName`, `bucketIndex`, `bucketCount`, `grain`, `seriesKey`,
|
|
129
|
+
`isBackfill`, and `raw` bucket stats.
|
|
130
|
+
- Warehouse verification adds `warehouse` / `warehouse-stats` story breakdowns
|
|
131
|
+
plus automatic audits over declared columns, gaps, monotonic time, empty numeric
|
|
132
|
+
cells, and sparse first-bucket coverage. Source correlation is available to story
|
|
133
|
+
assertions; it is not an automatic pass/fail gate.
|
|
134
|
+
- Sparse point-in-time comparison carries emitted levels across missing buckets,
|
|
135
|
+
excludes history from correlation, and compares the first live delta to the scaled
|
|
136
|
+
baseline. Warehouse generation preserves the user event stream, including with noise.
|
|
137
|
+
- Disk verification parses quoted multiline CSV records with `csv-parse` and keeps
|
|
138
|
+
disk and in-memory audit results consistent.
|
|
139
|
+
|
|
140
|
+
### Added — `/warehouse-metrics`: BigQuery load + warehouse metric save flow
|
|
141
|
+
|
|
142
|
+
The shipped skill at `.claude/skills/warehouse-metrics/` loads the generated
|
|
143
|
+
warehouse tables into BigQuery, connects that dataset to Mixpanel with the
|
|
144
|
+
existing powertools macro, previews each metric SQL, and saves new metrics when
|
|
145
|
+
the CRUD endpoints are available.
|
|
146
|
+
|
|
147
|
+
- Uses the emitted warehouse manifest as the contract.
|
|
148
|
+
- Maps manifest `recommendedAggregation: 'last value'` to the API's
|
|
149
|
+
`aggregation: 'last_value'`.
|
|
150
|
+
- If `GET /crud/getWarehouseMetrics` returns 404, the script still completes the
|
|
151
|
+
BigQuery load and source setup, then writes `warehouse/GAPS.md` for manual
|
|
152
|
+
metric creation.
|
|
153
|
+
- Preview fails fast on the raw substring block (`CREATE`, `UPDATE`, etc.), so
|
|
154
|
+
identifiers like `created_at` and `updated_at` are a real deploy-time trap.
|
|
155
|
+
- The canonical skill name is `/warehouse-metrics`. Bundled commands and handoffs
|
|
156
|
+
use `.claude/skills/warehouse-metrics/`.
|
|
157
|
+
- Uses the shipped Powertools warehouse CRUD and IAM setup macro. Runtime IAM is
|
|
158
|
+
configured; permission failures still stop deployment with the original error.
|
|
159
|
+
- Dry-run works before project provisioning, using placeholders without credentials.
|
|
160
|
+
Live table replacement requires explicit operator consent; the script does not prompt.
|
|
161
|
+
|
|
162
|
+
### Changed — skill workflow and provisioning context
|
|
163
|
+
|
|
164
|
+
- All nine bundled skills have parsed string argument hints and matching folder names.
|
|
165
|
+
Release tests check frontmatter and warehouse handoffs.
|
|
166
|
+
- Authoring, hooks, and verification distinguish person events, identity-less cadence
|
|
167
|
+
events, and identity-free warehouse rows. Synthetic IDs never count as people.
|
|
168
|
+
- Verification preserves the explicit run prefix and local uncompressed warehouse
|
|
169
|
+
artifacts for deployment. Soup analysis remains scoped to user-event timestamps.
|
|
170
|
+
- Project business context includes separate cadence and warehouse summaries without
|
|
171
|
+
evaluating property functions or including credentials.
|
|
172
|
+
- Headless builds preserve warehouse history and use query preview for fresh results;
|
|
173
|
+
warehouse refresh only invalidates the saved metric cache.
|
|
174
|
+
|
|
175
|
+
### Added - `/release-check`
|
|
176
|
+
|
|
177
|
+
The new `/release-check` skill audits tests, determinism, documentation, package
|
|
178
|
+
contents, and release handoffs. `.agents/skills` and `.github/skills` link to the
|
|
179
|
+
canonical `.claude/skills` directory for shared agent discovery. Publishing remains
|
|
180
|
+
an explicitly authorized operator action.
|
|
181
|
+
|
|
182
|
+
### Changed — `streamCSV` preserves falsy cells
|
|
183
|
+
|
|
184
|
+
CSV serialization now writes `0` and `false` as literal cell values instead of
|
|
185
|
+
empty strings. If downstream warehouse SQL or fixtures were treating blank cells
|
|
186
|
+
as zero or false, update them to read the actual value.
|
|
187
|
+
|
|
5
188
|
## 1.7.0 — 2026-09-03
|
|
6
189
|
|
|
7
190
|
The engine round for DM4 v5. Executes the 1.6.4 "Deferred to 1.7.0" table plus
|