opencastle 0.27.0 → 0.27.2
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/bin/cli.mjs +6 -0
- package/dist/cli/agents.d.ts +3 -0
- package/dist/cli/agents.d.ts.map +1 -0
- package/dist/cli/agents.js +161 -0
- package/dist/cli/agents.js.map +1 -0
- package/dist/cli/baselines.d.ts +3 -0
- package/dist/cli/baselines.d.ts.map +1 -0
- package/dist/cli/baselines.js +128 -0
- package/dist/cli/baselines.js.map +1 -0
- package/dist/cli/convoy/dashboard-types.d.ts +146 -0
- package/dist/cli/convoy/dashboard-types.d.ts.map +1 -0
- package/dist/cli/convoy/dashboard-types.js +2 -0
- package/dist/cli/convoy/dashboard-types.js.map +1 -0
- package/dist/cli/convoy/engine.d.ts +67 -2
- package/dist/cli/convoy/engine.d.ts.map +1 -1
- package/dist/cli/convoy/engine.js +2036 -28
- package/dist/cli/convoy/engine.js.map +1 -1
- package/dist/cli/convoy/engine.test.js +1659 -70
- package/dist/cli/convoy/engine.test.js.map +1 -1
- package/dist/cli/convoy/event-schemas.d.ts +9 -0
- package/dist/cli/convoy/event-schemas.d.ts.map +1 -0
- package/dist/cli/convoy/event-schemas.js +185 -0
- package/dist/cli/convoy/event-schemas.js.map +1 -0
- package/dist/cli/convoy/events.d.ts +12 -1
- package/dist/cli/convoy/events.d.ts.map +1 -1
- package/dist/cli/convoy/events.js +186 -13
- package/dist/cli/convoy/events.js.map +1 -1
- package/dist/cli/convoy/events.test.js +325 -28
- package/dist/cli/convoy/events.test.js.map +1 -1
- package/dist/cli/convoy/expertise.d.ts +16 -0
- package/dist/cli/convoy/expertise.d.ts.map +1 -0
- package/dist/cli/convoy/expertise.js +121 -0
- package/dist/cli/convoy/expertise.js.map +1 -0
- package/dist/cli/convoy/expertise.test.d.ts +2 -0
- package/dist/cli/convoy/expertise.test.d.ts.map +1 -0
- package/dist/cli/convoy/expertise.test.js +96 -0
- package/dist/cli/convoy/expertise.test.js.map +1 -0
- package/dist/cli/convoy/export.test.js +1 -0
- package/dist/cli/convoy/export.test.js.map +1 -1
- package/dist/cli/convoy/formula.d.ts +19 -0
- package/dist/cli/convoy/formula.d.ts.map +1 -0
- package/dist/cli/convoy/formula.js +142 -0
- package/dist/cli/convoy/formula.js.map +1 -0
- package/dist/cli/convoy/formula.test.d.ts +2 -0
- package/dist/cli/convoy/formula.test.d.ts.map +1 -0
- package/dist/cli/convoy/formula.test.js +342 -0
- package/dist/cli/convoy/formula.test.js.map +1 -0
- package/dist/cli/convoy/gates.d.ts +128 -0
- package/dist/cli/convoy/gates.d.ts.map +1 -0
- package/dist/cli/convoy/gates.js +606 -0
- package/dist/cli/convoy/gates.js.map +1 -0
- package/dist/cli/convoy/gates.test.d.ts +2 -0
- package/dist/cli/convoy/gates.test.d.ts.map +1 -0
- package/dist/cli/convoy/gates.test.js +976 -0
- package/dist/cli/convoy/gates.test.js.map +1 -0
- package/dist/cli/convoy/health.d.ts +11 -0
- package/dist/cli/convoy/health.d.ts.map +1 -1
- package/dist/cli/convoy/health.js +54 -0
- package/dist/cli/convoy/health.js.map +1 -1
- package/dist/cli/convoy/health.test.js +56 -1
- package/dist/cli/convoy/health.test.js.map +1 -1
- package/dist/cli/convoy/issues.d.ts +8 -0
- package/dist/cli/convoy/issues.d.ts.map +1 -0
- package/dist/cli/convoy/issues.js +98 -0
- package/dist/cli/convoy/issues.js.map +1 -0
- package/dist/cli/convoy/issues.test.d.ts +2 -0
- package/dist/cli/convoy/issues.test.d.ts.map +1 -0
- package/dist/cli/convoy/issues.test.js +107 -0
- package/dist/cli/convoy/issues.test.js.map +1 -0
- package/dist/cli/convoy/knowledge.d.ts +5 -0
- package/dist/cli/convoy/knowledge.d.ts.map +1 -0
- package/dist/cli/convoy/knowledge.js +116 -0
- package/dist/cli/convoy/knowledge.js.map +1 -0
- package/dist/cli/convoy/knowledge.test.d.ts +2 -0
- package/dist/cli/convoy/knowledge.test.d.ts.map +1 -0
- package/dist/cli/convoy/knowledge.test.js +87 -0
- package/dist/cli/convoy/knowledge.test.js.map +1 -0
- package/dist/cli/convoy/lessons.d.ts +17 -0
- package/dist/cli/convoy/lessons.d.ts.map +1 -0
- package/dist/cli/convoy/lessons.js +149 -0
- package/dist/cli/convoy/lessons.js.map +1 -0
- package/dist/cli/convoy/lessons.test.d.ts +2 -0
- package/dist/cli/convoy/lessons.test.d.ts.map +1 -0
- package/dist/cli/convoy/lessons.test.js +135 -0
- package/dist/cli/convoy/lessons.test.js.map +1 -0
- package/dist/cli/convoy/lock.d.ts +13 -0
- package/dist/cli/convoy/lock.d.ts.map +1 -0
- package/dist/cli/convoy/lock.js +88 -0
- package/dist/cli/convoy/lock.js.map +1 -0
- package/dist/cli/convoy/lock.test.d.ts +2 -0
- package/dist/cli/convoy/lock.test.d.ts.map +1 -0
- package/dist/cli/convoy/lock.test.js +136 -0
- package/dist/cli/convoy/lock.test.js.map +1 -0
- package/dist/cli/convoy/log-merge.test.d.ts +2 -0
- package/dist/cli/convoy/log-merge.test.d.ts.map +1 -0
- package/dist/cli/convoy/log-merge.test.js +147 -0
- package/dist/cli/convoy/log-merge.test.js.map +1 -0
- package/dist/cli/convoy/merge.d.ts +4 -0
- package/dist/cli/convoy/merge.d.ts.map +1 -1
- package/dist/cli/convoy/merge.js +18 -1
- package/dist/cli/convoy/merge.js.map +1 -1
- package/dist/cli/convoy/merge.test.js +6 -7
- package/dist/cli/convoy/merge.test.js.map +1 -1
- package/dist/cli/convoy/partition.d.ts +51 -0
- package/dist/cli/convoy/partition.d.ts.map +1 -0
- package/dist/cli/convoy/partition.js +186 -0
- package/dist/cli/convoy/partition.js.map +1 -0
- package/dist/cli/convoy/partition.test.d.ts +2 -0
- package/dist/cli/convoy/partition.test.d.ts.map +1 -0
- package/dist/cli/convoy/partition.test.js +315 -0
- package/dist/cli/convoy/partition.test.js.map +1 -0
- package/dist/cli/convoy/pipeline.test.js +6 -0
- package/dist/cli/convoy/pipeline.test.js.map +1 -1
- package/dist/cli/convoy/store.d.ts +99 -7
- package/dist/cli/convoy/store.d.ts.map +1 -1
- package/dist/cli/convoy/store.js +764 -31
- package/dist/cli/convoy/store.js.map +1 -1
- package/dist/cli/convoy/store.test.js +1810 -18
- package/dist/cli/convoy/store.test.js.map +1 -1
- package/dist/cli/convoy/types.d.ts +427 -5
- package/dist/cli/convoy/types.d.ts.map +1 -1
- package/dist/cli/convoy/types.js +42 -1
- package/dist/cli/convoy/types.js.map +1 -1
- package/dist/cli/log.d.ts +11 -0
- package/dist/cli/log.d.ts.map +1 -1
- package/dist/cli/log.js +114 -2
- package/dist/cli/log.js.map +1 -1
- package/dist/cli/run/adapters/claude.d.ts +2 -0
- package/dist/cli/run/adapters/claude.d.ts.map +1 -1
- package/dist/cli/run/adapters/claude.js +89 -49
- package/dist/cli/run/adapters/claude.js.map +1 -1
- package/dist/cli/run/adapters/claude.test.d.ts +2 -0
- package/dist/cli/run/adapters/claude.test.d.ts.map +1 -0
- package/dist/cli/run/adapters/claude.test.js +205 -0
- package/dist/cli/run/adapters/claude.test.js.map +1 -0
- package/dist/cli/run/adapters/copilot.d.ts +1 -0
- package/dist/cli/run/adapters/copilot.d.ts.map +1 -1
- package/dist/cli/run/adapters/copilot.js +84 -46
- package/dist/cli/run/adapters/copilot.js.map +1 -1
- package/dist/cli/run/adapters/copilot.test.d.ts +2 -0
- package/dist/cli/run/adapters/copilot.test.d.ts.map +1 -0
- package/dist/cli/run/adapters/copilot.test.js +195 -0
- package/dist/cli/run/adapters/copilot.test.js.map +1 -0
- package/dist/cli/run/adapters/cursor.d.ts +1 -0
- package/dist/cli/run/adapters/cursor.d.ts.map +1 -1
- package/dist/cli/run/adapters/cursor.js +83 -47
- package/dist/cli/run/adapters/cursor.js.map +1 -1
- package/dist/cli/run/adapters/cursor.test.d.ts +2 -0
- package/dist/cli/run/adapters/cursor.test.d.ts.map +1 -0
- package/dist/cli/run/adapters/cursor.test.js +129 -0
- package/dist/cli/run/adapters/cursor.test.js.map +1 -0
- package/dist/cli/run/adapters/opencode.d.ts +1 -0
- package/dist/cli/run/adapters/opencode.d.ts.map +1 -1
- package/dist/cli/run/adapters/opencode.js +81 -47
- package/dist/cli/run/adapters/opencode.js.map +1 -1
- package/dist/cli/run/adapters/opencode.test.d.ts +2 -0
- package/dist/cli/run/adapters/opencode.test.d.ts.map +1 -0
- package/dist/cli/run/adapters/opencode.test.js +119 -0
- package/dist/cli/run/adapters/opencode.test.js.map +1 -0
- package/dist/cli/run/executor.js +1 -1
- package/dist/cli/run/executor.js.map +1 -1
- package/dist/cli/run/schema.d.ts.map +1 -1
- package/dist/cli/run/schema.js +245 -4
- package/dist/cli/run/schema.js.map +1 -1
- package/dist/cli/run/schema.test.js +669 -0
- package/dist/cli/run/schema.test.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +362 -22
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/types.d.ts +85 -2
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/types.js.map +1 -1
- package/dist/cli/watch.d.ts +15 -0
- package/dist/cli/watch.d.ts.map +1 -0
- package/dist/cli/watch.js +279 -0
- package/dist/cli/watch.js.map +1 -0
- package/package.json +5 -1
- package/src/cli/agents.ts +177 -0
- package/src/cli/baselines.ts +143 -0
- package/src/cli/convoy/TELEMETRY.md +203 -0
- package/src/cli/convoy/dashboard-types.ts +141 -0
- package/src/cli/convoy/engine.test.ts +1937 -70
- package/src/cli/convoy/engine.ts +2350 -40
- package/src/cli/convoy/event-schemas.ts +195 -0
- package/src/cli/convoy/events.test.ts +384 -39
- package/src/cli/convoy/events.ts +202 -16
- package/src/cli/convoy/expertise.test.ts +128 -0
- package/src/cli/convoy/expertise.ts +163 -0
- package/src/cli/convoy/export.test.ts +1 -0
- package/src/cli/convoy/formula.test.ts +405 -0
- package/src/cli/convoy/formula.ts +174 -0
- package/src/cli/convoy/gates.test.ts +1169 -0
- package/src/cli/convoy/gates.ts +774 -0
- package/src/cli/convoy/health.test.ts +64 -2
- package/src/cli/convoy/health.ts +80 -2
- package/src/cli/convoy/issues.test.ts +143 -0
- package/src/cli/convoy/issues.ts +136 -0
- package/src/cli/convoy/knowledge.test.ts +101 -0
- package/src/cli/convoy/knowledge.ts +132 -0
- package/src/cli/convoy/lessons.test.ts +188 -0
- package/src/cli/convoy/lessons.ts +164 -0
- package/src/cli/convoy/lock.test.ts +181 -0
- package/src/cli/convoy/lock.ts +103 -0
- package/src/cli/convoy/log-merge.test.ts +179 -0
- package/src/cli/convoy/merge.test.ts +6 -7
- package/src/cli/convoy/merge.ts +19 -1
- package/src/cli/convoy/partition.test.ts +423 -0
- package/src/cli/convoy/partition.ts +232 -0
- package/src/cli/convoy/pipeline.test.ts +6 -0
- package/src/cli/convoy/store.test.ts +2041 -20
- package/src/cli/convoy/store.ts +945 -46
- package/src/cli/convoy/types.ts +278 -4
- package/src/cli/log.ts +120 -2
- package/src/cli/run/adapters/claude.test.ts +234 -0
- package/src/cli/run/adapters/claude.ts +45 -5
- package/src/cli/run/adapters/copilot.test.ts +224 -0
- package/src/cli/run/adapters/copilot.ts +34 -4
- package/src/cli/run/adapters/cursor.test.ts +144 -0
- package/src/cli/run/adapters/cursor.ts +33 -2
- package/src/cli/run/adapters/opencode.test.ts +135 -0
- package/src/cli/run/adapters/opencode.ts +30 -2
- package/src/cli/run/executor.ts +1 -1
- package/src/cli/run/schema.test.ts +758 -0
- package/src/cli/run/schema.ts +300 -25
- package/src/cli/run.ts +341 -21
- package/src/cli/types.ts +86 -1
- package/src/cli/watch.ts +298 -0
- package/src/dashboard/dist/_astro/{index.DtnyD8a5.css → index.6L3_HsPT.css} +1 -1
- package/src/dashboard/dist/data/.gitkeep +0 -0
- package/src/dashboard/dist/data/convoy-list.json +1 -0
- package/src/dashboard/dist/data/overall-stats.json +24 -0
- package/src/dashboard/dist/index.html +701 -3
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
- package/src/dashboard/public/data/.gitkeep +0 -0
- package/src/dashboard/public/data/convoy-list.json +1 -0
- package/src/dashboard/public/data/overall-stats.json +24 -0
- package/src/dashboard/scripts/etl.test.ts +210 -0
- package/src/dashboard/scripts/etl.ts +108 -0
- package/src/dashboard/scripts/integration-test.ts +504 -0
- package/src/dashboard/src/pages/index.astro +854 -15
- package/src/dashboard/src/styles/dashboard.css +557 -1
- package/src/orchestrator/prompts/generate-convoy.prompt.md +212 -13
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { hostname as getHostname } from 'node:os';
|
|
2
|
+
export class EngineAlreadyRunningError extends Error {
|
|
3
|
+
pid;
|
|
4
|
+
hostname;
|
|
5
|
+
constructor(pid, hostname) {
|
|
6
|
+
super(`Another opencastle process (PID ${pid} on ${hostname}) is already running against this database.`);
|
|
7
|
+
this.pid = pid;
|
|
8
|
+
this.hostname = hostname;
|
|
9
|
+
this.name = 'EngineAlreadyRunningError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function checkStaleness(row) {
|
|
13
|
+
const heartbeatAge = Date.now() - new Date(row.last_heartbeat).getTime();
|
|
14
|
+
if (heartbeatAge <= 30_000)
|
|
15
|
+
return false;
|
|
16
|
+
if (row.hostname !== getHostname())
|
|
17
|
+
return true;
|
|
18
|
+
try {
|
|
19
|
+
process.kill(row.pid, 0);
|
|
20
|
+
return false; // PID is alive on this host
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return true; // PID is dead
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function isLockStale(db) {
|
|
27
|
+
const row = db
|
|
28
|
+
.prepare('SELECT pid, hostname, last_heartbeat FROM engine_lock WHERE id = 1')
|
|
29
|
+
.get();
|
|
30
|
+
if (!row)
|
|
31
|
+
return true;
|
|
32
|
+
return checkStaleness(row);
|
|
33
|
+
}
|
|
34
|
+
export function releaseEngineLock(db) {
|
|
35
|
+
db.exec('DELETE FROM engine_lock WHERE id = 1');
|
|
36
|
+
}
|
|
37
|
+
export function acquireEngineLock(db, _dbPath) {
|
|
38
|
+
// BEGIN IMMEDIATE acquires a write lock upfront, preventing concurrent writers
|
|
39
|
+
try {
|
|
40
|
+
db.exec('BEGIN IMMEDIATE');
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
const msg = err.message ?? '';
|
|
44
|
+
if (msg.includes('SQLITE_BUSY') || msg.includes('database is locked')) {
|
|
45
|
+
throw new EngineAlreadyRunningError(0, 'unknown');
|
|
46
|
+
}
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
const existing = db
|
|
50
|
+
.prepare('SELECT pid, hostname, last_heartbeat FROM engine_lock WHERE id = 1')
|
|
51
|
+
.get();
|
|
52
|
+
if (existing) {
|
|
53
|
+
const stale = checkStaleness(existing);
|
|
54
|
+
if (!stale) {
|
|
55
|
+
db.exec('ROLLBACK');
|
|
56
|
+
throw new EngineAlreadyRunningError(existing.pid, existing.hostname);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const now = new Date().toISOString();
|
|
60
|
+
db.prepare('INSERT OR REPLACE INTO engine_lock (id, pid, hostname, started_at, last_heartbeat) VALUES (1, ?, ?, ?, ?)').run(process.pid, getHostname(), now, now);
|
|
61
|
+
db.exec('COMMIT');
|
|
62
|
+
let heartbeatInterval;
|
|
63
|
+
function startHeartbeat() {
|
|
64
|
+
heartbeatInterval = setInterval(() => {
|
|
65
|
+
try {
|
|
66
|
+
db.prepare('UPDATE engine_lock SET last_heartbeat = ? WHERE id = 1').run(new Date().toISOString());
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Ignore errors — DB may have been closed
|
|
70
|
+
}
|
|
71
|
+
}, 10_000);
|
|
72
|
+
return heartbeatInterval;
|
|
73
|
+
}
|
|
74
|
+
function release() {
|
|
75
|
+
if (heartbeatInterval !== undefined) {
|
|
76
|
+
clearInterval(heartbeatInterval);
|
|
77
|
+
heartbeatInterval = undefined;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
db.exec('DELETE FROM engine_lock WHERE id = 1');
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Ignore errors — DB may have been closed
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { release, startHeartbeat };
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.js","sourceRoot":"","sources":["../../../src/cli/convoy/lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,WAAW,EAAE,MAAM,SAAS,CAAA;AAGjD,MAAM,OAAO,yBAA0B,SAAQ,KAAK;IACtB;IAA6B;IAAzD,YAA4B,GAAW,EAAkB,QAAgB;QACvE,KAAK,CACH,mCAAmC,GAAG,OAAO,QAAQ,6CAA6C,CACnG,CAAA;QAHyB,QAAG,GAAH,GAAG,CAAQ;QAAkB,aAAQ,GAAR,QAAQ,CAAQ;QAIvE,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAA;IACzC,CAAC;CACF;AAID,SAAS,cAAc,CAAC,GAAY;IAClC,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,OAAO,EAAE,CAAA;IACxE,IAAI,YAAY,IAAI,MAAM;QAAE,OAAO,KAAK,CAAA;IACxC,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,EAAE;QAAE,OAAO,IAAI,CAAA;IAC/C,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QACxB,OAAO,KAAK,CAAA,CAAC,4BAA4B;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA,CAAC,cAAc;IAC5B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,EAAgB;IAC1C,MAAM,GAAG,GAAG,EAAE;SACX,OAAO,CAAC,oEAAoE,CAAC;SAC7E,GAAG,EAAyB,CAAA;IAC/B,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAA;IACrB,OAAO,cAAc,CAAC,GAAG,CAAC,CAAA;AAC5B,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,EAAgB;IAChD,EAAE,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAA;AACjD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,EAAgB,EAChB,OAAe;IAKf,+EAA+E;IAC/E,IAAI,CAAC;QACH,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA;IAC5B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAI,GAAa,CAAC,OAAO,IAAI,EAAE,CAAA;QACxC,IAAI,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAAC;YACtE,MAAM,IAAI,yBAAyB,CAAC,CAAC,EAAE,SAAS,CAAC,CAAA;QACnD,CAAC;QACD,MAAM,GAAG,CAAA;IACX,CAAC;IAED,MAAM,QAAQ,GAAG,EAAE;SAChB,OAAO,CAAC,oEAAoE,CAAC;SAC7E,GAAG,EAAyB,CAAA;IAE/B,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAA;QACtC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;YACnB,MAAM,IAAI,yBAAyB,CAAC,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAA;QACtE,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACpC,EAAE,CAAC,OAAO,CACR,2GAA2G,CAC5G,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,WAAW,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;IAC3C,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAEjB,IAAI,iBAA6C,CAAA;IAEjD,SAAS,cAAc;QACrB,iBAAiB,GAAG,WAAW,CAAC,GAAG,EAAE;YACnC,IAAI,CAAC;gBACH,EAAE,CAAC,OAAO,CAAC,wDAAwD,CAAC,CAAC,GAAG,CACtE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CACzB,CAAA;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,0CAA0C;YAC5C,CAAC;QACH,CAAC,EAAE,MAAM,CAAC,CAAA;QACV,OAAO,iBAAiB,CAAA;IAC1B,CAAC;IAED,SAAS,OAAO;QACd,IAAI,iBAAiB,KAAK,SAAS,EAAE,CAAC;YACpC,aAAa,CAAC,iBAAiB,CAAC,CAAA;YAChC,iBAAiB,GAAG,SAAS,CAAA;QAC/B,CAAC;QACD,IAAI,CAAC;YACH,EAAE,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAA;QACjD,CAAC;QAAC,MAAM,CAAC;YACP,0CAA0C;QAC5C,CAAC;IACH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,CAAA;AACpC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.test.d.ts","sourceRoot":"","sources":["../../../src/cli/convoy/lock.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import { tmpdir, hostname } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
6
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
7
|
+
import { acquireEngineLock, EngineAlreadyRunningError, isLockStale, } from './lock.js';
|
|
8
|
+
const LOCK_TABLE_SQL = `
|
|
9
|
+
CREATE TABLE IF NOT EXISTS engine_lock (
|
|
10
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
11
|
+
pid INTEGER NOT NULL,
|
|
12
|
+
hostname TEXT NOT NULL,
|
|
13
|
+
started_at TEXT NOT NULL,
|
|
14
|
+
last_heartbeat TEXT NOT NULL
|
|
15
|
+
)
|
|
16
|
+
`;
|
|
17
|
+
let tmpDir;
|
|
18
|
+
let dbPath;
|
|
19
|
+
let db;
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
tmpDir = realpathSync(mkdtempSync(join(tmpdir(), 'lock-test-')));
|
|
22
|
+
dbPath = join(tmpDir, 'test.db');
|
|
23
|
+
db = new DatabaseSync(dbPath);
|
|
24
|
+
db.exec('PRAGMA journal_mode = WAL');
|
|
25
|
+
db.exec(LOCK_TABLE_SQL);
|
|
26
|
+
});
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
try {
|
|
29
|
+
db.close();
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// already closed
|
|
33
|
+
}
|
|
34
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
35
|
+
});
|
|
36
|
+
describe('engine lock', () => {
|
|
37
|
+
it('takes over a stale lock when heartbeat is expired and PID is dead', () => {
|
|
38
|
+
const staleTime = new Date(Date.now() - 60_000).toISOString();
|
|
39
|
+
const deadPid = 999999;
|
|
40
|
+
// Verify the PID is actually dead on this machine
|
|
41
|
+
expect(() => process.kill(deadPid, 0)).toThrow();
|
|
42
|
+
db.prepare('INSERT INTO engine_lock (id, pid, hostname, started_at, last_heartbeat) VALUES (1, ?, ?, ?, ?)').run(deadPid, hostname(), staleTime, staleTime);
|
|
43
|
+
const lock = acquireEngineLock(db, dbPath);
|
|
44
|
+
const row = db
|
|
45
|
+
.prepare('SELECT pid FROM engine_lock WHERE id = 1')
|
|
46
|
+
.get();
|
|
47
|
+
expect(row.pid).toBe(process.pid);
|
|
48
|
+
lock.release();
|
|
49
|
+
});
|
|
50
|
+
it('throws EngineAlreadyRunningError when lock is held by a live process', () => {
|
|
51
|
+
const now = new Date().toISOString();
|
|
52
|
+
db.prepare('INSERT INTO engine_lock (id, pid, hostname, started_at, last_heartbeat) VALUES (1, ?, ?, ?, ?)').run(process.pid, hostname(), now, now);
|
|
53
|
+
expect(() => acquireEngineLock(db, dbPath)).toThrow(EngineAlreadyRunningError);
|
|
54
|
+
});
|
|
55
|
+
it('takes over when hostname differs (treated as stale regardless of PID)', () => {
|
|
56
|
+
const staleTime = new Date(Date.now() - 60_000).toISOString();
|
|
57
|
+
db.prepare('INSERT INTO engine_lock (id, pid, hostname, started_at, last_heartbeat) VALUES (1, ?, ?, ?, ?)').run(process.pid, 'other-host.example.com', staleTime, staleTime);
|
|
58
|
+
const lock = acquireEngineLock(db, dbPath);
|
|
59
|
+
const row = db
|
|
60
|
+
.prepare('SELECT hostname FROM engine_lock WHERE id = 1')
|
|
61
|
+
.get();
|
|
62
|
+
expect(row.hostname).toBe(hostname());
|
|
63
|
+
lock.release();
|
|
64
|
+
});
|
|
65
|
+
it('release deletes the lock row', () => {
|
|
66
|
+
const lock = acquireEngineLock(db, dbPath);
|
|
67
|
+
lock.release();
|
|
68
|
+
const row = db.prepare('SELECT * FROM engine_lock WHERE id = 1').get();
|
|
69
|
+
expect(row).toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
it('startHeartbeat updates last_heartbeat after 10 seconds', () => {
|
|
72
|
+
vi.useFakeTimers();
|
|
73
|
+
try {
|
|
74
|
+
const lock = acquireEngineLock(db, dbPath);
|
|
75
|
+
const before = db
|
|
76
|
+
.prepare('SELECT last_heartbeat FROM engine_lock WHERE id = 1')
|
|
77
|
+
.get().last_heartbeat;
|
|
78
|
+
lock.startHeartbeat();
|
|
79
|
+
vi.advanceTimersByTime(10_000);
|
|
80
|
+
const after = db
|
|
81
|
+
.prepare('SELECT last_heartbeat FROM engine_lock WHERE id = 1')
|
|
82
|
+
.get().last_heartbeat;
|
|
83
|
+
expect(after).not.toBe(before);
|
|
84
|
+
lock.release();
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
vi.useRealTimers();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
it('throws EngineAlreadyRunningError when SQLITE_BUSY from a concurrent write lock', () => {
|
|
91
|
+
// Hold a BEGIN IMMEDIATE transaction on a second connection so the first
|
|
92
|
+
// connection's BEGIN IMMEDIATE will return SQLITE_BUSY.
|
|
93
|
+
const db2 = new DatabaseSync(dbPath);
|
|
94
|
+
db2.exec('PRAGMA journal_mode = WAL');
|
|
95
|
+
db2.exec(LOCK_TABLE_SQL);
|
|
96
|
+
db2.exec('BEGIN IMMEDIATE');
|
|
97
|
+
try {
|
|
98
|
+
expect(() => acquireEngineLock(db, dbPath)).toThrow(EngineAlreadyRunningError);
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
db2.exec('ROLLBACK');
|
|
102
|
+
db2.close();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
it('takes over lock from different hostname when heartbeat expired', () => {
|
|
106
|
+
const staleTime = new Date(Date.now() - 60_000).toISOString();
|
|
107
|
+
db.prepare('INSERT INTO engine_lock (id, pid, hostname, started_at, last_heartbeat) VALUES (1, ?, ?, ?, ?)').run(process.pid, 'ci-runner-42.example.com', staleTime, staleTime);
|
|
108
|
+
const lock = acquireEngineLock(db, dbPath);
|
|
109
|
+
const row = db
|
|
110
|
+
.prepare('SELECT hostname, pid FROM engine_lock WHERE id = 1')
|
|
111
|
+
.get();
|
|
112
|
+
expect(row.hostname).toBe(hostname());
|
|
113
|
+
expect(row.pid).toBe(process.pid);
|
|
114
|
+
lock.release();
|
|
115
|
+
});
|
|
116
|
+
it('does NOT take over lock from different hostname when heartbeat is fresh', () => {
|
|
117
|
+
const freshTime = new Date().toISOString();
|
|
118
|
+
db.prepare('INSERT INTO engine_lock (id, pid, hostname, started_at, last_heartbeat) VALUES (1, ?, ?, ?, ?)').run(12345, 'other-host.example.com', freshTime, freshTime);
|
|
119
|
+
expect(() => acquireEngineLock(db, dbPath)).toThrow(EngineAlreadyRunningError);
|
|
120
|
+
});
|
|
121
|
+
it('isLockStale returns true when no lock exists', () => {
|
|
122
|
+
expect(isLockStale(db)).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
it('isLockStale returns false for fresh lock on same host', () => {
|
|
125
|
+
const now = new Date().toISOString();
|
|
126
|
+
db.prepare('INSERT INTO engine_lock (id, pid, hostname, started_at, last_heartbeat) VALUES (1, ?, ?, ?, ?)').run(process.pid, hostname(), now, now);
|
|
127
|
+
expect(isLockStale(db)).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
it('isLockStale returns true for expired lock with dead PID on same host', () => {
|
|
130
|
+
const staleTime = new Date(Date.now() - 60_000).toISOString();
|
|
131
|
+
const deadPid = 999999;
|
|
132
|
+
db.prepare('INSERT INTO engine_lock (id, pid, hostname, started_at, last_heartbeat) VALUES (1, ?, ?, ?, ?)').run(deadPid, hostname(), staleTime, staleTime);
|
|
133
|
+
expect(isLockStale(db)).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
//# sourceMappingURL=lock.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.test.js","sourceRoot":"","sources":["../../../src/cli/convoy/lock.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-merge.test.d.ts","sourceRoot":"","sources":["../../../src/cli/convoy/log-merge.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, realpathSync, mkdirSync, writeFileSync, readFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
5
|
+
import { mergeConvoyLogs } from '../log.js';
|
|
6
|
+
const CONVOYS_REL = '.opencastle/logs/convoys';
|
|
7
|
+
const OUTPUT_REL = '.opencastle/logs/convoy-events.ndjson';
|
|
8
|
+
function makeBase() {
|
|
9
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), 'log-merge-test-')));
|
|
10
|
+
mkdirSync(join(dir, CONVOYS_REL), { recursive: true });
|
|
11
|
+
return dir;
|
|
12
|
+
}
|
|
13
|
+
function writeConvoyFile(base, convoyId, records) {
|
|
14
|
+
const path = join(base, CONVOYS_REL, `${convoyId}.ndjson`);
|
|
15
|
+
writeFileSync(path, records.map(r => JSON.stringify(r)).join('\n') + '\n', 'utf8');
|
|
16
|
+
}
|
|
17
|
+
let tmpDir;
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
tmpDir = makeBase();
|
|
20
|
+
});
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
23
|
+
});
|
|
24
|
+
describe('mergeConvoyLogs', () => {
|
|
25
|
+
it('returns zeros when convoys directory is missing', async () => {
|
|
26
|
+
rmSync(join(tmpDir, '.opencastle'), { recursive: true, force: true });
|
|
27
|
+
const result = await mergeConvoyLogs({ basePath: tmpDir });
|
|
28
|
+
expect(result).toEqual({ merged: 0, deduplicated: 0, written: 0 });
|
|
29
|
+
});
|
|
30
|
+
it('returns zeros when convoys directory is empty', async () => {
|
|
31
|
+
const result = await mergeConvoyLogs({ basePath: tmpDir });
|
|
32
|
+
expect(result).toEqual({ merged: 0, deduplicated: 0, written: 0 });
|
|
33
|
+
});
|
|
34
|
+
it('merges records from 3 convoy files', async () => {
|
|
35
|
+
writeConvoyFile(tmpDir, 'convoy-a', [
|
|
36
|
+
{ _event_id: 1, timestamp: '2026-01-01T10:00:00.000Z', type: 'task_started' },
|
|
37
|
+
]);
|
|
38
|
+
writeConvoyFile(tmpDir, 'convoy-b', [
|
|
39
|
+
{ _event_id: 2, timestamp: '2026-01-02T10:00:00.000Z', type: 'task_done' },
|
|
40
|
+
]);
|
|
41
|
+
writeConvoyFile(tmpDir, 'convoy-c', [
|
|
42
|
+
{ _event_id: 3, timestamp: '2026-01-03T10:00:00.000Z', type: 'session' },
|
|
43
|
+
]);
|
|
44
|
+
const result = await mergeConvoyLogs({ basePath: tmpDir });
|
|
45
|
+
expect(result.merged).toBe(3);
|
|
46
|
+
expect(result.written).toBe(3);
|
|
47
|
+
});
|
|
48
|
+
it('output is sorted by timestamp ascending', async () => {
|
|
49
|
+
writeConvoyFile(tmpDir, 'convoy-z', [
|
|
50
|
+
{ _event_id: 10, timestamp: '2026-03-01T00:00:00.000Z', type: 'task_done' },
|
|
51
|
+
{ _event_id: 11, timestamp: '2026-01-01T00:00:00.000Z', type: 'task_started' },
|
|
52
|
+
]);
|
|
53
|
+
writeConvoyFile(tmpDir, 'convoy-a', [
|
|
54
|
+
{ _event_id: 12, timestamp: '2026-02-01T00:00:00.000Z', type: 'session' },
|
|
55
|
+
]);
|
|
56
|
+
const outputPath = join(tmpDir, 'merged.ndjson');
|
|
57
|
+
await mergeConvoyLogs({ basePath: tmpDir, output: outputPath });
|
|
58
|
+
const lines = readFileSync(outputPath, 'utf8').split('\n').filter(l => l.trim());
|
|
59
|
+
const timestamps = lines.map(l => JSON.parse(l).timestamp);
|
|
60
|
+
expect(timestamps).toEqual([
|
|
61
|
+
'2026-01-01T00:00:00.000Z',
|
|
62
|
+
'2026-02-01T00:00:00.000Z',
|
|
63
|
+
'2026-03-01T00:00:00.000Z',
|
|
64
|
+
]);
|
|
65
|
+
});
|
|
66
|
+
it('deduplicates records by _event_id (keeps first occurrence)', async () => {
|
|
67
|
+
writeConvoyFile(tmpDir, 'convoy-a', [
|
|
68
|
+
{ _event_id: 5, timestamp: '2026-01-01T00:00:00.000Z', type: 'task_started', note: 'first' },
|
|
69
|
+
]);
|
|
70
|
+
writeConvoyFile(tmpDir, 'convoy-b', [
|
|
71
|
+
{ _event_id: 5, timestamp: '2026-01-01T00:00:00.000Z', type: 'task_started', note: 'duplicate' },
|
|
72
|
+
{ _event_id: 6, timestamp: '2026-01-02T00:00:00.000Z', type: 'task_done' },
|
|
73
|
+
]);
|
|
74
|
+
const outputPath = join(tmpDir, 'merged.ndjson');
|
|
75
|
+
const result = await mergeConvoyLogs({ basePath: tmpDir, output: outputPath });
|
|
76
|
+
expect(result.merged).toBe(3);
|
|
77
|
+
expect(result.deduplicated).toBe(1);
|
|
78
|
+
expect(result.written).toBe(2);
|
|
79
|
+
const lines = readFileSync(outputPath, 'utf8').split('\n').filter(l => l.trim());
|
|
80
|
+
expect(lines).toHaveLength(2);
|
|
81
|
+
const first = JSON.parse(lines[0]);
|
|
82
|
+
expect(first.note).toBe('first');
|
|
83
|
+
});
|
|
84
|
+
it('filters by --since (inclusive)', async () => {
|
|
85
|
+
writeConvoyFile(tmpDir, 'convoy-a', [
|
|
86
|
+
{ _event_id: 1, timestamp: '2026-01-01T00:00:00.000Z', type: 'session' },
|
|
87
|
+
{ _event_id: 2, timestamp: '2026-02-01T00:00:00.000Z', type: 'session' },
|
|
88
|
+
{ _event_id: 3, timestamp: '2026-03-01T00:00:00.000Z', type: 'session' },
|
|
89
|
+
]);
|
|
90
|
+
const outputPath = join(tmpDir, 'merged.ndjson');
|
|
91
|
+
const result = await mergeConvoyLogs({ basePath: tmpDir, since: '2026-02-01T00:00:00.000Z', output: outputPath });
|
|
92
|
+
expect(result.written).toBe(2);
|
|
93
|
+
const lines = readFileSync(outputPath, 'utf8').split('\n').filter(l => l.trim());
|
|
94
|
+
expect(lines).toHaveLength(2);
|
|
95
|
+
});
|
|
96
|
+
it('filters by --until (inclusive)', async () => {
|
|
97
|
+
writeConvoyFile(tmpDir, 'convoy-a', [
|
|
98
|
+
{ _event_id: 1, timestamp: '2026-01-01T00:00:00.000Z', type: 'session' },
|
|
99
|
+
{ _event_id: 2, timestamp: '2026-02-01T00:00:00.000Z', type: 'session' },
|
|
100
|
+
{ _event_id: 3, timestamp: '2026-03-01T00:00:00.000Z', type: 'session' },
|
|
101
|
+
]);
|
|
102
|
+
const outputPath = join(tmpDir, 'merged.ndjson');
|
|
103
|
+
const result = await mergeConvoyLogs({ basePath: tmpDir, until: '2026-02-01T00:00:00.000Z', output: outputPath });
|
|
104
|
+
expect(result.written).toBe(2);
|
|
105
|
+
});
|
|
106
|
+
it('filters by --since and --until together', async () => {
|
|
107
|
+
writeConvoyFile(tmpDir, 'convoy-a', [
|
|
108
|
+
{ _event_id: 1, timestamp: '2026-01-01T00:00:00.000Z', type: 'session' },
|
|
109
|
+
{ _event_id: 2, timestamp: '2026-02-15T00:00:00.000Z', type: 'session' },
|
|
110
|
+
{ _event_id: 3, timestamp: '2026-03-01T00:00:00.000Z', type: 'session' },
|
|
111
|
+
]);
|
|
112
|
+
const outputPath = join(tmpDir, 'merged.ndjson');
|
|
113
|
+
const result = await mergeConvoyLogs({
|
|
114
|
+
basePath: tmpDir,
|
|
115
|
+
since: '2026-02-01T00:00:00.000Z',
|
|
116
|
+
until: '2026-02-28T23:59:59.999Z',
|
|
117
|
+
output: outputPath,
|
|
118
|
+
});
|
|
119
|
+
expect(result.written).toBe(1);
|
|
120
|
+
const lines = readFileSync(outputPath, 'utf8').split('\n').filter(l => l.trim());
|
|
121
|
+
const record = JSON.parse(lines[0]);
|
|
122
|
+
expect(record.timestamp).toBe('2026-02-15T00:00:00.000Z');
|
|
123
|
+
});
|
|
124
|
+
it('writes to default output path when --output not specified', async () => {
|
|
125
|
+
writeConvoyFile(tmpDir, 'convoy-a', [
|
|
126
|
+
{ _event_id: 1, timestamp: '2026-01-01T00:00:00.000Z', type: 'session' },
|
|
127
|
+
]);
|
|
128
|
+
await mergeConvoyLogs({ basePath: tmpDir });
|
|
129
|
+
const defaultPath = join(tmpDir, '.opencastle', 'logs', 'convoy-events.ndjson');
|
|
130
|
+
expect(existsSync(defaultPath)).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
it('returns written: 0 when all records filtered out', async () => {
|
|
133
|
+
writeConvoyFile(tmpDir, 'convoy-a', [
|
|
134
|
+
{ _event_id: 1, timestamp: '2026-01-01T00:00:00.000Z', type: 'session' },
|
|
135
|
+
]);
|
|
136
|
+
const result = await mergeConvoyLogs({ basePath: tmpDir, since: '2027-01-01T00:00:00.000Z' });
|
|
137
|
+
expect(result.written).toBe(0);
|
|
138
|
+
expect(result.merged).toBe(1);
|
|
139
|
+
});
|
|
140
|
+
it('skips malformed JSON lines gracefully', async () => {
|
|
141
|
+
const path = join(tmpDir, CONVOYS_REL, 'convoy-bad.ndjson');
|
|
142
|
+
writeFileSync(path, '{"_event_id":1,"timestamp":"2026-01-01T00:00:00.000Z","type":"session"}\nnot-valid-json\n{"_event_id":2,"timestamp":"2026-01-02T00:00:00.000Z","type":"task_done"}\n', 'utf8');
|
|
143
|
+
const result = await mergeConvoyLogs({ basePath: tmpDir });
|
|
144
|
+
expect(result.written).toBe(2);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
//# sourceMappingURL=log-merge.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-merge.test.js","sourceRoot":"","sources":["../../../src/cli/convoy/log-merge.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,10 @@ export interface MergeResult {
|
|
|
3
3
|
conflicted: boolean;
|
|
4
4
|
message: string;
|
|
5
5
|
}
|
|
6
|
+
export declare class MergeConflictError extends Error {
|
|
7
|
+
readonly conflictingFiles: string[];
|
|
8
|
+
constructor(conflictingFiles: string[], message?: string);
|
|
9
|
+
}
|
|
6
10
|
export interface MergeQueue {
|
|
7
11
|
/**
|
|
8
12
|
* Merge a single worktree's changes back onto the target branch.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../../../src/cli/convoy/merge.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,EAAE,OAAO,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,UAAU;IACzB;;;;OAIG;IACH,KAAK,CAAC,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;CAChG;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,
|
|
1
|
+
{"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../../../src/cli/convoy/merge.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,EAAE,OAAO,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,qBAAa,kBAAmB,SAAQ,KAAK;aAEzB,gBAAgB,EAAE,MAAM,EAAE;gBAA1B,gBAAgB,EAAE,MAAM,EAAE,EAC1C,OAAO,CAAC,EAAE,MAAM;CAKnB;AAED,MAAM,WAAW,UAAU;IACzB;;;;OAIG;IACH,KAAK,CAAC,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;CAChG;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CA2E7D"}
|
package/dist/cli/convoy/merge.js
CHANGED
|
@@ -2,6 +2,14 @@ import { execFile as execFileCb } from 'node:child_process';
|
|
|
2
2
|
import { resolve, join, sep } from 'node:path';
|
|
3
3
|
import { promisify } from 'node:util';
|
|
4
4
|
const execFile = promisify(execFileCb);
|
|
5
|
+
export class MergeConflictError extends Error {
|
|
6
|
+
conflictingFiles;
|
|
7
|
+
constructor(conflictingFiles, message) {
|
|
8
|
+
super(message ?? `Merge conflict in: ${conflictingFiles.join(', ')}`);
|
|
9
|
+
this.conflictingFiles = conflictingFiles;
|
|
10
|
+
this.name = 'MergeConflictError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
5
13
|
export function createMergeQueue(repoPath) {
|
|
6
14
|
const worktreesDir = resolve(join(repoPath, '.opencastle', 'worktrees'));
|
|
7
15
|
async function merge(worktreePath, worktreeBranch, targetBranch) {
|
|
@@ -51,8 +59,17 @@ export function createMergeQueue(repoPath) {
|
|
|
51
59
|
const isConflict = error.code === 1 &&
|
|
52
60
|
((error.stderr ?? '').includes('CONFLICT') || (error.stdout ?? '').includes('CONFLICT'));
|
|
53
61
|
if (isConflict) {
|
|
62
|
+
// Collect conflicting files before aborting
|
|
63
|
+
let conflictingFiles = [];
|
|
64
|
+
try {
|
|
65
|
+
const { stdout: conflictOut } = await execFile('git', [
|
|
66
|
+
'-C', repoPath, 'diff', '--name-only', '--diff-filter=U',
|
|
67
|
+
]);
|
|
68
|
+
conflictingFiles = conflictOut.split('\n').filter(Boolean);
|
|
69
|
+
}
|
|
70
|
+
catch { /* ignore — we still abort */ }
|
|
54
71
|
await execFile('git', ['-C', repoPath, 'merge', '--abort']);
|
|
55
|
-
|
|
72
|
+
throw new MergeConflictError(conflictingFiles);
|
|
56
73
|
}
|
|
57
74
|
throw err;
|
|
58
75
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge.js","sourceRoot":"","sources":["../../../src/cli/convoy/merge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAC3D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAErC,MAAM,QAAQ,GAAG,SAAS,CAAC,UAAU,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"merge.js","sourceRoot":"","sources":["../../../src/cli/convoy/merge.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { execFile as execFileCb } from 'node:child_process';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
7
|
-
import { createMergeQueue } from './merge.js';
|
|
7
|
+
import { createMergeQueue, MergeConflictError } from './merge.js';
|
|
8
8
|
const execFile = promisify(execFileCb);
|
|
9
9
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
10
10
|
let repoPath;
|
|
@@ -76,17 +76,15 @@ describe('merge - no changes', () => {
|
|
|
76
76
|
});
|
|
77
77
|
// ── merge conflict ────────────────────────────────────────────────────────────
|
|
78
78
|
describe('merge - conflict', () => {
|
|
79
|
-
it('
|
|
79
|
+
it('throws MergeConflictError and aborts when two worktrees edit the same file', async () => {
|
|
80
80
|
const worktree1 = await addWorktree(repoPath, 'worker1', featureBranch);
|
|
81
81
|
const worktree2 = await addWorktree(repoPath, 'worker2', featureBranch);
|
|
82
82
|
writeFileSync(join(worktree1, 'shared.txt'), 'content from worker 1');
|
|
83
83
|
writeFileSync(join(worktree2, 'shared.txt'), 'content from worker 2');
|
|
84
84
|
const first = await queue.merge(worktree1, 'convoy-worker1', featureBranch);
|
|
85
85
|
expect(first).toEqual({ success: true, conflicted: false, message: 'Merged successfully' });
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
expect(second.conflicted).toBe(true);
|
|
89
|
-
expect(second.message).toContain('conflict');
|
|
86
|
+
await expect(queue.merge(worktree2, 'convoy-worker2', featureBranch))
|
|
87
|
+
.rejects.toThrow(MergeConflictError);
|
|
90
88
|
});
|
|
91
89
|
it('leaves the repo in a clean state (no pending merge) after aborting a conflict', async () => {
|
|
92
90
|
const worktree1 = await addWorktree(repoPath, 'worker1', featureBranch);
|
|
@@ -94,7 +92,8 @@ describe('merge - conflict', () => {
|
|
|
94
92
|
writeFileSync(join(worktree1, 'shared.txt'), 'content from worker 1');
|
|
95
93
|
writeFileSync(join(worktree2, 'shared.txt'), 'content from worker 2');
|
|
96
94
|
await queue.merge(worktree1, 'convoy-worker1', featureBranch);
|
|
97
|
-
await queue.merge(worktree2, 'convoy-worker2', featureBranch)
|
|
95
|
+
await expect(queue.merge(worktree2, 'convoy-worker2', featureBranch))
|
|
96
|
+
.rejects.toBeInstanceOf(MergeConflictError);
|
|
98
97
|
// --untracked-files=no excludes the .opencastle/worktrees/ dir from the check;
|
|
99
98
|
// we only want to verify there is no pending merge (no staged/modified tracked files).
|
|
100
99
|
const { stdout } = await execFile('git', ['-C', repoPath, 'status', '--porcelain', '--untracked-files=no']);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge.test.js","sourceRoot":"","sources":["../../../src/cli/convoy/merge.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AACrF,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,QAAQ,IAAI,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AACrC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"merge.test.js","sourceRoot":"","sources":["../../../src/cli/convoy/merge.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Task } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Normalize a file path for partition comparison.
|
|
4
|
+
* - Rejects glob patterns (* or ?)
|
|
5
|
+
* - Strips leading ./ and /
|
|
6
|
+
* - Replaces backslashes with forward slashes
|
|
7
|
+
* - Resolves . and .. via path.normalize()
|
|
8
|
+
* - Preserves trailing slash for directories
|
|
9
|
+
*/
|
|
10
|
+
export declare function normalizePath(p: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Returns true if path a and path b overlap (exact match or prefix containment).
|
|
13
|
+
* Example: 'src/auth/' overlaps 'src/auth/service.ts' in both directions.
|
|
14
|
+
*/
|
|
15
|
+
export declare function pathsOverlap(a: string, b: string): boolean;
|
|
16
|
+
export interface PartitionConflict {
|
|
17
|
+
phase: number;
|
|
18
|
+
taskA: string;
|
|
19
|
+
taskB: string;
|
|
20
|
+
overlapping: string[];
|
|
21
|
+
}
|
|
22
|
+
export interface PartitionValidationResult {
|
|
23
|
+
valid: boolean;
|
|
24
|
+
conflicts: PartitionConflict[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Validate that tasks within the same parallel phase do not have overlapping file partitions.
|
|
28
|
+
* Tasks in different phases (sequential) are allowed to share files.
|
|
29
|
+
*/
|
|
30
|
+
export declare function validateFilePartitions(_tasks: Task[], phases: Task[][]): PartitionValidationResult;
|
|
31
|
+
/**
|
|
32
|
+
* Probe whether the filesystem is case-sensitive by creating a mixed-case temp file
|
|
33
|
+
* and checking if the lowercase path resolves to the same inode.
|
|
34
|
+
*
|
|
35
|
+
* Uses realpathSync per LES-003: on macOS, os.tmpdir() returns /var/... which is a
|
|
36
|
+
* symlink to /private/var/... — realpathSync resolves this to the canonical path.
|
|
37
|
+
*
|
|
38
|
+
* Returns true if case-sensitive (git-compatible default), false if case-insensitive.
|
|
39
|
+
*/
|
|
40
|
+
export declare function determineFsCaseSensitivity(): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Before task execution: scan each file in the task's files[] partition.
|
|
43
|
+
* If any resolved symlink target escapes the basePath directory, throw symlink_escape.
|
|
44
|
+
*/
|
|
45
|
+
export declare function scanSymlinks(files: string[], basePath: string): void;
|
|
46
|
+
/**
|
|
47
|
+
* After task execution: scan files[] in the worktree for new symlinks that escape the partition.
|
|
48
|
+
* Throws symlink_escape_post_task if any symlink target is outside worktreePath.
|
|
49
|
+
*/
|
|
50
|
+
export declare function scanNewSymlinks(worktreePath: string, files: string[]): void;
|
|
51
|
+
//# sourceMappingURL=partition.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partition.d.ts","sourceRoot":"","sources":["../../../src/cli/convoy/partition.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAIvC;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAuC/C;AAID;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAM1D;AAID,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,EAAE,CAAA;CACtB;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,OAAO,CAAA;IACd,SAAS,EAAE,iBAAiB,EAAE,CAAA;CAC/B;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,IAAI,EAAE,EACd,MAAM,EAAE,IAAI,EAAE,EAAE,GACf,yBAAyB,CAsC3B;AAID;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,IAAI,OAAO,CAmBpD;AAID;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CA2BpE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CA6B3E"}
|