estela 0.1.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 (186) hide show
  1. package/README.md +138 -0
  2. package/dist/billing/amortize.d.ts +44 -0
  3. package/dist/billing/amortize.d.ts.map +1 -0
  4. package/dist/billing/amortize.js +83 -0
  5. package/dist/billing/amortize.js.map +1 -0
  6. package/dist/billing/amortize.test.d.ts +2 -0
  7. package/dist/billing/amortize.test.d.ts.map +1 -0
  8. package/dist/billing/amortize.test.js +121 -0
  9. package/dist/billing/amortize.test.js.map +1 -0
  10. package/dist/billing/format.test.d.ts +2 -0
  11. package/dist/billing/format.test.d.ts.map +1 -0
  12. package/dist/billing/format.test.js +110 -0
  13. package/dist/billing/format.test.js.map +1 -0
  14. package/dist/billing/invoice.d.ts +52 -0
  15. package/dist/billing/invoice.d.ts.map +1 -0
  16. package/dist/billing/invoice.js +148 -0
  17. package/dist/billing/invoice.js.map +1 -0
  18. package/dist/billing/invoice.test.d.ts +2 -0
  19. package/dist/billing/invoice.test.d.ts.map +1 -0
  20. package/dist/billing/invoice.test.js +200 -0
  21. package/dist/billing/invoice.test.js.map +1 -0
  22. package/dist/billing/sessionize.d.ts +104 -0
  23. package/dist/billing/sessionize.d.ts.map +1 -0
  24. package/dist/billing/sessionize.js +0 -0
  25. package/dist/billing/sessionize.js.map +1 -0
  26. package/dist/billing/sessionize.test.d.ts +2 -0
  27. package/dist/billing/sessionize.test.d.ts.map +1 -0
  28. package/dist/billing/sessionize.test.js +67 -0
  29. package/dist/billing/sessionize.test.js.map +1 -0
  30. package/dist/cli.d.ts +4 -0
  31. package/dist/cli.d.ts.map +1 -0
  32. package/dist/cli.js +1091 -0
  33. package/dist/cli.js.map +1 -0
  34. package/dist/cloud/auth.d.ts +4 -0
  35. package/dist/cloud/auth.d.ts.map +1 -0
  36. package/dist/cloud/auth.js +98 -0
  37. package/dist/cloud/auth.js.map +1 -0
  38. package/dist/cloud/client.d.ts +12 -0
  39. package/dist/cloud/client.d.ts.map +1 -0
  40. package/dist/cloud/client.js +34 -0
  41. package/dist/cloud/client.js.map +1 -0
  42. package/dist/db/schema.d.ts +11 -0
  43. package/dist/db/schema.d.ts.map +1 -0
  44. package/dist/db/schema.js +424 -0
  45. package/dist/db/schema.js.map +1 -0
  46. package/dist/db/schema.test.d.ts +2 -0
  47. package/dist/db/schema.test.d.ts.map +1 -0
  48. package/dist/db/schema.test.js +335 -0
  49. package/dist/db/schema.test.js.map +1 -0
  50. package/dist/db/store.d.ts +113 -0
  51. package/dist/db/store.d.ts.map +1 -0
  52. package/dist/db/store.js +550 -0
  53. package/dist/db/store.js.map +1 -0
  54. package/dist/doctor.d.ts +24 -0
  55. package/dist/doctor.d.ts.map +1 -0
  56. package/dist/doctor.js +229 -0
  57. package/dist/doctor.js.map +1 -0
  58. package/dist/export/csv.d.ts +9 -0
  59. package/dist/export/csv.d.ts.map +1 -0
  60. package/dist/export/csv.js +81 -0
  61. package/dist/export/csv.js.map +1 -0
  62. package/dist/export/invoice-pdf.d.ts +21 -0
  63. package/dist/export/invoice-pdf.d.ts.map +1 -0
  64. package/dist/export/invoice-pdf.js +135 -0
  65. package/dist/export/invoice-pdf.js.map +1 -0
  66. package/dist/export/panel.d.ts +78 -0
  67. package/dist/export/panel.d.ts.map +1 -0
  68. package/dist/export/panel.js +693 -0
  69. package/dist/export/panel.js.map +1 -0
  70. package/dist/export/panel.test.d.ts +2 -0
  71. package/dist/export/panel.test.d.ts.map +1 -0
  72. package/dist/export/panel.test.js +273 -0
  73. package/dist/export/panel.test.js.map +1 -0
  74. package/dist/export/pdf.d.ts +32 -0
  75. package/dist/export/pdf.d.ts.map +1 -0
  76. package/dist/export/pdf.js +229 -0
  77. package/dist/export/pdf.js.map +1 -0
  78. package/dist/export/pdf.test.d.ts +2 -0
  79. package/dist/export/pdf.test.d.ts.map +1 -0
  80. package/dist/export/pdf.test.js +151 -0
  81. package/dist/export/pdf.test.js.map +1 -0
  82. package/dist/export/share.d.ts +42 -0
  83. package/dist/export/share.d.ts.map +1 -0
  84. package/dist/export/share.js +181 -0
  85. package/dist/export/share.js.map +1 -0
  86. package/dist/export/share.test.d.ts +2 -0
  87. package/dist/export/share.test.d.ts.map +1 -0
  88. package/dist/export/share.test.js +116 -0
  89. package/dist/export/share.test.js.map +1 -0
  90. package/dist/metrics/index.d.ts +101 -0
  91. package/dist/metrics/index.d.ts.map +1 -0
  92. package/dist/metrics/index.js +221 -0
  93. package/dist/metrics/index.js.map +1 -0
  94. package/dist/metrics/metrics.test.d.ts +2 -0
  95. package/dist/metrics/metrics.test.d.ts.map +1 -0
  96. package/dist/metrics/metrics.test.js +162 -0
  97. package/dist/metrics/metrics.test.js.map +1 -0
  98. package/dist/metrics/people.d.ts +46 -0
  99. package/dist/metrics/people.d.ts.map +1 -0
  100. package/dist/metrics/people.js +126 -0
  101. package/dist/metrics/people.js.map +1 -0
  102. package/dist/metrics/people.test.d.ts +2 -0
  103. package/dist/metrics/people.test.d.ts.map +1 -0
  104. package/dist/metrics/people.test.js +84 -0
  105. package/dist/metrics/people.test.js.map +1 -0
  106. package/dist/metrics/team.d.ts +41 -0
  107. package/dist/metrics/team.d.ts.map +1 -0
  108. package/dist/metrics/team.js +0 -0
  109. package/dist/metrics/team.js.map +1 -0
  110. package/dist/metrics/team.test.d.ts +2 -0
  111. package/dist/metrics/team.test.d.ts.map +1 -0
  112. package/dist/metrics/team.test.js +90 -0
  113. package/dist/metrics/team.test.js.map +1 -0
  114. package/dist/pricing/catalog.d.ts +39 -0
  115. package/dist/pricing/catalog.d.ts.map +1 -0
  116. package/dist/pricing/catalog.js +59 -0
  117. package/dist/pricing/catalog.js.map +1 -0
  118. package/dist/pricing/cost.d.ts +25 -0
  119. package/dist/pricing/cost.d.ts.map +1 -0
  120. package/dist/pricing/cost.js +50 -0
  121. package/dist/pricing/cost.js.map +1 -0
  122. package/dist/pricing/cost.test.d.ts +2 -0
  123. package/dist/pricing/cost.test.d.ts.map +1 -0
  124. package/dist/pricing/cost.test.js +74 -0
  125. package/dist/pricing/cost.test.js.map +1 -0
  126. package/dist/server.d.ts +15 -0
  127. package/dist/server.d.ts.map +1 -0
  128. package/dist/server.js +1141 -0
  129. package/dist/server.js.map +1 -0
  130. package/dist/setup.d.ts +70 -0
  131. package/dist/setup.d.ts.map +1 -0
  132. package/dist/setup.js +223 -0
  133. package/dist/setup.js.map +1 -0
  134. package/dist/setup.test.d.ts +2 -0
  135. package/dist/setup.test.d.ts.map +1 -0
  136. package/dist/setup.test.js +131 -0
  137. package/dist/setup.test.js.map +1 -0
  138. package/dist/watchers/claude.d.ts +34 -0
  139. package/dist/watchers/claude.d.ts.map +1 -0
  140. package/dist/watchers/claude.js +235 -0
  141. package/dist/watchers/claude.js.map +1 -0
  142. package/dist/watchers/claude.test.d.ts +2 -0
  143. package/dist/watchers/claude.test.d.ts.map +1 -0
  144. package/dist/watchers/claude.test.js +180 -0
  145. package/dist/watchers/claude.test.js.map +1 -0
  146. package/dist/watchers/git.d.ts +42 -0
  147. package/dist/watchers/git.d.ts.map +1 -0
  148. package/dist/watchers/git.js +218 -0
  149. package/dist/watchers/git.js.map +1 -0
  150. package/dist/watchers/git.test.d.ts +2 -0
  151. package/dist/watchers/git.test.d.ts.map +1 -0
  152. package/dist/watchers/git.test.js +73 -0
  153. package/dist/watchers/git.test.js.map +1 -0
  154. package/dist/watchers/identity.d.ts +34 -0
  155. package/dist/watchers/identity.d.ts.map +1 -0
  156. package/dist/watchers/identity.js +85 -0
  157. package/dist/watchers/identity.js.map +1 -0
  158. package/dist/watchers/identity.test.d.ts +2 -0
  159. package/dist/watchers/identity.test.d.ts.map +1 -0
  160. package/dist/watchers/identity.test.js +48 -0
  161. package/dist/watchers/identity.test.js.map +1 -0
  162. package/dist/web-ui.test.d.ts +2 -0
  163. package/dist/web-ui.test.d.ts.map +1 -0
  164. package/dist/web-ui.test.js +312 -0
  165. package/dist/web-ui.test.js.map +1 -0
  166. package/node_modules/@estela/shared/dist/index.d.ts +3 -0
  167. package/node_modules/@estela/shared/dist/index.d.ts.map +1 -0
  168. package/node_modules/@estela/shared/dist/index.js +19 -0
  169. package/node_modules/@estela/shared/dist/index.js.map +1 -0
  170. package/node_modules/@estela/shared/dist/money.d.ts +71 -0
  171. package/node_modules/@estela/shared/dist/money.d.ts.map +1 -0
  172. package/node_modules/@estela/shared/dist/money.js +171 -0
  173. package/node_modules/@estela/shared/dist/money.js.map +1 -0
  174. package/node_modules/@estela/shared/dist/types.d.ts +365 -0
  175. package/node_modules/@estela/shared/dist/types.d.ts.map +1 -0
  176. package/node_modules/@estela/shared/dist/types.js +24 -0
  177. package/node_modules/@estela/shared/dist/types.js.map +1 -0
  178. package/node_modules/@estela/shared/package.json +5 -0
  179. package/package.json +41 -0
  180. package/web/app.css +749 -0
  181. package/web/app.js +1257 -0
  182. package/web/apple-touch-icon.png +0 -0
  183. package/web/favicon.png +0 -0
  184. package/web/index.html +132 -0
  185. package/web/lib.js +94 -0
  186. package/web/logo-mark.png +0 -0
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readCommits = readCommits;
4
+ exports.repoRoot = repoRoot;
5
+ exports.mergedBranches = mergedBranches;
6
+ exports.integrationBranch = integrationBranch;
7
+ exports.currentBranch = currentBranch;
8
+ exports.repoAuthors = repoAuthors;
9
+ exports.gitUserEmail = gitUserEmail;
10
+ const node_child_process_1 = require("node:child_process");
11
+ const node_util_1 = require("node:util");
12
+ const run = (0, node_util_1.promisify)(node_child_process_1.execFile);
13
+ /**
14
+ * Lector de git.
15
+ *
16
+ * Solo observa: no instala hooks, no escribe trailers, no toca los mensajes de
17
+ * commit. `core.hooksPath` es global y pisaría husky o lefthook del equipo, y un
18
+ * identificador inyectado en el historial es irreversible una vez subido. Leer
19
+ * `git log` no tiene ninguno de esos costes.
20
+ */
21
+ const SEP = "\x1f";
22
+ const RECORD = "\x1e";
23
+ async function readCommits(repoPath, options = {}) {
24
+ const root = await repoRoot(repoPath);
25
+ if (!root)
26
+ return [];
27
+ // %D son las referencias que APUNTAN al commit; no dicen a qué rama pertenece.
28
+ // Para eso se usa --source, que hace que git diga por cuál de las referencias
29
+ // recorridas llegó a cada commit. Sin esto, `branch` guardaba la rama actual
30
+ // del repositorio para todos los commits y cualquier métrica por feature
31
+ // salía mal.
32
+ // El separador va DELANTE de la cabecera, no detrás. Con `--numstat` git
33
+ // imprime la cabecera y a continuación las líneas de estadísticas: si el
34
+ // separador cierra la cabecera, esas líneas quedan pegadas al principio del
35
+ // commit siguiente y todos los recuentos salen a cero.
36
+ // El separador de registro va SIEMPRE el primero. Si se cuela un campo antes,
37
+ // ese campo queda pegado al final del commit anterior al partir por él.
38
+ const format = RECORD + ["%S", "%H", "%aI", "%ae", "%an", "%s"].join(SEP);
39
+ const args = ["log", "--source", "--all", `--pretty=format:${format}`,
40
+ "--numstat", "--no-merges"];
41
+ if (options.since)
42
+ args.push(`--since=${options.since.toISOString()}`);
43
+ // Varios --author en git se combinan con OR, que es justo lo que hace falta:
44
+ // en el repo de un cliente commiteas con el correo que te dieron, y en los
45
+ // tuyos con el propio. Filtrar por uno solo pierde casi todo tu trabajo.
46
+ const authors = options.authorEmails?.length
47
+ ? options.authorEmails
48
+ : options.authorEmail ? [options.authorEmail] : [];
49
+ // Sin ignorar mayúsculas, configurar un nombre ("JDev Leon") en vez de un
50
+ // correo no casaría con nada y el proyecto se quedaría sin commits.
51
+ if (authors.length)
52
+ args.push("--regexp-ignore-case");
53
+ for (const email of authors)
54
+ args.push(`--author=${email}`);
55
+ let stdout;
56
+ try {
57
+ ({ stdout } = await run("git", args, { cwd: root, maxBuffer: 64 * 1024 * 1024 }));
58
+ }
59
+ catch {
60
+ // Un repo sin commits, o sin git instalado, no debe tumbar el escaneo.
61
+ return [];
62
+ }
63
+ const commits = [];
64
+ for (const chunk of stdout.split(RECORD)) {
65
+ const trimmed = chunk.trim();
66
+ if (!trimmed)
67
+ continue;
68
+ const newlineAt = trimmed.indexOf("\n");
69
+ const header = newlineAt === -1 ? trimmed : trimmed.slice(0, newlineAt);
70
+ const body = newlineAt === -1 ? "" : trimmed.slice(newlineAt + 1);
71
+ const [source, hash, iso, authorEmail, authorName, subject] = header.split(SEP);
72
+ if (!hash || !iso || !authorEmail)
73
+ continue;
74
+ // --source devuelve "refs/heads/feature/x"; nos quedamos con el nombre.
75
+ const branch = source
76
+ ? source.replace(/^refs\/(heads|remotes\/[^/]+)\//, "").trim() || null
77
+ : null;
78
+ const at = new Date(iso);
79
+ if (Number.isNaN(at.getTime()))
80
+ continue;
81
+ let linesAdded = 0;
82
+ let linesDeleted = 0;
83
+ const files = [];
84
+ for (const line of body.split("\n")) {
85
+ const [added, deleted, file] = line.split("\t");
86
+ // "-" en --numstat significa archivo binario: no cuenta como líneas.
87
+ if (added && added !== "-")
88
+ linesAdded += Number(added) || 0;
89
+ if (deleted && deleted !== "-")
90
+ linesDeleted += Number(deleted) || 0;
91
+ // Los ficheros habilitan medir reescritura: sin ellos no se puede saber
92
+ // qué se está tocando una y otra vez.
93
+ if (file)
94
+ files.push(file.trim());
95
+ }
96
+ commits.push({
97
+ repoPath: root, hash, at, authorEmail, authorName: authorName ?? "",
98
+ branch, subject: subject ?? "", linesAdded, linesDeleted, files,
99
+ });
100
+ }
101
+ return commits;
102
+ }
103
+ /**
104
+ * Raíz del repositorio.
105
+ *
106
+ * `git rev-parse --show-toplevel` en vez de comprobar si existe un directorio
107
+ * `.git`: en un `git worktree` el `.git` es un *archivo*, no un directorio, y la
108
+ * comprobación ingenua devuelve falso en un repo perfectamente válido.
109
+ */
110
+ async function repoRoot(path) {
111
+ try {
112
+ const { stdout } = await run("git", ["rev-parse", "--show-toplevel"], { cwd: path });
113
+ return stdout.trim() || null;
114
+ }
115
+ catch {
116
+ return null;
117
+ }
118
+ }
119
+ /**
120
+ * Ramas ya integradas en la rama de integración.
121
+ *
122
+ * Lo que queda fuera es trabajo abierto, que es la señal de riesgo que un jefe
123
+ * de proyecto no tiene hoy: no la ve hasta que estalla al integrar.
124
+ *
125
+ * La rama base se detecta porque varía por equipo —unos integran en `main`,
126
+ * otros en `develop`— y equivocarse la marcaría todo como abierto.
127
+ */
128
+ async function mergedBranches(path) {
129
+ const root = await repoRoot(path);
130
+ if (!root)
131
+ return new Set();
132
+ const base = await integrationBranch(root);
133
+ if (!base)
134
+ return new Set();
135
+ const merged = new Set([base]);
136
+ for (const ref of ["--merged"]) {
137
+ try {
138
+ const { stdout } = await run("git", ["branch", "-a", ref, base], { cwd: root });
139
+ for (const line of stdout.split("\n")) {
140
+ const name = line.replace(/^[*+]?\s*/, "").trim();
141
+ if (!name || name.includes("->"))
142
+ continue;
143
+ merged.add(name.replace(/^remotes\/[^/]+\//, ""));
144
+ }
145
+ }
146
+ catch {
147
+ // Un repo recién creado no tiene aún la rama base.
148
+ }
149
+ }
150
+ return merged;
151
+ }
152
+ /** Rama donde el equipo integra. `develop` gana a `main` si existe. */
153
+ async function integrationBranch(root) {
154
+ for (const candidate of ["develop", "main", "master"]) {
155
+ try {
156
+ await run("git", ["rev-parse", "--verify", `refs/heads/${candidate}`], { cwd: root });
157
+ return candidate;
158
+ }
159
+ catch { /* probar la siguiente */ }
160
+ }
161
+ return null;
162
+ }
163
+ /** Rama activa del repositorio. Ya no se usa para etiquetar commits. */
164
+ async function currentBranch(root) {
165
+ try {
166
+ const { stdout } = await run("git", ["branch", "--show-current"], { cwd: root });
167
+ return stdout.trim() || null;
168
+ }
169
+ catch {
170
+ return null;
171
+ }
172
+ }
173
+ /**
174
+ * Autores que han commiteado en un repositorio, de más a menos reciente.
175
+ *
176
+ * Sirve para preguntar "¿cuál de estos eres tú?" en vez de adivinarlo: en el
177
+ * repositorio de un cliente hay cinco compañeros y tu correo global no aparece
178
+ * por ningún lado.
179
+ */
180
+ async function repoAuthors(path, limit = 12) {
181
+ const root = await repoRoot(path);
182
+ if (!root)
183
+ return [];
184
+ try {
185
+ const { stdout } = await run("git", ["log", "--pretty=format:%ae\t%aI", "--no-merges"], { cwd: root, maxBuffer: 32 * 1024 * 1024 });
186
+ const seen = new Map();
187
+ for (const line of stdout.split("\n")) {
188
+ const [email, at] = line.split("\t");
189
+ if (!email)
190
+ continue;
191
+ const entry = seen.get(email);
192
+ if (entry) {
193
+ entry.commits++;
194
+ if (at && at > entry.lastAt)
195
+ entry.lastAt = at;
196
+ }
197
+ else
198
+ seen.set(email, { commits: 1, lastAt: at ?? "" });
199
+ }
200
+ return [...seen.entries()]
201
+ .map(([email, v]) => ({ email, commits: v.commits, lastAt: v.lastAt.slice(0, 10) }))
202
+ .sort((a, b) => b.commits - a.commits)
203
+ .slice(0, limit);
204
+ }
205
+ catch {
206
+ return [];
207
+ }
208
+ }
209
+ async function gitUserEmail(path) {
210
+ try {
211
+ const { stdout } = await run("git", ["config", "user.email"], { cwd: path });
212
+ return stdout.trim() || null;
213
+ }
214
+ catch {
215
+ return null;
216
+ }
217
+ }
218
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/watchers/git.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=git.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.test.d.ts","sourceRoot":"","sources":["../../src/watchers/git.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_child_process_1 = require("node:child_process");
8
+ const node_fs_1 = require("node:fs");
9
+ const node_os_1 = require("node:os");
10
+ const node_path_1 = require("node:path");
11
+ const node_test_1 = require("node:test");
12
+ const git_js_1 = require("./git.js");
13
+ /** Repo de verdad: lo que falla aquí falla contra git real, no contra un mock. */
14
+ function makeRepo() {
15
+ const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "estela-git-"));
16
+ const git = (...args) => (0, node_child_process_1.execFileSync)("git", args, { cwd: dir, stdio: "pipe", env: {
17
+ ...process.env,
18
+ GIT_AUTHOR_NAME: "Prueba", GIT_AUTHOR_EMAIL: "prueba@ejemplo.com",
19
+ GIT_COMMITTER_NAME: "Prueba", GIT_COMMITTER_EMAIL: "prueba@ejemplo.com",
20
+ } });
21
+ git("init", "-q");
22
+ git("config", "user.email", "prueba@ejemplo.com");
23
+ git("config", "user.name", "Prueba");
24
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, "a.txt"), Array.from({ length: 10 }, (_, i) => `linea ${i}`).join("\n") + "\n");
25
+ git("add", "-A");
26
+ git("commit", "-q", "-m", "feat: primer commit con diez líneas");
27
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, "b.txt"), "una\ndos\ntres\n");
28
+ git("add", "-A");
29
+ git("commit", "-q", "-m", "feat(b): añade tres líneas más");
30
+ return dir;
31
+ }
32
+ (0, node_test_1.test)("cuenta las líneas añadidas y borradas de cada commit", async () => {
33
+ // El fallo que esto previene: con el separador de registro DETRÁS de la
34
+ // cabecera, las líneas de --numstat se pegaban al commit siguiente y todos
35
+ // los recuentos salían +0 −0.
36
+ const dir = makeRepo();
37
+ const commits = await (0, git_js_1.readCommits)(dir);
38
+ strict_1.default.equal(commits.length, 2);
39
+ const total = commits.reduce((s, c) => s + c.linesAdded, 0);
40
+ strict_1.default.equal(total, 13, "10 líneas del primer commit + 3 del segundo");
41
+ const primero = commits.find((c) => c.subject.includes("primer"));
42
+ strict_1.default.ok(primero);
43
+ strict_1.default.equal(primero.linesAdded, 10);
44
+ strict_1.default.equal(primero.linesDeleted, 0);
45
+ });
46
+ (0, node_test_1.test)("lee hash, fecha, autor y asunto", async () => {
47
+ const dir = makeRepo();
48
+ const commits = await (0, git_js_1.readCommits)(dir);
49
+ const c = commits[0];
50
+ strict_1.default.match(c.hash, /^[0-9a-f]{40}$/);
51
+ strict_1.default.equal(c.authorEmail, "prueba@ejemplo.com");
52
+ strict_1.default.ok(c.subject.length > 0);
53
+ strict_1.default.ok(!Number.isNaN(c.at.getTime()));
54
+ });
55
+ (0, node_test_1.test)("filtra por autor", async () => {
56
+ const dir = makeRepo();
57
+ strict_1.default.equal((await (0, git_js_1.readCommits)(dir, { authorEmail: "prueba@ejemplo.com" })).length, 2);
58
+ strict_1.default.equal((await (0, git_js_1.readCommits)(dir, { authorEmail: "otro@ejemplo.com" })).length, 0);
59
+ });
60
+ (0, node_test_1.test)("un directorio que no es repo devuelve vacío en vez de lanzar", async () => {
61
+ const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "estela-norepo-"));
62
+ strict_1.default.equal(await (0, git_js_1.repoRoot)(dir), null);
63
+ strict_1.default.deepEqual(await (0, git_js_1.readCommits)(dir), []);
64
+ });
65
+ (0, node_test_1.test)("repoRoot funciona donde la comprobación de un .git como directorio fallaría", async () => {
66
+ // En un `git worktree` el .git es un archivo, no un directorio. Por eso se
67
+ // usa `git rev-parse --show-toplevel` y no `existsSync(join(dir, '.git'))`.
68
+ const dir = makeRepo();
69
+ const root = await (0, git_js_1.repoRoot)(dir);
70
+ strict_1.default.ok(root);
71
+ strict_1.default.ok(root.endsWith(dir.split("/").pop()));
72
+ });
73
+ //# sourceMappingURL=git.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.test.js","sourceRoot":"","sources":["../../src/watchers/git.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type { DatabaseSync } from "node:sqlite";
2
+ import type { CommitRecord } from "@estela/shared";
3
+ /**
4
+ * Qué commits son tuyos.
5
+ *
6
+ * Desde que existe la vista de equipo, `commits` guarda a **todos** los autores
7
+ * del repositorio: es lo que permite enseñar el trabajo del equipo sin que cada
8
+ * compañero instale nada. Pero eso convierte una tabla que antes solo contenía
9
+ * trabajo tuyo en una que contiene el de seis personas.
10
+ *
11
+ * Todo lo que imputa horas tiene que filtrar por aquí antes de sessionizar. Si
12
+ * no, `sessionizeCommits()` derivaría bloques de los commits de tus compañeros
13
+ * y te los imputaría como tiempo tuyo: acabarías facturándole a un cliente el
14
+ * trabajo de otra persona. Es el peor fallo que este sistema puede cometer, así
15
+ * que la decisión vive en un solo sitio y la CLI y el servidor lo comparten.
16
+ */
17
+ /**
18
+ * Correos con los que commiteas en cada repositorio, indexados por su raíz.
19
+ *
20
+ * Manda lo configurado con `estela author`. Solo si un repositorio no tiene
21
+ * autores declarados se cae a `git config user.email`, que es la suposición que
22
+ * en el repositorio de un cliente casi nunca acierta — ahí commiteas con el
23
+ * correo de la empresa, no con el tuyo.
24
+ */
25
+ export declare function myEmailsByRepo(db: DatabaseSync, repoPaths: Iterable<string>): Promise<Map<string, Set<string>>>;
26
+ /**
27
+ * Se queda solo con tus commits.
28
+ *
29
+ * Un repositorio sin ningún correo conocido no aporta nada: preferimos perder
30
+ * horas a imputarte las de otro. Quedarse corto se arregla con
31
+ * `estela author`; haber facturado trabajo ajeno, no.
32
+ */
33
+ export declare function onlyMine(commits: readonly CommitRecord[], mine: ReadonlyMap<string, Set<string>>): CommitRecord[];
34
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/watchers/identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAKnD;;;;;;;;;;;;;GAaG;AAEH;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,EAAE,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,GAC5C,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAanC;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CACtB,OAAO,EAAE,SAAS,YAAY,EAAE,EAAE,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,GACvE,YAAY,EAAE,CAEhB"}
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.myEmailsByRepo = myEmailsByRepo;
37
+ exports.onlyMine = onlyMine;
38
+ const store = __importStar(require("../db/store.js"));
39
+ const git_js_1 = require("./git.js");
40
+ /**
41
+ * Qué commits son tuyos.
42
+ *
43
+ * Desde que existe la vista de equipo, `commits` guarda a **todos** los autores
44
+ * del repositorio: es lo que permite enseñar el trabajo del equipo sin que cada
45
+ * compañero instale nada. Pero eso convierte una tabla que antes solo contenía
46
+ * trabajo tuyo en una que contiene el de seis personas.
47
+ *
48
+ * Todo lo que imputa horas tiene que filtrar por aquí antes de sessionizar. Si
49
+ * no, `sessionizeCommits()` derivaría bloques de los commits de tus compañeros
50
+ * y te los imputaría como tiempo tuyo: acabarías facturándole a un cliente el
51
+ * trabajo de otra persona. Es el peor fallo que este sistema puede cometer, así
52
+ * que la decisión vive en un solo sitio y la CLI y el servidor lo comparten.
53
+ */
54
+ /**
55
+ * Correos con los que commiteas en cada repositorio, indexados por su raíz.
56
+ *
57
+ * Manda lo configurado con `estela author`. Solo si un repositorio no tiene
58
+ * autores declarados se cae a `git config user.email`, que es la suposición que
59
+ * en el repositorio de un cliente casi nunca acierta — ahí commiteas con el
60
+ * correo de la empresa, no con el tuyo.
61
+ */
62
+ async function myEmailsByRepo(db, repoPaths) {
63
+ const configured = store.authorsByRepo(db);
64
+ const mine = new Map();
65
+ for (const path of repoPaths) {
66
+ const root = await (0, git_js_1.repoRoot)(path);
67
+ if (!root || mine.has(root))
68
+ continue;
69
+ const declared = configured.get(root) ?? configured.get(path) ?? [];
70
+ const emails = declared.length ? declared : [await (0, git_js_1.gitUserEmail)(root)].filter(Boolean);
71
+ mine.set(root, new Set(emails));
72
+ }
73
+ return mine;
74
+ }
75
+ /**
76
+ * Se queda solo con tus commits.
77
+ *
78
+ * Un repositorio sin ningún correo conocido no aporta nada: preferimos perder
79
+ * horas a imputarte las de otro. Quedarse corto se arregla con
80
+ * `estela author`; haber facturado trabajo ajeno, no.
81
+ */
82
+ function onlyMine(commits, mine) {
83
+ return commits.filter((c) => mine.get(c.repoPath)?.has(c.authorEmail) ?? false);
84
+ }
85
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/watchers/identity.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,wCAeC;AASD,4BAIC;AAtDD,sDAAwC;AACxC,qCAAkD;AAElD;;;;;;;;;;;;;GAaG;AAEH;;;;;;;GAOG;AACI,KAAK,UAAU,cAAc,CAClC,EAAgB,EAAE,SAA2B;IAE7C,MAAM,UAAU,GAAG,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAuB,CAAC;IAE5C,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,IAAA,iBAAQ,EAAC,IAAI,CAAC,CAAC;QAClC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAEtC,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACpE,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,IAAA,qBAAY,EAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACvF,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,CAAC,MAAkB,CAAC,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,QAAQ,CACtB,OAAgC,EAAE,IAAsC;IAExE,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC,CAAC;AAClF,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=identity.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.test.d.ts","sourceRoot":"","sources":["../../src/watchers/identity.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_test_1 = require("node:test");
8
+ const sessionize_js_1 = require("../billing/sessionize.js");
9
+ const identity_js_1 = require("./identity.js");
10
+ function commit(email, iso, repo = "/repo") {
11
+ return {
12
+ repoPath: repo, hash: `${email}-${iso}`, at: new Date(iso),
13
+ authorEmail: email, authorName: email.split("@")[0], branch: "main",
14
+ subject: "trabajo", linesAdded: 1, linesDeleted: 0, files: [],
15
+ };
16
+ }
17
+ const MINE = new Map([["/repo", new Set(["yo@ejemplo.com"])]]);
18
+ (0, node_test_1.test)("el trabajo de un compañero nunca se convierte en horas tuyas", () => {
19
+ // La regresión más cara que este sistema puede sufrir: desde que `commits`
20
+ // guarda a todo el equipo, un filtro que falle te haría facturarle a un
21
+ // cliente el trabajo de otra persona.
22
+ const commits = [
23
+ commit("yo@ejemplo.com", "2026-08-20T09:00:00Z"),
24
+ commit("ana@empresa.com", "2026-08-20T10:00:00Z"),
25
+ commit("ana@empresa.com", "2026-08-21T10:00:00Z"),
26
+ ];
27
+ const mine = (0, identity_js_1.onlyMine)(commits, MINE);
28
+ strict_1.default.equal(mine.length, 1);
29
+ strict_1.default.equal(mine[0].authorEmail, "yo@ejemplo.com");
30
+ const blocks = (0, sessionize_js_1.sessionizeCommits)(mine);
31
+ strict_1.default.equal(blocks.length, 1, "solo tu commit produce un bloque");
32
+ strict_1.default.ok(blocks.every((b) => b.commits.every((c) => c.authorEmail === "yo@ejemplo.com")));
33
+ });
34
+ (0, node_test_1.test)("un repositorio sin autores conocidos no aporta horas", () => {
35
+ // Preferimos perder horas a imputarte las de otro: quedarse corto se corrige
36
+ // con `estela author`, haber facturado trabajo ajeno no.
37
+ const commits = [commit("quien.sea@empresa.com", "2026-08-20T09:00:00Z", "/otro")];
38
+ strict_1.default.equal((0, identity_js_1.onlyMine)(commits, MINE).length, 0);
39
+ strict_1.default.equal((0, sessionize_js_1.sessionizeCommits)((0, identity_js_1.onlyMine)(commits, MINE)).length, 0);
40
+ });
41
+ (0, node_test_1.test)("los correos de un repositorio no valen en otro", () => {
42
+ // Commiteas con un correo distinto en cada cliente. Si el filtro no
43
+ // distinguiera el repositorio, capturarías commits ajenos allí donde otra
44
+ // persona use el correo que tú usas aquí.
45
+ const commits = [commit("yo@ejemplo.com", "2026-08-20T09:00:00Z", "/ajeno")];
46
+ strict_1.default.equal((0, identity_js_1.onlyMine)(commits, MINE).length, 0);
47
+ });
48
+ //# sourceMappingURL=identity.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.test.js","sourceRoot":"","sources":["../../src/watchers/identity.test.ts"],"names":[],"mappings":";;;;;AAAA,gEAAwC;AACxC,yCAAiC;AAIjC,4DAA6D;AAC7D,+CAAyC;AAEzC,SAAS,MAAM,CAAC,KAAa,EAAE,GAAW,EAAE,IAAI,GAAG,OAAO;IACxD,OAAO;QACL,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC;QAC1D,WAAW,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,EAAE,MAAM,EAAE,MAAM;QACpE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE;KAC9D,CAAC;AACJ,CAAC;AAED,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,IAAI,GAAG,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE/D,IAAA,gBAAI,EAAC,8DAA8D,EAAE,GAAG,EAAE;IACxE,2EAA2E;IAC3E,wEAAwE;IACxE,sCAAsC;IACtC,MAAM,OAAO,GAAG;QACd,MAAM,CAAC,gBAAgB,EAAE,sBAAsB,CAAC;QAChD,MAAM,CAAC,iBAAiB,EAAE,sBAAsB,CAAC;QACjD,MAAM,CAAC,iBAAiB,EAAE,sBAAsB,CAAC;KAClD,CAAC;IAEF,MAAM,IAAI,GAAG,IAAA,sBAAQ,EAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACrC,gBAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC7B,gBAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAC;IAErD,MAAM,MAAM,GAAG,IAAA,iCAAiB,EAAC,IAAI,CAAC,CAAC;IACvC,gBAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,kCAAkC,CAAC,CAAC;IACnE,gBAAM,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,gBAAgB,CAAC,CAAC,CAAC,CAAC;AAC7F,CAAC,CAAC,CAAC;AAEH,IAAA,gBAAI,EAAC,sDAAsD,EAAE,GAAG,EAAE;IAChE,6EAA6E;IAC7E,yDAAyD;IACzD,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,uBAAuB,EAAE,sBAAsB,EAAE,OAAO,CAAC,CAAC,CAAC;IACnF,gBAAM,CAAC,KAAK,CAAC,IAAA,sBAAQ,EAAC,OAAO,EAAE,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAChD,gBAAM,CAAC,KAAK,CAAC,IAAA,iCAAiB,EAAC,IAAA,sBAAQ,EAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AACrE,CAAC,CAAC,CAAC;AAEH,IAAA,gBAAI,EAAC,gDAAgD,EAAE,GAAG,EAAE;IAC1D,oEAAoE;IACpE,0EAA0E;IAC1E,0CAA0C;IAC1C,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,gBAAgB,EAAE,sBAAsB,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC7E,gBAAM,CAAC,KAAK,CAAC,IAAA,sBAAQ,EAAC,OAAO,EAAE,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AAClD,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=web-ui.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-ui.test.d.ts","sourceRoot":"","sources":["../src/web-ui.test.ts"],"names":[],"mappings":""}