@virsanghavi/axis-server 1.9.1 → 1.10.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/context.md +1 -1
- package/dist/mcp-server.mjs +251 -77
- package/package.json +35 -31
|
@@ -21,7 +21,7 @@ The core value proposition: **agents that coordinate like a team, not individual
|
|
|
21
21
|
2. **File Locking**: Atomic, per-file locks with 30-minute TTL. Agents call `propose_file_access` before editing. Prevents merge conflicts.
|
|
22
22
|
3. **Live Notepad**: Real-time shared memory. Agents log progress so others know what's happening. Cleared on `finalize_session`.
|
|
23
23
|
4. **Context Mirroring**: `get_project_soul` returns this file + conventions to ground agents in project reality.
|
|
24
|
-
5. **RAG Search**: `search_codebase` and `search_docs` for semantic search over indexed files and documentation.
|
|
24
|
+
5. **RAG Search**: `search_codebase` and `search_docs` for semantic search over indexed files and documentation. **Agents MUST use `search_codebase` before writing new code** — search for similar patterns (e.g. "authentication flow", "API route handler") before creating files. Call `index_file` after creating or significantly changing files.
|
|
25
25
|
6. **Session Management**: `finalize_session` archives the notepad, clears locks, resets for new work.
|
|
26
26
|
7. **Billing**: Stripe-based Pro tier ($25/mo) with API key management, usage tracking, and retention flow.
|
|
27
27
|
|
package/dist/mcp-server.mjs
CHANGED
|
@@ -1119,8 +1119,10 @@ ${notepad}`;
|
|
|
1119
1119
|
}
|
|
1120
1120
|
async getProjectSoul() {
|
|
1121
1121
|
let soul = "## Project Soul\n";
|
|
1122
|
+
let context = "";
|
|
1123
|
+
let couldNotRead = false;
|
|
1122
1124
|
try {
|
|
1123
|
-
|
|
1125
|
+
context = await this.contextManager.readFile("context.md");
|
|
1124
1126
|
soul += `
|
|
1125
1127
|
### Context
|
|
1126
1128
|
${context}`;
|
|
@@ -1129,8 +1131,23 @@ ${context}`;
|
|
|
1129
1131
|
### Conventions
|
|
1130
1132
|
${conventions}`;
|
|
1131
1133
|
} catch (_e) {
|
|
1134
|
+
couldNotRead = true;
|
|
1132
1135
|
soul += "\n(Could not read local context files)";
|
|
1133
1136
|
}
|
|
1137
|
+
const unfilled = couldNotRead || /Describe your project|<!-- Describe|This project uses Axis/i.test(context) || context.trim().length < 450 && /# Project Context/i.test(context);
|
|
1138
|
+
if (unfilled) {
|
|
1139
|
+
soul += `
|
|
1140
|
+
|
|
1141
|
+
### MANDATORY: Project soul is not yet filled
|
|
1142
|
+
You MUST fill the project soul before doing any other work. Do not proceed with other tasks until it is filled.
|
|
1143
|
+
|
|
1144
|
+
**How to fill the project soul:**
|
|
1145
|
+
1. Use \`search_codebase\` to explore the repo and infer what this project is about.
|
|
1146
|
+
2. Call \`update_project_soul\` with \`context\` (project overview, architecture, core features, deployment) and \`conventions\` (language standards, styling, code patterns, agent norms).
|
|
1147
|
+
3. If the codebase is empty or has nothing to search: ask the user what the project is about, then call \`update_project_soul\` with their answer.
|
|
1148
|
+
|
|
1149
|
+
Do NOT skip this. Do NOT proceed with other work until the soul is populated. Working without a filled soul means every decision you make lacks context.`;
|
|
1150
|
+
}
|
|
1134
1151
|
return soul;
|
|
1135
1152
|
}
|
|
1136
1153
|
// --- Billing & Usage ---
|
|
@@ -1270,6 +1287,7 @@ import fs4 from "fs";
|
|
|
1270
1287
|
import fs3 from "fs/promises";
|
|
1271
1288
|
import fsSync2 from "fs";
|
|
1272
1289
|
import path3 from "path";
|
|
1290
|
+
import { spawnSync } from "child_process";
|
|
1273
1291
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
1274
1292
|
"node_modules",
|
|
1275
1293
|
".git",
|
|
@@ -1430,14 +1448,7 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
|
1430
1448
|
"also",
|
|
1431
1449
|
"very",
|
|
1432
1450
|
"really",
|
|
1433
|
-
"quite"
|
|
1434
|
-
"show",
|
|
1435
|
-
"look",
|
|
1436
|
-
"locate",
|
|
1437
|
-
"using",
|
|
1438
|
-
"used",
|
|
1439
|
-
"need",
|
|
1440
|
-
"want"
|
|
1451
|
+
"quite"
|
|
1441
1452
|
]);
|
|
1442
1453
|
var MAX_FILE_SIZE = 256 * 1024;
|
|
1443
1454
|
var MAX_RESULTS = 20;
|
|
@@ -1446,7 +1457,7 @@ var MAX_MATCHES_PER_FILE = 6;
|
|
|
1446
1457
|
function extractKeywords(query) {
|
|
1447
1458
|
const words = query.toLowerCase().replace(/[^\w\s\-_.]/g, " ").split(/\s+/).filter((w) => w.length >= 2);
|
|
1448
1459
|
const filtered = words.filter((w) => !STOP_WORDS.has(w));
|
|
1449
|
-
const result = filtered.length > 0 ? filtered : words
|
|
1460
|
+
const result = filtered.length > 0 ? filtered : words;
|
|
1450
1461
|
return [...new Set(result)];
|
|
1451
1462
|
}
|
|
1452
1463
|
var PROJECT_ROOT_MARKERS = [
|
|
@@ -1464,7 +1475,7 @@ var PROJECT_ROOT_MARKERS = [
|
|
|
1464
1475
|
"AGENTS.md"
|
|
1465
1476
|
];
|
|
1466
1477
|
function detectProjectRoot(startDir) {
|
|
1467
|
-
let current = startDir;
|
|
1478
|
+
let current = path3.resolve(startDir);
|
|
1468
1479
|
const root = path3.parse(current).root;
|
|
1469
1480
|
while (current !== root) {
|
|
1470
1481
|
for (const marker of PROJECT_ROOT_MARKERS) {
|
|
@@ -1527,8 +1538,7 @@ async function searchFile(filePath, rootDir, keywords) {
|
|
|
1527
1538
|
const matchedKeywords = keywords.filter((kw) => contentLower.includes(kw));
|
|
1528
1539
|
if (matchedKeywords.length === 0) return null;
|
|
1529
1540
|
const coverage = matchedKeywords.length / keywords.length;
|
|
1530
|
-
if (
|
|
1531
|
-
if (keywords.length === 2 && matchedKeywords.length < 1) return null;
|
|
1541
|
+
if (coverage < 0.2) return null;
|
|
1532
1542
|
const lines = content.split("\n");
|
|
1533
1543
|
let score = coverage * coverage * matchedKeywords.length;
|
|
1534
1544
|
const relLower = relativePath.toLowerCase();
|
|
@@ -1580,58 +1590,200 @@ async function searchFile(filePath, rootDir, keywords) {
|
|
|
1580
1590
|
}
|
|
1581
1591
|
return { filePath, relativePath, score, matchedKeywords, regions };
|
|
1582
1592
|
}
|
|
1593
|
+
function runRipgrep(pattern, cwd) {
|
|
1594
|
+
const p = (pattern || "").trim();
|
|
1595
|
+
if (!p || p.length > 200) return [];
|
|
1596
|
+
const result = spawnSync("rg", [
|
|
1597
|
+
"--line-number",
|
|
1598
|
+
"--no-heading",
|
|
1599
|
+
"--color",
|
|
1600
|
+
"never",
|
|
1601
|
+
"--max-count",
|
|
1602
|
+
"3",
|
|
1603
|
+
// Max 3 matches per file per pattern
|
|
1604
|
+
"-C",
|
|
1605
|
+
"1",
|
|
1606
|
+
// 1 line context
|
|
1607
|
+
"--ignore-case",
|
|
1608
|
+
"--max-filesize",
|
|
1609
|
+
"256K",
|
|
1610
|
+
"-F",
|
|
1611
|
+
p,
|
|
1612
|
+
// Fixed string (literal) — no regex escaping needed
|
|
1613
|
+
"."
|
|
1614
|
+
], {
|
|
1615
|
+
cwd,
|
|
1616
|
+
encoding: "utf-8",
|
|
1617
|
+
timeout: 8e3,
|
|
1618
|
+
maxBuffer: 4 * 1024 * 1024
|
|
1619
|
+
});
|
|
1620
|
+
if (result.error || result.status !== 0) {
|
|
1621
|
+
return [];
|
|
1622
|
+
}
|
|
1623
|
+
const hits = [];
|
|
1624
|
+
const lines = (result.stdout || "").trim().split("\n").filter(Boolean);
|
|
1625
|
+
for (const line of lines) {
|
|
1626
|
+
const match = line.match(/^(.+?):(\d+):(.+)$/);
|
|
1627
|
+
if (match) {
|
|
1628
|
+
const [, file, lineNum, content] = match;
|
|
1629
|
+
const relPath = path3.relative(cwd, file);
|
|
1630
|
+
hits.push({
|
|
1631
|
+
file: relPath,
|
|
1632
|
+
line: parseInt(lineNum, 10),
|
|
1633
|
+
content: content.trim(),
|
|
1634
|
+
pattern: p
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
return hits;
|
|
1639
|
+
}
|
|
1640
|
+
function ripgrepAvailable() {
|
|
1641
|
+
const r = spawnSync("rg", ["--version"], { encoding: "utf-8" });
|
|
1642
|
+
return !r.error && r.status === 0;
|
|
1643
|
+
}
|
|
1644
|
+
async function warpgrepSearch(query, cwd) {
|
|
1645
|
+
const keywords = extractKeywords(query);
|
|
1646
|
+
if (keywords.length === 0) {
|
|
1647
|
+
const tokens = query.replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length >= 2);
|
|
1648
|
+
if (tokens.length === 0) return "";
|
|
1649
|
+
keywords.push(tokens[0]);
|
|
1650
|
+
}
|
|
1651
|
+
const allHits = [];
|
|
1652
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1653
|
+
for (const kw of keywords.slice(0, 5)) {
|
|
1654
|
+
const hits = runRipgrep(kw, cwd);
|
|
1655
|
+
for (const h of hits) {
|
|
1656
|
+
const key = `${h.file}:${h.line}`;
|
|
1657
|
+
if (!seen.has(key)) {
|
|
1658
|
+
seen.add(key);
|
|
1659
|
+
allHits.push(h);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
if (allHits.length === 0) return "";
|
|
1664
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
1665
|
+
for (const h of allHits) {
|
|
1666
|
+
const list = byFile.get(h.file) || [];
|
|
1667
|
+
if (list.length < MAX_MATCHES_PER_FILE) list.push(h);
|
|
1668
|
+
byFile.set(h.file, list);
|
|
1669
|
+
}
|
|
1670
|
+
const lines = [];
|
|
1671
|
+
lines.push(`Found ${allHits.length} match(es) via ripgrep (keywords: ${keywords.join(", ")})
|
|
1672
|
+
`);
|
|
1673
|
+
lines.push("\u2550".repeat(60) + "\n");
|
|
1674
|
+
const sortedFiles = [...byFile.keys()].sort();
|
|
1675
|
+
for (const relPath of sortedFiles.slice(0, MAX_RESULTS)) {
|
|
1676
|
+
const hits = byFile.get(relPath);
|
|
1677
|
+
lines.push(`${relPath}
|
|
1678
|
+
`);
|
|
1679
|
+
for (const h of hits) {
|
|
1680
|
+
lines.push(` ${h.line.toString().padStart(4)}| ${h.content}`);
|
|
1681
|
+
}
|
|
1682
|
+
lines.push("");
|
|
1683
|
+
}
|
|
1684
|
+
return lines.join("\n");
|
|
1685
|
+
}
|
|
1583
1686
|
async function localSearch(query, rootDir) {
|
|
1687
|
+
const q = typeof query === "string" ? query.trim() : "";
|
|
1584
1688
|
const rawCwd = rootDir || process.cwd();
|
|
1585
1689
|
const cwd = detectProjectRoot(rawCwd);
|
|
1586
|
-
const keywords = extractKeywords(
|
|
1690
|
+
const keywords = extractKeywords(q);
|
|
1587
1691
|
if (cwd !== rawCwd) {
|
|
1588
1692
|
logger.info(`[localSearch] Detected project root: ${cwd} (CWD was: ${rawCwd})`);
|
|
1589
1693
|
}
|
|
1590
|
-
|
|
1694
|
+
const hasTerms = keywords.length > 0 || q.replace(/[^\w\s]/g, " ").split(/\s+/).some((w) => w.length >= 2);
|
|
1695
|
+
if (!hasTerms) {
|
|
1591
1696
|
return "Could not extract meaningful search terms from the query. Try being more specific (e.g. 'authentication middleware' instead of 'how does it work').";
|
|
1592
1697
|
}
|
|
1593
|
-
logger.info(`[localSearch] Query: "${
|
|
1594
|
-
const
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1698
|
+
logger.info(`[localSearch] Query: "${q}" \u2192 Keywords: [${keywords.join(", ")}] in ${cwd}`);
|
|
1699
|
+
const useRipgrep = ripgrepAvailable();
|
|
1700
|
+
const [rgResults, keyResults] = await Promise.all([
|
|
1701
|
+
useRipgrep ? warpgrepSearch(q, cwd) : Promise.resolve(""),
|
|
1702
|
+
(async () => {
|
|
1703
|
+
const kws2 = keywords.length > 0 ? keywords : q.replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length >= 2).slice(0, 5);
|
|
1704
|
+
if (kws2.length === 0) return "";
|
|
1705
|
+
const files = await walkDir(cwd);
|
|
1706
|
+
logger.info(`[localSearch] Scanning ${files.length} files (keyword search)`);
|
|
1707
|
+
const BATCH_SIZE = 50;
|
|
1708
|
+
const allMatches = [];
|
|
1709
|
+
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
1710
|
+
const batch = files.slice(i, i + BATCH_SIZE);
|
|
1711
|
+
const results = await Promise.all(batch.map((f) => searchFile(f, cwd, kws2)));
|
|
1712
|
+
for (const r of results) {
|
|
1713
|
+
if (r) allMatches.push(r);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
allMatches.sort((a, b) => b.score - a.score);
|
|
1717
|
+
const topMatches = allMatches.slice(0, MAX_RESULTS);
|
|
1718
|
+
if (topMatches.length === 0) return "";
|
|
1719
|
+
let out = `Found ${allMatches.length} matching file${allMatches.length === 1 ? "" : "s"} (showing top ${topMatches.length}, searched ${files.length} files)
|
|
1614
1720
|
`;
|
|
1615
|
-
|
|
1721
|
+
out += `Keywords: ${kws2.join(", ")}
|
|
1616
1722
|
`;
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1723
|
+
out += "\u2550".repeat(60) + "\n\n";
|
|
1724
|
+
for (const match of topMatches) {
|
|
1725
|
+
out += `${match.relativePath}
|
|
1620
1726
|
`;
|
|
1621
|
-
|
|
1727
|
+
out += ` Keywords matched: ${match.matchedKeywords.join(", ")} | Score: ${match.score.toFixed(1)}
|
|
1622
1728
|
`;
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1729
|
+
if (match.regions.length > 0) {
|
|
1730
|
+
out += " \u2500\u2500\u2500\u2500\u2500\n";
|
|
1731
|
+
for (const region of match.regions) {
|
|
1732
|
+
out += region.lines.split("\n").map((l) => ` ${l}`).join("\n") + "\n";
|
|
1733
|
+
if (region !== match.regions[match.regions.length - 1]) out += " ...\n";
|
|
1734
|
+
}
|
|
1629
1735
|
}
|
|
1736
|
+
out += "\n";
|
|
1737
|
+
}
|
|
1738
|
+
return out;
|
|
1739
|
+
})()
|
|
1740
|
+
]);
|
|
1741
|
+
const rgHasResults = rgResults && !rgResults.startsWith("Found 0");
|
|
1742
|
+
const keyHasResults = keyResults && keyResults.length > 50;
|
|
1743
|
+
if (rgHasResults && keyHasResults) {
|
|
1744
|
+
return rgResults + "\n\n--- Also from keyword search ---\n\n" + keyResults;
|
|
1745
|
+
}
|
|
1746
|
+
if (rgHasResults) return rgResults;
|
|
1747
|
+
if (keyHasResults) return keyResults;
|
|
1748
|
+
const kws = keywords.length > 0 ? keywords : q.replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length >= 2).slice(0, 5);
|
|
1749
|
+
if (kws.length >= 3) {
|
|
1750
|
+
const fallbackKws = kws.slice(0, 2);
|
|
1751
|
+
const files = await walkDir(cwd);
|
|
1752
|
+
const fallbackMatches = [];
|
|
1753
|
+
for (let i = 0; i < files.length; i += 50) {
|
|
1754
|
+
const batch = files.slice(i, i + 50);
|
|
1755
|
+
const results = await Promise.all(batch.map((f) => searchFile(f, cwd, fallbackKws)));
|
|
1756
|
+
for (const r of results) {
|
|
1757
|
+
if (r) fallbackMatches.push(r);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
if (fallbackMatches.length > 0) {
|
|
1761
|
+
fallbackMatches.sort((a, b) => b.score - a.score);
|
|
1762
|
+
const top = fallbackMatches.slice(0, MAX_RESULTS);
|
|
1763
|
+
let out = `Found ${fallbackMatches.length} matching file${fallbackMatches.length === 1 ? "" : "s"} (fallback: fewer keywords, showing top ${top.length})
|
|
1764
|
+
`;
|
|
1765
|
+
out += `Keywords: ${fallbackKws.join(", ")} (original: ${kws.join(", ")})
|
|
1766
|
+
`;
|
|
1767
|
+
out += "\u2550".repeat(60) + "\n\n";
|
|
1768
|
+
for (const match of top) {
|
|
1769
|
+
out += `\u{1F4C4} ${match.relativePath}
|
|
1770
|
+
`;
|
|
1771
|
+
out += ` Keywords matched: ${match.matchedKeywords.join(", ")} | Score: ${match.score.toFixed(1)}
|
|
1772
|
+
`;
|
|
1773
|
+
if (match.regions.length > 0) {
|
|
1774
|
+
out += " \u2500\u2500\u2500\u2500\u2500\n";
|
|
1775
|
+
for (const region of match.regions) {
|
|
1776
|
+
out += region.lines.split("\n").map((l) => ` ${l}`).join("\n") + "\n";
|
|
1777
|
+
if (region !== match.regions[match.regions.length - 1]) out += " ...\n";
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
out += "\n";
|
|
1630
1781
|
}
|
|
1782
|
+
return out;
|
|
1631
1783
|
}
|
|
1632
|
-
output += "\n";
|
|
1633
1784
|
}
|
|
1634
|
-
return
|
|
1785
|
+
return `No matches found for: "${q}" (searched for: ${kws.join(", ") || "query terms"}).
|
|
1786
|
+
Try different terms or check if the code exists in this project.`;
|
|
1635
1787
|
}
|
|
1636
1788
|
|
|
1637
1789
|
// ../../src/local/mcp-server.ts
|
|
@@ -1847,30 +1999,21 @@ async function ensureFileSystem() {
|
|
|
1847
1999
|
["context.md", `# Project Context
|
|
1848
2000
|
|
|
1849
2001
|
## Overview
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
2002
|
+
<!-- THIS FILE IS PART OF THE PROJECT SOUL. If it still contains placeholders, agents MUST fill it before doing any other work.
|
|
2003
|
+
How to fill: use search_codebase to explore the repo, then call update_project_soul(context: "...your content...") to replace this placeholder.
|
|
2004
|
+
If the repo is empty: ask the user what the project is about, then call update_project_soul with their answer.
|
|
2005
|
+
Do not proceed with other tasks until this file has real content. -->
|
|
1854
2006
|
|
|
1855
2007
|
## Architecture
|
|
1856
|
-
|
|
1857
|
-
- **Supabase Backend**: Postgres for state (locks, jobs, profiles); Realtime for live feeds.
|
|
1858
|
-
- **Frontend**: Next.js App Router + Tailwind CSS dashboard at useaxis.dev.
|
|
1859
|
-
- **npm Packages**: @virsanghavi/axis-server (runtime), @virsanghavi/axis-init (scaffolding).
|
|
2008
|
+
<!-- Stack, high-level design, and key systems -->
|
|
1860
2009
|
|
|
1861
2010
|
## Core Features
|
|
1862
|
-
|
|
1863
|
-
2. Job Board \u2014 post / claim / complete tasks with priorities and dependencies.
|
|
1864
|
-
3. Shared Context \u2014 live notepad visible to every agent in real time.
|
|
1865
|
-
4. RAG Search \u2014 vector search over the indexed codebase.
|
|
1866
|
-
5. Soul Files \u2014 context.md, conventions.md, activity.md define project identity.
|
|
2011
|
+
<!-- List main capabilities of this project -->
|
|
1867
2012
|
`],
|
|
1868
2013
|
["conventions.md", `# Coding Conventions
|
|
1869
2014
|
|
|
1870
2015
|
## Language & Style
|
|
1871
|
-
|
|
1872
|
-
- Tailwind CSS for styling; no raw CSS unless unavoidable.
|
|
1873
|
-
- Functional React components; prefer server components in Next.js App Router.
|
|
2016
|
+
<!-- Your language, framework, and styling guidelines -->
|
|
1874
2017
|
|
|
1875
2018
|
## Agent Behavioral Norms (MANDATORY)
|
|
1876
2019
|
|
|
@@ -1994,11 +2137,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1994
2137
|
},
|
|
1995
2138
|
{
|
|
1996
2139
|
name: UPDATE_CONTEXT_TOOL,
|
|
1997
|
-
description: "**APPEND OR OVERWRITE** shared context files.\n- Use this
|
|
2140
|
+
description: "**APPEND OR OVERWRITE** any shared context file.\n- To update the project soul (context.md / conventions.md), prefer `update_project_soul` instead \u2014 it handles both files in one call.\n- Use this tool for other context files (e.g., `activity.md`) or when you need to append to a file.\n- For short-term updates (like 'I just fixed bug X'), use `update_shared_context` (Notepad) instead.\n- Supports `append: true` (default: false) to add to the end of a file.",
|
|
1998
2141
|
inputSchema: {
|
|
1999
2142
|
type: "object",
|
|
2000
2143
|
properties: {
|
|
2001
|
-
filename: { type: "string", description: "File to update (e.g. 'activity.md')" },
|
|
2144
|
+
filename: { type: "string", description: "File to update (e.g. 'activity.md'). For soul files, prefer update_project_soul instead." },
|
|
2002
2145
|
content: { type: "string", description: "The new content to write or append." },
|
|
2003
2146
|
append: { type: "boolean", description: "Whether to append to the end of the file (true) or overwrite it (false). Default: false." }
|
|
2004
2147
|
},
|
|
@@ -2083,9 +2226,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
2083
2226
|
},
|
|
2084
2227
|
{
|
|
2085
2228
|
name: "get_project_soul",
|
|
2086
|
-
description: "**MANDATORY FIRST CALL**: Returns the project's goals, architecture, conventions, and active state.\n- Combines `context.md
|
|
2229
|
+
description: "**MANDATORY FIRST CALL**: Returns the project's goals, architecture, conventions, and active state.\n- Combines `context.md` (project goals/architecture) and `conventions.md` (coding standards/norms) 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- If the project soul is not yet filled (you'll see a 'MANDATORY: Project soul is not yet filled' message), you MUST fill it before any other work:\n 1. Use `search_codebase` to explore the repo and infer project details.\n 2. Call `update_project_soul` with `context` and/or `conventions` params to populate the soul in one call.\n 3. If there is nothing to search, ask the user what the project is about, then call `update_project_soul`.\n- Skipping this call means you are working without context and will make wrong decisions.",
|
|
2087
2230
|
inputSchema: { type: "object", properties: {}, required: [] }
|
|
2088
2231
|
},
|
|
2232
|
+
{
|
|
2233
|
+
name: "update_project_soul",
|
|
2234
|
+
description: "**UPDATE THE PROJECT SOUL** \u2014 write project context and/or conventions in a single call.\n- The project soul consists of `context.md` (goals, architecture, core features) and `conventions.md` (coding standards, agent norms).\n- Provide `context` to update `context.md`, `conventions` to update `conventions.md`, or both.\n- Use this when `get_project_soul` says the soul is unfilled, or whenever you need to update long-term project knowledge.\n- This replaces the file contents entirely (not append). For appending, use `update_context` instead.",
|
|
2235
|
+
inputSchema: {
|
|
2236
|
+
type: "object",
|
|
2237
|
+
properties: {
|
|
2238
|
+
context: { type: "string", description: "Full content for context.md (project overview, architecture, core features, deployment). Omit to leave unchanged." },
|
|
2239
|
+
conventions: { type: "string", description: "Full content for conventions.md (language standards, styling, code patterns, agent norms). Omit to leave unchanged." }
|
|
2240
|
+
},
|
|
2241
|
+
required: []
|
|
2242
|
+
}
|
|
2243
|
+
},
|
|
2089
2244
|
// --- Job Board (Task Orchestration) ---
|
|
2090
2245
|
{
|
|
2091
2246
|
name: "post_job",
|
|
@@ -2169,15 +2324,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
2169
2324
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
2170
2325
|
const { name, arguments: args } = request.params;
|
|
2171
2326
|
logger.info("Tool call", { name });
|
|
2172
|
-
if (
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2327
|
+
if (process.env.AXIS_SKIP_SUBSCRIPTION_CHECK === "1") {
|
|
2328
|
+
} else {
|
|
2329
|
+
if (isSubscriptionStale()) {
|
|
2330
|
+
await verifySubscription();
|
|
2331
|
+
}
|
|
2332
|
+
if (!subscription.valid) {
|
|
2333
|
+
logger.warn(`[subscription] Blocking tool call "${name}" \u2014 subscription invalid`);
|
|
2334
|
+
return {
|
|
2335
|
+
content: [{ type: "text", text: getSubscriptionBlockMessage() }],
|
|
2336
|
+
isError: true
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2181
2339
|
}
|
|
2182
2340
|
if (name === READ_CONTEXT_TOOL) {
|
|
2183
2341
|
const filename = String(args?.filename);
|
|
@@ -2330,6 +2488,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2330
2488
|
const result = await nerveCenter.getProjectSoul();
|
|
2331
2489
|
return { content: [{ type: "text", text: result }] };
|
|
2332
2490
|
}
|
|
2491
|
+
if (name === "update_project_soul") {
|
|
2492
|
+
const { context, conventions } = args;
|
|
2493
|
+
const updated = [];
|
|
2494
|
+
if (context) {
|
|
2495
|
+
await manager.updateFile("context.md", context, false);
|
|
2496
|
+
updated.push("context.md");
|
|
2497
|
+
}
|
|
2498
|
+
if (conventions) {
|
|
2499
|
+
await manager.updateFile("conventions.md", conventions, false);
|
|
2500
|
+
updated.push("conventions.md");
|
|
2501
|
+
}
|
|
2502
|
+
if (updated.length === 0) {
|
|
2503
|
+
return { content: [{ type: "text", text: "No changes \u2014 provide `context` and/or `conventions` parameters." }] };
|
|
2504
|
+
}
|
|
2505
|
+
return { content: [{ type: "text", text: `Project soul updated: ${updated.join(", ")}` }] };
|
|
2506
|
+
}
|
|
2333
2507
|
if (name === "post_job") {
|
|
2334
2508
|
const { title, description, priority, dependencies } = args;
|
|
2335
2509
|
const result = await nerveCenter.postJob(title, description, priority, dependencies);
|
package/package.json
CHANGED
|
@@ -1,33 +1,37 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
2
|
+
"name": "@virsanghavi/axis-server",
|
|
3
|
+
"version": "1.10.0",
|
|
4
|
+
"description": "Axis MCP Server CLI",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"axis-server": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node dist/cli.js",
|
|
11
|
+
"build": "tsup bin/cli.ts --format cjs --out-dir dist --clean --shims && tsup ../../src/local/mcp-server.ts --format esm --out-dir dist",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"commander": "^11.0.0",
|
|
16
|
+
"chalk": "^5.3.0",
|
|
17
|
+
"@modelcontextprotocol/sdk": "^0.6.0",
|
|
18
|
+
"@supabase/supabase-js": "^2.39.0",
|
|
19
|
+
"async-mutex": "^0.5.0",
|
|
20
|
+
"dotenv": "^16.3.1",
|
|
21
|
+
"fs-extra": "^11.2.0",
|
|
22
|
+
"openai": "^4.24.0",
|
|
23
|
+
"zod": "^3.22.4",
|
|
24
|
+
"execa": "^8.0.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^20.0.0",
|
|
28
|
+
"@types/fs-extra": "^11.0.4",
|
|
29
|
+
"tsup": "^8.0.1",
|
|
30
|
+
"tsx": "^4.7.0",
|
|
31
|
+
"typescript": "^5.0.0"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public",
|
|
35
|
+
"registry": "https://registry.npmjs.org/"
|
|
36
|
+
}
|
|
33
37
|
}
|