@askexenow/exe-os 0.8.32 → 0.8.36
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/dist/bin/backfill-conversations.js +332 -348
- package/dist/bin/backfill-responses.js +72 -12
- package/dist/bin/backfill-vectors.js +72 -12
- package/dist/bin/cleanup-stale-review-tasks.js +63 -3
- package/dist/bin/cli.js +1518 -1122
- package/dist/bin/exe-agent.js +4 -4
- package/dist/bin/exe-assign.js +80 -18
- package/dist/bin/exe-boot.js +408 -89
- package/dist/bin/exe-call.js +83 -24
- package/dist/bin/exe-dispatch.js +18 -10
- package/dist/bin/exe-doctor.js +63 -3
- package/dist/bin/exe-export-behaviors.js +64 -3
- package/dist/bin/exe-forget.js +69 -4
- package/dist/bin/exe-gateway.js +121 -36
- package/dist/bin/exe-heartbeat.js +77 -13
- package/dist/bin/exe-kill.js +64 -3
- package/dist/bin/exe-launch-agent.js +162 -35
- package/dist/bin/exe-link.js +946 -0
- package/dist/bin/exe-new-employee.js +121 -36
- package/dist/bin/exe-pending-messages.js +72 -7
- package/dist/bin/exe-pending-notifications.js +63 -3
- package/dist/bin/exe-pending-reviews.js +75 -10
- package/dist/bin/exe-rename.js +1287 -0
- package/dist/bin/exe-review.js +64 -4
- package/dist/bin/exe-search.js +79 -13
- package/dist/bin/exe-session-cleanup.js +91 -26
- package/dist/bin/exe-status.js +64 -4
- package/dist/bin/exe-team.js +64 -4
- package/dist/bin/git-sweep.js +71 -4
- package/dist/bin/graph-backfill.js +64 -3
- package/dist/bin/graph-export.js +64 -3
- package/dist/bin/install.js +3 -3
- package/dist/bin/scan-tasks.js +71 -4
- package/dist/bin/setup.js +156 -38
- package/dist/bin/shard-migrate.js +64 -3
- package/dist/bin/wiki-sync.js +64 -3
- package/dist/gateway/index.js +122 -37
- package/dist/hooks/bug-report-worker.js +209 -23
- package/dist/hooks/commit-complete.js +71 -4
- package/dist/hooks/error-recall.js +79 -13
- package/dist/hooks/ingest-worker.js +129 -43
- package/dist/hooks/instructions-loaded.js +71 -4
- package/dist/hooks/notification.js +71 -4
- package/dist/hooks/post-compact.js +71 -4
- package/dist/hooks/pre-compact.js +71 -4
- package/dist/hooks/pre-tool-use.js +413 -194
- package/dist/hooks/prompt-ingest-worker.js +82 -22
- package/dist/hooks/prompt-submit.js +103 -37
- package/dist/hooks/response-ingest-worker.js +87 -22
- package/dist/hooks/session-end.js +71 -4
- package/dist/hooks/session-start.js +79 -13
- package/dist/hooks/stop.js +71 -4
- package/dist/hooks/subagent-stop.js +71 -4
- package/dist/hooks/summary-worker.js +303 -50
- package/dist/index.js +134 -46
- package/dist/lib/cloud-sync.js +209 -15
- package/dist/lib/consolidation.js +4 -4
- package/dist/lib/database.js +64 -2
- package/dist/lib/device-registry.js +70 -3
- package/dist/lib/employee-templates.js +48 -22
- package/dist/lib/employees.js +34 -1
- package/dist/lib/exe-daemon.js +136 -53
- package/dist/lib/hybrid-search.js +79 -13
- package/dist/lib/identity-templates.js +57 -6
- package/dist/lib/identity.js +3 -3
- package/dist/lib/messaging.js +22 -14
- package/dist/lib/reminders.js +3 -3
- package/dist/lib/schedules.js +63 -3
- package/dist/lib/skill-learning.js +3 -3
- package/dist/lib/status-brief.js +63 -5
- package/dist/lib/store.js +64 -3
- package/dist/lib/task-router.js +4 -2
- package/dist/lib/tasks.js +48 -21
- package/dist/lib/tmux-routing.js +47 -20
- package/dist/mcp/server.js +727 -58
- package/dist/mcp/tools/complete-reminder.js +3 -3
- package/dist/mcp/tools/create-reminder.js +3 -3
- package/dist/mcp/tools/create-task.js +151 -24
- package/dist/mcp/tools/deactivate-behavior.js +3 -3
- package/dist/mcp/tools/list-reminders.js +3 -3
- package/dist/mcp/tools/list-tasks.js +17 -8
- package/dist/mcp/tools/send-message.js +24 -16
- package/dist/mcp/tools/update-task.js +25 -16
- package/dist/runtime/index.js +112 -24
- package/dist/tui/App.js +139 -36
- package/package.json +6 -2
- package/src/commands/exe/rename.md +12 -0
|
@@ -203,12 +203,58 @@ var init_config = __esm({
|
|
|
203
203
|
}
|
|
204
204
|
});
|
|
205
205
|
|
|
206
|
-
// src/
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
206
|
+
// src/lib/db-retry.ts
|
|
207
|
+
function isBusyError(err) {
|
|
208
|
+
if (err instanceof Error) {
|
|
209
|
+
const msg = err.message.toLowerCase();
|
|
210
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
211
|
+
}
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
function delay(ms) {
|
|
215
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
216
|
+
}
|
|
217
|
+
async function retryOnBusy(fn, label) {
|
|
218
|
+
let lastError;
|
|
219
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
220
|
+
try {
|
|
221
|
+
return await fn();
|
|
222
|
+
} catch (err) {
|
|
223
|
+
lastError = err;
|
|
224
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
225
|
+
throw err;
|
|
226
|
+
}
|
|
227
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
228
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
229
|
+
process.stderr.write(
|
|
230
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
231
|
+
`
|
|
232
|
+
);
|
|
233
|
+
await delay(backoff + jitter);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
throw lastError;
|
|
237
|
+
}
|
|
238
|
+
function wrapWithRetry(client) {
|
|
239
|
+
return new Proxy(client, {
|
|
240
|
+
get(target, prop, receiver) {
|
|
241
|
+
if (prop === "execute") {
|
|
242
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
243
|
+
}
|
|
244
|
+
if (prop === "batch") {
|
|
245
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
246
|
+
}
|
|
247
|
+
return Reflect.get(target, prop, receiver);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
|
|
252
|
+
var init_db_retry = __esm({
|
|
253
|
+
"src/lib/db-retry.ts"() {
|
|
210
254
|
"use strict";
|
|
211
|
-
|
|
255
|
+
MAX_RETRIES = 3;
|
|
256
|
+
BASE_DELAY_MS = 200;
|
|
257
|
+
MAX_JITTER_MS = 300;
|
|
212
258
|
}
|
|
213
259
|
});
|
|
214
260
|
|
|
@@ -219,6 +265,7 @@ __export(database_exports, {
|
|
|
219
265
|
disposeTurso: () => disposeTurso,
|
|
220
266
|
ensureSchema: () => ensureSchema,
|
|
221
267
|
getClient: () => getClient,
|
|
268
|
+
getRawClient: () => getRawClient,
|
|
222
269
|
initDatabase: () => initDatabase,
|
|
223
270
|
initTurso: () => initTurso,
|
|
224
271
|
isInitialized: () => isInitialized
|
|
@@ -228,6 +275,7 @@ async function initDatabase(config) {
|
|
|
228
275
|
if (_client) {
|
|
229
276
|
_client.close();
|
|
230
277
|
_client = null;
|
|
278
|
+
_resilientClient = null;
|
|
231
279
|
}
|
|
232
280
|
const opts = {
|
|
233
281
|
url: `file:${config.dbPath}`
|
|
@@ -236,20 +284,27 @@ async function initDatabase(config) {
|
|
|
236
284
|
opts.encryptionKey = config.encryptionKey;
|
|
237
285
|
}
|
|
238
286
|
_client = createClient(opts);
|
|
287
|
+
_resilientClient = wrapWithRetry(_client);
|
|
239
288
|
}
|
|
240
289
|
function isInitialized() {
|
|
241
290
|
return _client !== null;
|
|
242
291
|
}
|
|
243
292
|
function getClient() {
|
|
293
|
+
if (!_resilientClient) {
|
|
294
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
295
|
+
}
|
|
296
|
+
return _resilientClient;
|
|
297
|
+
}
|
|
298
|
+
function getRawClient() {
|
|
244
299
|
if (!_client) {
|
|
245
300
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
246
301
|
}
|
|
247
302
|
return _client;
|
|
248
303
|
}
|
|
249
304
|
async function ensureSchema() {
|
|
250
|
-
const client =
|
|
305
|
+
const client = getRawClient();
|
|
251
306
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
252
|
-
await client.execute("PRAGMA busy_timeout =
|
|
307
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
253
308
|
try {
|
|
254
309
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
255
310
|
} catch {
|
|
@@ -1042,28 +1097,344 @@ async function disposeDatabase() {
|
|
|
1042
1097
|
if (_client) {
|
|
1043
1098
|
_client.close();
|
|
1044
1099
|
_client = null;
|
|
1100
|
+
_resilientClient = null;
|
|
1045
1101
|
}
|
|
1046
1102
|
}
|
|
1047
|
-
var _client, initTurso, disposeTurso;
|
|
1103
|
+
var _client, _resilientClient, initTurso, disposeTurso;
|
|
1048
1104
|
var init_database = __esm({
|
|
1049
1105
|
"src/lib/database.ts"() {
|
|
1050
1106
|
"use strict";
|
|
1107
|
+
init_db_retry();
|
|
1051
1108
|
_client = null;
|
|
1109
|
+
_resilientClient = null;
|
|
1052
1110
|
initTurso = initDatabase;
|
|
1053
1111
|
disposeTurso = disposeDatabase;
|
|
1054
1112
|
}
|
|
1055
1113
|
});
|
|
1056
1114
|
|
|
1057
|
-
// src/lib/
|
|
1058
|
-
|
|
1059
|
-
|
|
1115
|
+
// src/lib/employees.ts
|
|
1116
|
+
var employees_exports = {};
|
|
1117
|
+
__export(employees_exports, {
|
|
1118
|
+
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
1119
|
+
addEmployee: () => addEmployee,
|
|
1120
|
+
getEmployee: () => getEmployee,
|
|
1121
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
1122
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
1123
|
+
hasRole: () => hasRole,
|
|
1124
|
+
isMultiInstance: () => isMultiInstance,
|
|
1125
|
+
loadEmployees: () => loadEmployees,
|
|
1126
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
1127
|
+
registerBinSymlinks: () => registerBinSymlinks,
|
|
1128
|
+
saveEmployees: () => saveEmployees,
|
|
1129
|
+
validateEmployeeName: () => validateEmployeeName
|
|
1130
|
+
});
|
|
1131
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1132
|
+
import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync3 } from "fs";
|
|
1133
|
+
import { execSync as execSync3 } from "child_process";
|
|
1060
1134
|
import path3 from "path";
|
|
1135
|
+
function validateEmployeeName(name) {
|
|
1136
|
+
if (!name) {
|
|
1137
|
+
return { valid: false, error: "Name is required" };
|
|
1138
|
+
}
|
|
1139
|
+
if (name.length > 32) {
|
|
1140
|
+
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
1141
|
+
}
|
|
1142
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
1143
|
+
return {
|
|
1144
|
+
valid: false,
|
|
1145
|
+
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
return { valid: true };
|
|
1149
|
+
}
|
|
1150
|
+
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
1151
|
+
if (!existsSync2(employeesPath)) {
|
|
1152
|
+
return [];
|
|
1153
|
+
}
|
|
1154
|
+
const raw = await readFile2(employeesPath, "utf-8");
|
|
1155
|
+
try {
|
|
1156
|
+
return JSON.parse(raw);
|
|
1157
|
+
} catch {
|
|
1158
|
+
return [];
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
1162
|
+
await mkdir2(path3.dirname(employeesPath), { recursive: true });
|
|
1163
|
+
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
1164
|
+
}
|
|
1165
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
1166
|
+
if (!existsSync2(employeesPath)) return [];
|
|
1167
|
+
try {
|
|
1168
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
1169
|
+
} catch {
|
|
1170
|
+
return [];
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
function getEmployee(employees, name) {
|
|
1174
|
+
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
1175
|
+
}
|
|
1176
|
+
function getEmployeeByRole(employees, role) {
|
|
1177
|
+
const lower = role.toLowerCase();
|
|
1178
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
1179
|
+
}
|
|
1180
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
1181
|
+
const lower = role.toLowerCase();
|
|
1182
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
1183
|
+
}
|
|
1184
|
+
function hasRole(agentName, role) {
|
|
1185
|
+
const employees = loadEmployeesSync();
|
|
1186
|
+
const emp = getEmployee(employees, agentName);
|
|
1187
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
1188
|
+
}
|
|
1189
|
+
function isMultiInstance(agentName, employees) {
|
|
1190
|
+
const roster = employees ?? loadEmployeesSync();
|
|
1191
|
+
const emp = getEmployee(roster, agentName);
|
|
1192
|
+
if (!emp) return false;
|
|
1193
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
1194
|
+
}
|
|
1195
|
+
function addEmployee(employees, employee) {
|
|
1196
|
+
const normalized = { ...employee, name: employee.name.toLowerCase() };
|
|
1197
|
+
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
1198
|
+
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
1199
|
+
}
|
|
1200
|
+
return [...employees, normalized];
|
|
1201
|
+
}
|
|
1202
|
+
function registerBinSymlinks(name) {
|
|
1203
|
+
const created = [];
|
|
1204
|
+
const skipped = [];
|
|
1205
|
+
const errors = [];
|
|
1206
|
+
let exeBinPath;
|
|
1207
|
+
try {
|
|
1208
|
+
exeBinPath = execSync3("which exe", { encoding: "utf-8" }).trim();
|
|
1209
|
+
} catch {
|
|
1210
|
+
errors.push("Could not find 'exe' in PATH");
|
|
1211
|
+
return { created, skipped, errors };
|
|
1212
|
+
}
|
|
1213
|
+
const binDir = path3.dirname(exeBinPath);
|
|
1214
|
+
let target;
|
|
1215
|
+
try {
|
|
1216
|
+
target = readlinkSync(exeBinPath);
|
|
1217
|
+
} catch {
|
|
1218
|
+
errors.push("Could not read 'exe' symlink");
|
|
1219
|
+
return { created, skipped, errors };
|
|
1220
|
+
}
|
|
1221
|
+
for (const suffix of ["", "-opencode"]) {
|
|
1222
|
+
const linkName = `${name}${suffix}`;
|
|
1223
|
+
const linkPath = path3.join(binDir, linkName);
|
|
1224
|
+
if (existsSync2(linkPath)) {
|
|
1225
|
+
skipped.push(linkName);
|
|
1226
|
+
continue;
|
|
1227
|
+
}
|
|
1228
|
+
try {
|
|
1229
|
+
symlinkSync(target, linkPath);
|
|
1230
|
+
created.push(linkName);
|
|
1231
|
+
} catch (err) {
|
|
1232
|
+
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
return { created, skipped, errors };
|
|
1236
|
+
}
|
|
1237
|
+
var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
|
|
1238
|
+
var init_employees = __esm({
|
|
1239
|
+
"src/lib/employees.ts"() {
|
|
1240
|
+
"use strict";
|
|
1241
|
+
init_config();
|
|
1242
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
1243
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
1244
|
+
}
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1247
|
+
// src/lib/yoshi-delegation-gate.ts
|
|
1248
|
+
var yoshi_delegation_gate_exports = {};
|
|
1249
|
+
__export(yoshi_delegation_gate_exports, {
|
|
1250
|
+
GATED_AGENT: () => GATED_AGENT,
|
|
1251
|
+
GATED_ROLE: () => GATED_ROLE,
|
|
1252
|
+
GATE_BLOCK_MESSAGE: () => GATE_BLOCK_MESSAGE,
|
|
1253
|
+
classifyPath: () => classifyPath,
|
|
1254
|
+
evaluateDelegationGate: () => evaluateDelegationGate,
|
|
1255
|
+
hasRecentTomDispatch: () => hasRecentTomDispatch,
|
|
1256
|
+
hasValidScratchpadEscape: () => hasValidScratchpadEscape,
|
|
1257
|
+
scratchpadPath: () => scratchpadPath
|
|
1258
|
+
});
|
|
1259
|
+
import os2 from "os";
|
|
1260
|
+
import path4 from "path";
|
|
1261
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, statSync } from "fs";
|
|
1262
|
+
function resolveGatedAgent() {
|
|
1263
|
+
try {
|
|
1264
|
+
const employees = loadEmployeesSync();
|
|
1265
|
+
const cto = getEmployeeByRole(employees, GATED_ROLE);
|
|
1266
|
+
return cto?.name ?? "yoshi";
|
|
1267
|
+
} catch {
|
|
1268
|
+
return "yoshi";
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
function toWorkspaceRelative(filePath, cwd = process.cwd()) {
|
|
1272
|
+
const normalized = path4.normalize(filePath);
|
|
1273
|
+
if (normalized.startsWith(cwd + path4.sep)) {
|
|
1274
|
+
return normalized.slice(cwd.length + 1);
|
|
1275
|
+
}
|
|
1276
|
+
if (path4.isAbsolute(normalized)) {
|
|
1277
|
+
return path4.basename(normalized);
|
|
1278
|
+
}
|
|
1279
|
+
return normalized;
|
|
1280
|
+
}
|
|
1281
|
+
function isDockerfile(basename) {
|
|
1282
|
+
return basename === BLOCKED_DOCKERFILE_PREFIX || basename.startsWith(`${BLOCKED_DOCKERFILE_PREFIX}.`);
|
|
1283
|
+
}
|
|
1284
|
+
function classifyPath(filePath, cwd) {
|
|
1285
|
+
const rel = toWorkspaceRelative(filePath, cwd);
|
|
1286
|
+
const basename = path4.basename(rel);
|
|
1287
|
+
const ext = path4.extname(rel).toLowerCase();
|
|
1288
|
+
if (ext === ".md") {
|
|
1289
|
+
for (const prefix of EXEMPT_MD_DIR_PREFIXES) {
|
|
1290
|
+
if (rel.startsWith(prefix)) return "exempt";
|
|
1291
|
+
}
|
|
1292
|
+
if (!BLOCKED_PATH_PREFIXES.some((p) => rel.startsWith(p))) {
|
|
1293
|
+
return "exempt";
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
if (BLOCKED_FILENAMES.includes(basename)) return "code";
|
|
1297
|
+
if (isDockerfile(basename)) return "code";
|
|
1298
|
+
if (BLOCKED_EXTENSIONS.includes(ext)) return "code";
|
|
1299
|
+
if (BLOCKED_PATH_PREFIXES.some((p) => rel.startsWith(p))) return "code";
|
|
1300
|
+
return "exempt";
|
|
1301
|
+
}
|
|
1302
|
+
async function hasRecentTomDispatch(now = Date.now()) {
|
|
1303
|
+
try {
|
|
1304
|
+
const client = getClient();
|
|
1305
|
+
const engineers = loadEmployeesSync().filter(
|
|
1306
|
+
(e) => e.role.toLowerCase() === "principal engineer"
|
|
1307
|
+
);
|
|
1308
|
+
const engineerNames = engineers.map((e) => e.name);
|
|
1309
|
+
if (engineerNames.length === 0) return false;
|
|
1310
|
+
const placeholders = engineerNames.map(() => "?").join(",");
|
|
1311
|
+
const result = await client.execute({
|
|
1312
|
+
sql: `SELECT MAX(created_at) AS last_dispatch
|
|
1313
|
+
FROM tasks
|
|
1314
|
+
WHERE assigned_by = ? AND assigned_to IN (${placeholders})`,
|
|
1315
|
+
args: [GATED_AGENT, ...engineerNames]
|
|
1316
|
+
});
|
|
1317
|
+
const raw = result.rows[0]?.last_dispatch;
|
|
1318
|
+
if (raw === null || raw === void 0) return false;
|
|
1319
|
+
const parsed = Date.parse(String(raw));
|
|
1320
|
+
if (Number.isNaN(parsed)) return false;
|
|
1321
|
+
return now - parsed <= RECENT_DISPATCH_WINDOW_MS;
|
|
1322
|
+
} catch {
|
|
1323
|
+
return false;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
function scratchpadPath(sessionId, homeDir = os2.homedir()) {
|
|
1327
|
+
return path4.join(
|
|
1328
|
+
homeDir,
|
|
1329
|
+
".exe-os",
|
|
1330
|
+
"session-cache",
|
|
1331
|
+
`yoshi-scratchpad-${sessionId}.txt`
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
function hasValidScratchpadEscape(sessionId, now = Date.now(), homeDir = os2.homedir()) {
|
|
1335
|
+
const filePath = scratchpadPath(sessionId, homeDir);
|
|
1336
|
+
if (!existsSync3(filePath)) return false;
|
|
1337
|
+
try {
|
|
1338
|
+
const stat = statSync(filePath);
|
|
1339
|
+
if (now - stat.mtimeMs > SCRATCHPAD_MAX_AGE_MS) return false;
|
|
1340
|
+
const body = readFileSync4(filePath, "utf-8");
|
|
1341
|
+
return SCRATCHPAD_ESCAPE_PATTERN.test(body);
|
|
1342
|
+
} catch {
|
|
1343
|
+
return false;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
async function evaluateDelegationGate(opts) {
|
|
1347
|
+
if (opts.agentId !== GATED_AGENT) {
|
|
1348
|
+
return { outcome: "allow", reason: "agent_not_gated" };
|
|
1349
|
+
}
|
|
1350
|
+
if (!/^(Write|Edit)$/.test(opts.toolName)) {
|
|
1351
|
+
return { outcome: "allow", reason: "tool_not_gated" };
|
|
1352
|
+
}
|
|
1353
|
+
if (classifyPath(opts.filePath, opts.cwd) === "exempt") {
|
|
1354
|
+
return { outcome: "allow", reason: "exempt_path" };
|
|
1355
|
+
}
|
|
1356
|
+
if (await hasRecentTomDispatch(opts.now)) {
|
|
1357
|
+
return { outcome: "allow", reason: "recent_tom_dispatch" };
|
|
1358
|
+
}
|
|
1359
|
+
if (hasValidScratchpadEscape(opts.sessionId, opts.now, opts.homeDir)) {
|
|
1360
|
+
return { outcome: "allow", reason: "scratchpad_escape" };
|
|
1361
|
+
}
|
|
1362
|
+
return { outcome: "block", reason: "no_delegation_no_escape" };
|
|
1363
|
+
}
|
|
1364
|
+
var GATED_ROLE, GATED_AGENT, BLOCKED_PATH_PREFIXES, BLOCKED_FILENAMES, BLOCKED_DOCKERFILE_PREFIX, BLOCKED_EXTENSIONS, EXEMPT_MD_DIR_PREFIXES, RECENT_DISPATCH_WINDOW_MS, SCRATCHPAD_MAX_AGE_MS, SCRATCHPAD_ESCAPE_PATTERN, GATE_BLOCK_MESSAGE;
|
|
1365
|
+
var init_yoshi_delegation_gate = __esm({
|
|
1366
|
+
"src/lib/yoshi-delegation-gate.ts"() {
|
|
1367
|
+
"use strict";
|
|
1368
|
+
init_database();
|
|
1369
|
+
init_employees();
|
|
1370
|
+
GATED_ROLE = "CTO";
|
|
1371
|
+
GATED_AGENT = resolveGatedAgent();
|
|
1372
|
+
BLOCKED_PATH_PREFIXES = [
|
|
1373
|
+
"src/",
|
|
1374
|
+
"tests/",
|
|
1375
|
+
"scripts/",
|
|
1376
|
+
"bin/",
|
|
1377
|
+
".github/workflows/",
|
|
1378
|
+
".claude/skills/"
|
|
1379
|
+
];
|
|
1380
|
+
BLOCKED_FILENAMES = [
|
|
1381
|
+
"package.json",
|
|
1382
|
+
"tsconfig.json",
|
|
1383
|
+
"docker-compose.yml"
|
|
1384
|
+
];
|
|
1385
|
+
BLOCKED_DOCKERFILE_PREFIX = "Dockerfile";
|
|
1386
|
+
BLOCKED_EXTENSIONS = [
|
|
1387
|
+
".ts",
|
|
1388
|
+
".js",
|
|
1389
|
+
".tsx",
|
|
1390
|
+
".jsx",
|
|
1391
|
+
".rs",
|
|
1392
|
+
".py",
|
|
1393
|
+
".go",
|
|
1394
|
+
".sh",
|
|
1395
|
+
".yaml",
|
|
1396
|
+
".yml",
|
|
1397
|
+
".prisma",
|
|
1398
|
+
".sql"
|
|
1399
|
+
];
|
|
1400
|
+
EXEMPT_MD_DIR_PREFIXES = [
|
|
1401
|
+
".planning/",
|
|
1402
|
+
"exe/output/",
|
|
1403
|
+
"exe/yoshi/",
|
|
1404
|
+
"exe/tom/",
|
|
1405
|
+
"exe/",
|
|
1406
|
+
"docs/"
|
|
1407
|
+
];
|
|
1408
|
+
RECENT_DISPATCH_WINDOW_MS = 5 * 60 * 1e3;
|
|
1409
|
+
SCRATCHPAD_MAX_AGE_MS = 10 * 60 * 1e3;
|
|
1410
|
+
SCRATCHPAD_ESCAPE_PATTERN = /^TOM-CAPABLE:\s*NO\s*—\s*reason:\s*\S+/m;
|
|
1411
|
+
GATE_BLOCK_MESSAGE = [
|
|
1412
|
+
"\u26A0\uFE0F DELEGATE CHECK: this edit looks implementable by Tom.",
|
|
1413
|
+
"Dispatch create_task now OR mark TOM-CAPABLE: NO with reason.",
|
|
1414
|
+
"No code until decided."
|
|
1415
|
+
].join("\n");
|
|
1416
|
+
}
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1419
|
+
// src/types/memory.ts
|
|
1420
|
+
var EMBEDDING_DIM;
|
|
1421
|
+
var init_memory = __esm({
|
|
1422
|
+
"src/types/memory.ts"() {
|
|
1423
|
+
"use strict";
|
|
1424
|
+
EMBEDDING_DIM = 1024;
|
|
1425
|
+
}
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1428
|
+
// src/lib/keychain.ts
|
|
1429
|
+
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod } from "fs/promises";
|
|
1430
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1431
|
+
import path5 from "path";
|
|
1061
1432
|
import crypto from "crypto";
|
|
1062
1433
|
function getKeyDir() {
|
|
1063
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
1434
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path5.join(process.env.HOME ?? "/tmp", ".exe-os");
|
|
1064
1435
|
}
|
|
1065
1436
|
function getKeyPath() {
|
|
1066
|
-
return
|
|
1437
|
+
return path5.join(getKeyDir(), "master.key");
|
|
1067
1438
|
}
|
|
1068
1439
|
async function tryKeytar() {
|
|
1069
1440
|
try {
|
|
@@ -1084,11 +1455,11 @@ async function getMasterKey() {
|
|
|
1084
1455
|
}
|
|
1085
1456
|
}
|
|
1086
1457
|
const keyPath = getKeyPath();
|
|
1087
|
-
if (!
|
|
1458
|
+
if (!existsSync4(keyPath)) {
|
|
1088
1459
|
return null;
|
|
1089
1460
|
}
|
|
1090
1461
|
try {
|
|
1091
|
-
const content = await
|
|
1462
|
+
const content = await readFile3(keyPath, "utf-8");
|
|
1092
1463
|
return Buffer.from(content.trim(), "base64");
|
|
1093
1464
|
} catch {
|
|
1094
1465
|
return null;
|
|
@@ -1116,12 +1487,12 @@ __export(shard_manager_exports, {
|
|
|
1116
1487
|
listShards: () => listShards,
|
|
1117
1488
|
shardExists: () => shardExists
|
|
1118
1489
|
});
|
|
1119
|
-
import
|
|
1120
|
-
import { existsSync as
|
|
1490
|
+
import path6 from "path";
|
|
1491
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
1121
1492
|
import { createClient as createClient2 } from "@libsql/client";
|
|
1122
1493
|
function initShardManager(encryptionKey) {
|
|
1123
1494
|
_encryptionKey = encryptionKey;
|
|
1124
|
-
if (!
|
|
1495
|
+
if (!existsSync5(SHARDS_DIR)) {
|
|
1125
1496
|
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
1126
1497
|
}
|
|
1127
1498
|
_shardingEnabled = true;
|
|
@@ -1142,7 +1513,7 @@ function getShardClient(projectName) {
|
|
|
1142
1513
|
}
|
|
1143
1514
|
const cached = _shards.get(safeName);
|
|
1144
1515
|
if (cached) return cached;
|
|
1145
|
-
const dbPath =
|
|
1516
|
+
const dbPath = path6.join(SHARDS_DIR, `${safeName}.db`);
|
|
1146
1517
|
const client = createClient2({
|
|
1147
1518
|
url: `file:${dbPath}`,
|
|
1148
1519
|
encryptionKey: _encryptionKey
|
|
@@ -1152,16 +1523,16 @@ function getShardClient(projectName) {
|
|
|
1152
1523
|
}
|
|
1153
1524
|
function shardExists(projectName) {
|
|
1154
1525
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1155
|
-
return
|
|
1526
|
+
return existsSync5(path6.join(SHARDS_DIR, `${safeName}.db`));
|
|
1156
1527
|
}
|
|
1157
1528
|
function listShards() {
|
|
1158
|
-
if (!
|
|
1529
|
+
if (!existsSync5(SHARDS_DIR)) return [];
|
|
1159
1530
|
const { readdirSync: readdirSync2 } = __require("fs");
|
|
1160
1531
|
return readdirSync2(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
1161
1532
|
}
|
|
1162
1533
|
async function ensureShardSchema(client) {
|
|
1163
1534
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
1164
|
-
await client.execute("PRAGMA busy_timeout =
|
|
1535
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
1165
1536
|
try {
|
|
1166
1537
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
1167
1538
|
} catch {
|
|
@@ -1338,7 +1709,7 @@ var init_shard_manager = __esm({
|
|
|
1338
1709
|
"src/lib/shard-manager.ts"() {
|
|
1339
1710
|
"use strict";
|
|
1340
1711
|
init_config();
|
|
1341
|
-
SHARDS_DIR =
|
|
1712
|
+
SHARDS_DIR = path6.join(EXE_AI_DIR, "shards");
|
|
1342
1713
|
_shards = /* @__PURE__ */ new Map();
|
|
1343
1714
|
_encryptionKey = null;
|
|
1344
1715
|
_shardingEnabled = false;
|
|
@@ -1780,160 +2151,6 @@ var init_store = __esm({
|
|
|
1780
2151
|
}
|
|
1781
2152
|
});
|
|
1782
2153
|
|
|
1783
|
-
// src/lib/yoshi-delegation-gate.ts
|
|
1784
|
-
var yoshi_delegation_gate_exports = {};
|
|
1785
|
-
__export(yoshi_delegation_gate_exports, {
|
|
1786
|
-
GATED_AGENT: () => GATED_AGENT,
|
|
1787
|
-
GATE_BLOCK_MESSAGE: () => GATE_BLOCK_MESSAGE,
|
|
1788
|
-
classifyPath: () => classifyPath,
|
|
1789
|
-
evaluateDelegationGate: () => evaluateDelegationGate,
|
|
1790
|
-
hasRecentTomDispatch: () => hasRecentTomDispatch,
|
|
1791
|
-
hasValidScratchpadEscape: () => hasValidScratchpadEscape,
|
|
1792
|
-
scratchpadPath: () => scratchpadPath
|
|
1793
|
-
});
|
|
1794
|
-
import os2 from "os";
|
|
1795
|
-
import path5 from "path";
|
|
1796
|
-
import { existsSync as existsSync4, readFileSync as readFileSync3, statSync } from "fs";
|
|
1797
|
-
function toWorkspaceRelative(filePath, cwd = process.cwd()) {
|
|
1798
|
-
const normalized = path5.normalize(filePath);
|
|
1799
|
-
if (normalized.startsWith(cwd + path5.sep)) {
|
|
1800
|
-
return normalized.slice(cwd.length + 1);
|
|
1801
|
-
}
|
|
1802
|
-
if (path5.isAbsolute(normalized)) {
|
|
1803
|
-
return path5.basename(normalized);
|
|
1804
|
-
}
|
|
1805
|
-
return normalized;
|
|
1806
|
-
}
|
|
1807
|
-
function isDockerfile(basename) {
|
|
1808
|
-
return basename === BLOCKED_DOCKERFILE_PREFIX || basename.startsWith(`${BLOCKED_DOCKERFILE_PREFIX}.`);
|
|
1809
|
-
}
|
|
1810
|
-
function classifyPath(filePath, cwd) {
|
|
1811
|
-
const rel = toWorkspaceRelative(filePath, cwd);
|
|
1812
|
-
const basename = path5.basename(rel);
|
|
1813
|
-
const ext = path5.extname(rel).toLowerCase();
|
|
1814
|
-
if (ext === ".md") {
|
|
1815
|
-
for (const prefix of EXEMPT_MD_DIR_PREFIXES) {
|
|
1816
|
-
if (rel.startsWith(prefix)) return "exempt";
|
|
1817
|
-
}
|
|
1818
|
-
if (!BLOCKED_PATH_PREFIXES.some((p) => rel.startsWith(p))) {
|
|
1819
|
-
return "exempt";
|
|
1820
|
-
}
|
|
1821
|
-
}
|
|
1822
|
-
if (BLOCKED_FILENAMES.includes(basename)) return "code";
|
|
1823
|
-
if (isDockerfile(basename)) return "code";
|
|
1824
|
-
if (BLOCKED_EXTENSIONS.includes(ext)) return "code";
|
|
1825
|
-
if (BLOCKED_PATH_PREFIXES.some((p) => rel.startsWith(p))) return "code";
|
|
1826
|
-
return "exempt";
|
|
1827
|
-
}
|
|
1828
|
-
async function hasRecentTomDispatch(now = Date.now()) {
|
|
1829
|
-
try {
|
|
1830
|
-
const client = getClient();
|
|
1831
|
-
const result = await client.execute({
|
|
1832
|
-
sql: `SELECT MAX(created_at) AS last_dispatch
|
|
1833
|
-
FROM tasks
|
|
1834
|
-
WHERE assigned_by = ? AND assigned_to = 'tom'`,
|
|
1835
|
-
args: [GATED_AGENT]
|
|
1836
|
-
});
|
|
1837
|
-
const raw = result.rows[0]?.last_dispatch;
|
|
1838
|
-
if (raw === null || raw === void 0) return false;
|
|
1839
|
-
const parsed = Date.parse(String(raw));
|
|
1840
|
-
if (Number.isNaN(parsed)) return false;
|
|
1841
|
-
return now - parsed <= RECENT_DISPATCH_WINDOW_MS;
|
|
1842
|
-
} catch {
|
|
1843
|
-
return false;
|
|
1844
|
-
}
|
|
1845
|
-
}
|
|
1846
|
-
function scratchpadPath(sessionId, homeDir = os2.homedir()) {
|
|
1847
|
-
return path5.join(
|
|
1848
|
-
homeDir,
|
|
1849
|
-
".exe-os",
|
|
1850
|
-
"session-cache",
|
|
1851
|
-
`yoshi-scratchpad-${sessionId}.txt`
|
|
1852
|
-
);
|
|
1853
|
-
}
|
|
1854
|
-
function hasValidScratchpadEscape(sessionId, now = Date.now(), homeDir = os2.homedir()) {
|
|
1855
|
-
const filePath = scratchpadPath(sessionId, homeDir);
|
|
1856
|
-
if (!existsSync4(filePath)) return false;
|
|
1857
|
-
try {
|
|
1858
|
-
const stat = statSync(filePath);
|
|
1859
|
-
if (now - stat.mtimeMs > SCRATCHPAD_MAX_AGE_MS) return false;
|
|
1860
|
-
const body = readFileSync3(filePath, "utf-8");
|
|
1861
|
-
return SCRATCHPAD_ESCAPE_PATTERN.test(body);
|
|
1862
|
-
} catch {
|
|
1863
|
-
return false;
|
|
1864
|
-
}
|
|
1865
|
-
}
|
|
1866
|
-
async function evaluateDelegationGate(opts) {
|
|
1867
|
-
if (opts.agentId !== GATED_AGENT) {
|
|
1868
|
-
return { outcome: "allow", reason: "agent_not_gated" };
|
|
1869
|
-
}
|
|
1870
|
-
if (!/^(Write|Edit)$/.test(opts.toolName)) {
|
|
1871
|
-
return { outcome: "allow", reason: "tool_not_gated" };
|
|
1872
|
-
}
|
|
1873
|
-
if (classifyPath(opts.filePath, opts.cwd) === "exempt") {
|
|
1874
|
-
return { outcome: "allow", reason: "exempt_path" };
|
|
1875
|
-
}
|
|
1876
|
-
if (await hasRecentTomDispatch(opts.now)) {
|
|
1877
|
-
return { outcome: "allow", reason: "recent_tom_dispatch" };
|
|
1878
|
-
}
|
|
1879
|
-
if (hasValidScratchpadEscape(opts.sessionId, opts.now, opts.homeDir)) {
|
|
1880
|
-
return { outcome: "allow", reason: "scratchpad_escape" };
|
|
1881
|
-
}
|
|
1882
|
-
return { outcome: "block", reason: "no_delegation_no_escape" };
|
|
1883
|
-
}
|
|
1884
|
-
var GATED_AGENT, BLOCKED_PATH_PREFIXES, BLOCKED_FILENAMES, BLOCKED_DOCKERFILE_PREFIX, BLOCKED_EXTENSIONS, EXEMPT_MD_DIR_PREFIXES, RECENT_DISPATCH_WINDOW_MS, SCRATCHPAD_MAX_AGE_MS, SCRATCHPAD_ESCAPE_PATTERN, GATE_BLOCK_MESSAGE;
|
|
1885
|
-
var init_yoshi_delegation_gate = __esm({
|
|
1886
|
-
"src/lib/yoshi-delegation-gate.ts"() {
|
|
1887
|
-
"use strict";
|
|
1888
|
-
init_database();
|
|
1889
|
-
GATED_AGENT = "yoshi";
|
|
1890
|
-
BLOCKED_PATH_PREFIXES = [
|
|
1891
|
-
"src/",
|
|
1892
|
-
"tests/",
|
|
1893
|
-
"scripts/",
|
|
1894
|
-
"bin/",
|
|
1895
|
-
".github/workflows/",
|
|
1896
|
-
".claude/skills/"
|
|
1897
|
-
];
|
|
1898
|
-
BLOCKED_FILENAMES = [
|
|
1899
|
-
"package.json",
|
|
1900
|
-
"tsconfig.json",
|
|
1901
|
-
"docker-compose.yml"
|
|
1902
|
-
];
|
|
1903
|
-
BLOCKED_DOCKERFILE_PREFIX = "Dockerfile";
|
|
1904
|
-
BLOCKED_EXTENSIONS = [
|
|
1905
|
-
".ts",
|
|
1906
|
-
".js",
|
|
1907
|
-
".tsx",
|
|
1908
|
-
".jsx",
|
|
1909
|
-
".rs",
|
|
1910
|
-
".py",
|
|
1911
|
-
".go",
|
|
1912
|
-
".sh",
|
|
1913
|
-
".yaml",
|
|
1914
|
-
".yml",
|
|
1915
|
-
".prisma",
|
|
1916
|
-
".sql"
|
|
1917
|
-
];
|
|
1918
|
-
EXEMPT_MD_DIR_PREFIXES = [
|
|
1919
|
-
".planning/",
|
|
1920
|
-
"exe/output/",
|
|
1921
|
-
"exe/yoshi/",
|
|
1922
|
-
"exe/tom/",
|
|
1923
|
-
"exe/",
|
|
1924
|
-
"docs/"
|
|
1925
|
-
];
|
|
1926
|
-
RECENT_DISPATCH_WINDOW_MS = 5 * 60 * 1e3;
|
|
1927
|
-
SCRATCHPAD_MAX_AGE_MS = 10 * 60 * 1e3;
|
|
1928
|
-
SCRATCHPAD_ESCAPE_PATTERN = /^TOM-CAPABLE:\s*NO\s*—\s*reason:\s*\S+/m;
|
|
1929
|
-
GATE_BLOCK_MESSAGE = [
|
|
1930
|
-
"\u26A0\uFE0F DELEGATE CHECK: this edit looks implementable by Tom.",
|
|
1931
|
-
"Dispatch create_task now OR mark TOM-CAPABLE: NO with reason.",
|
|
1932
|
-
"No code until decided."
|
|
1933
|
-
].join("\n");
|
|
1934
|
-
}
|
|
1935
|
-
});
|
|
1936
|
-
|
|
1937
2154
|
// src/lib/review-gate.ts
|
|
1938
2155
|
var review_gate_exports = {};
|
|
1939
2156
|
__export(review_gate_exports, {
|
|
@@ -1943,12 +2160,12 @@ __export(review_gate_exports, {
|
|
|
1943
2160
|
checkTestCoverage: () => checkTestCoverage,
|
|
1944
2161
|
runReviewGate: () => runReviewGate
|
|
1945
2162
|
});
|
|
1946
|
-
import { execSync as
|
|
1947
|
-
import { existsSync as
|
|
2163
|
+
import { execSync as execSync4 } from "child_process";
|
|
2164
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1948
2165
|
function checkCommitsExist(taskCreatedAt) {
|
|
1949
2166
|
try {
|
|
1950
2167
|
const since = new Date(taskCreatedAt).toISOString();
|
|
1951
|
-
const output =
|
|
2168
|
+
const output = execSync4(
|
|
1952
2169
|
`git log --oneline --since="${since}" --no-merges 2>/dev/null`,
|
|
1953
2170
|
{ encoding: "utf8", timeout: 5e3 }
|
|
1954
2171
|
).trim();
|
|
@@ -1965,7 +2182,7 @@ function checkLayerBoundaries(taskCreatedAt) {
|
|
|
1965
2182
|
const violations = [];
|
|
1966
2183
|
try {
|
|
1967
2184
|
const since = new Date(taskCreatedAt).toISOString();
|
|
1968
|
-
const filesOutput =
|
|
2185
|
+
const filesOutput = execSync4(
|
|
1969
2186
|
`git log --since="${since}" --no-merges --name-only --pretty=format: 2>/dev/null`,
|
|
1970
2187
|
{ encoding: "utf8", timeout: 5e3 }
|
|
1971
2188
|
).trim();
|
|
@@ -1974,7 +2191,7 @@ function checkLayerBoundaries(taskCreatedAt) {
|
|
|
1974
2191
|
const libFiles = changedFiles.filter((f) => f.startsWith("src/lib/"));
|
|
1975
2192
|
for (const file of libFiles) {
|
|
1976
2193
|
try {
|
|
1977
|
-
const grepResult =
|
|
2194
|
+
const grepResult = execSync4(
|
|
1978
2195
|
`grep -nE "from ['"]\\.\\./(adapters|tui|runtime)/" "${file}" 2>/dev/null`,
|
|
1979
2196
|
{ encoding: "utf8", timeout: 3e3 }
|
|
1980
2197
|
).trim();
|
|
@@ -1994,7 +2211,7 @@ function checkTestCoverage(taskCreatedAt) {
|
|
|
1994
2211
|
const warnings = [];
|
|
1995
2212
|
try {
|
|
1996
2213
|
const since = new Date(taskCreatedAt).toISOString();
|
|
1997
|
-
const output =
|
|
2214
|
+
const output = execSync4(
|
|
1998
2215
|
`git log --since="${since}" --no-merges --diff-filter=A --name-only --pretty=format: 2>/dev/null`,
|
|
1999
2216
|
{ encoding: "utf8", timeout: 5e3 }
|
|
2000
2217
|
).trim();
|
|
@@ -2007,10 +2224,10 @@ function checkTestCoverage(taskCreatedAt) {
|
|
|
2007
2224
|
const testPath = file.replace(/^src\//, "tests/").replace(/\.ts$/, ".test.ts");
|
|
2008
2225
|
const baseName = file.split("/").pop()?.replace(/\.ts$/, "") ?? "";
|
|
2009
2226
|
const testDir = testPath.substring(0, testPath.lastIndexOf("/"));
|
|
2010
|
-
const hasDirectTest =
|
|
2227
|
+
const hasDirectTest = existsSync6(testPath);
|
|
2011
2228
|
let hasRelatedTest = false;
|
|
2012
2229
|
try {
|
|
2013
|
-
const related =
|
|
2230
|
+
const related = execSync4(
|
|
2014
2231
|
`find "${testDir}" -name "*${baseName}*" -name "*.test.ts" 2>/dev/null`,
|
|
2015
2232
|
{ encoding: "utf8", timeout: 3e3 }
|
|
2016
2233
|
).trim();
|
|
@@ -2061,9 +2278,9 @@ var init_review_gate = __esm({
|
|
|
2061
2278
|
});
|
|
2062
2279
|
|
|
2063
2280
|
// src/adapters/claude/hooks/pre-tool-use.ts
|
|
2064
|
-
import { existsSync as
|
|
2065
|
-
import { execSync as
|
|
2066
|
-
import
|
|
2281
|
+
import { existsSync as existsSync7, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
2282
|
+
import { execSync as execSync5 } from "child_process";
|
|
2283
|
+
import path7 from "path";
|
|
2067
2284
|
|
|
2068
2285
|
// src/adapters/claude/active-agent.ts
|
|
2069
2286
|
init_config();
|
|
@@ -2163,17 +2380,17 @@ if (!process.env.AGENT_ID) {
|
|
|
2163
2380
|
var DELEGATION_TASK_THRESHOLD = 3;
|
|
2164
2381
|
var CTO_ROLES = ["CTO", "executive"];
|
|
2165
2382
|
var TOM_SESSION_PATTERN = /^tom.*-exe/;
|
|
2166
|
-
var CACHE_DIR2 =
|
|
2383
|
+
var CACHE_DIR2 = path7.join(EXE_AI_DIR, "session-cache");
|
|
2167
2384
|
var timeout = setTimeout(() => {
|
|
2168
2385
|
process.exit(0);
|
|
2169
2386
|
}, 5e3);
|
|
2170
2387
|
timeout.unref();
|
|
2171
2388
|
function getDelegationFlagPath() {
|
|
2172
|
-
return
|
|
2389
|
+
return path7.join(CACHE_DIR2, `delegation-checkpoint-${getSessionKey()}.json`);
|
|
2173
2390
|
}
|
|
2174
2391
|
function hasDelegationFired() {
|
|
2175
2392
|
try {
|
|
2176
|
-
return
|
|
2393
|
+
return existsSync7(getDelegationFlagPath());
|
|
2177
2394
|
} catch {
|
|
2178
2395
|
return false;
|
|
2179
2396
|
}
|
|
@@ -2187,7 +2404,7 @@ function markDelegationFired() {
|
|
|
2187
2404
|
}
|
|
2188
2405
|
function countTomSessions() {
|
|
2189
2406
|
try {
|
|
2190
|
-
const output =
|
|
2407
|
+
const output = execSync5("tmux list-sessions 2>/dev/null", {
|
|
2191
2408
|
encoding: "utf8",
|
|
2192
2409
|
timeout: 2e3
|
|
2193
2410
|
});
|
|
@@ -2222,7 +2439,8 @@ This write was prevented. Do NOT retry.`
|
|
|
2222
2439
|
process.exit(0);
|
|
2223
2440
|
}
|
|
2224
2441
|
}
|
|
2225
|
-
|
|
2442
|
+
const { GATED_AGENT: gatedAgent } = await Promise.resolve().then(() => (init_yoshi_delegation_gate(), yoshi_delegation_gate_exports));
|
|
2443
|
+
if (agent.agentId === gatedAgent && /^(Write|Edit)$/.test(data.tool_name)) {
|
|
2226
2444
|
const filePath = data.tool_input?.file_path ?? "";
|
|
2227
2445
|
if (filePath) {
|
|
2228
2446
|
try {
|
|
@@ -2284,7 +2502,8 @@ This write was prevented. Do NOT retry.`
|
|
|
2284
2502
|
});
|
|
2285
2503
|
if (taskRow.rows.length > 0) {
|
|
2286
2504
|
const task = taskRow.rows[0];
|
|
2287
|
-
|
|
2505
|
+
const { hasRole: hasEngineerRole } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
2506
|
+
if (hasEngineerRole(task.assigned_to, "Principal Engineer")) {
|
|
2288
2507
|
const { runReviewGate: runReviewGate2 } = await Promise.resolve().then(() => (init_review_gate(), review_gate_exports));
|
|
2289
2508
|
const gate = runReviewGate2({
|
|
2290
2509
|
taskCreatedAt: task.created_at,
|