@runchr/gstack-antigravity 0.1.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.

Potentially problematic release.


This version of @runchr/gstack-antigravity might be problematic. Click here for more details.

Files changed (297) hide show
  1. package/.agents/rules/ETHOS.md +129 -0
  2. package/.agents/rules/global-gstack.md +117 -0
  3. package/.agents/rules/persona-gstack-autoplan.md +14 -0
  4. package/.agents/rules/persona-gstack-benchmark.md +14 -0
  5. package/.agents/rules/persona-gstack-browse.md +14 -0
  6. package/.agents/rules/persona-gstack-canary.md +14 -0
  7. package/.agents/rules/persona-gstack-careful.md +14 -0
  8. package/.agents/rules/persona-gstack-codex.md +14 -0
  9. package/.agents/rules/persona-gstack-cso.md +14 -0
  10. package/.agents/rules/persona-gstack-design-consultation.md +14 -0
  11. package/.agents/rules/persona-gstack-design-review.md +14 -0
  12. package/.agents/rules/persona-gstack-document-release.md +14 -0
  13. package/.agents/rules/persona-gstack-freeze.md +14 -0
  14. package/.agents/rules/persona-gstack-gstack-upgrade.md +14 -0
  15. package/.agents/rules/persona-gstack-guard.md +14 -0
  16. package/.agents/rules/persona-gstack-investigate.md +14 -0
  17. package/.agents/rules/persona-gstack-land-and-deploy.md +14 -0
  18. package/.agents/rules/persona-gstack-office-hours.md +14 -0
  19. package/.agents/rules/persona-gstack-plan-ceo-review.md +14 -0
  20. package/.agents/rules/persona-gstack-plan-design-review.md +14 -0
  21. package/.agents/rules/persona-gstack-plan-eng-review.md +14 -0
  22. package/.agents/rules/persona-gstack-qa-only.md +14 -0
  23. package/.agents/rules/persona-gstack-qa.md +14 -0
  24. package/.agents/rules/persona-gstack-retro.md +14 -0
  25. package/.agents/rules/persona-gstack-review.md +14 -0
  26. package/.agents/rules/persona-gstack-setup-browser-cookies.md +14 -0
  27. package/.agents/rules/persona-gstack-setup-deploy.md +14 -0
  28. package/.agents/rules/persona-gstack-ship.md +14 -0
  29. package/.agents/rules/persona-gstack-unfreeze.md +14 -0
  30. package/.agents/rules/persona-gstack.md +40 -0
  31. package/.agents/rules/recursive-identities.md +22 -0
  32. package/.agents/workflows/autoplan.md +30 -0
  33. package/.agents/workflows/benchmark.md +31 -0
  34. package/.agents/workflows/browse.md +26 -0
  35. package/.agents/workflows/canary.md +33 -0
  36. package/.agents/workflows/careful.md +22 -0
  37. package/.agents/workflows/codex.md +36 -0
  38. package/.agents/workflows/cso.md +29 -0
  39. package/.agents/workflows/design-consultation.md +28 -0
  40. package/.agents/workflows/design-review.md +28 -0
  41. package/.agents/workflows/document-release.md +32 -0
  42. package/.agents/workflows/freeze.md +17 -0
  43. package/.agents/workflows/gstack-upgrade.md +54 -0
  44. package/.agents/workflows/gstack.md +56 -0
  45. package/.agents/workflows/guard.md +18 -0
  46. package/.agents/workflows/investigate.md +37 -0
  47. package/.agents/workflows/land-and-deploy.md +35 -0
  48. package/.agents/workflows/office-hours.md +27 -0
  49. package/.agents/workflows/plan-ceo-review.md +34 -0
  50. package/.agents/workflows/plan-design-review.md +31 -0
  51. package/.agents/workflows/plan-eng-review.md +28 -0
  52. package/.agents/workflows/qa-only.md +28 -0
  53. package/.agents/workflows/qa.md +73 -0
  54. package/.agents/workflows/retro.md +34 -0
  55. package/.agents/workflows/review.md +30 -0
  56. package/.agents/workflows/setup-browser-cookies.md +15 -0
  57. package/.agents/workflows/setup-cookies.md +8 -0
  58. package/.agents/workflows/setup-deploy.md +21 -0
  59. package/.agents/workflows/ship.md +93 -0
  60. package/.agents/workflows/unfreeze.md +12 -0
  61. package/LICENSE +22 -0
  62. package/README.md +189 -0
  63. package/README_KO.md +191 -0
  64. package/bin/install.js +105 -0
  65. package/gstack-origin/.agents/skills/gstack/SKILL.md +651 -0
  66. package/gstack-origin/.agents/skills/gstack-autoplan/SKILL.md +678 -0
  67. package/gstack-origin/.agents/skills/gstack-benchmark/SKILL.md +482 -0
  68. package/gstack-origin/.agents/skills/gstack-browse/SKILL.md +511 -0
  69. package/gstack-origin/.agents/skills/gstack-canary/SKILL.md +486 -0
  70. package/gstack-origin/.agents/skills/gstack-careful/SKILL.md +50 -0
  71. package/gstack-origin/.agents/skills/gstack-cso/SKILL.md +607 -0
  72. package/gstack-origin/.agents/skills/gstack-design-consultation/SKILL.md +615 -0
  73. package/gstack-origin/.agents/skills/gstack-design-review/SKILL.md +988 -0
  74. package/gstack-origin/.agents/skills/gstack-document-release/SKILL.md +604 -0
  75. package/gstack-origin/.agents/skills/gstack-freeze/SKILL.md +67 -0
  76. package/gstack-origin/.agents/skills/gstack-guard/SKILL.md +62 -0
  77. package/gstack-origin/.agents/skills/gstack-investigate/SKILL.md +415 -0
  78. package/gstack-origin/.agents/skills/gstack-land-and-deploy/SKILL.md +873 -0
  79. package/gstack-origin/.agents/skills/gstack-office-hours/SKILL.md +986 -0
  80. package/gstack-origin/.agents/skills/gstack-plan-ceo-review/SKILL.md +1268 -0
  81. package/gstack-origin/.agents/skills/gstack-plan-design-review/SKILL.md +668 -0
  82. package/gstack-origin/.agents/skills/gstack-plan-eng-review/SKILL.md +826 -0
  83. package/gstack-origin/.agents/skills/gstack-qa/SKILL.md +1006 -0
  84. package/gstack-origin/.agents/skills/gstack-qa-only/SKILL.md +626 -0
  85. package/gstack-origin/.agents/skills/gstack-retro/SKILL.md +1065 -0
  86. package/gstack-origin/.agents/skills/gstack-review/SKILL.md +704 -0
  87. package/gstack-origin/.agents/skills/gstack-setup-browser-cookies/SKILL.md +325 -0
  88. package/gstack-origin/.agents/skills/gstack-setup-deploy/SKILL.md +450 -0
  89. package/gstack-origin/.agents/skills/gstack-ship/SKILL.md +1312 -0
  90. package/gstack-origin/.agents/skills/gstack-unfreeze/SKILL.md +36 -0
  91. package/gstack-origin/.agents/skills/gstack-upgrade/SKILL.md +220 -0
  92. package/gstack-origin/.env.example +5 -0
  93. package/gstack-origin/.github/workflows/skill-docs.yml +17 -0
  94. package/gstack-origin/AGENTS.md +49 -0
  95. package/gstack-origin/ARCHITECTURE.md +359 -0
  96. package/gstack-origin/BROWSER.md +271 -0
  97. package/gstack-origin/CHANGELOG.md +800 -0
  98. package/gstack-origin/CLAUDE.md +284 -0
  99. package/gstack-origin/CONTRIBUTING.md +370 -0
  100. package/gstack-origin/ETHOS.md +129 -0
  101. package/gstack-origin/LICENSE +21 -0
  102. package/gstack-origin/README.md +228 -0
  103. package/gstack-origin/SKILL.md +657 -0
  104. package/gstack-origin/SKILL.md.tmpl +281 -0
  105. package/gstack-origin/TODOS.md +564 -0
  106. package/gstack-origin/VERSION +1 -0
  107. package/gstack-origin/autoplan/SKILL.md +689 -0
  108. package/gstack-origin/autoplan/SKILL.md.tmpl +416 -0
  109. package/gstack-origin/benchmark/SKILL.md +489 -0
  110. package/gstack-origin/benchmark/SKILL.md.tmpl +233 -0
  111. package/gstack-origin/bin/dev-setup +68 -0
  112. package/gstack-origin/bin/dev-teardown +56 -0
  113. package/gstack-origin/bin/gstack-analytics +191 -0
  114. package/gstack-origin/bin/gstack-community-dashboard +113 -0
  115. package/gstack-origin/bin/gstack-config +38 -0
  116. package/gstack-origin/bin/gstack-diff-scope +71 -0
  117. package/gstack-origin/bin/gstack-global-discover.ts +591 -0
  118. package/gstack-origin/bin/gstack-repo-mode +93 -0
  119. package/gstack-origin/bin/gstack-review-log +9 -0
  120. package/gstack-origin/bin/gstack-review-read +12 -0
  121. package/gstack-origin/bin/gstack-slug +15 -0
  122. package/gstack-origin/bin/gstack-telemetry-log +158 -0
  123. package/gstack-origin/bin/gstack-telemetry-sync +127 -0
  124. package/gstack-origin/bin/gstack-update-check +196 -0
  125. package/gstack-origin/browse/SKILL.md +517 -0
  126. package/gstack-origin/browse/SKILL.md.tmpl +141 -0
  127. package/gstack-origin/browse/bin/find-browse +21 -0
  128. package/gstack-origin/browse/bin/remote-slug +14 -0
  129. package/gstack-origin/browse/scripts/build-node-server.sh +48 -0
  130. package/gstack-origin/browse/src/browser-manager.ts +634 -0
  131. package/gstack-origin/browse/src/buffers.ts +137 -0
  132. package/gstack-origin/browse/src/bun-polyfill.cjs +109 -0
  133. package/gstack-origin/browse/src/cli.ts +420 -0
  134. package/gstack-origin/browse/src/commands.ts +111 -0
  135. package/gstack-origin/browse/src/config.ts +150 -0
  136. package/gstack-origin/browse/src/cookie-import-browser.ts +417 -0
  137. package/gstack-origin/browse/src/cookie-picker-routes.ts +207 -0
  138. package/gstack-origin/browse/src/cookie-picker-ui.ts +541 -0
  139. package/gstack-origin/browse/src/find-browse.ts +61 -0
  140. package/gstack-origin/browse/src/meta-commands.ts +269 -0
  141. package/gstack-origin/browse/src/platform.ts +17 -0
  142. package/gstack-origin/browse/src/read-commands.ts +335 -0
  143. package/gstack-origin/browse/src/server.ts +369 -0
  144. package/gstack-origin/browse/src/snapshot.ts +398 -0
  145. package/gstack-origin/browse/src/url-validation.ts +91 -0
  146. package/gstack-origin/browse/src/write-commands.ts +352 -0
  147. package/gstack-origin/browse/test/bun-polyfill.test.ts +72 -0
  148. package/gstack-origin/browse/test/commands.test.ts +1836 -0
  149. package/gstack-origin/browse/test/config.test.ts +250 -0
  150. package/gstack-origin/browse/test/cookie-import-browser.test.ts +397 -0
  151. package/gstack-origin/browse/test/cookie-picker-routes.test.ts +205 -0
  152. package/gstack-origin/browse/test/find-browse.test.ts +50 -0
  153. package/gstack-origin/browse/test/fixtures/basic.html +33 -0
  154. package/gstack-origin/browse/test/fixtures/cursor-interactive.html +22 -0
  155. package/gstack-origin/browse/test/fixtures/dialog.html +15 -0
  156. package/gstack-origin/browse/test/fixtures/empty.html +2 -0
  157. package/gstack-origin/browse/test/fixtures/forms.html +55 -0
  158. package/gstack-origin/browse/test/fixtures/qa-eval-checkout.html +108 -0
  159. package/gstack-origin/browse/test/fixtures/qa-eval-spa.html +98 -0
  160. package/gstack-origin/browse/test/fixtures/qa-eval.html +51 -0
  161. package/gstack-origin/browse/test/fixtures/responsive.html +49 -0
  162. package/gstack-origin/browse/test/fixtures/snapshot.html +55 -0
  163. package/gstack-origin/browse/test/fixtures/spa.html +24 -0
  164. package/gstack-origin/browse/test/fixtures/states.html +17 -0
  165. package/gstack-origin/browse/test/fixtures/upload.html +25 -0
  166. package/gstack-origin/browse/test/gstack-config.test.ts +125 -0
  167. package/gstack-origin/browse/test/gstack-update-check.test.ts +467 -0
  168. package/gstack-origin/browse/test/handoff.test.ts +235 -0
  169. package/gstack-origin/browse/test/path-validation.test.ts +63 -0
  170. package/gstack-origin/browse/test/platform.test.ts +37 -0
  171. package/gstack-origin/browse/test/snapshot.test.ts +467 -0
  172. package/gstack-origin/browse/test/test-server.ts +57 -0
  173. package/gstack-origin/browse/test/url-validation.test.ts +72 -0
  174. package/gstack-origin/canary/SKILL.md +493 -0
  175. package/gstack-origin/canary/SKILL.md.tmpl +220 -0
  176. package/gstack-origin/careful/SKILL.md +59 -0
  177. package/gstack-origin/careful/SKILL.md.tmpl +57 -0
  178. package/gstack-origin/careful/bin/check-careful.sh +112 -0
  179. package/gstack-origin/codex/SKILL.md +677 -0
  180. package/gstack-origin/codex/SKILL.md.tmpl +356 -0
  181. package/gstack-origin/conductor.json +6 -0
  182. package/gstack-origin/cso/SKILL.md +615 -0
  183. package/gstack-origin/cso/SKILL.md.tmpl +376 -0
  184. package/gstack-origin/design-consultation/SKILL.md +625 -0
  185. package/gstack-origin/design-consultation/SKILL.md.tmpl +369 -0
  186. package/gstack-origin/design-review/SKILL.md +998 -0
  187. package/gstack-origin/design-review/SKILL.md.tmpl +262 -0
  188. package/gstack-origin/docs/images/github-2013.png +0 -0
  189. package/gstack-origin/docs/images/github-2026.png +0 -0
  190. package/gstack-origin/docs/skills.md +877 -0
  191. package/gstack-origin/document-release/SKILL.md +613 -0
  192. package/gstack-origin/document-release/SKILL.md.tmpl +357 -0
  193. package/gstack-origin/freeze/SKILL.md +82 -0
  194. package/gstack-origin/freeze/SKILL.md.tmpl +80 -0
  195. package/gstack-origin/freeze/bin/check-freeze.sh +68 -0
  196. package/gstack-origin/gstack-upgrade/SKILL.md +226 -0
  197. package/gstack-origin/gstack-upgrade/SKILL.md.tmpl +224 -0
  198. package/gstack-origin/guard/SKILL.md +82 -0
  199. package/gstack-origin/guard/SKILL.md.tmpl +80 -0
  200. package/gstack-origin/investigate/SKILL.md +435 -0
  201. package/gstack-origin/investigate/SKILL.md.tmpl +196 -0
  202. package/gstack-origin/land-and-deploy/SKILL.md +880 -0
  203. package/gstack-origin/land-and-deploy/SKILL.md.tmpl +575 -0
  204. package/gstack-origin/office-hours/SKILL.md +996 -0
  205. package/gstack-origin/office-hours/SKILL.md.tmpl +624 -0
  206. package/gstack-origin/package.json +55 -0
  207. package/gstack-origin/plan-ceo-review/SKILL.md +1277 -0
  208. package/gstack-origin/plan-ceo-review/SKILL.md.tmpl +838 -0
  209. package/gstack-origin/plan-design-review/SKILL.md +676 -0
  210. package/gstack-origin/plan-design-review/SKILL.md.tmpl +314 -0
  211. package/gstack-origin/plan-eng-review/SKILL.md +836 -0
  212. package/gstack-origin/plan-eng-review/SKILL.md.tmpl +279 -0
  213. package/gstack-origin/qa/SKILL.md +1016 -0
  214. package/gstack-origin/qa/SKILL.md.tmpl +316 -0
  215. package/gstack-origin/qa/references/issue-taxonomy.md +85 -0
  216. package/gstack-origin/qa/templates/qa-report-template.md +126 -0
  217. package/gstack-origin/qa-only/SKILL.md +633 -0
  218. package/gstack-origin/qa-only/SKILL.md.tmpl +101 -0
  219. package/gstack-origin/retro/SKILL.md +1072 -0
  220. package/gstack-origin/retro/SKILL.md.tmpl +833 -0
  221. package/gstack-origin/review/SKILL.md +849 -0
  222. package/gstack-origin/review/SKILL.md.tmpl +259 -0
  223. package/gstack-origin/review/TODOS-format.md +62 -0
  224. package/gstack-origin/review/checklist.md +190 -0
  225. package/gstack-origin/review/design-checklist.md +132 -0
  226. package/gstack-origin/review/greptile-triage.md +220 -0
  227. package/gstack-origin/scripts/analytics.ts +190 -0
  228. package/gstack-origin/scripts/dev-skill.ts +82 -0
  229. package/gstack-origin/scripts/eval-compare.ts +96 -0
  230. package/gstack-origin/scripts/eval-list.ts +116 -0
  231. package/gstack-origin/scripts/eval-select.ts +86 -0
  232. package/gstack-origin/scripts/eval-summary.ts +187 -0
  233. package/gstack-origin/scripts/eval-watch.ts +172 -0
  234. package/gstack-origin/scripts/gen-skill-docs.ts +2414 -0
  235. package/gstack-origin/scripts/skill-check.ts +167 -0
  236. package/gstack-origin/setup +269 -0
  237. package/gstack-origin/setup-browser-cookies/SKILL.md +330 -0
  238. package/gstack-origin/setup-browser-cookies/SKILL.md.tmpl +74 -0
  239. package/gstack-origin/setup-deploy/SKILL.md +459 -0
  240. package/gstack-origin/setup-deploy/SKILL.md.tmpl +220 -0
  241. package/gstack-origin/ship/SKILL.md +1457 -0
  242. package/gstack-origin/ship/SKILL.md.tmpl +528 -0
  243. package/gstack-origin/supabase/config.sh +10 -0
  244. package/gstack-origin/supabase/functions/community-pulse/index.ts +59 -0
  245. package/gstack-origin/supabase/functions/telemetry-ingest/index.ts +135 -0
  246. package/gstack-origin/supabase/functions/update-check/index.ts +37 -0
  247. package/gstack-origin/supabase/migrations/001_telemetry.sql +89 -0
  248. package/gstack-origin/test/analytics.test.ts +277 -0
  249. package/gstack-origin/test/codex-e2e.test.ts +197 -0
  250. package/gstack-origin/test/fixtures/coverage-audit-fixture.ts +76 -0
  251. package/gstack-origin/test/fixtures/eval-baselines.json +7 -0
  252. package/gstack-origin/test/fixtures/qa-eval-checkout-ground-truth.json +43 -0
  253. package/gstack-origin/test/fixtures/qa-eval-ground-truth.json +43 -0
  254. package/gstack-origin/test/fixtures/qa-eval-spa-ground-truth.json +43 -0
  255. package/gstack-origin/test/fixtures/review-eval-design-slop.css +86 -0
  256. package/gstack-origin/test/fixtures/review-eval-design-slop.html +41 -0
  257. package/gstack-origin/test/fixtures/review-eval-enum-diff.rb +30 -0
  258. package/gstack-origin/test/fixtures/review-eval-enum.rb +27 -0
  259. package/gstack-origin/test/fixtures/review-eval-vuln.rb +14 -0
  260. package/gstack-origin/test/gemini-e2e.test.ts +173 -0
  261. package/gstack-origin/test/gen-skill-docs.test.ts +1049 -0
  262. package/gstack-origin/test/global-discover.test.ts +187 -0
  263. package/gstack-origin/test/helpers/codex-session-runner.ts +282 -0
  264. package/gstack-origin/test/helpers/e2e-helpers.ts +239 -0
  265. package/gstack-origin/test/helpers/eval-store.test.ts +548 -0
  266. package/gstack-origin/test/helpers/eval-store.ts +689 -0
  267. package/gstack-origin/test/helpers/gemini-session-runner.test.ts +104 -0
  268. package/gstack-origin/test/helpers/gemini-session-runner.ts +201 -0
  269. package/gstack-origin/test/helpers/llm-judge.ts +130 -0
  270. package/gstack-origin/test/helpers/observability.test.ts +283 -0
  271. package/gstack-origin/test/helpers/session-runner.test.ts +96 -0
  272. package/gstack-origin/test/helpers/session-runner.ts +357 -0
  273. package/gstack-origin/test/helpers/skill-parser.ts +206 -0
  274. package/gstack-origin/test/helpers/touchfiles.ts +260 -0
  275. package/gstack-origin/test/hook-scripts.test.ts +373 -0
  276. package/gstack-origin/test/skill-e2e-browse.test.ts +293 -0
  277. package/gstack-origin/test/skill-e2e-deploy.test.ts +279 -0
  278. package/gstack-origin/test/skill-e2e-design.test.ts +614 -0
  279. package/gstack-origin/test/skill-e2e-plan.test.ts +538 -0
  280. package/gstack-origin/test/skill-e2e-qa-bugs.test.ts +194 -0
  281. package/gstack-origin/test/skill-e2e-qa-workflow.test.ts +412 -0
  282. package/gstack-origin/test/skill-e2e-review.test.ts +535 -0
  283. package/gstack-origin/test/skill-e2e-workflow.test.ts +586 -0
  284. package/gstack-origin/test/skill-e2e.test.ts +3325 -0
  285. package/gstack-origin/test/skill-llm-eval.test.ts +787 -0
  286. package/gstack-origin/test/skill-parser.test.ts +179 -0
  287. package/gstack-origin/test/skill-routing-e2e.test.ts +605 -0
  288. package/gstack-origin/test/skill-validation.test.ts +1520 -0
  289. package/gstack-origin/test/telemetry.test.ts +278 -0
  290. package/gstack-origin/test/touchfiles.test.ts +262 -0
  291. package/gstack-origin/unfreeze/SKILL.md +40 -0
  292. package/gstack-origin/unfreeze/SKILL.md.tmpl +38 -0
  293. package/package.json +38 -0
  294. package/scripts/install-antigravity-skill.ps1 +33 -0
  295. package/scripts/install-antigravity-skill.sh +41 -0
  296. package/scripts/sync-gstack-origin.ps1 +37 -0
  297. package/scripts/sync-gstack-origin.sh +35 -0
@@ -0,0 +1,135 @@
1
+ // gstack telemetry-ingest edge function
2
+ // Validates and inserts a batch of telemetry events.
3
+ // Called by bin/gstack-telemetry-sync.
4
+
5
+ import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
6
+
7
+ interface TelemetryEvent {
8
+ v: number;
9
+ ts: string;
10
+ event_type: string;
11
+ skill: string;
12
+ session_id?: string;
13
+ gstack_version: string;
14
+ os: string;
15
+ arch?: string;
16
+ duration_s?: number;
17
+ outcome: string;
18
+ error_class?: string;
19
+ used_browse?: boolean;
20
+ sessions?: number;
21
+ installation_id?: string;
22
+ }
23
+
24
+ const MAX_BATCH_SIZE = 100;
25
+ const MAX_PAYLOAD_BYTES = 50_000; // 50KB
26
+
27
+ Deno.serve(async (req) => {
28
+ if (req.method !== "POST") {
29
+ return new Response("POST required", { status: 405 });
30
+ }
31
+
32
+ // Check payload size
33
+ const contentLength = parseInt(req.headers.get("content-length") || "0");
34
+ if (contentLength > MAX_PAYLOAD_BYTES) {
35
+ return new Response("Payload too large", { status: 413 });
36
+ }
37
+
38
+ try {
39
+ const body = await req.json();
40
+ const events: TelemetryEvent[] = Array.isArray(body) ? body : [body];
41
+
42
+ if (events.length > MAX_BATCH_SIZE) {
43
+ return new Response(`Batch too large (max ${MAX_BATCH_SIZE})`, { status: 400 });
44
+ }
45
+
46
+ const supabase = createClient(
47
+ Deno.env.get("SUPABASE_URL") ?? "",
48
+ Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? ""
49
+ );
50
+
51
+ // Validate and transform events
52
+ const rows = [];
53
+ const installationUpserts: Map<string, { version: string; os: string }> = new Map();
54
+
55
+ for (const event of events) {
56
+ // Required fields
57
+ if (!event.ts || !event.gstack_version || !event.os || !event.outcome) {
58
+ continue; // skip malformed
59
+ }
60
+
61
+ // Validate schema version
62
+ if (event.v !== 1) continue;
63
+
64
+ // Validate event_type
65
+ const validTypes = ["skill_run", "upgrade_prompted", "upgrade_completed"];
66
+ if (!validTypes.includes(event.event_type)) continue;
67
+
68
+ rows.push({
69
+ schema_version: event.v,
70
+ event_type: event.event_type,
71
+ gstack_version: String(event.gstack_version).slice(0, 20),
72
+ os: String(event.os).slice(0, 20),
73
+ arch: event.arch ? String(event.arch).slice(0, 20) : null,
74
+ event_timestamp: event.ts,
75
+ skill: event.skill ? String(event.skill).slice(0, 50) : null,
76
+ session_id: event.session_id ? String(event.session_id).slice(0, 50) : null,
77
+ duration_s: typeof event.duration_s === "number" ? event.duration_s : null,
78
+ outcome: String(event.outcome).slice(0, 20),
79
+ error_class: event.error_class ? String(event.error_class).slice(0, 100) : null,
80
+ used_browse: event.used_browse === true,
81
+ concurrent_sessions: typeof event.sessions === "number" ? event.sessions : 1,
82
+ installation_id: event.installation_id ? String(event.installation_id).slice(0, 64) : null,
83
+ });
84
+
85
+ // Track installations for upsert
86
+ if (event.installation_id) {
87
+ installationUpserts.set(event.installation_id, {
88
+ version: event.gstack_version,
89
+ os: event.os,
90
+ });
91
+ }
92
+ }
93
+
94
+ if (rows.length === 0) {
95
+ return new Response(JSON.stringify({ inserted: 0 }), {
96
+ status: 200,
97
+ headers: { "Content-Type": "application/json" },
98
+ });
99
+ }
100
+
101
+ // Insert events
102
+ const { error: insertError } = await supabase
103
+ .from("telemetry_events")
104
+ .insert(rows);
105
+
106
+ if (insertError) {
107
+ return new Response(JSON.stringify({ error: insertError.message }), {
108
+ status: 500,
109
+ headers: { "Content-Type": "application/json" },
110
+ });
111
+ }
112
+
113
+ // Upsert installations (update last_seen)
114
+ for (const [id, data] of installationUpserts) {
115
+ await supabase
116
+ .from("installations")
117
+ .upsert(
118
+ {
119
+ installation_id: id,
120
+ last_seen: new Date().toISOString(),
121
+ gstack_version: data.version,
122
+ os: data.os,
123
+ },
124
+ { onConflict: "installation_id" }
125
+ );
126
+ }
127
+
128
+ return new Response(JSON.stringify({ inserted: rows.length }), {
129
+ status: 200,
130
+ headers: { "Content-Type": "application/json" },
131
+ });
132
+ } catch {
133
+ return new Response("Invalid request", { status: 400 });
134
+ }
135
+ });
@@ -0,0 +1,37 @@
1
+ // gstack update-check edge function
2
+ // Logs an install ping and returns the current latest version.
3
+ // Called by bin/gstack-update-check as a parallel background request.
4
+
5
+ import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
6
+
7
+ const CURRENT_VERSION = Deno.env.get("GSTACK_CURRENT_VERSION") || "0.6.4.1";
8
+
9
+ Deno.serve(async (req) => {
10
+ if (req.method !== "POST") {
11
+ return new Response(CURRENT_VERSION, { status: 200 });
12
+ }
13
+
14
+ try {
15
+ const { version, os } = await req.json();
16
+
17
+ if (!version || !os) {
18
+ return new Response(CURRENT_VERSION, { status: 200 });
19
+ }
20
+
21
+ const supabase = createClient(
22
+ Deno.env.get("SUPABASE_URL") ?? "",
23
+ Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? ""
24
+ );
25
+
26
+ // Log the update check (fire-and-forget)
27
+ await supabase.from("update_checks").insert({
28
+ gstack_version: String(version).slice(0, 20),
29
+ os: String(os).slice(0, 20),
30
+ });
31
+
32
+ return new Response(CURRENT_VERSION, { status: 200 });
33
+ } catch {
34
+ // Always return the version, even if logging fails
35
+ return new Response(CURRENT_VERSION, { status: 200 });
36
+ }
37
+ });
@@ -0,0 +1,89 @@
1
+ -- gstack telemetry schema
2
+ -- Tables for tracking usage, installations, and update checks.
3
+
4
+ -- Main telemetry events (skill runs, upgrades)
5
+ CREATE TABLE telemetry_events (
6
+ id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
7
+ received_at TIMESTAMPTZ DEFAULT now(),
8
+ schema_version INTEGER NOT NULL DEFAULT 1,
9
+ event_type TEXT NOT NULL DEFAULT 'skill_run',
10
+ gstack_version TEXT NOT NULL,
11
+ os TEXT NOT NULL,
12
+ arch TEXT,
13
+ event_timestamp TIMESTAMPTZ NOT NULL,
14
+ skill TEXT,
15
+ session_id TEXT,
16
+ duration_s NUMERIC,
17
+ outcome TEXT NOT NULL,
18
+ error_class TEXT,
19
+ used_browse BOOLEAN DEFAULT false,
20
+ concurrent_sessions INTEGER DEFAULT 1,
21
+ installation_id TEXT -- nullable, only for "community" tier
22
+ );
23
+
24
+ -- Index for skill_sequences view performance
25
+ CREATE INDEX idx_telemetry_session_ts ON telemetry_events (session_id, event_timestamp);
26
+ -- Index for crash clustering
27
+ CREATE INDEX idx_telemetry_error ON telemetry_events (error_class, gstack_version) WHERE outcome = 'error';
28
+
29
+ -- Retention tracking per installation
30
+ CREATE TABLE installations (
31
+ installation_id TEXT PRIMARY KEY,
32
+ first_seen TIMESTAMPTZ DEFAULT now(),
33
+ last_seen TIMESTAMPTZ DEFAULT now(),
34
+ gstack_version TEXT,
35
+ os TEXT
36
+ );
37
+
38
+ -- Install pings from update checks
39
+ CREATE TABLE update_checks (
40
+ id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
41
+ checked_at TIMESTAMPTZ DEFAULT now(),
42
+ gstack_version TEXT NOT NULL,
43
+ os TEXT NOT NULL
44
+ );
45
+
46
+ -- RLS: anon key can INSERT and SELECT (all telemetry data is anonymous)
47
+ ALTER TABLE telemetry_events ENABLE ROW LEVEL SECURITY;
48
+ CREATE POLICY "anon_insert_only" ON telemetry_events FOR INSERT WITH CHECK (true);
49
+ CREATE POLICY "anon_select" ON telemetry_events FOR SELECT USING (true);
50
+
51
+ ALTER TABLE installations ENABLE ROW LEVEL SECURITY;
52
+ CREATE POLICY "anon_insert_only" ON installations FOR INSERT WITH CHECK (true);
53
+ CREATE POLICY "anon_select" ON installations FOR SELECT USING (true);
54
+ -- Allow upsert (update last_seen)
55
+ CREATE POLICY "anon_update_last_seen" ON installations FOR UPDATE USING (true) WITH CHECK (true);
56
+
57
+ ALTER TABLE update_checks ENABLE ROW LEVEL SECURITY;
58
+ CREATE POLICY "anon_insert_only" ON update_checks FOR INSERT WITH CHECK (true);
59
+ CREATE POLICY "anon_select" ON update_checks FOR SELECT USING (true);
60
+
61
+ -- Crash clustering view
62
+ CREATE VIEW crash_clusters AS
63
+ SELECT
64
+ error_class,
65
+ gstack_version,
66
+ COUNT(*) as total_occurrences,
67
+ COUNT(DISTINCT installation_id) as identified_users, -- community tier only
68
+ COUNT(*) - COUNT(installation_id) as anonymous_occurrences, -- events without installation_id
69
+ MIN(event_timestamp) as first_seen,
70
+ MAX(event_timestamp) as last_seen
71
+ FROM telemetry_events
72
+ WHERE outcome = 'error' AND error_class IS NOT NULL
73
+ GROUP BY error_class, gstack_version
74
+ ORDER BY total_occurrences DESC;
75
+
76
+ -- Skill sequence co-occurrence view
77
+ CREATE VIEW skill_sequences AS
78
+ SELECT
79
+ a.skill as skill_a,
80
+ b.skill as skill_b,
81
+ COUNT(DISTINCT a.session_id) as co_occurrences
82
+ FROM telemetry_events a
83
+ JOIN telemetry_events b ON a.session_id = b.session_id
84
+ AND a.skill != b.skill
85
+ AND a.event_timestamp < b.event_timestamp
86
+ WHERE a.event_type = 'skill_run' AND b.event_type = 'skill_run'
87
+ GROUP BY a.skill, b.skill
88
+ HAVING COUNT(DISTINCT a.session_id) >= 10
89
+ ORDER BY co_occurrences DESC;
@@ -0,0 +1,277 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
2
+ import { parseJSONL, filterByPeriod, formatReport } from '../scripts/analytics';
3
+ import type { AnalyticsEvent } from '../scripts/analytics';
4
+ import * as fs from 'fs';
5
+ import * as path from 'path';
6
+ import * as os from 'os';
7
+ import { execSync } from 'child_process';
8
+
9
+ const TMP_DIR = path.join(os.tmpdir(), 'analytics-test');
10
+ const SCRIPT = path.resolve(import.meta.dir, '../scripts/analytics.ts');
11
+
12
+ function writeTempJSONL(name: string, lines: string[]): string {
13
+ fs.mkdirSync(TMP_DIR, { recursive: true });
14
+ const p = path.join(TMP_DIR, name);
15
+ fs.writeFileSync(p, lines.join('\n') + '\n');
16
+ return p;
17
+ }
18
+
19
+ /**
20
+ * Run the analytics script with a custom JSONL file by overriding the path.
21
+ * We test the exported functions directly for unit tests, and use this
22
+ * helper for integration-style checks.
23
+ */
24
+ function runScript(jsonlPath: string | null, extraArgs: string = ''): string {
25
+ // We test via the exported functions; for CLI integration we read the file
26
+ // and run the pipeline manually to avoid needing to override the hardcoded path.
27
+ if (jsonlPath === null) {
28
+ return 'No analytics data found.';
29
+ }
30
+ if (!fs.existsSync(jsonlPath)) {
31
+ return 'No analytics data found.';
32
+ }
33
+ const content = fs.readFileSync(jsonlPath, 'utf-8').trim();
34
+ if (!content) {
35
+ return 'No analytics data found.';
36
+ }
37
+ const events = parseJSONL(content);
38
+ if (events.length === 0) {
39
+ return 'No analytics data found.';
40
+ }
41
+ // Parse period from extraArgs
42
+ let period = 'all';
43
+ const match = extraArgs.match(/--period\s+(\S+)/);
44
+ if (match) period = match[1];
45
+ const filtered = filterByPeriod(events, period);
46
+ return formatReport(filtered, period);
47
+ }
48
+
49
+ beforeEach(() => {
50
+ fs.mkdirSync(TMP_DIR, { recursive: true });
51
+ });
52
+
53
+ afterEach(() => {
54
+ fs.rmSync(TMP_DIR, { recursive: true, force: true });
55
+ });
56
+
57
+ describe('parseJSONL', () => {
58
+ test('parses valid JSONL lines', () => {
59
+ const content = [
60
+ '{"skill":"ship","ts":"2026-03-18T15:30:00Z","repo":"my-app"}',
61
+ '{"skill":"qa","ts":"2026-03-18T16:00:00Z","repo":"my-api"}',
62
+ ].join('\n');
63
+ const events = parseJSONL(content);
64
+ expect(events).toHaveLength(2);
65
+ expect(events[0].skill).toBe('ship');
66
+ expect(events[1].skill).toBe('qa');
67
+ });
68
+
69
+ test('skips malformed lines', () => {
70
+ const content = [
71
+ '{"skill":"ship","ts":"2026-03-18T15:30:00Z","repo":"my-app"}',
72
+ 'not valid json',
73
+ '{broken',
74
+ '',
75
+ '{"skill":"qa","ts":"2026-03-18T16:00:00Z","repo":"my-api"}',
76
+ ].join('\n');
77
+ const events = parseJSONL(content);
78
+ expect(events).toHaveLength(2);
79
+ expect(events[0].skill).toBe('ship');
80
+ expect(events[1].skill).toBe('qa');
81
+ });
82
+
83
+ test('returns empty array for empty string', () => {
84
+ expect(parseJSONL('')).toHaveLength(0);
85
+ });
86
+
87
+ test('skips objects missing ts field', () => {
88
+ const content = '{"skill":"ship","repo":"my-app"}\n';
89
+ const events = parseJSONL(content);
90
+ expect(events).toHaveLength(0);
91
+ });
92
+ });
93
+
94
+ describe('filterByPeriod', () => {
95
+ const now = new Date();
96
+ const daysAgo = (n: number) => new Date(now.getTime() - n * 24 * 60 * 60 * 1000).toISOString();
97
+
98
+ const events: AnalyticsEvent[] = [
99
+ { skill: 'ship', ts: daysAgo(1), repo: 'app' },
100
+ { skill: 'qa', ts: daysAgo(3), repo: 'app' },
101
+ { skill: 'review', ts: daysAgo(10), repo: 'app' },
102
+ { skill: 'retro', ts: daysAgo(40), repo: 'app' },
103
+ ];
104
+
105
+ test('period "all" returns all events', () => {
106
+ expect(filterByPeriod(events, 'all')).toHaveLength(4);
107
+ });
108
+
109
+ test('period "7d" returns only last 7 days', () => {
110
+ const filtered = filterByPeriod(events, '7d');
111
+ expect(filtered).toHaveLength(2);
112
+ expect(filtered[0].skill).toBe('ship');
113
+ expect(filtered[1].skill).toBe('qa');
114
+ });
115
+
116
+ test('period "30d" returns last 30 days', () => {
117
+ const filtered = filterByPeriod(events, '30d');
118
+ expect(filtered).toHaveLength(3);
119
+ });
120
+
121
+ test('invalid period string returns all events', () => {
122
+ expect(filterByPeriod(events, 'bogus')).toHaveLength(4);
123
+ });
124
+ });
125
+
126
+ describe('formatReport', () => {
127
+ test('includes header and period label', () => {
128
+ const report = formatReport([], 'all');
129
+ expect(report).toContain('gstack skill usage analytics');
130
+ expect(report).toContain('Period: all time');
131
+ });
132
+
133
+ test('shows "last 7 days" for 7d period', () => {
134
+ const report = formatReport([], '7d');
135
+ expect(report).toContain('Period: last 7 days');
136
+ });
137
+
138
+ test('shows "last 30 days" for 30d period', () => {
139
+ const report = formatReport([], '30d');
140
+ expect(report).toContain('Period: last 30 days');
141
+ });
142
+
143
+ test('counts skill invocations correctly', () => {
144
+ const events: AnalyticsEvent[] = [
145
+ { skill: 'ship', ts: '2026-03-18T15:30:00Z', repo: 'app' },
146
+ { skill: 'ship', ts: '2026-03-18T16:00:00Z', repo: 'app' },
147
+ { skill: 'qa', ts: '2026-03-18T16:30:00Z', repo: 'app' },
148
+ ];
149
+ const report = formatReport(events);
150
+ expect(report).toContain('/ship');
151
+ expect(report).toContain('2 invocations');
152
+ expect(report).toContain('/qa');
153
+ expect(report).toContain('1 invocation');
154
+ });
155
+
156
+ test('groups by repo', () => {
157
+ const events: AnalyticsEvent[] = [
158
+ { skill: 'ship', ts: '2026-03-18T15:30:00Z', repo: 'app-a' },
159
+ { skill: 'qa', ts: '2026-03-18T16:00:00Z', repo: 'app-a' },
160
+ { skill: 'ship', ts: '2026-03-18T16:30:00Z', repo: 'app-b' },
161
+ ];
162
+ const report = formatReport(events);
163
+ expect(report).toContain('app-a: ship(1) qa(1)');
164
+ expect(report).toContain('app-b: ship(1)');
165
+ });
166
+
167
+ test('counts hook fire events separately', () => {
168
+ const events: AnalyticsEvent[] = [
169
+ { skill: 'ship', ts: '2026-03-18T15:30:00Z', repo: 'app' },
170
+ { skill: 'careful', ts: '2026-03-18T16:00:00Z', repo: 'app', event: 'hook_fire', pattern: 'rm_recursive' },
171
+ { skill: 'careful', ts: '2026-03-18T16:30:00Z', repo: 'app', event: 'hook_fire', pattern: 'rm_recursive' },
172
+ { skill: 'careful', ts: '2026-03-18T17:00:00Z', repo: 'app', event: 'hook_fire', pattern: 'git_force_push' },
173
+ ];
174
+ const report = formatReport(events);
175
+ expect(report).toContain('Safety Hook Events');
176
+ expect(report).toContain('rm_recursive');
177
+ expect(report).toContain('2 fires');
178
+ expect(report).toContain('git_force_push');
179
+ expect(report).toContain('1 fire');
180
+ expect(report).toContain('Total: 1 skill invocation, 3 hook fires');
181
+ });
182
+
183
+ test('handles mixed events correctly', () => {
184
+ const events: AnalyticsEvent[] = [
185
+ { skill: 'ship', ts: '2026-03-18T15:30:00Z', repo: 'my-app' },
186
+ { skill: 'ship', ts: '2026-03-18T15:35:00Z', repo: 'my-app' },
187
+ { skill: 'qa', ts: '2026-03-18T16:00:00Z', repo: 'my-api' },
188
+ { skill: 'careful', ts: '2026-03-18T16:30:00Z', repo: 'my-app', event: 'hook_fire', pattern: 'rm_recursive' },
189
+ ];
190
+ const report = formatReport(events);
191
+ // Skills counted correctly (hook_fire events excluded from skill counts)
192
+ expect(report).toContain('Total: 3 skill invocations, 1 hook fire');
193
+ // Both sections present
194
+ expect(report).toContain('Top Skills');
195
+ expect(report).toContain('Safety Hook Events');
196
+ expect(report).toContain('By Repo');
197
+ });
198
+ });
199
+
200
+ describe('integration via runScript helper', () => {
201
+ test('missing file → "No analytics data found."', () => {
202
+ const output = runScript(path.join(TMP_DIR, 'nonexistent.jsonl'));
203
+ expect(output).toBe('No analytics data found.');
204
+ });
205
+
206
+ test('null path → "No analytics data found."', () => {
207
+ const output = runScript(null);
208
+ expect(output).toBe('No analytics data found.');
209
+ });
210
+
211
+ test('empty file → "No analytics data found."', () => {
212
+ const p = writeTempJSONL('empty.jsonl', ['']);
213
+ // Overwrite with truly empty content
214
+ fs.writeFileSync(p, '');
215
+ const output = runScript(p);
216
+ expect(output).toBe('No analytics data found.');
217
+ });
218
+
219
+ test('all malformed lines → "No analytics data found."', () => {
220
+ const p = writeTempJSONL('bad.jsonl', [
221
+ 'not json',
222
+ '{broken',
223
+ '42',
224
+ ]);
225
+ const output = runScript(p);
226
+ expect(output).toBe('No analytics data found.');
227
+ });
228
+
229
+ test('normal aggregation produces correct output', () => {
230
+ const p = writeTempJSONL('normal.jsonl', [
231
+ '{"skill":"ship","ts":"2026-03-18T15:30:00Z","repo":"my-app"}',
232
+ '{"skill":"ship","ts":"2026-03-18T15:35:00Z","repo":"my-app"}',
233
+ '{"skill":"qa","ts":"2026-03-18T16:00:00Z","repo":"my-app"}',
234
+ '{"skill":"review","ts":"2026-03-18T16:30:00Z","repo":"my-api"}',
235
+ ]);
236
+ const output = runScript(p);
237
+ expect(output).toContain('/ship');
238
+ expect(output).toContain('2 invocations');
239
+ expect(output).toContain('/qa');
240
+ expect(output).toContain('1 invocation');
241
+ expect(output).toContain('/review');
242
+ expect(output).toContain('Total: 4 skill invocations, 0 hook fires');
243
+ });
244
+
245
+ test('period filtering (7d) only includes recent entries', () => {
246
+ const now = new Date();
247
+ const recent = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000).toISOString();
248
+ const old = new Date(now.getTime() - 20 * 24 * 60 * 60 * 1000).toISOString();
249
+
250
+ const p = writeTempJSONL('period.jsonl', [
251
+ `{"skill":"ship","ts":"${recent}","repo":"app"}`,
252
+ `{"skill":"qa","ts":"${old}","repo":"app"}`,
253
+ ]);
254
+ const output = runScript(p, '--period 7d');
255
+ expect(output).toContain('Period: last 7 days');
256
+ expect(output).toContain('/ship');
257
+ expect(output).toContain('Total: 1 skill invocation, 0 hook fires');
258
+ // qa should be filtered out
259
+ expect(output).not.toContain('/qa');
260
+ });
261
+
262
+ test('hook fire events counted in full pipeline', () => {
263
+ const p = writeTempJSONL('hooks.jsonl', [
264
+ '{"skill":"ship","ts":"2026-03-18T15:30:00Z","repo":"app"}',
265
+ '{"event":"hook_fire","skill":"careful","pattern":"rm_recursive","ts":"2026-03-18T16:00:00Z","repo":"app"}',
266
+ '{"event":"hook_fire","skill":"careful","pattern":"rm_recursive","ts":"2026-03-18T16:30:00Z","repo":"app"}',
267
+ '{"event":"hook_fire","skill":"careful","pattern":"git_force_push","ts":"2026-03-18T17:00:00Z","repo":"app"}',
268
+ ]);
269
+ const output = runScript(p);
270
+ expect(output).toContain('Safety Hook Events');
271
+ expect(output).toContain('rm_recursive');
272
+ expect(output).toContain('2 fires');
273
+ expect(output).toContain('git_force_push');
274
+ expect(output).toContain('1 fire');
275
+ expect(output).toContain('Total: 1 skill invocation, 3 hook fires');
276
+ });
277
+ });