@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 CHANGED
@@ -9542,14 +9542,33 @@ function domainFromWorkspace(pkg) {
9542
9542
  allowedImportsFrom: []
9543
9543
  };
9544
9544
  }
9545
- function topLevelSrcSubdirs(root) {
9546
- const srcDir = join5(root, "src");
9547
- if (!existsSync7(srcDir)) return [];
9548
- try {
9549
- return readdirSync5(srcDir, { withFileTypes: true }).filter((e2) => e2.isDirectory() && !IGNORED_SUBDIRS.has(e2.name)).map((e2) => e2.name).sort();
9550
- } catch {
9551
- return [];
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 subdirs = topLevelSrcSubdirs(projectRoot);
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
- ...getTeamToolDefinitions(),
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 srcDir = join3(root, "src");
8365
- if (!existsSync5(srcDir)) return [];
8366
- try {
8367
- return readdirSync3(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_SUBDIRS.has(e.name)).map((e) => e.name).sort();
8368
- } catch {
8369
- return [];
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 subdirs = topLevelSrcSubdirs(projectRoot);
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.6.3",
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
- function topLevelSrcSubdirs(root: string): string[] {
71
- const srcDir = join(root, 'src');
72
- if (!existsSync(srcDir)) return [];
73
- try {
74
- return readdirSync(srcDir, { withFileTypes: true })
75
- .filter((e) => e.isDirectory() && !IGNORED_SUBDIRS.has(e.name))
76
- .map((e) => e.name)
77
- .sort();
78
- } catch {
79
- return [];
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 src/<subdir>/ if src/ exists.
104
- const subdirs = topLevelSrcSubdirs(projectRoot);
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-11T22:54:34.471Z.
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
- ...getTeamToolDefinitions(),
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 (isTeamTool(name)) {
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(); }