@kenkaiiii/ggcoder 5.6.3 → 5.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 (75) hide show
  1. package/dist/app-sidecar.js +291 -7
  2. package/dist/app-sidecar.js.map +1 -1
  3. package/dist/core/autopilot-cycle.d.ts +52 -17
  4. package/dist/core/autopilot-cycle.d.ts.map +1 -1
  5. package/dist/core/autopilot-cycle.js +53 -13
  6. package/dist/core/autopilot-cycle.js.map +1 -1
  7. package/dist/core/autopilot-cycle.test.js +195 -10
  8. package/dist/core/autopilot-cycle.test.js.map +1 -1
  9. package/dist/core/autopilot-gate.d.ts +12 -0
  10. package/dist/core/autopilot-gate.d.ts.map +1 -1
  11. package/dist/core/autopilot-gate.js +10 -1
  12. package/dist/core/autopilot-gate.js.map +1 -1
  13. package/dist/core/autopilot-gate.test.js +27 -2
  14. package/dist/core/autopilot-gate.test.js.map +1 -1
  15. package/dist/core/ken-context.d.ts +20 -0
  16. package/dist/core/ken-context.d.ts.map +1 -1
  17. package/dist/core/ken-context.js +46 -2
  18. package/dist/core/ken-context.js.map +1 -1
  19. package/dist/core/ken-context.test.js +52 -5
  20. package/dist/core/ken-context.test.js.map +1 -1
  21. package/dist/core/ken-prompt.js +14 -4
  22. package/dist/core/ken-prompt.js.map +1 -1
  23. package/dist/core/ken-prompt.test.js +20 -3
  24. package/dist/core/ken-prompt.test.js.map +1 -1
  25. package/dist/core/progress/engine.d.ts +23 -0
  26. package/dist/core/progress/engine.d.ts.map +1 -0
  27. package/dist/core/progress/engine.js +131 -0
  28. package/dist/core/progress/engine.js.map +1 -0
  29. package/dist/core/progress/engine.test.d.ts +2 -0
  30. package/dist/core/progress/engine.test.d.ts.map +1 -0
  31. package/dist/core/progress/engine.test.js +136 -0
  32. package/dist/core/progress/engine.test.js.map +1 -0
  33. package/dist/core/progress/git-xp.d.ts +16 -0
  34. package/dist/core/progress/git-xp.d.ts.map +1 -0
  35. package/dist/core/progress/git-xp.js +106 -0
  36. package/dist/core/progress/git-xp.js.map +1 -0
  37. package/dist/core/progress/git-xp.test.d.ts +2 -0
  38. package/dist/core/progress/git-xp.test.d.ts.map +1 -0
  39. package/dist/core/progress/git-xp.test.js +88 -0
  40. package/dist/core/progress/git-xp.test.js.map +1 -0
  41. package/dist/core/progress/ranks.d.ts +21 -0
  42. package/dist/core/progress/ranks.d.ts.map +1 -0
  43. package/dist/core/progress/ranks.js +141 -0
  44. package/dist/core/progress/ranks.js.map +1 -0
  45. package/dist/core/progress/ranks.test.d.ts +2 -0
  46. package/dist/core/progress/ranks.test.d.ts.map +1 -0
  47. package/dist/core/progress/ranks.test.js +59 -0
  48. package/dist/core/progress/ranks.test.js.map +1 -0
  49. package/dist/core/progress/rebuild.d.ts +7 -0
  50. package/dist/core/progress/rebuild.d.ts.map +1 -0
  51. package/dist/core/progress/rebuild.js +106 -0
  52. package/dist/core/progress/rebuild.js.map +1 -0
  53. package/dist/core/progress/rebuild.test.d.ts +2 -0
  54. package/dist/core/progress/rebuild.test.d.ts.map +1 -0
  55. package/dist/core/progress/rebuild.test.js +72 -0
  56. package/dist/core/progress/rebuild.test.js.map +1 -0
  57. package/dist/core/progress/store.d.ts +35 -0
  58. package/dist/core/progress/store.d.ts.map +1 -0
  59. package/dist/core/progress/store.js +200 -0
  60. package/dist/core/progress/store.js.map +1 -0
  61. package/dist/core/progress/store.test.d.ts +2 -0
  62. package/dist/core/progress/store.test.d.ts.map +1 -0
  63. package/dist/core/progress/store.test.js +108 -0
  64. package/dist/core/progress/store.test.js.map +1 -0
  65. package/dist/core/progress/types.d.ts +108 -0
  66. package/dist/core/progress/types.d.ts.map +1 -0
  67. package/dist/core/progress/types.js +3 -0
  68. package/dist/core/progress/types.js.map +1 -0
  69. package/dist/core/session-manager.d.ts +1 -1
  70. package/dist/core/session-manager.d.ts.map +1 -1
  71. package/dist/core/session-manager.js +5 -1
  72. package/dist/core/session-manager.js.map +1 -1
  73. package/dist/core/session-manager.test.js +9 -0
  74. package/dist/core/session-manager.test.js.map +1 -1
  75. package/package.json +4 -4
@@ -0,0 +1,136 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { awardCommits, awardPrompt, applyStreak, commitXp, streakMultiplier } from "./engine.js";
3
+ import { createEmptyProgress, dayKey } from "./store.js";
4
+ const T0 = Date.parse("2026-07-01T12:00:00Z");
5
+ const HOUR = 60 * 60 * 1000;
6
+ const DAY = 24 * HOUR;
7
+ function fresh() {
8
+ return createEmptyProgress(new Date(T0));
9
+ }
10
+ function commit(sha, lines) {
11
+ return { sha, patchId: `pid-${sha}`, linesChanged: lines };
12
+ }
13
+ describe("awardPrompt", () => {
14
+ it("gives +10 for a normal prompt", () => {
15
+ const { file, gained } = awardPrompt(fresh(), T0);
16
+ expect(gained).toBe(10);
17
+ expect(file.xp).toBe(10);
18
+ expect(file.totals.prompts).toBe(1);
19
+ expect(file.xpBySource.prompts).toBe(10);
20
+ });
21
+ it("diminishes to +2 after 15 prompts in a rolling hour", () => {
22
+ let file = fresh();
23
+ for (let i = 0; i < 15; i++) {
24
+ file = awardPrompt(file, T0 + i * 1000).file;
25
+ }
26
+ const { gained } = awardPrompt(file, T0 + 16_000);
27
+ expect(gained).toBe(2);
28
+ });
29
+ it("recovers full XP once old prompts age out of the hour", () => {
30
+ let file = fresh();
31
+ for (let i = 0; i < 15; i++) {
32
+ file = awardPrompt(file, T0 + i * 1000).file;
33
+ }
34
+ const { gained } = awardPrompt(file, T0 + HOUR + 20_000);
35
+ expect(gained).toBe(10);
36
+ });
37
+ it("tracks distinct projects by hash", () => {
38
+ let file = fresh();
39
+ file = awardPrompt(file, T0, "/a").file;
40
+ file = awardPrompt(file, T0 + 1, "/b").file;
41
+ file = awardPrompt(file, T0 + 2, "/a").file;
42
+ expect(file.totals.projects).toHaveLength(2);
43
+ });
44
+ it("sets a fresh lastEvent nonce on every award", () => {
45
+ const r1 = awardPrompt(fresh(), T0);
46
+ const nonce1 = r1.file.lastEvent?.nonce;
47
+ const r2 = awardPrompt(r1.file, T0 + 1000);
48
+ expect(nonce1).toBeTruthy();
49
+ expect(r2.file.lastEvent?.nonce).not.toBe(nonce1);
50
+ });
51
+ it("reports levelUp when crossing a boundary", () => {
52
+ const file = fresh();
53
+ file.xp = 300; // level 2 needs 303
54
+ const { levelUp } = awardPrompt(file, T0);
55
+ expect(levelUp).toEqual({ from: 1, to: 2, rankName: "Tinkerer" });
56
+ });
57
+ });
58
+ describe("commitXp", () => {
59
+ it("scales sub-linearly and caps at 100", () => {
60
+ expect(commitXp(0)).toBe(20);
61
+ expect(commitXp(25)).toBe(40); // 20 + √25×4
62
+ expect(commitXp(400)).toBe(100); // capped
63
+ expect(commitXp(1_000_000)).toBe(100);
64
+ });
65
+ });
66
+ describe("awardCommits", () => {
67
+ it("scores a commit by size", () => {
68
+ const { file, gained } = awardCommits(fresh(), [commit("a", 25)], T0);
69
+ expect(gained).toBe(40);
70
+ expect(file.totals.commits).toBe(1);
71
+ expect(file.totals.linesShipped).toBe(25);
72
+ expect(file.xpBySource.commits).toBe(40);
73
+ });
74
+ it("dedupes by patch-id — revert/recommit earns 0", () => {
75
+ const first = awardCommits(fresh(), [commit("a", 25)], T0);
76
+ const second = awardCommits(first.file, [{ ...commit("b", 25), patchId: "pid-a" }], T0 + 1000);
77
+ expect(second.gained).toBe(0);
78
+ expect(second.file.totals.commits).toBe(1); // not even counted
79
+ });
80
+ it("caps XP-earning commits at 6 per rolling hour", () => {
81
+ const commits = Array.from({ length: 8 }, (_, i) => commit(`c${i}`, 4));
82
+ const { file, gained } = awardCommits(fresh(), commits, T0);
83
+ expect(gained).toBe(6 * 28); // 20 + √4×4 = 28 each
84
+ expect(file.totals.commits).toBe(8); // totals still count all
85
+ });
86
+ });
87
+ describe("streaks", () => {
88
+ it("multiplier tiers", () => {
89
+ expect(streakMultiplier(1)).toBe(1);
90
+ expect(streakMultiplier(3)).toBe(1.1);
91
+ expect(streakMultiplier(7)).toBe(1.25);
92
+ expect(streakMultiplier(14)).toBe(1.4);
93
+ expect(streakMultiplier(30)).toBe(1.5);
94
+ });
95
+ it("increments on consecutive days and resets after a gap", () => {
96
+ const file = fresh();
97
+ applyStreak(file, T0);
98
+ expect(file.streak.current).toBe(1);
99
+ applyStreak(file, T0 + DAY);
100
+ expect(file.streak.current).toBe(2);
101
+ applyStreak(file, T0 + DAY + 1000); // same day — no change
102
+ expect(file.streak.current).toBe(2);
103
+ applyStreak(file, T0 + 4 * DAY); // gap
104
+ expect(file.streak.current).toBe(1);
105
+ expect(file.streak.best).toBe(2);
106
+ });
107
+ it("applies the multiplier to prompt XP", () => {
108
+ const file = fresh();
109
+ file.streak = { current: 6, best: 6, lastActiveDay: dayKey(new Date(T0 - DAY)) };
110
+ const { gained } = awardPrompt(file, T0); // day 7 → ×1.25
111
+ expect(gained).toBe(Math.round(10 * 1.25));
112
+ expect(file.xpBySource.prompts).toBe(10);
113
+ expect(file.xpBySource.streakBonus).toBe(gained - 10);
114
+ });
115
+ });
116
+ describe("daily soft cap", () => {
117
+ it("earns at 25% beyond 500 XP/day", () => {
118
+ const file = fresh();
119
+ file.rolling.dayXp = 500;
120
+ const { gained } = awardPrompt(file, T0);
121
+ expect(gained).toBe(Math.round(10 * 0.25));
122
+ });
123
+ it("splits an award straddling the cap", () => {
124
+ const file = fresh();
125
+ file.rolling.dayXp = 495;
126
+ const { gained } = awardPrompt(file, T0); // 5 full + 5×0.25
127
+ expect(gained).toBe(6);
128
+ });
129
+ it("resets on day rollover", () => {
130
+ const file = fresh();
131
+ file.rolling.dayXp = 500;
132
+ const { gained } = awardPrompt(file, T0 + DAY);
133
+ expect(gained).toBe(10);
134
+ });
135
+ });
136
+ //# sourceMappingURL=engine.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.test.js","sourceRoot":"","sources":["../../../src/core/progress/engine.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { ScoredCommit } from "./types.js";
2
+ /** Stable key for a repo root, used in ProgressFile.repos. */
3
+ export declare function repoKey(repoRoot: string): string;
4
+ export interface DetectResult {
5
+ repoRoot: string;
6
+ head: string;
7
+ commits: ScoredCommit[];
8
+ }
9
+ /**
10
+ * Detect new, scoreable commits since `lastHead`.
11
+ * - `lastHead` undefined (first time seeing this repo) → record HEAD, score nothing.
12
+ * - Merge commits excluded; commits authored before the run window excluded.
13
+ * - Any git failure → null (silent).
14
+ */
15
+ export declare function detectNewCommits(cwd: string, lastHead: string | undefined, runStartedAt: number): Promise<DetectResult | null>;
16
+ //# sourceMappingURL=git-xp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-xp.d.ts","sourceRoot":"","sources":["../../../src/core/progress/git-xp.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAqB/C,8DAA8D;AAC9D,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAsC9B"}
@@ -0,0 +1,106 @@
1
+ // Detect and score new commits made during an agent run. All failures are silent —
2
+ // progress must never break a run.
3
+ import { execFile } from "node:child_process";
4
+ import crypto from "node:crypto";
5
+ const GIT_TIMEOUT_MS = 5_000;
6
+ const MAX_COMMITS_PER_DETECT = 50;
7
+ /** Commits authored earlier than runStart - 5min are treated as imports (pull), not work. */
8
+ const AUTHOR_WINDOW_SLACK_MS = 5 * 60 * 1000;
9
+ function git(cwd, args, input) {
10
+ return new Promise((resolve, reject) => {
11
+ const opts = { cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 10 * 1024 * 1024 };
12
+ const child = execFile("git", args, opts, (err, stdout) => {
13
+ if (err)
14
+ reject(err);
15
+ else
16
+ resolve(String(stdout));
17
+ });
18
+ if (input !== undefined && child.stdin) {
19
+ child.stdin.write(input);
20
+ child.stdin.end();
21
+ }
22
+ });
23
+ }
24
+ /** Stable key for a repo root, used in ProgressFile.repos. */
25
+ export function repoKey(repoRoot) {
26
+ return crypto.createHash("sha256").update(repoRoot).digest("hex").slice(0, 16);
27
+ }
28
+ /**
29
+ * Detect new, scoreable commits since `lastHead`.
30
+ * - `lastHead` undefined (first time seeing this repo) → record HEAD, score nothing.
31
+ * - Merge commits excluded; commits authored before the run window excluded.
32
+ * - Any git failure → null (silent).
33
+ */
34
+ export async function detectNewCommits(cwd, lastHead, runStartedAt) {
35
+ try {
36
+ const repoRoot = (await git(cwd, ["rev-parse", "--show-toplevel"])).trim();
37
+ const head = (await git(cwd, ["rev-parse", "HEAD"])).trim();
38
+ if (!repoRoot || !head)
39
+ return null;
40
+ if (!lastHead || lastHead === head) {
41
+ return { repoRoot, head, commits: [] };
42
+ }
43
+ const sinceIso = new Date(runStartedAt - AUTHOR_WINDOW_SLACK_MS).toISOString();
44
+ let shas;
45
+ try {
46
+ const out = await git(cwd, [
47
+ "rev-list",
48
+ `${lastHead}..HEAD`,
49
+ "--no-merges",
50
+ `--since=${sinceIso}`,
51
+ `--max-count=${MAX_COMMITS_PER_DETECT}`,
52
+ ]);
53
+ shas = out
54
+ .split("\n")
55
+ .map((s) => s.trim())
56
+ .filter(Boolean);
57
+ }
58
+ catch {
59
+ // lastHead may be gone (rebase/gc) — reset the baseline without scoring.
60
+ return { repoRoot, head, commits: [] };
61
+ }
62
+ const commits = [];
63
+ for (const sha of shas.reverse()) {
64
+ const scored = await scoreCommit(cwd, sha);
65
+ if (scored)
66
+ commits.push(scored);
67
+ }
68
+ return { repoRoot, head, commits };
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ async function scoreCommit(cwd, sha) {
75
+ try {
76
+ // Lines changed: numstat sums added+deleted; binary files show "-" and count 0.
77
+ const numstat = await git(cwd, ["show", "--numstat", "--format=", sha]);
78
+ let linesChanged = 0;
79
+ for (const line of numstat.split("\n")) {
80
+ const parts = line.split("\t");
81
+ if (parts.length < 3)
82
+ continue;
83
+ const added = parseInt(parts[0], 10);
84
+ const deleted = parseInt(parts[1], 10);
85
+ if (Number.isFinite(added))
86
+ linesChanged += added;
87
+ if (Number.isFinite(deleted))
88
+ linesChanged += deleted;
89
+ }
90
+ // Patch-id dedupe: revert/recommit of the same diff scores 0.
91
+ let patchId = "";
92
+ try {
93
+ const diff = await git(cwd, ["show", sha]);
94
+ const out = await git(cwd, ["patch-id", "--stable"], diff);
95
+ patchId = out.trim().split(" ")[0] ?? "";
96
+ }
97
+ catch {
98
+ patchId = "";
99
+ }
100
+ return { sha, patchId, linesChanged };
101
+ }
102
+ catch {
103
+ return null;
104
+ }
105
+ }
106
+ //# sourceMappingURL=git-xp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-xp.js","sourceRoot":"","sources":["../../../src/core/progress/git-xp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=git-xp.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-xp.test.d.ts","sourceRoot":"","sources":["../../../src/core/progress/git-xp.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,88 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import fs from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
6
+ import { detectNewCommits } from "./git-xp.js";
7
+ function hasGit() {
8
+ try {
9
+ execFileSync("git", ["--version"], { stdio: "ignore" });
10
+ return true;
11
+ }
12
+ catch {
13
+ return false;
14
+ }
15
+ }
16
+ const gitAvailable = hasGit();
17
+ const d = gitAvailable ? describe : describe.skip;
18
+ let repo;
19
+ function git(...args) {
20
+ return execFileSync("git", args, {
21
+ cwd: repo,
22
+ encoding: "utf-8",
23
+ env: {
24
+ ...process.env,
25
+ GIT_AUTHOR_NAME: "Test",
26
+ GIT_AUTHOR_EMAIL: "t@t.t",
27
+ GIT_COMMITTER_NAME: "Test",
28
+ GIT_COMMITTER_EMAIL: "t@t.t",
29
+ },
30
+ }).trim();
31
+ }
32
+ async function commitFile(name, lines, message) {
33
+ const content = Array.from({ length: lines }, (_, i) => `line ${i} of ${name}`).join("\n");
34
+ await fs.writeFile(path.join(repo, name), content + "\n");
35
+ git("add", name);
36
+ git("commit", "-m", message);
37
+ return git("rev-parse", "HEAD");
38
+ }
39
+ d("detectNewCommits", () => {
40
+ beforeEach(async () => {
41
+ repo = await fs.mkdtemp(path.join(os.tmpdir(), "gg-gitxp-"));
42
+ git("init", "-q");
43
+ });
44
+ afterEach(async () => {
45
+ await fs.rm(repo, { recursive: true, force: true });
46
+ });
47
+ it("first sight of a repo records HEAD without scoring", async () => {
48
+ await commitFile("a.txt", 10, "initial");
49
+ const result = await detectNewCommits(repo, undefined, Date.now());
50
+ expect(result).not.toBeNull();
51
+ expect(result.commits).toHaveLength(0);
52
+ expect(result.head).toBe(git("rev-parse", "HEAD"));
53
+ });
54
+ it("scores new commits with line counts and patch ids", async () => {
55
+ const base = await commitFile("a.txt", 5, "initial");
56
+ const runStart = Date.now();
57
+ await commitFile("b.txt", 30, "feature");
58
+ const result = await detectNewCommits(repo, base, runStart);
59
+ expect(result.commits).toHaveLength(1);
60
+ expect(result.commits[0].linesChanged).toBe(30);
61
+ expect(result.commits[0].patchId).toMatch(/^[0-9a-f]{40}/);
62
+ });
63
+ it("returns no commits when HEAD is unchanged", async () => {
64
+ const head = await commitFile("a.txt", 5, "initial");
65
+ const result = await detectNewCommits(repo, head, Date.now());
66
+ expect(result.commits).toHaveLength(0);
67
+ });
68
+ it("excludes commits authored before the run window", async () => {
69
+ const base = await commitFile("a.txt", 5, "initial");
70
+ await commitFile("b.txt", 10, "old work");
71
+ // Run "started" an hour from now — the commit above predates the window.
72
+ const result = await detectNewCommits(repo, base, Date.now() + 60 * 60 * 1000);
73
+ expect(result.commits).toHaveLength(0);
74
+ });
75
+ it("resets the baseline silently when lastHead is unknown", async () => {
76
+ await commitFile("a.txt", 5, "initial");
77
+ const result = await detectNewCommits(repo, "0".repeat(40), Date.now());
78
+ expect(result).not.toBeNull();
79
+ expect(result.commits).toHaveLength(0);
80
+ });
81
+ it("returns null for a non-repo directory", async () => {
82
+ const plain = await fs.mkdtemp(path.join(os.tmpdir(), "gg-plain-"));
83
+ const result = await detectNewCommits(plain, undefined, Date.now());
84
+ expect(result).toBeNull();
85
+ await fs.rm(plain, { recursive: true, force: true });
86
+ });
87
+ });
88
+ //# sourceMappingURL=git-xp.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-xp.test.js","sourceRoot":"","sources":["../../../src/core/progress/git-xp.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { ProgressFile, ProgressSnapshot, RankLadderEntry } from "./types.js";
2
+ export declare const MAX_LEVEL = 50;
3
+ /** Cumulative XP required to reach level n (level 1 = 0). */
4
+ export declare function xpForLevel(n: number): number;
5
+ /** Level for a cumulative XP total (1..MAX_LEVEL). */
6
+ export declare function levelForXp(xp: number): number;
7
+ export interface RankInfo {
8
+ level: number;
9
+ name: string;
10
+ tier: number;
11
+ tierName: string;
12
+ tierGlyph: string;
13
+ effectId: string;
14
+ }
15
+ /** Rank metadata for a level (clamped to 1..MAX_LEVEL). */
16
+ export declare function rankForLevel(level: number): RankInfo;
17
+ /** Build the broadcast snapshot the webview renders verbatim. */
18
+ export declare function buildSnapshot(file: ProgressFile): ProgressSnapshot;
19
+ /** Full 50-entry ladder for the scorecard. */
20
+ export declare function rankLadder(): RankLadderEntry[];
21
+ //# sourceMappingURL=ranks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ranks.d.ts","sourceRoot":"","sources":["../../../src/core/progress/ranks.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElF,eAAO,MAAM,SAAS,KAAK,CAAC;AAwE5B,6DAA6D;AAC7D,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAG5C;AAED,sDAAsD;AACtD,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAI7C;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,2DAA2D;AAC3D,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAYpD;AAED,iEAAiE;AACjE,wBAAgB,aAAa,CAAC,IAAI,EAAE,YAAY,GAAG,gBAAgB,CA+BlE;AAED,8CAA8C;AAC9C,wBAAgB,UAAU,IAAI,eAAe,EAAE,CAc9C"}
@@ -0,0 +1,141 @@
1
+ // The 50-rank ladder: 10 tiers × 5 levels, with CSS effect ids and tier glyphs.
2
+ export const MAX_LEVEL = 50;
3
+ const TIERS = [
4
+ {
5
+ name: "Boot",
6
+ glyph: "○",
7
+ effectId: "dim",
8
+ ranks: ["Lurker", "Tinkerer", "Prompter", "Looper", "Scripter"],
9
+ },
10
+ {
11
+ name: "Ship",
12
+ glyph: "◇",
13
+ effectId: "plain",
14
+ ranks: ["Patcher", "Forker", "Merger", "Shipper", "Builder"],
15
+ },
16
+ {
17
+ name: "Flow",
18
+ glyph: "◆",
19
+ effectId: "blue",
20
+ ranks: ["Hacker", "Stacker", "Debugger", "Compiler", "Operator"],
21
+ },
22
+ {
23
+ name: "Craft",
24
+ glyph: "⬖",
25
+ effectId: "green",
26
+ ranks: ["Toolsmith", "Machinist", "Optimizer", "Artificer", "Architect"],
27
+ },
28
+ {
29
+ name: "Vibe",
30
+ glyph: "✦",
31
+ effectId: "gradient",
32
+ ranks: ["Vibesmith", "Codeslinger", "Bytebender", "Overclocker", "Netrunner"],
33
+ },
34
+ {
35
+ name: "Deep",
36
+ glyph: "✧",
37
+ effectId: "gradient-glow",
38
+ ranks: ["Cipher", "Daemon", "Phantom", "Glitch", "Specter"],
39
+ },
40
+ {
41
+ name: "Arcane",
42
+ glyph: "❖",
43
+ effectId: "animated",
44
+ ranks: ["Warlock", "Technomancer", "Codeweaver", "Archmage", "Oracle"],
45
+ },
46
+ {
47
+ name: "Root",
48
+ glyph: "⬢",
49
+ effectId: "gold",
50
+ ranks: ["Shellmaster", "Kernelghost", "Gitlord", "Mainframe", "Root"],
51
+ },
52
+ {
53
+ name: "Myth",
54
+ glyph: "★",
55
+ effectId: "gold-shimmer",
56
+ ranks: ["Basilisk", "Ascendant", "Sentinel", "Harbinger", "Titan"],
57
+ },
58
+ {
59
+ name: "Beyond",
60
+ glyph: "✹",
61
+ effectId: "iridescent",
62
+ ranks: ["Anomaly", "Entity", "Overmind", "Deity", "Singularity"],
63
+ },
64
+ ];
65
+ /** Cumulative XP required to reach level n (level 1 = 0). */
66
+ export function xpForLevel(n) {
67
+ if (n <= 1)
68
+ return 0;
69
+ return Math.round(100 * Math.pow(n, 1.6));
70
+ }
71
+ /** Level for a cumulative XP total (1..MAX_LEVEL). */
72
+ export function levelForXp(xp) {
73
+ let level = 1;
74
+ while (level < MAX_LEVEL && xp >= xpForLevel(level + 1))
75
+ level++;
76
+ return level;
77
+ }
78
+ /** Rank metadata for a level (clamped to 1..MAX_LEVEL). */
79
+ export function rankForLevel(level) {
80
+ const l = Math.min(Math.max(1, Math.floor(level)), MAX_LEVEL);
81
+ const tierIndex = Math.floor((l - 1) / 5);
82
+ const tier = TIERS[tierIndex];
83
+ return {
84
+ level: l,
85
+ name: tier.ranks[(l - 1) % 5],
86
+ tier: tierIndex + 1,
87
+ tierName: tier.name,
88
+ tierGlyph: tier.glyph,
89
+ effectId: tier.effectId,
90
+ };
91
+ }
92
+ /** Build the broadcast snapshot the webview renders verbatim. */
93
+ export function buildSnapshot(file) {
94
+ const level = levelForXp(file.xp);
95
+ const rank = rankForLevel(level);
96
+ const floor = xpForLevel(level);
97
+ const ceil = level >= MAX_LEVEL ? floor : xpForLevel(level + 1);
98
+ const span = Math.max(1, ceil - floor);
99
+ const into = Math.max(0, file.xp - floor);
100
+ return {
101
+ level,
102
+ rankName: rank.name,
103
+ tier: rank.tier,
104
+ tierName: rank.tierName,
105
+ tierGlyph: rank.tierGlyph,
106
+ effectId: rank.effectId,
107
+ xp: file.xp,
108
+ xpIntoLevel: into,
109
+ xpForLevel: span,
110
+ percent: level >= MAX_LEVEL ? 100 : Math.min(100, Math.floor((into / span) * 100)),
111
+ streak: { current: file.streak.current, best: file.streak.best },
112
+ totals: {
113
+ prompts: file.totals.prompts,
114
+ commits: file.totals.commits,
115
+ linesShipped: file.totals.linesShipped,
116
+ projects: file.totals.projects.length,
117
+ },
118
+ xpBySource: { ...file.xpBySource },
119
+ memberSince: file.createdAt,
120
+ ladder: rankLadder(),
121
+ levelUp: file.lastEvent?.levelUp ?? null,
122
+ eventNonce: file.lastEvent?.nonce ?? null,
123
+ };
124
+ }
125
+ /** Full 50-entry ladder for the scorecard. */
126
+ export function rankLadder() {
127
+ const ladder = [];
128
+ for (let level = 1; level <= MAX_LEVEL; level++) {
129
+ const rank = rankForLevel(level);
130
+ ladder.push({
131
+ level,
132
+ name: rank.name,
133
+ tier: rank.tier,
134
+ tierName: rank.tierName,
135
+ effectId: rank.effectId,
136
+ xpRequired: xpForLevel(level),
137
+ });
138
+ }
139
+ return ladder;
140
+ }
141
+ //# sourceMappingURL=ranks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ranks.js","sourceRoot":"","sources":["../../../src/core/progress/ranks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ranks.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ranks.test.d.ts","sourceRoot":"","sources":["../../../src/core/progress/ranks.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,59 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { MAX_LEVEL, levelForXp, rankForLevel, rankLadder, xpForLevel } from "./ranks.js";
3
+ describe("xpForLevel", () => {
4
+ it("matches the 100 × N^1.6 curve", () => {
5
+ expect(xpForLevel(1)).toBe(0);
6
+ expect(xpForLevel(2)).toBe(303);
7
+ expect(xpForLevel(10)).toBe(3981);
8
+ expect(xpForLevel(50)).toBe(52282);
9
+ });
10
+ it("is strictly increasing", () => {
11
+ for (let n = 2; n <= MAX_LEVEL; n++) {
12
+ expect(xpForLevel(n)).toBeGreaterThan(xpForLevel(n - 1));
13
+ }
14
+ });
15
+ });
16
+ describe("levelForXp", () => {
17
+ it("returns 1 at 0 XP", () => {
18
+ expect(levelForXp(0)).toBe(1);
19
+ });
20
+ it("crosses level boundaries exactly", () => {
21
+ expect(levelForXp(302)).toBe(1);
22
+ expect(levelForXp(303)).toBe(2);
23
+ expect(levelForXp(3981)).toBe(10);
24
+ });
25
+ it("caps at MAX_LEVEL", () => {
26
+ expect(levelForXp(10_000_000)).toBe(MAX_LEVEL);
27
+ });
28
+ });
29
+ describe("rankForLevel", () => {
30
+ it("names the tier boundaries correctly", () => {
31
+ expect(rankForLevel(1).name).toBe("Lurker");
32
+ expect(rankForLevel(5).name).toBe("Scripter");
33
+ expect(rankForLevel(6).name).toBe("Patcher");
34
+ expect(rankForLevel(20).name).toBe("Architect");
35
+ expect(rankForLevel(25).name).toBe("Netrunner");
36
+ expect(rankForLevel(50).name).toBe("Singularity");
37
+ });
38
+ it("assigns tiers 1–10", () => {
39
+ expect(rankForLevel(1).tier).toBe(1);
40
+ expect(rankForLevel(5).tier).toBe(1);
41
+ expect(rankForLevel(6).tier).toBe(2);
42
+ expect(rankForLevel(50).tier).toBe(10);
43
+ });
44
+ it("clamps out-of-range levels", () => {
45
+ expect(rankForLevel(0).level).toBe(1);
46
+ expect(rankForLevel(99).level).toBe(50);
47
+ });
48
+ });
49
+ describe("rankLadder", () => {
50
+ it("has 50 unique names with increasing xpRequired", () => {
51
+ const ladder = rankLadder();
52
+ expect(ladder).toHaveLength(50);
53
+ expect(new Set(ladder.map((r) => r.name)).size).toBe(50);
54
+ for (let i = 1; i < ladder.length; i++) {
55
+ expect(ladder[i].xpRequired).toBeGreaterThan(ladder[i - 1].xpRequired);
56
+ }
57
+ });
58
+ });
59
+ //# sourceMappingURL=ranks.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ranks.test.js","sourceRoot":"","sources":["../../../src/core/progress/ranks.test.ts"],"names":[],"mappings":"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"}