@massu/core 1.6.3 → 1.7.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/dist/cli.js +34 -10
- package/dist/hooks/session-start.js +28 -8
- package/package.json +1 -1
- package/src/detect/domain-inferrer.ts +67 -12
- package/src/license.ts +20 -0
- package/src/security/registry-pubkey.generated.ts +1 -1
- package/src/tools.ts +6 -3
package/dist/cli.js
CHANGED
|
@@ -9542,14 +9542,33 @@ function domainFromWorkspace(pkg) {
|
|
|
9542
9542
|
allowedImportsFrom: []
|
|
9543
9543
|
};
|
|
9544
9544
|
}
|
|
9545
|
-
function topLevelSrcSubdirs(root) {
|
|
9546
|
-
const
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
|
|
9551
|
-
|
|
9545
|
+
function topLevelSrcSubdirs(root, sourceDirs) {
|
|
9546
|
+
const effective = sourceDirs.length > 0 ? sourceDirs : ["src"];
|
|
9547
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9548
|
+
for (const rel of effective) {
|
|
9549
|
+
const abs = join5(root, rel);
|
|
9550
|
+
if (!existsSync7(abs)) continue;
|
|
9551
|
+
try {
|
|
9552
|
+
for (const e2 of readdirSync5(abs, { withFileTypes: true })) {
|
|
9553
|
+
if (!e2.isDirectory()) continue;
|
|
9554
|
+
if (IGNORED_SUBDIRS.has(e2.name)) continue;
|
|
9555
|
+
seen.add(e2.name);
|
|
9556
|
+
}
|
|
9557
|
+
} catch {
|
|
9558
|
+
}
|
|
9559
|
+
}
|
|
9560
|
+
return Array.from(seen).sort();
|
|
9561
|
+
}
|
|
9562
|
+
function flattenSourceDirs(sourceDirs) {
|
|
9563
|
+
const flat = /* @__PURE__ */ new Set();
|
|
9564
|
+
for (const entry of Object.values(sourceDirs)) {
|
|
9565
|
+
if (!entry) continue;
|
|
9566
|
+
for (const dir of entry.source_dirs) {
|
|
9567
|
+
if (dir === "." || dir === "") continue;
|
|
9568
|
+
flat.add(dir);
|
|
9569
|
+
}
|
|
9552
9570
|
}
|
|
9571
|
+
return Array.from(flat);
|
|
9553
9572
|
}
|
|
9554
9573
|
function inferDomains(projectRoot, monorepo, sourceDirs) {
|
|
9555
9574
|
const domains = [];
|
|
@@ -9558,7 +9577,8 @@ function inferDomains(projectRoot, monorepo, sourceDirs) {
|
|
|
9558
9577
|
domains.push(domainFromWorkspace(pkg));
|
|
9559
9578
|
}
|
|
9560
9579
|
} else {
|
|
9561
|
-
const
|
|
9580
|
+
const flat = flattenSourceDirs(sourceDirs);
|
|
9581
|
+
const subdirs = topLevelSrcSubdirs(projectRoot, flat);
|
|
9562
9582
|
for (const s of subdirs) {
|
|
9563
9583
|
domains.push({
|
|
9564
9584
|
name: titleCase(s),
|
|
@@ -13780,6 +13800,9 @@ function annotateToolDefinitions(defs) {
|
|
|
13780
13800
|
};
|
|
13781
13801
|
});
|
|
13782
13802
|
}
|
|
13803
|
+
function isCloudFeatureAvailable() {
|
|
13804
|
+
return getConfig().cloud?.enabled === true;
|
|
13805
|
+
}
|
|
13783
13806
|
async function validateLicense(apiKey) {
|
|
13784
13807
|
const keyHash = createHash4("sha256").update(apiKey).digest("hex");
|
|
13785
13808
|
const memDb = getMemoryDb();
|
|
@@ -26723,7 +26746,8 @@ function getToolDefinitions() {
|
|
|
26723
26746
|
...getSecurityToolDefinitions(),
|
|
26724
26747
|
...getDependencyToolDefinitions(),
|
|
26725
26748
|
// Enterprise layer (team knowledge, regression detection)
|
|
26726
|
-
|
|
26749
|
+
// P-A-003: team tools are cloud-gated — only listed when cloud.enabled is true.
|
|
26750
|
+
...isCloudFeatureAvailable() ? getTeamToolDefinitions() : [],
|
|
26727
26751
|
...getRegressionToolDefinitions(),
|
|
26728
26752
|
// Knowledge layer (indexed .claude/ knowledge — rules, patterns, incidents)
|
|
26729
26753
|
...getKnowledgeToolDefinitions(),
|
|
@@ -26927,7 +26951,7 @@ async function handleToolCall(name, args2, dataDb, codegraphDb) {
|
|
|
26927
26951
|
memDb.close();
|
|
26928
26952
|
}
|
|
26929
26953
|
}
|
|
26930
|
-
if (isTeamTool(name)) {
|
|
26954
|
+
if (isTeamTool(name) && isCloudFeatureAvailable()) {
|
|
26931
26955
|
const memDb = getMemoryDb();
|
|
26932
26956
|
try {
|
|
26933
26957
|
return handleTeamToolCall(name, args2, memDb);
|
|
@@ -8360,14 +8360,33 @@ function domainFromWorkspace(pkg) {
|
|
|
8360
8360
|
allowedImportsFrom: []
|
|
8361
8361
|
};
|
|
8362
8362
|
}
|
|
8363
|
-
function topLevelSrcSubdirs(root) {
|
|
8364
|
-
const
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8363
|
+
function topLevelSrcSubdirs(root, sourceDirs) {
|
|
8364
|
+
const effective = sourceDirs.length > 0 ? sourceDirs : ["src"];
|
|
8365
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8366
|
+
for (const rel of effective) {
|
|
8367
|
+
const abs = join3(root, rel);
|
|
8368
|
+
if (!existsSync5(abs)) continue;
|
|
8369
|
+
try {
|
|
8370
|
+
for (const e of readdirSync3(abs, { withFileTypes: true })) {
|
|
8371
|
+
if (!e.isDirectory()) continue;
|
|
8372
|
+
if (IGNORED_SUBDIRS.has(e.name)) continue;
|
|
8373
|
+
seen.add(e.name);
|
|
8374
|
+
}
|
|
8375
|
+
} catch {
|
|
8376
|
+
}
|
|
8377
|
+
}
|
|
8378
|
+
return Array.from(seen).sort();
|
|
8379
|
+
}
|
|
8380
|
+
function flattenSourceDirs(sourceDirs) {
|
|
8381
|
+
const flat = /* @__PURE__ */ new Set();
|
|
8382
|
+
for (const entry of Object.values(sourceDirs)) {
|
|
8383
|
+
if (!entry) continue;
|
|
8384
|
+
for (const dir of entry.source_dirs) {
|
|
8385
|
+
if (dir === "." || dir === "") continue;
|
|
8386
|
+
flat.add(dir);
|
|
8387
|
+
}
|
|
8370
8388
|
}
|
|
8389
|
+
return Array.from(flat);
|
|
8371
8390
|
}
|
|
8372
8391
|
function inferDomains(projectRoot, monorepo, sourceDirs) {
|
|
8373
8392
|
const domains = [];
|
|
@@ -8376,7 +8395,8 @@ function inferDomains(projectRoot, monorepo, sourceDirs) {
|
|
|
8376
8395
|
domains.push(domainFromWorkspace(pkg));
|
|
8377
8396
|
}
|
|
8378
8397
|
} else {
|
|
8379
|
-
const
|
|
8398
|
+
const flat = flattenSourceDirs(sourceDirs);
|
|
8399
|
+
const subdirs = topLevelSrcSubdirs(projectRoot, flat);
|
|
8380
8400
|
for (const s of subdirs) {
|
|
8381
8401
|
domains.push({
|
|
8382
8402
|
name: titleCase(s),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@massu/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI Engineering Governance MCP Server - Session memory, knowledge system, feature registry, code intelligence, rule enforcement, tiered tooling (12 free / 72 total), 55+ workflow commands, 11 agents, 20+ patterns",
|
|
6
6
|
"main": "src/server.ts",
|
|
@@ -67,17 +67,69 @@ function domainFromWorkspace(pkg: WorkspacePackage): DomainConfig {
|
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Enumerate domain candidates under each detected source directory.
|
|
72
|
+
*
|
|
73
|
+
* The `sourceDirs` argument is the flattened, unique list of relative
|
|
74
|
+
* source paths produced upstream by the source-dir detector
|
|
75
|
+
* (`detectSourceDirs` in `source-dir-detector.ts`). For each path that
|
|
76
|
+
* exists under `root`, this function lists immediate subdirectories as
|
|
77
|
+
* candidate domain names. Hardcoded `src/` lookup was removed (plan
|
|
78
|
+
* `plan-1.7.0-cohesive-cleanup` P-B-002) — the function now consumes
|
|
79
|
+
* the detection pipeline's output verbatim, so projects whose source
|
|
80
|
+
* lives at non-`src/` paths (e.g. `lib/`, `apps/<x>/src/`) are no
|
|
81
|
+
* longer silently dropped.
|
|
82
|
+
*
|
|
83
|
+
* Empty `sourceDirs` is treated as a legacy single-repo `src/` lookup
|
|
84
|
+
* to preserve behavior for callers that pre-date the source-dir
|
|
85
|
+
* pipeline (CLI / test harnesses that hand-wire `inferDomains`).
|
|
86
|
+
*
|
|
87
|
+
* Returns deduplicated subdir names sorted alphabetically.
|
|
88
|
+
*/
|
|
89
|
+
function topLevelSrcSubdirs(root: string, sourceDirs: readonly string[]): string[] {
|
|
90
|
+
const effective = sourceDirs.length > 0 ? sourceDirs : ['src'];
|
|
91
|
+
const seen = new Set<string>();
|
|
92
|
+
for (const rel of effective) {
|
|
93
|
+
const abs = join(root, rel);
|
|
94
|
+
if (!existsSync(abs)) continue;
|
|
95
|
+
try {
|
|
96
|
+
for (const e of readdirSync(abs, { withFileTypes: true })) {
|
|
97
|
+
if (!e.isDirectory()) continue;
|
|
98
|
+
if (IGNORED_SUBDIRS.has(e.name)) continue;
|
|
99
|
+
seen.add(e.name);
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
// skip directories that cannot be read; do not throw.
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return Array.from(seen).sort();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Flatten a `SourceDirMap` into a unique, deduplicated list of relative
|
|
110
|
+
* source paths across all detected languages.
|
|
111
|
+
*
|
|
112
|
+
* Drops the root sentinels `.` and `''` — those are emitted by the
|
|
113
|
+
* source-dir-detector when source files live directly at the project
|
|
114
|
+
* root (e.g. Django's `manage.py` or Swift's `Package.swift`). Treating
|
|
115
|
+
* them as enumerable source dirs causes spurious top-level directory
|
|
116
|
+
* inclusion (Tests/, Sources/, etc.), which collides with the
|
|
117
|
+
* language-fallback path in `inferDomains`. Root-source repos rely on
|
|
118
|
+
* the language-fallback path to emit `{Python}` / `{Swift}` domains,
|
|
119
|
+
* NOT a fan-out of every root subdirectory.
|
|
120
|
+
*
|
|
121
|
+
* Order is not guaranteed — callers that need determinism must sort.
|
|
122
|
+
*/
|
|
123
|
+
function flattenSourceDirs(sourceDirs: SourceDirMap): string[] {
|
|
124
|
+
const flat = new Set<string>();
|
|
125
|
+
for (const entry of Object.values(sourceDirs)) {
|
|
126
|
+
if (!entry) continue;
|
|
127
|
+
for (const dir of entry.source_dirs) {
|
|
128
|
+
if (dir === '.' || dir === '') continue;
|
|
129
|
+
flat.add(dir);
|
|
130
|
+
}
|
|
80
131
|
}
|
|
132
|
+
return Array.from(flat);
|
|
81
133
|
}
|
|
82
134
|
|
|
83
135
|
/**
|
|
@@ -100,8 +152,11 @@ export function inferDomains(
|
|
|
100
152
|
domains.push(domainFromWorkspace(pkg));
|
|
101
153
|
}
|
|
102
154
|
} else {
|
|
103
|
-
// Single repo: suggest one domain per top-level
|
|
104
|
-
|
|
155
|
+
// Single repo: suggest one domain per top-level <sourceDir>/<subdir>/ for
|
|
156
|
+
// every detected source dir (formerly hardcoded to `src/` only — see
|
|
157
|
+
// P-B-002 in plan-1.7.0-cohesive-cleanup).
|
|
158
|
+
const flat = flattenSourceDirs(sourceDirs);
|
|
159
|
+
const subdirs = topLevelSrcSubdirs(projectRoot, flat);
|
|
105
160
|
for (const s of subdirs) {
|
|
106
161
|
domains.push({
|
|
107
162
|
name: titleCase(s),
|
package/src/license.ts
CHANGED
|
@@ -203,6 +203,26 @@ export function annotateToolDefinitions(defs: ToolDefinition[]): ToolDefinition[
|
|
|
203
203
|
});
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
// ============================================================
|
|
207
|
+
// plan-1.7.0-cohesive-cleanup P-A-003: Cloud feature availability gate
|
|
208
|
+
// ============================================================
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Whether cloud-gated tool surfaces (team knowledge, etc.) are exposed.
|
|
212
|
+
*
|
|
213
|
+
* Returns true ONLY when `massu.config.yaml` opts the workspace into the
|
|
214
|
+
* cloud feature surface via `cloud.enabled: true`. Defaults to false for
|
|
215
|
+
* fresh installs (the schema's `enabled` default is `false`).
|
|
216
|
+
*
|
|
217
|
+
* This is distinct from {@link isLicenseTool} (which matches tool NAMES);
|
|
218
|
+
* `isCloudFeatureAvailable` is a runtime feature-availability check used
|
|
219
|
+
* by `tools.ts` to gate team-tool registration and routing at the
|
|
220
|
+
* tools-list and dispatch boundaries.
|
|
221
|
+
*/
|
|
222
|
+
export function isCloudFeatureAvailable(): boolean {
|
|
223
|
+
return getConfig().cloud?.enabled === true;
|
|
224
|
+
}
|
|
225
|
+
|
|
206
226
|
// ============================================================
|
|
207
227
|
// P3-005/P3-006/P3-007/P3-013: License validation & caching
|
|
208
228
|
// ============================================================
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// AUTO-GENERATED by scripts/bundle-pubkey.mjs at 2026-05-
|
|
1
|
+
// AUTO-GENERATED by scripts/bundle-pubkey.mjs at 2026-05-12T04:00:39.397Z.
|
|
2
2
|
// Source pem: packages/core/security/registry-pubkey.pem
|
|
3
3
|
// RAW-bytes sha256: 3b6226d036c472e533110d11a7d0cd2773ce1d7d4f1003517d5bd69c5418ed4c
|
|
4
4
|
// DO NOT EDIT — regenerate via `node scripts/bundle-pubkey.mjs` or
|
package/src/tools.ts
CHANGED
|
@@ -38,7 +38,7 @@ import { getKnowledgeToolDefinitions, isKnowledgeTool, handleKnowledgeToolCall }
|
|
|
38
38
|
import { getKnowledgeDb } from './knowledge-db.ts';
|
|
39
39
|
import { getPythonToolDefinitions, isPythonTool, handlePythonToolCall } from './python-tools.ts';
|
|
40
40
|
import { getConfig, getProjectRoot, getResolvedPaths } from './config.ts';
|
|
41
|
-
import { getCurrentTier, getToolTier, isToolAllowed, annotateToolDefinitions, getLicenseToolDefinitions, isLicenseTool, handleLicenseToolCall } from './license.ts';
|
|
41
|
+
import { getCurrentTier, getToolTier, isToolAllowed, annotateToolDefinitions, getLicenseToolDefinitions, isLicenseTool, handleLicenseToolCall, isCloudFeatureAvailable } from './license.ts';
|
|
42
42
|
|
|
43
43
|
export interface ToolDefinition {
|
|
44
44
|
name: string;
|
|
@@ -172,7 +172,8 @@ export function getToolDefinitions(): ToolDefinition[] {
|
|
|
172
172
|
...getSecurityToolDefinitions(),
|
|
173
173
|
...getDependencyToolDefinitions(),
|
|
174
174
|
// Enterprise layer (team knowledge, regression detection)
|
|
175
|
-
|
|
175
|
+
// P-A-003: team tools are cloud-gated — only listed when cloud.enabled is true.
|
|
176
|
+
...(isCloudFeatureAvailable() ? getTeamToolDefinitions() : []),
|
|
176
177
|
...getRegressionToolDefinitions(),
|
|
177
178
|
// Knowledge layer (indexed .claude/ knowledge — rules, patterns, incidents)
|
|
178
179
|
...getKnowledgeToolDefinitions(),
|
|
@@ -410,7 +411,9 @@ export async function handleToolCall(
|
|
|
410
411
|
}
|
|
411
412
|
|
|
412
413
|
// Route enterprise layer tools
|
|
413
|
-
if
|
|
414
|
+
// P-A-003: team-tool dispatch gated on cloud availability; if a stale client
|
|
415
|
+
// still has the tool name cached, fall through to the unknown-tool branch.
|
|
416
|
+
if (isTeamTool(name) && isCloudFeatureAvailable()) {
|
|
414
417
|
const memDb = getMemoryDb();
|
|
415
418
|
try { return handleTeamToolCall(name, args, memDb); }
|
|
416
419
|
finally { memDb.close(); }
|