@kenkaiiii/ggcoder 5.6.2 → 5.7.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 (50) hide show
  1. package/dist/app-sidecar.js +124 -2
  2. package/dist/app-sidecar.js.map +1 -1
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +26 -13
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/progress/engine.d.ts +23 -0
  7. package/dist/core/progress/engine.d.ts.map +1 -0
  8. package/dist/core/progress/engine.js +131 -0
  9. package/dist/core/progress/engine.js.map +1 -0
  10. package/dist/core/progress/engine.test.d.ts +2 -0
  11. package/dist/core/progress/engine.test.d.ts.map +1 -0
  12. package/dist/core/progress/engine.test.js +136 -0
  13. package/dist/core/progress/engine.test.js.map +1 -0
  14. package/dist/core/progress/git-xp.d.ts +16 -0
  15. package/dist/core/progress/git-xp.d.ts.map +1 -0
  16. package/dist/core/progress/git-xp.js +106 -0
  17. package/dist/core/progress/git-xp.js.map +1 -0
  18. package/dist/core/progress/git-xp.test.d.ts +2 -0
  19. package/dist/core/progress/git-xp.test.d.ts.map +1 -0
  20. package/dist/core/progress/git-xp.test.js +88 -0
  21. package/dist/core/progress/git-xp.test.js.map +1 -0
  22. package/dist/core/progress/ranks.d.ts +21 -0
  23. package/dist/core/progress/ranks.d.ts.map +1 -0
  24. package/dist/core/progress/ranks.js +141 -0
  25. package/dist/core/progress/ranks.js.map +1 -0
  26. package/dist/core/progress/ranks.test.d.ts +2 -0
  27. package/dist/core/progress/ranks.test.d.ts.map +1 -0
  28. package/dist/core/progress/ranks.test.js +59 -0
  29. package/dist/core/progress/ranks.test.js.map +1 -0
  30. package/dist/core/progress/rebuild.d.ts +7 -0
  31. package/dist/core/progress/rebuild.d.ts.map +1 -0
  32. package/dist/core/progress/rebuild.js +106 -0
  33. package/dist/core/progress/rebuild.js.map +1 -0
  34. package/dist/core/progress/rebuild.test.d.ts +2 -0
  35. package/dist/core/progress/rebuild.test.d.ts.map +1 -0
  36. package/dist/core/progress/rebuild.test.js +72 -0
  37. package/dist/core/progress/rebuild.test.js.map +1 -0
  38. package/dist/core/progress/store.d.ts +35 -0
  39. package/dist/core/progress/store.d.ts.map +1 -0
  40. package/dist/core/progress/store.js +200 -0
  41. package/dist/core/progress/store.js.map +1 -0
  42. package/dist/core/progress/store.test.d.ts +2 -0
  43. package/dist/core/progress/store.test.d.ts.map +1 -0
  44. package/dist/core/progress/store.test.js +108 -0
  45. package/dist/core/progress/store.test.js.map +1 -0
  46. package/dist/core/progress/types.d.ts +108 -0
  47. package/dist/core/progress/types.d.ts.map +1 -0
  48. package/dist/core/progress/types.js +3 -0
  49. package/dist/core/progress/types.js.map +1 -0
  50. package/package.json +4 -4
@@ -0,0 +1,72 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
+ import { levelForXp, xpForLevel } from "./ranks.js";
6
+ import { rebuildFromSessions } from "./rebuild.js";
7
+ let sessionsDir;
8
+ beforeEach(async () => {
9
+ sessionsDir = await fs.mkdtemp(path.join(os.tmpdir(), "gg-sessions-"));
10
+ });
11
+ afterEach(async () => {
12
+ await fs.rm(sessionsDir, { recursive: true, force: true });
13
+ });
14
+ function sessionJsonl(id, timestamp, userPrompts) {
15
+ const lines = [JSON.stringify({ type: "session", version: 2, id, timestamp, cwd: "/x" })];
16
+ for (let i = 0; i < userPrompts; i++) {
17
+ lines.push(JSON.stringify({
18
+ type: "message",
19
+ id: `m${i}`,
20
+ parentId: null,
21
+ timestamp,
22
+ message: { role: "user", content: `prompt ${i}` },
23
+ }), JSON.stringify({
24
+ type: "message",
25
+ id: `a${i}`,
26
+ parentId: `m${i}`,
27
+ timestamp,
28
+ message: { role: "assistant", content: "ok" },
29
+ }));
30
+ }
31
+ return lines.join("\n") + "\n";
32
+ }
33
+ async function writeSession(project, name, content) {
34
+ const dir = path.join(sessionsDir, project);
35
+ await fs.mkdir(dir, { recursive: true });
36
+ await fs.writeFile(path.join(dir, name), content);
37
+ }
38
+ describe("rebuildFromSessions", () => {
39
+ it("returns null when there is no history", async () => {
40
+ expect(await rebuildFromSessions(sessionsDir)).toBeNull();
41
+ expect(await rebuildFromSessions(path.join(sessionsDir, "missing"))).toBeNull();
42
+ });
43
+ it("awards +10 per historical user prompt", async () => {
44
+ await writeSession("_proj_a", "s1.jsonl", sessionJsonl("s1", "2025-01-01T00:00:00Z", 7));
45
+ const file = await rebuildFromSessions(sessionsDir);
46
+ expect(file).not.toBeNull();
47
+ expect(file.xp).toBe(70);
48
+ expect(file.totals.prompts).toBe(7);
49
+ expect(file.xpBySource.prompts).toBe(70);
50
+ });
51
+ it("caps seeded XP at level 15", async () => {
52
+ // 2000 prompts × 10 = 20,000 XP > level-15 cap.
53
+ await writeSession("_proj_a", "s1.jsonl", sessionJsonl("s1", "2025-01-01T00:00:00Z", 2000));
54
+ const file = await rebuildFromSessions(sessionsDir);
55
+ expect(file.xp).toBe(xpForLevel(15));
56
+ expect(levelForXp(file.xp)).toBe(15);
57
+ });
58
+ it("uses the oldest session timestamp as createdAt and counts projects", async () => {
59
+ await writeSession("_proj_a", "s1.jsonl", sessionJsonl("s1", "2024-06-15T00:00:00Z", 3));
60
+ await writeSession("_proj_b", "s2.jsonl", sessionJsonl("s2", "2023-02-01T00:00:00Z", 2));
61
+ const file = await rebuildFromSessions(sessionsDir);
62
+ expect(file.createdAt).toBe("2023-02-01T00:00:00Z");
63
+ expect(file.totals.projects).toHaveLength(2);
64
+ });
65
+ it("skips malformed lines and empty sessions", async () => {
66
+ await writeSession("_proj_a", "bad.jsonl", "not json\n{broken\n");
67
+ await writeSession("_proj_a", "s1.jsonl", sessionJsonl("s1", "2025-01-01T00:00:00Z", 1));
68
+ const file = await rebuildFromSessions(sessionsDir);
69
+ expect(file.xp).toBe(10);
70
+ });
71
+ });
72
+ //# sourceMappingURL=rebuild.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rebuild.test.js","sourceRoot":"","sources":["../../../src/core/progress/rebuild.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { ProgressFile } from "./types.js";
2
+ export interface ProgressStoreOptions {
3
+ filePath?: string;
4
+ backupPath?: string;
5
+ /** Called when neither the main file nor the backup is usable. */
6
+ rebuild?: () => Promise<ProgressFile | null>;
7
+ }
8
+ export declare function signProgress(file: ProgressFile): string;
9
+ export declare function verifyProgress(file: ProgressFile): boolean;
10
+ declare function dayKey(now: Date): string;
11
+ export declare function createEmptyProgress(now?: Date): ProgressFile;
12
+ /**
13
+ * Read-only peek: main file → backup → null. Never writes — safe for fs.watch
14
+ * reloads that may race an in-flight atomic rename.
15
+ */
16
+ export declare function peekProgress(opts?: ProgressStoreOptions): Promise<ProgressFile | null>;
17
+ /**
18
+ * Load progress with the recovery chain: main file → backup → rebuild → empty.
19
+ * Never silently zeroes a user if any recoverable state exists.
20
+ */
21
+ export declare function loadProgress(opts?: ProgressStoreOptions): Promise<ProgressFile>;
22
+ /**
23
+ * Save progress: sign, atomic write under a file lock, and maintain the backup
24
+ * (written when the level increased — caller passes `levelledUp` — or at most once/day).
25
+ */
26
+ export declare function saveProgress(file: ProgressFile, opts?: ProgressStoreOptions, levelledUp?: boolean): Promise<void>;
27
+ /**
28
+ * Read-modify-write under the lock — the safe way for concurrent sidecars to award XP.
29
+ */
30
+ export declare function updateProgress(mutate: (file: ProgressFile) => Promise<{
31
+ file: ProgressFile;
32
+ levelledUp: boolean;
33
+ }>, opts?: ProgressStoreOptions): Promise<ProgressFile>;
34
+ export { dayKey };
35
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/core/progress/store.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK/C,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;CAC9C;AA2BD,wBAAgB,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAEvD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAQ1D;AAED,iBAAS,MAAM,CAAC,GAAG,EAAE,IAAI,GAAG,MAAM,CAKjC;AAED,wBAAgB,mBAAmB,CAAC,GAAG,OAAa,GAAG,YAAY,CAgBlE;AAmCD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,IAAI,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAG5F;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,IAAI,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,YAAY,CAAC,CA4BrF;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,YAAY,EAClB,IAAI,CAAC,EAAE,oBAAoB,EAC3B,UAAU,UAAQ,GACjB,OAAO,CAAC,IAAI,CAAC,CAef;AAWD;;GAEG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC,EACpF,IAAI,CAAC,EAAE,oBAAoB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAyBvB;AAED,OAAO,EAAE,MAAM,EAAE,CAAC"}
@@ -0,0 +1,200 @@
1
+ // Durable progress persistence: locked atomic writes, HMAC signing, backup + recovery chain.
2
+ //
3
+ // The HMAC key ships in the app bundle, so this is a tamper *deterrent*, not tamper-proof —
4
+ // real integrity would need server-side recomputation (leaderboard phase).
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+ import crypto from "node:crypto";
8
+ import { getAppPaths, withFileLock } from "@kenkaiiii/gg-core";
9
+ const HMAC_KEY = "gg-coder-progress-v1-9f2c4e7a1b8d3f6c";
10
+ const PATCH_ID_CAP = 500;
11
+ function resolvePaths(opts) {
12
+ const paths = getAppPaths();
13
+ return {
14
+ filePath: opts?.filePath ?? paths.progressFile,
15
+ backupPath: opts?.backupPath ?? paths.progressBackupFile,
16
+ };
17
+ }
18
+ /** Stable-key JSON of the file minus `sig`, for signing. */
19
+ function canonicalJson(file) {
20
+ const clone = { ...file };
21
+ delete clone.sig;
22
+ return stableStringify(clone);
23
+ }
24
+ function stableStringify(value) {
25
+ if (value === null || typeof value !== "object")
26
+ return JSON.stringify(value);
27
+ if (Array.isArray(value))
28
+ return `[${value.map(stableStringify).join(",")}]`;
29
+ const keys = Object.keys(value).sort();
30
+ const parts = keys.map((k) => `${JSON.stringify(k)}:${stableStringify(value[k])}`);
31
+ return `{${parts.join(",")}}`;
32
+ }
33
+ export function signProgress(file) {
34
+ return crypto.createHmac("sha256", HMAC_KEY).update(canonicalJson(file)).digest("hex");
35
+ }
36
+ export function verifyProgress(file) {
37
+ if (typeof file.sig !== "string" || file.sig.length === 0)
38
+ return false;
39
+ const expected = signProgress(file);
40
+ try {
41
+ return crypto.timingSafeEqual(Buffer.from(file.sig, "hex"), Buffer.from(expected, "hex"));
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ function dayKey(now) {
48
+ const y = now.getFullYear();
49
+ const m = String(now.getMonth() + 1).padStart(2, "0");
50
+ const d = String(now.getDate()).padStart(2, "0");
51
+ return `${y}-${m}-${d}`;
52
+ }
53
+ export function createEmptyProgress(now = new Date()) {
54
+ const file = {
55
+ v: 1,
56
+ xp: 0,
57
+ createdAt: now.toISOString(),
58
+ totals: { prompts: 0, commits: 0, linesShipped: 0, projects: [] },
59
+ xpBySource: { prompts: 0, commits: 0, streakBonus: 0 },
60
+ streak: { current: 0, best: 0, lastActiveDay: "" },
61
+ rolling: { promptTimes: [], commitTimes: [], dayXp: 0, dayKey: dayKey(now) },
62
+ repos: {},
63
+ patchIds: [],
64
+ lastEvent: null,
65
+ sig: "",
66
+ };
67
+ file.sig = signProgress(file);
68
+ return file;
69
+ }
70
+ function isValidShape(file) {
71
+ if (typeof file !== "object" || file === null)
72
+ return false;
73
+ const f = file;
74
+ return (f.v === 1 &&
75
+ typeof f.xp === "number" &&
76
+ typeof f.createdAt === "string" &&
77
+ typeof f.totals === "object" &&
78
+ typeof f.streak === "object" &&
79
+ typeof f.rolling === "object" &&
80
+ typeof f.sig === "string");
81
+ }
82
+ async function readFileIfValid(filePath) {
83
+ try {
84
+ const raw = await fs.readFile(filePath, "utf-8");
85
+ const parsed = JSON.parse(raw);
86
+ if (!isValidShape(parsed))
87
+ return null;
88
+ if (!verifyProgress(parsed))
89
+ return null;
90
+ return parsed;
91
+ }
92
+ catch {
93
+ return null;
94
+ }
95
+ }
96
+ async function atomicWrite(filePath, contents) {
97
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
98
+ const tmp = `${filePath}.tmp`;
99
+ await fs.writeFile(tmp, contents, "utf-8");
100
+ await fs.rename(tmp, filePath);
101
+ }
102
+ /**
103
+ * Read-only peek: main file → backup → null. Never writes — safe for fs.watch
104
+ * reloads that may race an in-flight atomic rename.
105
+ */
106
+ export async function peekProgress(opts) {
107
+ const { filePath, backupPath } = resolvePaths(opts);
108
+ return (await readFileIfValid(filePath)) ?? (await readFileIfValid(backupPath));
109
+ }
110
+ /**
111
+ * Load progress with the recovery chain: main file → backup → rebuild → empty.
112
+ * Never silently zeroes a user if any recoverable state exists.
113
+ */
114
+ export async function loadProgress(opts) {
115
+ const { filePath, backupPath } = resolvePaths(opts);
116
+ const main = await readFileIfValid(filePath);
117
+ if (main)
118
+ return main;
119
+ const backup = await readFileIfValid(backupPath);
120
+ if (backup) {
121
+ // Restore the main file from backup.
122
+ await saveProgress(backup, opts).catch(() => { });
123
+ return backup;
124
+ }
125
+ if (opts?.rebuild) {
126
+ try {
127
+ const rebuilt = await opts.rebuild();
128
+ if (rebuilt) {
129
+ rebuilt.sig = signProgress(rebuilt);
130
+ await saveProgress(rebuilt, opts).catch(() => { });
131
+ return rebuilt;
132
+ }
133
+ }
134
+ catch {
135
+ // fall through to empty
136
+ }
137
+ }
138
+ const empty = createEmptyProgress();
139
+ await saveProgress(empty, opts).catch(() => { });
140
+ return empty;
141
+ }
142
+ /**
143
+ * Save progress: sign, atomic write under a file lock, and maintain the backup
144
+ * (written when the level increased — caller passes `levelledUp` — or at most once/day).
145
+ */
146
+ export async function saveProgress(file, opts, levelledUp = false) {
147
+ const { filePath, backupPath } = resolvePaths(opts);
148
+ // Keep the patch-id ring buffer bounded.
149
+ if (file.patchIds.length > PATCH_ID_CAP) {
150
+ file.patchIds = file.patchIds.slice(-PATCH_ID_CAP);
151
+ }
152
+ file.sig = signProgress(file);
153
+ const contents = JSON.stringify(file, null, 2);
154
+ await withFileLock(filePath, async () => {
155
+ await atomicWrite(filePath, contents);
156
+ if (levelledUp || (await backupIsStale(backupPath))) {
157
+ await atomicWrite(backupPath, contents).catch(() => { });
158
+ }
159
+ });
160
+ }
161
+ async function backupIsStale(backupPath) {
162
+ try {
163
+ const stat = await fs.stat(backupPath);
164
+ return Date.now() - stat.mtimeMs > 24 * 60 * 60 * 1000;
165
+ }
166
+ catch {
167
+ return true; // missing → write it
168
+ }
169
+ }
170
+ /**
171
+ * Read-modify-write under the lock — the safe way for concurrent sidecars to award XP.
172
+ */
173
+ export async function updateProgress(mutate, opts) {
174
+ const { filePath, backupPath } = resolvePaths(opts);
175
+ return withFileLock(filePath, async () => {
176
+ // Do the recovery read inside the SAME lock used by saveProgress, without
177
+ // calling loadProgress/saveProgress recursively. That keeps multi-sidecar
178
+ // read-modify-write atomic instead of racing a plain save on another lock.
179
+ let current = (await readFileIfValid(filePath)) ?? (await readFileIfValid(backupPath));
180
+ if (!current && opts?.rebuild) {
181
+ current = await opts.rebuild().catch(() => null);
182
+ if (current)
183
+ current.sig = signProgress(current);
184
+ }
185
+ current ??= createEmptyProgress();
186
+ const { file, levelledUp } = await mutate(current);
187
+ if (file.patchIds.length > PATCH_ID_CAP) {
188
+ file.patchIds = file.patchIds.slice(-PATCH_ID_CAP);
189
+ }
190
+ file.sig = signProgress(file);
191
+ const contents = JSON.stringify(file, null, 2);
192
+ await atomicWrite(filePath, contents);
193
+ if (levelledUp || (await backupIsStale(backupPath))) {
194
+ await atomicWrite(backupPath, contents).catch(() => { });
195
+ }
196
+ return file;
197
+ });
198
+ }
199
+ export { dayKey };
200
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/core/progress/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=store.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.test.d.ts","sourceRoot":"","sources":["../../../src/core/progress/store.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,108 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
+ import { createEmptyProgress, loadProgress, saveProgress, signProgress, updateProgress, verifyProgress, } from "./store.js";
6
+ let dir;
7
+ let opts;
8
+ beforeEach(async () => {
9
+ dir = await fs.mkdtemp(path.join(os.tmpdir(), "gg-progress-"));
10
+ opts = {
11
+ filePath: path.join(dir, "progress.json"),
12
+ backupPath: path.join(dir, "progress.backup.json"),
13
+ };
14
+ });
15
+ afterEach(async () => {
16
+ await fs.rm(dir, { recursive: true, force: true });
17
+ });
18
+ describe("HMAC signing", () => {
19
+ it("round-trips sign + verify", () => {
20
+ const file = createEmptyProgress();
21
+ expect(verifyProgress(file)).toBe(true);
22
+ });
23
+ it("rejects tampered XP", () => {
24
+ const file = createEmptyProgress();
25
+ file.xp = 999_999;
26
+ expect(verifyProgress(file)).toBe(false);
27
+ });
28
+ it("rejects a missing sig", () => {
29
+ const file = createEmptyProgress();
30
+ file.sig = "";
31
+ expect(verifyProgress(file)).toBe(false);
32
+ });
33
+ });
34
+ describe("save + load", () => {
35
+ it("round-trips through disk", async () => {
36
+ const file = createEmptyProgress();
37
+ file.xp = 1234;
38
+ await saveProgress(file, opts);
39
+ const loaded = await loadProgress(opts);
40
+ expect(loaded.xp).toBe(1234);
41
+ });
42
+ it("writes a backup on first save", async () => {
43
+ await saveProgress(createEmptyProgress(), opts);
44
+ const backup = JSON.parse(await fs.readFile(opts.backupPath, "utf-8"));
45
+ expect(verifyProgress(backup)).toBe(true);
46
+ });
47
+ it("restores from backup when main file is deleted", async () => {
48
+ const file = createEmptyProgress();
49
+ file.xp = 777;
50
+ await saveProgress(file, opts);
51
+ await fs.unlink(opts.filePath);
52
+ const loaded = await loadProgress(opts);
53
+ expect(loaded.xp).toBe(777);
54
+ // Main file restored too.
55
+ const raw = JSON.parse(await fs.readFile(opts.filePath, "utf-8"));
56
+ expect(raw.xp).toBe(777);
57
+ });
58
+ it("restores from backup when main file is hand-edited (bad HMAC)", async () => {
59
+ const file = createEmptyProgress();
60
+ file.xp = 500;
61
+ await saveProgress(file, opts);
62
+ const tampered = JSON.parse(await fs.readFile(opts.filePath, "utf-8"));
63
+ tampered.xp = 999_999;
64
+ await fs.writeFile(opts.filePath, JSON.stringify(tampered));
65
+ const loaded = await loadProgress(opts);
66
+ expect(loaded.xp).toBe(500);
67
+ });
68
+ it("rebuilds when both main and backup are gone", async () => {
69
+ const rebuilt = createEmptyProgress();
70
+ rebuilt.xp = 4242;
71
+ rebuilt.sig = signProgress(rebuilt);
72
+ const loaded = await loadProgress({ ...opts, rebuild: async () => rebuilt });
73
+ expect(loaded.xp).toBe(4242);
74
+ });
75
+ it("falls back to empty when nothing is recoverable", async () => {
76
+ const loaded = await loadProgress(opts);
77
+ expect(loaded.xp).toBe(0);
78
+ expect(loaded.v).toBe(1);
79
+ });
80
+ it("survives corrupt JSON in the main file", async () => {
81
+ await fs.mkdir(dir, { recursive: true });
82
+ await fs.writeFile(opts.filePath, "{not json!!");
83
+ const loaded = await loadProgress(opts);
84
+ expect(loaded.xp).toBe(0);
85
+ });
86
+ });
87
+ describe("updateProgress", () => {
88
+ it("applies a read-modify-write and re-signs", async () => {
89
+ await saveProgress(createEmptyProgress(), opts);
90
+ const updated = await updateProgress(async (f) => {
91
+ f.xp += 100;
92
+ return { file: f, levelledUp: false };
93
+ }, opts);
94
+ expect(updated.xp).toBe(100);
95
+ expect(verifyProgress(updated)).toBe(true);
96
+ const loaded = await loadProgress(opts);
97
+ expect(loaded.xp).toBe(100);
98
+ });
99
+ it("caps the patch-id ring buffer at 500", async () => {
100
+ const updated = await updateProgress(async (f) => {
101
+ f.patchIds = Array.from({ length: 600 }, (_, i) => `pid-${i}`);
102
+ return { file: f, levelledUp: false };
103
+ }, opts);
104
+ expect(updated.patchIds).toHaveLength(500);
105
+ expect(updated.patchIds[0]).toBe("pid-100");
106
+ });
107
+ });
108
+ //# sourceMappingURL=store.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.test.js","sourceRoot":"","sources":["../../../src/core/progress/store.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,108 @@
1
+ /** Rank-up event embedded in the file so watcher-side sidecars can re-broadcast it once. */
2
+ export interface LevelUpEvent {
3
+ from: number;
4
+ to: number;
5
+ rankName: string;
6
+ }
7
+ /** Last award event marker — nonce lets other windows dedupe celebrations. */
8
+ export interface ProgressLastEvent {
9
+ nonce: string;
10
+ levelUp: LevelUpEvent | null;
11
+ }
12
+ /** Durable on-disk progress file (~/.gg/progress.json), versioned + HMAC-signed. */
13
+ export interface ProgressFile {
14
+ v: 1;
15
+ xp: number;
16
+ createdAt: string;
17
+ totals: {
18
+ prompts: number;
19
+ commits: number;
20
+ linesShipped: number;
21
+ /** Hashed project cwds the user has earned XP in. */
22
+ projects: string[];
23
+ };
24
+ xpBySource: {
25
+ prompts: number;
26
+ commits: number;
27
+ streakBonus: number;
28
+ };
29
+ streak: {
30
+ current: number;
31
+ best: number;
32
+ /** Local calendar day (YYYY-MM-DD) of the last XP event. */
33
+ lastActiveDay: string;
34
+ };
35
+ rolling: {
36
+ /** Epoch-ms timestamps of prompt awards in the last hour. */
37
+ promptTimes: number[];
38
+ /** Epoch-ms timestamps of XP-earning commits in the last hour. */
39
+ commitTimes: number[];
40
+ /** XP earned so far today (for the daily soft cap). */
41
+ dayXp: number;
42
+ /** Local calendar day (YYYY-MM-DD) dayXp belongs to. */
43
+ dayKey: string;
44
+ };
45
+ /** Per-repo last seen HEAD, keyed by repo-root hash. */
46
+ repos: Record<string, {
47
+ lastHead: string;
48
+ }>;
49
+ /** Ring buffer of git patch-ids already scored (cap 500). */
50
+ patchIds: string[];
51
+ lastEvent: ProgressLastEvent | null;
52
+ /** HMAC-SHA256 of canonical JSON minus this field. */
53
+ sig: string;
54
+ }
55
+ /** One rung of the 50-rank ladder, as sent to the webview. */
56
+ export interface RankLadderEntry {
57
+ level: number;
58
+ name: string;
59
+ tier: number;
60
+ tierName: string;
61
+ effectId: string;
62
+ /** Cumulative XP required to reach this level. */
63
+ xpRequired: number;
64
+ }
65
+ /** What the sidecar broadcasts/serves — the webview renders this verbatim. */
66
+ export interface ProgressSnapshot {
67
+ level: number;
68
+ rankName: string;
69
+ tier: number;
70
+ tierName: string;
71
+ tierGlyph: string;
72
+ effectId: string;
73
+ xp: number;
74
+ xpIntoLevel: number;
75
+ xpForLevel: number;
76
+ /** 0–100 percent toward the next level. */
77
+ percent: number;
78
+ streak: {
79
+ current: number;
80
+ best: number;
81
+ };
82
+ totals: {
83
+ prompts: number;
84
+ commits: number;
85
+ linesShipped: number;
86
+ projects: number;
87
+ };
88
+ xpBySource: {
89
+ prompts: number;
90
+ commits: number;
91
+ streakBonus: number;
92
+ };
93
+ memberSince: string;
94
+ ladder: RankLadderEntry[];
95
+ levelUp: LevelUpEvent | null;
96
+ /** Nonce of the award event this snapshot was produced by (for celebration dedupe). */
97
+ eventNonce: string | null;
98
+ /** True only on the SSE frame sent to the window whose run earned the XP.
99
+ * Gates window-local feedback (sounds, XP chips); absent on GET /progress. */
100
+ origin?: boolean;
101
+ }
102
+ /** A commit that passed detection filters and is ready to be scored. */
103
+ export interface ScoredCommit {
104
+ sha: string;
105
+ patchId: string;
106
+ linesChanged: number;
107
+ }
108
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/progress/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ // Progress ("Ranks") system — shared types for the XP engine, store, and broadcast snapshot.
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/progress/types.ts"],"names":[],"mappings":"AAAA,6FAA6F"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenkaiiii/ggcoder",
3
- "version": "5.6.2",
3
+ "version": "5.7.0",
4
4
  "type": "module",
5
5
  "description": "CLI coding agent with OAuth authentication for Anthropic, OpenAI, and Gemini",
6
6
  "license": "MIT",
@@ -113,9 +113,9 @@
113
113
  "typescript-language-server": "^5.3.0",
114
114
  "wrap-ansi": "^10.0.0",
115
115
  "zod": "^4.4.3",
116
- "@kenkaiiii/gg-ai": "5.6.2",
117
- "@kenkaiiii/gg-core": "5.6.2",
118
- "@kenkaiiii/gg-agent": "5.6.2"
116
+ "@kenkaiiii/gg-agent": "5.7.0",
117
+ "@kenkaiiii/gg-ai": "5.7.0",
118
+ "@kenkaiiii/gg-core": "5.7.0"
119
119
  },
120
120
  "optionalDependencies": {
121
121
  "@huggingface/transformers": "^3.6.0",