@prestyj/cli 5.3.0 → 5.4.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 (182) hide show
  1. package/dist/app-sidecar.js +696 -59
  2. package/dist/app-sidecar.js.map +1 -1
  3. package/dist/cli.js +2 -2
  4. package/dist/cli.js.map +1 -1
  5. package/dist/core/agent-session-compaction.test.js +138 -0
  6. package/dist/core/agent-session-compaction.test.js.map +1 -1
  7. package/dist/core/agent-session.d.ts +71 -3
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +280 -47
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/autopilot-cycle.d.ts +69 -17
  12. package/dist/core/autopilot-cycle.d.ts.map +1 -1
  13. package/dist/core/autopilot-cycle.js +82 -13
  14. package/dist/core/autopilot-cycle.js.map +1 -1
  15. package/dist/core/autopilot-cycle.test.js +206 -10
  16. package/dist/core/autopilot-cycle.test.js.map +1 -1
  17. package/dist/core/autopilot-gate.d.ts +48 -22
  18. package/dist/core/autopilot-gate.d.ts.map +1 -1
  19. package/dist/core/autopilot-gate.js +110 -1
  20. package/dist/core/autopilot-gate.js.map +1 -1
  21. package/dist/core/autopilot-gate.test.js +139 -3
  22. package/dist/core/autopilot-gate.test.js.map +1 -1
  23. package/dist/core/autopilot-verdict.d.ts +6 -0
  24. package/dist/core/autopilot-verdict.d.ts.map +1 -1
  25. package/dist/core/autopilot-verdict.js +75 -1
  26. package/dist/core/autopilot-verdict.js.map +1 -1
  27. package/dist/core/autopilot-verdict.test.js +63 -0
  28. package/dist/core/autopilot-verdict.test.js.map +1 -1
  29. package/dist/core/bash-spawn-benchmark.d.ts +2 -0
  30. package/dist/core/bash-spawn-benchmark.d.ts.map +1 -0
  31. package/dist/core/bash-spawn-benchmark.js +139 -0
  32. package/dist/core/bash-spawn-benchmark.js.map +1 -0
  33. package/dist/core/compaction/compactor.test.js +1 -1
  34. package/dist/core/compaction/compactor.test.js.map +1 -1
  35. package/dist/core/fast-apply-benchmark.d.ts +1 -1
  36. package/dist/core/fast-apply-benchmark.js +2 -2
  37. package/dist/core/fast-apply-benchmark.js.map +1 -1
  38. package/dist/core/hashline-edit-benchmark.d.ts +33 -1
  39. package/dist/core/hashline-edit-benchmark.d.ts.map +1 -1
  40. package/dist/core/hashline-edit-benchmark.js +193 -21
  41. package/dist/core/hashline-edit-benchmark.js.map +1 -1
  42. package/dist/core/hashline-edit-benchmark.test.js +51 -2
  43. package/dist/core/hashline-edit-benchmark.test.js.map +1 -1
  44. package/dist/core/mcp/deferred-catalog.d.ts +28 -0
  45. package/dist/core/mcp/deferred-catalog.d.ts.map +1 -0
  46. package/dist/core/mcp/deferred-catalog.js +72 -0
  47. package/dist/core/mcp/deferred-catalog.js.map +1 -0
  48. package/dist/core/mcp/resolve-stdio.d.ts +19 -3
  49. package/dist/core/mcp/resolve-stdio.d.ts.map +1 -1
  50. package/dist/core/mcp/resolve-stdio.js +136 -5
  51. package/dist/core/mcp/resolve-stdio.js.map +1 -1
  52. package/dist/core/mcp/resolve-stdio.test.js +82 -1
  53. package/dist/core/mcp/resolve-stdio.test.js.map +1 -1
  54. package/dist/core/nolan-context.d.ts +26 -2
  55. package/dist/core/nolan-context.d.ts.map +1 -1
  56. package/dist/core/nolan-context.js +46 -4
  57. package/dist/core/nolan-context.js.map +1 -1
  58. package/dist/core/nolan-context.test.js +53 -12
  59. package/dist/core/nolan-context.test.js.map +1 -1
  60. package/dist/core/nolan-prompt.d.ts +2 -15
  61. package/dist/core/nolan-prompt.d.ts.map +1 -1
  62. package/dist/core/nolan-prompt.js +96 -15
  63. package/dist/core/nolan-prompt.js.map +1 -1
  64. package/dist/core/nolan-prompt.test.js +102 -7
  65. package/dist/core/nolan-prompt.test.js.map +1 -1
  66. package/dist/core/persistent-shell.d.ts +27 -0
  67. package/dist/core/persistent-shell.d.ts.map +1 -0
  68. package/dist/core/persistent-shell.js +150 -0
  69. package/dist/core/persistent-shell.js.map +1 -0
  70. package/dist/core/persistent-shell.test.d.ts +2 -0
  71. package/dist/core/persistent-shell.test.d.ts.map +1 -0
  72. package/dist/core/persistent-shell.test.js +84 -0
  73. package/dist/core/persistent-shell.test.js.map +1 -0
  74. package/dist/core/process-manager-dev-server-repro.test.js +34 -6
  75. package/dist/core/process-manager-dev-server-repro.test.js.map +1 -1
  76. package/dist/core/progress/engine.d.ts +23 -0
  77. package/dist/core/progress/engine.d.ts.map +1 -0
  78. package/dist/core/progress/engine.js +131 -0
  79. package/dist/core/progress/engine.js.map +1 -0
  80. package/dist/core/progress/engine.test.d.ts +2 -0
  81. package/dist/core/progress/engine.test.d.ts.map +1 -0
  82. package/dist/core/progress/engine.test.js +136 -0
  83. package/dist/core/progress/engine.test.js.map +1 -0
  84. package/dist/core/progress/git-xp.d.ts +16 -0
  85. package/dist/core/progress/git-xp.d.ts.map +1 -0
  86. package/dist/core/progress/git-xp.js +106 -0
  87. package/dist/core/progress/git-xp.js.map +1 -0
  88. package/dist/core/progress/git-xp.test.d.ts +2 -0
  89. package/dist/core/progress/git-xp.test.d.ts.map +1 -0
  90. package/dist/core/progress/git-xp.test.js +88 -0
  91. package/dist/core/progress/git-xp.test.js.map +1 -0
  92. package/dist/core/progress/ranks.d.ts +21 -0
  93. package/dist/core/progress/ranks.d.ts.map +1 -0
  94. package/dist/core/progress/ranks.js +141 -0
  95. package/dist/core/progress/ranks.js.map +1 -0
  96. package/dist/core/progress/ranks.test.d.ts +2 -0
  97. package/dist/core/progress/ranks.test.d.ts.map +1 -0
  98. package/dist/core/progress/ranks.test.js +59 -0
  99. package/dist/core/progress/ranks.test.js.map +1 -0
  100. package/dist/core/progress/rebuild.d.ts +9 -0
  101. package/dist/core/progress/rebuild.d.ts.map +1 -0
  102. package/dist/core/progress/rebuild.js +119 -0
  103. package/dist/core/progress/rebuild.js.map +1 -0
  104. package/dist/core/progress/rebuild.test.d.ts +2 -0
  105. package/dist/core/progress/rebuild.test.d.ts.map +1 -0
  106. package/dist/core/progress/rebuild.test.js +88 -0
  107. package/dist/core/progress/rebuild.test.js.map +1 -0
  108. package/dist/core/progress/store.d.ts +35 -0
  109. package/dist/core/progress/store.d.ts.map +1 -0
  110. package/dist/core/progress/store.js +200 -0
  111. package/dist/core/progress/store.js.map +1 -0
  112. package/dist/core/progress/store.test.d.ts +2 -0
  113. package/dist/core/progress/store.test.d.ts.map +1 -0
  114. package/dist/core/progress/store.test.js +108 -0
  115. package/dist/core/progress/store.test.js.map +1 -0
  116. package/dist/core/progress/types.d.ts +108 -0
  117. package/dist/core/progress/types.d.ts.map +1 -0
  118. package/dist/core/progress/types.js +3 -0
  119. package/dist/core/progress/types.js.map +1 -0
  120. package/dist/core/project-discovery.d.ts.map +1 -1
  121. package/dist/core/project-discovery.js +40 -9
  122. package/dist/core/project-discovery.js.map +1 -1
  123. package/dist/core/project-discovery.test.d.ts +2 -0
  124. package/dist/core/project-discovery.test.d.ts.map +1 -0
  125. package/dist/core/project-discovery.test.js +104 -0
  126. package/dist/core/project-discovery.test.js.map +1 -0
  127. package/dist/core/session-history.d.ts +51 -0
  128. package/dist/core/session-history.d.ts.map +1 -0
  129. package/dist/core/session-history.js +156 -0
  130. package/dist/core/session-history.js.map +1 -0
  131. package/dist/core/session-history.test.d.ts +2 -0
  132. package/dist/core/session-history.test.d.ts.map +1 -0
  133. package/dist/core/session-history.test.js +101 -0
  134. package/dist/core/session-history.test.js.map +1 -0
  135. package/dist/core/session-manager.d.ts +34 -0
  136. package/dist/core/session-manager.d.ts.map +1 -1
  137. package/dist/core/session-manager.js +67 -0
  138. package/dist/core/session-manager.js.map +1 -1
  139. package/dist/core/session-manager.test.js +140 -1
  140. package/dist/core/session-manager.test.js.map +1 -1
  141. package/dist/core/settings-manager.d.ts +1 -0
  142. package/dist/core/settings-manager.d.ts.map +1 -1
  143. package/dist/core/settings-manager.js +4 -0
  144. package/dist/core/settings-manager.js.map +1 -1
  145. package/dist/system-prompt.d.ts.map +1 -1
  146. package/dist/system-prompt.js +12 -8
  147. package/dist/system-prompt.js.map +1 -1
  148. package/dist/system-prompt.test.js +15 -20
  149. package/dist/system-prompt.test.js.map +1 -1
  150. package/dist/tools/bash.d.ts +1 -0
  151. package/dist/tools/bash.d.ts.map +1 -1
  152. package/dist/tools/bash.js +27 -2
  153. package/dist/tools/bash.js.map +1 -1
  154. package/dist/tools/edit.d.ts +9 -2
  155. package/dist/tools/edit.d.ts.map +1 -1
  156. package/dist/tools/edit.js +91 -10
  157. package/dist/tools/edit.js.map +1 -1
  158. package/dist/tools/edit.test.js +97 -0
  159. package/dist/tools/edit.test.js.map +1 -1
  160. package/dist/tools/plan-mode.test.js +25 -0
  161. package/dist/tools/plan-mode.test.js.map +1 -1
  162. package/dist/tools/prompt-hints.d.ts.map +1 -1
  163. package/dist/tools/prompt-hints.js +3 -0
  164. package/dist/tools/prompt-hints.js.map +1 -1
  165. package/dist/tools/subagent.d.ts +2 -0
  166. package/dist/tools/subagent.d.ts.map +1 -1
  167. package/dist/tools/subagent.js +213 -194
  168. package/dist/tools/subagent.js.map +1 -1
  169. package/dist/tools/subagent.test.d.ts +2 -0
  170. package/dist/tools/subagent.test.d.ts.map +1 -0
  171. package/dist/tools/subagent.test.js +73 -0
  172. package/dist/tools/subagent.test.js.map +1 -0
  173. package/dist/tools/tool-search.d.ts +15 -0
  174. package/dist/tools/tool-search.d.ts.map +1 -0
  175. package/dist/tools/tool-search.js +35 -0
  176. package/dist/tools/tool-search.js.map +1 -0
  177. package/dist/ui/components/Footer.js +3 -3
  178. package/dist/ui/components/Footer.js.map +1 -1
  179. package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
  180. package/dist/ui/hooks/useAgentLoop.js +22 -9
  181. package/dist/ui/hooks/useAgentLoop.js.map +1 -1
  182. package/package.json +4 -4
@@ -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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"}
@@ -0,0 +1,9 @@
1
+ import type { ProgressFile } from "./types.js";
2
+ /** Seed XP from a historical prompt count: full credit to level 15, 25% beyond, hard-capped at 25. */
3
+ export declare function seedXpForPrompts(totalPrompts: number): number;
4
+ /**
5
+ * Rebuild a progress file from session history. Returns null when there is no
6
+ * history at all (fresh install → start at Lurker via createEmptyProgress).
7
+ */
8
+ export declare function rebuildFromSessions(sessionsDir?: string): Promise<ProgressFile | null>;
9
+ //# 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;AAW/C,sGAAsG;AACtG,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAO7D;AA6CD;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAkD5F"}
@@ -0,0 +1,119 @@
1
+ // One-time retroactive seeding from the existing ~/.ezcoder/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 "@prestyj/core";
9
+ import { xpForLevel } from "./ranks.js";
10
+ import { createEmptyProgress, dayKey } from "./store.js";
11
+ // Grandfathered XP spreads across a curve instead of clamping everyone onto one level.
12
+ // Full credit up to the level-15 floor; historical usage beyond that keeps earning at a
13
+ // diminished rate (mirrors the engine's DAILY_OVERCAP_FACTOR) so heavy prior users land
14
+ // anywhere from 15 up to a hard ceiling instead of all piling on exactly level 15.
15
+ const SEED_SOFT_CAP_LEVEL = 15;
16
+ const SEED_HARD_CAP_LEVEL = 25;
17
+ const SEED_OVERCAP_FACTOR = 0.25;
18
+ const XP_PER_HISTORICAL_PROMPT = 10;
19
+ /** Seed XP from a historical prompt count: full credit to level 15, 25% beyond, hard-capped at 25. */
20
+ export function seedXpForPrompts(totalPrompts) {
21
+ const softCap = xpForLevel(SEED_SOFT_CAP_LEVEL);
22
+ const hardCap = xpForLevel(SEED_HARD_CAP_LEVEL);
23
+ const raw = totalPrompts * XP_PER_HISTORICAL_PROMPT;
24
+ const full = Math.min(raw, softCap);
25
+ const overflow = Math.max(0, raw - softCap);
26
+ return Math.min(hardCap, Math.round(full + overflow * SEED_OVERCAP_FACTOR));
27
+ }
28
+ /** Count user prompts + find the session header timestamp in one JSONL pass. */
29
+ function scanSessionFile(file) {
30
+ return new Promise((resolve) => {
31
+ const stream = createReadStream(file, { encoding: "utf-8" });
32
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
33
+ let userPrompts = 0;
34
+ let oldestTimestamp = null;
35
+ let done = false;
36
+ const finish = () => {
37
+ if (done)
38
+ return;
39
+ done = true;
40
+ resolve({ userPrompts, oldestTimestamp });
41
+ rl.close();
42
+ stream.destroy();
43
+ };
44
+ rl.on("line", (line) => {
45
+ if (done || !line)
46
+ return;
47
+ try {
48
+ const p = JSON.parse(line);
49
+ if (p.type === "session" && p.timestamp && !oldestTimestamp) {
50
+ oldestTimestamp = p.timestamp;
51
+ }
52
+ else if (p.type === "message" && p.message?.role === "user") {
53
+ userPrompts++;
54
+ }
55
+ }
56
+ catch {
57
+ // skip malformed line
58
+ }
59
+ });
60
+ rl.on("close", finish);
61
+ rl.on("error", finish);
62
+ stream.on("error", finish);
63
+ });
64
+ }
65
+ /**
66
+ * Rebuild a progress file from session history. Returns null when there is no
67
+ * history at all (fresh install → start at Lurker via createEmptyProgress).
68
+ */
69
+ export async function rebuildFromSessions(sessionsDir) {
70
+ const dir = sessionsDir ?? getAppPaths().sessionsDir;
71
+ let projectDirs;
72
+ try {
73
+ projectDirs = await fs.readdir(dir);
74
+ }
75
+ catch {
76
+ return null;
77
+ }
78
+ let totalPrompts = 0;
79
+ let projectCount = 0;
80
+ let oldest = null;
81
+ for (const entry of projectDirs) {
82
+ const projectDir = path.join(dir, entry);
83
+ let files;
84
+ try {
85
+ files = (await fs.readdir(projectDir)).filter((f) => f.endsWith(".jsonl"));
86
+ }
87
+ catch {
88
+ continue;
89
+ }
90
+ let projectPrompts = 0;
91
+ for (const f of files) {
92
+ const scan = await scanSessionFile(path.join(projectDir, f));
93
+ projectPrompts += scan.userPrompts;
94
+ if (scan.oldestTimestamp && (!oldest || scan.oldestTimestamp < oldest)) {
95
+ oldest = scan.oldestTimestamp;
96
+ }
97
+ }
98
+ if (projectPrompts > 0) {
99
+ totalPrompts += projectPrompts;
100
+ projectCount++;
101
+ }
102
+ }
103
+ if (totalPrompts === 0)
104
+ return null;
105
+ const now = new Date();
106
+ const file = createEmptyProgress(now);
107
+ const seeded = seedXpForPrompts(totalPrompts);
108
+ file.xp = seeded;
109
+ file.totals.prompts = totalPrompts;
110
+ // Historical projects are counted but their paths aren't rehashed — use opaque markers.
111
+ file.totals.projects = Array.from({ length: projectCount }, (_, i) => `seed-${i}`);
112
+ file.xpBySource.prompts = seeded;
113
+ if (oldest)
114
+ file.createdAt = oldest;
115
+ // Seeding is not "activity today" — leave streak at zero, but keep dayXp clean.
116
+ file.rolling.dayKey = dayKey(now);
117
+ return file;
118
+ }
119
+ //# sourceMappingURL=rebuild.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rebuild.js","sourceRoot":"","sources":["../../../src/core/progress/rebuild.ts"],"names":[],"mappings":"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"}
@@ -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,88 @@
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("gives full credit up to level 15, then diminishing returns beyond", async () => {
52
+ // Right at the level-15 floor: 762 prompts × 10 = 7620 ≈ xpForLevel(15).
53
+ await writeSession("_proj_a", "s1.jsonl", sessionJsonl("s1", "2025-01-01T00:00:00Z", 762));
54
+ const file = await rebuildFromSessions(sessionsDir);
55
+ expect(levelForXp(file.xp)).toBe(15);
56
+ });
57
+ it("spreads heavy prior users past level 15 instead of clamping them all onto it", async () => {
58
+ await writeSession("_light", "s1.jsonl", sessionJsonl("s1", "2025-01-01T00:00:00Z", 1500));
59
+ const light = await rebuildFromSessions(sessionsDir);
60
+ await fs.rm(sessionsDir, { recursive: true, force: true });
61
+ await fs.mkdir(sessionsDir, { recursive: true });
62
+ await writeSession("_heavy", "s1.jsonl", sessionJsonl("s1", "2025-01-01T00:00:00Z", 4000));
63
+ const heavy = await rebuildFromSessions(sessionsDir);
64
+ // Both clamped onto 15 before; now they land on different, higher levels.
65
+ expect(levelForXp(light.xp)).toBeGreaterThan(15);
66
+ expect(levelForXp(heavy.xp)).toBeGreaterThan(levelForXp(light.xp));
67
+ });
68
+ it("never seeds above the hard cap (level 25)", async () => {
69
+ await writeSession("_proj_a", "s1.jsonl", sessionJsonl("s1", "2025-01-01T00:00:00Z", 100000));
70
+ const file = await rebuildFromSessions(sessionsDir);
71
+ expect(levelForXp(file.xp)).toBe(25);
72
+ expect(file.xp).toBe(xpForLevel(25));
73
+ });
74
+ it("uses the oldest session timestamp as createdAt and counts projects", async () => {
75
+ await writeSession("_proj_a", "s1.jsonl", sessionJsonl("s1", "2024-06-15T00:00:00Z", 3));
76
+ await writeSession("_proj_b", "s2.jsonl", sessionJsonl("s2", "2023-02-01T00:00:00Z", 2));
77
+ const file = await rebuildFromSessions(sessionsDir);
78
+ expect(file.createdAt).toBe("2023-02-01T00:00:00Z");
79
+ expect(file.totals.projects).toHaveLength(2);
80
+ });
81
+ it("skips malformed lines and empty sessions", async () => {
82
+ await writeSession("_proj_a", "bad.jsonl", "not json\n{broken\n");
83
+ await writeSession("_proj_a", "s1.jsonl", sessionJsonl("s1", "2025-01-01T00:00:00Z", 1));
84
+ const file = await rebuildFromSessions(sessionsDir);
85
+ expect(file.xp).toBe(10);
86
+ });
87
+ });
88
+ //# 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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"}