Merge branch 'main' into garrytan/team-supabase-store

Resolved 4 conflicts:
- scripts/gen-skill-docs.ts: kept ARTIFACT_SETUP + added main's new
  resolvers (SPEC_REVIEW_LOOP, DESIGN_SKETCH, BENEFITS_FROM,
  CODEX_REVIEW_STEP). Updated codex review-log to use new paths.
- ship/SKILL.md.tmpl: adopted {{CODEX_REVIEW_STEP}} macro from main
- test/skill-e2e.test.ts: added main's new E2E tests (office-hours
  spec review, plan-ceo benefits-from) + kept our E2E isolation cleanup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-03-21 09:29:05 -07:00
96 changed files with 17868 additions and 342 deletions
@@ -0,0 +1,59 @@
// gstack community-pulse edge function
// Returns weekly active installation count for preamble display.
// Cached for 1 hour via Cache-Control header.
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
Deno.serve(async () => {
const supabase = createClient(
Deno.env.get("SUPABASE_URL") ?? "",
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? ""
);
try {
// Count unique update checks in the last 7 days (install base proxy)
const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
const twoWeeksAgo = new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString();
// This week's active
const { count: thisWeek } = await supabase
.from("update_checks")
.select("*", { count: "exact", head: true })
.gte("checked_at", weekAgo);
// Last week's active (for change %)
const { count: lastWeek } = await supabase
.from("update_checks")
.select("*", { count: "exact", head: true })
.gte("checked_at", twoWeeksAgo)
.lt("checked_at", weekAgo);
const current = thisWeek ?? 0;
const previous = lastWeek ?? 0;
const changePct = previous > 0
? Math.round(((current - previous) / previous) * 100)
: 0;
return new Response(
JSON.stringify({
weekly_active: current,
change_pct: changePct,
}),
{
status: 200,
headers: {
"Content-Type": "application/json",
"Cache-Control": "public, max-age=3600", // 1 hour cache
},
}
);
} catch {
return new Response(
JSON.stringify({ weekly_active: 0, change_pct: 0 }),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
}
});
@@ -0,0 +1,135 @@
// gstack telemetry-ingest edge function
// Validates and inserts a batch of telemetry events.
// Called by bin/gstack-telemetry-sync.
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
interface TelemetryEvent {
v: number;
ts: string;
event_type: string;
skill: string;
session_id?: string;
gstack_version: string;
os: string;
arch?: string;
duration_s?: number;
outcome: string;
error_class?: string;
used_browse?: boolean;
sessions?: number;
installation_id?: string;
}
const MAX_BATCH_SIZE = 100;
const MAX_PAYLOAD_BYTES = 50_000; // 50KB
Deno.serve(async (req) => {
if (req.method !== "POST") {
return new Response("POST required", { status: 405 });
}
// Check payload size
const contentLength = parseInt(req.headers.get("content-length") || "0");
if (contentLength > MAX_PAYLOAD_BYTES) {
return new Response("Payload too large", { status: 413 });
}
try {
const body = await req.json();
const events: TelemetryEvent[] = Array.isArray(body) ? body : [body];
if (events.length > MAX_BATCH_SIZE) {
return new Response(`Batch too large (max ${MAX_BATCH_SIZE})`, { status: 400 });
}
const supabase = createClient(
Deno.env.get("SUPABASE_URL") ?? "",
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? ""
);
// Validate and transform events
const rows = [];
const installationUpserts: Map<string, { version: string; os: string }> = new Map();
for (const event of events) {
// Required fields
if (!event.ts || !event.gstack_version || !event.os || !event.outcome) {
continue; // skip malformed
}
// Validate schema version
if (event.v !== 1) continue;
// Validate event_type
const validTypes = ["skill_run", "upgrade_prompted", "upgrade_completed"];
if (!validTypes.includes(event.event_type)) continue;
rows.push({
schema_version: event.v,
event_type: event.event_type,
gstack_version: String(event.gstack_version).slice(0, 20),
os: String(event.os).slice(0, 20),
arch: event.arch ? String(event.arch).slice(0, 20) : null,
event_timestamp: event.ts,
skill: event.skill ? String(event.skill).slice(0, 50) : null,
session_id: event.session_id ? String(event.session_id).slice(0, 50) : null,
duration_s: typeof event.duration_s === "number" ? event.duration_s : null,
outcome: String(event.outcome).slice(0, 20),
error_class: event.error_class ? String(event.error_class).slice(0, 100) : null,
used_browse: event.used_browse === true,
concurrent_sessions: typeof event.sessions === "number" ? event.sessions : 1,
installation_id: event.installation_id ? String(event.installation_id).slice(0, 64) : null,
});
// Track installations for upsert
if (event.installation_id) {
installationUpserts.set(event.installation_id, {
version: event.gstack_version,
os: event.os,
});
}
}
if (rows.length === 0) {
return new Response(JSON.stringify({ inserted: 0 }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
// Insert events
const { error: insertError } = await supabase
.from("telemetry_events")
.insert(rows);
if (insertError) {
return new Response(JSON.stringify({ error: insertError.message }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
// Upsert installations (update last_seen)
for (const [id, data] of installationUpserts) {
await supabase
.from("installations")
.upsert(
{
installation_id: id,
last_seen: new Date().toISOString(),
gstack_version: data.version,
os: data.os,
},
{ onConflict: "installation_id" }
);
}
return new Response(JSON.stringify({ inserted: rows.length }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
} catch {
return new Response("Invalid request", { status: 400 });
}
});
+37
View File
@@ -0,0 +1,37 @@
// gstack update-check edge function
// Logs an install ping and returns the current latest version.
// Called by bin/gstack-update-check as a parallel background request.
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
const CURRENT_VERSION = Deno.env.get("GSTACK_CURRENT_VERSION") || "0.6.4.1";
Deno.serve(async (req) => {
if (req.method !== "POST") {
return new Response(CURRENT_VERSION, { status: 200 });
}
try {
const { version, os } = await req.json();
if (!version || !os) {
return new Response(CURRENT_VERSION, { status: 200 });
}
const supabase = createClient(
Deno.env.get("SUPABASE_URL") ?? "",
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? ""
);
// Log the update check (fire-and-forget)
await supabase.from("update_checks").insert({
gstack_version: String(version).slice(0, 20),
os: String(os).slice(0, 20),
});
return new Response(CURRENT_VERSION, { status: 200 });
} catch {
// Always return the version, even if logging fails
return new Response(CURRENT_VERSION, { status: 200 });
}
});