@systemdox/mcp-server 0.1.49 → 0.1.51
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 +30 -1
- package/dist/cli.js +578 -169
- package/dist/http.js +1107 -730
- package/dist/index.d.ts +2 -2
- package/dist/index.js +751 -371
- package/dist/install-skill.js +13 -16
- package/dist/lib/api-client.d.ts +2 -3
- package/dist/lib/api-client.js +7 -14
- package/dist/lib/context-builder.js +14 -7
- package/dist/lib/session-brief.d.ts +59 -0
- package/dist/lib/session-brief.js +174 -0
- package/dist/lib/skills.d.ts +7 -0
- package/dist/lib/skills.js +7 -2
- package/dist/lib/team-setup-types.d.ts +18 -0
- package/dist/plugin-path.js +7 -14
- package/dist/server.d.ts +5 -1
- package/dist/server.js +3582 -0
- package/dist/setup-mcp.js +11 -15
- package/dist/setup-team.js +7 -14
- package/dist/sync-memory.js +7 -14
- package/dist/tools/document-graph.d.ts +87 -0
- package/dist/tools/get-team-setup.d.ts +6 -0
- package/dist/tools/skills.js +7 -3
- package/dist/tools/team-setup.js +2 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -301,14 +301,6 @@ var init_api_client = __esm({
|
|
|
301
301
|
this.config = config2;
|
|
302
302
|
this.cache = new ResponseCache(config2.cacheTtlMs);
|
|
303
303
|
}
|
|
304
|
-
async validate() {
|
|
305
|
-
const res = await this.fetchRaw("/v1/health");
|
|
306
|
-
if (!res.ok) {
|
|
307
|
-
throw new Error(
|
|
308
|
-
`SystemDox API authentication failed (${res.status}). Check your SYSTEMDOX_API_KEY.`
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
304
|
async get(path, params) {
|
|
313
305
|
const url = this.buildUrl(path, params);
|
|
314
306
|
const cacheKey = url.toString();
|
|
@@ -322,8 +314,8 @@ var init_api_client = __esm({
|
|
|
322
314
|
}
|
|
323
315
|
/** A read that skips the cache: an install must take the version that is
|
|
324
316
|
* latest now, not the one cached before someone published another. */
|
|
325
|
-
async getFresh(path) {
|
|
326
|
-
const res = await this.fetchRaw(path);
|
|
317
|
+
async getFresh(path, signal) {
|
|
318
|
+
const res = await this.fetchRaw(path, void 0, signal);
|
|
327
319
|
if (!res.ok) throw new SystemDoxApiError(res.status, await res.text());
|
|
328
320
|
return await res.json();
|
|
329
321
|
}
|
|
@@ -353,8 +345,8 @@ var init_api_client = __esm({
|
|
|
353
345
|
// One method per operationId. Every read skips the cache: setup, sync and
|
|
354
346
|
// plugin-path act on what the workspace holds now.
|
|
355
347
|
/** getOwnTeamSetup: the key's own workspace, named in `workspace`. */
|
|
356
|
-
getOwnTeamSetup() {
|
|
357
|
-
return this.getFresh(TEAM_SETUP);
|
|
348
|
+
getOwnTeamSetup(signal) {
|
|
349
|
+
return this.getFresh(TEAM_SETUP, signal);
|
|
358
350
|
}
|
|
359
351
|
getTeamSetup(workspace) {
|
|
360
352
|
return this.getFresh(`${TEAM_SETUP}/${seg(workspace)}`);
|
|
@@ -458,14 +450,15 @@ var init_api_client = __esm({
|
|
|
458
450
|
}
|
|
459
451
|
return url;
|
|
460
452
|
}
|
|
461
|
-
async fetchRaw(path, params) {
|
|
453
|
+
async fetchRaw(path, params, signal) {
|
|
462
454
|
const url = this.buildUrl(path, params);
|
|
463
455
|
return fetch(url, {
|
|
464
456
|
headers: {
|
|
465
457
|
Authorization: `Bearer ${this.config.apiKey}`,
|
|
466
458
|
"X-API-Key": this.config.apiKey,
|
|
467
459
|
Accept: "application/json"
|
|
468
|
-
}
|
|
460
|
+
},
|
|
461
|
+
signal
|
|
469
462
|
});
|
|
470
463
|
}
|
|
471
464
|
};
|
|
@@ -1030,11 +1023,11 @@ async function people(opts, ws, files, report, hint) {
|
|
|
1030
1023
|
report.people.push({ kind: g.kind, answer: ANSWER_WORDS[result.stored[0].answer], label: result.stored[0].label, personId: result.stored[0].personId });
|
|
1031
1024
|
break;
|
|
1032
1025
|
}
|
|
1033
|
-
const
|
|
1034
|
-
if (
|
|
1035
|
-
out(` ${
|
|
1026
|
+
const refused2 = result.refused[0];
|
|
1027
|
+
if (refused2?.code === "SYD-K021") {
|
|
1028
|
+
out(` ${refused2.detail}. Add them in Staff first, or answer p to file them.`);
|
|
1036
1029
|
choices = choices.filter((c) => c !== "m");
|
|
1037
|
-
} else if (
|
|
1030
|
+
} else if (refused2) out(` ${refused2.detail}`);
|
|
1038
1031
|
}
|
|
1039
1032
|
}
|
|
1040
1033
|
for (let i = 0; i < filed.length; i += 50) {
|
|
@@ -1773,9 +1766,9 @@ async function runTeamSetup(opts) {
|
|
|
1773
1766
|
const id = `${ws}-setup@${marketplaceName}`;
|
|
1774
1767
|
const installed = await readJsonQuietly(join3(pluginsDir, "installed_plugins.json"));
|
|
1775
1768
|
if (!installed?.plugins?.[id]) {
|
|
1776
|
-
const
|
|
1769
|
+
const known2 = await readJsonQuietly(join3(pluginsDir, "known_marketplaces.json"));
|
|
1777
1770
|
const install = `Run \`claude plugin install ${id}\` in your own terminal and accept the command it shows.`;
|
|
1778
|
-
const steps =
|
|
1771
|
+
const steps = known2 && typeof known2 === "object" && known2[marketplaceName] ? ["One step is left:", `1. ${install}`] : ["Two steps are left:", "1. Open Claude Code once in any folder, then close it.", `2. ${install}`];
|
|
1779
1772
|
out("");
|
|
1780
1773
|
steps.forEach((l) => out(l));
|
|
1781
1774
|
}
|
|
@@ -1835,6 +1828,7 @@ __export(setup_mcp_exports, {
|
|
|
1835
1828
|
writeServerEntry: () => writeServerEntry
|
|
1836
1829
|
});
|
|
1837
1830
|
import { existsSync as existsSync3, readFileSync } from "fs";
|
|
1831
|
+
import { mkdir as mkdir4 } from "fs/promises";
|
|
1838
1832
|
import { join as join4 } from "path";
|
|
1839
1833
|
async function validateApiKey(apiKey, apiUrl) {
|
|
1840
1834
|
try {
|
|
@@ -1962,6 +1956,7 @@ async function setupMcpServer(opts) {
|
|
|
1962
1956
|
}
|
|
1963
1957
|
try {
|
|
1964
1958
|
if ((await readCredentials())[workspace]?.apiKey !== apiKey) await writeCredentials(workspace, apiKey);
|
|
1959
|
+
await mkdir4(join4(homeDir(), ".claude", "skills"), { recursive: true });
|
|
1965
1960
|
const several = Object.keys(await readCredentials()).length > 1;
|
|
1966
1961
|
for (const target of selected) {
|
|
1967
1962
|
const removed = await writeServerEntry(target.path, apiUrl, several ? workspace : void 0);
|
|
@@ -1979,9 +1974,10 @@ async function setupMcpServer(opts) {
|
|
|
1979
1974
|
Your key for ${workspace} is in ${credentialsPath()} (readable by you alone), not in these configs.
|
|
1980
1975
|
|
|
1981
1976
|
Done! Restart your AI coding tool to activate the SystemDox MCP server.
|
|
1982
|
-
The server provides
|
|
1977
|
+
The server provides 25 tools: get_context, get_team_setup, get_checks,
|
|
1983
1978
|
get_project_rules, get_prompt_templates, get_specs, get_docs,
|
|
1984
1979
|
get_tech_stack, get_planning_context, get_plan_board, get_change,
|
|
1980
|
+
get_document_links, trace_document_path, get_graph_stats,
|
|
1985
1981
|
create_check, create_project_rule, create_prompt_template,
|
|
1986
1982
|
update_spec, report_decision, publish_skill, list_skills,
|
|
1987
1983
|
install_skill, publish_setup_part, resolve_person, sync_memory.
|
|
@@ -2006,7 +2002,7 @@ var init_setup_mcp = __esm({
|
|
|
2006
2002
|
|
|
2007
2003
|
// src/lib/skills.ts
|
|
2008
2004
|
import { createHash as createHash3 } from "crypto";
|
|
2009
|
-
import { lstat, mkdir as
|
|
2005
|
+
import { lstat, mkdir as mkdir5, readdir as readdir3, readFile as readFile4, rm as rm3, writeFile as writeFile4 } from "fs/promises";
|
|
2010
2006
|
import { existsSync as existsSync4 } from "fs";
|
|
2011
2007
|
import { homedir as homedir2 } from "os";
|
|
2012
2008
|
import { dirname as dirname4, join as join5, relative, resolve as resolve3, sep } from "path";
|
|
@@ -2017,7 +2013,7 @@ function isValidName(name) {
|
|
|
2017
2013
|
return name.length <= 64 && NAME_RE.test(name);
|
|
2018
2014
|
}
|
|
2019
2015
|
function isValidPath(path) {
|
|
2020
|
-
return path.length > 0 && path.length <= 512 && !path.startsWith("/") && path.split("/").every((s) => SEGMENT_RE.test(s) && s !== "." && s !== "..");
|
|
2016
|
+
return path.length > 0 && path.length <= 512 && !path.startsWith("/") && path.split("/").every((s) => SEGMENT_RE.test(s) && s !== "." && s !== ".." && s !== PLUGIN_MANIFEST_FOLDER);
|
|
2021
2017
|
}
|
|
2022
2018
|
function frontmatterName(skillMd) {
|
|
2023
2019
|
const block = /^---\r?\n([\s\S]*?)\r?\n---/.exec(skillMd);
|
|
@@ -2112,15 +2108,15 @@ async function installSkill(version, dest, { overwrite = false } = {}) {
|
|
|
2112
2108
|
}
|
|
2113
2109
|
const present = walked.files;
|
|
2114
2110
|
const manifest = await readManifest(dest);
|
|
2115
|
-
const
|
|
2111
|
+
const known2 = Object.fromEntries(
|
|
2116
2112
|
Object.entries(manifest?.files ?? {}).filter(([p, h]) => isValidPath(p) && typeof h === "string")
|
|
2117
2113
|
);
|
|
2118
2114
|
const edited = [];
|
|
2119
2115
|
const untracked = [];
|
|
2120
2116
|
for (const path of present) {
|
|
2121
2117
|
const content = await readFile4(join5(dest, ...path.split("/")), "utf8");
|
|
2122
|
-
if (Object.hasOwn(
|
|
2123
|
-
if (sha2563(content) !==
|
|
2118
|
+
if (Object.hasOwn(known2, path)) {
|
|
2119
|
+
if (sha2563(content) !== known2[path]) edited.push(path);
|
|
2124
2120
|
} else if (!incoming.has(path) || sha2563(content) !== incoming.get(path).sha256) {
|
|
2125
2121
|
if (incoming.has(path) || !manifest) edited.push(path);
|
|
2126
2122
|
else untracked.push(path);
|
|
@@ -2131,13 +2127,13 @@ async function installSkill(version, dest, { overwrite = false } = {}) {
|
|
|
2131
2127
|
const written = [];
|
|
2132
2128
|
for (const f of version.files) {
|
|
2133
2129
|
const full = within(dest, f.path);
|
|
2134
|
-
await
|
|
2130
|
+
await mkdir5(dirname4(full), { recursive: true });
|
|
2135
2131
|
await writeFile4(full, f.content, "utf8");
|
|
2136
2132
|
written.push(f.path);
|
|
2137
2133
|
}
|
|
2138
2134
|
const removed = [];
|
|
2139
2135
|
const keptEdited = [];
|
|
2140
|
-
for (const path of Object.keys(
|
|
2136
|
+
for (const path of Object.keys(known2)) {
|
|
2141
2137
|
if (incoming.has(path) || !present.includes(path)) continue;
|
|
2142
2138
|
if (edited.includes(path)) {
|
|
2143
2139
|
keptEdited.push(path);
|
|
@@ -2166,6 +2162,9 @@ function fenceFor(text3) {
|
|
|
2166
2162
|
const longest = Math.max(0, ...(text3.match(/`+/g) ?? []).map((r) => r.length));
|
|
2167
2163
|
return "`".repeat(Math.max(3, longest + 1));
|
|
2168
2164
|
}
|
|
2165
|
+
function afterInstall(skillMd) {
|
|
2166
|
+
return `No restart is needed: a running Claude Code session picks it up. If one does not list it yet, read ${skillMd} and follow it, or run /reload-skills.`;
|
|
2167
|
+
}
|
|
2169
2168
|
function describeInstall(name, r) {
|
|
2170
2169
|
const lines = [
|
|
2171
2170
|
`Installed ${name} version ${r.version} into ${r.dest} (${r.written.length} file(s)).`
|
|
@@ -2175,15 +2174,16 @@ function describeInstall(name, r) {
|
|
|
2175
2174
|
lines.push(`Kept, edited here and no longer in the skill: ${r.keptEdited.join(", ")}`);
|
|
2176
2175
|
}
|
|
2177
2176
|
if (r.keptUntracked.length) lines.push(`Kept, added here: ${r.keptUntracked.join(", ")}`);
|
|
2178
|
-
lines.push("
|
|
2177
|
+
lines.push(afterInstall(join5(r.dest, "SKILL.md")));
|
|
2179
2178
|
return lines.join("\n");
|
|
2180
2179
|
}
|
|
2181
|
-
var MANIFEST_FILE, ENTRY_FILE, MAX_FILES, MAX_FILE_BYTES, MAX_TOTAL_BYTES, NAME_RE, SEGMENT_RE, SKIPPED, SkillInstallRefused;
|
|
2180
|
+
var MANIFEST_FILE, ENTRY_FILE, PLUGIN_MANIFEST_FOLDER, MAX_FILES, MAX_FILE_BYTES, MAX_TOTAL_BYTES, NAME_RE, SEGMENT_RE, SKIPPED, SkillInstallRefused;
|
|
2182
2181
|
var init_skills = __esm({
|
|
2183
2182
|
"src/lib/skills.ts"() {
|
|
2184
2183
|
"use strict";
|
|
2185
2184
|
MANIFEST_FILE = ".systemdox-skill.json";
|
|
2186
2185
|
ENTRY_FILE = "SKILL.md";
|
|
2186
|
+
PLUGIN_MANIFEST_FOLDER = ".claude-plugin";
|
|
2187
2187
|
MAX_FILES = 100;
|
|
2188
2188
|
MAX_FILE_BYTES = 256 * 1024;
|
|
2189
2189
|
MAX_TOTAL_BYTES = 1024 * 1024;
|
|
@@ -2301,7 +2301,7 @@ __export(plugin_path_exports, {
|
|
|
2301
2301
|
});
|
|
2302
2302
|
import { createHash as createHash4, randomBytes } from "crypto";
|
|
2303
2303
|
import { existsSync as existsSync5 } from "fs";
|
|
2304
|
-
import { mkdir as
|
|
2304
|
+
import { mkdir as mkdir6, rename as rename2, writeFile as writeFile5 } from "fs/promises";
|
|
2305
2305
|
import { dirname as dirname5, join as join6, resolve as resolve5, sep as sep2 } from "path";
|
|
2306
2306
|
import { inflateRawSync } from "zlib";
|
|
2307
2307
|
function parsePluginPathArgs(argv) {
|
|
@@ -2399,13 +2399,13 @@ async function unpackZip(zip, dest) {
|
|
|
2399
2399
|
return full;
|
|
2400
2400
|
});
|
|
2401
2401
|
const data = entries2.map((e) => e.directory ? void 0 : entryData(zip, e));
|
|
2402
|
-
await
|
|
2402
|
+
await mkdir6(root, { recursive: true });
|
|
2403
2403
|
for (const [i, entry] of entries2.entries()) {
|
|
2404
2404
|
if (entry.directory) {
|
|
2405
|
-
await
|
|
2405
|
+
await mkdir6(targets[i], { recursive: true });
|
|
2406
2406
|
continue;
|
|
2407
2407
|
}
|
|
2408
|
-
await
|
|
2408
|
+
await mkdir6(dirname5(targets[i]), { recursive: true });
|
|
2409
2409
|
await writeFile5(targets[i], data[i], { mode: entry.mode ?? 420 });
|
|
2410
2410
|
}
|
|
2411
2411
|
return entries2.filter((e) => !e.directory).length;
|
|
@@ -2508,6 +2508,148 @@ var init_plugin_path = __esm({
|
|
|
2508
2508
|
}
|
|
2509
2509
|
});
|
|
2510
2510
|
|
|
2511
|
+
// src/lib/session-brief.ts
|
|
2512
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2513
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
2514
|
+
import { join as join7 } from "path";
|
|
2515
|
+
function noKey(reason) {
|
|
2516
|
+
return `This computer has no SystemDox key it can use (${reason}), so the team's rules and skills cannot be read and every SystemDox tool fails. Tell the person once: \`${SETUP_COMMAND}\` in their own terminal asks for one.`;
|
|
2517
|
+
}
|
|
2518
|
+
function keyReason(message) {
|
|
2519
|
+
if (/holds keys for/.test(message)) return "keys for several workspaces are stored; set SYSTEMDOX_WORKSPACE to the one to use";
|
|
2520
|
+
if (/holds no key for/.test(message)) return "SYSTEMDOX_WORKSPACE names a workspace with no stored key";
|
|
2521
|
+
if (/No SystemDox key/.test(message)) return "none is stored";
|
|
2522
|
+
return "the stored keys could not be read";
|
|
2523
|
+
}
|
|
2524
|
+
function olderApi(setup) {
|
|
2525
|
+
return `SystemDox workspace ${setup.workspace}. Before writing code in one of its repositories, call get_context with the task and the files you expect to touch, and obey what it returns. get_team_setup lists the team setup.`;
|
|
2526
|
+
}
|
|
2527
|
+
async function readJsonQuietly2(path) {
|
|
2528
|
+
try {
|
|
2529
|
+
return JSON.parse(await readFile5(path, "utf8"));
|
|
2530
|
+
} catch {
|
|
2531
|
+
return void 0;
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
async function computerState(setup) {
|
|
2535
|
+
const id = `${setup.workspace}-setup@systemdox-${setup.workspace}`;
|
|
2536
|
+
const installed = await readJsonQuietly2(join7(homeDir(), ".claude", "plugins", "installed_plugins.json"));
|
|
2537
|
+
const entries2 = installed?.plugins?.[id];
|
|
2538
|
+
const entry = Array.isArray(entries2) ? entries2.find((e) => (e?.scope ?? "user") === "user") : void 0;
|
|
2539
|
+
const state = {
|
|
2540
|
+
missing: [],
|
|
2541
|
+
behind: [],
|
|
2542
|
+
notFromSystemDox: [],
|
|
2543
|
+
pluginInstalled: !!entry,
|
|
2544
|
+
pluginVersion: entry?.version
|
|
2545
|
+
};
|
|
2546
|
+
const pluginCurrent = !!entry && !!setup.plugin && entry.version === `${setup.plugin.version}.0.0`;
|
|
2547
|
+
for (const skill of setup.sessionBrief?.skills ?? []) {
|
|
2548
|
+
const dir = defaultSkillDir(skill.name);
|
|
2549
|
+
if (!existsSync6(dir)) {
|
|
2550
|
+
if (!(pluginCurrent && setup.plugin?.skills?.[skill.name] !== void 0)) state.missing.push(skill.name);
|
|
2551
|
+
continue;
|
|
2552
|
+
}
|
|
2553
|
+
const manifest = await readJsonQuietly2(join7(dir, MANIFEST_FILE));
|
|
2554
|
+
if (typeof manifest?.version !== "number" || !Number.isInteger(manifest.version)) {
|
|
2555
|
+
state.notFromSystemDox.push(skill.name);
|
|
2556
|
+
} else if (manifest.version !== skill.latestVersion) {
|
|
2557
|
+
state.behind.push({ name: skill.name, here: manifest.version, latest: skill.latestVersion });
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
return state;
|
|
2561
|
+
}
|
|
2562
|
+
function hasPersonParts(setup) {
|
|
2563
|
+
return Object.keys(setup.parts ?? {}).length > 0 || (setup.memory ?? []).length > 0 || (setup.sessionBrief?.heldSkills ?? []).some((h) => h.reason === "not-plain");
|
|
2564
|
+
}
|
|
2565
|
+
function listWithin(items, room) {
|
|
2566
|
+
const whole2 = items.join(", ");
|
|
2567
|
+
if (whole2.length <= room) return whole2;
|
|
2568
|
+
const kept = [];
|
|
2569
|
+
let used = 0;
|
|
2570
|
+
for (let i = 0; i < items.length; i++) {
|
|
2571
|
+
const rest = items.length - i - 1;
|
|
2572
|
+
const cost = items[i].length + (kept.length ? 2 : 0);
|
|
2573
|
+
if (used + cost + ` and ${MORE(rest)}`.length > room) break;
|
|
2574
|
+
kept.push(items[i]);
|
|
2575
|
+
used += cost;
|
|
2576
|
+
}
|
|
2577
|
+
return kept.length ? `${kept.join(", ")} and ${MORE(items.length - kept.length)}` : `all ${MORE(items.length)}`;
|
|
2578
|
+
}
|
|
2579
|
+
function localSection(setup, state, room) {
|
|
2580
|
+
const rest = hasPersonParts(setup) ? ` and that \`${SETUP_COMMAND}\` in their own terminal adds the rest of the team setup` : "";
|
|
2581
|
+
const latest = new Map((setup.sessionBrief?.skills ?? []).map((s) => [s.name, s.latestVersion]));
|
|
2582
|
+
const lines = [];
|
|
2583
|
+
if (state.missing.length) {
|
|
2584
|
+
lines.push({
|
|
2585
|
+
head: "On this computer, missing: ",
|
|
2586
|
+
items: state.missing.map((name) => `${name} version ${latest.get(name)}`),
|
|
2587
|
+
tail: `. Install each now with install_skill at that version, before your first answer. Then tell the person what you installed${rest}. When a skill's line matches the request and it is not loaded yet, read ~/.claude/skills/<name>/SKILL.md and follow it.`
|
|
2588
|
+
});
|
|
2589
|
+
}
|
|
2590
|
+
if (state.behind.length) {
|
|
2591
|
+
lines.push({
|
|
2592
|
+
head: "On this computer, behind: ",
|
|
2593
|
+
items: state.behind.map((b) => `${b.name} (version ${b.here} here, latest ${b.latest})`),
|
|
2594
|
+
tail: ". install_skill updates each to that latest version; if it refuses because files were edited, tell the person and leave them."
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
if (state.notFromSystemDox.length) {
|
|
2598
|
+
lines.push({ head: "Not from SystemDox, left alone: ", items: state.notFromSystemDox, tail: "." });
|
|
2599
|
+
}
|
|
2600
|
+
const fixed = () => lines.reduce((n, l) => n + l.head.length + l.tail.length + LIST_MIN, 0) + Math.max(0, lines.length - 1);
|
|
2601
|
+
while (lines.length && fixed() > room) lines.pop();
|
|
2602
|
+
if (!lines.length) return "";
|
|
2603
|
+
const share = Math.floor((room - fixed()) / lines.length) + LIST_MIN;
|
|
2604
|
+
return lines.map((l) => `${l.head}${listWithin(l.items, share)}${l.tail}`).join("\n");
|
|
2605
|
+
}
|
|
2606
|
+
function fit(text3, section) {
|
|
2607
|
+
const whole2 = section ? `${text3}
|
|
2608
|
+
|
|
2609
|
+
${section}` : text3;
|
|
2610
|
+
if (whole2.length <= INSTRUCTIONS_MAX) return whole2;
|
|
2611
|
+
return text3.length <= INSTRUCTIONS_MAX ? text3 : `${text3.slice(0, INSTRUCTIONS_MAX - 1)}\u2026`;
|
|
2612
|
+
}
|
|
2613
|
+
async function readTeamSetup(apiClient2, { keyProblem: keyProblem2, timeoutMs = BRIEF_TIMEOUT_MS } = {}) {
|
|
2614
|
+
if (keyProblem2) return { problem: noKey(keyProblem2) };
|
|
2615
|
+
try {
|
|
2616
|
+
const setup = await apiClient2.getOwnTeamSetup(AbortSignal.timeout(timeoutMs));
|
|
2617
|
+
return typeof setup?.workspace === "string" ? { setup } : { problem: NOT_CHECKED };
|
|
2618
|
+
} catch (err) {
|
|
2619
|
+
const status = err?.status;
|
|
2620
|
+
return { problem: status === 401 || status === 403 ? REFUSED : NOT_CHECKED };
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
async function sessionInstructions(apiClient2, options = {}) {
|
|
2624
|
+
const read = await readTeamSetup(apiClient2, options);
|
|
2625
|
+
if ("problem" in read) return read.problem;
|
|
2626
|
+
const text3 = read.setup.sessionBrief?.text ?? olderApi(read.setup);
|
|
2627
|
+
try {
|
|
2628
|
+
if (!options.localFiles) return fit(text3, read.setup.sessionBrief?.skills?.length ? CANNOT_SEE : "");
|
|
2629
|
+
const room = INSTRUCTIONS_MAX - text3.length - 2;
|
|
2630
|
+
return fit(text3, localSection(read.setup, await computerState(read.setup), room));
|
|
2631
|
+
} catch {
|
|
2632
|
+
return fit(text3, "");
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
var INSTRUCTIONS_MAX, BRIEF_TIMEOUT_MS, NO_KEY, SETUP_COMMAND, REFUSED, NOT_CHECKED, CANNOT_SEE, MORE, LIST_MIN;
|
|
2636
|
+
var init_session_brief = __esm({
|
|
2637
|
+
"src/lib/session-brief.ts"() {
|
|
2638
|
+
"use strict";
|
|
2639
|
+
init_skills();
|
|
2640
|
+
init_team_setup_files();
|
|
2641
|
+
INSTRUCTIONS_MAX = 2e3;
|
|
2642
|
+
BRIEF_TIMEOUT_MS = 8e3;
|
|
2643
|
+
NO_KEY = "no-key-on-this-computer";
|
|
2644
|
+
SETUP_COMMAND = "npx -y @systemdox/mcp-server setup";
|
|
2645
|
+
REFUSED = `SystemDox refused this computer's key, so the team's rules and skills cannot be read and every SystemDox tool fails. Tell the person once: a new key comes from the SystemDox console, Settings \u203A API keys, and \`${SETUP_COMMAND}\` in their own terminal stores it.`;
|
|
2646
|
+
NOT_CHECKED = "SystemDox could not be read when this session started, so the team's skills were not checked. Call get_team_setup to check again before relying on them.";
|
|
2647
|
+
CANNOT_SEE = "This server cannot see this computer's disk. For each skill listed under Team skills: if ~/.claude/skills/<name>/ is absent, or its .systemdox-skill.json names another version than get_team_setup lists, call install_skill with that version and write what it returns, unless the latest team plugin carries it. Leave a folder with no such file alone. If a file there differs from the hash that file holds for it, it was edited: ask the person before replacing it.";
|
|
2648
|
+
MORE = (n) => `${n} more (get_team_setup lists them)`;
|
|
2649
|
+
LIST_MIN = `all ${MORE(100)}`.length;
|
|
2650
|
+
}
|
|
2651
|
+
});
|
|
2652
|
+
|
|
2511
2653
|
// src/lib/language.ts
|
|
2512
2654
|
function detectLanguage(filePath) {
|
|
2513
2655
|
const ext = filePath.slice(filePath.lastIndexOf("."));
|
|
@@ -2620,7 +2762,7 @@ var init_config_links = __esm({
|
|
|
2620
2762
|
|
|
2621
2763
|
// src/tools/get-guardrails.ts
|
|
2622
2764
|
import { z } from "zod";
|
|
2623
|
-
function registerGetGuardrails(
|
|
2765
|
+
function registerGetGuardrails(server, apiClient2) {
|
|
2624
2766
|
const handler = async ({
|
|
2625
2767
|
language,
|
|
2626
2768
|
category,
|
|
@@ -2674,7 +2816,7 @@ ${text3}`
|
|
|
2674
2816
|
]
|
|
2675
2817
|
};
|
|
2676
2818
|
};
|
|
2677
|
-
|
|
2819
|
+
server.registerTool(
|
|
2678
2820
|
"get_checks",
|
|
2679
2821
|
{
|
|
2680
2822
|
title: "Get Checks",
|
|
@@ -2683,7 +2825,7 @@ ${text3}`
|
|
|
2683
2825
|
},
|
|
2684
2826
|
handler
|
|
2685
2827
|
);
|
|
2686
|
-
|
|
2828
|
+
server.registerTool(
|
|
2687
2829
|
"get_guardrails",
|
|
2688
2830
|
{
|
|
2689
2831
|
title: "Get Checks (legacy name)",
|
|
@@ -2717,8 +2859,8 @@ var init_get_guardrails = __esm({
|
|
|
2717
2859
|
|
|
2718
2860
|
// src/tools/get-specs.ts
|
|
2719
2861
|
import { z as z2 } from "zod";
|
|
2720
|
-
function registerGetSpecs(
|
|
2721
|
-
|
|
2862
|
+
function registerGetSpecs(server, apiClient2) {
|
|
2863
|
+
server.registerTool(
|
|
2722
2864
|
"get_specs",
|
|
2723
2865
|
{
|
|
2724
2866
|
title: "Get Specs",
|
|
@@ -2808,8 +2950,8 @@ function matchGlob(path, pattern) {
|
|
|
2808
2950
|
const regex = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
|
|
2809
2951
|
return new RegExp(`^${regex}$`).test(path);
|
|
2810
2952
|
}
|
|
2811
|
-
function registerGetDocs(
|
|
2812
|
-
|
|
2953
|
+
function registerGetDocs(server, apiClient2) {
|
|
2954
|
+
server.registerTool(
|
|
2813
2955
|
"get_docs",
|
|
2814
2956
|
{
|
|
2815
2957
|
title: "Get Docs",
|
|
@@ -2912,8 +3054,8 @@ var init_get_docs = __esm({
|
|
|
2912
3054
|
});
|
|
2913
3055
|
|
|
2914
3056
|
// src/tools/get-tech-stack.ts
|
|
2915
|
-
function registerGetTechStack(
|
|
2916
|
-
|
|
3057
|
+
function registerGetTechStack(server, apiClient2) {
|
|
3058
|
+
server.registerTool(
|
|
2917
3059
|
"get_tech_stack",
|
|
2918
3060
|
{
|
|
2919
3061
|
title: "Get Tech Stack",
|
|
@@ -2968,8 +3110,8 @@ var init_get_tech_stack = __esm({
|
|
|
2968
3110
|
|
|
2969
3111
|
// src/tools/get-planning-context.ts
|
|
2970
3112
|
import { z as z4 } from "zod";
|
|
2971
|
-
function registerGetPlanningContext(
|
|
2972
|
-
|
|
3113
|
+
function registerGetPlanningContext(server, apiClient2) {
|
|
3114
|
+
server.registerTool(
|
|
2973
3115
|
"get_planning_context",
|
|
2974
3116
|
{
|
|
2975
3117
|
title: "Get Planning Context",
|
|
@@ -3003,7 +3145,7 @@ ${r.conventions.map((c) => `- ${c}`).join("\n")}` : "")
|
|
|
3003
3145
|
${repos}`);
|
|
3004
3146
|
}
|
|
3005
3147
|
if (ctx.dependencies.length > 0) {
|
|
3006
|
-
const deps = ctx.dependencies.map((d) => `- ${d.from} \u2192 ${d.to}`).join("\n");
|
|
3148
|
+
const deps = ctx.dependencies.map((d) => `- ${d.from} \u2192 ${d.to}${d.provenance === "inferred" ? " (inferred)" : ""}`).join("\n");
|
|
3007
3149
|
sections.push(`## Cross-Repo Dependencies
|
|
3008
3150
|
|
|
3009
3151
|
${deps}`);
|
|
@@ -3083,8 +3225,8 @@ function formatItem(item2) {
|
|
|
3083
3225
|
${meta.join(" \xB7 ")}` : "") + (item2.url ? `
|
|
3084
3226
|
${item2.url}` : "");
|
|
3085
3227
|
}
|
|
3086
|
-
function registerGetPlanBoard(
|
|
3087
|
-
|
|
3228
|
+
function registerGetPlanBoard(server, apiClient2) {
|
|
3229
|
+
server.registerTool(
|
|
3088
3230
|
"get_plan_board",
|
|
3089
3231
|
{
|
|
3090
3232
|
title: "Get Plan Board",
|
|
@@ -3341,8 +3483,8 @@ function formatChange(change) {
|
|
|
3341
3483
|
briefVerdict(change.states)
|
|
3342
3484
|
].join("\n\n");
|
|
3343
3485
|
}
|
|
3344
|
-
function registerGetChange(
|
|
3345
|
-
|
|
3486
|
+
function registerGetChange(server, apiClient2) {
|
|
3487
|
+
server.registerTool(
|
|
3346
3488
|
"get_change",
|
|
3347
3489
|
{
|
|
3348
3490
|
title: "Get Change",
|
|
@@ -3696,16 +3838,22 @@ async function hydrate(api, item2, meta) {
|
|
|
3696
3838
|
}
|
|
3697
3839
|
async function buildContext(api, { taskDescription, filePaths = [], maxTokens }) {
|
|
3698
3840
|
const budget = maxTokens ?? DEFAULT_BUDGET;
|
|
3841
|
+
const failures = [];
|
|
3842
|
+
const orEmpty = (empty) => (err) => {
|
|
3843
|
+
failures.push(err);
|
|
3844
|
+
return empty;
|
|
3845
|
+
};
|
|
3699
3846
|
const [rawGuardrails, rawSpecs, docs, stack, rules, docRepos] = await Promise.all([
|
|
3700
|
-
api.get("/v1/guardrails").catch((
|
|
3701
|
-
api.get("/v1/specs").catch((
|
|
3702
|
-
api.get("/v1/docs").catch((
|
|
3703
|
-
api.get("/v1/ai/settings/stack").catch((
|
|
3704
|
-
api.get("/v1/standards/rules").catch((
|
|
3847
|
+
api.get("/v1/guardrails").catch(orEmpty([])),
|
|
3848
|
+
api.get("/v1/specs").catch(orEmpty([])),
|
|
3849
|
+
api.get("/v1/docs").catch(orEmpty({ items: [] })),
|
|
3850
|
+
api.get("/v1/ai/settings/stack").catch(orEmpty([])),
|
|
3851
|
+
api.get("/v1/standards/rules").catch(orEmpty([])),
|
|
3705
3852
|
// Capability states — FAIL-OPEN: an unreadable config (older backend, a
|
|
3706
3853
|
// blip) gates nothing, it can never hide working context.
|
|
3707
3854
|
api.get("/v1/doc-repos").catch(() => null)
|
|
3708
3855
|
]);
|
|
3856
|
+
if (failures.length === 5) throw failures[0];
|
|
3709
3857
|
const disabled = (key2) => docRepos?.capabilities?.[key2]?.status === "disabled";
|
|
3710
3858
|
const guardrails = disabled("fitness") ? [] : rawGuardrails;
|
|
3711
3859
|
const specs = disabled("requirements") && disabled("design") && disabled("specs") ? [] : rawSpecs;
|
|
@@ -3912,12 +4060,13 @@ _Ranked lower \u2014 fetch with \`get_specs\` / \`get_docs\` if you need them._
|
|
|
3912
4060
|
guardrails: selectedGuardrails.length
|
|
3913
4061
|
};
|
|
3914
4062
|
if (sections.length === 0) {
|
|
3915
|
-
|
|
4063
|
+
const text3 = failures.length ? `No context found for this task, but ${failures.length} of 5 reads failed, so this is not an all-clear: the team's rules may not have been read.` : "No relevant context found for this task.";
|
|
4064
|
+
return { text: text3, stats };
|
|
3916
4065
|
}
|
|
3917
4066
|
const footer = `
|
|
3918
4067
|
|
|
3919
4068
|
---
|
|
3920
|
-
_Context: ${itemsWithBody} full text, ${itemsListed} listed, ${stats.itemsOmitted} omitted \xB7 ~${tokensUsed} of ${budget} tokens` + (bodyFailures > 0 ? ` \xB7 ${bodyFailures} body fetch(es) failed` : "") + `_`;
|
|
4069
|
+
_Context: ${itemsWithBody} full text, ${itemsListed} listed, ${stats.itemsOmitted} omitted \xB7 ~${tokensUsed} of ${budget} tokens` + (bodyFailures > 0 ? ` \xB7 ${bodyFailures} body fetch(es) failed` : "") + (failures.length > 0 ? ` \xB7 ${failures.length} of 5 reads failed` : "") + `_`;
|
|
3921
4070
|
return { text: body + footer, stats };
|
|
3922
4071
|
}
|
|
3923
4072
|
var DEFAULT_BUDGET, RESERVE_TOKENS, GUARDRAIL_SHARE, MIN_BODY_TOKENS, MAX_HYDRATED, MAX_TAIL, TAIL_SHARE, TRUNCATION_NOTICE_TOKENS, SECTION_STOP_WORDS;
|
|
@@ -3961,8 +4110,8 @@ var init_context_builder = __esm({
|
|
|
3961
4110
|
|
|
3962
4111
|
// src/tools/get-context.ts
|
|
3963
4112
|
import { z as z7 } from "zod";
|
|
3964
|
-
function registerGetContext(
|
|
3965
|
-
|
|
4113
|
+
function registerGetContext(server, apiClient2) {
|
|
4114
|
+
server.registerTool(
|
|
3966
4115
|
"get_context",
|
|
3967
4116
|
{
|
|
3968
4117
|
title: "Get Context",
|
|
@@ -3996,9 +4145,254 @@ var init_get_context = __esm({
|
|
|
3996
4145
|
}
|
|
3997
4146
|
});
|
|
3998
4147
|
|
|
3999
|
-
// src/tools/
|
|
4148
|
+
// src/tools/get-team-setup.ts
|
|
4149
|
+
function describeSetup(setup) {
|
|
4150
|
+
const lines = [`# Team setup of ${setup.workspace}`, ""];
|
|
4151
|
+
const skills = setup.sessionBrief?.skills ?? [];
|
|
4152
|
+
const held = setup.sessionBrief?.heldSkills ?? [];
|
|
4153
|
+
lines.push(`## Skills a session installs by itself (${skills.length})`, "");
|
|
4154
|
+
if (skills.length) {
|
|
4155
|
+
for (const s of skills) lines.push(`- **${s.name}** version ${s.latestVersion}: ${s.description}`);
|
|
4156
|
+
} else if (!setup.sessionBrief && setup.skills.length) {
|
|
4157
|
+
lines.push(`- ${setup.skills.join(", ")}`);
|
|
4158
|
+
} else {
|
|
4159
|
+
lines.push("None.");
|
|
4160
|
+
}
|
|
4161
|
+
lines.push("");
|
|
4162
|
+
if (held.length) {
|
|
4163
|
+
lines.push(`## Skills that wait for the person (${held.length})`, "");
|
|
4164
|
+
for (const h of held) lines.push(`- **${h.name}**: ${HELD_BECAUSE[h.reason] ?? h.reason}`);
|
|
4165
|
+
lines.push("", "Do not install these yourself: each is the person's to decide.", "");
|
|
4166
|
+
}
|
|
4167
|
+
if (setup.plugin) {
|
|
4168
|
+
lines.push(`## Team plugin`, "", `\`${setup.workspace}-setup\` version ${setup.plugin.version}.`);
|
|
4169
|
+
for (const c of setup.plugin.hookCommands ?? []) lines.push(`- Hook: ${JSON.stringify(c)}`);
|
|
4170
|
+
for (const m of setup.plugin.mcpServers ?? []) {
|
|
4171
|
+
lines.push(`- MCP server ${JSON.stringify(m.name)}: ${JSON.stringify(m.url ?? [m.command, ...m.args ?? []].filter(Boolean).join(" "))}`);
|
|
4172
|
+
}
|
|
4173
|
+
lines.push("");
|
|
4174
|
+
}
|
|
4175
|
+
const parts = Object.entries(setup.parts ?? {});
|
|
4176
|
+
if (parts.length) {
|
|
4177
|
+
lines.push(`## Published parts`, "", ...parts.map(([p, v]) => `- ${p}: version ${v}`), "");
|
|
4178
|
+
}
|
|
4179
|
+
if (setup.memory?.length) {
|
|
4180
|
+
lines.push(`## Team memory`, "", ...setup.memory.map((m) => `- ${m.project}: ${m.fileCount} file(s)`), "");
|
|
4181
|
+
}
|
|
4182
|
+
return lines;
|
|
4183
|
+
}
|
|
4184
|
+
function describeComputer(setup, state) {
|
|
4185
|
+
const lines = ["## This computer", ""];
|
|
4186
|
+
if (!setup.sessionBrief) {
|
|
4187
|
+
return [...lines, "This SystemDox API is too old to compare this computer with the team setup."];
|
|
4188
|
+
}
|
|
4189
|
+
const skills = setup.sessionBrief.skills;
|
|
4190
|
+
const latest = new Map(skills.map((s) => [s.name, s.latestVersion]));
|
|
4191
|
+
lines.push(
|
|
4192
|
+
state.missing.length ? `- Missing: ${state.missing.map((name) => `${name} version ${latest.get(name)}`).join(", ")}` : `- Missing: none of ${skills.length}`
|
|
4193
|
+
);
|
|
4194
|
+
if (state.behind.length) {
|
|
4195
|
+
lines.push(`- Behind: ${state.behind.map((b) => `${b.name} (version ${b.here} here, latest ${b.latest})`).join(", ")}`);
|
|
4196
|
+
}
|
|
4197
|
+
if (state.notFromSystemDox.length) lines.push(`- Not from SystemDox, left alone: ${state.notFromSystemDox.join(", ")}`);
|
|
4198
|
+
if (setup.plugin) {
|
|
4199
|
+
lines.push(
|
|
4200
|
+
state.pluginInstalled ? `- Team plugin: installed${state.pluginVersion ? `, ${state.pluginVersion}` : ""}; the latest is version ${setup.plugin.version}` : "- Team plugin: not installed"
|
|
4201
|
+
);
|
|
4202
|
+
}
|
|
4203
|
+
lines.push("", "## What to do", "");
|
|
4204
|
+
const steps = [];
|
|
4205
|
+
if (state.missing.length) steps.push("Call install_skill for each missing skill, at the version named above. These are plain skills: installing writes files and runs nothing.");
|
|
4206
|
+
if (state.behind.length) {
|
|
4207
|
+
steps.push("Call install_skill for each skill behind, at the latest version named above. If it refuses because files were edited, tell the person and leave them.");
|
|
4208
|
+
}
|
|
4209
|
+
if (hasPersonParts(setup) && !state.pluginInstalled) {
|
|
4210
|
+
steps.push(
|
|
4211
|
+
`Hooks, MCP servers, team instructions and memory need the person: \`${SETUP_COMMAND}\` in their own terminal. Never answer its prompts for them.`
|
|
4212
|
+
);
|
|
4213
|
+
}
|
|
4214
|
+
lines.push(...steps.length ? steps.map((s, i) => `${i + 1}. ${s}`) : ["Nothing."]);
|
|
4215
|
+
return lines;
|
|
4216
|
+
}
|
|
4217
|
+
function registerGetTeamSetup(server, apiClient2, { localFiles, keyProblem: keyProblem2 }) {
|
|
4218
|
+
server.registerTool(
|
|
4219
|
+
"get_team_setup",
|
|
4220
|
+
{
|
|
4221
|
+
title: "Get Team Setup",
|
|
4222
|
+
description: localFiles ? "What this workspace's team setup holds (skills, plugin, hooks, MCP servers, memory) and what this computer lacks, with what to do about it. Call it to check whether this computer is set up. Reads only." : "What this workspace's team setup holds (skills, plugin, hooks, MCP servers, memory), and how to check this computer against it. Reads only.",
|
|
4223
|
+
inputSchema: {},
|
|
4224
|
+
annotations: { readOnlyHint: true }
|
|
4225
|
+
},
|
|
4226
|
+
async () => {
|
|
4227
|
+
const read = await readTeamSetup(apiClient2, { keyProblem: keyProblem2, timeoutMs: 15e3 });
|
|
4228
|
+
if ("problem" in read) return { content: [{ type: "text", text: read.problem }] };
|
|
4229
|
+
const lines = describeSetup(read.setup);
|
|
4230
|
+
if (localFiles) lines.push(...describeComputer(read.setup, await computerState(read.setup)));
|
|
4231
|
+
else lines.push(CANNOT_SEE);
|
|
4232
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
4233
|
+
}
|
|
4234
|
+
);
|
|
4235
|
+
}
|
|
4236
|
+
var HELD_BECAUSE;
|
|
4237
|
+
var init_get_team_setup = __esm({
|
|
4238
|
+
"src/tools/get-team-setup.ts"() {
|
|
4239
|
+
"use strict";
|
|
4240
|
+
init_session_brief();
|
|
4241
|
+
HELD_BECAUSE = {
|
|
4242
|
+
"not-plain": `it is not a plain skill: it has frontmatter beyond a name and a description, files outside its own folders, or a command that runs as it loads. The team plugin carries it, and \`${SETUP_COMMAND}\` shows that before the person accepts`,
|
|
4243
|
+
"outside-default-space": "it is outside the default space, so the team plugin does not carry it either. The person can read it in the console, then install it with `npx -y @systemdox/mcp-server install-skill <name>`",
|
|
4244
|
+
"not-checked": "it could not be checked. Ask again later"
|
|
4245
|
+
};
|
|
4246
|
+
}
|
|
4247
|
+
});
|
|
4248
|
+
|
|
4249
|
+
// src/tools/document-graph.ts
|
|
4000
4250
|
import { z as z8 } from "zod";
|
|
4001
|
-
function
|
|
4251
|
+
function place(file, line) {
|
|
4252
|
+
return file ? `${file}:${line}` : "made by a person";
|
|
4253
|
+
}
|
|
4254
|
+
function known(provenance, confidence) {
|
|
4255
|
+
return `${provenance} ${confidence}`;
|
|
4256
|
+
}
|
|
4257
|
+
function formatNeighbours(answer) {
|
|
4258
|
+
const lines = [`# ${answer.doc.label}`, `\`${answer.doc.id}\``, ""];
|
|
4259
|
+
if (answer.links.length === 0) lines.push("No links.");
|
|
4260
|
+
for (const link of answer.links) {
|
|
4261
|
+
lines.push(
|
|
4262
|
+
`- ${link.direction} \xB7 ${link.relation} \xB7 ${known(link.provenance, link.confidence)} \xB7 ${link.other.label} \xB7 \`${link.other.id}\` \xB7 ${place(link.file, link.line)}`
|
|
4263
|
+
);
|
|
4264
|
+
}
|
|
4265
|
+
if (answer.truncated) {
|
|
4266
|
+
lines.push("", `Showing ${answer.links.length} of ${answer.total}. Raise limit to see more.`);
|
|
4267
|
+
}
|
|
4268
|
+
if (answer.ambiguous.length > 0) {
|
|
4269
|
+
lines.push("", "## Ambiguous: not drawn, because each could mean several documents");
|
|
4270
|
+
for (const entry of answer.ambiguous) {
|
|
4271
|
+
const candidates = entry.candidates.map((c) => `${c.label} (${c.repo}, \`${c.id}\`)`);
|
|
4272
|
+
lines.push(`- ${entry.reference} at ${place(entry.file, entry.line)}: ${candidates.join("; ")}`);
|
|
4273
|
+
}
|
|
4274
|
+
}
|
|
4275
|
+
return lines.join("\n");
|
|
4276
|
+
}
|
|
4277
|
+
function formatPath(answer, maxHops) {
|
|
4278
|
+
if (!answer.found) return `No path within ${maxHops} hops.`;
|
|
4279
|
+
const lines = answer.steps.map((step, index) => {
|
|
4280
|
+
const arrow = step.reversed ? "<-" : "->";
|
|
4281
|
+
return `${index + 1}. ${step.from.label} ${arrow} ${step.relation} ${arrow} ${step.to.label} \xB7 ${known(step.provenance, step.confidence)} \xB7 ${place(step.file, step.line)}`;
|
|
4282
|
+
});
|
|
4283
|
+
return [...lines, "", `Path confidence: ${answer.confidence}`].join("\n");
|
|
4284
|
+
}
|
|
4285
|
+
function formatStats(stats, repoLinks) {
|
|
4286
|
+
const { declared, extracted, inferred, ambiguous } = stats.links;
|
|
4287
|
+
const lines = [
|
|
4288
|
+
`# The graph: ${stats.nodeCount} documents, ${stats.edgeCount} links`,
|
|
4289
|
+
"",
|
|
4290
|
+
`Links: ${declared} declared \xB7 ${extracted} extracted \xB7 ${inferred} inferred \xB7 ${ambiguous} ambiguous (not drawn)`,
|
|
4291
|
+
"",
|
|
4292
|
+
"## Repositories"
|
|
4293
|
+
];
|
|
4294
|
+
for (const repo of stats.repos) {
|
|
4295
|
+
const state = repo.behind ? `BEHIND: pushed ${repo.headCommit || "unknown"} at ${repo.pushedAt}, index at ${repo.indexedCommit || "unknown"}` : `index at ${repo.indexedCommit || "unknown"}`;
|
|
4296
|
+
lines.push(`- ${repo.repo}: ${repo.documents} documents, ${repo.links} links, ${state}`);
|
|
4297
|
+
}
|
|
4298
|
+
if (repoLinks.length > 0) {
|
|
4299
|
+
lines.push("", "## Repository links");
|
|
4300
|
+
for (const link of repoLinks) {
|
|
4301
|
+
lines.push(
|
|
4302
|
+
`- ${link.source} ${link.relation} ${link.target} \xB7 ${known(link.provenance, link.confidence)} \xB7 ${place(link.file, link.line)}`
|
|
4303
|
+
);
|
|
4304
|
+
}
|
|
4305
|
+
}
|
|
4306
|
+
return lines.join("\n");
|
|
4307
|
+
}
|
|
4308
|
+
function refused(err) {
|
|
4309
|
+
const status = err.status;
|
|
4310
|
+
if (status !== 404 && status !== 400) throw err;
|
|
4311
|
+
return {
|
|
4312
|
+
content: [{ type: "text", text: err.message }],
|
|
4313
|
+
isError: true
|
|
4314
|
+
};
|
|
4315
|
+
}
|
|
4316
|
+
function registerDocumentGraph(server, apiClient2) {
|
|
4317
|
+
server.registerTool(
|
|
4318
|
+
"get_document_links",
|
|
4319
|
+
{
|
|
4320
|
+
description: "What one document links to and from, strongest first. Each link says how it is known: declared (a person made it), extracted (the file says so), or inferred (a reading that could be wrong), with a confidence and the file and line it came from. A reference that could mean several documents is listed as ambiguous with its candidates, and is never drawn as a link. doc_id is the id get_docs returns. Read-only.",
|
|
4321
|
+
inputSchema: {
|
|
4322
|
+
doc_id: z8.string().min(1).max(400).describe("A document id, as get_docs returns it"),
|
|
4323
|
+
direction: z8.enum(["out", "in", "both"]).optional().describe("Default both"),
|
|
4324
|
+
relation: z8.enum(["links_to", "cites", "mentions", "satisfies", "references"]).optional().describe("Only links of this kind"),
|
|
4325
|
+
limit: z8.number().int().min(1).max(MAX_LIMIT).optional().describe(`Most links to show (default ${DEFAULT_LIMIT2})`)
|
|
4326
|
+
},
|
|
4327
|
+
annotations: { readOnlyHint: true }
|
|
4328
|
+
},
|
|
4329
|
+
async ({ doc_id, direction, relation, limit }) => {
|
|
4330
|
+
const params = { doc: doc_id, limit: String(limit ?? DEFAULT_LIMIT2) };
|
|
4331
|
+
if (direction) params.direction = direction;
|
|
4332
|
+
if (relation) params.relation = relation;
|
|
4333
|
+
try {
|
|
4334
|
+
const answer = await apiClient2.get("/v1/graph/neighbours", params);
|
|
4335
|
+
return { content: [{ type: "text", text: formatNeighbours(answer) }] };
|
|
4336
|
+
} catch (err) {
|
|
4337
|
+
return refused(err);
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
);
|
|
4341
|
+
server.registerTool(
|
|
4342
|
+
"trace_document_path",
|
|
4343
|
+
{
|
|
4344
|
+
description: "The shortest way two documents connect, walking links in either direction. Each step says how it is known, and the path is only as strong as its weakest step. Read-only.",
|
|
4345
|
+
inputSchema: {
|
|
4346
|
+
from: z8.string().min(1).max(400).describe("A document id, as get_docs returns it"),
|
|
4347
|
+
to: z8.string().min(1).max(400).describe("A document id, as get_docs returns it"),
|
|
4348
|
+
max_hops: z8.number().int().min(1).max(MAX_HOPS).optional().describe(`Longest path to look for (default ${DEFAULT_HOPS})`)
|
|
4349
|
+
},
|
|
4350
|
+
annotations: { readOnlyHint: true }
|
|
4351
|
+
},
|
|
4352
|
+
async ({ from, to, max_hops }) => {
|
|
4353
|
+
const hops = max_hops ?? DEFAULT_HOPS;
|
|
4354
|
+
try {
|
|
4355
|
+
const answer = await apiClient2.get("/v1/graph/path", {
|
|
4356
|
+
from,
|
|
4357
|
+
to,
|
|
4358
|
+
maxHops: String(hops)
|
|
4359
|
+
});
|
|
4360
|
+
return { content: [{ type: "text", text: formatPath(answer, hops) }] };
|
|
4361
|
+
} catch (err) {
|
|
4362
|
+
return refused(err);
|
|
4363
|
+
}
|
|
4364
|
+
}
|
|
4365
|
+
);
|
|
4366
|
+
server.registerTool(
|
|
4367
|
+
"get_graph_stats",
|
|
4368
|
+
{
|
|
4369
|
+
description: "The state of the graph: links counted by how they are known, each repository's freshness (marked BEHIND when a push has not finished indexing), and how repositories depend on each other, from the files that declare it. Read-only.",
|
|
4370
|
+
inputSchema: {},
|
|
4371
|
+
annotations: { readOnlyHint: true }
|
|
4372
|
+
},
|
|
4373
|
+
async () => {
|
|
4374
|
+
const [stats, repos] = await Promise.all([
|
|
4375
|
+
apiClient2.getFresh("/v1/graph/stats"),
|
|
4376
|
+
apiClient2.getFresh("/v1/graph/repos")
|
|
4377
|
+
]);
|
|
4378
|
+
return { content: [{ type: "text", text: formatStats(stats, repos.links) }] };
|
|
4379
|
+
}
|
|
4380
|
+
);
|
|
4381
|
+
}
|
|
4382
|
+
var DEFAULT_LIMIT2, MAX_LIMIT, DEFAULT_HOPS, MAX_HOPS;
|
|
4383
|
+
var init_document_graph = __esm({
|
|
4384
|
+
"src/tools/document-graph.ts"() {
|
|
4385
|
+
"use strict";
|
|
4386
|
+
DEFAULT_LIMIT2 = 50;
|
|
4387
|
+
MAX_LIMIT = 200;
|
|
4388
|
+
DEFAULT_HOPS = 6;
|
|
4389
|
+
MAX_HOPS = 8;
|
|
4390
|
+
}
|
|
4391
|
+
});
|
|
4392
|
+
|
|
4393
|
+
// src/tools/create-guardrail.ts
|
|
4394
|
+
import { z as z9 } from "zod";
|
|
4395
|
+
function registerCreateGuardrail(server, apiClient2) {
|
|
4002
4396
|
const handler = async ({
|
|
4003
4397
|
title,
|
|
4004
4398
|
description,
|
|
@@ -4035,7 +4429,7 @@ function registerCreateGuardrail(server2, apiClient2) {
|
|
|
4035
4429
|
]
|
|
4036
4430
|
};
|
|
4037
4431
|
};
|
|
4038
|
-
|
|
4432
|
+
server.registerTool(
|
|
4039
4433
|
"create_check",
|
|
4040
4434
|
{
|
|
4041
4435
|
title: "Create Check",
|
|
@@ -4047,7 +4441,7 @@ function registerCreateGuardrail(server2, apiClient2) {
|
|
|
4047
4441
|
},
|
|
4048
4442
|
handler
|
|
4049
4443
|
);
|
|
4050
|
-
|
|
4444
|
+
server.registerTool(
|
|
4051
4445
|
"create_guardrail",
|
|
4052
4446
|
{
|
|
4053
4447
|
title: "Create Check (legacy name)",
|
|
@@ -4077,32 +4471,32 @@ var init_create_guardrail = __esm({
|
|
|
4077
4471
|
"data-access"
|
|
4078
4472
|
];
|
|
4079
4473
|
inputSchema2 = () => ({
|
|
4080
|
-
title:
|
|
4081
|
-
description:
|
|
4082
|
-
severity:
|
|
4083
|
-
category:
|
|
4084
|
-
pattern:
|
|
4085
|
-
fix:
|
|
4086
|
-
fitness_test:
|
|
4474
|
+
title: z9.string().describe("Short title for the check"),
|
|
4475
|
+
description: z9.string().describe("Detailed description of what the check enforces and why"),
|
|
4476
|
+
severity: z9.enum(["critical", "high", "medium", "low"]).describe("Severity level"),
|
|
4477
|
+
category: z9.enum(CATEGORIES).optional().describe("Category (defaults to conventions)"),
|
|
4478
|
+
pattern: z9.string().optional().describe("Bad code pattern that should be avoided"),
|
|
4479
|
+
fix: z9.string().optional().describe("Correct code pattern to use instead"),
|
|
4480
|
+
fitness_test: z9.string().optional().describe("Shell command to detect violations in CI")
|
|
4087
4481
|
});
|
|
4088
4482
|
}
|
|
4089
4483
|
});
|
|
4090
4484
|
|
|
4091
4485
|
// src/tools/update-spec.ts
|
|
4092
|
-
import { z as
|
|
4093
|
-
function registerUpdateSpec(
|
|
4094
|
-
|
|
4486
|
+
import { z as z10 } from "zod";
|
|
4487
|
+
function registerUpdateSpec(server, apiClient2) {
|
|
4488
|
+
server.registerTool(
|
|
4095
4489
|
"update_spec",
|
|
4096
4490
|
{
|
|
4097
4491
|
title: "Update Spec",
|
|
4098
4492
|
description: "Update an existing SystemDox spec's proposal, tasks, or requirements. Only draft specs can be modified \u2014 published specs must be versioned first.",
|
|
4099
4493
|
inputSchema: {
|
|
4100
|
-
draft_id:
|
|
4101
|
-
section:
|
|
4102
|
-
content:
|
|
4494
|
+
draft_id: z10.string().describe("ID of the spec to update"),
|
|
4495
|
+
section: z10.enum(["proposal", "tasks", "requirements"]).describe("Which section to update"),
|
|
4496
|
+
content: z10.string().describe(
|
|
4103
4497
|
"New markdown content for the section. For requirements, provide the full requirement content."
|
|
4104
4498
|
),
|
|
4105
|
-
requirement_name:
|
|
4499
|
+
requirement_name: z10.string().optional().describe(
|
|
4106
4500
|
"Name of the requirement (required when section is 'requirements')"
|
|
4107
4501
|
)
|
|
4108
4502
|
},
|
|
@@ -4157,19 +4551,19 @@ var init_update_spec = __esm({
|
|
|
4157
4551
|
});
|
|
4158
4552
|
|
|
4159
4553
|
// src/tools/report-decision.ts
|
|
4160
|
-
import { z as
|
|
4161
|
-
function registerReportDecision(
|
|
4162
|
-
|
|
4554
|
+
import { z as z11 } from "zod";
|
|
4555
|
+
function registerReportDecision(server, apiClient2) {
|
|
4556
|
+
server.registerTool(
|
|
4163
4557
|
"report_decision",
|
|
4164
4558
|
{
|
|
4165
4559
|
title: "Report Decision",
|
|
4166
4560
|
description: "Record an architecture decision made during development. Creates a draft ADR in SystemDox for human review.",
|
|
4167
4561
|
inputSchema: {
|
|
4168
|
-
title:
|
|
4169
|
-
context:
|
|
4170
|
-
decision:
|
|
4171
|
-
consequences:
|
|
4172
|
-
related_files:
|
|
4562
|
+
title: z11.string().describe("Decision title (e.g., 'Use WebSocket for real-time updates')"),
|
|
4563
|
+
context: z11.string().describe("What prompted this decision \u2014 the problem or constraint"),
|
|
4564
|
+
decision: z11.string().describe("What was decided and why"),
|
|
4565
|
+
consequences: z11.string().describe("What this decision means going forward \u2014 trade-offs, follow-up work"),
|
|
4566
|
+
related_files: z11.array(z11.string()).optional().describe("File paths related to this decision")
|
|
4173
4567
|
},
|
|
4174
4568
|
annotations: {
|
|
4175
4569
|
readOnlyHint: false
|
|
@@ -4225,15 +4619,15 @@ var init_report_decision = __esm({
|
|
|
4225
4619
|
});
|
|
4226
4620
|
|
|
4227
4621
|
// src/tools/get-project-rules.ts
|
|
4228
|
-
import { z as
|
|
4229
|
-
function registerGetProjectRules(
|
|
4230
|
-
|
|
4622
|
+
import { z as z12 } from "zod";
|
|
4623
|
+
function registerGetProjectRules(server, apiClient2) {
|
|
4624
|
+
server.registerTool(
|
|
4231
4625
|
"get_project_rules",
|
|
4232
4626
|
{
|
|
4233
4627
|
title: "Get Project Rules",
|
|
4234
4628
|
description: "Retrieve project rules defined in SystemDox Standards. These are team-defined principles covering deployment, architecture, security, testing, and process. Filter by category to get only relevant rules.",
|
|
4235
4629
|
inputSchema: {
|
|
4236
|
-
category:
|
|
4630
|
+
category: z12.enum(["deployment", "architecture", "security", "testing", "process", "other"]).optional().describe("Filter by rule category")
|
|
4237
4631
|
}
|
|
4238
4632
|
},
|
|
4239
4633
|
async ({ category }) => {
|
|
@@ -4300,18 +4694,18 @@ var init_get_project_rules = __esm({
|
|
|
4300
4694
|
});
|
|
4301
4695
|
|
|
4302
4696
|
// src/tools/create-project-rule.ts
|
|
4303
|
-
import { z as
|
|
4304
|
-
function registerCreateProjectRule(
|
|
4305
|
-
|
|
4697
|
+
import { z as z13 } from "zod";
|
|
4698
|
+
function registerCreateProjectRule(server, apiClient2) {
|
|
4699
|
+
server.registerTool(
|
|
4306
4700
|
"create_project_rule",
|
|
4307
4701
|
{
|
|
4308
4702
|
title: "Create Project Rule",
|
|
4309
4703
|
description: "Create a new project rule in SystemDox Standards. Use this when you discover a team principle or convention that should be enforced across the project.",
|
|
4310
4704
|
inputSchema: {
|
|
4311
|
-
text:
|
|
4705
|
+
text: z13.string().describe(
|
|
4312
4706
|
"The rule statement \u2014 one enforceable principle per rule (e.g. 'All deployments MUST go through GitHub Actions workflows')"
|
|
4313
4707
|
),
|
|
4314
|
-
category:
|
|
4708
|
+
category: z13.enum(["deployment", "architecture", "security", "testing", "process", "other"]).describe("Rule category")
|
|
4315
4709
|
},
|
|
4316
4710
|
annotations: {
|
|
4317
4711
|
readOnlyHint: false
|
|
@@ -4341,8 +4735,8 @@ var init_create_project_rule = __esm({
|
|
|
4341
4735
|
});
|
|
4342
4736
|
|
|
4343
4737
|
// src/tools/get-prompt-templates.ts
|
|
4344
|
-
function registerGetPromptTemplates(
|
|
4345
|
-
|
|
4738
|
+
function registerGetPromptTemplates(server, apiClient2) {
|
|
4739
|
+
server.registerTool(
|
|
4346
4740
|
"get_prompt_templates",
|
|
4347
4741
|
{
|
|
4348
4742
|
title: "Get Prompt Templates",
|
|
@@ -4393,17 +4787,17 @@ var init_get_prompt_templates = __esm({
|
|
|
4393
4787
|
});
|
|
4394
4788
|
|
|
4395
4789
|
// src/tools/create-prompt-template.ts
|
|
4396
|
-
import { z as
|
|
4397
|
-
function registerCreatePromptTemplate(
|
|
4398
|
-
|
|
4790
|
+
import { z as z14 } from "zod";
|
|
4791
|
+
function registerCreatePromptTemplate(server, apiClient2) {
|
|
4792
|
+
server.registerTool(
|
|
4399
4793
|
"create_prompt_template",
|
|
4400
4794
|
{
|
|
4401
4795
|
title: "Create Prompt Template",
|
|
4402
4796
|
description: "Create a new reusable prompt template in SystemDox Standards. Use this to store preconfigured prompts for common AI tasks like code reviews, documentation generation, or analysis frameworks.",
|
|
4403
4797
|
inputSchema: {
|
|
4404
|
-
name:
|
|
4405
|
-
description:
|
|
4406
|
-
prompt:
|
|
4798
|
+
name: z14.string().describe("Short name for the template"),
|
|
4799
|
+
description: z14.string().optional().describe("Brief description of when to use this template"),
|
|
4800
|
+
prompt: z14.string().describe("The full prompt text that Gen AI will use when generating content")
|
|
4407
4801
|
},
|
|
4408
4802
|
annotations: {
|
|
4409
4803
|
readOnlyHint: false
|
|
@@ -4437,7 +4831,7 @@ var init_create_prompt_template = __esm({
|
|
|
4437
4831
|
});
|
|
4438
4832
|
|
|
4439
4833
|
// src/tools/skills.ts
|
|
4440
|
-
import { z as
|
|
4834
|
+
import { z as z15 } from "zod";
|
|
4441
4835
|
import { homedir as homedir3 } from "os";
|
|
4442
4836
|
import { basename as basename2, resolve as resolve6 } from "path";
|
|
4443
4837
|
function expandHome(path) {
|
|
@@ -4446,25 +4840,25 @@ function expandHome(path) {
|
|
|
4446
4840
|
function text(t) {
|
|
4447
4841
|
return { content: [{ type: "text", text: t }] };
|
|
4448
4842
|
}
|
|
4449
|
-
function registerSkillTools(
|
|
4450
|
-
const fileSchema =
|
|
4451
|
-
path:
|
|
4452
|
-
content:
|
|
4843
|
+
function registerSkillTools(server, apiClient2, { localFiles }) {
|
|
4844
|
+
const fileSchema = z15.object({
|
|
4845
|
+
path: z15.string().describe('Path inside the skill folder, forward slashes, e.g. "references/guide.md"'),
|
|
4846
|
+
content: z15.string().describe("The file's full text, exactly as it is")
|
|
4453
4847
|
});
|
|
4454
|
-
|
|
4848
|
+
server.registerTool(
|
|
4455
4849
|
"publish_skill",
|
|
4456
4850
|
{
|
|
4457
4851
|
title: "Publish Skill",
|
|
4458
4852
|
description: localFiles ? "Publish a Claude Code skill folder (SKILL.md plus its references and scripts) to SystemDox so it can be installed on any computer. Pass `directory` (e.g. ~/.claude/skills/<name>): the server reads the files itself, byte for byte. Publishing a name again makes a new version. Never retype the files into `files` when the folder is on this computer." : "Publish a Claude Code skill (SKILL.md plus its references and scripts) to SystemDox so it can be installed on any computer. Pass every file of the folder in `files`, exactly as it is. Publishing a name again makes a new version.",
|
|
4459
4853
|
inputSchema: {
|
|
4460
4854
|
...localFiles ? {
|
|
4461
|
-
directory:
|
|
4855
|
+
directory: z15.string().optional().describe("The skill folder on this computer, holding SKILL.md at its root")
|
|
4462
4856
|
} : {},
|
|
4463
|
-
files:
|
|
4464
|
-
name:
|
|
4465
|
-
spaceId:
|
|
4466
|
-
note:
|
|
4467
|
-
baseVersion:
|
|
4857
|
+
files: z15.array(fileSchema).optional().describe(localFiles ? "Only when there is no folder to read" : "Every file of the skill folder"),
|
|
4858
|
+
name: z15.string().optional().describe("The skill's name; defaults to SKILL.md's frontmatter name and must match it"),
|
|
4859
|
+
spaceId: z15.string().optional().describe("Space for a NEW skill (default: your default space)"),
|
|
4860
|
+
note: z15.string().max(500).optional().describe(`A short change note for this version, shown in the console's history (e.g. "Fix the GA4 step")`),
|
|
4861
|
+
baseVersion: z15.number().int().positive().optional().describe(
|
|
4468
4862
|
"The version these files were edited from. When given, the publish is refused (SYD-K017) if someone published a newer version since, instead of replacing their changes"
|
|
4469
4863
|
)
|
|
4470
4864
|
},
|
|
@@ -4502,7 +4896,7 @@ Left out (hidden files are never part of a skill): ${skipped.join(", ")}` : "")
|
|
|
4502
4896
|
);
|
|
4503
4897
|
}
|
|
4504
4898
|
);
|
|
4505
|
-
|
|
4899
|
+
server.registerTool(
|
|
4506
4900
|
"list_skills",
|
|
4507
4901
|
{
|
|
4508
4902
|
title: "List Skills",
|
|
@@ -4524,16 +4918,16 @@ Left out (hidden files are never part of a skill): ${skipped.join(", ")}` : "")
|
|
|
4524
4918
|
);
|
|
4525
4919
|
}
|
|
4526
4920
|
);
|
|
4527
|
-
|
|
4921
|
+
server.registerTool(
|
|
4528
4922
|
"install_skill",
|
|
4529
4923
|
{
|
|
4530
4924
|
title: "Install Skill",
|
|
4531
4925
|
description: localFiles ? "Install a skill published to SystemDox onto this computer (default ~/.claude/skills/<name>/). Refuses, naming the files, when a file there was edited since it was installed, unless overwrite is true. Never runs anything." : "Fetch a skill published to SystemDox so you can install it: returns every file and the folder to write them into (default ~/.claude/skills/<name>/). Write each file exactly, then the manifest it shows.",
|
|
4532
4926
|
inputSchema: {
|
|
4533
|
-
name:
|
|
4534
|
-
version:
|
|
4535
|
-
target:
|
|
4536
|
-
...localFiles ? { overwrite:
|
|
4927
|
+
name: z15.string().describe("The skill's name"),
|
|
4928
|
+
version: z15.number().int().positive().optional().describe("A version number (default: the latest)"),
|
|
4929
|
+
target: z15.string().optional().describe("Folder to install into, e.g. a repo's .claude/skills/<name>"),
|
|
4930
|
+
...localFiles ? { overwrite: z15.boolean().optional().describe("Replace files edited since they were installed") } : {}
|
|
4537
4931
|
},
|
|
4538
4932
|
annotations: { readOnlyHint: !localFiles }
|
|
4539
4933
|
},
|
|
@@ -4564,7 +4958,7 @@ ${fence}
|
|
|
4564
4958
|
${f.content}${f.content.endsWith("\n") ? "" : "\n"}${fence}`;
|
|
4565
4959
|
}).join("\n\n");
|
|
4566
4960
|
return text(
|
|
4567
|
-
`Skill **${version.name}** version ${version.version}: write these ${version.files.length} file(s) into ${dest}/ exactly as shown (the content between the fences), creating folders as needed. If a file there differs from ${MANIFEST_FILE}'s hash for it, it was edited: ask before replacing it. Then write ${MANIFEST_FILE} with this content
|
|
4961
|
+
`Skill **${version.name}** version ${version.version}: write these ${version.files.length} file(s) into ${dest}/ exactly as shown (the content between the fences), creating folders as needed. If a file there differs from ${MANIFEST_FILE}'s hash for it, it was edited: ask before replacing it. Then write ${MANIFEST_FILE} with this content. ${afterInstall(`${dest}/SKILL.md`)}
|
|
4568
4962
|
|
|
4569
4963
|
### ${MANIFEST_FILE}
|
|
4570
4964
|
|
|
@@ -4599,11 +4993,11 @@ var init_team_setup_types = __esm({
|
|
|
4599
4993
|
});
|
|
4600
4994
|
|
|
4601
4995
|
// src/tools/team-setup.ts
|
|
4602
|
-
import { z as
|
|
4603
|
-
import { existsSync as
|
|
4996
|
+
import { z as z16 } from "zod";
|
|
4997
|
+
import { existsSync as existsSync7 } from "fs";
|
|
4604
4998
|
import { lstat as lstat2 } from "fs/promises";
|
|
4605
4999
|
import { homedir as homedir4 } from "os";
|
|
4606
|
-
import { join as
|
|
5000
|
+
import { join as join8, resolve as resolve7 } from "path";
|
|
4607
5001
|
function expandHome2(path) {
|
|
4608
5002
|
return path.replace(/^~(?=$|[\\/])/, homedir4());
|
|
4609
5003
|
}
|
|
@@ -4629,21 +5023,21 @@ async function readPartDirectory(part, directory) {
|
|
|
4629
5023
|
let present = true;
|
|
4630
5024
|
for (let i = 1; i <= segments.length; i++) {
|
|
4631
5025
|
const rel = segments.slice(0, i).join("/");
|
|
4632
|
-
if (await isLink(
|
|
5026
|
+
if (await isLink(join8(root, ...segments.slice(0, i)))) {
|
|
4633
5027
|
links.push(rel);
|
|
4634
5028
|
present = false;
|
|
4635
5029
|
break;
|
|
4636
5030
|
}
|
|
4637
5031
|
}
|
|
4638
|
-
if (present &&
|
|
5032
|
+
if (present && existsSync7(join8(root, ...segments))) paths.push(path);
|
|
4639
5033
|
}
|
|
4640
5034
|
for (const { dir, md } of layout.folders) {
|
|
4641
|
-
const full =
|
|
5035
|
+
const full = join8(root, dir);
|
|
4642
5036
|
if (await isLink(full)) {
|
|
4643
5037
|
links.push(dir);
|
|
4644
5038
|
continue;
|
|
4645
5039
|
}
|
|
4646
|
-
if (!
|
|
5040
|
+
if (!existsSync7(full)) continue;
|
|
4647
5041
|
const walked = await walk(full, { skipHidden: true });
|
|
4648
5042
|
links.push(...walked.links.map((p) => `${dir}/${p}`));
|
|
4649
5043
|
leftOut.push(...walked.hidden.map((p) => `${dir}/${p}`));
|
|
@@ -4682,24 +5076,24 @@ function describePerson(id, person) {
|
|
|
4682
5076
|
);
|
|
4683
5077
|
return lines.join("\n");
|
|
4684
5078
|
}
|
|
4685
|
-
function registerTeamSetupTools(
|
|
4686
|
-
const fileSchema =
|
|
4687
|
-
path:
|
|
4688
|
-
content:
|
|
5079
|
+
function registerTeamSetupTools(server, apiClient2, { localFiles }) {
|
|
5080
|
+
const fileSchema = z16.object({
|
|
5081
|
+
path: z16.string().describe(`Path inside the part's folder, forward slashes, e.g. "hooks/hooks.json"`),
|
|
5082
|
+
content: z16.string().describe("The file's full text, exactly as it is")
|
|
4689
5083
|
});
|
|
4690
|
-
|
|
5084
|
+
server.registerTool(
|
|
4691
5085
|
"publish_setup_part",
|
|
4692
5086
|
{
|
|
4693
5087
|
title: "Publish Setup Part",
|
|
4694
5088
|
description: "Publish one part of your team's Claude Code setup to SystemDox: `hooks` (hooks/hooks.json and the scripts/ it calls), `mcp` (.mcp.json), `agents` (agents/*.md), `commands` (commands/*.md) or `instructions` (CLAUDE.md). Every part but instructions makes a new version of the team plugin, which each member accepts at their next update. Publishing hooks, mcp, agents or commands needs the admin role. " + (localFiles ? "Pass `directory`: the server reads only the files the part accepts, byte for byte, and refuses a link, a binary file or a file too large before anything is sent. Never retype the files into `files` when the folder is on this computer." : "Pass every file of the part in `files`, exactly as it is."),
|
|
4695
5089
|
inputSchema: {
|
|
4696
|
-
part:
|
|
5090
|
+
part: z16.enum(PARTS2).describe("Which part of the setup"),
|
|
4697
5091
|
...localFiles ? {
|
|
4698
|
-
directory:
|
|
5092
|
+
directory: z16.string().optional().describe(
|
|
4699
5093
|
"The folder on this computer: for hooks, the one holding hooks/hooks.json and scripts/; for mcp, the one holding .mcp.json; for instructions, the one holding CLAUDE.md; for agents or commands, the one holding agents/ or commands/"
|
|
4700
5094
|
)
|
|
4701
5095
|
} : {},
|
|
4702
|
-
files:
|
|
5096
|
+
files: z16.array(fileSchema).optional().describe(localFiles ? "Only when there is no folder to read" : "Every file of the part")
|
|
4703
5097
|
},
|
|
4704
5098
|
annotations: { readOnlyHint: false }
|
|
4705
5099
|
},
|
|
@@ -4729,13 +5123,13 @@ function registerTeamSetupTools(server2, apiClient2, { localFiles }) {
|
|
|
4729
5123
|
return text2(lines.join("\n"));
|
|
4730
5124
|
}
|
|
4731
5125
|
);
|
|
4732
|
-
|
|
5126
|
+
server.registerTool(
|
|
4733
5127
|
"resolve_person",
|
|
4734
5128
|
{
|
|
4735
5129
|
title: "Resolve Person",
|
|
4736
5130
|
description: "Read back a person that a team memory names by identifier, written as `con_\u2026` (label), `ctc_\u2026` or `usr_\u2026`: their label, name, phone number and email address, from the vault that holds them. The details are for this session only: never write them into a memory file; the memory keeps the identifier. Writes nothing.",
|
|
4737
5131
|
inputSchema: {
|
|
4738
|
-
id:
|
|
5132
|
+
id: z16.string().regex(PERSON_ID_RE, "a con_, ctc_ or usr_ identifier, as written in the memory").describe("The identifier, e.g. con_01JC2Q8Y4M7T0X3K5N9P2R6S8V")
|
|
4739
5133
|
},
|
|
4740
5134
|
annotations: { readOnlyHint: true }
|
|
4741
5135
|
},
|
|
@@ -4746,14 +5140,14 @@ function registerTeamSetupTools(server2, apiClient2, { localFiles }) {
|
|
|
4746
5140
|
}
|
|
4747
5141
|
);
|
|
4748
5142
|
if (!localFiles) return;
|
|
4749
|
-
|
|
5143
|
+
server.registerTool(
|
|
4750
5144
|
"sync_memory",
|
|
4751
5145
|
{
|
|
4752
5146
|
title: "Sync Memory",
|
|
4753
5147
|
description: `Sync one project's team memory on this computer with SystemDox: pull the files the team changed, push the ones changed here, keep both texts of a file changed on both sides. It never answers for a person: a file that still holds a phone number, an email address or a home-folder path is skipped and listed, with the command for the member to run in their own terminal (\`${SYNC_MEMORY_COMMAND}\`). Pass dryRun to see what it would do without writing or sending anything.`,
|
|
4754
5148
|
inputSchema: {
|
|
4755
|
-
project:
|
|
4756
|
-
dryRun:
|
|
5149
|
+
project: z16.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "a project name: lower case letters, digits and -").optional().describe("The project's team memory, e.g. puglieseweb-ltd; needed when the workspace has more than one"),
|
|
5150
|
+
dryRun: z16.boolean().optional().describe("Show what would be pulled, pushed, deleted, conflicted and skipped, and change nothing")
|
|
4757
5151
|
},
|
|
4758
5152
|
annotations: { readOnlyHint: false }
|
|
4759
5153
|
},
|
|
@@ -4788,29 +5182,34 @@ var init_team_setup = __esm({
|
|
|
4788
5182
|
|
|
4789
5183
|
// src/server.ts
|
|
4790
5184
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4791
|
-
function createServer(apiClient2, { localFiles = false } = {}) {
|
|
4792
|
-
const
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
5185
|
+
function createServer(apiClient2, { localFiles = false, instructions, keyProblem: keyProblem2 } = {}) {
|
|
5186
|
+
const server = new McpServer(
|
|
5187
|
+
{
|
|
5188
|
+
name: "systemdox-mcp-server",
|
|
5189
|
+
version: SERVER_VERSION
|
|
5190
|
+
},
|
|
5191
|
+
instructions ? { instructions } : void 0
|
|
5192
|
+
);
|
|
5193
|
+
registerGetGuardrails(server, apiClient2);
|
|
5194
|
+
registerGetSpecs(server, apiClient2);
|
|
5195
|
+
registerGetDocs(server, apiClient2);
|
|
5196
|
+
registerGetTechStack(server, apiClient2);
|
|
5197
|
+
registerGetPlanningContext(server, apiClient2);
|
|
5198
|
+
registerGetPlanBoard(server, apiClient2);
|
|
5199
|
+
registerGetChange(server, apiClient2);
|
|
5200
|
+
registerGetContext(server, apiClient2);
|
|
5201
|
+
registerGetProjectRules(server, apiClient2);
|
|
5202
|
+
registerGetPromptTemplates(server, apiClient2);
|
|
5203
|
+
registerGetTeamSetup(server, apiClient2, { localFiles, keyProblem: keyProblem2 });
|
|
5204
|
+
registerDocumentGraph(server, apiClient2);
|
|
5205
|
+
registerCreateGuardrail(server, apiClient2);
|
|
5206
|
+
registerUpdateSpec(server, apiClient2);
|
|
5207
|
+
registerReportDecision(server, apiClient2);
|
|
5208
|
+
registerCreateProjectRule(server, apiClient2);
|
|
5209
|
+
registerCreatePromptTemplate(server, apiClient2);
|
|
5210
|
+
registerSkillTools(server, apiClient2, { localFiles });
|
|
5211
|
+
registerTeamSetupTools(server, apiClient2, { localFiles });
|
|
5212
|
+
return server;
|
|
4814
5213
|
}
|
|
4815
5214
|
var SERVER_VERSION;
|
|
4816
5215
|
var init_server = __esm({
|
|
@@ -4824,6 +5223,8 @@ var init_server = __esm({
|
|
|
4824
5223
|
init_get_plan_board();
|
|
4825
5224
|
init_get_change();
|
|
4826
5225
|
init_get_context();
|
|
5226
|
+
init_get_team_setup();
|
|
5227
|
+
init_document_graph();
|
|
4827
5228
|
init_create_guardrail();
|
|
4828
5229
|
init_update_spec();
|
|
4829
5230
|
init_report_decision();
|
|
@@ -4833,7 +5234,7 @@ var init_server = __esm({
|
|
|
4833
5234
|
init_create_prompt_template();
|
|
4834
5235
|
init_skills2();
|
|
4835
5236
|
init_team_setup();
|
|
4836
|
-
SERVER_VERSION = true ? "0.1.
|
|
5237
|
+
SERVER_VERSION = true ? "0.1.51" : "0.0.0-dev";
|
|
4837
5238
|
}
|
|
4838
5239
|
});
|
|
4839
5240
|
|
|
@@ -4846,15 +5247,18 @@ __export(index_exports, {
|
|
|
4846
5247
|
});
|
|
4847
5248
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4848
5249
|
async function main() {
|
|
4849
|
-
await apiClient
|
|
5250
|
+
const instructions = await sessionInstructions(apiClient, { localFiles: true, keyProblem });
|
|
5251
|
+
const server = createServer(apiClient, { localFiles: true, instructions, keyProblem });
|
|
4850
5252
|
const transport = new StdioServerTransport();
|
|
4851
5253
|
await server.connect(transport);
|
|
4852
5254
|
}
|
|
4853
|
-
var config,
|
|
5255
|
+
var config, keyProblem, apiClient;
|
|
4854
5256
|
var init_index = __esm({
|
|
4855
5257
|
async "src/index.ts"() {
|
|
4856
5258
|
"use strict";
|
|
4857
5259
|
init_api_client();
|
|
5260
|
+
init_config();
|
|
5261
|
+
init_session_brief();
|
|
4858
5262
|
init_team_setup_files();
|
|
4859
5263
|
init_server();
|
|
4860
5264
|
init_server();
|
|
@@ -4862,10 +5266,15 @@ var init_index = __esm({
|
|
|
4862
5266
|
config = await loadLocalConfig();
|
|
4863
5267
|
} catch (err) {
|
|
4864
5268
|
console.error(`SystemDox MCP server: ${err.message}`);
|
|
4865
|
-
|
|
5269
|
+
keyProblem = keyReason(err.message);
|
|
5270
|
+
try {
|
|
5271
|
+
config = loadConfig(NO_KEY);
|
|
5272
|
+
} catch (urlErr) {
|
|
5273
|
+
console.error(`SystemDox MCP server: ${urlErr.message}`);
|
|
5274
|
+
process.exit(1);
|
|
5275
|
+
}
|
|
4866
5276
|
}
|
|
4867
5277
|
apiClient = new SystemDoxApiClient(config);
|
|
4868
|
-
server = createServer(apiClient, { localFiles: true });
|
|
4869
5278
|
main();
|
|
4870
5279
|
}
|
|
4871
5280
|
});
|