diff --git a/scripts/_dbg.ts b/scripts/_dbg.ts
new file mode 100644
index 0000000..414c0f7
--- /dev/null
+++ b/scripts/_dbg.ts
@@ -0,0 +1,18 @@
+import PocketBase from "pocketbase";
+
+const PB_URL = process.env.NEXT_PUBLIC_PB_URL!;
+
+async function main() {
+ const pb = new PocketBase(PB_URL);
+ pb.autoCancellation(false);
+ await pb.collection("_superusers").authWithPassword(
+ process.env.PB_SUPERUSER_EMAIL!,
+ process.env.PB_SUPERUSER_PASSWORD!
+ );
+ const p = await pb.collection("projects").getOne("28eagx0bpf805p4");
+ console.log("type:", typeof p);
+ console.log("keys:", Object.keys(p));
+ console.log("session_gap_minutes:", (p as Record).session_gap_minutes);
+ console.log("raw:", JSON.stringify(p, null, 2));
+}
+main().catch(e => { console.error(e); process.exit(1); });
diff --git a/scripts/compute.ts b/scripts/compute.ts
new file mode 100644
index 0000000..ad76f9a
--- /dev/null
+++ b/scripts/compute.ts
@@ -0,0 +1,79 @@
+/**
+ * Standalone recompute helper.
+ * Not part of the product — this is a CLI shim that performs the same flow
+ * as the integration flow without pulling in server-only modules.
+ */
+import PocketBase from "pocketbase";
+import {
+ DEFAULT_SESSION_CONSTANTS,
+} from "../src/lib/metrics/sessions";
+import {
+ computeProjectMetrics,
+} from "../src/lib/metrics/compute";
+
+const PB_URL = process.env.NEXT_PUBLIC_PB_URL!;
+
+async function main() {
+ const projectId = process.argv[2];
+ if (!projectId) throw new Error("Usage: tsx scripts/compute.ts ");
+
+ const pb = new PocketBase(PB_URL);
+ pb.autoCancellation(false);
+ await pb.collection("_superusers").authWithPassword(
+ process.env.PB_SUPERUSER_EMAIL!,
+ process.env.PB_SUPERUSER_PASSWORD!
+ );
+
+ const commits = await pb.collection("commits").getFullList({
+ filter: `project = "${projectId}"`,
+ fields: "type,scope,authored_at,is_merge",
+ });
+
+ const project = await pb.collection("projects").getOne(projectId);
+ const constants = {
+ sessionGapMinutes:
+ Number((project as Record).session_gap_minutes) ||
+ DEFAULT_SESSION_CONSTANTS.sessionGapMinutes,
+ preSessionMinutes:
+ Number((project as Record).pre_session_minutes) ||
+ DEFAULT_SESSION_CONSTANTS.preSessionMinutes,
+ postSessionMinutes:
+ Number((project as Record).post_session_minutes) ||
+ DEFAULT_SESSION_CONSTANTS.postSessionMinutes,
+ };
+
+ const metrics = computeProjectMetrics(
+ commits.map((c) => ({
+ type: c.type ?? "other",
+ scope: c.scope ?? "",
+ authoredAt: c.authored_at,
+ isMerge: Boolean(c.is_merge),
+ })),
+ constants
+ );
+
+ console.log("Recomputed:", metrics.totalCommits, "commits,", metrics.monthly.length, "months");
+ console.log("Monthly:");
+ for (const mo of metrics.monthly) {
+ console.log(" ", mo);
+ }
+
+ const payload = {
+ project: projectId,
+ computed_at: new Date().toISOString(),
+ payload: metrics,
+ };
+
+ const existing = await pb
+ .collection("project_metrics")
+ .getFirstListItem(`project = "${projectId}"`)
+ .catch(() => null);
+ if (existing) {
+ await pb.collection("project_metrics").update(existing.id, payload);
+ } else {
+ await pb.collection("project_metrics").create(payload);
+ }
+ console.log("Stored.");
+}
+
+main().catch((e) => { console.error(e); process.exit(1); });
diff --git a/src/components/dashboard/methodology.tsx b/src/components/dashboard/methodology.tsx
index 1dbb8d4..89dabc6 100644
--- a/src/components/dashboard/methodology.tsx
+++ b/src/components/dashboard/methodology.tsx
@@ -9,9 +9,6 @@ import type { ProjectMetrics } from "@/lib/metrics/compute";
* worse than no number.
*/
export function Methodology({ metrics }: { metrics: ProjectMetrics }) {
- const { sessionGapMinutes, preSessionMinutes, postSessionMinutes } =
- metrics.constants;
-
return (
@@ -25,18 +22,6 @@ export function Methodology({ metrics }: { metrics: ProjectMetrics }) {
the timing of commits, not measured.
-
- Commits less than{" "}
- {sessionGapMinutes}{" "}
- minutes apart are treated as one working session. Each session counts
- the time between its first and last commit, plus{" "}
- {preSessionMinutes}{" "}
- minutes before it — reading and planning happen before code is
- committed — and{" "}
- {postSessionMinutes}{" "}
- minutes after, for testing and deploying.
-
-
What this cannot see:{" "}
design, meetings, reading code, and debugging that ended without a