@virsanghavi/axis-server 1.2.0 → 1.4.0
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/.axis/instructions/conventions.md +23 -28
- package/dist/mcp-server.mjs +389 -45
- package/package.json +1 -1
|
@@ -23,40 +23,35 @@
|
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
26
|
-
## Agent Behavioral Norms
|
|
26
|
+
## Agent Behavioral Norms (MANDATORY)
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
These norms apply to all AI coding agents. They are not optional or suggestive — execute them autonomously.
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
### Autonomous Execution
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
4. Complete when done → `complete_job` with outcome
|
|
32
|
+
- **NEVER** wait for the user to say "use Axis" or "post a job" or "lock the file." Just do it.
|
|
33
|
+
- **NEVER** pause after analysis to ask permission to proceed. Identify issues → start fixing immediately.
|
|
34
|
+
- **NEVER** leave locks or jobs dangling. Complete jobs and finalize sessions without being told.
|
|
35
|
+
- **IMMEDIATELY** call `get_project_soul` as the first action in any session.
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
- Single-line fixes, typos, config tweaks
|
|
40
|
-
- Clearly scoped changes the user asked for directly
|
|
37
|
+
### Plan Before Write
|
|
41
38
|
|
|
42
|
-
|
|
39
|
+
**No agent writes code unless it either owns a file lock OR has explicitly declined the job board for a scoped reason.**
|
|
43
40
|
|
|
44
|
-
|
|
41
|
+
On non-trivial tasks (2+ files, new features, refactors):
|
|
42
|
+
1. `post_job` — break into trackable jobs (do this IMMEDIATELY, not after being asked)
|
|
43
|
+
2. `claim_next_job` — claim before starting
|
|
44
|
+
3. `propose_file_access` — lock with a **descriptive intent**
|
|
45
|
+
4. `complete_job` — report outcome when done (this releases the lock)
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
1. **Never** call `force_unlock` on a file you didn't lock unless:
|
|
48
|
-
- The lock has been held for >25 minutes (close to TTL expiry), AND
|
|
49
|
-
- The locking agent is clearly not responding or has crashed
|
|
50
|
-
2. **Always** provide a specific reason (e.g. "Agent claude-code crashed 20 minutes ago, lock on auth.ts is blocking progress")
|
|
51
|
-
3. **Never** force-unlock to skip coordination. If another agent holds a lock, work on something else.
|
|
52
|
-
4. Prefer waiting for TTL expiry (30 min) over force-unlocking.
|
|
47
|
+
Skip jobs ONLY for: single-line fixes, typos, config tweaks.
|
|
53
48
|
|
|
54
49
|
### Lock Hygiene
|
|
55
|
-
-
|
|
56
|
-
- Release locks
|
|
57
|
-
-
|
|
58
|
-
|
|
59
|
-
###
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
50
|
+
- Descriptive `intent` when locking (not "editing file").
|
|
51
|
+
- Release locks IMMEDIATELY by completing jobs. Never hold a lock while doing unrelated work.
|
|
52
|
+
- `force_unlock` is a **last resort** — only for locks >25 min old from a crashed agent. Always give a reason.
|
|
53
|
+
|
|
54
|
+
### Session Cleanup (MANDATORY)
|
|
55
|
+
- `complete_job` after EVERY finished task — do not accumulate incomplete jobs.
|
|
56
|
+
- `update_shared_context` after meaningful steps — log decisions, not just actions.
|
|
57
|
+
- `finalize_session` when the user's request is fully complete — this is required, not optional.
|
package/dist/mcp-server.mjs
CHANGED
|
@@ -1161,23 +1161,338 @@ var RagEngine = class {
|
|
|
1161
1161
|
};
|
|
1162
1162
|
|
|
1163
1163
|
// ../../src/local/mcp-server.ts
|
|
1164
|
+
import path4 from "path";
|
|
1165
|
+
import fs4 from "fs";
|
|
1166
|
+
|
|
1167
|
+
// ../../src/local/local-search.ts
|
|
1168
|
+
import fs3 from "fs/promises";
|
|
1164
1169
|
import path3 from "path";
|
|
1165
|
-
|
|
1170
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
1171
|
+
"node_modules",
|
|
1172
|
+
".git",
|
|
1173
|
+
".next",
|
|
1174
|
+
".nuxt",
|
|
1175
|
+
".svelte-kit",
|
|
1176
|
+
"dist",
|
|
1177
|
+
"build",
|
|
1178
|
+
"out",
|
|
1179
|
+
".output",
|
|
1180
|
+
"coverage",
|
|
1181
|
+
"__pycache__",
|
|
1182
|
+
".pytest_cache",
|
|
1183
|
+
".mypy_cache",
|
|
1184
|
+
".venv",
|
|
1185
|
+
"venv",
|
|
1186
|
+
"env",
|
|
1187
|
+
".turbo",
|
|
1188
|
+
".cache",
|
|
1189
|
+
".parcel-cache",
|
|
1190
|
+
".axis",
|
|
1191
|
+
"history",
|
|
1192
|
+
".DS_Store"
|
|
1193
|
+
]);
|
|
1194
|
+
var SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1195
|
+
// Binary / media
|
|
1196
|
+
".png",
|
|
1197
|
+
".jpg",
|
|
1198
|
+
".jpeg",
|
|
1199
|
+
".gif",
|
|
1200
|
+
".webp",
|
|
1201
|
+
".ico",
|
|
1202
|
+
".svg",
|
|
1203
|
+
".mp3",
|
|
1204
|
+
".mp4",
|
|
1205
|
+
".wav",
|
|
1206
|
+
".webm",
|
|
1207
|
+
".ogg",
|
|
1208
|
+
".woff",
|
|
1209
|
+
".woff2",
|
|
1210
|
+
".ttf",
|
|
1211
|
+
".eot",
|
|
1212
|
+
".pdf",
|
|
1213
|
+
".zip",
|
|
1214
|
+
".tar",
|
|
1215
|
+
".gz",
|
|
1216
|
+
".br",
|
|
1217
|
+
// Compiled / generated
|
|
1218
|
+
".pyc",
|
|
1219
|
+
".pyo",
|
|
1220
|
+
".so",
|
|
1221
|
+
".dylib",
|
|
1222
|
+
".dll",
|
|
1223
|
+
".exe",
|
|
1224
|
+
".class",
|
|
1225
|
+
".jar",
|
|
1226
|
+
".war",
|
|
1227
|
+
".wasm",
|
|
1228
|
+
// Lock files (huge, not useful for search)
|
|
1229
|
+
".lock"
|
|
1230
|
+
]);
|
|
1231
|
+
var SKIP_FILENAMES = /* @__PURE__ */ new Set([
|
|
1232
|
+
"package-lock.json",
|
|
1233
|
+
"yarn.lock",
|
|
1234
|
+
"pnpm-lock.yaml",
|
|
1235
|
+
"Cargo.lock",
|
|
1236
|
+
"Gemfile.lock",
|
|
1237
|
+
"poetry.lock",
|
|
1238
|
+
".DS_Store",
|
|
1239
|
+
"Thumbs.db"
|
|
1240
|
+
]);
|
|
1241
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
1242
|
+
"a",
|
|
1243
|
+
"an",
|
|
1244
|
+
"the",
|
|
1245
|
+
"is",
|
|
1246
|
+
"are",
|
|
1247
|
+
"was",
|
|
1248
|
+
"were",
|
|
1249
|
+
"be",
|
|
1250
|
+
"been",
|
|
1251
|
+
"being",
|
|
1252
|
+
"have",
|
|
1253
|
+
"has",
|
|
1254
|
+
"had",
|
|
1255
|
+
"do",
|
|
1256
|
+
"does",
|
|
1257
|
+
"did",
|
|
1258
|
+
"will",
|
|
1259
|
+
"would",
|
|
1260
|
+
"could",
|
|
1261
|
+
"should",
|
|
1262
|
+
"may",
|
|
1263
|
+
"might",
|
|
1264
|
+
"shall",
|
|
1265
|
+
"can",
|
|
1266
|
+
"i",
|
|
1267
|
+
"me",
|
|
1268
|
+
"my",
|
|
1269
|
+
"we",
|
|
1270
|
+
"our",
|
|
1271
|
+
"you",
|
|
1272
|
+
"your",
|
|
1273
|
+
"he",
|
|
1274
|
+
"she",
|
|
1275
|
+
"it",
|
|
1276
|
+
"they",
|
|
1277
|
+
"them",
|
|
1278
|
+
"their",
|
|
1279
|
+
"this",
|
|
1280
|
+
"that",
|
|
1281
|
+
"these",
|
|
1282
|
+
"those",
|
|
1283
|
+
"what",
|
|
1284
|
+
"which",
|
|
1285
|
+
"who",
|
|
1286
|
+
"whom",
|
|
1287
|
+
"where",
|
|
1288
|
+
"when",
|
|
1289
|
+
"how",
|
|
1290
|
+
"why",
|
|
1291
|
+
"in",
|
|
1292
|
+
"on",
|
|
1293
|
+
"at",
|
|
1294
|
+
"to",
|
|
1295
|
+
"for",
|
|
1296
|
+
"of",
|
|
1297
|
+
"with",
|
|
1298
|
+
"by",
|
|
1299
|
+
"from",
|
|
1300
|
+
"up",
|
|
1301
|
+
"about",
|
|
1302
|
+
"into",
|
|
1303
|
+
"through",
|
|
1304
|
+
"during",
|
|
1305
|
+
"before",
|
|
1306
|
+
"after",
|
|
1307
|
+
"and",
|
|
1308
|
+
"but",
|
|
1309
|
+
"or",
|
|
1310
|
+
"nor",
|
|
1311
|
+
"not",
|
|
1312
|
+
"so",
|
|
1313
|
+
"if",
|
|
1314
|
+
"then",
|
|
1315
|
+
"all",
|
|
1316
|
+
"each",
|
|
1317
|
+
"every",
|
|
1318
|
+
"both",
|
|
1319
|
+
"few",
|
|
1320
|
+
"more",
|
|
1321
|
+
"most",
|
|
1322
|
+
"some",
|
|
1323
|
+
"any",
|
|
1324
|
+
"find",
|
|
1325
|
+
"show",
|
|
1326
|
+
"get",
|
|
1327
|
+
"look",
|
|
1328
|
+
"search",
|
|
1329
|
+
"locate",
|
|
1330
|
+
"check",
|
|
1331
|
+
"file",
|
|
1332
|
+
"files",
|
|
1333
|
+
"code",
|
|
1334
|
+
"function",
|
|
1335
|
+
"class",
|
|
1336
|
+
"method",
|
|
1337
|
+
"there",
|
|
1338
|
+
"here",
|
|
1339
|
+
"just",
|
|
1340
|
+
"also",
|
|
1341
|
+
"very",
|
|
1342
|
+
"really",
|
|
1343
|
+
"quite"
|
|
1344
|
+
]);
|
|
1345
|
+
var MAX_FILE_SIZE = 256 * 1024;
|
|
1346
|
+
var MAX_RESULTS = 20;
|
|
1347
|
+
var CONTEXT_LINES = 2;
|
|
1348
|
+
var MAX_MATCHES_PER_FILE = 6;
|
|
1349
|
+
function extractKeywords(query) {
|
|
1350
|
+
const raw = query.toLowerCase().replace(/[^\w\s\-_.]/g, " ").split(/\s+/).filter((w) => w.length >= 2 && !STOP_WORDS.has(w));
|
|
1351
|
+
return [...new Set(raw)];
|
|
1352
|
+
}
|
|
1353
|
+
async function walkDir(dir, maxDepth = 12) {
|
|
1354
|
+
const results = [];
|
|
1355
|
+
async function recurse(current, depth) {
|
|
1356
|
+
if (depth > maxDepth) return;
|
|
1357
|
+
let entries;
|
|
1358
|
+
try {
|
|
1359
|
+
entries = await fs3.readdir(current, { withFileTypes: true });
|
|
1360
|
+
} catch {
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
for (const entry of entries) {
|
|
1364
|
+
if (entry.name.startsWith(".") && entry.name !== ".env.example") {
|
|
1365
|
+
if (SKIP_DIRS.has(entry.name) || entry.isDirectory()) continue;
|
|
1366
|
+
}
|
|
1367
|
+
const fullPath = path3.join(current, entry.name);
|
|
1368
|
+
if (entry.isDirectory()) {
|
|
1369
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
1370
|
+
await recurse(fullPath, depth + 1);
|
|
1371
|
+
} else if (entry.isFile()) {
|
|
1372
|
+
if (SKIP_FILENAMES.has(entry.name)) continue;
|
|
1373
|
+
const ext = path3.extname(entry.name).toLowerCase();
|
|
1374
|
+
if (SKIP_EXTENSIONS.has(ext)) continue;
|
|
1375
|
+
try {
|
|
1376
|
+
const stat = await fs3.stat(fullPath);
|
|
1377
|
+
if (stat.size > MAX_FILE_SIZE || stat.size === 0) continue;
|
|
1378
|
+
} catch {
|
|
1379
|
+
continue;
|
|
1380
|
+
}
|
|
1381
|
+
results.push(fullPath);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
await recurse(dir, 0);
|
|
1386
|
+
return results;
|
|
1387
|
+
}
|
|
1388
|
+
async function searchFile(filePath, rootDir, keywords) {
|
|
1389
|
+
let content;
|
|
1390
|
+
try {
|
|
1391
|
+
content = await fs3.readFile(filePath, "utf-8");
|
|
1392
|
+
} catch {
|
|
1393
|
+
return null;
|
|
1394
|
+
}
|
|
1395
|
+
const contentLower = content.toLowerCase();
|
|
1396
|
+
const relativePath = path3.relative(rootDir, filePath);
|
|
1397
|
+
const matchedKeywords = keywords.filter((kw) => contentLower.includes(kw));
|
|
1398
|
+
if (matchedKeywords.length === 0) return null;
|
|
1399
|
+
const lines = content.split("\n");
|
|
1400
|
+
let score = matchedKeywords.length;
|
|
1401
|
+
const relLower = relativePath.toLowerCase();
|
|
1402
|
+
for (const kw of keywords) {
|
|
1403
|
+
if (relLower.includes(kw)) score += 2;
|
|
1404
|
+
}
|
|
1405
|
+
const matchingLineIndices = [];
|
|
1406
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1407
|
+
const lineLower = lines[i].toLowerCase();
|
|
1408
|
+
if (matchedKeywords.some((kw) => lineLower.includes(kw))) {
|
|
1409
|
+
matchingLineIndices.push(i);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
score += Math.min(matchingLineIndices.length, 20) * 0.1;
|
|
1413
|
+
const regions = [];
|
|
1414
|
+
let lastEnd = -1;
|
|
1415
|
+
for (const idx of matchingLineIndices) {
|
|
1416
|
+
if (regions.length >= MAX_MATCHES_PER_FILE) break;
|
|
1417
|
+
const start = Math.max(0, idx - CONTEXT_LINES);
|
|
1418
|
+
const end = Math.min(lines.length - 1, idx + CONTEXT_LINES);
|
|
1419
|
+
if (start <= lastEnd) continue;
|
|
1420
|
+
const regionLines = lines.slice(start, end + 1).map((line, i) => {
|
|
1421
|
+
const lineNum = start + i + 1;
|
|
1422
|
+
const marker = start + i === idx ? ">" : " ";
|
|
1423
|
+
return `${marker} ${lineNum.toString().padStart(4)}| ${line}`;
|
|
1424
|
+
}).join("\n");
|
|
1425
|
+
regions.push({ lineNumber: idx + 1, lines: regionLines });
|
|
1426
|
+
lastEnd = end;
|
|
1427
|
+
}
|
|
1428
|
+
return { filePath, relativePath, score, matchedKeywords, regions };
|
|
1429
|
+
}
|
|
1430
|
+
async function localSearch(query, rootDir) {
|
|
1431
|
+
const cwd = rootDir || process.cwd();
|
|
1432
|
+
const keywords = extractKeywords(query);
|
|
1433
|
+
if (keywords.length === 0) {
|
|
1434
|
+
return "Could not extract meaningful search terms from the query. Try being more specific (e.g. 'authentication middleware' instead of 'how does it work').";
|
|
1435
|
+
}
|
|
1436
|
+
logger.info(`[localSearch] Query: "${query}" \u2192 Keywords: [${keywords.join(", ")}] in ${cwd}`);
|
|
1437
|
+
const files = await walkDir(cwd);
|
|
1438
|
+
logger.info(`[localSearch] Scanning ${files.length} files`);
|
|
1439
|
+
const BATCH_SIZE = 50;
|
|
1440
|
+
const allMatches = [];
|
|
1441
|
+
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
1442
|
+
const batch = files.slice(i, i + BATCH_SIZE);
|
|
1443
|
+
const results = await Promise.all(
|
|
1444
|
+
batch.map((f) => searchFile(f, cwd, keywords))
|
|
1445
|
+
);
|
|
1446
|
+
for (const r of results) {
|
|
1447
|
+
if (r) allMatches.push(r);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
allMatches.sort((a, b) => b.score - a.score);
|
|
1451
|
+
const topMatches = allMatches.slice(0, MAX_RESULTS);
|
|
1452
|
+
if (topMatches.length === 0) {
|
|
1453
|
+
return `No matches found for: "${query}" (searched ${files.length} files for keywords: ${keywords.join(", ")}).
|
|
1454
|
+
Try different terms or check if the code exists in this project.`;
|
|
1455
|
+
}
|
|
1456
|
+
let output = `Found ${allMatches.length} matching file${allMatches.length === 1 ? "" : "s"} (showing top ${topMatches.length}, searched ${files.length} files)
|
|
1457
|
+
`;
|
|
1458
|
+
output += `Keywords: ${keywords.join(", ")}
|
|
1459
|
+
`;
|
|
1460
|
+
output += "\u2550".repeat(60) + "\n\n";
|
|
1461
|
+
for (const match of topMatches) {
|
|
1462
|
+
output += `\u{1F4C4} ${match.relativePath}
|
|
1463
|
+
`;
|
|
1464
|
+
output += ` Keywords matched: ${match.matchedKeywords.join(", ")} | Score: ${match.score.toFixed(1)}
|
|
1465
|
+
`;
|
|
1466
|
+
if (match.regions.length > 0) {
|
|
1467
|
+
output += " \u2500\u2500\u2500\u2500\u2500\n";
|
|
1468
|
+
for (const region of match.regions) {
|
|
1469
|
+
output += region.lines.split("\n").map((l) => ` ${l}`).join("\n") + "\n";
|
|
1470
|
+
if (region !== match.regions[match.regions.length - 1]) {
|
|
1471
|
+
output += " ...\n";
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
output += "\n";
|
|
1476
|
+
}
|
|
1477
|
+
return output;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
// ../../src/local/mcp-server.ts
|
|
1166
1481
|
if (process.env.SHARED_CONTEXT_API_URL || process.env.AXIS_API_KEY) {
|
|
1167
1482
|
logger.info("Using configuration from MCP client (mcp.json)");
|
|
1168
1483
|
} else {
|
|
1169
1484
|
const cwd = process.cwd();
|
|
1170
1485
|
const possiblePaths = [
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1486
|
+
path4.join(cwd, ".env.local"),
|
|
1487
|
+
path4.join(cwd, "..", ".env.local"),
|
|
1488
|
+
path4.join(cwd, "..", "..", ".env.local"),
|
|
1489
|
+
path4.join(cwd, "shared-context", ".env.local"),
|
|
1490
|
+
path4.join(cwd, "..", "shared-context", ".env.local")
|
|
1176
1491
|
];
|
|
1177
1492
|
let envLoaded = false;
|
|
1178
1493
|
for (const envPath of possiblePaths) {
|
|
1179
1494
|
try {
|
|
1180
|
-
if (
|
|
1495
|
+
if (fs4.existsSync(envPath)) {
|
|
1181
1496
|
logger.info(`[Fallback] Loading .env.local from: ${envPath}`);
|
|
1182
1497
|
dotenv2.config({ path: envPath });
|
|
1183
1498
|
envLoaded = true;
|
|
@@ -1241,21 +1556,21 @@ if (!useRemoteApiOnly && process.env.NEXT_PUBLIC_SUPABASE_URL && process.env.SUP
|
|
|
1241
1556
|
}
|
|
1242
1557
|
async function ensureFileSystem() {
|
|
1243
1558
|
try {
|
|
1244
|
-
const
|
|
1245
|
-
const
|
|
1559
|
+
const fs5 = await import("fs/promises");
|
|
1560
|
+
const path5 = await import("path");
|
|
1246
1561
|
const fsSync2 = await import("fs");
|
|
1247
1562
|
const cwd = process.cwd();
|
|
1248
1563
|
logger.info(`Server CWD: ${cwd}`);
|
|
1249
|
-
const historyDir =
|
|
1250
|
-
await
|
|
1564
|
+
const historyDir = path5.join(cwd, "history");
|
|
1565
|
+
await fs5.mkdir(historyDir, { recursive: true }).catch(() => {
|
|
1251
1566
|
});
|
|
1252
|
-
const axisDir =
|
|
1253
|
-
const axisInstructions =
|
|
1254
|
-
const legacyInstructions =
|
|
1567
|
+
const axisDir = path5.join(cwd, ".axis");
|
|
1568
|
+
const axisInstructions = path5.join(axisDir, "instructions");
|
|
1569
|
+
const legacyInstructions = path5.join(cwd, "agent-instructions");
|
|
1255
1570
|
if (fsSync2.existsSync(legacyInstructions) && !fsSync2.existsSync(axisDir)) {
|
|
1256
1571
|
logger.info("Using legacy agent-instructions directory");
|
|
1257
1572
|
} else {
|
|
1258
|
-
await
|
|
1573
|
+
await fs5.mkdir(axisInstructions, { recursive: true }).catch(() => {
|
|
1259
1574
|
});
|
|
1260
1575
|
const defaults = [
|
|
1261
1576
|
["context.md", `# Project Context
|
|
@@ -1286,32 +1601,35 @@ can work on the same codebase without conflicts.
|
|
|
1286
1601
|
- Tailwind CSS for styling; no raw CSS unless unavoidable.
|
|
1287
1602
|
- Functional React components; prefer server components in Next.js App Router.
|
|
1288
1603
|
|
|
1289
|
-
## Agent Behavioral Norms
|
|
1604
|
+
## Agent Behavioral Norms (MANDATORY)
|
|
1605
|
+
|
|
1606
|
+
### Autonomous Execution
|
|
1607
|
+
Agents MUST execute the full Axis workflow without being told:
|
|
1608
|
+
- NEVER wait for the user to say "use Axis" or "post a job." Just do it.
|
|
1609
|
+
- NEVER pause after analysis to ask permission. Identify issues \u2192 start fixing immediately.
|
|
1610
|
+
- NEVER leave locks or jobs dangling. Complete jobs and finalize sessions automatically.
|
|
1611
|
+
- IMMEDIATELY call get_project_soul as the first action in every session.
|
|
1290
1612
|
|
|
1291
1613
|
### Plan Before Write
|
|
1292
|
-
Every non-trivial task
|
|
1293
|
-
Skip
|
|
1614
|
+
Every non-trivial task MUST follow: post_job \u2192 claim_next_job \u2192 propose_file_access \u2192 (edit) \u2192 complete_job.
|
|
1615
|
+
Skip ONLY for single-line typo fixes.
|
|
1294
1616
|
|
|
1295
|
-
###
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
3. Provide a specific reason string.
|
|
1300
|
-
Never casually unlock files \u2014 always try propose_file_access first.
|
|
1617
|
+
### Session Cleanup (MANDATORY)
|
|
1618
|
+
- complete_job IMMEDIATELY after finishing each task.
|
|
1619
|
+
- update_shared_context after meaningful steps.
|
|
1620
|
+
- finalize_session when the user's request is fully complete \u2014 do not wait to be told.
|
|
1301
1621
|
|
|
1302
|
-
###
|
|
1303
|
-
|
|
1304
|
-
On session start, call get_project_soul or read_context to load project state.
|
|
1305
|
-
After significant progress, call update_shared_context.
|
|
1622
|
+
### Force-Unlock Policy
|
|
1623
|
+
force_unlock is a LAST RESORT \u2014 only for locks >25 min old from a crashed agent. Always give a reason.
|
|
1306
1624
|
`],
|
|
1307
1625
|
["activity.md", "# Activity Log\n\n"]
|
|
1308
1626
|
];
|
|
1309
1627
|
for (const [file, content] of defaults) {
|
|
1310
|
-
const p =
|
|
1628
|
+
const p = path5.join(axisInstructions, file);
|
|
1311
1629
|
try {
|
|
1312
|
-
await
|
|
1630
|
+
await fs5.access(p);
|
|
1313
1631
|
} catch {
|
|
1314
|
-
await
|
|
1632
|
+
await fs5.writeFile(p, content);
|
|
1315
1633
|
logger.info(`Created default context file: ${file}`);
|
|
1316
1634
|
}
|
|
1317
1635
|
}
|
|
@@ -1412,7 +1730,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1412
1730
|
},
|
|
1413
1731
|
{
|
|
1414
1732
|
name: SEARCH_CONTEXT_TOOL,
|
|
1415
|
-
description: "**
|
|
1733
|
+
description: "**CODEBASE SEARCH** \u2014 search the entire project by natural language or keywords.\n- Scans all source files on disk. Always returns results if matching code exists \u2014 no setup required.\n- Best for: 'Where is the auth logic?', 'How do I handle billing?', 'Find the database connection code'.\n- Also checks the RAG vector index if available, but the local filesystem search always works.\n- Use this INSTEAD of grep/ripgrep to stay within the Axis workflow. This tool searches file contents directly.",
|
|
1416
1734
|
inputSchema: {
|
|
1417
1735
|
type: "object",
|
|
1418
1736
|
properties: {
|
|
@@ -1458,7 +1776,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1458
1776
|
// --- Decision & Orchestration ---
|
|
1459
1777
|
{
|
|
1460
1778
|
name: "propose_file_access",
|
|
1461
|
-
description: "**CRITICAL: REQUEST FILE LOCK
|
|
1779
|
+
description: "**CRITICAL: REQUEST FILE LOCK** \u2014 call this before EVERY file edit, no exceptions.\n- Checks if another agent currently holds a lock.\n- Returns `GRANTED` if safe to proceed, or `REQUIRES_ORCHESTRATION` if someone else is editing.\n- Usage: Provide your `agentId` (e.g., 'cursor-agent'), `filePath` (absolute), and `intent` (descriptive \u2014 e.g. 'Refactor auth to use JWT', NOT 'editing file').\n- Locks expire after 30 minutes. Use `force_unlock` only as a last resort for crashed agents.",
|
|
1462
1780
|
inputSchema: {
|
|
1463
1781
|
type: "object",
|
|
1464
1782
|
properties: {
|
|
@@ -1485,18 +1803,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1485
1803
|
// --- Permanent Memory ---
|
|
1486
1804
|
{
|
|
1487
1805
|
name: "finalize_session",
|
|
1488
|
-
description: "**
|
|
1806
|
+
description: "**MANDATORY SESSION CLEANUP** \u2014 call this automatically when the user's request is fully complete.\n- Archives the current Live Notepad to a permanent session log.\n- Clears all active locks and completed jobs.\n- Resets the Live Notepad for the next session.\n- Do NOT wait for the user to say 'we are done.' When all tasks are finished, call this yourself.",
|
|
1489
1807
|
inputSchema: { type: "object", properties: {}, required: [] }
|
|
1490
1808
|
},
|
|
1491
1809
|
{
|
|
1492
1810
|
name: "get_project_soul",
|
|
1493
|
-
description: "**
|
|
1811
|
+
description: "**MANDATORY FIRST CALL**: Returns the project's goals, architecture, conventions, and active state.\n- Combines `context.md`, `conventions.md`, and other core directives into a single prompt.\n- You MUST call this as your FIRST action in every new session or task \u2014 before reading files, before responding to the user, before anything else.\n- Skipping this call means you are working without context and will make wrong decisions.",
|
|
1494
1812
|
inputSchema: { type: "object", properties: {}, required: [] }
|
|
1495
1813
|
},
|
|
1496
1814
|
// --- Job Board (Task Orchestration) ---
|
|
1497
1815
|
{
|
|
1498
1816
|
name: "post_job",
|
|
1499
|
-
description: "**CREATE TICKET**: Post a new task to the Job Board.\n-
|
|
1817
|
+
description: "**CREATE TICKET**: Post a new task to the Job Board.\n- Call this IMMEDIATELY when you receive a non-trivial task (2+ files, new features, refactors). Do not wait to be asked.\n- Break work into trackable jobs BEFORE you start coding.\n- Supports `dependencies` (list of other Job IDs that must be done first).\n- Priority: low, medium, high, critical.",
|
|
1500
1818
|
inputSchema: {
|
|
1501
1819
|
type: "object",
|
|
1502
1820
|
properties: {
|
|
@@ -1534,7 +1852,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1534
1852
|
},
|
|
1535
1853
|
{
|
|
1536
1854
|
name: "claim_next_job",
|
|
1537
|
-
description: "**
|
|
1855
|
+
description: "**CLAIM WORK**: Claim the next job from the Job Board before starting it.\n- You MUST claim a job before editing files for that job.\n- Respects priority (Critical > High > ...) and dependencies (won't assign a job if its deps aren't done).\n- Returns the Job object if successful, or 'NO_JOBS_AVAILABLE'.\n- Call this immediately after posting jobs, and again after completing each job to pick up the next one.",
|
|
1538
1856
|
inputSchema: {
|
|
1539
1857
|
type: "object",
|
|
1540
1858
|
properties: {
|
|
@@ -1545,7 +1863,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1545
1863
|
},
|
|
1546
1864
|
{
|
|
1547
1865
|
name: "complete_job",
|
|
1548
|
-
description: "**CLOSE TICKET**: Mark a job as done.\n- Requires `outcome` (what was done).\n- If you are not the assigned agent, you must provide the `completionKey
|
|
1866
|
+
description: "**CLOSE TICKET**: Mark a job as done and release file locks.\n- Call this IMMEDIATELY after finishing each job \u2014 do not accumulate completed-but-unclosed jobs.\n- Requires `outcome` (what was done).\n- If you are not the assigned agent, you must provide the `completionKey`.\n- Leaving jobs open holds locks and blocks other agents.",
|
|
1549
1867
|
inputSchema: {
|
|
1550
1868
|
type: "object",
|
|
1551
1869
|
properties: {
|
|
@@ -1622,16 +1940,42 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1622
1940
|
}
|
|
1623
1941
|
if (name === SEARCH_CONTEXT_TOOL) {
|
|
1624
1942
|
const query = String(args?.query);
|
|
1943
|
+
let ragResults = null;
|
|
1625
1944
|
try {
|
|
1626
|
-
const
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
if (ragEngine) {
|
|
1630
|
-
const results = await ragEngine.search(query);
|
|
1631
|
-
return { content: [{ type: "text", text: results.join("\n---\n") }] };
|
|
1945
|
+
const remote = await manager.searchContext(query, nerveCenter.currentProjectName);
|
|
1946
|
+
if (remote && !remote.includes("No results found") && remote.trim().length > 20) {
|
|
1947
|
+
ragResults = remote;
|
|
1632
1948
|
}
|
|
1633
|
-
|
|
1949
|
+
} catch {
|
|
1950
|
+
}
|
|
1951
|
+
if (!ragResults && ragEngine) {
|
|
1952
|
+
try {
|
|
1953
|
+
const localRag = await ragEngine.search(query);
|
|
1954
|
+
if (localRag.length > 0) {
|
|
1955
|
+
ragResults = localRag.join("\n---\n");
|
|
1956
|
+
}
|
|
1957
|
+
} catch {
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
let localResults = null;
|
|
1961
|
+
try {
|
|
1962
|
+
localResults = await localSearch(query);
|
|
1963
|
+
} catch (e) {
|
|
1964
|
+
logger.warn(`[search_codebase] Local search error: ${e}`);
|
|
1965
|
+
}
|
|
1966
|
+
const parts = [];
|
|
1967
|
+
if (ragResults) {
|
|
1968
|
+
parts.push("## Indexed Results (RAG)\n\n" + ragResults);
|
|
1969
|
+
}
|
|
1970
|
+
if (localResults && !localResults.startsWith("No matches found") && !localResults.startsWith("Could not extract")) {
|
|
1971
|
+
parts.push("## Local Codebase Search\n\n" + localResults);
|
|
1972
|
+
} else if (!ragResults) {
|
|
1973
|
+
return { content: [{ type: "text", text: localResults || "No results found." }] };
|
|
1974
|
+
}
|
|
1975
|
+
if (parts.length === 0) {
|
|
1976
|
+
return { content: [{ type: "text", text: "No results found for this query." }] };
|
|
1634
1977
|
}
|
|
1978
|
+
return { content: [{ type: "text", text: parts.join("\n\n---\n\n") }] };
|
|
1635
1979
|
}
|
|
1636
1980
|
if (name === "get_subscription_status") {
|
|
1637
1981
|
const email = String(args?.email);
|