@primitive.ai/prim 0.1.0-alpha.92 → 0.1.0-alpha.94
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.
- package/README.md +84 -10
- package/SKILL.md +5 -2
- package/dist/{chunk-67VNLLBN.js → chunk-2GP5UFV7.js} +1 -0
- package/dist/{chunk-26P57LAR.js → chunk-AXQQMX2U.js} +203 -9
- package/dist/{chunk-M6RIJVRE.js → chunk-DSWT6BCC.js} +78 -4
- package/dist/{chunk-FBAYCS3X.js → chunk-DXJQR4QI.js} +150 -38
- package/dist/chunk-FXF4JWK2.js +162 -0
- package/dist/{chunk-PCVGBE5B.js → chunk-G2VMA2QO.js} +2 -1
- package/dist/{chunk-NAHHFSLH.js → chunk-ICM2XCXS.js} +2 -2
- package/dist/{chunk-5HRR4AOR.js → chunk-IMK4JKTA.js} +1 -1
- package/dist/{chunk-OAAP2MY6.js → chunk-K7D2EYBS.js} +2 -2
- package/dist/{chunk-ADUTW66W.js → chunk-O7YAW7G6.js} +6 -6
- package/dist/{chunk-5AFRMOPX.js → chunk-OH3EZXYX.js} +1 -1
- package/dist/{chunk-KFXF2V5G.js → chunk-OTHKG76J.js} +1 -1
- package/dist/chunk-OYHYCK4K.js +13 -0
- package/dist/{chunk-FKXFUUWJ.js → chunk-TYAHC25F.js} +1 -1
- package/dist/{chunk-5GYQ4J3D.js → chunk-UKUAAAXE.js} +2 -2
- package/dist/{chunk-LGGMJYY4.js → chunk-WUXFLE7X.js} +720 -369
- package/dist/{chunk-WX52U7Z2.js → chunk-ZU563PQI.js} +58 -2
- package/dist/daemon/server.js +372 -123
- package/dist/hook-runtime/daemon/server.js +372 -123
- package/dist/hook-runtime/hooks/post-commit.js +192 -58
- package/dist/hook-runtime/hooks/post-rewrite.js +58 -40
- package/dist/hook-runtime/hooks/post-tool-use.js +150 -67
- package/dist/hook-runtime/hooks/pre-tool-use.js +87 -15
- package/dist/hook-runtime/hooks/prim-hook.js +197 -107
- package/dist/hook-runtime/hooks/session-end.js +53 -5
- package/dist/hook-runtime/hooks/session-start.js +880 -280
- package/dist/hook-runtime/index.js +4008 -2438
- package/dist/hook-runtime/statusline-main.js +23 -1
- package/dist/hooks/post-commit.js +15 -8
- package/dist/hooks/post-rewrite.js +11 -8
- package/dist/hooks/post-tool-use.js +13 -15
- package/dist/hooks/pre-commit.js +4 -4
- package/dist/hooks/pre-tool-use.js +10 -12
- package/dist/hooks/prim-hook.js +17 -14
- package/dist/hooks/session-end.js +4 -4
- package/dist/hooks/session-start.js +35 -45
- package/dist/index.js +1401 -620
- package/dist/statusline-main.js +23 -1
- package/package.json +1 -1
- package/dist/chunk-BTNNEH2W.js +0 -79
|
@@ -1,27 +1,120 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getSiteUrl
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-G2VMA2QO.js";
|
|
4
4
|
import {
|
|
5
|
+
atomicWriteFile,
|
|
6
|
+
ensureDurableDirectory,
|
|
5
7
|
jwtOrganizationId,
|
|
6
8
|
primConfigDirectory,
|
|
7
9
|
resolveAuthCredential
|
|
8
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-2GP5UFV7.js";
|
|
9
11
|
|
|
10
12
|
// src/journal.ts
|
|
11
13
|
import {
|
|
12
14
|
appendFileSync,
|
|
13
15
|
closeSync,
|
|
14
|
-
existsSync,
|
|
16
|
+
existsSync as existsSync2,
|
|
15
17
|
fstatSync,
|
|
16
18
|
mkdirSync,
|
|
17
19
|
openSync,
|
|
18
20
|
opendirSync,
|
|
19
|
-
readFileSync,
|
|
21
|
+
readFileSync as readFileSync2,
|
|
20
22
|
readSync,
|
|
21
|
-
readdirSync
|
|
23
|
+
readdirSync as readdirSync2
|
|
22
24
|
} from "fs";
|
|
23
|
-
import { dirname, join } from "path";
|
|
24
|
-
|
|
25
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
26
|
+
|
|
27
|
+
// src/drain-progress.ts
|
|
28
|
+
import {
|
|
29
|
+
chmodSync,
|
|
30
|
+
existsSync,
|
|
31
|
+
readFileSync,
|
|
32
|
+
readdirSync,
|
|
33
|
+
rmSync,
|
|
34
|
+
rmdirSync
|
|
35
|
+
} from "fs";
|
|
36
|
+
import { basename, dirname, join } from "path";
|
|
37
|
+
var DIRECTORY_MODE = 448;
|
|
38
|
+
var FILE_MODE = 384;
|
|
39
|
+
var DRAIN_PROGRESS_DIRNAME = "drain-progress";
|
|
40
|
+
var CHECKPOINT_NAME = /^(.+)\.json(?:\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.tmp)?$/;
|
|
41
|
+
function rotationIdentity(stat) {
|
|
42
|
+
return { size: stat.size, ino: stat.ino, dev: stat.dev };
|
|
43
|
+
}
|
|
44
|
+
function drainProgressDirectory(flushingPath) {
|
|
45
|
+
return join(dirname(flushingPath), DRAIN_PROGRESS_DIRNAME);
|
|
46
|
+
}
|
|
47
|
+
function drainProgressPath(flushingPath) {
|
|
48
|
+
return join(drainProgressDirectory(flushingPath), `${basename(flushingPath)}.json`);
|
|
49
|
+
}
|
|
50
|
+
function isByteCount(value) {
|
|
51
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
52
|
+
}
|
|
53
|
+
function drainResumeOffset(flushingPath, rotation) {
|
|
54
|
+
let parsed;
|
|
55
|
+
try {
|
|
56
|
+
parsed = JSON.parse(readFileSync(drainProgressPath(flushingPath), "utf8"));
|
|
57
|
+
} catch {
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
const { v, offset, size, ino, dev } = parsed;
|
|
64
|
+
if (v !== 2 || ino !== rotation.ino || dev !== rotation.dev || size !== rotation.size || !isByteCount(offset) || offset > rotation.size) {
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
return offset;
|
|
68
|
+
}
|
|
69
|
+
function writeDrainCheckpoint(flushingPath, checkpoint) {
|
|
70
|
+
const directory = drainProgressDirectory(flushingPath);
|
|
71
|
+
ensureDurableDirectory(directory, DIRECTORY_MODE);
|
|
72
|
+
chmodSync(directory, DIRECTORY_MODE);
|
|
73
|
+
atomicWriteFile(drainProgressPath(flushingPath), `${JSON.stringify(checkpoint)}
|
|
74
|
+
`, {
|
|
75
|
+
mode: FILE_MODE
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function removeEmptyDirectory(directory) {
|
|
79
|
+
try {
|
|
80
|
+
rmdirSync(directory);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
const code = error.code;
|
|
83
|
+
if (code !== "ENOENT" && code !== "ENOTEMPTY" && code !== "EEXIST") {
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function removeDrainCheckpoint(flushingPath) {
|
|
89
|
+
rmSync(drainProgressPath(flushingPath), { force: true });
|
|
90
|
+
removeEmptyDirectory(drainProgressDirectory(flushingPath));
|
|
91
|
+
}
|
|
92
|
+
function sweepOrphanedDrainCheckpoints(bucketDirectory) {
|
|
93
|
+
const directory = join(bucketDirectory, DRAIN_PROGRESS_DIRNAME);
|
|
94
|
+
let names;
|
|
95
|
+
try {
|
|
96
|
+
names = readdirSync(directory);
|
|
97
|
+
} catch {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
for (const name of names) {
|
|
101
|
+
const rotation = CHECKPOINT_NAME.exec(name)?.[1];
|
|
102
|
+
if (rotation === void 0 || existsSync(join(bucketDirectory, rotation))) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
rmSync(join(directory, name), { force: true });
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
removeEmptyDirectory(directory);
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/journal.ts
|
|
117
|
+
var JOURNAL_DIR = join2(primConfigDirectory(), "moves");
|
|
25
118
|
var UNBOUND_BUCKET = "_unbound";
|
|
26
119
|
var JOURNAL_BASENAME = "journal.ndjson";
|
|
27
120
|
var JOURNAL_STATS_SAMPLE_BYTES = 64 * 1024;
|
|
@@ -34,26 +127,26 @@ function envSlug(apiUrl) {
|
|
|
34
127
|
return slug;
|
|
35
128
|
}
|
|
36
129
|
function currentEnvDir() {
|
|
37
|
-
return
|
|
130
|
+
return join2(JOURNAL_DIR, envSlug(getSiteUrl()));
|
|
38
131
|
}
|
|
39
132
|
var DIR_MODE = 448;
|
|
40
|
-
var
|
|
133
|
+
var FILE_MODE2 = 384;
|
|
41
134
|
var SAFE_BUCKET = /^[A-Za-z0-9_-]+$/;
|
|
42
135
|
var RESERVED_BUCKETS = /* @__PURE__ */ new Set([UNBOUND_BUCKET, "_legacy"]);
|
|
43
136
|
function appendMoveToPath(path, move) {
|
|
44
|
-
const dir =
|
|
45
|
-
if (!
|
|
137
|
+
const dir = dirname2(path);
|
|
138
|
+
if (!existsSync2(dir)) {
|
|
46
139
|
mkdirSync(dir, { recursive: true, mode: DIR_MODE });
|
|
47
140
|
}
|
|
48
141
|
appendFileSync(path, `${JSON.stringify(move)}
|
|
49
|
-
`, { mode:
|
|
142
|
+
`, { mode: FILE_MODE2 });
|
|
50
143
|
}
|
|
51
144
|
function bucketDir(orgId) {
|
|
52
145
|
const safe = orgId !== void 0 && SAFE_BUCKET.test(orgId) && !RESERVED_BUCKETS.has(orgId);
|
|
53
|
-
return
|
|
146
|
+
return join2(currentEnvDir(), safe ? orgId : UNBOUND_BUCKET);
|
|
54
147
|
}
|
|
55
148
|
function journalPath(orgId) {
|
|
56
|
-
return
|
|
149
|
+
return join2(bucketDir(orgId), JOURNAL_BASENAME);
|
|
57
150
|
}
|
|
58
151
|
function appendMove(move, orgId) {
|
|
59
152
|
appendMoveToPath(journalPath(orgId), move);
|
|
@@ -72,23 +165,23 @@ function parseMoves(content) {
|
|
|
72
165
|
return moves;
|
|
73
166
|
}
|
|
74
167
|
function readMovesFromPath(path) {
|
|
75
|
-
if (!
|
|
168
|
+
if (!existsSync2(path)) {
|
|
76
169
|
return [];
|
|
77
170
|
}
|
|
78
|
-
return parseMoves(
|
|
171
|
+
return parseMoves(readFileSync2(path, "utf-8"));
|
|
79
172
|
}
|
|
80
173
|
function listBuckets() {
|
|
81
174
|
const out = [];
|
|
82
175
|
const envDir = currentEnvDir();
|
|
83
|
-
if (!
|
|
176
|
+
if (!existsSync2(envDir)) {
|
|
84
177
|
return out;
|
|
85
178
|
}
|
|
86
|
-
for (const entry of
|
|
179
|
+
for (const entry of readdirSync2(envDir, { withFileTypes: true })) {
|
|
87
180
|
if (!entry.isDirectory()) {
|
|
88
181
|
continue;
|
|
89
182
|
}
|
|
90
|
-
const path =
|
|
91
|
-
if (
|
|
183
|
+
const path = join2(envDir, entry.name, JOURNAL_BASENAME);
|
|
184
|
+
if (existsSync2(path)) {
|
|
92
185
|
out.push({ bucket: entry.name, path });
|
|
93
186
|
}
|
|
94
187
|
}
|
|
@@ -105,22 +198,23 @@ function oldestCapturedAt(moves) {
|
|
|
105
198
|
}
|
|
106
199
|
return oldest;
|
|
107
200
|
}
|
|
108
|
-
function sampleJournalFile(path, maxBytes = JOURNAL_STATS_SAMPLE_BYTES) {
|
|
201
|
+
function sampleJournalFile(path, maxBytes = JOURNAL_STATS_SAMPLE_BYTES, options = {}) {
|
|
109
202
|
const fd = openSync(path, "r");
|
|
110
203
|
try {
|
|
111
204
|
const initial = fstatSync(fd);
|
|
112
|
-
const
|
|
205
|
+
const start = options.fromDrainCheckpoint ? drainResumeOffset(path, rotationIdentity(initial)) : 0;
|
|
206
|
+
const target = Math.min(initial.size - start, Math.max(0, maxBytes));
|
|
113
207
|
const buffer = Buffer.allocUnsafe(target);
|
|
114
208
|
let sampledBytes = 0;
|
|
115
209
|
while (sampledBytes < target) {
|
|
116
|
-
const count = readSync(fd, buffer, sampledBytes, target - sampledBytes, sampledBytes);
|
|
210
|
+
const count = readSync(fd, buffer, sampledBytes, target - sampledBytes, start + sampledBytes);
|
|
117
211
|
if (count === 0) {
|
|
118
212
|
break;
|
|
119
213
|
}
|
|
120
214
|
sampledBytes += count;
|
|
121
215
|
}
|
|
122
216
|
const stat = fstatSync(fd);
|
|
123
|
-
const sampled = stat.size > sampledBytes;
|
|
217
|
+
const sampled = stat.size - start > sampledBytes;
|
|
124
218
|
let content = buffer.subarray(0, sampledBytes).toString("utf-8");
|
|
125
219
|
if (sampled) {
|
|
126
220
|
const lastNewline = content.lastIndexOf("\n");
|
|
@@ -130,6 +224,7 @@ function sampleJournalFile(path, maxBytes = JOURNAL_STATS_SAMPLE_BYTES) {
|
|
|
130
224
|
const moves = parseMoves(content);
|
|
131
225
|
return {
|
|
132
226
|
sizeBytes: stat.size,
|
|
227
|
+
pendingBytes: Math.max(0, stat.size - start),
|
|
133
228
|
mtimeMs: stat.mtimeMs,
|
|
134
229
|
lineCount: sampled && stat.size > 0 ? Math.max(1, lines.length) : lines.length,
|
|
135
230
|
oldestCapturedAt: oldestCapturedAt(moves),
|
|
@@ -148,7 +243,7 @@ function parseFlushingPid(name) {
|
|
|
148
243
|
return Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
|
|
149
244
|
}
|
|
150
245
|
function listFlushingInDir(dir, bucket, options = {}) {
|
|
151
|
-
if (!
|
|
246
|
+
if (!existsSync2(dir)) {
|
|
152
247
|
return [];
|
|
153
248
|
}
|
|
154
249
|
const out = [];
|
|
@@ -162,10 +257,10 @@ function listFlushingInDir(dir, bucket, options = {}) {
|
|
|
162
257
|
entry = entries.readSync();
|
|
163
258
|
continue;
|
|
164
259
|
}
|
|
165
|
-
const path =
|
|
260
|
+
const path = join2(dir, entry.name);
|
|
166
261
|
let sample;
|
|
167
262
|
try {
|
|
168
|
-
sample = sampleJournalFile(path, remainingBytes);
|
|
263
|
+
sample = sampleJournalFile(path, remainingBytes, { fromDrainCheckpoint: true });
|
|
169
264
|
} catch (err) {
|
|
170
265
|
if (err.code === "ENOENT") {
|
|
171
266
|
entry = entries.readSync();
|
|
@@ -189,18 +284,18 @@ function listFlushingInDir(dir, bucket, options = {}) {
|
|
|
189
284
|
}
|
|
190
285
|
function listFlushing(options = {}) {
|
|
191
286
|
const envDir = currentEnvDir();
|
|
192
|
-
if (!
|
|
287
|
+
if (!existsSync2(envDir)) {
|
|
193
288
|
return [];
|
|
194
289
|
}
|
|
195
290
|
const out = [];
|
|
196
291
|
let remainingBytes = options.sampleBytes ?? JOURNAL_STATS_SAMPLE_BYTES;
|
|
197
292
|
const maxFiles = options.maxFiles ?? Number.POSITIVE_INFINITY;
|
|
198
|
-
for (const entry of
|
|
293
|
+
for (const entry of readdirSync2(envDir, { withFileTypes: true })) {
|
|
199
294
|
if (out.length >= maxFiles) {
|
|
200
295
|
break;
|
|
201
296
|
}
|
|
202
297
|
if (entry.isDirectory()) {
|
|
203
|
-
const found = listFlushingInDir(
|
|
298
|
+
const found = listFlushingInDir(join2(envDir, entry.name), entry.name, {
|
|
204
299
|
sampleBytes: remainingBytes,
|
|
205
300
|
maxFiles: maxFiles - out.length
|
|
206
301
|
});
|
|
@@ -213,6 +308,17 @@ function listFlushing(options = {}) {
|
|
|
213
308
|
}
|
|
214
309
|
return out;
|
|
215
310
|
}
|
|
311
|
+
function sweepOrphanedDrainProgress() {
|
|
312
|
+
const envDir = currentEnvDir();
|
|
313
|
+
if (!existsSync2(envDir)) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
for (const entry of readdirSync2(envDir, { withFileTypes: true })) {
|
|
317
|
+
if (entry.isDirectory()) {
|
|
318
|
+
sweepOrphanedDrainCheckpoints(join2(envDir, entry.name));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
216
322
|
function bucketStats(options = {}) {
|
|
217
323
|
const out = [];
|
|
218
324
|
let remainingBytes = options.sampleBytes ?? JOURNAL_STATS_SAMPLE_BYTES;
|
|
@@ -258,17 +364,17 @@ function pendingJournalStats() {
|
|
|
258
364
|
}
|
|
259
365
|
|
|
260
366
|
// src/binding.ts
|
|
261
|
-
import { existsSync as
|
|
262
|
-
import { dirname as
|
|
367
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
368
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
263
369
|
var PRIM_CONFIG_DIR = primConfigDirectory();
|
|
264
|
-
var SESSIONS_DIR =
|
|
370
|
+
var SESSIONS_DIR = join3(PRIM_CONFIG_DIR, "sessions");
|
|
265
371
|
var WORKSPACE_FILE = ".prim/workspace.json";
|
|
266
372
|
function readJsonSafe(path) {
|
|
267
|
-
if (!
|
|
373
|
+
if (!existsSync3(path)) {
|
|
268
374
|
return;
|
|
269
375
|
}
|
|
270
376
|
try {
|
|
271
|
-
return JSON.parse(
|
|
377
|
+
return JSON.parse(readFileSync3(path, "utf-8"));
|
|
272
378
|
} catch {
|
|
273
379
|
return;
|
|
274
380
|
}
|
|
@@ -277,17 +383,17 @@ function fromSessionMarker(sessionId) {
|
|
|
277
383
|
if (!sessionId) {
|
|
278
384
|
return;
|
|
279
385
|
}
|
|
280
|
-
const marker = readJsonSafe(
|
|
386
|
+
const marker = readJsonSafe(join3(SESSIONS_DIR, `${sessionId}.json`));
|
|
281
387
|
return marker?.orgId;
|
|
282
388
|
}
|
|
283
389
|
function fromWorkspaceFile(startDir) {
|
|
284
390
|
let dir = startDir;
|
|
285
391
|
while (true) {
|
|
286
|
-
const config = readJsonSafe(
|
|
392
|
+
const config = readJsonSafe(join3(dir, WORKSPACE_FILE));
|
|
287
393
|
if (config?.orgId) {
|
|
288
394
|
return config.orgId;
|
|
289
395
|
}
|
|
290
|
-
const parent =
|
|
396
|
+
const parent = dirname3(dir);
|
|
291
397
|
if (parent === dir) {
|
|
292
398
|
return;
|
|
293
399
|
}
|
|
@@ -315,11 +421,17 @@ function resolveOrg(args) {
|
|
|
315
421
|
}
|
|
316
422
|
|
|
317
423
|
export {
|
|
424
|
+
rotationIdentity,
|
|
425
|
+
drainResumeOffset,
|
|
426
|
+
writeDrainCheckpoint,
|
|
427
|
+
removeDrainCheckpoint,
|
|
318
428
|
JOURNAL_DIR,
|
|
429
|
+
envSlug,
|
|
319
430
|
appendMove,
|
|
320
431
|
readMovesFromPath,
|
|
321
432
|
listBuckets,
|
|
322
433
|
listFlushing,
|
|
434
|
+
sweepOrphanedDrainProgress,
|
|
323
435
|
bucketStats,
|
|
324
436
|
pendingJournalStats,
|
|
325
437
|
SESSIONS_DIR,
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import {
|
|
2
|
+
repoActiveFlag
|
|
3
|
+
} from "./chunk-DSWT6BCC.js";
|
|
4
|
+
import {
|
|
5
|
+
atomicWriteFile
|
|
6
|
+
} from "./chunk-2GP5UFV7.js";
|
|
7
|
+
|
|
8
|
+
// src/lib/commit-heartbeat.ts
|
|
9
|
+
import { execFileSync } from "child_process";
|
|
10
|
+
import { mkdirSync, readFileSync, readdirSync, unlinkSync } from "fs";
|
|
11
|
+
import { dirname, join, resolve } from "path";
|
|
12
|
+
var FIRED_DIR = "prim/post-commit-fired";
|
|
13
|
+
var WIRED_PATH = "prim/git-hooks-wired";
|
|
14
|
+
var GIT_TIMEOUT_MS = 1e3;
|
|
15
|
+
var MAX_STAMP_BYTES = 1024;
|
|
16
|
+
var REFLOG_SCAN_LIMIT = 50;
|
|
17
|
+
var MAX_FIRED_STAMPS = 256;
|
|
18
|
+
var PRUNE_SLACK = 64;
|
|
19
|
+
var FULL_SHA_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u;
|
|
20
|
+
var REFLOG_LINE_RE = /^([0-9a-f]{40}|[0-9a-f]{64})\tHEAD@\{([0-9]+)\}\t(.*)$/u;
|
|
21
|
+
var COMMIT_SUBJECT_RE = /^commit(?: \([^)]*\))?:/u;
|
|
22
|
+
var REFLOG_RESOLUTION_MS = 1e3;
|
|
23
|
+
var POST_COMMIT_GRACE_MS = 6e4;
|
|
24
|
+
function commonPath(cwd, relative) {
|
|
25
|
+
const value = execFileSync("git", ["rev-parse", "--git-common-dir"], {
|
|
26
|
+
cwd,
|
|
27
|
+
encoding: "utf8",
|
|
28
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
29
|
+
timeout: GIT_TIMEOUT_MS
|
|
30
|
+
}).replace(/\r?\n$/u, "");
|
|
31
|
+
if (value === "" || /[\0\r\n]/u.test(value)) throw new Error("Git returned an unsafe path");
|
|
32
|
+
return join(resolve(cwd, value), relative);
|
|
33
|
+
}
|
|
34
|
+
function gitPath(cwd, relative) {
|
|
35
|
+
const value = execFileSync("git", ["rev-parse", "--git-path", relative], {
|
|
36
|
+
cwd,
|
|
37
|
+
encoding: "utf8",
|
|
38
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
39
|
+
timeout: GIT_TIMEOUT_MS
|
|
40
|
+
}).replace(/\r?\n$/u, "");
|
|
41
|
+
if (value === "" || /[\0\r\n]/u.test(value)) throw new Error("Git returned an unsafe path");
|
|
42
|
+
return resolve(cwd, value);
|
|
43
|
+
}
|
|
44
|
+
function ensurePrivateParent(path) {
|
|
45
|
+
mkdirSync(dirname(dirname(path)), { recursive: true, mode: 448 });
|
|
46
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
47
|
+
}
|
|
48
|
+
function writeStamp(path, at) {
|
|
49
|
+
ensurePrivateParent(path);
|
|
50
|
+
atomicWriteFile(path, `${JSON.stringify({ at })}
|
|
51
|
+
`, { mode: 384 });
|
|
52
|
+
}
|
|
53
|
+
function readStampAt(path) {
|
|
54
|
+
try {
|
|
55
|
+
const raw = readFileSync(path, "utf8");
|
|
56
|
+
if (raw.length > MAX_STAMP_BYTES) return void 0;
|
|
57
|
+
const parsed = JSON.parse(raw);
|
|
58
|
+
return typeof parsed.at === "number" && Number.isSafeInteger(parsed.at) && parsed.at > 0 ? parsed.at : void 0;
|
|
59
|
+
} catch {
|
|
60
|
+
return void 0;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function readFiredStamps(cwd) {
|
|
64
|
+
try {
|
|
65
|
+
const dir = gitPath(cwd, FIRED_DIR);
|
|
66
|
+
return readdirSync(dir).flatMap((sha) => {
|
|
67
|
+
if (!FULL_SHA_RE.test(sha)) return [];
|
|
68
|
+
const at = readStampAt(join(dir, sha));
|
|
69
|
+
return at === void 0 ? [] : [{ sha, at }];
|
|
70
|
+
});
|
|
71
|
+
} catch {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function recordPostCommitFired(cwd, sha, now = Date.now()) {
|
|
76
|
+
if (!sha || !FULL_SHA_RE.test(sha)) return;
|
|
77
|
+
try {
|
|
78
|
+
const dir = gitPath(cwd, FIRED_DIR);
|
|
79
|
+
writeStamp(join(dir, sha), now);
|
|
80
|
+
const names = readdirSync(dir).filter((name) => FULL_SHA_RE.test(name));
|
|
81
|
+
if (names.length <= MAX_FIRED_STAMPS + PRUNE_SLACK) return;
|
|
82
|
+
const stale = names.map((name) => ({ name, at: readStampAt(join(dir, name)) ?? 0 })).sort((left, right) => right.at - left.at || left.name.localeCompare(right.name)).slice(MAX_FIRED_STAMPS);
|
|
83
|
+
for (const { name } of stale) unlinkSync(join(dir, name));
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function recordHooksWired(cwd, options = {}) {
|
|
88
|
+
try {
|
|
89
|
+
const path = commonPath(cwd, WIRED_PATH);
|
|
90
|
+
if (options.onlyIfAbsent && readStampAt(path) !== void 0) return;
|
|
91
|
+
writeStamp(path, options.now ?? Date.now());
|
|
92
|
+
} catch {
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function clearHooksWired(cwd) {
|
|
96
|
+
try {
|
|
97
|
+
unlinkSync(commonPath(cwd, WIRED_PATH));
|
|
98
|
+
} catch {
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function latestLocalCommit(cwd) {
|
|
102
|
+
let output;
|
|
103
|
+
try {
|
|
104
|
+
output = execFileSync(
|
|
105
|
+
"git",
|
|
106
|
+
[
|
|
107
|
+
"log",
|
|
108
|
+
"-g",
|
|
109
|
+
"--no-show-signature",
|
|
110
|
+
"-n",
|
|
111
|
+
String(REFLOG_SCAN_LIMIT),
|
|
112
|
+
"--date=unix",
|
|
113
|
+
"--format=%H%x09%gd%x09%gs",
|
|
114
|
+
"HEAD"
|
|
115
|
+
],
|
|
116
|
+
{ cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: GIT_TIMEOUT_MS }
|
|
117
|
+
);
|
|
118
|
+
} catch {
|
|
119
|
+
return void 0;
|
|
120
|
+
}
|
|
121
|
+
for (const line of output.split("\n")) {
|
|
122
|
+
const match = REFLOG_LINE_RE.exec(line);
|
|
123
|
+
if (match?.[1] && match[2] && COMMIT_SUBJECT_RE.test(match[3] ?? "")) {
|
|
124
|
+
return { sha: match[1], at: Number(match[2]) * 1e3 };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return void 0;
|
|
128
|
+
}
|
|
129
|
+
function inspectPostCommitFiring(cwd, now = Date.now()) {
|
|
130
|
+
if (repoActiveFlag(cwd) !== "true") return { state: "inactive" };
|
|
131
|
+
const commit = latestLocalCommit(cwd);
|
|
132
|
+
if (!commit) return { state: "unverified" };
|
|
133
|
+
const stamps = readFiredStamps(cwd);
|
|
134
|
+
const own = stamps.find((stamp) => stamp.sha === commit.sha);
|
|
135
|
+
if (own) return { state: "fired", commitAt: commit.at, firedAt: own.at };
|
|
136
|
+
if (now - commit.at < POST_COMMIT_GRACE_MS) return { state: "pending", commitAt: commit.at };
|
|
137
|
+
const lastFiredAt = stamps.reduce(
|
|
138
|
+
(latest, stamp) => latest === void 0 || stamp.at > latest ? stamp.at : latest,
|
|
139
|
+
void 0
|
|
140
|
+
);
|
|
141
|
+
let wiredAt;
|
|
142
|
+
try {
|
|
143
|
+
wiredAt = readStampAt(commonPath(cwd, WIRED_PATH));
|
|
144
|
+
} catch {
|
|
145
|
+
wiredAt = void 0;
|
|
146
|
+
}
|
|
147
|
+
if (wiredAt === void 0) return { state: "unverified" };
|
|
148
|
+
const firedSinceWired = lastFiredAt !== void 0 && lastFiredAt >= wiredAt;
|
|
149
|
+
const judged = commit.at > wiredAt || firedSinceWired && commit.at + REFLOG_RESOLUTION_MS > lastFiredAt;
|
|
150
|
+
if (judged) {
|
|
151
|
+
return lastFiredAt === void 0 ? { state: "not_firing", commitAt: commit.at } : { state: "not_firing", commitAt: commit.at, firedAt: lastFiredAt };
|
|
152
|
+
}
|
|
153
|
+
return { state: "unverified" };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export {
|
|
157
|
+
POST_COMMIT_GRACE_MS,
|
|
158
|
+
recordPostCommitFired,
|
|
159
|
+
recordHooksWired,
|
|
160
|
+
clearHooksWired,
|
|
161
|
+
inspectPostCommitFiring
|
|
162
|
+
};
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
resolveAuthCredential,
|
|
20
20
|
withFileLock,
|
|
21
21
|
workosConnectTokenEndpoint
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-2GP5UFV7.js";
|
|
23
23
|
|
|
24
24
|
// src/lib/terminal-safe.ts
|
|
25
25
|
function replaceIsolatedSurrogates(value) {
|
|
@@ -645,6 +645,7 @@ export {
|
|
|
645
645
|
isTerminalSafeText,
|
|
646
646
|
stripControlChars,
|
|
647
647
|
getSiteUrl,
|
|
648
|
+
getSiteUrlForEnvironment,
|
|
648
649
|
getTokenExpiresAt,
|
|
649
650
|
isSessionEnded,
|
|
650
651
|
commitCredentials,
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
} from "./chunk-EZXEWFDG.js";
|
|
4
4
|
import {
|
|
5
5
|
scrubEnvironmentPaths
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-K7D2EYBS.js";
|
|
7
7
|
import {
|
|
8
8
|
isValidRepoSyncId
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-DSWT6BCC.js";
|
|
10
10
|
|
|
11
11
|
// src/hooks/prim-hook-core.ts
|
|
12
12
|
import { createHash, randomUUID } from "crypto";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
boundedHealthError
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-DSWT6BCC.js";
|
|
4
4
|
import {
|
|
5
5
|
terminalSafeLine
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-G2VMA2QO.js";
|
|
7
7
|
|
|
8
8
|
// src/hooks/redact.ts
|
|
9
9
|
import { existsSync, readFileSync, statSync } from "fs";
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
formatStatusline
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-ZU563PQI.js";
|
|
4
4
|
import {
|
|
5
5
|
decisionIngestionStatus,
|
|
6
6
|
repositoryBindingState
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-DSWT6BCC.js";
|
|
8
8
|
import {
|
|
9
9
|
getSiteUrl,
|
|
10
10
|
isSessionEnded,
|
|
11
11
|
terminalSafeText
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-G2VMA2QO.js";
|
|
13
13
|
import {
|
|
14
14
|
packageVersion
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-AXQQMX2U.js";
|
|
16
16
|
import {
|
|
17
17
|
gitToplevel
|
|
18
18
|
} from "./chunk-H4UNYFUC.js";
|
|
@@ -20,11 +20,11 @@ import {
|
|
|
20
20
|
daemonPrincipalsMatch,
|
|
21
21
|
daemonRequest,
|
|
22
22
|
resolveDaemonPrincipal
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-OTHKG76J.js";
|
|
24
24
|
import {
|
|
25
25
|
primConfigDirectory,
|
|
26
26
|
withFileLock
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-2GP5UFV7.js";
|
|
28
28
|
|
|
29
29
|
// src/hooks/codex-context.ts
|
|
30
30
|
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/lib/unattended.ts
|
|
2
|
+
var UNATTENDED_ENV = "PRIM_UNATTENDED";
|
|
3
|
+
function unattendedEnv(env = process.env) {
|
|
4
|
+
return { ...env, [UNATTENDED_ENV]: "1" };
|
|
5
|
+
}
|
|
6
|
+
function isUnattended(env = process.env) {
|
|
7
|
+
return env[UNATTENDED_ENV] === "1";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
unattendedEnv,
|
|
12
|
+
isUnattended
|
|
13
|
+
};
|
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
getClient,
|
|
3
3
|
getSiteUrl,
|
|
4
4
|
terminalSafeLine
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-G2VMA2QO.js";
|
|
6
6
|
import {
|
|
7
7
|
daemonRequest
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-OTHKG76J.js";
|
|
9
9
|
|
|
10
10
|
// src/daemon/proxy.ts
|
|
11
11
|
var DAEMON_HTTP_TIMEOUT_MS = 1e4;
|