mason-context 0.3.2 → 0.3.4

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/src/cli.js CHANGED
@@ -405,13 +405,12 @@ function spawnWithStdin(command, args, input) {
405
405
  });
406
406
  }
407
407
  async function callClaudeCLI(system, userMessage) {
408
- const prompt = `${system}
409
-
410
- ${userMessage}`;
411
- return spawnWithStdin("claude", ["-p"], prompt);
408
+ return spawnWithStdin("claude", ["-p", "--system-prompt", system], userMessage);
412
409
  }
413
410
  async function callGeminiCLI(system, userMessage) {
414
- const prompt = `${system}
411
+ const prompt = `<system>
412
+ ${system}
413
+ </system>
415
414
 
416
415
  ${userMessage}`;
417
416
  return spawnWithStdin("gemini", ["-p", ""], prompt);
@@ -519,10 +518,22 @@ async function loadProjectConfig(rootDir) {
519
518
  return {};
520
519
  }
521
520
  }
521
+ async function getTrackedFiles(rootDir) {
522
+ try {
523
+ const { stdout } = await exec5("git", ["ls-files", "--cached", "--others", "--exclude-standard"], {
524
+ cwd: rootDir,
525
+ maxBuffer: 1e7
526
+ });
527
+ return new Set(stdout.trim().split("\n").filter(Boolean));
528
+ } catch {
529
+ return null;
530
+ }
531
+ }
522
532
  async function sampleFiles(rootDir, maxFiles = 25) {
523
533
  const selected = /* @__PURE__ */ new Map();
524
534
  const projectConfig = await loadProjectConfig(rootDir);
525
535
  const ignorePatterns = [...IGNORE_PATTERNS, ...projectConfig.ignore ?? []];
536
+ const trackedFiles = await getTrackedFiles(rootDir);
526
537
  for (const filePath of projectConfig.alwaysInclude ?? []) {
527
538
  if (selected.size >= maxFiles) break;
528
539
  const resolvedPath = path2.resolve(rootDir, filePath);
@@ -701,6 +712,7 @@ async function sampleFiles(rootDir, maxFiles = 25) {
701
712
  const fullPath = path2.resolve(rootDir, filePath);
702
713
  if (!fullPath.startsWith(path2.resolve(rootDir))) continue;
703
714
  if (isSensitiveFile(filePath)) continue;
715
+ if (trackedFiles && !trackedFiles.has(filePath)) continue;
704
716
  const stat = await fs3.stat(fullPath);
705
717
  if (stat.size > 1e5) continue;
706
718
  const content = await fs3.readFile(fullPath, "utf-8");
@@ -1094,6 +1106,7 @@ import fs4 from "fs/promises";
1094
1106
  import path4 from "path";
1095
1107
  import { execFile as execFile6 } from "child_process";
1096
1108
  import { promisify as promisify6 } from "util";
1109
+ import fg4 from "fast-glob";
1097
1110
  function snapshotDir(rootDir) {
1098
1111
  return path4.join(rootDir, ".mason");
1099
1112
  }
@@ -1157,7 +1170,31 @@ function parseSnapshotResponse(raw) {
1157
1170
  }
1158
1171
  async function createSnapshot(rootDir, config) {
1159
1172
  const resolvedRoot = path4.resolve(rootDir);
1160
- const sampled = await sampleFiles(resolvedRoot, 25);
1173
+ const allFiles = await fg4(
1174
+ "**/*.{ts,tsx,js,jsx,kt,kts,java,py,go,rs,swift,rb,cs,cpp,c,dart}",
1175
+ {
1176
+ cwd: resolvedRoot,
1177
+ ignore: [
1178
+ "**/node_modules/**",
1179
+ "**/dist/**",
1180
+ "**/build/**",
1181
+ "**/.gradle/**",
1182
+ "**/target/**",
1183
+ "**/.git/**",
1184
+ "**/vendor/**",
1185
+ "**/__pycache__/**",
1186
+ "**/venv/**",
1187
+ "**/.venv/**",
1188
+ "**/*.min.*",
1189
+ "**/*.map",
1190
+ "**/generated/**",
1191
+ "**/R.java",
1192
+ "**/BuildConfig.java"
1193
+ ]
1194
+ }
1195
+ );
1196
+ const sampleCount = Math.min(80, Math.max(20, Math.round(allFiles.length * 0.15)));
1197
+ const sampled = await sampleFiles(resolvedRoot, sampleCount);
1161
1198
  const filesWithContent = [];
1162
1199
  for (const sample of sampled) {
1163
1200
  const full = await readFullFile(resolvedRoot, sample.path);
@@ -1324,7 +1361,7 @@ import fs5 from "fs/promises";
1324
1361
  import path5 from "path";
1325
1362
  import { execFile as execFile7 } from "child_process";
1326
1363
  import { promisify as promisify7 } from "util";
1327
- import fg4 from "fast-glob";
1364
+ import fg5 from "fast-glob";
1328
1365
  async function analyzeImpact(rootDir, targetFiles) {
1329
1366
  const resolvedRoot = path5.resolve(rootDir);
1330
1367
  const resolvedTargets = await resolveTargetFiles(resolvedRoot, targetFiles);
@@ -1347,7 +1384,7 @@ async function resolveTargetFiles(rootDir, targets) {
1347
1384
  resolved.push(target);
1348
1385
  continue;
1349
1386
  }
1350
- const matches = await fg4(`**/${target}`, {
1387
+ const matches = await fg5(`**/${target}`, {
1351
1388
  cwd: rootDir,
1352
1389
  ignore: IGNORE2
1353
1390
  });
@@ -1355,7 +1392,7 @@ async function resolveTargetFiles(rootDir, targets) {
1355
1392
  resolved.push(matches[0]);
1356
1393
  } else {
1357
1394
  const noExt = target.replace(/\.[^.]+$/, "");
1358
- const extMatches = await fg4(`**/${noExt}.*`, {
1395
+ const extMatches = await fg5(`**/${noExt}.*`, {
1359
1396
  cwd: rootDir,
1360
1397
  ignore: IGNORE2
1361
1398
  });
@@ -1412,7 +1449,7 @@ async function getReferences(rootDir, targetFiles) {
1412
1449
  const basename = path5.basename(target).replace(/\.[^.]+$/, "");
1413
1450
  searchNames.add(basename);
1414
1451
  }
1415
- const allSourceFiles = await fg4(`**/${SOURCE_EXTENSIONS2}`, {
1452
+ const allSourceFiles = await fg5(`**/${SOURCE_EXTENSIONS2}`, {
1416
1453
  cwd: rootDir,
1417
1454
  ignore: IGNORE2
1418
1455
  });
@@ -1461,7 +1498,7 @@ async function getRelatedTests(rootDir, targetFiles) {
1461
1498
  "**/*Test.swift",
1462
1499
  "**/*_test.rs"
1463
1500
  ];
1464
- const testFiles = await fg4(testPatterns, { cwd: rootDir, ignore: IGNORE2 });
1501
+ const testFiles = await fg5(testPatterns, { cwd: rootDir, ignore: IGNORE2 });
1465
1502
  const results = [];
1466
1503
  for (const target of targetFiles) {
1467
1504
  const targetBaseName = path5.basename(target).replace(/\.[^.]+$/, "");
@@ -1508,7 +1545,7 @@ import fs6 from "fs/promises";
1508
1545
  import path6 from "path";
1509
1546
  import { execFile as execFile8 } from "child_process";
1510
1547
  import { promisify as promisify8 } from "util";
1511
- import fg5 from "fast-glob";
1548
+ import fg6 from "fast-glob";
1512
1549
  async function buildContext(dir) {
1513
1550
  return {
1514
1551
  rootDir: dir,
@@ -1588,7 +1625,7 @@ async function detectProjectSnapshot(rootDir) {
1588
1625
  ];
1589
1626
  const testInfo = {};
1590
1627
  for (const pattern of testDirs) {
1591
- const files = await fg5(`${pattern}/**/*`, {
1628
+ const files = await fg6(`${pattern}/**/*`, {
1592
1629
  cwd: rootDir,
1593
1630
  ignore: IGNORE3,
1594
1631
  onlyFiles: true
@@ -1608,12 +1645,12 @@ async function detectProjectSnapshot(rootDir) {
1608
1645
  { pattern: "**/*_test.rs", label: "*_test.rs" }
1609
1646
  ];
1610
1647
  for (const { pattern, label } of testFilePatterns) {
1611
- const files = await fg5(pattern, { cwd: rootDir, ignore: IGNORE3 });
1648
+ const files = await fg6(pattern, { cwd: rootDir, ignore: IGNORE3 });
1612
1649
  if (files.length > 0) {
1613
1650
  testInfo[label] = files.length;
1614
1651
  }
1615
1652
  }
1616
- const sourceFiles = await fg5("**/*.{ts,tsx,js,jsx,kt,kts,java,py,go,rs,swift,rb,cs,cpp,c,dart}", {
1653
+ const sourceFiles = await fg6("**/*.{ts,tsx,js,jsx,kt,kts,java,py,go,rs,swift,rb,cs,cpp,c,dart}", {
1617
1654
  cwd: rootDir,
1618
1655
  ignore: IGNORE3
1619
1656
  });
@@ -1646,7 +1683,7 @@ async function getCodeSamples(dir, count = 15) {
1646
1683
  }
1647
1684
  async function getProjectStructure(dir) {
1648
1685
  const rootDir = path6.resolve(dir);
1649
- const allFiles = await fg5("**/*", {
1686
+ const allFiles = await fg6("**/*", {
1650
1687
  cwd: rootDir,
1651
1688
  ignore: IGNORE3,
1652
1689
  onlyFiles: true
@@ -1829,7 +1866,7 @@ function createMcpServer() {
1829
1866
  const server = new McpServer(
1830
1867
  {
1831
1868
  name: "mason",
1832
- version: "0.3.2"
1869
+ version: "0.3.4"
1833
1870
  },
1834
1871
  {
1835
1872
  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."
@@ -2014,7 +2051,7 @@ function createCLI() {
2014
2051
  const program = new Command();
2015
2052
  program.name("mason").description(
2016
2053
  "Context engineering CLI & MCP server \u2014 generates intelligent CLAUDE.md files"
2017
- ).version("0.3.2");
2054
+ ).version("0.3.4");
2018
2055
  program.command("setup").description("Register Mason as an MCP server with Claude Code").option("--scope <scope>", "Config scope: user or project", "user").action(async (opts) => {
2019
2056
  const { execFile: execFile9 } = await import("child_process");
2020
2057
  const { promisify: promisify9 } = await import("util");