@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/dist/http.js CHANGED
@@ -968,8 +968,149 @@ ${notes.join("\n")}` : "")
968
968
  );
969
969
  }
970
970
 
971
- // src/tools/get-context.ts
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: z6.string().describe(
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: z6.array(z6.string()).optional().describe(
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: z6.number().optional().describe("Maximum token budget for the response (default: 4000)")
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 z7 } from "zod";
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: z7.string().describe("Short title for the check"),
1576
- description: z7.string().describe("Detailed description of what the check enforces and why"),
1577
- severity: z7.enum(["critical", "high", "medium", "low"]).describe("Severity level"),
1578
- category: z7.enum(CATEGORIES).optional().describe("Category (defaults to conventions)"),
1579
- pattern: z7.string().optional().describe("Bad code pattern that should be avoided"),
1580
- fix: z7.string().optional().describe("Correct code pattern to use instead"),
1581
- fitness_test: z7.string().optional().describe("Shell command to detect violations in CI")
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 z8 } from "zod";
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: z8.string().describe("ID of the spec to update"),
1656
- section: z8.enum(["proposal", "tasks", "requirements"]).describe("Which section to update"),
1657
- content: z8.string().describe(
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: z8.string().optional().describe(
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 z9 } from "zod";
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: z9.string().describe("Decision title (e.g., 'Use WebSocket for real-time updates')"),
1719
- context: z9.string().describe("What prompted this decision \u2014 the problem or constraint"),
1720
- decision: z9.string().describe("What was decided and why"),
1721
- consequences: z9.string().describe("What this decision means going forward \u2014 trade-offs, follow-up work"),
1722
- related_files: z9.array(z9.string()).optional().describe("File paths related to this decision")
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 z10 } from "zod";
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: z10.enum(["deployment", "architecture", "security", "testing", "process", "other"]).optional().describe("Filter by rule 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 z11 } from "zod";
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: z11.string().describe(
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: z11.enum(["deployment", "architecture", "security", "testing", "process", "other"]).describe("Rule 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 z12 } from "zod";
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: z12.string().describe("Short name for the template"),
1932
- description: z12.string().optional().describe("Brief description of when to use this template"),
1933
- prompt: z12.string().describe("The full prompt text that Gen AI will use when generating content")
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 z13 } from "zod";
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 = z13.object({
2170
- path: z13.string().describe('Path inside the skill folder, forward slashes, e.g. "references/guide.md"'),
2171
- content: z13.string().describe("The file's full text, exactly as it is")
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: z13.string().optional().describe("The skill folder on this computer, holding SKILL.md at its root")
2321
+ directory: z14.string().optional().describe("The skill folder on this computer, holding SKILL.md at its root")
2181
2322
  } : {},
2182
- files: z13.array(fileSchema).optional().describe(localFiles ? "Only when there is no folder to read" : "Every file of the skill folder"),
2183
- name: z13.string().optional().describe("The skill's name; defaults to SKILL.md's frontmatter name and must match it"),
2184
- spaceId: z13.string().optional().describe("Space for a NEW skill (default: your default space)"),
2185
- note: z13.string().max(500).optional().describe(`A short change note for this version, shown in the console's history (e.g. "Fix the GA4 step")`),
2186
- baseVersion: z13.number().int().positive().optional().describe(
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: z13.string().describe("The skill's name"),
2253
- version: z13.number().int().positive().optional().describe("A version number (default: the latest)"),
2254
- target: z13.string().optional().describe("Folder to install into, e.g. a repo's .claude/skills/<name>"),
2255
- ...localFiles ? { overwrite: z13.boolean().optional().describe("Replace files edited since they were installed") } : {}
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 z14 } from "zod";
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 PARTS = ["hooks", "mcp", "agents", "commands", "instructions"];
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 SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2806
+ var SLUG_RE2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2666
2807
  function isWorkspace(value) {
2667
- return typeof value === "string" && value.length <= 64 && SLUG_RE.test(value);
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 = z14.object({
3302
- path: z14.string().describe(`Path inside the part's folder, forward slashes, e.g. "hooks/hooks.json"`),
3303
- content: z14.string().describe("The file's full text, exactly as it is")
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: z14.enum(PARTS).describe("Which part of the setup"),
3452
+ part: z15.enum(PARTS2).describe("Which part of the setup"),
3312
3453
  ...localFiles ? {
3313
- directory: z14.string().optional().describe(
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: z14.array(fileSchema).optional().describe(localFiles ? "Only when there is no folder to read" : "Every file of the part")
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: z14.string().regex(PERSON_ID_RE, "a con_, ctc_ or usr_ identifier, as written in the memory").describe("The identifier, e.g. con_01JC2Q8Y4M7T0X3K5N9P2R6S8V")
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: z14.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"),
3371
- dryRun: z14.boolean().optional().describe("Show what would be pulled, pushed, deleted, conflicted and skipped, and change nothing")
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.48" : "0.0.0-dev";
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
- declare const config: import("./lib/config.js").ServerConfig;
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";