mason-context 0.3.1 → 0.3.3

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/bin/mason.js CHANGED
@@ -378,6 +378,8 @@ function spawnWithStdin(command, args, input) {
378
378
  stdio: ["pipe", "pipe", "pipe"],
379
379
  timeout: 3e5
380
380
  });
381
+ const onSigint = () => proc.kill("SIGINT");
382
+ process.on("SIGINT", onSigint);
381
383
  let stdout = "";
382
384
  let stderr = "";
383
385
  proc.stdout.on("data", (data) => {
@@ -387,25 +389,28 @@ function spawnWithStdin(command, args, input) {
387
389
  stderr += data.toString();
388
390
  });
389
391
  proc.on("close", (code) => {
392
+ process.off("SIGINT", onSigint);
390
393
  if (code === 0) {
391
394
  resolve(stdout.trim());
392
395
  } else {
393
396
  reject(new Error(`${command} exited with code ${code}: ${stderr}`));
394
397
  }
395
398
  });
396
- proc.on("error", reject);
399
+ proc.on("error", (err) => {
400
+ process.off("SIGINT", onSigint);
401
+ reject(err);
402
+ });
397
403
  proc.stdin.write(input);
398
404
  proc.stdin.end();
399
405
  });
400
406
  }
401
407
  async function callClaudeCLI(system, userMessage) {
402
- const prompt = `${system}
403
-
404
- ${userMessage}`;
405
- return spawnWithStdin("claude", ["-p"], prompt);
408
+ return spawnWithStdin("claude", ["-p", "--system-prompt", system], userMessage);
406
409
  }
407
410
  async function callGeminiCLI(system, userMessage) {
408
- const prompt = `${system}
411
+ const prompt = `<system>
412
+ ${system}
413
+ </system>
409
414
 
410
415
  ${userMessage}`;
411
416
  return spawnWithStdin("gemini", ["-p", ""], prompt);
@@ -1088,6 +1093,7 @@ import fs4 from "fs/promises";
1088
1093
  import path4 from "path";
1089
1094
  import { execFile as execFile6 } from "child_process";
1090
1095
  import { promisify as promisify6 } from "util";
1096
+ import fg4 from "fast-glob";
1091
1097
  function snapshotDir(rootDir) {
1092
1098
  return path4.join(rootDir, ".mason");
1093
1099
  }
@@ -1151,7 +1157,31 @@ function parseSnapshotResponse(raw) {
1151
1157
  }
1152
1158
  async function createSnapshot(rootDir, config) {
1153
1159
  const resolvedRoot = path4.resolve(rootDir);
1154
- const sampled = await sampleFiles(resolvedRoot, 25);
1160
+ const allFiles = await fg4(
1161
+ "**/*.{ts,tsx,js,jsx,kt,kts,java,py,go,rs,swift,rb,cs,cpp,c,dart}",
1162
+ {
1163
+ cwd: resolvedRoot,
1164
+ ignore: [
1165
+ "**/node_modules/**",
1166
+ "**/dist/**",
1167
+ "**/build/**",
1168
+ "**/.gradle/**",
1169
+ "**/target/**",
1170
+ "**/.git/**",
1171
+ "**/vendor/**",
1172
+ "**/__pycache__/**",
1173
+ "**/venv/**",
1174
+ "**/.venv/**",
1175
+ "**/*.min.*",
1176
+ "**/*.map",
1177
+ "**/generated/**",
1178
+ "**/R.java",
1179
+ "**/BuildConfig.java"
1180
+ ]
1181
+ }
1182
+ );
1183
+ const sampleCount = Math.min(80, Math.max(20, Math.round(allFiles.length * 0.15)));
1184
+ const sampled = await sampleFiles(resolvedRoot, sampleCount);
1155
1185
  const filesWithContent = [];
1156
1186
  for (const sample of sampled) {
1157
1187
  const full = await readFullFile(resolvedRoot, sample.path);
@@ -1318,7 +1348,7 @@ import fs5 from "fs/promises";
1318
1348
  import path5 from "path";
1319
1349
  import { execFile as execFile7 } from "child_process";
1320
1350
  import { promisify as promisify7 } from "util";
1321
- import fg4 from "fast-glob";
1351
+ import fg5 from "fast-glob";
1322
1352
  async function analyzeImpact(rootDir, targetFiles) {
1323
1353
  const resolvedRoot = path5.resolve(rootDir);
1324
1354
  const resolvedTargets = await resolveTargetFiles(resolvedRoot, targetFiles);
@@ -1341,7 +1371,7 @@ async function resolveTargetFiles(rootDir, targets) {
1341
1371
  resolved.push(target);
1342
1372
  continue;
1343
1373
  }
1344
- const matches = await fg4(`**/${target}`, {
1374
+ const matches = await fg5(`**/${target}`, {
1345
1375
  cwd: rootDir,
1346
1376
  ignore: IGNORE2
1347
1377
  });
@@ -1349,7 +1379,7 @@ async function resolveTargetFiles(rootDir, targets) {
1349
1379
  resolved.push(matches[0]);
1350
1380
  } else {
1351
1381
  const noExt = target.replace(/\.[^.]+$/, "");
1352
- const extMatches = await fg4(`**/${noExt}.*`, {
1382
+ const extMatches = await fg5(`**/${noExt}.*`, {
1353
1383
  cwd: rootDir,
1354
1384
  ignore: IGNORE2
1355
1385
  });
@@ -1406,7 +1436,7 @@ async function getReferences(rootDir, targetFiles) {
1406
1436
  const basename = path5.basename(target).replace(/\.[^.]+$/, "");
1407
1437
  searchNames.add(basename);
1408
1438
  }
1409
- const allSourceFiles = await fg4(`**/${SOURCE_EXTENSIONS2}`, {
1439
+ const allSourceFiles = await fg5(`**/${SOURCE_EXTENSIONS2}`, {
1410
1440
  cwd: rootDir,
1411
1441
  ignore: IGNORE2
1412
1442
  });
@@ -1455,7 +1485,7 @@ async function getRelatedTests(rootDir, targetFiles) {
1455
1485
  "**/*Test.swift",
1456
1486
  "**/*_test.rs"
1457
1487
  ];
1458
- const testFiles = await fg4(testPatterns, { cwd: rootDir, ignore: IGNORE2 });
1488
+ const testFiles = await fg5(testPatterns, { cwd: rootDir, ignore: IGNORE2 });
1459
1489
  const results = [];
1460
1490
  for (const target of targetFiles) {
1461
1491
  const targetBaseName = path5.basename(target).replace(/\.[^.]+$/, "");
@@ -1502,7 +1532,7 @@ import fs6 from "fs/promises";
1502
1532
  import path6 from "path";
1503
1533
  import { execFile as execFile8 } from "child_process";
1504
1534
  import { promisify as promisify8 } from "util";
1505
- import fg5 from "fast-glob";
1535
+ import fg6 from "fast-glob";
1506
1536
  async function buildContext(dir) {
1507
1537
  return {
1508
1538
  rootDir: dir,
@@ -1582,7 +1612,7 @@ async function detectProjectSnapshot(rootDir) {
1582
1612
  ];
1583
1613
  const testInfo = {};
1584
1614
  for (const pattern of testDirs) {
1585
- const files = await fg5(`${pattern}/**/*`, {
1615
+ const files = await fg6(`${pattern}/**/*`, {
1586
1616
  cwd: rootDir,
1587
1617
  ignore: IGNORE3,
1588
1618
  onlyFiles: true
@@ -1602,12 +1632,12 @@ async function detectProjectSnapshot(rootDir) {
1602
1632
  { pattern: "**/*_test.rs", label: "*_test.rs" }
1603
1633
  ];
1604
1634
  for (const { pattern, label } of testFilePatterns) {
1605
- const files = await fg5(pattern, { cwd: rootDir, ignore: IGNORE3 });
1635
+ const files = await fg6(pattern, { cwd: rootDir, ignore: IGNORE3 });
1606
1636
  if (files.length > 0) {
1607
1637
  testInfo[label] = files.length;
1608
1638
  }
1609
1639
  }
1610
- const sourceFiles = await fg5("**/*.{ts,tsx,js,jsx,kt,kts,java,py,go,rs,swift,rb,cs,cpp,c,dart}", {
1640
+ const sourceFiles = await fg6("**/*.{ts,tsx,js,jsx,kt,kts,java,py,go,rs,swift,rb,cs,cpp,c,dart}", {
1611
1641
  cwd: rootDir,
1612
1642
  ignore: IGNORE3
1613
1643
  });
@@ -1640,7 +1670,7 @@ async function getCodeSamples(dir, count = 15) {
1640
1670
  }
1641
1671
  async function getProjectStructure(dir) {
1642
1672
  const rootDir = path6.resolve(dir);
1643
- const allFiles = await fg5("**/*", {
1673
+ const allFiles = await fg6("**/*", {
1644
1674
  cwd: rootDir,
1645
1675
  ignore: IGNORE3,
1646
1676
  onlyFiles: true
@@ -1823,7 +1853,7 @@ function createMcpServer() {
1823
1853
  const server = new McpServer(
1824
1854
  {
1825
1855
  name: "mason",
1826
- version: "0.3.1"
1856
+ version: "0.3.3"
1827
1857
  },
1828
1858
  {
1829
1859
  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."
@@ -2008,7 +2038,7 @@ function createCLI() {
2008
2038
  const program2 = new Command();
2009
2039
  program2.name("mason").description(
2010
2040
  "Context engineering CLI & MCP server \u2014 generates intelligent CLAUDE.md files"
2011
- ).version("0.3.1");
2041
+ ).version("0.3.3");
2012
2042
  program2.command("setup").description("Register Mason as an MCP server with Claude Code").option("--scope <scope>", "Config scope: user or project", "user").action(async (opts) => {
2013
2043
  const { execFile: execFile9 } = await import("child_process");
2014
2044
  const { promisify: promisify9 } = await import("util");
@@ -2090,7 +2120,7 @@ function createCLI() {
2090
2120
  }
2091
2121
  const rootDir = path7.resolve(dir);
2092
2122
  const runConfig = opts.model ? { ...config, model: opts.model } : config;
2093
- const spinner = ora("Analyzing codebase...").start();
2123
+ const spinner = ora({ discardStdin: false, text: "Analyzing codebase..." }).start();
2094
2124
  const analysisData = await fullAnalysis(rootDir);
2095
2125
  spinner.text = `Generating CLAUDE.md with ${runConfig.provider}...`;
2096
2126
  try {
@@ -2125,7 +2155,7 @@ ${analysisData}`
2125
2155
  success(`Generated ${outPath}`);
2126
2156
  try {
2127
2157
  const { createSnapshot: createSnapshot2 } = await Promise.resolve().then(() => (init_snapshot(), snapshot_exports));
2128
- const spinner2 = ora("Building concept map...").start();
2158
+ const spinner2 = ora({ discardStdin: false, text: "Building concept map..." }).start();
2129
2159
  const snapshot = await createSnapshot2(rootDir, runConfig);
2130
2160
  spinner2.stop();
2131
2161
  const featureCount = Object.keys(snapshot.features).length;
@@ -2184,7 +2214,7 @@ ${analysisData}`
2184
2214
  );
2185
2215
  process.exit(1);
2186
2216
  }
2187
- const spinner = ora("Building project snapshot...").start();
2217
+ const spinner = ora({ discardStdin: false, text: "Building project snapshot..." }).start();
2188
2218
  try {
2189
2219
  const snapshot = await createSnapshot2(rootDir, config);
2190
2220
  spinner.stop();
@@ -2218,7 +2248,7 @@ ${analysisData}`
2218
2248
  program2.command("impact").description("Show files affected by changes to a given file").argument("<files...>", "File paths or names to analyze").option("-d, --dir <dir>", "Project directory", ".").action(async (files, opts) => {
2219
2249
  const { analyzeImpact: analyzeImpact2 } = await Promise.resolve().then(() => (init_impact(), impact_exports));
2220
2250
  const rootDir = path7.resolve(opts.dir);
2221
- const spinner = ora("Analyzing impact...").start();
2251
+ const spinner = ora({ discardStdin: false, text: "Analyzing impact..." }).start();
2222
2252
  const result = await analyzeImpact2(rootDir, files);
2223
2253
  spinner.stop();
2224
2254
  console.log(chalk2.bold(`
@@ -2255,7 +2285,7 @@ Impact analysis for: ${result.targetFiles.join(", ")}
2255
2285
  });
2256
2286
  program2.command("analyze").description("Analyze the codebase and print findings").argument("[dir]", "Directory to analyze", ".").action(async (dir) => {
2257
2287
  const rootDir = path7.resolve(dir);
2258
- const spinner = ora("Analyzing codebase...").start();
2288
+ const spinner = ora({ discardStdin: false, text: "Analyzing codebase..." }).start();
2259
2289
  const context = await buildContext2(rootDir);
2260
2290
  const results = await runAll(context);
2261
2291
  spinner.stop();