@subseam/mcp 0.4.33 → 0.4.36

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 +290 -31
  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);
@@ -30597,11 +30597,26 @@ var definitionDraft = external_exports.object({
30597
30597
  rung: external_exports.object({ disposition: external_exports.enum(["block", "warn", "off"]), means: external_exports.string() }).optional(),
30598
30598
  conflicts: external_exports.literal("not checked")
30599
30599
  });
30600
- function notComputedLines(uncomputed) {
30601
- if (uncomputed.length <= 5) return uncomputed.map((u) => ` at tip, not computed: ${u.path} (${u.reason})`);
30600
+ var draftAt = external_exports.discriminatedUnion("state", [
30601
+ external_exports.object({ state: external_exports.literal("current"), sha: external_exports.string(), ordinal: external_exports.number() }),
30602
+ external_exports.object({
30603
+ state: external_exports.literal("computing"),
30604
+ tipSha: external_exports.string(),
30605
+ tipOrdinal: external_exports.number(),
30606
+ banked: external_exports.object({ sha: external_exports.string(), ordinal: external_exports.number() }).nullable()
30607
+ })
30608
+ ]);
30609
+ var sha8 = (sha) => sha.slice(0, 8);
30610
+ function draftAtLine(at) {
30611
+ if (at.state === "current") return `verdict at ${sha8(at.sha)} (the tip)`;
30612
+ return at.banked === null ? `computing at ${sha8(at.tipSha)}, no verdict banked yet` : `verdict at ${sha8(at.banked.sha)} (tip is ${sha8(at.tipSha)}, computing)`;
30613
+ }
30614
+ function notComputedLines(uncomputed, atTip) {
30615
+ const at = atTip ? "at tip" : "at that commit";
30616
+ if (uncomputed.length <= 5) return uncomputed.map((u) => ` ${at}, not computed: ${u.path} (${u.reason})`);
30602
30617
  const byCause = /* @__PURE__ */ new Map();
30603
30618
  for (const u of uncomputed) byCause.set(u.reason, [...byCause.get(u.reason) ?? [], u.path]);
30604
- return [...byCause].map(([reason, paths]) => ` at tip, not computed: ${paths.length} file${paths.length === 1 ? "" : "s"} (${reason}), e.g. ${paths.slice(0, 3).join(", ")}`);
30619
+ return [...byCause].map(([reason, paths]) => ` ${at}, not computed: ${paths.length} file${paths.length === 1 ? "" : "s"} (${reason}), e.g. ${paths.slice(0, 3).join(", ")}`);
30605
30620
  }
30606
30621
  var FAILURES_SHOWN = 20;
30607
30622
  var EXAMPLES_FAILING_SHOWN = 3;
@@ -30614,7 +30629,7 @@ function exampleLines(examples) {
30614
30629
  ...examples.wouldFail === null ? [] : [` would fail: ${where(examples.wouldFail)} ${examples.wouldFail.does} (no code does this yet)`]
30615
30630
  ];
30616
30631
  }
30617
- function renderDraft(d) {
30632
+ function renderDraft(d, atTip = true) {
30618
30633
  const at = (c) => `${c.sha.slice(0, 9)} (${c.committedAt.slice(0, 10)})`;
30619
30634
  const h = d.history;
30620
30635
  const since = h === null || h.since === null ? "" : ` since ${at(h.since)}`;
@@ -30625,10 +30640,10 @@ function renderDraft(d) {
30625
30640
  const shown = d.atTip.failures.slice(0, FAILURES_SHOWN).map((f) => `${f.path}:${f.line} ${f.cause}`);
30626
30641
  const more = d.atTip.failures.length - shown.length;
30627
30642
  const notEvaluated = d.atTip.notEvaluated ?? null;
30628
- const tipAt = ` at tip ${d.atTip.sha.slice(0, 9)}`;
30643
+ const tipAt = ` at ${atTip ? "tip " : ""}${d.atTip.sha.slice(0, 9)}`;
30629
30644
  const tip = d.atTip.failures.length > 0 ? `${tipAt}: ${d.atTip.failures.length} failing: ` + shown.join("; ") + (more > 0 ? `; \u2026 and ${more} more` : "") : notEvaluated !== null ? `${tipAt}: not evaluated (${notEvaluated})` : `${tipAt}: holds`;
30630
30645
  const tipRest = d.atTip.failures.length > 0 && notEvaluated !== null ? [`${tipAt}, the rest was not evaluated (${notEvaluated})`] : [];
30631
- const open7 = notComputedLines(d.atTip.uncomputed);
30646
+ const open7 = notComputedLines(d.atTip.uncomputed, atTip);
30632
30647
  const rung = d.rung === void 0 ? [] : [` on approval, in force at ${d.rung.means}`];
30633
30648
  if (typeof d.gated === "string") return [` ${d.statement}`, ` gated: ${d.gated} This definition is not evaluated.`, ...rung, ` conflicts: ${d.conflicts}`].join("\n");
30634
30649
  return [` ${d.statement}`, ...exampleLines(d.examples), ` history: ${history}`, ...floor, tip, ...tipRest, ...open7, ...rung, ` conflicts: ${d.conflicts}`].join("\n");
@@ -30989,6 +31004,236 @@ function renderSummary(s) {
30989
31004
 
30990
31005
  // ../define/src/tool/show.ts
30991
31006
  init_esm_shims();
31007
+
31008
+ // ../define/src/library/entries.ts
31009
+ init_esm_shims();
31010
+
31011
+ // ../define/src/library/entries/one-integration-point.ts
31012
+ init_esm_shims();
31013
+ var oneIntegrationPoint = {
31014
+ id: "one-integration-point",
31015
+ practice: "One integration point for each external system",
31016
+ otherNames: [
31017
+ "gateway",
31018
+ "adapter",
31019
+ "ports and adapters",
31020
+ "hexagonal architecture",
31021
+ "API client wrapper",
31022
+ "external API",
31023
+ "third-party API",
31024
+ "repository pattern",
31025
+ "data access layer",
31026
+ "separation of concerns"
31027
+ ],
31028
+ quality: { characteristic: "Maintainability", subcharacteristic: "Modularity" },
31029
+ sources: [
31030
+ { title: 'Martin Fowler, "Gateway" (2021)', url: "https://martinfowler.com/articles/gateway-pattern.html" }
31031
+ ],
31032
+ 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.",
31033
+ 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.",
31034
+ claims: [
31035
+ {
31036
+ sentence: "Controllers, services and middleware never call the database library directly; only repositories do.",
31037
+ concept: "the database library's API, and the repository classes",
31038
+ intended: {
31039
+ bindsTo: ["the MikroORM EntityManager and EntityRepository APIs", "classes that extend EntityRepository"],
31040
+ mustFail: ["ArticleService", "ProfileService", "UserService", "TagService", "AuthMiddleware"],
31041
+ mustPass: ["ArticleController", "UserController", "ProfileController", "TagController", "UserRepository"]
31042
+ }
31043
+ },
31044
+ {
31045
+ sentence: "Each stored entity has exactly one dedicated repository.",
31046
+ concept: "the stored entity classes, and the repository classes",
31047
+ intended: {
31048
+ bindsTo: ["classes decorated as MikroORM entities", "classes that extend EntityRepository"],
31049
+ mustFail: ["Article", "Comment", "Tag"],
31050
+ mustPass: ["User"]
31051
+ }
31052
+ },
31053
+ {
31054
+ sentence: "Every database lookup that fails when it finds nothing states the status code the request answers with.",
31055
+ concept: "the database library's find-or-fail lookups",
31056
+ intended: {
31057
+ bindsTo: ["calls to findOneOrFail"],
31058
+ mustFail: [
31059
+ "ArticleService.findAll",
31060
+ "ArticleService.findOne",
31061
+ "ArticleService.addComment",
31062
+ "ArticleService.deleteComment",
31063
+ "ArticleService.favorite",
31064
+ "ArticleService.unFavorite",
31065
+ "ArticleService.findComments",
31066
+ "ArticleService.update",
31067
+ "ProfileService.findOne",
31068
+ "ProfileService.findProfile",
31069
+ "ProfileService.follow",
31070
+ "ProfileService.unFollow",
31071
+ "UserService.update",
31072
+ "UserService.findByEmail"
31073
+ ],
31074
+ mustPass: ["AuthMiddleware.use"]
31075
+ }
31076
+ }
31077
+ ]
31078
+ };
31079
+
31080
+ // ../define/src/library/entries/least-privilege.ts
31081
+ init_esm_shims();
31082
+ var leastPrivilege = {
31083
+ id: "least-privilege",
31084
+ practice: "Least privilege: every change is made only by someone allowed to make it",
31085
+ otherNames: [
31086
+ "principle of least privilege",
31087
+ "access control",
31088
+ "authorization",
31089
+ "deny by default",
31090
+ "broken access control",
31091
+ "insecure direct object reference",
31092
+ "IDOR",
31093
+ "broken object level authorization",
31094
+ "BOLA",
31095
+ "ownership check",
31096
+ "authentication required"
31097
+ ],
31098
+ quality: { characteristic: "Security", subcharacteristic: "Integrity" },
31099
+ sources: [
31100
+ {
31101
+ title: 'Saltzer and Schroeder, "The Protection of Information in Computer Systems" (1975), design principle (f): least privilege',
31102
+ url: "https://web.mit.edu/Saltzer/www/publications/protection/Basic.html"
31103
+ },
31104
+ {
31105
+ title: "OWASP ASVS 5.0, V8 Authorization: 8.2.1 function-level and 8.2.2 data-specific access",
31106
+ url: "https://github.com/OWASP/ASVS/blob/master/5.0/en/0x17-V8-Authorization.md"
31107
+ }
31108
+ ],
31109
+ 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.",
31110
+ 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.`,
31111
+ claims: [
31112
+ {
31113
+ sentence: "Every request that changes stored data requires a signed-in user, except signing up.",
31114
+ concept: "the request handlers that change stored data, and the sign-in check",
31115
+ intended: {
31116
+ bindsTo: ["controller handlers whose calls reach a database write", "AuthMiddleware"],
31117
+ mustFail: ["UserController.delete"],
31118
+ mustPass: [
31119
+ "ArticleController.create",
31120
+ "ArticleController.update",
31121
+ "ArticleController.delete",
31122
+ "ArticleController.createComment",
31123
+ "ArticleController.deleteComment",
31124
+ "ArticleController.favorite",
31125
+ "ArticleController.unFavorite",
31126
+ "UserController.update",
31127
+ "ProfileController.follow",
31128
+ "ProfileController.unFollow",
31129
+ "UserController.create"
31130
+ ]
31131
+ }
31132
+ },
31133
+ {
31134
+ sentence: "Only an article's author may change or delete that article.",
31135
+ concept: "the operations that change an article, and the article's author",
31136
+ intended: {
31137
+ bindsTo: ["ArticleService.update", "ArticleService.delete", "Article.author"],
31138
+ mustFail: ["ArticleService.update", "ArticleService.delete"],
31139
+ mustPass: [],
31140
+ planted: [{ branch: "planted/article-author-check", mustPass: ["ArticleService.update", "ArticleService.delete"] }]
31141
+ }
31142
+ },
31143
+ {
31144
+ sentence: "Only a comment's author may delete that comment.",
31145
+ concept: "the operation that deletes a comment, and the comment's author",
31146
+ intended: {
31147
+ bindsTo: ["ArticleService.deleteComment", "Comment.author"],
31148
+ mustFail: ["ArticleService.deleteComment"],
31149
+ mustPass: [],
31150
+ planted: [{ branch: "planted/comment-author-check", mustPass: ["ArticleService.deleteComment"] }]
31151
+ }
31152
+ }
31153
+ ]
31154
+ };
31155
+
31156
+ // ../define/src/library/entries.ts
31157
+ var PRACTICE_ENTRIES = [oneIntegrationPoint, leastPrivilege];
31158
+
31159
+ // ../define/src/library/search.ts
31160
+ init_esm_shims();
31161
+ var IGNORED = /* @__PURE__ */ new Set(["how", "the", "and", "for", "one", "can", "what", "does", "define", "definition", "definitions", "write", "should", "with", "that", "this", "our", "your", "code"]);
31162
+ function words(text) {
31163
+ return text.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length >= 3 && !IGNORED.has(w));
31164
+ }
31165
+ function hits(queryWords, text) {
31166
+ const own = words(text);
31167
+ return queryWords.filter((q) => own.some((w) => w.startsWith(q))).length;
31168
+ }
31169
+ function searchPractices(entries, query) {
31170
+ const q = [...new Set(words(query))];
31171
+ const scored = entries.map((entry) => ({
31172
+ entry,
31173
+ 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(" "))
31174
+ }));
31175
+ const best = Math.max(0, ...scored.map((s) => s.score));
31176
+ return scored.filter((s) => s.score > 0 && 2 * s.score >= best).sort((a, b) => b.score - a.score).map((s) => s.entry);
31177
+ }
31178
+
31179
+ // ../define/src/library/render.ts
31180
+ init_esm_shims();
31181
+
31182
+ // ../define/src/library/entry.ts
31183
+ init_esm_shims();
31184
+ var EXAMPLE_PROJECT = {
31185
+ name: "RealWorld",
31186
+ spec: "https://github.com/gothinkster/realworld",
31187
+ repo: "mikro-orm/nestjs-realworld-example-app",
31188
+ commit: "e723ffb798f7c7c6cad0037b0ca25d05b93db8ef"
31189
+ };
31190
+ var QUALITY_MODEL_SOURCE = {
31191
+ title: "ISO/IEC 25010:2023 product quality model, as listed by the arc42 quality model",
31192
+ url: "https://quality.arc42.org/standards/iso-25010"
31193
+ };
31194
+
31195
+ // ../define/src/library/render.ts
31196
+ function recordLine(claim) {
31197
+ const r = claim.record;
31198
+ if (r === void 0) return "not yet run through the product";
31199
+ if (r.kind === "failing") return `the product cannot admit or evaluate it yet (${r.issue})`;
31200
+ return `admitted as ${r.definitionId} v${r.version}, ${r.rung}`;
31201
+ }
31202
+ function renderPractice(entry) {
31203
+ const out2 = [
31204
+ `## ${entry.practice}`,
31205
+ `Also known as: ${entry.otherNames.join(", ")}.`,
31206
+ `Quality (ISO/IEC 25010:2023): ${entry.quality.characteristic} / ${entry.quality.subcharacteristic}.`,
31207
+ "",
31208
+ entry.wisdom,
31209
+ "",
31210
+ `How to split it into definitions, shown on ${EXAMPLE_PROJECT.name} (${EXAMPLE_PROJECT.repo} at ${EXAMPLE_PROJECT.commit.slice(0, 8)}):`,
31211
+ entry.split,
31212
+ "",
31213
+ "Enter each sentence on its own (`define` with `nlIntent`), rewritten for your own code's concepts:"
31214
+ ];
31215
+ entry.claims.forEach((claim, i) => {
31216
+ out2.push(`${i + 1}. "${claim.sentence}"`);
31217
+ out2.push(` binds to: ${claim.concept}. On ${EXAMPLE_PROJECT.name}: ${recordLine(claim)}.`);
31218
+ });
31219
+ out2.push("", "Sources:");
31220
+ for (const s of [...entry.sources, QUALITY_MODEL_SOURCE]) out2.push(`- ${s.title}: ${s.url}`);
31221
+ return out2.join("\n");
31222
+ }
31223
+ function renderPracticeSearch(query, matches, all) {
31224
+ if (matches.length === 0) {
31225
+ return [
31226
+ `show: no practice entry matches "${query}". The entries are:`,
31227
+ ...all.map((e) => `- ${e.practice} (search: ${e.otherNames.slice(0, 3).join(", ")})`)
31228
+ ].join("\n");
31229
+ }
31230
+ return [
31231
+ `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.`,
31232
+ ...matches.map(renderPractice)
31233
+ ].join("\n\n");
31234
+ }
31235
+
31236
+ // ../define/src/tool/show.ts
30992
31237
  var input3 = external_exports.object({
30993
31238
  definitionId: external_exports.string().uuid().optional(),
30994
31239
  repo: external_exports.string().min(1).optional(),
@@ -30999,7 +31244,10 @@ var input3 = external_exports.object({
30999
31244
  // (`locationPrefix`). Dashboard-mode filters; meaningless beside `definitionId`.
31000
31245
  versionStatus: external_exports.enum(["defaulted", "ratified", "corrected"]).optional(),
31001
31246
  provenance: external_exports.enum(["authored", "mined", "witness-derived"]).optional(),
31002
- locationPrefix: external_exports.string().min(1).optional()
31247
+ locationPrefix: external_exports.string().min(1).optional(),
31248
+ // The practice library (direction/define/language.md §9.6): a search over product content, never
31249
+ // over this tenant's definitions, so it is an arm of its own and takes no other field beside it.
31250
+ practice: external_exports.string().min(1).optional()
31003
31251
  });
31004
31252
  var statusBody = external_exports.object({
31005
31253
  definitions: external_exports.array(external_exports.object({
@@ -31082,24 +31330,33 @@ var showBody = external_exports.discriminatedUnion("ok", [
31082
31330
  bindings: external_exports.array(external_exports.object({ role: external_exports.string(), repo: external_exports.string(), anchorGrain: external_exports.string(), boundAtCommit: external_exports.string() })),
31083
31331
  violations: external_exports.array(showViolation),
31084
31332
  liveSuppressions: external_exports.array(showSuppression),
31085
- draft: definitionDraft.nullable()
31333
+ draft: definitionDraft.nullable(),
31334
+ /** Absent from older servers. */
31335
+ draftAt: draftAt.nullable().optional()
31086
31336
  }),
31087
31337
  external_exports.object({ ok: external_exports.literal(false) })
31088
31338
  ]);
31089
31339
  var showTool = {
31090
31340
  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/`).",
31341
+ 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
31342
  capability: "define",
31093
31343
  inputSchema: input3.shape,
31094
31344
  async handler(raw, ctx) {
31095
31345
  const args2 = input3.parse(raw);
31346
+ if (args2.practice !== void 0) {
31347
+ const { practice, ...rest } = args2;
31348
+ if (Object.values(rest).some((v) => v !== void 0)) {
31349
+ 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." };
31350
+ }
31351
+ return { text: renderPracticeSearch(practice, searchPractices(PRACTICE_ENTRIES, practice), PRACTICE_ENTRIES) };
31352
+ }
31096
31353
  if (args2.definitionId !== void 0) {
31097
31354
  const raw2 = await ctx.gateway.invoke("define-show", { definitionId: args2.definitionId });
31098
31355
  const object2 = readBody(showBody, raw2, "show", "define-show");
31099
31356
  if (!object2.ok) return { text: object2.text };
31100
31357
  return { text: renderObject(args2.definitionId, object2.value) };
31101
31358
  }
31102
- const { definitionId: _omit, ...filters } = args2;
31359
+ const { definitionId: _omit, practice: _practice, ...filters } = args2;
31103
31360
  const result = await ctx.gateway.invoke("define-status", filters);
31104
31361
  const status = readBody(statusBody, result, "show", "define-status");
31105
31362
  if (!status.ok) return { text: status.text };
@@ -31129,8 +31386,10 @@ function renderObject(definitionId, r) {
31129
31386
  out2.push("Bindings (current version):");
31130
31387
  for (const [line, n] of counts) out2.push(` \u2022 ${line}${n > 1 ? ` (\xD7${n})` : ""}`);
31131
31388
  }
31132
- out2.push(r.draft === null ? "Draft (current version): none \u2014 the version has no subject binding, or canvas has published no version of its repo." : `Draft (current version):
31133
- ${renderDraft(r.draft)}`);
31389
+ const at = r.draftAt ?? null;
31390
+ const atLine = at === null ? "" : ` ${draftAtLine(at)}`;
31391
+ out2.push(r.draft === null ? at === null ? "Draft (current version): none \u2014 the version has no subject binding, or canvas has published no version of its repo." : `Draft (current version): ${draftAtLine(at)}` : `Draft (current version):${atLine}
31392
+ ${renderDraft(r.draft, at?.state !== "computing")}`);
31134
31393
  if (r.violations.length === 0) {
31135
31394
  out2.push("Standing violations: none on the ledger.");
31136
31395
  } else {
@@ -48433,7 +48692,7 @@ var workReadTools = createWorkReadToolProviders({ capability: "work" });
48433
48692
  var bundledProviders = [src_default2, src_default, tension_default, ...defineTools, ...decisionTools, ...captureTools, ...buildTools, secureTool, ...workTools, ...workReadTools];
48434
48693
 
48435
48694
  // package.json
48436
- var version2 = "0.4.33";
48695
+ var version2 = "0.4.36";
48437
48696
 
48438
48697
  // src/server.ts
48439
48698
  var SERVER_VERSION = version2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@subseam/mcp",
3
- "version": "0.4.33",
3
+ "version": "0.4.36",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {