@pathmode/mcp-server 1.4.0 → 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 +0 -30
- 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 +1071 -873
- package/dist/setup.d.ts +12 -0
- package/dist/setup.d.ts.map +1 -0
- package/manifest.json +13 -6
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -11512,24 +11512,6 @@ class PathmodeClient {
|
|
|
11512
11512
|
});
|
|
11513
11513
|
return res.json();
|
|
11514
11514
|
}
|
|
11515
|
-
// --- Signal operations ---
|
|
11516
|
-
async listSignals(filters = {}) {
|
|
11517
|
-
const params = new URLSearchParams();
|
|
11518
|
-
for (const [key, val] of Object.entries(filters)) {
|
|
11519
|
-
if (val !== undefined && val !== null)
|
|
11520
|
-
params.set(key, String(val));
|
|
11521
|
-
}
|
|
11522
|
-
const qs = params.toString();
|
|
11523
|
-
const res = await this.fetch(`/signals${qs ? `?${qs}` : ''}`);
|
|
11524
|
-
return res.json();
|
|
11525
|
-
}
|
|
11526
|
-
async dismissSignal(signalId) {
|
|
11527
|
-
const res = await this.fetch('/signals', {
|
|
11528
|
-
method: 'PATCH',
|
|
11529
|
-
body: JSON.stringify({ signalId }),
|
|
11530
|
-
});
|
|
11531
|
-
return res.json();
|
|
11532
|
-
}
|
|
11533
11515
|
}
|
|
11534
11516
|
exports.PathmodeClient = PathmodeClient;
|
|
11535
11517
|
|
|
@@ -11948,6 +11930,259 @@ function extractEdgeCases(body) {
|
|
|
11948
11930
|
}
|
|
11949
11931
|
|
|
11950
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
|
+
|
|
11951
12186
|
/***/ }),
|
|
11952
12187
|
|
|
11953
12188
|
/***/ 9896:
|
|
@@ -39746,8 +39981,9 @@ var exports = __webpack_exports__;
|
|
|
39746
39981
|
* Connects Claude Code, Cursor, and other AI agents to your Intent Layer.
|
|
39747
39982
|
*
|
|
39748
39983
|
* Usage:
|
|
39749
|
-
* npx @pathmode/mcp-server
|
|
39750
|
-
* 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
|
|
39751
39987
|
*
|
|
39752
39988
|
* The Intent Compiler (compile-intent prompt, intent_save, intent_export tools)
|
|
39753
39989
|
* works without an API key — zero-config intent spec building in Claude Code.
|
|
@@ -39771,921 +40007,883 @@ const fs_1 = __nccwpck_require__(9896);
|
|
|
39771
40007
|
const api_client_1 = __nccwpck_require__(7475);
|
|
39772
40008
|
const local_reader_1 = __nccwpck_require__(3518);
|
|
39773
40009
|
const intent_compiler_1 = __nccwpck_require__(6488);
|
|
39774
|
-
const
|
|
39775
|
-
|
|
39776
|
-
|
|
39777
|
-
|
|
39778
|
-
|
|
39779
|
-
|
|
39780
|
-
|
|
39781
|
-
|
|
39782
|
-
|
|
39783
|
-
}
|
|
39784
|
-
|
|
39785
|
-
|
|
39786
|
-
|
|
39787
|
-
|
|
39788
|
-
|
|
39789
|
-
|
|
39790
|
-
|
|
39791
|
-
|
|
39792
|
-
|
|
39793
|
-
const
|
|
39794
|
-
|
|
39795
|
-
|
|
39796
|
-
|
|
39797
|
-
|
|
39798
|
-
|
|
39799
|
-
|
|
39800
|
-
|
|
39801
|
-
|
|
39802
|
-
|
|
39803
|
-
|
|
39804
|
-
|
|
39805
|
-
|
|
39806
|
-
|
|
39807
|
-
|
|
39808
|
-
|
|
39809
|
-
|
|
39810
|
-
|
|
39811
|
-
|
|
39812
|
-
|
|
39813
|
-
|
|
39814
|
-
|
|
39815
|
-
|
|
39816
|
-
|
|
39817
|
-
|
|
39818
|
-
|
|
39819
|
-
|
|
39820
|
-
|
|
39821
|
-
|
|
39822
|
-
|
|
39823
|
-
|
|
39824
|
-
|
|
39825
|
-
|
|
39826
|
-
|
|
39827
|
-
}
|
|
39828
|
-
|
|
39829
|
-
|
|
39830
|
-
|
|
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);
|
|
39831
40091
|
return {
|
|
39832
40092
|
content: [{
|
|
39833
40093
|
type: 'text',
|
|
39834
|
-
text: JSON.stringify({ intents
|
|
40094
|
+
text: JSON.stringify({ intents, count: intents.length }, null, 2)
|
|
39835
40095
|
}]
|
|
39836
40096
|
};
|
|
39837
|
-
}
|
|
39838
|
-
|
|
39839
|
-
|
|
39840
|
-
|
|
39841
|
-
|
|
39842
|
-
|
|
39843
|
-
|
|
39844
|
-
|
|
39845
|
-
|
|
39846
|
-
|
|
39847
|
-
|
|
39848
|
-
|
|
39849
|
-
|
|
39850
|
-
|
|
39851
|
-
}
|
|
39852
|
-
|
|
39853
|
-
|
|
39854
|
-
|
|
39855
|
-
|
|
39856
|
-
|
|
39857
|
-
|
|
39858
|
-
|
|
39859
|
-
}
|
|
39860
|
-
|
|
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
|
+
}
|
|
39861
40129
|
const intent = await client.getIntent(intentId);
|
|
39862
|
-
return {
|
|
39863
|
-
|
|
39864
|
-
|
|
39865
|
-
|
|
39866
|
-
|
|
39867
|
-
|
|
39868
|
-
|
|
39869
|
-
|
|
39870
|
-
|
|
39871
|
-
|
|
39872
|
-
|
|
39873
|
-
|
|
39874
|
-
|
|
39875
|
-
|
|
39876
|
-
|
|
39877
|
-
|
|
39878
|
-
|
|
39879
|
-
|
|
39880
|
-
|
|
39881
|
-
|
|
39882
|
-
|
|
39883
|
-
|
|
39884
|
-
|
|
39885
|
-
|
|
39886
|
-
|
|
39887
|
-
|
|
39888
|
-
});
|
|
39889
|
-
|
|
39890
|
-
title: 'Search Intents',
|
|
39891
|
-
description: 'Search intents by keyword. Searches across user goals, objectives, outcomes, and constraints.',
|
|
39892
|
-
inputSchema: {
|
|
39893
|
-
query: zod_1.z.string().describe('Search keyword or phrase'),
|
|
39894
|
-
status: zod_1.z.string().optional().describe('Filter by status: draft, validated, approved, shipped, verified'),
|
|
39895
|
-
},
|
|
39896
|
-
annotations: READ_ONLY,
|
|
39897
|
-
}, async ({ query, status }) => {
|
|
39898
|
-
if (isLocalMode) {
|
|
39899
|
-
const intents = (0, local_reader_1.readLocalIntents)();
|
|
39900
|
-
const q = query.toLowerCase();
|
|
39901
|
-
const matches = intents.filter(i => {
|
|
39902
|
-
const text = [i.title, i.objective, ...i.outcomes, ...i.constraints].join(' ').toLowerCase();
|
|
39903
|
-
return text.includes(q) && (!status || i.status === status);
|
|
39904
|
-
});
|
|
39905
|
-
return { content: [{ type: 'text', text: JSON.stringify({ results: matches, count: matches.length, query }, null, 2) }] };
|
|
39906
|
-
}
|
|
39907
|
-
try {
|
|
39908
|
-
const intents = await client.listIntents(status);
|
|
39909
|
-
const q = query.toLowerCase();
|
|
39910
|
-
const matches = intents.filter(i => {
|
|
39911
|
-
const text = [i.title, i.objective, ...(i.outcomes || []), ...(i.constraints || [])].join(' ').toLowerCase();
|
|
39912
|
-
return text.includes(q);
|
|
39913
|
-
});
|
|
39914
|
-
const results = matches.map(i => ({
|
|
39915
|
-
id: i.id,
|
|
39916
|
-
title: i.title,
|
|
39917
|
-
objective: i.objective,
|
|
39918
|
-
status: i.status,
|
|
39919
|
-
stageName: i.stageName,
|
|
39920
|
-
}));
|
|
39921
|
-
return { content: [{ type: 'text', text: JSON.stringify({ results, count: results.length, query }, null, 2) }] };
|
|
39922
|
-
}
|
|
39923
|
-
catch (e) {
|
|
39924
|
-
return { content: [{ type: 'text', text: `Search failed: ${e.message}` }] };
|
|
39925
|
-
}
|
|
39926
|
-
});
|
|
39927
|
-
server.registerTool('analyze_intent_graph', {
|
|
39928
|
-
title: 'Analyze Intent Graph',
|
|
39929
|
-
description: 'Analyze the intent dependency graph for risks and strategic insights. Returns critical path, cycles, bottlenecks, orphans, status mismatches, and stalled intents.',
|
|
39930
|
-
inputSchema: {
|
|
39931
|
-
analysis: zod_1.z.enum(['full', 'critical-path', 'risks', 'status']).optional()
|
|
39932
|
-
.describe('Type of analysis: full (default), critical-path, risks, or status distribution'),
|
|
39933
|
-
},
|
|
39934
|
-
annotations: READ_ONLY,
|
|
39935
|
-
}, async ({ analysis }) => {
|
|
39936
|
-
if (isLocalMode) {
|
|
39937
|
-
return { content: [{ type: 'text', text: 'Graph analysis requires cloud mode.' }] };
|
|
39938
|
-
}
|
|
39939
|
-
try {
|
|
39940
|
-
const intents = await client.listIntents();
|
|
39941
|
-
if (intents.length === 0) {
|
|
39942
|
-
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) }] };
|
|
39943
40158
|
}
|
|
39944
|
-
|
|
39945
|
-
|
|
39946
|
-
|
|
39947
|
-
const
|
|
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
|
-
|
|
39977
|
-
|
|
39978
|
-
|
|
39979
|
-
|
|
39980
|
-
|
|
39981
|
-
|
|
39982
|
-
|
|
39983
|
-
|
|
39984
|
-
|
|
39985
|
-
|
|
39986
|
-
|
|
39987
|
-
|
|
39988
|
-
|
|
39989
|
-
|
|
39990
|
-
|
|
39991
|
-
|
|
39992
|
-
|
|
39993
|
-
|
|
39994
|
-
|
|
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);
|
|
39995
40251
|
}
|
|
39996
|
-
cycle.push(dep);
|
|
39997
|
-
cycle.reverse();
|
|
39998
|
-
cycles.push(cycle);
|
|
39999
40252
|
}
|
|
40000
40253
|
}
|
|
40254
|
+
else {
|
|
40255
|
+
color.set(id, BLACK);
|
|
40256
|
+
stack.pop();
|
|
40257
|
+
}
|
|
40001
40258
|
}
|
|
40002
|
-
|
|
40003
|
-
|
|
40004
|
-
|
|
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();
|
|
40005
40304
|
}
|
|
40006
40305
|
}
|
|
40007
|
-
|
|
40008
|
-
|
|
40009
|
-
|
|
40010
|
-
|
|
40011
|
-
|
|
40012
|
-
|
|
40013
|
-
|
|
40014
|
-
const queue = [];
|
|
40015
|
-
for (const [id, deg] of topoInDegree) {
|
|
40016
|
-
if (deg === 0)
|
|
40017
|
-
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
|
+
}
|
|
40018
40313
|
}
|
|
40019
|
-
|
|
40020
|
-
const
|
|
40021
|
-
for (const
|
|
40022
|
-
|
|
40023
|
-
|
|
40024
|
-
|
|
40025
|
-
|
|
40026
|
-
|
|
40027
|
-
|
|
40028
|
-
|
|
40029
|
-
|
|
40030
|
-
|
|
40031
|
-
|
|
40032
|
-
|
|
40033
|
-
|
|
40034
|
-
|
|
40035
|
-
|
|
40036
|
-
}
|
|
40037
|
-
}
|
|
40038
|
-
|
|
40039
|
-
|
|
40040
|
-
|
|
40041
|
-
|
|
40042
|
-
|
|
40043
|
-
}
|
|
40044
|
-
|
|
40045
|
-
|
|
40046
|
-
|
|
40047
|
-
|
|
40048
|
-
|
|
40049
|
-
|
|
40050
|
-
|
|
40051
|
-
|
|
40052
|
-
|
|
40053
|
-
|
|
40054
|
-
|
|
40055
|
-
|
|
40056
|
-
|
|
40057
|
-
|
|
40058
|
-
|
|
40059
|
-
|
|
40060
|
-
}
|
|
40061
|
-
|
|
40062
|
-
|
|
40063
|
-
|
|
40064
|
-
|
|
40065
|
-
|
|
40066
|
-
|
|
40067
|
-
|
|
40068
|
-
|
|
40069
|
-
|
|
40070
|
-
|
|
40071
|
-
|
|
40072
|
-
|
|
40073
|
-
|
|
40074
|
-
|
|
40075
|
-
|
|
40076
|
-
|
|
40077
|
-
|
|
40078
|
-
|
|
40079
|
-
|
|
40080
|
-
|
|
40081
|
-
|
|
40082
|
-
|
|
40083
|
-
|
|
40084
|
-
|
|
40085
|
-
|
|
40086
|
-
|
|
40087
|
-
|
|
40088
|
-
|
|
40089
|
-
|
|
40090
|
-
|
|
40091
|
-
|
|
40092
|
-
|
|
40093
|
-
|
|
40094
|
-
|
|
40095
|
-
|
|
40096
|
-
|
|
40097
|
-
|
|
40098
|
-
|
|
40099
|
-
|
|
40100
|
-
|
|
40101
|
-
|
|
40102
|
-
|
|
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) }] };
|
|
40357
|
+
}
|
|
40358
|
+
catch (e) {
|
|
40359
|
+
return { content: [{ type: 'text', text: `Graph analysis failed: ${e.message}` }] };
|
|
40360
|
+
}
|
|
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: [{
|
|
40397
|
+
type: 'text',
|
|
40398
|
+
text: result.prompt
|
|
40399
|
+
}]
|
|
40103
40400
|
};
|
|
40104
|
-
|
|
40105
|
-
|
|
40106
|
-
|
|
40107
|
-
|
|
40108
|
-
|
|
40109
|
-
})
|
|
40110
|
-
|
|
40111
|
-
|
|
40112
|
-
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.',
|
|
40113
|
-
inputSchema: {
|
|
40114
|
-
format: zod_1.z.enum(['claude-md', 'cursorrules', 'intent-md']).describe('Export format'),
|
|
40115
|
-
intentId: zod_1.z.string().optional().describe('Intent ID (optional, for cursorrules and intent-md)'),
|
|
40116
|
-
},
|
|
40117
|
-
annotations: READ_ONLY,
|
|
40118
|
-
}, async ({ format, intentId }) => {
|
|
40119
|
-
if (isLocalMode) {
|
|
40120
|
-
return { content: [{ type: 'text', text: 'Export requires cloud mode. Use PATHMODE_API_KEY to connect.' }] };
|
|
40121
|
-
}
|
|
40122
|
-
try {
|
|
40123
|
-
const content = await client.exportContext(format, intentId);
|
|
40124
|
-
return { content: [{ type: 'text', text: content }] };
|
|
40125
|
-
}
|
|
40126
|
-
catch (e) {
|
|
40127
|
-
return { content: [{ type: 'text', text: `Export failed: ${e.message}` }] };
|
|
40128
|
-
}
|
|
40129
|
-
});
|
|
40130
|
-
server.registerTool('get_agent_prompt', {
|
|
40131
|
-
title: 'Get Agent Prompt',
|
|
40132
|
-
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.',
|
|
40133
|
-
inputSchema: {
|
|
40134
|
-
intentId: zod_1.z.string().describe('The intent ID to generate a prompt for'),
|
|
40135
|
-
mode: zod_1.z.enum(['draft', 'execute']).optional().describe('draft = critique the spec, execute = implement it'),
|
|
40136
|
-
},
|
|
40137
|
-
annotations: READ_ONLY,
|
|
40138
|
-
}, async ({ intentId, mode }) => {
|
|
40139
|
-
if (isLocalMode) {
|
|
40140
|
-
return { content: [{ type: 'text', text: 'Agent prompts require cloud mode for full context generation.' }] };
|
|
40141
|
-
}
|
|
40142
|
-
const result = await client.getIntentPrompt(intentId, 'claude-code', mode || 'execute');
|
|
40143
|
-
return {
|
|
40144
|
-
content: [{
|
|
40145
|
-
type: 'text',
|
|
40146
|
-
text: result.prompt
|
|
40147
|
-
}]
|
|
40148
|
-
};
|
|
40149
|
-
});
|
|
40150
|
-
server.registerTool('get_workspace', {
|
|
40151
|
-
title: 'Get Workspace',
|
|
40152
|
-
description: 'Get workspace details including strategy (vision, non-negotiables, architecture principles) and constitution rules.',
|
|
40153
|
-
annotations: READ_ONLY,
|
|
40154
|
-
}, async () => {
|
|
40155
|
-
if (isLocalMode) {
|
|
40156
|
-
return { content: [{ type: 'text', text: 'Workspace details are not available in local mode.' }] };
|
|
40157
|
-
}
|
|
40158
|
-
const workspace = await client.getWorkspace();
|
|
40159
|
-
return {
|
|
40160
|
-
content: [{
|
|
40161
|
-
type: 'text',
|
|
40162
|
-
text: JSON.stringify(workspace, null, 2)
|
|
40163
|
-
}]
|
|
40164
|
-
};
|
|
40165
|
-
});
|
|
40166
|
-
server.registerTool('get_constitution', {
|
|
40167
|
-
title: 'Get Constitution',
|
|
40168
|
-
description: 'Get the workspace constitution rules. These are mandatory constraints that all implementations must respect.',
|
|
40169
|
-
annotations: READ_ONLY,
|
|
40170
|
-
}, async () => {
|
|
40171
|
-
if (isLocalMode) {
|
|
40172
|
-
return { content: [{ type: 'text', text: 'Constitution rules are not available in local mode.' }] };
|
|
40173
|
-
}
|
|
40174
|
-
const result = await client.getConstitution();
|
|
40175
|
-
return {
|
|
40176
|
-
content: [{
|
|
40177
|
-
type: 'text',
|
|
40178
|
-
text: JSON.stringify(result, null, 2)
|
|
40179
|
-
}]
|
|
40180
|
-
};
|
|
40181
|
-
});
|
|
40182
|
-
server.registerTool('list_signals', {
|
|
40183
|
-
title: 'List Signals',
|
|
40184
|
-
description: 'Query active signals (stale intents, loose evidence, evidence matches, verification failures). Signals surface things that need attention. By default returns only active (non-dismissed) signals.',
|
|
40185
|
-
inputSchema: {
|
|
40186
|
-
productId: zod_1.z.string().optional().describe('Filter by product ID'),
|
|
40187
|
-
type: zod_1.z.enum(['evidence_match', 'evidence_freshness', 'loose_evidence', 'stale_intent', 'alignment_drift', 'verification_failed']).optional().describe('Filter by signal type'),
|
|
40188
|
-
severity: zod_1.z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Filter by severity'),
|
|
40189
|
-
dismissed: zod_1.z.enum(['true', 'false', 'all']).optional().describe('Filter by dismissed status (default: false = active only)'),
|
|
40190
|
-
limit: zod_1.z.number().optional().describe('Max results (default 50, max 200)'),
|
|
40191
|
-
},
|
|
40192
|
-
annotations: READ_ONLY,
|
|
40193
|
-
}, async (filters) => {
|
|
40194
|
-
if (isLocalMode) {
|
|
40195
|
-
return { content: [{ type: 'text', text: 'Signals require cloud mode.' }] };
|
|
40196
|
-
}
|
|
40197
|
-
try {
|
|
40198
|
-
const result = await client.listSignals(filters);
|
|
40199
|
-
if (result.signals.length === 0) {
|
|
40200
|
-
return { content: [{ type: 'text', text: 'No signals found matching the filters. All clear!' }] };
|
|
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.' }] };
|
|
40201
40409
|
}
|
|
40410
|
+
const workspace = await client.getWorkspace();
|
|
40411
|
+
return {
|
|
40412
|
+
content: [{
|
|
40413
|
+
type: 'text',
|
|
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();
|
|
40202
40427
|
return {
|
|
40203
40428
|
content: [{
|
|
40204
40429
|
type: 'text',
|
|
40205
40430
|
text: JSON.stringify(result, null, 2)
|
|
40206
40431
|
}]
|
|
40207
40432
|
};
|
|
40208
|
-
}
|
|
40209
|
-
|
|
40210
|
-
|
|
40211
|
-
|
|
40212
|
-
|
|
40213
|
-
|
|
40214
|
-
|
|
40215
|
-
|
|
40216
|
-
|
|
40217
|
-
|
|
40218
|
-
|
|
40219
|
-
|
|
40220
|
-
|
|
40221
|
-
|
|
40222
|
-
|
|
40223
|
-
annotations: { ...WRITE_OP, idempotentHint: true },
|
|
40224
|
-
}, async ({ intentId, status }) => {
|
|
40225
|
-
if (isLocalMode) {
|
|
40226
|
-
return { content: [{ type: 'text', text: 'Status updates are not available in local mode. Use cloud mode.' }] };
|
|
40227
|
-
}
|
|
40228
|
-
const result = await client.updateIntentStatus(intentId, status);
|
|
40229
|
-
let responseText = `Intent ${intentId} status updated to "${status}".`;
|
|
40230
|
-
// Surface verification checklist for shipped/verified transitions
|
|
40231
|
-
if (result.verificationChecklist && result.verificationChecklist.length > 0) {
|
|
40232
|
-
responseText += `\n\nVerification Checklist (${result.verificationChecklistCount} items to verify):`;
|
|
40233
|
-
for (const item of result.verificationChecklist) {
|
|
40234
|
-
responseText += `\n [ ] [${item.category}] ${item.text}`;
|
|
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.' }] };
|
|
40235
40448
|
}
|
|
40236
|
-
|
|
40237
|
-
|
|
40238
|
-
|
|
40239
|
-
|
|
40240
|
-
|
|
40241
|
-
|
|
40242
|
-
|
|
40243
|
-
|
|
40244
|
-
|
|
40245
|
-
server.registerTool('log_implementation_note', {
|
|
40246
|
-
title: 'Log Implementation Note',
|
|
40247
|
-
description: 'Record a technical decision or implementation note for an intent. Use this to document why you chose a specific approach.',
|
|
40248
|
-
inputSchema: {
|
|
40249
|
-
intentId: zod_1.z.string().describe('The intent ID to attach the note to'),
|
|
40250
|
-
note: zod_1.z.string().describe('The implementation note or technical decision'),
|
|
40251
|
-
},
|
|
40252
|
-
annotations: WRITE_OP,
|
|
40253
|
-
}, async ({ intentId, note }) => {
|
|
40254
|
-
if (isLocalMode) {
|
|
40255
|
-
return { content: [{ type: 'text', text: 'Notes are not available in local mode. Use cloud mode.' }] };
|
|
40256
|
-
}
|
|
40257
|
-
const result = await client.logNote(intentId, note, 'mcp');
|
|
40258
|
-
return {
|
|
40259
|
-
content: [{
|
|
40260
|
-
type: 'text',
|
|
40261
|
-
text: `Note logged for intent ${intentId}: "${note}"`
|
|
40262
|
-
}]
|
|
40263
|
-
};
|
|
40264
|
-
});
|
|
40265
|
-
server.registerTool('create_intent', {
|
|
40266
|
-
title: 'Create Intent',
|
|
40267
|
-
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.',
|
|
40268
|
-
inputSchema: {
|
|
40269
|
-
title: zod_1.z.string().describe('Short name for the intent (e.g., "Improve onboarding flow")'),
|
|
40270
|
-
objective: zod_1.z.string().describe('Why this matters — the problem and who has it'),
|
|
40271
|
-
productId: zod_1.z.string().describe('Product (Space) ID this intent belongs to. Use get_workspace to find product IDs.'),
|
|
40272
|
-
outcomes: zod_1.z.array(zod_1.z.string()).optional().describe('Observable, testable state changes'),
|
|
40273
|
-
constraints: zod_1.z.array(zod_1.z.string()).optional().describe('Hard limits the implementation must respect'),
|
|
40274
|
-
healthMetrics: zod_1.z.array(zod_1.z.string()).optional().describe('What to monitor after shipping'),
|
|
40275
|
-
edgeCases: zod_1.z.array(zod_1.z.object({
|
|
40276
|
-
scenario: zod_1.z.string(),
|
|
40277
|
-
expectedBehavior: zod_1.z.string(),
|
|
40278
|
-
})).optional().describe('Failure modes and boundary conditions'),
|
|
40279
|
-
verification: zod_1.z.object({
|
|
40280
|
-
manualChecks: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40281
|
-
unitTests: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40282
|
-
e2eTests: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40283
|
-
}).optional().describe('How to confirm it works'),
|
|
40284
|
-
problemSeverity: zod_1.z.enum(['low', 'medium', 'high', 'critical']).optional().describe('How severe the problem is'),
|
|
40285
|
-
},
|
|
40286
|
-
annotations: WRITE_OP,
|
|
40287
|
-
}, async ({ productId, ...rest }) => {
|
|
40288
|
-
if (isLocalMode) {
|
|
40289
|
-
return { content: [{ type: 'text', text: 'Creating intents requires cloud mode. Use PATHMODE_API_KEY to connect.' }] };
|
|
40290
|
-
}
|
|
40291
|
-
const result = await client.createIntent({ productId, ...rest });
|
|
40292
|
-
return {
|
|
40293
|
-
content: [{
|
|
40294
|
-
type: 'text',
|
|
40295
|
-
text: `Intent created: ${result.id}\nTitle: "${result.title}"\nStatus: ${result.status}\nProduct: ${result.productId || productId}`
|
|
40296
|
-
}]
|
|
40297
|
-
};
|
|
40298
|
-
});
|
|
40299
|
-
server.registerTool('update_intent', {
|
|
40300
|
-
title: 'Update Intent',
|
|
40301
|
-
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).",
|
|
40302
|
-
inputSchema: {
|
|
40303
|
-
intentId: zod_1.z.string().describe('The intent ID to update'),
|
|
40304
|
-
title: zod_1.z.string().optional().describe('New title'),
|
|
40305
|
-
objective: zod_1.z.string().optional().describe('Updated objective'),
|
|
40306
|
-
outcomes: zod_1.z.array(zod_1.z.string()).optional().describe('Replace all outcomes'),
|
|
40307
|
-
constraints: zod_1.z.array(zod_1.z.string()).optional().describe('Replace all constraints'),
|
|
40308
|
-
healthMetrics: zod_1.z.array(zod_1.z.string()).optional().describe('Replace all health metrics'),
|
|
40309
|
-
edgeCases: zod_1.z.array(zod_1.z.object({
|
|
40310
|
-
scenario: zod_1.z.string(),
|
|
40311
|
-
expectedBehavior: zod_1.z.string(),
|
|
40312
|
-
})).optional().describe('Replace all edge cases'),
|
|
40313
|
-
verification: zod_1.z.object({
|
|
40314
|
-
manualChecks: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40315
|
-
unitTests: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40316
|
-
e2eTests: zod_1.z.array(zod_1.z.string()).optional(),
|
|
40317
|
-
}).optional().describe('Replace verification plan'),
|
|
40318
|
-
problemSeverity: zod_1.z.enum(['low', 'medium', 'high', 'critical']).optional(),
|
|
40319
|
-
},
|
|
40320
|
-
annotations: { ...WRITE_OP, idempotentHint: true },
|
|
40321
|
-
}, async ({ intentId, ...updates }) => {
|
|
40322
|
-
if (isLocalMode) {
|
|
40323
|
-
return { content: [{ type: 'text', text: 'Updating intents requires cloud mode.' }] };
|
|
40324
|
-
}
|
|
40325
|
-
const result = await client.updateIntent(intentId, updates);
|
|
40326
|
-
const changedFields = Object.keys(updates).filter(k => updates[k] !== undefined);
|
|
40327
|
-
return {
|
|
40328
|
-
content: [{
|
|
40329
|
-
type: 'text',
|
|
40330
|
-
text: `Intent ${intentId} updated. Fields changed: ${changedFields.join(', ')}`
|
|
40331
|
-
}]
|
|
40332
|
-
};
|
|
40333
|
-
});
|
|
40334
|
-
server.registerTool('query_evidence', {
|
|
40335
|
-
title: 'Query Evidence',
|
|
40336
|
-
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.',
|
|
40337
|
-
inputSchema: {
|
|
40338
|
-
productId: zod_1.z.string().optional().describe('Filter by product (Space) ID'),
|
|
40339
|
-
type: zod_1.z.enum(['friction', 'quote', 'observation', 'metric', 'request']).optional().describe('Filter by evidence type'),
|
|
40340
|
-
severity: zod_1.z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Filter by severity'),
|
|
40341
|
-
search: zod_1.z.string().optional().describe('Text search across evidence content'),
|
|
40342
|
-
tags: zod_1.z.string().optional().describe('Comma-separated tags to filter by'),
|
|
40343
|
-
limit: zod_1.z.number().optional().describe('Max results (default 50, max 200)'),
|
|
40344
|
-
},
|
|
40345
|
-
annotations: READ_ONLY,
|
|
40346
|
-
}, async (filters) => {
|
|
40347
|
-
if (isLocalMode) {
|
|
40348
|
-
return { content: [{ type: 'text', text: 'Evidence queries require cloud mode.' }] };
|
|
40349
|
-
}
|
|
40350
|
-
const result = await client.queryEvidence(filters);
|
|
40351
|
-
return {
|
|
40352
|
-
content: [{
|
|
40353
|
-
type: 'text',
|
|
40354
|
-
text: JSON.stringify(result, null, 2)
|
|
40355
|
-
}]
|
|
40356
|
-
};
|
|
40357
|
-
});
|
|
40358
|
-
server.registerTool('create_evidence', {
|
|
40359
|
-
title: 'Create Evidence',
|
|
40360
|
-
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.',
|
|
40361
|
-
inputSchema: {
|
|
40362
|
-
content: zod_1.z.string().describe('The evidence content — a finding, quote, or observation'),
|
|
40363
|
-
type: zod_1.z.enum(['friction', 'quote', 'observation', 'metric', 'request']).describe('Type of evidence'),
|
|
40364
|
-
productId: zod_1.z.string().describe('Product (Space) ID this evidence belongs to'),
|
|
40365
|
-
source: zod_1.z.string().optional().describe('Where this evidence came from (e.g., "User interview", "Bug report", "Support ticket")'),
|
|
40366
|
-
sourceUrl: zod_1.z.string().optional().describe('URL to the original source'),
|
|
40367
|
-
severity: zod_1.z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Severity level (required for friction type)'),
|
|
40368
|
-
sentiment: zod_1.z.enum(['positive', 'negative', 'neutral', 'mixed']).optional().describe('Emotional sentiment'),
|
|
40369
|
-
tags: zod_1.z.array(zod_1.z.string()).optional().describe('Category tags (e.g., ["Onboarding", "Performance"])'),
|
|
40370
|
-
stage: zod_1.z.string().optional().describe('User journey stage (e.g., "Discovery", "Checkout")'),
|
|
40371
|
-
},
|
|
40372
|
-
annotations: WRITE_OP,
|
|
40373
|
-
}, async ({ productId, ...rest }) => {
|
|
40374
|
-
if (isLocalMode) {
|
|
40375
|
-
return { content: [{ type: 'text', text: 'Creating evidence requires cloud mode.' }] };
|
|
40376
|
-
}
|
|
40377
|
-
const result = await client.createEvidence({ productId, ...rest });
|
|
40378
|
-
return {
|
|
40379
|
-
content: [{
|
|
40380
|
-
type: 'text',
|
|
40381
|
-
text: `Evidence created: ${result.id}\nType: ${result.type}\nContent: "${(result.content || '').slice(0, 80)}${(result.content || '').length > 80 ? '...' : ''}"`
|
|
40382
|
-
}]
|
|
40383
|
-
};
|
|
40384
|
-
});
|
|
40385
|
-
server.registerTool('link_evidence', {
|
|
40386
|
-
title: 'Link Evidence to Intent',
|
|
40387
|
-
description: 'Link or unlink evidence items to/from an intent. Linking evidence to intents establishes traceability between user problems and planned solutions.',
|
|
40388
|
-
inputSchema: {
|
|
40389
|
-
intentId: zod_1.z.string().describe('The intent ID to link evidence to'),
|
|
40390
|
-
link: zod_1.z.array(zod_1.z.string()).optional().describe('Evidence IDs to link to this intent'),
|
|
40391
|
-
unlink: zod_1.z.array(zod_1.z.string()).optional().describe('Evidence IDs to unlink from this intent'),
|
|
40392
|
-
},
|
|
40393
|
-
annotations: WRITE_OP,
|
|
40394
|
-
}, async ({ intentId, link, unlink }) => {
|
|
40395
|
-
if (isLocalMode) {
|
|
40396
|
-
return { content: [{ type: 'text', text: 'Evidence linking requires cloud mode.' }] };
|
|
40397
|
-
}
|
|
40398
|
-
const result = await client.linkEvidence(intentId, { link, unlink });
|
|
40399
|
-
const actions = [];
|
|
40400
|
-
if (link && link.length > 0)
|
|
40401
|
-
actions.push(`linked ${result.linked} evidence items`);
|
|
40402
|
-
if (unlink && unlink.length > 0)
|
|
40403
|
-
actions.push(`unlinked ${result.unlinked} evidence items`);
|
|
40404
|
-
return {
|
|
40405
|
-
content: [{
|
|
40406
|
-
type: 'text',
|
|
40407
|
-
text: `Intent ${intentId}: ${actions.join(', ')}. Total evidence linked: ${result.evidenceIds.length}`
|
|
40408
|
-
}]
|
|
40409
|
-
};
|
|
40410
|
-
});
|
|
40411
|
-
server.registerTool('verify_implementation', {
|
|
40412
|
-
title: 'Verify Implementation',
|
|
40413
|
-
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.',
|
|
40414
|
-
inputSchema: {
|
|
40415
|
-
intentId: zod_1.z.string().describe('The intent ID to verify against'),
|
|
40416
|
-
summary: zod_1.z.string().describe('Description of what was implemented and how'),
|
|
40417
|
-
codeChanges: zod_1.z.string().optional().describe('Summary of code changes (file list, key modifications)'),
|
|
40418
|
-
},
|
|
40419
|
-
annotations: WRITE_OP,
|
|
40420
|
-
}, async ({ intentId, summary, codeChanges }) => {
|
|
40421
|
-
if (isLocalMode) {
|
|
40422
|
-
return { content: [{ type: 'text', text: 'Verification requires cloud mode.' }] };
|
|
40423
|
-
}
|
|
40424
|
-
try {
|
|
40425
|
-
const result = await client.verifyImplementation(intentId, summary, codeChanges);
|
|
40426
|
-
let text = `Verification ${result.pass ? 'PASSED' : 'FAILED'} (score: ${result.score}/100)\n\n`;
|
|
40427
|
-
text += `${result.summary}\n\n`;
|
|
40428
|
-
for (const r of result.results) {
|
|
40429
|
-
const icon = r.status === 'pass' ? '[PASS]' : r.status === 'fail' ? '[FAIL]' : '[????]';
|
|
40430
|
-
text += `${icon} [${r.category}] ${r.item}\n ${r.reasoning}\n`;
|
|
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.`;
|
|
40431
40458
|
}
|
|
40432
|
-
return { content: [{ type: 'text', text }] };
|
|
40433
|
-
}
|
|
40434
|
-
catch (e) {
|
|
40435
|
-
return { content: [{ type: 'text', text: `Verification failed: ${e.message}` }] };
|
|
40436
|
-
}
|
|
40437
|
-
});
|
|
40438
|
-
server.registerTool('dismiss_signal', {
|
|
40439
|
-
title: 'Dismiss Signal',
|
|
40440
|
-
description: 'Dismiss a signal by ID. Use this when a signal has been addressed or is no longer relevant.',
|
|
40441
|
-
inputSchema: {
|
|
40442
|
-
signalId: zod_1.z.string().describe('The signal ID to dismiss'),
|
|
40443
|
-
},
|
|
40444
|
-
annotations: { ...WRITE_OP, idempotentHint: true },
|
|
40445
|
-
}, async ({ signalId }) => {
|
|
40446
|
-
if (isLocalMode) {
|
|
40447
|
-
return { content: [{ type: 'text', text: 'Dismissing signals requires cloud mode.' }] };
|
|
40448
|
-
}
|
|
40449
|
-
try {
|
|
40450
|
-
await client.dismissSignal(signalId);
|
|
40451
40459
|
return {
|
|
40452
40460
|
content: [{
|
|
40453
40461
|
type: 'text',
|
|
40454
|
-
text:
|
|
40462
|
+
text: responseText
|
|
40455
40463
|
}]
|
|
40456
40464
|
};
|
|
40457
|
-
}
|
|
40458
|
-
|
|
40459
|
-
|
|
40460
|
-
|
|
40461
|
-
|
|
40462
|
-
|
|
40463
|
-
|
|
40464
|
-
|
|
40465
|
-
|
|
40466
|
-
|
|
40467
|
-
|
|
40468
|
-
|
|
40469
|
-
|
|
40470
|
-
|
|
40471
|
-
|
|
40472
|
-
|
|
40473
|
-
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`,
|
|
40474
|
-
},
|
|
40475
|
-
}],
|
|
40476
|
-
};
|
|
40477
|
-
});
|
|
40478
|
-
server.prompt('review-risks', 'Analyze the intent graph for architectural risks, circular dependencies, bottlenecks, and stalled work.', {}, async () => {
|
|
40479
|
-
return {
|
|
40480
|
-
messages: [{
|
|
40481
|
-
role: 'user',
|
|
40482
|
-
content: {
|
|
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: [{
|
|
40483
40481
|
type: 'text',
|
|
40484
|
-
text:
|
|
40485
|
-
}
|
|
40486
|
-
|
|
40487
|
-
};
|
|
40488
|
-
|
|
40489
|
-
|
|
40490
|
-
|
|
40491
|
-
|
|
40492
|
-
|
|
40493
|
-
|
|
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 });
|
|
40513
|
+
return {
|
|
40514
|
+
content: [{
|
|
40494
40515
|
type: 'text',
|
|
40495
|
-
text:
|
|
40496
|
-
}
|
|
40497
|
-
|
|
40498
|
-
};
|
|
40499
|
-
|
|
40500
|
-
|
|
40501
|
-
|
|
40502
|
-
|
|
40503
|
-
|
|
40504
|
-
|
|
40505
|
-
|
|
40506
|
-
|
|
40516
|
+
text: `Intent created: ${result.id}\nTitle: "${result.title}"\nStatus: ${result.status}\nProduct: ${result.productId || productId}`
|
|
40517
|
+
}]
|
|
40518
|
+
};
|
|
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);
|
|
40548
|
+
return {
|
|
40549
|
+
content: [{
|
|
40507
40550
|
type: 'text',
|
|
40508
|
-
text: `
|
|
40509
|
-
}
|
|
40510
|
-
|
|
40511
|
-
};
|
|
40512
|
-
|
|
40513
|
-
|
|
40514
|
-
|
|
40515
|
-
|
|
40516
|
-
|
|
40517
|
-
|
|
40518
|
-
|
|
40519
|
-
|
|
40520
|
-
|
|
40521
|
-
|
|
40522
|
-
|
|
40523
|
-
|
|
40524
|
-
}
|
|
40525
|
-
|
|
40526
|
-
|
|
40527
|
-
|
|
40528
|
-
|
|
40529
|
-
|
|
40530
|
-
|
|
40531
|
-
};
|
|
40532
|
-
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 () => {
|
|
40533
|
-
return {
|
|
40534
|
-
messages: [{
|
|
40535
|
-
role: 'user',
|
|
40536
|
-
content: {
|
|
40551
|
+
text: `Intent ${intentId} updated. Fields changed: ${changedFields.join(', ')}`
|
|
40552
|
+
}]
|
|
40553
|
+
};
|
|
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);
|
|
40572
|
+
return {
|
|
40573
|
+
content: [{
|
|
40537
40574
|
type: 'text',
|
|
40538
|
-
text: (
|
|
40539
|
-
}
|
|
40540
|
-
|
|
40541
|
-
};
|
|
40542
|
-
|
|
40543
|
-
|
|
40544
|
-
|
|
40545
|
-
|
|
40546
|
-
|
|
40547
|
-
|
|
40548
|
-
|
|
40549
|
-
|
|
40550
|
-
|
|
40551
|
-
|
|
40552
|
-
|
|
40553
|
-
|
|
40554
|
-
|
|
40555
|
-
|
|
40556
|
-
|
|
40557
|
-
|
|
40558
|
-
|
|
40559
|
-
|
|
40560
|
-
|
|
40561
|
-
|
|
40562
|
-
if (format === 'cursorrules') {
|
|
40563
|
-
const content = (0, intent_compiler_1.formatCursorRules)(spec);
|
|
40564
|
-
const filePath = (0, path_1.resolve)(process.cwd(), path || '.cursorrules');
|
|
40565
|
-
(0, fs_1.writeFileSync)(filePath, content, 'utf-8');
|
|
40575
|
+
text: JSON.stringify(result, null, 2)
|
|
40576
|
+
}]
|
|
40577
|
+
};
|
|
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 });
|
|
40566
40599
|
return {
|
|
40567
40600
|
content: [{
|
|
40568
40601
|
type: 'text',
|
|
40569
|
-
text:
|
|
40570
|
-
}]
|
|
40602
|
+
text: `Evidence created: ${result.id}\nType: ${result.type}\nContent: "${(result.content || '').slice(0, 80)}${(result.content || '').length > 80 ? '...' : ''}"`
|
|
40603
|
+
}]
|
|
40571
40604
|
};
|
|
40572
|
-
}
|
|
40573
|
-
|
|
40574
|
-
|
|
40575
|
-
|
|
40576
|
-
|
|
40577
|
-
|
|
40578
|
-
|
|
40579
|
-
|
|
40580
|
-
}
|
|
40581
|
-
|
|
40582
|
-
|
|
40583
|
-
|
|
40584
|
-
|
|
40585
|
-
|
|
40586
|
-
|
|
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`);
|
|
40587
40625
|
return {
|
|
40588
40626
|
content: [{
|
|
40589
40627
|
type: 'text',
|
|
40590
|
-
text:
|
|
40628
|
+
text: `Intent ${intentId}: ${actions.join(', ')}. Total evidence linked: ${result.evidenceIds.length}`
|
|
40629
|
+
}]
|
|
40630
|
+
};
|
|
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
|
+
},
|
|
40591
40672
|
}],
|
|
40592
40673
|
};
|
|
40593
|
-
}
|
|
40594
|
-
})
|
|
40595
|
-
// ============================================================
|
|
40596
|
-
// Resources
|
|
40597
|
-
// ============================================================
|
|
40598
|
-
server.resource('intent://current', 'intent://current', async (uri) => {
|
|
40599
|
-
if (isLocalMode) {
|
|
40600
|
-
const intents = (0, local_reader_1.readLocalIntents)();
|
|
40674
|
+
});
|
|
40675
|
+
server.prompt('review-risks', 'Analyze the intent graph for architectural risks, circular dependencies, bottlenecks, and stalled work.', {}, async () => {
|
|
40601
40676
|
return {
|
|
40602
|
-
|
|
40603
|
-
|
|
40604
|
-
|
|
40605
|
-
|
|
40606
|
-
|
|
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
|
+
}],
|
|
40607
40684
|
};
|
|
40608
|
-
}
|
|
40609
|
-
|
|
40610
|
-
const current = intents[0] || (await client.listIntents())[0] || null;
|
|
40611
|
-
return {
|
|
40612
|
-
contents: [{
|
|
40613
|
-
uri: uri.href,
|
|
40614
|
-
mimeType: 'application/json',
|
|
40615
|
-
text: JSON.stringify(current, null, 2),
|
|
40616
|
-
}]
|
|
40617
|
-
};
|
|
40618
|
-
});
|
|
40619
|
-
server.resource('intent://graph', 'intent://graph', async (uri) => {
|
|
40620
|
-
if (isLocalMode) {
|
|
40685
|
+
});
|
|
40686
|
+
server.prompt('what-next', 'Suggest the highest-priority intent to work on next, based on dependency graph analysis and current status.', {}, async () => {
|
|
40621
40687
|
return {
|
|
40622
|
-
|
|
40623
|
-
|
|
40624
|
-
|
|
40625
|
-
|
|
40626
|
-
|
|
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
|
+
}],
|
|
40627
40695
|
};
|
|
40628
|
-
}
|
|
40629
|
-
|
|
40630
|
-
|
|
40631
|
-
|
|
40632
|
-
|
|
40633
|
-
|
|
40634
|
-
|
|
40635
|
-
|
|
40636
|
-
|
|
40637
|
-
|
|
40638
|
-
|
|
40639
|
-
|
|
40640
|
-
|
|
40641
|
-
|
|
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'),
|
|
40642
40728
|
};
|
|
40643
|
-
})
|
|
40644
|
-
server.resource('intent://workspace-strategy', 'intent://workspace-strategy', async (uri) => {
|
|
40645
|
-
if (isLocalMode) {
|
|
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 () => {
|
|
40646
40730
|
return {
|
|
40647
|
-
|
|
40648
|
-
|
|
40649
|
-
|
|
40650
|
-
|
|
40651
|
-
|
|
40731
|
+
messages: [{
|
|
40732
|
+
role: 'user',
|
|
40733
|
+
content: {
|
|
40734
|
+
type: 'text',
|
|
40735
|
+
text: (0, intent_compiler_1.getCompileIntentPrompt)(),
|
|
40736
|
+
},
|
|
40737
|
+
}],
|
|
40652
40738
|
};
|
|
40653
|
-
}
|
|
40654
|
-
|
|
40655
|
-
|
|
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;
|
|
40656
40808
|
return {
|
|
40657
40809
|
contents: [{
|
|
40658
40810
|
uri: uri.href,
|
|
40659
40811
|
mimeType: 'application/json',
|
|
40660
|
-
text: JSON.stringify(
|
|
40661
|
-
name: workspace.name,
|
|
40662
|
-
strategy: workspace.strategy,
|
|
40663
|
-
constitutionRules: workspace.constitutionRules?.filter(r => r.isActive),
|
|
40664
|
-
}, null, 2),
|
|
40812
|
+
text: JSON.stringify(current, null, 2),
|
|
40665
40813
|
}]
|
|
40666
40814
|
};
|
|
40667
|
-
}
|
|
40668
|
-
|
|
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
|
+
}));
|
|
40669
40833
|
return {
|
|
40670
40834
|
contents: [{
|
|
40671
40835
|
uri: uri.href,
|
|
40672
40836
|
mimeType: 'application/json',
|
|
40673
|
-
text: JSON.stringify(
|
|
40837
|
+
text: JSON.stringify(graph, null, 2),
|
|
40674
40838
|
}]
|
|
40675
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);
|
|
40676
40881
|
}
|
|
40677
|
-
|
|
40678
|
-
|
|
40679
|
-
|
|
40680
|
-
|
|
40681
|
-
|
|
40682
|
-
const transport = new stdio_js_1.StdioServerTransport();
|
|
40683
|
-
await server.connect(transport);
|
|
40684
|
-
}
|
|
40685
|
-
main().catch((error) => {
|
|
40686
|
-
console.error('Failed to start Pathmode MCP server:', error);
|
|
40687
|
-
process.exit(1);
|
|
40688
|
-
});
|
|
40882
|
+
main().catch((error) => {
|
|
40883
|
+
console.error('Failed to start Pathmode MCP server:', error);
|
|
40884
|
+
process.exit(1);
|
|
40885
|
+
});
|
|
40886
|
+
} // end startMcpServer()
|
|
40689
40887
|
|
|
40690
40888
|
})();
|
|
40691
40889
|
|