mason-context 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -114,6 +114,14 @@ mason snapshot # create/update concept map
114
114
 
115
115
  Most providers work without an API key — `claude`, `gemini`, and `ollama` all use their respective CLIs directly.
116
116
 
117
+ ## Security
118
+
119
+ **What the snapshot contains:** Feature names, relative file paths, and flow descriptions. No source code, secrets, or business logic.
120
+
121
+ **What it doesn't touch:** Mason respects `.gitignore` (via `git ls-files`) and has a deny-list that blocks `.env`, `.pem`, `.key`, credentials, and other sensitive files from being sampled. Path traversal protection ensures all file access stays within the project root.
122
+
123
+ **LLM data flow:** When generating a snapshot via CLI (`mason generate`, `mason snapshot`), Mason sends sampled file contents to your configured LLM provider. If you're using `claude`, `openai`, or `gemini`, this means source code is sent to an external API. Use `ollama` for fully local generation. The MCP server tools (`get_snapshot`, `get_impact`, etc.) do not send data to any LLM — they only read local files.
124
+
117
125
  ## Language support
118
126
 
119
127
  Mason is completely language-agnostic. It uses file naming patterns and git history rather than language-specific parsing, so it works with any project that has source files and a git repository — TypeScript, Kotlin, Python, Go, Rust, Swift, Java, C#, Dart, and more.
@@ -686,10 +686,22 @@ async function loadProjectConfig(rootDir) {
686
686
  return {};
687
687
  }
688
688
  }
689
+ async function getTrackedFiles(rootDir) {
690
+ try {
691
+ const { stdout } = await exec3("git", ["ls-files", "--cached", "--others", "--exclude-standard"], {
692
+ cwd: rootDir,
693
+ maxBuffer: 1e7
694
+ });
695
+ return new Set(stdout.trim().split("\n").filter(Boolean));
696
+ } catch {
697
+ return null;
698
+ }
699
+ }
689
700
  async function sampleFiles(rootDir, maxFiles = 25) {
690
701
  const selected = /* @__PURE__ */ new Map();
691
702
  const projectConfig = await loadProjectConfig(rootDir);
692
703
  const ignorePatterns = [...IGNORE_PATTERNS, ...projectConfig.ignore ?? []];
704
+ const trackedFiles = await getTrackedFiles(rootDir);
693
705
  for (const filePath of projectConfig.alwaysInclude ?? []) {
694
706
  if (selected.size >= maxFiles) break;
695
707
  const resolvedPath = path.resolve(rootDir, filePath);
@@ -868,6 +880,7 @@ async function sampleFiles(rootDir, maxFiles = 25) {
868
880
  const fullPath = path.resolve(rootDir, filePath);
869
881
  if (!fullPath.startsWith(path.resolve(rootDir))) continue;
870
882
  if (isSensitiveFile(filePath)) continue;
883
+ if (trackedFiles && !trackedFiles.has(filePath)) continue;
871
884
  const stat = await fs2.stat(fullPath);
872
885
  if (stat.size > 1e5) continue;
873
886
  const content = await fs2.readFile(fullPath, "utf-8");
@@ -1226,10 +1239,23 @@ async function fullAnalysis(dir) {
1226
1239
  }
1227
1240
  return JSON.stringify(output, null, 2);
1228
1241
  }
1242
+ function sanitizePaths(rootDir, files) {
1243
+ return files.filter((f) => {
1244
+ const resolved = path6.resolve(rootDir, f);
1245
+ return resolved.startsWith(rootDir) && !f.startsWith("/") && !f.includes("..");
1246
+ });
1247
+ }
1229
1248
  async function saveSnapshotData(dir, features, flows) {
1230
1249
  const rootDir = path6.resolve(dir);
1231
1250
  const gitHash = await getCurrentGitHash(rootDir);
1232
1251
  const now = (/* @__PURE__ */ new Date()).toISOString();
1252
+ for (const feat of Object.values(features)) {
1253
+ feat.files = sanitizePaths(rootDir, feat.files);
1254
+ if (feat.tests) feat.tests = sanitizePaths(rootDir, feat.tests);
1255
+ }
1256
+ for (const flow of Object.values(flows)) {
1257
+ flow.chain = sanitizePaths(rootDir, flow.chain);
1258
+ }
1233
1259
  const existing = await loadSnapshot(rootDir);
1234
1260
  if (existing) {
1235
1261
  existing.features = { ...existing.features, ...features };
@@ -1270,7 +1296,7 @@ function createMcpServer() {
1270
1296
  const server = new McpServer(
1271
1297
  {
1272
1298
  name: "mason",
1273
- version: "0.3.3"
1299
+ version: "0.3.5"
1274
1300
  },
1275
1301
  {
1276
1302
  instructions: "Mason is a context engineering tool. Always call get_snapshot before using Explore agents, Glob, or Grep to understand the codebase. The snapshot is a concept map that maps features and flows to their implementing files \u2014 it eliminates the need to search. This applies to ANY question about architecture, features, flows, how things work, cross-feature interactions, or bug investigation. Workflow: 1) Call get_snapshot first. 2) If no snapshot, call full_analysis and then save_snapshot to create one. 3) If the snapshot is stale, tell the user and offer to update it. 4) Use your native file reading tool to read files the snapshot points to. 5) Before modifying a file, call get_impact to check what else might be affected. 6) After making significant changes (new features, refactors, architecture changes), call save_snapshot to update the concept map."