@ak--47/dungeon-master 1.7.0 → 1.8.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 (37) hide show
  1. package/.claude/skills/analyze-soup/SKILL.md +21 -11
  2. package/.claude/skills/create-dungeon/SKILL.md +49 -10
  3. package/.claude/skills/create-project/SKILL.md +22 -3
  4. package/.claude/skills/create-project/context.mjs +89 -0
  5. package/.claude/skills/create-project/provision.mjs +1 -60
  6. package/.claude/skills/headless-build/SKILL.md +18 -1
  7. package/.claude/skills/powertools/SKILL.md +20 -1
  8. package/.claude/skills/release-check/SKILL.md +99 -0
  9. package/.claude/skills/verify-dungeon/SKILL.md +71 -16
  10. package/.claude/skills/verify-dungeon/references/counting-semantics.md +14 -0
  11. package/.claude/skills/verify-dungeon/references/report-format.md +18 -1
  12. package/.claude/skills/verify-dungeon/references/sql-recipes.md +36 -1
  13. package/.claude/skills/warehouse-metrics/GAPS-template.md +34 -0
  14. package/.claude/skills/warehouse-metrics/SKILL.md +105 -0
  15. package/.claude/skills/warehouse-metrics/deploy.mjs +651 -0
  16. package/.claude/skills/write-hooks/SKILL.md +33 -3
  17. package/CHANGELOG.md +142 -0
  18. package/HOOKS.md +105 -5
  19. package/README.md +228 -0
  20. package/docs/guides/1.8.0-upgrade-guide.md +151 -0
  21. package/dungeons/technical/warehouse.js +187 -0
  22. package/index.js +116 -2
  23. package/lib/core/config-validator.js +21 -0
  24. package/lib/core/dungeon-loader.js +1 -1
  25. package/lib/core/storage.js +51 -3
  26. package/lib/generators/standalone.js +248 -0
  27. package/lib/generators/warehouse.js +828 -0
  28. package/lib/orchestrators/mixpanel-sender.js +27 -2
  29. package/lib/orchestrators/user-loop.js +1 -0
  30. package/lib/templates/story-spec.schema.json +41 -16
  31. package/lib/utils/utils.js +37 -12
  32. package/lib/verify/index.js +1 -0
  33. package/lib/verify/story-runner.js +71 -8
  34. package/lib/verify/warehouse.js +683 -0
  35. package/package.json +4 -2
  36. package/scripts/verify-stories.mjs +150 -44
  37. package/types.d.ts +303 -4
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: analyze-soup
3
3
  description: Use when investigating TimeSoup parameters, diagnosing event-distribution shape, or comparing soup configs — runs a dungeon locally and analyzes time distribution at week/day/hour/minute granularities, producing a soup-analysis.md diagnostic report.
4
- argument-hint: [dungeon path, e.g. dungeons/technical/simplest.js]
4
+ argument-hint: '[dungeon path, e.g. dungeons/technical/simplest.js]'
5
5
  model: claude-opus-4-6
6
6
  effort: max
7
7
  ---
@@ -10,6 +10,12 @@ effort: max
10
10
 
11
11
  Run a dungeon and analyze the time distribution of generated events to evaluate TimeSoup parameters.
12
12
 
13
+ Analyze user `eventData` / EVENTS shards only. `standaloneEvents` runs on a
14
+ fixed cadence before the user loop; `warehouseMetrics` materializes tables
15
+ afterward. Neither measures TimeSoup. Report their cadence or table checks
16
+ separately through `/verify-dungeon`, and preserve warehouse files for
17
+ `/warehouse-metrics`. Never count standalone synthetic ids as people.
18
+
13
19
  **Dungeon file:** `$ARGUMENTS` (default: `dungeons/technical/simplest.js`)
14
20
 
15
21
  ## Step 1: Run the Dungeon
@@ -17,15 +23,20 @@ Run a dungeon and analyze the time distribution of generated events to evaluate
17
23
  Run the dungeon with forced local-only settings:
18
24
 
19
25
  ```bash
20
- npm run prune
21
- node -e "
26
+ node --input-type=module -e "
22
27
  import generate from './index.js';
23
28
  import config from './$ARGUMENTS';
24
- const result = await generate({ ...config, writeToDisk: true, format: 'json', token: '', verbose: true, name: 'soup-analysis' });
29
+ const result = await generate({ ...config, writeToDisk: true, gzip: false, format: 'json', token: '', verbose: true, name: 'soup-analysis' });
25
30
  console.log('Events:', result.eventCount, 'Users:', result.userCount);
26
31
  "
27
32
  ```
28
33
 
34
+ Choose an unused run name before executing; `soup-analysis` below is an example.
35
+ Record the explicit data prefix (`data/soup-analysis`) and use it in every query.
36
+ If that prefix already exists, choose another and update the query paths. Do not
37
+ prune or overwrite an earlier run. Use `<prefix>-EVENTS*.json` with
38
+ `union_by_name=true` for batched output; never glob STANDALONE or WAREHOUSE into it.
39
+
29
40
  Wait for generation to complete. Note the event count and EPS.
30
41
 
31
42
  ## Step 2: Query with DuckDB
@@ -147,7 +158,7 @@ Create `soup-analysis.md` with the structure below. For a user dungeon
147
158
  (`dungeons/user/<name>/<name>.js`) write it into the dungeon's folder
148
159
  (`dungeons/user/<name>/soup-analysis.md`) — everything about a dungeon lives in
149
160
  its folder. Otherwise write it to the project root. (The generated
150
- `./data/soup-analysis-EVENTS.json` is throwaway verification data leave it in
161
+ `./data/soup-analysis-EVENTS.json` is retained verification data; leave it in
151
162
  `./data/`.) Contents:
152
163
 
153
164
  1. **Config**: The soup parameters used (peaks, deviation, mean, numDays)
@@ -163,10 +174,9 @@ its folder. Otherwise write it to the project root. (The generated
163
174
  - **Last day spike**: < 1.5x average = PASS, < 2x = WARN, > 2x = FAIL
164
175
  - **Hourly pattern**: Should show visible peaks but no single hour > 5x average
165
176
 
166
- ## Step 4: Cleanup
167
-
168
- ```bash
169
- npm run prune
170
- ```
177
+ ## Step 4: Preserve artifacts
171
178
 
172
- Remove `soup-analysis.md` only if the user asks. It's meant to persist for comparison across runs.
179
+ Record the prefix and files in the report. Keep warehouse tables and the matching
180
+ manifest until deployment completes. Cleanup requires user consent and an explicit
181
+ list of this run's files. Never run blanket prune. Keep `soup-analysis.md` for
182
+ comparison across runs unless the user asks to remove it.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: create-dungeon
3
- description: Use when authoring a new dungeon-master config from an app description designs events, funnels, properties, identity model, and macro/soup. SCHEMA ONLY; engineered story trends + hooks are added separately by write-hooks.
4
- argument-hint: [free-text app description, e.g. "AI meeting assistant" or "B2B logistics platform"]
3
+ description: Use when authoring a new dungeon-master config from an app description, including standaloneEvents cadence streams and warehouseMetrics source tables. Designs events, funnels, properties, identity model, and macro/soup. SCHEMA ONLY; engineered story trends and hooks are added separately by write-hooks.
4
+ argument-hint: '[free-text app description, e.g. "AI meeting assistant" or "B2B logistics platform"]'
5
5
  model: claude-opus-4-6
6
6
  effort: max
7
7
  ---
@@ -87,9 +87,9 @@ import dayjs from "dayjs";
87
87
  import utc from "dayjs/plugin/utc.js";
88
88
  dayjs.extend(utc);
89
89
  import "dotenv/config";
90
- import * as u from "../../lib/utils/utils.js";
90
+ import * as u from "../../../lib/utils/utils.js";
91
91
  import * as v from "ak-tools";
92
- /** @typedef {import("../../types").Dungeon} Config */
92
+ /** @typedef {import("../../../types").Dungeon} Config */
93
93
 
94
94
  // ── OVERVIEW ──
95
95
  /*
@@ -133,8 +133,8 @@ const config = {
133
133
  numUsers: NUM_USERS,
134
134
  avgEventsPerUserPerDay: EVENTS_PER_DAY,
135
135
  format: "json",
136
- gzip: true,
137
- writeToDisk: false,
136
+ gzip: false,
137
+ writeToDisk: true,
138
138
  concurrency: 1,
139
139
  macro: "flat", // optional — see "Trend shape" below
140
140
  soup: "growth", // optional
@@ -319,7 +319,40 @@ group attributes. Skip for B2C apps.
319
319
  Slowly-changing dimensions for plan tier, role, etc. JSDoc on `SCDProp` covers
320
320
  type/frequency/timing/values/max.
321
321
 
322
- ### 7. Hook function DO NOT WRITE
322
+ ### 7. Metric data surfaces (v1.8.0)
323
+
324
+ Author these schemas here when the app needs system telemetry or warehouse tables.
325
+ Requests that call these surfaces "v2" still target v1.8.0; do not bump the version.
326
+
327
+ | Surface | Required design | Time settings | Output |
328
+ |---|---|---|---|
329
+ | `standaloneEvents` | `event`, declared `dimensions` and `properties`; optional `distinctIdFrom` naming a dimension | `cadence: 'hour'`, `'day'`, or `'week'` | `result.standaloneEventData`, `-STANDALONE*.json`; imports as events |
330
+ | `warehouseMetrics` | `name`, `type: 'additive'` or `'point-in-time'`, `source`; declare extra `columns` | `grain: 'day'`, `'week'`, or `'month'`, optional `history` bucket count | `result.warehouseMetricData`, `-WAREHOUSE-*` tables and `-WAREHOUSE-MANIFEST.json`; separate deploy |
331
+
332
+ `standaloneEvents` generates before the user loop. Each cadence tick emits one
333
+ record per dimension cross-product row. Its synthetic `distinct_id` identifies a
334
+ series, never a person. Do not put these events in funnels, retention, identity
335
+ stitch checks, or people counts. They do not inherit user metadata or superProps.
336
+
337
+ Standalone property functions receive `{ time, config, dimensions, tickIndex,
338
+ tickCount, cadence, event }`, with `time` in milliseconds. Guard `tickCount <= 1`
339
+ before dividing by `tickCount - 1`. Declare every property and dimension here.
340
+
341
+ Warehouse sources reference only user `events[]`, including names in both
342
+ `source.event` (plus) and optional `source.minus`. A `standaloneEvents` stream
343
+ cannot be a warehouse source. Declare `source.property` and every `source.groupBy`
344
+ key on every plus and minus event, or in `superProps`. `sum` and `avg` require
345
+ `source.property`; point-in-time disallows `avg` and `dau`. Use at most two group
346
+ keys. `sparse: true` applies only to point-in-time metrics.
347
+
348
+ Warehouse materialization runs after the user loop. Extra column functions receive
349
+ `{ value, row, time, bucketIndex, bucketCount, grain, isBackfill, seriesKey, spec,
350
+ config }`. `time` is milliseconds. `history` adds synthetic pre-window buckets;
351
+ it does not create historical user events. Keep `timeColumn`, `valueColumn`, group
352
+ keys, and extra `columns` distinct and declared. Read `dungeons/technical/warehouse.js`
353
+ and the warehouse interfaces in `types.d.ts` before choosing measures or backfill.
354
+
355
+ ### 8. Hook function — DO NOT WRITE
323
356
 
324
357
  Skip the `hook:` field entirely (engine defaults to pass-through). The
325
358
  `write-hooks` skill picks this up and engineers the trends.
@@ -620,10 +653,16 @@ This keeps `dungeons/user/` organized — EVERYTHING about this dungeon lives in
620
653
  the same folder: `hook-results.md` + `hook-query-log.txt` +
621
654
  `<name>-verifications.sql` (from `verify-dungeon`), `soup-analysis.md` (from
622
655
  `analyze-soup`), briefs, schema CSV/JSON, example data. The only thing kept
623
- outside is the throwaway verification data the runs write to `./data/` (cleaned
624
- after).
656
+ outside is the verification data the runs write to `./data/`. Preserve the exact
657
+ run prefix and its files until verification and warehouse deployment are complete.
625
658
 
626
659
  Do NOT inject hooks. Do NOT use `subscription`, `attribution`, `geo`,
627
660
  `features`, or `anomalies` (the engine will silently strip them and warn).
628
661
 
629
- When done, tell the user the next skill to run.
662
+ When done, hand off to `/write-hooks` when trends are needed, then `/verify-dungeon`.
663
+ For artifact generation use local `writeToDisk: true`, `format: 'json'`,
664
+ `gzip: false`, and an explicit unique run name. Verification runs must disable
665
+ sending with a top-level `token: ''` override. Provision with `/create-project`
666
+ after verification. If `warehouseMetrics` exists, route to `/warehouse-metrics`
667
+ with the verified disk artifact prefix after project provisioning. Keep those
668
+ files; ordinary event import does not deploy warehouse tables.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: create-project
3
3
  description: Use when an existing dungeon needs a real Mixpanel project provisioned before sending data — creates the project, sets timezone UTC, mints a scoped service account, adds the dungeon's group keys, uploads business context (AI context), and writes the resulting credentials back into the dungeon so it "just runs". Follows create-dungeon / write-hooks / verify-dungeon.
4
- argument-hint: [dungeon path, e.g. dungeons/user/shopstream/shopstream.js]
4
+ argument-hint: '[dungeon path, e.g. dungeons/user/shopstream/shopstream.js]'
5
5
  model: claude-opus-4-6
6
6
  effort: max
7
7
  ---
@@ -25,7 +25,7 @@ in order:
25
25
  1. **createProject** — name derived from the dungeon's `OVERVIEW` (`NAME:` line), region `US`, timezone `UTC` (set as a follow-up by the endpoint).
26
26
  2. **mintServiceAccount** — `admin`, expires `+30 days`, scoped to the new project. This is what the dungeon uses to **send** data.
27
27
  3. **addGroupKey** — one per `groupKeys` entry in the dungeon (`property_name` + a titleized `display_name`). Skipped if the dungeon has no group keys.
28
- 4. **setBusinessContext** — markdown built from the dungeon's `OVERVIEW` comment block plus the `stories` named export (per story: `narrative` + `mixpanelReport` + intentional deviations); dungeons without stories fall back to the `HOOK STORIES` comment scrape (via the package's `extractComments`). Plus an events/funnels/props/group-keys summary, capped at 50k chars. The dry-run plan prints which source was used.
28
+ 4. **setBusinessContext** — markdown built from the dungeon's `OVERVIEW` comment block plus the `stories` named export (per story: `narrative` + `mixpanelReport` + intentional deviations); dungeons without stories fall back to the `HOOK STORIES` comment scrape (via the package's `extractComments`). Plus an events/funnels/props/group-keys summary and separate standalone-event and warehouse-metric summaries, capped at 50k chars. The dry-run plan prints which source was used.
29
29
  5. **write-back** — replaces the dungeon's `credentials: { … }` block with `{ token, projectId, serviceAccount, serviceSecret, region }`.
30
30
 
31
31
  **Always creates a fresh project.** Re-running mints a new project and overwrites
@@ -83,7 +83,26 @@ run command:
83
83
  node scripts/run-dungeon.mjs <dungeon-path>
84
84
  ```
85
85
 
86
- ## Error handling
86
+ ## Metric handoff (v1.8.0)
87
+
88
+ Verify `standaloneEvents` and `warehouseMetrics` locally before provisioning.
89
+ Keep the verified run's disk files and record its explicit prefix. Cadence events
90
+ import through the normal send path; their synthetic `distinct_id` values must
91
+ never become people counts or identity-model evidence.
92
+
93
+ After credentials are written, route `warehouseMetrics` to `/warehouse-metrics`
94
+ with that prefix and `credentials.projectId`. Require local uncompressed table
95
+ files (`writeToDisk: true`, `gzip: false`) and the matching warehouse manifest.
96
+ Review the deploy dry run before obtaining consent for live writes. Project
97
+ provisioning alone does not load BigQuery tables or save warehouse metrics.
98
+
99
+ The pure `context.mjs` helper summarizes cadence, dimension keys and cardinalities,
100
+ property names, and synthetic identity separately from person events. Warehouse
101
+ summaries include sources, measures, grain, type, point-in-time baseline, history,
102
+ dimension and output columns, aggregation, and the separate deployment handoff.
103
+ It does not evaluate property functions or include config credentials.
104
+
105
+ ## Provisioning errors
87
106
 
88
107
  - **Missing `BEARER_TOKEN` / `ORG_ID`** — orchestrator exits; have the user fix `.env`.
89
108
  - **`createProject` fails** — nothing is provisioned; surface the power-tools error (`{ error }` or `{ errors:[{param,message}] }`) verbatim and stop.
@@ -0,0 +1,89 @@
1
+ export function buildContext(name, config, comments, groupKeys, stories) {
2
+ const parts = [`# ${name}`, ''];
3
+ if (comments.overview) parts.push(comments.overview, '');
4
+
5
+ if (stories?.length) {
6
+ parts.push('## Engineered Behaviors', '');
7
+ parts.push(`${stories.length} machine-verified story patterns (from the dungeon's \`stories\` export):`, '');
8
+ for (const s of stories) {
9
+ const arch = s.archetype ? ` — ${s.archetype}` : '';
10
+ parts.push(`### ${s.id}${arch}`, '');
11
+ if (s.narrative) parts.push(String(s.narrative).trim(), '');
12
+ if (s.mixpanelReport && typeof s.mixpanelReport === 'object') {
13
+ parts.push('How to see it in Mixpanel:');
14
+ for (const [k, v] of Object.entries(s.mixpanelReport)) {
15
+ parts.push(`- **${k}**: ${typeof v === 'string' ? v : JSON.stringify(v)}`);
16
+ }
17
+ parts.push('');
18
+ }
19
+ if (Array.isArray(s.intentionalDeviations) && s.intentionalDeviations.length) {
20
+ parts.push('Notes:', ...s.intentionalDeviations.map((d) => `- ${d}`), '');
21
+ }
22
+ }
23
+ } else if (comments.hookStories) {
24
+ parts.push('## Engineered Behaviors', '', comments.hookStories, '');
25
+ }
26
+
27
+ parts.push('## Schema', '');
28
+ const events = config.events || [];
29
+ parts.push(`### Events (${events.length})`);
30
+ for (const e of events) {
31
+ const props = e.properties ? Object.keys(e.properties).join(', ') : '';
32
+ const weight = e.weight != null ? ` (weight ${e.weight})` : '';
33
+ parts.push(`- ${e.event}${weight}${props ? ` — ${props}` : ''}`);
34
+ }
35
+ parts.push('');
36
+
37
+ const funnels = config.funnels || [];
38
+ if (funnels.length) {
39
+ parts.push(`### Funnels (${funnels.length})`);
40
+ for (const f of funnels) {
41
+ const seq = (f.sequence || []).join(' → ');
42
+ const rate = f.conversionRate != null ? ` (${f.conversionRate}%)` : '';
43
+ parts.push(`- ${f.name || '(unnamed)'}: ${seq}${rate}`);
44
+ }
45
+ parts.push('');
46
+ }
47
+
48
+ if (groupKeys.length) {
49
+ parts.push('### Group keys', ...groupKeys.map((g) => `- ${g.property_name} (${g.display_name})`), '');
50
+ }
51
+
52
+ const standaloneEvents = config.standaloneEvents || [];
53
+ if (standaloneEvents.length) {
54
+ parts.push(`### Standalone events (${standaloneEvents.length})`);
55
+ parts.push('Cadence streams use the normal event send path. Their synthetic distinct_id values are never people counts or identity-model evidence.', '');
56
+ for (const stream of standaloneEvents) {
57
+ const dimensions = Object.entries(stream.dimensions || {}).map(([key, values]) => `${key} (${values.length} values)`);
58
+ const properties = Object.keys(stream.properties || {});
59
+ const identity = stream.distinctIdFrom ? `dimension ${stream.distinctIdFrom}` : 'event name';
60
+ parts.push(`- ${stream.event}: cadence ${stream.cadence ?? 'day'}; dimensions ${contextList(dimensions)}; properties ${contextList(properties)}; synthetic distinct_id from ${identity}`);
61
+ }
62
+ parts.push('');
63
+ }
64
+
65
+ const warehouseMetrics = config.warehouseMetrics || [];
66
+ if (warehouseMetrics.length) {
67
+ parts.push(`### Warehouse metrics (${warehouseMetrics.length})`);
68
+ parts.push('Sum adds buckets; LastValue reads the latest snapshot.', '');
69
+ parts.push('Separate /warehouse-metrics deployment is required; provisioning and sendToMixpanel do not load warehouse tables or save warehouse metrics.', '');
70
+ for (const metric of warehouseMetrics) {
71
+ const type = metric.type ?? 'additive';
72
+ const source = metric.source;
73
+ const aggregation = type === 'point-in-time' ? 'LastValue' : 'Sum';
74
+ parts.push(`- ${metric.name}: type ${type}; grain ${metric.grain ?? 'day'}; history ${metric.history ?? 0} periods before the event window; sparse ${metric.sparse ?? false}; Mixpanel aggregation ${aggregation}`);
75
+ if (type === 'point-in-time') parts.push(` baseline ${metric.baseline ?? 0} (point-in-time starting level)`);
76
+ parts.push(` source events ${contextList(source.event)}; minus ${contextList(source.minus)}; measure ${source.measure ?? 'count'}; property ${source.property ?? '(none)'}; where ${source.where ? 'present (function, not evaluated)' : '(none)'}`);
77
+ parts.push(` columns: time ${metric.timeColumn ?? 'date'}; value ${metric.valueColumn ?? 'value'}; groupBy ${contextList(source.groupBy)}; extra ${contextList(Object.keys(metric.columns || {}))}`);
78
+ }
79
+ parts.push('');
80
+ }
81
+
82
+ let md = parts.join('\n');
83
+ if (md.length > 50000) md = md.slice(0, 49900) + '\n\n…(truncated to 50,000 chars)';
84
+ return md;
85
+ }
86
+
87
+ function contextList(value) {
88
+ return (Array.isArray(value) ? value.join(', ') : value) || '(none)';
89
+ }
@@ -32,6 +32,7 @@ import path, { dirname, resolve } from 'path';
32
32
  import { fileURLToPath, pathToFileURL } from 'url';
33
33
  import dotenv from 'dotenv';
34
34
  import { loadFromFile, extractComments } from '../../../index.js';
35
+ import { buildContext } from './context.mjs';
35
36
 
36
37
  const __dirname = dirname(fileURLToPath(import.meta.url));
37
38
  const REPO_ROOT = resolve(__dirname, '../../../');
@@ -204,66 +205,6 @@ function isoInDays(days) {
204
205
  return new Date(ms).toISOString().replace(/\.\d{3}Z$/, 'Z');
205
206
  }
206
207
 
207
- function buildContext(name, config, comments, groupKeys, stories) {
208
- const parts = [`# ${name}`, ''];
209
- if (comments.overview) parts.push(comments.overview, '');
210
-
211
- // Preferred source: the stories export — each story's narrative is the
212
- // human-readable behavior and mixpanelReport (free-form object) points at
213
- // the report where it shows. Comment scrape is the fallback for dungeons
214
- // without stories (schema-only or pre-1.6).
215
- if (stories?.length) {
216
- parts.push('## Engineered Behaviors', '');
217
- parts.push(`${stories.length} machine-verified story patterns (from the dungeon's \`stories\` export):`, '');
218
- for (const s of stories) {
219
- const arch = s.archetype ? ` — ${s.archetype}` : '';
220
- parts.push(`### ${s.id}${arch}`, '');
221
- if (s.narrative) parts.push(String(s.narrative).trim(), '');
222
- if (s.mixpanelReport && typeof s.mixpanelReport === 'object') {
223
- parts.push('How to see it in Mixpanel:');
224
- for (const [k, v] of Object.entries(s.mixpanelReport)) {
225
- parts.push(`- **${k}**: ${typeof v === 'string' ? v : JSON.stringify(v)}`);
226
- }
227
- parts.push('');
228
- }
229
- if (Array.isArray(s.intentionalDeviations) && s.intentionalDeviations.length) {
230
- parts.push('Notes:', ...s.intentionalDeviations.map((d) => `- ${d}`), '');
231
- }
232
- }
233
- } else if (comments.hookStories) {
234
- parts.push('## Engineered Behaviors', '', comments.hookStories, '');
235
- }
236
-
237
- parts.push('## Schema', '');
238
- const events = config.events || [];
239
- parts.push(`### Events (${events.length})`);
240
- for (const e of events) {
241
- const props = e.properties ? Object.keys(e.properties).join(', ') : '';
242
- const weight = e.weight != null ? ` (weight ${e.weight})` : '';
243
- parts.push(`- ${e.event}${weight}${props ? ` — ${props}` : ''}`);
244
- }
245
- parts.push('');
246
-
247
- const funnels = config.funnels || [];
248
- if (funnels.length) {
249
- parts.push(`### Funnels (${funnels.length})`);
250
- for (const f of funnels) {
251
- const seq = (f.sequence || []).join(' → ');
252
- const rate = f.conversionRate != null ? ` (${f.conversionRate}%)` : '';
253
- parts.push(`- ${f.name || '(unnamed)'}: ${seq}${rate}`);
254
- }
255
- parts.push('');
256
- }
257
-
258
- if (groupKeys.length) {
259
- parts.push('### Group keys', ...groupKeys.map((g) => `- ${g.property_name} (${g.display_name})`), '');
260
- }
261
-
262
- let md = parts.join('\n');
263
- if (md.length > 50000) md = md.slice(0, 49900) + '\n\n…(truncated to 50,000 chars)';
264
- return md;
265
- }
266
-
267
208
  function writeBackCredentials(p, creds) {
268
209
  let src = readFileSync(p, 'utf-8');
269
210
  const block =
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: headless-build
3
3
  description: Use after a dungeon's data is loaded into a real Mixpanel project — builds the full demoable environment with mixpanel-headless (dashboards, charts, Lexicon, cohorts, custom properties, behaviors/metrics/formulas, annotations) targeted at that dungeon's engineered stories, then verifies the stories still read live. Final step after create-dungeon / write-hooks / verify-dungeon / create-project.
4
- argument-hint: [dungeon path, e.g. dungeons/user/nyc-dcp/nyc-dcp.js]
4
+ argument-hint: '[dungeon path, e.g. dungeons/user/nyc-dcp/nyc-dcp.js]'
5
5
  model: claude-opus-4-6
6
6
  effort: max
7
7
  ---
@@ -14,6 +14,23 @@ Last step of the pipeline:
14
14
  /create-dungeon → /write-hooks → /verify-dungeon → /create-project → /headless-build
15
15
  ```
16
16
 
17
+ For `warehouseMetrics`, insert `/warehouse-metrics` after verification and project
18
+ provisioning, before this build. Use the verified local disk artifact prefix.
19
+ Ordinary event import loads `standaloneEvents` but does not deploy warehouse tables.
20
+
21
+ Keep cadence and warehouse metrics separate from people analyses. Synthetic
22
+ standalone `distinct_id` values identify series; never count them as users or
23
+ include them in funnels, retention, or identity stitching. Build aggregate reports
24
+ from their declared numeric properties and dimensions.
25
+
26
+ For warehouse-backed charts, read the manifest and saved metric ids from the
27
+ warehouse handoff. `history` may extend the metric window before the user-event
28
+ window; preserve that range and label synthetic backfill. Check saved definitions
29
+ and `previewWarehouseMetric` results before narrating values. A successful
30
+ `refreshWarehouseMetric` only invalidates cache; it does not execute the query.
31
+ Use the existing `/macro/setup-bq-warehouse` flow through `/warehouse-metrics`
32
+ for source setup. Do not reimplement source creation or IAM grants in build code.
33
+
17
34
  The project already has data. This skill builds everything a human sees: themed
18
35
  dashboards whose narrative is computed from the live data, an annotated Lexicon,
19
36
  saved cohorts and custom properties, saved behaviors/metrics/formulas, and
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: powertools
3
3
  description: Use when any task needs the Mixpanel Power Tools API ("use powertools") — schema export (get-schema), event volumes, project CRUD, query methods, macros, or snapshotting a prod project's schema to copy it into a dungeon. Companion to create-project (which handles provisioning specifically).
4
- argument-hint: [what to do, e.g. "get schema for project 12345" or "copy project 12345 into a dungeon"]
4
+ argument-hint: '[what to do, e.g. "get schema for project 12345" or "copy project 12345 into a dungeon"]'
5
5
  ---
6
6
 
7
7
  # Power Tools API
@@ -43,6 +43,25 @@ node .claude/skills/powertools/snapshot-project.mjs <project_id> --bearer <token
43
43
 
44
44
  ## Endpoint catalog (the useful subset)
45
45
 
46
+ ### Warehouse handoff (v1.8.0)
47
+
48
+ For a dungeon with `warehouseMetrics`, use `/warehouse-metrics` after local
49
+ verification produces disk tables and a manifest, and `/create-project` writes
50
+ the project id. Pass the explicit verified `--data-prefix`; preserve those files.
51
+ That skill owns load, source setup, SQL preview, and metric create-or-skip.
52
+ Reuse `/macro/setup-bq-warehouse` through `pt.mjs`; do not reimplement its
53
+ source creation, GCP IAM grant, or dataset ACL handling here.
54
+
55
+ Warehouse metric endpoints include `getWarehouseMetrics`, `previewWarehouseMetric`,
56
+ `createWarehouseMetric`, and `refreshWarehouseMetric` in the `/crud` family.
57
+ Read endpoint docs before live use. Preview executes the read query; create does
58
+ not validate SQL, and refresh only invalidates cache. A docs-route 404 uses the
59
+ warehouse skill's manual-setup fallback; other errors must surface.
60
+
61
+ `standaloneEvents` imports as a separate event stream through the ordinary sender.
62
+ Its synthetic ids are series keys, so schema snapshots and dashboard counts must
63
+ distinguish cadence telemetry from user `events[]`. It is not a warehouse source.
64
+
46
65
  GET the path for full docs. Full list: GET `/` and GET `/macro`.
47
66
 
48
67
  **crud** — 187 endpoints. GET `/crud` for the full list.
@@ -0,0 +1,99 @@
1
+ ---
2
+ name: release-check
3
+ description: "Use before a dungeon-master release to audit versioned docs, skills, tests, determinism, package contents, and Git state; report release blockers and prepare an explicitly authorized PR and merge handoff."
4
+ argument-hint: "[version, e.g. 1.8.0] [optional base branch]"
5
+ ---
6
+
7
+ # Release check
8
+
9
+ Check the current checkout before release. Read `AGENTS.md`, `package.json`,
10
+ the target changelog entry, and its upgrade guide. Default to validation only.
11
+ Never publish to npm unless the user gives explicit authorization for npm publishing.
12
+ An instruction to merge a PR is not permission to publish a package.
13
+
14
+ ## Establish the release scope
15
+
16
+ 1. Inspect `git status --short`, the current branch, remotes, and recent history.
17
+ Preserve unrelated changes. Never reset or clean the worktree to make checks pass.
18
+ 2. Confirm the requested version matches `package.json`, the lockfile, changelog,
19
+ and guide. Do not invent a release date or bump a version without authorization.
20
+ 3. Compare the release diff against the base branch. Check exports, dependencies,
21
+ config defaults, compatibility notes, examples, and migration instructions.
22
+ 4. Inspect test setup before running it. This repo's suite prunes `data/` and `tmp/`.
23
+ If pending deployment artifacts exist, preserve them outside those directories
24
+ or obtain consent before running the suite. Do not silently delete verified inputs.
25
+
26
+ ## Run executable gates
27
+
28
+ Use `set -o pipefail` for every piped command. Keep all Vitest output behind
29
+ `2>&1 | tail -50`; a successful tail command alone is not passing evidence.
30
+
31
+ ```bash
32
+ set -o pipefail
33
+ npm test 2>&1 | tail -50
34
+ npm run typecheck
35
+ git diff --check
36
+ npx vitest run tests/unit/engine-shape-canary.test.js 2>&1 | tail -50
37
+ RUN_FULL_SWEEP=1 npx vitest run tests/e2e/engine-shape-full-sweep.test.js 2>&1 | tail -50
38
+ node tests/engine/smoke-test-all.mjs
39
+ npm pack --dry-run --json
40
+ ```
41
+
42
+ Run commands separately or chain with `&&` so a failure cannot disappear behind
43
+ a later success. Record exit codes, pass/skip counts, and the tested commit/diff.
44
+ The smoke script discovers vertical dungeons only. Verify changed technical or
45
+ customer fixtures separately with the existing verification runners.
46
+
47
+ For generation changes, require the event-stream determinism test. Compare runs
48
+ with the same seed, pinned window, and `concurrency: 1`; strip only `insert_id`.
49
+ Use sequential test cases because the RNG is shared. Include warehouse noise in
50
+ warehouse determinism checks. Never accept an unrun or failing determinism gate.
51
+
52
+ ## Audit docs and skills
53
+
54
+ - Run `tests/e2e/skills-contract.test.js`. Parse YAML frontmatter rather than
55
+ guessing from appearance: names match folders, descriptions are strings, and
56
+ `argument-hint` is a quoted string.
57
+ - The canonical skills live in `.claude/skills`. Verify `.agents/skills` and
58
+ `.github/skills` resolve to that directory. Do not duplicate skill content.
59
+ - Check README, HOOKS, type comments, changelog, and guide against actual code.
60
+ Do not carry stale test counts, old skill paths, or historical operational
61
+ failures forward as current release claims.
62
+ - Follow the complete handoff: author, optional hooks, verify, provision,
63
+ generate/import, optional `/warehouse-metrics`, then headless build.
64
+ - Distinguish user events, identity-less `standaloneEvents`, and warehouse rows.
65
+ Synthetic IDs are not people. Standalone hooks return retained records;
66
+ warehouse hooks mutate rows and ignore returns. Preserve deployment artifacts.
67
+ - If API integrations changed, read the current endpoint docs and local handback.
68
+ Distinguish docs discovery, mocked execution, dry-run, and live verification.
69
+ Live writes require explicit authorization. Never claim an endpoint GET proves
70
+ authenticated write permission or end-to-end deployment.
71
+
72
+ ## Inspect the package and internal files
73
+
74
+ Read the file list from `npm pack --dry-run --json`. Confirm new runtime modules,
75
+ dependencies, scripts, and canonical skill files ship. Reject credentials,
76
+ customer data, `plans/`, `research/`, `.superpowers/`, or obsolete skill paths.
77
+ Check symlink handling in the package separately from workspace discovery.
78
+
79
+ Verify `git ls-files .superpowers` is empty and `.gitignore` covers the directory.
80
+ Ignoring an already tracked file does not untrack it. Obtain authorization before
81
+ removing index entries; preserve local reports. Archive completed plan folders
82
+ whole under `plans/archived/`; leave active or ambiguous work in place.
83
+
84
+ ## Report and optional shipping handoff
85
+
86
+ Report blockers first, then evidence, skipped checks, compatibility changes,
87
+ and remaining operational limits. Do not fix unrelated failures or weaken tests.
88
+
89
+ Only when explicitly requested: inspect all staged files for secrets, commit the
90
+ authorized changes, push the feature branch, create or reuse its PR, and inspect
91
+ CI and mergeability. Require successful completed checks for the current PR head
92
+ before squash merge. If no CI checks are configured, report that explicitly and
93
+ use recorded local release gates; do not describe absent CI as passing. A failed
94
+ release gate requires investigation and an explicit operator decision before shipping.
95
+ Never bypass protections or use admin merge
96
+ to hide a failing check. Confirm the PR is merged before switching local branches.
97
+ Fetch, switch to `main`, and pull with `--ff-only`; stop rather than discard local
98
+ changes or divergent commits. Report the PR URL, merge SHA, current branch, and
99
+ working-tree status. Leave npm publishing to the operator unless separately authorized.