@subseam/mcp 0.4.32 → 0.4.34

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.
Files changed (2) hide show
  1. package/dist/main.js +263 -23
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -14122,15 +14122,15 @@ async function runWalkGrep(pattern, root, signal) {
14122
14122
  } catch (e) {
14123
14123
  throw new ToolError(`grep: invalid regex: ${e instanceof Error ? e.message : String(e)}`);
14124
14124
  }
14125
- const hits = [];
14125
+ const hits2 = [];
14126
14126
  let budget = GREP_OUTPUT_LIMIT;
14127
14127
  const push = (line) => {
14128
14128
  budget -= line.length + 1;
14129
14129
  if (budget < 0) {
14130
- hits.push(`[output truncated at ${GREP_OUTPUT_LIMIT} bytes]`);
14130
+ hits2.push(`[output truncated at ${GREP_OUTPUT_LIMIT} bytes]`);
14131
14131
  return false;
14132
14132
  }
14133
- hits.push(line);
14133
+ hits2.push(line);
14134
14134
  return true;
14135
14135
  };
14136
14136
  const stat4 = await fs4.stat(root).catch(() => null);
@@ -14141,9 +14141,9 @@ async function runWalkGrep(pattern, root, signal) {
14141
14141
  }
14142
14142
  if (signal?.aborted)
14143
14143
  throw new ToolError("grep: aborted");
14144
- if (hits.length === 0)
14144
+ if (hits2.length === 0)
14145
14145
  return "no matches";
14146
- return hits.join("\n");
14146
+ return hits2.join("\n");
14147
14147
  }
14148
14148
  async function grepFile(file2, re, push) {
14149
14149
  const stream = fssync2.createReadStream(file2, { encoding: "utf8" });
@@ -30133,21 +30133,21 @@ function mergeSources(textByRepo, concepts) {
30133
30133
  else arr.push(c);
30134
30134
  }
30135
30135
  const used = /* @__PURE__ */ new Set();
30136
- const hits = [];
30136
+ const hits2 = [];
30137
30137
  for (const [repo2, matches] of textByRepo) {
30138
30138
  for (const m of matches) {
30139
30139
  const covering = smallestCovering(byFile.get(`${repo2}${SEP}${m.path}`), m.line);
30140
30140
  if (covering !== void 0) {
30141
30141
  used.add(covering);
30142
- hits.push({ repo: repo2, path: m.path, line: m.line, content: m.content, concept: covering, hitClass: "text+graph" });
30142
+ hits2.push({ repo: repo2, path: m.path, line: m.line, content: m.content, concept: covering, hitClass: "text+graph" });
30143
30143
  } else {
30144
- hits.push({ repo: repo2, path: m.path, line: m.line, content: m.content, hitClass: "text-only" });
30144
+ hits2.push({ repo: repo2, path: m.path, line: m.line, content: m.content, hitClass: "text-only" });
30145
30145
  }
30146
30146
  }
30147
30147
  }
30148
30148
  for (const c of concepts) {
30149
30149
  if (used.has(c)) continue;
30150
- hits.push({
30150
+ hits2.push({
30151
30151
  repo: c.repo,
30152
30152
  path: c.location.path,
30153
30153
  line: c.location.startLine,
@@ -30156,7 +30156,7 @@ function mergeSources(textByRepo, concepts) {
30156
30156
  hitClass: "graph-only"
30157
30157
  });
30158
30158
  }
30159
- return dedupe(hits);
30159
+ return dedupe(hits2);
30160
30160
  }
30161
30161
  function smallestCovering(concepts, line) {
30162
30162
  if (concepts === void 0) return void 0;
@@ -30170,9 +30170,9 @@ function smallestCovering(concepts, line) {
30170
30170
  function span(c) {
30171
30171
  return c.location.endLine - c.location.startLine;
30172
30172
  }
30173
- function dedupe(hits) {
30173
+ function dedupe(hits2) {
30174
30174
  const byLoc = /* @__PURE__ */ new Map();
30175
- for (const h of hits) {
30175
+ for (const h of hits2) {
30176
30176
  const key = `${h.repo}${SEP}${h.path}${SEP}${h.line}`;
30177
30177
  const existing = byLoc.get(key);
30178
30178
  if (existing === void 0 || existing.concept === void 0 && h.concept !== void 0) {
@@ -30184,8 +30184,8 @@ function dedupe(hits) {
30184
30184
 
30185
30185
  // ../explore-tool/src/rank.ts
30186
30186
  init_esm_shims();
30187
- function rankHits(hits) {
30188
- return [...hits].sort(compare);
30187
+ function rankHits(hits2) {
30188
+ return [...hits2].sort(compare);
30189
30189
  }
30190
30190
  function compare(a, b) {
30191
30191
  const ga = a.concept;
@@ -30221,8 +30221,8 @@ function historySummary(c) {
30221
30221
  }
30222
30222
 
30223
30223
  // ../explore-tool/src/project.ts
30224
- function projectHits(hits, stamps) {
30225
- return hits.map((h) => projectOne(h, stamps.get(h.repo)));
30224
+ function projectHits(hits2, stamps) {
30225
+ return hits2.map((h) => projectOne(h, stamps.get(h.repo)));
30226
30226
  }
30227
30227
  function projectOne(h, stamp) {
30228
30228
  const diverged = h.concept !== void 0 && (stamp?.modified.has(h.path) ?? false);
@@ -30340,8 +30340,8 @@ function conceptLine(hit) {
30340
30340
  else if (c.verdict !== null) parts.push(c.verdict);
30341
30341
  return ` \u21B3 ${parts.join(" \xB7 ")}`;
30342
30342
  }
30343
- function stampOf(hits) {
30344
- return hits[0]?.stamp ?? "unknown";
30343
+ function stampOf(hits2) {
30344
+ return hits2[0]?.stamp ?? "unknown";
30345
30345
  }
30346
30346
  function dirOf(path7) {
30347
30347
  const d = dirname13(path7);
@@ -30989,8 +30989,238 @@ function renderSummary(s) {
30989
30989
 
30990
30990
  // ../define/src/tool/show.ts
30991
30991
  init_esm_shims();
30992
+
30993
+ // ../define/src/library/entries.ts
30994
+ init_esm_shims();
30995
+
30996
+ // ../define/src/library/entries/one-integration-point.ts
30997
+ init_esm_shims();
30998
+ var oneIntegrationPoint = {
30999
+ id: "one-integration-point",
31000
+ practice: "One integration point for each external system",
31001
+ otherNames: [
31002
+ "gateway",
31003
+ "adapter",
31004
+ "ports and adapters",
31005
+ "hexagonal architecture",
31006
+ "API client wrapper",
31007
+ "external API",
31008
+ "third-party API",
31009
+ "repository pattern",
31010
+ "data access layer",
31011
+ "separation of concerns"
31012
+ ],
31013
+ quality: { characteristic: "Maintainability", subcharacteristic: "Modularity" },
31014
+ sources: [
31015
+ { title: 'Martin Fowler, "Gateway" (2021)', url: "https://martinfowler.com/articles/gateway-pattern.html" }
31016
+ ],
31017
+ wisdom: "Code that talks to an external system (a database, a payment provider, GitHub) does it through one object that encapsulates that access, so the rest of the code depends on your interface, not on the foreign one. When the external system changes, fails or is replaced, there is one place to change, one place that knows its errors, and one place to fake in tests.",
31018
+ split: "Three claims, because the practice makes three separate promises and each can break on its own. (1) The boundary: code that handles requests never calls the external library directly; it binds to the external library's API on one side and to the integration point's classes on the other. (2) The shape inside the boundary: each external resource (on RealWorld, each stored entity) has exactly one dedicated handler, so two handlers never drift apart on the same resource; it binds to the resource types and the handler classes. (3) The failure contract: when the external system answers with nothing or with an error, the request answers with an explicit status code instead of an unhandled error; it binds to the external API's calls that can fail. The cut falls at the promises, never at a folder: a claim about which folder may call the library breaks the day the code moves, and says nothing about the other two promises. On RealWorld the external system is the database, reached through its ORM library.",
31019
+ claims: [
31020
+ {
31021
+ sentence: "Controllers, services and middleware never call the database library directly; only repositories do.",
31022
+ concept: "the database library's API, and the repository classes",
31023
+ intended: {
31024
+ bindsTo: ["the MikroORM EntityManager and EntityRepository APIs", "classes that extend EntityRepository"],
31025
+ mustFail: ["ArticleService", "ProfileService", "UserService", "TagService", "AuthMiddleware"],
31026
+ mustPass: ["ArticleController", "UserController", "ProfileController", "TagController", "UserRepository"]
31027
+ }
31028
+ },
31029
+ {
31030
+ sentence: "Each stored entity has exactly one dedicated repository.",
31031
+ concept: "the stored entity classes, and the repository classes",
31032
+ intended: {
31033
+ bindsTo: ["classes decorated as MikroORM entities", "classes that extend EntityRepository"],
31034
+ mustFail: ["Article", "Comment", "Tag"],
31035
+ mustPass: ["User"]
31036
+ }
31037
+ },
31038
+ {
31039
+ sentence: "Every database lookup that fails when it finds nothing states the status code the request answers with.",
31040
+ concept: "the database library's find-or-fail lookups",
31041
+ intended: {
31042
+ bindsTo: ["calls to findOneOrFail"],
31043
+ mustFail: [
31044
+ "ArticleService.findAll",
31045
+ "ArticleService.findOne",
31046
+ "ArticleService.addComment",
31047
+ "ArticleService.deleteComment",
31048
+ "ArticleService.favorite",
31049
+ "ArticleService.unFavorite",
31050
+ "ArticleService.findComments",
31051
+ "ArticleService.update",
31052
+ "ProfileService.findOne",
31053
+ "ProfileService.findProfile",
31054
+ "ProfileService.follow",
31055
+ "ProfileService.unFollow",
31056
+ "UserService.update",
31057
+ "UserService.findByEmail"
31058
+ ],
31059
+ mustPass: ["AuthMiddleware.use"]
31060
+ }
31061
+ }
31062
+ ]
31063
+ };
31064
+
31065
+ // ../define/src/library/entries/least-privilege.ts
31066
+ init_esm_shims();
31067
+ var leastPrivilege = {
31068
+ id: "least-privilege",
31069
+ practice: "Least privilege: every change is made only by someone allowed to make it",
31070
+ otherNames: [
31071
+ "principle of least privilege",
31072
+ "access control",
31073
+ "authorization",
31074
+ "deny by default",
31075
+ "broken access control",
31076
+ "insecure direct object reference",
31077
+ "IDOR",
31078
+ "broken object level authorization",
31079
+ "BOLA",
31080
+ "ownership check",
31081
+ "authentication required"
31082
+ ],
31083
+ quality: { characteristic: "Security", subcharacteristic: "Integrity" },
31084
+ sources: [
31085
+ {
31086
+ title: 'Saltzer and Schroeder, "The Protection of Information in Computer Systems" (1975), design principle (f): least privilege',
31087
+ url: "https://web.mit.edu/Saltzer/www/publications/protection/Basic.html"
31088
+ },
31089
+ {
31090
+ title: "OWASP ASVS 5.0, V8 Authorization: 8.2.1 function-level and 8.2.2 data-specific access",
31091
+ url: "https://github.com/OWASP/ASVS/blob/master/5.0/en/0x17-V8-Authorization.md"
31092
+ }
31093
+ ],
31094
+ wisdom: "Every program and every user operates with the least set of privileges necessary for the job. In a web service that means two checks: an operation is reachable only by a caller allowed to use that operation at all (function level), and it acts only on records that caller is allowed to touch (data level). Missing the second is the classic insecure direct object reference: any signed-in user can change anyone's record by changing an id in the request.",
31095
+ split: `One claim per level, and one per kind of owned record, because each is enforced in a different place and each can be missing while the others hold. (1) Function level: every request that changes stored data requires a signed-in user, with signing up as the one named exception. It binds to the request handlers that write, and to the step that establishes the signed-in user. (2) and (3) Data level, per owned record: on RealWorld, articles and comments each have an author, so each gets its own claim that only its author may change or delete it. They bind to the operations that change that record and to the record's author. The cut falls at the record type, not at the route: one claim per route would miss the next route that changes the same record, and one claim for "all records" would hide which record lost its check.`,
31096
+ claims: [
31097
+ {
31098
+ sentence: "Every request that changes stored data requires a signed-in user, except signing up.",
31099
+ concept: "the request handlers that change stored data, and the sign-in check",
31100
+ intended: {
31101
+ bindsTo: ["controller handlers whose calls reach a database write", "AuthMiddleware"],
31102
+ mustFail: ["UserController.delete"],
31103
+ mustPass: [
31104
+ "ArticleController.create",
31105
+ "ArticleController.update",
31106
+ "ArticleController.delete",
31107
+ "ArticleController.createComment",
31108
+ "ArticleController.deleteComment",
31109
+ "ArticleController.favorite",
31110
+ "ArticleController.unFavorite",
31111
+ "UserController.update",
31112
+ "ProfileController.follow",
31113
+ "ProfileController.unFollow",
31114
+ "UserController.create"
31115
+ ]
31116
+ }
31117
+ },
31118
+ {
31119
+ sentence: "Only an article's author may change or delete that article.",
31120
+ concept: "the operations that change an article, and the article's author",
31121
+ intended: {
31122
+ bindsTo: ["ArticleService.update", "ArticleService.delete", "Article.author"],
31123
+ mustFail: ["ArticleService.update", "ArticleService.delete"],
31124
+ mustPass: [],
31125
+ planted: [{ branch: "planted/article-author-check", mustPass: ["ArticleService.update", "ArticleService.delete"] }]
31126
+ }
31127
+ },
31128
+ {
31129
+ sentence: "Only a comment's author may delete that comment.",
31130
+ concept: "the operation that deletes a comment, and the comment's author",
31131
+ intended: {
31132
+ bindsTo: ["ArticleService.deleteComment", "Comment.author"],
31133
+ mustFail: ["ArticleService.deleteComment"],
31134
+ mustPass: [],
31135
+ planted: [{ branch: "planted/comment-author-check", mustPass: ["ArticleService.deleteComment"] }]
31136
+ }
31137
+ }
31138
+ ]
31139
+ };
31140
+
31141
+ // ../define/src/library/entries.ts
31142
+ var PRACTICE_ENTRIES = [oneIntegrationPoint, leastPrivilege];
31143
+
31144
+ // ../define/src/library/search.ts
31145
+ init_esm_shims();
31146
+ var IGNORED = /* @__PURE__ */ new Set(["how", "the", "and", "for", "one", "can", "what", "does", "define", "definition", "definitions", "write", "should", "with", "that", "this", "our", "your", "code"]);
31147
+ function words(text) {
31148
+ return text.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length >= 3 && !IGNORED.has(w));
31149
+ }
31150
+ function hits(queryWords, text) {
31151
+ const own = words(text);
31152
+ return queryWords.filter((q) => own.some((w) => w.startsWith(q))).length;
31153
+ }
31154
+ function searchPractices(entries, query) {
31155
+ const q = [...new Set(words(query))];
31156
+ const scored = entries.map((entry) => ({
31157
+ entry,
31158
+ score: 3 * hits(q, [entry.practice, ...entry.otherNames].join(" ")) + 2 * hits(q, `${entry.quality.characteristic} ${entry.quality.subcharacteristic}`) + hits(q, [entry.wisdom, entry.split, ...entry.claims.map((c) => c.sentence)].join(" "))
31159
+ }));
31160
+ const best = Math.max(0, ...scored.map((s) => s.score));
31161
+ return scored.filter((s) => s.score > 0 && 2 * s.score >= best).sort((a, b) => b.score - a.score).map((s) => s.entry);
31162
+ }
31163
+
31164
+ // ../define/src/library/render.ts
31165
+ init_esm_shims();
31166
+
31167
+ // ../define/src/library/entry.ts
31168
+ init_esm_shims();
31169
+ var EXAMPLE_PROJECT = {
31170
+ name: "RealWorld",
31171
+ spec: "https://github.com/gothinkster/realworld",
31172
+ repo: "mikro-orm/nestjs-realworld-example-app",
31173
+ commit: "e723ffb798f7c7c6cad0037b0ca25d05b93db8ef"
31174
+ };
31175
+ var QUALITY_MODEL_SOURCE = {
31176
+ title: "ISO/IEC 25010:2023 product quality model, as listed by the arc42 quality model",
31177
+ url: "https://quality.arc42.org/standards/iso-25010"
31178
+ };
31179
+
31180
+ // ../define/src/library/render.ts
31181
+ function recordLine(claim) {
31182
+ const r = claim.record;
31183
+ if (r === void 0) return "not yet run through the product";
31184
+ if (r.kind === "failing") return `the product cannot admit or evaluate it yet (${r.issue})`;
31185
+ return `admitted as ${r.definitionId} v${r.version}, ${r.rung}`;
31186
+ }
31187
+ function renderPractice(entry) {
31188
+ const out2 = [
31189
+ `## ${entry.practice}`,
31190
+ `Also known as: ${entry.otherNames.join(", ")}.`,
31191
+ `Quality (ISO/IEC 25010:2023): ${entry.quality.characteristic} / ${entry.quality.subcharacteristic}.`,
31192
+ "",
31193
+ entry.wisdom,
31194
+ "",
31195
+ `How to split it into definitions, shown on ${EXAMPLE_PROJECT.name} (${EXAMPLE_PROJECT.repo} at ${EXAMPLE_PROJECT.commit.slice(0, 8)}):`,
31196
+ entry.split,
31197
+ "",
31198
+ "Enter each sentence on its own (`define` with `nlIntent`), rewritten for your own code's concepts:"
31199
+ ];
31200
+ entry.claims.forEach((claim, i) => {
31201
+ out2.push(`${i + 1}. "${claim.sentence}"`);
31202
+ out2.push(` binds to: ${claim.concept}. On ${EXAMPLE_PROJECT.name}: ${recordLine(claim)}.`);
31203
+ });
31204
+ out2.push("", "Sources:");
31205
+ for (const s of [...entry.sources, QUALITY_MODEL_SOURCE]) out2.push(`- ${s.title}: ${s.url}`);
31206
+ return out2.join("\n");
31207
+ }
31208
+ function renderPracticeSearch(query, matches, all) {
31209
+ if (matches.length === 0) {
31210
+ return [
31211
+ `show: no practice entry matches "${query}". The entries are:`,
31212
+ ...all.map((e) => `- ${e.practice} (search: ${e.otherNames.slice(0, 3).join(", ")})`)
31213
+ ].join("\n");
31214
+ }
31215
+ return [
31216
+ `show: ${matches.length} practice entr${matches.length === 1 ? "y" : "ies"} for "${query}". Each states a practice as definitions on concepts (classes, methods, routes), never on files or folders, so they follow the code when it moves.`,
31217
+ ...matches.map(renderPractice)
31218
+ ].join("\n\n");
31219
+ }
31220
+
31221
+ // ../define/src/tool/show.ts
30992
31222
  var input3 = external_exports.object({
30993
- definitionId: external_exports.string().min(1).optional(),
31223
+ definitionId: external_exports.string().uuid().optional(),
30994
31224
  repo: external_exports.string().min(1).optional(),
30995
31225
  status: external_exports.enum(["active", "contested", "abandoned", "superseded"]).optional(),
30996
31226
  // F6d discovery (ruling 2026-08-02). Seeded/defaulted definitions are deliberately NOT pushed
@@ -30999,7 +31229,10 @@ var input3 = external_exports.object({
30999
31229
  // (`locationPrefix`). Dashboard-mode filters; meaningless beside `definitionId`.
31000
31230
  versionStatus: external_exports.enum(["defaulted", "ratified", "corrected"]).optional(),
31001
31231
  provenance: external_exports.enum(["authored", "mined", "witness-derived"]).optional(),
31002
- locationPrefix: external_exports.string().min(1).optional()
31232
+ locationPrefix: external_exports.string().min(1).optional(),
31233
+ // The practice library (direction/define/language.md §9.6): a search over product content, never
31234
+ // over this tenant's definitions, so it is an arm of its own and takes no other field beside it.
31235
+ practice: external_exports.string().min(1).optional()
31003
31236
  });
31004
31237
  var statusBody = external_exports.object({
31005
31238
  definitions: external_exports.array(external_exports.object({
@@ -31088,18 +31321,25 @@ var showBody = external_exports.discriminatedUnion("ok", [
31088
31321
  ]);
31089
31322
  var showTool = {
31090
31323
  name: "show",
31091
- description: "Show this tenant's definitions. With `definitionId`: ONE definition as a coherent object \u2014 every version with its status and provenance, the approval stamp, subject bindings, standing violations, live suppressions, and banked witnesses. Without: the corpus dashboard (admitted definitions, the open drift backlog, corpus size + evaluation staleness), optionally filtered by `repo`, epistemic `status`, `versionStatus` (use `defaulted` to see the un-affirmed definitions), `provenance`, or `locationPrefix` (e.g. `app/src/billing/`).",
31324
+ description: 'Show this tenant\'s definitions. With `definitionId`: ONE definition as a coherent object \u2014 every version with its status and provenance, the approval stamp, subject bindings, standing violations, live suppressions, and banked witnesses. Without: the corpus dashboard (admitted definitions, the open drift backlog, corpus size + evaluation staleness), optionally filtered by `repo`, epistemic `status`, `versionStatus` (use `defaulted` to see the un-affirmed definitions), `provenance`, or `locationPrefix` (e.g. `app/src/billing/`). With `practice` (words, e.g. "one integration point for an external API" or "least privilege"): the practice library \u2014 how to state a coding best practice as definitions on your code\'s concepts, with the plain sentences to enter through `define`.',
31092
31325
  capability: "define",
31093
31326
  inputSchema: input3.shape,
31094
31327
  async handler(raw, ctx) {
31095
31328
  const args2 = input3.parse(raw);
31329
+ if (args2.practice !== void 0) {
31330
+ const { practice, ...rest } = args2;
31331
+ if (Object.values(rest).some((v) => v !== void 0)) {
31332
+ return { text: "show: `practice` searches the practice library and takes no other field beside it \u2014 search the library, or read this tenant's definitions, not both." };
31333
+ }
31334
+ return { text: renderPracticeSearch(practice, searchPractices(PRACTICE_ENTRIES, practice), PRACTICE_ENTRIES) };
31335
+ }
31096
31336
  if (args2.definitionId !== void 0) {
31097
31337
  const raw2 = await ctx.gateway.invoke("define-show", { definitionId: args2.definitionId });
31098
31338
  const object2 = readBody(showBody, raw2, "show", "define-show");
31099
31339
  if (!object2.ok) return { text: object2.text };
31100
31340
  return { text: renderObject(args2.definitionId, object2.value) };
31101
31341
  }
31102
- const { definitionId: _omit, ...filters } = args2;
31342
+ const { definitionId: _omit, practice: _practice, ...filters } = args2;
31103
31343
  const result = await ctx.gateway.invoke("define-status", filters);
31104
31344
  const status = readBody(statusBody, result, "show", "define-status");
31105
31345
  if (!status.ok) return { text: status.text };
@@ -48433,7 +48673,7 @@ var workReadTools = createWorkReadToolProviders({ capability: "work" });
48433
48673
  var bundledProviders = [src_default2, src_default, tension_default, ...defineTools, ...decisionTools, ...captureTools, ...buildTools, secureTool, ...workTools, ...workReadTools];
48434
48674
 
48435
48675
  // package.json
48436
- var version2 = "0.4.32";
48676
+ var version2 = "0.4.34";
48437
48677
 
48438
48678
  // src/server.ts
48439
48679
  var SERVER_VERSION = version2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@subseam/mcp",
3
- "version": "0.4.32",
3
+ "version": "0.4.34",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {