@primitive.ai/prim 0.1.0-alpha.8 → 0.1.0-alpha.80

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 (45) hide show
  1. package/README.md +295 -37
  2. package/SKILL.md +269 -0
  3. package/dist/chunk-2KW7DVFJ.js +1400 -0
  4. package/dist/chunk-3EEFL6NJ.js +529 -0
  5. package/dist/chunk-4A2XB2PC.js +310 -0
  6. package/dist/chunk-4GLVJLEQ.js +55 -0
  7. package/dist/chunk-BQUJ4AFJ.js +771 -0
  8. package/dist/chunk-CUE2LKLR.js +155 -0
  9. package/dist/chunk-EZXEWFDG.js +210 -0
  10. package/dist/chunk-FI3LIRCB.js +262 -0
  11. package/dist/chunk-KKP2NYZ3.js +505 -0
  12. package/dist/chunk-LEMX3IOG.js +79 -0
  13. package/dist/chunk-LMRYZL3D.js +209 -0
  14. package/dist/chunk-OUXH3UEP.js +247 -0
  15. package/dist/chunk-RO3F6JYV.js +126 -0
  16. package/dist/chunk-TJTVHDJ5.js +321 -0
  17. package/dist/chunk-WB7AUZ4C.js +52 -0
  18. package/dist/chunk-XGADQPCI.js +26 -0
  19. package/dist/chunk-YWO7O6QG.js +327 -0
  20. package/dist/chunk-Z7YPFWAZ.js +917 -0
  21. package/dist/chunk-ZOSNEO5N.js +77 -0
  22. package/dist/daemon/server.js +15329 -0
  23. package/dist/hook-runtime/daemon/server.js +15329 -0
  24. package/dist/hook-runtime/hooks/post-commit.js +1128 -0
  25. package/dist/hook-runtime/hooks/post-rewrite.js +1148 -0
  26. package/dist/hook-runtime/hooks/post-tool-use.js +7586 -0
  27. package/dist/hook-runtime/hooks/pre-commit.js +1314 -0
  28. package/dist/hook-runtime/hooks/pre-tool-use.js +7096 -0
  29. package/dist/hook-runtime/hooks/prim-hook.js +7691 -0
  30. package/dist/hook-runtime/hooks/session-end.js +495 -0
  31. package/dist/hook-runtime/hooks/session-start.js +11313 -0
  32. package/dist/hook-runtime/index.js +44686 -0
  33. package/dist/hook-runtime/statusline-main.js +765 -0
  34. package/dist/hooks/post-commit.js +179 -0
  35. package/dist/hooks/post-rewrite.js +197 -0
  36. package/dist/hooks/post-tool-use.js +250 -0
  37. package/dist/hooks/pre-commit.js +21 -74
  38. package/dist/hooks/pre-tool-use.js +345 -0
  39. package/dist/hooks/prim-hook.js +195 -0
  40. package/dist/hooks/session-end.js +70 -0
  41. package/dist/hooks/session-start.js +299 -0
  42. package/dist/index.js +20971 -519
  43. package/dist/statusline-main.js +765 -0
  44. package/package.json +35 -13
  45. package/dist/chunk-3APLWTLB.js +0 -165
@@ -0,0 +1,327 @@
1
+ import {
2
+ getSiteUrl
3
+ } from "./chunk-Z7YPFWAZ.js";
4
+ import {
5
+ jwtOrganizationId,
6
+ primConfigDirectory,
7
+ resolveAuthCredential
8
+ } from "./chunk-3EEFL6NJ.js";
9
+
10
+ // src/journal.ts
11
+ import {
12
+ appendFileSync,
13
+ closeSync,
14
+ existsSync,
15
+ fstatSync,
16
+ mkdirSync,
17
+ openSync,
18
+ opendirSync,
19
+ readFileSync,
20
+ readSync,
21
+ readdirSync
22
+ } from "fs";
23
+ import { dirname, join } from "path";
24
+ var JOURNAL_DIR = join(primConfigDirectory(), "moves");
25
+ var UNBOUND_BUCKET = "_unbound";
26
+ var JOURNAL_BASENAME = "journal.ndjson";
27
+ var JOURNAL_STATS_SAMPLE_BYTES = 64 * 1024;
28
+ var PENDING_STATS_MAX_FILES = 128;
29
+ function envSlug(apiUrl) {
30
+ const slug = apiUrl.replace(/^https?:\/\//, "").replace(/[^a-zA-Z0-9._-]/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
31
+ if (slug.length === 0 || slug === "." || slug === "..") {
32
+ return "default";
33
+ }
34
+ return slug;
35
+ }
36
+ function currentEnvDir() {
37
+ return join(JOURNAL_DIR, envSlug(getSiteUrl()));
38
+ }
39
+ var DIR_MODE = 448;
40
+ var FILE_MODE = 384;
41
+ var SAFE_BUCKET = /^[A-Za-z0-9_-]+$/;
42
+ var RESERVED_BUCKETS = /* @__PURE__ */ new Set([UNBOUND_BUCKET, "_legacy"]);
43
+ function appendMoveToPath(path, move) {
44
+ const dir = dirname(path);
45
+ if (!existsSync(dir)) {
46
+ mkdirSync(dir, { recursive: true, mode: DIR_MODE });
47
+ }
48
+ appendFileSync(path, `${JSON.stringify(move)}
49
+ `, { mode: FILE_MODE });
50
+ }
51
+ function bucketDir(orgId) {
52
+ const safe = orgId !== void 0 && SAFE_BUCKET.test(orgId) && !RESERVED_BUCKETS.has(orgId);
53
+ return join(currentEnvDir(), safe ? orgId : UNBOUND_BUCKET);
54
+ }
55
+ function journalPath(orgId) {
56
+ return join(bucketDir(orgId), JOURNAL_BASENAME);
57
+ }
58
+ function appendMove(move, orgId) {
59
+ appendMoveToPath(journalPath(orgId), move);
60
+ }
61
+ function parseMoves(content) {
62
+ const moves = [];
63
+ for (const line of content.split("\n")) {
64
+ if (line.length === 0) {
65
+ continue;
66
+ }
67
+ try {
68
+ moves.push(JSON.parse(line));
69
+ } catch {
70
+ }
71
+ }
72
+ return moves;
73
+ }
74
+ function readMovesFromPath(path) {
75
+ if (!existsSync(path)) {
76
+ return [];
77
+ }
78
+ return parseMoves(readFileSync(path, "utf-8"));
79
+ }
80
+ function listBuckets() {
81
+ const out = [];
82
+ const envDir = currentEnvDir();
83
+ if (!existsSync(envDir)) {
84
+ return out;
85
+ }
86
+ for (const entry of readdirSync(envDir, { withFileTypes: true })) {
87
+ if (!entry.isDirectory()) {
88
+ continue;
89
+ }
90
+ const path = join(envDir, entry.name, JOURNAL_BASENAME);
91
+ if (existsSync(path)) {
92
+ out.push({ bucket: entry.name, path });
93
+ }
94
+ }
95
+ return out;
96
+ }
97
+ var FLUSHING_PREFIX = `${JOURNAL_BASENAME}.flushing.`;
98
+ function oldestCapturedAt(moves) {
99
+ let oldest;
100
+ for (const move of moves) {
101
+ if (!Number.isFinite(move.capturedAt)) {
102
+ continue;
103
+ }
104
+ oldest = oldest === void 0 ? move.capturedAt : Math.min(oldest, move.capturedAt);
105
+ }
106
+ return oldest;
107
+ }
108
+ function sampleJournalFile(path, maxBytes = JOURNAL_STATS_SAMPLE_BYTES) {
109
+ const fd = openSync(path, "r");
110
+ try {
111
+ const initial = fstatSync(fd);
112
+ const target = Math.min(initial.size, Math.max(0, maxBytes));
113
+ const buffer = Buffer.allocUnsafe(target);
114
+ let sampledBytes = 0;
115
+ while (sampledBytes < target) {
116
+ const count = readSync(fd, buffer, sampledBytes, target - sampledBytes, sampledBytes);
117
+ if (count === 0) {
118
+ break;
119
+ }
120
+ sampledBytes += count;
121
+ }
122
+ const stat = fstatSync(fd);
123
+ const sampled = stat.size > sampledBytes;
124
+ let content = buffer.subarray(0, sampledBytes).toString("utf-8");
125
+ if (sampled) {
126
+ const lastNewline = content.lastIndexOf("\n");
127
+ content = lastNewline === -1 ? "" : content.slice(0, lastNewline + 1);
128
+ }
129
+ const lines = content.split("\n").filter((line) => line.length > 0);
130
+ const moves = parseMoves(content);
131
+ return {
132
+ sizeBytes: stat.size,
133
+ mtimeMs: stat.mtimeMs,
134
+ lineCount: sampled && stat.size > 0 ? Math.max(1, lines.length) : lines.length,
135
+ oldestCapturedAt: oldestCapturedAt(moves),
136
+ sampled,
137
+ sampledBytes
138
+ };
139
+ } finally {
140
+ closeSync(fd);
141
+ }
142
+ }
143
+ function parseFlushingPid(name) {
144
+ const segments = name.slice(FLUSHING_PREFIX.length).split(".");
145
+ const last = segments.length >= 2 ? segments[segments.length - 1] : void 0;
146
+ if (last === void 0 || !/^[0-9]+$/.test(last)) return void 0;
147
+ const pid = Number(last);
148
+ return Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
149
+ }
150
+ function listFlushingInDir(dir, bucket, options = {}) {
151
+ if (!existsSync(dir)) {
152
+ return [];
153
+ }
154
+ const out = [];
155
+ let remainingBytes = options.sampleBytes ?? JOURNAL_STATS_SAMPLE_BYTES;
156
+ const maxFiles = options.maxFiles ?? Number.POSITIVE_INFINITY;
157
+ const entries = opendirSync(dir);
158
+ try {
159
+ let entry = entries.readSync();
160
+ while (entry && out.length < maxFiles) {
161
+ if (!entry.isFile() || !entry.name.startsWith(FLUSHING_PREFIX)) {
162
+ entry = entries.readSync();
163
+ continue;
164
+ }
165
+ const path = join(dir, entry.name);
166
+ let sample;
167
+ try {
168
+ sample = sampleJournalFile(path, remainingBytes);
169
+ } catch (err) {
170
+ if (err.code === "ENOENT") {
171
+ entry = entries.readSync();
172
+ continue;
173
+ }
174
+ throw err;
175
+ }
176
+ remainingBytes = Math.max(0, remainingBytes - sample.sampledBytes);
177
+ out.push({
178
+ bucket,
179
+ path,
180
+ pid: parseFlushingPid(entry.name),
181
+ ...sample
182
+ });
183
+ entry = entries.readSync();
184
+ }
185
+ } finally {
186
+ entries.closeSync();
187
+ }
188
+ return out;
189
+ }
190
+ function listFlushing(options = {}) {
191
+ const envDir = currentEnvDir();
192
+ if (!existsSync(envDir)) {
193
+ return [];
194
+ }
195
+ const out = [];
196
+ let remainingBytes = options.sampleBytes ?? JOURNAL_STATS_SAMPLE_BYTES;
197
+ const maxFiles = options.maxFiles ?? Number.POSITIVE_INFINITY;
198
+ for (const entry of readdirSync(envDir, { withFileTypes: true })) {
199
+ if (out.length >= maxFiles) {
200
+ break;
201
+ }
202
+ if (entry.isDirectory()) {
203
+ const found = listFlushingInDir(join(envDir, entry.name), entry.name, {
204
+ sampleBytes: remainingBytes,
205
+ maxFiles: maxFiles - out.length
206
+ });
207
+ out.push(...found);
208
+ remainingBytes = Math.max(
209
+ 0,
210
+ remainingBytes - found.reduce((count, file) => count + file.sampledBytes, 0)
211
+ );
212
+ }
213
+ }
214
+ return out;
215
+ }
216
+ function bucketStats(options = {}) {
217
+ const out = [];
218
+ let remainingBytes = options.sampleBytes ?? JOURNAL_STATS_SAMPLE_BYTES;
219
+ const maxFiles = options.maxFiles ?? Number.POSITIVE_INFINITY;
220
+ for (const { bucket, path } of listBuckets()) {
221
+ if (out.length >= maxFiles) {
222
+ break;
223
+ }
224
+ let sample;
225
+ try {
226
+ sample = sampleJournalFile(path, remainingBytes);
227
+ } catch (err) {
228
+ if (err.code === "ENOENT") {
229
+ continue;
230
+ }
231
+ throw err;
232
+ }
233
+ remainingBytes = Math.max(0, remainingBytes - sample.sampledBytes);
234
+ out.push({
235
+ bucket,
236
+ path,
237
+ ...sample
238
+ });
239
+ }
240
+ return out;
241
+ }
242
+ function pendingJournalStats() {
243
+ const perKindBytes = Math.floor(JOURNAL_STATS_SAMPLE_BYTES / 2);
244
+ const perKindFiles = Math.floor(PENDING_STATS_MAX_FILES / 2);
245
+ const live = bucketStats({ sampleBytes: perKindBytes, maxFiles: perKindFiles });
246
+ const stranded = listFlushing({ sampleBytes: perKindBytes, maxFiles: perKindFiles });
247
+ const timestamps = [...live, ...stranded].map((entry) => entry.oldestCapturedAt).filter((value) => value !== void 0);
248
+ const liveSampled = live.some((entry) => entry.sampled) || live.length >= perKindFiles;
249
+ const strandedSampled = stranded.some((entry) => entry.sampled) || stranded.length >= perKindFiles;
250
+ return {
251
+ pendingCount: live.reduce((count, entry) => count + entry.lineCount, 0) + stranded.reduce((count, entry) => count + entry.lineCount, 0),
252
+ oldestPendingAt: timestamps.length > 0 ? Math.min(...timestamps) : void 0,
253
+ strandedCount: stranded.reduce((count, entry) => count + entry.lineCount, 0),
254
+ strandedFileCount: stranded.length,
255
+ sampled: liveSampled || strandedSampled,
256
+ strandedSampled
257
+ };
258
+ }
259
+
260
+ // src/binding.ts
261
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
262
+ import { dirname as dirname2, join as join2 } from "path";
263
+ var PRIM_CONFIG_DIR = primConfigDirectory();
264
+ var SESSIONS_DIR = join2(PRIM_CONFIG_DIR, "sessions");
265
+ var WORKSPACE_FILE = ".prim/workspace.json";
266
+ function readJsonSafe(path) {
267
+ if (!existsSync2(path)) {
268
+ return;
269
+ }
270
+ try {
271
+ return JSON.parse(readFileSync2(path, "utf-8"));
272
+ } catch {
273
+ return;
274
+ }
275
+ }
276
+ function fromSessionMarker(sessionId) {
277
+ if (!sessionId) {
278
+ return;
279
+ }
280
+ const marker = readJsonSafe(join2(SESSIONS_DIR, `${sessionId}.json`));
281
+ return marker?.orgId;
282
+ }
283
+ function fromWorkspaceFile(startDir) {
284
+ let dir = startDir;
285
+ while (true) {
286
+ const config = readJsonSafe(join2(dir, WORKSPACE_FILE));
287
+ if (config?.orgId) {
288
+ return config.orgId;
289
+ }
290
+ const parent = dirname2(dir);
291
+ if (parent === dir) {
292
+ return;
293
+ }
294
+ dir = parent;
295
+ }
296
+ }
297
+ function fromDefaultOrg(_cwd) {
298
+ const credential = resolveAuthCredential();
299
+ return credential ? jwtOrganizationId(credential.token) : void 0;
300
+ }
301
+ function resolveOrg(args) {
302
+ const fromSession = fromSessionMarker(args.sessionId);
303
+ if (fromSession) {
304
+ return { orgId: fromSession, source: "session" };
305
+ }
306
+ const fromWorkspace = fromWorkspaceFile(args.cwd);
307
+ if (fromWorkspace) {
308
+ return { orgId: fromWorkspace, source: "workspace" };
309
+ }
310
+ const fromDefault = fromDefaultOrg(args.cwd);
311
+ if (fromDefault) {
312
+ return { orgId: fromDefault, source: "defaultOrg" };
313
+ }
314
+ return { orgId: void 0, source: "unbound" };
315
+ }
316
+
317
+ export {
318
+ JOURNAL_DIR,
319
+ appendMove,
320
+ readMovesFromPath,
321
+ listBuckets,
322
+ listFlushing,
323
+ bucketStats,
324
+ pendingJournalStats,
325
+ SESSIONS_DIR,
326
+ resolveOrg
327
+ };