@askexenow/exe-os 0.8.33 → 0.8.37
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 +341 -349
- package/dist/bin/backfill-responses.js +81 -13
- package/dist/bin/backfill-vectors.js +72 -12
- package/dist/bin/cleanup-stale-review-tasks.js +63 -3
- package/dist/bin/cli.js +1737 -1117
- package/dist/bin/exe-assign.js +89 -19
- package/dist/bin/exe-boot.js +951 -101
- package/dist/bin/exe-call.js +61 -2
- package/dist/bin/exe-dispatch.js +61 -13
- package/dist/bin/exe-doctor.js +63 -3
- package/dist/bin/exe-export-behaviors.js +71 -3
- package/dist/bin/exe-forget.js +69 -4
- package/dist/bin/exe-gateway.js +178 -45
- package/dist/bin/exe-heartbeat.js +79 -14
- package/dist/bin/exe-kill.js +71 -3
- package/dist/bin/exe-launch-agent.js +148 -14
- package/dist/bin/exe-link.js +1437 -0
- package/dist/bin/exe-new-employee.js +98 -13
- package/dist/bin/exe-pending-messages.js +74 -8
- package/dist/bin/exe-pending-notifications.js +63 -3
- package/dist/bin/exe-pending-reviews.js +77 -11
- package/dist/bin/exe-rename.js +1287 -0
- package/dist/bin/exe-review.js +73 -5
- package/dist/bin/exe-search.js +88 -14
- package/dist/bin/exe-session-cleanup.js +102 -28
- package/dist/bin/exe-status.js +64 -4
- package/dist/bin/exe-team.js +64 -4
- package/dist/bin/git-sweep.js +80 -5
- package/dist/bin/graph-backfill.js +71 -3
- package/dist/bin/graph-export.js +71 -3
- package/dist/bin/install.js +38 -8
- package/dist/bin/scan-tasks.js +80 -5
- package/dist/bin/setup.js +128 -10
- package/dist/bin/shard-migrate.js +71 -3
- package/dist/bin/wiki-sync.js +71 -3
- package/dist/gateway/index.js +179 -46
- package/dist/hooks/bug-report-worker.js +254 -28
- package/dist/hooks/commit-complete.js +80 -5
- package/dist/hooks/error-recall.js +89 -15
- package/dist/hooks/exe-heartbeat-hook.js +1 -1
- package/dist/hooks/ingest-worker.js +185 -51
- package/dist/hooks/ingest.js +1 -1
- package/dist/hooks/instructions-loaded.js +81 -6
- package/dist/hooks/notification.js +81 -6
- package/dist/hooks/post-compact.js +81 -6
- package/dist/hooks/pre-compact.js +81 -6
- package/dist/hooks/pre-tool-use.js +423 -196
- package/dist/hooks/prompt-ingest-worker.js +91 -23
- package/dist/hooks/prompt-submit.js +159 -45
- package/dist/hooks/response-ingest-worker.js +96 -23
- package/dist/hooks/session-end.js +81 -6
- package/dist/hooks/session-start.js +89 -15
- package/dist/hooks/stop.js +81 -6
- package/dist/hooks/subagent-stop.js +81 -6
- package/dist/hooks/summary-worker.js +807 -55
- package/dist/index.js +198 -60
- package/dist/lib/cloud-sync.js +703 -18
- 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 +26 -0
- package/dist/lib/employees.js +34 -1
- package/dist/lib/exe-daemon.js +207 -74
- package/dist/lib/hybrid-search.js +88 -14
- package/dist/lib/identity-templates.js +51 -0
- package/dist/lib/identity.js +3 -3
- package/dist/lib/messaging.js +65 -17
- 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 +73 -4
- package/dist/lib/task-router.js +4 -2
- package/dist/lib/tasks.js +95 -28
- package/dist/lib/tmux-routing.js +92 -23
- package/dist/mcp/server.js +800 -74
- 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 +198 -31
- package/dist/mcp/tools/deactivate-behavior.js +4 -4
- package/dist/mcp/tools/list-reminders.js +3 -3
- package/dist/mcp/tools/list-tasks.js +19 -9
- package/dist/mcp/tools/send-message.js +69 -21
- package/dist/mcp/tools/update-task.js +28 -18
- package/dist/runtime/index.js +166 -28
- package/dist/tui/App.js +193 -40
- package/package.json +7 -3
- package/src/commands/exe/afk.md +116 -0
- 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;
|
|
@@ -1422,7 +1793,8 @@ async function writeMemory(record) {
|
|
|
1422
1793
|
has_error: record.has_error ? 1 : 0,
|
|
1423
1794
|
raw_text: record.raw_text,
|
|
1424
1795
|
vector: record.vector,
|
|
1425
|
-
version:
|
|
1796
|
+
version: 0,
|
|
1797
|
+
// Placeholder — assigned atomically at flush time
|
|
1426
1798
|
task_id: record.task_id ?? null,
|
|
1427
1799
|
importance: record.importance ?? 5,
|
|
1428
1800
|
status: record.status ?? "active",
|
|
@@ -1456,6 +1828,13 @@ async function flushBatch() {
|
|
|
1456
1828
|
_flushing = true;
|
|
1457
1829
|
try {
|
|
1458
1830
|
const batch = _pendingRecords.slice(0);
|
|
1831
|
+
const client = getClient();
|
|
1832
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1833
|
+
let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1834
|
+
for (const row of batch) {
|
|
1835
|
+
row.version = baseVersion++;
|
|
1836
|
+
}
|
|
1837
|
+
_nextVersion = baseVersion;
|
|
1459
1838
|
const buildStmt = (row) => {
|
|
1460
1839
|
const hasVector = row.vector !== null;
|
|
1461
1840
|
const taskId = row.task_id ?? null;
|
|
@@ -1780,160 +2159,6 @@ var init_store = __esm({
|
|
|
1780
2159
|
}
|
|
1781
2160
|
});
|
|
1782
2161
|
|
|
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
2162
|
// src/lib/review-gate.ts
|
|
1938
2163
|
var review_gate_exports = {};
|
|
1939
2164
|
__export(review_gate_exports, {
|
|
@@ -1943,12 +2168,12 @@ __export(review_gate_exports, {
|
|
|
1943
2168
|
checkTestCoverage: () => checkTestCoverage,
|
|
1944
2169
|
runReviewGate: () => runReviewGate
|
|
1945
2170
|
});
|
|
1946
|
-
import { execSync as
|
|
1947
|
-
import { existsSync as
|
|
2171
|
+
import { execSync as execSync4 } from "child_process";
|
|
2172
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1948
2173
|
function checkCommitsExist(taskCreatedAt) {
|
|
1949
2174
|
try {
|
|
1950
2175
|
const since = new Date(taskCreatedAt).toISOString();
|
|
1951
|
-
const output =
|
|
2176
|
+
const output = execSync4(
|
|
1952
2177
|
`git log --oneline --since="${since}" --no-merges 2>/dev/null`,
|
|
1953
2178
|
{ encoding: "utf8", timeout: 5e3 }
|
|
1954
2179
|
).trim();
|
|
@@ -1965,7 +2190,7 @@ function checkLayerBoundaries(taskCreatedAt) {
|
|
|
1965
2190
|
const violations = [];
|
|
1966
2191
|
try {
|
|
1967
2192
|
const since = new Date(taskCreatedAt).toISOString();
|
|
1968
|
-
const filesOutput =
|
|
2193
|
+
const filesOutput = execSync4(
|
|
1969
2194
|
`git log --since="${since}" --no-merges --name-only --pretty=format: 2>/dev/null`,
|
|
1970
2195
|
{ encoding: "utf8", timeout: 5e3 }
|
|
1971
2196
|
).trim();
|
|
@@ -1974,7 +2199,7 @@ function checkLayerBoundaries(taskCreatedAt) {
|
|
|
1974
2199
|
const libFiles = changedFiles.filter((f) => f.startsWith("src/lib/"));
|
|
1975
2200
|
for (const file of libFiles) {
|
|
1976
2201
|
try {
|
|
1977
|
-
const grepResult =
|
|
2202
|
+
const grepResult = execSync4(
|
|
1978
2203
|
`grep -nE "from ['"]\\.\\./(adapters|tui|runtime)/" "${file}" 2>/dev/null`,
|
|
1979
2204
|
{ encoding: "utf8", timeout: 3e3 }
|
|
1980
2205
|
).trim();
|
|
@@ -1994,7 +2219,7 @@ function checkTestCoverage(taskCreatedAt) {
|
|
|
1994
2219
|
const warnings = [];
|
|
1995
2220
|
try {
|
|
1996
2221
|
const since = new Date(taskCreatedAt).toISOString();
|
|
1997
|
-
const output =
|
|
2222
|
+
const output = execSync4(
|
|
1998
2223
|
`git log --since="${since}" --no-merges --diff-filter=A --name-only --pretty=format: 2>/dev/null`,
|
|
1999
2224
|
{ encoding: "utf8", timeout: 5e3 }
|
|
2000
2225
|
).trim();
|
|
@@ -2007,10 +2232,10 @@ function checkTestCoverage(taskCreatedAt) {
|
|
|
2007
2232
|
const testPath = file.replace(/^src\//, "tests/").replace(/\.ts$/, ".test.ts");
|
|
2008
2233
|
const baseName = file.split("/").pop()?.replace(/\.ts$/, "") ?? "";
|
|
2009
2234
|
const testDir = testPath.substring(0, testPath.lastIndexOf("/"));
|
|
2010
|
-
const hasDirectTest =
|
|
2235
|
+
const hasDirectTest = existsSync6(testPath);
|
|
2011
2236
|
let hasRelatedTest = false;
|
|
2012
2237
|
try {
|
|
2013
|
-
const related =
|
|
2238
|
+
const related = execSync4(
|
|
2014
2239
|
`find "${testDir}" -name "*${baseName}*" -name "*.test.ts" 2>/dev/null`,
|
|
2015
2240
|
{ encoding: "utf8", timeout: 3e3 }
|
|
2016
2241
|
).trim();
|
|
@@ -2061,9 +2286,9 @@ var init_review_gate = __esm({
|
|
|
2061
2286
|
});
|
|
2062
2287
|
|
|
2063
2288
|
// src/adapters/claude/hooks/pre-tool-use.ts
|
|
2064
|
-
import { existsSync as
|
|
2065
|
-
import { execSync as
|
|
2066
|
-
import
|
|
2289
|
+
import { existsSync as existsSync7, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
2290
|
+
import { execSync as execSync5 } from "child_process";
|
|
2291
|
+
import path7 from "path";
|
|
2067
2292
|
|
|
2068
2293
|
// src/adapters/claude/active-agent.ts
|
|
2069
2294
|
init_config();
|
|
@@ -2139,7 +2364,7 @@ function getActiveAgent() {
|
|
|
2139
2364
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
2140
2365
|
{ encoding: "utf8", timeout: 2e3 }
|
|
2141
2366
|
).trim();
|
|
2142
|
-
const empMatch = sessionName.match(/^(
|
|
2367
|
+
const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
|
|
2143
2368
|
if (empMatch && empMatch[1] !== "exe") {
|
|
2144
2369
|
return { agentId: empMatch[1], agentRole: "employee" };
|
|
2145
2370
|
}
|
|
@@ -2163,17 +2388,17 @@ if (!process.env.AGENT_ID) {
|
|
|
2163
2388
|
var DELEGATION_TASK_THRESHOLD = 3;
|
|
2164
2389
|
var CTO_ROLES = ["CTO", "executive"];
|
|
2165
2390
|
var TOM_SESSION_PATTERN = /^tom.*-exe/;
|
|
2166
|
-
var CACHE_DIR2 =
|
|
2391
|
+
var CACHE_DIR2 = path7.join(EXE_AI_DIR, "session-cache");
|
|
2167
2392
|
var timeout = setTimeout(() => {
|
|
2168
2393
|
process.exit(0);
|
|
2169
2394
|
}, 5e3);
|
|
2170
2395
|
timeout.unref();
|
|
2171
2396
|
function getDelegationFlagPath() {
|
|
2172
|
-
return
|
|
2397
|
+
return path7.join(CACHE_DIR2, `delegation-checkpoint-${getSessionKey()}.json`);
|
|
2173
2398
|
}
|
|
2174
2399
|
function hasDelegationFired() {
|
|
2175
2400
|
try {
|
|
2176
|
-
return
|
|
2401
|
+
return existsSync7(getDelegationFlagPath());
|
|
2177
2402
|
} catch {
|
|
2178
2403
|
return false;
|
|
2179
2404
|
}
|
|
@@ -2187,7 +2412,7 @@ function markDelegationFired() {
|
|
|
2187
2412
|
}
|
|
2188
2413
|
function countTomSessions() {
|
|
2189
2414
|
try {
|
|
2190
|
-
const output =
|
|
2415
|
+
const output = execSync5("tmux list-sessions 2>/dev/null", {
|
|
2191
2416
|
encoding: "utf8",
|
|
2192
2417
|
timeout: 2e3
|
|
2193
2418
|
});
|
|
@@ -2222,7 +2447,8 @@ This write was prevented. Do NOT retry.`
|
|
|
2222
2447
|
process.exit(0);
|
|
2223
2448
|
}
|
|
2224
2449
|
}
|
|
2225
|
-
|
|
2450
|
+
const { GATED_AGENT: gatedAgent } = await Promise.resolve().then(() => (init_yoshi_delegation_gate(), yoshi_delegation_gate_exports));
|
|
2451
|
+
if (agent.agentId === gatedAgent && /^(Write|Edit)$/.test(data.tool_name)) {
|
|
2226
2452
|
const filePath = data.tool_input?.file_path ?? "";
|
|
2227
2453
|
if (filePath) {
|
|
2228
2454
|
try {
|
|
@@ -2284,7 +2510,8 @@ This write was prevented. Do NOT retry.`
|
|
|
2284
2510
|
});
|
|
2285
2511
|
if (taskRow.rows.length > 0) {
|
|
2286
2512
|
const task = taskRow.rows[0];
|
|
2287
|
-
|
|
2513
|
+
const { hasRole: hasEngineerRole } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
2514
|
+
if (hasEngineerRole(task.assigned_to, "Principal Engineer")) {
|
|
2288
2515
|
const { runReviewGate: runReviewGate2 } = await Promise.resolve().then(() => (init_review_gate(), review_gate_exports));
|
|
2289
2516
|
const gate = runReviewGate2({
|
|
2290
2517
|
taskCreatedAt: task.created_at,
|