@virsanghavi/axis-server 1.9.2 → 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 +237 -64
- 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
|
@@ -1140,9 +1140,13 @@ ${conventions}`;
|
|
|
1140
1140
|
|
|
1141
1141
|
### MANDATORY: Project soul is not yet filled
|
|
1142
1142
|
You MUST fill the project soul before doing any other work. Do not proceed with other tasks until it is filled.
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
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.`;
|
|
1146
1150
|
}
|
|
1147
1151
|
return soul;
|
|
1148
1152
|
}
|
|
@@ -1283,6 +1287,7 @@ import fs4 from "fs";
|
|
|
1283
1287
|
import fs3 from "fs/promises";
|
|
1284
1288
|
import fsSync2 from "fs";
|
|
1285
1289
|
import path3 from "path";
|
|
1290
|
+
import { spawnSync } from "child_process";
|
|
1286
1291
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
1287
1292
|
"node_modules",
|
|
1288
1293
|
".git",
|
|
@@ -1443,14 +1448,7 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
|
1443
1448
|
"also",
|
|
1444
1449
|
"very",
|
|
1445
1450
|
"really",
|
|
1446
|
-
"quite"
|
|
1447
|
-
"show",
|
|
1448
|
-
"look",
|
|
1449
|
-
"locate",
|
|
1450
|
-
"using",
|
|
1451
|
-
"used",
|
|
1452
|
-
"need",
|
|
1453
|
-
"want"
|
|
1451
|
+
"quite"
|
|
1454
1452
|
]);
|
|
1455
1453
|
var MAX_FILE_SIZE = 256 * 1024;
|
|
1456
1454
|
var MAX_RESULTS = 20;
|
|
@@ -1459,7 +1457,7 @@ var MAX_MATCHES_PER_FILE = 6;
|
|
|
1459
1457
|
function extractKeywords(query) {
|
|
1460
1458
|
const words = query.toLowerCase().replace(/[^\w\s\-_.]/g, " ").split(/\s+/).filter((w) => w.length >= 2);
|
|
1461
1459
|
const filtered = words.filter((w) => !STOP_WORDS.has(w));
|
|
1462
|
-
const result = filtered.length > 0 ? filtered : words
|
|
1460
|
+
const result = filtered.length > 0 ? filtered : words;
|
|
1463
1461
|
return [...new Set(result)];
|
|
1464
1462
|
}
|
|
1465
1463
|
var PROJECT_ROOT_MARKERS = [
|
|
@@ -1477,7 +1475,7 @@ var PROJECT_ROOT_MARKERS = [
|
|
|
1477
1475
|
"AGENTS.md"
|
|
1478
1476
|
];
|
|
1479
1477
|
function detectProjectRoot(startDir) {
|
|
1480
|
-
let current = startDir;
|
|
1478
|
+
let current = path3.resolve(startDir);
|
|
1481
1479
|
const root = path3.parse(current).root;
|
|
1482
1480
|
while (current !== root) {
|
|
1483
1481
|
for (const marker of PROJECT_ROOT_MARKERS) {
|
|
@@ -1540,8 +1538,7 @@ async function searchFile(filePath, rootDir, keywords) {
|
|
|
1540
1538
|
const matchedKeywords = keywords.filter((kw) => contentLower.includes(kw));
|
|
1541
1539
|
if (matchedKeywords.length === 0) return null;
|
|
1542
1540
|
const coverage = matchedKeywords.length / keywords.length;
|
|
1543
|
-
if (
|
|
1544
|
-
if (keywords.length === 2 && matchedKeywords.length < 1) return null;
|
|
1541
|
+
if (coverage < 0.2) return null;
|
|
1545
1542
|
const lines = content.split("\n");
|
|
1546
1543
|
let score = coverage * coverage * matchedKeywords.length;
|
|
1547
1544
|
const relLower = relativePath.toLowerCase();
|
|
@@ -1593,58 +1590,200 @@ async function searchFile(filePath, rootDir, keywords) {
|
|
|
1593
1590
|
}
|
|
1594
1591
|
return { filePath, relativePath, score, matchedKeywords, regions };
|
|
1595
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
|
+
}
|
|
1596
1686
|
async function localSearch(query, rootDir) {
|
|
1687
|
+
const q = typeof query === "string" ? query.trim() : "";
|
|
1597
1688
|
const rawCwd = rootDir || process.cwd();
|
|
1598
1689
|
const cwd = detectProjectRoot(rawCwd);
|
|
1599
|
-
const keywords = extractKeywords(
|
|
1690
|
+
const keywords = extractKeywords(q);
|
|
1600
1691
|
if (cwd !== rawCwd) {
|
|
1601
1692
|
logger.info(`[localSearch] Detected project root: ${cwd} (CWD was: ${rawCwd})`);
|
|
1602
1693
|
}
|
|
1603
|
-
|
|
1694
|
+
const hasTerms = keywords.length > 0 || q.replace(/[^\w\s]/g, " ").split(/\s+/).some((w) => w.length >= 2);
|
|
1695
|
+
if (!hasTerms) {
|
|
1604
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').";
|
|
1605
1697
|
}
|
|
1606
|
-
logger.info(`[localSearch] Query: "${
|
|
1607
|
-
const
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
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)
|
|
1627
1720
|
`;
|
|
1628
|
-
|
|
1721
|
+
out += `Keywords: ${kws2.join(", ")}
|
|
1629
1722
|
`;
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1723
|
+
out += "\u2550".repeat(60) + "\n\n";
|
|
1724
|
+
for (const match of topMatches) {
|
|
1725
|
+
out += `${match.relativePath}
|
|
1633
1726
|
`;
|
|
1634
|
-
|
|
1727
|
+
out += ` Keywords matched: ${match.matchedKeywords.join(", ")} | Score: ${match.score.toFixed(1)}
|
|
1635
1728
|
`;
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
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
|
+
}
|
|
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
|
+
}
|
|
1642
1779
|
}
|
|
1780
|
+
out += "\n";
|
|
1643
1781
|
}
|
|
1782
|
+
return out;
|
|
1644
1783
|
}
|
|
1645
|
-
output += "\n";
|
|
1646
1784
|
}
|
|
1647
|
-
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.`;
|
|
1648
1787
|
}
|
|
1649
1788
|
|
|
1650
1789
|
// ../../src/local/mcp-server.ts
|
|
@@ -1860,7 +1999,10 @@ async function ensureFileSystem() {
|
|
|
1860
1999
|
["context.md", `# Project Context
|
|
1861
2000
|
|
|
1862
2001
|
## Overview
|
|
1863
|
-
<!--
|
|
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. -->
|
|
1864
2006
|
|
|
1865
2007
|
## Architecture
|
|
1866
2008
|
<!-- Stack, high-level design, and key systems -->
|
|
@@ -1995,11 +2137,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1995
2137
|
},
|
|
1996
2138
|
{
|
|
1997
2139
|
name: UPDATE_CONTEXT_TOOL,
|
|
1998
|
-
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.",
|
|
1999
2141
|
inputSchema: {
|
|
2000
2142
|
type: "object",
|
|
2001
2143
|
properties: {
|
|
2002
|
-
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." },
|
|
2003
2145
|
content: { type: "string", description: "The new content to write or append." },
|
|
2004
2146
|
append: { type: "boolean", description: "Whether to append to the end of the file (true) or overwrite it (false). Default: false." }
|
|
2005
2147
|
},
|
|
@@ -2084,9 +2226,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
2084
2226
|
},
|
|
2085
2227
|
{
|
|
2086
2228
|
name: "get_project_soul",
|
|
2087
|
-
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.",
|
|
2088
2230
|
inputSchema: { type: "object", properties: {}, required: [] }
|
|
2089
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
|
+
},
|
|
2090
2244
|
// --- Job Board (Task Orchestration) ---
|
|
2091
2245
|
{
|
|
2092
2246
|
name: "post_job",
|
|
@@ -2170,15 +2324,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
2170
2324
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
2171
2325
|
const { name, arguments: args } = request.params;
|
|
2172
2326
|
logger.info("Tool call", { name });
|
|
2173
|
-
if (
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
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
|
+
}
|
|
2182
2339
|
}
|
|
2183
2340
|
if (name === READ_CONTEXT_TOOL) {
|
|
2184
2341
|
const filename = String(args?.filename);
|
|
@@ -2331,6 +2488,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2331
2488
|
const result = await nerveCenter.getProjectSoul();
|
|
2332
2489
|
return { content: [{ type: "text", text: result }] };
|
|
2333
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
|
+
}
|
|
2334
2507
|
if (name === "post_job") {
|
|
2335
2508
|
const { title, description, priority, dependencies } = args;
|
|
2336
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
|
}
|