mason-context 0.6.0 → 0.8.0
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 +58 -1
- package/dist/mason-audit.js +1366 -0
- package/dist/mason-audit.js.map +1 -0
- package/dist/mason-drift.js.map +1 -1
- package/dist/mason-hook.js +332 -0
- package/dist/mason-hook.js.map +1 -0
- package/dist/mason-mcp.js +37 -22
- package/dist/mason-mcp.js.map +1 -1
- package/package.json +4 -2
package/dist/mason-mcp.js
CHANGED
|
@@ -1269,6 +1269,7 @@ async function getReferences(rootDir, targetFiles) {
|
|
|
1269
1269
|
const targetSet = new Set(targetFiles);
|
|
1270
1270
|
const filesToSearch = allSourceFiles.filter((f) => !targetSet.has(f));
|
|
1271
1271
|
const results = /* @__PURE__ */ new Map();
|
|
1272
|
+
const importLine = /^\s*(import\b|from\b.*\bimport\b|const\b.*=\s*require\(|use\b|#include\b|require\s*\()/;
|
|
1272
1273
|
const batchSize = 50;
|
|
1273
1274
|
for (let i = 0; i < filesToSearch.length; i += batchSize) {
|
|
1274
1275
|
const batch = filesToSearch.slice(i, i + batchSize);
|
|
@@ -1279,11 +1280,17 @@ async function getReferences(rootDir, targetFiles) {
|
|
|
1279
1280
|
path9.join(rootDir, file),
|
|
1280
1281
|
"utf-8"
|
|
1281
1282
|
);
|
|
1283
|
+
const lines = content.split("\n");
|
|
1282
1284
|
for (const name of searchNames) {
|
|
1283
1285
|
const regex = new RegExp(`\\b${escapeRegex(name)}\\b`);
|
|
1284
|
-
if (regex.test(content))
|
|
1285
|
-
|
|
1286
|
-
results.
|
|
1286
|
+
if (!regex.test(content)) continue;
|
|
1287
|
+
if (!results.has(file)) {
|
|
1288
|
+
results.set(file, { matches: /* @__PURE__ */ new Set(), isImport: false });
|
|
1289
|
+
}
|
|
1290
|
+
const entry = results.get(file);
|
|
1291
|
+
entry.matches.add(name);
|
|
1292
|
+
if (!entry.isImport && lines.some((l) => regex.test(l) && importLine.test(l))) {
|
|
1293
|
+
entry.isImport = true;
|
|
1287
1294
|
}
|
|
1288
1295
|
}
|
|
1289
1296
|
} catch {
|
|
@@ -1291,10 +1298,14 @@ async function getReferences(rootDir, targetFiles) {
|
|
|
1291
1298
|
})
|
|
1292
1299
|
);
|
|
1293
1300
|
}
|
|
1294
|
-
return [...results.entries()].map(([file, matches]) => ({
|
|
1301
|
+
return [...results.entries()].map(([file, { matches, isImport }]) => ({
|
|
1295
1302
|
file,
|
|
1296
|
-
matches: [...matches]
|
|
1297
|
-
|
|
1303
|
+
matches: [...matches],
|
|
1304
|
+
kind: isImport ? "import" : "mention"
|
|
1305
|
+
})).sort((a, b) => {
|
|
1306
|
+
if (a.kind !== b.kind) return a.kind === "import" ? -1 : 1;
|
|
1307
|
+
return b.matches.length - a.matches.length;
|
|
1308
|
+
});
|
|
1298
1309
|
}
|
|
1299
1310
|
async function getRelatedTests(rootDir, targetFiles) {
|
|
1300
1311
|
const testPatterns = [
|
|
@@ -2320,13 +2331,13 @@ async function exportToConfluence(rootDir, config, options = {}, deps) {
|
|
|
2320
2331
|
const confluence = config.confluence;
|
|
2321
2332
|
if (!confluence) {
|
|
2322
2333
|
throw new Error(
|
|
2323
|
-
|
|
2334
|
+
"No Confluence credentials configured. Ask your assistant to call mason_set_confluence first."
|
|
2324
2335
|
);
|
|
2325
2336
|
}
|
|
2326
2337
|
const snapshot = await loadSnapshot(rootDir);
|
|
2327
2338
|
if (!snapshot) {
|
|
2328
2339
|
throw new Error(
|
|
2329
|
-
|
|
2340
|
+
"No snapshot found. Build the concept map first (ask your assistant to run mason_init and follow the playbook)."
|
|
2330
2341
|
);
|
|
2331
2342
|
}
|
|
2332
2343
|
const client = deps?.client ?? createConfluenceClient(confluence);
|
|
@@ -2931,15 +2942,15 @@ function uninitializedResponse(action) {
|
|
|
2931
2942
|
var CLAUDE_MD_SECTION = `<!-- mason:start -->
|
|
2932
2943
|
## Mason concept map
|
|
2933
2944
|
|
|
2934
|
-
This project has a Mason concept map (\`.mason/snapshot.json\`) served over MCP. Use
|
|
2945
|
+
This project has a Mason concept map (\`.mason/snapshot.json\`) and decision store (\`.mason/decisions/\`) served over MCP. Use them BEFORE grep, glob, or file reads:
|
|
2935
2946
|
|
|
2936
|
-
-
|
|
2937
|
-
-
|
|
2938
|
-
- Before editing any file \u2192
|
|
2939
|
-
- Learned something the code
|
|
2940
|
-
- Decisions returned by \`get_context\` are constraints \u2014 follow them
|
|
2947
|
+
- Task, bug, or change request \u2192 \`get_context\` with the task text: relevant features, files, tests, blast radius, freshness, and decisions in one call.
|
|
2948
|
+
- "How does X work / where is Y" \u2192 \`get_snapshot\` first.
|
|
2949
|
+
- Before editing any file \u2192 \`get_impact\`.
|
|
2950
|
+
- Learned something the code can't tell you (a failed approach, a deprecation, a workaround's reason, a review-settled convention) \u2192 record it with \`save_decision\`. Never record code-derivable facts, session trivia, or secrets.
|
|
2951
|
+
- Decisions returned by \`get_context\` are constraints \u2014 follow them; verify any marked stale before relying on it.
|
|
2941
2952
|
|
|
2942
|
-
Fall back to manual exploration only for
|
|
2953
|
+
Fall back to manual exploration only for what the map doesn't answer.
|
|
2943
2954
|
<!-- mason:end -->`;
|
|
2944
2955
|
var SETUP_PLAYBOOK = `You are walking the user through one-time Mason setup for this project. Mason persists a concept map of this codebase so future questions don't re-explore from scratch. The map is built via a Map-Reduce pattern so it covers the WHOLE codebase, not just a sample. Surface each question to the user in plain language and wait for their answer before proceeding.
|
|
2945
2956
|
|
|
@@ -2994,9 +3005,13 @@ If the credentials are rejected with a 401/403 the tool returns a friendly error
|
|
|
2994
3005
|
PHASE 4 \u2014 Assistant instructions (recommended)
|
|
2995
3006
|
Goal: make sure future assistant sessions actually use the map instead of re-exploring.
|
|
2996
3007
|
|
|
2997
|
-
Tell the user: "Assistants reliably follow project
|
|
3008
|
+
Tell the user: "Assistants reliably follow project instruction files but often ignore available tools. Mason works best if I add a short section to this project's instruction file telling assistants to consult the concept map first. Add it?"
|
|
2998
3009
|
On no: skip to Phase 5.
|
|
2999
|
-
On yes
|
|
3010
|
+
On yes, pick the target file by what the project already uses:
|
|
3011
|
+
- \`AGENTS.md\` exists \u2192 put the section there (it's the tool-agnostic standard). If a \`CLAUDE.md\` also exists and doesn't reference AGENTS.md, add a one-line pointer to it.
|
|
3012
|
+
- only \`CLAUDE.md\` (or \`.claude/CLAUDE.md\`) exists \u2192 put the section there.
|
|
3013
|
+
- neither exists \u2192 create \`CLAUDE.md\` with just the section.
|
|
3014
|
+
Append the following section verbatim; if the \`<!-- mason:start -->\` marker is already present in the target file, replace the marked block instead of appending:
|
|
3000
3015
|
|
|
3001
3016
|
${CLAUDE_MD_SECTION}
|
|
3002
3017
|
|
|
@@ -3153,7 +3168,7 @@ async function getCodeSamples(dir, count = 15) {
|
|
|
3153
3168
|
const rootDir = path14.resolve(dir);
|
|
3154
3169
|
const samples = await sampleFiles(rootDir, count);
|
|
3155
3170
|
const output = {
|
|
3156
|
-
note: "These are previews (first ~60 lines).
|
|
3171
|
+
note: "These are previews (first ~60 lines). Read the file directly with your own tools to see it in full.",
|
|
3157
3172
|
files: samples.map((s) => ({
|
|
3158
3173
|
path: s.path,
|
|
3159
3174
|
reason: s.reason,
|
|
@@ -3260,7 +3275,7 @@ async function getSnapshot(dir) {
|
|
|
3260
3275
|
if (!snapshot) {
|
|
3261
3276
|
return JSON.stringify({
|
|
3262
3277
|
exists: false,
|
|
3263
|
-
hint: "Project is initialized but no concept map exists yet.
|
|
3278
|
+
hint: "Project is initialized but no concept map exists yet. Run mason_init for the setup playbook (generate_snapshot_batch \u2192 save_partial_snapshot per batch, then reduce_snapshot and save_snapshot)."
|
|
3264
3279
|
});
|
|
3265
3280
|
}
|
|
3266
3281
|
const drift = await computeDrift(rootDir);
|
|
@@ -3530,7 +3545,7 @@ async function fullAnalysis(dir) {
|
|
|
3530
3545
|
loadSnapshot(rootDir)
|
|
3531
3546
|
]);
|
|
3532
3547
|
const output = {
|
|
3533
|
-
note: "Full project analysis. Code samples are previews (~60 lines).
|
|
3548
|
+
note: "Full project analysis. Code samples are previews (~60 lines). Read files directly with your own tools to see them in full.",
|
|
3534
3549
|
analysis: JSON.parse(analysis),
|
|
3535
3550
|
structure: JSON.parse(structure),
|
|
3536
3551
|
codeSamples: JSON.parse(samples),
|
|
@@ -3542,7 +3557,7 @@ async function fullAnalysis(dir) {
|
|
|
3542
3557
|
features: snapshot.features,
|
|
3543
3558
|
flows: snapshot.flows
|
|
3544
3559
|
};
|
|
3545
|
-
output.note = "Full project analysis with concept map. The concept map shows which files implement each feature and how data flows through them. Use it to jump straight to relevant files instead of exploring
|
|
3560
|
+
output.note = "Full project analysis with concept map. The concept map shows which files implement each feature and how data flows through them. Use it to jump straight to relevant files instead of exploring, then read them directly with your own tools.";
|
|
3546
3561
|
}
|
|
3547
3562
|
return JSON.stringify(output, null, 2);
|
|
3548
3563
|
}
|
|
@@ -3932,7 +3947,7 @@ function createMcpServer() {
|
|
|
3932
3947
|
const server = new McpServer(
|
|
3933
3948
|
{
|
|
3934
3949
|
name: "mason",
|
|
3935
|
-
version: "0.
|
|
3950
|
+
version: "0.8.0"
|
|
3936
3951
|
},
|
|
3937
3952
|
{
|
|
3938
3953
|
instructions: "Mason maintains a persistent feature-to-file concept map of this codebase so you can skip manual exploration. RULE: when given a task, bug, or change request, call `get_context` with the task text first \u2014 one call returns the relevant features, files, tests, blast radius, and freshness. Before answering ANY question about features, architecture, data flows, or where something lives \u2014 and before any grep/glob/file-read exploration for such a question \u2014 call `get_snapshot` first. One call returns the whole map and replaces 5-10 search round-trips; if it has drifted it says so and self-corrects. Likewise call `get_impact` BEFORE editing or refactoring a file (git co-change history + references + related tests \u2014 signals you cannot get from reading the file itself), and `mason_check_drift` to verify the map is fresh in long sessions. When you learn something the code alone can't tell you \u2014 a failed approach, a deprecation, a workaround's reason, a review-settled convention \u2014 record it with `save_decision` so the whole team's assistants inherit it; `get_context` returns matching decisions as constraints. If `get_snapshot` reports no snapshot exists, offer to set Mason up: `mason_init` returns a setup playbook (a Map-Reduce loop of `generate_snapshot_batch` + `save_partial_snapshot`, then `reduce_snapshot` + `save_snapshot`, optionally `mason_set_confluence`, then `mason_complete_init`). `full_analysis`, `analyze_project`, and `get_code_samples` are read-only diagnostics for unmapped projects and never need init. Mason has no CLI; everything happens through these tools."
|