@pathmode/mcp-server 1.3.1 → 1.4.1
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/README.md +29 -0
- package/dist/api-client.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1082 -811
- package/dist/setup.d.ts +12 -0
- package/dist/setup.d.ts.map +1 -0
- package/manifest.json +13 -6
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -11930,6 +11930,259 @@ function extractEdgeCases(body) {
|
|
|
11930
11930
|
}
|
|
11931
11931
|
|
|
11932
11932
|
|
|
11933
|
+
/***/ }),
|
|
11934
|
+
|
|
11935
|
+
/***/ 8294:
|
|
11936
|
+
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
|
11937
|
+
|
|
11938
|
+
"use strict";
|
|
11939
|
+
|
|
11940
|
+
/**
|
|
11941
|
+
* Pathmode MCP Setup Command
|
|
11942
|
+
*
|
|
11943
|
+
* Auto-detects AI tools (Claude Code, Claude Desktop, Cursor, Windsurf)
|
|
11944
|
+
* and configures the MCP server with a single command.
|
|
11945
|
+
*
|
|
11946
|
+
* Usage:
|
|
11947
|
+
* npx @pathmode/mcp-server setup pm_live_xxxxx
|
|
11948
|
+
*/
|
|
11949
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
11950
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11951
|
+
};
|
|
11952
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
11953
|
+
exports.getSetupArgs = getSetupArgs;
|
|
11954
|
+
exports.isSetupCommand = isSetupCommand;
|
|
11955
|
+
exports.runSetup = runSetup;
|
|
11956
|
+
const fs_1 = __importDefault(__nccwpck_require__(9896));
|
|
11957
|
+
const path_1 = __importDefault(__nccwpck_require__(6928));
|
|
11958
|
+
const os_1 = __importDefault(__nccwpck_require__(857));
|
|
11959
|
+
function claudeDesktopPaths() {
|
|
11960
|
+
const platform = process.platform;
|
|
11961
|
+
if (platform === 'darwin') {
|
|
11962
|
+
return [path_1.default.join(os_1.default.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json')];
|
|
11963
|
+
}
|
|
11964
|
+
if (platform === 'win32') {
|
|
11965
|
+
return [path_1.default.join(process.env.APPDATA || '', 'Claude', 'claude_desktop_config.json')];
|
|
11966
|
+
}
|
|
11967
|
+
// Linux
|
|
11968
|
+
return [path_1.default.join(os_1.default.homedir(), '.config', 'Claude', 'claude_desktop_config.json')];
|
|
11969
|
+
}
|
|
11970
|
+
function claudeCodePaths() {
|
|
11971
|
+
return [path_1.default.join(os_1.default.homedir(), '.claude', 'settings.json')];
|
|
11972
|
+
}
|
|
11973
|
+
function cursorPaths() {
|
|
11974
|
+
return [path_1.default.join(os_1.default.homedir(), '.cursor', 'mcp.json')];
|
|
11975
|
+
}
|
|
11976
|
+
function windsurfPaths() {
|
|
11977
|
+
const platform = process.platform;
|
|
11978
|
+
if (platform === 'darwin') {
|
|
11979
|
+
return [path_1.default.join(os_1.default.homedir(), '.codeium', 'windsurf', 'mcp_config.json')];
|
|
11980
|
+
}
|
|
11981
|
+
if (platform === 'win32') {
|
|
11982
|
+
return [path_1.default.join(process.env.APPDATA || '', 'Codeium', 'windsurf', 'mcp_config.json')];
|
|
11983
|
+
}
|
|
11984
|
+
return [path_1.default.join(os_1.default.homedir(), '.codeium', 'windsurf', 'mcp_config.json')];
|
|
11985
|
+
}
|
|
11986
|
+
const TOOLS = [
|
|
11987
|
+
{ name: 'Claude Code', paths: claudeCodePaths, configKey: 'mcpServers' },
|
|
11988
|
+
{ name: 'Claude Desktop', paths: claudeDesktopPaths, configKey: 'mcpServers' },
|
|
11989
|
+
{ name: 'Cursor', paths: cursorPaths, configKey: 'mcpServers' },
|
|
11990
|
+
{ name: 'Windsurf', paths: windsurfPaths, configKey: 'mcpServers' },
|
|
11991
|
+
];
|
|
11992
|
+
// ─── Helpers ──────────────────────────────────────────────────
|
|
11993
|
+
const BOLD = '\x1b[1m';
|
|
11994
|
+
const DIM = '\x1b[2m';
|
|
11995
|
+
const GREEN = '\x1b[32m';
|
|
11996
|
+
const RED = '\x1b[31m';
|
|
11997
|
+
const YELLOW = '\x1b[33m';
|
|
11998
|
+
const CYAN = '\x1b[36m';
|
|
11999
|
+
const RESET = '\x1b[0m';
|
|
12000
|
+
function log(msg) { console.log(msg); }
|
|
12001
|
+
function success(msg) { console.log(` ${GREEN}✓${RESET} ${msg}`); }
|
|
12002
|
+
function warn(msg) { console.log(` ${YELLOW}!${RESET} ${msg}`); }
|
|
12003
|
+
function fail(msg) { console.log(` ${RED}✗${RESET} ${msg}`); }
|
|
12004
|
+
function getMcpServerBlock(apiKey) {
|
|
12005
|
+
return {
|
|
12006
|
+
command: 'npx',
|
|
12007
|
+
args: ['@pathmode/mcp-server'],
|
|
12008
|
+
env: { PATHMODE_API_KEY: apiKey },
|
|
12009
|
+
};
|
|
12010
|
+
}
|
|
12011
|
+
function readJsonFile(filePath) {
|
|
12012
|
+
let raw;
|
|
12013
|
+
try {
|
|
12014
|
+
raw = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
12015
|
+
}
|
|
12016
|
+
catch {
|
|
12017
|
+
return { ok: false, reason: 'not_found' };
|
|
12018
|
+
}
|
|
12019
|
+
try {
|
|
12020
|
+
return { ok: true, data: JSON.parse(raw) };
|
|
12021
|
+
}
|
|
12022
|
+
catch {
|
|
12023
|
+
return { ok: false, reason: 'parse_error', raw };
|
|
12024
|
+
}
|
|
12025
|
+
}
|
|
12026
|
+
function writeJsonSafe(filePath, data) {
|
|
12027
|
+
try {
|
|
12028
|
+
const dir = path_1.default.dirname(filePath);
|
|
12029
|
+
if (!fs_1.default.existsSync(dir)) {
|
|
12030
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
12031
|
+
}
|
|
12032
|
+
fs_1.default.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
12033
|
+
return true;
|
|
12034
|
+
}
|
|
12035
|
+
catch (err) {
|
|
12036
|
+
fail(`Could not write ${filePath}: ${err.message}`);
|
|
12037
|
+
return false;
|
|
12038
|
+
}
|
|
12039
|
+
}
|
|
12040
|
+
function shortenPath(p) {
|
|
12041
|
+
const home = os_1.default.homedir();
|
|
12042
|
+
return p.startsWith(home) ? '~' + p.slice(home.length) : p;
|
|
12043
|
+
}
|
|
12044
|
+
function getSetupArgs(argv = process.argv) {
|
|
12045
|
+
const setupIndex = argv.findIndex(arg => arg === 'setup');
|
|
12046
|
+
return setupIndex === -1 ? [] : argv.slice(setupIndex + 1);
|
|
12047
|
+
}
|
|
12048
|
+
function isSetupCommand(argv = process.argv) {
|
|
12049
|
+
return argv.includes('setup');
|
|
12050
|
+
}
|
|
12051
|
+
// ─── Main ─────────────────────────────────────────────────────
|
|
12052
|
+
async function runSetup() {
|
|
12053
|
+
const args = getSetupArgs();
|
|
12054
|
+
const apiKey = args.find(a => a.startsWith('pm_live_') || a.startsWith('pm_test_'));
|
|
12055
|
+
log('');
|
|
12056
|
+
log(`${BOLD}Pathmode MCP Setup${RESET}`);
|
|
12057
|
+
log(`${DIM}──────────────────${RESET}`);
|
|
12058
|
+
log('');
|
|
12059
|
+
if (!apiKey) {
|
|
12060
|
+
log(`Usage: npx @pathmode/mcp-server setup ${DIM}<api-key>${RESET}`);
|
|
12061
|
+
log('');
|
|
12062
|
+
log(`Get your API key from ${CYAN}https://pathmode.io${RESET} → Settings → API Keys`);
|
|
12063
|
+
log('');
|
|
12064
|
+
process.exit(1);
|
|
12065
|
+
}
|
|
12066
|
+
// ─── Step 1: Validate key ─────────────────────────────────
|
|
12067
|
+
process.stdout.write(` Validating API key...`);
|
|
12068
|
+
let workspaceName = '';
|
|
12069
|
+
let workspaceId = '';
|
|
12070
|
+
const apiUrl = args.includes('--staging')
|
|
12071
|
+
? 'https://staging.pathmode.io'
|
|
12072
|
+
: 'https://pathmode.io';
|
|
12073
|
+
try {
|
|
12074
|
+
const res = await fetch(`${apiUrl}/api/v1/workspace`, {
|
|
12075
|
+
headers: {
|
|
12076
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
12077
|
+
'Content-Type': 'application/json',
|
|
12078
|
+
},
|
|
12079
|
+
});
|
|
12080
|
+
if (!res.ok) {
|
|
12081
|
+
if (res.status === 401 || res.status === 403) {
|
|
12082
|
+
log(` ${RED}✗${RESET}`);
|
|
12083
|
+
log('');
|
|
12084
|
+
fail('Invalid or expired API key.');
|
|
12085
|
+
log(` Get a new key from ${CYAN}${apiUrl}${RESET} → Settings → API Keys`);
|
|
12086
|
+
log('');
|
|
12087
|
+
process.exit(1);
|
|
12088
|
+
}
|
|
12089
|
+
throw new Error(`HTTP ${res.status}`);
|
|
12090
|
+
}
|
|
12091
|
+
const workspace = await res.json();
|
|
12092
|
+
workspaceName = workspace.name;
|
|
12093
|
+
workspaceId = workspace.id;
|
|
12094
|
+
log(` ${GREEN}✓${RESET}`);
|
|
12095
|
+
success(`Connected to "${BOLD}${workspaceName}${RESET}"`);
|
|
12096
|
+
}
|
|
12097
|
+
catch (err) {
|
|
12098
|
+
log(` ${RED}✗${RESET}`);
|
|
12099
|
+
log('');
|
|
12100
|
+
if (err.cause?.code === 'ENOTFOUND' || err.cause?.code === 'ECONNREFUSED') {
|
|
12101
|
+
fail('Could not reach pathmode.io. Check your internet connection.');
|
|
12102
|
+
}
|
|
12103
|
+
else {
|
|
12104
|
+
fail(`Connection failed: ${err.message}`);
|
|
12105
|
+
}
|
|
12106
|
+
log('');
|
|
12107
|
+
process.exit(1);
|
|
12108
|
+
}
|
|
12109
|
+
log('');
|
|
12110
|
+
// ─── Step 2: Detect & configure tools ─────────────────────
|
|
12111
|
+
let configured = 0;
|
|
12112
|
+
for (const tool of TOOLS) {
|
|
12113
|
+
const possiblePaths = tool.paths();
|
|
12114
|
+
// Find existing config, or use first path to create
|
|
12115
|
+
const existingPath = possiblePaths.find(p => fs_1.default.existsSync(p));
|
|
12116
|
+
const configPath = existingPath || possiblePaths[0];
|
|
12117
|
+
// Only configure if the tool's config dir exists (tool is installed)
|
|
12118
|
+
// Exception: Claude Code — always configure since ~/.claude/ may not exist yet
|
|
12119
|
+
const configDir = path_1.default.dirname(configPath);
|
|
12120
|
+
const toolInstalled = tool.name === 'Claude Code' || fs_1.default.existsSync(configDir);
|
|
12121
|
+
if (!toolInstalled) {
|
|
12122
|
+
continue;
|
|
12123
|
+
}
|
|
12124
|
+
let config = {};
|
|
12125
|
+
if (existingPath) {
|
|
12126
|
+
const result = readJsonFile(configPath);
|
|
12127
|
+
if (result.ok) {
|
|
12128
|
+
config = result.data;
|
|
12129
|
+
}
|
|
12130
|
+
else if (result.reason === 'parse_error') {
|
|
12131
|
+
// Back up the corrupt file instead of silently overwriting
|
|
12132
|
+
const backupPath = configPath + '.backup';
|
|
12133
|
+
try {
|
|
12134
|
+
fs_1.default.writeFileSync(backupPath, result.raw, 'utf-8');
|
|
12135
|
+
}
|
|
12136
|
+
catch { /* best effort */ }
|
|
12137
|
+
warn(`${tool.name}: ${DIM}${shortenPath(configPath)}${RESET} is not valid JSON — backed up to ${DIM}${shortenPath(backupPath)}${RESET}`);
|
|
12138
|
+
continue;
|
|
12139
|
+
}
|
|
12140
|
+
}
|
|
12141
|
+
// Deep merge: preserve other mcpServers, only set/overwrite "pathmode"
|
|
12142
|
+
if (!config[tool.configKey]) {
|
|
12143
|
+
config[tool.configKey] = {};
|
|
12144
|
+
}
|
|
12145
|
+
config[tool.configKey].pathmode = getMcpServerBlock(apiKey);
|
|
12146
|
+
if (writeJsonSafe(configPath, config)) {
|
|
12147
|
+
success(`${tool.name} → ${DIM}${shortenPath(configPath)}${RESET}`);
|
|
12148
|
+
configured++;
|
|
12149
|
+
}
|
|
12150
|
+
}
|
|
12151
|
+
// ─── Step 3: Save ~/.pathmode/config.json ─────────────────
|
|
12152
|
+
const pathmodeConfigDir = path_1.default.join(os_1.default.homedir(), '.pathmode');
|
|
12153
|
+
const pathmodeConfigFile = path_1.default.join(pathmodeConfigDir, 'config.json');
|
|
12154
|
+
const pathmodeConfig = {
|
|
12155
|
+
apiKey,
|
|
12156
|
+
apiUrl,
|
|
12157
|
+
workspaceId,
|
|
12158
|
+
};
|
|
12159
|
+
if (writeJsonSafe(pathmodeConfigFile, pathmodeConfig)) {
|
|
12160
|
+
success(`Config saved → ${DIM}${shortenPath(pathmodeConfigFile)}${RESET}`);
|
|
12161
|
+
}
|
|
12162
|
+
log('');
|
|
12163
|
+
// ─── Step 4: Summary ──────────────────────────────────────
|
|
12164
|
+
if (configured === 0) {
|
|
12165
|
+
log(`${YELLOW}No supported tools detected.${RESET} Add manually:`);
|
|
12166
|
+
log('');
|
|
12167
|
+
log(` ${DIM}// .claude/settings.json, ~/.cursor/mcp.json, or claude_desktop_config.json${RESET}`);
|
|
12168
|
+
log(` ${CYAN}{${RESET}`);
|
|
12169
|
+
log(` ${CYAN} "mcpServers": {${RESET}`);
|
|
12170
|
+
log(` ${CYAN} "pathmode": {${RESET}`);
|
|
12171
|
+
log(` ${CYAN} "command": "npx",${RESET}`);
|
|
12172
|
+
log(` ${CYAN} "args": ["@pathmode/mcp-server"],${RESET}`);
|
|
12173
|
+
log(` ${CYAN} "env": { "PATHMODE_API_KEY": "${apiKey}" }${RESET}`);
|
|
12174
|
+
log(` ${CYAN} }${RESET}`);
|
|
12175
|
+
log(` ${CYAN} }${RESET}`);
|
|
12176
|
+
log(` ${CYAN}}${RESET}`);
|
|
12177
|
+
log('');
|
|
12178
|
+
}
|
|
12179
|
+
else {
|
|
12180
|
+
log(`${GREEN}Done!${RESET} Restart your tools to activate Pathmode.`);
|
|
12181
|
+
log('');
|
|
12182
|
+
}
|
|
12183
|
+
}
|
|
12184
|
+
|
|
12185
|
+
|
|
11933
12186
|
/***/ }),
|
|
11934
12187
|
|
|
11935
12188
|
/***/ 9896:
|
|
@@ -39728,8 +39981,9 @@ var exports = __webpack_exports__;
|
|
|
39728
39981
|
* Connects Claude Code, Cursor, and other AI agents to your Intent Layer.
|
|
39729
39982
|
*
|
|
39730
39983
|
* Usage:
|
|
39731
|
-
* npx @pathmode/mcp-server
|
|
39732
|
-
* npx @pathmode/mcp-server --local
|
|
39984
|
+
* npx @pathmode/mcp-server # Cloud mode (uses ~/.pathmode/config.json)
|
|
39985
|
+
* npx @pathmode/mcp-server --local # Local mode (reads intent.md from cwd)
|
|
39986
|
+
* npx @pathmode/mcp-server setup pm_live_xxx # Auto-configure your tools
|
|
39733
39987
|
*
|
|
39734
39988
|
* The Intent Compiler (compile-intent prompt, intent_save, intent_export tools)
|
|
39735
39989
|
* works without an API key — zero-config intent spec building in Claude Code.
|
|
@@ -39753,866 +40007,883 @@ const fs_1 = __nccwpck_require__(9896);
|
|
|
39753
40007
|
const api_client_1 = __nccwpck_require__(7475);
|
|
39754
40008
|
const local_reader_1 = __nccwpck_require__(3518);
|
|
39755
40009
|
const intent_compiler_1 = __nccwpck_require__(6488);
|
|
39756
|
-
const
|
|
39757
|
-
|
|
39758
|
-
|
|
39759
|
-
|
|
39760
|
-
|
|
39761
|
-
|
|
39762
|
-
|
|
39763
|
-
|
|
39764
|
-
|
|
39765
|
-
}
|
|
39766
|
-
|
|
39767
|
-
|
|
39768
|
-
|
|
39769
|
-
|
|
39770
|
-
|
|
39771
|
-
|
|
39772
|
-
|
|
39773
|
-
|
|
39774
|
-
|
|
39775
|
-
const
|
|
39776
|
-
|
|
39777
|
-
|
|
39778
|
-
|
|
39779
|
-
|
|
39780
|
-
|
|
39781
|
-
|
|
39782
|
-
|
|
39783
|
-
|
|
39784
|
-
|
|
39785
|
-
|
|
39786
|
-
|
|
39787
|
-
|
|
39788
|
-
|
|
39789
|
-
|
|
39790
|
-
|
|
39791
|
-
|
|
39792
|
-
|
|
39793
|
-
|
|
39794
|
-
|
|
39795
|
-
|
|
39796
|
-
|
|
39797
|
-
|
|
39798
|
-
|
|
39799
|
-
|
|
39800
|
-
|
|
39801
|
-
|
|
39802
|
-
|
|
39803
|
-
|
|
39804
|
-
|
|
39805
|
-
|
|
39806
|
-
|
|
39807
|
-
|
|
39808
|
-
|
|
39809
|
-
}
|
|
39810
|
-
|
|
39811
|
-
|
|
39812
|
-
|
|
40010
|
+
const setup_1 = __nccwpck_require__(8294);
|
|
40011
|
+
// ─── Subcommand routing ───────────────────────────────────────
|
|
40012
|
+
// `setup` uses stdout for human-readable output and must run
|
|
40013
|
+
// before StdioServerTransport claims stdout for JSON-RPC.
|
|
40014
|
+
// We call startMcpServer() only when NOT in setup mode.
|
|
40015
|
+
if ((0, setup_1.isSetupCommand)()) {
|
|
40016
|
+
(0, setup_1.runSetup)().then(() => process.exit(0)).catch((err) => {
|
|
40017
|
+
console.error(err);
|
|
40018
|
+
process.exit(1);
|
|
40019
|
+
});
|
|
40020
|
+
}
|
|
40021
|
+
else {
|
|
40022
|
+
startMcpServer();
|
|
40023
|
+
}
|
|
40024
|
+
function startMcpServer() {
|
|
40025
|
+
// ─── MCP Server ───────────────────────────────────────────────
|
|
40026
|
+
const isLocalMode = process.argv.includes('--local');
|
|
40027
|
+
let client = null;
|
|
40028
|
+
if (!isLocalMode) {
|
|
40029
|
+
const config = (0, api_client_1.loadConfig)();
|
|
40030
|
+
console.error(`[pathmode-mcp] API key present: ${!!config?.apiKey}, prefix: ${config?.apiKey?.substring(0, 16) || 'none'}, url: ${config?.apiUrl || 'none'}`);
|
|
40031
|
+
if (config) {
|
|
40032
|
+
client = new api_client_1.PathmodeClient(config);
|
|
40033
|
+
}
|
|
40034
|
+
// No exit — Intent Compiler tools work without an API key
|
|
40035
|
+
}
|
|
40036
|
+
// ============================================================
|
|
40037
|
+
// Server Setup
|
|
40038
|
+
// ============================================================
|
|
40039
|
+
const server = new mcp_js_1.McpServer({
|
|
40040
|
+
name: 'pathmode',
|
|
40041
|
+
version: '1.3.0',
|
|
40042
|
+
});
|
|
40043
|
+
// Annotation presets
|
|
40044
|
+
const READ_ONLY = { readOnlyHint: true, openWorldHint: true };
|
|
40045
|
+
const WRITE_OP = { readOnlyHint: false, destructiveHint: false, openWorldHint: true };
|
|
40046
|
+
// ============================================================
|
|
40047
|
+
// Tools — Read Operations
|
|
40048
|
+
// ============================================================
|
|
40049
|
+
server.registerTool('get_current_intent', {
|
|
40050
|
+
title: 'Get Current Intent',
|
|
40051
|
+
description: 'Get the currently active intent (first approved, or most recently updated). Returns the full IntentSpec with objective, outcomes, constraints, and edge cases.',
|
|
40052
|
+
inputSchema: { status: zod_1.z.string().optional().describe('Filter by status: draft, validated, approved, shipped, verified') },
|
|
40053
|
+
annotations: READ_ONLY,
|
|
40054
|
+
}, async ({ status }) => {
|
|
40055
|
+
if (isLocalMode) {
|
|
40056
|
+
const intents = (0, local_reader_1.readLocalIntents)();
|
|
40057
|
+
const filtered = status ? intents.filter(i => i.status === status) : intents;
|
|
40058
|
+
const current = filtered[0];
|
|
40059
|
+
if (!current) {
|
|
40060
|
+
return { content: [{ type: 'text', text: 'No intents found locally.' }] };
|
|
40061
|
+
}
|
|
40062
|
+
return { content: [{ type: 'text', text: JSON.stringify(current, null, 2) }] };
|
|
40063
|
+
}
|
|
40064
|
+
const intents = await client.listIntents(status || 'approved');
|
|
40065
|
+
if (intents.length === 0) {
|
|
40066
|
+
const allIntents = await client.listIntents();
|
|
40067
|
+
if (allIntents.length === 0) {
|
|
40068
|
+
return { content: [{ type: 'text', text: 'No intents found in workspace.' }] };
|
|
40069
|
+
}
|
|
40070
|
+
return { content: [{ type: 'text', text: JSON.stringify(allIntents[0], null, 2) }] };
|
|
40071
|
+
}
|
|
40072
|
+
return { content: [{ type: 'text', text: JSON.stringify(intents[0], null, 2) }] };
|
|
40073
|
+
});
|
|
40074
|
+
server.registerTool('list_intents', {
|
|
40075
|
+
title: 'List Intents',
|
|
40076
|
+
description: 'List all intents in the workspace. Returns an array of IntentSpecs with their status, objectives, and metadata.',
|
|
40077
|
+
inputSchema: { status: zod_1.z.string().optional().describe('Filter by status: draft, validated, approved, shipped, verified') },
|
|
40078
|
+
annotations: READ_ONLY,
|
|
40079
|
+
}, async ({ status }) => {
|
|
40080
|
+
if (isLocalMode) {
|
|
40081
|
+
const intents = (0, local_reader_1.readLocalIntents)();
|
|
40082
|
+
const filtered = status ? intents.filter(i => i.status === status) : intents;
|
|
40083
|
+
return {
|
|
40084
|
+
content: [{
|
|
40085
|
+
type: 'text',
|
|
40086
|
+
text: JSON.stringify({ intents: filtered, count: filtered.length }, null, 2)
|
|
40087
|
+
}]
|
|
40088
|
+
};
|
|
40089
|
+
}
|
|
40090
|
+
const intents = await client.listIntents(status);
|
|
39813
40091
|
return {
|
|
39814
40092
|
content: [{
|
|
39815
40093
|
type: 'text',
|
|
39816
|
-
text: JSON.stringify({ intents
|
|
40094
|
+
text: JSON.stringify({ intents, count: intents.length }, null, 2)
|
|
39817
40095
|
}]
|
|
39818
40096
|
};
|
|
39819
|
-
}
|
|
39820
|
-
|
|
39821
|
-
|
|
39822
|
-
|
|
39823
|
-
|
|
39824
|
-
|
|
39825
|
-
|
|
39826
|
-
|
|
39827
|
-
|
|
39828
|
-
|
|
39829
|
-
|
|
39830
|
-
|
|
39831
|
-
|
|
39832
|
-
|
|
39833
|
-
}
|
|
39834
|
-
|
|
39835
|
-
|
|
39836
|
-
|
|
39837
|
-
|
|
39838
|
-
|
|
39839
|
-
|
|
39840
|
-
|
|
39841
|
-
}
|
|
39842
|
-
|
|
40097
|
+
});
|
|
40098
|
+
server.registerTool('get_intent', {
|
|
40099
|
+
title: 'Get Intent',
|
|
40100
|
+
description: 'Get a single intent by ID with full details including objective, outcomes, constraints, edge cases, and relations.',
|
|
40101
|
+
inputSchema: { intentId: zod_1.z.string().describe('The intent ID to fetch') },
|
|
40102
|
+
annotations: READ_ONLY,
|
|
40103
|
+
}, async ({ intentId }) => {
|
|
40104
|
+
if (isLocalMode) {
|
|
40105
|
+
const intents = (0, local_reader_1.readLocalIntents)();
|
|
40106
|
+
const intent = intents.find(i => i.id === intentId);
|
|
40107
|
+
if (!intent) {
|
|
40108
|
+
return { content: [{ type: 'text', text: `No intent found with ID "${intentId}" locally.` }] };
|
|
40109
|
+
}
|
|
40110
|
+
return { content: [{ type: 'text', text: JSON.stringify(intent, null, 2) }] };
|
|
40111
|
+
}
|
|
40112
|
+
try {
|
|
40113
|
+
const intent = await client.getIntent(intentId);
|
|
40114
|
+
return { content: [{ type: 'text', text: JSON.stringify(intent, null, 2) }] };
|
|
40115
|
+
}
|
|
40116
|
+
catch (e) {
|
|
40117
|
+
return { content: [{ type: 'text', text: `Failed to fetch intent: ${e.message}` }] };
|
|
40118
|
+
}
|
|
40119
|
+
});
|
|
40120
|
+
server.registerTool('get_intent_relations', {
|
|
40121
|
+
title: 'Get Intent Relations',
|
|
40122
|
+
description: 'Get the dependency graph for a specific intent. Shows what it depends on, enables, or blocks.',
|
|
40123
|
+
inputSchema: { intentId: zod_1.z.string().describe('The intent ID to get relations for') },
|
|
40124
|
+
annotations: READ_ONLY,
|
|
40125
|
+
}, async ({ intentId }) => {
|
|
40126
|
+
if (isLocalMode) {
|
|
40127
|
+
return { content: [{ type: 'text', text: 'Relations are not available in local mode.' }] };
|
|
40128
|
+
}
|
|
39843
40129
|
const intent = await client.getIntent(intentId);
|
|
39844
|
-
return {
|
|
39845
|
-
|
|
39846
|
-
|
|
39847
|
-
|
|
39848
|
-
|
|
39849
|
-
|
|
39850
|
-
|
|
39851
|
-
|
|
39852
|
-
|
|
39853
|
-
|
|
39854
|
-
|
|
39855
|
-
|
|
39856
|
-
|
|
39857
|
-
|
|
39858
|
-
|
|
39859
|
-
|
|
39860
|
-
|
|
39861
|
-
|
|
39862
|
-
|
|
39863
|
-
|
|
39864
|
-
|
|
39865
|
-
|
|
39866
|
-
|
|
39867
|
-
|
|
39868
|
-
|
|
39869
|
-
|
|
39870
|
-
});
|
|
39871
|
-
|
|
39872
|
-
title: 'Search Intents',
|
|
39873
|
-
description: 'Search intents by keyword. Searches across user goals, objectives, outcomes, and constraints.',
|
|
39874
|
-
inputSchema: {
|
|
39875
|
-
query: zod_1.z.string().describe('Search keyword or phrase'),
|
|
39876
|
-
status: zod_1.z.string().optional().describe('Filter by status: draft, validated, approved, shipped, verified'),
|
|
39877
|
-
},
|
|
39878
|
-
annotations: READ_ONLY,
|
|
39879
|
-
}, async ({ query, status }) => {
|
|
39880
|
-
if (isLocalMode) {
|
|
39881
|
-
const intents = (0, local_reader_1.readLocalIntents)();
|
|
39882
|
-
const q = query.toLowerCase();
|
|
39883
|
-
const matches = intents.filter(i => {
|
|
39884
|
-
const text = [i.title, i.objective, ...i.outcomes, ...i.constraints].join(' ').toLowerCase();
|
|
39885
|
-
return text.includes(q) && (!status || i.status === status);
|
|
39886
|
-
});
|
|
39887
|
-
return { content: [{ type: 'text', text: JSON.stringify({ results: matches, count: matches.length, query }, null, 2) }] };
|
|
39888
|
-
}
|
|
39889
|
-
try {
|
|
39890
|
-
const intents = await client.listIntents(status);
|
|
39891
|
-
const q = query.toLowerCase();
|
|
39892
|
-
const matches = intents.filter(i => {
|
|
39893
|
-
const text = [i.title, i.objective, ...(i.outcomes || []), ...(i.constraints || [])].join(' ').toLowerCase();
|
|
39894
|
-
return text.includes(q);
|
|
39895
|
-
});
|
|
39896
|
-
const results = matches.map(i => ({
|
|
39897
|
-
id: i.id,
|
|
39898
|
-
title: i.title,
|
|
39899
|
-
objective: i.objective,
|
|
39900
|
-
status: i.status,
|
|
39901
|
-
stageName: i.stageName,
|
|
39902
|
-
}));
|
|
39903
|
-
return { content: [{ type: 'text', text: JSON.stringify({ results, count: results.length, query }, null, 2) }] };
|
|
39904
|
-
}
|
|
39905
|
-
catch (e) {
|
|
39906
|
-
return { content: [{ type: 'text', text: `Search failed: ${e.message}` }] };
|
|
39907
|
-
}
|
|
39908
|
-
});
|
|
39909
|
-
server.registerTool('analyze_intent_graph', {
|
|
39910
|
-
title: 'Analyze Intent Graph',
|
|
39911
|
-
description: 'Analyze the intent dependency graph for risks and strategic insights. Returns critical path, cycles, bottlenecks, orphans, status mismatches, and stalled intents.',
|
|
39912
|
-
inputSchema: {
|
|
39913
|
-
analysis: zod_1.z.enum(['full', 'critical-path', 'risks', 'status']).optional()
|
|
39914
|
-
.describe('Type of analysis: full (default), critical-path, risks, or status distribution'),
|
|
39915
|
-
},
|
|
39916
|
-
annotations: READ_ONLY,
|
|
39917
|
-
}, async ({ analysis }) => {
|
|
39918
|
-
if (isLocalMode) {
|
|
39919
|
-
return { content: [{ type: 'text', text: 'Graph analysis requires cloud mode.' }] };
|
|
39920
|
-
}
|
|
39921
|
-
try {
|
|
39922
|
-
const intents = await client.listIntents();
|
|
39923
|
-
if (intents.length === 0) {
|
|
39924
|
-
return { content: [{ type: 'text', text: 'No intents found in workspace.' }] };
|
|
40130
|
+
return {
|
|
40131
|
+
content: [{
|
|
40132
|
+
type: 'text',
|
|
40133
|
+
text: JSON.stringify({
|
|
40134
|
+
intentId: intent.id,
|
|
40135
|
+
title: intent.title,
|
|
40136
|
+
relations: intent.relations,
|
|
40137
|
+
}, null, 2)
|
|
40138
|
+
}]
|
|
40139
|
+
};
|
|
40140
|
+
});
|
|
40141
|
+
server.registerTool('search_intents', {
|
|
40142
|
+
title: 'Search Intents',
|
|
40143
|
+
description: 'Search intents by keyword. Searches across user goals, objectives, outcomes, and constraints.',
|
|
40144
|
+
inputSchema: {
|
|
40145
|
+
query: zod_1.z.string().describe('Search keyword or phrase'),
|
|
40146
|
+
status: zod_1.z.string().optional().describe('Filter by status: draft, validated, approved, shipped, verified'),
|
|
40147
|
+
},
|
|
40148
|
+
annotations: READ_ONLY,
|
|
40149
|
+
}, async ({ query, status }) => {
|
|
40150
|
+
if (isLocalMode) {
|
|
40151
|
+
const intents = (0, local_reader_1.readLocalIntents)();
|
|
40152
|
+
const q = query.toLowerCase();
|
|
40153
|
+
const matches = intents.filter(i => {
|
|
40154
|
+
const text = [i.title, i.objective, ...i.outcomes, ...i.constraints].join(' ').toLowerCase();
|
|
40155
|
+
return text.includes(q) && (!status || i.status === status);
|
|
40156
|
+
});
|
|
40157
|
+
return { content: [{ type: 'text', text: JSON.stringify({ results: matches, count: matches.length, query }, null, 2) }] };
|
|
39925
40158
|
}
|
|
39926
|
-
|
|
39927
|
-
|
|
39928
|
-
|
|
39929
|
-
const
|
|
39930
|
-
|
|
39931
|
-
|
|
39932
|
-
|
|
39933
|
-
|
|
39934
|
-
|
|
39935
|
-
|
|
39936
|
-
|
|
39937
|
-
|
|
39938
|
-
|
|
39939
|
-
|
|
39940
|
-
|
|
39941
|
-
}
|
|
39942
|
-
|
|
39943
|
-
|
|
39944
|
-
|
|
39945
|
-
|
|
39946
|
-
|
|
39947
|
-
|
|
39948
|
-
|
|
39949
|
-
|
|
39950
|
-
|
|
39951
|
-
|
|
39952
|
-
|
|
39953
|
-
|
|
39954
|
-
|
|
39955
|
-
|
|
39956
|
-
|
|
39957
|
-
|
|
39958
|
-
|
|
39959
|
-
|
|
39960
|
-
|
|
39961
|
-
|
|
39962
|
-
|
|
39963
|
-
|
|
39964
|
-
|
|
39965
|
-
|
|
39966
|
-
|
|
39967
|
-
|
|
39968
|
-
|
|
39969
|
-
|
|
39970
|
-
|
|
39971
|
-
|
|
39972
|
-
|
|
39973
|
-
|
|
39974
|
-
|
|
39975
|
-
|
|
39976
|
-
|
|
40159
|
+
try {
|
|
40160
|
+
const intents = await client.listIntents(status);
|
|
40161
|
+
const q = query.toLowerCase();
|
|
40162
|
+
const matches = intents.filter(i => {
|
|
40163
|
+
const text = [i.title, i.objective, ...(i.outcomes || []), ...(i.constraints || [])].join(' ').toLowerCase();
|
|
40164
|
+
return text.includes(q);
|
|
40165
|
+
});
|
|
40166
|
+
const results = matches.map(i => ({
|
|
40167
|
+
id: i.id,
|
|
40168
|
+
title: i.title,
|
|
40169
|
+
objective: i.objective,
|
|
40170
|
+
status: i.status,
|
|
40171
|
+
stageName: i.stageName,
|
|
40172
|
+
}));
|
|
40173
|
+
return { content: [{ type: 'text', text: JSON.stringify({ results, count: results.length, query }, null, 2) }] };
|
|
40174
|
+
}
|
|
40175
|
+
catch (e) {
|
|
40176
|
+
return { content: [{ type: 'text', text: `Search failed: ${e.message}` }] };
|
|
40177
|
+
}
|
|
40178
|
+
});
|
|
40179
|
+
server.registerTool('analyze_intent_graph', {
|
|
40180
|
+
title: 'Analyze Intent Graph',
|
|
40181
|
+
description: 'Analyze the intent dependency graph for risks and strategic insights. Returns critical path, cycles, bottlenecks, orphans, status mismatches, and stalled intents.',
|
|
40182
|
+
inputSchema: {
|
|
40183
|
+
analysis: zod_1.z.enum(['full', 'critical-path', 'risks', 'status']).optional()
|
|
40184
|
+
.describe('Type of analysis: full (default), critical-path, risks, or status distribution'),
|
|
40185
|
+
},
|
|
40186
|
+
annotations: READ_ONLY,
|
|
40187
|
+
}, async ({ analysis }) => {
|
|
40188
|
+
if (isLocalMode) {
|
|
40189
|
+
return { content: [{ type: 'text', text: 'Graph analysis requires cloud mode.' }] };
|
|
40190
|
+
}
|
|
40191
|
+
try {
|
|
40192
|
+
const intents = await client.listIntents();
|
|
40193
|
+
if (intents.length === 0) {
|
|
40194
|
+
return { content: [{ type: 'text', text: 'No intents found in workspace.' }] };
|
|
40195
|
+
}
|
|
40196
|
+
const specMap = new Map(intents.map(i => [i.id, i]));
|
|
40197
|
+
const type = analysis || 'full';
|
|
40198
|
+
if (type === 'status') {
|
|
40199
|
+
const dist = { draft: 0, validated: 0, approved: 0, shipped: 0, verified: 0 };
|
|
40200
|
+
for (const i of intents)
|
|
40201
|
+
dist[i.status] = (dist[i.status] || 0) + 1;
|
|
40202
|
+
return { content: [{ type: 'text', text: JSON.stringify({ statusDistribution: dist, total: intents.length }, null, 2) }] };
|
|
40203
|
+
}
|
|
40204
|
+
// Build dependency graph
|
|
40205
|
+
const specIds = new Set(intents.map(i => i.id));
|
|
40206
|
+
const forward = new Map();
|
|
40207
|
+
const reverse = new Map();
|
|
40208
|
+
for (const id of specIds) {
|
|
40209
|
+
forward.set(id, new Set());
|
|
40210
|
+
reverse.set(id, new Set());
|
|
40211
|
+
}
|
|
40212
|
+
for (const intent of intents) {
|
|
40213
|
+
for (const rel of intent.relations || []) {
|
|
40214
|
+
if (rel.type === 'depends_on' && specIds.has(rel.targetId)) {
|
|
40215
|
+
forward.get(intent.id).add(rel.targetId);
|
|
40216
|
+
reverse.get(rel.targetId).add(intent.id);
|
|
40217
|
+
}
|
|
40218
|
+
}
|
|
40219
|
+
}
|
|
40220
|
+
// Detect cycles
|
|
40221
|
+
const WHITE = 0, GRAY = 1, BLACK = 2;
|
|
40222
|
+
const color = new Map();
|
|
40223
|
+
const parent = new Map();
|
|
40224
|
+
const cycles = [];
|
|
40225
|
+
for (const id of forward.keys())
|
|
40226
|
+
color.set(id, WHITE);
|
|
40227
|
+
for (const startId of forward.keys()) {
|
|
40228
|
+
if (color.get(startId) !== WHITE)
|
|
40229
|
+
continue;
|
|
40230
|
+
const stack = [startId];
|
|
40231
|
+
parent.set(startId, null);
|
|
40232
|
+
while (stack.length > 0) {
|
|
40233
|
+
const id = stack[stack.length - 1];
|
|
40234
|
+
if (color.get(id) === WHITE) {
|
|
40235
|
+
color.set(id, GRAY);
|
|
40236
|
+
for (const dep of forward.get(id) || new Set()) {
|
|
40237
|
+
if (color.get(dep) === WHITE) {
|
|
40238
|
+
parent.set(dep, id);
|
|
40239
|
+
stack.push(dep);
|
|
40240
|
+
}
|
|
40241
|
+
else if (color.get(dep) === GRAY) {
|
|
40242
|
+
const cycle = [dep];
|
|
40243
|
+
let cur = id;
|
|
40244
|
+
while (cur !== dep) {
|
|
40245
|
+
cycle.push(cur);
|
|
40246
|
+
cur = parent.get(cur);
|
|
40247
|
+
}
|
|
40248
|
+
cycle.push(dep);
|
|
40249
|
+
cycle.reverse();
|
|
40250
|
+
cycles.push(cycle);
|
|
39977
40251
|
}
|
|
39978
|
-
cycle.push(dep);
|
|
39979
|
-
cycle.reverse();
|
|
39980
|
-
cycles.push(cycle);
|
|
39981
40252
|
}
|
|
39982
40253
|
}
|
|
40254
|
+
else {
|
|
40255
|
+
color.set(id, BLACK);
|
|
40256
|
+
stack.pop();
|
|
40257
|
+
}
|
|
39983
40258
|
}
|
|
39984
|
-
|
|
39985
|
-
|
|
39986
|
-
|
|
40259
|
+
}
|
|
40260
|
+
// Critical path (longest path via topo sort)
|
|
40261
|
+
let criticalPath = [];
|
|
40262
|
+
if (cycles.length === 0) {
|
|
40263
|
+
const topoInDegree = new Map();
|
|
40264
|
+
for (const id of specIds)
|
|
40265
|
+
topoInDegree.set(id, (forward.get(id) || new Set()).size);
|
|
40266
|
+
const queue = [];
|
|
40267
|
+
for (const [id, deg] of topoInDegree) {
|
|
40268
|
+
if (deg === 0)
|
|
40269
|
+
queue.push(id);
|
|
40270
|
+
}
|
|
40271
|
+
const dist = new Map();
|
|
40272
|
+
const prev = new Map();
|
|
40273
|
+
for (const id of specIds) {
|
|
40274
|
+
dist.set(id, 1);
|
|
40275
|
+
prev.set(id, null);
|
|
40276
|
+
}
|
|
40277
|
+
while (queue.length > 0) {
|
|
40278
|
+
const id = queue.shift();
|
|
40279
|
+
for (const dep of reverse.get(id) || new Set()) {
|
|
40280
|
+
const newDist = dist.get(id) + 1;
|
|
40281
|
+
if (newDist > dist.get(dep)) {
|
|
40282
|
+
dist.set(dep, newDist);
|
|
40283
|
+
prev.set(dep, id);
|
|
40284
|
+
}
|
|
40285
|
+
topoInDegree.set(dep, topoInDegree.get(dep) - 1);
|
|
40286
|
+
if (topoInDegree.get(dep) === 0)
|
|
40287
|
+
queue.push(dep);
|
|
40288
|
+
}
|
|
40289
|
+
}
|
|
40290
|
+
let maxDist = 0, endNode = null;
|
|
40291
|
+
for (const [id, d] of dist) {
|
|
40292
|
+
if (d > maxDist) {
|
|
40293
|
+
maxDist = d;
|
|
40294
|
+
endNode = id;
|
|
40295
|
+
}
|
|
40296
|
+
}
|
|
40297
|
+
if (endNode) {
|
|
40298
|
+
let cur = endNode;
|
|
40299
|
+
while (cur) {
|
|
40300
|
+
criticalPath.push(cur);
|
|
40301
|
+
cur = prev.get(cur) || null;
|
|
40302
|
+
}
|
|
40303
|
+
criticalPath.reverse();
|
|
39987
40304
|
}
|
|
39988
40305
|
}
|
|
39989
|
-
|
|
39990
|
-
|
|
39991
|
-
|
|
39992
|
-
|
|
39993
|
-
|
|
39994
|
-
|
|
39995
|
-
|
|
39996
|
-
const queue = [];
|
|
39997
|
-
for (const [id, deg] of topoInDegree) {
|
|
39998
|
-
if (deg === 0)
|
|
39999
|
-
queue.push(id);
|
|
40306
|
+
// Bottlenecks
|
|
40307
|
+
const bottlenecks = [];
|
|
40308
|
+
for (const [id, dependents] of reverse) {
|
|
40309
|
+
if (dependents.size >= 3) {
|
|
40310
|
+
const spec = specMap.get(id);
|
|
40311
|
+
bottlenecks.push({ id, title: spec?.title || 'Untitled', dependentCount: dependents.size, status: spec?.status || 'unknown' });
|
|
40312
|
+
}
|
|
40000
40313
|
}
|
|
40001
|
-
|
|
40002
|
-
const
|
|
40003
|
-
for (const
|
|
40004
|
-
|
|
40005
|
-
|
|
40006
|
-
|
|
40007
|
-
|
|
40008
|
-
|
|
40009
|
-
|
|
40010
|
-
|
|
40011
|
-
|
|
40012
|
-
|
|
40013
|
-
|
|
40014
|
-
|
|
40015
|
-
|
|
40016
|
-
|
|
40017
|
-
|
|
40018
|
-
}
|
|
40019
|
-
}
|
|
40020
|
-
|
|
40021
|
-
|
|
40022
|
-
|
|
40023
|
-
|
|
40024
|
-
|
|
40025
|
-
}
|
|
40026
|
-
|
|
40027
|
-
|
|
40028
|
-
|
|
40029
|
-
|
|
40030
|
-
|
|
40031
|
-
|
|
40032
|
-
|
|
40033
|
-
|
|
40034
|
-
|
|
40035
|
-
|
|
40036
|
-
|
|
40037
|
-
|
|
40038
|
-
|
|
40039
|
-
|
|
40040
|
-
|
|
40041
|
-
|
|
40042
|
-
}
|
|
40043
|
-
|
|
40044
|
-
// Orphans
|
|
40045
|
-
const orphans = [];
|
|
40046
|
-
for (const intent of intents) {
|
|
40047
|
-
const hasRelations = intent.relations && intent.relations.length > 0;
|
|
40048
|
-
const isTargeted = intents.some(i => i.relations?.some(r => r.targetId === intent.id));
|
|
40049
|
-
if (!hasRelations && !isTargeted)
|
|
40050
|
-
orphans.push(intent.id);
|
|
40051
|
-
}
|
|
40052
|
-
// Status distribution
|
|
40053
|
-
const statusDist = { draft: 0, validated: 0, approved: 0, shipped: 0, verified: 0 };
|
|
40054
|
-
for (const i of intents)
|
|
40055
|
-
statusDist[i.status] = (statusDist[i.status] || 0) + 1;
|
|
40056
|
-
if (type === 'critical-path') {
|
|
40057
|
-
const pathDetails = criticalPath.map(id => {
|
|
40058
|
-
const s = specMap.get(id);
|
|
40059
|
-
return { id, title: s?.title || 'Untitled', status: s?.status || 'unknown' };
|
|
40060
|
-
});
|
|
40061
|
-
return { content: [{ type: 'text', text: JSON.stringify({ criticalPath: pathDetails, length: criticalPath.length }, null, 2) }] };
|
|
40062
|
-
}
|
|
40063
|
-
if (type === 'risks') {
|
|
40064
|
-
const risks = [];
|
|
40065
|
-
for (const cycle of cycles) {
|
|
40066
|
-
const names = cycle.slice(0, -1).map(id => specMap.get(id)?.title || 'Untitled');
|
|
40067
|
-
risks.push({ type: 'cycle', severity: 'critical', message: `Circular dependency: ${names.join(' \u2192 ')}` });
|
|
40068
|
-
}
|
|
40069
|
-
for (const b of bottlenecks) {
|
|
40070
|
-
const isDraft = b.status === 'draft' || b.status === 'validated';
|
|
40071
|
-
risks.push({ type: 'bottleneck', severity: isDraft ? 'critical' : 'warning', message: `"${b.title}" blocks ${b.dependentCount} intents${isDraft ? ` and is still ${b.status}` : ''}` });
|
|
40072
|
-
}
|
|
40073
|
-
return { content: [{ type: 'text', text: JSON.stringify({ risks }, null, 2) }] };
|
|
40074
|
-
}
|
|
40075
|
-
// Full analysis
|
|
40076
|
-
const result = {
|
|
40077
|
-
summary: { total: intents.length, statusDistribution: statusDist },
|
|
40078
|
-
criticalPath: criticalPath.map(id => {
|
|
40079
|
-
const s = specMap.get(id);
|
|
40080
|
-
return { id, title: s?.title || 'Untitled', status: s?.status || 'unknown' };
|
|
40081
|
-
}),
|
|
40082
|
-
cycles: cycles.map(c => c.slice(0, -1).map(id => ({ id, title: specMap.get(id)?.title || 'Untitled' }))),
|
|
40083
|
-
bottlenecks,
|
|
40084
|
-
orphanCount: orphans.length,
|
|
40085
|
-
};
|
|
40086
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
40087
|
-
}
|
|
40088
|
-
catch (e) {
|
|
40089
|
-
return { content: [{ type: 'text', text: `Graph analysis failed: ${e.message}` }] };
|
|
40090
|
-
}
|
|
40091
|
-
});
|
|
40092
|
-
server.registerTool('export_context', {
|
|
40093
|
-
title: 'Export Context',
|
|
40094
|
-
description: 'Export workspace context as a formatted file. Use "claude-md" for CLAUDE.md (full workspace context), "cursorrules" for Cursor AI rules, or "intent-md" for a single intent specification file.',
|
|
40095
|
-
inputSchema: {
|
|
40096
|
-
format: zod_1.z.enum(['claude-md', 'cursorrules', 'intent-md']).describe('Export format'),
|
|
40097
|
-
intentId: zod_1.z.string().optional().describe('Intent ID (optional, for cursorrules and intent-md)'),
|
|
40098
|
-
},
|
|
40099
|
-
annotations: READ_ONLY,
|
|
40100
|
-
}, async ({ format, intentId }) => {
|
|
40101
|
-
if (isLocalMode) {
|
|
40102
|
-
return { content: [{ type: 'text', text: 'Export requires cloud mode. Use PATHMODE_API_KEY to connect.' }] };
|
|
40103
|
-
}
|
|
40104
|
-
try {
|
|
40105
|
-
const content = await client.exportContext(format, intentId);
|
|
40106
|
-
return { content: [{ type: 'text', text: content }] };
|
|
40107
|
-
}
|
|
40108
|
-
catch (e) {
|
|
40109
|
-
return { content: [{ type: 'text', text: `Export failed: ${e.message}` }] };
|
|
40110
|
-
}
|
|
40111
|
-
});
|
|
40112
|
-
server.registerTool('get_agent_prompt', {
|
|
40113
|
-
title: 'Get Agent Prompt',
|
|
40114
|
-
description: 'Get a formatted execution prompt for a specific intent. This is the full structured prompt including objective, outcomes, constraints, edge cases, and verification steps.',
|
|
40115
|
-
inputSchema: {
|
|
40116
|
-
intentId: zod_1.z.string().describe('The intent ID to generate a prompt for'),
|
|
40117
|
-
mode: zod_1.z.enum(['draft', 'execute']).optional().describe('draft = critique the spec, execute = implement it'),
|
|
40118
|
-
},
|
|
40119
|
-
annotations: READ_ONLY,
|
|
40120
|
-
}, async ({ intentId, mode }) => {
|
|
40121
|
-
if (isLocalMode) {
|
|
40122
|
-
return { content: [{ type: 'text', text: 'Agent prompts require cloud mode for full context generation.' }] };
|
|
40123
|
-
}
|
|
40124
|
-
const result = await client.getIntentPrompt(intentId, 'claude-code', mode || 'execute');
|
|
40125
|
-
return {
|
|
40126
|
-
content: [{
|
|
40127
|
-
type: 'text',
|
|
40128
|
-
text: result.prompt
|
|
40129
|
-
}]
|
|
40130
|
-
};
|
|
40131
|
-
});
|
|
40132
|
-
server.registerTool('get_workspace', {
|
|
40133
|
-
title: 'Get Workspace',
|
|
40134
|
-
description: 'Get workspace details including strategy (vision, non-negotiables, architecture principles) and constitution rules.',
|
|
40135
|
-
annotations: READ_ONLY,
|
|
40136
|
-
}, async () => {
|
|
40137
|
-
if (isLocalMode) {
|
|
40138
|
-
return { content: [{ type: 'text', text: 'Workspace details are not available in local mode.' }] };
|
|
40139
|
-
}
|
|
40140
|
-
const workspace = await client.getWorkspace();
|
|
40141
|
-
return {
|
|
40142
|
-
content: [{
|
|
40143
|
-
type: 'text',
|
|
40144
|
-
text: JSON.stringify(workspace, null, 2)
|
|
40145
|
-
}]
|
|
40146
|
-
};
|
|
40147
|
-
});
|
|
40148
|
-
server.registerTool('get_constitution', {
|
|
40149
|
-
title: 'Get Constitution',
|
|
40150
|
-
description: 'Get the workspace constitution rules. These are mandatory constraints that all implementations must respect.',
|
|
40151
|
-
annotations: READ_ONLY,
|
|
40152
|
-
}, async () => {
|
|
40153
|
-
if (isLocalMode) {
|
|
40154
|
-
return { content: [{ type: 'text', text: 'Constitution rules are not available in local mode.' }] };
|
|
40155
|
-
}
|
|
40156
|
-
const result = await client.getConstitution();
|
|
40157
|
-
return {
|
|
40158
|
-
content: [{
|
|
40159
|
-
type: 'text',
|
|
40160
|
-
text: JSON.stringify(result, null, 2)
|
|
40161
|
-
}]
|
|
40162
|
-
};
|
|
40163
|
-
});
|
|
40164
|
-
// ============================================================
|
|
40165
|
-
// Tools — Write Operations
|
|
40166
|
-
// ============================================================
|
|
40167
|
-
server.registerTool('update_intent_status', {
|
|
40168
|
-
title: 'Update Intent Status',
|
|
40169
|
-
description: 'Update the status of an intent. Use this to mark an intent as shipped after implementation, or verified after testing. For shipped/verified transitions, the response includes a verification checklist of outcomes, constitution rules, and health metrics that should be confirmed.',
|
|
40170
|
-
inputSchema: {
|
|
40171
|
-
intentId: zod_1.z.string().describe('The intent ID to update'),
|
|
40172
|
-
status: zod_1.z.enum(['draft', 'validated', 'approved', 'shipped', 'verified']).describe('The new status'),
|
|
40173
|
-
},
|
|
40174
|
-
annotations: { ...WRITE_OP, idempotentHint: true },
|
|
40175
|
-
}, async ({ intentId, status }) => {
|
|
40176
|
-
if (isLocalMode) {
|
|
40177
|
-
return { content: [{ type: 'text', text: 'Status updates are not available in local mode. Use cloud mode.' }] };
|
|
40178
|
-
}
|
|
40179
|
-
const result = await client.updateIntentStatus(intentId, status);
|
|
40180
|
-
let responseText = `Intent ${intentId} status updated to "${status}".`;
|
|
40181
|
-
// Surface verification checklist for shipped/verified transitions
|
|
40182
|
-
if (result.verificationChecklist && result.verificationChecklist.length > 0) {
|
|
40183
|
-
responseText += `\n\nVerification Checklist (${result.verificationChecklistCount} items to verify):`;
|
|
40184
|
-
for (const item of result.verificationChecklist) {
|
|
40185
|
-
responseText += `\n [ ] [${item.category}] ${item.text}`;
|
|
40314
|
+
// Orphans
|
|
40315
|
+
const orphans = [];
|
|
40316
|
+
for (const intent of intents) {
|
|
40317
|
+
const hasRelations = intent.relations && intent.relations.length > 0;
|
|
40318
|
+
const isTargeted = intents.some(i => i.relations?.some(r => r.targetId === intent.id));
|
|
40319
|
+
if (!hasRelations && !isTargeted)
|
|
40320
|
+
orphans.push(intent.id);
|
|
40321
|
+
}
|
|
40322
|
+
// Status distribution
|
|
40323
|
+
const statusDist = { draft: 0, validated: 0, approved: 0, shipped: 0, verified: 0 };
|
|
40324
|
+
for (const i of intents)
|
|
40325
|
+
statusDist[i.status] = (statusDist[i.status] || 0) + 1;
|
|
40326
|
+
if (type === 'critical-path') {
|
|
40327
|
+
const pathDetails = criticalPath.map(id => {
|
|
40328
|
+
const s = specMap.get(id);
|
|
40329
|
+
return { id, title: s?.title || 'Untitled', status: s?.status || 'unknown' };
|
|
40330
|
+
});
|
|
40331
|
+
return { content: [{ type: 'text', text: JSON.stringify({ criticalPath: pathDetails, length: criticalPath.length }, null, 2) }] };
|
|
40332
|
+
}
|
|
40333
|
+
if (type === 'risks') {
|
|
40334
|
+
const risks = [];
|
|
40335
|
+
for (const cycle of cycles) {
|
|
40336
|
+
const names = cycle.slice(0, -1).map(id => specMap.get(id)?.title || 'Untitled');
|
|
40337
|
+
risks.push({ type: 'cycle', severity: 'critical', message: `Circular dependency: ${names.join(' \u2192 ')}` });
|
|
40338
|
+
}
|
|
40339
|
+
for (const b of bottlenecks) {
|
|
40340
|
+
const isDraft = b.status === 'draft' || b.status === 'validated';
|
|
40341
|
+
risks.push({ type: 'bottleneck', severity: isDraft ? 'critical' : 'warning', message: `"${b.title}" blocks ${b.dependentCount} intents${isDraft ? ` and is still ${b.status}` : ''}` });
|
|
40342
|
+
}
|
|
40343
|
+
return { content: [{ type: 'text', text: JSON.stringify({ risks }, null, 2) }] };
|
|
40344
|
+
}
|
|
40345
|
+
// Full analysis
|
|
40346
|
+
const result = {
|
|
40347
|
+
summary: { total: intents.length, statusDistribution: statusDist },
|
|
40348
|
+
criticalPath: criticalPath.map(id => {
|
|
40349
|
+
const s = specMap.get(id);
|
|
40350
|
+
return { id, title: s?.title || 'Untitled', status: s?.status || 'unknown' };
|
|
40351
|
+
}),
|
|
40352
|
+
cycles: cycles.map(c => c.slice(0, -1).map(id => ({ id, title: specMap.get(id)?.title || 'Untitled' }))),
|
|
40353
|
+
bottlenecks,
|
|
40354
|
+
orphanCount: orphans.length,
|
|
40355
|
+
};
|
|
40356
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
40186
40357
|
}
|
|
40187
|
-
|
|
40188
|
-
|
|
40189
|
-
return {
|
|
40190
|
-
content: [{
|
|
40191
|
-
type: 'text',
|
|
40192
|
-
text: responseText
|
|
40193
|
-
}]
|
|
40194
|
-
};
|
|
40195
|
-
});
|
|
40196
|
-
server.registerTool('log_implementation_note', {
|
|
40197
|
-
title: 'Log Implementation Note',
|
|
40198
|
-
description: 'Record a technical decision or implementation note for an intent. Use this to document why you chose a specific approach.',
|
|
40199
|
-
inputSchema: {
|
|
40200
|
-
intentId: zod_1.z.string().describe('The intent ID to attach the note to'),
|
|
40201
|
-
note: zod_1.z.string().describe('The implementation note or technical decision'),
|
|
40202
|
-
},
|
|
40203
|
-
annotations: WRITE_OP,
|
|
40204
|
-
}, async ({ intentId, note }) => {
|
|
40205
|
-
if (isLocalMode) {
|
|
40206
|
-
return { content: [{ type: 'text', text: 'Notes are not available in local mode. Use cloud mode.' }] };
|
|
40207
|
-
}
|
|
40208
|
-
const result = await client.logNote(intentId, note, 'mcp');
|
|
40209
|
-
return {
|
|
40210
|
-
content: [{
|
|
40211
|
-
type: 'text',
|
|
40212
|
-
text: `Note logged for intent ${intentId}: "${note}"`
|
|
40213
|
-
}]
|
|
40214
|
-
};
|
|
40215
|
-
});
|
|
40216
|
-
server.registerTool('create_intent', {
|
|
40217
|
-
title: 'Create Intent',
|
|
40218
|
-
description: 'Create a new intent spec in the workspace. Requires at minimum a title, objective, and productId. Returns the created intent with its ID. Use list_intents first to see existing intents and avoid duplicates.',
|
|
40219
|
-
inputSchema: {
|
|
40220
|
-
title: zod_1.z.string().describe('Short name for the intent (e.g., "Improve onboarding flow")'),
|
|
40221
|
-
objective: zod_1.z.string().describe('Why this matters — the problem and who has it'),
|
|
40222
|
-
productId: zod_1.z.string().describe('Product (Space) ID this intent belongs to. Use get_workspace to find product IDs.'),
|
|
40223
|
-
outcomes: zod_1.z.array(zod_1.z.string()).optional().describe('Observable, testable state changes'),
|
|
40224
|
-
constraints: zod_1.z.array(zod_1.z.string()).optional().describe('Hard limits the implementation must respect'),
|
|
40225
|
-
healthMetrics: zod_1.z.array(zod_1.z.string()).optional().describe('What to monitor after shipping'),
|
|
40226
|
-
edgeCases: zod_1.z.array(zod_1.z.object({
|
|
40227
|
-
scenario: zod_1.z.string(),
|
|
40228
|
-
expectedBehavior: zod_1.z.string(),
|
|
40229
|
-
})).optional().describe('Failure modes and boundary conditions'),
|
|
40230
|
-
verification: zod_1.z.object({
|
|
40231
|
-
manualChecks: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40232
|
-
unitTests: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40233
|
-
e2eTests: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40234
|
-
}).optional().describe('How to confirm it works'),
|
|
40235
|
-
problemSeverity: zod_1.z.enum(['low', 'medium', 'high', 'critical']).optional().describe('How severe the problem is'),
|
|
40236
|
-
},
|
|
40237
|
-
annotations: WRITE_OP,
|
|
40238
|
-
}, async ({ productId, ...rest }) => {
|
|
40239
|
-
if (isLocalMode) {
|
|
40240
|
-
return { content: [{ type: 'text', text: 'Creating intents requires cloud mode. Use PATHMODE_API_KEY to connect.' }] };
|
|
40241
|
-
}
|
|
40242
|
-
const result = await client.createIntent({ productId, ...rest });
|
|
40243
|
-
return {
|
|
40244
|
-
content: [{
|
|
40245
|
-
type: 'text',
|
|
40246
|
-
text: `Intent created: ${result.id}\nTitle: "${result.title}"\nStatus: ${result.status}\nProduct: ${result.productId || productId}`
|
|
40247
|
-
}]
|
|
40248
|
-
};
|
|
40249
|
-
});
|
|
40250
|
-
server.registerTool('update_intent', {
|
|
40251
|
-
title: 'Update Intent',
|
|
40252
|
-
description: "Update an existing intent's content. Provide only the fields you want to change. Does NOT change intent status (use update_intent_status for that).",
|
|
40253
|
-
inputSchema: {
|
|
40254
|
-
intentId: zod_1.z.string().describe('The intent ID to update'),
|
|
40255
|
-
title: zod_1.z.string().optional().describe('New title'),
|
|
40256
|
-
objective: zod_1.z.string().optional().describe('Updated objective'),
|
|
40257
|
-
outcomes: zod_1.z.array(zod_1.z.string()).optional().describe('Replace all outcomes'),
|
|
40258
|
-
constraints: zod_1.z.array(zod_1.z.string()).optional().describe('Replace all constraints'),
|
|
40259
|
-
healthMetrics: zod_1.z.array(zod_1.z.string()).optional().describe('Replace all health metrics'),
|
|
40260
|
-
edgeCases: zod_1.z.array(zod_1.z.object({
|
|
40261
|
-
scenario: zod_1.z.string(),
|
|
40262
|
-
expectedBehavior: zod_1.z.string(),
|
|
40263
|
-
})).optional().describe('Replace all edge cases'),
|
|
40264
|
-
verification: zod_1.z.object({
|
|
40265
|
-
manualChecks: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40266
|
-
unitTests: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40267
|
-
e2eTests: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40268
|
-
}).optional().describe('Replace verification plan'),
|
|
40269
|
-
problemSeverity: zod_1.z.enum(['low', 'medium', 'high', 'critical']).optional(),
|
|
40270
|
-
},
|
|
40271
|
-
annotations: { ...WRITE_OP, idempotentHint: true },
|
|
40272
|
-
}, async ({ intentId, ...updates }) => {
|
|
40273
|
-
if (isLocalMode) {
|
|
40274
|
-
return { content: [{ type: 'text', text: 'Updating intents requires cloud mode.' }] };
|
|
40275
|
-
}
|
|
40276
|
-
const result = await client.updateIntent(intentId, updates);
|
|
40277
|
-
const changedFields = Object.keys(updates).filter(k => updates[k] !== undefined);
|
|
40278
|
-
return {
|
|
40279
|
-
content: [{
|
|
40280
|
-
type: 'text',
|
|
40281
|
-
text: `Intent ${intentId} updated. Fields changed: ${changedFields.join(', ')}`
|
|
40282
|
-
}]
|
|
40283
|
-
};
|
|
40284
|
-
});
|
|
40285
|
-
server.registerTool('query_evidence', {
|
|
40286
|
-
title: 'Query Evidence',
|
|
40287
|
-
description: 'Search evidence items (friction points, user quotes, observations, metrics, feature requests) by product, type, severity, or text. Returns matching evidence with IDs that can be linked to intents.',
|
|
40288
|
-
inputSchema: {
|
|
40289
|
-
productId: zod_1.z.string().optional().describe('Filter by product (Space) ID'),
|
|
40290
|
-
type: zod_1.z.enum(['friction', 'quote', 'observation', 'metric', 'request']).optional().describe('Filter by evidence type'),
|
|
40291
|
-
severity: zod_1.z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Filter by severity'),
|
|
40292
|
-
search: zod_1.z.string().optional().describe('Text search across evidence content'),
|
|
40293
|
-
tags: zod_1.z.string().optional().describe('Comma-separated tags to filter by'),
|
|
40294
|
-
limit: zod_1.z.number().optional().describe('Max results (default 50, max 200)'),
|
|
40295
|
-
},
|
|
40296
|
-
annotations: READ_ONLY,
|
|
40297
|
-
}, async (filters) => {
|
|
40298
|
-
if (isLocalMode) {
|
|
40299
|
-
return { content: [{ type: 'text', text: 'Evidence queries require cloud mode.' }] };
|
|
40300
|
-
}
|
|
40301
|
-
const result = await client.queryEvidence(filters);
|
|
40302
|
-
return {
|
|
40303
|
-
content: [{
|
|
40304
|
-
type: 'text',
|
|
40305
|
-
text: JSON.stringify(result, null, 2)
|
|
40306
|
-
}]
|
|
40307
|
-
};
|
|
40308
|
-
});
|
|
40309
|
-
server.registerTool('create_evidence', {
|
|
40310
|
-
title: 'Create Evidence',
|
|
40311
|
-
description: 'Create a new evidence item (e.g., a discovered bug, user feedback quote, behavioral observation, or feature request). Evidence can later be linked to intents to support prioritization.',
|
|
40312
|
-
inputSchema: {
|
|
40313
|
-
content: zod_1.z.string().describe('The evidence content — a finding, quote, or observation'),
|
|
40314
|
-
type: zod_1.z.enum(['friction', 'quote', 'observation', 'metric', 'request']).describe('Type of evidence'),
|
|
40315
|
-
productId: zod_1.z.string().describe('Product (Space) ID this evidence belongs to'),
|
|
40316
|
-
source: zod_1.z.string().optional().describe('Where this evidence came from (e.g., "User interview", "Bug report", "Support ticket")'),
|
|
40317
|
-
sourceUrl: zod_1.z.string().optional().describe('URL to the original source'),
|
|
40318
|
-
severity: zod_1.z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Severity level (required for friction type)'),
|
|
40319
|
-
sentiment: zod_1.z.enum(['positive', 'negative', 'neutral', 'mixed']).optional().describe('Emotional sentiment'),
|
|
40320
|
-
tags: zod_1.z.array(zod_1.z.string()).optional().describe('Category tags (e.g., ["Onboarding", "Performance"])'),
|
|
40321
|
-
stage: zod_1.z.string().optional().describe('User journey stage (e.g., "Discovery", "Checkout")'),
|
|
40322
|
-
},
|
|
40323
|
-
annotations: WRITE_OP,
|
|
40324
|
-
}, async ({ productId, ...rest }) => {
|
|
40325
|
-
if (isLocalMode) {
|
|
40326
|
-
return { content: [{ type: 'text', text: 'Creating evidence requires cloud mode.' }] };
|
|
40327
|
-
}
|
|
40328
|
-
const result = await client.createEvidence({ productId, ...rest });
|
|
40329
|
-
return {
|
|
40330
|
-
content: [{
|
|
40331
|
-
type: 'text',
|
|
40332
|
-
text: `Evidence created: ${result.id}\nType: ${result.type}\nContent: "${(result.content || '').slice(0, 80)}${(result.content || '').length > 80 ? '...' : ''}"`
|
|
40333
|
-
}]
|
|
40334
|
-
};
|
|
40335
|
-
});
|
|
40336
|
-
server.registerTool('link_evidence', {
|
|
40337
|
-
title: 'Link Evidence to Intent',
|
|
40338
|
-
description: 'Link or unlink evidence items to/from an intent. Linking evidence to intents establishes traceability between user problems and planned solutions.',
|
|
40339
|
-
inputSchema: {
|
|
40340
|
-
intentId: zod_1.z.string().describe('The intent ID to link evidence to'),
|
|
40341
|
-
link: zod_1.z.array(zod_1.z.string()).optional().describe('Evidence IDs to link to this intent'),
|
|
40342
|
-
unlink: zod_1.z.array(zod_1.z.string()).optional().describe('Evidence IDs to unlink from this intent'),
|
|
40343
|
-
},
|
|
40344
|
-
annotations: WRITE_OP,
|
|
40345
|
-
}, async ({ intentId, link, unlink }) => {
|
|
40346
|
-
if (isLocalMode) {
|
|
40347
|
-
return { content: [{ type: 'text', text: 'Evidence linking requires cloud mode.' }] };
|
|
40348
|
-
}
|
|
40349
|
-
const result = await client.linkEvidence(intentId, { link, unlink });
|
|
40350
|
-
const actions = [];
|
|
40351
|
-
if (link && link.length > 0)
|
|
40352
|
-
actions.push(`linked ${result.linked} evidence items`);
|
|
40353
|
-
if (unlink && unlink.length > 0)
|
|
40354
|
-
actions.push(`unlinked ${result.unlinked} evidence items`);
|
|
40355
|
-
return {
|
|
40356
|
-
content: [{
|
|
40357
|
-
type: 'text',
|
|
40358
|
-
text: `Intent ${intentId}: ${actions.join(', ')}. Total evidence linked: ${result.evidenceIds.length}`
|
|
40359
|
-
}]
|
|
40360
|
-
};
|
|
40361
|
-
});
|
|
40362
|
-
server.registerTool('verify_implementation', {
|
|
40363
|
-
title: 'Verify Implementation',
|
|
40364
|
-
description: 'AI-grade your implementation against the intent spec. Checks each outcome, constraint, constitution rule, and edge case. Returns pass/fail per item with reasoning and an overall score. Also logs the result as an implementation note.',
|
|
40365
|
-
inputSchema: {
|
|
40366
|
-
intentId: zod_1.z.string().describe('The intent ID to verify against'),
|
|
40367
|
-
summary: zod_1.z.string().describe('Description of what was implemented and how'),
|
|
40368
|
-
codeChanges: zod_1.z.string().optional().describe('Summary of code changes (file list, key modifications)'),
|
|
40369
|
-
},
|
|
40370
|
-
annotations: WRITE_OP,
|
|
40371
|
-
}, async ({ intentId, summary, codeChanges }) => {
|
|
40372
|
-
if (isLocalMode) {
|
|
40373
|
-
return { content: [{ type: 'text', text: 'Verification requires cloud mode.' }] };
|
|
40374
|
-
}
|
|
40375
|
-
try {
|
|
40376
|
-
const result = await client.verifyImplementation(intentId, summary, codeChanges);
|
|
40377
|
-
let text = `Verification ${result.pass ? 'PASSED' : 'FAILED'} (score: ${result.score}/100)\n\n`;
|
|
40378
|
-
text += `${result.summary}\n\n`;
|
|
40379
|
-
for (const r of result.results) {
|
|
40380
|
-
const icon = r.status === 'pass' ? '[PASS]' : r.status === 'fail' ? '[FAIL]' : '[????]';
|
|
40381
|
-
text += `${icon} [${r.category}] ${r.item}\n ${r.reasoning}\n`;
|
|
40358
|
+
catch (e) {
|
|
40359
|
+
return { content: [{ type: 'text', text: `Graph analysis failed: ${e.message}` }] };
|
|
40382
40360
|
}
|
|
40383
|
-
|
|
40384
|
-
|
|
40385
|
-
|
|
40386
|
-
|
|
40387
|
-
|
|
40388
|
-
|
|
40389
|
-
|
|
40390
|
-
|
|
40391
|
-
|
|
40392
|
-
|
|
40393
|
-
|
|
40394
|
-
|
|
40395
|
-
|
|
40396
|
-
|
|
40397
|
-
|
|
40398
|
-
|
|
40361
|
+
});
|
|
40362
|
+
server.registerTool('export_context', {
|
|
40363
|
+
title: 'Export Context',
|
|
40364
|
+
description: 'Export workspace context as a formatted file. Use "claude-md" for CLAUDE.md (full workspace context), "cursorrules" for Cursor AI rules, or "intent-md" for a single intent specification file.',
|
|
40365
|
+
inputSchema: {
|
|
40366
|
+
format: zod_1.z.enum(['claude-md', 'cursorrules', 'intent-md']).describe('Export format'),
|
|
40367
|
+
intentId: zod_1.z.string().optional().describe('Intent ID (optional, for cursorrules and intent-md)'),
|
|
40368
|
+
},
|
|
40369
|
+
annotations: READ_ONLY,
|
|
40370
|
+
}, async ({ format, intentId }) => {
|
|
40371
|
+
if (isLocalMode) {
|
|
40372
|
+
return { content: [{ type: 'text', text: 'Export requires cloud mode. Use PATHMODE_API_KEY to connect.' }] };
|
|
40373
|
+
}
|
|
40374
|
+
try {
|
|
40375
|
+
const content = await client.exportContext(format, intentId);
|
|
40376
|
+
return { content: [{ type: 'text', text: content }] };
|
|
40377
|
+
}
|
|
40378
|
+
catch (e) {
|
|
40379
|
+
return { content: [{ type: 'text', text: `Export failed: ${e.message}` }] };
|
|
40380
|
+
}
|
|
40381
|
+
});
|
|
40382
|
+
server.registerTool('get_agent_prompt', {
|
|
40383
|
+
title: 'Get Agent Prompt',
|
|
40384
|
+
description: 'Get a formatted execution prompt for a specific intent. This is the full structured prompt including objective, outcomes, constraints, edge cases, and verification steps.',
|
|
40385
|
+
inputSchema: {
|
|
40386
|
+
intentId: zod_1.z.string().describe('The intent ID to generate a prompt for'),
|
|
40387
|
+
mode: zod_1.z.enum(['draft', 'execute']).optional().describe('draft = critique the spec, execute = implement it'),
|
|
40388
|
+
},
|
|
40389
|
+
annotations: READ_ONLY,
|
|
40390
|
+
}, async ({ intentId, mode }) => {
|
|
40391
|
+
if (isLocalMode) {
|
|
40392
|
+
return { content: [{ type: 'text', text: 'Agent prompts require cloud mode for full context generation.' }] };
|
|
40393
|
+
}
|
|
40394
|
+
const result = await client.getIntentPrompt(intentId, 'claude-code', mode || 'execute');
|
|
40395
|
+
return {
|
|
40396
|
+
content: [{
|
|
40399
40397
|
type: 'text',
|
|
40400
|
-
text:
|
|
40401
|
-
}
|
|
40402
|
-
|
|
40403
|
-
};
|
|
40404
|
-
|
|
40405
|
-
|
|
40406
|
-
|
|
40407
|
-
|
|
40408
|
-
|
|
40409
|
-
|
|
40398
|
+
text: result.prompt
|
|
40399
|
+
}]
|
|
40400
|
+
};
|
|
40401
|
+
});
|
|
40402
|
+
server.registerTool('get_workspace', {
|
|
40403
|
+
title: 'Get Workspace',
|
|
40404
|
+
description: 'Get workspace details including strategy (vision, non-negotiables, architecture principles) and constitution rules.',
|
|
40405
|
+
annotations: READ_ONLY,
|
|
40406
|
+
}, async () => {
|
|
40407
|
+
if (isLocalMode) {
|
|
40408
|
+
return { content: [{ type: 'text', text: 'Workspace details are not available in local mode.' }] };
|
|
40409
|
+
}
|
|
40410
|
+
const workspace = await client.getWorkspace();
|
|
40411
|
+
return {
|
|
40412
|
+
content: [{
|
|
40410
40413
|
type: 'text',
|
|
40411
|
-
text:
|
|
40412
|
-
}
|
|
40413
|
-
|
|
40414
|
-
};
|
|
40415
|
-
|
|
40416
|
-
|
|
40417
|
-
|
|
40418
|
-
|
|
40419
|
-
|
|
40420
|
-
|
|
40414
|
+
text: JSON.stringify(workspace, null, 2)
|
|
40415
|
+
}]
|
|
40416
|
+
};
|
|
40417
|
+
});
|
|
40418
|
+
server.registerTool('get_constitution', {
|
|
40419
|
+
title: 'Get Constitution',
|
|
40420
|
+
description: 'Get the workspace constitution rules. These are mandatory constraints that all implementations must respect.',
|
|
40421
|
+
annotations: READ_ONLY,
|
|
40422
|
+
}, async () => {
|
|
40423
|
+
if (isLocalMode) {
|
|
40424
|
+
return { content: [{ type: 'text', text: 'Constitution rules are not available in local mode.' }] };
|
|
40425
|
+
}
|
|
40426
|
+
const result = await client.getConstitution();
|
|
40427
|
+
return {
|
|
40428
|
+
content: [{
|
|
40421
40429
|
type: 'text',
|
|
40422
|
-
text:
|
|
40423
|
-
}
|
|
40424
|
-
|
|
40425
|
-
};
|
|
40426
|
-
|
|
40427
|
-
|
|
40428
|
-
|
|
40429
|
-
|
|
40430
|
-
|
|
40431
|
-
|
|
40432
|
-
|
|
40433
|
-
|
|
40430
|
+
text: JSON.stringify(result, null, 2)
|
|
40431
|
+
}]
|
|
40432
|
+
};
|
|
40433
|
+
});
|
|
40434
|
+
// ============================================================
|
|
40435
|
+
// Tools — Write Operations
|
|
40436
|
+
// ============================================================
|
|
40437
|
+
server.registerTool('update_intent_status', {
|
|
40438
|
+
title: 'Update Intent Status',
|
|
40439
|
+
description: 'Update the status of an intent. Use this to mark an intent as shipped after implementation, or verified after testing. For shipped/verified transitions, the response includes a verification checklist of outcomes, constitution rules, and health metrics that should be confirmed.',
|
|
40440
|
+
inputSchema: {
|
|
40441
|
+
intentId: zod_1.z.string().describe('The intent ID to update'),
|
|
40442
|
+
status: zod_1.z.enum(['draft', 'validated', 'approved', 'shipped', 'verified']).describe('The new status'),
|
|
40443
|
+
},
|
|
40444
|
+
annotations: { ...WRITE_OP, idempotentHint: true },
|
|
40445
|
+
}, async ({ intentId, status }) => {
|
|
40446
|
+
if (isLocalMode) {
|
|
40447
|
+
return { content: [{ type: 'text', text: 'Status updates are not available in local mode. Use cloud mode.' }] };
|
|
40448
|
+
}
|
|
40449
|
+
const result = await client.updateIntentStatus(intentId, status);
|
|
40450
|
+
let responseText = `Intent ${intentId} status updated to "${status}".`;
|
|
40451
|
+
// Surface verification checklist for shipped/verified transitions
|
|
40452
|
+
if (result.verificationChecklist && result.verificationChecklist.length > 0) {
|
|
40453
|
+
responseText += `\n\nVerification Checklist (${result.verificationChecklistCount} items to verify):`;
|
|
40454
|
+
for (const item of result.verificationChecklist) {
|
|
40455
|
+
responseText += `\n [ ] [${item.category}] ${item.text}`;
|
|
40456
|
+
}
|
|
40457
|
+
responseText += `\n\nThese items should be verified. Use log_implementation_note to record verification results.`;
|
|
40458
|
+
}
|
|
40459
|
+
return {
|
|
40460
|
+
content: [{
|
|
40434
40461
|
type: 'text',
|
|
40435
|
-
text:
|
|
40436
|
-
}
|
|
40437
|
-
|
|
40438
|
-
};
|
|
40439
|
-
|
|
40440
|
-
|
|
40441
|
-
|
|
40442
|
-
|
|
40443
|
-
|
|
40444
|
-
|
|
40445
|
-
|
|
40446
|
-
|
|
40447
|
-
|
|
40448
|
-
|
|
40449
|
-
|
|
40450
|
-
|
|
40451
|
-
|
|
40452
|
-
|
|
40453
|
-
|
|
40454
|
-
manualChecks: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40455
|
-
unitTests: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40456
|
-
e2eTests: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40457
|
-
}).optional().describe('How to confirm it works'),
|
|
40458
|
-
};
|
|
40459
|
-
server.prompt('compile-intent', 'Start a Socratic conversation to build a structured intent spec from user feedback or a problem description. No Pathmode account needed.', {}, async () => {
|
|
40460
|
-
return {
|
|
40461
|
-
messages: [{
|
|
40462
|
-
role: 'user',
|
|
40463
|
-
content: {
|
|
40462
|
+
text: responseText
|
|
40463
|
+
}]
|
|
40464
|
+
};
|
|
40465
|
+
});
|
|
40466
|
+
server.registerTool('log_implementation_note', {
|
|
40467
|
+
title: 'Log Implementation Note',
|
|
40468
|
+
description: 'Record a technical decision or implementation note for an intent. Use this to document why you chose a specific approach.',
|
|
40469
|
+
inputSchema: {
|
|
40470
|
+
intentId: zod_1.z.string().describe('The intent ID to attach the note to'),
|
|
40471
|
+
note: zod_1.z.string().describe('The implementation note or technical decision'),
|
|
40472
|
+
},
|
|
40473
|
+
annotations: WRITE_OP,
|
|
40474
|
+
}, async ({ intentId, note }) => {
|
|
40475
|
+
if (isLocalMode) {
|
|
40476
|
+
return { content: [{ type: 'text', text: 'Notes are not available in local mode. Use cloud mode.' }] };
|
|
40477
|
+
}
|
|
40478
|
+
const result = await client.logNote(intentId, note, 'mcp');
|
|
40479
|
+
return {
|
|
40480
|
+
content: [{
|
|
40464
40481
|
type: 'text',
|
|
40465
|
-
text:
|
|
40466
|
-
}
|
|
40467
|
-
|
|
40468
|
-
};
|
|
40469
|
-
|
|
40470
|
-
|
|
40471
|
-
|
|
40472
|
-
|
|
40473
|
-
|
|
40474
|
-
|
|
40475
|
-
|
|
40476
|
-
|
|
40477
|
-
|
|
40478
|
-
|
|
40479
|
-
|
|
40480
|
-
|
|
40481
|
-
|
|
40482
|
-
|
|
40483
|
-
|
|
40484
|
-
|
|
40485
|
-
|
|
40486
|
-
|
|
40487
|
-
|
|
40488
|
-
|
|
40489
|
-
|
|
40490
|
-
|
|
40491
|
-
|
|
40492
|
-
(
|
|
40482
|
+
text: `Note logged for intent ${intentId}: "${note}"`
|
|
40483
|
+
}]
|
|
40484
|
+
};
|
|
40485
|
+
});
|
|
40486
|
+
server.registerTool('create_intent', {
|
|
40487
|
+
title: 'Create Intent',
|
|
40488
|
+
description: 'Create a new intent spec in the workspace. Requires at minimum a title, objective, and productId. Returns the created intent with its ID. Use list_intents first to see existing intents and avoid duplicates.',
|
|
40489
|
+
inputSchema: {
|
|
40490
|
+
title: zod_1.z.string().describe('Short name for the intent (e.g., "Improve onboarding flow")'),
|
|
40491
|
+
objective: zod_1.z.string().describe('Why this matters — the problem and who has it'),
|
|
40492
|
+
productId: zod_1.z.string().describe('Product (Space) ID this intent belongs to. Use get_workspace to find product IDs.'),
|
|
40493
|
+
outcomes: zod_1.z.array(zod_1.z.string()).optional().describe('Observable, testable state changes'),
|
|
40494
|
+
constraints: zod_1.z.array(zod_1.z.string()).optional().describe('Hard limits the implementation must respect'),
|
|
40495
|
+
healthMetrics: zod_1.z.array(zod_1.z.string()).optional().describe('What to monitor after shipping'),
|
|
40496
|
+
edgeCases: zod_1.z.array(zod_1.z.object({
|
|
40497
|
+
scenario: zod_1.z.string(),
|
|
40498
|
+
expectedBehavior: zod_1.z.string(),
|
|
40499
|
+
})).optional().describe('Failure modes and boundary conditions'),
|
|
40500
|
+
verification: zod_1.z.object({
|
|
40501
|
+
manualChecks: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40502
|
+
unitTests: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40503
|
+
e2eTests: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40504
|
+
}).optional().describe('How to confirm it works'),
|
|
40505
|
+
problemSeverity: zod_1.z.enum(['low', 'medium', 'high', 'critical']).optional().describe('How severe the problem is'),
|
|
40506
|
+
},
|
|
40507
|
+
annotations: WRITE_OP,
|
|
40508
|
+
}, async ({ productId, ...rest }) => {
|
|
40509
|
+
if (isLocalMode) {
|
|
40510
|
+
return { content: [{ type: 'text', text: 'Creating intents requires cloud mode. Use PATHMODE_API_KEY to connect.' }] };
|
|
40511
|
+
}
|
|
40512
|
+
const result = await client.createIntent({ productId, ...rest });
|
|
40493
40513
|
return {
|
|
40494
40514
|
content: [{
|
|
40495
40515
|
type: 'text',
|
|
40496
|
-
text:
|
|
40497
|
-
}]
|
|
40516
|
+
text: `Intent created: ${result.id}\nTitle: "${result.title}"\nStatus: ${result.status}\nProduct: ${result.productId || productId}`
|
|
40517
|
+
}]
|
|
40498
40518
|
};
|
|
40499
|
-
}
|
|
40500
|
-
|
|
40501
|
-
|
|
40502
|
-
|
|
40503
|
-
|
|
40504
|
-
|
|
40505
|
-
|
|
40506
|
-
|
|
40507
|
-
|
|
40508
|
-
|
|
40509
|
-
|
|
40510
|
-
|
|
40511
|
-
|
|
40512
|
-
|
|
40513
|
-
|
|
40519
|
+
});
|
|
40520
|
+
server.registerTool('update_intent', {
|
|
40521
|
+
title: 'Update Intent',
|
|
40522
|
+
description: "Update an existing intent's content. Provide only the fields you want to change. Does NOT change intent status (use update_intent_status for that).",
|
|
40523
|
+
inputSchema: {
|
|
40524
|
+
intentId: zod_1.z.string().describe('The intent ID to update'),
|
|
40525
|
+
title: zod_1.z.string().optional().describe('New title'),
|
|
40526
|
+
objective: zod_1.z.string().optional().describe('Updated objective'),
|
|
40527
|
+
outcomes: zod_1.z.array(zod_1.z.string()).optional().describe('Replace all outcomes'),
|
|
40528
|
+
constraints: zod_1.z.array(zod_1.z.string()).optional().describe('Replace all constraints'),
|
|
40529
|
+
healthMetrics: zod_1.z.array(zod_1.z.string()).optional().describe('Replace all health metrics'),
|
|
40530
|
+
edgeCases: zod_1.z.array(zod_1.z.object({
|
|
40531
|
+
scenario: zod_1.z.string(),
|
|
40532
|
+
expectedBehavior: zod_1.z.string(),
|
|
40533
|
+
})).optional().describe('Replace all edge cases'),
|
|
40534
|
+
verification: zod_1.z.object({
|
|
40535
|
+
manualChecks: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40536
|
+
unitTests: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40537
|
+
e2eTests: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40538
|
+
}).optional().describe('Replace verification plan'),
|
|
40539
|
+
problemSeverity: zod_1.z.enum(['low', 'medium', 'high', 'critical']).optional(),
|
|
40540
|
+
},
|
|
40541
|
+
annotations: { ...WRITE_OP, idempotentHint: true },
|
|
40542
|
+
}, async ({ intentId, ...updates }) => {
|
|
40543
|
+
if (isLocalMode) {
|
|
40544
|
+
return { content: [{ type: 'text', text: 'Updating intents requires cloud mode.' }] };
|
|
40545
|
+
}
|
|
40546
|
+
const result = await client.updateIntent(intentId, updates);
|
|
40547
|
+
const changedFields = Object.keys(updates).filter(k => updates[k] !== undefined);
|
|
40514
40548
|
return {
|
|
40515
40549
|
content: [{
|
|
40516
40550
|
type: 'text',
|
|
40517
|
-
text:
|
|
40518
|
-
}]
|
|
40551
|
+
text: `Intent ${intentId} updated. Fields changed: ${changedFields.join(', ')}`
|
|
40552
|
+
}]
|
|
40519
40553
|
};
|
|
40520
|
-
}
|
|
40521
|
-
|
|
40522
|
-
|
|
40523
|
-
|
|
40524
|
-
|
|
40525
|
-
|
|
40526
|
-
|
|
40527
|
-
|
|
40554
|
+
});
|
|
40555
|
+
server.registerTool('query_evidence', {
|
|
40556
|
+
title: 'Query Evidence',
|
|
40557
|
+
description: 'Search evidence items (friction points, user quotes, observations, metrics, feature requests) by product, type, severity, or text. Returns matching evidence with IDs that can be linked to intents.',
|
|
40558
|
+
inputSchema: {
|
|
40559
|
+
productId: zod_1.z.string().optional().describe('Filter by product (Space) ID'),
|
|
40560
|
+
type: zod_1.z.enum(['friction', 'quote', 'observation', 'metric', 'request']).optional().describe('Filter by evidence type'),
|
|
40561
|
+
severity: zod_1.z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Filter by severity'),
|
|
40562
|
+
search: zod_1.z.string().optional().describe('Text search across evidence content'),
|
|
40563
|
+
tags: zod_1.z.string().optional().describe('Comma-separated tags to filter by'),
|
|
40564
|
+
limit: zod_1.z.number().optional().describe('Max results (default 50, max 200)'),
|
|
40565
|
+
},
|
|
40566
|
+
annotations: READ_ONLY,
|
|
40567
|
+
}, async (filters) => {
|
|
40568
|
+
if (isLocalMode) {
|
|
40569
|
+
return { content: [{ type: 'text', text: 'Evidence queries require cloud mode.' }] };
|
|
40570
|
+
}
|
|
40571
|
+
const result = await client.queryEvidence(filters);
|
|
40528
40572
|
return {
|
|
40529
|
-
|
|
40530
|
-
|
|
40531
|
-
|
|
40532
|
-
text: JSON.stringify(intents[0] || null, null, 2),
|
|
40573
|
+
content: [{
|
|
40574
|
+
type: 'text',
|
|
40575
|
+
text: JSON.stringify(result, null, 2)
|
|
40533
40576
|
}]
|
|
40534
40577
|
};
|
|
40535
|
-
}
|
|
40536
|
-
|
|
40537
|
-
|
|
40538
|
-
|
|
40539
|
-
|
|
40540
|
-
|
|
40541
|
-
|
|
40542
|
-
|
|
40543
|
-
|
|
40544
|
-
|
|
40545
|
-
|
|
40546
|
-
|
|
40547
|
-
|
|
40578
|
+
});
|
|
40579
|
+
server.registerTool('create_evidence', {
|
|
40580
|
+
title: 'Create Evidence',
|
|
40581
|
+
description: 'Create a new evidence item (e.g., a discovered bug, user feedback quote, behavioral observation, or feature request). Evidence can later be linked to intents to support prioritization.',
|
|
40582
|
+
inputSchema: {
|
|
40583
|
+
content: zod_1.z.string().describe('The evidence content — a finding, quote, or observation'),
|
|
40584
|
+
type: zod_1.z.enum(['friction', 'quote', 'observation', 'metric', 'request']).describe('Type of evidence'),
|
|
40585
|
+
productId: zod_1.z.string().describe('Product (Space) ID this evidence belongs to'),
|
|
40586
|
+
source: zod_1.z.string().optional().describe('Where this evidence came from (e.g., "User interview", "Bug report", "Support ticket")'),
|
|
40587
|
+
sourceUrl: zod_1.z.string().optional().describe('URL to the original source'),
|
|
40588
|
+
severity: zod_1.z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Severity level (required for friction type)'),
|
|
40589
|
+
sentiment: zod_1.z.enum(['positive', 'negative', 'neutral', 'mixed']).optional().describe('Emotional sentiment'),
|
|
40590
|
+
tags: zod_1.z.array(zod_1.z.string()).optional().describe('Category tags (e.g., ["Onboarding", "Performance"])'),
|
|
40591
|
+
stage: zod_1.z.string().optional().describe('User journey stage (e.g., "Discovery", "Checkout")'),
|
|
40592
|
+
},
|
|
40593
|
+
annotations: WRITE_OP,
|
|
40594
|
+
}, async ({ productId, ...rest }) => {
|
|
40595
|
+
if (isLocalMode) {
|
|
40596
|
+
return { content: [{ type: 'text', text: 'Creating evidence requires cloud mode.' }] };
|
|
40597
|
+
}
|
|
40598
|
+
const result = await client.createEvidence({ productId, ...rest });
|
|
40548
40599
|
return {
|
|
40549
|
-
|
|
40550
|
-
|
|
40551
|
-
|
|
40552
|
-
text: JSON.stringify({ error: 'Graph not available in local mode' }),
|
|
40600
|
+
content: [{
|
|
40601
|
+
type: 'text',
|
|
40602
|
+
text: `Evidence created: ${result.id}\nType: ${result.type}\nContent: "${(result.content || '').slice(0, 80)}${(result.content || '').length > 80 ? '...' : ''}"`
|
|
40553
40603
|
}]
|
|
40554
40604
|
};
|
|
40555
|
-
}
|
|
40556
|
-
|
|
40557
|
-
|
|
40558
|
-
|
|
40559
|
-
|
|
40560
|
-
|
|
40561
|
-
|
|
40562
|
-
|
|
40563
|
-
|
|
40564
|
-
|
|
40565
|
-
|
|
40566
|
-
|
|
40567
|
-
|
|
40568
|
-
|
|
40569
|
-
|
|
40570
|
-
|
|
40571
|
-
|
|
40572
|
-
|
|
40605
|
+
});
|
|
40606
|
+
server.registerTool('link_evidence', {
|
|
40607
|
+
title: 'Link Evidence to Intent',
|
|
40608
|
+
description: 'Link or unlink evidence items to/from an intent. Linking evidence to intents establishes traceability between user problems and planned solutions.',
|
|
40609
|
+
inputSchema: {
|
|
40610
|
+
intentId: zod_1.z.string().describe('The intent ID to link evidence to'),
|
|
40611
|
+
link: zod_1.z.array(zod_1.z.string()).optional().describe('Evidence IDs to link to this intent'),
|
|
40612
|
+
unlink: zod_1.z.array(zod_1.z.string()).optional().describe('Evidence IDs to unlink from this intent'),
|
|
40613
|
+
},
|
|
40614
|
+
annotations: WRITE_OP,
|
|
40615
|
+
}, async ({ intentId, link, unlink }) => {
|
|
40616
|
+
if (isLocalMode) {
|
|
40617
|
+
return { content: [{ type: 'text', text: 'Evidence linking requires cloud mode.' }] };
|
|
40618
|
+
}
|
|
40619
|
+
const result = await client.linkEvidence(intentId, { link, unlink });
|
|
40620
|
+
const actions = [];
|
|
40621
|
+
if (link && link.length > 0)
|
|
40622
|
+
actions.push(`linked ${result.linked} evidence items`);
|
|
40623
|
+
if (unlink && unlink.length > 0)
|
|
40624
|
+
actions.push(`unlinked ${result.unlinked} evidence items`);
|
|
40573
40625
|
return {
|
|
40574
|
-
|
|
40575
|
-
|
|
40576
|
-
|
|
40577
|
-
text: JSON.stringify({ error: 'Workspace strategy not available in local mode' }),
|
|
40626
|
+
content: [{
|
|
40627
|
+
type: 'text',
|
|
40628
|
+
text: `Intent ${intentId}: ${actions.join(', ')}. Total evidence linked: ${result.evidenceIds.length}`
|
|
40578
40629
|
}]
|
|
40579
40630
|
};
|
|
40580
|
-
}
|
|
40581
|
-
|
|
40582
|
-
|
|
40631
|
+
});
|
|
40632
|
+
server.registerTool('verify_implementation', {
|
|
40633
|
+
title: 'Verify Implementation',
|
|
40634
|
+
description: 'AI-grade your implementation against the intent spec. Checks each outcome, constraint, constitution rule, and edge case. Returns pass/fail per item with reasoning and an overall score. Also logs the result as an implementation note.',
|
|
40635
|
+
inputSchema: {
|
|
40636
|
+
intentId: zod_1.z.string().describe('The intent ID to verify against'),
|
|
40637
|
+
summary: zod_1.z.string().describe('Description of what was implemented and how'),
|
|
40638
|
+
codeChanges: zod_1.z.string().optional().describe('Summary of code changes (file list, key modifications)'),
|
|
40639
|
+
},
|
|
40640
|
+
annotations: WRITE_OP,
|
|
40641
|
+
}, async ({ intentId, summary, codeChanges }) => {
|
|
40642
|
+
if (isLocalMode) {
|
|
40643
|
+
return { content: [{ type: 'text', text: 'Verification requires cloud mode.' }] };
|
|
40644
|
+
}
|
|
40645
|
+
try {
|
|
40646
|
+
const result = await client.verifyImplementation(intentId, summary, codeChanges);
|
|
40647
|
+
let text = `Verification ${result.pass ? 'PASSED' : 'FAILED'} (score: ${result.score}/100)\n\n`;
|
|
40648
|
+
text += `${result.summary}\n\n`;
|
|
40649
|
+
for (const r of result.results) {
|
|
40650
|
+
const icon = r.status === 'pass' ? '[PASS]' : r.status === 'fail' ? '[FAIL]' : '[????]';
|
|
40651
|
+
text += `${icon} [${r.category}] ${r.item}\n ${r.reasoning}\n`;
|
|
40652
|
+
}
|
|
40653
|
+
return { content: [{ type: 'text', text }] };
|
|
40654
|
+
}
|
|
40655
|
+
catch (e) {
|
|
40656
|
+
return { content: [{ type: 'text', text: `Verification failed: ${e.message}` }] };
|
|
40657
|
+
}
|
|
40658
|
+
});
|
|
40659
|
+
// ============================================================
|
|
40660
|
+
// Prompts
|
|
40661
|
+
// ============================================================
|
|
40662
|
+
server.prompt('implement-intent', 'Get full implementation context for a specific intent, including objective, outcomes, constraints, edge cases, and verification steps.', {
|
|
40663
|
+
intentId: zod_1.z.string().describe('The intent ID to implement'),
|
|
40664
|
+
}, async ({ intentId }) => {
|
|
40665
|
+
return {
|
|
40666
|
+
messages: [{
|
|
40667
|
+
role: 'user',
|
|
40668
|
+
content: {
|
|
40669
|
+
type: 'text',
|
|
40670
|
+
text: `I need to implement intent ${intentId}. Please:\n1. Use the get_agent_prompt tool to fetch the full execution prompt for this intent\n2. Use get_constitution to check for workspace constraints I must respect\n3. Review the intent details and create an implementation plan\n4. After implementation, use verify_implementation to AI-grade your work against the spec\n5. If verification passes, use update_intent_status to mark it as "shipped"\n6. Use log_implementation_note to document key technical decisions`,
|
|
40671
|
+
},
|
|
40672
|
+
}],
|
|
40673
|
+
};
|
|
40674
|
+
});
|
|
40675
|
+
server.prompt('review-risks', 'Analyze the intent graph for architectural risks, circular dependencies, bottlenecks, and stalled work.', {}, async () => {
|
|
40676
|
+
return {
|
|
40677
|
+
messages: [{
|
|
40678
|
+
role: 'user',
|
|
40679
|
+
content: {
|
|
40680
|
+
type: 'text',
|
|
40681
|
+
text: 'Please analyze our intent graph for risks:\n1. Use analyze_intent_graph with analysis "full" to get the complete graph analysis\n2. Summarize the critical path and explain why it matters\n3. Flag any cycles (circular dependencies) as urgent issues\n4. Identify bottlenecks \u2014 intents that block many others, especially if still in draft\n5. Suggest concrete actions to reduce risk',
|
|
40682
|
+
},
|
|
40683
|
+
}],
|
|
40684
|
+
};
|
|
40685
|
+
});
|
|
40686
|
+
server.prompt('what-next', 'Suggest the highest-priority intent to work on next, based on dependency graph analysis and current status.', {}, async () => {
|
|
40687
|
+
return {
|
|
40688
|
+
messages: [{
|
|
40689
|
+
role: 'user',
|
|
40690
|
+
content: {
|
|
40691
|
+
type: 'text',
|
|
40692
|
+
text: 'Help me decide what to work on next:\n1. Use analyze_intent_graph with analysis "critical-path" to find the critical path\n2. Use list_intents with status "approved" to see what\'s ready for implementation\n3. Consider: which approved intents are on the critical path? Which unblock the most other work?\n4. Recommend the single highest-impact intent to implement next, and explain why',
|
|
40693
|
+
},
|
|
40694
|
+
}],
|
|
40695
|
+
};
|
|
40696
|
+
});
|
|
40697
|
+
server.prompt('create-from-evidence', 'Search for evidence items, identify patterns, and create an intent based on the strongest evidence cluster.', {
|
|
40698
|
+
productId: zod_1.z.string().describe('The product ID to search evidence for'),
|
|
40699
|
+
}, async ({ productId }) => {
|
|
40700
|
+
return {
|
|
40701
|
+
messages: [{
|
|
40702
|
+
role: 'user',
|
|
40703
|
+
content: {
|
|
40704
|
+
type: 'text',
|
|
40705
|
+
text: `Help me create a new intent from evidence:\n1. Use query_evidence with productId "${productId}" to see what evidence exists\n2. Look for patterns — multiple friction points about the same issue, or high-severity items\n3. Propose an intent based on the strongest evidence cluster\n4. Use create_intent to create the intent\n5. Use link_evidence to link the relevant evidence items to the new intent`,
|
|
40706
|
+
},
|
|
40707
|
+
}],
|
|
40708
|
+
};
|
|
40709
|
+
});
|
|
40710
|
+
// ============================================================
|
|
40711
|
+
// Intent Compiler — Zero-config tools (no API key needed)
|
|
40712
|
+
// ============================================================
|
|
40713
|
+
const intentSpecSchema = {
|
|
40714
|
+
title: zod_1.z.string().describe('Short name for the intent'),
|
|
40715
|
+
objective: zod_1.z.string().describe('Why this matters — the problem and who has it'),
|
|
40716
|
+
outcomes: zod_1.z.array(zod_1.z.string()).describe('Observable, testable state changes'),
|
|
40717
|
+
constraints: zod_1.z.array(zod_1.z.string()).optional().describe('Hard limits the implementation must respect'),
|
|
40718
|
+
edgeCases: zod_1.z.array(zod_1.z.object({
|
|
40719
|
+
scenario: zod_1.z.string(),
|
|
40720
|
+
expectedBehavior: zod_1.z.string(),
|
|
40721
|
+
})).optional().describe('Failure modes and boundary conditions'),
|
|
40722
|
+
healthMetrics: zod_1.z.array(zod_1.z.string()).optional().describe('What to monitor after shipping'),
|
|
40723
|
+
verification: zod_1.z.object({
|
|
40724
|
+
manualChecks: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40725
|
+
unitTests: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40726
|
+
e2eTests: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40727
|
+
}).optional().describe('How to confirm it works'),
|
|
40728
|
+
};
|
|
40729
|
+
server.prompt('compile-intent', 'Start a Socratic conversation to build a structured intent spec from user feedback or a problem description. No Pathmode account needed.', {}, async () => {
|
|
40730
|
+
return {
|
|
40731
|
+
messages: [{
|
|
40732
|
+
role: 'user',
|
|
40733
|
+
content: {
|
|
40734
|
+
type: 'text',
|
|
40735
|
+
text: (0, intent_compiler_1.getCompileIntentPrompt)(),
|
|
40736
|
+
},
|
|
40737
|
+
}],
|
|
40738
|
+
};
|
|
40739
|
+
});
|
|
40740
|
+
server.tool('intent_save', 'Save an intent spec to intent.md in the project root. Called after building a spec through conversation.', {
|
|
40741
|
+
spec: zod_1.z.object(intentSpecSchema),
|
|
40742
|
+
path: zod_1.z.string().optional().describe('File path relative to cwd. Defaults to intent.md'),
|
|
40743
|
+
}, async ({ spec, path }) => {
|
|
40744
|
+
const filePath = (0, path_1.resolve)(process.cwd(), path || 'intent.md');
|
|
40745
|
+
const content = (0, intent_compiler_1.formatIntentMd)({ ...spec, id: `intent_${Date.now()}` });
|
|
40746
|
+
(0, fs_1.writeFileSync)(filePath, content, 'utf-8');
|
|
40747
|
+
return {
|
|
40748
|
+
content: [{
|
|
40749
|
+
type: 'text',
|
|
40750
|
+
text: `✓ Saved intent spec to ${filePath}\n\nTo connect this to Pathmode for dependency tracking and team collaboration, visit pathmode.io`,
|
|
40751
|
+
}],
|
|
40752
|
+
};
|
|
40753
|
+
});
|
|
40754
|
+
server.tool('intent_export', 'Export an intent spec as .cursorrules or CLAUDE.md section for AI agent consumption.', {
|
|
40755
|
+
format: zod_1.z.enum(['cursorrules', 'claude-md']).describe('Export format'),
|
|
40756
|
+
spec: zod_1.z.object(intentSpecSchema),
|
|
40757
|
+
path: zod_1.z.string().optional().describe('Output file path. Defaults to .cursorrules or CLAUDE.md'),
|
|
40758
|
+
}, async ({ format, spec, path }) => {
|
|
40759
|
+
if (format === 'cursorrules') {
|
|
40760
|
+
const content = (0, intent_compiler_1.formatCursorRules)(spec);
|
|
40761
|
+
const filePath = (0, path_1.resolve)(process.cwd(), path || '.cursorrules');
|
|
40762
|
+
(0, fs_1.writeFileSync)(filePath, content, 'utf-8');
|
|
40763
|
+
return {
|
|
40764
|
+
content: [{
|
|
40765
|
+
type: 'text',
|
|
40766
|
+
text: `✓ Exported .cursorrules to ${filePath}\n\nCursor and other AI agents will now see this intent as their implementation context.`,
|
|
40767
|
+
}],
|
|
40768
|
+
};
|
|
40769
|
+
}
|
|
40770
|
+
else {
|
|
40771
|
+
const section = (0, intent_compiler_1.formatClaudeMdSection)(spec);
|
|
40772
|
+
const filePath = (0, path_1.resolve)(process.cwd(), path || 'CLAUDE.md');
|
|
40773
|
+
// Append or replace PATHMODE section in existing file
|
|
40774
|
+
let existing = '';
|
|
40775
|
+
try {
|
|
40776
|
+
existing = (0, fs_1.readFileSync)(filePath, 'utf-8');
|
|
40777
|
+
}
|
|
40778
|
+
catch { /* file doesn't exist yet */ }
|
|
40779
|
+
const marker = /<!-- PATHMODE:START -->[\s\S]*?<!-- PATHMODE:END -->/;
|
|
40780
|
+
const updated = marker.test(existing)
|
|
40781
|
+
? existing.replace(marker, section)
|
|
40782
|
+
: existing ? existing + '\n\n' + section : section;
|
|
40783
|
+
(0, fs_1.writeFileSync)(filePath, updated, 'utf-8');
|
|
40784
|
+
return {
|
|
40785
|
+
content: [{
|
|
40786
|
+
type: 'text',
|
|
40787
|
+
text: `✓ Exported CLAUDE.md section to ${filePath}\n\nClaude Code will now see this intent as context in every conversation.`,
|
|
40788
|
+
}],
|
|
40789
|
+
};
|
|
40790
|
+
}
|
|
40791
|
+
});
|
|
40792
|
+
// ============================================================
|
|
40793
|
+
// Resources
|
|
40794
|
+
// ============================================================
|
|
40795
|
+
server.resource('intent://current', 'intent://current', async (uri) => {
|
|
40796
|
+
if (isLocalMode) {
|
|
40797
|
+
const intents = (0, local_reader_1.readLocalIntents)();
|
|
40798
|
+
return {
|
|
40799
|
+
contents: [{
|
|
40800
|
+
uri: uri.href,
|
|
40801
|
+
mimeType: 'application/json',
|
|
40802
|
+
text: JSON.stringify(intents[0] || null, null, 2),
|
|
40803
|
+
}]
|
|
40804
|
+
};
|
|
40805
|
+
}
|
|
40806
|
+
const intents = await client.listIntents('approved');
|
|
40807
|
+
const current = intents[0] || (await client.listIntents())[0] || null;
|
|
40583
40808
|
return {
|
|
40584
40809
|
contents: [{
|
|
40585
40810
|
uri: uri.href,
|
|
40586
40811
|
mimeType: 'application/json',
|
|
40587
|
-
text: JSON.stringify(
|
|
40588
|
-
name: workspace.name,
|
|
40589
|
-
strategy: workspace.strategy,
|
|
40590
|
-
constitutionRules: workspace.constitutionRules?.filter(r => r.isActive),
|
|
40591
|
-
}, null, 2),
|
|
40812
|
+
text: JSON.stringify(current, null, 2),
|
|
40592
40813
|
}]
|
|
40593
40814
|
};
|
|
40594
|
-
}
|
|
40595
|
-
|
|
40815
|
+
});
|
|
40816
|
+
server.resource('intent://graph', 'intent://graph', async (uri) => {
|
|
40817
|
+
if (isLocalMode) {
|
|
40818
|
+
return {
|
|
40819
|
+
contents: [{
|
|
40820
|
+
uri: uri.href,
|
|
40821
|
+
mimeType: 'application/json',
|
|
40822
|
+
text: JSON.stringify({ error: 'Graph not available in local mode' }),
|
|
40823
|
+
}]
|
|
40824
|
+
};
|
|
40825
|
+
}
|
|
40826
|
+
const intents = await client.listIntents();
|
|
40827
|
+
const graph = intents.map(i => ({
|
|
40828
|
+
id: i.id,
|
|
40829
|
+
title: i.title,
|
|
40830
|
+
status: i.status,
|
|
40831
|
+
relations: i.relations,
|
|
40832
|
+
}));
|
|
40596
40833
|
return {
|
|
40597
40834
|
contents: [{
|
|
40598
40835
|
uri: uri.href,
|
|
40599
40836
|
mimeType: 'application/json',
|
|
40600
|
-
text: JSON.stringify(
|
|
40837
|
+
text: JSON.stringify(graph, null, 2),
|
|
40601
40838
|
}]
|
|
40602
40839
|
};
|
|
40840
|
+
});
|
|
40841
|
+
server.resource('intent://workspace-strategy', 'intent://workspace-strategy', async (uri) => {
|
|
40842
|
+
if (isLocalMode) {
|
|
40843
|
+
return {
|
|
40844
|
+
contents: [{
|
|
40845
|
+
uri: uri.href,
|
|
40846
|
+
mimeType: 'application/json',
|
|
40847
|
+
text: JSON.stringify({ error: 'Workspace strategy not available in local mode' }),
|
|
40848
|
+
}]
|
|
40849
|
+
};
|
|
40850
|
+
}
|
|
40851
|
+
try {
|
|
40852
|
+
const workspace = await client.getWorkspace();
|
|
40853
|
+
return {
|
|
40854
|
+
contents: [{
|
|
40855
|
+
uri: uri.href,
|
|
40856
|
+
mimeType: 'application/json',
|
|
40857
|
+
text: JSON.stringify({
|
|
40858
|
+
name: workspace.name,
|
|
40859
|
+
strategy: workspace.strategy,
|
|
40860
|
+
constitutionRules: workspace.constitutionRules?.filter(r => r.isActive),
|
|
40861
|
+
}, null, 2),
|
|
40862
|
+
}]
|
|
40863
|
+
};
|
|
40864
|
+
}
|
|
40865
|
+
catch {
|
|
40866
|
+
return {
|
|
40867
|
+
contents: [{
|
|
40868
|
+
uri: uri.href,
|
|
40869
|
+
mimeType: 'application/json',
|
|
40870
|
+
text: JSON.stringify({ error: 'Failed to fetch workspace strategy' }),
|
|
40871
|
+
}]
|
|
40872
|
+
};
|
|
40873
|
+
}
|
|
40874
|
+
});
|
|
40875
|
+
// ============================================================
|
|
40876
|
+
// Start
|
|
40877
|
+
// ============================================================
|
|
40878
|
+
async function main() {
|
|
40879
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
40880
|
+
await server.connect(transport);
|
|
40603
40881
|
}
|
|
40604
|
-
|
|
40605
|
-
|
|
40606
|
-
|
|
40607
|
-
|
|
40608
|
-
|
|
40609
|
-
const transport = new stdio_js_1.StdioServerTransport();
|
|
40610
|
-
await server.connect(transport);
|
|
40611
|
-
}
|
|
40612
|
-
main().catch((error) => {
|
|
40613
|
-
console.error('Failed to start Pathmode MCP server:', error);
|
|
40614
|
-
process.exit(1);
|
|
40615
|
-
});
|
|
40882
|
+
main().catch((error) => {
|
|
40883
|
+
console.error('Failed to start Pathmode MCP server:', error);
|
|
40884
|
+
process.exit(1);
|
|
40885
|
+
});
|
|
40886
|
+
} // end startMcpServer()
|
|
40616
40887
|
|
|
40617
40888
|
})();
|
|
40618
40889
|
|