@kenkaiiii/ggcoder 5.6.3 → 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 (47) hide show
  1. package/dist/app-sidecar.js +124 -2
  2. package/dist/app-sidecar.js.map +1 -1
  3. package/dist/core/progress/engine.d.ts +23 -0
  4. package/dist/core/progress/engine.d.ts.map +1 -0
  5. package/dist/core/progress/engine.js +131 -0
  6. package/dist/core/progress/engine.js.map +1 -0
  7. package/dist/core/progress/engine.test.d.ts +2 -0
  8. package/dist/core/progress/engine.test.d.ts.map +1 -0
  9. package/dist/core/progress/engine.test.js +136 -0
  10. package/dist/core/progress/engine.test.js.map +1 -0
  11. package/dist/core/progress/git-xp.d.ts +16 -0
  12. package/dist/core/progress/git-xp.d.ts.map +1 -0
  13. package/dist/core/progress/git-xp.js +106 -0
  14. package/dist/core/progress/git-xp.js.map +1 -0
  15. package/dist/core/progress/git-xp.test.d.ts +2 -0
  16. package/dist/core/progress/git-xp.test.d.ts.map +1 -0
  17. package/dist/core/progress/git-xp.test.js +88 -0
  18. package/dist/core/progress/git-xp.test.js.map +1 -0
  19. package/dist/core/progress/ranks.d.ts +21 -0
  20. package/dist/core/progress/ranks.d.ts.map +1 -0
  21. package/dist/core/progress/ranks.js +141 -0
  22. package/dist/core/progress/ranks.js.map +1 -0
  23. package/dist/core/progress/ranks.test.d.ts +2 -0
  24. package/dist/core/progress/ranks.test.d.ts.map +1 -0
  25. package/dist/core/progress/ranks.test.js +59 -0
  26. package/dist/core/progress/ranks.test.js.map +1 -0
  27. package/dist/core/progress/rebuild.d.ts +7 -0
  28. package/dist/core/progress/rebuild.d.ts.map +1 -0
  29. package/dist/core/progress/rebuild.js +106 -0
  30. package/dist/core/progress/rebuild.js.map +1 -0
  31. package/dist/core/progress/rebuild.test.d.ts +2 -0
  32. package/dist/core/progress/rebuild.test.d.ts.map +1 -0
  33. package/dist/core/progress/rebuild.test.js +72 -0
  34. package/dist/core/progress/rebuild.test.js.map +1 -0
  35. package/dist/core/progress/store.d.ts +35 -0
  36. package/dist/core/progress/store.d.ts.map +1 -0
  37. package/dist/core/progress/store.js +200 -0
  38. package/dist/core/progress/store.js.map +1 -0
  39. package/dist/core/progress/store.test.d.ts +2 -0
  40. package/dist/core/progress/store.test.d.ts.map +1 -0
  41. package/dist/core/progress/store.test.js +108 -0
  42. package/dist/core/progress/store.test.js.map +1 -0
  43. package/dist/core/progress/types.d.ts +108 -0
  44. package/dist/core/progress/types.d.ts.map +1 -0
  45. package/dist/core/progress/types.js +3 -0
  46. package/dist/core/progress/types.js.map +1 -0
  47. package/package.json +4 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ranks.test.js","sourceRoot":"","sources":["../../../src/core/progress/ranks.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { ProgressFile } from "./types.js";
2
+ /**
3
+ * Rebuild a progress file from session history. Returns null when there is no
4
+ * history at all (fresh install → start at Lurker via createEmptyProgress).
5
+ */
6
+ export declare function rebuildFromSessions(sessionsDir?: string): Promise<ProgressFile | null>;
7
+ //# sourceMappingURL=rebuild.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rebuild.d.ts","sourceRoot":"","sources":["../../../src/core/progress/rebuild.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAiD/C;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAmD5F"}
@@ -0,0 +1,106 @@
1
+ // One-time retroactive seeding from the existing ~/.gg/sessions store. Runs only when
2
+ // both progress.json and its backup are absent — existing users open the update already
3
+ // ranked instead of starting at Lurker.
4
+ import fs from "node:fs/promises";
5
+ import { createReadStream } from "node:fs";
6
+ import { createInterface } from "node:readline";
7
+ import path from "node:path";
8
+ import { getAppPaths } from "@kenkaiiii/gg-core";
9
+ import { xpForLevel } from "./ranks.js";
10
+ import { createEmptyProgress, dayKey } from "./store.js";
11
+ /** Grandfathered XP is capped at the XP needed to reach level 15. */
12
+ const SEED_LEVEL_CAP = 15;
13
+ const XP_PER_HISTORICAL_PROMPT = 10;
14
+ /** Count user prompts + find the session header timestamp in one JSONL pass. */
15
+ function scanSessionFile(file) {
16
+ return new Promise((resolve) => {
17
+ const stream = createReadStream(file, { encoding: "utf-8" });
18
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
19
+ let userPrompts = 0;
20
+ let oldestTimestamp = null;
21
+ let done = false;
22
+ const finish = () => {
23
+ if (done)
24
+ return;
25
+ done = true;
26
+ resolve({ userPrompts, oldestTimestamp });
27
+ rl.close();
28
+ stream.destroy();
29
+ };
30
+ rl.on("line", (line) => {
31
+ if (done || !line)
32
+ return;
33
+ try {
34
+ const p = JSON.parse(line);
35
+ if (p.type === "session" && p.timestamp && !oldestTimestamp) {
36
+ oldestTimestamp = p.timestamp;
37
+ }
38
+ else if (p.type === "message" && p.message?.role === "user") {
39
+ userPrompts++;
40
+ }
41
+ }
42
+ catch {
43
+ // skip malformed line
44
+ }
45
+ });
46
+ rl.on("close", finish);
47
+ rl.on("error", finish);
48
+ stream.on("error", finish);
49
+ });
50
+ }
51
+ /**
52
+ * Rebuild a progress file from session history. Returns null when there is no
53
+ * history at all (fresh install → start at Lurker via createEmptyProgress).
54
+ */
55
+ export async function rebuildFromSessions(sessionsDir) {
56
+ const dir = sessionsDir ?? getAppPaths().sessionsDir;
57
+ let projectDirs;
58
+ try {
59
+ projectDirs = await fs.readdir(dir);
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ let totalPrompts = 0;
65
+ let projectCount = 0;
66
+ let oldest = null;
67
+ for (const entry of projectDirs) {
68
+ const projectDir = path.join(dir, entry);
69
+ let files;
70
+ try {
71
+ files = (await fs.readdir(projectDir)).filter((f) => f.endsWith(".jsonl"));
72
+ }
73
+ catch {
74
+ continue;
75
+ }
76
+ let projectPrompts = 0;
77
+ for (const f of files) {
78
+ const scan = await scanSessionFile(path.join(projectDir, f));
79
+ projectPrompts += scan.userPrompts;
80
+ if (scan.oldestTimestamp && (!oldest || scan.oldestTimestamp < oldest)) {
81
+ oldest = scan.oldestTimestamp;
82
+ }
83
+ }
84
+ if (projectPrompts > 0) {
85
+ totalPrompts += projectPrompts;
86
+ projectCount++;
87
+ }
88
+ }
89
+ if (totalPrompts === 0)
90
+ return null;
91
+ const now = new Date();
92
+ const file = createEmptyProgress(now);
93
+ const cap = xpForLevel(SEED_LEVEL_CAP);
94
+ const seeded = Math.min(totalPrompts * XP_PER_HISTORICAL_PROMPT, cap);
95
+ file.xp = seeded;
96
+ file.totals.prompts = totalPrompts;
97
+ // Historical projects are counted but their paths aren't rehashed — use opaque markers.
98
+ file.totals.projects = Array.from({ length: projectCount }, (_, i) => `seed-${i}`);
99
+ file.xpBySource.prompts = seeded;
100
+ if (oldest)
101
+ file.createdAt = oldest;
102
+ // Seeding is not "activity today" — leave streak at zero, but keep dayXp clean.
103
+ file.rolling.dayKey = dayKey(now);
104
+ return file;
105
+ }
106
+ //# sourceMappingURL=rebuild.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rebuild.js","sourceRoot":"","sources":["../../../src/core/progress/rebuild.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,wFAAwF;AACxF,wCAAwC;AAExC,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAGzD,qEAAqE;AACrE,MAAM,cAAc,GAAG,EAAE,CAAC;AAC1B,MAAM,wBAAwB,GAAG,EAAE,CAAC;AAOpC,gFAAgF;AAChF,SAAS,eAAe,CAAC,IAAY;IACnC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QAC7D,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;QACnE,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,IAAI,eAAe,GAAkB,IAAI,CAAC;QAC1C,IAAI,IAAI,GAAG,KAAK,CAAC;QACjB,MAAM,MAAM,GAAG,GAAS,EAAE;YACxB,IAAI,IAAI;gBAAE,OAAO;YACjB,IAAI,GAAG,IAAI,CAAC;YACZ,OAAO,CAAC,EAAE,WAAW,EAAE,eAAe,EAAE,CAAC,CAAC;YAC1C,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC,CAAC;QACF,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACrB,IAAI,IAAI,IAAI,CAAC,IAAI;gBAAE,OAAO;YAC1B,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAIxB,CAAC;gBACF,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,eAAe,EAAE,CAAC;oBAC5D,eAAe,GAAG,CAAC,CAAC,SAAS,CAAC;gBAChC,CAAC;qBAAM,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,OAAO,EAAE,IAAI,KAAK,MAAM,EAAE,CAAC;oBAC9D,WAAW,EAAE,CAAC;gBAChB,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,sBAAsB;YACxB,CAAC;QACH,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACvB,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACvB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,WAAoB;IAC5D,MAAM,GAAG,GAAG,WAAW,IAAI,WAAW,EAAE,CAAC,WAAW,CAAC;IACrD,IAAI,WAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,WAAW,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,MAAM,GAAkB,IAAI,CAAC;IAEjC,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;QAChC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACzC,IAAI,KAAe,CAAC;QACpB,IAAI,CAAC;YACH,KAAK,GAAG,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC7E,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,cAAc,GAAG,CAAC,CAAC;QACvB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;YAC7D,cAAc,IAAI,IAAI,CAAC,WAAW,CAAC;YACnC,IAAI,IAAI,CAAC,eAAe,IAAI,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC,EAAE,CAAC;gBACvE,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC;YAChC,CAAC;QACH,CAAC;QACD,IAAI,cAAc,GAAG,CAAC,EAAE,CAAC;YACvB,YAAY,IAAI,cAAc,CAAC;YAC/B,YAAY,EAAE,CAAC;QACjB,CAAC;IACH,CAAC;IAED,IAAI,YAAY,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,IAAI,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,UAAU,CAAC,cAAc,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,GAAG,wBAAwB,EAAE,GAAG,CAAC,CAAC;IAEtE,IAAI,CAAC,EAAE,GAAG,MAAM,CAAC;IACjB,IAAI,CAAC,MAAM,CAAC,OAAO,GAAG,YAAY,CAAC;IACnC,wFAAwF;IACxF,IAAI,CAAC,MAAM,CAAC,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACnF,IAAI,CAAC,UAAU,CAAC,OAAO,GAAG,MAAM,CAAC;IACjC,IAAI,MAAM;QAAE,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC;IACpC,gFAAgF;IAChF,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rebuild.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rebuild.test.d.ts","sourceRoot":"","sources":["../../../src/core/progress/rebuild.test.ts"],"names":[],"mappings":""}
@@ -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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"}