mason-context 0.16.0 → 0.16.1

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/mason-mcp.js CHANGED
@@ -3136,7 +3136,7 @@ var init_evidence = __esm({
3136
3136
  init_dead_command();
3137
3137
  init_storage();
3138
3138
  exec11 = promisify12(execFile12);
3139
- engineVersion = true ? "0.16.0" : "development";
3139
+ engineVersion = true ? "0.16.1" : "development";
3140
3140
  hash = (value) => createHash4("sha256").update(JSON.stringify(value)).digest("hex");
3141
3141
  content = readAuditInput;
3142
3142
  internal = (file) => file === ".mason" || file === ".mason/reports" || file.startsWith(".mason/reports/");
@@ -3752,7 +3752,7 @@ var init_launcher = __esm({
3752
3752
  "use strict";
3753
3753
  init_evidence();
3754
3754
  init_model();
3755
- executingVersion = () => true ? "0.16.0" : "development";
3755
+ executingVersion = () => true ? "0.16.1" : "development";
3756
3756
  mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
3757
3757
  hookCommand = (host) => `mason --setup-host ${host} auto`;
3758
3758
  }
@@ -3942,9 +3942,9 @@ var init_init = __esm({
3942
3942
 
3943
3943
  Mason provides recorded decisions and file impact over MCP. A concept map is optional.
3944
3944
 
3945
- - Task, bug, or change request \u2192 \`get_context\` with the task text and known files: matching decisions, related tests, impact, and any available map entries.
3945
+ - Task, bug, change request, or investigation of why the project works this way \u2192 \`get_context\` with the task text and known files: matching decisions, related tests, impact, and any available map entries.
3946
3946
  - Before editing a file \u2192 \`get_impact\` to check references, tests, and historical change partners.
3947
- - Learned something the code cannot explain (a failed approach, an incident's cause, a workaround's reason, a review-settled convention) \u2192 \`save_decision\` with rationale, anchors, and any known owner, sources, and recorder. It creates a proposal immediately without setup or a map. Never invent attribution or record code-derivable facts, session trivia, or secrets.
3947
+ - Investigation or correction reveals a reusable project lesson \u2192 \`save_decision\` before finishing. Capture only the lasting constraint and reason, with anchors and known sources, owner, and recorder. This creates a proposal; never infer approval. Preserve uncertain causes and unknown attribution. Route project lessons here; keep personal preferences in agent memory. Skip code summaries, current checkout status, temporary task permissions, session trivia, and secrets. No qualifying lesson means no record.
3948
3948
  - Consult trust metadata before relying on entries: unknown or changed freshness requires inspection, and failed verification means the description must be corrected. Check approval too: proposals are suggestions, legacy records are unreviewed, and accepted decisions are recorded constraints subject to freshness checks. An accepted revision remains operative while a pending proposal is reviewed; keep both versions and their freshness distinct.
3949
3949
  - Asked to review or re-verify a decision \u2192 \`review_decision\` first to inspect content, sources, history, and code changes. Record acceptance, reaffirmation, or retirement only when authorized by the user or a cited team review, with the actual reviewer and reason. Never infer approval from unchanged code. Review and commit the local record through the normal project workflow.
3950
3950
  - For an architectural overview, use \`get_snapshot\` if a map is available. If \`map.status\` is missing or invalid, use available decisions and source evidence; do not start building a map unless requested.
@@ -4751,16 +4751,204 @@ var init_onboarding = __esm({
4751
4751
  }
4752
4752
  });
4753
4753
 
4754
+ // src/impact/references.ts
4755
+ import path23 from "path";
4756
+ function templateEnd(content2, start) {
4757
+ let i = start + 1, interpolation = 0;
4758
+ while (i < content2.length) {
4759
+ const c = content2[i];
4760
+ if (c === "\\") {
4761
+ i += 2;
4762
+ continue;
4763
+ }
4764
+ if (!interpolation) {
4765
+ if (c === "`") return i + 1;
4766
+ if (c === "$" && content2[i + 1] === "{") {
4767
+ interpolation = 1;
4768
+ i += 2;
4769
+ continue;
4770
+ }
4771
+ } else {
4772
+ if (c === "`") {
4773
+ i = templateEnd(content2, i);
4774
+ continue;
4775
+ }
4776
+ if (c === '"' || c === "'") {
4777
+ const quote = c;
4778
+ for (i++; i < content2.length; i++) {
4779
+ if (content2[i] === "\\") i++;
4780
+ else if (content2[i] === quote) {
4781
+ i++;
4782
+ break;
4783
+ }
4784
+ }
4785
+ continue;
4786
+ }
4787
+ if (content2.startsWith("//", i)) {
4788
+ const end = content2.indexOf("\n", i + 2);
4789
+ i = end < 0 ? content2.length : end;
4790
+ continue;
4791
+ }
4792
+ if (content2.startsWith("/*", i)) {
4793
+ const end = content2.indexOf("*/", i + 2);
4794
+ i = end < 0 ? content2.length : end + 2;
4795
+ continue;
4796
+ }
4797
+ if (c === "{") interpolation++;
4798
+ if (c === "}") interpolation--;
4799
+ }
4800
+ i++;
4801
+ }
4802
+ return content2.length;
4803
+ }
4804
+ function tokens(content2) {
4805
+ const result = [];
4806
+ const re = /\/\/[^\n]*|\/\*[\s\S]*?(?:\*\/|$)|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`|[\w$]+|[^\s]/g;
4807
+ let match;
4808
+ while (match = re.exec(content2)) {
4809
+ const value = match[0];
4810
+ if (value.startsWith("//") || value.startsWith("/*")) continue;
4811
+ if (/^["'`]/.test(value)) {
4812
+ if (value[0] === "`") {
4813
+ re.lastIndex = templateEnd(content2, match.index);
4814
+ result.push({ value: "<opaque>" });
4815
+ continue;
4816
+ }
4817
+ result.push({ value: value.slice(1, -1), string: !value.includes("\\") && value[0] !== "`" });
4818
+ if (!result.at(-1).string) result[result.length - 1] = { value: "<opaque>" };
4819
+ } else result.push({ value });
4820
+ }
4821
+ return result;
4822
+ }
4823
+ function imports(content2) {
4824
+ const ts = tokens(content2), found = [];
4825
+ for (let i = 0; i < ts.length; i++) {
4826
+ const t = ts[i];
4827
+ if (t.string || ts[i - 1]?.value === ".") continue;
4828
+ if (["require", "import"].includes(t.value) && ts[i + 1]?.value === "(" && ts[i + 2]?.string && ts[i + 3]?.value === ")") {
4829
+ found.push(ts[i + 2].value);
4830
+ }
4831
+ if (!["import", "export"].includes(t.value) || ts[i + 1]?.value === "(") continue;
4832
+ if (t.value === "import" && ts[i + 1]?.string) {
4833
+ found.push(ts[i + 1].value);
4834
+ continue;
4835
+ }
4836
+ for (let j = i + 1; j < Math.min(ts.length, i + 150); j++) {
4837
+ if ([";", "=", "import", "export"].includes(ts[j].value) && !ts[j].string) break;
4838
+ if (ts[j].value === "from" && !ts[j].string && ts[j + 1]?.string) {
4839
+ found.push(ts[j + 1].value);
4840
+ break;
4841
+ }
4842
+ }
4843
+ }
4844
+ return found;
4845
+ }
4846
+ function qualifiedName(ts, start) {
4847
+ let name = "";
4848
+ for (let i = start; i < ts.length; i++) {
4849
+ if (ts[i].string || !/^[A-Za-z_$][\w$]*$/.test(ts[i].value)) break;
4850
+ name += ts[i].value;
4851
+ if (ts[i + 1]?.value !== "." || !/^[A-Za-z_$][\w$]*$/.test(ts[i + 2]?.value ?? "")) break;
4852
+ name += ".";
4853
+ i++;
4854
+ }
4855
+ return name;
4856
+ }
4857
+ function declaredSymbols(content2) {
4858
+ const ts = tokens(content2);
4859
+ const packageAt = ts.findIndex((t) => !t.string && t.value === "package");
4860
+ if (packageAt < 0) return [];
4861
+ const pkg = qualifiedName(ts, packageAt + 1), names = [];
4862
+ let depth = 0;
4863
+ for (let i = 0; i < ts.length; i++) {
4864
+ if (ts[i].string) continue;
4865
+ if (ts[i].value === "{") depth++;
4866
+ if (ts[i].value === "}") depth--;
4867
+ if (depth === 0 && ["class", "interface", "object", "enum", "typealias", "fun"].includes(ts[i].value) && /^[A-Za-z_$][\w$]*$/.test(ts[i + 1]?.value ?? "") && ts[i + 1].value !== "class") names.push(pkg + "." + ts[i + 1].value);
4868
+ }
4869
+ return names;
4870
+ }
4871
+ function jvmImports(content2) {
4872
+ const ts = tokens(content2), result = [];
4873
+ for (let i = 0; i < ts.length; i++) if (!ts[i].string && ts[i].value === "import") {
4874
+ const name = qualifiedName(ts, i + (ts[i + 1]?.value === "static" ? 2 : 1));
4875
+ if (name) result.push(name);
4876
+ }
4877
+ return result;
4878
+ }
4879
+ function resolveImport(file, specifier, available) {
4880
+ if (!specifier.startsWith("./") && !specifier.startsWith("../")) return null;
4881
+ const relative = path23.posix.normalize(path23.posix.join(path23.posix.dirname(file), specifier));
4882
+ if (relative.startsWith("../")) return null;
4883
+ if (available.has(relative)) return relative;
4884
+ const ext = path23.posix.extname(relative);
4885
+ const replacements = { ".js": [".ts", ".tsx"], ".mjs": [".mts"], ".cjs": [".cts"] };
4886
+ const candidates = ext ? (replacements[ext] ?? []).map((e) => relative.slice(0, -ext.length) + e) : jsExtensions.flatMap((e) => [relative + e, relative + "/index" + e]);
4887
+ const found = candidates.filter((f) => available.has(f));
4888
+ return found.length === 1 ? found[0] : null;
4889
+ }
4890
+ function mentionsPath(content2, target) {
4891
+ return new RegExp(`(?<![\\w./\\\\-])(?:\\./)?${escape(target)}(?![\\w./\\\\-])`).test(content2);
4892
+ }
4893
+ function createReferenceMatcher(targets, available, targetContents = /* @__PURE__ */ new Map()) {
4894
+ const counts = /* @__PURE__ */ new Map();
4895
+ for (const file of available) {
4896
+ const name = path23.posix.basename(file, path23.posix.extname(file));
4897
+ counts.set(name, (counts.get(name) ?? 0) + 1);
4898
+ }
4899
+ const jvmTargets = new Map([...targetContents].filter(([file]) => /\.(?:kt|kts|java)$/.test(file)).map(([file, content2]) => [file, declaredSymbols(content2)]));
4900
+ return (source) => {
4901
+ if (targets.includes(source.path)) return [];
4902
+ const resolved = new Set(jsExtensions.includes(path23.posix.extname(source.path)) ? imports(source.content).map((s) => resolveImport(source.path, s, available)).filter(Boolean) : []);
4903
+ if (/\.(?:kt|kts|java)$/.test(source.path)) {
4904
+ const imported = jvmImports(source.content);
4905
+ for (const [target, symbols] of jvmTargets) if (symbols.some((symbol) => imported.some((name) => name === symbol || name.startsWith(symbol + ".")))) resolved.add(target);
4906
+ }
4907
+ const groups = /* @__PURE__ */ new Map();
4908
+ const add = (evidence, match) => groups.set(evidence, [...groups.get(evidence) ?? [], match]);
4909
+ for (const target of targets) {
4910
+ if (resolved.has(target)) {
4911
+ add("resolved-import", target);
4912
+ continue;
4913
+ }
4914
+ if (mentionsPath(source.content, target)) {
4915
+ add("explicit-path", target);
4916
+ continue;
4917
+ }
4918
+ const ext = path23.posix.extname(target), name = path23.posix.basename(target, ext);
4919
+ if (!/\.(?:[cm]?[jt]sx?|py|kt|java|go|rs|swift|rb|cs|cpp|c|h|hpp|dart|php)$/.test(ext) || name.startsWith(".") || name.length < 3 || genericNames.has(name.toLowerCase()) || counts.get(name) !== 1) continue;
4920
+ if (word(name).test(source.content)) {
4921
+ add("name-candidate", name);
4922
+ }
4923
+ }
4924
+ return [...groups].map(([evidence, matches]) => ({ file: source.path, matches, kind: evidence === "resolved-import" ? "import" : "mention", evidence }));
4925
+ };
4926
+ }
4927
+ function sortReferences(result) {
4928
+ const rank = { "resolved-import": 0, "explicit-path": 1, "name-candidate": 2 };
4929
+ return result.sort((a, b) => rank[a.evidence] - rank[b.evidence] || b.matches.length - a.matches.length || a.file.localeCompare(b.file));
4930
+ }
4931
+ var jsExtensions, genericNames, escape, word;
4932
+ var init_references = __esm({
4933
+ "src/impact/references.ts"() {
4934
+ "use strict";
4935
+ jsExtensions = [".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs"];
4936
+ genericNames = /* @__PURE__ */ new Set(["index", "main", "app", "init", "config", "settings", "types", "utils", "test", "tests"]);
4937
+ escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4938
+ word = (s) => new RegExp(`(?<![\\w$])${escape(s)}(?![\\w$])`);
4939
+ }
4940
+ });
4941
+
4754
4942
  // src/impact/impact.ts
4755
4943
  var impact_exports = {};
4756
4944
  __export(impact_exports, {
4757
4945
  analyzeImpact: () => analyzeImpact
4758
4946
  });
4759
- import path23 from "path";
4947
+ import path24 from "path";
4760
4948
  import { execFile as execFile16 } from "child_process";
4761
4949
  import { promisify as promisify16 } from "util";
4762
4950
  async function analyzeImpact(rootDir, targetFiles) {
4763
- const resolvedRoot = path23.resolve(rootDir);
4951
+ const resolvedRoot = path24.resolve(rootDir);
4764
4952
  const resolvedTargets = await resolveTargetFiles(resolvedRoot, targetFiles);
4765
4953
  const [cochange, references, tests] = await Promise.all([
4766
4954
  getCochangeFiles(resolvedRoot, resolvedTargets),
@@ -4837,52 +5025,24 @@ async function getCochangeFiles(rootDir, targetFiles) {
4837
5025
  })).filter((e) => e.cochangeRate >= 0.3 || e.sharedCommits >= 3).sort((a, b) => b.cochangeRate - a.cochangeRate).slice(0, 20);
4838
5026
  }
4839
5027
  async function getReferences(rootDir, targetFiles) {
4840
- const searchNames = /* @__PURE__ */ new Set();
4841
- for (const target of targetFiles) {
4842
- const basename = path23.basename(target).replace(/\.[^.]+$/, "");
4843
- searchNames.add(basename);
4844
- }
4845
5028
  const access = await createFileAccess(rootDir);
4846
5029
  const allSourceFiles = await access.list(SOURCE_GLOB);
4847
- const targetSet = new Set(targetFiles);
4848
- const filesToSearch = allSourceFiles.filter((f) => !targetSet.has(f));
4849
- const results = /* @__PURE__ */ new Map();
4850
- const importLine = /^\s*(import\b|from\b.*\bimport\b|const\b.*=\s*require\(|use\b|#include\b|require\s*\()/;
4851
- const batchSize = 50;
4852
- for (let i = 0; i < filesToSearch.length; i += batchSize) {
4853
- const batch = filesToSearch.slice(i, i + batchSize);
4854
- await Promise.all(
4855
- batch.map(async (file) => {
4856
- try {
4857
- const full = await access.read(file);
4858
- if (!full) return;
4859
- const content2 = full.content;
4860
- const lines = content2.split("\n");
4861
- for (const name of searchNames) {
4862
- const regex = new RegExp(`\\b${escapeRegex(name)}\\b`);
4863
- if (!regex.test(content2)) continue;
4864
- if (!results.has(file)) {
4865
- results.set(file, { matches: /* @__PURE__ */ new Set(), isImport: false });
4866
- }
4867
- const entry = results.get(file);
4868
- entry.matches.add(name);
4869
- if (!entry.isImport && lines.some((l) => regex.test(l) && importLine.test(l))) {
4870
- entry.isImport = true;
4871
- }
4872
- }
4873
- } catch {
4874
- }
4875
- })
4876
- );
5030
+ const available = new Set(allSourceFiles);
5031
+ const targetContents = /* @__PURE__ */ new Map();
5032
+ for (const target of targetFiles) {
5033
+ const source = await access.read(target);
5034
+ if (source) {
5035
+ available.add(target);
5036
+ targetContents.set(target, source.content);
5037
+ }
4877
5038
  }
4878
- return [...results.entries()].map(([file, { matches, isImport }]) => ({
4879
- file,
4880
- matches: [...matches],
4881
- kind: isImport ? "import" : "mention"
4882
- })).sort((a, b) => {
4883
- if (a.kind !== b.kind) return a.kind === "import" ? -1 : 1;
4884
- return b.matches.length - a.matches.length;
4885
- });
5039
+ const match = createReferenceMatcher(targetFiles, available, targetContents);
5040
+ const references = [];
5041
+ for (let i = 0; i < allSourceFiles.length; i += 50) {
5042
+ const batch = await Promise.all(allSourceFiles.slice(i, i + 50).map((file) => access.read(file)));
5043
+ for (const source of batch) if (source) references.push(...match(source));
5044
+ }
5045
+ return sortReferences(references);
4886
5046
  }
4887
5047
  async function getRelatedTests(rootDir, targetFiles) {
4888
5048
  const testPatterns = [
@@ -4902,9 +5062,9 @@ async function getRelatedTests(rootDir, targetFiles) {
4902
5062
  const testFiles = await (await createFileAccess(rootDir)).list(testPatterns);
4903
5063
  const results = [];
4904
5064
  for (const target of targetFiles) {
4905
- const targetBaseName = path23.basename(target).replace(/\.[^.]+$/, "");
5065
+ const targetBaseName = path24.basename(target).replace(/\.[^.]+$/, "");
4906
5066
  for (const testFile of testFiles) {
4907
- const testBaseName = path23.basename(testFile).replace(/\.[^.]+$/, "");
5067
+ const testBaseName = path24.basename(testFile).replace(/\.[^.]+$/, "");
4908
5068
  const sourceName = testBaseName.replace(/Test$|Tests$|Spec$|\.test$|\.spec$/, "").replace(/^test_|_test$/, "");
4909
5069
  if (sourceName === targetBaseName) {
4910
5070
  results.push({
@@ -4916,15 +5076,13 @@ async function getRelatedTests(rootDir, targetFiles) {
4916
5076
  }
4917
5077
  return results;
4918
5078
  }
4919
- function escapeRegex(str) {
4920
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4921
- }
4922
5079
  var exec14;
4923
5080
  var init_impact = __esm({
4924
5081
  "src/impact/impact.ts"() {
4925
5082
  "use strict";
4926
5083
  init_files();
4927
5084
  init_paths();
5085
+ init_references();
4928
5086
  exec14 = promisify16(execFile16);
4929
5087
  }
4930
5088
  });
@@ -4934,13 +5092,13 @@ var assemble_exports = {};
4934
5092
  __export(assemble_exports, {
4935
5093
  assembleContext: () => assembleContext
4936
5094
  });
4937
- import path24 from "path";
5095
+ import path25 from "path";
4938
5096
  import fs17 from "fs/promises";
4939
5097
  async function collectImpact(root, candidates) {
4940
5098
  const targets = /* @__PURE__ */ new Set();
4941
5099
  let sourceFiles;
4942
5100
  for (const candidate of sanitizeRepoPaths(candidates)) {
4943
- const stat = await fs17.stat(path24.join(root, candidate)).catch(() => null);
5101
+ const stat = await fs17.stat(path25.join(root, candidate)).catch(() => null);
4944
5102
  if (stat?.isDirectory()) {
4945
5103
  sourceFiles ??= await (await createFileAccess(root)).list();
4946
5104
  for (const file of sourceFiles) {
@@ -4954,13 +5112,19 @@ async function collectImpact(root, candidates) {
4954
5112
  }
4955
5113
  if (!targets.size) return { impact: null, relatedTests: [] };
4956
5114
  const result = await analyzeImpact(root, [...targets]);
5115
+ const referenceCandidatesOmitted = result.references.filter((r) => r.evidence === "name-candidate").length;
4957
5116
  return {
4958
- impact: { targets: result.targetFiles, cochange: result.cochange, references: result.references.slice(0, 10) },
5117
+ impact: {
5118
+ targets: result.targetFiles,
5119
+ cochange: result.cochange,
5120
+ references: result.references.filter((r) => r.evidence !== "name-candidate").slice(0, 10),
5121
+ ...referenceCandidatesOmitted ? { referenceCandidatesOmitted } : {}
5122
+ },
4959
5123
  relatedTests: [...new Set(result.tests.map((t) => t.file))]
4960
5124
  };
4961
5125
  }
4962
5126
  async function assembleContext(rootDir, task, files) {
4963
- const resolvedRoot = path24.resolve(rootDir);
5127
+ const resolvedRoot = path25.resolve(rootDir);
4964
5128
  const mapState = await inspectSnapshot(resolvedRoot);
4965
5129
  const snapshot = mapState.snapshot;
4966
5130
  const store = await loadDecisionStore(resolvedRoot);
@@ -5481,8 +5645,8 @@ __export(client_exports, {
5481
5645
  function createConfluenceClient(config, fetchFn = fetch) {
5482
5646
  const baseUrl = config.baseUrl.replace(/\/+$/, "");
5483
5647
  const auth = "Basic " + Buffer.from(`${config.email}:${config.apiToken}`).toString("base64");
5484
- async function call(method, path28, body) {
5485
- const res = await fetchFn(`${baseUrl}${path28}`, {
5648
+ async function call(method, path29, body) {
5649
+ const res = await fetchFn(`${baseUrl}${path29}`, {
5486
5650
  method,
5487
5651
  headers: {
5488
5652
  Authorization: auth,
@@ -5494,7 +5658,7 @@ function createConfluenceClient(config, fetchFn = fetch) {
5494
5658
  if (!res.ok) {
5495
5659
  const text2 = await res.text();
5496
5660
  throw new Error(
5497
- `Confluence ${method} ${path28} failed: ${res.status} ${res.statusText} \u2014 ${text2}`
5661
+ `Confluence ${method} ${path29} failed: ${res.status} ${res.statusText} \u2014 ${text2}`
5498
5662
  );
5499
5663
  }
5500
5664
  if (res.status === 204) return null;
@@ -5596,15 +5760,15 @@ __export(config_exports2, {
5596
5760
  validateProvider: () => validateProvider
5597
5761
  });
5598
5762
  import fs19 from "fs/promises";
5599
- import path25 from "path";
5763
+ import path26 from "path";
5600
5764
  import os3 from "os";
5601
5765
  import { execFile as execFile17 } from "child_process";
5602
5766
  import { promisify as promisify17 } from "util";
5603
5767
  function configDir() {
5604
- return path25.join(os3.homedir(), ".mason");
5768
+ return path26.join(os3.homedir(), ".mason");
5605
5769
  }
5606
5770
  function configFile() {
5607
- return path25.join(configDir(), "config.json");
5771
+ return path26.join(configDir(), "config.json");
5608
5772
  }
5609
5773
  async function loadConfig() {
5610
5774
  try {
@@ -5684,21 +5848,21 @@ var init_url = __esm({
5684
5848
  });
5685
5849
 
5686
5850
  // src/confluence/renderer.ts
5687
- function escape(value) {
5851
+ function escape2(value) {
5688
5852
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
5689
5853
  }
5690
5854
  function infoPanel(text2) {
5691
- return `<ac:structured-macro ac:name="info"><ac:rich-text-body><p>${escape(text2)}</p></ac:rich-text-body></ac:structured-macro>`;
5855
+ return `<ac:structured-macro ac:name="info"><ac:rich-text-body><p>${escape2(text2)}</p></ac:rich-text-body></ac:structured-macro>`;
5692
5856
  }
5693
5857
  function featurePageTitle(prefix, name) {
5694
5858
  return `${prefix}${name}`;
5695
5859
  }
5696
5860
  function renderFeaturePage(options) {
5697
- const overviewBody = `<h2>What it does</h2><p>${escape(options.productDescription)}</p>`;
5861
+ const overviewBody = `<h2>What it does</h2><p>${escape2(options.productDescription)}</p>`;
5698
5862
  const flowsBody = options.flowDescriptions.length ? `<h2>How it fits in</h2><ul>` + options.flowDescriptions.map(
5699
- (f) => `<li><strong>${escape(f.name)}</strong> \u2014 ${escape(f.description)}</li>`
5863
+ (f) => `<li><strong>${escape2(f.name)}</strong> \u2014 ${escape2(f.description)}</li>`
5700
5864
  ).join("") + `</ul>` : "";
5701
- const navBody = `<p><ac:link><ri:page ri:content-title="${escape(options.indexPageTitle)}"/><ac:plain-text-link-body><![CDATA[Back to ${options.indexPageTitle}]]></ac:plain-text-link-body></ac:link></p>`;
5865
+ const navBody = `<p><ac:link><ri:page ri:content-title="${escape2(options.indexPageTitle)}"/><ac:plain-text-link-body><![CDATA[Back to ${options.indexPageTitle}]]></ac:plain-text-link-body></ac:link></p>`;
5702
5866
  const footer = infoPanel(
5703
5867
  `Generated from code by Mason. This page is overwritten on each sync \u2014 edit the code, not the page.`
5704
5868
  );
@@ -5714,7 +5878,7 @@ function renderIndexPage(options) {
5714
5878
  }
5715
5879
  const list = `<h2>Features</h2><ul>` + options.featureTitles.map((name) => {
5716
5880
  const pageTitle = featurePageTitle(options.featurePrefix, name);
5717
- return `<li><ac:link><ri:page ri:content-title="${escape(pageTitle)}"/><ac:plain-text-link-body><![CDATA[${name}]]></ac:plain-text-link-body></ac:link></li>`;
5881
+ return `<li><ac:link><ri:page ri:content-title="${escape2(pageTitle)}"/><ac:plain-text-link-body><![CDATA[${name}]]></ac:plain-text-link-body></ac:link></li>`;
5718
5882
  }).join("") + `</ul>`;
5719
5883
  const banner = infoPanel(
5720
5884
  `Generated from code by Mason. Maintained automatically \u2014 edit the code, not this page.`
@@ -5725,33 +5889,33 @@ function renderChangelogSection(section) {
5725
5889
  const segments = [];
5726
5890
  if (section.addedFeatures.length) {
5727
5891
  segments.push(
5728
- `<p><strong>Added features:</strong> ${section.addedFeatures.map(escape).join(", ")}</p>`
5892
+ `<p><strong>Added features:</strong> ${section.addedFeatures.map(escape2).join(", ")}</p>`
5729
5893
  );
5730
5894
  }
5731
5895
  if (section.removedFeatures.length) {
5732
5896
  segments.push(
5733
- `<p><strong>Removed features:</strong> ${section.removedFeatures.map(escape).join(", ")}</p>`
5897
+ `<p><strong>Removed features:</strong> ${section.removedFeatures.map(escape2).join(", ")}</p>`
5734
5898
  );
5735
5899
  }
5736
5900
  if (section.changedFeatures.length) {
5737
5901
  segments.push(
5738
- `<p><strong>Updated features:</strong> ${section.changedFeatures.map(escape).join(", ")}</p>`
5902
+ `<p><strong>Updated features:</strong> ${section.changedFeatures.map(escape2).join(", ")}</p>`
5739
5903
  );
5740
5904
  }
5741
5905
  if (section.addedFlows.length) {
5742
5906
  segments.push(
5743
- `<p><strong>Added flows:</strong> ${section.addedFlows.map(escape).join(", ")}</p>`
5907
+ `<p><strong>Added flows:</strong> ${section.addedFlows.map(escape2).join(", ")}</p>`
5744
5908
  );
5745
5909
  }
5746
5910
  if (section.removedFlows.length) {
5747
5911
  segments.push(
5748
- `<p><strong>Removed flows:</strong> ${section.removedFlows.map(escape).join(", ")}</p>`
5912
+ `<p><strong>Removed flows:</strong> ${section.removedFlows.map(escape2).join(", ")}</p>`
5749
5913
  );
5750
5914
  }
5751
5915
  if (segments.length === 0) {
5752
5916
  segments.push(`<p><em>No meaningful changes detected.</em></p>`);
5753
5917
  }
5754
- return `<h3>${escape(section.syncedAt)}</h3>` + segments.join("");
5918
+ return `<h3>${escape2(section.syncedAt)}</h3>` + segments.join("");
5755
5919
  }
5756
5920
  function renderChangelogPage(sections) {
5757
5921
  if (sections.length === 0) {
@@ -5777,16 +5941,16 @@ var init_renderer = __esm({
5777
5941
 
5778
5942
  // src/confluence/diff.ts
5779
5943
  import fs20 from "fs/promises";
5780
- import path26 from "path";
5944
+ import path27 from "path";
5781
5945
  import { createHash as createHash5 } from "crypto";
5782
5946
  function hashDescription(description) {
5783
5947
  return createHash5("sha256").update(description, "utf8").digest("hex");
5784
5948
  }
5785
5949
  function syncStateDir(rootDir) {
5786
- return path26.join(rootDir, ".mason");
5950
+ return path27.join(rootDir, ".mason");
5787
5951
  }
5788
5952
  function syncStatePath(rootDir) {
5789
- return path26.join(syncStateDir(rootDir), "confluence-sync.json");
5953
+ return path27.join(syncStateDir(rootDir), "confluence-sync.json");
5790
5954
  }
5791
5955
  async function loadSyncState(rootDir) {
5792
5956
  try {
@@ -6355,7 +6519,7 @@ import { z as z19 } from "zod";
6355
6519
  // src/mcp/tools.ts
6356
6520
  init_repair();
6357
6521
  import fs21 from "fs/promises";
6358
- import path27 from "path";
6522
+ import path28 from "path";
6359
6523
  import { execFile as execFile19 } from "child_process";
6360
6524
  import { promisify as promisify19 } from "util";
6361
6525
 
@@ -7250,7 +7414,7 @@ async function buildContext(dir) {
7250
7414
  };
7251
7415
  }
7252
7416
  async function analyzeProject(dir) {
7253
- const rootDir = path27.resolve(dir);
7417
+ const rootDir = path28.resolve(dir);
7254
7418
  const context = await buildContext(rootDir);
7255
7419
  const results = await runAll(context);
7256
7420
  const projectSnapshot = await detectProjectSnapshot(rootDir);
@@ -7305,7 +7469,7 @@ async function detectProjectSnapshot(rootDir) {
7305
7469
  const present = [];
7306
7470
  for (const file of buildFiles) {
7307
7471
  try {
7308
- await fs21.access(path27.join(rootDir, file));
7472
+ await fs21.access(path28.join(rootDir, file));
7309
7473
  present.push(file);
7310
7474
  } catch {
7311
7475
  }
@@ -7347,7 +7511,7 @@ async function detectProjectSnapshot(rootDir) {
7347
7511
  const sourceFiles = await access.list();
7348
7512
  const fileCounts = {};
7349
7513
  for (const file of sourceFiles) {
7350
- const ext = path27.extname(file).slice(1);
7514
+ const ext = path28.extname(file).slice(1);
7351
7515
  fileCounts[ext] = (fileCounts[ext] ?? 0) + 1;
7352
7516
  }
7353
7517
  return {
@@ -7358,7 +7522,7 @@ async function detectProjectSnapshot(rootDir) {
7358
7522
  };
7359
7523
  }
7360
7524
  async function getCodeSamples(dir, count3 = 15) {
7361
- const rootDir = path27.resolve(dir);
7525
+ const rootDir = path28.resolve(dir);
7362
7526
  const samples = await sampleFiles(rootDir, count3);
7363
7527
  const output = {
7364
7528
  note: "These are previews (first ~60 lines). Read the file directly with your own tools to see it in full.",
@@ -7401,7 +7565,7 @@ async function unmappedContextResponse(rootDir) {
7401
7565
  });
7402
7566
  }
7403
7567
  async function getProjectStructure(dir) {
7404
- const rootDir = path27.resolve(dir);
7568
+ const rootDir = path28.resolve(dir);
7405
7569
  const allFiles = await (await createFileAccess(rootDir)).list("**/*");
7406
7570
  const dirInfo = /* @__PURE__ */ new Map();
7407
7571
  for (const file of allFiles) {
@@ -7413,7 +7577,7 @@ async function getProjectStructure(dir) {
7413
7577
  }
7414
7578
  const info = dirInfo.get(dirPath);
7415
7579
  info.fileCount++;
7416
- const ext = path27.extname(file).slice(1);
7580
+ const ext = path28.extname(file).slice(1);
7417
7581
  if (ext) {
7418
7582
  info.extensions.set(ext, (info.extensions.get(ext) ?? 0) + 1);
7419
7583
  }
@@ -7458,7 +7622,7 @@ async function buildChangedFilePreviews(rootDir, changedFiles) {
7458
7622
  return previews2;
7459
7623
  }
7460
7624
  async function getSnapshot(dir) {
7461
- const rootDir = path27.resolve(dir);
7625
+ const rootDir = path28.resolve(dir);
7462
7626
  const snapshot = await loadSnapshot(rootDir);
7463
7627
  if (!snapshot) {
7464
7628
  return unmappedContextResponse(rootDir);
@@ -7559,7 +7723,7 @@ function driftHint(report) {
7559
7723
  return "Read the changed files under staleFeatures/staleFlows, update those entries (fold unmappedFiles into the right features), and call save_snapshot with only the affected entries \u2014 unchanged entries are preserved. Drop ghostFiles from any entries that reference them, and delete features/flows that no longer exist via save_snapshot's removeFeatures/removeFlows.";
7560
7724
  }
7561
7725
  async function checkDrift(dir) {
7562
- const rootDir = path27.resolve(dir);
7726
+ const rootDir = path28.resolve(dir);
7563
7727
  const report = await computeDrift(rootDir);
7564
7728
  if (!report) {
7565
7729
  return JSON.stringify({
@@ -7590,7 +7754,7 @@ async function checkDrift(dir) {
7590
7754
  return JSON.stringify({ exists: true, ...report, verification, hint });
7591
7755
  }
7592
7756
  async function generateSnapshotBatch(dir, offset = 0, batchSize = DEFAULT_BATCH_SIZE, files) {
7593
- const rootDir = path27.resolve(dir);
7757
+ const rootDir = path28.resolve(dir);
7594
7758
  const scoped = files !== void 0 && files.length > 0;
7595
7759
  const scopeFiles = scoped ? sanitizePaths(rootDir, files) : void 0;
7596
7760
  const batch = await prepareSnapshotBatch(dir, offset, batchSize, scopeFiles);
@@ -7637,7 +7801,7 @@ async function generateSnapshotBatch(dir, offset = 0, batchSize = DEFAULT_BATCH_
7637
7801
  );
7638
7802
  }
7639
7803
  async function saveSnapshotPartial(dir, batchId, offset, features, flows) {
7640
- const rootDir = path27.resolve(dir);
7804
+ const rootDir = path28.resolve(dir);
7641
7805
  for (const feat of Object.values(features)) {
7642
7806
  feat.files = sanitizePaths(rootDir, feat.files);
7643
7807
  if (feat.tests) feat.tests = sanitizePaths(rootDir, feat.tests);
@@ -7666,7 +7830,7 @@ async function saveSnapshotPartial(dir, batchId, offset, features, flows) {
7666
7830
  );
7667
7831
  }
7668
7832
  async function reduceSnapshot(dir) {
7669
- const rootDir = path27.resolve(dir);
7833
+ const rootDir = path28.resolve(dir);
7670
7834
  const partials = await loadAllPartials(rootDir);
7671
7835
  if (partials.length === 0) {
7672
7836
  return JSON.stringify(
@@ -7728,7 +7892,7 @@ async function reduceSnapshot(dir) {
7728
7892
  );
7729
7893
  }
7730
7894
  async function fullAnalysis(dir) {
7731
- const rootDir = path27.resolve(dir);
7895
+ const rootDir = path28.resolve(dir);
7732
7896
  const [analysis, structure, samples, testMap, snapshot] = await Promise.all([
7733
7897
  analyzeProject(dir),
7734
7898
  getProjectStructure(dir),
@@ -7757,7 +7921,7 @@ function sanitizePaths(rootDir, files) {
7757
7921
  return sanitizeRepoPaths(files);
7758
7922
  }
7759
7923
  async function saveSnapshotData(dir, features, flows, removeFeatures = [], removeFlows = []) {
7760
- const rootDir = path27.resolve(dir);
7924
+ const rootDir = path28.resolve(dir);
7761
7925
  const gitHash = await getCurrentGitHash(rootDir);
7762
7926
  const now = (/* @__PURE__ */ new Date()).toISOString();
7763
7927
  for (const feat of Object.values(features)) {
@@ -7841,7 +8005,7 @@ async function saveSnapshotData(dir, features, flows, removeFeatures = [], remov
7841
8005
  });
7842
8006
  }
7843
8007
  async function getImpact(dir, files) {
7844
- const rootDir = path27.resolve(dir);
8008
+ const rootDir = path28.resolve(dir);
7845
8009
  const { analyzeImpact: analyzeImpact2 } = await Promise.resolve().then(() => (init_impact(), impact_exports));
7846
8010
  const result = await analyzeImpact2(rootDir, files);
7847
8011
  return JSON.stringify(result, null, 2);
@@ -7850,7 +8014,7 @@ var VERIFY_DEFAULT_SAMPLE = 5;
7850
8014
  var VERIFY_MAX_FILES_PER_ENTRY = 8;
7851
8015
  var VERIFY_SKELETON_CHARS = 500;
7852
8016
  async function verifySnapshot(dir, sample = VERIFY_DEFAULT_SAMPLE) {
7853
- const rootDir = path27.resolve(dir);
8017
+ const rootDir = path28.resolve(dir);
7854
8018
  const snapshot = await loadSnapshot(rootDir);
7855
8019
  if (!snapshot) {
7856
8020
  return JSON.stringify({
@@ -7915,7 +8079,7 @@ async function verifySnapshot(dir, sample = VERIFY_DEFAULT_SAMPLE) {
7915
8079
  });
7916
8080
  }
7917
8081
  async function saveVerification(dir, verdicts) {
7918
- const rootDir = path27.resolve(dir);
8082
+ const rootDir = path28.resolve(dir);
7919
8083
  const snapshot = await loadSnapshot(rootDir);
7920
8084
  if (!snapshot) {
7921
8085
  return JSON.stringify({ exists: false, hint: "No concept map exists." });
@@ -7953,16 +8117,16 @@ async function saveVerification(dir, verdicts) {
7953
8117
  });
7954
8118
  }
7955
8119
  async function saveDecision(dir, input) {
7956
- const rootDir = path27.resolve(dir);
8120
+ const rootDir = path28.resolve(dir);
7957
8121
  const { upsertDecision: upsertDecision2 } = await Promise.resolve().then(() => (init_decisions(), decisions_exports));
7958
8122
  const result = await upsertDecision2(rootDir, input);
7959
8123
  return JSON.stringify(result);
7960
8124
  }
7961
8125
  async function reviewDecision2(dir, input) {
7962
- return JSON.stringify(await reviewDecision(path27.resolve(dir), input));
8126
+ return JSON.stringify(await reviewDecision(path28.resolve(dir), input));
7963
8127
  }
7964
8128
  async function getContext(dir, task, files) {
7965
- const rootDir = path27.resolve(dir);
8129
+ const rootDir = path28.resolve(dir);
7966
8130
  const { assembleContext: assembleContext2 } = await Promise.resolve().then(() => (init_assemble(), assemble_exports));
7967
8131
  const bundle = await assembleContext2(rootDir, task, files);
7968
8132
  return JSON.stringify(bundle);
@@ -8004,7 +8168,7 @@ async function masonInit(dir, options = {}) {
8004
8168
  return JSON.stringify(await setupProject2(dir, options), null, 2);
8005
8169
  }
8006
8170
  if (options.host) throw new Error("host applies only to mode: setup.");
8007
- const rootDir = path27.resolve(dir);
8171
+ const rootDir = path28.resolve(dir);
8008
8172
  const marker = await loadProjectMarker(rootDir);
8009
8173
  const { loadSetup: loadSetup2 } = await Promise.resolve().then(() => (init_model(), model_exports));
8010
8174
  const setup = await loadSetup2(rootDir);
@@ -8024,7 +8188,7 @@ async function masonInit(dir, options = {}) {
8024
8188
  );
8025
8189
  }
8026
8190
  async function masonCompleteInit(dir, options = {}) {
8027
- const rootDir = path27.resolve(dir);
8191
+ const rootDir = path28.resolve(dir);
8028
8192
  const existing = await loadProjectMarker(rootDir);
8029
8193
  const features = await loadProjectFeatures(rootDir);
8030
8194
  const marker = {
@@ -8133,7 +8297,7 @@ async function masonSetConfluence(input) {
8133
8297
  );
8134
8298
  }
8135
8299
  async function exportToConfluenceTool(dir, overrides) {
8136
- const rootDir = path27.resolve(dir);
8300
+ const rootDir = path28.resolve(dir);
8137
8301
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), config_exports2));
8138
8302
  const { exportToConfluence: exportToConfluence2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
8139
8303
  const config = await loadConfig2();
@@ -8171,7 +8335,7 @@ function createMcpServer() {
8171
8335
  const server = new McpServer(
8172
8336
  {
8173
8337
  name: "mason",
8174
- version: "0.16.0"
8338
+ version: "0.16.1"
8175
8339
  },
8176
8340
  {
8177
8341
  instructions: 'Mason retrieves recorded decisions, file impact, and optional feature/flow maps. Use get_context with the task and known files, and get_impact before editing. Save learned rationale and constraints with save_decision; review and commit the local records through the project workflow. These tools work without initialization or a concept map. Consult trust and diagnostics: changed or unknown freshness needs source inspection, failed verification needs correction, and proposals are suggestions and legacy records are unreviewed. Accepted decisions are recorded team constraints, still subject to freshness checks. Use review_decision to prepare code evidence and record only authorized acceptance, reaffirmation, or retirement. Never invent a reviewer or treat these recorded identities as authenticated approval. mason_init returns documentation audit and committed-diff review findings with a quickstart guide. Use mode: "map" only when a full architecture map is requested. A missing map is not a setup failure; use decisions and source evidence. get_snapshot provides architecture navigation when a map is available. The mason-audit and mason-review CLIs also work without setup.'
@@ -8508,7 +8672,7 @@ function createMcpServer() {
8508
8672
  );
8509
8673
  server.tool(
8510
8674
  "get_impact",
8511
- "Trace the impact of changing files: historical co-change partners, references, and related tests. Deterministic, read-only, and usable without initialization, saved decisions, or a concept map.",
8675
+ "Trace historical co-change partners, related tests, and references with evidence: resolved imports, explicit paths, or uncertain name candidates. Candidates are not proven dependencies. Deterministic and read-only; no initialization or concept map required.",
8512
8676
  {
8513
8677
  dir: z19.string().describe("Absolute path to the project root directory"),
8514
8678
  files: z19.array(z19.string()).describe("File paths or names to analyze (e.g., ['WeatherRepository.kt'] or ['src/services/auth.ts'])")