@systemdox/mcp-server 0.1.48 → 0.1.49
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 +24 -11
- package/dist/cli.js +462 -260
- package/dist/http.js +203 -61
- package/dist/index.d.ts +2 -1
- package/dist/index.js +348 -168
- package/dist/install-skill.js +1 -2
- package/dist/lib/config.d.ts +4 -1
- package/dist/lib/config.js +1 -2
- package/dist/lib/team-setup-files.d.ts +14 -1
- package/dist/lib/team-setup-files.js +43 -0
- package/dist/setup-mcp.d.ts +66 -0
- package/dist/setup-mcp.js +636 -0
- package/dist/setup-team.d.ts +5 -1
- package/dist/setup-team.js +1 -0
- package/dist/sync-memory.js +22 -12
- package/dist/tools/get-change.d.ts +134 -0
- package/dist/tools/get-change.js +147 -0
- package/dist/tools/skills.js +1 -1
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -968,8 +968,149 @@ ${notes.join("\n")}` : "")
|
|
|
968
968
|
);
|
|
969
969
|
}
|
|
970
970
|
|
|
971
|
-
// src/tools/get-
|
|
971
|
+
// src/tools/get-change.ts
|
|
972
972
|
import { z as z6 } from "zod";
|
|
973
|
+
var GITHUB_NAME = "[A-Za-z0-9][A-Za-z0-9._-]{0,99}";
|
|
974
|
+
var REPO_RE = new RegExp(`^${GITHUB_NAME}/${GITHUB_NAME}$`);
|
|
975
|
+
var SLUG_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
976
|
+
var SLUG_MAX = 64;
|
|
977
|
+
var PARTS = ["decision", "contract", "behaviour", "plan", "release"];
|
|
978
|
+
var BRIEF = ["decision", "contract", "behaviour", "plan"];
|
|
979
|
+
var PAST_REVIEW = /* @__PURE__ */ new Set(["passed", "being built", "done"]);
|
|
980
|
+
function briefVerdict(states) {
|
|
981
|
+
const waiting = BRIEF.filter(
|
|
982
|
+
(part) => !(part === "decision" && states.decision === null) && !PAST_REVIEW.has(String(states[part]))
|
|
983
|
+
).map((part) => `${part} is ${states[part] ?? "unknown"}`);
|
|
984
|
+
return waiting.length === 0 ? "The brief is passed" : `The brief is not passed: ${waiting.join(", ")}`;
|
|
985
|
+
}
|
|
986
|
+
function prLink(pr) {
|
|
987
|
+
return `[${pr.repo}#${pr.number}](${pr.url}) ${pr.state}`;
|
|
988
|
+
}
|
|
989
|
+
function fileSource(file) {
|
|
990
|
+
if (file.pullRequest) return prLink(file.pullRequest);
|
|
991
|
+
return file.direct ? "committed directly" : "\u2014";
|
|
992
|
+
}
|
|
993
|
+
function partPullRequests(change, part) {
|
|
994
|
+
const files = part === "contract" ? change.contract.files : part === "behaviour" ? change.behaviour.files : part === "plan" ? [change.plan.file] : [];
|
|
995
|
+
const prs = part === "decision" ? [change.decision?.pullRequest ?? null] : part === "release" ? change.release.prs : files.map((file) => file?.pullRequest ?? null);
|
|
996
|
+
const unique = /* @__PURE__ */ new Map();
|
|
997
|
+
for (const pr of prs) {
|
|
998
|
+
if (pr && !unique.has(`${pr.repo}#${pr.number}`)) unique.set(`${pr.repo}#${pr.number}`, pr);
|
|
999
|
+
}
|
|
1000
|
+
return [...unique.values()].sort((a, b) => Number(b.state === "open") - Number(a.state === "open"));
|
|
1001
|
+
}
|
|
1002
|
+
function countsLine(counts) {
|
|
1003
|
+
const tags = ["proposed", "built", "defect", "unbuilt", "open", "untagged"].filter((tag) => counts[tag] > 0).map((tag) => `${counts[tag]} ${tag}`);
|
|
1004
|
+
return `${counts.total} scenario(s)` + (tags.length > 0 ? `: ${tags.join(", ")}` : "");
|
|
1005
|
+
}
|
|
1006
|
+
function partsTable(change) {
|
|
1007
|
+
const rows = PARTS.filter((part) => !(part === "decision" && change.states.decision === null)).map((part) => {
|
|
1008
|
+
const prs = partPullRequests(change, part);
|
|
1009
|
+
return `| ${part} | ${change.states[part]} | ${prs.length > 0 ? prs.map(prLink).join(", ") : "\u2014"} |`;
|
|
1010
|
+
});
|
|
1011
|
+
return ["| Part | State | Pull request |", "|---|---|---|", ...rows].join("\n");
|
|
1012
|
+
}
|
|
1013
|
+
function decisionSection(change) {
|
|
1014
|
+
const decision = change.decision;
|
|
1015
|
+
if (decision === null) return "## Decision\n\nNone: the proposal names no ADR (a small change).";
|
|
1016
|
+
const status = decision.status || "no record in a connected repository";
|
|
1017
|
+
return `## Decision
|
|
1018
|
+
|
|
1019
|
+
${decision.adr} \xB7 ${status}${decision.title ? ` \u2014 ${decision.title}` : ""}`;
|
|
1020
|
+
}
|
|
1021
|
+
function contractSection(change) {
|
|
1022
|
+
const files = change.contract.files;
|
|
1023
|
+
return "## Contract\n\n" + (files.length > 0 ? files.map((file) => `- ${file.path} \u2014 ${fileSource(file)}`).join("\n") : "No contract file yet (openapi.yaml, asyncapi.yaml or contract.md).");
|
|
1024
|
+
}
|
|
1025
|
+
function behaviourSection(change) {
|
|
1026
|
+
const { files, counts } = change.behaviour;
|
|
1027
|
+
if (files.length === 0) {
|
|
1028
|
+
return `## Behaviour
|
|
1029
|
+
|
|
1030
|
+
No feature file names this change. A feature file joins it when its path or its first ten lines name \`${change.slug}\`${change.decision ? ` or ${change.decision.adr}` : ""}.`;
|
|
1031
|
+
}
|
|
1032
|
+
const blocks = files.map(
|
|
1033
|
+
(file) => [
|
|
1034
|
+
`### ${file.path}`,
|
|
1035
|
+
"",
|
|
1036
|
+
[file.title, file.runner ? `runner ${file.runner}` : "", fileSource(file), countsLine(file.counts)].filter(Boolean).join(" \xB7 "),
|
|
1037
|
+
"",
|
|
1038
|
+
...file.scenarios.map((s) => `- ${s.id || "(no id)"} \xB7 ${s.state || "untagged"} \xB7 ${s.title}`)
|
|
1039
|
+
].join("\n")
|
|
1040
|
+
);
|
|
1041
|
+
return ["## Behaviour", countsLine(counts), ...blocks].join("\n\n");
|
|
1042
|
+
}
|
|
1043
|
+
function planSection(change) {
|
|
1044
|
+
const { file, draftId, board } = change.plan;
|
|
1045
|
+
const boardLine = board ? `[#${board.number} ${board.title}](${board.url}) \u2014 todo ${board.items.todo} \xB7 in progress ${board.items.inProgress} \xB7 done ${board.items.done}` + (board.items.truncated ? " (counted over its first 500 items)" : "") : "none";
|
|
1046
|
+
return [
|
|
1047
|
+
"## Plan",
|
|
1048
|
+
"",
|
|
1049
|
+
`- plan.md: ${file ? `${file.path} \u2014 ${fileSource(file)}` : "none"}`,
|
|
1050
|
+
`- Draft: ${draftId ?? "none"}`,
|
|
1051
|
+
`- Board: ${boardLine}`
|
|
1052
|
+
].join("\n");
|
|
1053
|
+
}
|
|
1054
|
+
function releaseSection(change) {
|
|
1055
|
+
const lines = change.release.prs.map(
|
|
1056
|
+
(pr) => `- ${prLink(pr)} \xB7 ${pr.title} \xB7 ` + (pr.approvingReview ? `approved by ${pr.approvingReview.by} at ${pr.approvingReview.at}` : "no approving review on its head yet")
|
|
1057
|
+
);
|
|
1058
|
+
return `## Release
|
|
1059
|
+
|
|
1060
|
+
${lines.length > 0 ? lines.join("\n") : "No release pull request is open."}`;
|
|
1061
|
+
}
|
|
1062
|
+
function formatChange(change) {
|
|
1063
|
+
const header = `# ${change.title}
|
|
1064
|
+
|
|
1065
|
+
\`${change.key}\` \xB7 size: ${change.size}` + (change.archived ? " \xB7 archived: read-only, done" : "") + ` \xB7 synced ${change.syncedAt}`;
|
|
1066
|
+
return [
|
|
1067
|
+
header,
|
|
1068
|
+
partsTable(change),
|
|
1069
|
+
decisionSection(change),
|
|
1070
|
+
contractSection(change),
|
|
1071
|
+
behaviourSection(change),
|
|
1072
|
+
planSection(change),
|
|
1073
|
+
releaseSection(change),
|
|
1074
|
+
// Compact: a change's every scenario with its steps rides in this block.
|
|
1075
|
+
"```json\n" + JSON.stringify(change) + "\n```",
|
|
1076
|
+
briefVerdict(change.states)
|
|
1077
|
+
].join("\n\n");
|
|
1078
|
+
}
|
|
1079
|
+
function registerGetChange(server, apiClient) {
|
|
1080
|
+
server.registerTool(
|
|
1081
|
+
"get_change",
|
|
1082
|
+
{
|
|
1083
|
+
title: "Get Change",
|
|
1084
|
+
description: `Read one change (a specs/changes/<slug>/ folder of a connected repository) as SystemDox last synced it from GitHub: the state of each part of its brief (decision, contract, behaviour, plan) and of its release, each with its pull request; the decision's ADR and status; the contract files; every feature file that names the change, with its scenarios as id \xB7 state \xB7 title; the plan (plan.md, the draft, the board); the release pull requests; and the Change object as JSON. The last line says "The brief is passed" or "The brief is not passed: <part> is <state>". Call it before building a story, and stop if the brief is not passed or the story's scenarios are not proposed. Read-only.`,
|
|
1085
|
+
inputSchema: {
|
|
1086
|
+
repo: z6.string().regex(REPO_RE, "the repository as owner/name, e.g. acme/booking-api").describe("The repository that holds the change, as owner/name"),
|
|
1087
|
+
slug: z6.string().max(SLUG_MAX).regex(SLUG_RE, "a change folder's name: lower-case words joined by -, e.g. customer-reminders").describe("The change's folder name: specs/changes/<slug>/")
|
|
1088
|
+
},
|
|
1089
|
+
annotations: { readOnlyHint: true }
|
|
1090
|
+
},
|
|
1091
|
+
async ({ repo, slug }) => {
|
|
1092
|
+
const [owner, name] = repo.split("/");
|
|
1093
|
+
let change;
|
|
1094
|
+
try {
|
|
1095
|
+
change = await apiClient.getFresh(
|
|
1096
|
+
`/v1/changes/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/${encodeURIComponent(slug)}`
|
|
1097
|
+
);
|
|
1098
|
+
} catch (err) {
|
|
1099
|
+
if (err.status === 404) {
|
|
1100
|
+
return {
|
|
1101
|
+
content: [{ type: "text", text: `No such change ${repo}/${slug}` }],
|
|
1102
|
+
isError: true
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
throw err;
|
|
1106
|
+
}
|
|
1107
|
+
return { content: [{ type: "text", text: formatChange(change) }] };
|
|
1108
|
+
}
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// src/tools/get-context.ts
|
|
1113
|
+
import { z as z7 } from "zod";
|
|
973
1114
|
|
|
974
1115
|
// src/lib/scoring.ts
|
|
975
1116
|
var WEIGHT_PATH = 0.4;
|
|
@@ -1536,13 +1677,13 @@ function registerGetContext(server, apiClient) {
|
|
|
1536
1677
|
title: "Get Context",
|
|
1537
1678
|
description: "Intelligent context aggregator: fetches checks, specs, docs, and tech stack, ranks them by relevance to your current task, and returns the full text of the highest-ranked items within a token budget \u2014 each with the ID needed to fetch the rest. Use this instead of calling individual tools when you want curated, relevant context.",
|
|
1538
1679
|
inputSchema: {
|
|
1539
|
-
task_description:
|
|
1680
|
+
task_description: z7.string().describe(
|
|
1540
1681
|
"What you're working on (e.g., 'add a new REST endpoint for user preferences')"
|
|
1541
1682
|
),
|
|
1542
|
-
file_paths:
|
|
1683
|
+
file_paths: z7.array(z7.string()).optional().describe(
|
|
1543
1684
|
"File paths being edited \u2014 used for language/category detection"
|
|
1544
1685
|
),
|
|
1545
|
-
max_tokens:
|
|
1686
|
+
max_tokens: z7.number().optional().describe("Maximum token budget for the response (default: 4000)")
|
|
1546
1687
|
}
|
|
1547
1688
|
},
|
|
1548
1689
|
async ({ task_description, file_paths, max_tokens }) => {
|
|
@@ -1559,7 +1700,7 @@ function registerGetContext(server, apiClient) {
|
|
|
1559
1700
|
}
|
|
1560
1701
|
|
|
1561
1702
|
// src/tools/create-guardrail.ts
|
|
1562
|
-
import { z as
|
|
1703
|
+
import { z as z8 } from "zod";
|
|
1563
1704
|
var DESCRIPTION2 = "Create a new check in SystemDox. Use this when you discover a pattern that should be enforced (banned pattern, required pattern, coding rule).";
|
|
1564
1705
|
var CATEGORIES = [
|
|
1565
1706
|
"imports",
|
|
@@ -1572,13 +1713,13 @@ var CATEGORIES = [
|
|
|
1572
1713
|
"data-access"
|
|
1573
1714
|
];
|
|
1574
1715
|
var inputSchema2 = () => ({
|
|
1575
|
-
title:
|
|
1576
|
-
description:
|
|
1577
|
-
severity:
|
|
1578
|
-
category:
|
|
1579
|
-
pattern:
|
|
1580
|
-
fix:
|
|
1581
|
-
fitness_test:
|
|
1716
|
+
title: z8.string().describe("Short title for the check"),
|
|
1717
|
+
description: z8.string().describe("Detailed description of what the check enforces and why"),
|
|
1718
|
+
severity: z8.enum(["critical", "high", "medium", "low"]).describe("Severity level"),
|
|
1719
|
+
category: z8.enum(CATEGORIES).optional().describe("Category (defaults to conventions)"),
|
|
1720
|
+
pattern: z8.string().optional().describe("Bad code pattern that should be avoided"),
|
|
1721
|
+
fix: z8.string().optional().describe("Correct code pattern to use instead"),
|
|
1722
|
+
fitness_test: z8.string().optional().describe("Shell command to detect violations in CI")
|
|
1582
1723
|
});
|
|
1583
1724
|
function registerCreateGuardrail(server, apiClient) {
|
|
1584
1725
|
const handler2 = async ({
|
|
@@ -1644,7 +1785,7 @@ function registerCreateGuardrail(server, apiClient) {
|
|
|
1644
1785
|
}
|
|
1645
1786
|
|
|
1646
1787
|
// src/tools/update-spec.ts
|
|
1647
|
-
import { z as
|
|
1788
|
+
import { z as z9 } from "zod";
|
|
1648
1789
|
function registerUpdateSpec(server, apiClient) {
|
|
1649
1790
|
server.registerTool(
|
|
1650
1791
|
"update_spec",
|
|
@@ -1652,12 +1793,12 @@ function registerUpdateSpec(server, apiClient) {
|
|
|
1652
1793
|
title: "Update Spec",
|
|
1653
1794
|
description: "Update an existing SystemDox spec's proposal, tasks, or requirements. Only draft specs can be modified \u2014 published specs must be versioned first.",
|
|
1654
1795
|
inputSchema: {
|
|
1655
|
-
draft_id:
|
|
1656
|
-
section:
|
|
1657
|
-
content:
|
|
1796
|
+
draft_id: z9.string().describe("ID of the spec to update"),
|
|
1797
|
+
section: z9.enum(["proposal", "tasks", "requirements"]).describe("Which section to update"),
|
|
1798
|
+
content: z9.string().describe(
|
|
1658
1799
|
"New markdown content for the section. For requirements, provide the full requirement content."
|
|
1659
1800
|
),
|
|
1660
|
-
requirement_name:
|
|
1801
|
+
requirement_name: z9.string().optional().describe(
|
|
1661
1802
|
"Name of the requirement (required when section is 'requirements')"
|
|
1662
1803
|
)
|
|
1663
1804
|
},
|
|
@@ -1707,7 +1848,7 @@ function registerUpdateSpec(server, apiClient) {
|
|
|
1707
1848
|
}
|
|
1708
1849
|
|
|
1709
1850
|
// src/tools/report-decision.ts
|
|
1710
|
-
import { z as
|
|
1851
|
+
import { z as z10 } from "zod";
|
|
1711
1852
|
function registerReportDecision(server, apiClient) {
|
|
1712
1853
|
server.registerTool(
|
|
1713
1854
|
"report_decision",
|
|
@@ -1715,11 +1856,11 @@ function registerReportDecision(server, apiClient) {
|
|
|
1715
1856
|
title: "Report Decision",
|
|
1716
1857
|
description: "Record an architecture decision made during development. Creates a draft ADR in SystemDox for human review.",
|
|
1717
1858
|
inputSchema: {
|
|
1718
|
-
title:
|
|
1719
|
-
context:
|
|
1720
|
-
decision:
|
|
1721
|
-
consequences:
|
|
1722
|
-
related_files:
|
|
1859
|
+
title: z10.string().describe("Decision title (e.g., 'Use WebSocket for real-time updates')"),
|
|
1860
|
+
context: z10.string().describe("What prompted this decision \u2014 the problem or constraint"),
|
|
1861
|
+
decision: z10.string().describe("What was decided and why"),
|
|
1862
|
+
consequences: z10.string().describe("What this decision means going forward \u2014 trade-offs, follow-up work"),
|
|
1863
|
+
related_files: z10.array(z10.string()).optional().describe("File paths related to this decision")
|
|
1723
1864
|
},
|
|
1724
1865
|
annotations: {
|
|
1725
1866
|
readOnlyHint: false
|
|
@@ -1770,7 +1911,7 @@ Status: draft \u2014 review in SystemDox before publishing.`
|
|
|
1770
1911
|
}
|
|
1771
1912
|
|
|
1772
1913
|
// src/tools/get-project-rules.ts
|
|
1773
|
-
import { z as
|
|
1914
|
+
import { z as z11 } from "zod";
|
|
1774
1915
|
var CATEGORY_LABELS = {
|
|
1775
1916
|
deployment: "Deployment",
|
|
1776
1917
|
architecture: "Architecture",
|
|
@@ -1786,7 +1927,7 @@ function registerGetProjectRules(server, apiClient) {
|
|
|
1786
1927
|
title: "Get Project Rules",
|
|
1787
1928
|
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.",
|
|
1788
1929
|
inputSchema: {
|
|
1789
|
-
category:
|
|
1930
|
+
category: z11.enum(["deployment", "architecture", "security", "testing", "process", "other"]).optional().describe("Filter by rule category")
|
|
1790
1931
|
}
|
|
1791
1932
|
},
|
|
1792
1933
|
async ({ category }) => {
|
|
@@ -1838,7 +1979,7 @@ function registerGetProjectRules(server, apiClient) {
|
|
|
1838
1979
|
}
|
|
1839
1980
|
|
|
1840
1981
|
// src/tools/create-project-rule.ts
|
|
1841
|
-
import { z as
|
|
1982
|
+
import { z as z12 } from "zod";
|
|
1842
1983
|
function registerCreateProjectRule(server, apiClient) {
|
|
1843
1984
|
server.registerTool(
|
|
1844
1985
|
"create_project_rule",
|
|
@@ -1846,10 +1987,10 @@ function registerCreateProjectRule(server, apiClient) {
|
|
|
1846
1987
|
title: "Create Project Rule",
|
|
1847
1988
|
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.",
|
|
1848
1989
|
inputSchema: {
|
|
1849
|
-
text:
|
|
1990
|
+
text: z12.string().describe(
|
|
1850
1991
|
"The rule statement \u2014 one enforceable principle per rule (e.g. 'All deployments MUST go through GitHub Actions workflows')"
|
|
1851
1992
|
),
|
|
1852
|
-
category:
|
|
1993
|
+
category: z12.enum(["deployment", "architecture", "security", "testing", "process", "other"]).describe("Rule category")
|
|
1853
1994
|
},
|
|
1854
1995
|
annotations: {
|
|
1855
1996
|
readOnlyHint: false
|
|
@@ -1920,7 +2061,7 @@ ${text3}`
|
|
|
1920
2061
|
}
|
|
1921
2062
|
|
|
1922
2063
|
// src/tools/create-prompt-template.ts
|
|
1923
|
-
import { z as
|
|
2064
|
+
import { z as z13 } from "zod";
|
|
1924
2065
|
function registerCreatePromptTemplate(server, apiClient) {
|
|
1925
2066
|
server.registerTool(
|
|
1926
2067
|
"create_prompt_template",
|
|
@@ -1928,9 +2069,9 @@ function registerCreatePromptTemplate(server, apiClient) {
|
|
|
1928
2069
|
title: "Create Prompt Template",
|
|
1929
2070
|
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.",
|
|
1930
2071
|
inputSchema: {
|
|
1931
|
-
name:
|
|
1932
|
-
description:
|
|
1933
|
-
prompt:
|
|
2072
|
+
name: z13.string().describe("Short name for the template"),
|
|
2073
|
+
description: z13.string().optional().describe("Brief description of when to use this template"),
|
|
2074
|
+
prompt: z13.string().describe("The full prompt text that Gen AI will use when generating content")
|
|
1934
2075
|
},
|
|
1935
2076
|
annotations: {
|
|
1936
2077
|
readOnlyHint: false
|
|
@@ -1959,7 +2100,7 @@ function registerCreatePromptTemplate(server, apiClient) {
|
|
|
1959
2100
|
}
|
|
1960
2101
|
|
|
1961
2102
|
// src/tools/skills.ts
|
|
1962
|
-
import { z as
|
|
2103
|
+
import { z as z14 } from "zod";
|
|
1963
2104
|
import { homedir as homedir2 } from "os";
|
|
1964
2105
|
import { basename, resolve as resolve2 } from "path";
|
|
1965
2106
|
|
|
@@ -2166,9 +2307,9 @@ function text(t) {
|
|
|
2166
2307
|
return { content: [{ type: "text", text: t }] };
|
|
2167
2308
|
}
|
|
2168
2309
|
function registerSkillTools(server, apiClient, { localFiles }) {
|
|
2169
|
-
const fileSchema =
|
|
2170
|
-
path:
|
|
2171
|
-
content:
|
|
2310
|
+
const fileSchema = z14.object({
|
|
2311
|
+
path: z14.string().describe('Path inside the skill folder, forward slashes, e.g. "references/guide.md"'),
|
|
2312
|
+
content: z14.string().describe("The file's full text, exactly as it is")
|
|
2172
2313
|
});
|
|
2173
2314
|
server.registerTool(
|
|
2174
2315
|
"publish_skill",
|
|
@@ -2177,13 +2318,13 @@ function registerSkillTools(server, apiClient, { localFiles }) {
|
|
|
2177
2318
|
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.",
|
|
2178
2319
|
inputSchema: {
|
|
2179
2320
|
...localFiles ? {
|
|
2180
|
-
directory:
|
|
2321
|
+
directory: z14.string().optional().describe("The skill folder on this computer, holding SKILL.md at its root")
|
|
2181
2322
|
} : {},
|
|
2182
|
-
files:
|
|
2183
|
-
name:
|
|
2184
|
-
spaceId:
|
|
2185
|
-
note:
|
|
2186
|
-
baseVersion:
|
|
2323
|
+
files: z14.array(fileSchema).optional().describe(localFiles ? "Only when there is no folder to read" : "Every file of the skill folder"),
|
|
2324
|
+
name: z14.string().optional().describe("The skill's name; defaults to SKILL.md's frontmatter name and must match it"),
|
|
2325
|
+
spaceId: z14.string().optional().describe("Space for a NEW skill (default: your default space)"),
|
|
2326
|
+
note: z14.string().max(500).optional().describe(`A short change note for this version, shown in the console's history (e.g. "Fix the GA4 step")`),
|
|
2327
|
+
baseVersion: z14.number().int().positive().optional().describe(
|
|
2187
2328
|
"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"
|
|
2188
2329
|
)
|
|
2189
2330
|
},
|
|
@@ -2233,7 +2374,7 @@ Left out (hidden files are never part of a skill): ${skipped.join(", ")}` : "")
|
|
|
2233
2374
|
const { skills } = await apiClient.get(SKILLS);
|
|
2234
2375
|
if (!skills.length) {
|
|
2235
2376
|
return text(
|
|
2236
|
-
`No skills published yet. Publish one with publish_skill; they appear at ${configLink("/docs/skills", await workspaceBase(apiClient))}`
|
|
2377
|
+
`No skills published yet. Publish one with publish_skill; they appear at ${configLink("/docs/team-setup/skills", await workspaceBase(apiClient))}`
|
|
2237
2378
|
);
|
|
2238
2379
|
}
|
|
2239
2380
|
return text(
|
|
@@ -2249,10 +2390,10 @@ Left out (hidden files are never part of a skill): ${skipped.join(", ")}` : "")
|
|
|
2249
2390
|
title: "Install Skill",
|
|
2250
2391
|
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.",
|
|
2251
2392
|
inputSchema: {
|
|
2252
|
-
name:
|
|
2253
|
-
version:
|
|
2254
|
-
target:
|
|
2255
|
-
...localFiles ? { overwrite:
|
|
2393
|
+
name: z14.string().describe("The skill's name"),
|
|
2394
|
+
version: z14.number().int().positive().optional().describe("A version number (default: the latest)"),
|
|
2395
|
+
target: z14.string().optional().describe("Folder to install into, e.g. a repo's .claude/skills/<name>"),
|
|
2396
|
+
...localFiles ? { overwrite: z14.boolean().optional().describe("Replace files edited since they were installed") } : {}
|
|
2256
2397
|
},
|
|
2257
2398
|
annotations: { readOnlyHint: !localFiles }
|
|
2258
2399
|
},
|
|
@@ -2298,7 +2439,7 @@ ${body}`
|
|
|
2298
2439
|
}
|
|
2299
2440
|
|
|
2300
2441
|
// src/tools/team-setup.ts
|
|
2301
|
-
import { z as
|
|
2442
|
+
import { z as z15 } from "zod";
|
|
2302
2443
|
import { existsSync as existsSync3 } from "fs";
|
|
2303
2444
|
import { lstat as lstat2 } from "fs/promises";
|
|
2304
2445
|
import { homedir as homedir4 } from "os";
|
|
@@ -2379,7 +2520,7 @@ function findSecrets(text3) {
|
|
|
2379
2520
|
}
|
|
2380
2521
|
|
|
2381
2522
|
// src/lib/team-setup-types.ts
|
|
2382
|
-
var
|
|
2523
|
+
var PARTS2 = ["hooks", "mcp", "agents", "commands", "instructions"];
|
|
2383
2524
|
var PERSON_ID_RE = new RegExp(`^${PERSON_ID}$`);
|
|
2384
2525
|
|
|
2385
2526
|
// src/sync-memory.ts
|
|
@@ -2662,9 +2803,9 @@ import { chmod, mkdir as mkdir2, readFile as readFile2, rename, rm as rm2, write
|
|
|
2662
2803
|
import { homedir as homedir3, userInfo } from "os";
|
|
2663
2804
|
import { dirname as dirname2, join as join2 } from "path";
|
|
2664
2805
|
import { promisify } from "util";
|
|
2665
|
-
var
|
|
2806
|
+
var SLUG_RE2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
2666
2807
|
function isWorkspace(value) {
|
|
2667
|
-
return typeof value === "string" && value.length <= 64 &&
|
|
2808
|
+
return typeof value === "string" && value.length <= 64 && SLUG_RE2.test(value);
|
|
2668
2809
|
}
|
|
2669
2810
|
function homeDir() {
|
|
2670
2811
|
return homedir3();
|
|
@@ -3298,9 +3439,9 @@ function describePerson(id, person) {
|
|
|
3298
3439
|
return lines.join("\n");
|
|
3299
3440
|
}
|
|
3300
3441
|
function registerTeamSetupTools(server, apiClient, { localFiles }) {
|
|
3301
|
-
const fileSchema =
|
|
3302
|
-
path:
|
|
3303
|
-
content:
|
|
3442
|
+
const fileSchema = z15.object({
|
|
3443
|
+
path: z15.string().describe(`Path inside the part's folder, forward slashes, e.g. "hooks/hooks.json"`),
|
|
3444
|
+
content: z15.string().describe("The file's full text, exactly as it is")
|
|
3304
3445
|
});
|
|
3305
3446
|
server.registerTool(
|
|
3306
3447
|
"publish_setup_part",
|
|
@@ -3308,13 +3449,13 @@ function registerTeamSetupTools(server, apiClient, { localFiles }) {
|
|
|
3308
3449
|
title: "Publish Setup Part",
|
|
3309
3450
|
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."),
|
|
3310
3451
|
inputSchema: {
|
|
3311
|
-
part:
|
|
3452
|
+
part: z15.enum(PARTS2).describe("Which part of the setup"),
|
|
3312
3453
|
...localFiles ? {
|
|
3313
|
-
directory:
|
|
3454
|
+
directory: z15.string().optional().describe(
|
|
3314
3455
|
"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/"
|
|
3315
3456
|
)
|
|
3316
3457
|
} : {},
|
|
3317
|
-
files:
|
|
3458
|
+
files: z15.array(fileSchema).optional().describe(localFiles ? "Only when there is no folder to read" : "Every file of the part")
|
|
3318
3459
|
},
|
|
3319
3460
|
annotations: { readOnlyHint: false }
|
|
3320
3461
|
},
|
|
@@ -3350,7 +3491,7 @@ function registerTeamSetupTools(server, apiClient, { localFiles }) {
|
|
|
3350
3491
|
title: "Resolve Person",
|
|
3351
3492
|
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.",
|
|
3352
3493
|
inputSchema: {
|
|
3353
|
-
id:
|
|
3494
|
+
id: z15.string().regex(PERSON_ID_RE, "a con_, ctc_ or usr_ identifier, as written in the memory").describe("The identifier, e.g. con_01JC2Q8Y4M7T0X3K5N9P2R6S8V")
|
|
3354
3495
|
},
|
|
3355
3496
|
annotations: { readOnlyHint: true }
|
|
3356
3497
|
},
|
|
@@ -3367,8 +3508,8 @@ function registerTeamSetupTools(server, apiClient, { localFiles }) {
|
|
|
3367
3508
|
title: "Sync Memory",
|
|
3368
3509
|
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.`,
|
|
3369
3510
|
inputSchema: {
|
|
3370
|
-
project:
|
|
3371
|
-
dryRun:
|
|
3511
|
+
project: z15.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"),
|
|
3512
|
+
dryRun: z15.boolean().optional().describe("Show what would be pulled, pushed, deleted, conflicted and skipped, and change nothing")
|
|
3372
3513
|
},
|
|
3373
3514
|
annotations: { readOnlyHint: false }
|
|
3374
3515
|
},
|
|
@@ -3386,7 +3527,7 @@ function registerTeamSetupTools(server, apiClient, { localFiles }) {
|
|
|
3386
3527
|
}
|
|
3387
3528
|
|
|
3388
3529
|
// src/server.ts
|
|
3389
|
-
var SERVER_VERSION = true ? "0.1.
|
|
3530
|
+
var SERVER_VERSION = true ? "0.1.49" : "0.0.0-dev";
|
|
3390
3531
|
function createServer(apiClient, { localFiles = false } = {}) {
|
|
3391
3532
|
const server = new McpServer({
|
|
3392
3533
|
name: "systemdox-mcp-server",
|
|
@@ -3398,6 +3539,7 @@ function createServer(apiClient, { localFiles = false } = {}) {
|
|
|
3398
3539
|
registerGetTechStack(server, apiClient);
|
|
3399
3540
|
registerGetPlanningContext(server, apiClient);
|
|
3400
3541
|
registerGetPlanBoard(server, apiClient);
|
|
3542
|
+
registerGetChange(server, apiClient);
|
|
3401
3543
|
registerGetContext(server, apiClient);
|
|
3402
3544
|
registerGetProjectRules(server, apiClient);
|
|
3403
3545
|
registerGetPromptTemplates(server, apiClient);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SystemDoxApiClient } from "./lib/api-client.js";
|
|
2
|
-
|
|
2
|
+
import { loadLocalConfig } from "./lib/team-setup-files.js";
|
|
3
|
+
declare let config: Awaited<ReturnType<typeof loadLocalConfig>>;
|
|
3
4
|
declare const apiClient: SystemDoxApiClient;
|
|
4
5
|
export { apiClient, config };
|
|
5
6
|
export { createServer } from "./server.js";
|